Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions features/preview/synth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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();
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions public/worker/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function handleInitialize(sampleRate, sab) {
control.byteLength + leftBuffer.byteLength,
bufferSize
);
fillThreshold = (bufferSize * 3) / 4;
fillThreshold = bufferSize / 2;

self.postMessage({
type: "initialized",
Expand Down Expand Up @@ -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;
}

Expand Down
9 changes: 7 additions & 2 deletions public/worklet/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down