diff --git a/features/preview/synth.ts b/features/preview/synth.ts index 8aa2ec4..293fc1d 100644 --- a/features/preview/synth.ts +++ b/features/preview/synth.ts @@ -17,15 +17,26 @@ export type Synthesizer = { changeChip: (chip: AvailableChip) => void; }; -const RING_BUFFER_SIZE = 8192; -const RING_BUFFER_CHANNELS = 2; -const RING_BUFFER_BYTE_SIZE = - RING_BUFFER_SIZE * RING_BUFFER_CHANNELS * Float32Array.BYTES_PER_ELEMENT; -const RING_BUFFER_READ_POS_SIZE = Int32Array.BYTES_PER_ELEMENT; -const RING_BUFFER_WRITE_POS_SIZE = Int32Array.BYTES_PER_ELEMENT; -const RING_BUFFER_CONTROLS_SIZE = - RING_BUFFER_READ_POS_SIZE + RING_BUFFER_WRITE_POS_SIZE; -const SAB_SIZE = RING_BUFFER_CONTROLS_SIZE + RING_BUFFER_BYTE_SIZE; +function calculateRingBufferSize( + time: number, + sampleRate: number +): { + sabSize: number; + bufferSize: number; +} { + const RING_BUFFER_READ_POS_SIZE = Int32Array.BYTES_PER_ELEMENT; + const RING_BUFFER_WRITE_POS_SIZE = Int32Array.BYTES_PER_ELEMENT; + const RING_BUFFER_CONTROLS_SIZE = + RING_BUFFER_READ_POS_SIZE + RING_BUFFER_WRITE_POS_SIZE; + + const RING_BUFFER_CHANNELS = 2; + + const bufferSize = Math.floor(time * sampleRate); + const ringBufferSize = + bufferSize * RING_BUFFER_CHANNELS * Float32Array.BYTES_PER_ELEMENT; + const sabSize = RING_BUFFER_CONTROLS_SIZE + ringBufferSize; + return { sabSize, bufferSize }; +} type KeyOnWorkerRequestMessage = { type: "keyOn"; @@ -140,7 +151,6 @@ export function useSynthesizer() { * * It is necessary to use Atomics API to read and write positions. */ - const sab = new SharedArrayBuffer(SAB_SIZE); const wasmWorker = new Worker("/worker/worker.js"); const audioContext = new AudioContext(); @@ -151,6 +161,9 @@ export function useSynthesizer() { }); synthNode.connect(audioContext.destination); + const { sabSize } = calculateRingBufferSize(0.2, audioContext.sampleRate); + const sab = new SharedArrayBuffer(sabSize); + // Setup message handlers. wasmWorker.onmessage = async ({ data, diff --git a/public/worker/worker.js b/public/worker/worker.js index ff2e79d..6b8dcf2 100644 --- a/public/worker/worker.js +++ b/public/worker/worker.js @@ -80,7 +80,7 @@ function handleInitialize(sampleRate, sab) { control.byteLength + leftBuffer.byteLength, bufferSize ); - fillThreshold = (bufferSize * 3) / 4; + fillThreshold = bufferSize / 2; self.postMessage({ type: "initialized", @@ -177,8 +177,8 @@ function handleGenerate() { let nWritables = (readPos + bufferSize - 1 - writePos) % bufferSize; if (nWritables < fillThreshold) { - const TIMEOUT = 50; - Atomics.wait(control, SAB_READ_POSITION_INDEX, readPos, TIMEOUT); + const TIMEOUT_MSEC = 50; + Atomics.wait(control, SAB_READ_POSITION_INDEX, readPos, TIMEOUT_MSEC); return; } diff --git a/public/worklet/processor.js b/public/worklet/processor.js index db0d1ab..6aebab6 100644 --- a/public/worklet/processor.js +++ b/public/worklet/processor.js @@ -74,17 +74,22 @@ class SynthProcessor extends AudioWorkletProcessor { const nReadables = (writePos + this.bufferSize - readPos) % this.bufferSize; let nReads = Math.min(leftChannel.length, nReadables); + // if (nReads !== leftChannel.length) { + // console.warn("buffer underrun!", leftChannel.length - nReads); + // } let pos = readPos; + let destOffset = 0; while (nReads > 0) { const chunk = Math.min(nReads, this.bufferSize - pos); const end = pos + chunk; - leftChannel.set(this.leftBuffer.subarray(pos, end)); - rightChannel.set(this.rightBuffer.subarray(pos, end)); + leftChannel.set(this.leftBuffer.subarray(pos, end), destOffset); + rightChannel.set(this.rightBuffer.subarray(pos, end), destOffset); nReads -= chunk; pos = end % this.bufferSize; + destOffset += chunk; } // It is unnecessary to fill zero-samples in buffer-underrun