diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 38fce20bf..9eb9f1057 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -99,6 +99,7 @@ version = "0.1.0" dependencies = [ "midir", "rosc", + "rusty_link", "serde", "serde_json", "tauri", @@ -163,6 +164,28 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -285,6 +308,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -334,6 +366,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -637,6 +689,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "embed-resource" version = "2.1.1" @@ -1431,12 +1489,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -1814,6 +1888,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -2206,6 +2286,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2235,6 +2321,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +[[package]] +name = "rusty_link" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212e275351a46badd88b67ab8a4a8e17580317c728c361299b245cb51debe81c" +dependencies = [ + "bindgen", + "cmake", +] + [[package]] name = "ryu" version = "1.0.13" @@ -2428,6 +2524,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3375,6 +3477,17 @@ dependencies = [ "windows-metadata", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 35aab489e..4c517cfba 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ tauri = { version = "1.4.0", features = ["fs-all"] } midir = "0.9.1" tokio = { version = "1.29.0", features = ["full"] } rosc = "0.10.1" +rusty_link = "0.3.6" [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/ablelinkbridge.rs b/src-tauri/src/ablelinkbridge.rs new file mode 100644 index 000000000..daa75ff8b --- /dev/null +++ b/src-tauri/src/ablelinkbridge.rs @@ -0,0 +1,170 @@ +use std::time::Duration; +use rusty_link::{ AblLink, SessionState }; +use std::sync::Arc; +use tokio::sync::{ mpsc, Mutex }; +use serde::Deserialize; +use std::thread::sleep; + +use crate::loggerbridge::Logger; + +use tauri::{ Window, App, Manager }; + +#[derive(Deserialize, Clone, serde::Serialize)] +pub struct LinkMsg { + pub play: bool, + pub bpm: f64, +} + +#[derive(Clone)] +pub struct AbeLinkToJs { + pub window: Arc, +} + +impl AbeLinkToJs { + pub fn send(&self, payload: LinkMsg) { + let _ = self.window.emit("abelink-event", payload); + } +} + +pub struct State2 { + pub inner: Mutex>, + pub ablelink_state: Mutex, +} +// #[derive(Sync)] +pub struct AbleLinkState { + pub link: AblLink, + pub session_state: SessionState, + pub running: bool, + pub quantum: f64, +} + +impl AbleLinkState { + pub fn new() -> Self { + Self { + link: AblLink::new(120.0), + session_state: SessionState::new(), + running: true, + quantum: 4.0, + } + } + + pub fn capture_app_state(&mut self) { + self.link.capture_app_session_state(&mut self.session_state); + } + + pub fn commit_app_state(&mut self) { + self.link.commit_app_session_state(&self.session_state); + } +} + +pub async fn init( + abelink_to_js: AbeLinkToJs, + _logger: Logger, + async_input_receiver: mpsc::Receiver, + mut async_output_receiver: mpsc::Receiver, + async_output_transmitter: mpsc::Sender +) { + println!("init"); + 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())); + //let s = app.state::().ablelink_state.lock().await; + // tauri::async_runtime::spawn(async move { + // abelink_to_js.send(LinkMsg { play: true, bpm: 100.0 }); + + // //let mut s = a.state::().ablelink_state.lock().await; + // // s.capture_app_state(); + // }); + + // let state = unlocked_state.ablelink_state.try_lock().unwrap(); + + /* ........................................................... + 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(message) = async_output_receiver.recv().await { + let mut message_queue = message_queue_clone.lock().await; + (*message_queue).push(message); + } + } + }); + + let message_queue_clone = Arc::clone(&message_queue); + // tauri::async_runtime::spawn(async move { + // let a = app.lock().await; + // let mut state = a.state::().ablelink_state.lock().await; + + // /* ........................................................... + // Initialize Ableton link + // ............................................................*/ + // //let mut state = State::new(); + // state.link.enable(true); + // state.link.enable_start_stop_sync(true); + + // let mut prev_is_playing = state.session_state.is_playing(); + // let mut prev_bpm = state.session_state.tempo(); + + // /* ........................................................... + // Process queued messages + // ............................................................*/ + + // loop { + // let mut message_queue = message_queue_clone.lock().await; + + // state.capture_app_state(); + // let time_stamp = state.link.clock_micros(); + // let bpm = state.session_state.tempo(); + // let play = state.session_state.is_playing(); + // let quantum = state.quantum; + // println!("quant: {}", quantum); + + // if bpm != prev_bpm || play != prev_is_playing { + // //let cycle = state.session_state.time_at_beat(beat, quantum) + // let payload = LinkMsg { + // bpm, + // play, + // }; + // abelink_to_js.send(payload); + // prev_is_playing = play; + // prev_bpm = bpm; + // } + + // message_queue.retain(|message| { + // let is_playing = message.play; + // println!("is playing {}", is_playing); + // if is_playing != prev_is_playing { + // if is_playing == false { + // state.session_state.set_is_playing(false, time_stamp as u64); + // } else { + // state.session_state.set_is_playing_and_request_beat_at_time(true, time_stamp as u64, 0.0, quantum); + // } + // state.commit_app_state(); + // } + // return false; + // }); + + // sleep(Duration::from_millis(10)); + // } + // }); +} + +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(()) +} + +// Called from JS +#[tauri::command] +pub async fn sendabelinkmsg(linkmsg: LinkMsg, state: tauri::State<'_, State2>) -> Result<(), String> { + println!("bpm {} play {}", linkmsg.bpm, linkmsg.play); + let async_proc_input_tx = state.inner.lock().await; + async_proc_input_tx.send(linkmsg).await.map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6f97b8c98..c866fbdc6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,8 +4,11 @@ mod midibridge; mod oscbridge; mod loggerbridge; +mod ablelinkbridge; use std::sync::Arc; +use ablelinkbridge::AbeLinkToJs; +use ablelinkbridge::State2; use loggerbridge::Logger; use tauri::Manager; use tokio::sync::mpsc; @@ -21,6 +24,8 @@ fn main() { 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); + let (async_input_transmitter_abelink, async_input_receiver_abelink) = mpsc::channel(1); + let (async_output_transmitter_abelink, async_output_receiver_abelink) = mpsc::channel(1); tauri::Builder ::default() .manage(midibridge::AsyncInputTransmit { @@ -29,17 +34,42 @@ fn main() { .manage(oscbridge::AsyncInputTransmit { inner: Mutex::new(async_input_transmitter_osc), }) - .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc]) + .manage(ablelinkbridge::State2 { + inner: Mutex::new(async_input_transmitter_abelink), + ablelink_state: Mutex::new(ablelinkbridge::AbleLinkState::new()), + }) + .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc, ablelinkbridge::sendabelinkmsg]) .setup(|app| { + // let mut able_link_state = Arc::new( + // Mutex::new(ablelinkbridge::State::new(Mutex::new(async_input_transmitter_abelink))) + // ); + // app.manage(able_link_state.clone()); let window = Arc::new(app.get_window("main").unwrap()); - let logger = Logger { window }; + let logger = Logger { window: window.clone() }; + // let state_mutex = app.state::>(); + // state_mutex.lock(); + midibridge::init( logger.clone(), async_input_receiver_midi, async_output_receiver_midi, async_output_transmitter_midi ); - oscbridge::init(logger, async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); + oscbridge::init( + logger.clone(), + async_input_receiver_osc, + async_output_receiver_osc, + async_output_transmitter_osc + ); + + ablelinkbridge::init( + AbeLinkToJs { window }, + logger.clone(), + async_input_receiver_abelink, + async_output_receiver_abelink, + async_output_transmitter_abelink + ); + Ok(()) }) .run(tauri::generate_context!())