diff --git a/src/audio_processing/playback.rs b/src/audio_processing/playback.rs index 4c80945..bba8750 100644 --- a/src/audio_processing/playback.rs +++ b/src/audio_processing/playback.rs @@ -237,7 +237,7 @@ impl Iterator for PlaybackIter<'_, '_, INTERPOLATION> { } // add volume and panning let channel_vol = scale_channel_vol(vol); - if let Pan::Value(pan) = pan { + if let Pan::Value(pan) = pan.get() { let angle = scale_pan(pan); out.pan_constant_power(angle); } diff --git a/src/file/header.rs b/src/file/header.rs index c0631ba..cc8b48f 100644 --- a/src/file/header.rs +++ b/src/file/header.rs @@ -2,7 +2,7 @@ use alloc::{boxed::Box, vec::Vec}; use crate::{ file::{SansIO, SansIOWritten, err}, - project::song::{PackedPatternOrder, Pan, PatternOrder}, + project::song::{PackedPan, PackedPatternOrder, Pan, PatternOrder}, }; use core::{ ffi::CStr, @@ -13,23 +13,6 @@ use core::mem; use crate::file::InFilePtr; -fn pan_try_from(value: u8) -> Option { - match value { - 100 => Some(Pan::Surround), - 128 => Some(Pan::Disabled), - 0..=64 => Some(Pan::Value(value)), - _ => None, - } -} - -fn pan_into(pan: Pan) -> u8 { - match pan { - Pan::Value(v) => v, - Pan::Surround => 100, - Pan::Disabled => 128, - } -} - #[derive(Debug, PartialEq, Eq)] pub struct ImpulseHeader { // C-String @@ -50,7 +33,7 @@ pub struct ImpulseHeader { pub message_length: u16, pub message_offset: u32, - pub channel_pan: [Pan; 64], + pub channel_pan: [PackedPan; 64], pub channel_volume: [u8; 64], // file_size: 1 @@ -149,7 +132,7 @@ impl ImpulseHeader { base[0x40..0x80] .iter_mut() .zip(self.channel_pan) - .for_each(|(base, pan)| *base = pan_into(pan)); + .for_each(|(base, pan)| *base = pan.0); *base[0x80..0xC0].first_chunk_mut().unwrap() = self.channel_volume; @@ -253,12 +236,12 @@ impl ImpulseHeader { // can unwrap here, because the length is const let pan_vals: [u8; 64] = base[0x40..0x80].try_into().unwrap(); - let channel_pan: [Pan; 64] = pan_vals.map(|pan| match pan_try_from(pan) { - Some(pan) => pan, - None => { - // defect_handler(LoadDefect::OutOfBoundsValue); - Pan::default() - } + let channel_pan: [PackedPan; 64] = pan_vals.map(|pan| match pan { + PackedPan::DISABLED => Pan::Disabled.into(), + PackedPan::SURROUND => Pan::Surround.into(), + 0..=PackedPan::MAX_VALID => Pan::Value(pan).into(), + // report err + _ => Pan::default().into(), }); let channel_volume: [u8; 64] = { @@ -322,7 +305,7 @@ pub struct HeaderParser { message_length: u16, message_offset: u32, - channel_pan: [Pan; 64], + channel_pan: [PackedPan; 64], channel_volume: [u8; 64], order_num: usize, diff --git a/src/project/song.rs b/src/project/song.rs index 83973e1..e04c0d6 100644 --- a/src/project/song.rs +++ b/src/project/song.rs @@ -20,7 +20,8 @@ pub struct Song { pub patterns: [Pattern; Song::MAX_PATTERNS], pub pattern_order: [PackedPatternOrder; Self::MAX_ORDERS], pub volume: [u8; Self::MAX_CHANNELS], - pub pan: [Pan; Self::MAX_CHANNELS], + pub pan: [PackedPan; Self::MAX_CHANNELS], + // maybe move to struct of arrays or similar pub samples: [Option<(SampleMetaData, Sample)>; Self::MAX_SAMPLES_INSTR], } @@ -90,7 +91,8 @@ impl Default for Song { // please const traits i need you pattern_order: array::from_fn(|_| PackedPatternOrder::default()), volume: [64; _], - pan: array::from_fn(|_| Pan::default()), + // please const traits + pan: array::from_fn(|_| PackedPan::default()), samples: array::from_fn(|_| None), } } @@ -114,7 +116,7 @@ pub enum SongOperation { #[derive(Clone, Debug)] pub(crate) enum ValidOperation { SetVolume(u8, u8), - SetPan(u8, Pan), + SetPan(u8, PackedPan), SetSample(u8, SampleMetaData, Sample), RemoveSample(u8), PatternOperation(u8, PatternOperation), @@ -132,14 +134,20 @@ impl ValidOperation { ) -> Result { let valid = match op { SongOperation::SetVolume(c, _) => usize::from(c) < Song::MAX_CHANNELS, - SongOperation::SetPan(c, _) => usize::from(c) < Song::MAX_CHANNELS, + SongOperation::SetPan(c, pan) => { + usize::from(c) < Song::MAX_CHANNELS + && !matches!(pan, Pan::Value(v) if v > PackedPan::MAX_VALID) + } SongOperation::SetSample(idx, _, _) => usize::from(idx) < Song::MAX_SAMPLES_INSTR, SongOperation::RemoveSample(idx) => usize::from(idx) < Song::MAX_SAMPLES_INSTR, SongOperation::PatternOperation(idx, op) => match song.patterns.get(usize::from(idx)) { Some(pattern) => pattern.operation_is_valid(&op), None => false, }, - SongOperation::SetOrder(idx, _) => usize::from(idx) < Song::MAX_ORDERS, + SongOperation::SetOrder(idx, order) => { + usize::from(idx) < Song::MAX_ORDERS + && !matches!(order, PatternOrder::Number(n) if n > PackedPatternOrder::MAX_VALID) + } SongOperation::SetInitialSpeed(_) => true, SongOperation::SetInitialTempo(_) => true, SongOperation::SetGlobalVol(_) => true, @@ -148,7 +156,7 @@ impl ValidOperation { if valid { Ok(match op { SongOperation::SetVolume(c, v) => Self::SetVolume(c, v), - SongOperation::SetPan(c, pan) => Self::SetPan(c, pan), + SongOperation::SetPan(c, pan) => Self::SetPan(c, pan.into()), SongOperation::SetSample(i, meta_data, sample) => { handle.add_sample(sample.clone()); Self::SetSample(i, meta_data, sample) @@ -193,6 +201,8 @@ impl simple_left_right::Absorb for Song { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Pan { /// Value ranges from 0 to 64, with 32 being center + // please pattern types + // find a way to actually enforce the constraints, so that conversion to and from Packed can be unchecked Value(u8), Surround, Disabled, @@ -204,9 +214,58 @@ impl Default for Pan { } } +#[derive(Clone, Copy, PartialEq, Eq)] +// always a valid pan +pub struct PackedPan(pub(crate) u8); + +impl PackedPan { + pub(crate) const SURROUND: u8 = 100; + pub(crate) const DISABLED: u8 = 128; + pub(crate) const MAX_VALID: u8 = 64; + #[inline] + pub fn get(self) -> Pan { + match self.0 { + Self::SURROUND => Pan::Surround, + Self::DISABLED => Pan::Disabled, + 0..=Self::MAX_VALID => Pan::Value(self.0), + // Pan value currently public + _ => unreachable!(), + } + } +} + +impl From for PackedPan { + fn from(value: Pan) -> Self { + match value { + Pan::Value(v) => { + // pan value currently public + assert!(v <= Self::MAX_VALID); + Self(v) + } + Pan::Surround => Self(Self::SURROUND), + Pan::Disabled => Self(Self::DISABLED), + } + } +} + +impl Default for PackedPan { + fn default() -> Self { + Pan::default().into() + } +} + +impl Debug for PackedPan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Packed") + .field("value", &self.get()) + .finish() + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum PatternOrder { // please pattern types i need you + // somehow enforce that this stays below 200 Number(u8), #[default] EndOfSong, @@ -227,7 +286,7 @@ impl PackedPatternOrder { Self::END_OF_SONG => PatternOrder::EndOfSong, Self::SKIP_ORDER => PatternOrder::SkipOrder, 0..=Self::MAX_VALID => PatternOrder::Number(self.0), - // could maybe be unchecked + // value is currently public _ => unreachable!(), } } @@ -236,7 +295,10 @@ impl PackedPatternOrder { impl From for PackedPatternOrder { fn from(value: PatternOrder) -> Self { match value { - PatternOrder::Number(n) => Self(n), + PatternOrder::Number(n) => { + assert!(n <= Self::MAX_VALID); + Self(n) + } PatternOrder::EndOfSong => Self(Self::END_OF_SONG), PatternOrder::SkipOrder => Self(Self::SKIP_ORDER), } @@ -245,14 +307,13 @@ impl From for PackedPatternOrder { impl Default for PackedPatternOrder { fn default() -> Self { - // synced with PatternOrder - Self(Self::END_OF_SONG) + PatternOrder::default().into() } } impl Debug for PackedPatternOrder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PackedPatternOrder") + f.debug_struct("Packed") .field("value", &self.get()) .finish() }