mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
Refactor to effects
This commit is contained in:
parent
7861571a96
commit
aa635e9aa6
|
@ -29,14 +29,24 @@ pub struct Pattern {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PatternSlot {
|
pub struct PatternSlot {
|
||||||
pub volume: Num<i16, 4>,
|
|
||||||
pub speed: Num<u32, 8>,
|
pub speed: Num<u32, 8>,
|
||||||
pub panning: Num<i16, 4>,
|
|
||||||
pub sample: usize,
|
pub sample: usize,
|
||||||
|
pub effect1: PatternEffect,
|
||||||
|
pub effect2: PatternEffect,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SKIP_SLOT: usize = 277;
|
#[derive(Debug, Default)]
|
||||||
pub const STOP_CHANNEL: usize = 278;
|
pub enum PatternEffect {
|
||||||
|
/// Don't play an effect
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Stops playing the current note
|
||||||
|
Stop,
|
||||||
|
/// Plays an arpeggiation of three notes in one row, cycling betwen the current note, current note + first speed, current note + second speed
|
||||||
|
Arpeggio(Num<u16, 8>, Num<u16, 8>),
|
||||||
|
Panning(Num<i16, 4>),
|
||||||
|
Volume(Num<i16, 4>),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "quote")]
|
#[cfg(feature = "quote")]
|
||||||
impl<'a> quote::ToTokens for Track<'a> {
|
impl<'a> quote::ToTokens for Track<'a> {
|
||||||
|
@ -116,22 +126,20 @@ impl quote::ToTokens for PatternSlot {
|
||||||
use quote::{quote, TokenStreamExt};
|
use quote::{quote, TokenStreamExt};
|
||||||
|
|
||||||
let PatternSlot {
|
let PatternSlot {
|
||||||
volume,
|
|
||||||
speed,
|
speed,
|
||||||
panning,
|
|
||||||
sample,
|
sample,
|
||||||
|
effect1,
|
||||||
|
effect2,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let volume = volume.to_raw();
|
|
||||||
let speed = speed.to_raw();
|
let speed = speed.to_raw();
|
||||||
let panning = panning.to_raw();
|
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
agb_tracker::__private::agb_tracker_interop::PatternSlot {
|
agb_tracker::__private::agb_tracker_interop::PatternSlot {
|
||||||
volume: agb_tracker::__private::Num::from_raw(#volume),
|
|
||||||
speed: agb_tracker::__private::Num::from_raw(#speed),
|
speed: agb_tracker::__private::Num::from_raw(#speed),
|
||||||
panning: agb_tracker::__private::Num::from_raw(#panning),
|
|
||||||
sample: #sample,
|
sample: #sample,
|
||||||
|
effect1: #effect1,
|
||||||
|
effect2: #effect2,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -155,3 +163,32 @@ impl quote::ToTokens for Pattern {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "quote")]
|
||||||
|
impl quote::ToTokens for PatternEffect {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
use quote::{quote, TokenStreamExt};
|
||||||
|
|
||||||
|
let type_bit = match self {
|
||||||
|
PatternEffect::None => quote! { None },
|
||||||
|
PatternEffect::Stop => quote! { Stop },
|
||||||
|
PatternEffect::Arpeggio(first, second) => {
|
||||||
|
let first = first.to_raw();
|
||||||
|
let second = second.to_raw();
|
||||||
|
quote! { Arpeggio(agb_tracker::__private::Num::from_raw(#first), agb_tracker::__private::Num::from_raw(#second)) }
|
||||||
|
}
|
||||||
|
PatternEffect::Panning(panning) => {
|
||||||
|
let panning = panning.to_raw();
|
||||||
|
quote! { Panning(agb_tracker::__private::Num::from_raw(#panning))}
|
||||||
|
}
|
||||||
|
PatternEffect::Volume(volume) => {
|
||||||
|
let volume = volume.to_raw();
|
||||||
|
quote! { Volume(agb_tracker::__private::Num::from_raw(#volume))}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.append_all(quote! {
|
||||||
|
agb_tracker::__private::agb_tracker_interop::PatternEffect::#type_bit
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
use agb_tracker_interop::{PatternEffect, Sample};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
|
@ -26,7 +27,7 @@ pub use agb_tracker_interop::Track;
|
||||||
|
|
||||||
pub struct Tracker {
|
pub struct Tracker {
|
||||||
track: &'static Track<'static>,
|
track: &'static Track<'static>,
|
||||||
channels: Vec<Option<ChannelId>>,
|
channels: Vec<TrackerChannel>,
|
||||||
|
|
||||||
frame: Num<u16, 8>,
|
frame: Num<u16, 8>,
|
||||||
tick: u16,
|
tick: u16,
|
||||||
|
@ -35,10 +36,14 @@ pub struct Tracker {
|
||||||
current_pattern: usize,
|
current_pattern: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TrackerChannel {
|
||||||
|
channel_id: Option<ChannelId>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Tracker {
|
impl Tracker {
|
||||||
pub fn new(track: &'static Track<'static>) -> Self {
|
pub fn new(track: &'static Track<'static>) -> Self {
|
||||||
let mut channels = Vec::new();
|
let mut channels = Vec::new();
|
||||||
channels.resize_with(track.num_channels, || None);
|
channels.resize_with(track.num_channels, || TrackerChannel { channel_id: None });
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
track,
|
track,
|
||||||
|
@ -66,55 +71,62 @@ impl Tracker {
|
||||||
let pattern_slots =
|
let pattern_slots =
|
||||||
&self.track.pattern_data[pattern_data_pos..pattern_data_pos + self.track.num_channels];
|
&self.track.pattern_data[pattern_data_pos..pattern_data_pos + self.track.num_channels];
|
||||||
|
|
||||||
for (channel_id, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) {
|
for (channel, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) {
|
||||||
if pattern_slot.sample == agb_tracker_interop::SKIP_SLOT {
|
if pattern_slot.sample != 0 {
|
||||||
// completely skip
|
|
||||||
} else if pattern_slot.sample == agb_tracker_interop::STOP_CHANNEL {
|
|
||||||
if let Some(channel) = channel_id
|
|
||||||
.take()
|
|
||||||
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
||||||
{
|
|
||||||
channel.stop();
|
|
||||||
}
|
|
||||||
} else if pattern_slot.sample == 0 {
|
|
||||||
if let Some(channel) = channel_id
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|channel_id| mixer.channel(channel_id))
|
|
||||||
{
|
|
||||||
if pattern_slot.volume != 0.into() {
|
|
||||||
channel.volume(pattern_slot.volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
if pattern_slot.panning != 0.into() {
|
|
||||||
channel.panning(pattern_slot.panning);
|
|
||||||
}
|
|
||||||
|
|
||||||
if pattern_slot.speed != 0.into() {
|
|
||||||
channel.playback(pattern_slot.speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(channel) = channel_id
|
|
||||||
.take()
|
|
||||||
.and_then(|channel_id| mixer.channel(&channel_id))
|
|
||||||
{
|
|
||||||
channel.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let sample = &self.track.samples[pattern_slot.sample - 1];
|
let sample = &self.track.samples[pattern_slot.sample - 1];
|
||||||
let mut new_channel = SoundChannel::new(sample.data);
|
channel.play_sound(mixer, sample);
|
||||||
new_channel
|
|
||||||
.panning(pattern_slot.panning)
|
|
||||||
.volume(pattern_slot.volume)
|
|
||||||
.playback(pattern_slot.speed)
|
|
||||||
.restart_point(sample.restart_point);
|
|
||||||
|
|
||||||
if sample.should_loop {
|
|
||||||
new_channel.should_loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
*channel_id = mixer.play_sound(new_channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel.apply_effect(mixer, &pattern_slot.effect1, self.tick, pattern_slot.speed);
|
||||||
|
channel.apply_effect(mixer, &pattern_slot.effect2, self.tick, pattern_slot.speed);
|
||||||
|
// if pattern_slot.sample == agb_tracker_interop::SKIP_SLOT {
|
||||||
|
// // completely skip
|
||||||
|
// } else if pattern_slot.sample == agb_tracker_interop::STOP_CHANNEL {
|
||||||
|
// if let Some(channel) = channel_id
|
||||||
|
// .take()
|
||||||
|
// .and_then(|channel_id| mixer.channel(&channel_id))
|
||||||
|
// {
|
||||||
|
// channel.stop();
|
||||||
|
// }
|
||||||
|
// } else if pattern_slot.sample == 0 {
|
||||||
|
// if let Some(channel) = channel_id
|
||||||
|
// .as_ref()
|
||||||
|
// .and_then(|channel_id| mixer.channel(channel_id))
|
||||||
|
// {
|
||||||
|
// if pattern_slot.volume != 0.into() {
|
||||||
|
// channel.volume(pattern_slot.volume);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if pattern_slot.panning != 0.into() {
|
||||||
|
// channel.panning(pattern_slot.panning);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if pattern_slot.speed != 0.into() {
|
||||||
|
// channel.playback(pattern_slot.speed);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if let Some(channel) = channel_id
|
||||||
|
// .take()
|
||||||
|
// .and_then(|channel_id| mixer.channel(&channel_id))
|
||||||
|
// {
|
||||||
|
// channel.stop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let sample = &self.track.samples[pattern_slot.sample - 1];
|
||||||
|
// let mut new_channel = SoundChannel::new(sample.data);
|
||||||
|
// new_channel
|
||||||
|
// .panning(pattern_slot.panning)
|
||||||
|
// .volume(pattern_slot.volume)
|
||||||
|
// .playback(pattern_slot.speed)
|
||||||
|
// .restart_point(sample.restart_point);
|
||||||
|
|
||||||
|
// if sample.should_loop {
|
||||||
|
// new_channel.should_loop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// *channel_id = mixer.play_sound(new_channel);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
self.increment_step();
|
self.increment_step();
|
||||||
|
@ -147,6 +159,57 @@ impl Tracker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TrackerChannel {
|
||||||
|
fn play_sound(&mut self, mixer: &mut Mixer<'_>, sample: &Sample<'static>) {
|
||||||
|
self.channel_id
|
||||||
|
.take()
|
||||||
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
||||||
|
.map(|channel| channel.stop());
|
||||||
|
|
||||||
|
let mut new_channel = SoundChannel::new(sample.data);
|
||||||
|
|
||||||
|
if sample.should_loop {
|
||||||
|
new_channel
|
||||||
|
.should_loop()
|
||||||
|
.restart_point(sample.restart_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.channel_id = mixer.play_sound(new_channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_effect(
|
||||||
|
&mut self,
|
||||||
|
mixer: &mut Mixer<'_>,
|
||||||
|
effect: &PatternEffect,
|
||||||
|
tick: u16,
|
||||||
|
speed: Num<u32, 8>,
|
||||||
|
) {
|
||||||
|
if let Some(channel) = self
|
||||||
|
.channel_id
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|channel_id| mixer.channel(&channel_id))
|
||||||
|
{
|
||||||
|
if speed != 0.into() {
|
||||||
|
channel.playback(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
match effect {
|
||||||
|
PatternEffect::None => {}
|
||||||
|
PatternEffect::Stop => {
|
||||||
|
channel.stop();
|
||||||
|
}
|
||||||
|
PatternEffect::Arpeggio(_, _) => todo!(),
|
||||||
|
PatternEffect::Panning(panning) => {
|
||||||
|
channel.panning(*panning);
|
||||||
|
}
|
||||||
|
PatternEffect::Volume(volume) => {
|
||||||
|
channel.volume(*volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[agb::entry]
|
#[agb::entry]
|
||||||
fn main(gba: agb::Gba) -> ! {
|
fn main(gba: agb::Gba) -> ! {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{collections::HashMap, error::Error, fs, path::Path};
|
use std::{collections::HashMap, error::Error, fs, path::Path};
|
||||||
|
|
||||||
|
use agb_tracker_interop::PatternEffect;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use proc_macro_error::abort;
|
use proc_macro_error::abort;
|
||||||
|
|
||||||
|
@ -53,7 +54,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
|
||||||
should_loop: bool,
|
should_loop: bool,
|
||||||
fine_tune: f64,
|
fine_tune: f64,
|
||||||
relative_note: i8,
|
relative_note: i8,
|
||||||
volume: f64,
|
|
||||||
restart_point: u32,
|
restart_point: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
|
||||||
let should_loop = !matches!(sample.flags, LoopType::No);
|
let should_loop = !matches!(sample.flags, LoopType::No);
|
||||||
let fine_tune = sample.finetune as f64;
|
let fine_tune = sample.finetune as f64;
|
||||||
let relative_note = sample.relative_note;
|
let relative_note = sample.relative_note;
|
||||||
let volume = sample.volume as f64;
|
|
||||||
let restart_point = sample.loop_start;
|
let restart_point = sample.loop_start;
|
||||||
let sample_len = if sample.loop_length > 0 {
|
let sample_len = if sample.loop_length > 0 {
|
||||||
(sample.loop_length + sample.loop_start) as usize
|
(sample.loop_length + sample.loop_start) as usize
|
||||||
|
@ -93,7 +92,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
|
||||||
should_loop,
|
should_loop,
|
||||||
fine_tune,
|
fine_tune,
|
||||||
relative_note,
|
relative_note,
|
||||||
volume,
|
|
||||||
restart_point,
|
restart_point,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -125,50 +123,35 @@ pub fn parse_module(module: &Module) -> TokenStream {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut volume, mut panning) = match slot.volume {
|
let mut effect1 = match slot.volume {
|
||||||
0x10..=0x50 => (Some((slot.volume - 0x10) as f64 / 64.0), None),
|
0x10..=0x50 => {
|
||||||
0xC0..=0xCF => (
|
PatternEffect::Volume(Num::new((slot.volume - 0x10) as i16) / 64)
|
||||||
None,
|
}
|
||||||
Some(Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 64),
|
0xC0..=0xCF => PatternEffect::Panning(
|
||||||
|
Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 64,
|
||||||
),
|
),
|
||||||
_ => (None, Some(0.into())),
|
_ => PatternEffect::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if slot.effect_type == 0xC {
|
let effect2 = match slot.effect_type {
|
||||||
volume = Some(slot.effect_parameter as f64 / 255.0);
|
0x8 => {
|
||||||
}
|
PatternEffect::Panning(Num::new(slot.effect_parameter as i16 - 128) / 128)
|
||||||
|
}
|
||||||
|
0xC => PatternEffect::Volume(Num::new(slot.effect_parameter as i16) / 255),
|
||||||
|
_ => PatternEffect::None,
|
||||||
|
};
|
||||||
|
|
||||||
if slot.effect_type == 0x8 {
|
if matches!(slot.note, Note::KeyOff) {
|
||||||
panning = Some(Num::new(slot.effect_parameter as i16 - 128) / 128);
|
effect1 = PatternEffect::Stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
if sample == 0 {
|
if sample == 0 {
|
||||||
if slot.volume == 0 && slot.effect_type == 0 {
|
pattern_data.push(agb_tracker_interop::PatternSlot {
|
||||||
pattern_data.push(agb_tracker_interop::PatternSlot {
|
speed: 0.into(),
|
||||||
volume: 0.into(),
|
sample: 0,
|
||||||
speed: 0.into(),
|
effect1,
|
||||||
panning: 0.into(),
|
effect2,
|
||||||
sample: agb_tracker_interop::SKIP_SLOT,
|
});
|
||||||
});
|
|
||||||
} else if matches!(slot.note, Note::KeyOff) || volume == Some(0.0) {
|
|
||||||
pattern_data.push(agb_tracker_interop::PatternSlot {
|
|
||||||
volume: 0.into(),
|
|
||||||
speed: 0.into(),
|
|
||||||
panning: 0.into(),
|
|
||||||
sample: agb_tracker_interop::STOP_CHANNEL,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let volume: Num<i16, 4> =
|
|
||||||
Num::from_raw((volume.unwrap_or(0.into()) * (1 << 4) as f64) as i16);
|
|
||||||
let panning = panning.unwrap_or(0.into());
|
|
||||||
|
|
||||||
pattern_data.push(agb_tracker_interop::PatternSlot {
|
|
||||||
volume,
|
|
||||||
speed: 0.into(),
|
|
||||||
panning,
|
|
||||||
sample: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let sample_played = &samples[sample - 1];
|
let sample_played = &samples[sample - 1];
|
||||||
|
|
||||||
|
@ -179,15 +162,11 @@ pub fn parse_module(module: &Module) -> TokenStream {
|
||||||
module.frequency_type,
|
module.frequency_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
let overall_volume = volume.unwrap_or(1.into()) * sample_played.volume;
|
|
||||||
let volume: Num<i16, 4> =
|
|
||||||
Num::from_raw((overall_volume * (1 << 4) as f64) as i16);
|
|
||||||
|
|
||||||
pattern_data.push(agb_tracker_interop::PatternSlot {
|
pattern_data.push(agb_tracker_interop::PatternSlot {
|
||||||
volume,
|
|
||||||
speed,
|
speed,
|
||||||
panning: panning.unwrap_or(0.into()),
|
|
||||||
sample,
|
sample,
|
||||||
|
effect1,
|
||||||
|
effect2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue