Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# 4.0.0 (unreleased)
## Features
- `MidiFile::into_events` returns an iterator of `Timed<LiveEvent>`
- `Micros` and `UMicros`: strongly typed microseconds
- `DurationExt` for `core::time::Duration`. Converts the duration into `UMicros`

## Breaking Changes
- `Note` -> `Key`, and `Key` -> `Note`
- `key!` -> `note!`
- All variants that contained a `key` field have been replaced with a `note` field


# 3.2.0
## `bevy_midix` (April 15, 2025)
- feat: WASM compatability with example!
Expand Down
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "midix"
version = "4.0.0-alpha.2"
version = "4.0.0-alpha.5"
authors = ["dsgallups <dsgallups@protonmail.com>"]
edition = "2024"
description = "MIDI structures designed for humans"
Expand All @@ -10,15 +10,16 @@ readme = "README.md"
keywords = ["midi", "audio", "parser", "bevy"]
categories = ["multimedia::audio", "multimedia::encoding", "multimedia"]
license = "MIT OR Apache-2.0"
exclude = ["assets/*"]
exclude = ["assets/*", "test-asset"]


[features]
default = ["std"]
std = ["thiserror/std"]
std = ["thiserror/std", "tracing?/std", "bevy?/std"]
web = ["bevy_platform/web"]
bevy = ["dep:bevy"]
bevy_asset = ["bevy/bevy_asset"]
tracing = ["dep:tracing"]
serde = ["dep:serde"]


Expand All @@ -34,6 +35,7 @@ bevy_platform = { version = "0.17.0-rc", optional = true, default-features = fal
"alloc",
] }
serde = {version = "1.0", features = ["derive"], optional = true}
tracing = { version = "0.1.41", default-features = false, optional = true }

[dev-dependencies]
pretty_assertions = { default-features = false, features = [
Expand Down
124 changes: 3 additions & 121 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,135 +71,17 @@ let Ok(LiveEvent::ChannelVoice(channel_voice_msg)) = LiveEvent::from_bytes(&note
panic!("Expected a channel voice event");
};

let VoiceEvent::NoteOn { key, velocity } = channel_voice_msg.event() else {
let VoiceEvent::NoteOn { note, 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!(note.key(), Key::C);
assert_eq!(note.octave(), Octave::new(4));
assert_eq!(velocity.byte(), 96);
```





## Bevy Support

Midix has been built with the bevy engine in mind. this feature uses `rustysynth` to play midi sounds under the hood!

### Note
When running the examples, try using `cargo run --example <EXAMPLE_NAME> --features example --release` for the best results!

### Example
```rust, no_run
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.


## Semantic Versioning and Support
`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)

Expand Down
6 changes: 3 additions & 3 deletions src/file/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
reader::{ReadError, ReaderErrorKind},
};

use super::ParsedMidiFile;
use super::MidiFile;

#[derive(Default)]
pub enum FormatStage<'a> {
Expand Down Expand Up @@ -112,15 +112,15 @@ impl<'a> MidiFileBuilder<'a> {
EOF => Err(ReaderErrorKind::ReadError(ReadError::OutOfBounds)),
}
}
pub fn build(self) -> Result<ParsedMidiFile<'a>, FileError> {
pub fn build(self) -> Result<MidiFile<'a>, FileError> {
let FormatStage::Formatted(format) = self.format else {
return Err(FileError::NoFormat);
};
let Some(timing) = self.timing else {
return Err(FileError::NoTiming);
};

Ok(ParsedMidiFile {
Ok(MidiFile {
format,
header: Header::new(timing),
})
Expand Down
1 change: 1 addition & 0 deletions src/file/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::prelude::*;
#[doc = r#"
Information about the timing of the MIDI file
"#]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "bevy", derive(bevy::reflect::Reflect))]
pub struct Header {
timing: Timing,
Expand Down
22 changes: 18 additions & 4 deletions src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,35 @@ pub use header::*;
mod track;
pub use track::*;

mod timed_event_iter;
pub use timed_event_iter::*;

use crate::{
ParseError,
events::LiveEvent,
message::Timed,
prelude::FormatType,
reader::{ReadResult, Reader, ReaderError, ReaderErrorKind},
};

#[doc = r#"
TODO
"#]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "bevy", derive(bevy::reflect::Reflect))]
pub struct ParsedMidiFile<'a> {
pub struct MidiFile<'a> {
header: Header,
format: Format<'a>,
}
#[cfg(feature = "bevy_asset")]
impl bevy::asset::Asset for ParsedMidiFile<'static> {}
impl bevy::asset::Asset for MidiFile<'static> {}

#[cfg(feature = "bevy_asset")]
impl bevy::asset::VisitAssetDependencies for ParsedMidiFile<'static> {
impl bevy::asset::VisitAssetDependencies for MidiFile<'static> {
fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy::asset::UntypedAssetId)) {}
}

impl<'a> ParsedMidiFile<'a> {
impl<'a> MidiFile<'a> {
/// Parse a set of bytes into a file struct
pub fn parse<B>(bytes: B) -> ReadResult<Self>
where
Expand Down Expand Up @@ -86,4 +92,12 @@ impl<'a> ParsedMidiFile<'a> {
Format::SingleMultiChannel(_) => FormatType::SingleMultiChannel,
}
}

/// Returns a set of timed events from the midi file.
pub fn into_events(self) -> impl Iterator<Item = Timed<LiveEvent<'a>>> {
match TimedEventIterator::new(self) {
Some(iter) => OptTimedEventIterator::Some(iter),
None => OptTimedEventIterator::None,
}
}
}
Loading
Loading