diff --git a/midi2/src/channel_voice1/note_off.rs b/midi2/src/channel_voice1/note_off.rs index d8515cc..be463a8 100644 --- a/midi2/src/channel_voice1/note_off.rs +++ b/midi2/src/channel_voice1/note_off.rs @@ -37,11 +37,72 @@ struct NoteOff { velocity: crate::ux::u7, } +/// Converts a CV2 Note Off message to CV1 Note Off message, +/// storing the result in a pre-allocated CV1 Note Off. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + crate::buffer::BufferMut, + > From<(crate::channel_voice2::NoteOff, NoteOff)> for NoteOff +{ + fn from(val: (crate::channel_voice2::NoteOff, NoteOff)) -> Self { + use crate::conversion::MinCenterMax; + use crate::traits::{Channeled, Grouped}; + + let (src, mut dest) = val; + dest.set_group(src.group()); + dest.set_channel(src.channel()); + dest.set_note_number(src.note_number()); + dest.set_velocity(src.velocity().mcm_downscale::()); + dest + } +} + +/// Converts a CV2 Note Off message to a CV1 Note Off message. +/// This is only infallible for resizable buffers. +/// For fixed size buffers, see TryFromCv2. +/// +/// Note: Due to 0 velocity Note Off messages being considered +/// a Note Off in CV1 but not in CV2, a 0 velocity CV2 message +/// will be converted to a 1 velocity CV1 message. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferResize, + > crate::FromCv2> for NoteOff +{ + fn from_cv2(val: crate::channel_voice2::NoteOff) -> Self { + let dest = NoteOff::::new(); + (val, dest).into() + } +} + +/// Tries to Convert a CV2 Note Off message to a CV1 Note Off message. +/// Fails if the underlying buffer doesn't have enough room for a new Note Off message. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferTryResize, + > crate::TryFromCv2> for NoteOff +{ + type Error = crate::error::BufferOverflow; + fn try_from_cv2(val: crate::channel_voice2::NoteOff) -> Result { + let dest = NoteOff::::try_new()?; + Ok((val, dest).into()) + } +} + #[cfg(test)] mod tests { use super::*; use crate::{ - traits::{Channeled, Grouped}, + traits::{Channeled, Grouped, IntoCv1, TryIntoCv1}, ux::*, }; use pretty_assertions::assert_eq; @@ -91,4 +152,71 @@ mod tests { u7::new(0x1B), ); } + + #[test] + fn from_midi_2_with_dest() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = crate::channel_voice2::NoteOff::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOff::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21 = NoteOff::<[u32; 4]>::new(); + let message21: NoteOff<[u32; 4]> = (message2, message21).into(); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2() { + use crate::traits::{Channeled, Grouped}; + use std::vec::Vec; + + let mut message2 = crate::channel_voice2::NoteOff::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOff::>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21: NoteOff> = message2.into_cv1(); + + assert_eq!(message21, message1); + } + + #[test] + fn try_from_midi_2() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = crate::channel_voice2::NoteOff::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOff::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21: NoteOff<[u32; 4]> = message2 + .try_into_cv1() + .expect("Conversion should not fail."); + + assert_eq!(message21, message1); + } } diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 1ddb82c..9f87b39 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -37,11 +37,86 @@ struct NoteOn { velocity: crate::ux::u7, } +/// Converts a CV2 Note On message to CV1 Note On message, +/// storing the result in a pre-allocated CV1 Note On. +/// +/// Note: Due to 0 velocity Note On messages being considered +/// a Note Off in CV1 but not in CV2, a 0 velocity CV2 message +/// will be converted to a 1 velocity CV1 message. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + crate::buffer::BufferMut, + > From<(crate::channel_voice2::NoteOn, NoteOn)> for NoteOn +{ + fn from(val: (crate::channel_voice2::NoteOn, NoteOn)) -> Self { + use crate::conversion::MinCenterMax; + use crate::traits::{Channeled, Grouped}; + + let (src, mut dest) = val; + dest.set_group(src.group()); + dest.set_channel(src.channel()); + dest.set_note_number(src.note_number()); + match src.velocity() { + // Since 0 velocity doesn't trigger a note off in CV2 like in CV1, + // we need to convert 0 velocity in CV2 to 1 velocity in CV1. + // See MIDI 2.0 spec 7.4.2: MIDI 2.0 Note On Message -> Velocity + // for details. + 0 => dest.set_velocity(ux::u7::new(0x01)), + _ => dest.set_velocity(src.velocity().mcm_downscale::()), + } + dest + } +} + +/// Tries to Convert a CV2 Note On message to a CV1 Note On message. +/// +/// Note: Due to 0 velocity Note On messages being considered +/// a Note Off in CV1 but not in CV2, a 0 velocity CV2 message +/// will be converted to a 1 velocity CV1 message. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferTryResize, + > crate::TryFromCv2> for NoteOn +{ + type Error = crate::error::BufferOverflow; + fn try_from_cv2(val: crate::channel_voice2::NoteOn) -> Result { + let dest = NoteOn::::try_new()?; + Ok((val, dest).into()) + } +} + +/// Converts a CV2 Note On message to a CV1 Note On message. +/// This is only infallible for resizable buffers. +/// For fixed size buffers, see the Into impl for (CV2, CV1). +/// +/// Note: Due to 0 velocity Note On messages being considered +/// a Note Off in CV1 but not in CV2, a 0 velocity CV2 message +/// will be converted to a 1 velocity CV1 message. +#[cfg(feature = "channel-voice2")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferResize, + > crate::FromCv2> for NoteOn +{ + fn from_cv2(val: crate::channel_voice2::NoteOn) -> Self { + let dest = NoteOn::::new(); + (val, dest).into() + } +} + #[cfg(test)] mod tests { use super::*; use crate::{ - traits::{Channeled, Grouped}, + traits::{Channeled, Grouped, IntoCv1, TryIntoCv1}, ux::*, }; use pretty_assertions::assert_eq; @@ -103,4 +178,115 @@ mod tests { u7::new(0x3D), ); } + + #[test] + fn from_midi_2() { + use crate::traits::{Channeled, Grouped}; + use std::vec::Vec; + + let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOn::>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21: NoteOn> = message2.into_cv1(); + + assert_eq!(message21, message1); + } + + #[test] + fn try_from_midi_2() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21: NoteOn<[u32; 4]> = message2 + .try_into_cv1() + .expect("Conversion should not fail."); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2_zero_velocity() { + use crate::traits::{Channeled, Grouped}; + use std::vec::Vec; + + let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x0000); + + let mut message1 = NoteOn::>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x01)); + + let message21: NoteOn> = message2.into_cv1(); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2_with_dest() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message21 = NoteOn::<[u32; 4]>::new(); + let message21: NoteOn<[u32; 4]> = (message2, message21).into(); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2_zero_velocity_with_dest() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x0000); + + let mut message1 = NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x01)); + + let message21 = NoteOn::<[u32; 4]>::new(); + let message21: NoteOn<[u32; 4]> = (message2, message21).into(); + + assert_eq!(message21, message1); + } } diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index de795da..1085948 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -30,6 +30,72 @@ struct NoteOn { attribute: Option, } +/// Tries to convert a CV1 Note On message to CV2 Note On message, +/// storing the result in a pre-allocated CV2 Note On. +/// +/// Will fail if the CV1 Note On has 0 Velocity, as it must be converted +/// to a CV2 Note Off message instead. +#[cfg(feature = "channel-voice1")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + crate::buffer::BufferMut, + > TryFrom<(crate::channel_voice1::NoteOn, NoteOn)> for NoteOn +{ + type Error = crate::error::InvalidData; + + fn try_from(val: (crate::channel_voice1::NoteOn, NoteOn)) -> Result { + use crate::conversion::MinCenterMax; + use crate::error::InvalidData; + use crate::traits::{Channeled, Grouped}; + + let (src, mut dest) = val; + if src.velocity() == ux::u7::new(0) { + Err(InvalidData("CV1 Note On messages with 0 veolicty should be converted to CV2 Note Off messages.")) + } else { + dest.set_group(src.group()); + dest.set_channel(src.channel()); + dest.set_note_number(src.note_number()); + dest.set_velocity(src.velocity().mcm_upscale::()); + Ok(dest) + } + } +} + +/// Tries to convert a CV1 Note On message to CV2 Note On message. +/// +/// Will fail if the CV1 Note On has 0 Velocity, as it must be converted +/// to a CV2 Note Off message instead. +/// +/// Will also fail if there is not enough room in the destination buffer to +/// allocate a new CV2 NoteOn. +/// +/// Will only attempt to allocate a new CV2 Note On if the given +/// CV1 NoteOn has a non-zero velocity. +#[cfg(feature = "channel-voice1")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferTryResize, + > crate::TryFromCv1> for NoteOn +{ + type Error = crate::error::Error; + + fn try_from_cv1(val: crate::channel_voice1::NoteOn) -> Result { + use crate::error::InvalidData; + + if val.velocity() == ux::u7::new(0) { + Err(Self::Error::InvalidData(InvalidData("CV1 Note On messages with 0 veolicty should be converted to CV2 Note Off messages."))) + } else { + let dest = NoteOn::::try_new()?; + Ok((val, dest) + .try_into() + .expect("Conversion should not fail. We already checked for 0 velocity.")) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -96,4 +162,67 @@ mod tests { Some(Attribute::Pitch7_9(Fixed7_9::from_bits(0b1110100110001010))), ); } + + #[test] + fn try_from_midi_2() { + use crate::traits::{Channeled, Grouped, TryIntoCv2}; + + let mut message2 = NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = crate::channel_voice1::NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message12: NoteOn<[u32; 4]> = message1 + .try_into_cv2() + .expect("Conversion should not fail."); + + assert_eq!(message12, message2); + } + + #[test] + fn try_from_midi_2_with_dest() { + use crate::traits::{Channeled, Grouped}; + + let mut message2 = NoteOn::<[u32; 4]>::new(); + message2.set_group(u4::new(0x8)); + message2.set_channel(u4::new(0x8)); + message2.set_note_number(u7::new(0x5E)); + message2.set_velocity(0x8000); + + let mut message1 = crate::channel_voice1::NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x40)); + + let message12: NoteOn<[u32; 4]> = (message1, NoteOn::<[u32; 4]>::new()) + .try_into() + .expect("Message should convert."); + + assert_eq!(message12, message2); + } + + #[test] + fn try_from_midi_2_with_dest_zero_velocity() { + use crate::error::InvalidData; + use crate::traits::{Channeled, Grouped}; + + let mut message1 = crate::channel_voice1::NoteOn::<[u32; 4]>::new(); + message1.set_group(u4::new(0x8)); + message1.set_channel(u4::new(0x8)); + message1.set_note_number(u7::new(0x5E)); + message1.set_velocity(u7::new(0x00)); + + let message12: Result, InvalidData> = + (message1, NoteOn::<[u32; 4]>::new()).try_into(); + + assert_eq!(message12, Err(InvalidData("CV1 Note On messages with 0 veolicty should be converted to CV2 Note Off messages."))); + } } diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs new file mode 100644 index 0000000..aacc29a --- /dev/null +++ b/midi2/src/conversion.rs @@ -0,0 +1,634 @@ +pub(crate) trait Center: + Into + TryFrom + Sized + Copy + PartialEq + PartialOrd +where + >::Error: core::fmt::Debug, +{ + const MIN: Self; + const MAX: Self; + + #[allow(dead_code)] + fn center_value() -> Self { + let min: u32 = Self::MIN.into(); + let max: u32 = Self::MAX.into(); + + ((max - min) / 2_u32 + 1) + .try_into() + .expect("Center shouldn't be larger than max.") + } +} + +impl Center for ux::u7 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +impl Center for ux::u9 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +impl Center for ux::u14 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +impl Center for u16 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +impl Center for ux::u25 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +impl Center for u32 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} + +pub(crate) trait MinCenterMax: Center + core::ops::Shr +where + >::Output: Into, + >::Error: core::fmt::Debug, +{ + #[allow(dead_code)] + fn mcm_upscale< + U: Center + + core::ops::Add + + core::ops::BitAnd + + core::fmt::Debug + + core::ops::BitOr + + core::ops::Shr + + core::ops::Shl, + >( + self, + ) -> U + where + Self: Into, + >::Error: core::fmt::Debug, + { + let min = Self::MIN; + let center = Self::center_value(); + let max = Self::MAX; + + match self { + s if s == min => U::MIN, + s if s == max => U::MAX, + s if s == center => U::center_value(), + + s if (min..center).contains(&s) => { + let self_max: u32 = Self::MAX.into(); + let other_max: u32 = U::MAX.into(); + let shift = (other_max - self_max).count_ones(); + let other: U = self.into(); + other << shift + } + + s if (center..max).contains(&s) => { + let self_max: u32 = Self::MAX.into(); + let other_max: u32 = U::MAX.into(); + let self_bits = self_max.count_ones(); + let other_bits = other_max.count_ones(); + let shift = self_bits - 1; + + let initial: U = self.into(); + let initial_mask = U::MAX >> (other_bits - self_bits + 1); + let repeating = initial & initial_mask; + + let mut mcm_upscaled = initial; + let mut remainder = other_bits - self_bits; + + while remainder > 0 { + remainder = match remainder { + remainder if remainder > shift => { + mcm_upscaled = (mcm_upscaled << shift) | repeating; + remainder - shift + } + _ => { + let shift = shift - remainder; + mcm_upscaled = (mcm_upscaled << remainder) | (repeating >> shift); + 0 + } + }; + } + mcm_upscaled + } + + _ => self.into(), + } + } + + fn mcm_downscale>(self) -> U + where + >::Error: core::fmt::Debug, + >::Error: core::fmt::Debug, + { + let self_max: u32 = Self::MAX.into(); + let other_max: u32 = U::MAX.into(); + let shift = (self_max - other_max).count_ones(); + let mcm_downscaled = self >> shift; + let mcm_downscaled: Self = mcm_downscaled.into(); + + mcm_downscaled + .try_into() + .expect("Downscaling should not fail.") + } +} + +#[allow(dead_code)] +pub(crate) trait ZeroExtensionScaling: Center +where + Self: core::ops::Add, + >::Error: core::fmt::Debug, +{ + fn ze_upscale(&self) -> U + where + >::Error: core::fmt::Debug, + >::Error: core::fmt::Debug, + { + let self_u32: u32 = (*self).into(); + let self_max: u32 = Self::MAX.into(); + let other_max: u32 = U::MAX.into(); + let shift = (other_max - self_max).count_ones(); + let ze_upscaled = self_u32 << shift; + + ze_upscaled.try_into().expect("Upscaling should not fail.") + } + + fn ze_downscale>(self) -> U + where + Self: core::fmt::Debug, + Self: core::ops::Shr, + Self: core::ops::Add, + >::Error: core::fmt::Debug, + >::Error: core::fmt::Debug, + { + let min = Self::MIN; + let center = Self::center_value(); + let max = Self::MAX; + + match self { + s if s == min => U::MIN, + s if s == max => U::MAX, + s if s == center => U::center_value(), + _ => { + let self_u64: u64 = self.into().into(); + let self_max: u64 = Self::MAX.into().into(); + let other_max: u64 = U::MAX.into().into(); + let shift: u32 = (self_max - other_max).count_ones(); + let half_scale_range: u64 = (1_u32 << (shift - 1_u32)).into(); + let shifted = (self_u64 + half_scale_range) >> shift; + + match TryInto::::try_into(shifted) { + Ok(ds) => match TryInto::::try_into(ds) { + Ok(ds) => ds, + _ => U::MAX, + }, + _ => U::MAX, + } + } + } + } +} + +impl> MinCenterMax for U +where + >::Error: core::fmt::Debug, + >::Output: Into, +{ +} + +impl + core::ops::Add> ZeroExtensionScaling for U +where + >::Error: core::fmt::Debug, + >::Output: Into, +{ +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mcm_mins_upscaling() { + let min_u7 = ux::u7::new(0); + let min_u9 = ux::u9::new(0); + let min_u14 = ux::u14::new(0); + let min_u16 = 0_u16; + let min_u32 = 0_u32; + + assert_eq!(min_u7.mcm_upscale::(), min_u7); + assert_eq!(min_u7.mcm_upscale::(), min_u9); + assert_eq!(min_u7.mcm_upscale::(), min_u14); + assert_eq!(min_u7.mcm_upscale::(), min_u16); + assert_eq!(min_u7.mcm_upscale::(), min_u32); + + assert_eq!(min_u9.mcm_upscale::(), min_u9); + assert_eq!(min_u9.mcm_upscale::(), min_u14); + assert_eq!(min_u9.mcm_upscale::(), min_u16); + assert_eq!(min_u9.mcm_upscale::(), min_u32); + + assert_eq!(min_u14.mcm_upscale::(), min_u14); + assert_eq!(min_u14.mcm_upscale::(), min_u16); + assert_eq!(min_u14.mcm_upscale::(), min_u32); + + assert_eq!(min_u16.mcm_upscale::(), min_u16); + assert_eq!(min_u16.mcm_upscale::(), min_u32); + + assert_eq!(min_u32.mcm_upscale::(), min_u32); + } + + #[test] + fn test_mcm_maxs_upscaling() { + let max_u7 = ux::u7::new(127); + let max_u9 = ux::u9::new(511); + let max_u14 = ux::u14::new(16383); + let max_u16 = 0xFFFF_u16; + let max_u32 = 0xFFFFFFFF_u32; + + assert_eq!(max_u7.mcm_upscale::(), max_u7); + assert_eq!(max_u7.mcm_upscale::(), max_u9); + assert_eq!(max_u7.mcm_upscale::(), max_u14); + assert_eq!(max_u7.mcm_upscale::(), max_u16); + assert_eq!(max_u7.mcm_upscale::(), max_u32); + + assert_eq!(max_u9.mcm_upscale::(), max_u9); + assert_eq!(max_u9.mcm_upscale::(), max_u14); + assert_eq!(max_u9.mcm_upscale::(), max_u16); + assert_eq!(max_u9.mcm_upscale::(), max_u32); + + assert_eq!(max_u14.mcm_upscale::(), max_u14); + assert_eq!(max_u14.mcm_upscale::(), max_u16); + assert_eq!(max_u14.mcm_upscale::(), max_u32); + + assert_eq!(max_u16.mcm_upscale::(), max_u16); + assert_eq!(max_u16.mcm_upscale::(), max_u32); + + assert_eq!(max_u32.mcm_upscale::(), max_u32); + } + + #[test] + fn test_mcm_centers_upscaling() { + let center_u7 = ux::u7::new(0x40); + let center_u9 = ux::u9::new(256); + let center_u14 = ux::u14::new(8192); + let center_u16 = 0x8000_u16; + let center_u32 = 0x80000000_u32; + + assert_eq!(center_u7.mcm_upscale::(), center_u7); + assert_eq!(center_u7.mcm_upscale::(), center_u9); + assert_eq!(center_u7.mcm_upscale::(), center_u14); + assert_eq!(center_u7.mcm_upscale::(), center_u16); + assert_eq!(center_u7.mcm_upscale::(), center_u32); + + assert_eq!(center_u9.mcm_upscale::(), center_u9); + assert_eq!(center_u9.mcm_upscale::(), center_u14); + assert_eq!(center_u9.mcm_upscale::(), center_u16); + assert_eq!(center_u9.mcm_upscale::(), center_u32); + + assert_eq!(center_u14.mcm_upscale::(), center_u14); + assert_eq!(center_u14.mcm_upscale::(), center_u16); + assert_eq!(center_u14.mcm_upscale::(), center_u32); + + assert_eq!(center_u16.mcm_upscale::(), center_u16); + assert_eq!(center_u16.mcm_upscale::(), center_u32); + + assert_eq!(center_u32.mcm_upscale::(), center_u32); + } + + #[test] + fn test_mcm_lower_range_upscaling() { + let lower_u7 = ux::u7::new(0x1F); + let lower_u9 = ux::u9::new(0x7C); + let lower_u14 = ux::u14::new(0xF80); + let lower_u16 = 0x3E00_u16; + let lower_u32 = 0x3E00_0000_u32; + + assert_eq!(lower_u7.mcm_upscale::(), lower_u7); + assert_eq!(lower_u7.mcm_upscale::(), lower_u9); + assert_eq!(lower_u7.mcm_upscale::(), lower_u14); + assert_eq!(lower_u7.mcm_upscale::(), lower_u16); + assert_eq!(lower_u7.mcm_upscale::(), lower_u32); + + assert_eq!(lower_u9.mcm_upscale::(), lower_u9); + assert_eq!(lower_u9.mcm_upscale::(), lower_u14); + assert_eq!(lower_u9.mcm_upscale::(), lower_u16); + assert_eq!(lower_u9.mcm_upscale::(), lower_u32); + + assert_eq!(lower_u14.mcm_upscale::(), lower_u14); + assert_eq!(lower_u14.mcm_upscale::(), lower_u16); + assert_eq!(lower_u14.mcm_upscale::(), lower_u32); + + assert_eq!(lower_u16.mcm_upscale::(), lower_u16); + assert_eq!(lower_u16.mcm_upscale::(), lower_u32); + + assert_eq!(lower_u32.mcm_upscale::(), lower_u32); + } + + #[test] + fn test_mcm_upper_range_upscaling() { + // Test cases from MIDI 2.0 Bit Scaling and Resolution v1.0.2 + let upper_u7_to_u16 = [ + (ux::u7::new(0x46).mcm_upscale::(), 0x8C30_u16), + (ux::u7::new(0x60).mcm_upscale::(), 0xC104_u16), + (ux::u7::new(0x78).mcm_upscale::(), 0xF1C7_u16), + ]; + + let upper_u7_to_u32 = [ + (ux::u7::new(0x46).mcm_upscale::(), 0x8C30C30C_u32), + (ux::u7::new(0x60).mcm_upscale::(), 0xC1041041_u32), + (ux::u7::new(0x78).mcm_upscale::(), 0xF1C71C71_u32), + ]; + + let upper_u16_to_u32 = [ + (0x9C40_u16.mcm_upscale::(), 0x9C403880_u32), + (0xC000_u16.mcm_upscale::(), 0xC0008001_u32), + (0xFDE8_u16.mcm_upscale::(), 0xFDE8FBD1_u32), + ]; + + for (result, expected) in upper_u7_to_u16 { + assert_eq!(result, expected); + } + + for (result, expected) in upper_u7_to_u32 { + assert_eq!(result, expected); + } + + for (result, expected) in upper_u16_to_u32 { + assert_eq!(result, expected); + } + } + + #[test] + fn test_mcm_lower_range_downscaling() { + let lower_u7 = ux::u7::new(31); + let lower_u9 = ux::u9::new(127); + let lower_u14 = ux::u14::new(4095); + let lower_u16 = 0x3FFF_u16; + let lower_u32 = 0x3FFFFFFF_u32; + + assert_eq!(lower_u32.mcm_downscale::(), lower_u7); + assert_eq!(lower_u32.mcm_downscale::(), lower_u9); + assert_eq!(lower_u32.mcm_downscale::(), lower_u14); + assert_eq!(lower_u32.mcm_downscale::(), lower_u16); + assert_eq!(lower_u32.mcm_downscale::(), lower_u32); + + assert_eq!(lower_u16.mcm_downscale::(), lower_u7); + assert_eq!(lower_u16.mcm_downscale::(), lower_u9); + assert_eq!(lower_u16.mcm_downscale::(), lower_u14); + assert_eq!(lower_u16.mcm_downscale::(), lower_u16); + + assert_eq!(lower_u14.mcm_downscale::(), lower_u7); + assert_eq!(lower_u14.mcm_downscale::(), lower_u9); + assert_eq!(lower_u14.mcm_downscale::(), lower_u14); + + assert_eq!(lower_u9.mcm_downscale::(), lower_u7); + assert_eq!(lower_u9.mcm_downscale::(), lower_u9); + + assert_eq!(lower_u7.mcm_downscale::(), lower_u7); + } + + #[test] + fn test_mcm_centers_downscaling() { + let center_u7 = ux::u7::new(0x40); + let center_u9 = ux::u9::new(256); + let center_u14 = ux::u14::new(8192); + let center_u16 = 0x8000_u16; + let center_u32 = 0x80000000_u32; + + assert_eq!(center_u32.mcm_downscale::(), center_u7); + assert_eq!(center_u32.mcm_downscale::(), center_u9); + assert_eq!(center_u32.mcm_downscale::(), center_u14); + assert_eq!(center_u32.mcm_downscale::(), center_u16); + assert_eq!(center_u32.mcm_downscale::(), center_u32); + + assert_eq!(center_u16.mcm_downscale::(), center_u7); + assert_eq!(center_u16.mcm_downscale::(), center_u9); + assert_eq!(center_u16.mcm_downscale::(), center_u14); + assert_eq!(center_u16.mcm_downscale::(), center_u16); + + assert_eq!(center_u14.mcm_downscale::(), center_u7); + assert_eq!(center_u14.mcm_downscale::(), center_u9); + assert_eq!(center_u14.mcm_downscale::(), center_u14); + + assert_eq!(center_u9.mcm_downscale::(), center_u7); + assert_eq!(center_u9.mcm_downscale::(), center_u9); + + assert_eq!(center_u7.mcm_downscale::(), center_u7); + } + + #[test] + fn test_mcm_mins_downscaling() { + let min_u7 = ux::u7::new(0); + let min_u9 = ux::u9::new(0); + let min_u14 = ux::u14::new(0); + let min_u16 = 0_u16; + let min_u32 = 0_u32; + + assert_eq!(min_u32.mcm_downscale::(), min_u7); + assert_eq!(min_u32.mcm_downscale::(), min_u9); + assert_eq!(min_u32.mcm_downscale::(), min_u14); + assert_eq!(min_u32.mcm_downscale::(), min_u16); + assert_eq!(min_u32.mcm_downscale::(), min_u32); + + assert_eq!(min_u16.mcm_downscale::(), min_u7); + assert_eq!(min_u16.mcm_downscale::(), min_u9); + assert_eq!(min_u16.mcm_downscale::(), min_u14); + assert_eq!(min_u16.mcm_downscale::(), min_u16); + + assert_eq!(min_u14.mcm_downscale::(), min_u7); + assert_eq!(min_u14.mcm_downscale::(), min_u9); + assert_eq!(min_u14.mcm_downscale::(), min_u14); + + assert_eq!(min_u9.mcm_downscale::(), min_u7); + assert_eq!(min_u9.mcm_downscale::(), min_u9); + + assert_eq!(min_u7.mcm_downscale::(), min_u7); + } + + #[test] + fn test_mcm_maxs_downscaling() { + let max_u7 = ux::u7::new(127); + let max_u9 = ux::u9::new(511); + let max_u14 = ux::u14::new(16383); + let max_u16 = 0xFFFF_u16; + let max_u32 = 0xFFFFFFFF_u32; + + assert_eq!(max_u32.mcm_downscale::(), max_u7); + assert_eq!(max_u32.mcm_downscale::(), max_u9); + assert_eq!(max_u32.mcm_downscale::(), max_u14); + assert_eq!(max_u32.mcm_downscale::(), max_u16); + assert_eq!(max_u32.mcm_downscale::(), max_u32); + + assert_eq!(max_u16.mcm_downscale::(), max_u7); + assert_eq!(max_u16.mcm_downscale::(), max_u9); + assert_eq!(max_u16.mcm_downscale::(), max_u14); + assert_eq!(max_u16.mcm_downscale::(), max_u16); + + assert_eq!(max_u14.mcm_downscale::(), max_u7); + assert_eq!(max_u14.mcm_downscale::(), max_u9); + assert_eq!(max_u14.mcm_downscale::(), max_u14); + + assert_eq!(max_u9.mcm_downscale::(), max_u7); + assert_eq!(max_u9.mcm_downscale::(), max_u9); + + assert_eq!(max_u7.mcm_downscale::(), max_u7); + } + + #[test] + fn test_mcm_example_downscaling() { + let examples = [ + (5120_u16.mcm_downscale::(), ux::u7::new(10)), + (32768_u16.mcm_downscale::(), ux::u7::new(64)), + (44730_u16.mcm_downscale::(), ux::u7::new(87)), + (65535_u16.mcm_downscale::(), ux::u7::new(127)), + ]; + + for (input, expected) in examples { + assert_eq!(input, expected); + } + } + + #[test] + fn test_ze_centers_downscaling() { + let center_u7 = ux::u7::new(0x40); + let center_u9 = ux::u9::new(256); + let center_u14 = ux::u14::new(8192); + let center_u25 = ux::u25::new(0x0100_0000); + let center_u16 = 0x8000_u16; + let center_u32 = 0x80000000_u32; + + assert_eq!(center_u32.ze_downscale::(), center_u7); + assert_eq!(center_u32.ze_downscale::(), center_u9); + assert_eq!(center_u32.ze_downscale::(), center_u14); + assert_eq!(center_u32.ze_downscale::(), center_u25); + assert_eq!(center_u32.ze_downscale::(), center_u16); + assert_eq!(center_u32.ze_downscale::(), center_u32); + + assert_eq!(center_u25.ze_downscale::(), center_u7); + assert_eq!(center_u25.ze_downscale::(), center_u9); + assert_eq!(center_u25.ze_downscale::(), center_u14); + assert_eq!(center_u25.ze_downscale::(), center_u16); + assert_eq!(center_u25.ze_downscale::(), center_u25); + + assert_eq!(center_u16.ze_downscale::(), center_u7); + assert_eq!(center_u16.ze_downscale::(), center_u9); + assert_eq!(center_u16.ze_downscale::(), center_u14); + assert_eq!(center_u16.ze_downscale::(), center_u16); + + assert_eq!(center_u14.ze_downscale::(), center_u7); + assert_eq!(center_u14.ze_downscale::(), center_u9); + assert_eq!(center_u14.ze_downscale::(), center_u14); + + assert_eq!(center_u9.ze_downscale::(), center_u7); + assert_eq!(center_u9.ze_downscale::(), center_u9); + + assert_eq!(center_u7.ze_downscale::(), center_u7); + } + + #[test] + fn test_ze_mins_downscaling() { + let min_u7 = ux::u7::new(0); + let min_u9 = ux::u9::new(0); + let min_u14 = ux::u14::new(0); + let min_u25 = ux::u25::new(0); + let min_u16 = 0_u16; + let min_u32 = 0_u32; + + assert_eq!(min_u32.ze_downscale::(), min_u7); + assert_eq!(min_u32.ze_downscale::(), min_u9); + assert_eq!(min_u32.ze_downscale::(), min_u14); + assert_eq!(min_u32.ze_downscale::(), min_u25); + assert_eq!(min_u32.ze_downscale::(), min_u16); + assert_eq!(min_u32.ze_downscale::(), min_u32); + + assert_eq!(min_u16.ze_downscale::(), min_u7); + assert_eq!(min_u16.ze_downscale::(), min_u9); + assert_eq!(min_u16.ze_downscale::(), min_u14); + assert_eq!(min_u16.ze_downscale::(), min_u16); + + assert_eq!(min_u14.ze_downscale::(), min_u7); + assert_eq!(min_u14.ze_downscale::(), min_u9); + assert_eq!(min_u14.ze_downscale::(), min_u14); + + assert_eq!(min_u25.ze_downscale::(), min_u7); + assert_eq!(min_u25.ze_downscale::(), min_u9); + assert_eq!(min_u25.ze_downscale::(), min_u14); + assert_eq!(min_u25.ze_downscale::(), min_u16); + assert_eq!(min_u25.ze_downscale::(), min_u25); + + assert_eq!(min_u9.ze_downscale::(), min_u7); + assert_eq!(min_u9.ze_downscale::(), min_u9); + + assert_eq!(min_u7.ze_downscale::(), min_u7); + } + + #[test] + fn test_ze_maxs_downscaling() { + let max_u7 = ux::u7::new(127); + let max_u9 = ux::u9::new(511); + let max_u14 = ux::u14::new(16383); + let max_u25 = ux::u25::new(0x1FFFFFF); + let max_u16 = 0xFFFF_u16; + let max_u32 = 0xFFFFFFFF_u32; + + assert_eq!(max_u32.ze_downscale::(), max_u7); + assert_eq!(max_u32.ze_downscale::(), max_u9); + assert_eq!(max_u32.ze_downscale::(), max_u14); + assert_eq!(max_u32.ze_downscale::(), max_u25); + assert_eq!(max_u32.ze_downscale::(), max_u16); + assert_eq!(max_u32.ze_downscale::(), max_u32); + + assert_eq!(max_u25.ze_downscale::(), max_u7); + assert_eq!(max_u25.ze_downscale::(), max_u9); + assert_eq!(max_u25.ze_downscale::(), max_u14); + assert_eq!(max_u25.ze_downscale::(), max_u16); + assert_eq!(max_u25.ze_downscale::(), max_u25); + + assert_eq!(max_u16.ze_downscale::(), max_u7); + assert_eq!(max_u16.ze_downscale::(), max_u9); + assert_eq!(max_u16.ze_downscale::(), max_u14); + assert_eq!(max_u16.ze_downscale::(), max_u16); + + assert_eq!(max_u14.ze_downscale::(), max_u7); + assert_eq!(max_u14.ze_downscale::(), max_u9); + assert_eq!(max_u14.ze_downscale::(), max_u14); + + assert_eq!(max_u9.ze_downscale::(), max_u7); + assert_eq!(max_u9.ze_downscale::(), max_u9); + + assert_eq!(max_u7.ze_downscale::(), max_u7); + } + + #[test] + fn test_ze_example_downscaling() { + let examples = [ + (5120_u16.ze_downscale::(), ux::u7::new(10)), + (5631_u16.ze_downscale::(), ux::u7::new(11)), + (32768_u16.ze_downscale::(), ux::u7::new(64)), + (44544_u16.ze_downscale::(), ux::u7::new(87)), + (44730_u16.ze_downscale::(), ux::u7::new(87)), + (44800_u16.ze_downscale::(), ux::u7::new(88)), + ]; + + for (input, expected) in examples { + std::dbg!(input, expected); + assert_eq!(input, expected); + } + } + + #[test] + fn test_ze_example_upscaling() { + let examples = [ + (ux::u7::new(10).ze_upscale::(), 5120_u16), + (ux::u7::new(64).ze_upscale::(), 32768_u16), + (ux::u7::new(87).ze_upscale::(), 44544_u16), + (ux::u7::new(127).ze_upscale::(), 65024_u16), + ]; + + for (input, expected) in examples { + assert_eq!(input, expected); + } + } +} diff --git a/midi2/src/lib.rs b/midi2/src/lib.rs index 795c93c..c9af15a 100644 --- a/midi2/src/lib.rs +++ b/midi2/src/lib.rs @@ -11,6 +11,7 @@ pub mod channel_voice1; pub mod channel_voice2; #[cfg(feature = "ci")] pub mod ci; +pub mod conversion; #[cfg(feature = "flex-data")] pub mod flex_data; #[cfg(feature = "sysex7")] diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 348a5f9..a5b3b7c 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -731,3 +731,77 @@ pub(crate) trait BufferAccess { where B: BufferMut; } + +pub trait FromCv1: Sized { + fn from_cv1(other: T) -> Self; +} + +pub trait FromCv2: Sized { + fn from_cv2(other: T) -> Self; +} + +pub trait IntoCv1 { + fn into_cv1(self) -> T; +} + +pub trait IntoCv2 { + fn into_cv2(self) -> T; +} + +impl IntoCv1 for T +where + U: FromCv2, +{ + fn into_cv1(self) -> U { + >::from_cv2(self) + } +} + +impl IntoCv2 for T +where + U: FromCv1, +{ + fn into_cv2(self) -> U { + >::from_cv1(self) + } +} + +pub trait TryFromCv1: Sized { + type Error; + fn try_from_cv1(other: T) -> Result; +} + +pub trait TryFromCv2: Sized { + type Error; + fn try_from_cv2(other: T) -> Result; +} + +pub trait TryIntoCv1 { + type Error; + fn try_into_cv1(self) -> Result; +} + +pub trait TryIntoCv2 { + type Error; + fn try_into_cv2(self) -> Result; +} + +impl TryIntoCv1 for T +where + U: TryFromCv2, +{ + type Error = U::Error; + fn try_into_cv1(self) -> Result { + >::try_from_cv2(self) + } +} + +impl TryIntoCv2 for T +where + U: TryFromCv1, +{ + type Error = U::Error; + fn try_into_cv2(self) -> Result { + >::try_from_cv1(self) + } +}