Skip to content

Commit

Permalink
Merge pull request #37 from masonium/midly-owned
Browse files Browse the repository at this point in the history
Midly owned
  • Loading branch information
BlackPhlox authored Nov 20, 2023
2 parents a96f7c3 + b8d9556 commit 4c682e5
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 89 deletions.
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
40 changes: 24 additions & 16 deletions examples/piano.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,24 +147,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);
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 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>();
}
}
} 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!(
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];
// 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);
// }
}
}
40 changes: 7 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,16 @@
/// Re-export [`midly::num`] module .
pub mod num {
pub use midly::num::{u14, u15, u24, u28, u4, u7};
}

pub mod input;
pub mod output;
pub mod types;

pub mod prelude {
pub use crate::{input::*, output::*, *};
pub use crate::{input::*, output::*, types::*, *};
}

pub const KEY_RANGE: [&str; 12] = [
"C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B",
];

const NOTE_ON_STATUS: u8 = 0b1001_0000;
const NOTE_OFF_STATUS: u8 = 0b1000_0000;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct MidiMessage {
pub msg: [u8; 3],
}

impl From<[u8; 3]> for MidiMessage {
fn from(msg: [u8; 3]) -> Self {
MidiMessage { msg }
}
}

impl MidiMessage {
#[must_use]
pub fn is_note_on(&self) -> bool {
(self.msg[0] & 0b1111_0000) == NOTE_ON_STATUS
}

#[must_use]
pub fn is_note_off(&self) -> bool {
(self.msg[0] & 0b1111_0000) == NOTE_OFF_STATUS
}

/// Get the channel of a message, assuming the message is not a system message.
#[must_use]
pub fn channel(&self) -> u8 {
self.msg[0] & 0b0000_1111
}
}
26 changes: 20 additions & 6 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use super::MidiMessage;
use bevy::prelude::*;
use bevy::tasks::IoTaskPool;
use crossbeam_channel::{Receiver, Sender};
Expand All @@ -8,6 +7,8 @@ use std::fmt::Display;
use std::{error::Error, future::Future};
use MidiOutputError::{ConnectionError, PortRefreshError, SendDisconnectedError, SendError};

use crate::types::OwnedLiveEvent;

pub struct MidiOutputPlugin;

impl Plugin for MidiOutputPlugin {
Expand Down Expand Up @@ -69,7 +70,7 @@ impl MidiOutput {
}

/// Send a midi message.
pub fn send(&self, msg: MidiMessage) {
pub fn send(&self, msg: OwnedLiveEvent) {
self.sender
.send(Message::Midi(msg))
.expect("Couldn't send MIDI message");
Expand Down Expand Up @@ -103,7 +104,7 @@ impl MidiOutputConnection {
pub enum MidiOutputError {
ConnectionError(ConnectErrorKind),
SendError(midir::SendError),
SendDisconnectedError(MidiMessage),
SendDisconnectedError(OwnedLiveEvent),
PortRefreshError,
}

Expand Down Expand Up @@ -168,6 +169,9 @@ fn reply(
warn!("{}", e);
err.send(e);
}
Reply::IoError(e) => {
warn!("{}", e);
}
Reply::Connected => {
conn.connected = true;
}
Expand All @@ -182,12 +186,13 @@ enum Message {
RefreshPorts,
ConnectToPort(MidiOutputPort),
DisconnectFromPort,
Midi(MidiMessage),
Midi(OwnedLiveEvent),
}

enum Reply {
AvailablePorts(Vec<(String, MidiOutputPort)>),
Error(MidiOutputError),
IoError(std::io::Error),
Connected,
Disconnected,
}
Expand Down Expand Up @@ -279,8 +284,17 @@ impl Future for MidiOutputTask {
},
Midi(message) => {
if let Some((conn, _)) = &mut self.connection {
if let Err(e) = conn.send(&message.msg) {
self.sender.send(Reply::Error(SendError(e))).unwrap();
let mut byte_msg = Vec::with_capacity(4);
let live: midly::live::LiveEvent = (&message).into();
match live.write_std(&mut byte_msg) {
Ok(_) => {
if let Err(e) = conn.send(&byte_msg) {
self.sender.send(Reply::Error(SendError(e))).unwrap();
}
}
Err(write_err) => {
self.sender.send(Reply::IoError(write_err)).unwrap();
}
}
} else {
self.sender
Expand Down
Loading

0 comments on commit 4c682e5

Please sign in to comment.