Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.5.0 #22

Merged
merged 12 commits into from
Jan 17, 2025
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.5.0] - 2025-01-17

This release focuses on improving the internal message data types and their usage.

### Added

- `Message` enum variants for *System Common* and *System Realtime* messages.
- `U14` primitive value type used by *Pitch Wheel* and *Song Position Pointer* messages.
- Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `U4`.
- Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `InvalidU4`.
- Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `InvalidU7`.
- Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `Payload`.
- Derive `Debug`, `Clone`, `Eq`, and `PartialEq` for `Raw`.
- Re-exports of common data types in the `message` module.

### Changed

- 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.

## [0.4.0] - 2025-01-03

This release focuses on:
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "usbd-midi"
version = "0.4.0"
version = "0.5.0"
authors = [
"Beau Trepp <[email protected]>",
"Florian Jung <[email protected]>",
"Oliver Rockstedt <[email protected]>",
]
edition = "2021"
description = "A USB MIDI implementation for usb-device."
rust-version = "1.78"
description = "USB MIDI device class implementation for use with usb-device."
homepage = "https://github.com/rust-embedded-community/usbd-midi"
repository = "https://github.com/rust-embedded-community/usbd-midi"
license = "MIT"
Expand Down
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
# usbd-midi

A simple USB MIDI device class for [usb-device](https://crates.io/crates/usb-device).
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.

Currently this aims to be a very simple implementation, that allows the microcontroller to send or receive MIDI information to/from a host like a desktop computer.
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.

This crate requires the use of a HAL that implements the `usb-device` traits.
**NOTE:** only MIDI 1.0 protocol is currently supported.

## Example
## Message Types

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.

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.

## Examples

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.

### Receive MIDI

Turn on an LED as long as note C2 is pressed. The example only shows the hardware-independent parts.
Turn on an LED as long as note C2 is pressed.

```rust ignore
use usb_device::prelude::*;
use usbd_midi::{
message::{channel::Channel, notes::Note},
Message,
message::{Message, Channel, Note},
UsbMidiClass,
UsbMidiPacketReader,
};
Expand Down
6 changes: 3 additions & 3 deletions examples/example-esp32s3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ version = "0.1.0"
edition = "2021"

[dependencies]
esp-hal = { version = "0.22", features = ["esp32s3"] }
esp-backtrace = { version = "0.14.2", features = [
esp-hal = { version = "0.23", features = ["esp32s3"] }
esp-backtrace = { version = "0.15.0", features = [
"esp32s3",
"panic-handler",
"println",
] }
esp-println = { version = "0.12.0", features = ["esp32s3", "log"] }
esp-println = { version = "0.13.0", features = ["esp32s3", "log"] }
usb-device = { version = "0.3.2", features = ["control-buffer-256"] }
usbd-midi = { path = "../../" }
midi-convert = "0.2.0"
Expand Down
2 changes: 1 addition & 1 deletion examples/example-esp32s3/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const SYSEX_BUFFER_SIZE: usize = 64;
fn main() -> ! {
// Some basic setup to run the MCU at maximum clock speed.
let mut config = Config::default();
config.cpu_clock = clock::CpuClock::Clock240MHz;
config.cpu_clock = clock::CpuClock::_240MHz;
let peripherals = esp_hal::init(config);

let usb_bus_allocator = otg_fs::UsbBus::new(
Expand Down
1 change: 1 addition & 0 deletions src/message/data/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Primitives and structures used in USB MIDI data.

pub mod u14;
pub mod u4;
pub mod u7;

Expand Down
98 changes: 98 additions & 0 deletions src/message/data/u14.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! A primitive value with 14-bit length.

use crate::message::data::{u7::U7, FromClamped, FromOverFlow};

/// A primitive value that can be from 0-0x4000
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct U14(pub(crate) u16);

/// Error representing that this value is not a valid u14
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InvalidU14(pub u16);

impl TryFrom<u16> for U14 {
type Error = InvalidU14;

fn try_from(value: u16) -> Result<Self, Self::Error> {
if value > 0x3FFF {
Err(InvalidU14(value))
} else {
Ok(U14(value))
}
}
}

impl From<U14> for u16 {
fn from(value: U14) -> u16 {
value.0
}
}

impl FromOverFlow<u16> for U14 {
fn from_overflow(value: u16) -> U14 {
const MASK: u16 = 0b0011_1111_1111_1111;
let value = MASK & value;
U14(value)
}
}

impl FromClamped<u16> for U14 {
fn from_clamped(value: u16) -> U14 {
match U14::try_from(value) {
Ok(x) => x,
_ => U14::MAX,
}
}
}

impl U14 {
/// Maximum value for the type.
pub const MAX: U14 = U14(0x3FFF);
/// Minimum value for the type.
pub const MIN: U14 = U14(0);

/// Creates a new U14 value from an (U7, U7) tuple containing the LSB and MSB.
pub fn from_split_u7(value: (U7, U7)) -> Self {
Self((value.0 .0 as u16) | ((value.1 .0 as u16) << 7))
}

/// Returns the LSB and MSB of the value as (U7, U7) tuple.
pub fn split_u7(&self) -> (U7, U7) {
(U7((self.0 & 0x7F) as u8), U7((self.0 >> 7) as u8))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn try_from_valid() {
assert_eq!(U14::try_from(0x1234), Ok(U14(0x1234)));
}

#[test]
fn try_from_invalid() {
assert_eq!(U14::try_from(0x4000), Err(InvalidU14(0x4000)));
}

#[test]
fn from_overflow() {
assert_eq!(U14::from_overflow(0x400F), U14(0x0F));
}

#[test]
fn from_clamped() {
assert_eq!(U14::from_clamped(0x400F), U14(0x3FFF));
}

#[test]
fn from_split_u7() {
assert_eq!(U14::from_split_u7((U7(0x7F), U7(0x6F))), U14(0x37FF));
}

#[test]
fn split_u7() {
assert_eq!(U14(0x37FF).split_u7(), (U7(0x7F), U7(0x6F)));
}
}
2 changes: 2 additions & 0 deletions src/message/data/u4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
use crate::message::data::{FromClamped, FromOverFlow};

/// A primitive value that can be from 0-0x0F
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct U4(u8);

/// Error representing that this value is not a valid u4
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InvalidU4(pub u8);

impl TryFrom<u8> for U4 {
Expand Down
1 change: 1 addition & 0 deletions src/message/data/u7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::message::data::{FromClamped, FromOverFlow};
pub struct U7(pub(crate) u8);

/// Error representing that this value is not a valid u7
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InvalidU7(pub u8);

impl TryFrom<u8> for U7 {
Expand Down
Loading
Loading