wav-file-encoder
Version:
A simple encoder for WAV audio files
104 lines (103 loc) • 4.21 kB
JavaScript
export function encodeWavFileFromAudioBuffer(audioBuffer, wavFileType) {
const numberOfChannels = audioBuffer.numberOfChannels;
const numberOfFrames = audioBuffer.length;
const sampleRate = audioBuffer.sampleRate;
const channelData = Array(numberOfChannels);
for (let channelNo = 0; channelNo < numberOfChannels; channelNo++) {
channelData[channelNo] = audioBuffer.getChannelData(channelNo);
if (channelData[channelNo].length != numberOfFrames) {
throw new Error("Unexpected channel data array size.");
}
}
return encodeWavFileFromArrays(channelData, sampleRate, wavFileType);
}
export function encodeWavFileFromArrays(channelData, sampleRate, wavFileType) {
const numberOfChannels = channelData.length;
if (numberOfChannels < 1) {
throw new Error("No audio channels.");
}
const numberOfFrames = channelData[0].length;
let bitsPerSample;
let formatCode;
let fmtChunkSize;
let writeSampleData;
switch (wavFileType) {
case 0: {
bitsPerSample = 16;
formatCode = 1;
fmtChunkSize = 16;
writeSampleData = writeSampleData_int16;
break;
}
case 1: {
bitsPerSample = 32;
formatCode = 3;
fmtChunkSize = 18;
writeSampleData = writeSampleData_float32;
break;
}
default: {
throw new Error();
}
}
const bytesPerSample = Math.ceil(bitsPerSample / 8);
const bytesPerFrame = numberOfChannels * bytesPerSample;
const bytesPerSec = sampleRate * numberOfChannels * bytesPerSample;
const headerLength = 20 + fmtChunkSize + 8;
const sampleDataLength = numberOfChannels * numberOfFrames * bytesPerSample;
const fileLength = headerLength + sampleDataLength;
const arrayBuffer = new ArrayBuffer(fileLength);
const dataView = new DataView(arrayBuffer);
writeWavFileHeader();
writeSampleData();
return arrayBuffer;
function writeWavFileHeader() {
setString(0, "RIFF");
dataView.setUint32(4, fileLength - 8, true);
setString(8, "WAVE");
setString(12, "fmt ");
dataView.setUint32(16, fmtChunkSize, true);
dataView.setUint16(20, formatCode, true);
dataView.setUint16(22, numberOfChannels, true);
dataView.setUint32(24, sampleRate, true);
dataView.setUint32(28, bytesPerSec, true);
dataView.setUint16(32, bytesPerFrame, true);
dataView.setUint16(34, bitsPerSample, true);
if (fmtChunkSize > 16) {
dataView.setUint16(36, 0, true);
}
const p = 20 + fmtChunkSize;
setString(p, "data");
dataView.setUint32(p + 4, sampleDataLength, true);
}
function writeSampleData_int16() {
let offs = headerLength;
for (let frameNo = 0; frameNo < numberOfFrames; frameNo++) {
for (let channelNo = 0; channelNo < numberOfChannels; channelNo++) {
const sampleValueFloat = channelData[channelNo][frameNo];
const sampleValueInt16 = convertFloatSampleToInt16(sampleValueFloat);
dataView.setInt16(offs, sampleValueInt16, true);
offs += 2;
}
}
}
function writeSampleData_float32() {
let offs = headerLength;
for (let frameNo = 0; frameNo < numberOfFrames; frameNo++) {
for (let channelNo = 0; channelNo < numberOfChannels; channelNo++) {
const sampleValueFloat = channelData[channelNo][frameNo];
dataView.setFloat32(offs, sampleValueFloat, true);
offs += 4;
}
}
}
function convertFloatSampleToInt16(v) {
return Math.max(-32768, Math.min(32767, Math.round(v * 32768)));
}
function setString(offset, value) {
for (let p = 0; p < value.length; p++) {
dataView.setUint8(offset + p, value.charCodeAt(p));
}
}
}
export { encodeWavFileFromAudioBuffer as encodeWavFile, encodeWavFileFromArrays as encodeWavFile2 };