From 97cc0e034b3bc7d2291123ae708f20cad3256bf1 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Thu, 16 Nov 2023 22:39:13 -0800 Subject: [PATCH 1/9] use midly::live::LiveEvent as MidiMessage --- Cargo.toml | 1 + src/input.rs | 46 ++++++++++++++++++++++++++-------------------- src/lib.rs | 34 +++------------------------------- src/output.rs | 21 +++++++++++++++++---- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17910df..ed326fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"]} diff --git a/src/input.rs b/src/input.rs index cb10c2b..7deb053 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,11 @@ -use super::{MidiMessage, KEY_RANGE}; +use crate::MidiMessage; + 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 std::error::Error; use std::fmt::Display; use std::future::Future; @@ -240,14 +242,14 @@ 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.to_static() })); + }); }, (), ); @@ -287,14 +289,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, |event| { + let _ = s.send(Reply::Midi(MidiData { + stamp, + message: event.to_static() + })); + }) }, (), ); @@ -343,16 +348,17 @@ fn get_available_ports(input: &midir::MidiInput) -> Reply { // A system which debug prints note events fn debug(mut midi: EventReader) { 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); + // } } } diff --git a/src/lib.rs b/src/lib.rs index 16076bd..53ef867 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use midly::live::LiveEvent; + pub mod input; pub mod output; @@ -9,34 +11,4 @@ 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 - } -} +pub type MidiMessage = LiveEvent<'static>; diff --git a/src/output.rs b/src/output.rs index 3d49568..2293866 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,4 +1,3 @@ -use super::MidiMessage; use bevy::prelude::*; use bevy::tasks::IoTaskPool; use crossbeam_channel::{Receiver, Sender}; @@ -8,6 +7,8 @@ use std::fmt::Display; use std::{error::Error, future::Future}; use MidiOutputError::{ConnectionError, PortRefreshError, SendDisconnectedError, SendError}; +use crate::MidiMessage; + pub struct MidiOutputPlugin; impl Plugin for MidiOutputPlugin { @@ -168,6 +169,9 @@ fn reply( warn!("{}", e); err.send(e); } + Reply::IoError(e) => { + warn!("{}", e); + } Reply::Connected => { conn.connected = true; } @@ -188,6 +192,7 @@ enum Message { enum Reply { AvailablePorts(Vec<(String, MidiOutputPort)>), Error(MidiOutputError), + IoError(std::io::Error), Connected, Disconnected, } @@ -279,9 +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); + match message.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 .send(Reply::Error(SendDisconnectedError(message))) From 127d68126a342319b7d9b9669e2de9483fefdf83 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Thu, 16 Nov 2023 23:13:27 -0800 Subject: [PATCH 2/9] re-export used midly items, fix piano example --- examples/input.rs | 23 ++++++++++++----------- examples/output.rs | 12 ++++++++++-- examples/piano.rs | 43 ++++++++++++++++++++++++------------------- src/input.rs | 40 +++++++++++++++++++++++++++++++--------- src/lib.rs | 10 ++++++++-- src/output.rs | 32 ++++++++++++++++---------------- 6 files changed, 101 insertions(+), 59 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index a25caf4..1d98953 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -103,17 +103,18 @@ 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 - ); + let event_str = match data.message { + LiveEvent::Midi { channel, message } => { + format!("Channel {channel} - {message:?}") + } + LiveEvent::Common(sc) => { + format!("{:?}", sc) + } + LiveEvent::Realtime(rt) => { + format!("{:?}", rt) + } + }; + text_section.value = format!("Last Message: {:?}", event_str); } } diff --git a/examples/output.rs b/examples/output.rs index 433c837..0e5e0bc 100644 --- a/examples/output.rs +++ b/examples/output.rs @@ -70,11 +70,19 @@ fn disconnect(input: Res>, output: Res) { fn play_notes(input: Res>, output: Res) { for (keycode, note) in &KEY_NOTE_MAP { + let key: num::u7 = (*note).into(); + let vel: num::u7 = 127.into(); if input.just_pressed(*keycode) { - output.send([0b1001_0000, *note, 127].into()); // Note on, channel 1, max velocity + output.send(LiveEvent::Midi { + channel: 0.into(), + message: midly::MidiMessage::NoteOn { key, vel }, + }); // Note on, channel 1, max velocity } if input.just_released(*keycode) { - output.send([0b1000_0000, *note, 127].into()); // Note on, channel 1, max velocity + output.send(LiveEvent::Midi { + channel: 0.into(), + message: midly::MidiMessage::NoteOff { key, vel }, + }); // Note on, channel 1, max velocity } } } diff --git a/examples/piano.rs b/examples/piano.rs index 84c8dc0..f215433 100644 --- a/examples/piano.rs +++ b/examples/piano.rs @@ -147,25 +147,30 @@ 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::(); - } - } - } else { - } + match data.message { + OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { 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::(); + } + } + } else { + } + }, + _ => {} + } } } diff --git a/src/input.rs b/src/input.rs index 7deb053..4234698 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,10 +1,11 @@ -use crate::MidiMessage; +use crate::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::MidiMessage; use midly::stream::MidiStream; use std::error::Error; use std::fmt::Display; @@ -112,11 +113,29 @@ 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 { + match self.message { + OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { .. }, .. } => true, + _ => false + } + } + + /// Return `true` iff the underlying message represents a MIDI note off event. + pub fn is_note_off(&self) -> bool { + match self.message { + OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { .. }, .. } => true, + _ => false + } + } +} + /// The [`Error`] type for midi input operations, accessible as an [`Event`](bevy::ecs::event::Event). #[derive(Clone, Debug)] pub enum MidiInputError { @@ -248,7 +267,10 @@ impl Future for MidiInputTask { self.settings.port_name, move |stamp, message, _| { stream.feed(message, |live_event| { - let _ = s.send(Reply::Midi(MidiData { stamp, message: live_event.to_static() })); + let _ = s.send(Reply::Midi(MidiData { + stamp, + message: live_event.to_static(), + })); }); }, (), @@ -289,17 +311,17 @@ impl Future for MidiInputTask { self.sender.send(get_available_ports(&i)).unwrap(); let s = self.sender.clone(); - let mut stream = MidiStream::new(); + let mut stream = MidiStream::new(); let conn = i.connect( &port, self.settings.port_name, move |stamp, message, _| { - stream.feed(message, |event| { + stream.feed(message, |event| { let _ = s.send(Reply::Midi(MidiData { - stamp, - message: event.to_static() + stamp, + message: event.to_static(), })); - }) + }) }, (), ); @@ -348,7 +370,7 @@ fn get_available_ports(input: &midir::MidiInput) -> Reply { // A system which debug prints note events fn debug(mut midi: EventReader) { for data in midi.read() { - debug!("{:?}", data.message); + debug!("{:?}", data.message); // let pitch = data.message.msg[1]; // let octave = pitch / 12; // let key = KEY_RANGE[pitch as usize % 12]; diff --git a/src/lib.rs b/src/lib.rs index 53ef867..3940f51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,10 @@ -use midly::live::LiveEvent; +pub use midly::live::{LiveEvent, SystemCommon, SystemRealtime}; +pub use midly::MidiMessage; + +/// Re-export [`midly::num`] module. +pub mod num { + pub use midly::num::{u14, u15, u24, u28, u4, u7}; +} pub mod input; pub mod output; @@ -11,4 +17,4 @@ pub const KEY_RANGE: [&str; 12] = [ "C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B", ]; -pub type MidiMessage = LiveEvent<'static>; +pub type OwnedLiveEvent = LiveEvent<'static>; diff --git a/src/output.rs b/src/output.rs index 2293866..6aa5904 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,7 +7,7 @@ use std::fmt::Display; use std::{error::Error, future::Future}; use MidiOutputError::{ConnectionError, PortRefreshError, SendDisconnectedError, SendError}; -use crate::MidiMessage; +use crate::OwnedLiveEvent; pub struct MidiOutputPlugin; @@ -70,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"); @@ -104,7 +104,7 @@ impl MidiOutputConnection { pub enum MidiOutputError { ConnectionError(ConnectErrorKind), SendError(midir::SendError), - SendDisconnectedError(MidiMessage), + SendDisconnectedError(OwnedLiveEvent), PortRefreshError, } @@ -169,9 +169,9 @@ fn reply( warn!("{}", e); err.send(e); } - Reply::IoError(e) => { - warn!("{}", e); - } + Reply::IoError(e) => { + warn!("{}", e); + } Reply::Connected => { conn.connected = true; } @@ -186,7 +186,7 @@ enum Message { RefreshPorts, ConnectToPort(MidiOutputPort), DisconnectFromPort, - Midi(MidiMessage), + Midi(OwnedLiveEvent), } enum Reply { @@ -286,15 +286,15 @@ impl Future for MidiOutputTask { if let Some((conn, _)) = &mut self.connection { let mut byte_msg = Vec::with_capacity(4); match message.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(); - } - } + 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 .send(Reply::Error(SendDisconnectedError(message))) From e5b46df71519fb87d544c8716971f23d803d0571 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sat, 18 Nov 2023 13:42:23 -0800 Subject: [PATCH 3/9] create OwnedLiveEvent for fully-owned data --- examples/input.rs | 8 +-- examples/output.rs | 11 +--- examples/piano.rs | 51 +++++++++--------- src/input.rs | 30 ++++++----- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++++++-- src/output.rs | 3 +- 6 files changed, 177 insertions(+), 53 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index 1d98953..fc631a9 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -103,14 +103,14 @@ fn show_last_message( ) { for data in midi_data.read() { let text_section = &mut instructions.single_mut().sections[3]; - let event_str = match data.message { - LiveEvent::Midi { channel, message } => { + let event_str = match &data.message { + OwnedLiveEvent::Midi { channel, message } => { format!("Channel {channel} - {message:?}") } - LiveEvent::Common(sc) => { + OwnedLiveEvent::Common(sc) => { format!("{:?}", sc) } - LiveEvent::Realtime(rt) => { + OwnedLiveEvent::Realtime(rt) => { format!("{:?}", rt) } }; diff --git a/examples/output.rs b/examples/output.rs index 0e5e0bc..876380f 100644 --- a/examples/output.rs +++ b/examples/output.rs @@ -71,18 +71,11 @@ fn disconnect(input: Res>, output: Res) { fn play_notes(input: Res>, output: Res) { for (keycode, note) in &KEY_NOTE_MAP { let key: num::u7 = (*note).into(); - let vel: num::u7 = 127.into(); if input.just_pressed(*keycode) { - output.send(LiveEvent::Midi { - channel: 0.into(), - message: midly::MidiMessage::NoteOn { key, vel }, - }); // Note on, channel 1, max velocity + output.send(OwnedLiveEvent::note_on(0, key, 127)); } if input.just_released(*keycode) { - output.send(LiveEvent::Midi { - channel: 0.into(), - message: midly::MidiMessage::NoteOff { key, vel }, - }); // Note on, channel 1, max velocity + output.send(OwnedLiveEvent::note_off(0, key, 127)); } } } diff --git a/examples/piano.rs b/examples/piano.rs index f215433..ee4688f 100644 --- a/examples/piano.rs +++ b/examples/piano.rs @@ -147,30 +147,33 @@ fn handle_midi_input( query: Query<(Entity, &Key)>, ) { for data in midi_events.read() { - match data.message { - OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { 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::(); - } - } - } else { - } - }, - _ => {} - } + match data.message { + OwnedLiveEvent::Midi { + message: MidiMessage::NoteOn { 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::(); + } + } + } else { + } + } + _ => {} + } } } diff --git a/src/input.rs b/src/input.rs index 4234698..4a892f8 100644 --- a/src/input.rs +++ b/src/input.rs @@ -5,8 +5,8 @@ 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::MidiMessage; use midly::stream::MidiStream; +use midly::MidiMessage; use std::error::Error; use std::fmt::Display; use std::future::Future; @@ -121,18 +121,24 @@ 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 { - match self.message { - OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { .. }, .. } => true, - _ => false - } + match self.message { + OwnedLiveEvent::Midi { + message: MidiMessage::NoteOn { .. }, + .. + } => true, + _ => false, + } } /// Return `true` iff the underlying message represents a MIDI note off event. pub fn is_note_off(&self) -> bool { - match self.message { - OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { .. }, .. } => true, - _ => false - } + match self.message { + OwnedLiveEvent::Midi { + message: MidiMessage::NoteOn { .. }, + .. + } => true, + _ => false, + } } } @@ -269,7 +275,7 @@ impl Future for MidiInputTask { stream.feed(message, |live_event| { let _ = s.send(Reply::Midi(MidiData { stamp, - message: live_event.to_static(), + message: live_event.into(), })); }); }, @@ -316,10 +322,10 @@ impl Future for MidiInputTask { &port, self.settings.port_name, move |stamp, message, _| { - stream.feed(message, |event| { + stream.feed(message, |live_event| { let _ = s.send(Reply::Midi(MidiData { stamp, - message: event.to_static(), + message: live_event.into(), })); }) }, diff --git a/src/lib.rs b/src/lib.rs index 3940f51..40c881f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ -pub use midly::live::{LiveEvent, SystemCommon, SystemRealtime}; -pub use midly::MidiMessage; +use midly::live::{LiveEvent, SystemCommon}; +pub use midly::{ + live::{MtcQuarterFrameMessage, SystemRealtime}, + MidiMessage, +}; /// Re-export [`midly::num`] module. pub mod num { @@ -17,4 +20,122 @@ pub const KEY_RANGE: [&str; 12] = [ "C", "C#/Db", "D", "D#/Eb", "E", "F", "F#/Gb", "G", "G#/Ab", "A", "A#/Bb", "B", ]; -pub type OwnedLiveEvent = LiveEvent<'static>; +/// Owned version of a [`midly::live::LiveEvent`]. +/// +/// Standard [`midly::live::LiveEvent`]s have a lifetime parameter limiting them to the scope in +/// which they are generated to avoid any copying. However, because we are sending these messages +/// through the bevy event system, they need to outlive this original scope. +/// +/// Creating [`OwnedLiveEvent`]s only allocates when the message is a an [`OwnedSystemCommon`] that +/// itself contains an allocation. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum OwnedLiveEvent { + /// A midi message with a channel and music data. + Midi { + channel: num::u4, + message: midly::MidiMessage, + }, + + /// A System Common message with owned data. + Common(OwnedSystemCommon), + + /// A one-byte System Realtime Message. + Realtime(SystemRealtime), +} + +/// Owned version of [`midly::live::SystemCommon`]. +/// +/// [`OwnedSystemCommon`] fully owns any underlying value data, including +/// [`OwnedSystemCommon::SysEx`] messages. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum OwnedSystemCommon { + /// A system-exclusive event. + /// + /// Only contains the data bytes; does not inclde the `0xF0` and `0xF6` begin/end marker bytes. + /// slice does not include either: it only includes data bytes in the `0x00..=0x7F` range. + SysEx(Vec), + /// A MIDI Time Code Quarter Frame message, carrying a tag type and a 4-bit tag value. + MidiTimeCodeQuarterFrame(MtcQuarterFrameMessage, num::u4), + /// The number of MIDI beats (6 x MIDI clocks) that have elapsed since the start of the + /// sequence. + SongPosition(num::u14), + /// Select a given song index. + SongSelect(num::u7), + /// Request the device to tune itself. + TuneRequest, + /// An undefined System Common message, with arbitrary data bytes. + Undefined(u8, Vec), +} + +impl OwnedLiveEvent { + /// Returns a [`MidiMessage::NoteOn`] event. + pub fn note_on, K: Into, V: Into>( + channel: C, + key: K, + vel: V, + ) -> OwnedLiveEvent { + OwnedLiveEvent::Midi { + channel: channel.into(), + message: midly::MidiMessage::NoteOn { + key: key.into(), + vel: vel.into(), + }, + } + } + + /// Returns a [`MidiMessage::NoteOff`] event. + pub fn note_off, K: Into, V: Into>( + channel: C, + key: K, + vel: V, + ) -> OwnedLiveEvent { + OwnedLiveEvent::Midi { + channel: channel.into(), + message: midly::MidiMessage::NoteOff { + key: key.into(), + vel: vel.into(), + }, + } + } +} + +impl<'a> From> for OwnedLiveEvent { + fn from(value: LiveEvent) -> Self { + match value { + LiveEvent::Midi { channel, message } => OwnedLiveEvent::Midi { channel, message }, + LiveEvent::Realtime(rt) => OwnedLiveEvent::Realtime(rt), + LiveEvent::Common(sc) => OwnedLiveEvent::Common(match sc { + SystemCommon::MidiTimeCodeQuarterFrame(m, v) => { + OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) + } + SystemCommon::SongPosition(pos) => OwnedSystemCommon::SongPosition(pos), + SystemCommon::SongSelect(ss) => OwnedSystemCommon::SongSelect(ss), + SystemCommon::TuneRequest => OwnedSystemCommon::TuneRequest, + SystemCommon::SysEx(b) => OwnedSystemCommon::SysEx(b.to_vec()), + SystemCommon::Undefined(tag, b) => OwnedSystemCommon::Undefined(tag, b.to_vec()), + }), + } + } +} + +impl<'a, 'b: 'a> From<&'b OwnedLiveEvent> for LiveEvent<'a> { + fn from(value: &'b OwnedLiveEvent) -> Self { + match value { + OwnedLiveEvent::Midi { channel, message } => LiveEvent::Midi { + channel: *channel, + message: *message, + }, + OwnedLiveEvent::Realtime(rt) => LiveEvent::Realtime(*rt), + OwnedLiveEvent::Common(sc) => LiveEvent::Common(match sc { + OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) => { + SystemCommon::MidiTimeCodeQuarterFrame(*m, *v) + } + OwnedSystemCommon::SongPosition(pos) => SystemCommon::SongPosition(*pos), + OwnedSystemCommon::SongSelect(ss) => SystemCommon::SongSelect(*ss), + OwnedSystemCommon::TuneRequest => SystemCommon::TuneRequest, + OwnedSystemCommon::SysEx(b) => SystemCommon::SysEx(&b), + OwnedSystemCommon::Undefined(tag, b) => SystemCommon::Undefined(*tag, &b), + }), + } + } +} diff --git a/src/output.rs b/src/output.rs index 6aa5904..527428b 100644 --- a/src/output.rs +++ b/src/output.rs @@ -285,7 +285,8 @@ impl Future for MidiOutputTask { Midi(message) => { if let Some((conn, _)) = &mut self.connection { let mut byte_msg = Vec::with_capacity(4); - match message.write_std(&mut byte_msg) { + 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(); From a1af62dd4222a97b9c7f7e9d8f755925e473f1bd Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sat, 18 Nov 2023 13:49:56 -0800 Subject: [PATCH 4/9] move OwnedLiveEvent, etc. to types.rs --- examples/output.rs | 5 +- src/input.rs | 2 +- src/lib.rs | 131 ++------------------------------------------- src/output.rs | 2 +- src/types.rs | 128 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 133 deletions(-) create mode 100644 src/types.rs diff --git a/examples/output.rs b/examples/output.rs index 876380f..ead028e 100644 --- a/examples/output.rs +++ b/examples/output.rs @@ -70,12 +70,11 @@ fn disconnect(input: Res>, output: Res) { fn play_notes(input: Res>, output: Res) { for (keycode, note) in &KEY_NOTE_MAP { - let key: num::u7 = (*note).into(); if input.just_pressed(*keycode) { - output.send(OwnedLiveEvent::note_on(0, key, 127)); + output.send(OwnedLiveEvent::note_on(0, *note, 127)); } if input.just_released(*keycode) { - output.send(OwnedLiveEvent::note_off(0, key, 127)); + output.send(OwnedLiveEvent::note_off(0, *note, 127)); } } } diff --git a/src/input.rs b/src/input.rs index 4a892f8..639c97b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,4 +1,4 @@ -use crate::OwnedLiveEvent; +use crate::types::OwnedLiveEvent; use bevy::prelude::Plugin; use bevy::{prelude::*, tasks::IoTaskPool}; diff --git a/src/lib.rs b/src/lib.rs index 40c881f..aafad51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,141 +1,16 @@ -use midly::live::{LiveEvent, SystemCommon}; -pub use midly::{ - live::{MtcQuarterFrameMessage, SystemRealtime}, - MidiMessage, -}; - -/// Re-export [`midly::num`] module. +/// 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", ]; - -/// Owned version of a [`midly::live::LiveEvent`]. -/// -/// Standard [`midly::live::LiveEvent`]s have a lifetime parameter limiting them to the scope in -/// which they are generated to avoid any copying. However, because we are sending these messages -/// through the bevy event system, they need to outlive this original scope. -/// -/// Creating [`OwnedLiveEvent`]s only allocates when the message is a an [`OwnedSystemCommon`] that -/// itself contains an allocation. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum OwnedLiveEvent { - /// A midi message with a channel and music data. - Midi { - channel: num::u4, - message: midly::MidiMessage, - }, - - /// A System Common message with owned data. - Common(OwnedSystemCommon), - - /// A one-byte System Realtime Message. - Realtime(SystemRealtime), -} - -/// Owned version of [`midly::live::SystemCommon`]. -/// -/// [`OwnedSystemCommon`] fully owns any underlying value data, including -/// [`OwnedSystemCommon::SysEx`] messages. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum OwnedSystemCommon { - /// A system-exclusive event. - /// - /// Only contains the data bytes; does not inclde the `0xF0` and `0xF6` begin/end marker bytes. - /// slice does not include either: it only includes data bytes in the `0x00..=0x7F` range. - SysEx(Vec), - /// A MIDI Time Code Quarter Frame message, carrying a tag type and a 4-bit tag value. - MidiTimeCodeQuarterFrame(MtcQuarterFrameMessage, num::u4), - /// The number of MIDI beats (6 x MIDI clocks) that have elapsed since the start of the - /// sequence. - SongPosition(num::u14), - /// Select a given song index. - SongSelect(num::u7), - /// Request the device to tune itself. - TuneRequest, - /// An undefined System Common message, with arbitrary data bytes. - Undefined(u8, Vec), -} - -impl OwnedLiveEvent { - /// Returns a [`MidiMessage::NoteOn`] event. - pub fn note_on, K: Into, V: Into>( - channel: C, - key: K, - vel: V, - ) -> OwnedLiveEvent { - OwnedLiveEvent::Midi { - channel: channel.into(), - message: midly::MidiMessage::NoteOn { - key: key.into(), - vel: vel.into(), - }, - } - } - - /// Returns a [`MidiMessage::NoteOff`] event. - pub fn note_off, K: Into, V: Into>( - channel: C, - key: K, - vel: V, - ) -> OwnedLiveEvent { - OwnedLiveEvent::Midi { - channel: channel.into(), - message: midly::MidiMessage::NoteOff { - key: key.into(), - vel: vel.into(), - }, - } - } -} - -impl<'a> From> for OwnedLiveEvent { - fn from(value: LiveEvent) -> Self { - match value { - LiveEvent::Midi { channel, message } => OwnedLiveEvent::Midi { channel, message }, - LiveEvent::Realtime(rt) => OwnedLiveEvent::Realtime(rt), - LiveEvent::Common(sc) => OwnedLiveEvent::Common(match sc { - SystemCommon::MidiTimeCodeQuarterFrame(m, v) => { - OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) - } - SystemCommon::SongPosition(pos) => OwnedSystemCommon::SongPosition(pos), - SystemCommon::SongSelect(ss) => OwnedSystemCommon::SongSelect(ss), - SystemCommon::TuneRequest => OwnedSystemCommon::TuneRequest, - SystemCommon::SysEx(b) => OwnedSystemCommon::SysEx(b.to_vec()), - SystemCommon::Undefined(tag, b) => OwnedSystemCommon::Undefined(tag, b.to_vec()), - }), - } - } -} - -impl<'a, 'b: 'a> From<&'b OwnedLiveEvent> for LiveEvent<'a> { - fn from(value: &'b OwnedLiveEvent) -> Self { - match value { - OwnedLiveEvent::Midi { channel, message } => LiveEvent::Midi { - channel: *channel, - message: *message, - }, - OwnedLiveEvent::Realtime(rt) => LiveEvent::Realtime(*rt), - OwnedLiveEvent::Common(sc) => LiveEvent::Common(match sc { - OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) => { - SystemCommon::MidiTimeCodeQuarterFrame(*m, *v) - } - OwnedSystemCommon::SongPosition(pos) => SystemCommon::SongPosition(*pos), - OwnedSystemCommon::SongSelect(ss) => SystemCommon::SongSelect(*ss), - OwnedSystemCommon::TuneRequest => SystemCommon::TuneRequest, - OwnedSystemCommon::SysEx(b) => SystemCommon::SysEx(&b), - OwnedSystemCommon::Undefined(tag, b) => SystemCommon::Undefined(*tag, &b), - }), - } - } -} diff --git a/src/output.rs b/src/output.rs index 527428b..36b1070 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,7 +7,7 @@ use std::fmt::Display; use std::{error::Error, future::Future}; use MidiOutputError::{ConnectionError, PortRefreshError, SendDisconnectedError, SendError}; -use crate::OwnedLiveEvent; +use crate::types::OwnedLiveEvent; pub struct MidiOutputPlugin; diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..525ac61 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,128 @@ +//! Module definined owned variants of `[midly]` structures. These owned variants allow for more +//! ergonomic usage. +use midly::live::{LiveEvent, SystemCommon}; +use midly::num; +pub use midly::{ + live::{MtcQuarterFrameMessage, SystemRealtime}, + MidiMessage, +}; + +/// Owned version of a [`midly::live::LiveEvent`]. +/// +/// Standard [`midly::live::LiveEvent`]s have a lifetime parameter limiting them to the scope in +/// which they are generated to avoid any copying. However, because we are sending these messages +/// through the bevy event system, they need to outlive this original scope. +/// +/// Creating [`OwnedLiveEvent`]s only allocates when the message is a an [`OwnedSystemCommon`] that +/// itself contains an allocation. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum OwnedLiveEvent { + /// A midi message with a channel and music data. + Midi { + channel: num::u4, + message: midly::MidiMessage, + }, + + /// A System Common message with owned data. + Common(OwnedSystemCommon), + + /// A one-byte System Realtime Message. + Realtime(SystemRealtime), +} + +/// Owned version of [`midly::live::SystemCommon`]. +/// +/// [`OwnedSystemCommon`] fully owns any underlying value data, including +/// [`OwnedSystemCommon::SysEx`] messages. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum OwnedSystemCommon { + /// A system-exclusive event. + /// + /// Only contains the data bytes; does not inclde the `0xF0` and `0xF6` begin/end marker bytes. + /// slice does not include either: it only includes data bytes in the `0x00..=0x7F` range. + SysEx(Vec), + /// A MIDI Time Code Quarter Frame message, carrying a tag type and a 4-bit tag value. + MidiTimeCodeQuarterFrame(MtcQuarterFrameMessage, num::u4), + /// The number of MIDI beats (6 x MIDI clocks) that have elapsed since the start of the + /// sequence. + SongPosition(num::u14), + /// Select a given song index. + SongSelect(num::u7), + /// Request the device to tune itself. + TuneRequest, + /// An undefined System Common message, with arbitrary data bytes. + Undefined(u8, Vec), +} + +impl OwnedLiveEvent { + /// Returns a [`MidiMessage::NoteOn`] event. + pub fn note_on, K: Into, V: Into>( + channel: C, + key: K, + vel: V, + ) -> OwnedLiveEvent { + OwnedLiveEvent::Midi { + channel: channel.into(), + message: midly::MidiMessage::NoteOn { + key: key.into(), + vel: vel.into(), + }, + } + } + + /// Returns a [`MidiMessage::NoteOff`] event. + pub fn note_off, K: Into, V: Into>( + channel: C, + key: K, + vel: V, + ) -> OwnedLiveEvent { + OwnedLiveEvent::Midi { + channel: channel.into(), + message: midly::MidiMessage::NoteOff { + key: key.into(), + vel: vel.into(), + }, + } + } +} + +impl<'a> From> for OwnedLiveEvent { + fn from(value: LiveEvent) -> Self { + match value { + LiveEvent::Midi { channel, message } => OwnedLiveEvent::Midi { channel, message }, + LiveEvent::Realtime(rt) => OwnedLiveEvent::Realtime(rt), + LiveEvent::Common(sc) => OwnedLiveEvent::Common(match sc { + SystemCommon::MidiTimeCodeQuarterFrame(m, v) => { + OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) + } + SystemCommon::SongPosition(pos) => OwnedSystemCommon::SongPosition(pos), + SystemCommon::SongSelect(ss) => OwnedSystemCommon::SongSelect(ss), + SystemCommon::TuneRequest => OwnedSystemCommon::TuneRequest, + SystemCommon::SysEx(b) => OwnedSystemCommon::SysEx(b.to_vec()), + SystemCommon::Undefined(tag, b) => OwnedSystemCommon::Undefined(tag, b.to_vec()), + }), + } + } +} + +impl<'a, 'b: 'a> From<&'b OwnedLiveEvent> for LiveEvent<'a> { + fn from(value: &'b OwnedLiveEvent) -> Self { + match value { + OwnedLiveEvent::Midi { channel, message } => LiveEvent::Midi { + channel: *channel, + message: *message, + }, + OwnedLiveEvent::Realtime(rt) => LiveEvent::Realtime(*rt), + OwnedLiveEvent::Common(sc) => LiveEvent::Common(match sc { + OwnedSystemCommon::MidiTimeCodeQuarterFrame(m, v) => { + SystemCommon::MidiTimeCodeQuarterFrame(*m, *v) + } + OwnedSystemCommon::SongPosition(pos) => SystemCommon::SongPosition(*pos), + OwnedSystemCommon::SongSelect(ss) => SystemCommon::SongSelect(*ss), + OwnedSystemCommon::TuneRequest => SystemCommon::TuneRequest, + OwnedSystemCommon::SysEx(b) => SystemCommon::SysEx(&b), + OwnedSystemCommon::Undefined(tag, b) => SystemCommon::Undefined(*tag, &b), + }), + } + } +} From cc92f635a1cb842a2c298e7703ea51cd435c28de Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sun, 19 Nov 2023 02:37:00 -0800 Subject: [PATCH 5/9] clippy fixes --- src/input.rs | 18 +++++++++--------- src/types.rs | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/input.rs b/src/input.rs index 639c97b..28b6094 100644 --- a/src/input.rs +++ b/src/input.rs @@ -121,24 +121,24 @@ 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 { - match self.message { + matches!( + self.message, OwnedLiveEvent::Midi { message: MidiMessage::NoteOn { .. }, .. - } => true, - _ => false, - } + } + ) } /// Return `true` iff the underlying message represents a MIDI note off event. pub fn is_note_off(&self) -> bool { - match self.message { + matches!( + self.message, OwnedLiveEvent::Midi { - message: MidiMessage::NoteOn { .. }, + message: MidiMessage::NoteOff { .. }, .. - } => true, - _ => false, - } + } + ) } } diff --git a/src/types.rs b/src/types.rs index 525ac61..11f6e59 100644 --- a/src/types.rs +++ b/src/types.rs @@ -120,8 +120,8 @@ impl<'a, 'b: 'a> From<&'b OwnedLiveEvent> for LiveEvent<'a> { OwnedSystemCommon::SongPosition(pos) => SystemCommon::SongPosition(*pos), OwnedSystemCommon::SongSelect(ss) => SystemCommon::SongSelect(*ss), OwnedSystemCommon::TuneRequest => SystemCommon::TuneRequest, - OwnedSystemCommon::SysEx(b) => SystemCommon::SysEx(&b), - OwnedSystemCommon::Undefined(tag, b) => SystemCommon::Undefined(*tag, &b), + OwnedSystemCommon::SysEx(b) => SystemCommon::SysEx(b), + OwnedSystemCommon::Undefined(tag, b) => SystemCommon::Undefined(*tag, b), }), } } From e0e8ab7f807e488a3f503eec22cf2658c334e49d Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sun, 19 Nov 2023 11:50:58 -0800 Subject: [PATCH 6/9] Apply suggestions from code review fix for piano example, better message debugging Co-authored-by: Mikkel Rasmussen --- examples/piano.rs | 2 +- src/types.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/examples/piano.rs b/examples/piano.rs index ee4688f..daa0f6f 100644 --- a/examples/piano.rs +++ b/examples/piano.rs @@ -149,7 +149,7 @@ fn handle_midi_input( for data in midi_events.read() { match data.message { OwnedLiveEvent::Midi { - message: MidiMessage::NoteOn { key, .. }, + message: MidiMessage::NoteOn { key, .. } | MidiMessage::NoteOff { key, .. }, .. } => { let index: u8 = key.into(); diff --git a/src/types.rs b/src/types.rs index 11f6e59..5f59127 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,12 +1,13 @@ //! Module definined owned variants of `[midly]` structures. These owned variants allow for more //! ergonomic usage. +use std::fmt::Debug; use midly::live::{LiveEvent, SystemCommon}; -use midly::num; +use midly::num::{self, u4, u7}; pub use midly::{ live::{MtcQuarterFrameMessage, SystemRealtime}, MidiMessage, }; - +use crate::KEY_RANGE; /// Owned version of a [`midly::live::LiveEvent`]. /// /// Standard [`midly::live::LiveEvent`]s have a lifetime parameter limiting them to the scope in @@ -15,7 +16,7 @@ pub use midly::{ /// /// Creating [`OwnedLiveEvent`]s only allocates when the message is a an [`OwnedSystemCommon`] that /// itself contains an allocation. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum OwnedLiveEvent { /// A midi message with a channel and music data. Midi { @@ -86,6 +87,54 @@ impl OwnedLiveEvent { } } +fn fmt_note( + f: &mut std::fmt::Formatter<'_>, + msg: &str, + ch: &u4, + key: &u7, + vel: &u7, +) -> std::fmt::Result { + let index: u8 = key.as_int(); + let off = index % 12; + let oct = index.overflowing_div(12).0; + let key_str = KEY_RANGE.iter().nth(off.into()).unwrap(); + + f.write_fmt(format_args!( + "Ch: {} {}: {}{:?} Vel: {}", + ch, msg, key_str, oct, vel + )) +} + +impl Debug for OwnedLiveEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Midi { channel, message } => { + { + let _ = match message { + MidiMessage::NoteOff { key, vel } => { + fmt_note(f, "NoteOff", channel, key, vel) + } + MidiMessage::NoteOn { key, vel } => { + fmt_note(f, "NoteOn", channel, key, vel) + } + MidiMessage::Aftertouch { key, vel } => { + fmt_note(f, "Aftertouch", channel, key, vel) + } + _ => f + .debug_struct("Midi") + .field("channel", channel) + .field("message", message) + .finish(), + }; + }; + Ok(()) + } + Self::Common(arg) => f.debug_tuple("Common").field(arg).finish(), + Self::Realtime(arg) => f.debug_tuple("Realtime").field(arg).finish(), + } + } +} + impl<'a> From> for OwnedLiveEvent { fn from(value: LiveEvent) -> Self { match value { From 2268387ca386569c116b607c66d1293065d2ae6d Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sun, 19 Nov 2023 11:54:28 -0800 Subject: [PATCH 7/9] apply cargo fmt --- src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.rs b/src/types.rs index 5f59127..29585cd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,13 +1,13 @@ //! Module definined owned variants of `[midly]` structures. These owned variants allow for more //! ergonomic usage. -use std::fmt::Debug; +use crate::KEY_RANGE; use midly::live::{LiveEvent, SystemCommon}; use midly::num::{self, u4, u7}; pub use midly::{ live::{MtcQuarterFrameMessage, SystemRealtime}, MidiMessage, }; -use crate::KEY_RANGE; +use std::fmt::Debug; /// Owned version of a [`midly::live::LiveEvent`]. /// /// Standard [`midly::live::LiveEvent`]s have a lifetime parameter limiting them to the scope in From b8d9556b0a2ad07aea98d4e0357e09f9bcfcd036 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Sun, 19 Nov 2023 12:01:38 -0800 Subject: [PATCH 8/9] input example: use new Debug impl for last message --- examples/input.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index fc631a9..901dd48 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -103,18 +103,7 @@ fn show_last_message( ) { for data in midi_data.read() { let text_section = &mut instructions.single_mut().sections[3]; - let event_str = match &data.message { - OwnedLiveEvent::Midi { channel, message } => { - format!("Channel {channel} - {message:?}") - } - OwnedLiveEvent::Common(sc) => { - format!("{:?}", sc) - } - OwnedLiveEvent::Realtime(rt) => { - format!("{:?}", rt) - } - }; - text_section.value = format!("Last Message: {:?}", event_str); + text_section.value = format!("Last Message: {:?}", data.message); } } From c9b1d4e1d06619ee793e04b87eeedb093f5fdedd Mon Sep 17 00:00:00 2001 From: Mikkel Rasmussen Date: Mon, 20 Nov 2023 01:22:47 +0100 Subject: [PATCH 9/9] Added orth proj and relative keypress to piano example --- examples/piano.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git a/examples/piano.rs b/examples/piano.rs index daa0f6f..e3aef80 100644 --- a/examples/piano.rs +++ b/examples/piano.rs @@ -22,6 +22,7 @@ fn main() { .init_resource::() .add_plugins(MidiOutputPlugin) .init_resource::() + .add_state::() .add_systems(Startup, setup) .add_systems( Update, @@ -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, @@ -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.); @@ -129,9 +191,53 @@ fn spawn_note( )); } -fn display_press(mut query: Query<&mut Transform, With>) { - 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>, + proj: Res>, + mut proj_txt: Query<&mut Text, With>, + mut nxt_proj: ResMut>, + mut q_pers: Query<&mut Camera, (With, Without)>, + mut q_orth: Query<&mut Camera, (With, Without)>, +) { + 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>) { + for (mut t, k) in &mut query { + if t.translation.y == k.y_reset { + t.translation.y += -0.05; + } } }