diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index ad8a9b66..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, @@ -94,7 +102,8 @@ pub enum PatternEffect { PitchBend(Num), Jump(Jump), SampleOffset(u16), - Retrigger(u8), + /// Retrigger the note every u8 ticks with the volume change specified + Retrigger(RetriggerVolumeChange, u8), } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -130,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) { @@ -404,7 +429,9 @@ impl quote::ToTokens for PatternEffect { quote! { Jump(#jump) } } PatternEffect::SampleOffset(offset) => quote! { SampleOffset(#offset) }, - PatternEffect::Retrigger(ticks) => quote! { Retrigger(#ticks) }, + PatternEffect::Retrigger(retrigger_volume_change, ticks) => { + quote! { Retrigger(#retrigger_volume_change, #ticks) } + } }; tokens.append_all(quote! { diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index 500f067f..8a5b5ed7 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -613,8 +613,18 @@ impl TrackerChannel { self.current_pos = Some(*offset); } } - PatternEffect::Retrigger(ticks) => { + 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); } } diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index 4fb0cced..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,7 +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()]; + let mut previous_retriggers: Vec> = + vec![None; module.get_num_channels()]; for row in pattern.iter() { // the combined jump for each row @@ -411,7 +412,7 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { 0x9 => { let retrigger_amount = slot.effect_parameter & 0xf; let modified_amount = if retrigger_amount == 0 { - if let Some(previous_retrigger) = + if let Some((_, previous_retrigger)) = previous_retriggers[channel_number] { previous_retrigger @@ -419,11 +420,15 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { 1 } } else { - previous_retriggers[channel_number] = Some(retrigger_amount); + previous_retriggers[channel_number] = + Some((RetriggerVolumeChange::NoChange, retrigger_amount)); retrigger_amount }; - PatternEffect::Retrigger(modified_amount) + PatternEffect::Retrigger( + RetriggerVolumeChange::NoChange, + modified_amount, + ) } 0xA => PatternEffect::FineVolumeSlide( Num::new((slot.effect_parameter & 0xf) as i16) / 128, @@ -465,22 +470,32 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { let first = effect_parameter >> 4; let second = effect_parameter & 0xF; - if first != 0 { - eprintln!("Unsupported retrigger effect volume {first}"); - } + 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_retriggers[channel_number] { - previous_retrigger + if let Some((_, previous_retrigger)) = previous_retrigger { + *previous_retrigger } else { 1 } } else { - previous_retriggers[channel_number] = Some(second); second }; - PatternEffect::Retrigger(ticks_between_retriggers) + *previous_retrigger = Some((volume_type, ticks_between_retriggers)); + + PatternEffect::Retrigger(volume_type, ticks_between_retriggers) } e => { let effect_char = char::from_digit(e as u32, 36) diff --git a/tracker/desktop-player/tests/retrigger.xm b/tracker/desktop-player/tests/retrigger.xm index 9494dd02..e1e2739f 100644 Binary files a/tracker/desktop-player/tests/retrigger.xm and b/tracker/desktop-player/tests/retrigger.xm differ