From 2e0f89978ab82b71682313699b319e4815a489dc Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 15 May 2024 22:30:13 +0100 Subject: [PATCH 1/4] Add note delay support (effect EDx) --- tracker/agb-tracker-interop/src/lib.rs | 2 ++ tracker/agb-tracker/src/lib.rs | 9 +++++++++ tracker/agb-xm-core/src/lib.rs | 1 + 3 files changed, 12 insertions(+) diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index d0eb1aac..e9ccebc0 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -62,6 +62,7 @@ pub enum PatternEffect { VolumeSlide(Num), FineVolumeSlide(Num), NoteCut(u32), + NoteDelay(u32), Portamento(Num), /// Slide each tick the first amount to at most the second amount TonePortamento(Num, Num), @@ -289,6 +290,7 @@ impl quote::ToTokens for PatternEffect { quote! { FineVolumeSlide(agb_tracker::__private::Num::from_raw(#amount))} } PatternEffect::NoteCut(wait) => quote! { NoteCut(#wait) }, + PatternEffect::NoteDelay(wait) => quote! { NoteDelay(#wait) }, PatternEffect::Portamento(amount) => { let amount = amount.to_raw(); quote! { Portamento(agb_tracker::__private::Num::from_raw(#amount))} diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index 11212b20..9e1b55ab 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -405,6 +405,15 @@ impl TrackerChannel { } } } + PatternEffect::NoteDelay(wait) => { + if tick <= *wait { + channel.volume(0); + } + + if tick == *wait + 1 { + channel.volume((self.volume * global_settings.volume).try_change_base().unwrap()); + } + } PatternEffect::Portamento(amount) => { if tick != 0 { self.base_speed *= amount.change_base(); diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index fcc20e0a..ee644b01 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -347,6 +347,7 @@ pub fn parse_module(module: &Module) -> TokenStream { -Num::new((slot.effect_parameter & 0xf) as i16) / 128, ), 0xC => PatternEffect::NoteCut((slot.effect_parameter & 0xf).into()), + 0xD => PatternEffect::NoteDelay((slot.effect_parameter & 0xf).into()), _ => PatternEffect::None, }, 0xF => match slot.effect_parameter { From 5494ae29e1d77d99f794719bd4f7cbf1ade5fc3f Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Wed, 15 May 2024 23:47:37 +0100 Subject: [PATCH 2/4] Get the envelopes correct --- tracker/agb-xm-core/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index ee644b01..ce224809 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -71,7 +71,7 @@ pub fn parse_module(module: &Module) -> TokenStream { let envelope = &instrument.volume_envelope; let envelope_id = if envelope.enabled { - let envelope: EnvelopeData = envelope.as_ref().into(); + let envelope = EnvelopeData::new(envelope, module.default_bpm as u32); let id = existing_envelopes .entry(envelope) .or_insert_with_key(|envelope| { @@ -520,23 +520,23 @@ struct EnvelopeData { loop_end: Option, } -impl From<&xmrs::envelope::Envelope> for EnvelopeData { - fn from(e: &xmrs::envelope::Envelope) -> Self { +impl EnvelopeData { + fn new(e: &xmrs::envelope::Envelope, bpm: u32) -> Self { let mut amounts = vec![]; - // it should be sampled at 50fps, but we're sampling at 60fps, so need to do a bit of cheating here. - for frame in 0..(e.point.last().unwrap().frame * 60 / 50) { - let xm_frame = frame * 50 / 60; + // FT2 manual says number of ticks / second = BPM * 0.4 = BPM * 4 / 10. GBA runs at 60Hz + for frame in 0..(e.point.last().unwrap().frame as u32 * 60 * 10 / bpm / 4) { + let xm_frame = frame * bpm * 4 / 60 / 10; let index = e .point .iter() - .rposition(|point| point.frame < xm_frame) + .rposition(|point| point.frame < xm_frame as u16) .unwrap_or(0); let first_point = &e.point[index]; let second_point = &e.point[index + 1]; - let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame) / 64.0; + let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame as u16) / 64.0; let amount = Num::from_f32(amount); amounts.push(amount); From 352b352b27ecf0d4aba013cff1e0f45cb3a0384d Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Thu, 16 May 2024 00:27:20 +0100 Subject: [PATCH 3/4] Properly implement delay and envelopes --- tracker/agb-tracker/src/lib.rs | 7 ++++--- tracker/agb-xm-core/src/lib.rs | 34 ++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index 9e1b55ab..ba5570f3 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -406,11 +406,12 @@ impl TrackerChannel { } } PatternEffect::NoteDelay(wait) => { - if tick <= *wait { - channel.volume(0); + if tick < *wait { + channel.pause(); } - if tick == *wait + 1 { + if tick == *wait { + channel.resume(); channel.volume((self.volume * global_settings.volume).try_change_base().unwrap()); } } diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index ce224809..5181ca80 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -524,33 +524,41 @@ impl EnvelopeData { fn new(e: &xmrs::envelope::Envelope, bpm: u32) -> Self { let mut amounts = vec![]; - // FT2 manual says number of ticks / second = BPM * 0.4 = BPM * 4 / 10. GBA runs at 60Hz - for frame in 0..(e.point.last().unwrap().frame as u32 * 60 * 10 / bpm / 4) { - let xm_frame = frame * bpm * 4 / 60 / 10; + for frame in 0..=(Self::envelope_frame_to_gba_frame(e.point.last().unwrap().frame, bpm)) { + let xm_frame = Self::gba_frame_to_envelope_frame(frame, bpm); let index = e .point .iter() - .rposition(|point| point.frame < xm_frame as u16) + .rposition(|point| point.frame < xm_frame) .unwrap_or(0); let first_point = &e.point[index]; let second_point = &e.point[index + 1]; - let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame as u16) / 64.0; + let amount = EnvelopePoint::lerp(first_point, second_point, xm_frame) / 64.0; let amount = Num::from_f32(amount); amounts.push(amount); } let sustain = if e.sustain_enabled { - Some(e.point[e.sustain_point as usize].frame as usize * 60 / 50) + Some( + Self::envelope_frame_to_gba_frame(e.point[e.sustain_point as usize].frame, bpm) + as usize, + ) } else { None }; let (loop_start, loop_end) = if e.loop_enabled { ( - Some(e.point[e.loop_start_point as usize].frame as usize * 60 / 50), - Some(e.point[e.loop_end_point as usize].frame as usize * 60 / 50), + Some(Self::envelope_frame_to_gba_frame( + e.point[e.loop_start_point as usize].frame, + bpm, + ) as usize), + Some(Self::envelope_frame_to_gba_frame( + e.point[e.loop_end_point as usize].frame, + bpm, + ) as usize), ) } else { (None, None) @@ -563,4 +571,14 @@ impl EnvelopeData { loop_end, } } + + fn envelope_frame_to_gba_frame(envelope_frame: u16, bpm: u32) -> u16 { + // FT2 manual says number of ticks / second = BPM * 0.4 + // somehow this works as a good approximation :/ + (envelope_frame as u32 * 250 / bpm) as u16 + } + + fn gba_frame_to_envelope_frame(gba_frame: u16, bpm: u32) -> u16 { + (gba_frame as u32 * bpm / 250) as u16 + } } From 93a3bc8e56b88de1e1e857194275b798a86fc813 Mon Sep 17 00:00:00 2001 From: Gwilym Inzani Date: Thu, 16 May 2024 23:04:26 +0100 Subject: [PATCH 4/4] just fmt --- tracker/agb-tracker/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index ba5570f3..8312717f 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -412,7 +412,11 @@ impl TrackerChannel { if tick == *wait { channel.resume(); - channel.volume((self.volume * global_settings.volume).try_change_base().unwrap()); + channel.volume( + (self.volume * global_settings.volume) + .try_change_base() + .unwrap(), + ); } } PatternEffect::Portamento(amount) => {