node-wav
Version:
High performance WAV file decoder and encoder
292 lines (267 loc) • 8.09 kB
JavaScript
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
const data_decoders = {
pcm8: (buffer, offset, output, channels, samples) => {
let input = new Uint8Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let data = input[pos++] - 128;
output[ch][i] = data < 0 ? data / 128 : data / 127;
}
}
},
pcm16: (buffer, offset, output, channels, samples) => {
let input = new Int16Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let data = input[pos++];
output[ch][i] = data < 0 ? data / 32768 : data / 32767;
}
}
},
pcm24: (buffer, offset, output, channels, samples) => {
let input = new Uint8Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let x0 = input[pos++];
let x1 = input[pos++];
let x2 = input[pos++];
let xx = (x0 + (x1 << 8) + (x2 << 16));
let data = xx > 0x800000 ? xx - 0x1000000 : xx;
output[ch][i] = data < 0 ? data / 8388608 : data / 8388607;
}
}
},
pcm32: (buffer, offset, output, channels, samples) => {
let input = new Int32Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let data = input[pos++];
output[ch][i] = data < 0 ? data / 2147483648 : data / 2147483647;
}
}
},
pcm32f: (buffer, offset, output, channels, samples) => {
let input = new Float32Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch)
output[ch][i] = input[pos++];
}
},
pcm64f: (buffer, offset, output, channels, samples) => {
let input = new Float64Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch)
output[ch][i] = input[pos++];
}
},
};
const data_encoders = {
pcm8: (buffer, offset, input, channels, samples) => {
let output = new Uint8Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
v = ((v * 0.5 + 0.5) * 255) | 0;
output[pos++] = v;
}
}
},
pcm16: (buffer, offset, input, channels, samples) => {
let output = new Int16Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
v = ((v < 0) ? v * 32768 : v * 32767) | 0;
output[pos++] = v;
}
}
},
pcm24: (buffer, offset, input, channels, samples) => {
let output = new Uint8Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
v = ((v < 0) ? 0x1000000 + v * 8388608 : v * 8388607) | 0;
output[pos++] = (v >> 0) & 0xff;
output[pos++] = (v >> 8) & 0xff;
output[pos++] = (v >> 16) & 0xff;
}
}
},
pcm32: (buffer, offset, input, channels, samples) => {
let output = new Int32Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
v = ((v < 0) ? v * 2147483648 : v * 2147483647) | 0;
output[pos++] = v;
}
}
},
pcm32f: (buffer, offset, input, channels, samples) => {
let output = new Float32Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
output[pos++] = v;
}
}
},
pcm64f: (buffer, offset, input, channels, samples) => {
let output = new Float64Array(buffer, offset);
let pos = 0;
for (let i = 0; i < samples; ++i) {
for (let ch = 0; ch < channels; ++ch) {
let v = Math.max(-1, Math.min(input[ch][i], 1));
output[pos++] = v;
}
}
},
};
function lookup(table, bitDepth, floatingPoint) {
let name = 'pcm' + bitDepth + (floatingPoint ? 'f' : '');
let fn = table[name];
if (!fn)
throw new TypeError('Unsupported data format: ' + name);
return fn;
}
function decode(buffer) {
let pos = 0, end = 0;
if (buffer.buffer) {
// If we are handed a typed array or a buffer, then we have to consider the
// offset and length into the underlying array buffer.
pos = buffer.byteOffset;
end = buffer.length;
buffer = buffer.buffer;
} else {
// If we are handed a straight up array buffer, start at offset 0 and use
// the full length of the buffer.
pos = 0;
end = buffer.byteLength;
}
let v = new DataView(buffer);
function u8() {
let x = v.getUint8(pos);
pos++;
return x;
}
function u16() {
let x = v.getUint16(pos, true);
pos += 2;
return x;
}
function u32() {
let x = v.getUint32(pos, true);
pos += 4;
return x;
}
function string(len) {
let str = '';
for (let i = 0; i < len; ++i)
str += String.fromCharCode(u8());
return str;
}
if (string(4) !== 'RIFF')
throw new TypeError('Invalid WAV file');
u32();
if (string(4) !== 'WAVE')
throw new TypeError('Invalid WAV file');
let fmt;
while (pos < end) {
let type = string(4);
let size = u32();
let next = pos + size;
switch (type) {
case 'fmt ':
let formatId = u16();
if (formatId !== 0x0001 && formatId !== 0x0003)
throw new TypeError('Unsupported format in WAV file: ' + formatId.toString(16));
fmt = {
format: 'lpcm',
floatingPoint: formatId === 0x0003,
channels: u16(),
sampleRate: u32(),
byteRate: u32(),
blockSize: u16(),
bitDepth: u16(),
};
break;
case 'data':
if (!fmt)
throw new TypeError('Missing "fmt " chunk.');
let samples = Math.floor(size / fmt.blockSize);
let channels = fmt.channels;
let sampleRate = fmt.sampleRate;
let channelData = [];
for (let ch = 0; ch < channels; ++ch)
channelData[ch] = new Float32Array(samples);
lookup(data_decoders, fmt.bitDepth, fmt.floatingPoint)(buffer, pos, channelData, channels, samples);
return {
sampleRate: sampleRate,
channelData: channelData
};
break;
}
pos = next;
}
}
function encode(channelData, opts) {
let sampleRate = opts.sampleRate || 16000;
let floatingPoint = !!(opts.float || opts.floatingPoint);
let bitDepth = floatingPoint ? 32 : ((opts.bitDepth | 0) || 16);
let channels = channelData.length;
let samples = channelData[0].length;
let buffer = new ArrayBuffer(44 + (samples * channels * (bitDepth >> 3)));
let v = new DataView(buffer);
let pos = 0;
function u8(x) {
v.setUint8(pos++, x);
}
function u16(x) {
v.setUint16(pos, x, true);
pos += 2;
}
function u32(x) {
v.setUint32(pos, x, true);
pos += 4;
}
function string(s) {
for (var i = 0; i < s.length; ++i)
u8(s.charCodeAt(i));
}
// write header
string('RIFF');
u32(buffer.byteLength - 8);
string('WAVE');
// write 'fmt ' chunk
string('fmt ');
u32(16);
u16(floatingPoint ? 0x0003 : 0x0001);
u16(channels);
u32(sampleRate);
u32(sampleRate * channels * (bitDepth >> 3));
u16(channels * (bitDepth >> 3));
u16(bitDepth);
// write 'data' chunk
string('data');
u32(buffer.byteLength - 44);
lookup(data_encoders, bitDepth, floatingPoint)(buffer, pos, channelData, channels, samples);
return Buffer(buffer);
}
module.exports = {
decode: decode,
encode: encode,
};