From 830668a21cd1397049a489e29fd8bc1dba7101b6 Mon Sep 17 00:00:00 2001 From: nenikitov Date: Mon, 6 May 2024 21:07:55 -0400 Subject: [PATCH] feat(effect): porta tone --- Cargo.toml | 9 +++ engine/src/asset/sound/dat/finetune.rs | 8 ++- engine/src/asset/sound/dat/mixer.rs | 57 ++++++++++++------ engine/src/asset/sound/dat/pattern_effect.rs | 63 ++++++++++---------- engine/src/asset/sound/dat/pattern_event.rs | 2 +- engine/src/asset/sound/mod.rs | 15 +++-- engine/src/utils/iterator.rs | 24 ++++---- 7 files changed, 108 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 62c5a0f..ca258b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,12 @@ members = [ "ashen", "engine", ] + + +[profile.perf] +inherits = "release" +codegen-units = 1 +incremental = false +lto = "fat" +opt-level = 3 +panic = "abort" diff --git a/engine/src/asset/sound/dat/finetune.rs b/engine/src/asset/sound/dat/finetune.rs index c183bf6..7f7cd11 100644 --- a/engine/src/asset/sound/dat/finetune.rs +++ b/engine/src/asset/sound/dat/finetune.rs @@ -1,6 +1,6 @@ use std::ops::{Add, AddAssign, Neg, Sub}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord)] pub struct FineTune { cents: i32, } @@ -68,6 +68,12 @@ impl Neg for FineTune { } } +impl PartialOrd for FineTune { + fn partial_cmp(&self, other: &Self) -> Option { + self.cents.partial_cmp(&other.cents) + } +} + #[cfg(test)] mod tests { use assert_approx_eq::assert_approx_eq; diff --git a/engine/src/asset/sound/dat/mixer.rs b/engine/src/asset/sound/dat/mixer.rs index 1b4fae4..70541e3 100644 --- a/engine/src/asset/sound/dat/mixer.rs +++ b/engine/src/asset/sound/dat/mixer.rs @@ -1,7 +1,5 @@ use std::{collections::HashMap, rc::Rc}; -use itertools::Itertools; - use super::{pattern_effect::*, pattern_event::*, t_instrument::*, t_song::*}; use crate::asset::sound::{ dat::finetune::FineTune, @@ -92,18 +90,23 @@ impl TSongMixerUtils for TSong { PatternEffect::Speed(Speed::TicksPerRow(s)) => { speed = s; } - PatternEffect::Volume(Volume::Set(volume)) => { - channel.volume = volume; - } PatternEffect::Porta(Porta::Bump { - up: _, - small: _, finetune: Some(finetune), + .. }) => { if let Some(note) = &mut channel.note { note.finetune += finetune; } } + PatternEffect::Volume(Volume::Set(volume)) => { + channel.volume = volume; + } + PatternEffect::Volume(Volume::Bump { + volume: Some(volume), + .. + }) => { + channel.volume += volume; + } PatternEffect::SampleOffset(Some(offset)) => { channel.sample_position = offset; } @@ -120,9 +123,13 @@ impl TSongMixerUtils for TSong { + sample.sample_loop().len(); } } - PatternEffect::Volume(Volume::Slide(None)) + PatternEffect::Porta(Porta::Tone(None)) + | PatternEffect::Porta(Porta::Slide { finetune: None, .. }) + | PatternEffect::Porta(Porta::Bump { finetune: None, .. }) + | PatternEffect::Volume(Volume::Slide(None)) + | PatternEffect::Volume(Volume::Bump { volume: None, .. }) | PatternEffect::SampleOffset(None) => { - unreachable!("effect memory should already be initialized") + unreachable!("effect {effect:?} ({:?}) at ({p} {r} {c}) memory should already be initialized", effect.memory_key()) } _ => {} }; @@ -131,17 +138,33 @@ impl TSongMixerUtils for TSong { // Process repeatable effects for effect in channel.effects.iter().flatten() { match effect { - PatternEffect::Volume(Volume::Slide(Some(volume))) => { - channel.volume += volume; + PatternEffect::Porta(Porta::Tone(Some(step))) => { + if let Some(note) = &mut channel.note { + note.finetune = match note.finetune.cmp(¬e.finetune_initial) + { + std::cmp::Ordering::Less => FineTune::min( + note.finetune + *step, + note.finetune_initial, + ), + std::cmp::Ordering::Greater => FineTune::max( + note.finetune - *step, + note.finetune_initial, + ), + std::cmp::Ordering::Equal => note.finetune, + } + } } PatternEffect::Porta(Porta::Slide { - up: _, finetune: Some(finetune), + .. }) => { if let Some(note) = &mut channel.note { note.finetune += *finetune; } } + PatternEffect::Volume(Volume::Slide(Some(volume))) => { + channel.volume += volume; + } _ => {} } } @@ -154,11 +177,6 @@ impl TSongMixerUtils for TSong { let sample_length = sample_length as usize; let tick_length = sample_length / speed; - let volumes = channels - .iter() - .map(|c| format!("{: >10}", c.volume)) - .join(" "); - for (i, c) in channels.iter_mut().enumerate() { for j in 0..speed { let offset = offset + j * tick_length; @@ -187,8 +205,10 @@ impl TSongMixerUtils for TSong { } } +#[derive(Clone)] struct ChannelNote { finetune: FineTune, + finetune_initial: FineTune, on: bool, } @@ -216,6 +236,7 @@ impl<'a> Channel<'a> { (None, PatternEventNote::On(target)) => { self.note = Some(ChannelNote { finetune: target, + finetune_initial: target, on: true, }); } @@ -266,6 +287,8 @@ impl<'a> Channel<'a> { fn tick(&mut self, duration: usize, volume_scale: f32) -> Vec<[i16; 1]> { if let Some((note, instrument, sample)) = self.get_note_instrument_sample() { // Generate data + // TODO(nenikitov): If `volume_envelope` is `0`, this means that the sample already finished playing + // and there is no reason to keep `note.on`. let volume_envelope = match &instrument.volume { TInstrumentVolume::Envelope(envelope) => { let (envelope, default) = if note.on { diff --git a/engine/src/asset/sound/dat/pattern_effect.rs b/engine/src/asset/sound/dat/pattern_effect.rs index 70227c6..d193a6a 100644 --- a/engine/src/asset/sound/dat/pattern_effect.rs +++ b/engine/src/asset/sound/dat/pattern_effect.rs @@ -12,13 +12,6 @@ pub enum Speed { Bpm(usize), } -#[derive(Debug, Clone, Copy)] -pub enum Volume { - Set(f32), - Slide(Option), - Bump { up: bool, volume: Option }, -} - #[derive(Debug, Clone, Copy)] pub enum Porta { Tone(Option), @@ -33,6 +26,13 @@ pub enum Porta { }, } +#[derive(Debug, Clone, Copy)] +pub enum Volume { + Set(f32), + Slide(Option), + Bump { up: bool, volume: Option }, +} + #[derive(Debug, Default, Clone, Copy)] pub enum PlaybackDirection { #[default] @@ -69,43 +69,39 @@ impl PatternEffect { pub fn memory_key(&self) -> Option { match self { PatternEffect::Porta(Porta::Tone(_)) => Some(PatternEffectMemoryKey::PortaTone), - PatternEffect::Porta(Porta::Slide { - up: true, - finetune: _, - }) => Some(PatternEffectMemoryKey::PortaSlideUp), - PatternEffect::Porta(Porta::Slide { - up: false, - finetune: _, - }) => Some(PatternEffectMemoryKey::PortaSlideDown), + PatternEffect::Porta(Porta::Slide { up: true, .. }) => { + Some(PatternEffectMemoryKey::PortaSlideUp) + } + PatternEffect::Porta(Porta::Slide { up: false, .. }) => { + Some(PatternEffectMemoryKey::PortaSlideDown) + } PatternEffect::Porta(Porta::Bump { up: true, small: false, - finetune: _, + .. }) => Some(PatternEffectMemoryKey::PortaBumpUp), PatternEffect::Porta(Porta::Bump { up: false, small: false, - finetune: _, + .. }) => Some(PatternEffectMemoryKey::PortaBumpDown), PatternEffect::Porta(Porta::Bump { up: true, small: true, - finetune: _, + .. }) => Some(PatternEffectMemoryKey::PortaBumpSmallUp), PatternEffect::Porta(Porta::Bump { up: false, small: true, - finetune: _, + .. }) => Some(PatternEffectMemoryKey::PortaBumpSmallDown), PatternEffect::Volume(Volume::Slide(_)) => Some(PatternEffectMemoryKey::VolumeSlide), - PatternEffect::Volume(Volume::Bump { - up: true, - volume: _, - }) => Some(PatternEffectMemoryKey::VolumeBumpUp), - PatternEffect::Volume(Volume::Bump { - up: down, - volume: _, - }) => Some(PatternEffectMemoryKey::VolumeBumpDown), + PatternEffect::Volume(Volume::Bump { up: true, .. }) => { + Some(PatternEffectMemoryKey::VolumeBumpUp) + } + PatternEffect::Volume(Volume::Bump { up: down, .. }) => { + Some(PatternEffectMemoryKey::VolumeBumpDown) + } PatternEffect::SampleOffset(_) => Some(PatternEffectMemoryKey::SampleOffset), _ => None, } @@ -118,7 +114,12 @@ impl PatternEffect { pub fn is_empty(&self) -> bool { matches!( self, - PatternEffect::Volume(Volume::Slide(None)) | PatternEffect::SampleOffset(None) + PatternEffect::Porta(Porta::Tone(None)) + | PatternEffect::Porta(Porta::Slide { finetune: None, .. }) + | PatternEffect::Porta(Porta::Bump { finetune: None, .. }) + | PatternEffect::Volume(Volume::Slide(None)) + | PatternEffect::Volume(Volume::Bump { volume: None, .. }) + | PatternEffect::SampleOffset(None) ) } } @@ -155,17 +156,17 @@ impl AssetParser for Option { 0x16 => PatternEffect::Porta(Porta::Bump { up: false, small: false, - finetune: (value != 0).then_some(FineTune::new(8 * value as i32)), + finetune: (value != 0).then_some(-FineTune::new(8 * value as i32)), }), 0x24 => PatternEffect::Porta(Porta::Bump { up: true, small: true, - finetune: (value != 0).then_some(FineTune::new(2 * value as i32)), + finetune: (value != 0).then_some(FineTune::new(2 * (value & 0xF) as i32)), }), 0x25 => PatternEffect::Porta(Porta::Bump { up: false, small: true, - finetune: (value != 0).then_some(FineTune::new(2 * value as i32)), + finetune: (value != 0).then_some(-FineTune::new(2 * (value & 0xF) as i32)), }), 0x09 => { PatternEffect::SampleOffset((value != 0).then_some(value as usize * 256)) diff --git a/engine/src/asset/sound/dat/pattern_event.rs b/engine/src/asset/sound/dat/pattern_event.rs index 3ef77f2..066c85b 100644 --- a/engine/src/asset/sound/dat/pattern_event.rs +++ b/engine/src/asset/sound/dat/pattern_event.rs @@ -105,7 +105,7 @@ impl AssetParser for Option { } } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum PatternEventVolume { Sample, Value(f32), diff --git a/engine/src/asset/sound/mod.rs b/engine/src/asset/sound/mod.rs index 7e372bb..c6dc6c9 100644 --- a/engine/src/asset/sound/mod.rs +++ b/engine/src/asset/sound/mod.rs @@ -89,14 +89,13 @@ mod tests { Sound::Song(s) => Some(s), Sound::Effect(_) => None, }) - .collect::>()[0x9]; - let effects = test_music - .orders - .iter() - .flat_map(|p| p.iter().flat_map(|p| p.iter().flat_map(|p| p.effects))) - .flatten() - .collect::>(); - //dbg!(effects); + .collect::>()[0x1]; + // let effects = test_music + // .orders + // .iter() + // .flat_map(|p| p.iter().flat_map(|p| p.iter().flat_map(|p| p.effects))) + // .flatten() + // .collect::>(); sounds .iter() diff --git a/engine/src/utils/iterator.rs b/engine/src/utils/iterator.rs index e25531b..dd032ab 100644 --- a/engine/src/utils/iterator.rs +++ b/engine/src/utils/iterator.rs @@ -1,23 +1,23 @@ use std::mem::{self, MaybeUninit}; +use itertools::Itertools; + // Code from [this post](https://www.reddit.com/r/learnrust/comments/lfw6uy/comment/gn16m4o) pub trait CollectArray: Sized + Iterator { fn collect_array(self) -> [Self::Item; N] { + // TODO(nenikitov): Replace with compile-time assertions or const generic expressions + // When it will be supported. assert!(N > 0 && mem::size_of::() > 0); - let mut array = MaybeUninit::uninit(); - let array_ptr = array.as_mut_ptr() as *mut Self::Item; - let mut i = 0; - unsafe { - for item in self { - assert!(i < N); - array_ptr.add(i).write(item); - i += 1; - } - assert!(i == N); - array.assume_init() - } + let mut array = MaybeUninit::<[Self::Item; N]>::uninit().transpose(); + + Itertools::zip_eq(array.iter_mut(), self).for_each(|(dest, item)| _ = dest.write(item)); + + // SAFETY: Every single element in the array is initialized + // because we wrote a valid iterator element into it. + unsafe { array.transpose().assume_init() } } } impl CollectArray for T where T: Iterator {} +