array/index.js

/**
 * > Array manipulation functions
 *
 * This module contains helper functions to work with arrays (usually typed arrays,
 * but not required).
 *
 * This module accepts the premise that explicit is better than implicit.
 * For this reason:
 * - The first parameter of all the functions is the number of samples to process.
 * - The last parameter of all modifyng functions is the array to use as output
 * allowing _explicit_ in-place modification
 *
 * [![npm install dsp-array](https://nodei.co/npm/dsp-array.png?mini=true)](https://npmjs.org/package/dsp-array/)
 *
 * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
 *
 * @example
 * var array = require('dsp-array')
 * const sine = array.fill(1024, (x) => Math.sin(0.5 * x))
 *
 * @example
 * // included in dsp-kit package
 * var dsp = require('dsp-kit')
 * dsp.fill(...)
 *
 * @module array
 */

/**
 * Create a typed array (a Float64Array) filled with zeros
 *
 * @param {Integer} size
 * @return {Array} the array
 */
export function zeros (size) { return new Float64Array(size) }

/**
 * Fill an array using a function
 *
 * @param {Number|Array} array - The array (to reuse) or an array length to create one
 * @param {Function} fn - the generator function. It receives the following parameters:
 *
 * - n: a number from [0..1]
 * - index: a number from [0...length]
 * - length: the array length
 *
 * @example
 * const sine = array.fill(10, (x) => Math.sin(x))
 */
export function fill (N, fn, output) {
  if (arguments.length < 3) output = zeros(N)
  for (let n = 0; n < N; n++) output[n] = fn(n, N)
  return output
}

/**
 * Concatenate two arrays
 * @param {Array} arrayA
 * @param {Array} arrayB
 * @param {Array} destination - (Optional) If provided, the length must be
 * _at least_ the sum of the arrayA and arrayB length plus the destOffset
 * @return {Array} destination
 * @example
 * // concat into a new array
 * const arrayC = array.concat(arrayA, arrayB)
 */
export function concat (a, b, dest = null, offset = 0) {
  const al = a.length
  const bl = b.length
  if (dest === null) dest = zeros(al + bl + offset)
  for (let i = 0; i < al; i++) dest[i + offset] = a[i]
  for (let i = 0; i < bl; i++) dest[i + al + offset] = b[i]
  return dest
}

/**
 * Add elements from two arrays. Can work in-place
 *
 * @param {Integer} numberOfSamples - the number of samples to add
 * @param {Array} a - one buffer to add
 * @param {Array} b - the other buffer
 * @param {Array} output - (Optional) the output buffer (or a new one if not provided)
 * @return {Array} the output buffer
 * @example
 * add(10, signalA, signalB)
 * // in-place (store the result in signalA)
 * add(10, signalA, signalB, signalA)
 */
export function add (N, a, b, out) {
  out = out || zeros(N)
  for (var i = 0; i < N; i++) out[i] = a[i] + b[i]
  return out
}

/**
 * Multiply elements from two arrays. Can work in-place
 *
 * @param {Integer} numberOfSamples - the number of samples to add
 * @param {Array} a - one buffer to add
 * @param {Array} b - the other buffer
 * @param {Array} output - (Optional) the output buffer (or a new one if not provided)
 * @return {Array} the output buffer
 * @example
 * mult(10, signalA, signalB)
 * // in-place (store the result in signalA)
 * mult(10, signalA, signalB, signalA)
 */
export function mult (N, a, b, out) {
  out = out || zeros(N)
  for (var i = 0; i < N; i++) out[i] = a[i] * b[i]
  return out
}

/**
 * Substract elements from two arrays. Can work in-place
 *
 * @param {Integer} numberOfSamples - the number of samples to add
 * @param {Array} minuend - the buffer to substract from
 * @param {Array} subtrahend - the buffer to get the numbers to being subtracted
 * @param {Array} output - (Optional) the output buffer (or a new one if not provided)
 * @return {Array} the output buffer
 * @example
 * var signalA = [3, 3, 3, 3]
 * var signalB = [0, 1, 2, 3]
 * substr(10, signalA, signalB) // => [3, 2, 1, 0]
 * // in-place (store the result in signalA)
 * substr(10, signalA, signalB, signalA) // => signalA contains the result
 */
export function substr (N, a, b, out) {
  out = out || zeros(N)
  for (var i = 0; i < N; i++) out[i] = a[i] - b[i]
  return out
}

const isSame = Object.is
/**
 * Round the values of an array to a number of decimals.
 *
 * There are small differences of precission between algorithms. This helper
 * function allows to compare them discarding the precission errors.
 *
 * @function
 * @param {Array} array
 * @param {Integer} decimals - (Optional) the number of decimals (8 by default)
 */
export const round = roundTo(8)

/**
 * Create a function that rounds to the given decimals
 * @param {Integer} decimals - The number of decimals
 * @return {Function} a function
 * @see round
 */
export function roundTo (dec) {
  return function round (arr, n = dec, output) {
    const size = arr.length
    if (!output) output = new Float64Array(size)
    const limit = Math.min(size, output.length)
    const m = Math.pow(10, n)
    for (let i = 0; i < limit; i++) {
      const r = Math.round(arr[i] * m) / m
      output[i] = isSame(r, -0) ? 0 : r
    }
    return output
  }
}

/**
 * Test if the N first elements of an array is true for a given predicate
 *
 * @param {Integer} N - the number of elements to test
 * @param {Function} predicate - a function that receive an element of the
 * array and should return true or false
 * @param {Array} array - the array
 * @return {Boolean}
 *
 * @example
 * var signal = [1, 1, 1, 2, 2, 2]
 * testAll(3, signal, (x) => x === 1) // => true
 * testAll(4, signal, (x) => x === 1) // => false
 */
export function testAll (N, fn, array) {
  for (var i = 0; i < N; i++) {
    if (!fn(array[i])) return false
  }
  return true
}