From 6eda7af14bc006ea30daee873abc71267c8d4bb1 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Fri, 29 Sep 2023 01:01:01 -0400 Subject: [PATCH] play sync now accurate --- packages/core/cyclist.mjs | 56 +++++------ packages/react/src/hooks/useStrudel.mjs | 32 +++--- src-tauri/src/ablelinkbridge.rs | 126 ++++++------------------ src-tauri/src/main.rs | 13 +-- 4 files changed, 73 insertions(+), 154 deletions(-) diff --git a/packages/core/cyclist.mjs b/packages/core/cyclist.mjs index 9cabceb45..7304558a8 100644 --- a/packages/core/cyclist.mjs +++ b/packages/core/cyclist.mjs @@ -18,24 +18,30 @@ export class Cyclist { this.lastEnd = 0; // query end of last tick this.getTime = getTime; // get absolute time this.onToggle = onToggle; - // this.abeLinkListener = listen('abelink-event', async (e) => { - // const payload = e?.payload; - // if (payload == null) { - // return; - // } - // const { play, bpm, timestamp } = payload; - // console.log('play'); + this.start_timer; + this.abeLinkListener = listen('abelink-event', async (e) => { + const payload = e?.payload; + if (payload == null) { + return; + } + const { play, bpm, timestamp } = payload; + // (if bpm !== prev_bpm) { + //update the clock + // } + if (this.started !== play && play != null) { + if (play) { + this.start_timer = window.setTimeout(() => { + logger('[cyclist] start'); + this.clock.start(); + this.setStarted(true); + }, timestamp - Date.now()); + } else { + this.stop(); + } + } - // if (this.started !== play && play != null) { - // if (play) { - // this.start(); - // } else { - // this.stop(); - // } - // } - - // const { message, message_type } = e.payload; - // }); + const { message, message_type } = e.payload; + }); this.latency = latency; // fixed trigger time offset const round = (x) => Math.round(x * 1000) / 1000; this.clock = createClock( @@ -78,26 +84,18 @@ export class Cyclist { this.started = v; this.onToggle?.(v); } + startClock() {} start() { if (!this.pattern) { throw new Error('Scheduler: no pattern set! call .setPattern first.'); } const linkmsg = { - bpm: this.cps * 60, + bpm: 110, play: true, timestamp: Date.now(), }; - Invoke('sendabelinkmsg', { linkmsg }).then((res) => { - const timeoffset = res.timestamp - Date.now(); - - console.log({ res, timeoffset }); - window.setTimeout(() => { - logger('[cyclist] start'); - this.clock.start(); - this.setStarted(true); - }, timeoffset); - }); + Invoke('sendabelinkmsg', { linkmsg }); } pause() { logger('[cyclist] pause'); @@ -110,7 +108,7 @@ export class Cyclist { this.lastEnd = 0; this.setStarted(false); const linkmsg = { - bpm: this.clock.interval, + bpm: 110, play: false, timestamp: Date.now(), }; diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 93674af5a..051b2a170 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -128,24 +128,24 @@ function useStrudel({ } }; - listen('abelink-event', async (e) => { - const payload = e?.payload; - if (payload == null) { - return; - } - const { play, bpm, timestamp } = payload; + // listen('abelink-event', async (e) => { + // const payload = e?.payload; + // if (payload == null) { + // return; + // } + // const { play, bpm, timestamp } = payload; - if (started !== play && play != null) { - if (play) { - // activateCode(); - start(); - } else { - stop(); - } - } + // if (started !== play && play != null) { + // if (play) { + // // activateCode(); + // start(); + // } else { + // stop(); + // } + // } - const { message, message_type } = e.payload; - }); + // const { message, message_type } = e.payload; + // }); const error = schedulerError || evalError; diff --git a/src-tauri/src/ablelinkbridge.rs b/src-tauri/src/ablelinkbridge.rs index 16daada29..d1bf7d652 100644 --- a/src-tauri/src/ablelinkbridge.rs +++ b/src-tauri/src/ablelinkbridge.rs @@ -1,7 +1,7 @@ use std::time::{ Duration, SystemTime, UNIX_EPOCH }; use rusty_link::{ AblLink, SessionState }; use std::sync::Arc; -use tokio::sync::{ mpsc, Mutex }; +use tokio::sync::Mutex; use serde::Deserialize; use std::thread::sleep; @@ -28,7 +28,6 @@ impl AbeLinkToJs { } pub struct AsyncInputTransmit { - pub inner: Mutex>, pub abelink: Arc>, } pub struct AbeLinkState { @@ -57,43 +56,15 @@ impl AbeLinkState { } } -pub fn init( - _logger: Logger, - abelink_to_js: AbeLinkToJs, - abelink: Arc>, - 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(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); +pub fn init(_logger: Logger, abelink_to_js: AbeLinkToJs, abelink: Arc>) { tauri::async_runtime::spawn(async move { /* ........................................................... Initialize Ableton link ............................................................*/ - //let mut state = AbeLinkState::new(); let mut prev_is_playing = false; let mut prev_bpm = 120.0; - /* ........................................................... - Process queued messages - ............................................................*/ - loop { let mut state = abelink.lock().await; if state.link.is_enabled() == false { @@ -101,95 +72,54 @@ pub fn init( state.link.enable_start_stop_sync(true); } - let mut message_queue = message_queue_clone.lock().await; - let time_stamp = state.link.clock_micros(); + let link_time_stamp = state.link.clock_micros(); let bpm = state.session_state.tempo(); - let play = state.session_state.is_playing(); + let started = state.session_state.is_playing(); let quantum = state.quantum; + let beat = state.session_state.beat_at_time(link_time_stamp, quantum); + let phase = state.session_state.phase_at_time(link_time_stamp, quantum); + + let time_at_next_cycle = state.session_state.time_at_beat(beat + (quantum - phase), quantum); + + let time_offset = Duration::from_micros((time_at_next_cycle - link_time_stamp) as u64); + let current_unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let message_timestamp = (current_unix_time + time_offset).as_millis() - 140; + state.capture_app_state(); - if bpm != prev_bpm || play != prev_is_playing { - //let cycle = state.session_state.time_at_beat(beat, quantum) + if bpm != prev_bpm || started != prev_is_playing { let payload = LinkMsg { bpm, - play, - timestamp: 1, + play: started, + timestamp: message_timestamp as u64, }; abelink_to_js.send(payload); - prev_is_playing = play; + prev_is_playing = started; 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; - }); drop(state); 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<'_, AsyncInputTransmit>) -> Result { +pub async fn sendabelinkmsg(linkmsg: LinkMsg, state: tauri::State<'_, AsyncInputTransmit>) -> Result<(), String> { println!("bpm {} play {}", linkmsg.bpm, linkmsg.play); - let async_proc_input_tx = state.inner.lock().await; let mut abelink = state.abelink.lock().await; + let started = abelink.session_state.is_playing(); + let time_stamp = abelink.link.clock_micros(); let quantum = abelink.quantum; - let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - // abelink.session_state.phase_at_time(time, quantum) - //let x = abelink.session_state.request_beat_at_start_playing_time(1.0, quantum); - - let x = Duration::from_millis(abelink.session_state.time_at_beat(0.0, quantum) as u64); - let link_clock = Duration::from_millis(abelink.link.clock_micros() as u64); - - let timestamp = current_time.as_millis(); - let phase = abelink.session_state.phase_at_time(abelink.link.clock_micros(), quantum); - let bpm = abelink.session_state.tempo(); - let beat = abelink.session_state.beat_at_time(abelink.link.clock_micros(), quantum); - - let bps = 60.0 / bpm; - let phase_offset = quantum - phase; - let seconds_offset = phase_offset * bps; - let milliseconds_offset = seconds_offset * 1000.0; - let transport_offset = (milliseconds_offset as u64) + (current_time.as_millis() as u64); - - // let d = Duration::from_millis(x as u64); - // let d2 = Duration::from_secs(0); - // let d3 = d + d2; - - let x2 = Duration::from_millis(abelink.session_state.time_for_is_playing()); - - println!("quantum {}, phase {}, bpm {}, beat {}, seconds offset {}", quantum, phase, bpm, beat, seconds_offset); - let ret_message = LinkMsg { - bpm: linkmsg.bpm.clone(), - play: linkmsg.play.clone(), - timestamp: transport_offset, - }; - - async_proc_input_tx.send(linkmsg).await.unwrap(); + if linkmsg.play != started { + abelink.session_state.set_is_playing_and_request_beat_at_time(linkmsg.play, time_stamp as u64, 0.0, quantum); + } + if linkmsg.bpm != abelink.session_state.tempo() { + abelink.session_state.set_tempo(linkmsg.bpm, time_stamp); + } + abelink.commit_app_state(); drop(abelink); - Ok(ret_message) + Ok(()) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index aa3e7a12f..6e3e47ac5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -24,8 +24,7 @@ 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 { @@ -41,7 +40,6 @@ fn main() { let abelink = Arc::new(Mutex::new(AbeLinkState::new())); app.manage(ablelinkbridge::AsyncInputTransmit { - inner: Mutex::new(async_input_transmitter_abelink), abelink: abelink.clone(), }); @@ -58,14 +56,7 @@ fn main() { async_output_transmitter_osc ); - ablelinkbridge::init( - logger.clone(), - AbeLinkToJs { window }, - abelink, - async_input_receiver_abelink, - async_output_receiver_abelink, - async_output_transmitter_abelink - ); + ablelinkbridge::init(logger.clone(), AbeLinkToJs { window }, abelink); Ok(()) })