From 1d85d60b11b8651d91e7144969d4a70633a5ec52 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 1 Oct 2023 00:46:40 -0400 Subject: [PATCH] cleaning up --- packages/desktopbridge/cyclistbridge.mjs | 26 ++- packages/react/src/hooks/useStrudel.mjs | 23 +-- src-tauri/src/ablelinkbridge.rs | 193 ++++++++++++++--------- src-tauri/src/main.rs | 13 +- 4 files changed, 142 insertions(+), 113 deletions(-) diff --git a/packages/desktopbridge/cyclistbridge.mjs b/packages/desktopbridge/cyclistbridge.mjs index c2017dd49..8fd6558f2 100644 --- a/packages/desktopbridge/cyclistbridge.mjs +++ b/packages/desktopbridge/cyclistbridge.mjs @@ -6,20 +6,31 @@ import { listen } from '@tauri-apps/api/event'; export class CyclistBridge extends Cyclist { constructor(params) { super(params); - this.start_timer; this.abeLinkListener = listen('abelink-event', async (e) => { const payload = e?.payload; if (payload == null) { return; } - const { started, cps, timestamp } = payload; - // (if bpm !== prev_bpm) { - //update the clock + const { started, cps, phase, timestamp } = payload; + + // TODO: I'm not sure how to hook this up this cps adjustment in Strudel + // (if cps !== clock_cps) { + // updateClock(cps) // } + + // TODO: I'm not sure how to hook this up this phase adjustment in Strudel + // a phase adjustment message is sent every 30 seconds from backend to keep clocks in sync + // if (Math.abs(phase - this.clock.getPhase()) > someDelta) { + // setCyclistPhase(phase) + // } + if (this.started !== started && started != null) { if (started) { + // when start message comes from abelink, delay starting cyclist clock until the start of the next abelink phase this.start_timer = window.setTimeout(() => { + // TODO: evaluate the code so if another source triggers the play there will not be an error + logger('[cyclist] start'); this.clock.start(); this.setStarted(true); @@ -28,8 +39,6 @@ export class CyclistBridge extends Cyclist { this.stop(); } } - - const { message, message_type } = e.payload; }); } @@ -37,11 +46,12 @@ export class CyclistBridge extends Cyclist { if (!this.pattern) { throw new Error('Scheduler: no pattern set! call .setPattern first.'); } - const linkmsg = { + // TODO: change this to value of "main" clock cps cps: 0.5, started: true, timestamp: Date.now(), + phase: this.clock.getPhase(), }; Invoke('sendabelinkmsg', { linkmsg }); } @@ -52,9 +62,11 @@ export class CyclistBridge extends Cyclist { this.lastEnd = 0; this.setStarted(false); const linkmsg = { + // TODO: change this to value of "main" clock cps cps: 0.5, started: false, timestamp: Date.now(), + phase: this.clock.getPhase(), }; Invoke('sendabelinkmsg', { linkmsg }); } diff --git a/packages/react/src/hooks/useStrudel.mjs b/packages/react/src/hooks/useStrudel.mjs index 051b2a170..a10998e73 100644 --- a/packages/react/src/hooks/useStrudel.mjs +++ b/packages/react/src/hooks/useStrudel.mjs @@ -3,7 +3,7 @@ import { repl } from '@strudel.cycles/core'; import { transpiler } from '@strudel.cycles/transpiler'; import usePatternFrame from './usePatternFrame'; import usePostMessage from './usePostMessage.mjs'; -import { listen } from '@tauri-apps/api/event'; + function useStrudel({ defaultOutput, interval, @@ -67,7 +67,6 @@ function useStrudel({ }), [defaultOutput, interval, getTime], ); - const broadcast = usePostMessage(({ data: { from, type } }) => { if (type === 'start' && from !== id) { // console.log('message', from, type); @@ -127,26 +126,6 @@ function useStrudel({ await activateCode(); } }; - - // 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(); - // } - // } - - // const { message, message_type } = e.payload; - // }); - const error = schedulerError || evalError; usePatternFrame({ diff --git a/src-tauri/src/ablelinkbridge.rs b/src-tauri/src/ablelinkbridge.rs index 79654bba7..8e31ba937 100644 --- a/src-tauri/src/ablelinkbridge.rs +++ b/src-tauri/src/ablelinkbridge.rs @@ -9,53 +9,6 @@ use crate::loggerbridge::Logger; use tauri::Window; -#[derive(Deserialize, Clone, serde::Serialize)] -pub struct LinkMsg { - pub started: bool, - pub cps: f64, - pub timestamp: u64, -} - -#[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 AsyncInputTransmit { - pub abelink: Arc>, -} -pub struct AbeLinkState { - pub link: AblLink, - pub session_state: SessionState, - pub running: bool, - pub quantum: f64, -} - -impl AbeLinkState { - 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); - } -} - fn bpm_to_cps(bpm: f64) -> f64 { let cpm = bpm / 4.0; return cpm / 60.0; @@ -66,15 +19,111 @@ fn cps_to_bpm(cps: f64) -> f64 { return cpm * 4.0; } -pub fn init(_logger: Logger, abelink_to_js: AbeLinkToJs, abelink: Arc>) { +fn current_unix_time() -> Duration { + let current_unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + return current_unix_time; +} + +#[derive(Deserialize, Clone, serde::Serialize)] +pub struct LinkMsg { + pub started: bool, + pub cps: f64, + pub timestamp: u64, + pub phase: f64, +} + +pub struct AbeLinkStateContainer { + pub abelink: Arc>, +} +pub struct AbeLinkState { + pub link: AblLink, + pub session_state: SessionState, + pub running: bool, + pub quantum: f64, + pub window: Arc, +} + +impl AbeLinkState { + pub fn new(window: Arc) -> Self { + Self { + link: AblLink::new(120.0), + session_state: SessionState::new(), + running: true, + quantum: 4.0, + window, + } + } + + pub fn unix_time_at_next_phase(&self) -> u64 { + let link_time_stamp = self.link.clock_micros(); + let quantum = self.quantum; + let beat = self.session_state.beat_at_time(link_time_stamp, quantum); + let phase = self.session_state.phase_at_time(link_time_stamp, quantum); + let internal_time_at_next_phase = self.session_state.time_at_beat(beat + (quantum - phase), quantum); + let time_offset = Duration::from_micros((internal_time_at_next_phase - link_time_stamp) as u64); + let current_unix_time = current_unix_time(); + let unix_time_at_next_phase = (current_unix_time + time_offset).as_millis() - 140; + return unix_time_at_next_phase as u64; + } + + pub fn cps(&self) -> f64 { + let bpm = self.session_state.tempo(); + let cps = bpm_to_cps(bpm); + return cps; + } + + 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 fn send(&self, payload: LinkMsg) { + let _ = self.window.emit("abelink-event", payload); + } + + pub fn send_started(&self) { + let cps = self.cps(); + let started = self.session_state.is_playing(); + let payload = LinkMsg { + cps, + started, + timestamp: self.unix_time_at_next_phase(), + phase: 0.0, + }; + self.send(payload); + } + + pub fn send_cps(&self) { + let cps = self.cps(); + let started = self.session_state.is_playing(); + let phase = self.session_state.phase_at_time(self.link.clock_micros(), self.quantum); + let payload = LinkMsg { + cps, + started, + timestamp: current_unix_time().as_millis() as u64, + phase, + }; + self.send(payload); + } + + pub fn send_phase(&self) { + self.send_started(); + } +} + +pub fn init(_logger: Logger, abelink: Arc>) { tauri::async_runtime::spawn(async move { - /* ........................................................... - Initialize Ableton link - ............................................................*/ - - let mut prev_is_playing = false; - let mut prev_bpm = 120.0; + let mut prev_is_started = false; + let mut prev_cps = 0.5; + let mut time_since_last_phase_send = 0; + let sleep_time = 10; + /* ....................................................................... + Evaluate Abelink State and send messages back to JS side when needed. + ........................................................................*/ loop { let mut state = abelink.lock().await; if state.link.is_enabled() == false { @@ -82,42 +131,32 @@ pub fn init(_logger: Logger, abelink_to_js: AbeLinkToJs, abelink: Arc 30000 { + state.send_phase(); + time_since_last_phase_send = 0; } drop(state); - sleep(Duration::from_millis(10)); + sleep(Duration::from_millis(sleep_time)); + time_since_last_phase_send = time_since_last_phase_send + sleep_time; } }); } // Called from JS #[tauri::command] -pub async fn sendabelinkmsg(linkmsg: LinkMsg, state: tauri::State<'_, AsyncInputTransmit>) -> Result<(), String> { +pub async fn sendabelinkmsg(linkmsg: LinkMsg, state: tauri::State<'_, AbeLinkStateContainer>) -> Result<(), String> { let mut abelink = state.abelink.lock().await; let started = abelink.session_state.is_playing(); let time_stamp = abelink.link.clock_micros(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6e3e47ac5..e4288b83b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,7 +8,6 @@ mod ablelinkbridge; use std::sync::Arc; use ablelinkbridge::AbeLinkState; -use ablelinkbridge::AbeLinkToJs; use loggerbridge::Logger; use tauri::Manager; use tokio::sync::mpsc; @@ -38,11 +37,6 @@ fn main() { let window = Arc::new(app.get_window("main").unwrap()); let logger = Logger { window: window.clone() }; - let abelink = Arc::new(Mutex::new(AbeLinkState::new())); - app.manage(ablelinkbridge::AsyncInputTransmit { - abelink: abelink.clone(), - }); - midibridge::init( logger.clone(), async_input_receiver_midi, @@ -56,7 +50,12 @@ fn main() { async_output_transmitter_osc ); - ablelinkbridge::init(logger.clone(), AbeLinkToJs { window }, abelink); + // This state must be declared in the setup so it can be shared between invoked commands and the initialized function + let abelink = Arc::new(Mutex::new(AbeLinkState::new(window))); + app.manage(ablelinkbridge::AbeLinkStateContainer { + abelink: abelink.clone(), + }); + ablelinkbridge::init(logger.clone(), abelink); Ok(()) })