function generateAudioBuffer(conf) {
if (typeof AudioBuffer === 'undefined')
return new require('audio-buffer')(conf);
return new AudioBuffer(conf);
}
function isInstanceOfAudioBuffer(v) {
return (v.constructor.name === 'AudioBuffer');
}
export class InvalidBufferLengthError extends Error {
constructor(expected, real) {
super(`Expected buffer length is '${expected}' but real is '${real}'`);
}
}
export class InvalidAudioBufferLengthError extends Error {
constructor(expected, real) {
super(`Expected audio buffer length is '${expected}' but real is '${real}'`);
}
}
export class InvalidSampleRateError extends Error {
constructor(expected, real) {
super(`Expected audio buffer sampleRate is '${expected}' but real is '${real}'`);
}
}
export class InvalidNumberOfChannelsError extends Error {
constructor(expected, real) {
super(`Expected audio buffer numberOfChannels is '${expected}' but real is '${real}'`);
}
}
export class InvalidDurationError extends Error {}
class Base {
constructor(conf) {
if (conf.littleEndian)
this.littleEndian = conf.littleEndian;
else
this.littleEndian = true;
}
generateDestinationBuffer(src) {
throw new Error(`This method must be implemented: generateDestinationBuffer`);
}
validate(src, dst) {
throw new Error(`This method must be implemented: generateDestinationBuffer`);
}
each(src, dst) {
throw new Error(`This method must be implemented: generateDestinationBuffer`);
}
checkArgumentExecute(src, dst) {
throw new Error(`This method must be implemented: generateDestinationBuffer`);
}
execute(src, dst) {
this.checkSrcExecute(src);
if (dst === undefined) {
dst = this.generateDestinationBuffer(src);
}
this.checkDstExecute(dst);
this.validate(src, dst);
return this.each(src, dst);
}
}
/** Configuration for Encoder/Decoder
* @property {boolean} littleEndian - Specify whether use littleEndian or not.
*/
export class Configuration {
constructor(littleEndian) {
this.littleEndian = littleEndian;
}
}
/** Serialize {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer AudioBuffer} to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer ArrayBuffer}.
*/
export class Encoder extends Base {
/**
* @param {Configuration} conf Configuration object.
*/
constructor(conf) {
if (!conf) conf = {};
super(conf);
}
generateDestinationBuffer(src) {
return generateDestinationBufferOnEncoder(src);
}
checkSrcExecute(src) {
if (isInstanceOfAudioBuffer(src) === false) { throw new TypeError(`'src' must be instance of AudioBuffer`); }
}
checkDstExecute(dst) {
if (dst instanceof ArrayBuffer === false) { throw new TypeError(`'dst' must be instance of ArrayBuffer`); }
}
each(src, dst) {
return encode(src, dst, this.littleEndian);
}
validate(src, dst) {
validateOnEncoder(src, dst);
}
/**
* Serialize AudioBuffer to ArrayBuffer.
*
* @param {AudioBuffer} src
* @param {ArrayBuffer} [dst] If not specified, generate ArrayBuffer object in this method.
* @return {ArrayBuffer}
* @throws {InvalidBufferLengthError} If dst.byteLength does not match buffer length to store src.
*/
execute(src, dst) {
return super.execute(src, dst);
}
}
function generateDestinationBufferOnEncoder(src) {
return new ArrayBuffer(16 + (src.length * src.numberOfChannels * 4));
}
function validateOnEncoder(src, dst) {
const lengthArrayBuffer =
16 /** 4 * 4 **/ +
(4 * src.length * src.numberOfChannels)
;
// console.log(lengthArrayBuffer);
if (lengthArrayBuffer !== dst.byteLength) { throw new InvalidBufferLengthError(lengthArrayBuffer, dst.byteLength); }
}
function encode(src, dst, littleEndian) {
let dv = new DataView(dst);
dv.setFloat32( 0, src.sampleRate, littleEndian);
dv.setFloat32( 4, src.duration, littleEndian);
dv.setUint32 ( 8, src.length, littleEndian);
dv.setUint32 (12, src.numberOfChannels, littleEndian);
for (let c = 0; c < src.numberOfChannels; c++) {
const f64 = src.getChannelData(c);
for (let i = 0; i < f64.length; i++) {
let j = 16 + (c * src.length * 4) + (i * 4);
dv.setFloat32(j, f64[i], littleEndian);
}
}
return dst;
}
/** Deserialize {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer ArrayBuffer} to {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer AudioBuffer}.
*/
export class Decoder extends Base {
/**
* @param {Configuration} conf Configuration object.
*/
constructor(conf) {
if (!conf) conf = {};
super(conf);
}
generateDestinationBuffer(src) {
return generateDestinationBufferOnDecoder(src, this.littleEndian);
}
checkSrcExecute(src) {
if (src instanceof ArrayBuffer === false) { throw new TypeError(`'src' must be instance of ArrayBuffer`); }
}
checkDstExecute(dst) {
if (isInstanceOfAudioBuffer(dst) === false) { throw new TypeError(`'dst' must be instance of AudioBuffer`); }
}
each(src, dst) {
return decode(src, dst, this.littleEndian);
}
validate(src, dst) {
validateOnDecoder(src, dst, this.littleEndian)
}
/**
* Deserialize ArrayBuffer to AudioBuffer.
*
* @param {ArrayBuffer} src
* @param {AudioBuffer} [dst] If not specified, generate AudioBuffer object in this method.
* @return {AudioBuffer}
* @throws {InvalidAudioBufferLengthError} If the length extracted from 'src' argument does not match dst.length.
* @throws {InvalidSampleRateError} If the sampleRate extracted from 'src' argument does not match dst.sampleRate.
* @throws {InvalidNumberOfChannelsError} If the numberOfChannels extracted from 'src' argument does not match dst.numberOfChannels.
*/
execute(src, dst) {
return super.execute(src, dst);
}
}
function generateDestinationBufferOnDecoder(src, littleEndian) {
let dv = new DataView(src);
return generateAudioBuffer({
sampleRate: dv.getFloat32(0, littleEndian),
length: dv.getUint32(8, littleEndian),
numberOfChannels: dv.getUint32(12, littleEndian),
})
}
function validateOnDecoder(src, dst, littleEndian) {
const dv = new DataView(src);
const sampleRate = dv.getFloat32(0, littleEndian);
const duration = dv.getFloat32(4, littleEndian);
const length = dv.getUint32(8, littleEndian);
const numberOfChannels = dv.getUint32(12, littleEndian);
if (length !== dst.length) { throw new InvalidAudioBufferLengthError(length, dst.length); }
if (sampleRate !== dst.sampleRate) { throw new InvalidSampleRateError(sampleRate, dst.sampleRate); }
if (numberOfChannels !== dst.numberOfChannels) { throw new InvalidNumberOfChannelsError(numberOfChannels, dst.numberOfChannels); }
// if (duration !== dst.duration) { throw new InvalidDurationError(); }
}
function decode(src, dst, littleEndian) {
const dv = new DataView(src);
for (let c = 0; c < dst.numberOfChannels; c++) {
let f32 = new Float32Array(dst.length);
for (let i = 0; i < f32.length; i++) {
let j = 16 + (c * src.length * 4) + (i * 4);
f32[i] = dv.getFloat32(j, littleEndian);
}
dst.copyToChannel(f32, c);
}
return dst;
}
export class ObjectKeyNotFoundError extends Error {
constructor(name, key) {
super(`'${name}' must have '${key}' property`);
}
}
/** Create an instance of ArrayBuffer from AudioBuffer object.
* @function createArrayBuffer
* @param {AudioBuffer} audioBuffer
* @return {ArrayBuffer}
*/
/** Create an instance of ArrayBuffer from specified configuration.
* @function createArrayBuffer
* @param {Object} conf Configuration object for initialization of ArrayBuffer.
* @param {String} conf.length AudioBuffer.length property. {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer see}
* @param {String} conf.numberOfChannels AudioBuffer.numberOfChannels property. {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer see}
* @param {String} conf.sampleRate AudioBuffer.sampleRate property. {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer see}
* @return {ArrayBuffer}
*/
export function createArrayBuffer() {
if (typeof arguments[0] === 'object') {
let o = arguments[0];
if ('length' in o === false) throw new ObjectKeyNotFoundError('object', 'length');
if ('numberOfChannels' in o === false) throw new ObjectKeyNotFoundError('object', 'numberOfChannels');
if ('sampleRate' in o === false) throw new ObjectKeyNotFoundError('object', 'sampleRate');
return generateDestinationBufferOnEncoder(o);
}
throw new TypeError(`'arguments[0]' must be object`);
}