diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index 565c1d89..f31509c0 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -29,6 +29,16 @@ pub struct Sample { pub fadeout: Num, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Jump { + /// Jump to the given pattern position `pattern` at row index 0 + Position { pattern: u8 }, + /// Jump to the next pattern position, at row index `row` + PatternBreak { row: u8 }, + /// Jump to the pattern position `pattern` at row index `row` + Combined { pattern: u8, row: u8 }, +} + #[derive(Debug, Clone)] pub struct Pattern { pub length: usize, @@ -82,6 +92,7 @@ pub enum PatternEffect { GlobalVolumeSlide(Num), /// Increase / decrease the pitch by the specified amount immediately PitchBend(Num), + Jump(Jump), } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -92,6 +103,31 @@ pub enum Waveform { Square, } +#[cfg(feature = "quote")] +impl quote::ToTokens for Jump { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::{quote, TokenStreamExt}; + + let type_bit = match self { + Jump::Position { pattern } => { + quote! {Position{pattern: #pattern} } + } + Jump::PatternBreak { row } => { + quote! { PatternBreak{row: #row} } + } + Jump::Combined { pattern, row } => { + quote! { + Combined{pattern: #pattern, row: #row } + } + } + }; + + tokens.append_all(quote! { + agb_tracker::__private::agb_tracker_interop::Jump::#type_bit + }); + } +} + #[cfg(feature = "quote")] impl quote::ToTokens for Track { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { @@ -286,6 +322,7 @@ impl quote::ToTokens for Pattern { } = self; tokens.append_all(quote! { + agb_tracker::__private::agb_tracker_interop::Pattern { length: #length, start_position: #start_position, @@ -361,6 +398,9 @@ impl quote::ToTokens for PatternEffect { let amount = amount.to_raw(); quote! { Vibrato(#waveform, agb_tracker::__private::Num::from_raw(#amount), #speed) } } + PatternEffect::Jump(jump) => { + quote! { Jump(#jump) } + } }; tokens.append_all(quote! { diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index c575bb98..b55a5a6f 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -68,7 +68,7 @@ extern crate alloc; mod lookups; mod mixer; -use agb_tracker_interop::{PatternEffect, Sample, Waveform}; +use agb_tracker_interop::{Jump, PatternEffect, Sample, Waveform}; use alloc::vec::Vec; pub use mixer::{Mixer, SoundChannel}; @@ -111,6 +111,7 @@ pub struct TrackerInner<'track, TChannelId> { current_row: usize, current_pattern: usize, + current_jump: Option, } #[derive(Default)] @@ -204,6 +205,7 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> { current_pattern: 0, current_row: 0, + current_jump: None, } } @@ -269,12 +271,14 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> { self.tick, &mut self.global_settings, &mut self.envelopes[i], + &mut self.current_jump, ); channel.apply_effect( &pattern_slot.effect2, self.tick, &mut self.global_settings, &mut self.envelopes[i], + &mut self.current_jump, ); } @@ -373,16 +377,21 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> { self.frame -= self.global_settings.frames_per_tick; if self.tick >= self.global_settings.ticks_per_step { - self.current_row += 1; + if let Some(jump) = self.current_jump.take() { + self.handle_jump(jump); + } else { + self.current_row += 1; - if self.current_row - >= self.track.patterns[self.track.patterns_to_play[self.current_pattern]].length - { - self.current_pattern += 1; - self.current_row = 0; + if self.current_row + >= self.track.patterns[self.track.patterns_to_play[self.current_pattern]] + .length + { + self.current_pattern += 1; + self.current_row = 0; - if self.current_pattern >= self.track.patterns_to_play.len() { - self.current_pattern = self.track.repeat; + if self.current_pattern >= self.track.patterns_to_play.len() { + self.current_pattern = self.track.repeat; + } } } @@ -394,6 +403,32 @@ impl<'track, TChannelId> TrackerInner<'track, TChannelId> { false } } + + fn handle_jump(&mut self, jump: Jump) { + match jump { + Jump::Position { pattern } => { + self.current_pattern = pattern as usize; + self.current_row = 0; + } + Jump::PatternBreak { row } => { + self.current_pattern += 1; + self.current_row = row as usize; + } + Jump::Combined { pattern, row } => { + self.current_pattern = pattern as usize; + self.current_row = row as usize; + } + }; + if self.current_pattern >= self.track.patterns_to_play.len() { + self.current_pattern = self.track.repeat; + } + if self.current_row + >= self.track.patterns[self.track.patterns_to_play[self.current_pattern]].length + { + // TODO: reconsider this default + self.current_row = 0; + } + } } impl TrackerChannel { @@ -419,6 +454,7 @@ impl TrackerChannel { tick: u32, global_settings: &mut GlobalSettings, envelope_state: &mut Option, + current_jump: &mut Option, ) { match effect { PatternEffect::None => {} @@ -547,6 +583,9 @@ impl TrackerChannel { self.vibrato.waveform = *waveform; self.vibrato.enable = true; } + PatternEffect::Jump(jump) => { + *current_jump = Some(jump.clone()); + } } } diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index 6ad3e88c..7b39c1d4 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::{PatternEffect, Waveform}; +use agb_tracker_interop::{Jump, PatternEffect, Waveform}; use xmrs::prelude::*; @@ -101,6 +101,9 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { let mut note_and_sample = vec![None; module.get_num_channels()]; for row in pattern.iter() { + // the combined jump for each row + let mut jump = None; + for (i, slot) in row.iter().enumerate() { let channel_number = i % module.get_num_channels(); @@ -319,6 +322,18 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { ) } } + 0xB => { + let pattern_idx = slot.effect_parameter; + + jump = Some(( + channel_number, + Jump::Position { + pattern: pattern_idx, + }, + )); + + PatternEffect::None + } 0xC => { if let Some((_, sample)) = maybe_note_and_sample { PatternEffect::Volume( @@ -328,6 +343,29 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { PatternEffect::None } } + 0xD => { + // NOTE: this field is generally interpreted as decimal. + let first = slot.effect_parameter >> 4; + let second = slot.effect_parameter & 0xF; + let row_idx = first * 10 + second; + + let pattern_break = Jump::PatternBreak { row: row_idx }; + + // if to the *right* of 0xD effect, make combined + if let Some((idx, Jump::Position { pattern })) = jump { + jump = Some(( + idx, + Jump::Combined { + pattern, + row: row_idx, + }, + )) + } else { + jump = Some((channel_number, pattern_break)); + } + + PatternEffect::None + } 0xE => match slot.effect_parameter >> 4 { 0x1 => { let c4_speed: Num = @@ -433,6 +471,16 @@ pub fn parse_module(module: &Module) -> agb_tracker_interop::Track { }); } } + // At the last channel, evaluate the combined jump, + // and place at the first jump effect channel index + if let Some((jump_channel, jump)) = jump.take() { + let jump_effect = PatternEffect::Jump(jump); + let pattern_data_idx = + pattern_data.len() - module.get_num_channels() + jump_channel; + if let Some(data) = pattern_data.get_mut(pattern_data_idx) { + data.effect2 = jump_effect; + } + } } patterns.push(agb_tracker_interop::Pattern {