mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-25 20:48:27 +00:00
testing
This commit is contained in:
parent
3945abd7e9
commit
9c899d5308
@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
function getTime() {
|
function getTime() {
|
||||||
const precision = 10 ** 4;
|
const precision = 10 ** 4;
|
||||||
const seconds = performance.now() / 1000;
|
const seconds = performance.now() * .001;
|
||||||
return Math.round(seconds * precision) / precision;
|
return seconds
|
||||||
|
// return Math.round(seconds * precision) / precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_cycles_at_cps_change = 0;
|
let num_cycles_at_cps_change = 0;
|
||||||
@ -41,10 +42,8 @@ const sendTick = (phase, duration, tick, time) => {
|
|||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
cps,
|
cps,
|
||||||
tickdeadline,
|
time: num_seconds_at_cps_change + num_seconds_since_cps_change + tickdeadline,
|
||||||
num_cycles_at_cps_change,
|
num_cycles_at_cps_change,
|
||||||
num_seconds_at_cps_change,
|
|
||||||
num_seconds_since_cps_change,
|
|
||||||
cycle,
|
cycle,
|
||||||
});
|
});
|
||||||
num_ticks_since_cps_change++;
|
num_ticks_since_cps_change++;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
import { ClockCollator, cycleToSeconds } from './util.mjs';
|
||||||
|
|
||||||
export class NeoCyclist {
|
export class NeoCyclist {
|
||||||
constructor({ onTrigger, onToggle, getTime }) {
|
constructor({ onTrigger, onToggle, getTime }) {
|
||||||
@ -13,65 +14,30 @@ export class NeoCyclist {
|
|||||||
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
this.lastTick = 0; // absolute time when last tick (clock callback) happened
|
||||||
this.getTime = getTime; // get absolute time
|
this.getTime = getTime; // get absolute time
|
||||||
this.time_at_last_tick_message = 0;
|
this.time_at_last_tick_message = 0;
|
||||||
|
|
||||||
this.num_cycles_at_cps_change = 0;
|
|
||||||
this.onToggle = onToggle;
|
|
||||||
this.latency = 0.1; // fixed trigger time offset
|
|
||||||
this.cycle = 0;
|
|
||||||
this.id = Math.round(Date.now() * Math.random());
|
|
||||||
this.worker_time_dif;
|
|
||||||
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
|
|
||||||
this.worker.port.start();
|
|
||||||
|
|
||||||
this.channel = new BroadcastChannel('strudeltick');
|
|
||||||
let weight = 0; // the amount of weight that is applied to the current average when averaging a new time dif
|
|
||||||
const maxWeight = 20;
|
|
||||||
const precision = 10 ** 3; //round off time diff to prevent accumulating outliers
|
|
||||||
|
|
||||||
// the clock of the worker and the audio context clock can drift apart over time
|
// the clock of the worker and the audio context clock can drift apart over time
|
||||||
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
|
// aditionally, the message time of the worker pinging the callback to process haps can be inconsistent.
|
||||||
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
|
// we need to keep a rolling weighted average of the time difference between the worker clock and audio context clock
|
||||||
// in order to schedule events consistently.
|
// in order to schedule events consistently.
|
||||||
const setTimeReference = (num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline) => {
|
this.collator = new ClockCollator({ getTargetClockTime: getTime });
|
||||||
const time_dif = getTime() - (num_seconds_at_cps_change + num_seconds_since_cps_change) + tickdeadline;
|
this.onToggle = onToggle;
|
||||||
if (this.worker_time_dif == null) {
|
this.latency = 0.1; // fixed trigger time offset
|
||||||
this.worker_time_dif = time_dif;
|
this.cycle = 0;
|
||||||
} else {
|
this.id = Math.round(Date.now() * Math.random());
|
||||||
const w = 1; //weight of new time diff;
|
this.worker = new SharedWorker(new URL('./clockworker.js', import.meta.url));
|
||||||
const new_dif =
|
this.worker.port.start();
|
||||||
Math.round(((this.worker_time_dif * weight + time_dif * w) / (weight + w)) * precision) / precision;
|
this.channel = new BroadcastChannel('strudeltick');
|
||||||
|
|
||||||
if (new_dif != this.worker_time_dif) {
|
|
||||||
// reset the weight so the clock recovers faster from an audio context freeze/dropout if it happens
|
|
||||||
weight = 4;
|
|
||||||
}
|
|
||||||
this.worker_time_dif = new_dif;
|
|
||||||
}
|
|
||||||
weight = Math.min(weight + 1, maxWeight);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tickCallback = (payload) => {
|
const tickCallback = (payload) => {
|
||||||
const {
|
const { num_cycles_at_cps_change, cps, begin, end, cycle, time } = payload;
|
||||||
num_cycles_at_cps_change,
|
|
||||||
cps,
|
|
||||||
num_seconds_at_cps_change,
|
|
||||||
num_seconds_since_cps_change,
|
|
||||||
begin,
|
|
||||||
end,
|
|
||||||
tickdeadline,
|
|
||||||
cycle,
|
|
||||||
} = payload;
|
|
||||||
this.cps = cps;
|
this.cps = cps;
|
||||||
this.cycle = cycle;
|
this.cycle = cycle;
|
||||||
|
const currentTime = cycleToSeconds(num_cycles_at_cps_change + this.cycle, this.cps)
|
||||||
setTimeReference(num_seconds_at_cps_change, num_seconds_since_cps_change, tickdeadline);
|
console.log(time, currentTime, this.cycle)
|
||||||
|
processHaps(begin, end, currentTime, num_cycles_at_cps_change);
|
||||||
processHaps(begin, end, num_cycles_at_cps_change, num_seconds_at_cps_change);
|
|
||||||
|
|
||||||
this.time_at_last_tick_message = this.getTime();
|
this.time_at_last_tick_message = this.getTime();
|
||||||
};
|
|
||||||
|
|
||||||
const processHaps = (begin, end, num_cycles_at_cps_change, seconds_at_cps_change) => {
|
};
|
||||||
|
|
||||||
|
const processHaps = (begin, end, currentTime, num_cycles_at_cps_change) => {
|
||||||
if (this.started === false) {
|
if (this.started === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -80,12 +46,10 @@ export class NeoCyclist {
|
|||||||
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
if (hap.hasOnset()) {
|
if (hap.hasOnset()) {
|
||||||
const targetTime =
|
const target = cycleToSeconds(hap.whole.begin - num_cycles_at_cps_change, this.cps)
|
||||||
(hap.whole.begin - num_cycles_at_cps_change) / this.cps +
|
// const target = (hap.whole.begin - num_cycles_at_cps_change) / this.cps;
|
||||||
seconds_at_cps_change +
|
const targetTime = this.collator.calculateTimestamp(currentTime, target) + this.latency;
|
||||||
this.latency +
|
const duration = cycleToSeconds(hap.duration, this.cps)
|
||||||
this.worker_time_dif;
|
|
||||||
const duration = hap.duration / this.cps;
|
|
||||||
onTrigger?.(hap, 0, duration, this.cps, targetTime);
|
onTrigger?.(hap, 0, duration, this.cps, targetTime);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -129,8 +93,8 @@ export class NeoCyclist {
|
|||||||
this.setStarted(true);
|
this.setStarted(true);
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
this.worker_time_dif = null;
|
|
||||||
logger('[cyclist] stop');
|
logger('[cyclist] stop');
|
||||||
|
this.collator.reset()
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
setPattern(pat, autostart = false) {
|
setPattern(pat, autostart = false) {
|
||||||
|
|||||||
@ -363,6 +363,66 @@ export function objectMap(obj, fn) {
|
|||||||
}
|
}
|
||||||
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
|
return Object.fromEntries(Object.entries(obj).map(([k, v], i) => [k, fn(v, k, i)]));
|
||||||
}
|
}
|
||||||
|
export function cycleToSeconds(cycle, cps) {
|
||||||
|
return cycle / cps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility for averaging two clocks together to account for drift
|
||||||
|
export class ClockCollator {
|
||||||
|
constructor({
|
||||||
|
getTargetClockTime = () => Date.now() * 0.001,
|
||||||
|
weight = 16,
|
||||||
|
offsetDelta = 0.005,
|
||||||
|
checkAfterTime = 2,
|
||||||
|
resetAfterTime = 8,
|
||||||
|
}) {
|
||||||
|
this.offsetTime;
|
||||||
|
this.timeAtPrevOffsetSample;
|
||||||
|
this.prevOffsetTimes = [];
|
||||||
|
this.getTargetClockTime = getTargetClockTime;
|
||||||
|
this.weight = weight;
|
||||||
|
this.offsetDelta = offsetDelta;
|
||||||
|
this.checkAfterTime = checkAfterTime;
|
||||||
|
this.resetAfterTime = resetAfterTime;
|
||||||
|
this.reset = () => {
|
||||||
|
this.prevOffsetTimes = [];
|
||||||
|
this.offsetTime = null;
|
||||||
|
this.timeAtPrevOffsetSample = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateTimestamp(currentTime, targetTime) {
|
||||||
|
const targetClockTime = this.getTargetClockTime();
|
||||||
|
const diffBetweenTimeSamples = targetClockTime - this.timeAtPrevOffsetSample;
|
||||||
|
const newOffsetTime = targetClockTime - currentTime;
|
||||||
|
// recalcuate the diff from scratch if the clock has been paused for some time.
|
||||||
|
if (diffBetweenTimeSamples > this.resetAfterTime) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.offsetTime == null) {
|
||||||
|
this.offsetTime = newOffsetTime;
|
||||||
|
}
|
||||||
|
this.prevOffsetTimes.push(newOffsetTime);
|
||||||
|
if (this.prevOffsetTimes.length > this.weight) {
|
||||||
|
this.prevOffsetTimes.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// after X time has passed, the average of the previous weight offset times is calculated and used as a stable reference
|
||||||
|
// for calculating the timestamp
|
||||||
|
if (this.timeAtPrevOffsetSample == null || diffBetweenTimeSamples > this.checkAfterTime) {
|
||||||
|
this.timeAtPrevOffsetSample = targetClockTime;
|
||||||
|
const rollingOffsetTime = averageArray(this.prevOffsetTimes);
|
||||||
|
//when the clock offsets surpass the delta, set the new reference time
|
||||||
|
if (Math.abs(rollingOffsetTime - this.offsetTime) > this.offsetDelta) {
|
||||||
|
this.offsetTime = rollingOffsetTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = this.offsetTime + targetTime;
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Floating point versions, see Fraction for rational versions
|
// Floating point versions, see Fraction for rational versions
|
||||||
// // greatest common divisor
|
// // greatest common divisor
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user