UNPKG

pcm-convert

Version:

Convert pcm data from one format to another

286 lines (247 loc) 6.99 kB
/** * @module pcm-convert */ 'use strict' var assert = require('assert') var isBuffer = require('is-buffer') var format = require('audio-format') var extend = require('object-assign') var isAudioBuffer = require('is-audio-buffer') module.exports = convert function convert (buffer, from, to, target) { assert(buffer, 'First argument should be data') assert(from, 'Second argument should be format string or object') //quick ignore if (from === to) { return buffer } //2-containers case if (isContainer(from)) { target = from to = format.detect(target) from = format.detect(buffer) } //if no source format defined, just target format else if (to === undefined && target === undefined) { to = getFormat(from) from = format.detect(buffer) } //if no source format but container is passed with from as target format else if (isContainer(to)) { target = to to = getFormat(from) from = format.detect(buffer) } //all arguments else { var inFormat = getFormat(from) var srcFormat = format.detect(buffer) srcFormat.dtype = inFormat.type === 'arraybuffer' ? srcFormat.type : inFormat.type from = extend(inFormat, srcFormat) var outFormat = getFormat(to) var dstFormat = format.detect(target) if (outFormat.type) { dstFormat.dtype = outFormat.type === 'arraybuffer' ? (dstFormat.type || from.dtype) : outFormat.type } to = extend(outFormat, dstFormat) } if (to.channels == null && from.channels != null) { to.channels = from.channels } if (to.type == null) { to.type = from.type to.dtype = from.dtype } if (to.interleaved != null && from.channels == null) { from.channels = 2 } //ignore same format if (from.type === to.type && from.interleaved === to.interleaved && from.endianness === to.endianness) return buffer normalize(from) normalize(to) //audio-buffer-list/audio types if (buffer.buffers || (buffer.buffer && buffer.buffer.buffers)) { //handle audio if (buffer.buffer) buffer = buffer.buffer //handle audiobufferlist if (buffer.buffers) buffer = buffer.join() } var src //convert buffer/alike to arrayBuffer if (isAudioBuffer(buffer)) { if (buffer._data) src = buffer._data else { src = new Float32Array(buffer.length * buffer.numberOfChannels) for (var c = 0, l = buffer.numberOfChannels; c < l; c++) { src.set(buffer.getChannelData(c), buffer.length * c) } } } else if (buffer instanceof ArrayBuffer) { src = new (dtypeClass[from.dtype])(buffer) } else if (isBuffer(buffer)) { if (buffer.byteOffset != null) src = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); else src = buffer.buffer; src = new (dtypeClass[from.dtype])(src) } //typed arrays are unchanged as is else { src = buffer } //dst is automatically filled with mapped values //but in some cases mapped badly, e. g. float → int(round + rotate) var dst = to.type === 'array' ? Array.from(src) : new (dtypeClass[to.dtype])(src) //if range differ, we should apply more thoughtful mapping if (from.max !== to.max) { var fromRange = from.max - from.min, toRange = to.max - to.min for (var i = 0, l = src.length; i < l; i++) { var value = src[i] //ignore not changed range //bring to 0..1 var normalValue = (value - from.min) / fromRange //bring to new format ranges value = normalValue * toRange + to.min //clamp (buffers do not like values outside of bounds) dst[i] = Math.max(to.min, Math.min(to.max, value)) } } //reinterleave, if required if (from.interleaved != to.interleaved) { var channels = from.channels var len = Math.floor(src.length / channels) //deinterleave if (from.interleaved && !to.interleaved) { dst = dst.map(function (value, idx, data) { var offset = idx % len var channel = ~~(idx / len) return data[offset * channels + channel] }) } //interleave else if (!from.interleaved && to.interleaved) { dst = dst.map(function (value, idx, data) { var offset = ~~(idx / channels) var channel = idx % channels return data[channel * len + offset] }) } } //ensure endianness if (to.dtype != 'array' && to.dtype != 'int8' && to.dtype != 'uint8' && from.endianness && to.endianness && from.endianness !== to.endianness) { var le = to.endianness === 'le' var view = new DataView(dst.buffer) var step = dst.BYTES_PER_ELEMENT var methodName = 'set' + to.dtype[0].toUpperCase() + to.dtype.slice(1) for (var i = 0, l = dst.length; i < l; i++) { view[methodName](i*step, dst[i], le) } } if (to.type === 'audiobuffer') { //TODO } if (target) { if (Array.isArray(target)) { for (var i = 0; i < dst.length; i++) { target[i] = dst[i] } } else if (target instanceof ArrayBuffer) { var targetContainer = new dtypeClass[to.dtype](target) targetContainer.set(dst) target = targetContainer } else { target.set(dst) } dst = target } if (to.type === 'arraybuffer' || to.type === 'buffer') dst = dst.buffer return dst } function getFormat (arg) { return typeof arg === 'string' ? format.parse(arg) : format.detect(arg) } function isContainer (arg) { return typeof arg != 'string' && (Array.isArray(arg) || ArrayBuffer.isView(arg) || arg instanceof ArrayBuffer) } var dtypeClass = { 'uint8': Uint8Array, 'uint8_clamped': Uint8ClampedArray, 'uint16': Uint16Array, 'uint32': Uint32Array, 'int8': Int8Array, 'int16': Int16Array, 'int32': Int32Array, 'float32': Float32Array, 'float64': Float64Array, 'array': Array, 'arraybuffer': Uint8Array, 'buffer': Uint8Array, } var defaultDtype = { 'float32': 'float32', 'audiobuffer': 'float32', 'ndsamples': 'float32', 'ndarray': 'float32', 'float64': 'float64', 'buffer': 'uint8', 'arraybuffer': 'uint8', 'uint8': 'uint8', 'uint8_clamped': 'uint8', 'uint16': 'uint16', 'uint32': 'uint32', 'int8': 'int8', 'int16': 'int16', 'int32': 'int32', 'array': 'array' } //make sure all format properties are present function normalize (obj) { if (!obj.dtype) { obj.dtype = defaultDtype[obj.type] || 'array' } //provide limits switch (obj.dtype) { case 'float32': case 'float64': case 'audiobuffer': case 'ndsamples': case 'ndarray': obj.min = -1 obj.max = 1 break; case 'uint8': obj.min = 0 obj.max = 255 break; case 'uint16': obj.min = 0 obj.max = 65535 break; case 'uint32': obj.min = 0 obj.max = 4294967295 break; case 'int8': obj.min = -128 obj.max = 127 break; case 'int16': obj.min = -32768 obj.max = 32767 break; case 'int32': obj.min = -2147483648 obj.max = 2147483647 break; default: obj.min = -1 obj.max = 1 break; } return obj }