oramics/dsp-kit

View on GitHub
packages/fft-radix2/test/test.js

Summary

Maintainability
A
0 mins
Test Coverage
var almost = require('almost-equal')
const test = require('tst')
const assert = require('assert')
const kdft = require('dsp-dft')
const dspjs = require('dspjs')
const fftRadix2 = require('..').fftRadix2
const { PI, sin, cos } = Math
const arr = require('dsp-array')

test('small signal against dft', function () {
  const size = 8
  const signal = arr.fill(size, (n, N) => sin(n / N) + cos(0.5 * n / N))
  const dftFreqDomain = kdft.dft('forward', signal)
  const fftFreqDomain = fftRadix2(size).forward(signal)
  assert.almost(fftFreqDomain.real, dftFreqDomain.real)
  assert.almost(fftFreqDomain.imag, dftFreqDomain.imag)

  // inverse-able
  const dftTimeDomain = kdft.dft('inverse', dftFreqDomain)
  const fftTimeDomain = fftRadix2(size).inverse(fftFreqDomain)
  assert.almost(fftTimeDomain.real, signal)
  assert.almost(fftTimeDomain.real, dftTimeDomain.real)
  assert.almost(fftTimeDomain.imag, new Float32Array(size))
})

test('big random signal against dspjs', () => {
  var SIZE = 1024 * 8
  var ref = new dspjs.FFT(SIZE, 44100)
  var ft = fftRadix2(SIZE)
  var signal = arr.fill(SIZE, () => Math.random() * 2 - 1)
  var refFreqDomain, freqDomain
  var refTimeDomain, timeDomain

  test('forward', () => {
    test('dspjs', () => {
      ref.forward(signal)
      refFreqDomain = { real: ref.real, imag: ref.imag }
    })
    test('dsp-kit', () => {
      freqDomain = ft.forward(signal)
    })
    test('compare', () => assert.almost(freqDomain.real, refFreqDomain.real, 0.01))
  })

  test('inverse', () => {
    test('dspjs', () => {
      ref.inverse(refFreqDomain.real, refFreqDomain.imag)
      refTimeDomain = ref.real
    })
    test('dsp-kit', () => {
      timeDomain = ft.inverse(freqDomain).real
    })
    test('dsp-kit vs. signal', () => assert.almost(timeDomain, signal))
    test.skip('dspjs vs signal', () => assert.almost(refTimeDomain, signal))
    test.skip('dsp-kit vs. dspjs', () => assert.almost(timeDomain, refTimeDomain))
  })
})

test('common signal against ref dspjs', () => {
  const size = 1024

  // create signal
  var signal = arr.fill(size, (n, N) => sin(n / N) - cos(n / N + 0.75 * PI))
  assert.equal(Math.max(...signal), 1.8182117098462318)

  // forward
  var ref = new dspjs.FFT(size, 44100)
  ref.forward(signal)
  const refFreqDomain = { real: ref.real, imag: ref.imag }
  const fftFreqDomain = fftRadix2(size).forward(signal)
  const dftFreqDomain = kdft.dft('forward', signal)

  assert.almost(fftFreqDomain.real, refFreqDomain.real, 0, 'fft vs ref (real)')
  assert.almost(fftFreqDomain.imag, refFreqDomain.imag)
  assert.almost(fftFreqDomain.real, dftFreqDomain.real)
  assert.almost(fftFreqDomain.imag, dftFreqDomain.imag)

  // inverse
  const refTimeDomain = ref.inverse(refFreqDomain.real, refFreqDomain.imag)
  const fftTimeDomain = fftRadix2(size).inverse(fftFreqDomain)
  const dftTimeDomain = kdft.dft('inverse', dftFreqDomain)
  assert.almost(fftTimeDomain.real, refTimeDomain)
  assert.almost(fftTimeDomain.real, dftTimeDomain.real)
  assert.almost(fftTimeDomain.imag, dftTimeDomain.imag)
})

const isNum = (x) => typeof x === 'number' && !isNaN(x)

test('semi real example', function () {
  const SIZE = 1024
  const WINDOW = 128
  const HOP = 40
  const signal = arr.fill(SIZE, (n, N) => sin(2 * PI * (n / (N - 1))))
  const window = arr.fill(WINDOW, (n, N) => 0.5 * (1 - cos(2 * PI * n / (N - 1))))
  const buffer = arr.zeros(WINDOW)
  const win = arr.zeros(WINDOW)
  const complex = { real: arr.zeros(WINDOW), imag: arr.zeros(WINDOW) }
  const fft = fftRadix2(WINDOW)
  var ref = new dspjs.FFT(WINDOW, 44100)
  const result = []
  for (var i = 0; i < SIZE; i += HOP) {
    buffer.set(signal.subarray(i, i + WINDOW))
    assert(arr.testAll(WINDOW, isNum, buffer), 'subarray is numeric')
    arr.mult(buffer, window, win)
    assert(arr.testAll(WINDOW, isNum, win), 'window is numeric')
    fft.forward(buffer, complex)
    ref.forward(buffer)
    assert.almost(complex.real, ref.real)
    result.push(arr.testAll(WINDOW, isNum, complex.real))
  }
  const num = Math.floor(SIZE / HOP) + 1
  assert.equal(result.length, num)
  const expected = arr.fill(num, () => true)
  assert.almost(result, expected)
})

var EPSILON = 10e-4
assert.almost = function (x, y, epsilon, message) {
  epsilon = epsilon || EPSILON
  if (x.length && y.length) {
    return x.every(function (x, i) {
      return assert.almost(x, y[i], epsilon, (message || '') + ' - index: ' + i)
    })
  }

  if (!almost(x, y, epsilon)) assert.fail(x, y, `${x} ≈ ${y}`, '≈ ' + message + '(' + epsilon + ')')
  return true
}