diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index 2ab812f0..3a835660 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -54,6 +54,8 @@ pub enum PatternEffect { Portamento(Num), /// Slide each tick the first amount to at most the second amount TonePortamento(Num, Num), + SetTicksPerStep(u32), + SetFramesPerTick(Num), } #[cfg(feature = "quote")] @@ -223,6 +225,13 @@ impl quote::ToTokens for PatternEffect { let target = target.to_raw(); quote! { TonePortamento(agb_tracker::__private::Num::from_raw(#amount), agb_tracker::__private::Num::from_raw(#target))} } + PatternEffect::SetTicksPerStep(new_ticks) => { + quote! { SetTicksPerStep(#new_ticks) } + } + PatternEffect::SetFramesPerTick(new_frames_per_tick) => { + let amount = new_frames_per_tick.to_raw(); + quote! { SetFramesPerTick(agb_tracker::__private::Num::from_raw(#amount)) } + } }; tokens.append_all(quote! { diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index b5b76783..5c09eb51 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -95,6 +95,8 @@ pub struct Tracker { tick: u32, first: bool, + global_settings: GlobalSettings, + current_row: usize, current_pattern: usize, } @@ -105,6 +107,13 @@ struct TrackerChannel { volume: Num, } +#[derive(Clone)] +struct GlobalSettings { + ticks_per_step: u32, + + frames_per_tick: Num, +} + impl Tracker { /// Create a new tracker playing a specified track. See the [example](crate#example) for how to use the tracker. pub fn new(track: &'static Track<'static>) -> Self { @@ -115,6 +124,11 @@ impl Tracker { volume: 0.into(), }); + let global_settings = GlobalSettings { + ticks_per_step: track.ticks_per_step, + frames_per_tick: track.frames_per_tick, + }; + Self { track, channels, @@ -123,8 +137,10 @@ impl Tracker { first: true, tick: 0, - current_row: 0, + global_settings, + current_pattern: 0, + current_row: 0, } } @@ -153,8 +169,18 @@ impl Tracker { channel.set_speed(mixer, pattern_slot.speed.change_base()); } - channel.apply_effect(mixer, &pattern_slot.effect1, self.tick); - channel.apply_effect(mixer, &pattern_slot.effect2, self.tick); + channel.apply_effect( + mixer, + &pattern_slot.effect1, + self.tick, + &mut self.global_settings, + ); + channel.apply_effect( + mixer, + &pattern_slot.effect2, + self.tick, + &mut self.global_settings, + ); } self.increment_step(); @@ -168,11 +194,11 @@ impl Tracker { self.frame += 1; - if self.frame >= self.track.frames_per_tick { + if self.frame >= self.global_settings.frames_per_tick { self.tick += 1; - self.frame -= self.track.frames_per_tick; + self.frame -= self.global_settings.frames_per_tick; - if self.tick == self.track.ticks_per_step { + if self.tick >= self.global_settings.ticks_per_step { self.current_row += 1; if self.current_row @@ -236,7 +262,13 @@ impl TrackerChannel { } } - fn apply_effect(&mut self, mixer: &mut Mixer<'_>, effect: &PatternEffect, tick: u32) { + fn apply_effect( + &mut self, + mixer: &mut Mixer<'_>, + effect: &PatternEffect, + tick: u32, + global_settings: &mut GlobalSettings, + ) { if let Some(channel) = self .channel_id .as_ref() @@ -298,8 +330,25 @@ impl TrackerChannel { channel.playback(self.base_speed.change_base()); } + // These are global effects handled below + PatternEffect::SetTicksPerStep(_) | PatternEffect::SetFramesPerTick(_) => {} } } + + // Some effects have to happen regardless of if we're actually playing anything + match effect { + PatternEffect::SetTicksPerStep(amount) => { + if tick == 0 { + global_settings.ticks_per_step = *amount; + } + } + PatternEffect::SetFramesPerTick(new_frames_per_tick) => { + if tick == 0 { + global_settings.frames_per_tick = *new_frames_per_tick; + } + } + _ => {} + } } } diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index 8e2549fa..a93ac214 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -323,6 +323,13 @@ pub fn parse_module(module: &Module) -> TokenStream { 0xC => PatternEffect::NoteCut((slot.effect_parameter & 0xf).into()), _ => PatternEffect::None, }, + 0xF => { + if slot.effect_parameter < 0x20 { + PatternEffect::SetTicksPerStep(slot.effect_parameter as u32) + } else { + PatternEffect::None + } + } _ => PatternEffect::None, }; @@ -379,7 +386,7 @@ pub fn parse_module(module: &Module) -> TokenStream { .collect::>(); // Number 150 here deduced experimentally - let frames_per_tick = Num::::new(150) / module.default_bpm as u32; + let frames_per_tick = bpm_to_frames_per_tick(module.default_bpm as u32); let ticks_per_step = module.default_tempo; let interop = agb_tracker_interop::Track { @@ -397,6 +404,10 @@ pub fn parse_module(module: &Module) -> TokenStream { quote!(#interop) } +fn bpm_to_frames_per_tick(bpm: u32) -> Num { + Num::::new(150) / bpm +} + fn note_to_speed( note: Note, fine_tune: f64,