audio-buffer
Version:
Audio data container
193 lines (163 loc) • 6.09 kB
JavaScript
/**
* AudioBuffer class
*
* @module audio-buffer/buffer
*/
'use strict'
var isBuffer = require('is-buffer')
var b2ab = require('buffer-to-arraybuffer')
var isBrowser = require('is-browser')
var isAudioBuffer = require('is-audio-buffer')
var context = require('audio-context')
var isPlainObj = require('is-plain-obj')
module.exports = AudioBuffer
/**
* @constructor
*
* @param {∀} data Any collection-like object
*/
function AudioBuffer (channels, data, sampleRate, options) {
//enforce class
if (!(this instanceof AudioBuffer)) return new AudioBuffer(channels, data, sampleRate, options);
//detect last argument
var c = arguments.length
while (!arguments[c] && c) c--;
var last = arguments[c];
//figure out options
var ctx, isWAA, floatArray, isForcedType = false
if (last && typeof last != 'number') {
ctx = last.context || context
isWAA = last.isWAA != null ? last.isWAA : !!(isBrowser && context.createBuffer)
floatArray = last.floatArray || Float32Array
if (last.floatArray) isForcedType = true
}
else {
ctx = context
isWAA = false
floatArray = Float32Array
}
//if one argument only - it is surely data or length
//having new AudioBuffer(2) does not make sense as 2 being number of channels
if (data == null || isPlainObj(data)) {
data = channels || 1;
channels = null;
}
//audioCtx.createBuffer() - complacent arguments
else {
if (typeof sampleRate == 'number') this.sampleRate = sampleRate;
else if (isBrowser) this.sampleRate = ctx.sampleRate;
if (channels != null) this.numberOfChannels = channels;
}
//if AudioBuffer(channels?, number, rate?) = create new array
//this is the default WAA-compatible case
if (typeof data === 'number') {
this.length = data;
this.data = []
for (var c = 0; c < this.numberOfChannels; c++) {
this.data[c] = new floatArray(data)
}
}
//if other audio buffer passed - create fast clone of it
//if WAA AudioBuffer - get buffer’s data (it is bounded)
else if (isAudioBuffer(data)) {
this.length = data.length;
if (channels == null) this.numberOfChannels = data.numberOfChannels;
if (sampleRate == null) this.sampleRate = data.sampleRate;
this.data = []
//copy channel's data
for (var c = 0, l = this.numberOfChannels; c < l; c++) {
this.data[c] = data.getChannelData(c).slice()
}
}
//TypedArray, Buffer, DataView etc, or ArrayBuffer
//NOTE: node 4.x+ detects Buffer as ArrayBuffer view
else if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer || isBuffer(data)) {
if (isBuffer(data)) {
data = b2ab(data);
}
//convert non-float array to floatArray
if (!(data instanceof Float32Array) && !(data instanceof Float64Array)) {
data = new floatArray(data.buffer || data);
}
this.length = Math.floor(data.length / this.numberOfChannels);
this.data = []
for (var c = 0; c < this.numberOfChannels; c++) {
this.data[c] = data.subarray(c * this.length, (c + 1) * this.length);
}
}
//if array - parse channeled data
else if (Array.isArray(data)) {
//if separated data passed already - send sub-arrays to channels
if (data[0] instanceof Object) {
if (channels == null) this.numberOfChannels = data.length;
this.length = data[0].length;
this.data = []
for (var c = 0; c < this.numberOfChannels; c++ ) {
this.data[c] = (!isForcedType && ((data[c] instanceof Float32Array) || (data[c] instanceof Float64Array))) ? data[c] : new floatArray(data[c])
}
}
//plain array passed - split array equipartially
else {
this.length = Math.floor(data.length / this.numberOfChannels);
this.data = []
for (var c = 0; c < this.numberOfChannels; c++) {
this.data[c] = new floatArray(data.slice(c * this.length, (c + 1) * this.length))
}
}
}
//if ndarray, typedarray or other data-holder passed - redirect plain databuffer
else if (data && (data.data || data.buffer)) {
return new AudioBuffer(this.numberOfChannels, data.data || data.buffer, this.sampleRate);
}
//if other - unable to parse arguments
else {
throw Error('Failed to create buffer: check provided arguments');
}
//for browser - return WAA buffer, no sub-buffering allowed
if (isWAA) {
//create WAA buffer
var audioBuffer = ctx.createBuffer(this.numberOfChannels, this.length, this.sampleRate);
//fill channels
for (var c = 0; c < this.numberOfChannels; c++) {
audioBuffer.getChannelData(c).set(this.getChannelData(c));
}
return audioBuffer;
}
this.duration = this.length / this.sampleRate;
}
/**
* Default params
*/
AudioBuffer.prototype.numberOfChannels = 2;
AudioBuffer.prototype.sampleRate = context.sampleRate || 44100;
/**
* Return data associated with the channel.
*
* @return {Array} Array containing the data
*/
AudioBuffer.prototype.getChannelData = function (channel) {
//FIXME: ponder on this, whether we really need that rigorous check, it may affect performance
if (channel >= this.numberOfChannels || channel < 0 || channel == null) throw Error('Cannot getChannelData: channel number (' + channel + ') exceeds number of channels (' + this.numberOfChannels + ')');
return this.data[channel]
};
/**
* Place data to the destination buffer, starting from the position
*/
AudioBuffer.prototype.copyFromChannel = function (destination, channelNumber, startInChannel) {
if (startInChannel == null) startInChannel = 0;
var data = this.data[channelNumber]
for (var i = startInChannel, j = 0; i < this.length && j < destination.length; i++, j++) {
destination[j] = data[i];
}
}
/**
* Place data from the source to the channel, starting (in self) from the position
* Clone of WAAudioBuffer
*/
AudioBuffer.prototype.copyToChannel = function (source, channelNumber, startInChannel) {
var data = this.data[channelNumber]
if (!startInChannel) startInChannel = 0;
for (var i = startInChannel, j = 0; i < this.length && j < source.length; i++, j++) {
data[i] = source[j];
}
};