mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-05 22:58:09 -04:00
cleaning up
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
+116
-77
@@ -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<Window>,
|
||||
}
|
||||
|
||||
impl AbeLinkToJs {
|
||||
pub fn send(&self, payload: LinkMsg) {
|
||||
let _ = self.window.emit("abelink-event", payload);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncInputTransmit {
|
||||
pub abelink: Arc<Mutex<AbeLinkState>>,
|
||||
}
|
||||
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<Mutex<AbeLinkState>>) {
|
||||
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<Mutex<AbeLinkState>>,
|
||||
}
|
||||
pub struct AbeLinkState {
|
||||
pub link: AblLink,
|
||||
pub session_state: SessionState,
|
||||
pub running: bool,
|
||||
pub quantum: f64,
|
||||
pub window: Arc<Window>,
|
||||
}
|
||||
|
||||
impl AbeLinkState {
|
||||
pub fn new(window: Arc<Window>) -> 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<Mutex<AbeLinkState>>) {
|
||||
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<Mutex<AbeL
|
||||
state.link.enable_start_stop_sync(true);
|
||||
}
|
||||
|
||||
let link_time_stamp = state.link.clock_micros();
|
||||
let bpm = state.session_state.tempo();
|
||||
let cps = bpm_to_cps(bpm);
|
||||
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 || started != prev_is_playing {
|
||||
let payload = LinkMsg {
|
||||
cps,
|
||||
started,
|
||||
timestamp: message_timestamp as u64,
|
||||
};
|
||||
abelink_to_js.send(payload);
|
||||
prev_is_playing = started;
|
||||
prev_bpm = bpm;
|
||||
if started != prev_is_started {
|
||||
state.send_started();
|
||||
prev_is_started = started;
|
||||
} else if state.cps() != prev_cps {
|
||||
state.send_cps();
|
||||
prev_cps = state.cps();
|
||||
// a phase sync message needs to be sent to strudel every 30 seconds to keep clock drift at bay
|
||||
} else if time_since_last_phase_send > 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();
|
||||
|
||||
@@ -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(())
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user