UNPKG

wav-file-encoder

Version:
104 lines (103 loc) 4.21 kB
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 };