Skip to content

Commit 1797e72

Browse files
authored
feat: Micros, timed even iter on MidiFile (#5)
* fix: add micros to prelude * fix: update docs * publish alpha.4 * add duration extension trait * feat: new timed event iter - Added tracing feature - Added the ability to turn a parsed midi file into an iterator of live events * tests * fix: bug * exclude test-asset * fix: add size hint to iterator * fix: derives on parsed file * rename parsemidifile * bump version * add changelog
1 parent 69bb02c commit 1797e72

14 files changed

Lines changed: 633 additions & 163 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# 4.0.0 (unreleased)
2+
## Features
3+
- `MidiFile::into_events` returns an iterator of `Timed<LiveEvent>`
4+
- `Micros` and `UMicros`: strongly typed microseconds
5+
- `DurationExt` for `core::time::Duration`. Converts the duration into `UMicros`
6+
7+
## Breaking Changes
8+
- `Note` -> `Key`, and `Key` -> `Note`
9+
- `key!` -> `note!`
10+
- All variants that contained a `key` field have been replaced with a `note` field
11+
12+
113
# 3.2.0
214
## `bevy_midix` (April 15, 2025)
315
- feat: WASM compatability with example!

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "midix"
3-
version = "4.0.0-alpha.2"
3+
version = "4.0.0-alpha.5"
44
authors = ["dsgallups <dsgallups@protonmail.com>"]
55
edition = "2024"
66
description = "MIDI structures designed for humans"
@@ -10,15 +10,16 @@ readme = "README.md"
1010
keywords = ["midi", "audio", "parser", "bevy"]
1111
categories = ["multimedia::audio", "multimedia::encoding", "multimedia"]
1212
license = "MIT OR Apache-2.0"
13-
exclude = ["assets/*"]
13+
exclude = ["assets/*", "test-asset"]
1414

1515

1616
[features]
1717
default = ["std"]
18-
std = ["thiserror/std"]
18+
std = ["thiserror/std", "tracing?/std", "bevy?/std"]
1919
web = ["bevy_platform/web"]
2020
bevy = ["dep:bevy"]
2121
bevy_asset = ["bevy/bevy_asset"]
22+
tracing = ["dep:tracing"]
2223
serde = ["dep:serde"]
2324

2425

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

3840
[dev-dependencies]
3941
pretty_assertions = { default-features = false, features = [

README.md

Lines changed: 3 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -71,135 +71,17 @@ let Ok(LiveEvent::ChannelVoice(channel_voice_msg)) = LiveEvent::from_bytes(&note
7171
panic!("Expected a channel voice event");
7272
};
7373

74-
let VoiceEvent::NoteOn { key, velocity } = channel_voice_msg.event() else {
74+
let VoiceEvent::NoteOn { note, velocity } = channel_voice_msg.event() else {
7575
panic!("Expected a note on event");
7676
};
7777

7878
assert_eq!(channel_voice_msg.channel(), Channel::Three);
79-
assert_eq!(key.note(), Note::C);
80-
assert_eq!(key.octave(), Octave::new(4));
79+
assert_eq!(note.key(), Key::C);
80+
assert_eq!(note.octave(), Octave::new(4));
8181
assert_eq!(velocity.byte(), 96);
8282
```
8383

8484

85-
86-
87-
88-
## Bevy Support
89-
90-
Midix has been built with the bevy engine in mind. this feature uses `rustysynth` to play midi sounds under the hood!
91-
92-
### Note
93-
When running the examples, try using `cargo run --example <EXAMPLE_NAME> --features example --release` for the best results!
94-
95-
### Example
96-
```rust, no_run
97-
use bevy_platform::prelude::*;
98-
use std::time::Duration;
99-
use bevy::{
100-
log::{Level, LogPlugin},
101-
prelude::*,
102-
};
103-
use midix::prelude::*;
104-
fn main() {
105-
App::new()
106-
.add_plugins((
107-
DefaultPlugins.set(LogPlugin {
108-
level: Level::INFO,
109-
..default()
110-
}),
111-
MidiPlugin {
112-
input: None,
113-
..Default::default()
114-
},
115-
))
116-
.add_systems(Startup, load_sf2)
117-
.add_systems(Update, scale_me)
118-
.run();
119-
}
120-
/// Take a look here for some soundfonts:
121-
///
122-
/// <https://sites.google.com/site/soundfonts4u/>
123-
fn load_sf2(asset_server: Res<AssetServer>, mut synth: ResMut<Synth>) {
124-
synth.use_soundfont(asset_server.load("soundfont.sf2"));
125-
}
126-
127-
struct Scale {
128-
timer: Timer,
129-
current_key: Key,
130-
note_on: bool,
131-
forward: bool,
132-
incremented_by: u8,
133-
max_increment: u8,
134-
}
135-
136-
impl Scale {
137-
pub fn calculate_next_key(&mut self) {
138-
if self.forward {
139-
if self.incremented_by == self.max_increment {
140-
self.forward = false;
141-
self.incremented_by -= 1;
142-
self.current_key -= 1;
143-
} else {
144-
self.incremented_by += 1;
145-
self.current_key += 1;
146-
}
147-
} else if self.incremented_by == 0 {
148-
self.forward = true;
149-
self.incremented_by += 1;
150-
self.current_key += 1;
151-
} else {
152-
self.incremented_by -= 1;
153-
self.current_key -= 1;
154-
}
155-
}
156-
}
157-
158-
impl Default for Scale {
159-
fn default() -> Self {
160-
let timer = Timer::new(Duration::from_millis(200), TimerMode::Repeating);
161-
Scale {
162-
timer,
163-
current_key: Key::new(Note::C, Octave::new(2)),
164-
note_on: true,
165-
forward: true,
166-
incremented_by: 0,
167-
max_increment: 11,
168-
}
169-
}
170-
}
171-
172-
fn scale_me(synth: Res<Synth>, time: Res<Time>, mut scale: Local<Scale>) {
173-
// don't do anything until the soundfont has been loaded
174-
if !synth.is_ready() {
175-
return;
176-
}
177-
scale.timer.tick(time.delta());
178-
if !scale.timer.just_finished() {
179-
return;
180-
}
181-
if scale.note_on {
182-
//play note on
183-
_ = synth.push_event(ChannelVoiceMessage::new(
184-
Channel::One,
185-
VoiceEvent::note_on(scale.current_key, Velocity::MAX),
186-
));
187-
} else {
188-
//turn off the note
189-
_ = synth.push_event(ChannelVoiceMessage::new(
190-
Channel::One,
191-
VoiceEvent::note_off(scale.current_key, Velocity::MAX),
192-
));
193-
scale.calculate_next_key()
194-
}
195-
196-
scale.note_on = !scale.note_on;
197-
}
198-
```
199-
200-
See `/examples` for details.
201-
202-
20385
## Semantic Versioning and Support
20486
`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)
20587

src/file/builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
reader::{ReadError, ReaderErrorKind},
66
};
77

8-
use super::ParsedMidiFile;
8+
use super::MidiFile;
99

1010
#[derive(Default)]
1111
pub enum FormatStage<'a> {
@@ -112,15 +112,15 @@ impl<'a> MidiFileBuilder<'a> {
112112
EOF => Err(ReaderErrorKind::ReadError(ReadError::OutOfBounds)),
113113
}
114114
}
115-
pub fn build(self) -> Result<ParsedMidiFile<'a>, FileError> {
115+
pub fn build(self) -> Result<MidiFile<'a>, FileError> {
116116
let FormatStage::Formatted(format) = self.format else {
117117
return Err(FileError::NoFormat);
118118
};
119119
let Some(timing) = self.timing else {
120120
return Err(FileError::NoTiming);
121121
};
122122

123-
Ok(ParsedMidiFile {
123+
Ok(MidiFile {
124124
format,
125125
header: Header::new(timing),
126126
})

src/file/header.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::prelude::*;
33
#[doc = r#"
44
Information about the timing of the MIDI file
55
"#]
6+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67
#[cfg_attr(feature = "bevy", derive(bevy::reflect::Reflect))]
78
pub struct Header {
89
timing: Timing,

src/file/mod.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,35 @@ pub use header::*;
1515
mod track;
1616
pub use track::*;
1717

18+
mod timed_event_iter;
19+
pub use timed_event_iter::*;
20+
1821
use crate::{
1922
ParseError,
23+
events::LiveEvent,
24+
message::Timed,
2025
prelude::FormatType,
2126
reader::{ReadResult, Reader, ReaderError, ReaderErrorKind},
2227
};
2328

2429
#[doc = r#"
2530
TODO
2631
"#]
32+
#[derive(Clone, Debug, PartialEq)]
2733
#[cfg_attr(feature = "bevy", derive(bevy::reflect::Reflect))]
28-
pub struct ParsedMidiFile<'a> {
34+
pub struct MidiFile<'a> {
2935
header: Header,
3036
format: Format<'a>,
3137
}
3238
#[cfg(feature = "bevy_asset")]
33-
impl bevy::asset::Asset for ParsedMidiFile<'static> {}
39+
impl bevy::asset::Asset for MidiFile<'static> {}
3440

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

40-
impl<'a> ParsedMidiFile<'a> {
46+
impl<'a> MidiFile<'a> {
4147
/// Parse a set of bytes into a file struct
4248
pub fn parse<B>(bytes: B) -> ReadResult<Self>
4349
where
@@ -86,4 +92,12 @@ impl<'a> ParsedMidiFile<'a> {
8692
Format::SingleMultiChannel(_) => FormatType::SingleMultiChannel,
8793
}
8894
}
95+
96+
/// Returns a set of timed events from the midi file.
97+
pub fn into_events(self) -> impl Iterator<Item = Timed<LiveEvent<'a>>> {
98+
match TimedEventIterator::new(self) {
99+
Some(iter) => OptTimedEventIterator::Some(iter),
100+
None => OptTimedEventIterator::None,
101+
}
102+
}
89103
}

0 commit comments

Comments
 (0)