|
1 |
| -midi2/README.md |
| 1 | +# 🎹 MIDI2 🎹 |
| 2 | + |
| 3 | +[](https://crates.io/crates/midi2) |
| 4 | +[](https://docs.rs/midi2) |
| 5 | +[](https://github.com/pre-commit/pre-commit) |
| 6 | +[](https://conventionalcommits.org) |
| 7 | + |
| 8 | + |
| 9 | +Ergonomic, versatile, strong types wrapping MIDI 2.0 message data. |
| 10 | + |
| 11 | +This implementation of MIDI 2.0 is based on the 1.1 revision of the specifications. |
| 12 | +See [the official MIDI 2.0 specification](https://midi.org/) |
| 13 | +for more details on the data protocol standard. |
| 14 | + |
| 15 | +> [!CAUTION] |
| 16 | +> |
| 17 | +> This project is still in early development. |
| 18 | +> Expect breaking changes and bugs, and please report any issues you encounter. |
| 19 | +
|
| 20 | +We would welcome contributions! |
| 21 | +Please refer to the [CONTRIBUTOR.md](CONTRIBUTOR.md) |
| 22 | + |
| 23 | +## Strongly Typed Message Wrappers |
| 24 | + |
| 25 | +A strongly typed message wrapper is provided for every message in the MIDI 2.0 specification. |
| 26 | + |
| 27 | + |
| 28 | +```rust |
| 29 | +use midi2::prelude::*; |
| 30 | + |
| 31 | +// Messages have a simple setter / getter interface |
| 32 | +let mut note_on = channel_voice2::NoteOn::<[u32; 4]>::new(); |
| 33 | +note_on.set_group(u4::new(0x8)); |
| 34 | +note_on.set_channel(u4::new(0xA)); |
| 35 | +note_on.set_note_number(u7::new(0x5E)); |
| 36 | +note_on.set_velocity(0x6A14); |
| 37 | + |
| 38 | +assert_eq!(note_on.group(), u4::new(0x8)); |
| 39 | +assert_eq!(note_on.channel(), u4::new(0xA)); |
| 40 | +assert_eq!(note_on.note_number(), u7::new(0x5E)); |
| 41 | +assert_eq!(note_on.velocity(), 0x6A14); |
| 42 | +assert_eq!(note_on.data(), &[0x489A_5E00, 0x6A14_0000]); |
| 43 | + |
| 44 | +// Messages wrap an underlying buffer of data which can be read as an |
| 45 | +// ordinary slice. |
| 46 | +let mut composer_name = flex_data::ComposerName::<Vec<u32>>::new(); |
| 47 | +composer_name.set_name("Pinch b2b Peverelist"); |
| 48 | +assert_eq!( |
| 49 | + composer_name.data(), |
| 50 | + &[ |
| 51 | + 0xD050_0105, |
| 52 | + 0x5069_6E63, |
| 53 | + 0x6820_6232, |
| 54 | + 0x6220_5065, |
| 55 | + 0xD0D0_0105, |
| 56 | + 0x7665_7265, |
| 57 | + 0x6C69_7374, |
| 58 | + 0x0000_0000, |
| 59 | + ] |
| 60 | +); |
| 61 | +``` |
| 62 | + |
| 63 | +## Aggregate Message Types |
| 64 | + |
| 65 | +All message wrappers are grouped into aggregate enum types. |
| 66 | +There's a top level enum type which can represent all messages, |
| 67 | +and there's sub enum types for each different UMP type specified |
| 68 | +by the MIDI 2.0 specification. |
| 69 | + |
| 70 | +```rust |
| 71 | +fn handle_message(buffer: &[u32]) { |
| 72 | + use midi2::prelude::*; |
| 73 | + |
| 74 | + match UmpMessage::try_from(buffer) { |
| 75 | + Ok(UmpMessage::ChannelVoice2(m)) => { |
| 76 | + println!("Channel Voice2: channel: {}", m.channel()); |
| 77 | + match m { |
| 78 | + channel_voice2::ChannelVoice2::NoteOn(m) => { |
| 79 | + println!("Note On! note: {}, velocity: {}", m.note_number(), m.velocity()); |
| 80 | + } |
| 81 | + channel_voice2::ChannelVoice2::NoteOff(m) => { |
| 82 | + println!("Note Off! note: {}, velocity: {}", m.note_number(), m.velocity()); |
| 83 | + } |
| 84 | + _ => {} |
| 85 | + } |
| 86 | + } |
| 87 | + Ok(UmpMessage::Sysex7(m)) => { |
| 88 | + println!( |
| 89 | + "Sysex 7bit: payload: {:?}", |
| 90 | + m.payload().collect::<Vec<u7>>() |
| 91 | + ); |
| 92 | + } |
| 93 | + Ok(UmpMessage::FlexData(m)) => { |
| 94 | + use midi2::flex_data::FlexDataMessage; |
| 95 | + |
| 96 | + println!("FlexData: bank: {:?}", m.bank()); |
| 97 | + match m { |
| 98 | + _ => {}, // further matching on different flex data types |
| 99 | + } |
| 100 | + } |
| 101 | + // further matching on other message types |
| 102 | + Err(e) => { |
| 103 | + println!("Error parsing ump buffer: {:?}", e); |
| 104 | + } |
| 105 | + _ => {} |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## Full Sysex Support |
| 111 | + |
| 112 | +Sysex message can be represented with MIDI 2.0 Universal Message Packets. |
| 113 | + |
| 114 | +```rust |
| 115 | +use midi2::prelude::*; |
| 116 | + |
| 117 | +let mut message = sysex7::Sysex7::<Vec<u32>>::new(); |
| 118 | +message.set_payload((0u8..30u8).map(u7::new)); |
| 119 | +message.set_group(u4::new(0xA)); |
| 120 | + |
| 121 | +assert_eq!( |
| 122 | + message.data(), |
| 123 | + &[ |
| 124 | + 0x3A16_0001, |
| 125 | + 0x0203_0405, |
| 126 | + 0x3A26_0607, |
| 127 | + 0x0809_0A0B, |
| 128 | + 0x3A26_0C0D, |
| 129 | + 0x0E0F_1011, |
| 130 | + 0x3A26_1213, |
| 131 | + 0x1415_1617, |
| 132 | + 0x3A36_1819, |
| 133 | + 0x1A1B_1C1D, |
| 134 | + ], |
| 135 | +); |
| 136 | +``` |
| 137 | + |
| 138 | +Or with classical MIDI 2.0 byte streams. |
| 139 | + |
| 140 | +```rust |
| 141 | +use midi2::prelude::*; |
| 142 | + |
| 143 | +let mut message = sysex7::Sysex7::<Vec<u8>>::new(); |
| 144 | +message.set_payload((0u8..30u8).map(u7::new)); |
| 145 | + |
| 146 | +assert_eq!( |
| 147 | + message.data(), |
| 148 | + &[ |
| 149 | + 0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, |
| 150 | + 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, |
| 151 | + 0x1B, 0x1C, 0x1D, 0xF7, |
| 152 | + ], |
| 153 | +); |
| 154 | +``` |
| 155 | + |
| 156 | +## Almost Entirely `#![no_std]` Friendly |
| 157 | + |
| 158 | +`#![no_std]` is a first class use case in midi2. |
| 159 | +All message types can be read and written without allocation, |
| 160 | +even messages of arbitrary length, like sysex or flex-data. |
| 161 | + |
| 162 | +You'll want to setup midi2 without default features to compile |
| 163 | +without the `std` feature. |
| 164 | + |
| 165 | +```toml |
| 166 | +midi2 = { version = "0.8.0", default-features = false, features = ["channel-voice2", "sysex7"], } |
| 167 | +``` |
| 168 | + |
| 169 | +### Generic Representation |
| 170 | + |
| 171 | +All messages are generic over their representation. |
| 172 | +For example, a simple non-allocating use case would be to |
| 173 | +represent messages within a fixed size array. |
| 174 | + |
| 175 | +```rust |
| 176 | +use midi2::prelude::*; |
| 177 | + |
| 178 | +let mut message = sysex8::Sysex8::<[u32; 16]>::new(); |
| 179 | + |
| 180 | +// in this mode methods which would require a |
| 181 | +// buffer resize are fallible |
| 182 | +assert_eq!(message.try_set_payload(0..50), Ok(())); |
| 183 | + |
| 184 | +// if there's not enough room in the buffer to |
| 185 | +// accommodate the resize then an overflow error is returned. |
| 186 | +assert_eq!(message.try_set_payload(0..60), Err(midi2::error::BufferOverflow)); |
| 187 | +``` |
| 188 | + |
| 189 | +A more advanced use case might be to make a custom buffer which |
| 190 | +uses an arena allocator to back your messages. |
| 191 | +See the [buffer] docs for more info. |
| 192 | + |
| 193 | +### Borrowed Messages |
| 194 | + |
| 195 | +When reading messages from an existing buffer, the message wrappers |
| 196 | +own a borrowed reference to the data, so no copying or allocation takes place. |
| 197 | +In this case the generic message buffer type is `&[u32]`. |
| 198 | + |
| 199 | +```rust |
| 200 | +use midi2::prelude::*; |
| 201 | + |
| 202 | +let buffer = [ |
| 203 | + 0xD050_0100_u32, |
| 204 | + 0x4469_6769, |
| 205 | + 0x7461_6C20, |
| 206 | + 0x4175_6469, |
| 207 | + 0xD090_0100, |
| 208 | + 0x6F20_576F, |
| 209 | + 0x726B_7374, |
| 210 | + 0x6174_696F, |
| 211 | + 0xD0D0_0100, |
| 212 | + 0x6E20_2D20, |
| 213 | + 0x4441_5733, |
| 214 | + 0x362D_3136, |
| 215 | +]; |
| 216 | +let message = UmpMessage::try_from(&buffer[..]).expect("Valid data"); |
| 217 | +``` |
| 218 | + |
| 219 | +Of course this means that such borrowed messages are immutable |
| 220 | +and also have their lifetimes tied to the original buffer. |
| 221 | + |
| 222 | +To remedy this messages can be `rebuffered` into a different |
| 223 | +generic backing buffer type. |
| 224 | + |
| 225 | +```rust |
| 226 | +use midi2::{ |
| 227 | + prelude::*, |
| 228 | + channel_voice2::NoteOn, |
| 229 | +}; |
| 230 | + |
| 231 | +let mut owned: NoteOn::<[u32; 4]> = { |
| 232 | + let buffer = [0x4898_5E03_u32, 0x6A14_E98A]; |
| 233 | + // the borrowed message is immutable and cannot outlive `buffer` |
| 234 | + let borrowed = NoteOn::try_from(&buffer[..]).expect("Data is valid"); |
| 235 | + borrowed.array_rebuffer_into() |
| 236 | +}; |
| 237 | + |
| 238 | +// the owned message is mutable and liberated from the buffer lifetime. |
| 239 | +owned.set_channel(u4::new(0x9)); |
| 240 | +assert_eq!(owned.data(), &[0x4899_5E03, 0x6A14_E98A]) |
| 241 | +``` |
| 242 | + |
| 243 | +## Support For Classical MIDI Byte Stream Messages |
| 244 | + |
| 245 | +Messages which can be represented in classical MIDI byte stream format are also supported. |
| 246 | +To do this simply use a backing buffer over `u8` instead of `u32`! ✨🎩 |
| 247 | + |
| 248 | +```rust |
| 249 | +use midi2::prelude::*; |
| 250 | + |
| 251 | +let mut message = channel_voice1::ChannelPressure::<[u8; 3]>::new(); |
| 252 | +message.set_channel(u4::new(0x6)); |
| 253 | +message.set_pressure(u7::new(0x09)); |
| 254 | + |
| 255 | +assert_eq!(message.data(), &[0xD6, 0x09]); |
| 256 | +``` |
| 257 | + |
| 258 | +Messages represented in bytes can be transformed to ump and back using conversion traits. |
| 259 | + |
| 260 | +```rust |
| 261 | +use midi2::{ |
| 262 | + prelude::*, |
| 263 | + channel_voice1::ChannelPressure, |
| 264 | +}; |
| 265 | + |
| 266 | +let message = ChannelPressure::<[u8; 3]>::new(); |
| 267 | +let message: ChannelPressure<[u32; 4]> = message.into_ump(); |
| 268 | + |
| 269 | +assert_eq!(message.data(), &[0x20D0_0000]); |
| 270 | +``` |
| 271 | + |
| 272 | +## Cargo Features |
| 273 | + |
| 274 | +Several compile-time features are provided that you can enable or disable to customize |
| 275 | +functionality according to your needs. |
| 276 | + |
| 277 | +Here's a list of available features: |
| 278 | + |
| 279 | +- `default`: |
| 280 | + - **std** - Include [buffer] integration for `std::vec::Vec` and enable allocating getters for values which return `std::string::String` values. |
| 281 | + - **channel-voice2** — Include message wrappers for the MIDI 2.0 channel voice message type. |
| 282 | + |
| 283 | +- `optional`: These features are not enabled by default and can be included by adding them to your `Cargo.toml`. |
| 284 | + - **flex-data** - Include message wrappers for the MIDI 2.0 Flex Data message type. |
| 285 | + - **channel-voice1** - Include message wrappers for the classical MIDI channel voice message type. |
| 286 | + - **sysex7** — Include message wrappers for the MIDI 7bit system exclusive message type. |
| 287 | + - **sysex8** - Include message wrappers for the MIDI 2.0 System Exclusive 8bit message type. |
| 288 | + - **system-common** - Include message wrappers for the MIDI 2.0 System Common / System Real Time message type. |
| 289 | + - **ump-stream** - Include message wrappers for the MIDI 2.0 Ump Stream message type. |
| 290 | + - **ci** — 🚧 WIP 🚧 |
0 commit comments