93 lines
3.0 KiB
JavaScript
93 lines
3.0 KiB
JavaScript
/**
|
|
* Copyright 2026 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* An audio worklet processor that stores the PCM audio data sent from the main thread
|
|
* to a buffer and plays it.
|
|
*/
|
|
class PCMPlayerProcessor extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
|
|
// Init buffer
|
|
this.bufferSize = 24000 * 180; // 24kHz x 180 seconds
|
|
this.buffer = new Float32Array(this.bufferSize);
|
|
this.writeIndex = 0;
|
|
this.readIndex = 0;
|
|
|
|
// Handle incoming messages from main thread
|
|
this.port.onmessage = (event) => {
|
|
// Reset the buffer when 'endOfAudio' message received
|
|
if (event.data.command === 'endOfAudio') {
|
|
this.readIndex = this.writeIndex; // Clear the buffer
|
|
console.log("endOfAudio received, clearing the buffer.");
|
|
return;
|
|
}
|
|
|
|
// Decode the base64 data to int16 array.
|
|
const int16Samples = new Int16Array(event.data);
|
|
|
|
// Add the audio data to the buffer
|
|
this._enqueue(int16Samples);
|
|
};
|
|
}
|
|
|
|
// Push incoming Int16 data into our ring buffer.
|
|
_enqueue(int16Samples) {
|
|
for (let i = 0; i < int16Samples.length; i++) {
|
|
// Convert 16-bit integer to float in [-1, 1]
|
|
const floatVal = int16Samples[i] / 32768;
|
|
|
|
// Store in ring buffer for left channel only (mono)
|
|
this.buffer[this.writeIndex] = floatVal;
|
|
this.writeIndex = (this.writeIndex + 1) % this.bufferSize;
|
|
|
|
// Overflow handling (overwrite oldest samples)
|
|
if (this.writeIndex === this.readIndex) {
|
|
this.readIndex = (this.readIndex + 1) % this.bufferSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The system calls `process()` ~128 samples at a time (depending on the browser).
|
|
// We fill the output buffers from our ring buffer.
|
|
process(inputs, outputs, parameters) {
|
|
|
|
// Write a frame to the output
|
|
const output = outputs[0];
|
|
const framesPerBlock = output[0].length;
|
|
for (let frame = 0; frame < framesPerBlock; frame++) {
|
|
|
|
// Write the sample(s) into the output buffer
|
|
output[0][frame] = this.buffer[this.readIndex]; // left channel
|
|
if (output.length > 1) {
|
|
output[1][frame] = this.buffer[this.readIndex]; // right channel
|
|
}
|
|
|
|
// Move the read index forward unless underflowing
|
|
if (this.readIndex != this.writeIndex) {
|
|
this.readIndex = (this.readIndex + 1) % this.bufferSize;
|
|
}
|
|
}
|
|
|
|
// Returning true tells the system to keep the processor alive
|
|
return true;
|
|
}
|
|
}
|
|
|
|
registerProcessor('pcm-player-processor', PCMPlayerProcessor);
|
|
|