scheduling in a worker, draft
This commit is contained in:
parent
4511732252
commit
0565558e21
7 changed files with 677 additions and 20 deletions
24
examples/worker-repl/.gitignore
vendored
Normal file
24
examples/worker-repl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
13
examples/worker-repl/index.html
Normal file
13
examples/worker-repl/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Worker REPL</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
<p>click somewhere...</p>
|
||||
</body>
|
||||
</html>
|
||||
89
examples/worker-repl/main.js
Normal file
89
examples/worker-repl/main.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import workerUrl from './worker.mjs?url';
|
||||
import { samples, superdough } from 'superdough';
|
||||
|
||||
let ctx;
|
||||
document.addEventListener('click', async () => {
|
||||
ctx = new AudioContext();
|
||||
await samples('github:tidalcycles/dirt-samples');
|
||||
initWorker();
|
||||
});
|
||||
|
||||
function initWorker() {
|
||||
if (!globalThis.Worker) {
|
||||
throw new Error('initWorker: Worker not supported in this environment');
|
||||
}
|
||||
const worker = new Worker(workerUrl);
|
||||
worker.postMessage({ origin: performance.timeOrigin });
|
||||
// worker.postMessage(1);
|
||||
let origin;
|
||||
let minLatency = 0.05;
|
||||
|
||||
let init = performance.now();
|
||||
|
||||
// performance.timeOrigin; // unix timestamp when the page loads
|
||||
// Date.now() // unix timestamp (ms since January 1st 1970)
|
||||
// performance.timeOrigin // unix timestamp when the page loaded
|
||||
// performance.now() // precise ms since the page loaded
|
||||
// performance.timeOrigin + performance.now() ~= Date.now()
|
||||
|
||||
// worker: performance.now() // precise ms since the worker loaded
|
||||
// worker: performance.timeOrigin // unix timestamp when the worker loaded
|
||||
|
||||
worker.onmessage = (e) => {
|
||||
const { contextTime, performanceTime } = ctx.getOutputTimestamp();
|
||||
console.log('e', e.timeStamp, performance.now());
|
||||
/* // clock offsets
|
||||
const { contextTime: t } = outputTimestamp; // audio clock time in seconds
|
||||
const { performanceTime: now } = outputTimestamp; // estimated system clock time for contextTime
|
||||
const { origin: workerOrigin } = e.data; // seconds when the worker was loaded
|
||||
const { phase } = e.data; // target time in seconds of current tick (relative to system clock)
|
||||
|
||||
let workerOriginDiffMs = workerOrigin - performance.timeOrigin;
|
||||
// console.log('worker created after seconds:', workerOriginDiffMs);
|
||||
|
||||
let workerOriginDiffSeconds = workerOriginDiffMs / 1000;
|
||||
|
||||
let workerPhaseSeconds = phase + workerOriginDiffSeconds;
|
||||
let nowSeconds = now / 1000;
|
||||
|
||||
let workerDeadline = workerPhaseSeconds - nowSeconds; */
|
||||
|
||||
// console.log('workerDeadline', workerDeadline);
|
||||
|
||||
// superdough({ s: 'bd' }, workerDeadline);
|
||||
|
||||
e.data.forEach((event, i) => {
|
||||
let phase = event.phase * 1000;
|
||||
//let phase = event.tick * 100 + origin;
|
||||
let diff = (phase - performanceTime) / 1000;
|
||||
superdough({ s: 'bd' }, contextTime + diff + 2);
|
||||
});
|
||||
|
||||
/* const deadline = phase - now;
|
||||
|
||||
const { tickOrigin } = e.data; // ms when the first tick happened
|
||||
|
||||
const { tick, begin, end, haps, cps } = e.data;
|
||||
|
||||
// console.log('phase', phase);
|
||||
if (tick === 0) {
|
||||
console.log('set origin', t);
|
||||
origin = t;
|
||||
}
|
||||
// console.log('tick', tick, begin, end, phase, origin);
|
||||
haps.forEach((hap) => {
|
||||
let hapBegin = hap.whole.begin;
|
||||
hapBegin = (hapBegin.s * hapBegin.n) / hapBegin.d;
|
||||
const stamp = ctx.getOutputTimestamp();
|
||||
|
||||
let targetTime = origin + hapBegin * cps;
|
||||
let deadline = t - targetTime + minLatency;
|
||||
// console.log('--hap, begin:', begin, 'deadline', deadline);
|
||||
// console.log('hap', hap.value, begin, deadline);
|
||||
superdough(hap.value, deadline);
|
||||
}); */
|
||||
|
||||
// const stamp = ctx?.getOutputTimestamp();
|
||||
// console.log('stamp', stamp);
|
||||
};
|
||||
}
|
||||
15
examples/worker-repl/package.json
Normal file
15
examples/worker-repl/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "worker-repl",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.2.0"
|
||||
},
|
||||
"dependencies": {"superdough":"workspace:*"}
|
||||
}
|
||||
116
examples/worker-repl/worker.mjs
Normal file
116
examples/worker-repl/worker.mjs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// used to consistently schedule events
|
||||
const createClock = (
|
||||
getTime,
|
||||
callback, // called slightly before each cycle
|
||||
duration = 0.05, // duration of each cycle
|
||||
interval = 0.1, // interval between callbacks
|
||||
overlap = 0.1, // overlap between callbacks
|
||||
) => {
|
||||
let tick = 0; // counts callbacks
|
||||
let phase = 0; // next callback time
|
||||
let precision = 10 ** 4; // used to round phase
|
||||
let minLatency = 0;
|
||||
const setDuration = (setter) => (duration = setter(duration));
|
||||
overlap = overlap || interval / 2;
|
||||
const onTick = () => {
|
||||
const t = getTime();
|
||||
const lookahead = t + interval + overlap; // the time window for this tick
|
||||
if (phase === 0) {
|
||||
phase = t + minLatency;
|
||||
}
|
||||
// callback as long as we're inside the lookahead
|
||||
while (phase < lookahead) {
|
||||
// phase = Math.round(phase * precision) / precision;
|
||||
phase >= t && callback(phase, duration, tick, t);
|
||||
phase < t && console.log('TOO LATE', phase); // what if latency is added from outside?
|
||||
phase += duration; // increment phase by duration
|
||||
tick++;
|
||||
}
|
||||
};
|
||||
let intervalID;
|
||||
const start = () => {
|
||||
clear(); // just in case start was called more than once
|
||||
onTick();
|
||||
intervalID = setInterval(onTick, interval * 1000);
|
||||
};
|
||||
const clear = () => intervalID !== undefined && clearInterval(intervalID);
|
||||
const pause = () => clear();
|
||||
const stop = () => {
|
||||
tick = 0;
|
||||
phase = 0;
|
||||
clear();
|
||||
};
|
||||
const getPhase = () => phase;
|
||||
// setCallback
|
||||
return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency };
|
||||
};
|
||||
|
||||
onmessage = async function (e) {
|
||||
console.log('Worker: Message received from main script', e);
|
||||
|
||||
/* const { seq } = await import('@strudel/core');
|
||||
let pat = seq('bd').fast(4).s(); */
|
||||
if (!e.data.origin) {
|
||||
console.log('no origin');
|
||||
return;
|
||||
}
|
||||
console.log('start!', e.data.origin);
|
||||
// e.data.origin = unix ms when main document was created
|
||||
// performance.timeOrigin = unix ms when worker was created
|
||||
let originDiff = (performance.timeOrigin - e.data.origin) / 1000;
|
||||
let firstTick;
|
||||
console.log('originDiff', originDiff);
|
||||
|
||||
let precision = 10 ** 4;
|
||||
let cps = 0.5;
|
||||
let interval = 0.05;
|
||||
let tickOrigin;
|
||||
let lastEnd = 0;
|
||||
let bag = [];
|
||||
let clock = createClock(
|
||||
() => performance.now() / 1000,
|
||||
(phase, duration, tick, t) => {
|
||||
if (tick === 0) {
|
||||
firstTick = t;
|
||||
console.log('firstTick', t);
|
||||
}
|
||||
// let durationInCycles = duration * cps;
|
||||
/* phase = lastEnd; */
|
||||
// phase = Math.round(phase * precision) / precision;
|
||||
|
||||
bag.push({ tick, duration, phase, cps, origin: performance.timeOrigin });
|
||||
//postMessage({ tick, phase, cps, origin: performance.timeOrigin });
|
||||
let pack = 4;
|
||||
if (bag.length === pack) {
|
||||
console.log('send', performance.now());
|
||||
postMessage(bag);
|
||||
bag = [];
|
||||
}
|
||||
|
||||
/* phase += duration;
|
||||
lastEnd = phase; */
|
||||
/*if (tick === 0) {
|
||||
tickOrigin = performance.now();
|
||||
}
|
||||
let begin = lastEnd || 0;
|
||||
let end = begin + duration * cps;
|
||||
lastEnd = end;
|
||||
//console.log('query', begin, phase);
|
||||
const haps = pat.queryArc(begin, end).filter((hap) => hap.hasOnset());
|
||||
if (haps.length) {
|
||||
postMessage({ tick, phase, begin, end, haps, cps, tickOrigin, origin: performance.timeOrigin });
|
||||
}
|
||||
*/
|
||||
/* console.log(
|
||||
'tick',
|
||||
phase,
|
||||
`${begin.toFixed(2)}-${end.toFixed(2)}: ${haps
|
||||
.map((h) => `${h.value.s}:${(h.whole.begin + 0).toFixed(2)}`)
|
||||
.join(' ')}`,
|
||||
); */
|
||||
},
|
||||
interval,
|
||||
interval,
|
||||
);
|
||||
clock.start();
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue