wmidi-4.0.10/.cargo_vcs_info.json0000644000000001360000000000100122270ustar { "git": { "sha1": "f11cfeca9f603a0b43c30a8ce2b098431ea5d19b" }, "path_in_vcs": "" }wmidi-4.0.10/.github/FUNDING.yml000064400000000000000000000012021046102023000141670ustar 00000000000000# These are supported funding model platforms github: wmedrano patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] wmidi-4.0.10/.github/workflows/benchmark_main.yml000064400000000000000000000013261046102023000200770ustar 00000000000000# Benchmark HEAD in the main branch against the previous commit. name: benchmark_main on: push: branches: [main] env: CARGO_TERM_COLOR: always jobs: benchmark: runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v2 - name: Set up Rust Cache uses: Swatinem/rust-cache@v1 - name: Install benchmarking utilities run: cargo install critcmp - name: After run: cargo bench -- --noplot --save-baseline after - name: Before run: git checkout $(git log --no-decorate --skip 1 | head -n 1 | cut -d ' ' -f2) && cargo bench -- --noplot --save-baseline before - name: Compare benchmarks run: critcmp before after wmidi-4.0.10/.github/workflows/benchmark_pr.yml000064400000000000000000000016441046102023000175770ustar 00000000000000# Benchmark the PR against the main branch. name: benchmark_pr on: pull_request: branches: [main] env: CARGO_TERM_COLOR: always jobs: benchmark: runs-on: ubuntu-latest env: # A different directory is required so that checkouting out the main # branch doesn't affect the PR branch. CARGO_TARGET_DIR: /tmp/target steps: - name: Checkout PR uses: actions/checkout@v2 - name: Rust Cache uses: Swatinem/rust-cache@v1 - name: Install benchmarking utilities run: cargo install critcmp - name: Benchmark After run: git branch && cargo bench -- --noplot --save-baseline after - name: Checkout Main uses: actions/checkout@v2 with: ref: main - name: Benchmark Before run: git branch && cargo bench -- --noplot --save-baseline before - name: Compare benchmarks run: critcmp before after wmidi-4.0.10/.github/workflows/test.yml000064400000000000000000000023631046102023000161220ustar 00000000000000# Runs general build and test logic. name: test on: push: branches: [main] pull_request: branches: [main] env: CARGO_TERM_COLOR: always jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Lint run: cargo clippy --all-targets --all-features -- -D clippy::all continue-on-error: true unit_tests: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Rust Cache uses: Swatinem/rust-cache@v1 - name: Build run: cargo build --verbose - name: Lint run: cargo clippy --all-targets --all-features -- -D clippy::all - name: Test run: cargo test --verbose - name: Test nostd run: cargo test --no-default-features release: runs-on: ubuntu-latest needs: [unit_tests] if: contains('refs/heads/main', github.ref) steps: - name: Checkout Repository uses: actions/checkout@v2 - name: Rust Cache uses: Swatinem/rust-cache@v1 - name: Publish uses: katyo/publish-crates@v1 continue-on-error: true with: registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} wmidi-4.0.10/.gitignore000064400000000000000000000000401046102023000130010ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock wmidi-4.0.10/Cargo.toml0000644000000017200000000000100102250ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "wmidi" version = "4.0.10" authors = ["wmedrano "] description = "Midi parsing library." documentation = "https://docs.rs/wmidi" readme = "README.md" keywords = [ "midi", "music", "audio", "realtime", "real-time", ] license = "MIT" repository = "https://github.com/RustAudio/wmidi" [lib] bench = false [[bench]] name = "bench" harness = false [dependencies] [dev-dependencies.criterion] version = "0.3" [features] default = ["std"] std = [] wmidi-4.0.10/Cargo.toml.orig000064400000000000000000000013111046102023000137020ustar 00000000000000[package] authors = ["wmedrano "] description = "Midi parsing library." documentation = "https://docs.rs/wmidi" keywords = ["midi", "music", "audio", "realtime", "real-time"] license = "MIT" name = "wmidi" readme = "README.md" repository = "https://github.com/RustAudio/wmidi" version = "4.0.10" [lib] # Required to pass flags to criterion benchmark. # https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options bench = false [dependencies] [dev-dependencies] criterion = "0.3" [features] # Meta-features: default = ["std"] # Without "std", wmidi uses libcore. std = [] [[bench]] harness = false name = "bench" wmidi-4.0.10/LICENSE000064400000000000000000000017761046102023000120370ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.wmidi-4.0.10/README.md000064400000000000000000000037121046102023000123010ustar 00000000000000# WMIDI Midi encoding and decoding library. [![crates.io](https://img.shields.io/crates/v/wmidi.svg)](https://crates.io/crates/wmidi) [![docs.rs](https://docs.rs/wmidi/badge.svg)](https://docs.rs/wmidi) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://github.com/RustAudio/wmidi/workflows/Rust/badge.svg)](https://github.com/RustAudio/wmidi/actions?query=branch%3Amain) ## Usage ```rust use std::convert::TryFrom; // Decoding messages from bytes. fn handle_midi_message(bytes: &[u8]) -> Result<(), wmidi::FromBytesError> { let message = wmidi::MidiMessage::try_from(bytes)?; if let wmidi::MidiMessage::NoteOn(_, note, val) = message { let volume = u8::from(val) as u8 / 127.0; println!("Singing {} at volume {}", note, volume); } Ok(()) } // Encoding messages to bytes. fn midi_to_bytes(message: wmidi::MidiMessage<'_>) -> Vec { let mut bytes = vec![0u8; message.bytes_size()]; message.copy_to_slice(bytes.as_mut_slice()).unwrap(); bytes } ``` ## Features * Supports `no_std` environments. * No memory allocations (therefore realtime safe) for parsing and encoding. * No memory allocations for creating `MidiMessage`, except for `MidiMessage::OwnedSysEx`. ## Testing & Benchmarking * Build with `cargo build`. * Test with `cargo test`. * Benchmark with `cargo bench`. The results will be under `./target/criterion/report/index.html`. ## Changelog ### 4.0.0 * New ControlFunction type which simply wraps a U7. * Constants and documentation for all ControlFunction values. * Renumber Note enums/consts to be more consistent with midi; for example, C0 is now C1. ### 3.1.0 * Rename `MidiMessage::wire_size()` to `MidiMessage::bytes_size()`. * Introduce `MidiMessage::copy_to_slice()` method. ### 3.0.0 * Instances of U7 and U14 now have bounds checking. * Note is now an enum instead of a u8. Can be converted with `Note::try_from` and `u8::from`. wmidi-4.0.10/benches/bench.rs000064400000000000000000000131261046102023000140560ustar 00000000000000#[macro_use] extern crate criterion; use criterion::{black_box, Criterion}; use std::convert::TryFrom; const MESSAGES: [wmidi::MidiMessage<'static>; 19] = [ wmidi::MidiMessage::NoteOn(wmidi::Channel::Ch1, wmidi::Note::C3, wmidi::U7::MAX), wmidi::MidiMessage::NoteOff(wmidi::Channel::Ch2, wmidi::Note::A3, wmidi::U7::MIN), wmidi::MidiMessage::PolyphonicKeyPressure(wmidi::Channel::Ch3, wmidi::Note::B1, wmidi::U7::MAX), wmidi::MidiMessage::ControlChange( wmidi::Channel::Ch4, wmidi::ControlFunction::DAMPER_PEDAL, wmidi::U7::MAX, ), wmidi::MidiMessage::ProgramChange(wmidi::Channel::Ch5, wmidi::U7::MIN), wmidi::MidiMessage::ChannelPressure(wmidi::Channel::Ch6, wmidi::U7::MAX), wmidi::MidiMessage::PitchBendChange(wmidi::Channel::Ch7, wmidi::U14::MAX), wmidi::MidiMessage::Start, wmidi::MidiMessage::SysEx(&[wmidi::U7::MIN, wmidi::U7::MAX]), wmidi::MidiMessage::MidiTimeCode(wmidi::U7::MAX), wmidi::MidiMessage::SongPositionPointer(wmidi::U14::MIN), wmidi::MidiMessage::SongSelect(wmidi::U7::MIN), wmidi::MidiMessage::TuneRequest, wmidi::MidiMessage::TimingClock, wmidi::MidiMessage::Start, wmidi::MidiMessage::Continue, wmidi::MidiMessage::Stop, wmidi::MidiMessage::ActiveSensing, wmidi::MidiMessage::Reset, ]; fn bench_to_slice(c: &mut Criterion) { c.bench_function("MidiMessage::copy_to_slice", |b| { let message = black_box(wmidi::MidiMessage::NoteOn( wmidi::Channel::Ch1, wmidi::Note::C3, wmidi::U7::MAX, )); b.iter(|| { let mut slice = [0u8; 3]; message.copy_to_slice(&mut slice).unwrap(); slice }) }); c.bench_function("MidiMessage::bytes_size sum", |b| { let messages = black_box(MESSAGES.clone()); b.iter(|| messages.iter().map(|b| b.bytes_size()).sum::()) }); c.bench_function("MidiMessage::copy_to_slice many", |b| { let messages = black_box(MESSAGES.clone()); let mut buffer = vec![0u8; messages.iter().map(|b| b.bytes_size()).sum()]; b.iter(|| { let mut start = 0; for message in messages.iter() { let end = start + message .copy_to_slice(&mut buffer.as_mut_slice()[start..]) .unwrap(); start = end; } }) }); } fn bench_from_bytes(c: &mut Criterion) { let bytes = { let mut bytes = vec![0u8; MESSAGES.iter().map(|m| m.bytes_size()).sum()]; let mut start = 0; for message in MESSAGES.iter() { let end = start + message .copy_to_slice(&mut bytes.as_mut_slice()[start..]) .unwrap(); start = end; } bytes }; c.bench_function("MidiMessage::try_from", |b| { let bytes = black_box(bytes.clone()); b.iter(|| wmidi::MidiMessage::try_from(bytes.as_slice()).unwrap()); }); c.bench_function("MidiMessage::try_from many", |b| { let bytes = black_box(bytes.clone()); b.iter(|| { let mut messages: Vec = Vec::with_capacity(MESSAGES.len()); let mut start = 0; while start < bytes.len() { let message = wmidi::MidiMessage::try_from(&bytes[start..]).unwrap(); start += message.bytes_size(); messages.push(message); } messages }); }); } fn bench_notes(c: &mut Criterion) { c.bench_function("Note::try_from", |b| { b.iter(|| { let mut notes = [wmidi::Note::LOWEST_NOTE; 128]; for (note_number, dst) in (0..128).zip(notes.iter_mut()) { let note_number = black_box(note_number); let note = wmidi::Note::try_from(note_number).unwrap(); *dst = note; } }) }); c.bench_function("Note::from_u8_unchecked", |b| { b.iter(|| { let mut notes = [wmidi::Note::LOWEST_NOTE; 128]; for (note_number, dst) in (0..128).zip(notes.iter_mut()) { let note_number = black_box(note_number); let note = unsafe { wmidi::Note::from_u8_unchecked(note_number) }; *dst = note; } }) }); } #[cfg(feature = "std")] fn bench_frequency(c: &mut Criterion) { let all_notes: Vec = (0..128) .map(|n| wmidi::Note::try_from(n).unwrap()) .collect(); let all_notes = black_box(all_notes); c.bench_function("Note::to_freq_f32", |b| { b.iter(|| { let mut freqs = [0f32; 128]; for (note, dst) in all_notes.iter().zip(freqs.iter_mut()) { *dst = note.to_freq_f32(); } freqs }) }); c.bench_function("Note::to_freq_f32 result cast into f64", |b| { b.iter(|| { let mut freqs = [0f64; 128]; for (note, dst) in all_notes.iter().zip(freqs.iter_mut()) { *dst = f64::from(note.to_freq_f32()); } freqs }) }); c.bench_function("Note::to_freq_f64", |b| { b.iter(|| { let mut freqs = [0f64; 128]; for (note, dst) in all_notes.iter().zip(freqs.iter_mut()) { *dst = note.to_freq_f64(); } freqs }) }); } #[cfg(not(feature = "std"))] fn bench_frequency(_: &mut Criterion) {} criterion_group!( benchmarks, bench_to_slice, bench_from_bytes, bench_notes, bench_frequency ); criterion_main!(benchmarks); wmidi-4.0.10/src/byte.rs000064400000000000000000000154441046102023000131270ustar 00000000000000use crate::Error; use core::convert::TryFrom; /// A data byte that holds 7 bits of information. #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct U7(pub(crate) u8); impl U7 { /// The minimum value for a u7 data byte. pub const MIN: U7 = U7(0x00); /// The maximum value for a u7 data byte. pub const MAX: U7 = U7(0x80 - 0x01); /// Create a new `U7` or return an error if it is out of range. #[inline(always)] pub fn new(data: u8) -> Result { if data > u8::from(U7::MAX) { Err(Error::DataByteOutOfRange) } else { Ok(U7(data)) } } /// Convert a `u8` into a `U7` without bounds checking. /// /// # Safety /// Behavior is undefined if data > 127. #[inline(always)] pub unsafe fn from_unchecked(data: u8) -> U7 { U7(data) } /// Create a `U7` from a `u8`. Only the 7 least significant bits of `note` are kept. #[inline(always)] pub const fn from_u8_lossy(data: u8) -> U7 { U7(data & 0x7F) } /// Convert a slice of `u8` into a slice of `U7`. If any of the data is out of range, then an /// error is returned. #[inline(always)] pub fn try_from_bytes(bytes: &[u8]) -> Result<&[U7], Error> { for b in bytes.iter() { U7::try_from(*b)?; } unsafe { Ok(U7::from_bytes_unchecked(bytes)) } } /// Convert a slice of `U7` into a slice `u8`. Since `U7` is a subset of `u8`, this is a simple /// cast. #[inline(always)] pub fn data_to_bytes(data: &[U7]) -> &[u8] { unsafe { &*(data as *const [U7] as *const [u8]) } } /// Convert a slice of `u8` to a slice of `U7` without bounds checking. /// /// # Safety /// Behavior is undefined if any byte is > 127. #[inline(always)] pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &[U7] { &*(bytes as *const [u8] as *const [U7]) } } impl From for u8 { #[inline(always)] fn from(data: U7) -> u8 { data.0 } } impl TryFrom for U7 { type Error = Error; #[inline(always)] fn try_from(data: u8) -> Result { U7::new(data) } } /// A combination of 2 data bytes that holds 14 bits of information. #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct U14(u16); impl U14 { /// The minimum value for a u14 data byte. pub const MIN: U14 = U14(0); /// The maximum value for a u7 data byte. pub const MAX: U14 = U14(0x4000 - 0x0001); /// Convert a `u8` into a `U7` without bounds checking. /// /// # Safety /// Behavior is undefined if data is > 16383. #[inline(always)] pub unsafe fn from_unchecked(data: u16) -> U14 { U14(data) } /// Convert a slice of `u16` into a slice of `U14`. If any of the data is out of range, then an /// error is returned. #[inline(always)] pub fn try_from_slice(slice: &[u16]) -> Result<&[U14], Error> { for d in slice.iter() { U14::try_from(*d)?; } unsafe { Ok(U14::from_slice_unchecked(slice)) } } /// Convert a slice of `U14` into a slice `u16`. Since `U14` is a subset of `u16`, this is a /// simple cast. #[inline(always)] pub fn data_to_slice(data: &[U14]) -> &[u16] { unsafe { &*(data as *const [U14] as *const [u16]) } } /// Convert a slice of `u16` to a slice of `U14` without bounds checking. /// /// # Safety /// Behavior is undefined if any byte is > 16383. #[inline(always)] pub unsafe fn from_slice_unchecked(slice: &[u16]) -> &[U14] { &*(slice as *const [u16] as *const [U14]) } } impl From for u16 { #[inline(always)] fn from(data: U14) -> u16 { data.0 } } impl TryFrom for U14 { type Error = Error; #[inline(always)] fn try_from(data: u16) -> Result { if data > u16::from(U14::MAX) { Err(Error::U14OutOfRange) } else { Ok(U14(data)) } } } #[cfg(test)] mod tests { use super::*; #[test] fn try_from_passes() { for n in 0x00..0x80 { U7::try_from(n).unwrap(); } } #[test] fn min_and_max_constant_are_valid() { assert_eq!(U7::try_from(u8::from(U7::MIN)).unwrap(), U7::MIN); assert_eq!(U7::try_from(u8::from(U7::MAX)).unwrap(), U7::MAX); } #[test] fn try_from_out_of_range_fails() { for n in 0x80..=u8::MAX { assert_eq!(U7::try_from(n), Err(Error::DataByteOutOfRange)); } } #[test] fn try_from_bytes_is_ok_on_valid_bytes() { U7::try_from_bytes(&[]).unwrap(); U7::try_from_bytes(&[0x00, 0x08, 0x10, 0x20, 0x30, 0x40, 0x7F]).unwrap(); } #[test] fn try_from_bytes_fails_on_out_of_range() { assert_eq!( U7::try_from_bytes(&[0x00, 0x80]), Err(Error::DataByteOutOfRange) ); } #[test] fn data_to_bytes_converts_exactly() { assert_eq!( &[0x00, 0x0F, 0x7F], U7::data_to_bytes(&[ U7::try_from(0x00).unwrap(), U7::try_from(0x0F).unwrap(), U7::try_from(0x7F).unwrap() ]), ); } #[test] fn try_from_16_passes() { for n in 0x0000..0x4000 { U14::try_from(n).unwrap(); } } #[test] fn min_and_max_14_constant_are_valid() { assert_eq!(U14::try_from(u16::from(U14::MIN)).unwrap(), U14::MIN); assert_eq!(U14::try_from(u16::from(U14::MAX)).unwrap(), U14::MAX); } #[test] fn try_from_out_of_range_16_fails() { for n in 0x4000..=u16::MAX { assert_eq!(U14::try_from(n), Err(Error::U14OutOfRange)); } } #[test] fn try_from_slice_is_ok_on_valid_range() { U14::try_from_slice(&[]).unwrap(); U14::try_from_slice(&[0x0000, 0x0080, 0x0180, 0x01FF]).unwrap(); } #[test] fn try_from_slice_fails_on_out_of_range() { assert_eq!( U14::try_from_slice(&[0x0000, 0x5000]), Err(Error::U14OutOfRange) ); } #[test] fn data_to_slice_converts_exactly() { assert_eq!( &[0x0000, 0x010F, 0x017F], U14::data_to_slice(&[ U14::try_from(0x0000).unwrap(), U14::try_from(0x010F).unwrap(), U14::try_from(0x017F).unwrap() ]), ); } #[test] fn test_from_u8_lossy() { assert_eq!(U7::from_u8_lossy(0), U7::try_from(0).unwrap()); assert_eq!(U7::from_u8_lossy(64), U7::try_from(64).unwrap()); assert_eq!(U7::from_u8_lossy(127), U7::try_from(127).unwrap()); assert_eq!(U7::from_u8_lossy(128), U7::try_from(0).unwrap()); assert_eq!(U7::from_u8_lossy(200), U7::try_from(72).unwrap()); } } wmidi-4.0.10/src/cc.rs000064400000000000000000000567661046102023000125650ustar 00000000000000//! Documents referred to in this module: //! * [MIDI 1.0]: The Complete MIDI 1.0 Detailed Specification, Third Edition (1996) //! * [GM1]: General MIDI System Level 1 //! * [GM2]: General MIDI 2, version 1.2a //! * [RP-015]: Recommended Practice (RP-015): Response to Reset All Controllers //! * [RP-018]: Recommended Practice (RP-018): Response to Data Inc/Dec Controllers //! * [RP-021]: Recommended Practice (RP-021): Sound Controller Defaults (Revised) //! * [RP-023]: Recommended Practice (RP-023): Renaming of CC91 and CC93 //! * [CA-026]: CC #88 High Resolution Velocity Prefix (CA-031) //! * [CA-031]: RPN05 Modulation Depth Range use crate::byte::U7; /// A Control Change signal. The names of each variant of the constants /// are from the 1997 MIDI 1.0 specification. The names and description /// reflect the standard assignment and behavior for each CC number, /// though manufacturers may ignore some controllers or use non-standard /// mappings. MIDI devices should provide a controller allocation table /// as part of their user manual. /// /// * 0 - 31: Continuous Controller Data (MSB) /// * 32 - 63: Continuous Controller Data (LSB) /// * 64 - 119: Single-byte controllers /// * 120 - 127: Channel mode messages /// /// Setting the MSB on a continuous controller resets the LSB to zero. /// The LSB can be omitted afterwards if the finer resolution is not needed. /// /// Channel mode messages affect the entire instrument and /// are only valid when sent over the instrument's "basic channel". #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct ControlFunction(pub U7); impl ControlFunction { pub const MIN: ControlFunction = ControlFunction(U7::MIN); pub const MAX: ControlFunction = ControlFunction(U7::MAX); /// [MIDI 1.0] Specifies which program bank to use when changing program. /// The MSB and LSB are always sent as a pair, immediately followed /// by a program change. pub const BANK_SELECT: ControlFunction = ControlFunction(U7(0)); /// Either known as Modulation Wheel (MIDI 1.0 and GM1) or Modulation Depth (GM2) /// /// [GM1] "For all instruments, the Modulation Wheel will change the nature of the sound /// in the most natural (expected) way. i.e. depth of LFO; change of timbre; /// add more tine sound; etc.)" /// /// [GM2] Specifies the vibrato (LFO pitch modulation) depth to use for that channel. /// The depth response ranges from 0 cents (no modulation) to the value of /// RPN 05.00 "Modulation Depth Range", following a curve that is linear in cents. pub const MODULATION_WHEEL: ControlFunction = ControlFunction(U7(1)); /// *Effect was never standardized* pub const BREATH_CONTROLLER: ControlFunction = ControlFunction(U7(2)); pub const UNDEFINED_3: ControlFunction = ControlFunction(U7(3)); /// *Effect was never standardized* pub const FOOT_CONTROLLER: ControlFunction = ControlFunction(U7(4)); /// [MIDI 1.0] Specifies the pitch increment speed for the portamento effect. /// The relation between the parameter value and the speed in cents per ms /// is outside the MIDI specification. pub const PORTAMENTO_TIME: ControlFunction = ControlFunction(U7(5)); /// [MIDI 1.0] Sets the value of the last selected RPN/NRPN. pub const DATA_ENTRY_MSB: ControlFunction = ControlFunction(U7(6)); /// [MIDI 1.0] Specifies the mixing volume for that channel. /// /// [GM1] The gain in dB should be equivalent to `L = 40 * log10(cc7/127)`. pub const CHANNEL_VOLUME: ControlFunction = ControlFunction(U7(7)); /// [MIDI 1.0] Specifies the volume balance for that channel. /// Center is 64, left/bottom is 0 and right/top is 127. pub const BALANCE: ControlFunction = ControlFunction(U7(8)); pub const UNDEFINED_9: ControlFunction = ControlFunction(U7(9)); /// [MIDI 1.0] Specifies the sound location (in stereo) balance for that channel. /// Center is 64, left is 0 and right is 127. pub const PAN: ControlFunction = ControlFunction(U7(10)); /// [MIDI 1.0] Specifies the volume accent for that channel. /// Unlike ChannelVolume (7), this one is meant to change during performance /// to create diminuendos and crescendos. /// /// [GM1] The gain in dB should be equivalent to /// `L = 40 * log10(cc7/127) + 40 * log10(cc11/127)`. pub const EXPRESSION_CONTROLLER: ControlFunction = ControlFunction(U7(11)); pub const EFFECT_CONTROL_1: ControlFunction = ControlFunction(U7(12)); pub const EFFECT_CONTROL_2: ControlFunction = ControlFunction(U7(13)); pub const UNDEFINED_14: ControlFunction = ControlFunction(U7(14)); pub const UNDEFINED_15: ControlFunction = ControlFunction(U7(15)); /// [MIDI 1.0] 2 bytes general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_1: ControlFunction = ControlFunction(U7(16)); /// [MIDI 1.0] 2 bytes general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_2: ControlFunction = ControlFunction(U7(17)); /// [MIDI 1.0] 2 bytes general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_3: ControlFunction = ControlFunction(U7(18)); /// [MIDI 1.0] 2 bytes general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_4: ControlFunction = ControlFunction(U7(19)); pub const UNDEFINED_20: ControlFunction = ControlFunction(U7(20)); pub const UNDEFINED_21: ControlFunction = ControlFunction(U7(21)); pub const UNDEFINED_22: ControlFunction = ControlFunction(U7(22)); pub const UNDEFINED_23: ControlFunction = ControlFunction(U7(23)); pub const UNDEFINED_24: ControlFunction = ControlFunction(U7(24)); pub const UNDEFINED_25: ControlFunction = ControlFunction(U7(25)); pub const UNDEFINED_26: ControlFunction = ControlFunction(U7(26)); pub const UNDEFINED_27: ControlFunction = ControlFunction(U7(27)); pub const UNDEFINED_28: ControlFunction = ControlFunction(U7(28)); pub const UNDEFINED_29: ControlFunction = ControlFunction(U7(29)); pub const UNDEFINED_30: ControlFunction = ControlFunction(U7(30)); pub const UNDEFINED_31: ControlFunction = ControlFunction(U7(31)); pub const BANK_SELECT_LSB: ControlFunction = ControlFunction(U7(32)); pub const MODULATION_WHEEL_LSB: ControlFunction = ControlFunction(U7(33)); pub const BREATH_CONTROLLER_LSB: ControlFunction = ControlFunction(U7(34)); pub const UNDEFINED_3_LSB: ControlFunction = ControlFunction(U7(35)); pub const FOOT_CONTROLLER_LSB: ControlFunction = ControlFunction(U7(36)); pub const PORTAMENTO_TIME_LSB: ControlFunction = ControlFunction(U7(37)); /// [MIDI 1.0] Sets the value of the last selected RPN/NRPN pub const DATA_ENTRY_LSB: ControlFunction = ControlFunction(U7(38)); pub const CHANNEL_VOLUME_LSB: ControlFunction = ControlFunction(U7(39)); pub const BALANCE_LSB: ControlFunction = ControlFunction(U7(40)); pub const UNDEFINED_9_LSB: ControlFunction = ControlFunction(U7(41)); pub const PAN_LSB: ControlFunction = ControlFunction(U7(42)); pub const EXPRESSION_CONTROLLER_LSB: ControlFunction = ControlFunction(U7(43)); pub const EFFECT_CONTROL_1_LSB: ControlFunction = ControlFunction(U7(44)); pub const EFFECT_CONTROL_2_LSB: ControlFunction = ControlFunction(U7(45)); pub const UNDEFINED_14_LSB: ControlFunction = ControlFunction(U7(46)); pub const UNDEFINED_15_LSB: ControlFunction = ControlFunction(U7(47)); pub const GENERAL_PURPOSE_CONTROLLER_1_LSB: ControlFunction = ControlFunction(U7(48)); pub const GENERAL_PURPOSE_CONTROLLER_2_LSB: ControlFunction = ControlFunction(U7(49)); pub const GENERAL_PURPOSE_CONTROLLER_3_LSB: ControlFunction = ControlFunction(U7(50)); pub const GENERAL_PURPOSE_CONTROLLER_4_LSB: ControlFunction = ControlFunction(U7(51)); pub const UNDEFINED_20_LSB: ControlFunction = ControlFunction(U7(52)); pub const UNDEFINED_21_LSB: ControlFunction = ControlFunction(U7(53)); pub const UNDEFINED_22_LSB: ControlFunction = ControlFunction(U7(54)); pub const UNDEFINED_23_LSB: ControlFunction = ControlFunction(U7(55)); pub const UNDEFINED_24_LSB: ControlFunction = ControlFunction(U7(56)); pub const UNDEFINED_25_LSB: ControlFunction = ControlFunction(U7(57)); pub const UNDEFINED_26_LSB: ControlFunction = ControlFunction(U7(58)); pub const UNDEFINED_27_LSB: ControlFunction = ControlFunction(U7(59)); pub const UNDEFINED_28_LSB: ControlFunction = ControlFunction(U7(60)); pub const UNDEFINED_29_LSB: ControlFunction = ControlFunction(U7(61)); pub const UNDEFINED_30_LSB: ControlFunction = ControlFunction(U7(62)); pub const UNDEFINED_31_LSB: ControlFunction = ControlFunction(U7(63)); /// Either known as the Hold, Sustain or Damper pedal. /// /// [MIDI 1.0] Response to NoteOff and AllNotesOff should be delayed while /// this switch is on (value >= 64) until it transitions to off. /// /// [GM2] May be treated as a continuous controller instead of a switch /// for the "Half Damper" and "re-damper" effects. pub const DAMPER_PEDAL: ControlFunction = ControlFunction(U7(64)); /// [MIDI 1.0] Turns the Portamento effect on (value >= 64) or off. pub const PORTAMENTO_ON_OFF: ControlFunction = ControlFunction(U7(65)); /// [MIDI 1.0] Same as DamperPedal (64), but only affects the notes being held /// **while** the switch transitions to on (value >= 64). Any note played /// while the switch is already on behaves as normal. pub const SOSTENUTO: ControlFunction = ControlFunction(U7(66)); /// [GM2] Notes played while this switch is on (value >= 64) should be /// played at a reduced volume. pub const SOFT_PEDAL: ControlFunction = ControlFunction(U7(67)); /// [MIDI 1.0] This switch turns on (value >= 64) the monophonic legato response /// mode for that channel, where receiving a NoteOn while a note is already /// playing will change the pitch of the current note accordingly (without /// replaying the attack or re-attacking the envelopes). pub const LEGATO_FOOTSWITCH: ControlFunction = ControlFunction(U7(68)); /// [MIDI 1.0] Additional controller for hold functions that don't match /// the specified definition of DamperPedal (64). pub const HOLD_2: ControlFunction = ControlFunction(U7(69)); /// [MIDI 1.0] Remappable Sound Controller, Default Name: "Sound Variation" pub const SOUND_CONTROLLER_1: ControlFunction = ControlFunction(U7(70)); /// [MIDI 1.0] Remappable Sound Controller, Default Name: "Timbre/Harmonic Intensity" /// /// [GM2] Sets the strength of the resonance effect for filter(s) for the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_2: ControlFunction = ControlFunction(U7(71)); /// [MIDI 1.0] Remappable Sound Controller, Default Name: "Release Time" /// /// [GM2] Controls the release time of the envelope for the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_3: ControlFunction = ControlFunction(U7(72)); /// [MIDI 1.0] Remappable Sound Controller, Default Name: "Attack Time" /// /// [GM2] Controls the attack time of the envelope for the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_4: ControlFunction = ControlFunction(U7(73)); /// [MIDI 1.0] Remappable Sound Controller, Default Name: "Brightness" /// /// [GM2] Controls the preset cut-off frequency of the filter. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_5: ControlFunction = ControlFunction(U7(74)); /// [RP-021] Remappable Sound Controller, Default Name: "Decay Time" /// /// [GM2] Controls the decay time of the envelope for the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_6: ControlFunction = ControlFunction(U7(75)); /// [RP-021] Remappable Sound Controller, Default Name: "Vibrato Rate" /// /// [GM2] Controls the vibrato rate on the specified Channel relative to the sound's preset rate. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_7: ControlFunction = ControlFunction(U7(76)); /// [RP-021] Remappable Sound Controller, Default Name: "Vibrato Depth" /// /// [GM2] Controls the vibrato depth for the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_8: ControlFunction = ControlFunction(U7(77)); /// [RP-021] Remappable Sound Controller, Default Name: "Vibrato Delay" /// /// [GM2] Controls the vibrato delay on the specified Channel. /// Exact behavior is left to the manufacturer's discretion. pub const SOUND_CONTROLLER_9: ControlFunction = ControlFunction(U7(78)); /// [MIDI 1.0] Remappable Sound Controller, no default pub const SOUND_CONTROLLER_10: ControlFunction = ControlFunction(U7(79)); /// [MIDI 1.0] 1 byte general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_5: ControlFunction = ControlFunction(U7(80)); /// [MIDI 1.0] 1 byte general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_6: ControlFunction = ControlFunction(U7(81)); /// [MIDI 1.0] 1 byte general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_7: ControlFunction = ControlFunction(U7(82)); /// [MIDI 1.0] 1 byte general-purpose controller for device-specific features. pub const GENERAL_PURPOSE_CONTROLLER_8: ControlFunction = ControlFunction(U7(83)); /// [MIDI 1.0] Specifies the starting MIDI note from which the next NoteOn will slide. pub const PORTAMENTO_CONTROL: ControlFunction = ControlFunction(U7(84)); pub const UNDEFINED_85: ControlFunction = ControlFunction(U7(85)); pub const UNDEFINED_86: ControlFunction = ControlFunction(U7(86)); pub const UNDEFINED_87: ControlFunction = ControlFunction(U7(87)); /// [CA-031] High Resolution Velocity Prefix /// /// If sent before a NoteOn message, the value of this controller /// acts as an LSB for the velocity of that note. pub const UNDEFINED_88: ControlFunction = ControlFunction(U7(88)); pub const UNDEFINED_89: ControlFunction = ControlFunction(U7(89)); pub const UNDEFINED_90: ControlFunction = ControlFunction(U7(90)); /// [MIDI 1.0] General-purpose effect depth Controller (default: "External Effects Depth") /// /// [RP-023] No-longer general-purpose, renamed to "Reverb Send Level" /// /// [GM2] Specifies the Reverb Send Level for that channel, linearly from 0% to 100% of amplitude. pub const EFFECTS_1_DEPTH: ControlFunction = ControlFunction(U7(91)); /// [MIDI 1.0] General-purpose effect depth Controller (default: "Tremolo Depth") pub const EFFECTS_2_DEPTH: ControlFunction = ControlFunction(U7(92)); /// [MIDI 1.0] General-purpose effect depth Controller (default: "Chorus Depth") /// /// [RP-023] No-longer general-purpose, renamed to "Chorus Send Level" /// /// [GM2] Specifies the Chorus Send Level for that channel, linearly from 0% to 100% of amplitude. pub const EFFECTS_3_DEPTH: ControlFunction = ControlFunction(U7(93)); /// [MIDI 1.0] General-purpose effect depth Controller (default: "Celeste (Detune) Depth") pub const EFFECTS_4_DEPTH: ControlFunction = ControlFunction(U7(94)); /// [MIDI 1.0] General-purpose effect depth Controller (default: "Phaser Depth") pub const EFFECTS_5_DEPTH: ControlFunction = ControlFunction(U7(95)); /// [RP-018] Increments the value of a RPN/NRPN by 1, regardless of the value byte. /// The actual behavior depends on the parameter. pub const DATA_INCREMENT: ControlFunction = ControlFunction(U7(96)); /// [RP-018] Decrements the value of a RPN/NRPN by 1, regardless of the value byte. /// The actual behavior depends on the parameter. pub const DATA_DECREMENT: ControlFunction = ControlFunction(U7(97)); /// [MIDI 1.0] Selects a parameter to be modified by DataIncrement (96), /// DataDecrement (97) and DataEntry (6 & 38). Unlike RPNs, NRPNs are /// manufacturer-specific. pub const NON_REGISTERED_PARAMETER_NUMBER_LSB: ControlFunction = ControlFunction(U7(98)); pub const NON_REGISTERED_PARAMETER_NUMBER_MSB: ControlFunction = ControlFunction(U7(99)); /// [MIDI 1.0] Selects a parameter to be modified by DataIncrement (96), /// DataDecrement (97) and DataEntry (6 & 38). /// /// ###### [MIDI 1.0] `MSB=00` `LSB=00`: Pitch Bend Sensitivity /// Specifies the range of the pitch bender (both up and down), /// with the MSB value being in semitones and the LSB in cents. /// /// [RP-018] Incrementing/decrementing this parameter changes the LSB, /// which wraps into the MSB at 100 cents to change it by one semitone. /// /// ###### [MIDI 1.0] `MSB=00` `LSB=01`: (Channel) Fine Tuning /// Specifies the note displacement from A440 in 8192ths of 100 cents, /// with `0x40 0x00` acting as zero, `0x00 0x00` as -8192 (-100 cents) /// and `0x7F 0x7F` as +8191 (almost +100 cents). /// /// [RP-018] Incrementing/decrementing this parameter changes the LSB by 1. /// /// ###### [MIDI 1.0] `MSB=00` `LSB=02`: (Channel) Coarse Tuning /// Specifies the note displacement from A440 in increments of 100 cents /// for the MSB, (the LSB is ignored), with `0x40` acting as zero, /// `0x00` as -64 (-64 semitones) and `0x7F` as +63 (+63 semitones). /// /// [RP-018] Incrementing/decrementing this parameter changes the MSB by 1. /// /// ###### [CA-026] `MSB=00` `LSB=05`: Modulation Depth Range /// [GM2] Specifies the peak value of ModulationWheel (1), with the MSB /// value being in semitones and the LSB being in 128ths of 100 cents. /// /// [CA-026] Incrementing/decrementing this parameter changes the LSB by 1. /// /// ###### [GM2] `MSB=7F` `LSB=7F`: RPN NULL /// This RPN is invalid, data entry will be ignored while this parameter /// is selected. pub const REGISTERED_PARAMETER_NUMBER_LSB: ControlFunction = ControlFunction(U7(100)); pub const REGISTERED_PARAMETER_NUMBER_MSB: ControlFunction = ControlFunction(U7(101)); pub const UNDEFINED_102: ControlFunction = ControlFunction(U7(102)); pub const UNDEFINED_103: ControlFunction = ControlFunction(U7(103)); pub const UNDEFINED_104: ControlFunction = ControlFunction(U7(104)); pub const UNDEFINED_105: ControlFunction = ControlFunction(U7(105)); pub const UNDEFINED_106: ControlFunction = ControlFunction(U7(106)); pub const UNDEFINED_107: ControlFunction = ControlFunction(U7(107)); pub const UNDEFINED_108: ControlFunction = ControlFunction(U7(108)); pub const UNDEFINED_109: ControlFunction = ControlFunction(U7(109)); pub const UNDEFINED_110: ControlFunction = ControlFunction(U7(110)); pub const UNDEFINED_111: ControlFunction = ControlFunction(U7(111)); pub const UNDEFINED_112: ControlFunction = ControlFunction(U7(112)); pub const UNDEFINED_113: ControlFunction = ControlFunction(U7(113)); pub const UNDEFINED_114: ControlFunction = ControlFunction(U7(114)); pub const UNDEFINED_115: ControlFunction = ControlFunction(U7(115)); pub const UNDEFINED_116: ControlFunction = ControlFunction(U7(116)); pub const UNDEFINED_117: ControlFunction = ControlFunction(U7(117)); pub const UNDEFINED_118: ControlFunction = ControlFunction(U7(118)); pub const UNDEFINED_119: ControlFunction = ControlFunction(U7(119)); /// [MIDI 1.0] Indicates that the receiver should immediately silence (without /// going through the release phase and ignoring sustain) all notes currently /// sounding on that channel. May also be used to turn off lights. pub const ALL_SOUND_OFF: ControlFunction = ControlFunction(U7(120)); /// [MIDI 1.0] Indicates that all controllers (including pitch bend and /// pressure) should be reset to an ideal initial state. This message /// is ignored if the device is in Omni mode (mode 1 or 2). /// /// [RP-015] Indicates that the following controllers should be reset as such: /// specified channel: /// * Modulation (cc1): Set to 0 /// * Expression (cc11): Set to 127 /// * DamperPedal (cc64): Set to 0 /// * PortamentoOnOff (cc65): Set to 0 /// * Sostenuto (cc66): Set to 0 /// * SoftPedal (cc67): Set to 0 /// * NRPN (cc98 & cc99): Set to NULL NRPN (`0x7F 0x7F`) /// * RPN (cc100 & cc101): Set to NULL RPN (`0x7F 0x7F`) /// /// Also reset the following for the specified channel: /// * Reset pitch-bend to center (`0x40 0x00`) /// * Reset channel pressure to 0 /// * Reset polyphonic pressure of all notes to 0 pub const RESET_ALL_CONTROLLERS: ControlFunction = ControlFunction(U7(121)); /// [MIDI 1.0] Specifies whether the instrument should react to notes being /// physically played on it (0: ControlFunction = off, 127 = on), as opposed to notes /// sent via MIDI-in. pub const LOCAL_CONTROL: ControlFunction = ControlFunction(U7(122)); /// [MIDI 1.0] Ignored in Omni mode (mode 1 & 2). In Poly operation (mode 3), /// acts as a NoteOff for all notes playing on the instrument's basic channel /// (it is ignored for all other channels). In Mono operation (mode 4), /// acts as a NoteOff for all notes playing on the specified channel. In all cases, /// notes being played on the instrument itself should remain unaffected. /// /// [GM2] Turns off all Notes sounding on the specified Channel. pub const ALL_NOTES_OFF: ControlFunction = ControlFunction(U7(123)); /// [MIDI 1.0] Same as AllNotesOff (123), then set receiver to mode 1 (omni-on, poly) /// or 2 (omni-on, mono) based on the current mode. /// /// [GM2] Same as AllNotesOff (123), since GM2 does not support Omni mode. pub const OMNI_MODE_ON: ControlFunction = ControlFunction(U7(124)); /// [MIDI 1.0] Same as AllNotesOff (123), then set receiver to mode 3 (omni-off, poly) /// or 4 (omni-off, mono) based on the current mode. /// /// [GM2] Same as AllNotesOff (123), since GM2 does not support Omni mode. pub const OMNI_MODE_OFF: ControlFunction = ControlFunction(U7(125)); /// [MIDI 1.0] Same as AllNotesOff (123), then set receiver to mode 2 (omni-on, mono) /// or 4 (omni-off, mono) based on the current mode. The value byte indicates how many /// channels to use, with 0 being "auto". /// /// [GM2] Same as AllNotesOff (123), then set the **channel** to mode 4. Will be /// ignored if the value byte is not equal to 1 of if the channel is a rhythm channel. pub const MONO_OPERATION: ControlFunction = ControlFunction(U7(126)); /// [MIDI 1.0] Same as AllNotesOff (123), then set receiver to mode 1 (omni-on, poly) /// or 3 (omni-off, poly) based on the current mode. /// /// [GM2] Same as AllNotesOff (123), then set the **channel** to mode 3. pub const POLY_OPERATION: ControlFunction = ControlFunction(U7(127)); } impl From for ControlFunction { fn from(data: U7) -> ControlFunction { ControlFunction(data) } } impl From for U7 { fn from(control_function: ControlFunction) -> U7 { control_function.0 } } impl From for u8 { fn from(control_function: ControlFunction) -> u8 { control_function.0.into() } } #[cfg(test)] mod test { use super::*; use crate::U7; #[test] fn from_u7() { for value in 0..128 { let data = U7::new(value).unwrap(); let cc = ControlFunction::from(data); assert_eq!(value, cc.into()); } } } wmidi-4.0.10/src/error.rs000064400000000000000000000035771046102023000133210ustar 00000000000000use core::fmt; #[cfg(feature = "std")] use std::error; /// Midi decoding errors. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum FromBytesError { /// The MIDI channel is not between 1 and 16 inclusive. ChannelOutOfRange, /// No MIDI bytes were provided. NoBytes, /// A SysEx start byte was provided, but there was no corresponding SysEx end byte. NoSysExEndByte, /// Not enough data bytes for the specified MIDI message. NotEnoughBytes, /// Found a SysEx end byte, but there was no start byte. UnexpectedEndSysExByte, /// Found a status byte interleaved with SysEx data. SysEx messages should be a start byte, followed by data bytes, /// and ending in a end byte. UnexpectedNonSysExEndByte(u8), /// The first byte of a midi message must be a status byte. UnexpectedDataByte, /// Found a status byte, but expected a `U7` data byte. UnexpectedStatusByte, /// Midi notes must be in the range [0, 127] inclusive. NoteOutOfRange, /// Data (U7) bytes must be between [0, 127] inclusive. DataByteOutOfRange, /// Data (U14) bytes must be between [0x0000, 0x03FF] or [0, 16383] inclusive. U14OutOfRange, } #[cfg(feature = "std")] impl error::Error for FromBytesError {} impl fmt::Display for FromBytesError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } /// An error that can occur converting a midi message to a bytes slice. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ToSliceError { /// The destination buffer cannot fit all the bytes. BufferTooSmall, } #[cfg(feature = "std")] impl error::Error for ToSliceError {} impl fmt::Display for ToSliceError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ToSliceError::BufferTooSmall => write!(f, "buffer size too small"), } } } wmidi-4.0.10/src/lib.rs000064400000000000000000000016571046102023000127330ustar 00000000000000#![no_std] #[cfg(feature = "std")] #[macro_use] extern crate std; mod byte; mod cc; mod error; mod midi_message; mod note; pub use byte::{U14, U7}; pub use cc::ControlFunction; pub use error::{FromBytesError, ToSliceError}; pub use midi_message::{ Channel, ControlValue, MidiMessage, PitchBend, ProgramNumber, Song, SongPosition, Velocity, }; pub use note::Note; /// Use `FromBytesError` instead. pub type Error = FromBytesError; /// The frequency for `note` using the standard 440Hz tuning. #[cfg(feature = "std")] #[inline(always)] #[deprecated(since = "3.0.0", note = "Use note.to_freq_f32() instead.")] pub fn note_to_frequency_f32(note: Note) -> f32 { note.to_freq_f32() } /// The frequency for `note` using the standard 440Hz tuning. #[cfg(feature = "std")] #[inline(always)] #[deprecated(since = "3.0.0", note = "Use note.to_freq_f64() instead.")] pub fn note_to_frequency_f64(note: Note) -> f64 { note.to_freq_f64() } wmidi-4.0.10/src/midi_message.rs000064400000000000000000000655701046102023000146170ustar 00000000000000use crate::{ControlFunction, Error, Note, ToSliceError, U14, U7}; use core::convert::TryFrom; #[cfg(feature = "std")] use std::{io, vec::Vec}; /// Holds information based on the Midi 1.0 spec. #[derive(Clone, Debug, PartialEq, Eq)] pub enum MidiMessage<'a> { /// This message is sent when a note is released (ended). NoteOff(Channel, Note, Velocity), /// This message is sent when a note is depressed (start). NoteOn(Channel, Note, Velocity), /// This message is most often sent by pressing down on the key after it "bottoms out". PolyphonicKeyPressure(Channel, Note, Velocity), /// This message is sent when a controller value changes. Controllers include devices such as pedals and levers. /// /// Controller numbers 120-127 are reserved as "Channel Mode Messages". ControlChange(Channel, ControlFunction, ControlValue), /// This message is sent when the patch number changes. ProgramChange(Channel, ProgramNumber), /// This message is most often sent by pressing down on the key after it "bottoms out". This message is different /// from polyphonic after-touch. Use this message to send the single greatest pressure value (of all the current /// depressed keys). ChannelPressure(Channel, Velocity), /// This message is sent to indicate a change in the pitch bender (wheel or level, typically). The pitch bender is /// measured by a fourteen bit value. Center is 8192. PitchBendChange(Channel, PitchBend), /// This message type allows manufacturers to create their own messages (such as bulk dumps, patch parameters, and /// other non-spec data) and provides a mechanism for creating additional MIDI Specification messages. /// /// In the data held by the SysEx message, the Manufacturer's ID code (assigned by MMA or AMEI) is either 1 byte or /// 3 bytes. Two of the 1 Byte IDs are reserved for extensions called Universal Exclusive Messages, which are not /// manufacturer-specific. If a device recognizes the ID code as its own (or as a supported Universal message) it /// will listen to the rest of the message. Otherwise the message will be ignored. SysEx(&'a [U7]), /// This message type allows manufacturers to create their own messages (such as bulk dumps, patch parameters, and /// other non-spec data) and provides a mechanism for creating additional MIDI Specification messages. /// /// In the data held by the SysEx message, the Manufacturer's ID code (assigned by MMA or AMEI) is either 1 byte or /// 3 bytes. Two of the 1 Byte IDs are reserved for extensions called Universal Exclusive Messages, which are not /// manufacturer-specific. If a device recognizes the ID code as its own (or as a supported Universal message) it /// will listen to the rest of the message. Otherwise the message will be ignored. #[cfg(feature = "std")] OwnedSysEx(Vec), /// MIDI Time Code Quarter Frame. /// /// The data is in the format 0nnndddd where nnn is the Message Type and dddd is the Value. /// /// TODO: Interpret data instead of providing the raw format. MidiTimeCode(U7), /// This is an internal 14 bit value that holds the number of MIDI beats (1 beat = six MIDI clocks) since the start /// of the song. SongPositionPointer(SongPosition), /// The Song Select specifies which sequence or song is to be played. SongSelect(Song), /// The u8 data holds the status byte. Reserved(u8), /// Upon receiving a Tune Request, all analog synthesizers should tune their oscillators. TuneRequest, /// Timing Clock. Sent 24 times per quarter note when synchronization is required. TimingClock, /// Start the current sequence playing. (This message will be followed with Timing Clocks). Start, /// Continue at the point the sequence was Stopped. Continue, /// Stop the current sequence. Stop, /// This message is intended to be sent repeatedly to tell the receiver that a connection is alive. Use of this /// message is optional. When initially received, the receiver will expect to receive another Active Sensing message /// each 300ms (max), and if it idoes not, then it will assume that the connection has been terminated. At /// termination, the receiver will turn off all voices and return to normal (non-active sensing) operation. ActiveSensing, /// Reset all receivers in the system to power-up status. This should be used sparingly, preferably under manual /// control. In particular, it should not be sent on power-up. Reset, } impl<'a> TryFrom<&'a [u8]> for MidiMessage<'a> { type Error = Error; /// Construct a midi message from bytes. fn try_from(bytes: &'a [u8]) -> Result { if bytes.is_empty() { return Err(Error::NoBytes); } if !is_status_byte(bytes[0]) { return Err(Error::UnexpectedDataByte); } let chan = Channel::from_index(bytes[0] & 0x0F)?; let data_a = bytes .get(1) .ok_or(Error::NotEnoughBytes) .and_then(|b| valid_data_byte(*b)); let data_b = bytes .get(2) .ok_or(Error::NotEnoughBytes) .and_then(|b| valid_data_byte(*b)); match bytes[0] & 0xF0 { 0x80 => Ok(MidiMessage::NoteOff(chan, Note::from(data_a?), data_b?)), 0x90 => match data_b? { U7::MIN => Ok(MidiMessage::NoteOff(chan, Note::from(data_a?), U7::MIN)), _ => Ok(MidiMessage::NoteOn(chan, Note::from(data_a?), data_b?)), }, 0xA0 => Ok(MidiMessage::PolyphonicKeyPressure( chan, Note::from(data_a?), data_b?, )), 0xB0 => Ok(MidiMessage::ControlChange(chan, data_a?.into(), data_b?)), 0xC0 => Ok(MidiMessage::ProgramChange(chan, data_a?)), 0xD0 => Ok(MidiMessage::ChannelPressure(chan, data_a?)), 0xE0 => Ok(MidiMessage::PitchBendChange( chan, combine_data(data_a?, data_b?), )), 0xF0 => match bytes[0] { 0xF0 => MidiMessage::new_sysex(bytes), 0xF1 => Ok(MidiMessage::MidiTimeCode(data_a?)), 0xF2 => Ok(MidiMessage::SongPositionPointer(combine_data( data_a?, data_b?, ))), 0xF3 => Ok(MidiMessage::SongSelect(data_a?)), 0xF4 | 0xF5 => Ok(MidiMessage::Reserved(bytes[0])), 0xF6 => Ok(MidiMessage::TuneRequest), 0xF7 => Err(Error::UnexpectedEndSysExByte), 0xF8 => Ok(MidiMessage::TimingClock), 0xF9 => Ok(MidiMessage::Reserved(bytes[0])), 0xFA => Ok(MidiMessage::Start), 0xFB => Ok(MidiMessage::Continue), 0xFC => Ok(MidiMessage::Stop), 0xFD => Ok(MidiMessage::Reserved(bytes[0])), 0xFE => Ok(MidiMessage::ActiveSensing), 0xFF => Ok(MidiMessage::Reset), _ => unreachable!(), }, _ => unreachable!(), } } } impl<'a> MidiMessage<'a> { /// Construct a midi message from bytes. pub fn from_bytes(bytes: &'a [u8]) -> Result { MidiMessage::try_from(bytes) } /// Copies the message as bytes to slice. If slice does not have enough capacity to fit the /// message, then an error is returned. On success, the number of bytes written will be /// returned. This should be the same number obtained from `self.bytes_size()`. #[allow(clippy::range_plus_one)] pub fn copy_to_slice(&self, slice: &mut [u8]) -> Result { if slice.len() < self.bytes_size() { Err(ToSliceError::BufferTooSmall) } else { let slice = &mut slice[..self.bytes_size()]; match self { MidiMessage::NoteOff(a, b, c) => { slice.copy_from_slice(&[0x80 | a.index(), u8::from(*b), u8::from(*c)]); } MidiMessage::NoteOn(a, b, c) => { slice.copy_from_slice(&[0x90 | a.index(), u8::from(*b), u8::from(*c)]); } MidiMessage::PolyphonicKeyPressure(a, b, c) => { slice.copy_from_slice(&[0xA0 | a.index(), *b as u8, u8::from(*c)]); } MidiMessage::ControlChange(a, b, c) => { slice.copy_from_slice(&[0xB0 | a.index(), u8::from(*b), u8::from(*c)]); } MidiMessage::ProgramChange(a, b) => { slice.copy_from_slice(&[0xC0 | a.index(), u8::from(*b)]); } MidiMessage::ChannelPressure(a, b) => { slice.copy_from_slice(&[0xD0 | a.index(), u8::from(*b)]); } MidiMessage::PitchBendChange(a, b) => { let (b1, b2) = split_data(*b); slice.copy_from_slice(&[0xE0 | a.index(), b1, b2]); } MidiMessage::SysEx(b) => { slice[0] = 0xF0; slice[1..1 + b.len()].copy_from_slice(U7::data_to_bytes(b)); slice[1 + b.len()] = 0xF7; } #[cfg(feature = "std")] MidiMessage::OwnedSysEx(ref b) => { slice[0] = 0xF0; slice[1..1 + b.len()].copy_from_slice(U7::data_to_bytes(b)); slice[1 + b.len()] = 0xF7; } MidiMessage::MidiTimeCode(a) => slice.copy_from_slice(&[0xF1, u8::from(*a)]), MidiMessage::SongPositionPointer(a) => { let (a1, a2) = split_data(*a); slice.copy_from_slice(&[0xF2, a1, a2]); } MidiMessage::SongSelect(a) => slice.copy_from_slice(&[0xF3, u8::from(*a)]), MidiMessage::Reserved(a) => slice.copy_from_slice(&[*a]), MidiMessage::TuneRequest => slice.copy_from_slice(&[0xF6]), MidiMessage::TimingClock => slice.copy_from_slice(&[0xF8]), MidiMessage::Start => slice.copy_from_slice(&[0xFA]), MidiMessage::Continue => slice.copy_from_slice(&[0xFB]), MidiMessage::Stop => slice.copy_from_slice(&[0xFC]), MidiMessage::ActiveSensing => slice.copy_from_slice(&[0xFE]), MidiMessage::Reset => slice.copy_from_slice(&[0xFF]), }; Ok(self.bytes_size()) } } /// Return `Some(midi_message)` if `self` is not a SysEx message, or `None` if it is. This expands the lifetime of /// the `MidiMessage` from `'a` to `'static`. pub fn drop_unowned_sysex(self) -> Option> { match self { MidiMessage::NoteOff(a, b, c) => Some(MidiMessage::NoteOff(a, b, c)), MidiMessage::NoteOn(a, b, c) => Some(MidiMessage::NoteOn(a, b, c)), MidiMessage::PolyphonicKeyPressure(a, b, c) => { Some(MidiMessage::PolyphonicKeyPressure(a, b, c)) } MidiMessage::ControlChange(a, b, c) => Some(MidiMessage::ControlChange(a, b, c)), MidiMessage::ProgramChange(a, b) => Some(MidiMessage::ProgramChange(a, b)), MidiMessage::ChannelPressure(a, b) => Some(MidiMessage::ChannelPressure(a, b)), MidiMessage::PitchBendChange(a, b) => Some(MidiMessage::PitchBendChange(a, b)), MidiMessage::SysEx(_) => None, #[cfg(feature = "std")] MidiMessage::OwnedSysEx(bytes) => Some(MidiMessage::OwnedSysEx(bytes)), MidiMessage::MidiTimeCode(a) => Some(MidiMessage::MidiTimeCode(a)), MidiMessage::SongPositionPointer(a) => Some(MidiMessage::SongPositionPointer(a)), MidiMessage::SongSelect(a) => Some(MidiMessage::SongSelect(a)), MidiMessage::Reserved(a) => Some(MidiMessage::Reserved(a)), MidiMessage::TuneRequest => Some(MidiMessage::TuneRequest), MidiMessage::TimingClock => Some(MidiMessage::TimingClock), MidiMessage::Start => Some(MidiMessage::Start), MidiMessage::Continue => Some(MidiMessage::Continue), MidiMessage::Stop => Some(MidiMessage::Stop), MidiMessage::ActiveSensing => Some(MidiMessage::ActiveSensing), MidiMessage::Reset => Some(MidiMessage::Reset), } } /// Take ownership of the SysEx data. This expands the lifetime of the message to `'static`. If `'static` lifetime /// is needed but SysEx messages can be dropped, consider using `self.drop_unowned_sysex()`. #[inline(always)] pub fn to_owned(&self) -> MidiMessage<'static> { match self.clone() { MidiMessage::NoteOff(a, b, c) => MidiMessage::NoteOff(a, b, c), MidiMessage::NoteOn(a, b, c) => MidiMessage::NoteOn(a, b, c), MidiMessage::PolyphonicKeyPressure(a, b, c) => { MidiMessage::PolyphonicKeyPressure(a, b, c) } MidiMessage::ControlChange(a, b, c) => MidiMessage::ControlChange(a, b, c), MidiMessage::ProgramChange(a, b) => MidiMessage::ProgramChange(a, b), MidiMessage::ChannelPressure(a, b) => MidiMessage::ChannelPressure(a, b), MidiMessage::PitchBendChange(a, b) => MidiMessage::PitchBendChange(a, b), #[cfg(feature = "std")] MidiMessage::SysEx(bytes) => MidiMessage::OwnedSysEx(bytes.to_vec()), #[cfg(not(feature = "std"))] MidiMessage::SysEx(_) => MidiMessage::SysEx(&[]), //to be updated with a better solution. #[cfg(feature = "std")] MidiMessage::OwnedSysEx(bytes) => MidiMessage::OwnedSysEx(bytes), MidiMessage::MidiTimeCode(a) => MidiMessage::MidiTimeCode(a), MidiMessage::SongPositionPointer(a) => MidiMessage::SongPositionPointer(a), MidiMessage::SongSelect(a) => MidiMessage::SongSelect(a), MidiMessage::Reserved(a) => MidiMessage::Reserved(a), MidiMessage::TuneRequest => MidiMessage::TuneRequest, MidiMessage::TimingClock => MidiMessage::TimingClock, MidiMessage::Start => MidiMessage::Start, MidiMessage::Continue => MidiMessage::Continue, MidiMessage::Stop => MidiMessage::Stop, MidiMessage::ActiveSensing => MidiMessage::ActiveSensing, MidiMessage::Reset => MidiMessage::Reset, } } /// The number of bytes the MIDI message takes when converted to bytes. pub fn bytes_size(&self) -> usize { match self { MidiMessage::NoteOff(..) => 3, MidiMessage::NoteOn(..) => 3, MidiMessage::PolyphonicKeyPressure(..) => 3, MidiMessage::ControlChange(..) => 3, MidiMessage::ProgramChange(..) => 2, MidiMessage::ChannelPressure(..) => 2, MidiMessage::PitchBendChange(..) => 3, MidiMessage::SysEx(b) => 2 + b.len(), #[cfg(feature = "std")] MidiMessage::OwnedSysEx(b) => 2 + b.len(), MidiMessage::MidiTimeCode(_) => 2, MidiMessage::SongPositionPointer(_) => 3, MidiMessage::SongSelect(_) => 2, MidiMessage::Reserved(_) => 1, MidiMessage::TuneRequest => 1, MidiMessage::TimingClock => 1, MidiMessage::Start => 1, MidiMessage::Continue => 1, MidiMessage::Stop => 1, MidiMessage::ActiveSensing => 1, MidiMessage::Reset => 1, } } /// The number of bytes the MIDI message takes when encoded with the `std::io::Read` trait. #[deprecated( since = "3.1.0", note = "Function has been renamed to MidiMessage::bytes_size()." )] pub fn wire_size(&self) -> usize { self.bytes_size() } /// The channel associated with the MIDI message, if applicable for the message type. pub fn channel(&self) -> Option { match self { MidiMessage::NoteOff(c, ..) => Some(*c), MidiMessage::NoteOn(c, ..) => Some(*c), MidiMessage::PolyphonicKeyPressure(c, ..) => Some(*c), MidiMessage::ControlChange(c, ..) => Some(*c), MidiMessage::ProgramChange(c, ..) => Some(*c), MidiMessage::ChannelPressure(c, ..) => Some(*c), MidiMessage::PitchBendChange(c, ..) => Some(*c), _ => None, } } #[inline(always)] fn new_sysex(bytes: &'a [u8]) -> Result { debug_assert!(bytes[0] == 0xF0); let end_i = 1 + bytes[1..] .iter() .copied() .position(is_status_byte) .ok_or(Error::NoSysExEndByte)?; if bytes[end_i] != 0xF7 { return Err(Error::UnexpectedNonSysExEndByte(bytes[end_i])); } // We've already gone through the bytes to find the first non data byte so we are assured // that values from 1..end_i are valid data bytes. let data_bytes = unsafe { U7::from_bytes_unchecked(&bytes[1..end_i]) }; Ok(MidiMessage::SysEx(data_bytes)) } /// Convert the message to a vector of bytes. Prefer using /// `copy_to_slice` if possible for better performance. #[cfg(feature = "std")] pub fn to_vec(&self) -> Vec { let mut data = vec![0; self.bytes_size()]; // Unwrapping is ok as data has enough capacity for the data. self.copy_to_slice(&mut data).unwrap(); data } } #[cfg(feature = "std")] impl<'a> io::Read for MidiMessage<'a> { // Use MidiMessage::copy_from_slice instead. fn read(&mut self, buf: &mut [u8]) -> io::Result { match self.copy_to_slice(buf) { Ok(n) => Ok(n), Err(ToSliceError::BufferTooSmall) => Ok(0), } } } /// Specifies the velocity of an action (often key press, release, or aftertouch). pub type Velocity = U7; /// Specifies the value of a MIDI control. pub type ControlValue = U7; /// Specifies a program. Sometimes known as patch. pub type ProgramNumber = U7; /// A 14bit value specifying the pitch bend. Neutral is 8192. pub type PitchBend = U14; /// 14 bit value that holds the number of MIDI beats (1 beat = six MIDI clocks) since the start of the song. pub type SongPosition = U14; /// A song or sequence. pub type Song = U7; /// The MIDI channel. There are 16 channels. They are numbered between 1 and 16 /// inclusive, or indexed between 0 and 15 inclusive. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Channel { Ch1, Ch2, Ch3, Ch4, Ch5, Ch6, Ch7, Ch8, Ch9, Ch10, Ch11, Ch12, Ch13, Ch14, Ch15, Ch16, } impl Channel { /// Get a MIDI channel from an index that is between 0 and 15 inclusive. pub fn from_index(i: u8) -> Result { match i { 0 => Ok(Channel::Ch1), 1 => Ok(Channel::Ch2), 2 => Ok(Channel::Ch3), 3 => Ok(Channel::Ch4), 4 => Ok(Channel::Ch5), 5 => Ok(Channel::Ch6), 6 => Ok(Channel::Ch7), 7 => Ok(Channel::Ch8), 8 => Ok(Channel::Ch9), 9 => Ok(Channel::Ch10), 10 => Ok(Channel::Ch11), 11 => Ok(Channel::Ch12), 12 => Ok(Channel::Ch13), 13 => Ok(Channel::Ch14), 14 => Ok(Channel::Ch15), 15 => Ok(Channel::Ch16), _ => Err(Error::ChannelOutOfRange), } } /// The index of this midi channel. The returned value is between 0 and 15 /// inclusive. pub fn index(self) -> u8 { match self { Channel::Ch1 => 0, Channel::Ch2 => 1, Channel::Ch3 => 2, Channel::Ch4 => 3, Channel::Ch5 => 4, Channel::Ch6 => 5, Channel::Ch7 => 6, Channel::Ch8 => 7, Channel::Ch9 => 8, Channel::Ch10 => 9, Channel::Ch11 => 10, Channel::Ch12 => 11, Channel::Ch13 => 12, Channel::Ch14 => 13, Channel::Ch15 => 14, Channel::Ch16 => 15, } } /// The number of this midi channel. The returned value is between 1 and 16 /// inclusive. pub fn number(self) -> u8 { self.index() + 1 } } #[inline(always)] fn combine_data(lower: U7, higher: U7) -> U14 { let raw = u16::from(u8::from(lower)) + 128 * u16::from(u8::from(higher)); unsafe { U14::from_unchecked(raw) } } #[inline(always)] fn split_data(data: U14) -> (u8, u8) { ((u16::from(data) % 128) as u8, (u16::from(data) / 128) as u8) } #[inline(always)] fn is_status_byte(b: u8) -> bool { b & 0x80 == 0x80 } #[inline(always)] fn valid_data_byte(b: u8) -> Result { U7::try_from(b).map_err(|_| Error::UnexpectedStatusByte) } #[cfg(test)] mod test { use super::*; use crate::{ControlFunction, Error, Note}; #[test] fn try_from() { assert_eq!( MidiMessage::try_from([].as_ref()), Err(Error::NoBytes), "no bytes produces an error", ); assert_eq!( MidiMessage::try_from([0x00].as_ref()), Err(Error::UnexpectedDataByte), "no status byte produces an error", ); assert_eq!( MidiMessage::try_from([0x84].as_ref()), Err(Error::NotEnoughBytes), "NoteOff event produces errors with only 1 byte", ); assert_eq!( MidiMessage::try_from([0x84, 64].as_ref()), Err(Error::NotEnoughBytes), "NoteOff event produces errors with only 2 bytes", ); assert_eq!( MidiMessage::try_from([0x84, 64, 100].as_ref()), Ok(MidiMessage::NoteOff( Channel::Ch5, Note::E4, U7::try_from(100).unwrap() )), "NoteOff event is decoded.", ); assert_eq!( MidiMessage::try_from([0x94].as_ref()), Err(Error::NotEnoughBytes), "NoteOn event produces errors with only 1 byte", ); assert_eq!( MidiMessage::try_from([0x94, 64].as_ref()), Err(Error::NotEnoughBytes), "NoteOn event produces errors with only 2 bytes", ); assert_eq!( MidiMessage::try_from([0x94, 64, 100].as_ref()), Ok(MidiMessage::NoteOn( Channel::Ch5, Note::E4, U7::try_from(100).unwrap() )), "NoteOn event is decoded.", ); assert_eq!( MidiMessage::try_from([0x94, 64, 0].as_ref()), Ok(MidiMessage::NoteOff( Channel::Ch5, Note::E4, U7::try_from(0).unwrap() )), "NoteOn message with 0 veloctiy decodes as NoteOff", ); assert_eq!( MidiMessage::try_from([0xF0, 4, 8, 12, 16, 0xF7].as_ref()), Ok(MidiMessage::SysEx( U7::try_from_bytes(&[4, 8, 12, 16]).unwrap() )), "SysEx message is decoded with borrowed data.", ); assert_eq!( MidiMessage::try_from([0xF0, 3, 6, 9, 12, 15, 0xF7, 125].as_ref()), Ok(MidiMessage::SysEx( U7::try_from_bytes(&[3, 6, 9, 12, 15]).unwrap() )), "SysEx message does not include bytes after the end byte.", ); assert_eq!( MidiMessage::try_from([0xF0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as_ref()), Err(Error::NoSysExEndByte), "SysEx message without end status produces error.", ); assert_eq!( MidiMessage::try_from([0xE4].as_ref()), Err(Error::NotEnoughBytes), "PitchBend with single byte produces error.", ); assert_eq!( MidiMessage::try_from([0xE4, 64].as_ref()), Err(Error::NotEnoughBytes), "PitchBend with only 2 bytes produces error.", ); assert_eq!( MidiMessage::try_from([0xE4, 64, 100].as_ref()), Ok(MidiMessage::PitchBendChange( Channel::Ch5, U14::try_from(12864).unwrap() )), "PitchBendChange is decoded.", ); } #[test] fn copy_to_slice() { let b = { let mut b = [0u8; 6]; let bytes_copied = MidiMessage::PolyphonicKeyPressure( Channel::Ch10, Note::A6, U7::try_from(43).unwrap(), ) .copy_to_slice(&mut b) .unwrap(); assert_eq!(bytes_copied, 3); b }; assert_eq!(b, [0xA9, 93, 43, 0, 0, 0]); } #[test] fn copy_to_slice_sysex() { let b = { let mut b = [0u8; 8]; let bytes_copied = MidiMessage::SysEx(U7::try_from_bytes(&[10, 20, 30, 40, 50]).unwrap()) .copy_to_slice(&mut b) .unwrap(); assert_eq!(bytes_copied, 7); b }; assert_eq!(b, [0xF0, 10, 20, 30, 40, 50, 0xF7, 0]); } #[cfg(feature = "std")] #[test] fn drop_unowned_sysex_with_std() { assert_eq!( MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).drop_unowned_sysex(), None ); assert_eq!( MidiMessage::OwnedSysEx(vec![ U7::try_from(1).unwrap(), U7::try_from(2).unwrap(), U7::try_from(3).unwrap() ]) .drop_unowned_sysex(), Some(MidiMessage::OwnedSysEx(vec![ U7::try_from(1).unwrap(), U7::try_from(2).unwrap(), U7::try_from(3).unwrap() ])) ); assert_eq!( MidiMessage::TuneRequest.drop_unowned_sysex(), Some(MidiMessage::TuneRequest) ); } #[test] fn drop_unowned_sysex_with_nostd() { assert_eq!( MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).drop_unowned_sysex(), None ); assert_eq!( MidiMessage::TuneRequest.drop_unowned_sysex(), Some(MidiMessage::TuneRequest) ); } #[cfg(feature = "std")] #[test] fn to_owned() { assert_eq!( MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).to_owned(), MidiMessage::OwnedSysEx(vec![ U7::try_from(1).unwrap(), U7::try_from(2).unwrap(), U7::try_from(3).unwrap() ]) ); assert_ne!( MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).to_owned(), MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()) ); } #[test] fn channel() { assert_eq!( MidiMessage::ControlChange( Channel::Ch8, ControlFunction::DAMPER_PEDAL, U7::try_from(55).unwrap() ) .channel(), Some(Channel::Ch8) ); assert_eq!(MidiMessage::Start.channel(), None); } } wmidi-4.0.10/src/note.rs000064400000000000000000000333141046102023000131250ustar 00000000000000use crate::Error; use core::convert::TryFrom; use core::fmt; /// A midi note. /// /// The format for the enum is `$NOTE` `$MODIFIER?` `$OCTAVE`. Note can be a note from `A` to `G`. /// Modifier can be `b` for flat or `Sharp` for sharp. Octave is the number. The octave `-1` is /// represented as `Minus1`. /// # Example /// ``` /// use wmidi::Note; /// let ab7_chord = [Note::AbMinus1, Note::C4, Note::Gb4]; // We omit the 5th for a jazzier sound /// let dmaj_chord = [Note::D2, Note::FSharp3, Note::A3]; /// assert_eq!(u8::from(Note::C3), 48u8); /// assert_eq!(Note::from_u8_lossy(48), Note::C3); /// ``` #[repr(u8)] #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum Note { CMinus1 = 0, DbMinus1 = 1, DMinus1 = 2, EbMinus1 = 3, EMinus1 = 4, FMinus1 = 5, GbMinus1 = 6, GMinus1 = 7, AbMinus1 = 8, AMinus1 = 9, BbMinus1 = 10, BMinus1 = 11, C0 = 12, Db0 = 13, D0 = 14, Eb0 = 15, E0 = 16, F0 = 17, Gb0 = 18, G0 = 19, Ab0 = 20, A0 = 21, Bb0 = 22, B0 = 23, C1 = 24, Db1 = 25, D1 = 26, Eb1 = 27, E1 = 28, F1 = 29, Gb1 = 30, G1 = 31, Ab1 = 32, A1 = 33, Bb1 = 34, B1 = 35, C2 = 36, Db2 = 37, D2 = 38, Eb2 = 39, E2 = 40, F2 = 41, Gb2 = 42, G2 = 43, Ab2 = 44, A2 = 45, Bb2 = 46, B2 = 47, C3 = 48, Db3 = 49, D3 = 50, Eb3 = 51, E3 = 52, F3 = 53, Gb3 = 54, G3 = 55, Ab3 = 56, A3 = 57, Bb3 = 58, B3 = 59, /// Middle C. C4 = 60, Db4 = 61, D4 = 62, Eb4 = 63, E4 = 64, F4 = 65, Gb4 = 66, G4 = 67, Ab4 = 68, /// A440. A4 = 69, Bb4 = 70, B4 = 71, C5 = 72, Db5 = 73, D5 = 74, Eb5 = 75, E5 = 76, F5 = 77, Gb5 = 78, G5 = 79, Ab5 = 80, A5 = 81, Bb5 = 82, B5 = 83, C6 = 84, Db6 = 85, D6 = 86, Eb6 = 87, E6 = 88, F6 = 89, Gb6 = 90, G6 = 91, Ab6 = 92, A6 = 93, Bb6 = 94, B6 = 95, C7 = 96, Db7 = 97, D7 = 98, Eb7 = 99, E7 = 100, F7 = 101, Gb7 = 102, G7 = 103, Ab7 = 104, A7 = 105, Bb7 = 106, B7 = 107, C8 = 108, Db8 = 109, D8 = 110, Eb8 = 111, E8 = 112, F8 = 113, Gb8 = 114, G8 = 115, Ab8 = 116, A8 = 117, Bb8 = 118, B8 = 119, C9 = 120, Db9 = 121, D9 = 122, Eb9 = 123, E9 = 124, F9 = 125, Gb9 = 126, G9 = 127, } #[allow(non_upper_case_globals)] impl Note { pub const CSharpMinus1: Note = Note::DbMinus1; pub const DSharpMinus1: Note = Note::EbMinus1; pub const FSharpMinus1: Note = Note::GbMinus1; pub const GSharpMinus1: Note = Note::AbMinus1; pub const ASharpMinus1: Note = Note::BbMinus1; pub const CSharp0: Note = Note::Db0; pub const DSharp0: Note = Note::Eb0; pub const FSharp0: Note = Note::Gb0; pub const GSharp0: Note = Note::Ab0; pub const ASharp0: Note = Note::Bb0; pub const CSharp1: Note = Note::Db1; pub const DSharp1: Note = Note::Eb1; pub const FSharp1: Note = Note::Gb1; pub const GSharp1: Note = Note::Ab1; pub const ASharp1: Note = Note::Bb1; pub const CSharp2: Note = Note::Db2; pub const DSharp2: Note = Note::Eb2; pub const FSharp2: Note = Note::Gb2; pub const GSharp2: Note = Note::Ab2; pub const ASharp2: Note = Note::Bb2; pub const CSharp3: Note = Note::Db3; pub const DSharp3: Note = Note::Eb3; pub const FSharp3: Note = Note::Gb3; pub const GSharp3: Note = Note::Ab3; pub const ASharp3: Note = Note::Bb3; pub const CSharp4: Note = Note::Db4; pub const DSharp4: Note = Note::Eb4; pub const FSharp4: Note = Note::Gb4; pub const GSharp4: Note = Note::Ab4; pub const ASharp4: Note = Note::Bb4; pub const CSharp5: Note = Note::Db5; pub const DSharp5: Note = Note::Eb5; pub const FSharp5: Note = Note::Gb5; pub const GSharp5: Note = Note::Ab5; pub const ASharp5: Note = Note::Bb5; pub const CSharp6: Note = Note::Db6; pub const DSharp6: Note = Note::Eb6; pub const FSharp6: Note = Note::Gb6; pub const GSharp6: Note = Note::Ab6; pub const ASharp6: Note = Note::Bb6; pub const CSharp7: Note = Note::Db7; pub const DSharp7: Note = Note::Eb7; pub const FSharp7: Note = Note::Gb7; pub const GSharp7: Note = Note::Ab7; pub const ASharp7: Note = Note::Bb7; pub const CSharp8: Note = Note::Db8; pub const DSharp8: Note = Note::Eb8; pub const FSharp8: Note = Note::Gb8; pub const GSharp8: Note = Note::Ab8; pub const ASharp8: Note = Note::Bb8; pub const CSharp9: Note = Note::Db9; pub const DSharp9: Note = Note::Eb9; pub const FSharp9: Note = Note::Gb9; /// The lowest representable note. pub const LOWEST_NOTE: Note = Note::CMinus1; /// The highest representable note. pub const HIGHEST_NOTE: Note = Note::G9; /// Creates a note from a `u8`. `note` must be between [0, 127] inclusive to create a valid /// note. /// /// # Example ///``` /// let parsed_note = 60; /// let note = unsafe { wmidi::Note::from_u8_unchecked(parsed_note) }; ///``` /// /// # Safety /// `note` must be less than or equal to 127. #[inline(always)] pub unsafe fn from_u8_unchecked(note: u8) -> Note { core::mem::transmute(note) } /// Create a note from a `u8`. Only the 7 least significant bits of `note` are used. #[inline(always)] pub fn from_u8_lossy(note: u8) -> Note { Note::from(crate::U7::from_u8_lossy(note)) } /// The frequency using the standard 440Hz tuning. /// /// # Example /// ``` /// # fn sing(frequency: f32) {} /// let note = wmidi::Note::A3; /// sing(note.to_freq_f32()); /// ``` #[cfg(feature = "std")] #[inline(always)] pub fn to_freq_f32(self) -> f32 { let exp = (f32::from(self as u8) + 36.376_316) / 12.0; 2_f32.powf(exp) } /// The frequency using the standard 440Hz tuning. /// /// # Example /// ``` /// # fn sing(frequency: f64) {} /// let note = wmidi::Note::A3; /// sing(note.to_freq_f64()); /// ``` #[cfg(feature = "std")] #[inline(always)] pub fn to_freq_f64(self) -> f64 { let exp = (f64::from(self as u8) + 36.376_316_562_295_91) / 12.0; 2_f64.powf(exp) } /// Get the note relative to `self`. /// /// # Example /// ``` /// use wmidi::Note; /// fn minor_chord(root: Note) -> Result<[Note; 3], wmidi::Error> { /// Ok([root, root.step(3)?, root.step(7)?]) /// } /// assert_eq!(minor_chord(Note::C2), Ok([Note::C2, Note::Eb2, Note::G2])); /// ``` pub fn step(self, half_steps: i8) -> Result { let half_steps: i16 = half_steps.into(); let raw_note = self as i16 + half_steps; if Note::LOWEST_NOTE as i16 <= raw_note && raw_note <= Note::HIGHEST_NOTE as i16 { Ok(unsafe { Note::from_u8_unchecked(raw_note as u8) }) } else { Err(Error::NoteOutOfRange) } } /// Get a `str` representation of the note. For example: `"C3"` or `"A#/Bb2"`. pub fn to_str(self) -> &'static str { match self { Note::CMinus1 => "C-1", Note::DbMinus1 => "C#/Db-1", Note::DMinus1 => "D-1", Note::EbMinus1 => "D#/Eb-1", Note::EMinus1 => "E-1", Note::FMinus1 => "F-1", Note::GbMinus1 => "F#/Gb-1", Note::GMinus1 => "G-1", Note::AbMinus1 => "G#/Ab-1", Note::AMinus1 => "A-1", Note::BbMinus1 => "A#/Bb-1", Note::BMinus1 => "B-1", Note::C0 => "C0", Note::Db0 => "C#/Db0", Note::D0 => "D0", Note::Eb0 => "D#/Eb0", Note::E0 => "E0", Note::F0 => "F0", Note::Gb0 => "F#/Gb0", Note::G0 => "G0", Note::Ab0 => "G#/Ab0", Note::A0 => "A0", Note::Bb0 => "A#/Bb0", Note::B0 => "B0", Note::C1 => "C1", Note::Db1 => "C#/Db1", Note::D1 => "D1", Note::Eb1 => "D#/Eb1", Note::E1 => "E1", Note::F1 => "F1", Note::Gb1 => "F#/Gb1", Note::G1 => "G1", Note::Ab1 => "G#/Ab1", Note::A1 => "A1", Note::Bb1 => "A#/Bb1", Note::B1 => "B1", Note::C2 => "C2", Note::Db2 => "C#/Db2", Note::D2 => "D2", Note::Eb2 => "D#/Eb2", Note::E2 => "E2", Note::F2 => "F2", Note::Gb2 => "F#/Gb2", Note::G2 => "G2", Note::Ab2 => "G#/Ab2", Note::A2 => "A2", Note::Bb2 => "A#/Bb2", Note::B2 => "B2", Note::C3 => "C3", Note::Db3 => "C#/Db3", Note::D3 => "D3", Note::Eb3 => "D#/Eb3", Note::E3 => "E3", Note::F3 => "F3", Note::Gb3 => "F#/Gb3", Note::G3 => "G3", Note::Ab3 => "G#/Ab3", Note::A3 => "A3", Note::Bb3 => "A#/Bb3", Note::B3 => "B3", Note::C4 => "C4", Note::Db4 => "C#/Db4", Note::D4 => "D4", Note::Eb4 => "D#/Eb4", Note::E4 => "E4", Note::F4 => "F4", Note::Gb4 => "F#/Gb4", Note::G4 => "G4", Note::Ab4 => "G#/Ab4", Note::A4 => "A4", Note::Bb4 => "A#/Bb4", Note::B4 => "B4", Note::C5 => "C5", Note::Db5 => "C#/Db5", Note::D5 => "D5", Note::Eb5 => "D#/Eb5", Note::E5 => "E5", Note::F5 => "F5", Note::Gb5 => "F#/Gb5", Note::G5 => "G5", Note::Ab5 => "G#/Ab5", Note::A5 => "A5", Note::Bb5 => "A#/Bb5", Note::B5 => "B5", Note::C6 => "C6", Note::Db6 => "C#/Db6", Note::D6 => "D6", Note::Eb6 => "D#/Eb6", Note::E6 => "E6", Note::F6 => "F6", Note::Gb6 => "F#/Gb6", Note::G6 => "G6", Note::Ab6 => "G#/Ab6", Note::A6 => "A6", Note::Bb6 => "A#/Bb6", Note::B6 => "B6", Note::C7 => "C7", Note::Db7 => "C#/Db7", Note::D7 => "D7", Note::Eb7 => "D#/Eb7", Note::E7 => "E7", Note::F7 => "F7", Note::Gb7 => "F#/Gb7", Note::G7 => "G7", Note::Ab7 => "G#/Ab7", Note::A7 => "A7", Note::Bb7 => "A#/Bb7", Note::B7 => "B7", Note::C8 => "C8", Note::Db8 => "C#/Db8", Note::D8 => "D8", Note::Eb8 => "D#/Eb8", Note::E8 => "E8", Note::F8 => "F8", Note::Gb8 => "F#/Gb8", Note::G8 => "G8", Note::Ab8 => "G#/Ab8", Note::A8 => "A8", Note::Bb8 => "A#/Bb8", Note::B8 => "B8", Note::C9 => "C9", Note::Db9 => "C#/Db9", Note::D9 => "D9", Note::Eb9 => "D#/Eb9", Note::E9 => "E9", Note::F9 => "F9", Note::Gb9 => "F#/Gb9", Note::G9 => "G9", } } } /// Convert from a `u8` to a `Note`. The `u8` must be in the range [0, 127] inclusive. impl TryFrom for Note { type Error = Error; /// Creates a note from a `u8`. `note` must be between [0, 127] inclusive to create a valid /// note. /// /// # Example ///``` /// use std::convert::TryFrom; /// fn decode_note(number: u8) -> Result { /// let parsed_note = 60; /// let note = wmidi::Note::try_from(parsed_note)?; /// Ok(note) /// } ///``` #[inline(always)] fn try_from(note: u8) -> Result { if note > 127 { Err(Error::NoteOutOfRange) } else { Ok(unsafe { Note::from_u8_unchecked(note) }) } } } impl From for Note { #[inline(always)] fn from(note: crate::U7) -> Note { unsafe { Note::from_u8_unchecked(u8::from(note)) } } } /// Convert from a `Note` to a `u8`. impl From for u8 { /// # Example ///``` /// use std::convert::TryFrom; /// fn encode_note(note: wmidi::Note) -> u8 { /// u8::from(note) /// } ///``` #[inline(always)] fn from(note: Note) -> u8 { note as u8 } } impl fmt::Debug for Note { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}({})", self.to_str(), *self as u8) } } impl fmt::Display for Note { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.to_str()) } } #[cfg(test)] mod test { use super::*; #[cfg(feature = "std")] #[test] fn note_to_frequency() { let a440_f64 = Note::A4.to_freq_f64(); assert!((a440_f64 - 440.0).abs() < 1E-10, "{} != 440", a440_f64); let a440_f32 = Note::A4.to_freq_f32(); assert!((a440_f32 - 440.0).abs() < 1E-10, "{} != 440", a440_f32); } #[test] fn step() { assert_eq!(Note::CMinus1.step(12), Ok(Note::C0)); assert_eq!(Note::C0.step(-12), Ok(Note::CMinus1)); assert_eq!(Note::B3.step(1), Ok(Note::C4)); assert_eq!(Note::B3.step(100), Err(Error::NoteOutOfRange)); assert_eq!(Note::B3.step(-100), Err(Error::NoteOutOfRange)); } #[cfg(feature = "std")] #[test] fn test_debug() { let debug_str = format!("{:?}", Note::Bb3); assert!(debug_str.contains("Bb"), "{}", debug_str); assert!(debug_str.contains('3'), "{}", debug_str); assert!(debug_str.contains("A#"), "{}", debug_str); } }