import { EncoderBase } from './EncoderBase.mjs';
/**
* Class containing all WAV file encoding functionality.
* @extends EncoderBase
*/
export class WavFileEncoder extends EncoderBase {
/**
* Constructs a new {@link WavFileEncoder} object.
*/
constructor() {
super();
}
async encode(audioData) {
// Code taken from https://russellgood.com/how-to-convert-audiobuffer-to-audio-file/
const numChannels = audioData.numberOfChannels, length = audioData.length * numChannels * 2 + 44;
const buffer = new ArrayBuffer(length), channels = [];
const view = new DataView(buffer);
let offset = 0, pos = 0;
// Nested helper functions
function setUint16(data) {
view.setUint16(pos, data, true);
pos += 2;
}
function setUint32(data) {
view.setUint32(pos, data, true);
pos += 4;
}
// Write WAVE header
setUint32(0x46464952); // "RIFF"
setUint32(length - 8); // file length - 8
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numChannels);
setUint32(audioData.sampleRate);
setUint32(audioData.sampleRate * 2 * numChannels); // avg. bytes/sec
setUint16(numChannels * 2); // block-align
setUint16(16); // 16-bit (hardcoded in this demo)
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// Write interleaved data
for (let i = 0; i < audioData.numberOfChannels; ++i)
channels.push(audioData.getChannelData(i));
while (pos < length) {
for (let i = 0; i < numChannels; ++i) {
let sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; // scale to 16-bit signed int
view.setInt16(pos, sample, true); // write 16-bit sample
pos += 2;
}
++offset; // next source sample
}
return new Blob([view.buffer], { type: 'audio/wav' });
}
}