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

Integration with midly for full MIDI message parsing / support using "Owned" message variants #36

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ name = "bevy_midi"
[dependencies]
midir = "0.9"
crossbeam-channel = "0.5.8"
midly = { version = "0.5.3", default-features = false, features = ["std", "alloc"] }

[dev-dependencies]
bevy_egui = { version = "0.23", features = ["immutable_ctx"]}
Expand Down
12 changes: 1 addition & 11 deletions examples/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,7 @@ fn show_last_message(
) {
for data in midi_data.read() {
let text_section = &mut instructions.single_mut().sections[3];
text_section.value = format!(
"Last Message: {} - {:?}",
if data.message.is_note_on() {
"NoteOn"
} else if data.message.is_note_off() {
"NoteOff"
} else {
"Other"
},
data.message.msg
);
text_section.value = format!("Last Message: {:?}", data.message);
}
}

Expand Down
4 changes: 2 additions & 2 deletions examples/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ fn disconnect(input: Res<Input<KeyCode>>, output: Res<MidiOutput>) {
fn play_notes(input: Res<Input<KeyCode>>, output: Res<MidiOutput>) {
for (keycode, note) in &KEY_NOTE_MAP {
if input.just_pressed(*keycode) {
output.send([0b1001_0000, *note, 127].into()); // Note on, channel 1, max velocity
output.send(OwnedLiveEvent::note_on(0, *note, 127));
}
if input.just_released(*keycode) {
output.send([0b1000_0000, *note, 127].into()); // Note on, channel 1, max velocity
output.send(OwnedLiveEvent::note_off(0, *note, 127));
}
}
}
Expand Down
154 changes: 134 additions & 20 deletions examples/piano.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn main() {
.init_resource::<MidiInputSettings>()
.add_plugins(MidiOutputPlugin)
.init_resource::<MidiOutputSettings>()
.add_state::<ProjectionType>()
.add_systems(Startup, setup)
.add_systems(
Update,
Expand All @@ -31,11 +32,15 @@ fn main() {
connect_to_first_output_port,
display_press,
display_release,
swap_camera,
),
)
.run();
}

#[derive(Component)]
struct ProjectionStatus;

#[derive(Component, Debug)]
struct Key {
key_val: String,
Expand All @@ -59,12 +64,69 @@ fn setup(
..Default::default()
});

//Camera
//Perspective Camera
cmds.spawn((
Camera3dBundle {
transform: Transform::from_xyz(8., 5., mid).looking_at(Vec3::new(0., 0., mid), Vec3::Y),
camera: Camera{
is_active: false,
..Default::default()
},
..Default::default()
},
PersCamera
));

// Top-down camera
cmds.spawn((
Camera3dBundle {
transform: Transform::from_xyz(1., 2., mid).looking_at(Vec3::new(0., 0., mid), Vec3::Y),
projection: Projection::Orthographic(OrthographicProjection{
//scaling_mode: todo!(),
scale: 0.011,
//area: todo!(),
..Default::default()
}),
..Default::default()
},
OrthCamera
));

//UI
cmds.spawn((
TextBundle {
text: Text {
sections: vec![
TextSection::new(
"Projection:\n",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 30.0,
color: Color::WHITE,
},
),
TextSection::new(
"Orthographic\n",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 30.0,
color: Color::AQUAMARINE,
},
),
TextSection::new(
"Press T to switch camera",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 15.0,
color: Color::WHITE,
},
),
],
..Default::default()
},
..default()
},
ProjectionStatus,
));

let pos: Vec3 = Vec3::new(0., 0., 0.);
Expand Down Expand Up @@ -129,9 +191,53 @@ fn spawn_note(
));
}

