├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── examples └── example-esp32s3 │ ├── .cargo │ └── config.toml │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── rust-toolchain.toml │ └── src │ └── main.rs └── src ├── class.rs ├── lib.rs ├── message ├── channel.rs ├── control_function.rs ├── data │ ├── mod.rs │ ├── u14.rs │ ├── u4.rs │ └── u7.rs ├── mod.rs ├── notes.rs └── raw.rs └── packet ├── cable_number.rs ├── code_index_number.rs ├── mod.rs └── reader.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target 3 | /.idea 4 | /.vscode 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.5.0] - 2025-01-17 9 | 10 | This release focuses on improving the internal message data types and their usage. 11 | 12 | ### Added 13 | 14 | - `Message` enum variants for *System Common* and *System Realtime* messages. 15 | - `U14` primitive value type used by *Pitch Wheel* and *Song Position Pointer* messages. 16 | - Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `U4`. 17 | - Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `InvalidU4`. 18 | - Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `InvalidU7`. 19 | - Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `Payload`. 20 | - Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `Raw`. 21 | - Re-exports of common data types in the `message` module. 22 | 23 | ### Changed 24 | 25 | - Changed pitch wheel `Message` variant from `PitchWheelChange(Channel, U7, U7)` to `PitchWheelChange(Channel, U14)`. Use `U14::from_split_u7` and `U14::split_u7` functions for conversions. 26 | 27 | ## [0.4.0] - 2025-01-03 28 | 29 | This release focuses on: 30 | 31 | - Increased usability by simplifying the internal module structure. 32 | - Interfacing with third-party crates like `midi-types`. 33 | - Support for System Exclusive messages (SysEx). 34 | 35 | **Important:** 36 | 37 | - The `message` module containing the `Message` struct and related types is now gated behind the `message-types` feature. This feature is enabled by default. 38 | - The constants `USB_AUDIO_CLASS`, `USB_AUDIOCONTROL_SUBCLASS` and `USB_MIDISTREAMING_SUBCLASS` are now private to prevent them from being used in the device setup. Doing so would lead to incorrect enumeration on certain hosts (e.g. macOS). 39 | 40 | ### Added 41 | 42 | - `UsbMidiEventPacket::cable_number` function. 43 | - `UsbMidiEventPacket::header` function. 44 | - `UsbMidiEventPacket::payload_bytes` function. 45 | - `UsbMidiEventPacket::as_raw_bytes` function. 46 | - `UsbMidiEventPacket::to_raw_bytes` function. 47 | - `UsbMidiEventPacket::try_from_payload_bytes` function. 48 | - `UsbMidiEventPacket::is_sysex` function. 49 | - `UsbMidiEventPacket::is_sysex_start` function. 50 | - `UsbMidiEventPacket::is_sysex_end` function. 51 | - `TryFrom<&UsbMidiEventPacket>` implementation for `Message` type. 52 | - `Message::into_packet` function. 53 | - `Message::code_index_number` function. 54 | - `CodeIndexNumber::try_from_payload` function. 55 | - `CodeIndexNumber::payload_size` function. 56 | - `CableNumber::Cable0` as default value. 57 | - `FromOverFlow for U4` implementation. 58 | - `FromClamped for U4` implementation. 59 | - Re-exports of common items in crate root. 60 | 61 | ### Changed 62 | 63 | - Renamed `MidiClass` to `UsbMidiClass`. 64 | - Renamed `UsbMidiClass::send_message` function to `UsbMidiClass::send_packet`. 65 | - Renamed `midi_device` module to `class`. 66 | - Renamed `usb_midi` module to `packet` and moved it into crate root. 67 | - Renamed `midi_packet_reader` module to `reader`. 68 | - Renamed `MidiPacketBufferReader` to `UsbMidiPacketReader`. 69 | - Renamed `MidiPacketParsingError` to `UsbMidiEventPacketError` 70 | - Renamed `MidiReadError` to `UsbMidiReadError` 71 | - Moved `usb_midi_event_packet` code into parent `packet` module. 72 | - Moved `channel` and `notes` modules into `message` module. 73 | - Moved `message` module to crate root. 74 | - Moved `byte` submodules into `message::data` module. 75 | - Moved `from_traits` code into parent `data` module. 76 | - Consolidated separate `InvalidCableNumber` struct into `UsbMidiEventPacketError`. 77 | - Consolidated separate `InvalidCodeIndexNumber` struct into `UsbMidiEventPacketError`. 78 | - Converted `CodeIndexNumber` struct to enum. 79 | - Moved descriptor constants into class module and made them private. 80 | - Set edition to 2021. 81 | 82 | ### Removed 83 | 84 | - `UsbMidiEventPacket::cable_number` field, use function instead. 85 | - `UsbMidiEventPacket::message` field, use `Message::try_from(&UsbMidiEventPacket)` instead. 86 | - `UsbMidiEventPacket::from_midi` function, use `Message::into_packet` instead. 87 | - `CodeIndexNumber::find_from_message` function, use `Message::code_index_number` instead. 88 | - `From for U4` implementation. 89 | - `From for U4` implementation. 90 | 91 | ## [0.3.0] - 2024-05-27 92 | 93 | ### Changed 94 | 95 | - Updated `usb-device` dependency to 0.3. 96 | - Updated `num_enum` dependency to 0.7.2. 97 | - Extended endpoint descriptors to 9 bytes as stated in specification. 98 | 99 | ### Removed 100 | 101 | - Removed unused `embedded-hal` and `nb` dependencies. 102 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usbd-midi" 3 | version = "0.5.0" 4 | authors = [ 5 | "Beau Trepp ", 6 | "Florian Jung ", 7 | "Oliver Rockstedt ", 8 | ] 9 | edition = "2021" 10 | rust-version = "1.78" 11 | description = "USB MIDI device class implementation for use with usb-device." 12 | homepage = "https://github.com/rust-embedded-community/usbd-midi" 13 | repository = "https://github.com/rust-embedded-community/usbd-midi" 14 | license = "MIT" 15 | categories = ["no-std", "embedded", "hardware-support"] 16 | keywords = ["usb", "midi"] 17 | 18 | [features] 19 | default = ["message-types"] 20 | message-types = ["dep:num_enum"] 21 | 22 | [dependencies] 23 | usb-device = "0.3" 24 | 25 | [dependencies.num_enum] 26 | version = "0.7.3" 27 | default-features = false 28 | optional = true 29 | 30 | [lints.rust] 31 | missing_docs = "warn" 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Beau Trepp 2 | Copyright (c) 2021 Florian Jung 3 | Copyright (c) 2024 Oliver Rockstedt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # usbd-midi 2 | 3 | A USB MIDI device class implementation for [usb-device](https://crates.io/crates/usb-device) based on the [USB Device Class Definition for MIDI Devices](https://www.usb.org/sites/default/files/midi10.pdf) specification. 4 | 5 | This class allows the device to exchange MIDI messages with a host like a desktop computer. It requires the use of a driver (e.g. a HAL) that implements the `usb-device` traits. 6 | 7 | **NOTE:** only MIDI 1.0 protocol is currently supported. 8 | 9 | ## Message Types 10 | 11 | While the crate focuses on transfer functionality, it provides some basic message types with conversions for convenience. These types are gated behind a `message-types` feature, which is enabled by default. 12 | 13 | For more complex use cases, it is recommended to use a specialized crate like [midi-types](https://crates.io/crates/midi-types) or [wmidi](https://crates.io/crates/wmidi) and interface with it by using the raw event packet bytes. The [ESP32-S3 example](examples/example-esp32s3/) shows how to do this in detail. 14 | 15 | ## Examples 16 | 17 | The example below shows some basic usage without any platform-dependent parts. Please refer to the [examples](examples/) directory for code that can be run on real hardware. 18 | 19 | ### Receive MIDI 20 | 21 | Turn on an LED as long as note C2 is pressed. 22 | 23 | ```rust ignore 24 | use usb_device::prelude::*; 25 | use usbd_midi::{ 26 | message::{Message, Channel, Note}, 27 | UsbMidiClass, 28 | UsbMidiPacketReader, 29 | }; 30 | 31 | // Prerequisites, must be setup according to the used board. 32 | let mut led = todo!(); // Must implement `embedded_hal::digital::OutputPin`. 33 | let usb_bus = todo!(); // Must be of type `usb_device::bus::UsbBusAllocator`. 34 | 35 | // Create a MIDI class with 1 input and 1 output jack. 36 | let mut midi = UsbMidiClass::new(&usb_bus, 1, 1).unwrap(); 37 | 38 | let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x5e4)) 39 | .device_class(0) 40 | .device_sub_class(0) 41 | .strings(&[StringDescriptors::default() 42 | .manufacturer("Music Company") 43 | .product("MIDI Device") 44 | .serial_number("12345678")]) 45 | .unwrap() 46 | .build(); 47 | 48 | loop { 49 | if !usb_dev.poll(&mut [&mut midi]) { 50 | continue; 51 | } 52 | 53 | let mut buffer = [0; 64]; 54 | 55 | if let Ok(size) = midi.read(&mut buffer) { 56 | let packet_reader = UsbMidiPacketReader::new(&buffer, size); 57 | for packet in packet_reader.into_iter() { 58 | if let Ok(packet) = packet { 59 | match Message::try_from(&packet).unwrap() { 60 | Message::NoteOn(Channel1, Note::C2, ..) => { 61 | led.set_low().unwrap(); 62 | }, 63 | Message::NoteOff(Channel1, Note::C2, ..) => { 64 | led.set_high().unwrap(); 65 | }, 66 | _ => {} 67 | } 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ## Using more than one MIDI port 75 | 76 | Calling `MidiClass::new(&usb_bus, N, M);` with `N, M >= 1` to provide more 77 | than one input or output port requires the `control-buffer-256` feature of 78 | the `usb-device` crate: 79 | 80 | Cargo.toml: 81 | 82 | ```ignore 83 | usb-device = { version = ">=0.3.2", features = ["control-buffer-256"] } 84 | ``` 85 | 86 | Up to 5 in/out pairs can be used this way until we again run out of buffer 87 | space. Note that exceeding the available buffer space will silently fail 88 | to send the descriptors correctly, no obvious `panic!` will hint the 89 | actual problem. 90 | -------------------------------------------------------------------------------- /examples/example-esp32s3/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.xtensa-esp32s3-none-elf] 2 | runner = "espflash flash --monitor" 3 | 4 | [build] 5 | rustflags = ["-C", "link-arg=-nostartfiles", "-C", "link-arg=-Wl,-Tlinkall.x"] 6 | target = "xtensa-esp32s3-none-elf" 7 | 8 | [unstable] 9 | build-std = ["core", "alloc"] 10 | -------------------------------------------------------------------------------- /examples/example-esp32s3/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | -------------------------------------------------------------------------------- /examples/example-esp32s3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-esp32s3" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | esp-hal = { version = "0.23", features = ["esp32s3"] } 8 | esp-backtrace = { version = "0.15.0", features = [ 9 | "esp32s3", 10 | "panic-handler", 11 | "println", 12 | ] } 13 | esp-println = { version = "0.13.0", features = ["esp32s3", "log"] } 14 | usb-device = { version = "0.3.2", features = ["control-buffer-256"] } 15 | usbd-midi = { path = "../../" } 16 | midi-convert = "0.2.0" 17 | heapless = "0.8.0" 18 | -------------------------------------------------------------------------------- /examples/example-esp32s3/README.md: -------------------------------------------------------------------------------- 1 | # ESP32-S3 Example 2 | 3 | This example was developed and tested on an [ESP32-S3-DevKitC-1](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/index.html) using the [esp-hal crate](https://crates.io/crates/esp-hal). 4 | 5 | It features: 6 | 7 | - Sending and receiving of regular MIDI messages. 8 | - Sending and receiving of MIDI System Exclusive messages with buffering. 9 | - Conversion of USB MIDI packets from and to types provided by the [midi-types crate](https://crates.io/crates/midi-types). 10 | 11 | It does not provide a fully production-ready setup, especially time-critical tasks like polling the USB bus in an interrupt and managing bus timeouts are out of scope of this example. 12 | 13 | ## Requirements 14 | 15 | To build the example, an installed toolchain for the Xtensa target is required. Please refer to the [Rust on ESP book](https://docs.esp-rs.org/book/) for further instructions. 16 | 17 | You can build the example by running: 18 | 19 | cargo build --release 20 | 21 | If [espflash](https://crates.io/crates/espflash) is installed, you can flash the example to the board and run it: 22 | 23 | cargo run --release 24 | 25 | ## Functionality 26 | 27 | - Incoming MIDI messages are logged to the console. 28 | - Pressing and releasing the *BOOT* button on the board sends MIDI messages. 29 | - A received *Device Inquiry* SysEx request is responded to the host. 30 | 31 | Please note that all chosen vendor and product ids and names are just for demonstration purposes and should not be used with a real product. 32 | -------------------------------------------------------------------------------- /examples/example-esp32s3/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /examples/example-esp32s3/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Example for ESP32-S3. Tested on ESP32-S3-DevKitC-1. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | use core::ptr::addr_of_mut; 7 | 8 | use esp_backtrace as _; 9 | use esp_hal::{clock, gpio, otg_fs, xtensa_lx_rt, Config}; 10 | use esp_println::println; 11 | use heapless::Vec; 12 | use midi_convert::midi_types::{Channel, MidiMessage, Note, Value7}; 13 | use midi_convert::{parse::MidiTryParseSlice, render_slice::MidiRenderSlice}; 14 | use usb_device::prelude::*; 15 | use usbd_midi::{CableNumber, UsbMidiClass, UsbMidiEventPacket, UsbMidiPacketReader}; 16 | 17 | static mut EP_MEMORY: [u32; 1024] = [0; 1024]; 18 | 19 | // Size of the used SysEx buffers in bytes. 20 | const SYSEX_BUFFER_SIZE: usize = 64; 21 | 22 | #[xtensa_lx_rt::entry] 23 | fn main() -> ! { 24 | // Some basic setup to run the MCU at maximum clock speed. 25 | let mut config = Config::default(); 26 | config.cpu_clock = clock::CpuClock::_240MHz; 27 | let peripherals = esp_hal::init(config); 28 | 29 | let usb_bus_allocator = otg_fs::UsbBus::new( 30 | otg_fs::Usb::new(peripherals.USB0, peripherals.GPIO20, peripherals.GPIO19), 31 | unsafe { &mut *addr_of_mut!(EP_MEMORY) }, 32 | ); 33 | 34 | // Create a MIDI class with 1 input and 1 output jack. 35 | let mut midi_class = UsbMidiClass::new(&usb_bus_allocator, 1, 1).unwrap(); 36 | 37 | // Build the device. It's important to use `0` for the class and subclass fields because 38 | // otherwise the device will not enumerate correctly on certain hosts. 39 | let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, UsbVidPid(0x16c0, 0x5e4)) 40 | .device_class(0) 41 | .device_sub_class(0) 42 | .strings(&[StringDescriptors::default() 43 | .manufacturer("Music Company") 44 | .product("MIDI Device") 45 | .serial_number("12345678")]) 46 | .unwrap() 47 | .build(); 48 | 49 | // This is the *BOOT* button on the ESP32-S3-DevKitC-1. 50 | let button = gpio::Input::new(peripherals.GPIO0, gpio::Pull::Up); 51 | let mut last_button_level = button.level(); 52 | 53 | // Buffer for received SysEx messages from the host. 54 | let mut sysex_receive_buffer = Vec::::new(); 55 | 56 | loop { 57 | if usb_dev.poll(&mut [&mut midi_class]) { 58 | // Receive messages. 59 | let mut buffer = [0; 64]; 60 | 61 | if let Ok(size) = midi_class.read(&mut buffer) { 62 | let packet_reader = UsbMidiPacketReader::new(&buffer, size); 63 | for packet in packet_reader.into_iter().flatten() { 64 | if !packet.is_sysex() { 65 | // Just a regular 3-byte message that can be processed directly. 66 | let message = MidiMessage::try_parse_slice(packet.payload_bytes()); 67 | println!( 68 | "Regular Message, cable: {:?}, message: {:?}", 69 | packet.cable_number(), 70 | message 71 | ); 72 | } else { 73 | // If a packet containing a SysEx payload is detected, the data is saved 74 | // into a buffer and processed after the message is complete. 75 | if packet.is_sysex_start() { 76 | println!("SysEx message start"); 77 | sysex_receive_buffer.clear(); 78 | } 79 | 80 | match sysex_receive_buffer.extend_from_slice(packet.payload_bytes()) { 81 | Ok(_) => { 82 | if packet.is_sysex_end() { 83 | println!("SysEx message end"); 84 | println!("Buffered SysEx message: {:?}", sysex_receive_buffer); 85 | 86 | // Process the SysEx message as request in a separate function 87 | // and send an optional response back to the host. 88 | if let Some(response) = 89 | process_sysex(sysex_receive_buffer.as_ref()) 90 | { 91 | for chunk in response.chunks(3) { 92 | let packet = UsbMidiEventPacket::try_from_payload_bytes( 93 | CableNumber::Cable0, 94 | chunk, 95 | ); 96 | match packet { 97 | Ok(packet) => loop { 98 | // Make sure to add some timeout in case the host 99 | // does not read the data. 100 | let result = 101 | midi_class.send_packet(packet.clone()); 102 | match result { 103 | Ok(_) => break, 104 | Err(err) => { 105 | if err != UsbError::WouldBlock { 106 | break; 107 | } 108 | } 109 | } 110 | }, 111 | Err(err) => println!( 112 | "SysEx response packet error: {:?}", 113 | err 114 | ), 115 | } 116 | } 117 | } 118 | } 119 | } 120 | Err(_) => { 121 | println!("SysEx buffer overflow."); 122 | break; 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | let button_level = button.level(); 131 | 132 | // Send a message when the button state changes. 133 | if button_level != last_button_level { 134 | last_button_level = button_level; 135 | 136 | let mut bytes = [0; 3]; 137 | 138 | let message = if button_level == gpio::Level::Low { 139 | MidiMessage::NoteOn(Channel::C1, Note::C3, Value7::from(100)) 140 | } else { 141 | MidiMessage::NoteOff(Channel::C1, Note::C3, Value7::from(0)) 142 | }; 143 | 144 | message.render_slice(&mut bytes); 145 | 146 | let packet = 147 | UsbMidiEventPacket::try_from_payload_bytes(CableNumber::Cable0, &bytes).unwrap(); 148 | 149 | // Try to send the packet. 150 | // An `UsbError::WouldBlock` is returned if the host has not read previous data. 151 | let result = midi_class.send_packet(packet); 152 | println!("Send result {:?}", result); 153 | } 154 | } 155 | } 156 | 157 | /// Processes a SysEx request and returns an optional response. 158 | pub fn process_sysex(request: &[u8]) -> Option> { 159 | /// Identity request message. 160 | /// 161 | /// See section *DEVICE INQUIRY* of the *MIDI 1.0 Detailed Specification* for further details. 162 | const IDENTITY_REQUEST: [u8; 6] = [0xF0, 0x7E, 0x7F, 0x06, 0x01, 0xF7]; 163 | 164 | if request == IDENTITY_REQUEST { 165 | let mut response = Vec::::new(); 166 | response 167 | .extend_from_slice(&[ 168 | 0xF0, 0x7E, 0x7F, 0x06, 0x02, // Header 169 | 0x01, // Manufacturer ID 170 | 0x02, // Family code 171 | 0x03, // Family code 172 | 0x04, // Family member code 173 | 0x05, // Family member code 174 | 0x00, // Software revision level 175 | 0x00, // Software revision level 176 | 0x00, // Software revision level 177 | 0x00, // Software revision level 178 | 0xF7, 179 | ]) 180 | .ok(); 181 | 182 | return Some(response); 183 | } 184 | 185 | None 186 | } 187 | -------------------------------------------------------------------------------- /src/class.rs: -------------------------------------------------------------------------------- 1 | //! Contains the class implementation. 2 | 3 | use usb_device::class_prelude::*; 4 | use usb_device::Result; 5 | 6 | use crate::packet::{UsbMidiEventPacket, UsbMidiEventPacketError}; 7 | 8 | // Constants for use in descriptors. 9 | const USB_AUDIO_CLASS: u8 = 0x01; 10 | const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; 11 | const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; 12 | const MIDI_IN_JACK_SUBTYPE: u8 = 0x02; 13 | const MIDI_OUT_JACK_SUBTYPE: u8 = 0x03; 14 | const EMBEDDED: u8 = 0x01; 15 | const EXTERNAL: u8 = 0x02; 16 | const CS_INTERFACE: u8 = 0x24; 17 | const CS_ENDPOINT: u8 = 0x25; 18 | const HEADER_SUBTYPE: u8 = 0x01; 19 | const MS_HEADER_SUBTYPE: u8 = 0x01; 20 | const MS_GENERAL: u8 = 0x01; 21 | 22 | const MIDI_IN_SIZE: u8 = 0x06; 23 | const MIDI_OUT_SIZE: u8 = 0x09; 24 | 25 | /// Size of a single packet in bytes. 26 | pub const MIDI_PACKET_SIZE: usize = 4; 27 | 28 | /// Maximum transfer size of an endpoint. 29 | pub const MAX_PACKET_SIZE: usize = 64; 30 | 31 | /// Packet-level implementation of a USB MIDI device. 32 | pub struct UsbMidiClass<'a, B: UsbBus> { 33 | standard_ac: InterfaceNumber, 34 | standard_mc: InterfaceNumber, 35 | standard_bulkout: EndpointOut<'a, B>, 36 | standard_bulkin: EndpointIn<'a, B>, 37 | n_in_jacks: u8, 38 | n_out_jacks: u8, 39 | } 40 | 41 | /// Error variants for read operations. 42 | #[derive(Debug, Clone, Eq, PartialEq)] 43 | pub enum UsbMidiReadError { 44 | /// Parsing of the packet failed. 45 | ParsingFailed(UsbMidiEventPacketError), 46 | /// USB stack error returned from `usb-device`. 47 | UsbError(UsbError), 48 | } 49 | 50 | /// Error returned when passing invalid arguments to `UsbMidiClass::new`. 51 | #[derive(Debug)] 52 | pub struct InvalidArguments; 53 | 54 | impl UsbMidiClass<'_, B> { 55 | /// Creates a new UsbMidiClass with the provided UsbBus and `n_in/out_jacks` embedded input/output jacks 56 | /// (or "cables", depending on the terminology). 57 | /// Note that a maximum of 16 in and 16 out jacks is supported. 58 | pub fn new( 59 | alloc: &UsbBusAllocator, 60 | n_in_jacks: u8, 61 | n_out_jacks: u8, 62 | ) -> core::result::Result, InvalidArguments> { 63 | if n_in_jacks > 16 || n_out_jacks > 16 { 64 | return Err(InvalidArguments); 65 | } 66 | Ok(UsbMidiClass { 67 | standard_ac: alloc.interface(), 68 | standard_mc: alloc.interface(), 69 | standard_bulkout: alloc.bulk(MAX_PACKET_SIZE as u16), 70 | standard_bulkin: alloc.bulk(MAX_PACKET_SIZE as u16), 71 | n_in_jacks, 72 | n_out_jacks, 73 | }) 74 | } 75 | 76 | /// Sends bytes from a 4-byte buffer and returns either the transferred size or an error. 77 | pub fn send_bytes(&mut self, buffer: [u8; 4]) -> Result { 78 | self.standard_bulkin.write(&buffer) 79 | } 80 | 81 | /// Sends a `UsbMidiEventPacket` and returns either the transferred size or an error. 82 | pub fn send_packet(&mut self, usb_midi: UsbMidiEventPacket) -> Result { 83 | let bytes: [u8; MIDI_PACKET_SIZE] = usb_midi.into(); 84 | self.standard_bulkin.write(&bytes) 85 | } 86 | 87 | /// Reads received bytes into a buffer and returns either the transferred size or an error. 88 | pub fn read(&mut self, buffer: &mut [u8]) -> Result { 89 | self.standard_bulkout.read(buffer) 90 | } 91 | 92 | /// Calculates the index'th external midi in jack id. 93 | fn in_jack_id_ext(&self, index: u8) -> u8 { 94 | debug_assert!(index < self.n_in_jacks); 95 | 2 * index + 1 96 | } 97 | 98 | /// Calculates the index'th embedded midi out jack id. 99 | fn out_jack_id_emb(&self, index: u8) -> u8 { 100 | debug_assert!(index < self.n_in_jacks); 101 | 2 * index + 2 102 | } 103 | 104 | /// Calculates the index'th external midi out jack id. 105 | fn out_jack_id_ext(&self, index: u8) -> u8 { 106 | debug_assert!(index < self.n_out_jacks); 107 | 2 * self.n_in_jacks + 2 * index + 1 108 | } 109 | 110 | /// Calculates the index'th embedded midi in jack id. 111 | fn in_jack_id_emb(&self, index: u8) -> u8 { 112 | debug_assert!(index < self.n_out_jacks); 113 | 2 * self.n_in_jacks + 2 * index + 2 114 | } 115 | } 116 | 117 | impl UsbClass for UsbMidiClass<'_, B> { 118 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { 119 | // AUDIO CONTROL STANDARD 120 | writer.interface( 121 | self.standard_ac, 122 | USB_AUDIO_CLASS, 123 | USB_AUDIOCONTROL_SUBCLASS, 124 | 0, // no protocol, 125 | )?; 126 | 127 | // AUDIO CONTROL EXTRA INFO 128 | writer.write( 129 | CS_INTERFACE, 130 | &[ 131 | HEADER_SUBTYPE, 132 | 0x00, 133 | 0x01, // REVISION 134 | 0x09, 135 | 0x00, // SIZE of class specific descriptions 136 | 0x01, // Number of streaming interfaces 137 | 0x01, // MIDIStreaming interface 1 belongs to this AC interface 138 | ], 139 | )?; 140 | 141 | // Streaming Standard 142 | writer.interface( 143 | self.standard_mc, 144 | USB_AUDIO_CLASS, 145 | USB_MIDISTREAMING_SUBCLASS, 146 | 0, // no protocol 147 | )?; // Num endpoints? 148 | 149 | let midi_streaming_start_byte = writer.position(); 150 | let midi_streaming_total_length = 7 151 | + (self.n_in_jacks + self.n_out_jacks) as usize 152 | * (MIDI_IN_SIZE + MIDI_OUT_SIZE) as usize 153 | + 9 154 | + (4 + self.n_out_jacks as usize) 155 | + 9 156 | + (4 + self.n_in_jacks as usize); 157 | 158 | // Streaming extra info 159 | writer.write( 160 | // len = 7 161 | CS_INTERFACE, 162 | &[ 163 | MS_HEADER_SUBTYPE, 164 | 0x00, 165 | 0x01, //REVISION 166 | (midi_streaming_total_length & 0xFF) as u8, 167 | ((midi_streaming_total_length >> 8) & 0xFF) as u8, 168 | ], 169 | )?; 170 | 171 | // JACKS 172 | for i in 0..self.n_in_jacks { 173 | writer.write( 174 | // len = 6 = MIDI_IN_SIZE 175 | CS_INTERFACE, 176 | &[ 177 | MIDI_IN_JACK_SUBTYPE, 178 | EXTERNAL, 179 | self.in_jack_id_ext(i), // id 180 | 0x00, 181 | ], 182 | )?; 183 | } 184 | 185 | for i in 0..self.n_out_jacks { 186 | writer.write( 187 | // len = 6 = MIDI_IN_SIZE 188 | CS_INTERFACE, 189 | &[ 190 | MIDI_IN_JACK_SUBTYPE, 191 | EMBEDDED, 192 | self.in_jack_id_emb(i), // id 193 | 0x00, 194 | ], 195 | )?; 196 | } 197 | 198 | for i in 0..self.n_out_jacks { 199 | writer.write( 200 | // len = 9 = MIDI_OUT_SIZE 201 | CS_INTERFACE, 202 | &[ 203 | MIDI_OUT_JACK_SUBTYPE, 204 | EXTERNAL, 205 | self.out_jack_id_ext(i), // id 206 | 0x01, // 1 pin 207 | self.in_jack_id_emb(i), // pin is connected to this entity... 208 | 0x01, // ...to the first pin 209 | 0x00, 210 | ], 211 | )?; 212 | } 213 | 214 | for i in 0..self.n_in_jacks { 215 | writer.write( 216 | // len = 9 = MIDI_OUT_SIZE 217 | CS_INTERFACE, 218 | &[ 219 | MIDI_OUT_JACK_SUBTYPE, 220 | EMBEDDED, 221 | self.out_jack_id_emb(i), // id 222 | 0x01, // 1 pin 223 | self.in_jack_id_ext(i), // pin is connected to this entity... 224 | 0x01, // ...to the first pin 225 | 0x00, 226 | ], 227 | )?; 228 | } 229 | 230 | let mut endpoint_data = [ 231 | MS_GENERAL, 0, // number of jacks. must be filled in! 232 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233 | 0, // jack mappings. must be filled in and cropped. 234 | ]; 235 | 236 | writer.endpoint_ex(&self.standard_bulkout, |data| { 237 | data[0] = 0; // bRefresh 238 | data[1] = 0; // bSynchAddress 239 | Ok(2) 240 | })?; // len = 9 241 | 242 | endpoint_data[1] = self.n_out_jacks; 243 | for i in 0..self.n_out_jacks { 244 | endpoint_data[2 + i as usize] = self.in_jack_id_emb(i); 245 | } 246 | writer.write( 247 | // len = 4 + self.n_out_jacks 248 | CS_ENDPOINT, 249 | &endpoint_data[0..2 + self.n_out_jacks as usize], 250 | )?; 251 | 252 | writer.endpoint_ex(&self.standard_bulkin, |data| { 253 | data[0] = 0; // bRefresh 254 | data[1] = 0; // bSynchAddress 255 | Ok(2) 256 | })?; // len = 9 257 | 258 | endpoint_data[1] = self.n_in_jacks; 259 | for i in 0..self.n_in_jacks { 260 | endpoint_data[2 + i as usize] = self.out_jack_id_emb(i); 261 | } 262 | writer.write( 263 | // len = 4 + self.n_in_jacks 264 | CS_ENDPOINT, 265 | &endpoint_data[0..2 + self.n_in_jacks as usize], 266 | )?; 267 | 268 | let midi_streaming_end_byte = writer.position(); 269 | assert!(midi_streaming_end_byte - midi_streaming_start_byte == midi_streaming_total_length); 270 | 271 | Ok(()) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![no_std] 3 | 4 | pub mod class; 5 | pub mod packet; 6 | 7 | #[cfg(feature = "message-types")] 8 | pub mod message; 9 | 10 | pub use crate::class::{UsbMidiClass, UsbMidiReadError}; 11 | pub use crate::packet::cable_number::CableNumber; 12 | pub use crate::packet::reader::UsbMidiPacketReader; 13 | pub use crate::packet::{UsbMidiEventPacket, UsbMidiEventPacketError}; 14 | 15 | #[cfg(feature = "message-types")] 16 | pub use crate::message::Message; 17 | -------------------------------------------------------------------------------- /src/message/channel.rs: -------------------------------------------------------------------------------- 1 | //! Enum representing the MIDI channel. 2 | 3 | /// The Channel is a value ranging from 0x0 to 0xF. 4 | /// 5 | /// This is a standard midi concept. 6 | /// Note Channel1 = 0 on the wire. 7 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 8 | #[repr(u8)] 9 | pub enum Channel { 10 | /// MIDI channel 1. 11 | Channel1 = 0x0, 12 | /// MIDI channel 2. 13 | Channel2 = 0x1, 14 | /// MIDI channel 3. 15 | Channel3 = 0x2, 16 | /// MIDI channel 4. 17 | Channel4 = 0x3, 18 | /// MIDI channel 5. 19 | Channel5 = 0x4, 20 | /// MIDI channel 6. 21 | Channel6 = 0x5, 22 | /// MIDI channel 7. 23 | Channel7 = 0x6, 24 | /// MIDI channel 8. 25 | Channel8 = 0x7, 26 | /// MIDI channel 9. 27 | Channel9 = 0x8, 28 | /// MIDI channel 10. 29 | Channel10 = 0x9, 30 | /// MIDI channel 11. 31 | Channel11 = 0xA, 32 | /// MIDI channel 12. 33 | Channel12 = 0xB, 34 | /// MIDI channel 13. 35 | Channel13 = 0xC, 36 | /// MIDI channel 14. 37 | Channel14 = 0xD, 38 | /// MIDI channel 15. 39 | Channel15 = 0xE, 40 | /// MIDI channel 16. 41 | Channel16 = 0xF, 42 | } 43 | 44 | /// Error indicating an invalid MIDI channel. 45 | pub struct InvalidChannel(pub u8); 46 | 47 | impl TryFrom for Channel { 48 | type Error = InvalidChannel; 49 | fn try_from(value: u8) -> Result { 50 | match value { 51 | x if x == Channel::Channel1 as u8 => Ok(Channel::Channel1), 52 | x if x == Channel::Channel2 as u8 => Ok(Channel::Channel2), 53 | x if x == Channel::Channel3 as u8 => Ok(Channel::Channel3), 54 | x if x == Channel::Channel4 as u8 => Ok(Channel::Channel4), 55 | x if x == Channel::Channel5 as u8 => Ok(Channel::Channel5), 56 | x if x == Channel::Channel6 as u8 => Ok(Channel::Channel6), 57 | x if x == Channel::Channel7 as u8 => Ok(Channel::Channel7), 58 | x if x == Channel::Channel8 as u8 => Ok(Channel::Channel8), 59 | x if x == Channel::Channel9 as u8 => Ok(Channel::Channel9), 60 | x if x == Channel::Channel10 as u8 => Ok(Channel::Channel10), 61 | x if x == Channel::Channel11 as u8 => Ok(Channel::Channel11), 62 | x if x == Channel::Channel12 as u8 => Ok(Channel::Channel12), 63 | x if x == Channel::Channel13 as u8 => Ok(Channel::Channel13), 64 | x if x == Channel::Channel14 as u8 => Ok(Channel::Channel14), 65 | x if x == Channel::Channel15 as u8 => Ok(Channel::Channel15), 66 | x if x == Channel::Channel16 as u8 => Ok(Channel::Channel16), 67 | _ => Err(InvalidChannel(value)), 68 | } 69 | } 70 | } 71 | 72 | impl From for u8 { 73 | fn from(src: Channel) -> u8 { 74 | src as u8 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | 81 | use super::*; 82 | macro_rules! channel_test { 83 | ($($id:ident:$value:expr,)*) => { 84 | $( 85 | #[test] 86 | fn $id() { 87 | let (input,expected) = $value; 88 | assert_eq!(input as u8, expected); 89 | } 90 | )* 91 | } 92 | } 93 | 94 | channel_test! { 95 | channel_1: (Channel::Channel1,0), 96 | channel_2: (Channel::Channel2,1), 97 | channel_3: (Channel::Channel3,2), 98 | channel_4: (Channel::Channel4,3), 99 | channel_5: (Channel::Channel5,4), 100 | channel_6: (Channel::Channel6,5), 101 | channel_7: (Channel::Channel7,6), 102 | channel_8: (Channel::Channel8,7), 103 | channel_9: (Channel::Channel9,8), 104 | channel_10: (Channel::Channel10,9), 105 | channel_11: (Channel::Channel11,10), 106 | channel_12: (Channel::Channel12,11), 107 | channel_13: (Channel::Channel13,12), 108 | channel_14: (Channel::Channel14,13), 109 | channel_15: (Channel::Channel15,14), 110 | channel_16: (Channel::Channel16,15), 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/message/control_function.rs: -------------------------------------------------------------------------------- 1 | //! Control function definitions. 2 | 3 | use crate::message::data::u7::U7; 4 | 5 | /// Custom type for a control function. 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub struct ControlFunction(pub U7); 8 | 9 | /// Control Functions as defined in the MIDI 1.0 Specification. 10 | /// 11 | /// Source: 12 | #[allow(missing_docs)] 13 | impl ControlFunction { 14 | pub const BANK_SELECT_0: Self = ControlFunction(U7(0)); 15 | pub const MOD_WHEEL_1: Self = ControlFunction(U7(1)); 16 | pub const BREATH_CONTROLLER_2: Self = ControlFunction(U7(2)); 17 | pub const UNDEFINED_3: Self = ControlFunction(U7(3)); 18 | pub const FOOT_CONTROLLER_4: Self = ControlFunction(U7(4)); 19 | pub const PORTAMENTO_TIME_5: Self = ControlFunction(U7(5)); 20 | pub const DATA_ENTRY_MSB_6: Self = ControlFunction(U7(6)); 21 | pub const CHANNEL_VOLUME_7: Self = ControlFunction(U7(7)); 22 | pub const BALANCE_8: Self = ControlFunction(U7(8)); 23 | pub const UNDEFINED_9: Self = ControlFunction(U7(9)); 24 | pub const PAN_10: Self = ControlFunction(U7(10)); 25 | pub const EXPRESSION_CONTROLLER_11: Self = ControlFunction(U7(11)); 26 | pub const EFFECT_CONTROL_1_12: Self = ControlFunction(U7(12)); 27 | pub const EFFECT_CONTROL_2_13: Self = ControlFunction(U7(13)); 28 | pub const UNDEFINED_14: Self = ControlFunction(U7(14)); 29 | pub const UNDEFINED_15: Self = ControlFunction(U7(15)); 30 | pub const GENERAL_PURPOSE_CONTROLLER_1_16: Self = ControlFunction(U7(16)); 31 | pub const GENERAL_PURPOSE_CONTROLLER_2_17: Self = ControlFunction(U7(17)); 32 | pub const GENERAL_PURPOSE_CONTROLLER_3_18: Self = ControlFunction(U7(18)); 33 | pub const GENERAL_PURPOSE_CONTROLLER_4_19: Self = ControlFunction(U7(19)); 34 | pub const UNDEFINED_20: Self = ControlFunction(U7(20)); 35 | pub const UNDEFINED_21: Self = ControlFunction(U7(21)); 36 | pub const UNDEFINED_22: Self = ControlFunction(U7(22)); 37 | pub const UNDEFINED_23: Self = ControlFunction(U7(23)); 38 | pub const UNDEFINED_24: Self = ControlFunction(U7(24)); 39 | pub const UNDEFINED_25: Self = ControlFunction(U7(25)); 40 | pub const UNDEFINED_26: Self = ControlFunction(U7(26)); 41 | pub const UNDEFINED_27: Self = ControlFunction(U7(27)); 42 | pub const UNDEFINED_28: Self = ControlFunction(U7(28)); 43 | pub const UNDEFINED_29: Self = ControlFunction(U7(29)); 44 | pub const UNDEFINED_30: Self = ControlFunction(U7(30)); 45 | pub const UNDEFINED_31: Self = ControlFunction(U7(31)); 46 | pub const LSB_FOR_BANK_SELECT_32: Self = ControlFunction(U7(32)); 47 | pub const LSB_FOR_MOD_WHEEL_33: Self = ControlFunction(U7(33)); 48 | pub const LSB_FOR_BREATH_CONTROLLER_34: Self = ControlFunction(U7(34)); 49 | pub const LSB_FOR_UNDEFINED_35: Self = ControlFunction(U7(35)); 50 | pub const LSB_FOR_FOOT_CONTROLLER_36: Self = ControlFunction(U7(36)); 51 | pub const LSB_FOR_PORTAMENTO_TIME_37: Self = ControlFunction(U7(37)); 52 | pub const LSB_FOR_DATA_ENTRY_MSB_38: Self = ControlFunction(U7(38)); 53 | pub const LSB_FOR_CHANNEL_VOLUME_39: Self = ControlFunction(U7(39)); 54 | pub const LSB_FOR_BALANCE_40: Self = ControlFunction(U7(40)); 55 | pub const LSB_FOR_UNDEFINED_41: Self = ControlFunction(U7(41)); 56 | pub const LSB_FOR_PAN_42: Self = ControlFunction(U7(42)); 57 | pub const LSB_FOR_EXPRESSION_CONTROLLER_43: Self = ControlFunction(U7(43)); 58 | pub const LSB_FOR_EFFECT_CONTROL_1_44: Self = ControlFunction(U7(44)); 59 | pub const LSB_FOR_EFFECT_CONTROL_2_45: Self = ControlFunction(U7(45)); 60 | pub const LSB_FOR_UNDEFINED_14_46: Self = ControlFunction(U7(46)); 61 | pub const LSB_FOR_UNDEFINED_15_47: Self = ControlFunction(U7(47)); 62 | pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_1_48: Self = ControlFunction(U7(48)); 63 | pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_2_49: Self = ControlFunction(U7(49)); 64 | pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_3_50: Self = ControlFunction(U7(50)); 65 | pub const LSB_FOR_GENERAL_PURPOSE_CONTROLLER_4_51: Self = ControlFunction(U7(51)); 66 | pub const LSB_FOR_UNDEFINED_20_52: Self = ControlFunction(U7(52)); 67 | pub const LSB_FOR_UNDEFINED_21_53: Self = ControlFunction(U7(53)); 68 | pub const LSB_FOR_UNDEFINED_22_54: Self = ControlFunction(U7(54)); 69 | pub const LSB_FOR_UNDEFINED_23_55: Self = ControlFunction(U7(55)); 70 | pub const LSB_FOR_UNDEFINED_24_56: Self = ControlFunction(U7(56)); 71 | pub const LSB_FOR_UNDEFINED_25_57: Self = ControlFunction(U7(57)); 72 | pub const LSB_FOR_UNDEFINED_26_58: Self = ControlFunction(U7(58)); 73 | pub const LSB_FOR_UNDEFINED_27_59: Self = ControlFunction(U7(59)); 74 | pub const LSB_FOR_UNDEFINED_28_60: Self = ControlFunction(U7(60)); 75 | pub const LSB_FOR_UNDEFINED_29_61: Self = ControlFunction(U7(61)); 76 | pub const LSB_FOR_UNDEFINED_30_62: Self = ControlFunction(U7(62)); 77 | pub const LSB_FOR_UNDEFINED_31_63: Self = ControlFunction(U7(63)); 78 | pub const DAMPER_PEDAL_ON_OFF_64: Self = ControlFunction(U7(64)); 79 | pub const PORTAMENTO_ON_OFF_65: Self = ControlFunction(U7(65)); 80 | pub const SOSTENUTO_ON_OFF_66: Self = ControlFunction(U7(66)); 81 | pub const SOFT_PEDAL_ON_OFF_67: Self = ControlFunction(U7(67)); 82 | pub const LEGATO_FOOTSWITCH_68: Self = ControlFunction(U7(68)); 83 | pub const HOLD_2_69: Self = ControlFunction(U7(69)); 84 | pub const SOUND_CONTROLLER_1_70: Self = ControlFunction(U7(70)); 85 | pub const SOUND_CONTROLLER_2_71: Self = ControlFunction(U7(71)); 86 | pub const SOUND_CONTROLLER_3_72: Self = ControlFunction(U7(72)); 87 | pub const SOUND_CONTROLLER_4_73: Self = ControlFunction(U7(73)); 88 | pub const SOUND_CONTROLLER_5_74: Self = ControlFunction(U7(74)); 89 | pub const SOUND_CONTROLLER_6_75: Self = ControlFunction(U7(75)); 90 | pub const SOUND_CONTROLLER_7_76: Self = ControlFunction(U7(76)); 91 | pub const SOUND_CONTROLLER_8_77: Self = ControlFunction(U7(77)); 92 | pub const SOUND_CONTROLLER_9_78: Self = ControlFunction(U7(78)); 93 | pub const SOUND_CONTROLLER_10_79: Self = ControlFunction(U7(79)); 94 | pub const GENERAL_PURPOSE_CONTROLLER_5_80: Self = ControlFunction(U7(80)); 95 | pub const GENERAL_PURPOSE_CONTROLLER_6_81: Self = ControlFunction(U7(81)); 96 | pub const GENERAL_PURPOSE_CONTROLLER_7_82: Self = ControlFunction(U7(82)); 97 | pub const GENERAL_PURPOSE_CONTROLLER_8_83: Self = ControlFunction(U7(83)); 98 | pub const PORTAMENTO_CONTROL_84: Self = ControlFunction(U7(84)); 99 | pub const UNDEFINED_85: Self = ControlFunction(U7(85)); 100 | pub const UNDEFINED_86: Self = ControlFunction(U7(86)); 101 | pub const UNDEFINED_87: Self = ControlFunction(U7(87)); 102 | pub const HIGH_RESOLUTION_VELOCITY_PREFIX_88: Self = ControlFunction(U7(88)); 103 | pub const UNDEFINED_89: Self = ControlFunction(U7(89)); 104 | pub const UNDEFINED_90: Self = ControlFunction(U7(90)); 105 | pub const EFFECTS_1_DEPTH_91: Self = ControlFunction(U7(91)); 106 | pub const EFFECTS_2_DEPTH_92: Self = ControlFunction(U7(92)); 107 | pub const EFFECTS_3_DEPTH_93: Self = ControlFunction(U7(93)); 108 | pub const EFFECTS_4_DEPTH_94: Self = ControlFunction(U7(94)); 109 | pub const EFFECTS_5_DEPTH_95: Self = ControlFunction(U7(95)); 110 | pub const DATA_INCREMENT_96: Self = ControlFunction(U7(96)); 111 | pub const DATA_DECREMENT_97: Self = ControlFunction(U7(97)); 112 | pub const NPRN_LSB_98: Self = ControlFunction(U7(98)); 113 | pub const NPRN_MSB_99: Self = ControlFunction(U7(99)); 114 | pub const RPN_LSB_100: Self = ControlFunction(U7(100)); 115 | pub const UNDEFINED_101: Self = ControlFunction(U7(101)); 116 | pub const UNDEFINED_102: Self = ControlFunction(U7(102)); 117 | pub const UNDEFINED_103: Self = ControlFunction(U7(103)); 118 | pub const UNDEFINED_104: Self = ControlFunction(U7(104)); 119 | pub const UNDEFINED_105: Self = ControlFunction(U7(105)); 120 | pub const UNDEFINED_106: Self = ControlFunction(U7(106)); 121 | pub const UNDEFINED_107: Self = ControlFunction(U7(107)); 122 | pub const UNDEFINED_108: Self = ControlFunction(U7(108)); 123 | pub const UNDEFINED_109: Self = ControlFunction(U7(109)); 124 | pub const UNDEFINED_110: Self = ControlFunction(U7(110)); 125 | pub const UNDEFINED_111: Self = ControlFunction(U7(111)); 126 | pub const UNDEFINED_112: Self = ControlFunction(U7(112)); 127 | pub const UNDEFINED_113: Self = ControlFunction(U7(113)); 128 | pub const UNDEFINED_114: Self = ControlFunction(U7(114)); 129 | pub const UNDEFINED_115: Self = ControlFunction(U7(115)); 130 | pub const UNDEFINED_116: Self = ControlFunction(U7(116)); 131 | pub const UNDEFINED_117: Self = ControlFunction(U7(117)); 132 | pub const UNDEFINED_118: Self = ControlFunction(U7(118)); 133 | pub const UNDEFINED_119: Self = ControlFunction(U7(119)); 134 | pub const ALL_SOUND_OFF_120: Self = ControlFunction(U7(120)); 135 | pub const RESET_ALL_CONTROLLERS_121: Self = ControlFunction(U7(121)); 136 | pub const LOCAL_CONTROL_OFF_122: Self = ControlFunction(U7(122)); 137 | pub const ALL_NOTES_OFF_123: Self = ControlFunction(U7(123)); 138 | pub const OMNI_MODE_OFF_124: Self = ControlFunction(U7(124)); 139 | pub const OMNI_MODE_ON_125: Self = ControlFunction(U7(125)); 140 | pub const MONO_MODE_ON_126: Self = ControlFunction(U7(126)); 141 | pub const POLY_MODE_ON_127: Self = ControlFunction(U7(127)); 142 | } 143 | -------------------------------------------------------------------------------- /src/message/data/mod.rs: -------------------------------------------------------------------------------- 1 | //! Primitives and structures used in USB MIDI data. 2 | 3 | pub mod u14; 4 | pub mod u4; 5 | pub mod u7; 6 | 7 | /// Like from, but will conceptually overflow if the value is too big 8 | /// this is useful from going from higher ranges to lower ranges 9 | pub trait FromOverFlow: Sized { 10 | /// Convert with overflowing. 11 | fn from_overflow(_: T) -> Self; 12 | } 13 | 14 | /// Like from, but will clamp the value to a maximum value 15 | pub trait FromClamped: Sized { 16 | /// Convert with clamping. 17 | fn from_clamped(_: T) -> Self; 18 | } 19 | -------------------------------------------------------------------------------- /src/message/data/u14.rs: -------------------------------------------------------------------------------- 1 | //! A primitive value with 14-bit length. 2 | 3 | use crate::message::data::{u7::U7, FromClamped, FromOverFlow}; 4 | 5 | /// A primitive value that can be from 0-0x4000 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub struct U14(pub(crate) u16); 8 | 9 | /// Error representing that this value is not a valid u14 10 | #[derive(Debug, Clone, Eq, PartialEq)] 11 | pub struct InvalidU14(pub u16); 12 | 13 | impl TryFrom for U14 { 14 | type Error = InvalidU14; 15 | 16 | fn try_from(value: u16) -> Result { 17 | if value > 0x3FFF { 18 | Err(InvalidU14(value)) 19 | } else { 20 | Ok(U14(value)) 21 | } 22 | } 23 | } 24 | 25 | impl From for u16 { 26 | fn from(value: U14) -> u16 { 27 | value.0 28 | } 29 | } 30 | 31 | impl FromOverFlow for U14 { 32 | fn from_overflow(value: u16) -> U14 { 33 | const MASK: u16 = 0b0011_1111_1111_1111; 34 | let value = MASK & value; 35 | U14(value) 36 | } 37 | } 38 | 39 | impl FromClamped for U14 { 40 | fn from_clamped(value: u16) -> U14 { 41 | match U14::try_from(value) { 42 | Ok(x) => x, 43 | _ => U14::MAX, 44 | } 45 | } 46 | } 47 | 48 | impl U14 { 49 | /// Maximum value for the type. 50 | pub const MAX: U14 = U14(0x3FFF); 51 | /// Minimum value for the type. 52 | pub const MIN: U14 = U14(0); 53 | 54 | /// Creates a new U14 value from an (U7, U7) tuple containing the LSB and MSB. 55 | pub fn from_split_u7(value: (U7, U7)) -> Self { 56 | Self((value.0 .0 as u16) | ((value.1 .0 as u16) << 7)) 57 | } 58 | 59 | /// Returns the LSB and MSB of the value as (U7, U7) tuple. 60 | pub fn split_u7(&self) -> (U7, U7) { 61 | (U7((self.0 & 0x7F) as u8), U7((self.0 >> 7) as u8)) 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn try_from_valid() { 71 | assert_eq!(U14::try_from(0x1234), Ok(U14(0x1234))); 72 | } 73 | 74 | #[test] 75 | fn try_from_invalid() { 76 | assert_eq!(U14::try_from(0x4000), Err(InvalidU14(0x4000))); 77 | } 78 | 79 | #[test] 80 | fn from_overflow() { 81 | assert_eq!(U14::from_overflow(0x400F), U14(0x0F)); 82 | } 83 | 84 | #[test] 85 | fn from_clamped() { 86 | assert_eq!(U14::from_clamped(0x400F), U14(0x3FFF)); 87 | } 88 | 89 | #[test] 90 | fn from_split_u7() { 91 | assert_eq!(U14::from_split_u7((U7(0x7F), U7(0x6F))), U14(0x37FF)); 92 | } 93 | 94 | #[test] 95 | fn split_u7() { 96 | assert_eq!(U14(0x37FF).split_u7(), (U7(0x7F), U7(0x6F))); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/message/data/u4.rs: -------------------------------------------------------------------------------- 1 | //! A primitive value with 4-bit length. 2 | 3 | use crate::message::data::{FromClamped, FromOverFlow}; 4 | 5 | /// A primitive value that can be from 0-0x0F 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub struct U4(u8); 8 | 9 | /// Error representing that this value is not a valid u4 10 | #[derive(Debug, Clone, Eq, PartialEq)] 11 | pub struct InvalidU4(pub u8); 12 | 13 | impl TryFrom for U4 { 14 | type Error = InvalidU4; 15 | 16 | fn try_from(value: u8) -> Result { 17 | if value > U4::MAX.0 { 18 | Err(InvalidU4(value)) 19 | } else { 20 | Ok(U4(value)) 21 | } 22 | } 23 | } 24 | 25 | impl From for u8 { 26 | fn from(value: U4) -> u8 { 27 | value.0 28 | } 29 | } 30 | 31 | impl FromOverFlow for U4 { 32 | fn from_overflow(value: u8) -> U4 { 33 | const MASK: u8 = 0b0000_1111; 34 | let value = MASK & value; 35 | U4(value) 36 | } 37 | } 38 | 39 | impl FromClamped for U4 { 40 | fn from_clamped(value: u8) -> U4 { 41 | match U4::try_from(value) { 42 | Ok(x) => x, 43 | _ => U4::MAX, 44 | } 45 | } 46 | } 47 | 48 | impl U4 { 49 | /// Maximum value for the type. 50 | pub const MAX: U4 = U4(0x0F); 51 | /// Minimum value for the type. 52 | pub const MIN: U4 = U4(0); 53 | 54 | /// Combines two nibbles (u4) eg half byte 55 | /// result will be a full byte 56 | pub fn combine(upper: U4, lower: U4) -> u8 { 57 | let upper = upper.0.overflowing_shl(8).0; 58 | let lower = lower.0 & U4::MAX.0; 59 | upper | lower 60 | } 61 | 62 | /// Constructs a U4 from a u8. 63 | /// Note this clamps off the upper portions 64 | pub fn from_overflowing_u8(value: u8) -> U4 { 65 | const MASK: u8 = 0b0000_1111; 66 | let number = MASK & value; 67 | U4(number) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/message/data/u7.rs: -------------------------------------------------------------------------------- 1 | //! A primitive value with 7-bit length. 2 | 3 | use crate::message::data::{FromClamped, FromOverFlow}; 4 | 5 | /// A primitive value that can be from 0-0x7F 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub struct U7(pub(crate) u8); 8 | 9 | /// Error representing that this value is not a valid u7 10 | #[derive(Debug, Clone, Eq, PartialEq)] 11 | pub struct InvalidU7(pub u8); 12 | 13 | impl TryFrom for U7 { 14 | type Error = InvalidU7; 15 | 16 | fn try_from(value: u8) -> Result { 17 | if value > 0x7F { 18 | Err(InvalidU7(value)) 19 | } else { 20 | Ok(U7(value)) 21 | } 22 | } 23 | } 24 | 25 | impl From for u8 { 26 | fn from(value: U7) -> u8 { 27 | value.0 28 | } 29 | } 30 | 31 | impl FromOverFlow for U7 { 32 | fn from_overflow(value: u8) -> U7 { 33 | const MASK: u8 = 0b0111_1111; 34 | let value = MASK & value; 35 | U7(value) 36 | } 37 | } 38 | 39 | impl FromClamped for U7 { 40 | fn from_clamped(value: u8) -> U7 { 41 | match U7::try_from(value) { 42 | Ok(x) => x, 43 | _ => U7::MAX, 44 | } 45 | } 46 | } 47 | 48 | impl U7 { 49 | /// Maximum value for the type. 50 | pub const MAX: U7 = U7(0x7F); 51 | /// Minimum value for the type. 52 | pub const MIN: U7 = U7(0); 53 | } 54 | -------------------------------------------------------------------------------- /src/message/mod.rs: -------------------------------------------------------------------------------- 1 | //! Enum representing the MIDI message. 2 | 3 | pub mod channel; 4 | pub mod control_function; 5 | pub mod data; 6 | pub mod notes; 7 | pub mod raw; 8 | 9 | pub use crate::message::channel::Channel; 10 | pub use crate::message::control_function::ControlFunction; 11 | pub use crate::message::data::u14::U14; 12 | pub use crate::message::data::u7::U7; 13 | pub use crate::message::data::{FromClamped, FromOverFlow}; 14 | pub use crate::message::notes::Note; 15 | 16 | use crate::message::raw::{Payload, Raw}; 17 | use crate::packet::cable_number::CableNumber; 18 | use crate::packet::code_index_number::CodeIndexNumber; 19 | use crate::packet::{UsbMidiEventPacket, UsbMidiEventPacketError}; 20 | 21 | /// Note velocity value. 22 | pub type Velocity = U7; 23 | 24 | /// Represents the MIDI messages. 25 | #[derive(Debug, Eq, PartialEq, Clone)] 26 | pub enum Message { 27 | /// Note On message. 28 | NoteOff(Channel, Note, Velocity), 29 | /// Note Off message. 30 | NoteOn(Channel, Note, Velocity), 31 | /// Polyphonic aftertouch (poly-pressure) message. 32 | PolyphonicAftertouch(Channel, Note, U7), 33 | /// Program change message. 34 | ProgramChange(Channel, U7), 35 | /// Channel aftertouch message. 36 | ChannelAftertouch(Channel, U7), 37 | /// Pitchwheel message. 38 | PitchWheelChange(Channel, U14), 39 | /// Control Change (CC) message. 40 | ControlChange(Channel, ControlFunction, U7), 41 | /// MTC Quarter Frame message. 42 | MtcQuarterFrame(U7), 43 | /// Song Position Pointer message. 44 | SongPositionPointer(U14), 45 | /// Song Select message. 46 | SongSelect(U7), 47 | /// Tune Request message. 48 | TuneRequest, 49 | /// Timing Clock message. 50 | TimingClock, 51 | /// Tick message. 52 | Tick, 53 | /// Start message. 54 | Start, 55 | /// Continue message. 56 | Continue, 57 | /// Stop message. 58 | Stop, 59 | /// Active Sensing message. 60 | ActiveSensing, 61 | /// Reset message. 62 | Reset, 63 | } 64 | 65 | const NOTE_OFF_MASK: u8 = 0b1000_0000; 66 | const NOTE_ON_MASK: u8 = 0b1001_0000; 67 | const POLYPHONIC_MASK: u8 = 0b1010_0000; 68 | const PROGRAM_MASK: u8 = 0b1100_0000; 69 | const CHANNEL_AFTERTOUCH_MASK: u8 = 0b1101_0000; 70 | const PITCH_BEND_MASK: u8 = 0b1110_0000; 71 | const CONTROL_CHANGE_MASK: u8 = 0b1011_0000; 72 | 73 | const MTC_QUARTER_FRAME: u8 = 0xF1; 74 | const SONG_POSITION_POINTER: u8 = 0xF2; 75 | const SONG_SELECT: u8 = 0xF3; 76 | const TUNE_REQUEST: u8 = 0xF6; 77 | const TIMING_CLOCK: u8 = 0xF8; 78 | const TICK: u8 = 0xF9; 79 | const START: u8 = 0xFA; 80 | const CONTINUE: u8 = 0xFB; 81 | const STOP: u8 = 0xFC; 82 | const ACTIVE_SENSING: u8 = 0xFE; 83 | const RESET: u8 = 0xFF; 84 | 85 | impl From for Raw { 86 | fn from(value: Message) -> Raw { 87 | match value { 88 | Message::NoteOn(chan, note, vel) => { 89 | let payload = Payload::DoubleByte(note.into(), vel); 90 | let status = NOTE_ON_MASK | u8::from(chan); 91 | Raw { status, payload } 92 | } 93 | Message::NoteOff(chan, note, vel) => { 94 | let payload = Payload::DoubleByte(note.into(), vel); 95 | let status = NOTE_OFF_MASK | u8::from(chan); 96 | Raw { status, payload } 97 | } 98 | Message::PolyphonicAftertouch(chan, note, pressure) => { 99 | let payload = Payload::DoubleByte(note.into(), pressure); 100 | let status = POLYPHONIC_MASK | u8::from(chan); 101 | Raw { status, payload } 102 | } 103 | Message::ProgramChange(chan, program) => { 104 | let payload = Payload::SingleByte(program); 105 | let status = PROGRAM_MASK | u8::from(chan); 106 | Raw { status, payload } 107 | } 108 | Message::ChannelAftertouch(chan, pressure) => { 109 | let payload = Payload::SingleByte(pressure); 110 | let status = CHANNEL_AFTERTOUCH_MASK | u8::from(chan); 111 | Raw { status, payload } 112 | } 113 | Message::PitchWheelChange(chan, value) => { 114 | let value = u16::from(value); 115 | let lsb = U7::try_from((value & 0x7F) as u8).unwrap(); 116 | let msb = U7::try_from(((value >> 7) & 0x7F) as u8).unwrap(); 117 | let payload = Payload::DoubleByte(lsb, msb); 118 | let status = PITCH_BEND_MASK | u8::from(chan); 119 | Raw { status, payload } 120 | } 121 | Message::ControlChange(chan, control_function, value) => { 122 | let payload = Payload::DoubleByte(control_function.0, value); 123 | let status = CONTROL_CHANGE_MASK | u8::from(chan); 124 | Raw { status, payload } 125 | } 126 | Message::MtcQuarterFrame(frame) => { 127 | let payload = Payload::SingleByte(frame); 128 | let status = MTC_QUARTER_FRAME; 129 | Raw { status, payload } 130 | } 131 | Message::SongPositionPointer(value) => { 132 | let value = u16::from(value); 133 | let lsb = U7::try_from((value & 0x7F) as u8).unwrap(); 134 | let msb = U7::try_from(((value >> 7) & 0x7F) as u8).unwrap(); 135 | let payload = Payload::DoubleByte(lsb, msb); 136 | let status = SONG_POSITION_POINTER; 137 | Raw { status, payload } 138 | } 139 | Message::SongSelect(song) => { 140 | let payload = Payload::SingleByte(song); 141 | let status = SONG_SELECT; 142 | Raw { status, payload } 143 | } 144 | Message::TuneRequest => { 145 | let payload = Payload::Empty; 146 | let status = TUNE_REQUEST; 147 | Raw { status, payload } 148 | } 149 | Message::TimingClock => { 150 | let payload = Payload::Empty; 151 | let status = TIMING_CLOCK; 152 | Raw { status, payload } 153 | } 154 | Message::Tick => { 155 | let payload = Payload::Empty; 156 | let status = TICK; 157 | Raw { status, payload } 158 | } 159 | Message::Start => { 160 | let payload = Payload::Empty; 161 | let status = START; 162 | Raw { status, payload } 163 | } 164 | Message::Continue => { 165 | let payload = Payload::Empty; 166 | let status = CONTINUE; 167 | Raw { status, payload } 168 | } 169 | Message::Stop => { 170 | let payload = Payload::Empty; 171 | let status = STOP; 172 | Raw { status, payload } 173 | } 174 | Message::ActiveSensing => { 175 | let payload = Payload::Empty; 176 | let status = ACTIVE_SENSING; 177 | Raw { status, payload } 178 | } 179 | Message::Reset => { 180 | let payload = Payload::Empty; 181 | let status = RESET; 182 | Raw { status, payload } 183 | } 184 | } 185 | } 186 | } 187 | 188 | impl TryFrom<&[u8]> for Message { 189 | type Error = UsbMidiEventPacketError; 190 | 191 | fn try_from(data: &[u8]) -> Result { 192 | let status_byte = match data.first() { 193 | Some(byte) => byte, 194 | None => return Err(UsbMidiEventPacketError::MissingDataPacket), 195 | }; 196 | 197 | match *status_byte { 198 | MTC_QUARTER_FRAME => { 199 | return Ok(Message::MtcQuarterFrame(get_u7_at(data, 1)?)); 200 | } 201 | SONG_POSITION_POINTER => { 202 | return Ok(Message::SongPositionPointer(get_u14(data)?)); 203 | } 204 | SONG_SELECT => { 205 | return Ok(Message::SongSelect(get_u7_at(data, 1)?)); 206 | } 207 | TUNE_REQUEST => { 208 | return Ok(Message::TuneRequest); 209 | } 210 | TIMING_CLOCK => { 211 | return Ok(Message::TimingClock); 212 | } 213 | TICK => { 214 | return Ok(Message::Tick); 215 | } 216 | START => { 217 | return Ok(Message::Start); 218 | } 219 | CONTINUE => { 220 | return Ok(Message::Continue); 221 | } 222 | STOP => { 223 | return Ok(Message::Stop); 224 | } 225 | ACTIVE_SENSING => { 226 | return Ok(Message::ActiveSensing); 227 | } 228 | RESET => { 229 | return Ok(Message::Reset); 230 | } 231 | _ => {} 232 | } 233 | 234 | let event_type = status_byte & 0b1111_0000; 235 | let channel_bytes = (status_byte) & 0b0000_1111; 236 | 237 | let channel = Channel::try_from(channel_bytes).ok().unwrap(); 238 | 239 | match event_type { 240 | NOTE_ON_MASK => Ok(Message::NoteOn( 241 | channel, 242 | get_note(data)?, 243 | get_u7_at(data, 2)?, 244 | )), 245 | NOTE_OFF_MASK => Ok(Message::NoteOff( 246 | channel, 247 | get_note(data)?, 248 | get_u7_at(data, 2)?, 249 | )), 250 | POLYPHONIC_MASK => Ok(Message::PolyphonicAftertouch( 251 | channel, 252 | get_note(data)?, 253 | get_u7_at(data, 2)?, 254 | )), 255 | PROGRAM_MASK => Ok(Message::ProgramChange(channel, get_u7_at(data, 1)?)), 256 | CHANNEL_AFTERTOUCH_MASK => Ok(Message::ChannelAftertouch(channel, get_u7_at(data, 1)?)), 257 | PITCH_BEND_MASK => Ok(Message::PitchWheelChange(channel, get_u14(data)?)), 258 | CONTROL_CHANGE_MASK => Ok(Message::ControlChange( 259 | channel, 260 | ControlFunction(get_u7_at(data, 1)?), 261 | get_u7_at(data, 2)?, 262 | )), 263 | _ => Err(UsbMidiEventPacketError::InvalidEventType(event_type)), 264 | } 265 | } 266 | } 267 | 268 | impl TryFrom<&UsbMidiEventPacket> for Message { 269 | type Error = UsbMidiEventPacketError; 270 | 271 | fn try_from(value: &UsbMidiEventPacket) -> Result { 272 | Self::try_from(&value.as_raw_bytes()[1..]) 273 | } 274 | } 275 | 276 | impl Message { 277 | /// Create a packet from the message. 278 | pub fn into_packet(self, cable: CableNumber) -> UsbMidiEventPacket { 279 | let cin = self.code_index_number() as u8; 280 | 281 | let mut raw = [0; 4]; 282 | raw[0] = (cable as u8) << 4 | cin; 283 | let r = Raw::from(self); 284 | raw[1] = r.status; 285 | 286 | match r.payload { 287 | Payload::Empty => { 288 | raw[2] = 0; 289 | raw[3] = 0; 290 | } 291 | Payload::SingleByte(byte) => { 292 | raw[2] = byte.0; 293 | raw[3] = 0; 294 | } 295 | Payload::DoubleByte(byte1, byte2) => { 296 | raw[2] = byte1.0; 297 | raw[3] = byte2.0; 298 | } 299 | }; 300 | 301 | UsbMidiEventPacket::try_from(raw.as_slice()).unwrap() 302 | } 303 | 304 | /// Returns the code index number for a message. 305 | pub fn code_index_number(&self) -> CodeIndexNumber { 306 | match self { 307 | Self::NoteOn(_, _, _) => CodeIndexNumber::NoteOn, 308 | Self::NoteOff(_, _, _) => CodeIndexNumber::NoteOff, 309 | Self::ChannelAftertouch(_, _) => CodeIndexNumber::ChannelPressure, 310 | Self::PitchWheelChange(_, _) => CodeIndexNumber::PitchBendChange, 311 | Self::PolyphonicAftertouch(_, _, _) => CodeIndexNumber::PolyKeyPress, 312 | Self::ProgramChange(_, _) => CodeIndexNumber::ProgramChange, 313 | Self::ControlChange(_, _, _) => CodeIndexNumber::ControlChange, 314 | Self::MtcQuarterFrame(_) => CodeIndexNumber::SystemCommon2Bytes, 315 | Self::SongPositionPointer(_) => CodeIndexNumber::SystemCommon3Bytes, 316 | Self::SongSelect(_) => CodeIndexNumber::SystemCommon2Bytes, 317 | Self::TuneRequest => CodeIndexNumber::SystemCommon1Byte, 318 | Self::TimingClock => CodeIndexNumber::SingleByte, 319 | Self::Tick => CodeIndexNumber::SingleByte, 320 | Self::Start => CodeIndexNumber::SingleByte, 321 | Self::Continue => CodeIndexNumber::SingleByte, 322 | Self::Stop => CodeIndexNumber::SingleByte, 323 | Self::ActiveSensing => CodeIndexNumber::SingleByte, 324 | Self::Reset => CodeIndexNumber::SingleByte, 325 | } 326 | } 327 | } 328 | 329 | fn get_note(data: &[u8]) -> Result { 330 | let note_byte = get_byte_at_position(data, 1)?; 331 | match Note::try_from(note_byte) { 332 | Ok(note) => Ok(note), 333 | Err(_) => Err(UsbMidiEventPacketError::InvalidNote(note_byte)), 334 | } 335 | } 336 | 337 | fn get_u7_at(data: &[u8], index: usize) -> Result { 338 | let data_byte = get_byte_at_position(data, index)?; 339 | Ok(U7::from_clamped(data_byte)) 340 | } 341 | 342 | fn get_u14(data: &[u8]) -> Result { 343 | let lsb = get_byte_at_position(data, 1)?; 344 | let msb = get_byte_at_position(data, 2)?; 345 | Ok(U14::from_clamped(((msb as u16) << 7) | (lsb as u16))) 346 | } 347 | 348 | fn get_byte_at_position(data: &[u8], index: usize) -> Result { 349 | match data.get(index) { 350 | Some(byte) => Ok(*byte), 351 | None => Err(UsbMidiEventPacketError::MissingDataPacket), 352 | } 353 | } 354 | 355 | #[cfg(test)] 356 | mod tests { 357 | use crate::message::channel::Channel::{Channel1, Channel2}; 358 | use crate::message::control_function::ControlFunction; 359 | use crate::message::data::u14::U14; 360 | use crate::message::data::u7::U7; 361 | use crate::message::notes::Note; 362 | use crate::message::Message; 363 | use crate::packet::cable_number::CableNumber::{Cable0, Cable1}; 364 | use crate::packet::UsbMidiEventPacket; 365 | 366 | macro_rules! decode_message_test { 367 | ($($id:ident:$value:expr,)*) => { 368 | $( 369 | #[test] 370 | fn $id() { 371 | let (usb_midi_data_packet, expected) = $value; 372 | let message = UsbMidiEventPacket::try_from(&usb_midi_data_packet[..]).unwrap(); 373 | assert_eq!(expected, message); 374 | } 375 | )* 376 | } 377 | } 378 | 379 | decode_message_test! { 380 | note_on: ([9, 144, 36, 127], Message::NoteOn(Channel1, Note::C2, U7(127)).into_packet(Cable0)), 381 | note_off: ([8, 128, 36, 0], Message::NoteOff(Channel1, Note::C2, U7(0)).into_packet(Cable0)), 382 | polyphonic_aftertouch: ([10, 160, 36, 64], Message::PolyphonicAftertouch(Channel1, Note::C2, U7(64)).into_packet(Cable0)), 383 | program_change: ([28, 192, 127, 0], Message::ProgramChange(Channel1, U7(127)).into_packet(Cable1)), 384 | channel_aftertouch: ([13, 208, 127, 0], Message::ChannelAftertouch(Channel1, U7(127)).into_packet(Cable0)), 385 | pitch_wheel: ([14, 224, 64, 32], Message::PitchWheelChange(Channel1, U14(4160)).into_packet(Cable0)), 386 | control_change: ([11, 177, 1, 32], Message::ControlChange(Channel2, ControlFunction::MOD_WHEEL_1, U7(32)).into_packet(Cable0)), 387 | mtc_quarter_frame: ([0x02, 0xF1, 12, 0], Message::MtcQuarterFrame(U7(12)).into_packet(Cable0)), 388 | song_position_pointer: ([0x03, 0xF2, 38, 75], Message::SongPositionPointer(U14(9638)).into_packet(Cable0)), 389 | song_select: ([0x02, 0xF3, 4, 0], Message::SongSelect(U7(4)).into_packet(Cable0)), 390 | tune_request: ([0x05, 0xF6, 0, 0], Message::TuneRequest.into_packet(Cable0)), 391 | timing_clock: ([0x0F, 0xF8, 0, 0], Message::TimingClock.into_packet(Cable0)), 392 | tick: ([0x0F, 0xF9, 0, 0], Message::Tick.into_packet(Cable0)), 393 | start: ([0x0F, 0xFA, 0, 0], Message::Start.into_packet(Cable0)), 394 | continue_: ([0x0F, 0xFB, 0, 0], Message::Continue.into_packet(Cable0)), 395 | stop: ([0x0F, 0xFC, 0, 0], Message::Stop.into_packet(Cable0)), 396 | active_sensing: ([0x0F, 0xFE, 0, 0], Message::ActiveSensing.into_packet(Cable0)), 397 | reset: ([0x0F, 0xFF, 0, 0], Message::Reset.into_packet(Cable0)), 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/message/notes.rs: -------------------------------------------------------------------------------- 1 | //! Enum representing the MIDI notes. 2 | 3 | use crate::message::data::u7::U7; 4 | use crate::message::data::FromOverFlow; 5 | use num_enum::TryFromPrimitive; 6 | 7 | /// A simple enum type that represents all the midi 'notes'. 8 | /// 9 | /// Note the flat versions are associated constants 10 | /// but can be referenced like Note::Bb3 11 | /// C1m is the C-1 12 | #[allow(missing_docs)] 13 | #[derive(Debug, Copy, Clone, TryFromPrimitive, Eq, PartialEq)] 14 | #[repr(u8)] 15 | pub enum Note { 16 | C1m, 17 | Cs1m, 18 | D1m, 19 | Ds1m, 20 | E1m, 21 | F1m, 22 | Fs1m, 23 | G1m, 24 | Gs1m, 25 | A1m, 26 | As1m, 27 | B1m, 28 | C0, 29 | Cs0, 30 | D0, 31 | Ds0, 32 | E0, 33 | F0, 34 | Fs0, 35 | G0, 36 | Gs0, 37 | A0, 38 | As0, 39 | B0, 40 | C1, 41 | Cs1, 42 | D1, 43 | Ds1, 44 | E1, 45 | F1, 46 | Fs1, 47 | G1, 48 | Gs1, 49 | A1, 50 | As1, 51 | B1, 52 | C2, 53 | Cs2, 54 | D2, 55 | Ds2, 56 | E2, 57 | F2, 58 | Fs2, 59 | G2, 60 | Gs2, 61 | A2, 62 | As2, 63 | B2, 64 | C3, 65 | Cs3, 66 | D3, 67 | Ds3, 68 | E3, 69 | F3, 70 | Fs3, 71 | G3, 72 | Gs3, 73 | A3, 74 | As3, 75 | B3, 76 | C4, 77 | Cs4, 78 | D4, 79 | Ds4, 80 | E4, 81 | F4, 82 | Fs4, 83 | G4, 84 | Gs4, 85 | A4, 86 | As4, 87 | B4, 88 | C5, 89 | Cs5, 90 | D5, 91 | Ds5, 92 | E5, 93 | F5, 94 | Fs5, 95 | G5, 96 | Gs5, 97 | A5, 98 | As5, 99 | B5, 100 | C6, 101 | Cs6, 102 | D6, 103 | Ds6, 104 | E6, 105 | F6, 106 | Fs6, 107 | G6, 108 | Gs6, 109 | A6, 110 | As6, 111 | B6, 112 | C7, 113 | Cs7, 114 | D7, 115 | Ds7, 116 | E7, 117 | F7, 118 | Fs7, 119 | G7, 120 | Gs7, 121 | A7, 122 | As7, 123 | B7, 124 | C8, 125 | Cs8, 126 | D8, 127 | Ds8, 128 | E8, 129 | F8, 130 | Fs8, 131 | G8, 132 | Gs8, 133 | A8, 134 | As8, 135 | B8, 136 | C9, 137 | Cs9, 138 | D9, 139 | Ds9, 140 | E9, 141 | F9, 142 | Fs9, 143 | G9, 144 | Gs9, 145 | } 146 | 147 | impl From for u8 { 148 | fn from(val: Note) -> Self { 149 | val as u8 150 | } 151 | } 152 | 153 | impl From for U7 { 154 | fn from(value: Note) -> U7 { 155 | let byte = value as u8; 156 | U7::from_overflow(byte) 157 | } 158 | } 159 | 160 | #[allow(missing_docs)] 161 | impl Note { 162 | #[allow(non_upper_case_globals)] 163 | pub const Db1m: Note = Note::Cs1m; 164 | #[allow(non_upper_case_globals)] 165 | pub const Eb1m: Note = Note::Ds1m; 166 | #[allow(non_upper_case_globals)] 167 | pub const Gb1m: Note = Note::Fs1m; 168 | #[allow(non_upper_case_globals)] 169 | pub const Ab1m: Note = Note::Gs1m; 170 | #[allow(non_upper_case_globals)] 171 | pub const Bb1m: Note = Note::As1m; 172 | #[allow(non_upper_case_globals)] 173 | pub const Db0: Note = Note::Cs0; 174 | #[allow(non_upper_case_globals)] 175 | pub const Eb0: Note = Note::Ds0; 176 | #[allow(non_upper_case_globals)] 177 | pub const Gb0: Note = Note::Fs0; 178 | #[allow(non_upper_case_globals)] 179 | pub const Ab0: Note = Note::Gs0; 180 | #[allow(non_upper_case_globals)] 181 | pub const Bb0: Note = Note::As0; 182 | #[allow(non_upper_case_globals)] 183 | pub const Db1: Note = Note::Cs1; 184 | #[allow(non_upper_case_globals)] 185 | pub const Eb1: Note = Note::Ds1; 186 | #[allow(non_upper_case_globals)] 187 | pub const Gb1: Note = Note::Fs1; 188 | #[allow(non_upper_case_globals)] 189 | pub const Ab1: Note = Note::Gs1; 190 | #[allow(non_upper_case_globals)] 191 | pub const Bb1: Note = Note::As1; 192 | #[allow(non_upper_case_globals)] 193 | pub const Db2: Note = Note::Cs2; 194 | #[allow(non_upper_case_globals)] 195 | pub const Eb2: Note = Note::Ds2; 196 | #[allow(non_upper_case_globals)] 197 | pub const Gb2: Note = Note::Fs2; 198 | #[allow(non_upper_case_globals)] 199 | pub const Ab2: Note = Note::Gs2; 200 | #[allow(non_upper_case_globals)] 201 | pub const Bb2: Note = Note::As2; 202 | #[allow(non_upper_case_globals)] 203 | pub const Db3: Note = Note::Cs3; 204 | #[allow(non_upper_case_globals)] 205 | pub const Eb3: Note = Note::Ds3; 206 | #[allow(non_upper_case_globals)] 207 | pub const Gb3: Note = Note::Fs3; 208 | #[allow(non_upper_case_globals)] 209 | pub const Ab3: Note = Note::Gs3; 210 | #[allow(non_upper_case_globals)] 211 | pub const Bb3: Note = Note::As3; 212 | #[allow(non_upper_case_globals)] 213 | pub const Db4: Note = Note::Cs4; 214 | #[allow(non_upper_case_globals)] 215 | pub const Eb4: Note = Note::Ds4; 216 | #[allow(non_upper_case_globals)] 217 | pub const Gb4: Note = Note::Fs4; 218 | #[allow(non_upper_case_globals)] 219 | pub const Ab4: Note = Note::Gs4; 220 | #[allow(non_upper_case_globals)] 221 | pub const Bb4: Note = Note::As4; 222 | #[allow(non_upper_case_globals)] 223 | pub const Db5: Note = Note::Cs5; 224 | #[allow(non_upper_case_globals)] 225 | pub const Eb5: Note = Note::Ds5; 226 | #[allow(non_upper_case_globals)] 227 | pub const Gb5: Note = Note::Fs5; 228 | #[allow(non_upper_case_globals)] 229 | pub const Ab5: Note = Note::Gs5; 230 | #[allow(non_upper_case_globals)] 231 | pub const Bb5: Note = Note::As5; 232 | #[allow(non_upper_case_globals)] 233 | pub const Db6: Note = Note::Cs6; 234 | #[allow(non_upper_case_globals)] 235 | pub const Eb6: Note = Note::Ds6; 236 | #[allow(non_upper_case_globals)] 237 | pub const Gb6: Note = Note::Fs6; 238 | #[allow(non_upper_case_globals)] 239 | pub const Ab6: Note = Note::Gs6; 240 | #[allow(non_upper_case_globals)] 241 | pub const Bb6: Note = Note::As6; 242 | #[allow(non_upper_case_globals)] 243 | pub const Db7: Note = Note::Cs7; 244 | #[allow(non_upper_case_globals)] 245 | pub const Eb7: Note = Note::Ds7; 246 | #[allow(non_upper_case_globals)] 247 | pub const Gb7: Note = Note::Fs7; 248 | #[allow(non_upper_case_globals)] 249 | pub const Ab7: Note = Note::Gs7; 250 | #[allow(non_upper_case_globals)] 251 | pub const Bb7: Note = Note::As7; 252 | #[allow(non_upper_case_globals)] 253 | pub const Db8: Note = Note::Cs8; 254 | #[allow(non_upper_case_globals)] 255 | pub const Eb8: Note = Note::Ds8; 256 | #[allow(non_upper_case_globals)] 257 | pub const Gb8: Note = Note::Fs8; 258 | #[allow(non_upper_case_globals)] 259 | pub const Ab8: Note = Note::Gs8; 260 | #[allow(non_upper_case_globals)] 261 | pub const Bb8: Note = Note::As8; 262 | #[allow(non_upper_case_globals)] 263 | pub const Db9: Note = Note::Cs9; 264 | #[allow(non_upper_case_globals)] 265 | pub const Eb9: Note = Note::Ds9; 266 | #[allow(non_upper_case_globals)] 267 | pub const Gb9: Note = Note::Fs9; 268 | #[allow(non_upper_case_globals)] 269 | pub const Ab9: Note = Note::Gs9; 270 | } 271 | 272 | #[cfg(test)] 273 | mod tests { 274 | 275 | use super::*; 276 | macro_rules! note_test { 277 | ($($id:ident:$value:expr,)*) => { 278 | $( 279 | #[test] 280 | fn $id() { 281 | let (input,expected) = $value; 282 | assert_eq!(input as u8, expected); 283 | } 284 | )* 285 | } 286 | } 287 | 288 | //These test mainly prove we are generating all the sharps/flats 289 | //(as the same number) correctly. 290 | note_test! { 291 | note_c1m: (Note::C1m,0), 292 | note_cs1m: (Note::Cs1m,1), 293 | note_db1m: (Note::Db1m,1), 294 | note_d1m: (Note::D1m,2), 295 | note_ds1m: (Note::Ds1m,3), 296 | note_eb1m: (Note::Eb1m,3), 297 | note_e1m: (Note::E1m,4), 298 | note_f1m: (Note::F1m,5), 299 | note_fs1m: (Note::Fs1m,6), 300 | note_gb1m: (Note::Gb1m,6), 301 | note_g1m: (Note::G1m,7), 302 | note_gs1m: (Note::Gs1m,8), 303 | note_ab1m: (Note::Ab1m,8), 304 | note_a1m: (Note::A1m,9), 305 | note_as1m: (Note::As1m,10), 306 | note_bb1m: (Note::Bb1m,10), 307 | note_b1m: (Note::B1m,11), 308 | note_c0: (Note::C0,12), 309 | note_cs0: (Note::Cs0,13), 310 | note_db0: (Note::Db0,13), 311 | note_d0: (Note::D0,14), 312 | note_ds0: (Note::Ds0,15), 313 | note_eb0: (Note::Eb0,15), 314 | note_e0: (Note::E0,16), 315 | note_f0: (Note::F0,17), 316 | note_fs0: (Note::Fs0,18), 317 | note_gb0: (Note::Gb0,18), 318 | note_g0: (Note::G0,19), 319 | note_gs0: (Note::Gs0,20), 320 | note_ab0: (Note::Ab0,20), 321 | note_a0: (Note::A0,21), 322 | note_as0: (Note::As0,22), 323 | note_bb0: (Note::Bb0,22), 324 | note_b0: (Note::B0,23), 325 | note_c1: (Note::C1,24), 326 | note_cs1: (Note::Cs1,25), 327 | note_db1: (Note::Db1,25), 328 | note_d1: (Note::D1,26), 329 | note_ds1: (Note::Ds1,27), 330 | note_eb1: (Note::Eb1,27), 331 | note_e1: (Note::E1,28), 332 | note_f1: (Note::F1,29), 333 | note_fs1: (Note::Fs1,30), 334 | note_gb1: (Note::Gb1,30), 335 | note_g1: (Note::G1,31), 336 | note_gs1: (Note::Gs1,32), 337 | note_ab1: (Note::Ab1,32), 338 | note_a1: (Note::A1,33), 339 | note_as1: (Note::As1,34), 340 | note_bb1: (Note::Bb1,34), 341 | note_b1: (Note::B1,35), 342 | note_c2: (Note::C2,36), 343 | note_cs2: (Note::Cs2,37), 344 | note_db2: (Note::Db2,37), 345 | note_d2: (Note::D2,38), 346 | note_ds2: (Note::Ds2,39), 347 | note_eb2: (Note::Eb2,39), 348 | note_e2: (Note::E2,40), 349 | note_f2: (Note::F2,41), 350 | note_fs2: (Note::Fs2,42), 351 | note_gb2: (Note::Gb2,42), 352 | note_g2: (Note::G2,43), 353 | note_gs2: (Note::Gs2,44), 354 | note_ab2: (Note::Ab2,44), 355 | note_a2: (Note::A2,45), 356 | note_as2: (Note::As2,46), 357 | note_bb2: (Note::Bb2,46), 358 | note_b2: (Note::B2,47), 359 | note_c3: (Note::C3,48), 360 | note_cs3: (Note::Cs3,49), 361 | note_db3: (Note::Db3,49), 362 | note_d3: (Note::D3,50), 363 | note_ds3: (Note::Ds3,51), 364 | note_eb3: (Note::Eb3,51), 365 | note_e3: (Note::E3,52), 366 | note_f3: (Note::F3,53), 367 | note_fs3: (Note::Fs3,54), 368 | note_gb3: (Note::Gb3,54), 369 | note_g3: (Note::G3,55), 370 | note_gs3: (Note::Gs3,56), 371 | note_ab3: (Note::Ab3,56), 372 | note_a3: (Note::A3,57), 373 | note_as3: (Note::As3,58), 374 | note_bb3: (Note::Bb3,58), 375 | note_b3: (Note::B3,59), 376 | note_c4: (Note::C4,60), 377 | note_cs4: (Note::Cs4,61), 378 | note_db4: (Note::Db4,61), 379 | note_d4: (Note::D4,62), 380 | note_ds4: (Note::Ds4,63), 381 | note_eb4: (Note::Eb4,63), 382 | note_e4: (Note::E4,64), 383 | note_f4: (Note::F4,65), 384 | note_fs4: (Note::Fs4,66), 385 | note_gb4: (Note::Gb4,66), 386 | note_g4: (Note::G4,67), 387 | note_gs4: (Note::Gs4,68), 388 | note_ab4: (Note::Ab4,68), 389 | note_a4: (Note::A4,69), 390 | note_as4: (Note::As4,70), 391 | note_bb4: (Note::Bb4,70), 392 | note_b4: (Note::B4,71), 393 | note_c5: (Note::C5,72), 394 | note_cs5: (Note::Cs5,73), 395 | note_db5: (Note::Db5,73), 396 | note_d5: (Note::D5,74), 397 | note_ds5: (Note::Ds5,75), 398 | note_eb5: (Note::Eb5,75), 399 | note_e5: (Note::E5,76), 400 | note_f5: (Note::F5,77), 401 | note_fs5: (Note::Fs5,78), 402 | note_gb5: (Note::Gb5,78), 403 | note_g5: (Note::G5,79), 404 | note_gs5: (Note::Gs5,80), 405 | note_ab5: (Note::Ab5,80), 406 | note_a5: (Note::A5,81), 407 | note_as5: (Note::As5,82), 408 | note_bb5: (Note::Bb5,82), 409 | note_b5: (Note::B5,83), 410 | note_c6: (Note::C6,84), 411 | note_cs6: (Note::Cs6,85), 412 | note_db6: (Note::Db6,85), 413 | note_d6: (Note::D6,86), 414 | note_ds6: (Note::Ds6,87), 415 | note_eb6: (Note::Eb6,87), 416 | note_e6: (Note::E6,88), 417 | note_f6: (Note::F6,89), 418 | note_fs6: (Note::Fs6,90), 419 | note_gb6: (Note::Gb6,90), 420 | note_g6: (Note::G6,91), 421 | note_gs6: (Note::Gs6,92), 422 | note_ab6: (Note::Ab6,92), 423 | note_a6: (Note::A6,93), 424 | note_as6: (Note::As6,94), 425 | note_bb6: (Note::Bb6,94), 426 | note_b6: (Note::B6,95), 427 | note_c7: (Note::C7,96), 428 | note_cs7: (Note::Cs7,97), 429 | note_db7: (Note::Db7,97), 430 | note_d7: (Note::D7,98), 431 | note_ds7: (Note::Ds7,99), 432 | note_eb7: (Note::Eb7,99), 433 | note_e7: (Note::E7,100), 434 | note_f7: (Note::F7,101), 435 | note_fs7: (Note::Fs7,102), 436 | note_gb7: (Note::Gb7,102), 437 | note_g7: (Note::G7,103), 438 | note_gs7: (Note::Gs7,104), 439 | note_ab7: (Note::Ab7,104), 440 | note_a7: (Note::A7,105), 441 | note_as7: (Note::As7,106), 442 | note_bb7: (Note::Bb7,106), 443 | note_b7: (Note::B7,107), 444 | note_c8: (Note::C8,108), 445 | note_cs8: (Note::Cs8,109), 446 | note_db8: (Note::Db8,109), 447 | note_d8: (Note::D8,110), 448 | note_ds8: (Note::Ds8,111), 449 | note_eb8: (Note::Eb8,111), 450 | note_e8: (Note::E8,112), 451 | note_f8: (Note::F8,113), 452 | note_fs8: (Note::Fs8,114), 453 | note_gb8: (Note::Gb8,114), 454 | note_g8: (Note::G8,115), 455 | note_gs8: (Note::Gs8,116), 456 | note_ab8: (Note::Ab8,116), 457 | note_a8: (Note::A8,117), 458 | note_as8: (Note::As8,118), 459 | note_bb8: (Note::Bb8,118), 460 | note_b8: (Note::B8,119), 461 | note_c9: (Note::C9,120), 462 | note_cs9: (Note::Cs9,121), 463 | note_db9: (Note::Db9,121), 464 | note_d9: (Note::D9,122), 465 | note_ds9: (Note::Ds9,123), 466 | note_eb9: (Note::Eb9,123), 467 | note_e9: (Note::E9,124), 468 | note_f9: (Note::F9,125), 469 | note_fs9: (Note::Fs9,126), 470 | note_gb9: (Note::Gb9,126), 471 | note_g9: (Note::G9,127), 472 | note_gs9: (Note::Gs9,128), 473 | note_ab9: (Note::Ab9,128), 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /src/message/raw.rs: -------------------------------------------------------------------------------- 1 | //! Type for the raw MIDI message. 2 | 3 | use crate::message::data::u7::U7; 4 | 5 | /// Represents the payloads that the midi message may contain. 6 | #[derive(Debug, Clone, Eq, PartialEq)] 7 | pub enum Payload { 8 | /// No payload. 9 | Empty, 10 | /// One-byte payload. 11 | SingleByte(U7), 12 | /// Two-byte payload. 13 | DoubleByte(U7, U7), 14 | } 15 | 16 | /// A struct that captures the valid states. 17 | /// 18 | /// A midi message may be in, but without domain logic mainly useful for serializing. 19 | /// This represents the possible 'shapes', doesn't verify if the data makes sense though! 20 | #[derive(Debug, Clone, Eq, PartialEq)] 21 | pub struct Raw { 22 | /// Status byte. 23 | pub status: u8, 24 | /// Payload of maximum 2 bytes. 25 | pub payload: Payload, 26 | } 27 | -------------------------------------------------------------------------------- /src/packet/cable_number.rs: -------------------------------------------------------------------------------- 1 | //! Enum representing the cable number of a packet. 2 | 3 | use crate::packet::UsbMidiEventPacketError; 4 | 5 | /// The Cable Number (CN) is a value ranging from 0x0 to 0xF 6 | /// indicating the number assignment of the Embedded MIDI Jack associated 7 | /// with the endpoint that is transferring the data 8 | #[allow(missing_docs)] 9 | #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] 10 | #[repr(u8)] 11 | pub enum CableNumber { 12 | #[default] 13 | Cable0 = 0x0, 14 | Cable1 = 0x1, 15 | Cable2 = 0x2, 16 | Cable3 = 0x3, 17 | Cable4 = 0x4, 18 | Cable5 = 0x5, 19 | Cable6 = 0x6, 20 | Cable7 = 0x7, 21 | Cable8 = 0x8, 22 | Cable9 = 0x9, 23 | Cable10 = 0xA, 24 | Cable11 = 0xB, 25 | Cable12 = 0xC, 26 | Cable13 = 0xD, 27 | Cable14 = 0xE, 28 | Cable15 = 0xF, 29 | } 30 | 31 | impl TryFrom for CableNumber { 32 | type Error = UsbMidiEventPacketError; 33 | 34 | fn try_from(value: u8) -> Result { 35 | match value { 36 | x if x == CableNumber::Cable0 as u8 => Ok(CableNumber::Cable0), 37 | x if x == CableNumber::Cable1 as u8 => Ok(CableNumber::Cable1), 38 | x if x == CableNumber::Cable2 as u8 => Ok(CableNumber::Cable2), 39 | x if x == CableNumber::Cable3 as u8 => Ok(CableNumber::Cable3), 40 | x if x == CableNumber::Cable4 as u8 => Ok(CableNumber::Cable4), 41 | x if x == CableNumber::Cable5 as u8 => Ok(CableNumber::Cable5), 42 | x if x == CableNumber::Cable6 as u8 => Ok(CableNumber::Cable6), 43 | x if x == CableNumber::Cable7 as u8 => Ok(CableNumber::Cable7), 44 | x if x == CableNumber::Cable8 as u8 => Ok(CableNumber::Cable8), 45 | x if x == CableNumber::Cable9 as u8 => Ok(CableNumber::Cable9), 46 | x if x == CableNumber::Cable10 as u8 => Ok(CableNumber::Cable10), 47 | x if x == CableNumber::Cable11 as u8 => Ok(CableNumber::Cable11), 48 | x if x == CableNumber::Cable12 as u8 => Ok(CableNumber::Cable12), 49 | x if x == CableNumber::Cable13 as u8 => Ok(CableNumber::Cable13), 50 | x if x == CableNumber::Cable14 as u8 => Ok(CableNumber::Cable14), 51 | x if x == CableNumber::Cable15 as u8 => Ok(CableNumber::Cable15), 52 | _ => Err(UsbMidiEventPacketError::InvalidCableNumber(value)), 53 | } 54 | } 55 | } 56 | 57 | impl From for u8 { 58 | fn from(value: CableNumber) -> u8 { 59 | value as u8 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | 66 | use super::*; 67 | macro_rules! cable_test { 68 | ($($id:ident:$value:expr,)*) => { 69 | $( 70 | #[test] 71 | fn $id() { 72 | let (input,expected) = $value; 73 | assert_eq!(u8::from(input), expected); 74 | } 75 | )* 76 | } 77 | } 78 | 79 | cable_test! { 80 | cable_0: (CableNumber::Cable0,0), 81 | cable_1: (CableNumber::Cable1,1), 82 | cable_2: (CableNumber::Cable2,2), 83 | cable_3: (CableNumber::Cable3,3), 84 | cable_4: (CableNumber::Cable4,4), 85 | cable_5: (CableNumber::Cable5,5), 86 | cable_6: (CableNumber::Cable6,6), 87 | cable_7: (CableNumber::Cable7,7), 88 | cable_8: (CableNumber::Cable8,8), 89 | cable_9: (CableNumber::Cable9,9), 90 | cable_10: (CableNumber::Cable10,10), 91 | cable_11: (CableNumber::Cable11,11), 92 | cable_12: (CableNumber::Cable12,12), 93 | cable_13: (CableNumber::Cable13,13), 94 | cable_14: (CableNumber::Cable14,14), 95 | cable_15: (CableNumber::Cable15,15), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/packet/code_index_number.rs: -------------------------------------------------------------------------------- 1 | //! Enum representing the code index number of a packet. 2 | 3 | use crate::packet::UsbMidiEventPacketError; 4 | 5 | /// The Code Index Number(CIN) indicates the classification 6 | /// of the bytes in the MIDI_x fields. 7 | /// Code Index Number classifications. 8 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 9 | #[repr(u8)] 10 | pub enum CodeIndexNumber { 11 | /// Miscellaneous function codes. Reserved for future extensions. 12 | MiscFunction = 0x00, 13 | /// Cable events. Reserved for future expansion. 14 | CableEvents = 0x1, 15 | /// Two-byte System Common messages like MTC, SongSelect, etc. 16 | SystemCommon2Bytes = 0x2, 17 | /// Three-byte System Common messages like SPP, etc. 18 | SystemCommon3Bytes = 0x3, 19 | /// SysEx starts or continues. 20 | SysexStartsOrContinues = 0x4, 21 | /// Single-byte System Common Message or SysEx ends with following single byte. 22 | SystemCommon1Byte = 0x5, 23 | /// SysEx ends with following two bytes. 24 | SysexEnds2Bytes = 0x6, 25 | /// SysEx ends with following three bytes. 26 | SysexEnds3Bytes = 0x7, 27 | /// Note-off. 28 | NoteOff = 0x8, 29 | /// Note-on. 30 | NoteOn = 0x9, 31 | /// Poly-KeyPress. 32 | PolyKeyPress = 0xA, 33 | /// Control Change. 34 | ControlChange = 0xB, 35 | /// Program Change. 36 | ProgramChange = 0xC, 37 | /// Channel Pressure. 38 | ChannelPressure = 0xD, 39 | /// PitchBend Change. 40 | PitchBendChange = 0xE, 41 | /// Single Byte. 42 | SingleByte = 0xF, 43 | } 44 | 45 | impl TryFrom for CodeIndexNumber { 46 | type Error = UsbMidiEventPacketError; 47 | 48 | fn try_from(value: u8) -> Result { 49 | match value { 50 | x if x == CodeIndexNumber::MiscFunction as u8 => Ok(CodeIndexNumber::MiscFunction), 51 | x if x == CodeIndexNumber::CableEvents as u8 => Ok(CodeIndexNumber::CableEvents), 52 | x if x == CodeIndexNumber::SystemCommon2Bytes as u8 => { 53 | Ok(CodeIndexNumber::SystemCommon2Bytes) 54 | } 55 | x if x == CodeIndexNumber::SystemCommon3Bytes as u8 => { 56 | Ok(CodeIndexNumber::SystemCommon3Bytes) 57 | } 58 | x if x == CodeIndexNumber::SysexStartsOrContinues as u8 => { 59 | Ok(CodeIndexNumber::SysexStartsOrContinues) 60 | } 61 | x if x == CodeIndexNumber::SystemCommon1Byte as u8 => { 62 | Ok(CodeIndexNumber::SystemCommon1Byte) 63 | } 64 | x if x == CodeIndexNumber::SysexEnds2Bytes as u8 => { 65 | Ok(CodeIndexNumber::SysexEnds2Bytes) 66 | } 67 | x if x == CodeIndexNumber::SysexEnds3Bytes as u8 => { 68 | Ok(CodeIndexNumber::SysexEnds3Bytes) 69 | } 70 | x if x == CodeIndexNumber::NoteOff as u8 => Ok(CodeIndexNumber::NoteOff), 71 | x if x == CodeIndexNumber::NoteOn as u8 => Ok(CodeIndexNumber::NoteOn), 72 | x if x == CodeIndexNumber::PolyKeyPress as u8 => Ok(CodeIndexNumber::PolyKeyPress), 73 | x if x == CodeIndexNumber::ControlChange as u8 => Ok(CodeIndexNumber::ControlChange), 74 | x if x == CodeIndexNumber::ProgramChange as u8 => Ok(CodeIndexNumber::ProgramChange), 75 | x if x == CodeIndexNumber::ChannelPressure as u8 => { 76 | Ok(CodeIndexNumber::ChannelPressure) 77 | } 78 | x if x == CodeIndexNumber::PitchBendChange as u8 => { 79 | Ok(CodeIndexNumber::PitchBendChange) 80 | } 81 | x if x == CodeIndexNumber::SingleByte as u8 => Ok(CodeIndexNumber::SingleByte), 82 | _ => Err(UsbMidiEventPacketError::InvalidCodeIndexNumber(value)), 83 | } 84 | } 85 | } 86 | 87 | impl CodeIndexNumber { 88 | /// Creates a new number from a MIDI event payload. 89 | /// 90 | /// The detection is based on the content and ignores the slice length. 91 | pub fn try_from_payload(payload: &[u8]) -> Result { 92 | let Some(status) = payload.first() else { 93 | return Err(UsbMidiEventPacketError::EmptyPayload); 94 | }; 95 | 96 | if *status < 0xF0 { 97 | match status & 0xF0 { 98 | 0x80 => Ok(Self::NoteOff), 99 | 0x90 => Ok(Self::NoteOn), 100 | 0xA0 => Ok(Self::PolyKeyPress), 101 | 0xB0 => Ok(Self::ControlChange), 102 | 0xC0 => Ok(Self::ProgramChange), 103 | 0xD0 => Ok(Self::ChannelPressure), 104 | 0xE0 => Ok(Self::PitchBendChange), 105 | _ => { 106 | if payload.len() > 1 && payload[1] == 0xF7 { 107 | Ok(Self::SysexEnds2Bytes) 108 | } else if payload.len() > 2 && payload[2] == 0xF7 { 109 | Ok(Self::SysexEnds3Bytes) 110 | } else if payload.len() == 1 { 111 | Ok(Self::SingleByte) 112 | } else if payload.len() > 2 { 113 | Ok(Self::SysexStartsOrContinues) 114 | } else { 115 | Err(UsbMidiEventPacketError::InvalidPayloadSize) 116 | } 117 | } 118 | } 119 | } else { 120 | match status { 121 | 0xF0 => { 122 | if payload.len() > 1 && payload[1] == 0xF7 { 123 | Ok(Self::SysexEnds2Bytes) 124 | } else if payload.len() > 2 && payload[2] == 0xF7 { 125 | Ok(Self::SysexEnds3Bytes) 126 | } else if payload.len() > 2 { 127 | Ok(Self::SysexStartsOrContinues) 128 | } else { 129 | Err(UsbMidiEventPacketError::InvalidPayloadSize) 130 | } 131 | } 132 | 0xF1 | 0xF3 => Ok(Self::SystemCommon2Bytes), 133 | 0xF2 => Ok(Self::SystemCommon3Bytes), 134 | 0xF6 | 0xF7 => Ok(Self::SystemCommon1Byte), 135 | 0xF8 | 0xF9 | 0xFA | 0xFB | 0xFC | 0xFE | 0xFF => Ok(Self::SingleByte), 136 | _ => Err(UsbMidiEventPacketError::InvalidPayloadStatus), 137 | } 138 | } 139 | } 140 | 141 | /// Returns the size of the MIDI_x event payload in bytes. 142 | pub fn payload_size(&self) -> usize { 143 | match self { 144 | Self::SystemCommon1Byte | Self::SingleByte => 1, 145 | Self::SystemCommon2Bytes 146 | | Self::SysexEnds2Bytes 147 | | Self::ProgramChange 148 | | Self::ChannelPressure => 2, 149 | Self::SystemCommon3Bytes 150 | | Self::SysexEnds3Bytes 151 | | Self::SysexStartsOrContinues 152 | | Self::NoteOff 153 | | Self::NoteOn 154 | | Self::PolyKeyPress 155 | | Self::ControlChange 156 | | Self::PitchBendChange => 3, 157 | 158 | // These variants are reserved for future use. 159 | // We assume the maximum length of 3 bytes so that no data can get lost. 160 | Self::MiscFunction | Self::CableEvents => 3, 161 | } 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | 169 | macro_rules! encode_payload_test { 170 | ($($id:ident: $value:expr,)*) => { 171 | $( 172 | #[test] 173 | fn $id() { 174 | let (payload, expected) = $value; 175 | let cin = CodeIndexNumber::try_from_payload(&payload); 176 | assert_eq!(cin, expected); 177 | } 178 | )* 179 | } 180 | } 181 | 182 | encode_payload_test! { 183 | note_off: ([0x80, 60, 64], Ok(CodeIndexNumber::NoteOff)), 184 | note_on: ([0x90, 60, 64], Ok(CodeIndexNumber::NoteOn)), 185 | poly_key_press: ([0xA0, 48, 32], Ok(CodeIndexNumber::PolyKeyPress)), 186 | control_change: ([0xB0, 10, 127], Ok(CodeIndexNumber::ControlChange)), 187 | program_change: ([0xC0, 5], Ok(CodeIndexNumber::ProgramChange)), 188 | channel_pressure: ([0xD0, 54], Ok(CodeIndexNumber::ChannelPressure)), 189 | pitch_bend: ([0xE0, 32, 96], Ok(CodeIndexNumber::PitchBendChange)), 190 | mtc_quarter_frame: ([0xF1, 12], Ok(CodeIndexNumber::SystemCommon2Bytes)), 191 | song_position_pointer: ([0xF2, 3, 8], Ok(CodeIndexNumber::SystemCommon3Bytes)), 192 | song_select: ([0xF3, 15], Ok(CodeIndexNumber::SystemCommon2Bytes)), 193 | tune_request: ([0xF6], Ok(CodeIndexNumber::SystemCommon1Byte)), 194 | timing_clock: ([0xF8], Ok(CodeIndexNumber::SingleByte)), 195 | tick: ([0xF9], Ok(CodeIndexNumber::SingleByte)), 196 | start: ([0xFA], Ok(CodeIndexNumber::SingleByte)), 197 | continue_: ([0xFB], Ok(CodeIndexNumber::SingleByte)), 198 | stop: ([0xFC], Ok(CodeIndexNumber::SingleByte)), 199 | active_sensing: ([0xFE], Ok(CodeIndexNumber::SingleByte)), 200 | system_reset: ([0xFF], Ok(CodeIndexNumber::SingleByte)), 201 | sysex_starts: ([0xF0, 1, 2], Ok(CodeIndexNumber::SysexStartsOrContinues)), 202 | sysex_starts_1byte: ([0xF0], Err(UsbMidiEventPacketError::InvalidPayloadSize)), 203 | sysex_starts_2bytes: ([0xF0, 1], Err(UsbMidiEventPacketError::InvalidPayloadSize)), 204 | sysex_continues_1byte: ([1], Ok(CodeIndexNumber::SingleByte)), 205 | sysex_continues_2bytes: ([1, 2], Err(UsbMidiEventPacketError::InvalidPayloadSize)), 206 | sysex_continues_3bytes: ([1, 2, 3], Ok(CodeIndexNumber::SysexStartsOrContinues)), 207 | sysex_ends_1byte: ([0xF7], Ok(CodeIndexNumber::SystemCommon1Byte)), 208 | sysex_ends_2bytes: ([1, 0xF7], Ok(CodeIndexNumber::SysexEnds2Bytes)), 209 | sysex_ends_3bytes: ([1, 2, 0xF7], Ok(CodeIndexNumber::SysexEnds3Bytes)), 210 | sysex_2bytes: ([0xF0, 0xF7], Ok(CodeIndexNumber::SysexEnds2Bytes)), 211 | sysex_3bytes: ([0xF0, 1, 0xF7], Ok(CodeIndexNumber::SysexEnds3Bytes)), 212 | undefined_f4: ([0xF4], Err(UsbMidiEventPacketError::InvalidPayloadStatus)), 213 | undefined_f5: ([0xF5], Err(UsbMidiEventPacketError::InvalidPayloadStatus)), 214 | empty: ([], Err(UsbMidiEventPacketError::EmptyPayload)), 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/packet/mod.rs: -------------------------------------------------------------------------------- 1 | //! USB MIDI event packet parser and related types. 2 | 3 | pub mod cable_number; 4 | pub mod code_index_number; 5 | pub mod reader; 6 | 7 | use crate::packet::cable_number::CableNumber; 8 | use crate::packet::code_index_number::CodeIndexNumber; 9 | 10 | /// A packet that communicates with the host. 11 | /// 12 | /// Currently supported is sending the specified normal midi 13 | /// message over the supplied cable number 14 | #[derive(Debug, Clone, Eq, PartialEq)] 15 | pub struct UsbMidiEventPacket { 16 | /// Raw packet data. 17 | raw: [u8; 4], 18 | } 19 | 20 | impl From for [u8; 4] { 21 | fn from(value: UsbMidiEventPacket) -> [u8; 4] { 22 | value.raw 23 | } 24 | } 25 | 26 | /// Error variants for parsing the packet. 27 | #[derive(Debug, Clone, Eq, PartialEq)] 28 | pub enum UsbMidiEventPacketError { 29 | /// Invalid packet. 30 | InvalidPacket, 31 | /// Invalid note. 32 | InvalidNote(u8), 33 | /// Invalid cable number. 34 | InvalidCableNumber(u8), 35 | /// Invalid code index number. 36 | InvalidCodeIndexNumber(u8), 37 | /// Invalid event type. 38 | InvalidEventType(u8), 39 | /// Missing data packet. 40 | MissingDataPacket, 41 | /// Empty payload. 42 | EmptyPayload, 43 | /// Invalid payload status. 44 | InvalidPayloadStatus, 45 | /// Invalid payload size. 46 | InvalidPayloadSize, 47 | } 48 | 49 | impl TryFrom<&[u8]> for UsbMidiEventPacket { 50 | type Error = UsbMidiEventPacketError; 51 | 52 | fn try_from(value: &[u8]) -> Result { 53 | let Ok(raw) = value.try_into() else { 54 | return Err(UsbMidiEventPacketError::InvalidPacket); 55 | }; 56 | 57 | Ok(Self { raw }) 58 | } 59 | } 60 | 61 | impl UsbMidiEventPacket { 62 | /// Returns the cable number. 63 | pub fn cable_number(&self) -> CableNumber { 64 | let raw_cable_number = self.raw[0] >> 4; 65 | 66 | // Unwrap can't fail because of limited `raw_cable_number` value range. 67 | CableNumber::try_from(raw_cable_number).unwrap_or_default() 68 | } 69 | 70 | /// Returns the header byte. 71 | pub fn header(&self) -> u8 { 72 | self.raw[0] 73 | } 74 | 75 | /// Returns a slice to the event payload bytes. The length is dependent on the payload type. 76 | pub fn payload_bytes(&self) -> &[u8] { 77 | let raw_cin = self.raw[0] & 0x0F; 78 | 79 | match CodeIndexNumber::try_from(raw_cin) { 80 | Ok(cin) => { 81 | let size = cin.payload_size(); 82 | &self.raw[1..1 + size] 83 | } 84 | 85 | // Can't happen because of limited `raw_cin` value range. 86 | Err(_) => &[], 87 | } 88 | } 89 | 90 | /// Returns a reference to the raw bytes. 91 | pub fn as_raw_bytes(&self) -> &[u8] { 92 | &self.raw 93 | } 94 | 95 | /// Returns the raw bytes as owned array. 96 | pub fn to_raw_bytes(&self) -> [u8; 4] { 97 | self.raw 98 | } 99 | 100 | /// Creates a packet from a slice of event payload bytes. 101 | pub fn try_from_payload_bytes( 102 | cable: CableNumber, 103 | bytes: &[u8], 104 | ) -> Result { 105 | let cin = CodeIndexNumber::try_from_payload(bytes)?; 106 | let payload_size = cin.payload_size(); 107 | 108 | if bytes.len() < payload_size { 109 | return Err(UsbMidiEventPacketError::InvalidPayloadSize); 110 | } 111 | 112 | let mut raw = [0; 4]; 113 | raw[0] = (cable as u8) << 4 | cin as u8; 114 | raw[1..1 + payload_size].copy_from_slice(&bytes[..payload_size]); 115 | 116 | Ok(Self { raw }) 117 | } 118 | 119 | /// Returns if the packet payload is part of a SysEx message. 120 | pub fn is_sysex(&self) -> bool { 121 | let Ok(cin) = CodeIndexNumber::try_from(self.raw[0] & 0x0F) else { 122 | return false; 123 | }; 124 | 125 | match cin { 126 | CodeIndexNumber::SysexStartsOrContinues 127 | | CodeIndexNumber::SysexEnds2Bytes 128 | | CodeIndexNumber::SysexEnds3Bytes => true, 129 | CodeIndexNumber::SystemCommon1Byte => self.raw[1] == 0xF7, 130 | CodeIndexNumber::SingleByte => self.raw[1] < 0x80, 131 | _ => false, 132 | } 133 | } 134 | 135 | /// Returns if the packet payload contains the start of a SysEx message. 136 | pub fn is_sysex_start(&self) -> bool { 137 | self.is_sysex() && self.raw[1] == 0xF0 138 | } 139 | 140 | /// Returns if the packet payload contains the end of a SysEx message. 141 | pub fn is_sysex_end(&self) -> bool { 142 | self.is_sysex() && (self.raw[1] == 0xF7 || self.raw[2] == 0xF7 || self.raw[3] == 0xF7) 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | 150 | mod decode { 151 | use super::*; 152 | 153 | macro_rules! decode_packet_test { 154 | ($($id:ident: $value:expr,)*) => { 155 | $( 156 | #[test] 157 | fn $id() { 158 | let (raw, expected) = $value; 159 | let expected = ( 160 | expected.0, 161 | expected.1.as_slice(), 162 | expected.2, expected.3, 163 | expected.4 164 | ); 165 | let packet = UsbMidiEventPacket::try_from(raw.as_slice()).unwrap(); 166 | let decoded = ( 167 | packet.cable_number(), 168 | packet.payload_bytes(), 169 | packet.is_sysex(), 170 | packet.is_sysex_start(), 171 | packet.is_sysex_end() 172 | ); 173 | assert_eq!(decoded, expected); 174 | } 175 | )* 176 | } 177 | } 178 | 179 | decode_packet_test! { 180 | note_off: ([0x18, 0x80, 60, 64], (CableNumber::Cable1, [0x80, 60, 64], false, false, false)), 181 | note_on: ([0x49, 0x92, 48, 20], (CableNumber::Cable4, [0x92, 48, 20], false, false, false)), 182 | poly_key_press: ([0x0A, 0xA0, 15, 74], (CableNumber::Cable0, [0xA0, 15, 74], false, false, false)), 183 | control_change: ([0x0B, 0xB0, 64, 127], (CableNumber::Cable0, [0xB0, 64, 127], false, false, false)), 184 | program_change: ([0x0C, 0xC0, 5, 0], (CableNumber::Cable0, [0xC0, 5], false, false, false)), 185 | channel_pressure: ([0x0D, 0xD0, 85, 0], (CableNumber::Cable0, [0xD0, 85], false, false, false)), 186 | pitch_bend: ([0x0E, 0xE0, 40, 120], (CableNumber::Cable0, [0xE0, 40, 120], false, false, false)), 187 | mtc_quarter_frame: ([0x02, 0xF1, 27, 0], (CableNumber::Cable0, [0xF1, 27], false, false, false)), 188 | song_position_pointer: ([0x03, 0xF2, 38, 17], (CableNumber::Cable0, [0xF2, 38, 17], false, false, false)), 189 | song_select: ([0x02, 0xF3, 2, 0], (CableNumber::Cable0, [0xF3, 2], false, false, false)), 190 | tune_request: ([0x05, 0xF6, 0, 0], (CableNumber::Cable0, [0xF6], false, false, false)), 191 | timing_clock: ([0x0F, 0xF8, 0, 0], (CableNumber::Cable0, [0xF8], false, false, false)), 192 | tick: ([0x0F, 0xF9, 0, 0], (CableNumber::Cable0, [0xF9], false, false, false)), 193 | start: ([0x0F, 0xFA, 0, 0], (CableNumber::Cable0, [0xFA], false, false, false)), 194 | continue_: ([0x0F, 0xFB, 0, 0], (CableNumber::Cable0, [0xFB], false, false, false)), 195 | stop: ([0x0F, 0xFC, 0, 0], (CableNumber::Cable0, [0xFC], false, false, false)), 196 | active_sensing: ([0x0F, 0xFE, 0, 0], (CableNumber::Cable0, [0xFE], false, false, false)), 197 | system_reset: ([0x0F, 0xFF, 0, 0], (CableNumber::Cable0, [0xFF], false, false, false)), 198 | sysex_starts: ([0x04, 0xF0, 1, 2], (CableNumber::Cable0, [0xF0, 1, 2], true, true, false)), 199 | sysex_continues_1byte: ([0x0F, 1, 0, 0], (CableNumber::Cable0, [1], true, false, false)), 200 | sysex_continues_3bytes: ([0x04, 1, 2, 3], (CableNumber::Cable0, [1, 2, 3], true, false, false)), 201 | sysex_ends_1byte: ([0x05, 0xF7, 0, 0], (CableNumber::Cable0, [0xF7], true, false, true)), 202 | sysex_ends_2bytes: ([0x06, 1, 0xF7, 0], (CableNumber::Cable0, [1, 0xF7], true, false, true)), 203 | sysex_ends_3bytes: ([0x07, 1, 2, 0xF7], (CableNumber::Cable0, [1, 2, 0xF7], true, false, true)), 204 | sysex_2bytes: ([0x06, 0xF0, 0xF7, 0], (CableNumber::Cable0, [0xF0, 0xF7], true, true, true)), 205 | sysex_3bytes: ([0x07, 0xF0, 1, 0xF7], (CableNumber::Cable0, [0xF0, 1, 0xF7], true, true, true)), 206 | undefined_f4: ([0x02, 0xF4, 1, 0], (CableNumber::Cable0, [0xF4, 1], false, false, false)), 207 | undefined_f5: ([0x03, 0xF5, 1, 2], (CableNumber::Cable0, [0xF5, 1, 2], false, false, false)), 208 | empty: ([0x00, 0, 0, 0], (CableNumber::Cable0, [0, 0, 0], false, false, false)), 209 | } 210 | } 211 | 212 | mod encode { 213 | use super::*; 214 | 215 | macro_rules! encode_packet_test { 216 | ($($id:ident: $value:expr,)*) => { 217 | $( 218 | #[test] 219 | fn $id() { 220 | let ((cable, payload), expected) = $value; 221 | let payload = payload.as_slice(); 222 | let encoded = UsbMidiEventPacket::try_from_payload_bytes(cable, payload); 223 | let expected: Result<[u8; 4], UsbMidiEventPacketError> = expected; 224 | assert_eq!(encoded, expected.map( 225 | |v| UsbMidiEventPacket::try_from(v.as_slice()).unwrap()) 226 | ); 227 | } 228 | )* 229 | } 230 | } 231 | 232 | encode_packet_test! { 233 | note_off: ((CableNumber::Cable3, [0x80, 33, 75]), Ok([0x38, 0x80, 33, 75])), 234 | note_on: ((CableNumber::Cable2, [0x96, 67, 14]), Ok([0x29, 0x96, 67, 14])), 235 | poly_key_press: ((CableNumber::Cable0, [0xA0, 48, 72]), Ok([0x0A, 0xA0, 48, 72])), 236 | control_change: ((CableNumber::Cable0, [0xB0, 10, 127]), Ok([0x0B, 0xB0, 10, 127])), 237 | program_change: ((CableNumber::Cable0, [0xC0, 36]), Ok([0x0C, 0xC0, 36, 0])), 238 | program_change_extra: ((CableNumber::Cable0, [0xC0, 36, 54]), Ok([0x0C, 0xC0, 36, 0])), 239 | channel_pressure: ((CableNumber::Cable0, [0xD0, 115]), Ok([0x0D, 0xD0, 115, 0])), 240 | channel_pressure_extra: ((CableNumber::Cable0, [0xD0, 115, 27]), Ok([0x0D, 0xD0, 115, 0])), 241 | pitch_bend: ((CableNumber::Cable0, [0xE0, 93, 46]), Ok([0x0E, 0xE0, 93, 46])), 242 | mtc_quarter_frame: ((CableNumber::Cable0, [0xF1, 102, 46]), Ok([0x02, 0xF1, 102, 0])), 243 | mtc_quarter_frame_extra: ((CableNumber::Cable0, [0xF1, 102, 46, 7]), Ok([0x02, 0xF1, 102, 0])), 244 | song_position_pointer: ((CableNumber::Cable0, [0xF2, 42, 74]), Ok([0x03, 0xF2, 42, 74])), 245 | song_select: ((CableNumber::Cable0, [0xF3, 24]), Ok([0x02, 0xF3, 24, 0])), 246 | song_select_extra: ((CableNumber::Cable0, [0xF3, 24, 96]), Ok([0x02, 0xF3, 24, 0])), 247 | tune_request: ((CableNumber::Cable0, [0xF6]), Ok([0x05, 0xF6, 0, 0])), 248 | tune_request_extra: ((CableNumber::Cable0, [0xF6, 67, 72]), Ok([0x05, 0xF6, 0, 0])), 249 | timing_clock: ((CableNumber::Cable0, [0xF8]), Ok([0x0F, 0xF8, 0, 0])), 250 | timing_clock_extra: ((CableNumber::Cable0, [0xF8, 38, 126]), Ok([0x0F, 0xF8, 0, 0])), 251 | tick: ((CableNumber::Cable0, [0xF9]), Ok([0x0F, 0xF9, 0, 0])), 252 | start: ((CableNumber::Cable0, [0xFA]), Ok([0x0F, 0xFA, 0, 0])), 253 | continue_: ((CableNumber::Cable0, [0xFB]), Ok([0x0F, 0xFB, 0, 0])), 254 | stop: ((CableNumber::Cable0, [0xFC]), Ok([0x0F, 0xFC, 0, 0])), 255 | active_sensing: ((CableNumber::Cable0, [0xFE]), Ok([0x0F, 0xFE, 0, 0])), 256 | system_reset: ((CableNumber::Cable0, [0xFF]), Ok([0x0F, 0xFF, 0, 0])), 257 | sysex_starts: ((CableNumber::Cable0, [0xF0, 1, 2]), Ok([0x04, 0xF0, 1, 2])), 258 | sysex_starts_1byte: ((CableNumber::Cable0, [0xF0]), Err(UsbMidiEventPacketError::InvalidPayloadSize)), 259 | sysex_starts_2bytes: ((CableNumber::Cable0, [0xF0, 1]), Err(UsbMidiEventPacketError::InvalidPayloadSize)), 260 | sysex_continues_1byte: ((CableNumber::Cable0, [1]), Ok([0x0F, 1, 0, 0])), 261 | sysex_continues_2bytes: ((CableNumber::Cable0, [1, 2]), Err(UsbMidiEventPacketError::InvalidPayloadSize)), 262 | sysex_continues_3bytes: ((CableNumber::Cable0, [1, 2, 3]), Ok([0x04, 1, 2, 3])), 263 | sysex_ends_1byte: ((CableNumber::Cable0, [0xF7]), Ok([0x05, 0xF7, 0, 0])), 264 | sysex_ends_2bytes: ((CableNumber::Cable0, [1, 0xF7]), Ok([0x06, 1, 0xF7, 0])), 265 | sysex_ends_3bytes: ((CableNumber::Cable0, [1, 2, 0xF7]), Ok([0x07, 1, 2, 0xF7])), 266 | sysex_2bytes: ((CableNumber::Cable0, [0xF0, 0xF7]), Ok([0x06, 0xF0, 0xF7, 0])), 267 | sysex_3bytes: ((CableNumber::Cable0, [0xF0, 1, 0xF7]), Ok([0x07, 0xF0, 1, 0xF7])), 268 | undefined_f4: ((CableNumber::Cable0, [0xF4]), Err(UsbMidiEventPacketError::InvalidPayloadStatus)), 269 | undefined_f5: ((CableNumber::Cable0, [0xF5]), Err(UsbMidiEventPacketError::InvalidPayloadStatus)), 270 | note_off_missing_1byte: ((CableNumber::Cable3, [0x80, 26]), Err(UsbMidiEventPacketError::InvalidPayloadSize)), 271 | note_off_missing_2bytes: ((CableNumber::Cable3, [0x80]), Err(UsbMidiEventPacketError::InvalidPayloadSize)), 272 | empty: ((CableNumber::Cable0, []), Err(UsbMidiEventPacketError::EmptyPayload)), 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/packet/reader.rs: -------------------------------------------------------------------------------- 1 | //! Reader for received packets. 2 | 3 | use crate::class::{MAX_PACKET_SIZE, MIDI_PACKET_SIZE}; 4 | use crate::packet::{UsbMidiEventPacket, UsbMidiEventPacketError}; 5 | 6 | /// Packet reader with internal buffer for received message. 7 | pub struct UsbMidiPacketReader<'a> { 8 | buffer: &'a [u8; MAX_PACKET_SIZE], 9 | position: usize, 10 | raw_bytes_received: usize, 11 | } 12 | 13 | impl<'a> UsbMidiPacketReader<'a> { 14 | /// Creates a new reader. 15 | pub fn new(buffer: &'a [u8; MAX_PACKET_SIZE], raw_bytes_received: usize) -> Self { 16 | UsbMidiPacketReader { 17 | buffer, 18 | position: 0, 19 | raw_bytes_received, 20 | } 21 | } 22 | } 23 | 24 | impl Iterator for UsbMidiPacketReader<'_> { 25 | type Item = Result; 26 | 27 | fn next(&mut self) -> Option { 28 | if self.position <= MAX_PACKET_SIZE && self.position < self.raw_bytes_received { 29 | let packet = self 30 | .buffer 31 | .get(self.position..(self.position + MIDI_PACKET_SIZE)) 32 | .map(UsbMidiEventPacket::try_from); 33 | 34 | self.position += MIDI_PACKET_SIZE; 35 | return packet; 36 | } 37 | None 38 | } 39 | } 40 | --------------------------------------------------------------------------------