1// eslint-disable-next-line no-undef 2// TODO: swap below line with above one when firefox supports esm imports in service workers 3// see https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker?retiredLocale=de#browser_compatibility 4// import createClock from './zyklus.mjs'; 5 6function getTime() { 7 const seconds = performance.now() * 0.001; 8 return seconds; 9 // return Math.round(seconds * precision) / precision; 10} 11 12let num_cycles_at_cps_change = 0; 13let num_ticks_since_cps_change = 0; 14let num_seconds_at_cps_change = 0; 15let cps = 0.5; 16// {id: {started: boolean}} 17const clients = new Map(); 18const duration = 0.1; 19const channel = new BroadcastChannel('strudeltick'); 20 21const sendMessage = (type, payload) => { 22 channel.postMessage({ type, payload }); 23}; 24 25const sendTick = (phase, duration, tick, time) => { 26 const num_seconds_since_cps_change = num_ticks_since_cps_change * duration; 27 const tickdeadline = phase - time; 28 const lastTick = time + tickdeadline; 29 const num_cycles_since_cps_change = num_seconds_since_cps_change * cps; 30 const begin = num_cycles_at_cps_change + num_cycles_since_cps_change; 31 const secondsSinceLastTick = time - lastTick - duration; 32 const eventLength = duration * cps; 33 const end = begin + eventLength; 34 const cycle = begin + secondsSinceLastTick * cps; 35 36 sendMessage('tick', { 37 begin, 38 end, 39 cps, 40 time, 41 cycle, 42 }); 43 num_ticks_since_cps_change++; 44}; 45 46//create clock method from zyklus 47const clock = createClock(getTime, sendTick, duration); 48let started = false; 49 50const startClock = (id) => { 51 clients.set(id, { started: true }); 52 if (started) { 53 return; 54 } 55 clock.start(); 56 started = true; 57}; 58const stopClock = async (id) => { 59 clients.set(id, { started: false }); 60 61 const otherClientStarted = Array.from(clients.values()).some((c) => c.started); 62 //dont stop the clock if other instances are running... 63 if (!started || otherClientStarted) { 64 return; 65 } 66 67 clock.stop(); 68 setCycle(0); 69 started = false; 70}; 71 72const setCycle = (cycle) => { 73 num_ticks_since_cps_change = 0; 74 num_cycles_at_cps_change = cycle; 75}; 76 77const processMessage = (message) => { 78 const { type, payload } = message; 79 80 switch (type) { 81 case 'cpschange': { 82 if (payload.cps !== cps) { 83 const num_seconds_since_cps_change = num_ticks_since_cps_change * duration; 84 num_cycles_at_cps_change = num_cycles_at_cps_change + num_seconds_since_cps_change * cps; 85 num_seconds_at_cps_change = num_seconds_at_cps_change + num_seconds_since_cps_change; 86 cps = payload.cps; 87 num_ticks_since_cps_change = 0; 88 } 89 break; 90 } 91 case 'setcycle': { 92 setCycle(payload.cycle); 93 break; 94 } 95 case 'toggle': { 96 if (payload.started) { 97 startClock(message.id); 98 } else { 99 stopClock(message.id); 100 } 101 break; 102 } 103 } 104}; 105 106self.onconnect = function (e) { 107 // the incoming port 108 const port = e.ports[0]; 109 110 port.addEventListener('message', function (e) { 111 processMessage(e.data); 112 }); 113 port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter. 114}; 115 116// used to consistently schedule events, for use in a service worker - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/core/clockworker.mjs> 117function createClock( 118 getTime, 119 callback, // called slightly before each cycle 120 duration = 0.05, // duration of each cycle 121 interval = 0.1, // interval between callbacks 122 overlap = 0.1, // overlap between callbacks 123) { 124 let tick = 0; // counts callbacks 125 let phase = 0; // next callback time 126 let precision = 10 ** 4; // used to round phase 127 let minLatency = 0.01; 128 const setDuration = (setter) => (duration = setter(duration)); 129 overlap = overlap || interval / 2; 130 const onTick = () => { 131 const t = getTime(); 132 const lookahead = t + interval + overlap; // the time window for this tick 133 if (phase === 0) { 134 phase = t + minLatency; 135 } 136 // callback as long as we're inside the lookahead 137 while (phase < lookahead) { 138 phase = Math.round(phase * precision) / precision; 139 phase >= t && callback(phase, duration, tick, t); 140 phase < t && console.log('TOO LATE', phase); // what if latency is added from outside? 141 phase += duration; // increment phase by duration 142 tick++; 143 } 144 }; 145 let intervalID; 146 const start = () => { 147 clear(); // just in case start was called more than once 148 onTick(); 149 intervalID = setInterval(onTick, interval * 1000); 150 }; 151 const clear = () => intervalID !== undefined && clearInterval(intervalID); 152 const pause = () => clear(); 153 const stop = () => { 154 tick = 0; 155 phase = 0; 156 clear(); 157 }; 158 const getPhase = () => phase; 159 // setCallback 160 return { setDuration, start, stop, pause, duration, interval, getPhase, minLatency }; 161}