From 72381647102f6f8fdeadf7102b1a18831f2c8b99 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Fri, 16 Jan 2026 20:23:21 -0700 Subject: [PATCH 01/30] chore: Added nix flake for starting a development shell. --- .gitignore | 2 - Cargo.lock | 367 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.lock | 27 ++++ flake.nix | 16 +++ shell.nix | 19 +++ 5 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore index 8cfe61d..b463637 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ /target -/Cargo.lock midi2_proc/target -midi2_proc/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8d34415 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,367 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "fixed" +version = "1.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "handling_messages" +version = "0.0.0" +dependencies = [ + "midi2", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "midi2" +version = "0.11.0" +dependencies = [ + "derive_more", + "fixed", + "midi2_proc", + "pretty_assertions", + "static_assertions", + "ux", +] + +[[package]] +name = "midi2-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "libfuzzer-sys", + "midi2", + "rand", +] + +[[package]] +name = "midi2_proc" +version = "0.11.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "no_std_dynamic_message_generator" +version = "0.0.0" +dependencies = [ + "midi2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "ux" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b59fc5417e036e53226bbebd90196825d358624fd5577432c4e486c95b1b096" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6fbc907 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1768323494, + "narHash": "sha256-yBXJLE6WCtrGo7LKiB6NOt6nisBEEkguC/lq/rP3zRQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "2c3e5ec5df46d3aeee2a1da0bfedd74e21f4bf3a", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9ac3cd7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,16 @@ +{ + description = "Ergonomic, versatile, strong types wrapping MIDI 2.0 message data."; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; + }; + outputs = { self, nixpkgs }: + let + supportedSystems = [ "x86_64-linux" ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + pkgsFor = nixpkgs.legacyPackages; + in { + devShells = forAllSystems (system: { + default = pkgsFor.${system}.callPackage ./shell.nix { }; + }); + }; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..75ee467 --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +{ pkgs ? import { }}: +pkgs.mkShell { + # Additional tooling + buildInputs = with pkgs; [ + rust-analyzer # LSP Server + rustfmt # Formatter + clippy # Linter + cargo # Dependency Manager + rustc # Compiler + + python313Packages.wheel + pre-commit + codespell + ]; + + shellHook = '' + ./configure-hooks.sh install + ''; +} From 8d49357a123e95213f6bd505ce942186c48a1081 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sat, 17 Jan 2026 00:39:36 -0700 Subject: [PATCH 02/30] feat: Implemented conversion from CV2 note on to CV1. --- midi2/src/channel_voice2/note_on.rs | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index de795da..45d60de 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -7,6 +7,8 @@ use crate::{ ux::{u4, u7}, }; +use crate::traits::{Channeled, Grouped}; +use crate::{channel_voice1::NoteOn as NoteOn1, Data}; pub(crate) const STATUS: u8 = 0b1001; /// MIDI 2.0 Channel Voice Note On Message @@ -30,6 +32,21 @@ struct NoteOn { attribute: Option, } +#[cfg(feature = "channel-voice1")] +impl Into> for NoteOn<[u32; N]> { + fn into(self) -> NoteOn1<[u32; N]> { + let mut message = NoteOn1::<[u32; N]>::new(); + message.set_group(self.group()); + message.set_channel(self.channel()); + message.set_note_number(self.note_number()); + match self.velocity() { + 0 => message.set_velocity(u7::new(0x01)), + _ => message.set_velocity(u7::new((self.velocity() >> 8).try_into().unwrap())), + } + message + } +} + #[cfg(test)] mod tests { use super::*; @@ -96,4 +113,27 @@ mod tests { Some(Attribute::Pitch7_9(Fixed7_9::from_bits(0b1110100110001010))), ); } + + #[test] + fn into_midi_1() { + 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(0x6A14); + + assert_eq!(message2, NoteOn([0x4898_5E00, 0x6A14_0000, 0x0, 0x0]),); + + let mut message1 = NoteOn1::<[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(0x6A)); + + let message21: NoteOn1<[u32; 4]> = message2.into(); + + assert_eq!(message21, message1); + } } From dcb588bebe72a235e88fbe17aa049a3a5d9be87c Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sat, 17 Jan 2026 10:50:39 -0700 Subject: [PATCH 03/30] fix: Fixes issue with CV1 velocity calculation when converting from CV2. --- midi2/src/channel_voice2/note_on.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index 45d60de..93a9456 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -41,7 +41,7 @@ impl Into> for NoteOn<[u32; N]> { message.set_note_number(self.note_number()); match self.velocity() { 0 => message.set_velocity(u7::new(0x01)), - _ => message.set_velocity(u7::new((self.velocity() >> 8).try_into().unwrap())), + _ => message.set_velocity(u7::new((self.velocity() >> 9) as u8)), } message } @@ -122,15 +122,15 @@ mod tests { message2.set_group(u4::new(0x8)); message2.set_channel(u4::new(0x8)); message2.set_note_number(u7::new(0x5E)); - message2.set_velocity(0x6A14); + message2.set_velocity(0x8000); - assert_eq!(message2, NoteOn([0x4898_5E00, 0x6A14_0000, 0x0, 0x0]),); + assert_eq!(message2, NoteOn([0x4898_5E00, 0x8000_0000, 0x0, 0x0]),); let mut message1 = NoteOn1::<[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(0x6A)); + message1.set_velocity(u7::new(0x40)); let message21: NoteOn1<[u32; 4]> = message2.into(); From 52dde91e248c1df9eca527d2fd67e8640826f901 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sat, 17 Jan 2026 15:30:28 -0700 Subject: [PATCH 04/30] test: Added test to ensure that CV2 -> CV1 velocity is always > 0 so it isn't ignored. --- midi2/src/channel_voice2/note_on.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index 93a9456..6bd4d3d 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -136,4 +136,27 @@ mod tests { assert_eq!(message21, message1); } + + #[test] + fn into_midi_1_zero_velocity() { + 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(0x0000); + + assert_eq!(message2, NoteOn([0x4898_5E00, 0x0000_0000, 0x0, 0x0]),); + + let mut message1 = NoteOn1::<[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: NoteOn1<[u32; 4]> = message2.into(); + + assert_eq!(message21, message1); + } } From e3cc0f004f62942f51151db11f583faf0aea7e14 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 01:12:35 -0700 Subject: [PATCH 05/30] Revert "chore: Added nix flake for starting a development shell." This reverts commit be3eead235c9843e0af02cf4d822d889e712d6e1. --- .gitignore | 2 + Cargo.lock | 367 ----------------------------------------------------- flake.lock | 27 ---- flake.nix | 16 --- shell.nix | 19 --- 5 files changed, 2 insertions(+), 429 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 flake.lock delete mode 100644 flake.nix delete mode 100644 shell.nix diff --git a/.gitignore b/.gitignore index b463637..8cfe61d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target +/Cargo.lock midi2_proc/target +midi2_proc/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 8d34415..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,367 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" - -[[package]] -name = "cc" -version = "1.2.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "find-msvc-tools" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" - -[[package]] -name = "fixed" -version = "1.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "handling_messages" -version = "0.0.0" -dependencies = [ - "midi2", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom", - "libc", -] - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "midi2" -version = "0.11.0" -dependencies = [ - "derive_more", - "fixed", - "midi2_proc", - "pretty_assertions", - "static_assertions", - "ux", -] - -[[package]] -name = "midi2-fuzz" -version = "0.0.0" -dependencies = [ - "arbitrary", - "libfuzzer-sys", - "midi2", - "rand", -] - -[[package]] -name = "midi2_proc" -version = "0.11.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "no_std_dynamic_message_generator" -version = "0.0.0" -dependencies = [ - "midi2", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "ux" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b59fc5417e036e53226bbebd90196825d358624fd5577432c4e486c95b1b096" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "zerocopy" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 6fbc907..0000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1768323494, - "narHash": "sha256-yBXJLE6WCtrGo7LKiB6NOt6nisBEEkguC/lq/rP3zRQ=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "2c3e5ec5df46d3aeee2a1da0bfedd74e21f4bf3a", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-25.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index 9ac3cd7..0000000 --- a/flake.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - description = "Ergonomic, versatile, strong types wrapping MIDI 2.0 message data."; - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; - }; - outputs = { self, nixpkgs }: - let - supportedSystems = [ "x86_64-linux" ]; - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - pkgsFor = nixpkgs.legacyPackages; - in { - devShells = forAllSystems (system: { - default = pkgsFor.${system}.callPackage ./shell.nix { }; - }); - }; -} diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 75ee467..0000000 --- a/shell.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ pkgs ? import { }}: -pkgs.mkShell { - # Additional tooling - buildInputs = with pkgs; [ - rust-analyzer # LSP Server - rustfmt # Formatter - clippy # Linter - cargo # Dependency Manager - rustc # Compiler - - python313Packages.wheel - pre-commit - codespell - ]; - - shellHook = '' - ./configure-hooks.sh install - ''; -} From cc70e0981d34196ccf70ee9a9dda22b2d36f289b Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 01:32:42 -0700 Subject: [PATCH 06/30] fix: Remove CV1 NotOn alias in CV2 -> CV1 trait implementation. --- midi2/src/channel_voice2/note_on.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index 6bd4d3d..eabf010 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -7,8 +7,6 @@ use crate::{ ux::{u4, u7}, }; -use crate::traits::{Channeled, Grouped}; -use crate::{channel_voice1::NoteOn as NoteOn1, Data}; pub(crate) const STATUS: u8 = 0b1001; /// MIDI 2.0 Channel Voice Note On Message @@ -31,11 +29,16 @@ struct NoteOn { #[property(AttributeProperty)] attribute: Option, } +#[cfg(feature = "channel-voice1")] +use crate::{ + channel_voice1, + traits::{Channeled, Grouped}, +}; #[cfg(feature = "channel-voice1")] -impl Into> for NoteOn<[u32; N]> { - fn into(self) -> NoteOn1<[u32; N]> { - let mut message = NoteOn1::<[u32; N]>::new(); +impl Into> for NoteOn<[u32; N]> { + fn into(self) -> channel_voice1::NoteOn<[u32; N]> { + let mut message = channel_voice1::NoteOn::<[u32; N]>::new(); message.set_group(self.group()); message.set_channel(self.channel()); message.set_note_number(self.note_number()); @@ -126,13 +129,13 @@ mod tests { assert_eq!(message2, NoteOn([0x4898_5E00, 0x8000_0000, 0x0, 0x0]),); - let mut message1 = NoteOn1::<[u32; 4]>::new(); + let mut message1 = 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 message21: NoteOn1<[u32; 4]> = message2.into(); + let message21: channel_voice1::NoteOn<[u32; 4]> = message2.into(); assert_eq!(message21, message1); } @@ -149,13 +152,13 @@ mod tests { assert_eq!(message2, NoteOn([0x4898_5E00, 0x0000_0000, 0x0, 0x0]),); - let mut message1 = NoteOn1::<[u32; 4]>::new(); + let mut message1 = 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(0x01)); - let message21: NoteOn1<[u32; 4]> = message2.into(); + let message21: channel_voice1::NoteOn<[u32; 4]> = message2.into(); assert_eq!(message21, message1); } From 5c207c614d5ec533d4d95e0ce87fe2dc10bc27dc Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 12:17:11 -0700 Subject: [PATCH 07/30] docs: Add comment that 0 Vel CV2 will produce 1 Vel CV1. --- midi2/src/channel_voice2/note_on.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index eabf010..558b855 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -35,6 +35,10 @@ use crate::{ traits::{Channeled, Grouped}, }; +/// Converts 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-voice1")] impl Into> for NoteOn<[u32; N]> { fn into(self) -> channel_voice1::NoteOn<[u32; N]> { @@ -43,6 +47,10 @@ impl Into> for NoteOn<[u32; N]> message.set_channel(self.channel()); message.set_note_number(self.note_number()); match self.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 => message.set_velocity(u7::new(0x01)), _ => message.set_velocity(u7::new((self.velocity() >> 9) as u8)), } From 005c0b907e96a8d615b7c63966d38233da1174c1 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 19:05:08 -0700 Subject: [PATCH 08/30] refactor: Makes CV2 Note On -> CV1 Note On generic over Buffer. --- midi2/src/channel_voice2/note_on.rs | 37 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index 558b855..a852acb 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -30,31 +30,32 @@ struct NoteOn { attribute: Option, } #[cfg(feature = "channel-voice1")] -use crate::{ - channel_voice1, - traits::{Channeled, Grouped}, -}; +use crate::traits::{Channeled, Grouped}; /// Converts 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-voice1")] -impl Into> for NoteOn<[u32; N]> { - fn into(self) -> channel_voice1::NoteOn<[u32; N]> { - let mut message = channel_voice1::NoteOn::<[u32; N]>::new(); - message.set_group(self.group()); - message.set_channel(self.channel()); - message.set_note_number(self.note_number()); - match self.velocity() { +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + crate::buffer::BufferMut, + > Into> for (NoteOn, crate::channel_voice1::NoteOn) +{ + fn into(self) -> crate::channel_voice1::NoteOn { + let (src, mut dest) = self; + 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 => message.set_velocity(u7::new(0x01)), - _ => message.set_velocity(u7::new((self.velocity() >> 9) as u8)), + 0 => dest.set_velocity(u7::new(0x01)), + _ => dest.set_velocity(u7::new((src.velocity() >> 9) as u8)), } - message + dest } } @@ -127,6 +128,7 @@ mod tests { #[test] fn into_midi_1() { + use crate::channel_voice1; use crate::traits::{Channeled, Grouped}; let mut message2 = NoteOn::<[u32; 4]>::new(); @@ -143,13 +145,15 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x40)); - let message21: channel_voice1::NoteOn<[u32; 4]> = message2.into(); + let mut message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); + let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); assert_eq!(message21, message1); } #[test] fn into_midi_1_zero_velocity() { + use crate::channel_voice1; use crate::traits::{Channeled, Grouped}; let mut message2 = NoteOn::<[u32; 4]>::new(); @@ -166,7 +170,8 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x01)); - let message21: channel_voice1::NoteOn<[u32; 4]> = message2.into(); + let mut message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); + let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); assert_eq!(message21, message1); } From cd3da57152939b66eee21f4b3f2d3631238f37d9 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 19:51:06 -0700 Subject: [PATCH 09/30] feat: Added direct CV2 -> CV1 Note On for resizable buffers. --- midi2/src/channel_voice2/note_on.rs | 82 +++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index a852acb..cf243e9 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -32,7 +32,9 @@ struct NoteOn { #[cfg(feature = "channel-voice1")] use crate::traits::{Channeled, Grouped}; -/// Converts a CV2 Note On message to a CV1 Note On message. +/// Converts a CV2 Note On message to CV1 Note On message, +/// storing the result in a pre-instantiated 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. @@ -59,6 +61,28 @@ impl< } } +/// 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-voice1")] +impl< + A: crate::buffer::Buffer, + B: crate::buffer::Buffer + + crate::buffer::BufferMut + + crate::buffer::BufferDefault + + crate::buffer::BufferResize, + > Into> for NoteOn +{ + fn into(self) -> crate::channel_voice1::NoteOn { + let mut dest = crate::channel_voice1::NoteOn::::new(); + (self, dest).into() + } +} + #[cfg(test)] mod tests { use super::*; @@ -130,6 +154,56 @@ mod tests { fn into_midi_1() { use crate::channel_voice1; use crate::traits::{Channeled, Grouped}; + use std::vec::Vec; + + 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); + + assert_eq!(message2, NoteOn([0x4898_5E00, 0x8000_0000, 0x0, 0x0]),); + + let mut message1 = channel_voice1::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: channel_voice1::NoteOn> = message2.into(); + + assert_eq!(message21, message1); + } + + #[test] + fn into_midi_1_zero_velocity() { + use crate::channel_voice1; + use crate::traits::{Channeled, Grouped}; + use std::vec::Vec; + + 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(0x0000); + + assert_eq!(message2, NoteOn([0x4898_5E00, 0x0000_0000, 0x0, 0x0]),); + + let mut message1 = channel_voice1::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: channel_voice1::NoteOn> = message2.into(); + + assert_eq!(message21, message1); + } + + #[test] + fn into_midi_1_with_dest() { + use crate::channel_voice1; + use crate::traits::{Channeled, Grouped}; let mut message2 = NoteOn::<[u32; 4]>::new(); message2.set_group(u4::new(0x8)); @@ -145,14 +219,14 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x40)); - let mut message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); + let message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); assert_eq!(message21, message1); } #[test] - fn into_midi_1_zero_velocity() { + fn into_midi_1_zero_velocity_with_dest() { use crate::channel_voice1; use crate::traits::{Channeled, Grouped}; @@ -170,7 +244,7 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x01)); - let mut message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); + let message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); assert_eq!(message21, message1); From 335de23fb5dda06bda55ae51dff32dcb46d053d0 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 18 Jan 2026 20:39:54 -0700 Subject: [PATCH 10/30] refactor: Moved Channeled and Grouped trait import to function body of CV2 -> CV1. --- midi2/src/channel_voice2/note_on.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index cf243e9..c98aa1d 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -29,8 +29,6 @@ struct NoteOn { #[property(AttributeProperty)] attribute: Option, } -#[cfg(feature = "channel-voice1")] -use crate::traits::{Channeled, Grouped}; /// Converts a CV2 Note On message to CV1 Note On message, /// storing the result in a pre-instantiated CV1 Note On. @@ -45,6 +43,8 @@ impl< > Into> for (NoteOn, crate::channel_voice1::NoteOn) { fn into(self) -> crate::channel_voice1::NoteOn { + use crate::traits::{Channeled, Grouped}; + let (src, mut dest) = self; dest.set_group(src.group()); dest.set_channel(src.channel()); From c0456c48fb694b4c4eda9cb8e52838818e42039d Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 20 Jan 2026 01:29:02 -0700 Subject: [PATCH 11/30] Feat: Added upscaling for all ranges but upper range and downscaling for lower range. --- midi2/src/traits.rs | 270 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 348a5f9..5e804c9 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -731,3 +731,273 @@ pub(crate) trait BufferAccess { where B: BufferMut; } + +mod conversion { + pub(crate) trait Center: + Into + TryFrom + Sized + Copy + PartialEq + PartialOrd + where + >::Error: core::fmt::Debug, + { + fn min_value() -> Self; + fn max_value() -> Self; + + fn center_value() -> Self { + let min: u32 = Self::min_value().into(); + let max: u32 = Self::max_value().into(); + + ((max - min) / 2_u32) + .try_into() + .expect("Center shouldn't be larger than max.") + } + } + + impl Center for ux::u7 { + fn min_value() -> Self { + Self::min_value() + } + fn max_value() -> Self { + Self::max_value() + } + } + + impl Center for ux::u9 { + fn min_value() -> Self { + Self::min_value() + } + fn max_value() -> Self { + Self::max_value() + } + } + + impl Center for ux::u14 { + fn min_value() -> Self { + Self::min_value() + } + fn max_value() -> Self { + Self::max_value() + } + } + + impl Center for u16 { + fn min_value() -> Self { + Self::min_value() + } + fn max_value() -> Self { + Self::max_value() + } + } + + impl Center for u32 { + fn min_value() -> Self { + Self::min_value() + } + fn max_value() -> Self { + Self::max_value() + } + } + + pub(crate) trait MinCenterMax: Center + core::ops::Shr + where + >::Output: Into, + >::Error: core::fmt::Debug, + { + fn upscale(self) -> U + where + Self: Into, + U: core::ops::Shl, + U: From<>::Output>, + >::Error: core::fmt::Debug, + { + let min = Self::min_value(); + let center = Self::center_value(); + let max = Self::max_value(); + + match self { + s if s == min => U::min_value(), + s if s == max => U::max_value(), + s if s == center => U::center_value(), + s if (min..center).contains(&s) => { + let self_max: u32 = Self::max_value().into(); + let other_max: u32 = U::max_value().into(); + let shift = (other_max - self_max).count_ones(); + let other: U = self.into(); + let upscaled = other << shift; + upscaled.into() + } + s if (center..max).contains(&s) => s.into(), + _ => self.into(), + } + } + + fn downscale>(self) -> U + where + >::Error: core::fmt::Debug, + >::Error: core::fmt::Debug, + { + let self_max: u32 = Self::max_value().into(); + let other_max: u32 = U::max_value().into(); + let shift = (self_max - other_max).count_ones(); + let downscaled = self >> shift; + let downscaled: Self = downscaled.into(); + + downscaled.try_into().expect("Downscaling should not fail.") + } + } + + impl> MinCenterMax for U + where + >::Error: core::fmt::Debug, + >::Output: Into, + { + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_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.upscale::(), min_u7); + assert_eq!(min_u7.upscale::(), min_u9); + assert_eq!(min_u7.upscale::(), min_u14); + assert_eq!(min_u7.upscale::(), min_u16); + assert_eq!(min_u7.upscale::(), min_u32); + + assert_eq!(min_u9.upscale::(), min_u9); + assert_eq!(min_u9.upscale::(), min_u14); + assert_eq!(min_u9.upscale::(), min_u16); + assert_eq!(min_u9.upscale::(), min_u32); + + assert_eq!(min_u14.upscale::(), min_u14); + assert_eq!(min_u14.upscale::(), min_u16); + assert_eq!(min_u14.upscale::(), min_u32); + + assert_eq!(min_u16.upscale::(), min_u16); + assert_eq!(min_u16.upscale::(), min_u32); + + assert_eq!(min_u32.upscale::(), min_u32); + } + + #[test] + fn test_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.upscale::(), max_u7); + assert_eq!(max_u7.upscale::(), max_u9); + assert_eq!(max_u7.upscale::(), max_u14); + assert_eq!(max_u7.upscale::(), max_u16); + assert_eq!(max_u7.upscale::(), max_u32); + + assert_eq!(max_u9.upscale::(), max_u9); + assert_eq!(max_u9.upscale::(), max_u14); + assert_eq!(max_u9.upscale::(), max_u16); + assert_eq!(max_u9.upscale::(), max_u32); + + assert_eq!(max_u14.upscale::(), max_u14); + assert_eq!(max_u14.upscale::(), max_u16); + assert_eq!(max_u14.upscale::(), max_u32); + + assert_eq!(max_u16.upscale::(), max_u16); + assert_eq!(max_u16.upscale::(), max_u32); + + assert_eq!(max_u32.upscale::(), max_u32); + } + + #[test] + fn test_centers_upscaling() { + let center_u7 = ux::u7::new(63); + let center_u9 = ux::u9::new(255); + let center_u14 = ux::u14::new(8191); + let center_u16 = 0x7FFF_u16; + let center_u32 = 0x7FFFFFFF_u32; + + assert_eq!(center_u7.upscale::(), center_u7); + assert_eq!(center_u7.upscale::(), center_u9); + assert_eq!(center_u7.upscale::(), center_u14); + assert_eq!(center_u7.upscale::(), center_u16); + assert_eq!(center_u7.upscale::(), center_u32); + + assert_eq!(center_u9.upscale::(), center_u9); + assert_eq!(center_u9.upscale::(), center_u14); + assert_eq!(center_u9.upscale::(), center_u16); + assert_eq!(center_u9.upscale::(), center_u32); + + assert_eq!(center_u14.upscale::(), center_u14); + assert_eq!(center_u14.upscale::(), center_u16); + assert_eq!(center_u14.upscale::(), center_u32); + + assert_eq!(center_u16.upscale::(), center_u16); + assert_eq!(center_u16.upscale::(), center_u32); + + assert_eq!(center_u32.upscale::(), center_u32); + } + + #[test] + fn test_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.downscale::(), lower_u7); + assert_eq!(lower_u32.downscale::(), lower_u9); + assert_eq!(lower_u32.downscale::(), lower_u14); + assert_eq!(lower_u32.downscale::(), lower_u16); + assert_eq!(lower_u32.downscale::(), lower_u32); + + assert_eq!(lower_u16.downscale::(), lower_u7); + assert_eq!(lower_u16.downscale::(), lower_u9); + assert_eq!(lower_u16.downscale::(), lower_u14); + assert_eq!(lower_u16.downscale::(), lower_u16); + + assert_eq!(lower_u14.downscale::(), lower_u7); + assert_eq!(lower_u14.downscale::(), lower_u9); + assert_eq!(lower_u14.downscale::(), lower_u14); + + assert_eq!(lower_u9.downscale::(), lower_u7); + assert_eq!(lower_u9.downscale::(), lower_u9); + + assert_eq!(lower_u7.downscale::(), lower_u7); + } + + #[test] + fn test_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.upscale::(), lower_u7); + assert_eq!(lower_u7.upscale::(), lower_u9); + assert_eq!(lower_u7.upscale::(), lower_u14); + assert_eq!(lower_u7.upscale::(), lower_u16); + assert_eq!(lower_u7.upscale::(), lower_u32); + + assert_eq!(lower_u9.upscale::(), lower_u9); + assert_eq!(lower_u9.upscale::(), lower_u14); + assert_eq!(lower_u9.upscale::(), lower_u16); + assert_eq!(lower_u9.upscale::(), lower_u32); + + assert_eq!(lower_u14.upscale::(), lower_u14); + assert_eq!(lower_u14.upscale::(), lower_u16); + assert_eq!(lower_u14.upscale::(), lower_u32); + + assert_eq!(lower_u16.upscale::(), lower_u16); + assert_eq!(lower_u16.upscale::(), lower_u32); + + assert_eq!(lower_u32.upscale::(), lower_u32); + } + } +} From 4c73fd78686b28ef14916545807237d24ab1d9bf Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 20 Jan 2026 20:16:58 -0700 Subject: [PATCH 12/30] fix: Fixed center implementation to round up instead of down. --- midi2/src/traits.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 5e804c9..4566913 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -745,7 +745,7 @@ mod conversion { let min: u32 = Self::min_value().into(); let max: u32 = Self::max_value().into(); - ((max - min) / 2_u32) + ((max - min) / 2_u32 + 1) .try_into() .expect("Center shouldn't be larger than max.") } @@ -915,11 +915,11 @@ mod conversion { #[test] fn test_centers_upscaling() { - let center_u7 = ux::u7::new(63); - let center_u9 = ux::u9::new(255); - let center_u14 = ux::u14::new(8191); - let center_u16 = 0x7FFF_u16; - let center_u32 = 0x7FFFFFFF_u32; + 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.upscale::(), center_u7); assert_eq!(center_u7.upscale::(), center_u9); From dc0a4a8811de8d28d9e2884b0426811ef3c17b8e Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 20 Jan 2026 23:42:52 -0700 Subject: [PATCH 13/30] feat: Implemented mincentermax upscaling for upper range. --- midi2/src/traits.rs | 141 +++++++++++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 33 deletions(-) diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 4566913..89545b0 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -801,10 +801,19 @@ mod conversion { >::Output: Into, >::Error: core::fmt::Debug, { - fn upscale(self) -> U + fn upscale< + U: Center + + core::ops::Add + + core::ops::BitAnd + + core::fmt::Debug + + core::ops::BitOr, + >( + self, + ) -> U where Self: Into, - U: core::ops::Shl, + U: core::ops::Shr, + U: core::ops::Shl, U: From<>::Output>, >::Error: core::fmt::Debug, { @@ -816,15 +825,47 @@ mod conversion { s if s == min => U::min_value(), s if s == max => U::max_value(), s if s == center => U::center_value(), + s if (min..center).contains(&s) => { let self_max: u32 = Self::max_value().into(); let other_max: u32 = U::max_value().into(); let shift = (other_max - self_max).count_ones(); let other: U = self.into(); let upscaled = other << shift; - upscaled.into() + upscaled + } + + s if (center..max).contains(&s) => { + let self_max: u32 = Self::max_value().into(); + let other_max: u32 = U::max_value().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_value() >> (other_bits - self_bits + 1); + let repeating = initial & initial_mask; + + let mut upscaled = initial; + let mut remainder = other_bits - self_bits; + + while remainder > 0 { + remainder = match remainder { + remainder if remainder > shift => { + upscaled = (upscaled << shift) | repeating; + remainder - shift + } + _ => { + let shift = shift - remainder; + upscaled = (upscaled << remainder) | (repeating >> shift); + 0 + } + }; + } + std::dbg!(upscaled, initial, repeating); + upscaled } - s if (center..max).contains(&s) => s.into(), + _ => self.into(), } } @@ -942,35 +983,6 @@ mod conversion { assert_eq!(center_u32.upscale::(), center_u32); } - #[test] - fn test_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.downscale::(), lower_u7); - assert_eq!(lower_u32.downscale::(), lower_u9); - assert_eq!(lower_u32.downscale::(), lower_u14); - assert_eq!(lower_u32.downscale::(), lower_u16); - assert_eq!(lower_u32.downscale::(), lower_u32); - - assert_eq!(lower_u16.downscale::(), lower_u7); - assert_eq!(lower_u16.downscale::(), lower_u9); - assert_eq!(lower_u16.downscale::(), lower_u14); - assert_eq!(lower_u16.downscale::(), lower_u16); - - assert_eq!(lower_u14.downscale::(), lower_u7); - assert_eq!(lower_u14.downscale::(), lower_u9); - assert_eq!(lower_u14.downscale::(), lower_u14); - - assert_eq!(lower_u9.downscale::(), lower_u7); - assert_eq!(lower_u9.downscale::(), lower_u9); - - assert_eq!(lower_u7.downscale::(), lower_u7); - } - #[test] fn test_lower_range_upscaling() { let lower_u7 = ux::u7::new(0x1F); @@ -999,5 +1011,68 @@ mod conversion { assert_eq!(lower_u32.upscale::(), lower_u32); } + + #[test] + fn test_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).upscale::(), 0x8C30_u16), + (ux::u7::new(0x60).upscale::(), 0xC104_u16), + (ux::u7::new(0x78).upscale::(), 0xF1C7_u16), + ]; + + let upper_u7_to_u32 = [ + (ux::u7::new(0x46).upscale::(), 0x8C30C30C_u32), + (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), + (ux::u7::new(0x78).upscale::(), 0xF1C71C71_u32), + ]; + + let upper_u16_to_u32 = [ + (0x9C40_u16.upscale::(), 0x9C403880_u32), + (0xC000_u16.upscale::(), 0xC0008001_u32), + (0xFDE8_u16.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_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.downscale::(), lower_u7); + assert_eq!(lower_u32.downscale::(), lower_u9); + assert_eq!(lower_u32.downscale::(), lower_u14); + assert_eq!(lower_u32.downscale::(), lower_u16); + assert_eq!(lower_u32.downscale::(), lower_u32); + + assert_eq!(lower_u16.downscale::(), lower_u7); + assert_eq!(lower_u16.downscale::(), lower_u9); + assert_eq!(lower_u16.downscale::(), lower_u14); + assert_eq!(lower_u16.downscale::(), lower_u16); + + assert_eq!(lower_u14.downscale::(), lower_u7); + assert_eq!(lower_u14.downscale::(), lower_u9); + assert_eq!(lower_u14.downscale::(), lower_u14); + + assert_eq!(lower_u9.downscale::(), lower_u7); + assert_eq!(lower_u9.downscale::(), lower_u9); + + assert_eq!(lower_u7.downscale::(), lower_u7); + } } } From 3f5c45cbc174b10040d2b286f0de2b72bdefa3c6 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 20 Jan 2026 23:47:58 -0700 Subject: [PATCH 14/30] refactor: Removed dbg! output in upscale() and unnesecary mut in CV2 -> CV1. --- midi2/src/channel_voice2/note_on.rs | 2 +- midi2/src/traits.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index c98aa1d..a9ca8a9 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -78,7 +78,7 @@ impl< > Into> for NoteOn { fn into(self) -> crate::channel_voice1::NoteOn { - let mut dest = crate::channel_voice1::NoteOn::::new(); + let dest = crate::channel_voice1::NoteOn::::new(); (self, dest).into() } } diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 89545b0..450e5f8 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -862,7 +862,6 @@ mod conversion { } }; } - std::dbg!(upscaled, initial, repeating); upscaled } From b8ad70b03c99373c8f07156c15f9722f0e145030 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Wed, 21 Jan 2026 16:37:14 -0700 Subject: [PATCH 15/30] style: Resolve clippy errors. --- midi2/src/channel_voice2/note_on.rs | 15 +++--- midi2/src/traits.rs | 83 +++++++++++------------------ 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index a9ca8a9..22055b7 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -40,12 +40,13 @@ struct NoteOn { impl< A: crate::buffer::Buffer, B: crate::buffer::Buffer + crate::buffer::BufferMut, - > Into> for (NoteOn, crate::channel_voice1::NoteOn) + > From<(NoteOn, crate::channel_voice1::NoteOn)> for crate::channel_voice1::NoteOn { - fn into(self) -> crate::channel_voice1::NoteOn { + fn from(val: (NoteOn, crate::channel_voice1::NoteOn)) -> Self { + use crate::traits::conversion::MinCenterMax; use crate::traits::{Channeled, Grouped}; - let (src, mut dest) = self; + let (src, mut dest) = val; dest.set_group(src.group()); dest.set_channel(src.channel()); dest.set_note_number(src.note_number()); @@ -55,7 +56,7 @@ impl< // See MIDI 2.0 spec 7.4.2: MIDI 2.0 Note On Message -> Velocity // for details. 0 => dest.set_velocity(u7::new(0x01)), - _ => dest.set_velocity(u7::new((src.velocity() >> 9) as u8)), + _ => dest.set_velocity(src.velocity().downscale::()), } dest } @@ -75,11 +76,11 @@ impl< + crate::buffer::BufferMut + crate::buffer::BufferDefault + crate::buffer::BufferResize, - > Into> for NoteOn + > From> for crate::channel_voice1::NoteOn { - fn into(self) -> crate::channel_voice1::NoteOn { + fn from(val: NoteOn) -> Self { let dest = crate::channel_voice1::NoteOn::::new(); - (self, dest).into() + (val, dest).into() } } diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 450e5f8..3410854 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -732,18 +732,19 @@ pub(crate) trait BufferAccess { B: BufferMut; } -mod conversion { +#[allow(dead_code)] +pub(crate) mod conversion { pub(crate) trait Center: Into + TryFrom + Sized + Copy + PartialEq + PartialOrd where >::Error: core::fmt::Debug, { - fn min_value() -> Self; - fn max_value() -> Self; + const MIN: Self; + const MAX: Self; fn center_value() -> Self { - let min: u32 = Self::min_value().into(); - let max: u32 = Self::max_value().into(); + let min: u32 = Self::MIN.into(); + let max: u32 = Self::MAX.into(); ((max - min) / 2_u32 + 1) .try_into() @@ -752,48 +753,28 @@ mod conversion { } impl Center for ux::u7 { - fn min_value() -> Self { - Self::min_value() - } - fn max_value() -> Self { - Self::max_value() - } + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; } impl Center for ux::u9 { - fn min_value() -> Self { - Self::min_value() - } - fn max_value() -> Self { - Self::max_value() - } + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; } impl Center for ux::u14 { - fn min_value() -> Self { - Self::min_value() - } - fn max_value() -> Self { - Self::max_value() - } + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; } impl Center for u16 { - fn min_value() -> Self { - Self::min_value() - } - fn max_value() -> Self { - Self::max_value() - } + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; } impl Center for u32 { - fn min_value() -> Self { - Self::min_value() - } - fn max_value() -> Self { - Self::max_value() - } + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; } pub(crate) trait MinCenterMax: Center + core::ops::Shr @@ -806,44 +787,42 @@ mod conversion { + core::ops::Add + core::ops::BitAnd + core::fmt::Debug - + core::ops::BitOr, + + core::ops::BitOr + + core::ops::Shr + + core::ops::Shl, >( self, ) -> U where Self: Into, - U: core::ops::Shr, - U: core::ops::Shl, - U: From<>::Output>, >::Error: core::fmt::Debug, { - let min = Self::min_value(); + let min = Self::MIN; let center = Self::center_value(); - let max = Self::max_value(); + let max = Self::MAX; match self { - s if s == min => U::min_value(), - s if s == max => U::max_value(), + 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_value().into(); - let other_max: u32 = U::max_value().into(); + 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(); - let upscaled = other << shift; - upscaled + other << shift } s if (center..max).contains(&s) => { - let self_max: u32 = Self::max_value().into(); - let other_max: u32 = U::max_value().into(); + 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_value() >> (other_bits - self_bits + 1); + let initial_mask = U::MAX >> (other_bits - self_bits + 1); let repeating = initial & initial_mask; let mut upscaled = initial; @@ -874,8 +853,8 @@ mod conversion { >::Error: core::fmt::Debug, >::Error: core::fmt::Debug, { - let self_max: u32 = Self::max_value().into(); - let other_max: u32 = U::max_value().into(); + let self_max: u32 = Self::MAX.into(); + let other_max: u32 = U::MAX.into(); let shift = (self_max - other_max).count_ones(); let downscaled = self >> shift; let downscaled: Self = downscaled.into(); From 16a3cf3713d3404ff6e06b3e7096c719f1159051 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Wed, 21 Jan 2026 17:27:33 -0700 Subject: [PATCH 16/30] refactor: Moved From note on to CV1 note on file. --- midi2/src/channel_voice1/note_on.rs | 146 ++++++++++++++++++++++++++ midi2/src/channel_voice2/note_on.rs | 154 ---------------------------- 2 files changed, 146 insertions(+), 154 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 1ddb82c..592208e 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -37,6 +37,60 @@ struct NoteOn { velocity: crate::ux::u7, } +/// Converts a CV2 Note On message to CV1 Note On message, +/// storing the result in a pre-instantiated 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::traits::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().downscale::()), + } + dest + } +} + +/// 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, + > From> for NoteOn +{ + fn from(val: crate::channel_voice2::NoteOn) -> Self { + let dest = NoteOn::::new(); + (val, dest).into() + } +} + #[cfg(test)] mod tests { use super::*; @@ -103,4 +157,96 @@ mod tests { u7::new(0x3D), ); } + + #[test] + fn from_midi_2() { + use crate::channel_voice2; + 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(); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2_zero_velocity() { + use crate::channel_voice2; + 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(); + + assert_eq!(message21, message1); + } + + #[test] + fn from_midi_2_with_dest() { + use crate::channel_voice2; + 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::channel_voice2; + 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 22055b7..de795da 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -30,60 +30,6 @@ struct NoteOn { attribute: Option, } -/// Converts a CV2 Note On message to CV1 Note On message, -/// storing the result in a pre-instantiated 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-voice1")] -impl< - A: crate::buffer::Buffer, - B: crate::buffer::Buffer + crate::buffer::BufferMut, - > From<(NoteOn, crate::channel_voice1::NoteOn)> for crate::channel_voice1::NoteOn -{ - fn from(val: (NoteOn, crate::channel_voice1::NoteOn)) -> Self { - use crate::traits::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(u7::new(0x01)), - _ => dest.set_velocity(src.velocity().downscale::()), - } - dest - } -} - -/// 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-voice1")] -impl< - A: crate::buffer::Buffer, - B: crate::buffer::Buffer - + crate::buffer::BufferMut - + crate::buffer::BufferDefault - + crate::buffer::BufferResize, - > From> for crate::channel_voice1::NoteOn -{ - fn from(val: NoteOn) -> Self { - let dest = crate::channel_voice1::NoteOn::::new(); - (val, dest).into() - } -} - #[cfg(test)] mod tests { use super::*; @@ -150,104 +96,4 @@ mod tests { Some(Attribute::Pitch7_9(Fixed7_9::from_bits(0b1110100110001010))), ); } - - #[test] - fn into_midi_1() { - use crate::channel_voice1; - use crate::traits::{Channeled, Grouped}; - use std::vec::Vec; - - 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); - - assert_eq!(message2, NoteOn([0x4898_5E00, 0x8000_0000, 0x0, 0x0]),); - - let mut message1 = channel_voice1::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: channel_voice1::NoteOn> = message2.into(); - - assert_eq!(message21, message1); - } - - #[test] - fn into_midi_1_zero_velocity() { - use crate::channel_voice1; - use crate::traits::{Channeled, Grouped}; - use std::vec::Vec; - - 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(0x0000); - - assert_eq!(message2, NoteOn([0x4898_5E00, 0x0000_0000, 0x0, 0x0]),); - - let mut message1 = channel_voice1::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: channel_voice1::NoteOn> = message2.into(); - - assert_eq!(message21, message1); - } - - #[test] - fn into_midi_1_with_dest() { - use crate::channel_voice1; - 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); - - assert_eq!(message2, NoteOn([0x4898_5E00, 0x8000_0000, 0x0, 0x0]),); - - let mut message1 = 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 message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); - let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); - - assert_eq!(message21, message1); - } - - #[test] - fn into_midi_1_zero_velocity_with_dest() { - use crate::channel_voice1; - 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(0x0000); - - assert_eq!(message2, NoteOn([0x4898_5E00, 0x0000_0000, 0x0, 0x0]),); - - let mut message1 = 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(0x01)); - - let message21 = channel_voice1::NoteOn::<[u32; 4]>::new(); - let message21: channel_voice1::NoteOn<[u32; 4]> = (message2, message21).into(); - - assert_eq!(message21, message1); - } } From d3763f0b43f144fe642a406c9fff96b33058d47c Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sun, 25 Jan 2026 19:41:33 -0700 Subject: [PATCH 17/30] refactor: Move conversion module to its own file. --- midi2/src/channel_voice1/note_on.rs | 2 +- midi2/src/conversion.rs | 322 +++++++++++++++++++++++++++ midi2/src/lib.rs | 1 + midi2/src/traits.rs | 323 ---------------------------- 4 files changed, 324 insertions(+), 324 deletions(-) create mode 100644 midi2/src/conversion.rs diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 592208e..84994ce 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -50,7 +50,7 @@ impl< > From<(crate::channel_voice2::NoteOn, NoteOn)> for NoteOn { fn from(val: (crate::channel_voice2::NoteOn, NoteOn)) -> Self { - use crate::traits::conversion::MinCenterMax; + use crate::conversion::conversion::MinCenterMax; use crate::traits::{Channeled, Grouped}; let (src, mut dest) = val; diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs new file mode 100644 index 0000000..88aa50e --- /dev/null +++ b/midi2/src/conversion.rs @@ -0,0 +1,322 @@ +#[allow(dead_code)] +pub(crate) mod conversion { + pub(crate) trait Center: + Into + TryFrom + Sized + Copy + PartialEq + PartialOrd + where + >::Error: core::fmt::Debug, + { + const MIN: Self; + const MAX: Self; + + 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 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, + { + fn 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 upscaled = initial; + let mut remainder = other_bits - self_bits; + + while remainder > 0 { + remainder = match remainder { + remainder if remainder > shift => { + upscaled = (upscaled << shift) | repeating; + remainder - shift + } + _ => { + let shift = shift - remainder; + upscaled = (upscaled << remainder) | (repeating >> shift); + 0 + } + }; + } + upscaled + } + + _ => self.into(), + } + } + + fn 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 downscaled = self >> shift; + let downscaled: Self = downscaled.into(); + + downscaled.try_into().expect("Downscaling should not fail.") + } + } + + impl> MinCenterMax for U + where + >::Error: core::fmt::Debug, + >::Output: Into, + { + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn test_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.upscale::(), min_u7); + assert_eq!(min_u7.upscale::(), min_u9); + assert_eq!(min_u7.upscale::(), min_u14); + assert_eq!(min_u7.upscale::(), min_u16); + assert_eq!(min_u7.upscale::(), min_u32); + + assert_eq!(min_u9.upscale::(), min_u9); + assert_eq!(min_u9.upscale::(), min_u14); + assert_eq!(min_u9.upscale::(), min_u16); + assert_eq!(min_u9.upscale::(), min_u32); + + assert_eq!(min_u14.upscale::(), min_u14); + assert_eq!(min_u14.upscale::(), min_u16); + assert_eq!(min_u14.upscale::(), min_u32); + + assert_eq!(min_u16.upscale::(), min_u16); + assert_eq!(min_u16.upscale::(), min_u32); + + assert_eq!(min_u32.upscale::(), min_u32); + } + + #[test] + fn test_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.upscale::(), max_u7); + assert_eq!(max_u7.upscale::(), max_u9); + assert_eq!(max_u7.upscale::(), max_u14); + assert_eq!(max_u7.upscale::(), max_u16); + assert_eq!(max_u7.upscale::(), max_u32); + + assert_eq!(max_u9.upscale::(), max_u9); + assert_eq!(max_u9.upscale::(), max_u14); + assert_eq!(max_u9.upscale::(), max_u16); + assert_eq!(max_u9.upscale::(), max_u32); + + assert_eq!(max_u14.upscale::(), max_u14); + assert_eq!(max_u14.upscale::(), max_u16); + assert_eq!(max_u14.upscale::(), max_u32); + + assert_eq!(max_u16.upscale::(), max_u16); + assert_eq!(max_u16.upscale::(), max_u32); + + assert_eq!(max_u32.upscale::(), max_u32); + } + + #[test] + fn test_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.upscale::(), center_u7); + assert_eq!(center_u7.upscale::(), center_u9); + assert_eq!(center_u7.upscale::(), center_u14); + assert_eq!(center_u7.upscale::(), center_u16); + assert_eq!(center_u7.upscale::(), center_u32); + + assert_eq!(center_u9.upscale::(), center_u9); + assert_eq!(center_u9.upscale::(), center_u14); + assert_eq!(center_u9.upscale::(), center_u16); + assert_eq!(center_u9.upscale::(), center_u32); + + assert_eq!(center_u14.upscale::(), center_u14); + assert_eq!(center_u14.upscale::(), center_u16); + assert_eq!(center_u14.upscale::(), center_u32); + + assert_eq!(center_u16.upscale::(), center_u16); + assert_eq!(center_u16.upscale::(), center_u32); + + assert_eq!(center_u32.upscale::(), center_u32); + } + + #[test] + fn test_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.upscale::(), lower_u7); + assert_eq!(lower_u7.upscale::(), lower_u9); + assert_eq!(lower_u7.upscale::(), lower_u14); + assert_eq!(lower_u7.upscale::(), lower_u16); + assert_eq!(lower_u7.upscale::(), lower_u32); + + assert_eq!(lower_u9.upscale::(), lower_u9); + assert_eq!(lower_u9.upscale::(), lower_u14); + assert_eq!(lower_u9.upscale::(), lower_u16); + assert_eq!(lower_u9.upscale::(), lower_u32); + + assert_eq!(lower_u14.upscale::(), lower_u14); + assert_eq!(lower_u14.upscale::(), lower_u16); + assert_eq!(lower_u14.upscale::(), lower_u32); + + assert_eq!(lower_u16.upscale::(), lower_u16); + assert_eq!(lower_u16.upscale::(), lower_u32); + + assert_eq!(lower_u32.upscale::(), lower_u32); + } + + #[test] + fn test_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).upscale::(), 0x8C30_u16), + (ux::u7::new(0x60).upscale::(), 0xC104_u16), + (ux::u7::new(0x78).upscale::(), 0xF1C7_u16), + ]; + + let upper_u7_to_u32 = [ + (ux::u7::new(0x46).upscale::(), 0x8C30C30C_u32), + (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), + (ux::u7::new(0x78).upscale::(), 0xF1C71C71_u32), + ]; + + let upper_u16_to_u32 = [ + (0x9C40_u16.upscale::(), 0x9C403880_u32), + (0xC000_u16.upscale::(), 0xC0008001_u32), + (0xFDE8_u16.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_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.downscale::(), lower_u7); + assert_eq!(lower_u32.downscale::(), lower_u9); + assert_eq!(lower_u32.downscale::(), lower_u14); + assert_eq!(lower_u32.downscale::(), lower_u16); + assert_eq!(lower_u32.downscale::(), lower_u32); + + assert_eq!(lower_u16.downscale::(), lower_u7); + assert_eq!(lower_u16.downscale::(), lower_u9); + assert_eq!(lower_u16.downscale::(), lower_u14); + assert_eq!(lower_u16.downscale::(), lower_u16); + + assert_eq!(lower_u14.downscale::(), lower_u7); + assert_eq!(lower_u14.downscale::(), lower_u9); + assert_eq!(lower_u14.downscale::(), lower_u14); + + assert_eq!(lower_u9.downscale::(), lower_u7); + assert_eq!(lower_u9.downscale::(), lower_u9); + + assert_eq!(lower_u7.downscale::(), lower_u7); + } + } +} 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 3410854..348a5f9 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -731,326 +731,3 @@ pub(crate) trait BufferAccess { where B: BufferMut; } - -#[allow(dead_code)] -pub(crate) mod conversion { - pub(crate) trait Center: - Into + TryFrom + Sized + Copy + PartialEq + PartialOrd - where - >::Error: core::fmt::Debug, - { - const MIN: Self; - const MAX: Self; - - 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 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, - { - fn 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 upscaled = initial; - let mut remainder = other_bits - self_bits; - - while remainder > 0 { - remainder = match remainder { - remainder if remainder > shift => { - upscaled = (upscaled << shift) | repeating; - remainder - shift - } - _ => { - let shift = shift - remainder; - upscaled = (upscaled << remainder) | (repeating >> shift); - 0 - } - }; - } - upscaled - } - - _ => self.into(), - } - } - - fn 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 downscaled = self >> shift; - let downscaled: Self = downscaled.into(); - - downscaled.try_into().expect("Downscaling should not fail.") - } - } - - impl> MinCenterMax for U - where - >::Error: core::fmt::Debug, - >::Output: Into, - { - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_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.upscale::(), min_u7); - assert_eq!(min_u7.upscale::(), min_u9); - assert_eq!(min_u7.upscale::(), min_u14); - assert_eq!(min_u7.upscale::(), min_u16); - assert_eq!(min_u7.upscale::(), min_u32); - - assert_eq!(min_u9.upscale::(), min_u9); - assert_eq!(min_u9.upscale::(), min_u14); - assert_eq!(min_u9.upscale::(), min_u16); - assert_eq!(min_u9.upscale::(), min_u32); - - assert_eq!(min_u14.upscale::(), min_u14); - assert_eq!(min_u14.upscale::(), min_u16); - assert_eq!(min_u14.upscale::(), min_u32); - - assert_eq!(min_u16.upscale::(), min_u16); - assert_eq!(min_u16.upscale::(), min_u32); - - assert_eq!(min_u32.upscale::(), min_u32); - } - - #[test] - fn test_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.upscale::(), max_u7); - assert_eq!(max_u7.upscale::(), max_u9); - assert_eq!(max_u7.upscale::(), max_u14); - assert_eq!(max_u7.upscale::(), max_u16); - assert_eq!(max_u7.upscale::(), max_u32); - - assert_eq!(max_u9.upscale::(), max_u9); - assert_eq!(max_u9.upscale::(), max_u14); - assert_eq!(max_u9.upscale::(), max_u16); - assert_eq!(max_u9.upscale::(), max_u32); - - assert_eq!(max_u14.upscale::(), max_u14); - assert_eq!(max_u14.upscale::(), max_u16); - assert_eq!(max_u14.upscale::(), max_u32); - - assert_eq!(max_u16.upscale::(), max_u16); - assert_eq!(max_u16.upscale::(), max_u32); - - assert_eq!(max_u32.upscale::(), max_u32); - } - - #[test] - fn test_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.upscale::(), center_u7); - assert_eq!(center_u7.upscale::(), center_u9); - assert_eq!(center_u7.upscale::(), center_u14); - assert_eq!(center_u7.upscale::(), center_u16); - assert_eq!(center_u7.upscale::(), center_u32); - - assert_eq!(center_u9.upscale::(), center_u9); - assert_eq!(center_u9.upscale::(), center_u14); - assert_eq!(center_u9.upscale::(), center_u16); - assert_eq!(center_u9.upscale::(), center_u32); - - assert_eq!(center_u14.upscale::(), center_u14); - assert_eq!(center_u14.upscale::(), center_u16); - assert_eq!(center_u14.upscale::(), center_u32); - - assert_eq!(center_u16.upscale::(), center_u16); - assert_eq!(center_u16.upscale::(), center_u32); - - assert_eq!(center_u32.upscale::(), center_u32); - } - - #[test] - fn test_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.upscale::(), lower_u7); - assert_eq!(lower_u7.upscale::(), lower_u9); - assert_eq!(lower_u7.upscale::(), lower_u14); - assert_eq!(lower_u7.upscale::(), lower_u16); - assert_eq!(lower_u7.upscale::(), lower_u32); - - assert_eq!(lower_u9.upscale::(), lower_u9); - assert_eq!(lower_u9.upscale::(), lower_u14); - assert_eq!(lower_u9.upscale::(), lower_u16); - assert_eq!(lower_u9.upscale::(), lower_u32); - - assert_eq!(lower_u14.upscale::(), lower_u14); - assert_eq!(lower_u14.upscale::(), lower_u16); - assert_eq!(lower_u14.upscale::(), lower_u32); - - assert_eq!(lower_u16.upscale::(), lower_u16); - assert_eq!(lower_u16.upscale::(), lower_u32); - - assert_eq!(lower_u32.upscale::(), lower_u32); - } - - #[test] - fn test_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).upscale::(), 0x8C30_u16), - (ux::u7::new(0x60).upscale::(), 0xC104_u16), - (ux::u7::new(0x78).upscale::(), 0xF1C7_u16), - ]; - - let upper_u7_to_u32 = [ - (ux::u7::new(0x46).upscale::(), 0x8C30C30C_u32), - (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), - (ux::u7::new(0x78).upscale::(), 0xF1C71C71_u32), - ]; - - let upper_u16_to_u32 = [ - (0x9C40_u16.upscale::(), 0x9C403880_u32), - (0xC000_u16.upscale::(), 0xC0008001_u32), - (0xFDE8_u16.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_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.downscale::(), lower_u7); - assert_eq!(lower_u32.downscale::(), lower_u9); - assert_eq!(lower_u32.downscale::(), lower_u14); - assert_eq!(lower_u32.downscale::(), lower_u16); - assert_eq!(lower_u32.downscale::(), lower_u32); - - assert_eq!(lower_u16.downscale::(), lower_u7); - assert_eq!(lower_u16.downscale::(), lower_u9); - assert_eq!(lower_u16.downscale::(), lower_u14); - assert_eq!(lower_u16.downscale::(), lower_u16); - - assert_eq!(lower_u14.downscale::(), lower_u7); - assert_eq!(lower_u14.downscale::(), lower_u9); - assert_eq!(lower_u14.downscale::(), lower_u14); - - assert_eq!(lower_u9.downscale::(), lower_u7); - assert_eq!(lower_u9.downscale::(), lower_u9); - - assert_eq!(lower_u7.downscale::(), lower_u7); - } - } -} From a49d3a6a27b92543e2d21cdbc0d779290d50044f Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 29 Jan 2026 08:50:11 -0700 Subject: [PATCH 18/30] test: Added downscaling test for center values. --- midi2/src/conversion.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index 88aa50e..cca6fef 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -318,5 +318,34 @@ pub(crate) mod conversion { assert_eq!(lower_u7.downscale::(), lower_u7); } + + #[test] + fn test_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.downscale::(), center_u7); + assert_eq!(center_u32.downscale::(), center_u9); + assert_eq!(center_u32.downscale::(), center_u14); + assert_eq!(center_u32.downscale::(), center_u16); + assert_eq!(center_u32.downscale::(), center_u32); + + assert_eq!(center_u16.downscale::(), center_u7); + assert_eq!(center_u16.downscale::(), center_u9); + assert_eq!(center_u16.downscale::(), center_u14); + assert_eq!(center_u16.downscale::(), center_u16); + + assert_eq!(center_u14.downscale::(), center_u7); + assert_eq!(center_u14.downscale::(), center_u9); + assert_eq!(center_u14.downscale::(), center_u14); + + assert_eq!(center_u9.downscale::(), center_u7); + assert_eq!(center_u9.downscale::(), center_u9); + + assert_eq!(center_u7.downscale::(), center_u7); + } } } From 9baf1fcd4a8f48aaa347783336f7b2f2f5f6cf2a Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 29 Jan 2026 09:06:03 -0700 Subject: [PATCH 19/30] test: Added min, max, and example downscaling tests. --- midi2/src/conversion.rs | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index cca6fef..276530e 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -347,5 +347,77 @@ pub(crate) mod conversion { assert_eq!(center_u7.downscale::(), center_u7); } + + #[test] + fn test_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.downscale::(), min_u7); + assert_eq!(min_u32.downscale::(), min_u9); + assert_eq!(min_u32.downscale::(), min_u14); + assert_eq!(min_u32.downscale::(), min_u16); + assert_eq!(min_u32.downscale::(), min_u32); + + assert_eq!(min_u16.downscale::(), min_u7); + assert_eq!(min_u16.downscale::(), min_u9); + assert_eq!(min_u16.downscale::(), min_u14); + assert_eq!(min_u16.downscale::(), min_u16); + + assert_eq!(min_u14.downscale::(), min_u7); + assert_eq!(min_u14.downscale::(), min_u9); + assert_eq!(min_u14.downscale::(), min_u14); + + assert_eq!(min_u9.downscale::(), min_u7); + assert_eq!(min_u9.downscale::(), min_u9); + + assert_eq!(min_u7.downscale::(), min_u7); + } + + #[test] + fn test_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.downscale::(), max_u7); + assert_eq!(max_u32.downscale::(), max_u9); + assert_eq!(max_u32.downscale::(), max_u14); + assert_eq!(max_u32.downscale::(), max_u16); + assert_eq!(max_u32.downscale::(), max_u32); + + assert_eq!(max_u16.downscale::(), max_u7); + assert_eq!(max_u16.downscale::(), max_u9); + assert_eq!(max_u16.downscale::(), max_u14); + assert_eq!(max_u16.downscale::(), max_u16); + + assert_eq!(max_u14.downscale::(), max_u7); + assert_eq!(max_u14.downscale::(), max_u9); + assert_eq!(max_u14.downscale::(), max_u14); + + assert_eq!(max_u9.downscale::(), max_u7); + assert_eq!(max_u9.downscale::(), max_u9); + + assert_eq!(max_u7.downscale::(), max_u7); + } + + #[test] + fn test_example_downscaling() { + let examples = [ + (5120_u16.downscale::(), ux::u7::new(10)), + (32768_u16.downscale::(), ux::u7::new(64)), + (44730_u16.downscale::(), ux::u7::new(87)), + (65535_u16.downscale::(), ux::u7::new(127)), + ]; + + for (input, expected) in examples { + assert_eq!(input, expected); + } + } } } From 6ea9378bd84d5fb33b319ddf917d0b8d0110b5ae Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 29 Jan 2026 09:12:06 -0700 Subject: [PATCH 20/30] style: Removed unnesecary module nesting in conversion.rs. --- midi2/src/channel_voice1/note_on.rs | 2 +- midi2/src/conversion.rs | 770 ++++++++++++++-------------- 2 files changed, 385 insertions(+), 387 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 84994ce..b8601a2 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -50,7 +50,7 @@ impl< > From<(crate::channel_voice2::NoteOn, NoteOn)> for NoteOn { fn from(val: (crate::channel_voice2::NoteOn, NoteOn)) -> Self { - use crate::conversion::conversion::MinCenterMax; + use crate::conversion::MinCenterMax; use crate::traits::{Channeled, Grouped}; let (src, mut dest) = val; diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index 276530e..5691568 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -1,423 +1,421 @@ -#[allow(dead_code)] -pub(crate) mod conversion { - pub(crate) trait Center: - Into + TryFrom + Sized + Copy + PartialEq + PartialOrd - where - >::Error: core::fmt::Debug, - { - const MIN: Self; - const MAX: Self; - - 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.") - } +pub(crate) trait Center: + Into + TryFrom + Sized + Copy + PartialEq + PartialOrd +where + >::Error: core::fmt::Debug, +{ + const MIN: Self; + const MAX: Self; + + 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::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::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 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 u16 { + const MIN: Self = Self::MIN; + const MAX: Self = Self::MAX; +} - impl Center for u32 { - 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 +pub(crate) trait MinCenterMax: Center + core::ops::Shr +where + >::Output: Into, + >::Error: core::fmt::Debug, +{ + #[allow(dead_code)] + fn upscale< + U: Center + + core::ops::Add + + core::ops::BitAnd + + core::fmt::Debug + + core::ops::BitOr + + core::ops::Shr + + core::ops::Shl, + >( + self, + ) -> U where - >::Output: Into, - >::Error: core::fmt::Debug, + Self: Into, + >::Error: core::fmt::Debug, { - fn 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 - } + 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 upscaled = initial; - let mut remainder = other_bits - self_bits; - - while remainder > 0 { - remainder = match remainder { - remainder if remainder > shift => { - upscaled = (upscaled << shift) | repeating; - remainder - shift - } - _ => { - let shift = shift - remainder; - upscaled = (upscaled << remainder) | (repeating >> shift); - 0 - } - }; - } - upscaled + 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 upscaled = initial; + let mut remainder = other_bits - self_bits; + + while remainder > 0 { + remainder = match remainder { + remainder if remainder > shift => { + upscaled = (upscaled << shift) | repeating; + remainder - shift + } + _ => { + let shift = shift - remainder; + upscaled = (upscaled << remainder) | (repeating >> shift); + 0 + } + }; } - - _ => self.into(), + upscaled } - } - fn 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 downscaled = self >> shift; - let downscaled: Self = downscaled.into(); - - downscaled.try_into().expect("Downscaling should not fail.") + _ => self.into(), } } - impl> MinCenterMax for U + fn downscale>(self) -> U where >::Error: core::fmt::Debug, - >::Output: Into, + >::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 downscaled = self >> shift; + let downscaled: Self = downscaled.into(); - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn test_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.upscale::(), min_u7); - assert_eq!(min_u7.upscale::(), min_u9); - assert_eq!(min_u7.upscale::(), min_u14); - assert_eq!(min_u7.upscale::(), min_u16); - assert_eq!(min_u7.upscale::(), min_u32); - - assert_eq!(min_u9.upscale::(), min_u9); - assert_eq!(min_u9.upscale::(), min_u14); - assert_eq!(min_u9.upscale::(), min_u16); - assert_eq!(min_u9.upscale::(), min_u32); - - assert_eq!(min_u14.upscale::(), min_u14); - assert_eq!(min_u14.upscale::(), min_u16); - assert_eq!(min_u14.upscale::(), min_u32); - - assert_eq!(min_u16.upscale::(), min_u16); - assert_eq!(min_u16.upscale::(), min_u32); - - assert_eq!(min_u32.upscale::(), min_u32); - } + downscaled.try_into().expect("Downscaling should not fail.") + } +} - #[test] - fn test_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.upscale::(), max_u7); - assert_eq!(max_u7.upscale::(), max_u9); - assert_eq!(max_u7.upscale::(), max_u14); - assert_eq!(max_u7.upscale::(), max_u16); - assert_eq!(max_u7.upscale::(), max_u32); - - assert_eq!(max_u9.upscale::(), max_u9); - assert_eq!(max_u9.upscale::(), max_u14); - assert_eq!(max_u9.upscale::(), max_u16); - assert_eq!(max_u9.upscale::(), max_u32); - - assert_eq!(max_u14.upscale::(), max_u14); - assert_eq!(max_u14.upscale::(), max_u16); - assert_eq!(max_u14.upscale::(), max_u32); - - assert_eq!(max_u16.upscale::(), max_u16); - assert_eq!(max_u16.upscale::(), max_u32); - - assert_eq!(max_u32.upscale::(), max_u32); - } +impl> MinCenterMax for U +where + >::Error: core::fmt::Debug, + >::Output: Into, +{ +} - #[test] - fn test_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.upscale::(), center_u7); - assert_eq!(center_u7.upscale::(), center_u9); - assert_eq!(center_u7.upscale::(), center_u14); - assert_eq!(center_u7.upscale::(), center_u16); - assert_eq!(center_u7.upscale::(), center_u32); - - assert_eq!(center_u9.upscale::(), center_u9); - assert_eq!(center_u9.upscale::(), center_u14); - assert_eq!(center_u9.upscale::(), center_u16); - assert_eq!(center_u9.upscale::(), center_u32); - - assert_eq!(center_u14.upscale::(), center_u14); - assert_eq!(center_u14.upscale::(), center_u16); - assert_eq!(center_u14.upscale::(), center_u32); - - assert_eq!(center_u16.upscale::(), center_u16); - assert_eq!(center_u16.upscale::(), center_u32); - - assert_eq!(center_u32.upscale::(), center_u32); - } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_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.upscale::(), min_u7); + assert_eq!(min_u7.upscale::(), min_u9); + assert_eq!(min_u7.upscale::(), min_u14); + assert_eq!(min_u7.upscale::(), min_u16); + assert_eq!(min_u7.upscale::(), min_u32); + + assert_eq!(min_u9.upscale::(), min_u9); + assert_eq!(min_u9.upscale::(), min_u14); + assert_eq!(min_u9.upscale::(), min_u16); + assert_eq!(min_u9.upscale::(), min_u32); + + assert_eq!(min_u14.upscale::(), min_u14); + assert_eq!(min_u14.upscale::(), min_u16); + assert_eq!(min_u14.upscale::(), min_u32); + + assert_eq!(min_u16.upscale::(), min_u16); + assert_eq!(min_u16.upscale::(), min_u32); + + assert_eq!(min_u32.upscale::(), min_u32); + } - #[test] - fn test_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.upscale::(), lower_u7); - assert_eq!(lower_u7.upscale::(), lower_u9); - assert_eq!(lower_u7.upscale::(), lower_u14); - assert_eq!(lower_u7.upscale::(), lower_u16); - assert_eq!(lower_u7.upscale::(), lower_u32); - - assert_eq!(lower_u9.upscale::(), lower_u9); - assert_eq!(lower_u9.upscale::(), lower_u14); - assert_eq!(lower_u9.upscale::(), lower_u16); - assert_eq!(lower_u9.upscale::(), lower_u32); - - assert_eq!(lower_u14.upscale::(), lower_u14); - assert_eq!(lower_u14.upscale::(), lower_u16); - assert_eq!(lower_u14.upscale::(), lower_u32); - - assert_eq!(lower_u16.upscale::(), lower_u16); - assert_eq!(lower_u16.upscale::(), lower_u32); - - assert_eq!(lower_u32.upscale::(), lower_u32); - } + #[test] + fn test_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.upscale::(), max_u7); + assert_eq!(max_u7.upscale::(), max_u9); + assert_eq!(max_u7.upscale::(), max_u14); + assert_eq!(max_u7.upscale::(), max_u16); + assert_eq!(max_u7.upscale::(), max_u32); + + assert_eq!(max_u9.upscale::(), max_u9); + assert_eq!(max_u9.upscale::(), max_u14); + assert_eq!(max_u9.upscale::(), max_u16); + assert_eq!(max_u9.upscale::(), max_u32); + + assert_eq!(max_u14.upscale::(), max_u14); + assert_eq!(max_u14.upscale::(), max_u16); + assert_eq!(max_u14.upscale::(), max_u32); + + assert_eq!(max_u16.upscale::(), max_u16); + assert_eq!(max_u16.upscale::(), max_u32); + + assert_eq!(max_u32.upscale::(), max_u32); + } - #[test] - fn test_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).upscale::(), 0x8C30_u16), - (ux::u7::new(0x60).upscale::(), 0xC104_u16), - (ux::u7::new(0x78).upscale::(), 0xF1C7_u16), - ]; - - let upper_u7_to_u32 = [ - (ux::u7::new(0x46).upscale::(), 0x8C30C30C_u32), - (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), - (ux::u7::new(0x78).upscale::(), 0xF1C71C71_u32), - ]; - - let upper_u16_to_u32 = [ - (0x9C40_u16.upscale::(), 0x9C403880_u32), - (0xC000_u16.upscale::(), 0xC0008001_u32), - (0xFDE8_u16.upscale::(), 0xFDE8FBD1_u32), - ]; - - for (result, expected) in upper_u7_to_u16 { - assert_eq!(result, expected); - } + #[test] + fn test_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.upscale::(), center_u7); + assert_eq!(center_u7.upscale::(), center_u9); + assert_eq!(center_u7.upscale::(), center_u14); + assert_eq!(center_u7.upscale::(), center_u16); + assert_eq!(center_u7.upscale::(), center_u32); + + assert_eq!(center_u9.upscale::(), center_u9); + assert_eq!(center_u9.upscale::(), center_u14); + assert_eq!(center_u9.upscale::(), center_u16); + assert_eq!(center_u9.upscale::(), center_u32); + + assert_eq!(center_u14.upscale::(), center_u14); + assert_eq!(center_u14.upscale::(), center_u16); + assert_eq!(center_u14.upscale::(), center_u32); + + assert_eq!(center_u16.upscale::(), center_u16); + assert_eq!(center_u16.upscale::(), center_u32); + + assert_eq!(center_u32.upscale::(), center_u32); + } - for (result, expected) in upper_u7_to_u32 { - assert_eq!(result, expected); - } + #[test] + fn test_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.upscale::(), lower_u7); + assert_eq!(lower_u7.upscale::(), lower_u9); + assert_eq!(lower_u7.upscale::(), lower_u14); + assert_eq!(lower_u7.upscale::(), lower_u16); + assert_eq!(lower_u7.upscale::(), lower_u32); + + assert_eq!(lower_u9.upscale::(), lower_u9); + assert_eq!(lower_u9.upscale::(), lower_u14); + assert_eq!(lower_u9.upscale::(), lower_u16); + assert_eq!(lower_u9.upscale::(), lower_u32); + + assert_eq!(lower_u14.upscale::(), lower_u14); + assert_eq!(lower_u14.upscale::(), lower_u16); + assert_eq!(lower_u14.upscale::(), lower_u32); + + assert_eq!(lower_u16.upscale::(), lower_u16); + assert_eq!(lower_u16.upscale::(), lower_u32); + + assert_eq!(lower_u32.upscale::(), lower_u32); + } - for (result, expected) in upper_u16_to_u32 { - assert_eq!(result, expected); - } + #[test] + fn test_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).upscale::(), 0x8C30_u16), + (ux::u7::new(0x60).upscale::(), 0xC104_u16), + (ux::u7::new(0x78).upscale::(), 0xF1C7_u16), + ]; + + let upper_u7_to_u32 = [ + (ux::u7::new(0x46).upscale::(), 0x8C30C30C_u32), + (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), + (ux::u7::new(0x78).upscale::(), 0xF1C71C71_u32), + ]; + + let upper_u16_to_u32 = [ + (0x9C40_u16.upscale::(), 0x9C403880_u32), + (0xC000_u16.upscale::(), 0xC0008001_u32), + (0xFDE8_u16.upscale::(), 0xFDE8FBD1_u32), + ]; + + for (result, expected) in upper_u7_to_u16 { + assert_eq!(result, expected); } - #[test] - fn test_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.downscale::(), lower_u7); - assert_eq!(lower_u32.downscale::(), lower_u9); - assert_eq!(lower_u32.downscale::(), lower_u14); - assert_eq!(lower_u32.downscale::(), lower_u16); - assert_eq!(lower_u32.downscale::(), lower_u32); - - assert_eq!(lower_u16.downscale::(), lower_u7); - assert_eq!(lower_u16.downscale::(), lower_u9); - assert_eq!(lower_u16.downscale::(), lower_u14); - assert_eq!(lower_u16.downscale::(), lower_u16); - - assert_eq!(lower_u14.downscale::(), lower_u7); - assert_eq!(lower_u14.downscale::(), lower_u9); - assert_eq!(lower_u14.downscale::(), lower_u14); - - assert_eq!(lower_u9.downscale::(), lower_u7); - assert_eq!(lower_u9.downscale::(), lower_u9); - - assert_eq!(lower_u7.downscale::(), lower_u7); + for (result, expected) in upper_u7_to_u32 { + assert_eq!(result, expected); } - #[test] - fn test_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.downscale::(), center_u7); - assert_eq!(center_u32.downscale::(), center_u9); - assert_eq!(center_u32.downscale::(), center_u14); - assert_eq!(center_u32.downscale::(), center_u16); - assert_eq!(center_u32.downscale::(), center_u32); - - assert_eq!(center_u16.downscale::(), center_u7); - assert_eq!(center_u16.downscale::(), center_u9); - assert_eq!(center_u16.downscale::(), center_u14); - assert_eq!(center_u16.downscale::(), center_u16); - - assert_eq!(center_u14.downscale::(), center_u7); - assert_eq!(center_u14.downscale::(), center_u9); - assert_eq!(center_u14.downscale::(), center_u14); - - assert_eq!(center_u9.downscale::(), center_u7); - assert_eq!(center_u9.downscale::(), center_u9); - - assert_eq!(center_u7.downscale::(), center_u7); + for (result, expected) in upper_u16_to_u32 { + assert_eq!(result, expected); } + } - #[test] - fn test_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.downscale::(), min_u7); - assert_eq!(min_u32.downscale::(), min_u9); - assert_eq!(min_u32.downscale::(), min_u14); - assert_eq!(min_u32.downscale::(), min_u16); - assert_eq!(min_u32.downscale::(), min_u32); - - assert_eq!(min_u16.downscale::(), min_u7); - assert_eq!(min_u16.downscale::(), min_u9); - assert_eq!(min_u16.downscale::(), min_u14); - assert_eq!(min_u16.downscale::(), min_u16); - - assert_eq!(min_u14.downscale::(), min_u7); - assert_eq!(min_u14.downscale::(), min_u9); - assert_eq!(min_u14.downscale::(), min_u14); - - assert_eq!(min_u9.downscale::(), min_u7); - assert_eq!(min_u9.downscale::(), min_u9); - - assert_eq!(min_u7.downscale::(), min_u7); - } + #[test] + fn test_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.downscale::(), lower_u7); + assert_eq!(lower_u32.downscale::(), lower_u9); + assert_eq!(lower_u32.downscale::(), lower_u14); + assert_eq!(lower_u32.downscale::(), lower_u16); + assert_eq!(lower_u32.downscale::(), lower_u32); + + assert_eq!(lower_u16.downscale::(), lower_u7); + assert_eq!(lower_u16.downscale::(), lower_u9); + assert_eq!(lower_u16.downscale::(), lower_u14); + assert_eq!(lower_u16.downscale::(), lower_u16); + + assert_eq!(lower_u14.downscale::(), lower_u7); + assert_eq!(lower_u14.downscale::(), lower_u9); + assert_eq!(lower_u14.downscale::(), lower_u14); + + assert_eq!(lower_u9.downscale::(), lower_u7); + assert_eq!(lower_u9.downscale::(), lower_u9); + + assert_eq!(lower_u7.downscale::(), lower_u7); + } - #[test] - fn test_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.downscale::(), max_u7); - assert_eq!(max_u32.downscale::(), max_u9); - assert_eq!(max_u32.downscale::(), max_u14); - assert_eq!(max_u32.downscale::(), max_u16); - assert_eq!(max_u32.downscale::(), max_u32); - - assert_eq!(max_u16.downscale::(), max_u7); - assert_eq!(max_u16.downscale::(), max_u9); - assert_eq!(max_u16.downscale::(), max_u14); - assert_eq!(max_u16.downscale::(), max_u16); - - assert_eq!(max_u14.downscale::(), max_u7); - assert_eq!(max_u14.downscale::(), max_u9); - assert_eq!(max_u14.downscale::(), max_u14); - - assert_eq!(max_u9.downscale::(), max_u7); - assert_eq!(max_u9.downscale::(), max_u9); - - assert_eq!(max_u7.downscale::(), max_u7); - } + #[test] + fn test_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.downscale::(), center_u7); + assert_eq!(center_u32.downscale::(), center_u9); + assert_eq!(center_u32.downscale::(), center_u14); + assert_eq!(center_u32.downscale::(), center_u16); + assert_eq!(center_u32.downscale::(), center_u32); + + assert_eq!(center_u16.downscale::(), center_u7); + assert_eq!(center_u16.downscale::(), center_u9); + assert_eq!(center_u16.downscale::(), center_u14); + assert_eq!(center_u16.downscale::(), center_u16); + + assert_eq!(center_u14.downscale::(), center_u7); + assert_eq!(center_u14.downscale::(), center_u9); + assert_eq!(center_u14.downscale::(), center_u14); + + assert_eq!(center_u9.downscale::(), center_u7); + assert_eq!(center_u9.downscale::(), center_u9); + + assert_eq!(center_u7.downscale::(), center_u7); + } - #[test] - fn test_example_downscaling() { - let examples = [ - (5120_u16.downscale::(), ux::u7::new(10)), - (32768_u16.downscale::(), ux::u7::new(64)), - (44730_u16.downscale::(), ux::u7::new(87)), - (65535_u16.downscale::(), ux::u7::new(127)), - ]; - - for (input, expected) in examples { - assert_eq!(input, expected); - } + #[test] + fn test_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.downscale::(), min_u7); + assert_eq!(min_u32.downscale::(), min_u9); + assert_eq!(min_u32.downscale::(), min_u14); + assert_eq!(min_u32.downscale::(), min_u16); + assert_eq!(min_u32.downscale::(), min_u32); + + assert_eq!(min_u16.downscale::(), min_u7); + assert_eq!(min_u16.downscale::(), min_u9); + assert_eq!(min_u16.downscale::(), min_u14); + assert_eq!(min_u16.downscale::(), min_u16); + + assert_eq!(min_u14.downscale::(), min_u7); + assert_eq!(min_u14.downscale::(), min_u9); + assert_eq!(min_u14.downscale::(), min_u14); + + assert_eq!(min_u9.downscale::(), min_u7); + assert_eq!(min_u9.downscale::(), min_u9); + + assert_eq!(min_u7.downscale::(), min_u7); + } + + #[test] + fn test_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.downscale::(), max_u7); + assert_eq!(max_u32.downscale::(), max_u9); + assert_eq!(max_u32.downscale::(), max_u14); + assert_eq!(max_u32.downscale::(), max_u16); + assert_eq!(max_u32.downscale::(), max_u32); + + assert_eq!(max_u16.downscale::(), max_u7); + assert_eq!(max_u16.downscale::(), max_u9); + assert_eq!(max_u16.downscale::(), max_u14); + assert_eq!(max_u16.downscale::(), max_u16); + + assert_eq!(max_u14.downscale::(), max_u7); + assert_eq!(max_u14.downscale::(), max_u9); + assert_eq!(max_u14.downscale::(), max_u14); + + assert_eq!(max_u9.downscale::(), max_u7); + assert_eq!(max_u9.downscale::(), max_u9); + + assert_eq!(max_u7.downscale::(), max_u7); + } + + #[test] + fn test_example_downscaling() { + let examples = [ + (5120_u16.downscale::(), ux::u7::new(10)), + (32768_u16.downscale::(), ux::u7::new(64)), + (44730_u16.downscale::(), ux::u7::new(87)), + (65535_u16.downscale::(), ux::u7::new(127)), + ]; + + for (input, expected) in examples { + assert_eq!(input, expected); } } } From e9a67091e9e7d2f40df290bc21a4e78f29f3ab95 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 29 Jan 2026 11:00:01 -0700 Subject: [PATCH 21/30] feat: Implemented zero extension upscaling. --- midi2/src/channel_voice1/note_on.rs | 2 +- midi2/src/conversion.rs | 351 ++++++++++++++++------------ 2 files changed, 200 insertions(+), 153 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index b8601a2..45f4751 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -63,7 +63,7 @@ impl< // 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().downscale::()), + _ => dest.set_velocity(src.velocity().mcm_downscale::()), } dest } diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index 5691568..e61f1de 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -36,6 +36,11 @@ impl Center for u16 { 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; @@ -47,7 +52,7 @@ where >::Error: core::fmt::Debug, { #[allow(dead_code)] - fn upscale< + fn mcm_upscale< U: Center + core::ops::Add + core::ops::BitAnd @@ -90,30 +95,30 @@ where let initial_mask = U::MAX >> (other_bits - self_bits + 1); let repeating = initial & initial_mask; - let mut upscaled = initial; + let mut mcm_upscaled = initial; let mut remainder = other_bits - self_bits; while remainder > 0 { remainder = match remainder { remainder if remainder > shift => { - upscaled = (upscaled << shift) | repeating; + mcm_upscaled = (mcm_upscaled << shift) | repeating; remainder - shift } _ => { let shift = shift - remainder; - upscaled = (upscaled << remainder) | (repeating >> shift); + mcm_upscaled = (mcm_upscaled << remainder) | (repeating >> shift); 0 } }; } - upscaled + mcm_upscaled } _ => self.into(), } } - fn downscale>(self) -> U + fn mcm_downscale>(self) -> U where >::Error: core::fmt::Debug, >::Error: core::fmt::Debug, @@ -121,10 +126,31 @@ where let self_max: u32 = Self::MAX.into(); let other_max: u32 = U::MAX.into(); let shift = (self_max - other_max).count_ones(); - let downscaled = self >> shift; - let downscaled: Self = downscaled.into(); + let mcm_downscaled = self >> shift; + let mcm_downscaled: Self = mcm_downscaled.into(); + + mcm_downscaled + .try_into() + .expect("Downscaling should not fail.") + } +} + +pub(crate) trait ZeroExtensionScaling: Center +where + >::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; - downscaled.try_into().expect("Downscaling should not fail.") + ze_upscaled.try_into().expect("Upscaling should not fail.") } } @@ -135,145 +161,152 @@ where { } +impl> ZeroExtensionScaling for U +where + >::Error: core::fmt::Debug, + >::Output: Into, +{ +} + #[cfg(test)] mod tests { use super::*; #[test] - fn test_mins_upscaling() { + 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.upscale::(), min_u7); - assert_eq!(min_u7.upscale::(), min_u9); - assert_eq!(min_u7.upscale::(), min_u14); - assert_eq!(min_u7.upscale::(), min_u16); - assert_eq!(min_u7.upscale::(), min_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.upscale::(), min_u9); - assert_eq!(min_u9.upscale::(), min_u14); - assert_eq!(min_u9.upscale::(), min_u16); - assert_eq!(min_u9.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.upscale::(), min_u14); - assert_eq!(min_u14.upscale::(), min_u16); - assert_eq!(min_u14.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.upscale::(), min_u16); - assert_eq!(min_u16.upscale::(), min_u32); + assert_eq!(min_u16.mcm_upscale::(), min_u16); + assert_eq!(min_u16.mcm_upscale::(), min_u32); - assert_eq!(min_u32.upscale::(), min_u32); + assert_eq!(min_u32.mcm_upscale::(), min_u32); } #[test] - fn test_maxs_upscaling() { + 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.upscale::(), max_u7); - assert_eq!(max_u7.upscale::(), max_u9); - assert_eq!(max_u7.upscale::(), max_u14); - assert_eq!(max_u7.upscale::(), max_u16); - assert_eq!(max_u7.upscale::(), max_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.upscale::(), max_u9); - assert_eq!(max_u9.upscale::(), max_u14); - assert_eq!(max_u9.upscale::(), max_u16); - assert_eq!(max_u9.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.upscale::(), max_u14); - assert_eq!(max_u14.upscale::(), max_u16); - assert_eq!(max_u14.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.upscale::(), max_u16); - assert_eq!(max_u16.upscale::(), max_u32); + assert_eq!(max_u16.mcm_upscale::(), max_u16); + assert_eq!(max_u16.mcm_upscale::(), max_u32); - assert_eq!(max_u32.upscale::(), max_u32); + assert_eq!(max_u32.mcm_upscale::(), max_u32); } #[test] - fn test_centers_upscaling() { + 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.upscale::(), center_u7); - assert_eq!(center_u7.upscale::(), center_u9); - assert_eq!(center_u7.upscale::(), center_u14); - assert_eq!(center_u7.upscale::(), center_u16); - assert_eq!(center_u7.upscale::(), center_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.upscale::(), center_u9); - assert_eq!(center_u9.upscale::(), center_u14); - assert_eq!(center_u9.upscale::(), center_u16); - assert_eq!(center_u9.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.upscale::(), center_u14); - assert_eq!(center_u14.upscale::(), center_u16); - assert_eq!(center_u14.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.upscale::(), center_u16); - assert_eq!(center_u16.upscale::(), center_u32); + assert_eq!(center_u16.mcm_upscale::(), center_u16); + assert_eq!(center_u16.mcm_upscale::(), center_u32); - assert_eq!(center_u32.upscale::(), center_u32); + assert_eq!(center_u32.mcm_upscale::(), center_u32); } #[test] - fn test_lower_range_upscaling() { + 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.upscale::(), lower_u7); - assert_eq!(lower_u7.upscale::(), lower_u9); - assert_eq!(lower_u7.upscale::(), lower_u14); - assert_eq!(lower_u7.upscale::(), lower_u16); - assert_eq!(lower_u7.upscale::(), lower_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.upscale::(), lower_u9); - assert_eq!(lower_u9.upscale::(), lower_u14); - assert_eq!(lower_u9.upscale::(), lower_u16); - assert_eq!(lower_u9.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.upscale::(), lower_u14); - assert_eq!(lower_u14.upscale::(), lower_u16); - assert_eq!(lower_u14.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.upscale::(), lower_u16); - assert_eq!(lower_u16.upscale::(), lower_u32); + assert_eq!(lower_u16.mcm_upscale::(), lower_u16); + assert_eq!(lower_u16.mcm_upscale::(), lower_u32); - assert_eq!(lower_u32.upscale::(), lower_u32); + assert_eq!(lower_u32.mcm_upscale::(), lower_u32); } #[test] - fn test_upper_range_upscaling() { + 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).upscale::(), 0x8C30_u16), - (ux::u7::new(0x60).upscale::(), 0xC104_u16), - (ux::u7::new(0x78).upscale::(), 0xF1C7_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).upscale::(), 0x8C30C30C_u32), - (ux::u7::new(0x60).upscale::(), 0xC1041041_u32), - (ux::u7::new(0x78).upscale::(), 0xF1C71C71_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.upscale::(), 0x9C403880_u32), - (0xC000_u16.upscale::(), 0xC0008001_u32), - (0xFDE8_u16.upscale::(), 0xFDE8FBD1_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 { @@ -290,128 +323,142 @@ mod tests { } #[test] - fn test_lower_range_downscaling() { + 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.downscale::(), lower_u7); - assert_eq!(lower_u32.downscale::(), lower_u9); - assert_eq!(lower_u32.downscale::(), lower_u14); - assert_eq!(lower_u32.downscale::(), lower_u16); - assert_eq!(lower_u32.downscale::(), lower_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.downscale::(), lower_u7); - assert_eq!(lower_u16.downscale::(), lower_u9); - assert_eq!(lower_u16.downscale::(), lower_u14); - assert_eq!(lower_u16.downscale::(), lower_u16); + 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.downscale::(), lower_u7); - assert_eq!(lower_u14.downscale::(), lower_u9); - assert_eq!(lower_u14.downscale::(), lower_u14); + 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.downscale::(), lower_u7); - assert_eq!(lower_u9.downscale::(), lower_u9); + assert_eq!(lower_u9.mcm_downscale::(), lower_u7); + assert_eq!(lower_u9.mcm_downscale::(), lower_u9); - assert_eq!(lower_u7.downscale::(), lower_u7); + assert_eq!(lower_u7.mcm_downscale::(), lower_u7); } #[test] - fn test_centers_downscaling() { + 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.downscale::(), center_u7); - assert_eq!(center_u32.downscale::(), center_u9); - assert_eq!(center_u32.downscale::(), center_u14); - assert_eq!(center_u32.downscale::(), center_u16); - assert_eq!(center_u32.downscale::(), center_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.downscale::(), center_u7); - assert_eq!(center_u16.downscale::(), center_u9); - assert_eq!(center_u16.downscale::(), center_u14); - assert_eq!(center_u16.downscale::(), center_u16); + 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.downscale::(), center_u7); - assert_eq!(center_u14.downscale::(), center_u9); - assert_eq!(center_u14.downscale::(), center_u14); + 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.downscale::(), center_u7); - assert_eq!(center_u9.downscale::(), center_u9); + assert_eq!(center_u9.mcm_downscale::(), center_u7); + assert_eq!(center_u9.mcm_downscale::(), center_u9); - assert_eq!(center_u7.downscale::(), center_u7); + assert_eq!(center_u7.mcm_downscale::(), center_u7); } #[test] - fn test_mins_downscaling() { + 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.downscale::(), min_u7); - assert_eq!(min_u32.downscale::(), min_u9); - assert_eq!(min_u32.downscale::(), min_u14); - assert_eq!(min_u32.downscale::(), min_u16); - assert_eq!(min_u32.downscale::(), min_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.downscale::(), min_u7); - assert_eq!(min_u16.downscale::(), min_u9); - assert_eq!(min_u16.downscale::(), min_u14); - assert_eq!(min_u16.downscale::(), min_u16); + 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.downscale::(), min_u7); - assert_eq!(min_u14.downscale::(), min_u9); - assert_eq!(min_u14.downscale::(), min_u14); + 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.downscale::(), min_u7); - assert_eq!(min_u9.downscale::(), min_u9); + assert_eq!(min_u9.mcm_downscale::(), min_u7); + assert_eq!(min_u9.mcm_downscale::(), min_u9); - assert_eq!(min_u7.downscale::(), min_u7); + assert_eq!(min_u7.mcm_downscale::(), min_u7); } #[test] - fn test_maxs_downscaling() { + 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.downscale::(), max_u7); - assert_eq!(max_u32.downscale::(), max_u9); - assert_eq!(max_u32.downscale::(), max_u14); - assert_eq!(max_u32.downscale::(), max_u16); - assert_eq!(max_u32.downscale::(), max_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_u16.downscale::(), max_u7); - assert_eq!(max_u16.downscale::(), max_u9); - assert_eq!(max_u16.downscale::(), max_u14); - assert_eq!(max_u16.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_u14.downscale::(), max_u7); - assert_eq!(max_u14.downscale::(), max_u9); - assert_eq!(max_u14.downscale::(), max_u14); + assert_eq!(max_u9.mcm_downscale::(), max_u7); + assert_eq!(max_u9.mcm_downscale::(), max_u9); - assert_eq!(max_u9.downscale::(), max_u7); - assert_eq!(max_u9.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)), + ]; - assert_eq!(max_u7.downscale::(), max_u7); + for (input, expected) in examples { + assert_eq!(input, expected); + } } #[test] - fn test_example_downscaling() { + fn test_ze_example_upscaling() { let examples = [ - (5120_u16.downscale::(), ux::u7::new(10)), - (32768_u16.downscale::(), ux::u7::new(64)), - (44730_u16.downscale::(), ux::u7::new(87)), - (65535_u16.downscale::(), ux::u7::new(127)), + (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 { From 2e2865be563d36837b41accd8edda8f0f2be0343 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 29 Jan 2026 22:47:59 -0700 Subject: [PATCH 22/30] feat: Added Zero-Extension downscaling. --- midi2/src/conversion.rs | 56 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index e61f1de..962509f 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -137,6 +137,7 @@ where pub(crate) trait ZeroExtensionScaling: Center where + Self: core::ops::Add, >::Error: core::fmt::Debug, { fn ze_upscale(&self) -> U @@ -152,6 +153,42 @@ where ze_upscaled.try_into().expect("Upscaling should not fail.") } + + fn ze_downscale>(self) -> U + where + Self: core::fmt::Debug, + Self: core::ops::Div, + 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().into(); + 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 @@ -161,7 +198,7 @@ where { } -impl> ZeroExtensionScaling for U +impl + core::ops::Add> ZeroExtensionScaling for U where >::Error: core::fmt::Debug, >::Output: Into, @@ -465,4 +502,21 @@ mod tests { assert_eq!(input, expected); } } + + #[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); + } + } } From 2446a0f3e1a9d3b61e5f4b8482fb3ef3343f0890 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Mon, 9 Feb 2026 22:44:31 -0700 Subject: [PATCH 23/30] test: Added additional tests for ze downscaling. --- midi2/src/conversion.rs | 135 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 11 deletions(-) diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index 962509f..d307012 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -16,6 +16,9 @@ where } } +trait Uxs {} +impl Uxs for ux::u7 {} + impl Center for ux::u7 { const MIN: Self = Self::MIN; const MAX: Self = Self::MAX; @@ -157,7 +160,6 @@ where fn ze_downscale>(self) -> U where Self: core::fmt::Debug, - Self: core::ops::Div, Self: core::ops::Shr, Self: core::ops::Add, >::Error: core::fmt::Debug, @@ -490,17 +492,114 @@ mod tests { } #[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), - ]; + 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; - for (input, expected) in examples { - assert_eq!(input, expected); - } + 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] @@ -519,4 +618,18 @@ mod tests { 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); + } + } } From 9ef5fecfd1ff5cdf8fcefa6303c83d62b2cc49b0 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sat, 14 Feb 2026 01:21:08 -0700 Subject: [PATCH 24/30] refactor: Change from/try_from for CV1 into from_cv2/try_from_cv2. --- midi2/src/channel_voice1/note_on.rs | 57 +++++++++++++++++++++++++++-- midi2/src/conversion.rs | 37 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 45f4751..1c7abd0 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -69,6 +69,27 @@ impl< } } +/// 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::conversion::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). @@ -83,9 +104,9 @@ impl< + crate::buffer::BufferMut + crate::buffer::BufferDefault + crate::buffer::BufferResize, - > From> for NoteOn + > crate::conversion::FromCv2> for NoteOn { - fn from(val: crate::channel_voice2::NoteOn) -> Self { + fn from_cv2(val: crate::channel_voice2::NoteOn) -> Self { let dest = NoteOn::::new(); (val, dest).into() } @@ -161,6 +182,7 @@ mod tests { #[test] fn from_midi_2() { use crate::channel_voice2; + use crate::conversion::IntoCv1; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -176,7 +198,33 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x40)); - let message21: NoteOn> = message2.into(); + let message21: NoteOn> = message2.into_cv1(); + + assert_eq!(message21, message1); + } + + #[test] + fn try_from_midi_2() { + use crate::channel_voice2; + use crate::conversion::TryIntoCv1; + 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::<[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); } @@ -184,6 +232,7 @@ mod tests { #[test] fn from_midi_2_zero_velocity() { use crate::channel_voice2; + use crate::conversion::IntoCv1; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -199,7 +248,7 @@ mod tests { message1.set_note_number(u7::new(0x5E)); message1.set_velocity(u7::new(0x01)); - let message21: NoteOn> = message2.into(); + let message21: NoteOn> = message2.into_cv1(); assert_eq!(message21, message1); } diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index d307012..badd8b4 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -1,3 +1,40 @@ +pub trait FromCv2: Sized { + fn from_cv2(other: T) -> Self; +} + +pub trait IntoCv1 { + fn into_cv1(self) -> T; +} + +impl IntoCv1 for T +where + U: FromCv2, +{ + fn into_cv1(self) -> U { + >::from_cv2(self) + } +} + +pub trait TryFromCv2: Sized { + type Error; + fn try_from_cv2(other: T) -> Result; +} + +pub trait TryIntoCv1 { + type Error; + fn try_into_cv1(self) -> Result; +} + +impl TryIntoCv1 for T +where + U: TryFromCv2, +{ + type Error = U::Error; + fn try_into_cv1(self) -> Result { + >::try_from_cv2(self) + } +} + pub(crate) trait Center: Into + TryFrom + Sized + Copy + PartialEq + PartialOrd where From 00b2393e0d500134f66ae0b35dd57c2f79b6d9e2 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Sat, 14 Feb 2026 11:16:42 -0700 Subject: [PATCH 25/30] refactor: Moved CV1 <-> CV2 conversion traits to traits.rs. --- midi2/src/channel_voice1/note_on.rs | 9 ++---- midi2/src/conversion.rs | 44 ++--------------------------- midi2/src/traits.rs | 37 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 1c7abd0..1a3498f 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -81,7 +81,7 @@ impl< + crate::buffer::BufferMut + crate::buffer::BufferDefault + crate::buffer::BufferTryResize, - > crate::conversion::TryFromCv2> for NoteOn + > crate::TryFromCv2> for NoteOn { type Error = crate::error::BufferOverflow; fn try_from_cv2(val: crate::channel_voice2::NoteOn) -> Result { @@ -104,7 +104,7 @@ impl< + crate::buffer::BufferMut + crate::buffer::BufferDefault + crate::buffer::BufferResize, - > crate::conversion::FromCv2> for NoteOn + > crate::FromCv2> for NoteOn { fn from_cv2(val: crate::channel_voice2::NoteOn) -> Self { let dest = NoteOn::::new(); @@ -116,7 +116,7 @@ impl< mod tests { use super::*; use crate::{ - traits::{Channeled, Grouped}, + traits::{Channeled, FromCv2, Grouped, IntoCv1, TryFromCv2, TryIntoCv1}, ux::*, }; use pretty_assertions::assert_eq; @@ -182,7 +182,6 @@ mod tests { #[test] fn from_midi_2() { use crate::channel_voice2; - use crate::conversion::IntoCv1; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -206,7 +205,6 @@ mod tests { #[test] fn try_from_midi_2() { use crate::channel_voice2; - use crate::conversion::TryIntoCv1; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -232,7 +230,6 @@ mod tests { #[test] fn from_midi_2_zero_velocity() { use crate::channel_voice2; - use crate::conversion::IntoCv1; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; diff --git a/midi2/src/conversion.rs b/midi2/src/conversion.rs index badd8b4..aacc29a 100644 --- a/midi2/src/conversion.rs +++ b/midi2/src/conversion.rs @@ -1,40 +1,3 @@ -pub trait FromCv2: Sized { - fn from_cv2(other: T) -> Self; -} - -pub trait IntoCv1 { - fn into_cv1(self) -> T; -} - -impl IntoCv1 for T -where - U: FromCv2, -{ - fn into_cv1(self) -> U { - >::from_cv2(self) - } -} - -pub trait TryFromCv2: Sized { - type Error; - fn try_from_cv2(other: T) -> Result; -} - -pub trait TryIntoCv1 { - type Error; - fn try_into_cv1(self) -> Result; -} - -impl TryIntoCv1 for T -where - U: TryFromCv2, -{ - type Error = U::Error; - fn try_into_cv1(self) -> Result { - >::try_from_cv2(self) - } -} - pub(crate) trait Center: Into + TryFrom + Sized + Copy + PartialEq + PartialOrd where @@ -43,6 +6,7 @@ where 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(); @@ -53,9 +17,6 @@ where } } -trait Uxs {} -impl Uxs for ux::u7 {} - impl Center for ux::u7 { const MIN: Self = Self::MIN; const MAX: Self = Self::MAX; @@ -175,6 +136,7 @@ where } } +#[allow(dead_code)] pub(crate) trait ZeroExtensionScaling: Center where Self: core::ops::Add, @@ -214,7 +176,7 @@ where 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().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; diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 348a5f9..3d09be1 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -731,3 +731,40 @@ pub(crate) trait BufferAccess { where B: BufferMut; } + +pub trait FromCv2: Sized { + fn from_cv2(other: T) -> Self; +} + +pub trait IntoCv1 { + fn into_cv1(self) -> T; +} + +impl IntoCv1 for T +where + U: FromCv2, +{ + fn into_cv1(self) -> U { + >::from_cv2(self) + } +} + +pub trait TryFromCv2: Sized { + type Error; + fn try_from_cv2(other: T) -> Result; +} + +pub trait TryIntoCv1 { + type Error; + fn try_into_cv1(self) -> Result; +} + +impl TryIntoCv1 for T +where + U: TryFromCv2, +{ + type Error = U::Error; + fn try_into_cv1(self) -> Result { + >::try_from_cv2(self) + } +} From d665dfad522f8aee8a348aac290c8a1198b609c7 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Wed, 15 Jul 2026 22:13:10 -0600 Subject: [PATCH 26/30] style: removed unused imports in CV1/note_on. --- midi2/src/channel_voice1/note_on.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 1a3498f..8054ef4 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -116,7 +116,7 @@ impl< mod tests { use super::*; use crate::{ - traits::{Channeled, FromCv2, Grouped, IntoCv1, TryFromCv2, TryIntoCv1}, + traits::{Channeled, Grouped, IntoCv1, TryIntoCv1}, ux::*, }; use pretty_assertions::assert_eq; @@ -181,7 +181,6 @@ mod tests { #[test] fn from_midi_2() { - use crate::channel_voice2; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -204,9 +203,7 @@ mod tests { #[test] fn try_from_midi_2() { - use crate::channel_voice2; 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)); @@ -229,7 +226,6 @@ mod tests { #[test] fn from_midi_2_zero_velocity() { - use crate::channel_voice2; use crate::traits::{Channeled, Grouped}; use std::vec::Vec; @@ -252,7 +248,6 @@ mod tests { #[test] fn from_midi_2_with_dest() { - use crate::channel_voice2; use crate::traits::{Channeled, Grouped}; let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); @@ -275,7 +270,6 @@ mod tests { #[test] fn from_midi_2_zero_velocity_with_dest() { - use crate::channel_voice2; use crate::traits::{Channeled, Grouped}; let mut message2 = crate::channel_voice2::NoteOn::<[u32; 4]>::new(); From de43a29fd49c0690020af749e2c2887e26449858 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Thu, 16 Jul 2026 02:08:32 -0600 Subject: [PATCH 27/30] feat: added CV2 -> CV1 conversion for NoteOff. --- midi2/src/channel_voice1/note_off.rs | 130 ++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) 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); + } } From aa3a94c723f8435f6f93cdf959dd3f5f33410472 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 28 Jul 2026 14:00:06 -0600 Subject: [PATCH 28/30] feat: Implemented TryFrom<(CV1::NoteOn, CV2::NoteOn)> for CV2::NoteOn. --- midi2/src/channel_voice2/note_on.rs | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index de795da..3a6e343 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -30,6 +30,37 @@ struct NoteOn { attribute: Option, } +/// Tries to convert a CV1 Note On message to CV2 Note On message, +/// storing the result in a pre-instantiated 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) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -96,4 +127,27 @@ mod tests { Some(Attribute::Pitch7_9(Fixed7_9::from_bits(0b1110100110001010))), ); } + + #[test] + fn try_from_midi_2() { + 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); + } } From d023fa6483666ecd81c64be5eaaefbfa64de755c Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 28 Jul 2026 14:15:50 -0600 Subject: [PATCH 29/30] test: Added test to ensure that (CV1::NoteOn, CV2::NoteOn) -> CV2::NoteOn conversion fails if CV1 velocity is 0. --- midi2/src/channel_voice2/note_on.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index 3a6e343..ef0797e 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -129,7 +129,7 @@ mod tests { } #[test] - fn try_from_midi_2() { + fn try_from_midi_2_with_dest() { use crate::traits::{Channeled, Grouped}; let mut message2 = NoteOn::<[u32; 4]>::new(); @@ -150,4 +150,21 @@ mod tests { 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."))); + } } From f8dc34bcd6d4e22fce8e8bd57171251f3428db15 Mon Sep 17 00:00:00 2001 From: Rahzael Date: Tue, 28 Jul 2026 15:20:04 -0600 Subject: [PATCH 30/30] feat: Implemented TryFromCV1 for CV2 Note On. --- midi2/src/channel_voice1/note_on.rs | 2 +- midi2/src/channel_voice2/note_on.rs | 60 ++++++++++++++++++++++++++++- midi2/src/traits.rs | 37 ++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) diff --git a/midi2/src/channel_voice1/note_on.rs b/midi2/src/channel_voice1/note_on.rs index 8054ef4..9f87b39 100644 --- a/midi2/src/channel_voice1/note_on.rs +++ b/midi2/src/channel_voice1/note_on.rs @@ -38,7 +38,7 @@ struct NoteOn { } /// Converts a CV2 Note On message to CV1 Note On message, -/// storing the result in a pre-instantiated CV1 Note On. +/// 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 diff --git a/midi2/src/channel_voice2/note_on.rs b/midi2/src/channel_voice2/note_on.rs index ef0797e..1085948 100644 --- a/midi2/src/channel_voice2/note_on.rs +++ b/midi2/src/channel_voice2/note_on.rs @@ -31,7 +31,7 @@ struct NoteOn { } /// Tries to convert a CV1 Note On message to CV2 Note On message, -/// storing the result in a pre-instantiated CV2 Note On. +/// 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. @@ -61,6 +61,41 @@ impl< } } +/// 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::*; @@ -128,6 +163,29 @@ mod tests { ); } + #[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}; diff --git a/midi2/src/traits.rs b/midi2/src/traits.rs index 3d09be1..a5b3b7c 100644 --- a/midi2/src/traits.rs +++ b/midi2/src/traits.rs @@ -732,6 +732,10 @@ pub(crate) trait BufferAccess { B: BufferMut; } +pub trait FromCv1: Sized { + fn from_cv1(other: T) -> Self; +} + pub trait FromCv2: Sized { fn from_cv2(other: T) -> Self; } @@ -740,6 +744,10 @@ pub trait IntoCv1 { fn into_cv1(self) -> T; } +pub trait IntoCv2 { + fn into_cv2(self) -> T; +} + impl IntoCv1 for T where U: FromCv2, @@ -749,6 +757,20 @@ where } } +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; @@ -759,6 +781,11 @@ pub trait TryIntoCv1 { fn try_into_cv1(self) -> Result; } +pub trait TryIntoCv2 { + type Error; + fn try_into_cv2(self) -> Result; +} + impl TryIntoCv1 for T where U: TryFromCv2, @@ -768,3 +795,13 @@ where >::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) + } +}