diff --git a/lighthouse-client/Cargo.toml b/lighthouse-client/Cargo.toml index 1afac61..5c95e41 100644 --- a/lighthouse-client/Cargo.toml +++ b/lighthouse-client/Cargo.toml @@ -33,3 +33,4 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "std"] } tokio = { version = "1.21", features = ["rt", "rt-multi-thread", "macros", "time"] } clap = { version = "4.5", features = ["derive", "env"] } dotenvy = "0.15" +midi-msg = "0.8.0" diff --git a/lighthouse-client/examples/midi_events.rs b/lighthouse-client/examples/midi_events.rs new file mode 100644 index 0000000..07e29d8 --- /dev/null +++ b/lighthouse-client/examples/midi_events.rs @@ -0,0 +1,49 @@ +use clap::Parser; +use futures::StreamExt; +use lighthouse_client::{protocol::Authentication, Lighthouse, Result, TokioWebSocket, LIGHTHOUSE_URL}; +use lighthouse_protocol::InputEvent; +use midi_msg::MidiMsg; +use tracing::{info, warn}; + +async fn run(lh: Lighthouse) -> Result<()> { + info!("Connected to the Lighthouse server"); + + // Stream input events + let mut stream = lh.stream_input().await?; + while let Some(msg) = stream.next().await { + let event = msg?.payload; + if let InputEvent::Midi(midi) = event { + match MidiMsg::from_midi(&midi.data) { + Ok((msg, _)) => info!("Got MIDI message: {:?}", msg), + Err(e) => warn!("Could not parse MIDI message: {:?}", e), + }; + } + } + + Ok(()) +} + +#[derive(Parser)] +struct Args { + /// The username. + #[arg(short, long, env = "LIGHTHOUSE_USER")] + username: String, + /// The API token. + #[arg(short, long, env = "LIGHTHOUSE_TOKEN")] + token: String, + /// The server URL. + #[arg(long, env = "LIGHTHOUSE_URL", default_value = LIGHTHOUSE_URL)] + url: String, +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + tracing_subscriber::fmt().init(); + _ = dotenvy::dotenv(); + + let args = Args::parse(); + let auth = Authentication::new(&args.username, &args.token); + let lh = Lighthouse::connect_with_tokio_to(&args.url, auth).await?; + + run(lh).await +} diff --git a/lighthouse-protocol/Cargo.toml b/lighthouse-protocol/Cargo.toml index cb2fe5e..17a5875 100644 --- a/lighthouse-protocol/Cargo.toml +++ b/lighthouse-protocol/Cargo.toml @@ -13,6 +13,7 @@ license.workspace = true rand = "0.8" rmpv = { version = "1.0.1", features = ["with-serde"] } serde = { version = "1.0", features = ["derive"] } +serde_bytes = "0.11.17" serde_with = "3.4" [dev-dependencies] diff --git a/lighthouse-protocol/src/input/input_event.rs b/lighthouse-protocol/src/input/input_event.rs index a78fd5f..a871f3b 100644 --- a/lighthouse-protocol/src/input/input_event.rs +++ b/lighthouse-protocol/src/input/input_event.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::Direction; -use super::{EventSource, GamepadEvent, KeyEvent, MouseEvent, UnknownEvent}; +use super::{EventSource, GamepadEvent, KeyEvent, MidiEvent, MouseEvent, UnknownEvent}; /// A user input event, as generated by the new frontend (LUNA). #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] @@ -11,6 +11,7 @@ pub enum InputEvent { Key(KeyEvent), Mouse(MouseEvent), Gamepad(GamepadEvent), + Midi(MidiEvent), #[serde(untagged)] Unknown(UnknownEvent), } @@ -22,6 +23,7 @@ impl InputEvent { InputEvent::Key(KeyEvent { source, .. }) => source, InputEvent::Mouse(MouseEvent { source, .. }) => source, InputEvent::Gamepad(GamepadEvent { source, .. }) => source, + InputEvent::Midi(MidiEvent { source, .. }) => source, InputEvent::Unknown(UnknownEvent { source, .. }) => source, } } diff --git a/lighthouse-protocol/src/input/midi_event.rs b/lighthouse-protocol/src/input/midi_event.rs new file mode 100644 index 0000000..5197152 --- /dev/null +++ b/lighthouse-protocol/src/input/midi_event.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; + +use super::EventSource; + +/// A MIDI message event. +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct MidiEvent { + /// The client identifier. Also unique per MIDI input device. + pub source: EventSource, + /// The binary MIDI message. + /// + /// The first byte is a status byte (first/most significant bit = 1), the + /// remaining bytes are data bytes (first/most significant bit = 0). + /// + /// To give a simple example, pressing C5 on a MIDI keyboard would generate the + /// following message: + /// + /// ```plaintext + /// [0x90, 0x48, 0x64] + /// Ch.1 Note 72 Velocity 100 + /// NoteOn i.e. C5 + /// ``` + /// + /// The note values can be looked up online: + /// + /// - https://www.phys.unsw.edu.au/jw/notes.html + /// + /// Same goes for a full description of the packet structure: + /// + /// - https://www.w3.org/TR/webmidi/#terminology + /// - http://www.opensound.com/pguide/midi/midi5.html + /// - https://www.songstuff.com/recording/article/midi-message-format/ + #[serde(with = "serde_bytes")] + pub data: Vec, +} diff --git a/lighthouse-protocol/src/input/mod.rs b/lighthouse-protocol/src/input/mod.rs index dd05c64..be60063 100644 --- a/lighthouse-protocol/src/input/mod.rs +++ b/lighthouse-protocol/src/input/mod.rs @@ -8,6 +8,7 @@ mod input_event; mod key_event; mod key_modifiers; mod legacy_input_event; +mod midi_event; mod mouse_button; mod mouse_event; mod unknown_event; @@ -22,6 +23,7 @@ pub use input_event::*; pub use key_event::*; pub use key_modifiers::*; pub use legacy_input_event::*; +pub use midi_event::*; pub use mouse_button::*; pub use mouse_event::*; pub use unknown_event::*;