fn display_press(mut query: Query<&mut Transform, With<PressedKey>>) {
for mut t in &mut query {
t.translation.y = -0.05;
#[derive(States, Default, PartialEq, Eq, Debug, Clone, Copy, Hash)]
enum ProjectionType {
#[default]
Orthographic,
Perspective,
}

#[derive(Component)]
struct OrthCamera;

#[derive(Component)]
struct PersCamera;

fn swap_camera(
keys: Res<Input<KeyCode>>,
proj: Res<State<ProjectionType>>,
mut proj_txt: Query<&mut Text, With<ProjectionStatus>>,
mut nxt_proj: ResMut<NextState<ProjectionType>>,
mut q_pers: Query<&mut Camera, (With<PersCamera>, Without<OrthCamera>)>,
mut q_orth: Query<&mut Camera, (With<OrthCamera>, Without<PersCamera>)>,
) {
if keys.just_pressed(KeyCode::T) {
match (&mut q_pers.get_single_mut(), &mut q_orth.get_single_mut()) {
(Ok(pers), Ok(orth)) => {
let text_section = &mut proj_txt.single_mut().sections[1];
nxt_proj.set(if *proj == ProjectionType::Orthographic {
orth.is_active = false;
pers.is_active = true;
text_section.value = "Perspective\n".to_string();
ProjectionType::Perspective
} else {
pers.is_active = false;
orth.is_active = true;
text_section.value = "Orthographic\n".to_string();
ProjectionType::Orthographic
});
}
_ => (),
}
}
}

fn display_press(mut query: Query<(&mut Transform, &Key), With<PressedKey>>) {
for (mut t, k) in &mut query {
if t.translation.y == k.y_reset {
t.translation.y += -0.05;
}
}
}

Expand All @@ -147,24 +253,32 @@ fn handle_midi_input(
query: Query<(Entity, &Key)>,
) {
for data in midi_events.read() {
let [_, index, _value] = data.message.msg;
let off = index % 12;
let oct = index.overflowing_div(12).0;
let key_str = KEY_RANGE.iter().nth(off.into()).unwrap();

if data.message.is_note_on() {
for (entity, key) in query.iter() {
if key.key_val.eq(&format!("{}{}", key_str, oct).to_string()) {
commands.entity(entity).insert(PressedKey);
}
}
} else if data.message.is_note_off() {
for (entity, key) in query.iter() {
if key.key_val.eq(&format!("{}{}", key_str, oct).to_string()) {
commands.entity(entity).remove::<PressedKey>();
match data.message {
OwnedLiveEvent::Midi {
message: MidiMessage::NoteOn { key, .. } | MidiMessage::NoteOff { key, .. },
..
} => {
let index: u8 = key.into();
let off = index % 12;
let oct = index.overflowing_div(12).0;
let key_str = KEY_RANGE.iter().nth(off.into()).unwrap();

if data.is_note_on() {
for (entity, key) in query.iter() {
if key.key_val.eq(&format!("{}{}", key_str, oct).to_string()) {
commands.entity(entity).insert(PressedKey);
}
}
} else if data.is_note_off() {
for (entity, key) in query.iter() {
if key.key_val.eq(&format!("{}{}", key_str, oct).to_string()) {
commands.entity(entity).remove::<PressedKey>();
}
}
} else {
}
}
} else {
_ => {}
}
}
}
Expand Down
76 changes: 55 additions & 21 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use super::{MidiMessage, KEY_RANGE};
use crate::types::OwnedLiveEvent;

use bevy::prelude::Plugin;
use bevy::{prelude::*, tasks::IoTaskPool};
use crossbeam_channel::{Receiver, Sender};
use midir::ConnectErrorKind; // XXX: do we expose this?
pub use midir::{Ignore, MidiInputPort};
use midly::stream::MidiStream;
use midly::MidiMessage;
use std::error::Error;
use std::fmt::Display;
use std::future::Future;
Expand Down Expand Up @@ -110,11 +113,35 @@ impl MidiInputConnection {
#[derive(Resource)]
pub struct MidiData {
pub stamp: u64,
pub message: MidiMessage,
pub message: OwnedLiveEvent,
}

impl bevy::prelude::Event for MidiData {}

impl MidiData {
/// Return `true` iff the underlying message represents a MIDI note on event.
pub fn is_note_on(&self) -> bool {
matches!(
self.message,
OwnedLiveEvent::Midi {
message: MidiMessage::NoteOn { .. },
..
}
)
}

/// Return `true` iff the underlying message represents a MIDI note off event.
pub fn is_note_off(&self) -> bool {
matches!(
BlackPhlox marked this conversation as resolved.
Show resolved Hide resolved
self.message,
OwnedLiveEvent::Midi {
message: MidiMessage::NoteOff { .. },
..
}
)
}
}

/// The [`Error`] type for midi input operations, accessible as an [`Event`](bevy::ecs::event::Event).
#[derive(Clone, Debug)]
pub enum MidiInputError {
Expand Down Expand Up @@ -240,14 +267,17 @@ impl Future for MidiInputTask {
.input
.take()
.unwrap_or_else(|| self.connection.take().unwrap().0.close().0);
let mut stream = MidiStream::new();
let conn = i.connect(
&port,
self.settings.port_name,
move |stamp, message, _| {
let _ = s.send(Reply::Midi(MidiData {
stamp,
message: [message[0], message[1], message[2]].into(),
}));
stream.feed(message, |live_event| {
let _ = s.send(Reply::Midi(MidiData {
stamp,
message: live_event.into(),
}));
});
},
(),
);
Expand Down Expand Up @@ -287,14 +317,17 @@ impl Future for MidiInputTask {
self.sender.send(get_available_ports(&i)).unwrap();

let s = self.sender.clone();
let mut stream = MidiStream::new();
let conn = i.connect(
&port,
self.settings.port_name,
move |stamp, message, _| {
let _ = s.send(Reply::Midi(MidiData {
stamp,
message: [message[0], message[1], message[2]].into(),
}));
stream.feed(message, |live_event| {
let _ = s.send(Reply::Midi(MidiData {
stamp,
message: live_event.into(),
}));
})
},
(),
);
Expand Down Expand Up @@ -343,16 +376,17 @@ fn get_available_ports(input: &midir::MidiInput) -> Reply {
// A system which debug prints note events
fn debug(mut midi: EventReader<MidiData>) {
for data in midi.read() {
let pitch = data.message.msg[1];
let octave = pitch / 12;
let key = KEY_RANGE[pitch as usize % 12];

if data.message.is_note_on() {
debug!("NoteOn: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
} else if data.message.is_note_off() {
debug!("NoteOff: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
} else {
debug!("Other: {:?}", data.message.msg);
}
debug!("{:?}", data.message);
// let pitch = data.message.msg[1];
BlackPhlox marked this conversation as resolved.
Show resolved Hide resolved
// let octave = pitch / 12;
// let key = KEY_RANGE[pitch as usize % 12];

// if data.message.is_note_on() {
// debug!("NoteOn: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
// } else if data.message.is_note_off() {
// debug!("NoteOff: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
// } else {
// debug!("Other: {:?}", data.message.msg);
// }
}
Comment on lines +380 to +390
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// let pitch = data.message.msg[1];
// let octave = pitch / 12;
// let key = KEY_RANGE[pitch as usize % 12];
// if data.message.is_note_on() {
// debug!("NoteOn: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
// } else if data.message.is_note_off() {
// debug!("NoteOff: {}{:?} - Raw: {:?}", key, octave, data.message.msg);
// } else {
// debug!("Other: {:?}", data.message.msg);
// }

}
Loading
Loading