A suite of tools used to read, modify, and manage MIDI-related systems
midix
provides users with human readable MIDI structures without invariant states. That is, the midi 1.0 specification has been strongly typed such that programatic commands built with this crate are not invariant.
midix
provides a parser (Reader
) to read events from .mid
files.
calling Reader::read_event
will yield a FileEvent
.
Additionally, midix
provides the user with LiveEvent::from_bytes
, which will parse events from a live MIDI source.
You may also make your own MIDI representation using the provided structs. A significant portion of
this library lives within the bevy
feature. See details below on usage with the bevy engine.
midix
is NOT designed to be as fast as possible. It is designed for a user to navigate the MIDI format to read and write to. Instead of working directly with bytes, use language to define what your MIDI is supposed to do.
MIDI can be interpreted in two main ways: through LiveEvent
s and regular file Events
.
To read from a file, use the Reader
:
use midix::prelude::*;
let midi_header = [
/* MIDI Header */
0x4D, 0x54, 0x68, 0x64, // "MThd"
0x00, 0x00, 0x00, 0x06, // Chunk length (6)
0x00, 0x00, // format 0
0x00, 0x01, // one track
0x00, 0x60 // 96 per quarter note
];
let mut reader = Reader::from_byte_slice(&midi_header);
// The first and only event will be the midi header
let Ok(FileEvent::Header(header)) = reader.read_event() else {
panic!("Expected a header event");
};
// format 0 implies a single multi-channel file (only one track)
assert_eq!(header.format_type(), FormatType::SingleMultiChannel);
assert_eq!(
header.timing().ticks_per_quarter_note(),
Some(96)
);
To parse a LiveEvent
use midix::prelude::*;
/* Ch.3 Note On C4, forte */
let note_on = [0x92, 0x3C, 0x60];
// NoteOn is a channel voice message
// Alternatively, use VoiceEvent::read_bytes(¬e_on)
let Ok(LiveEvent::ChannelVoice(channel_voice_msg)) = LiveEvent::from_bytes(¬e_on) else {
panic!("Expected a channel voice event");
};
let VoiceEvent::NoteOn { key, velocity } = channel_voice_msg.event() else {
panic!("Expected a note on event");
};
assert_eq!(channel_voice_msg.channel(), Channel::Three);
assert_eq!(key.note(), Note::C);
assert_eq!(key.octave(), Octave::new(4));
assert_eq!(velocity.byte(), 96);
Midix has been built with the bevy engine in mind. this feature uses rustysynth
to play midi sounds under the hood!
When running the examples, try using cargo run --example <EXAMPLE_NAME> --features example --release
for the best results!
use bevy_platform::prelude::*;
use std::time::Duration;
use bevy::{
log::{Level, LogPlugin},
prelude::*,
};
use midix::prelude::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(LogPlugin {
level: Level::INFO,
..default()
}),
MidiPlugin {
input: None,
..Default::default()
},
))
.add_systems(Startup, load_sf2)
.add_systems(Update, scale_me)
.run();
}
/// Take a look here for some soundfonts:
///
/// <https://sites.google.com/site/soundfonts4u/>
fn load_sf2(asset_server: Res<AssetServer>, mut synth: ResMut<Synth>) {
synth.use_soundfont(asset_server.load("soundfont.sf2"));
}
struct Scale {
timer: Timer,
current_key: Key,
note_on: bool,
forward: bool,
incremented_by: u8,
max_increment: u8,
}
impl Scale {
pub fn calculate_next_key(&mut self) {
if self.forward {
if self.incremented_by == self.max_increment {
self.forward = false;
self.incremented_by -= 1;
self.current_key -= 1;
} else {
self.incremented_by += 1;
self.current_key += 1;
}
} else if self.incremented_by == 0 {
self.forward = true;
self.incremented_by += 1;
self.current_key += 1;
} else {
self.incremented_by -= 1;
self.current_key -= 1;
}
}
}
impl Default for Scale {
fn default() -> Self {
let timer = Timer::new(Duration::from_millis(200), TimerMode::Repeating);
Scale {
timer,
current_key: Key::new(Note::C, Octave::new(2)),
note_on: true,
forward: true,
incremented_by: 0,
max_increment: 11,
}
}
}
fn scale_me(synth: Res<Synth>, time: Res<Time>, mut scale: Local<Scale>) {
// don't do anything until the soundfont has been loaded
if !synth.is_ready() {
return;
}
scale.timer.tick(time.delta());
if !scale.timer.just_finished() {
return;
}
if scale.note_on {
//play note on
_ = synth.push_event(ChannelVoiceMessage::new(
Channel::One,
VoiceEvent::note_on(scale.current_key, Velocity::MAX),
));
} else {
//turn off the note
_ = synth.push_event(ChannelVoiceMessage::new(
Channel::One,
VoiceEvent::note_off(scale.current_key, Velocity::MAX),
));
scale.calculate_next_key()
}
scale.note_on = !scale.note_on;
}
See /examples
for details.
midix
will adhere to semantic versioning. This means that I've opted to use major versions, even if this crate does not consider itself feature complete (you might get a midix v29.3.1
someday)
The current MSRV is rust 1.87
This crate was originally forked from bevy_midi
. Please check them out if this crate doesn't suit your needs!
no_std
- Streamer (midir ext)
- Interfaces between
MidiSource
andMidi
(some representable MIDI type, like a file, events, etc.) - MIDI writers
- Streamer (async timed stream event via midir)
- MidiFile
The SUPPORT.md file denotes the length of time major revisions are supported.
When the major version of the crate is incremented, new features for the previous version(s) will likely be neglected. If you need a non-breaking feature for an older version before the end of its maintenence period, please let me know!
no_std
- Streamer (midir ext)
- Interfaces between
MidiSource
andMidi
(some representable MIDI type, like a file, events, etc.) - MIDI writers
- Streamer (async timed stream event via midir)
- MidiFile
A lot of the documentation is copied directly from this documentation.
This reference states "This document may be freely copied in whole or in part provided the copy contains this Acknowledgement.":
This document was originally distributed in text format by The International MIDI Association.
© Copyright 1999 David Back.
EMail: [email protected]
Web: http://www.csw2.co.uk
Inspired by/copied from
inspired by midly
and quick-xml
.
If you are in need of a MIDI writer, I highly
recommend using midly
, as this midix
does not yet
support file writing.
Thanks to these mainters and contributors for inspiration!
Forked originally from bevy_midi
. Huge thank you for the examples and docs!
hidden synth
feature
Forked originally from rustysynth.