diff --git a/tracker/agb-tracker-interop/src/lib.rs b/tracker/agb-tracker-interop/src/lib.rs index e8502a50..365a4020 100644 --- a/tracker/agb-tracker-interop/src/lib.rs +++ b/tracker/agb-tracker-interop/src/lib.rs @@ -29,14 +29,24 @@ pub struct Pattern { #[derive(Debug)] pub struct PatternSlot { - pub volume: Num, pub speed: Num, - pub panning: Num, pub sample: usize, + pub effect1: PatternEffect, + pub effect2: PatternEffect, } -pub const SKIP_SLOT: usize = 277; -pub const STOP_CHANNEL: usize = 278; +#[derive(Debug, Default)] +pub enum PatternEffect { + /// Don't play an effect + #[default] + None, + /// Stops playing the current note + Stop, + /// Plays an arpeggiation of three notes in one row, cycling betwen the current note, current note + first speed, current note + second speed + Arpeggio(Num, Num), + Panning(Num), + Volume(Num), +} #[cfg(feature = "quote")] impl<'a> quote::ToTokens for Track<'a> { @@ -116,22 +126,20 @@ impl quote::ToTokens for PatternSlot { use quote::{quote, TokenStreamExt}; let PatternSlot { - volume, speed, - panning, sample, + effect1, + effect2, } = &self; - let volume = volume.to_raw(); let speed = speed.to_raw(); - let panning = panning.to_raw(); tokens.append_all(quote! { agb_tracker::__private::agb_tracker_interop::PatternSlot { - volume: agb_tracker::__private::Num::from_raw(#volume), speed: agb_tracker::__private::Num::from_raw(#speed), - panning: agb_tracker::__private::Num::from_raw(#panning), sample: #sample, + effect1: #effect1, + effect2: #effect2, } }); } @@ -155,3 +163,32 @@ impl quote::ToTokens for Pattern { }) } } + +#[cfg(feature = "quote")] +impl quote::ToTokens for PatternEffect { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use quote::{quote, TokenStreamExt}; + + let type_bit = match self { + PatternEffect::None => quote! { None }, + PatternEffect::Stop => quote! { Stop }, + PatternEffect::Arpeggio(first, second) => { + let first = first.to_raw(); + let second = second.to_raw(); + quote! { Arpeggio(agb_tracker::__private::Num::from_raw(#first), agb_tracker::__private::Num::from_raw(#second)) } + } + PatternEffect::Panning(panning) => { + let panning = panning.to_raw(); + quote! { Panning(agb_tracker::__private::Num::from_raw(#panning))} + } + PatternEffect::Volume(volume) => { + let volume = volume.to_raw(); + quote! { Volume(agb_tracker::__private::Num::from_raw(#volume))} + } + }; + + tokens.append_all(quote! { + agb_tracker::__private::agb_tracker_interop::PatternEffect::#type_bit + }); + } +} diff --git a/tracker/agb-tracker/src/lib.rs b/tracker/agb-tracker/src/lib.rs index ab88bd32..a0fa3cd8 100644 --- a/tracker/agb-tracker/src/lib.rs +++ b/tracker/agb-tracker/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc; +use agb_tracker_interop::{PatternEffect, Sample}; use alloc::vec::Vec; use agb::{ @@ -26,7 +27,7 @@ pub use agb_tracker_interop::Track; pub struct Tracker { track: &'static Track<'static>, - channels: Vec>, + channels: Vec, frame: Num, tick: u16, @@ -35,10 +36,14 @@ pub struct Tracker { current_pattern: usize, } +struct TrackerChannel { + channel_id: Option, +} + impl Tracker { pub fn new(track: &'static Track<'static>) -> Self { let mut channels = Vec::new(); - channels.resize_with(track.num_channels, || None); + channels.resize_with(track.num_channels, || TrackerChannel { channel_id: None }); Self { track, @@ -66,55 +71,62 @@ impl Tracker { let pattern_slots = &self.track.pattern_data[pattern_data_pos..pattern_data_pos + self.track.num_channels]; - for (channel_id, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) { - if pattern_slot.sample == agb_tracker_interop::SKIP_SLOT { - // completely skip - } else if pattern_slot.sample == agb_tracker_interop::STOP_CHANNEL { - if let Some(channel) = channel_id - .take() - .and_then(|channel_id| mixer.channel(&channel_id)) - { - channel.stop(); - } - } else if pattern_slot.sample == 0 { - if let Some(channel) = channel_id - .as_ref() - .and_then(|channel_id| mixer.channel(channel_id)) - { - if pattern_slot.volume != 0.into() { - channel.volume(pattern_slot.volume); - } - - if pattern_slot.panning != 0.into() { - channel.panning(pattern_slot.panning); - } - - if pattern_slot.speed != 0.into() { - channel.playback(pattern_slot.speed); - } - } - } else { - if let Some(channel) = channel_id - .take() - .and_then(|channel_id| mixer.channel(&channel_id)) - { - channel.stop(); - } - + for (channel, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) { + if pattern_slot.sample != 0 { let sample = &self.track.samples[pattern_slot.sample - 1]; - let mut new_channel = SoundChannel::new(sample.data); - new_channel - .panning(pattern_slot.panning) - .volume(pattern_slot.volume) - .playback(pattern_slot.speed) - .restart_point(sample.restart_point); - - if sample.should_loop { - new_channel.should_loop(); - } - - *channel_id = mixer.play_sound(new_channel); + channel.play_sound(mixer, sample); } + + channel.apply_effect(mixer, &pattern_slot.effect1, self.tick, pattern_slot.speed); + channel.apply_effect(mixer, &pattern_slot.effect2, self.tick, pattern_slot.speed); + // if pattern_slot.sample == agb_tracker_interop::SKIP_SLOT { + // // completely skip + // } else if pattern_slot.sample == agb_tracker_interop::STOP_CHANNEL { + // if let Some(channel) = channel_id + // .take() + // .and_then(|channel_id| mixer.channel(&channel_id)) + // { + // channel.stop(); + // } + // } else if pattern_slot.sample == 0 { + // if let Some(channel) = channel_id + // .as_ref() + // .and_then(|channel_id| mixer.channel(channel_id)) + // { + // if pattern_slot.volume != 0.into() { + // channel.volume(pattern_slot.volume); + // } + + // if pattern_slot.panning != 0.into() { + // channel.panning(pattern_slot.panning); + // } + + // if pattern_slot.speed != 0.into() { + // channel.playback(pattern_slot.speed); + // } + // } + // } else { + // if let Some(channel) = channel_id + // .take() + // .and_then(|channel_id| mixer.channel(&channel_id)) + // { + // channel.stop(); + // } + + // let sample = &self.track.samples[pattern_slot.sample - 1]; + // let mut new_channel = SoundChannel::new(sample.data); + // new_channel + // .panning(pattern_slot.panning) + // .volume(pattern_slot.volume) + // .playback(pattern_slot.speed) + // .restart_point(sample.restart_point); + + // if sample.should_loop { + // new_channel.should_loop(); + // } + + // *channel_id = mixer.play_sound(new_channel); + // } } self.increment_step(); @@ -147,6 +159,57 @@ impl Tracker { } } +impl TrackerChannel { + fn play_sound(&mut self, mixer: &mut Mixer<'_>, sample: &Sample<'static>) { + self.channel_id + .take() + .and_then(|channel_id| mixer.channel(&channel_id)) + .map(|channel| channel.stop()); + + let mut new_channel = SoundChannel::new(sample.data); + + if sample.should_loop { + new_channel + .should_loop() + .restart_point(sample.restart_point); + } + + self.channel_id = mixer.play_sound(new_channel) + } + + fn apply_effect( + &mut self, + mixer: &mut Mixer<'_>, + effect: &PatternEffect, + tick: u16, + speed: Num, + ) { + if let Some(channel) = self + .channel_id + .as_ref() + .and_then(|channel_id| mixer.channel(&channel_id)) + { + if speed != 0.into() { + channel.playback(speed); + } + + match effect { + PatternEffect::None => {} + PatternEffect::Stop => { + channel.stop(); + } + PatternEffect::Arpeggio(_, _) => todo!(), + PatternEffect::Panning(panning) => { + channel.panning(*panning); + } + PatternEffect::Volume(volume) => { + channel.volume(*volume); + } + } + } + } +} + #[cfg(test)] #[agb::entry] fn main(gba: agb::Gba) -> ! { diff --git a/tracker/agb-xm-core/src/lib.rs b/tracker/agb-xm-core/src/lib.rs index 217e431a..f9bdf5b0 100644 --- a/tracker/agb-xm-core/src/lib.rs +++ b/tracker/agb-xm-core/src/lib.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, error::Error, fs, path::Path}; +use agb_tracker_interop::PatternEffect; use proc_macro2::TokenStream; use proc_macro_error::abort; @@ -53,7 +54,6 @@ pub fn parse_module(module: &Module) -> TokenStream { should_loop: bool, fine_tune: f64, relative_note: i8, - volume: f64, restart_point: u32, } @@ -66,7 +66,6 @@ pub fn parse_module(module: &Module) -> TokenStream { let should_loop = !matches!(sample.flags, LoopType::No); let fine_tune = sample.finetune as f64; let relative_note = sample.relative_note; - let volume = sample.volume as f64; let restart_point = sample.loop_start; let sample_len = if sample.loop_length > 0 { (sample.loop_length + sample.loop_start) as usize @@ -93,7 +92,6 @@ pub fn parse_module(module: &Module) -> TokenStream { should_loop, fine_tune, relative_note, - volume, restart_point, }); } @@ -125,50 +123,35 @@ pub fn parse_module(module: &Module) -> TokenStream { } }; - let (mut volume, mut panning) = match slot.volume { - 0x10..=0x50 => (Some((slot.volume - 0x10) as f64 / 64.0), None), - 0xC0..=0xCF => ( - None, - Some(Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 64), + let mut effect1 = match slot.volume { + 0x10..=0x50 => { + PatternEffect::Volume(Num::new((slot.volume - 0x10) as i16) / 64) + } + 0xC0..=0xCF => PatternEffect::Panning( + Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 64, ), - _ => (None, Some(0.into())), + _ => PatternEffect::None, }; - if slot.effect_type == 0xC { - volume = Some(slot.effect_parameter as f64 / 255.0); - } + let effect2 = match slot.effect_type { + 0x8 => { + PatternEffect::Panning(Num::new(slot.effect_parameter as i16 - 128) / 128) + } + 0xC => PatternEffect::Volume(Num::new(slot.effect_parameter as i16) / 255), + _ => PatternEffect::None, + }; - if slot.effect_type == 0x8 { - panning = Some(Num::new(slot.effect_parameter as i16 - 128) / 128); + if matches!(slot.note, Note::KeyOff) { + effect1 = PatternEffect::Stop; } if sample == 0 { - if slot.volume == 0 && slot.effect_type == 0 { - pattern_data.push(agb_tracker_interop::PatternSlot { - volume: 0.into(), - speed: 0.into(), - panning: 0.into(), - sample: agb_tracker_interop::SKIP_SLOT, - }); - } else if matches!(slot.note, Note::KeyOff) || volume == Some(0.0) { - pattern_data.push(agb_tracker_interop::PatternSlot { - volume: 0.into(), - speed: 0.into(), - panning: 0.into(), - sample: agb_tracker_interop::STOP_CHANNEL, - }); - } else { - let volume: Num = - Num::from_raw((volume.unwrap_or(0.into()) * (1 << 4) as f64) as i16); - let panning = panning.unwrap_or(0.into()); - - pattern_data.push(agb_tracker_interop::PatternSlot { - volume, - speed: 0.into(), - panning, - sample: 0, - }); - } + pattern_data.push(agb_tracker_interop::PatternSlot { + speed: 0.into(), + sample: 0, + effect1, + effect2, + }); } else { let sample_played = &samples[sample - 1]; @@ -179,15 +162,11 @@ pub fn parse_module(module: &Module) -> TokenStream { module.frequency_type, ); - let overall_volume = volume.unwrap_or(1.into()) * sample_played.volume; - let volume: Num = - Num::from_raw((overall_volume * (1 << 4) as f64) as i16); - pattern_data.push(agb_tracker_interop::PatternSlot { - volume, speed, - panning: panning.unwrap_or(0.into()), sample, + effect1, + effect2, }); } }