Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7238164
chore: Added nix flake for starting a development shell.
Rahzael Jan 17, 2026
8d49357
feat: Implemented conversion from CV2 note on to CV1.
Rahzael Jan 17, 2026
dcb588b
fix: Fixes issue with CV1 velocity calculation when converting from CV2.
Rahzael Jan 17, 2026
52dde91
test: Added test to ensure that CV2 -> CV1 velocity is always > 0 so …
Rahzael Jan 17, 2026
e3cc0f0
Revert "chore: Added nix flake for starting a development shell."
Rahzael Jan 18, 2026
cc70e09
fix: Remove CV1 NotOn alias in CV2 -> CV1 trait implementation.
Rahzael Jan 18, 2026
5c207c6
docs: Add comment that 0 Vel CV2 will produce 1 Vel CV1.
Rahzael Jan 18, 2026
005c0b9
refactor: Makes CV2 Note On -> CV1 Note On generic over Buffer<Unit =…
Rahzael Jan 19, 2026
cd3da57
feat: Added direct CV2 -> CV1 Note On for resizable buffers.
Rahzael Jan 19, 2026
335de23
refactor: Moved Channeled and Grouped trait import to function body o…
Rahzael Jan 19, 2026
c0456c4
Feat: Added upscaling for all ranges but upper range and downscaling …
Rahzael Jan 20, 2026
4c73fd7
fix: Fixed center implementation to round up instead of down.
Rahzael Jan 21, 2026
dc0a4a8
feat: Implemented mincentermax upscaling for upper range.
Rahzael Jan 21, 2026
3f5c45c
refactor: Removed dbg! output in upscale() and unnesecary mut in CV2 …
Rahzael Jan 21, 2026
b8ad70b
style: Resolve clippy errors.
Rahzael Jan 21, 2026
16a3cf3
refactor: Moved From<CV2> note on to CV1 note on file.
Rahzael Jan 22, 2026
d3763f0
refactor: Move conversion module to its own file.
Rahzael Jan 26, 2026
a49d3a6
test: Added downscaling test for center values.
Rahzael Jan 29, 2026
9baf1fc
test: Added min, max, and example downscaling tests.
Rahzael Jan 29, 2026
6ea9378
style: Removed unnesecary module nesting in conversion.rs.
Rahzael Jan 29, 2026
e9a6709
feat: Implemented zero extension upscaling.
Rahzael Jan 29, 2026
2e2865b
feat: Added Zero-Extension downscaling.
Rahzael Jan 30, 2026
2446a0f
test: Added additional tests for ze downscaling.
Feb 10, 2026
9ef5fec
refactor: Change from/try_from for CV1 into from_cv2/try_from_cv2.
Feb 14, 2026
00b2393
refactor: Moved CV1 <-> CV2 conversion traits to traits.rs.
Feb 14, 2026
d665dfa
style: removed unused imports in CV1/note_on.
Jul 16, 2026
de43a29
feat: added CV2 -> CV1 conversion for NoteOff.
Jul 16, 2026
aa3a94c
feat: Implemented TryFrom<(CV1::NoteOn, CV2::NoteOn)> for CV2::NoteOn.
Jul 28, 2026
d023fa6
test: Added test to ensure that (CV1::NoteOn, CV2::NoteOn) -> CV2::No…
Jul 28, 2026
f8dc34b
feat: Implemented TryFromCV1 for CV2 Note On.
Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 129 additions & 1 deletion midi2/src/channel_voice1/note_off.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32> + crate::buffer::BufferMut,
> From<(crate::channel_voice2::NoteOff<A>, NoteOff<B>)> for NoteOff<B>
{
fn from(val: (crate::channel_voice2::NoteOff<A>, NoteOff<B>)) -> 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::<ux::u7>());
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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32>
+ crate::buffer::BufferMut
+ crate::buffer::BufferDefault
+ crate::buffer::BufferResize,
> crate::FromCv2<crate::channel_voice2::NoteOff<A>> for NoteOff<B>
{
fn from_cv2(val: crate::channel_voice2::NoteOff<A>) -> Self {
let dest = NoteOff::<B>::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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32>
+ crate::buffer::BufferMut
+ crate::buffer::BufferDefault
+ crate::buffer::BufferTryResize,
> crate::TryFromCv2<crate::channel_voice2::NoteOff<A>> for NoteOff<B>
{
type Error = crate::error::BufferOverflow;
fn try_from_cv2(val: crate::channel_voice2::NoteOff<A>) -> Result<Self, Self::Error> {
let dest = NoteOff::<B>::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;
Expand Down Expand Up @@ -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::<Vec<u32>>::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<Vec<u32>> = 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);
}
}
188 changes: 187 additions & 1 deletion midi2/src/channel_voice1/note_on.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32> + crate::buffer::BufferMut,
> From<(crate::channel_voice2::NoteOn<A>, NoteOn<B>)> for NoteOn<B>
{
fn from(val: (crate::channel_voice2::NoteOn<A>, NoteOn<B>)) -> 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::<ux::u7>()),
}
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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32>
+ crate::buffer::BufferMut
+ crate::buffer::BufferDefault
+ crate::buffer::BufferTryResize,
> crate::TryFromCv2<crate::channel_voice2::NoteOn<A>> for NoteOn<B>
{
type Error = crate::error::BufferOverflow;
fn try_from_cv2(val: crate::channel_voice2::NoteOn<A>) -> Result<Self, Self::Error> {
let dest = NoteOn::<B>::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<Unit = u32>,
B: crate::buffer::Buffer<Unit = u32>
+ crate::buffer::BufferMut
+ crate::buffer::BufferDefault
+ crate::buffer::BufferResize,
> crate::FromCv2<crate::channel_voice2::NoteOn<A>> for NoteOn<B>
{
fn from_cv2(val: crate::channel_voice2::NoteOn<A>) -> Self {
let dest = NoteOn::<B>::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;
Expand Down Expand Up @@ -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::<Vec<u32>>::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<Vec<u32>> = 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::<Vec<u32>>::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<Vec<u32>> = 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);
}
}
Loading
Loading