class UltraFastPCMBuffer {
constructor(maxSamples, channels) {
this.maxSamples = maxSamples;
this.channels = channels;
this.totalSize = maxSamples * channels;
this.buffer = new Float32Array(this.totalSize);
this.readPos = 0;
this.writePos = 0;
this.availableSamples = 0;
console.log(`Ultra-fast PCM buffer: ${maxSamples} samples (~${(maxSamples/48000*1000).toFixed(1)}ms), ${channels} channels`);
}
dropOldestSamples(samplesToDrop) {
if (samplesToDrop <= 0) return 0;
const drop = Math.min(samplesToDrop, this.availableSamples);
this.readPos = (this.readPos + drop) % this.maxSamples;
this.availableSamples -= drop;
return drop;
}
ensureCapacityFor(frameLength, highWatermark, lowWatermark) {
let dropped = 0;
if (this.availableSamples >= highWatermark) {
const target = Math.max(0, Math.min(lowWatermark, this.maxSamples));
const toDrop = this.availableSamples - target;
dropped += this.dropOldestSamples(toDrop);
}
const overflow = (this.availableSamples + frameLength) - this.maxSamples;
if (overflow > 0) {
dropped += this.dropOldestSamples(overflow);
}
return dropped;
}
pushInterleaved(data) {
const frameLength = data.length / this.channels;
if (this.availableSamples + frameLength > this.maxSamples) {
return false; }
if (this.channels === 1) {
const writeStart = this.writePos;
if (writeStart + frameLength <= this.maxSamples) {
this.buffer.set(data, writeStart);
} else {
const firstChunk = this.maxSamples - writeStart;
this.buffer.set(data.subarray(0, firstChunk), writeStart);
this.buffer.set(data.subarray(firstChunk), 0);
}
} else {
const channelOffset = this.maxSamples;
for (let i = 0; i < frameLength; i++) {
const writeIndex = (this.writePos + i) % this.maxSamples;
this.buffer[writeIndex] = data[i * 2]; this.buffer[channelOffset + writeIndex] = data[i * 2 + 1]; }
}
this.writePos = (this.writePos + frameLength) % this.maxSamples;
this.availableSamples += frameLength;
return true;
}
pullToChannels(leftOut, rightOut) {
const frameLength = leftOut.length;
if (this.availableSamples < frameLength) {
leftOut.fill(0);
if (this.channels > 1) rightOut.fill(0);
return false;
}
if (this.channels === 1) {
if (this.readPos + frameLength <= this.maxSamples) {
leftOut.set(this.buffer.subarray(this.readPos, this.readPos + frameLength));
} else {
const firstChunk = this.maxSamples - this.readPos;
leftOut.set(this.buffer.subarray(this.readPos, this.maxSamples));
leftOut.set(this.buffer.subarray(0, frameLength - firstChunk), firstChunk);
}
if (rightOut !== leftOut) rightOut.set(leftOut);
} else {
const channelOffset = this.maxSamples;
if (this.readPos + frameLength <= this.maxSamples) {
leftOut.set(this.buffer.subarray(this.readPos, this.readPos + frameLength));
rightOut.set(this.buffer.subarray(channelOffset + this.readPos, channelOffset + this.readPos + frameLength));
} else {
const firstChunk = this.maxSamples - this.readPos;
leftOut.set(this.buffer.subarray(this.readPos, this.maxSamples));
leftOut.set(this.buffer.subarray(0, frameLength - firstChunk), firstChunk);
rightOut.set(this.buffer.subarray(channelOffset + this.readPos, channelOffset + this.maxSamples));
rightOut.set(this.buffer.subarray(channelOffset, channelOffset + frameLength - firstChunk), firstChunk);
}
}
this.readPos = (this.readPos + frameLength) % this.maxSamples;
this.availableSamples -= frameLength;
return true;
}
reset() {
this.readPos = 0;
this.writePos = 0;
this.availableSamples = 0;
}
}
class PCMPlayerProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.buffer = new UltraFastPCMBuffer(4096 * 3, 1); this.sampleRate = 48000;
this.channels = 1;
this.highWatermarkRatio = 0.80; this.lowWatermarkRatio = 0.50; this.lastDropWarnTime = 0;
this.dropWarnCooldownMs = 1000;
console.log('Ultra-Fast PCM Player Worklet initialized - JavaScript optimized for maximum performance');
this.port.onmessage = (event) => this.handleMessage(event.data);
}
handleMessage(data) {
const { command, pcm, sampleRate, channels } = data;
switch (command) {
case 'configure':
this.sampleRate = sampleRate || 48000;
this.channels = channels || 2;
this.buffer = new UltraFastPCMBuffer(4096 * 2, this.channels);
break;
case 'play':
if (pcm && pcm instanceof Float32Array) {
const frameLength = pcm.length / this.channels;
const highWM = Math.floor(this.buffer.maxSamples * this.highWatermarkRatio);
const lowWM = Math.floor(this.buffer.maxSamples * this.lowWatermarkRatio);
const dropped = this.buffer.ensureCapacityFor(frameLength, highWM, lowWM);
if (dropped > 0) {
const now = Date.now();
if (now - this.lastDropWarnTime >= this.dropWarnCooldownMs) {
console.warn(`PCM buffer high-watermark: dropped ${dropped} samples to cap latency (avail=${this.buffer.availableSamples}/${this.buffer.maxSamples})`);
this.lastDropWarnTime = now;
}
}
const success = this.buffer.pushInterleaved(pcm);
if (!success) {
const now = Date.now();
if (now - this.lastDropWarnTime >= this.dropWarnCooldownMs) {
console.warn('Failed to enqueue PCM data: frame larger than buffer capacity');
this.lastDropWarnTime = now;
}
}
}
break;
case 'flush':
this.buffer.reset();
break;
}
}
process(inputs, outputs, parameters) {
const output = outputs[0];
if (output.length >= 2) {
this.buffer.pullToChannels(output[0], output[1]);
} else {
this.buffer.pullToChannels(output[0], output[0]);
}
return true;
}
static get parameterDescriptors() {
return [];
}
}
registerProcessor('pcm-player', PCMPlayerProcessor);