fix(playground): move arpeggiator to Worker thread, throttle synth params
Hand tracking at 30fps was writing 126 params per frame to the C15 ring buffer (capacity 512), flooding it and starving arpeggiator noteOn/noteOff messages — causing stuck/dropped notes. Three-part fix: - Arpeggiator now runs in a dedicated Web Worker with direct SharedArrayBuffer access, bypassing the main thread entirely for note timing. Worker setInterval isn't subject to main thread jank. - RingBufferWriter uses CAS (Atomics.compareExchange) for multi-producer safety — both main thread (params) and worker (notes) write safely. - routeOutputs throttles synth parameter sends: dead-zone filter (0.2% change threshold) + rate cap (~20fps), reducing ring buffer pressure from ~3800 msg/s to ~50-100 msg/s of actual changes.
This commit is contained in:
parent
4fd0b39d26
commit
2fb00f20a9
4 changed files with 270 additions and 123 deletions
|
|
@ -1040,22 +1040,37 @@ function onJoystickMove() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Output routing ----
|
// ---- Output routing ----
|
||||||
|
// Synth param throttling: only send values that changed beyond a dead zone.
|
||||||
|
// Prevents ring buffer flooding (126 params × 30fps = 3780 msg/s > 512 capacity).
|
||||||
|
const _lastSentParams = new Float32Array(N_OUTPUTS);
|
||||||
|
const PARAM_DEAD_ZONE = 0.002; // ~0.2% change threshold
|
||||||
|
let _lastParamSendTime = 0;
|
||||||
|
const PARAM_SEND_INTERVAL = 50; // max ~20fps for synth param updates
|
||||||
|
|
||||||
function routeOutputs(outputs) {
|
function routeOutputs(outputs) {
|
||||||
if (outputMode === 'synth') {
|
if (outputMode === 'synth') {
|
||||||
// Synth mode: synth visualizer + C15
|
|
||||||
// Apply group overrides before visualization and C15
|
|
||||||
const overridden = new Array(outputs.length);
|
const overridden = new Array(outputs.length);
|
||||||
for (let i = 0; i < outputs.length; i++) {
|
for (let i = 0; i < outputs.length; i++) {
|
||||||
overridden[i] = applyGroupOverrides(outputs[i], i);
|
overridden[i] = applyGroupOverrides(outputs[i], i);
|
||||||
}
|
}
|
||||||
|
// Visualizer always gets every frame (it's local, no buffer)
|
||||||
synthVisualizer.setParams(overridden);
|
synthVisualizer.setParams(overridden);
|
||||||
|
|
||||||
|
// C15 ring buffer: throttle + dead-zone filter
|
||||||
if (c15 && c15.running) {
|
if (c15 && c15.running) {
|
||||||
for (let i = 0; i < overridden.length && i < SYNTH_PARAM_MAP.length; i++) {
|
const now = performance.now();
|
||||||
c15.setParameter(SYNTH_PARAM_MAP[i].id, overridden[i]);
|
if (now - _lastParamSendTime >= PARAM_SEND_INTERVAL) {
|
||||||
|
_lastParamSendTime = now;
|
||||||
|
for (let i = 0; i < overridden.length && i < SYNTH_PARAM_MAP.length; i++) {
|
||||||
|
const v = overridden[i];
|
||||||
|
if (Math.abs(v - _lastSentParams[i]) > PARAM_DEAD_ZONE) {
|
||||||
|
c15.setParameter(SYNTH_PARAM_MAP[i].id, v);
|
||||||
|
_lastSentParams[i] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Visual mode: flow field uses first 20
|
|
||||||
visualizer.setParams(outputs.slice(0, N_VISUAL_OUTPUTS));
|
visualizer.setParams(outputs.slice(0, N_VISUAL_OUTPUTS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
138
playground/js/synth/arpeggiator-worker.js
Normal file
138
playground/js/synth/arpeggiator-worker.js
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
// Arpeggiator Worker — runs note scheduling on a dedicated thread
|
||||||
|
// for reliable timing independent of main thread load.
|
||||||
|
// Writes noteOn/noteOff directly to the C15 SharedArrayBuffer ring buffer.
|
||||||
|
|
||||||
|
const MESSAGE_TYPE = { PARAMETER: 0, NOTE_ON: 1, NOTE_OFF: 2 };
|
||||||
|
const HEADER_SIZE = 3;
|
||||||
|
const MESSAGE_SIZE = 4;
|
||||||
|
const RING_CAPACITY = 512;
|
||||||
|
|
||||||
|
const PROGRESSIONS = {
|
||||||
|
'I-vi-IV-V': [[0,4,7],[9,12,16],[5,9,12],[7,11,14]],
|
||||||
|
'I-IV-vi-V': [[0,4,7],[5,9,12],[9,12,16],[7,11,14]],
|
||||||
|
'i-VI-III-VII': [[0,3,7],[8,12,15],[3,7,10],[10,14,17]],
|
||||||
|
'I-V-vi-IV': [[0,4,7],[7,11,14],[9,12,16],[5,9,12]],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ring buffer writer (CAS-safe, matches c15-bridge.js)
|
||||||
|
let ringBuffer = null;
|
||||||
|
let ringF32 = null;
|
||||||
|
let ringI32 = null;
|
||||||
|
|
||||||
|
function ringWrite(type, id, value) {
|
||||||
|
if (!ringI32) return false;
|
||||||
|
for (let attempt = 0; attempt < 4; attempt++) {
|
||||||
|
const writeIdx = Atomics.load(ringI32, 0);
|
||||||
|
const readIdx = Atomics.load(ringI32, 1);
|
||||||
|
const next = (writeIdx + 1) % RING_CAPACITY;
|
||||||
|
if (next === readIdx) return false;
|
||||||
|
if (Atomics.compareExchange(ringI32, 0, writeIdx, next) === writeIdx) {
|
||||||
|
const off = HEADER_SIZE + writeIdx * MESSAGE_SIZE;
|
||||||
|
ringF32[off] = type;
|
||||||
|
ringF32[off + 1] = id;
|
||||||
|
ringF32[off + 2] = value;
|
||||||
|
ringF32[off + 3] = 0;
|
||||||
|
Atomics.add(ringI32, 2, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arpeggiator state
|
||||||
|
let playing = false;
|
||||||
|
let bpm = 120;
|
||||||
|
let octaves = 2;
|
||||||
|
let octaveOffset = 0;
|
||||||
|
let progression = 'I-vi-IV-V';
|
||||||
|
let chordIndex = 0;
|
||||||
|
let noteIndex = 0;
|
||||||
|
let lastNote = -1;
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
function scheduleNext() {
|
||||||
|
if (!playing) return;
|
||||||
|
const msPerBeat = 60000 / bpm;
|
||||||
|
const noteDuration = msPerBeat / 4; // 16th notes
|
||||||
|
playNextNote();
|
||||||
|
timer = setTimeout(scheduleNext, noteDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
function playNextNote() {
|
||||||
|
const chords = PROGRESSIONS[progression] || PROGRESSIONS['I-vi-IV-V'];
|
||||||
|
|
||||||
|
// Release previous note
|
||||||
|
if (lastNote >= 0) {
|
||||||
|
ringWrite(MESSAGE_TYPE.NOTE_OFF, lastNote, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chord = chords[chordIndex];
|
||||||
|
const baseNote = 48 + (octaveOffset * 12);
|
||||||
|
|
||||||
|
const notes = [];
|
||||||
|
for (let oct = 0; oct < octaves; oct++) {
|
||||||
|
for (const interval of chord) {
|
||||||
|
const note = baseNote + interval + (oct * 12);
|
||||||
|
if (note >= 0 && note <= 127) notes.push(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notes.length === 0) return;
|
||||||
|
|
||||||
|
const note = notes[noteIndex % notes.length];
|
||||||
|
const velocity = 0.6 + Math.random() * 0.2;
|
||||||
|
ringWrite(MESSAGE_TYPE.NOTE_ON, note, velocity);
|
||||||
|
lastNote = note;
|
||||||
|
|
||||||
|
noteIndex++;
|
||||||
|
if (noteIndex >= notes.length) {
|
||||||
|
noteIndex = 0;
|
||||||
|
chordIndex = (chordIndex + 1) % chords.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
if (playing) return;
|
||||||
|
playing = true;
|
||||||
|
chordIndex = 0;
|
||||||
|
noteIndex = 0;
|
||||||
|
scheduleNext();
|
||||||
|
postMessage({ type: 'state', playing: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
playing = false;
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
if (lastNote >= 0) {
|
||||||
|
ringWrite(MESSAGE_TYPE.NOTE_OFF, lastNote, 0);
|
||||||
|
lastNote = -1;
|
||||||
|
}
|
||||||
|
postMessage({ type: 'state', playing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle messages from main thread
|
||||||
|
self.onmessage = (e) => {
|
||||||
|
const { type, data } = e.data;
|
||||||
|
switch (type) {
|
||||||
|
case 'init':
|
||||||
|
ringBuffer = data.sharedBuffer;
|
||||||
|
ringF32 = new Float32Array(ringBuffer);
|
||||||
|
ringI32 = new Int32Array(ringBuffer);
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
start();
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
stop();
|
||||||
|
break;
|
||||||
|
case 'set':
|
||||||
|
if ('bpm' in data) bpm = data.bpm;
|
||||||
|
if ('octaves' in data) octaves = data.octaves;
|
||||||
|
if ('octaveOffset' in data) octaveOffset = data.octaveOffset;
|
||||||
|
if ('progression' in data) progression = data.progression;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,121 +1,97 @@
|
||||||
// Arpeggiator — plays chord progressions through the C15 engine
|
// Arpeggiator — plays chord progressions through the C15 engine
|
||||||
// Supports tempo, octave range, octave offset, and multiple chord progressions
|
// Delegates to a dedicated Web Worker for reliable timing.
|
||||||
|
// The worker writes noteOn/noteOff directly to the SharedArrayBuffer
|
||||||
// Chord progressions as arrays of arrays of intervals (semitones from root)
|
// ring buffer, bypassing main thread entirely.
|
||||||
const PROGRESSIONS = {
|
|
||||||
'I-vi-IV-V': [
|
|
||||||
[0, 4, 7], // C major
|
|
||||||
[9, 12, 16], // A minor
|
|
||||||
[5, 9, 12], // F major
|
|
||||||
[7, 11, 14], // G major
|
|
||||||
],
|
|
||||||
'I-IV-vi-V': [
|
|
||||||
[0, 4, 7],
|
|
||||||
[5, 9, 12],
|
|
||||||
[9, 12, 16],
|
|
||||||
[7, 11, 14],
|
|
||||||
],
|
|
||||||
'i-VI-III-VII': [
|
|
||||||
[0, 3, 7], // C minor
|
|
||||||
[8, 12, 15], // Ab major
|
|
||||||
[3, 7, 10], // Eb major
|
|
||||||
[10, 14, 17], // Bb major
|
|
||||||
],
|
|
||||||
'I-V-vi-IV': [
|
|
||||||
[0, 4, 7],
|
|
||||||
[7, 11, 14],
|
|
||||||
[9, 12, 16],
|
|
||||||
[5, 9, 12],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Arpeggiator {
|
export class Arpeggiator {
|
||||||
constructor(bridge) {
|
constructor(bridge) {
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
this.bpm = 120;
|
this._worker = null;
|
||||||
this.octaves = 2; // how many octaves to span
|
this._playing = false;
|
||||||
this.octaveOffset = 0; // base octave shift (-2 to +2)
|
this._bpm = 120;
|
||||||
this.progression = 'I-vi-IV-V';
|
this._octaves = 2;
|
||||||
this.playing = false;
|
this._octaveOffset = 0;
|
||||||
this._timer = null;
|
this._progression = 'I-vi-IV-V';
|
||||||
this._chordIndex = 0;
|
|
||||||
this._noteIndex = 0;
|
|
||||||
this._currentNotes = [];
|
|
||||||
this._lastNote = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get progressionNames() {
|
get progressionNames() {
|
||||||
return Object.keys(PROGRESSIONS);
|
return ['I-vi-IV-V', 'I-IV-vi-V', 'i-VI-III-VII', 'I-V-vi-IV'];
|
||||||
|
}
|
||||||
|
|
||||||
|
get playing() { return this._playing; }
|
||||||
|
|
||||||
|
get bpm() { return this._bpm; }
|
||||||
|
set bpm(v) {
|
||||||
|
this._bpm = v;
|
||||||
|
this._send('set', { bpm: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
get octaves() { return this._octaves; }
|
||||||
|
set octaves(v) {
|
||||||
|
this._octaves = v;
|
||||||
|
this._send('set', { octaves: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
get octaveOffset() { return this._octaveOffset; }
|
||||||
|
set octaveOffset(v) {
|
||||||
|
this._octaveOffset = v;
|
||||||
|
this._send('set', { octaveOffset: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
get progression() { return this._progression; }
|
||||||
|
set progression(v) {
|
||||||
|
this._progression = v;
|
||||||
|
this._send('set', { progression: v });
|
||||||
|
}
|
||||||
|
|
||||||
|
_ensureWorker() {
|
||||||
|
if (this._worker) return;
|
||||||
|
|
||||||
|
this._worker = new Worker(
|
||||||
|
new URL('./arpeggiator-worker.js', import.meta.url),
|
||||||
|
{ type: 'module' }
|
||||||
|
);
|
||||||
|
|
||||||
|
this._worker.onmessage = (e) => {
|
||||||
|
if (e.data.type === 'state') {
|
||||||
|
this._playing = e.data.playing;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pass the SharedArrayBuffer so the worker can write notes directly
|
||||||
|
const sab = this.bridge.sharedBuffer;
|
||||||
|
if (sab) {
|
||||||
|
this._worker.postMessage({ type: 'init', data: { sharedBuffer: sab } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync current settings
|
||||||
|
this._send('set', {
|
||||||
|
bpm: this._bpm,
|
||||||
|
octaves: this._octaves,
|
||||||
|
octaveOffset: this._octaveOffset,
|
||||||
|
progression: this._progression,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(type, data) {
|
||||||
|
if (this._worker) {
|
||||||
|
this._worker.postMessage({ type, data });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.playing) return;
|
this._ensureWorker();
|
||||||
this.playing = true;
|
// If bridge wasn't ready when worker was created, send the buffer now
|
||||||
this._chordIndex = 0;
|
const sab = this.bridge.sharedBuffer;
|
||||||
this._noteIndex = 0;
|
if (sab) {
|
||||||
this._scheduleNext();
|
this._worker.postMessage({ type: 'init', data: { sharedBuffer: sab } });
|
||||||
|
}
|
||||||
|
this._send('start');
|
||||||
|
this._playing = true; // optimistic, worker confirms
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.playing = false;
|
this._send('stop');
|
||||||
if (this._timer) {
|
this._playing = false;
|
||||||
clearTimeout(this._timer);
|
|
||||||
this._timer = null;
|
|
||||||
}
|
|
||||||
// Release current note
|
|
||||||
if (this._lastNote >= 0) {
|
|
||||||
this.bridge.noteOff(this._lastNote);
|
|
||||||
this._lastNote = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_scheduleNext() {
|
|
||||||
if (!this.playing) return;
|
|
||||||
|
|
||||||
const msPerBeat = 60000 / this.bpm;
|
|
||||||
// Each note gets a 16th-note duration, chord changes every bar (4 beats)
|
|
||||||
const noteDuration = msPerBeat / 4;
|
|
||||||
|
|
||||||
this._playNextNote();
|
|
||||||
|
|
||||||
this._timer = setTimeout(() => this._scheduleNext(), noteDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
_playNextNote() {
|
|
||||||
const chords = PROGRESSIONS[this.progression] || PROGRESSIONS['I-vi-IV-V'];
|
|
||||||
|
|
||||||
// Release previous note
|
|
||||||
if (this._lastNote >= 0) {
|
|
||||||
this.bridge.noteOff(this._lastNote);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build arpeggiated note sequence from current chord across octaves
|
|
||||||
const chord = chords[this._chordIndex];
|
|
||||||
const baseNote = 48 + (this.octaveOffset * 12); // C3 as base + offset
|
|
||||||
|
|
||||||
// Build notes across octave range
|
|
||||||
const notes = [];
|
|
||||||
for (let oct = 0; oct < this.octaves; oct++) {
|
|
||||||
for (const interval of chord) {
|
|
||||||
const note = baseNote + interval + (oct * 12);
|
|
||||||
if (note >= 0 && note <= 127) {
|
|
||||||
notes.push(note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notes.length === 0) return;
|
|
||||||
|
|
||||||
// Play the next note in sequence
|
|
||||||
const note = notes[this._noteIndex % notes.length];
|
|
||||||
this.bridge.noteOn(note, 0.6 + Math.random() * 0.2);
|
|
||||||
this._lastNote = note;
|
|
||||||
|
|
||||||
// Advance
|
|
||||||
this._noteIndex++;
|
|
||||||
if (this._noteIndex >= notes.length) {
|
|
||||||
this._noteIndex = 0;
|
|
||||||
this._chordIndex = (this._chordIndex + 1) % chords.length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,37 @@ const HEADER_SIZE = 3;
|
||||||
const MESSAGE_SIZE = 4;
|
const MESSAGE_SIZE = 4;
|
||||||
const RING_CAPACITY = 512;
|
const RING_CAPACITY = 512;
|
||||||
|
|
||||||
|
// Multi-producer-safe ring buffer writer using CAS (compare-and-swap).
|
||||||
|
// Safe for concurrent writes from main thread + arpeggiator Worker.
|
||||||
class RingBufferWriter {
|
class RingBufferWriter {
|
||||||
constructor(sharedBuffer) {
|
constructor(sharedBuffer) {
|
||||||
|
this._sab = sharedBuffer;
|
||||||
this._buffer = new Float32Array(sharedBuffer);
|
this._buffer = new Float32Array(sharedBuffer);
|
||||||
this._int32 = new Int32Array(sharedBuffer);
|
this._int32 = new Int32Array(sharedBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
write(type, id, value) {
|
write(type, id, value) {
|
||||||
const writeIdx = Atomics.load(this._int32, 0);
|
// CAS loop: claim a slot atomically
|
||||||
const readIdx = Atomics.load(this._int32, 1);
|
for (let attempt = 0; attempt < 4; attempt++) {
|
||||||
const next = (writeIdx + 1) % RING_CAPACITY;
|
const writeIdx = Atomics.load(this._int32, 0);
|
||||||
if (next === readIdx) return false;
|
const readIdx = Atomics.load(this._int32, 1);
|
||||||
|
const next = (writeIdx + 1) % RING_CAPACITY;
|
||||||
|
if (next === readIdx) return false; // full
|
||||||
|
|
||||||
const off = HEADER_SIZE + writeIdx * MESSAGE_SIZE;
|
// Try to claim this slot
|
||||||
this._buffer[off] = type;
|
if (Atomics.compareExchange(this._int32, 0, writeIdx, next) === writeIdx) {
|
||||||
this._buffer[off + 1] = id;
|
// Won the slot — write data
|
||||||
this._buffer[off + 2] = value;
|
const off = HEADER_SIZE + writeIdx * MESSAGE_SIZE;
|
||||||
this._buffer[off + 3] = 0;
|
this._buffer[off] = type;
|
||||||
|
this._buffer[off + 1] = id;
|
||||||
Atomics.store(this._int32, 0, next);
|
this._buffer[off + 2] = value;
|
||||||
Atomics.add(this._int32, 2, 1);
|
this._buffer[off + 3] = 0;
|
||||||
return true;
|
Atomics.add(this._int32, 2, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Another writer took it — retry
|
||||||
|
}
|
||||||
|
return false; // contention too high
|
||||||
}
|
}
|
||||||
|
|
||||||
writeParameter(paramId, value) {
|
writeParameter(paramId, value) {
|
||||||
|
|
@ -46,6 +56,9 @@ class RingBufferWriter {
|
||||||
writeNoteOff(note, velocity) {
|
writeNoteOff(note, velocity) {
|
||||||
return this.write(MESSAGE_TYPE.NOTE_OFF, note, velocity || 0);
|
return this.write(MESSAGE_TYPE.NOTE_OFF, note, velocity || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Expose the SharedArrayBuffer for passing to Workers */
|
||||||
|
get sharedBuffer() { return this._sab; }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class C15Bridge {
|
export class C15Bridge {
|
||||||
|
|
@ -58,11 +71,15 @@ export class C15Bridge {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.allParams = null;
|
this.allParams = null;
|
||||||
this.activeNotes = new Set();
|
this.activeNotes = new Set();
|
||||||
|
this._sab = null; // SharedArrayBuffer, exposed after start()
|
||||||
this._onStatusChange = null;
|
this._onStatusChange = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
set onStatusChange(fn) { this._onStatusChange = fn; }
|
set onStatusChange(fn) { this._onStatusChange = fn; }
|
||||||
|
|
||||||
|
/** SharedArrayBuffer for the ring buffer — available after start() */
|
||||||
|
get sharedBuffer() { return this._sab; }
|
||||||
|
|
||||||
_status(msg) {
|
_status(msg) {
|
||||||
console.log('[C15]', msg);
|
console.log('[C15]', msg);
|
||||||
this._onStatusChange?.(msg);
|
this._onStatusChange?.(msg);
|
||||||
|
|
@ -106,6 +123,7 @@ export class C15Bridge {
|
||||||
const sabSize = (HEADER_SIZE + RING_CAPACITY * MESSAGE_SIZE) * 4;
|
const sabSize = (HEADER_SIZE + RING_CAPACITY * MESSAGE_SIZE) * 4;
|
||||||
const sab = new SharedArrayBuffer(sabSize);
|
const sab = new SharedArrayBuffer(sabSize);
|
||||||
new Float32Array(sab).fill(0);
|
new Float32Array(sab).fill(0);
|
||||||
|
this._sab = sab;
|
||||||
this.ringWriter = new RingBufferWriter(sab);
|
this.ringWriter = new RingBufferWriter(sab);
|
||||||
|
|
||||||
// Load worklet
|
// Load worklet
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue