diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index f31509c0..fd1fa3b2 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -39,6 +39,14 @@ pub enum Jump { Combined { pattern: u8, row: u8 }, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RetriggerVolumeChange { + /// Decrease the volume by one each retrigger + DecreaseByOne, + /// Don't change it + NoChange, +} + #[derive(Debug, Clone)] pub struct Pattern { pub length: usize, @@ -93,6 +101,9 @@ pub enum PatternEffect { /// Increase / decrease the pitch by the specified amount immediately PitchBend(Num), Jump(Jump), + SampleOffset(u16), + /// Retrigger the note every u8 ticks with the volume change specified + Retrigger(RetriggerVolumeChange, u8), } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -128,6 +139,22 @@ impl quote::ToTokens for Jump { } } +#[cfg(feature = "quote")] +impl quote::ToTokens for RetriggerVolumeChange { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::{quote, TokenStreamExt}; + + let type_bit = match self { + RetriggerVolumeChange::DecreaseByOne => quote!(DecreaseByOne), + RetriggerVolumeChange::NoChange => quote!(NoChange), + }; + + tokens.append_all(quote! { + agb_tracker::__private::agb_tracker_interop::RetriggerVolumeChange::#type_bit + }); + } +} + #[cfg(feature = "quote")] impl quote::ToTokens for Track { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { @@ -401,6 +428,10 @@ impl quote::ToTokens for PatternEffect { PatternEffect::Jump(jump) => { quote! { Jump(#jump) } } + PatternEffect::SampleOffset(offset) => quote! { SampleOffset(#offset) }, + PatternEffect::Retrigger(retrigger_volume_change, ticks) => { + quote! { Retrigger(#retrigger_volume_change, #ticks) } + } }; tokens.append_all(quote! { diff --git a/tracker/agb-tracker/examples/algar_-_ninja_on_speed.xm b/tracker/agb-tracker/examples/algar_-_ninja_on_speed.xm deleted file mode 100644 index 9f084544..00000000 Binary files a/tracker/agb-tracker/examples/algar_-_ninja_on_speed.xm and /dev/null differ diff --git a/tracker/agb-tracker/examples/basic.rs b/tracker/agb-tracker/examples/basic.rs index 53740009..61a65ad6 100644 --- a/tracker/agb-tracker/examples/basic.rs +++ b/tracker/agb-tracker/examples/basic.rs @@ -7,8 +7,7 @@ use agb::sound::mixer::Frequency; use agb::Gba; use agb_tracker::{include_xm, Track, Tracker}; -// Found on: https://modarchive.org/index.php?request=view_by_moduleid&query=36662 -static DB_TOFFE: Track = include_xm!("examples/db_toffe.xm"); +static SPECTRUM: Track = include_xm!("examples/tracks/peak_and_drozerix_-_spectrum.xm"); #[agb::entry] fn main(mut gba: Gba) -> ! { @@ -17,7 +16,7 @@ fn main(mut gba: Gba) -> ! { let mut mixer = gba.mixer.mixer(Frequency::Hz32768); mixer.enable(); - let mut tracker = Tracker::new(&DB_TOFFE); + let mut tracker = Tracker::new(&SPECTRUM); loop { tracker.step(&mut mixer); diff --git a/tracker/agb-tracker/examples/db_toffe.xm b/tracker/agb-tracker/examples/db_toffe.xm deleted file mode 100644 index b2b22a10..00000000 Binary files a/tracker/agb-tracker/examples/db_toffe.xm and /dev/null differ diff --git a/tracker/agb-tracker/examples/timing.rs b/tracker/agb-tracker/examples/timing.rs index 81addb6a..fecc6e7c 100644 --- a/tracker/agb-tracker/examples/timing.rs +++ b/tracker/agb-tracker/examples/timing.rs @@ -7,8 +7,7 @@ use agb::sound::mixer::Frequency; use agb::Gba; use agb_tracker::{include_xm, Track, Tracker}; -// Found on: https://modarchive.org/index.php?request=view_by_moduleid&query=36662 -static DB_TOFFE: Track = include_xm!("examples/db_toffe.xm"); +static SPECTRUM: Track = include_xm!("examples/tracks/peak_and_drozerix_-_spectrum.xm"); #[agb::entry] fn main(mut gba: Gba) -> ! { @@ -23,7 +22,7 @@ fn main(mut gba: Gba) -> ! { let mut mixer = gba.mixer.mixer(Frequency::Hz32768); mixer.enable(); - let mut tracker = Tracker::new(&DB_TOFFE); + let mut tracker = Tracker::new(&SPECTRUM); loop { let before_mixing_cycles_high = timer2.value(); diff --git a/tracker/agb-tracker/examples/tracks/drozerix_-_mecanum_overdrive.xm b/tracker/agb-tracker/examples/tracks/drozerix_-_mecanum_overdrive.xm new file mode 100644 index 00000000..e3e1ea9d Binary files /dev/null and b/tracker/agb-tracker/examples/tracks/drozerix_-_mecanum_overdrive.xm differ diff --git a/tracker/agb-tracker/examples/tracks/kokesz_-_natural.xm b/tracker/agb-tracker/examples/tracks/kokesz_-_natural.xm new file mode 100644 index 00000000..ca0d7e45 Binary files /dev/null and b/tracker/agb-tracker/examples/tracks/kokesz_-_natural.xm differ diff --git a/tracker/agb-tracker/examples/tracks/peak_and_drozerix_-_spectrum.xm b/tracker/agb-tracker/examples/tracks/peak_and_drozerix_-_spectrum.xm new file mode 100644 index 00000000..c3ed3d3c Binary files /dev/null and b/tracker/agb-tracker/examples/tracks/peak_and_drozerix_-_spectrum.xm differ diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index 9edd20fa..8a5b5ed7 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -126,6 +126,9 @@ struct TrackerChannel { current_speed: Num, current_panning: Num, is_playing: bool, + + // if some, should set the current position to this + current_pos: Option, } #[derive(Default)] @@ -337,6 +340,10 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> { channel.volume(tracker_channel.current_volume.try_change_base().unwrap()); channel.panning(tracker_channel.current_panning.try_change_base().unwrap()); + if let Some(offset) = tracker_channel.current_pos.take() { + channel.set_pos(offset as u32); + } + if tracker_channel.is_playing { channel.resume(); } else { @@ -601,6 +608,26 @@ impl TrackerChannel { PatternEffect::Jump(jump) => { *current_jump = Some(jump.clone()); } + PatternEffect::SampleOffset(offset) => { + if tick == 0 { + self.current_pos = Some(*offset); + } + } + PatternEffect::Retrigger(volume_change, ticks) => { + if tick % *ticks as u32 == 0 { + match volume_change { + agb_tracker_interop::RetriggerVolumeChange::DecreaseByOne => { + self.volume = (self.volume - Num::new(1) / 64).max(0.into()); + self.current_volume = (self.volume * global_settings.volume) + .try_change_base() + .unwrap(); + } + agb_tracker_interop::RetriggerVolumeChange::NoChange => {} + } + + self.current_pos = Some(0); + } + } } } @@ -677,6 +704,10 @@ impl SoundChannel for agb::sound::mixer::SoundChannel { fn panning(&mut self, panning: impl Into>) -> &mut Self { self.panning(panning) } + + fn set_pos(&mut self, pos: impl Into>) -> &mut Self { + self.set_pos(pos) + } } #[cfg(feature = "agb")] diff --git a/tracker/agb-tracker/src/mixer.rs b/tracker/agb-tracker/src/mixer.rs index 110627e9..dcb1cc60 100644 --- a/tracker/agb-tracker/src/mixer.rs +++ b/tracker/agb-tracker/src/mixer.rs @@ -17,6 +17,8 @@ pub trait SoundChannel { fn restart_point(&mut self, value: impl Into>) -> &mut Self; fn playback(&mut self, playback_speed: impl Into>) -> &mut Self; fn panning(&mut self, panning: impl Into>) -> &mut Self; + + fn set_pos(&mut self, pos: impl Into>) -> &mut Self; } pub trait Mixer { diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index d2ad32fc..735a925a 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use agb_fixnum::Num; -use agb_tracker_interop::{Jump, PatternEffect, Waveform}; +use agb_tracker_interop::{Jump, PatternEffect, RetriggerVolumeChange, Waveform}; use xmrs::prelude::*; @@ -99,6 +99,8 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { let mut effect_parameters: [u8; 255] = [0; u8::MAX as usize]; let mut tone_portamento_directions = vec![0; module.get_num_channels()]; let mut note_and_sample = vec![None; module.get_num_channels()]; + let mut previous_retriggers: Vec> = + vec![None; module.get_num_channels()]; for row in pattern.iter() { // the combined jump for each row @@ -324,6 +326,7 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { ) } } + 0x9 => PatternEffect::SampleOffset(effect_parameter as u16 * 256), 0xB => { let pattern_idx = slot.effect_parameter; @@ -403,6 +406,30 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { ) } + 0x8 => PatternEffect::Panning( + Num::new(((slot.effect_parameter & 0xf) as i16) - 8) / 8, + ), + 0x9 => { + let retrigger_amount = slot.effect_parameter & 0xf; + let modified_amount = if retrigger_amount == 0 { + if let Some((_, previous_retrigger)) = + previous_retriggers[channel_number] + { + previous_retrigger + } else { + 1 + } + } else { + previous_retriggers[channel_number] = + Some((RetriggerVolumeChange::NoChange, retrigger_amount)); + retrigger_amount + }; + + PatternEffect::Retrigger( + RetriggerVolumeChange::NoChange, + modified_amount, + ) + } 0xA => PatternEffect::FineVolumeSlide( Num::new((slot.effect_parameter & 0xf) as i16) / 128, ), @@ -438,8 +465,43 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { PatternEffect::GlobalVolumeSlide(Num::new(first as i32) / 0x40) } } + // R + 0x1B => { + let first = effect_parameter >> 4; + let second = effect_parameter & 0xF; + + let previous_retrigger = &mut previous_retriggers[channel_number]; + let volume_type = match first { + 0 => previous_retrigger + .map(|retrigger| retrigger.0) + .unwrap_or(RetriggerVolumeChange::NoChange), + 1 => RetriggerVolumeChange::DecreaseByOne, + 8 => RetriggerVolumeChange::NoChange, + _ => { + eprintln!("Unsupported retrigger effect volume {first}"); + RetriggerVolumeChange::NoChange + } + }; + + let ticks_between_retriggers = if second == 0 { + if let Some((_, previous_retrigger)) = previous_retrigger { + *previous_retrigger + } else { + 1 + } + } else { + second + }; + + *previous_retrigger = Some((volume_type, ticks_between_retriggers)); + + PatternEffect::Retrigger(volume_type, ticks_between_retriggers) + } e => { - eprintln!("Unsupported effect {e:X}xy"); + let effect_char = char::from_digit(e as u32, 36) + .unwrap_or('?') + .to_ascii_uppercase(); + eprintln!("Unsupported effect {effect_char}xy"); PatternEffect::None } diff --git a/tracker/desktop-player/src/mixer.rs b/tracker/desktop-player/src/mixer.rs index 990e56e9..69a928fd 100644 --- a/tracker/desktop-player/src/mixer.rs +++ b/tracker/desktop-player/src/mixer.rs @@ -171,6 +171,11 @@ impl agb_tracker::SoundChannel for SoundChannel { self.panning = panning.into(); self } + + fn set_pos(&mut self, pos: impl Into>) -> &mut Self { + self.pos = pos.into(); + self + } } impl agb_tracker::Mixer for Mixer { diff --git a/tracker/desktop-player/tests/delay.xm b/tracker/desktop-player/tests/delay.xm new file mode 100644 index 00000000..a41345a3 Binary files /dev/null and b/tracker/desktop-player/tests/delay.xm differ diff --git a/tracker/desktop-player/tests/retrigger.xm b/tracker/desktop-player/tests/retrigger.xm new file mode 100644 index 00000000..e1e2739f Binary files /dev/null and b/tracker/desktop-player/tests/retrigger.xm differ