/* eslint-disable camelcase */ // To allow for p_name style pointer variables var lib = require('../build/libsamplerate.js') var util = require('util') var extend = require('extend') var Transform = require('stream').Transform var e = function (msg) { return new Error(msg) } var Type = { SINC_BEST_QUALITY: 0, SINC_MEDIUM_QUALITY: 1, SINC_FASTEST: 2, ZERO_ORDER_HOLD: 3, LINEAR: 4 } /** * @typedef {Object} Data * @property {number} [ratio] - Conversion ratio for this batch of data * @property {Buffer} data - Buffer of data */ /** * Transform stream that resamples raw PCM audio samples in the Float32 format. * When in object mode, {@link Data}-like objects or Buffers are expected. * This transformer will always output Buffers. * For all audio to be processed, this stream has to be closed when finished. * * @param {object} [opts={}] - Options for the resampler * @param {Type} opts.type - Converter type * @param {number} opts.ratio - Default conversion ratio (output/input) * @param {number} [opts.channels=1] - Number of (interleaved) channels * @param {boolean} [opts.unsafe=false] - Mark this resampler as unsafe.
* Resamplers in unsafe mode generally operate faster and use less memory.
* Warning: {@link #destroy()} MUST be called on an unsafe resampler before * it is garbage collected. Otherwise it will leak memory. It is called * automatically if the underlying transform stream ends ('end' event). * @constructor */ function Resampler (opts) { // Allow use without new if (!(this instanceof Resampler)) return new Resampler(opts) Transform.call(this, {}) opts = extend({ type: Type.SINC_MEDIUM_QUALITY, ratio: 1, channels: 1, unsafe: false }, opts) if (opts.channels < 1) { throw e('channels must be greater than 1') } if (typeof opts.type !== 'number' || opts.type < 0 || opts.type > 4) { throw new TypeError('opts.type must be a number in [0, 4]') } if (typeof opts.ratio !== 'number' || opts.ratio <= 0) { throw new TypeError('opts.ratio must be a positive number') } this._type = opts.type this._ratio = opts.ratio this._channels = opts.channels this._unsafe = opts.unsafe this._inputBufferUsed = 0 this._inputBufferSize = 0 this._inputBuffer = 0 if (this._unsafe) { // In unsafe mode, the global libsamplerate instance is used this._lib = lib.instance this.on('end', this.destroy.bind(this)) } else { // In normal mode, every resampler gets its own instance of the lib this._lib = lib() } // Create SRC state var p_err = this._lib._malloc(4) try { this._state = this._lib._src_new(this._type, this._channels, p_err) if (this._state === 0) { throw this._error(p_err) } } finally { this._lib._free(p_err) } } util.inherits(Resampler, Transform) /** * Handles an error returned by SRC. * * @param {number} p_err - Pointer to the error code * @returns {Error} A new Error object ready to be thrown */ Resampler.prototype._error = function (p_err) { var err = this._lib.HEAPU32[p_err >> 2] var str = this._lib._src_strerror(err) if (str === 0) { return new Error('unknown error') } else { return new Error(this._lib.Pointer_stringify(str)) } } /** * Destroy this resampler. * This method should only be called if this resampler is in unsafe mode. * Any subsequent resampling will result in undefined behavior. */ Resampler.prototype.destroy = function () { if (this._unsafe && this._lib) { this._lib._free(this._inputBuffer) this._state = this._lib._src_delete(this._state) this._lib = null } } Resampler.prototype._addInput = function (buf) { var HEAPU8 = this._lib.HEAPU8 // Dynamically resize input buffer if too small if (buf.length + this._inputBufferUsed > this._inputBufferSize) { this._inputBufferSize = buf.length + this._inputBufferUsed // Create new buffer var newBuffer = this._lib._malloc(this._inputBufferSize) // Copy old data to new buffer HEAPU8.set(HEAPU8.subarray(this._inputBuffer, this._inputBuffer + this._inputBufferUsed), newBuffer) // Replace old buffer this._lib._free(this._inputBuffer) this._inputBuffer = newBuffer } // Copy new data to input buffer HEAPU8.set(buf, this._inputBuffer + this._inputBufferUsed) this._inputBufferUsed += buf.length } Resampler.prototype._transform = function (chunk, encoding, callback) { try { this._process(chunk, false) } catch (err) { return callback(err) } callback(null) } Resampler.prototype._flush = function (callback) { try { this._process(Buffer.alloc(0), true) } catch (err) { return callback(err) } callback(null) } Resampler.prototype._process = function (chunk, end) { var ratio = this._ratio // Handle object mode if (!(chunk instanceof Buffer)) { if (!chunk.data || !(chunk.data instanceof Buffer)) { throw new TypeError('chunk does not have a data property') } if (chunk.ratio) { ratio = chunk.ratio } chunk = chunk.data } this._addInput(chunk) // Prepare data struct var outputFrames = 1 << 20 // Arbitrary value var p_data_out = this._lib._malloc(outputFrames * 4 * this._channels) var p_data = this._lib._malloc(40) this._lib.HEAP32[(p_data + 4) >> 2] = p_data_out this._lib.HEAP32[(p_data + 12) >> 2] = outputFrames this._lib.HEAP32[(p_data + 24) >> 2] = 0 this._lib.HEAPF64[(p_data + 32) >> 3] = ratio var inputBufferOffset = 0 while (true) { // Set input pointer this._lib.HEAP32[(p_data + 0) >> 2] = this._inputBuffer + inputBufferOffset // Set input length this._lib.HEAP32[(p_data + 8) >> 2] = (this._inputBufferUsed - inputBufferOffset) / 4 / this._channels // Start processing var err = this._lib._src_process(this._state, p_data) if (err) { throw this._error(err) } // Read used input frames inputBufferOffset += this._lib.HEAP32[(p_data + 16) >> 2] * 4 * this._channels // Read generated output frames var len = this._lib.HEAP32[(p_data + 20) >> 2] * 4 * this._channels if (len > 0) { this.push(Buffer.from(this._lib.HEAPU8.slice(p_data_out, p_data_out + len))) } else { // Remove used samples from the input buffer var inputBuffer = this._lib.HEAPU8.subarray(this._inputBuffer, this._inputBuffer + this._inputBufferUsed) inputBuffer.copyWithin(0, inputBufferOffset) this._inputBufferUsed -= inputBufferOffset break } } this._lib._free(p_data_out) this._lib._free(p_data) } Resampler.Type = Type module.exports = Resampler