Refactor to effects

This commit is contained in:
Gwilym Inzani 2023-07-16 23:57:11 +01:00
parent 7861571a96
commit aa635e9aa6
3 changed files with 184 additions and 105 deletions

View file

@ -29,14 +29,24 @@ pub struct Pattern {
#[derive(Debug)] #[derive(Debug)]
pub struct PatternSlot { pub struct PatternSlot {
pub volume: Num<i16, 4>,
pub speed: Num<u32, 8>, pub speed: Num<u32, 8>,
pub panning: Num<i16, 4>,
pub sample: usize, pub sample: usize,
pub effect1: PatternEffect,
pub effect2: PatternEffect,
} }
pub const SKIP_SLOT: usize = 277; #[derive(Debug, Default)]
pub const STOP_CHANNEL: usize = 278; 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<u16, 8>, Num<u16, 8>),
Panning(Num<i16, 4>),
Volume(Num<i16, 4>),
}
#[cfg(feature = "quote")] #[cfg(feature = "quote")]
impl<'a> quote::ToTokens for Track<'a> { impl<'a> quote::ToTokens for Track<'a> {
@ -116,22 +126,20 @@ impl quote::ToTokens for PatternSlot {
use quote::{quote, TokenStreamExt}; use quote::{quote, TokenStreamExt};
let PatternSlot { let PatternSlot {
volume,
speed, speed,
panning,
sample, sample,
effect1,
effect2,
} = &self; } = &self;
let volume = volume.to_raw();
let speed = speed.to_raw(); let speed = speed.to_raw();
let panning = panning.to_raw();
tokens.append_all(quote! { tokens.append_all(quote! {
agb_tracker::__private::agb_tracker_interop::PatternSlot { agb_tracker::__private::agb_tracker_interop::PatternSlot {
volume: agb_tracker::__private::Num::from_raw(#volume),
speed: agb_tracker::__private::Num::from_raw(#speed), speed: agb_tracker::__private::Num::from_raw(#speed),
panning: agb_tracker::__private::Num::from_raw(#panning),
sample: #sample, 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
});
}
}

View file

@ -7,6 +7,7 @@
extern crate alloc; extern crate alloc;
use agb_tracker_interop::{PatternEffect, Sample};
use alloc::vec::Vec; use alloc::vec::Vec;
use agb::{ use agb::{
@ -26,7 +27,7 @@ pub use agb_tracker_interop::Track;
pub struct Tracker { pub struct Tracker {
track: &'static Track<'static>, track: &'static Track<'static>,
channels: Vec<Option<ChannelId>>, channels: Vec<TrackerChannel>,
frame: Num<u16, 8>, frame: Num<u16, 8>,
tick: u16, tick: u16,
@ -35,10 +36,14 @@ pub struct Tracker {
current_pattern: usize, current_pattern: usize,
} }
struct TrackerChannel {
channel_id: Option<ChannelId>,
}
impl Tracker { impl Tracker {
pub fn new(track: &'static Track<'static>) -> Self { pub fn new(track: &'static Track<'static>) -> Self {
let mut channels = Vec::new(); let mut channels = Vec::new();
channels.resize_with(track.num_channels, || None); channels.resize_with(track.num_channels, || TrackerChannel { channel_id: None });
Self { Self {
track, track,
@ -66,55 +71,62 @@ impl Tracker {
let pattern_slots = let pattern_slots =
&self.track.pattern_data[pattern_data_pos..pattern_data_pos + self.track.num_channels]; &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) { for (channel, pattern_slot) in self.channels.iter_mut().zip(pattern_slots) {
if pattern_slot.sample == agb_tracker_interop::SKIP_SLOT { if pattern_slot.sample != 0 {
// 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 sample = &self.track.samples[pattern_slot.sample - 1];
let mut new_channel = SoundChannel::new(sample.data); channel.play_sound(mixer, sample);
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.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(); 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<u32, 8>,
) {
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)] #[cfg(test)]
#[agb::entry] #[agb::entry]
fn main(gba: agb::Gba) -> ! { fn main(gba: agb::Gba) -> ! {

View file

@ -1,5 +1,6 @@
use std::{collections::HashMap, error::Error, fs, path::Path}; use std::{collections::HashMap, error::Error, fs, path::Path};
use agb_tracker_interop::PatternEffect;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use proc_macro_error::abort; use proc_macro_error::abort;
@ -53,7 +54,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
should_loop: bool, should_loop: bool,
fine_tune: f64, fine_tune: f64,
relative_note: i8, relative_note: i8,
volume: f64,
restart_point: u32, restart_point: u32,
} }
@ -66,7 +66,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
let should_loop = !matches!(sample.flags, LoopType::No); let should_loop = !matches!(sample.flags, LoopType::No);
let fine_tune = sample.finetune as f64; let fine_tune = sample.finetune as f64;
let relative_note = sample.relative_note; let relative_note = sample.relative_note;
let volume = sample.volume as f64;
let restart_point = sample.loop_start; let restart_point = sample.loop_start;
let sample_len = if sample.loop_length > 0 { let sample_len = if sample.loop_length > 0 {
(sample.loop_length + sample.loop_start) as usize (sample.loop_length + sample.loop_start) as usize
@ -93,7 +92,6 @@ pub fn parse_module(module: &Module) -> TokenStream {
should_loop, should_loop,
fine_tune, fine_tune,
relative_note, relative_note,
volume,
restart_point, restart_point,
}); });
} }
@ -125,50 +123,35 @@ pub fn parse_module(module: &Module) -> TokenStream {
} }
}; };
let (mut volume, mut panning) = match slot.volume { let mut effect1 = match slot.volume {
0x10..=0x50 => (Some((slot.volume - 0x10) as f64 / 64.0), None), 0x10..=0x50 => {
0xC0..=0xCF => ( PatternEffect::Volume(Num::new((slot.volume - 0x10) as i16) / 64)
None, }
Some(Num::new(slot.volume as i16 - (0xC0 + (0xCF - 0xC0) / 2)) / 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 { let effect2 = match slot.effect_type {
volume = Some(slot.effect_parameter as f64 / 255.0); 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 { if matches!(slot.note, Note::KeyOff) {
panning = Some(Num::new(slot.effect_parameter as i16 - 128) / 128); effect1 = PatternEffect::Stop;
} }
if sample == 0 { if sample == 0 {
if slot.volume == 0 && slot.effect_type == 0 {
pattern_data.push(agb_tracker_interop::PatternSlot { pattern_data.push(agb_tracker_interop::PatternSlot {
volume: 0.into(),
speed: 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<i16, 4> =
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, sample: 0,
effect1,
effect2,
}); });
}
} else { } else {
let sample_played = &samples[sample - 1]; let sample_played = &samples[sample - 1];
@ -179,15 +162,11 @@ pub fn parse_module(module: &Module) -> TokenStream {
module.frequency_type, module.frequency_type,
); );
let overall_volume = volume.unwrap_or(1.into()) * sample_played.volume;
let volume: Num<i16, 4> =
Num::from_raw((overall_volume * (1 << 4) as f64) as i16);
pattern_data.push(agb_tracker_interop::PatternSlot { pattern_data.push(agb_tracker_interop::PatternSlot {
volume,
speed, speed,
panning: panning.unwrap_or(0.into()),
sample, sample,
effect1,
effect2,
}); });
} }
} }