diff --git a/packages/core/cyclistworker.js b/packages/core/cyclistworker.js index b0028698..56af5531 100644 --- a/packages/core/cyclistworker.js +++ b/packages/core/cyclistworker.js @@ -7,7 +7,6 @@ let lastEnd = 0; // query end of last tick // let getTime = getTime; // get absolute time let num_cycles_at_cps_change = 0; // let onToggle = onToggle; -let latency = 0.1; // fixed trigger time offset let interval = 0.1; //incoming @@ -18,7 +17,7 @@ let interval = 0.1; // {type: toggle, payload?: {started: boolean}} //sending -//{type: 'tick', payload: {begin, end, phase, time }} +//{type: 'tick', payload: {begin, end, deadline }} //{type: 'log', payload: {type, text}} const getTime = () => { @@ -34,7 +33,7 @@ const log = (text, type) => { sendMessage({ type: 'log', payload: { text, type } }); }; -createClock( +let clock = createClock( getTime, // called slightly before each cycle (phase, duration, tick) => { @@ -53,7 +52,7 @@ createClock( lastEnd = end; const tickdeadline = phase - time; // time left until the phase is a whole number lastTick = time + tickdeadline; - sendMessage({ type: 'tick', payload: { begin, end, phase, time } }); + sendMessage({ type: 'tick', payload: { begin, end, tickdeadline } }); } catch (e) { log(`[cyclist] error: ${e.message}`, 'error'); } @@ -73,6 +72,33 @@ self.onconnect = function (e) { port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter. }; +self.onmessage = (message) => { + const { type, payload } = message; + switch (type) { + case 'cpschange': { + if (payload.cps !== cps) { + cps = payload.cps; + num_ticks_since_cps_change = 0; + } + break; + } + case 'toggle': { + const { started } = payload; + if (started) { + clock.start(); + } else { + clock.stop(); + } + break; + } + case 'requestcycles': { + const secondsSinceLastTick = getTime() - lastTick - clock.duration; + const cycles = this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency; + sendMessage({ type: 'requestedcycles', payload: { cycles } }); + } + } +}; + function createClock( getTime, callback, // called slightly before each cycle @@ -84,6 +110,7 @@ function createClock( let phase = 0; // next callback time let precision = 10 ** 4; // used to round phase let minLatency = 0.01; + const setDuration = (setter) => (duration = setter(duration)); overlap = overlap || interval / 2; const onTick = () => { @@ -115,6 +142,7 @@ function createClock( clear(); }; const getPhase = () => phase; + // setCallback return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }; } diff --git a/packages/core/neocyclist.mjs b/packages/core/neocyclist.mjs new file mode 100644 index 00000000..da2b7794 --- /dev/null +++ b/packages/core/neocyclist.mjs @@ -0,0 +1,73 @@ +import { logger } from './logger.mjs'; + +const sharedworker = new SharedWorker(new URL('./cyclistworker.js', import.meta.url)); +sharedworker.port.start(); + +export class NeoCyclist { + constructor({ onTrigger, onToggle, latency = 0.1, onError }) { + this.started = false; + this.pattern; + this.onToggle = onToggle; + this.latency = latency; + this.worker = new SharedWorker(new URL('./cyclistworker.js', import.meta.url)); + this.worker.port.addEventListener('message', (message) => { + const { payload, type } = message; + switch (type) { + case 'tick': { + console.log('tick'); + const { begin, end } = payload; + const haps = this.pattern.queryArc(begin, end); + haps.forEach((hap) => { + if (hap.part.begin.equals(hap.whole.begin)) { + const deadline = (hap.whole.begin - begin) / this.cps + payload.deadline + latency; + const duration = hap.duration / this.cps; + onTrigger?.(hap, deadline, duration, this.cps); + } + }); + break; + } + case 'log': { + const { type, text } = payload; + if (type == 'error') { + onError(text); + } else { + logger(text, type); + } + } + } + }); + } + sendMessage(type, payload) { + this.worker.port.postMessage({ type, payload }); + } + + now() { + this.sendMessage('requestcycles', {}); + } + setCps(cps = 1) { + this.sendMessage('cpschange', { cps }); + } + setStarted(started) { + this.sendMessage('toggle', { started }); + this.started = started; + this.onToggle?.(started); + } + start() { + logger('[cyclist] start'); + this.setStarted(true); + } + stop() { + logger('[cyclist] stop'); + this.setStarted(false); + } + setPattern(pat, autostart = false) { + this.pattern = pat; + if (autostart && !this.started) { + this.start(); + } + } + log(begin, end, haps) { + const onsets = haps.filter((h) => h.hasOnset()); + console.log(`${begin.toFixed(4)} - ${end.toFixed(4)} ${Array(onsets.length).fill('I').join('')}`); + } +} diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 245b651f..b3b810c0 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -4,6 +4,7 @@ import { logger } from './logger.mjs'; import { setTime } from './time.mjs'; import { evalScope } from './evaluate.mjs'; import { register, Pattern, isPattern, silence, stack } from './pattern.mjs'; +import { NeoCyclist } from './neocyclist.mjs'; export function repl({ interval, @@ -52,11 +53,21 @@ export function repl({ onUpdateState?.(state); }; - const scheduler = new Cyclist({ + // const scheduler = new Cyclist({ + // interval, + // onTrigger: getTrigger({ defaultOutput, getTime }), + // onError: onSchedulerError, + // getTime, + // onToggle: (started) => { + // updateState({ started }); + // onToggle?.(started); + // }, + // }); + + const scheduler = new NeoCyclist({ interval, onTrigger: getTrigger({ defaultOutput, getTime }), onError: onSchedulerError, - getTime, onToggle: (started) => { updateState({ started }); onToggle?.(started);