diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 90767fec..38fce20b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -98,6 +98,7 @@ name = "app" version = "0.1.0" dependencies = [ "midir", + "rosc", "serde", "serde_json", "tauri", @@ -1557,6 +1558,12 @@ dependencies = [ "windows 0.43.0", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1629,6 +1636,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2173,6 +2190,16 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "rosc" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e63d9e6b0d090be1485cf159b1e04c3973d2d3e1614963544ea2ff47a4a981" +dependencies = [ + "byteorder", + "nom", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 22170cac..35aab489 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -20,6 +20,7 @@ serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.4.0", features = ["fs-all"] } midir = "0.9.1" tokio = { version = "1.29.0", features = ["full"] } +rosc = "0.10.1" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0643cefd..c91e37df 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,20 +2,27 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod midibridge; +mod oscbridge; use tokio::sync::mpsc; use tokio::sync::Mutex; fn main() { - let (async_input_transmitter, async_input_receiver) = mpsc::channel(1); - let (async_output_transmitter, async_output_receiver) = mpsc::channel(1); + let (async_input_transmitter_midi, async_input_receiver_midi) = mpsc::channel(1); + let (async_output_transmitter_midi, async_output_receiver_midi) = mpsc::channel(1); + let (async_input_transmitter_osc, async_input_receiver_osc) = mpsc::channel(1); + let (async_output_transmitter_osc, async_output_receiver_osc) = mpsc::channel(1); tauri::Builder ::default() .manage(midibridge::AsyncInputTransmit { - inner: Mutex::new(async_input_transmitter), + inner: Mutex::new(async_input_transmitter_midi), }) - .invoke_handler(tauri::generate_handler![midibridge::sendmidi]) + .manage(oscbridge::AsyncInputTransmit { + inner: Mutex::new(async_input_transmitter_osc), + }) + .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc]) .setup(|_app| { - midibridge::init(async_input_receiver, async_output_receiver, async_output_transmitter); + midibridge::init(async_input_receiver_midi, async_output_receiver_midi, async_output_transmitter_midi); + oscbridge::init(async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); Ok(()) }) .run(tauri::generate_context!()) diff --git a/src-tauri/src/oscbridge.rs b/src-tauri/src/oscbridge.rs new file mode 100644 index 00000000..0ea7e6c5 --- /dev/null +++ b/src-tauri/src/oscbridge.rs @@ -0,0 +1,143 @@ +use rosc::encoder; +use rosc::{ OscMessage, OscPacket, OscType }; +use std::net::{ SocketAddrV4, UdpSocket }; +use std::str::FromStr; +use std::time::Duration; +use std::{ env, f32 }; +use std::sync::Arc; +use tokio::sync::{ mpsc, Mutex }; +use tokio::time::Instant; +use serde::Deserialize; +use std::thread::sleep; +pub struct OscMsg { + pub msg_buf: Vec, + pub instant: Instant, + pub offset: u64, +} + +pub struct AsyncInputTransmit { + pub inner: Mutex>>, +} +fn get_addr_from_arg(arg: &str) -> SocketAddrV4 { + SocketAddrV4::from_str(arg).unwrap() +} +pub fn init( + async_input_receiver: mpsc::Receiver>, + mut async_output_receiver: mpsc::Receiver>, + async_output_transmitter: mpsc::Sender> +) { + tauri::async_runtime::spawn(async move { async_process_model(async_input_receiver, async_output_transmitter).await }); + let message_queue: Arc>> = Arc::new(Mutex::new(Vec::new())); + /* ........................................................... + Listen For incoming messages and add to queue + ............................................................*/ + let message_queue_clone = Arc::clone(&message_queue); + tauri::async_runtime::spawn(async move { + loop { + if let Some(package) = async_output_receiver.recv().await { + let mut message_queue = message_queue_clone.lock().await; + let messages = package; + //println!("received message"); + for message in messages { + (*message_queue).push(message); + } + } + } + }); + println!("cloning message queue"); + + let message_queue_clone = Arc::clone(&message_queue); + tauri::async_runtime::spawn(async move { + println!("opening osc port"); + /* ........................................................... + Open OSC Ports + ............................................................*/ + + let sock = UdpSocket::bind("localhost:57121").unwrap(); + let to_addr = String::from("localhost:57120"); + + /* ........................................................... + Process queued messages + ............................................................*/ + + loop { + let mut message_queue = message_queue_clone.lock().await; + println!("num messages {}", message_queue.len()); + + //iterate over each message, play and remove messages when they are ready + message_queue.retain(|message| { + if message.instant.elapsed().as_millis() < message.offset.into() { + return true; + } + sock.send_to(&message.msg_buf, to_addr.clone()).unwrap(); + return false; + }); + + sleep(Duration::from_millis(1)); + } + }); +} + +pub async fn async_process_model( + mut input_reciever: mpsc::Receiver>, + output_transmitter: mpsc::Sender> +) -> Result<(), Box> { + while let Some(input) = input_reciever.recv().await { + let output = input; + output_transmitter.send(output).await?; + } + Ok(()) +} + +#[derive(Deserialize)] +pub struct Param { + name: String, + value: String, + valueisnumber: bool, +} +#[derive(Deserialize)] +pub struct MessageFromJS { + params: Vec, + offset: u64, + target: String, +} +// Called from JS +#[tauri::command] +pub async fn sendosc( + messagesfromjs: Vec, + state: tauri::State<'_, AsyncInputTransmit> +) -> Result<(), String> { + let async_proc_input_tx = state.inner.lock().await; + let mut messages_to_process: Vec = Vec::new(); + + for m in messagesfromjs { + let mut args = Vec::new(); + for p in m.params { + args.push(OscType::String(p.name)); + + if p.valueisnumber { + args.push(OscType::Float(p.value.parse().unwrap())); + } else { + args.push(OscType::String(p.value)); + } + } + + let msg_buf = encoder + ::encode( + &OscPacket::Message(OscMessage { + addr: m.target, + args, + }) + ) + .unwrap(); + + let message_to_process = OscMsg { + instant: Instant::now(), + msg_buf, + offset: m.offset, + }; + messages_to_process.push(message_to_process); + } + + async_proc_input_tx.send(messages_to_process).await.map_err(|e| e.to_string()) +}