",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/oramics/dsp-kit/issues"
24 | },
25 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-kit/#readme",
26 | "devDependencies": {
27 | "dsp-array": "^0.0.1",
28 | "dsp-dft": "^0.0.1",
29 | "dsp-fft": "^0.0.1",
30 | "dsp-noise": "^0.0.1",
31 | "dsp-fftshift": "^0.0.1",
32 | "dsp-spectrum": "^0.0.1",
33 | "dsp-window": "^0.0.1"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/dsp/rollup.config.js:
--------------------------------------------------------------------------------
1 | import nodeResolve from 'rollup-plugin-node-resolve'
2 | import cleanup from 'rollup-plugin-cleanup'
3 | import babel from 'rollup-plugin-babel'
4 |
5 | export default {
6 | entry: 'index.js',
7 | format: 'cjs',
8 | plugins: [ nodeResolve(), cleanup({ maxEmptyLines: 1 }), babel() ],
9 | dest: 'build/index.js'
10 | }
11 |
--------------------------------------------------------------------------------
/packages/dsp/test/test.js:
--------------------------------------------------------------------------------
1 | const test = require('tst')
2 | const assert = require('assert')
3 | const dsp = require('..')
4 |
5 | test('export all', function () {
6 | assert.equal(Object.keys(dsp).length, 20)
7 | })
8 |
--------------------------------------------------------------------------------
/packages/elastica/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## elastica
4 | > timestretch audio in the browser
5 |
6 | It bridges the web audio api (audio buffers, audio workers) with the
7 | timestretch functions of dsp-kit
8 |
9 | Will be published as an independent module
10 |
11 |
12 |
13 | ### elastica~stretch(factor, buffer, [options]) ⇒ AudioBuffer
14 | Perform time-stretch to an audio buffer
15 |
16 | **Kind**: inner method of [elastica](#module_elastica)
17 | **Returns**: AudioBuffer
- a new audio buffer
18 |
19 | | Param | Type | Description |
20 | | --- | --- | --- |
21 | | factor | Number
| the stretch factor (< 1 reduce duration, > 1 expand duration) |
22 | | buffer | AudioBuffer
| a WebAudio's AudioBuffer |
23 | | [options] | Object
| An optional object with configuration: - {String} algorithm = 'phase-vocoder': the algorithm to be use. Valid values are: 'phase-vocoder', 'ola', 'paul-stretch'. Default: 'phase-vocoder' - {Integer} size = 4096: the frame size - {Integer} hop = 1024: the hop size - {AudioContext} context: the audio context to use (or use 'audio-context' npm package) |
24 |
25 |
--------------------------------------------------------------------------------
/packages/elastica/example/amen-mono.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/elastica/example/amen-mono.wav
--------------------------------------------------------------------------------
/packages/elastica/example/amen-ola.js:
--------------------------------------------------------------------------------
1 | var { print, addCanvas } = require('exemplary')
2 | var h = require('h')
3 | var ac = require('audio-context')
4 | var draw = require('draw-waveform')
5 | var elastica = require('..')
6 | var decodeArrayBuffer = require('./lib/decode-array-buffer')
7 | var player = require('./lib/player.js')
8 |
9 | print('Amen break with OLA timestretch', 'h1')
10 | console.log(elastica)
11 |
12 | fetch('example/amen-mono.wav').then(function (response) {
13 | return response.arrayBuffer()
14 | }).then(decodeArrayBuffer(ac))
15 | .then(function (buffer) {
16 | console.log(buffer)
17 | draw(addCanvas(600), buffer.getChannelData(0))
18 | link('Play', player(buffer))
19 | document.body.appendChild(
20 | h('div',
21 | h('input', {
22 | type: 'range', min: 0.2, max: 2.5, step: 0.1, value: 1.2,
23 | change: function(e) {
24 | var val = e.target.value
25 | document.getElementById('factor').innerText = val
26 | performStretch(val)
27 | }}),
28 | h('span', 'Stretch factor: '),
29 | h('span#factor', '1.2')
30 | )
31 | )
32 | var canvas = addCanvas(600)
33 | function performStretch (factor) {
34 | console.time('ola')
35 | var result = elastica.stretch(1.2, buffer)
36 | console.timeEnd('ola')
37 | draw(canvas, buffer.getChannelData(0))
38 | link('Play', player(result))
39 | }
40 | })
41 |
42 |
43 | function link (text, fn, parent) {
44 | parent = parent || document.body
45 |
46 | var el = document.createElement('a')
47 | el.href = '#'
48 | el.innerText = text
49 | el.onclick = function (e) {
50 | e.preventDefault()
51 | fn(e, el)
52 | }
53 | parent.append(el)
54 | return el
55 | }
56 |
--------------------------------------------------------------------------------
/packages/elastica/example/amen-stereo.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/elastica/example/amen-stereo.wav
--------------------------------------------------------------------------------
/packages/elastica/example/app.js:
--------------------------------------------------------------------------------
1 | /* global fetch */
2 | var { app, html } = require('hyperapp')
3 | var elastica = require('..')
4 | var waa = require('dsp-waa')
5 |
6 | var srcPlayer = waa.player({ loop: true, gain: 0.2 })
7 | var olaPlayer = waa.player({ loop: true, gain: 0.2 })
8 |
9 | function draw (canvas, buffer) {
10 | console.log('draw', canvas, buffer)
11 | waa.drawWaveform(canvas, buffer.getChannelData(0), '#0cc')
12 | }
13 |
14 | const Source = (model, msg) => html`
15 |
16 |
Source
17 |
${model.duration}
18 |
19 |
Play!
20 |
21 | `
22 |
23 | const Overlap = (model, msg) => html`
24 |
25 |
Overlap
26 |
${model.duration}
27 |
28 |
Play!
29 |
30 | `
31 |
32 | const view = (model, msg) => html`
33 |
34 |
Elastica demo
35 | ${model.source ? Source(model.source, msg) : 'loading...'}
36 | ${model.ola ? Overlap(model.ola, msg) : 'waiting for data...'}
37 |
38 | `
39 |
40 | const model = { duration: 0 }
41 |
42 | const update = {
43 | loadSource: (model, buffer) => ({
44 | source: { buffer: buffer, duration: buffer.length / 44100 }
45 | }),
46 | performOla: (model, buffer) => ({
47 | source: model.source,
48 | ola: { buffer, duration: buffer.length / 44100 }
49 | })
50 | }
51 |
52 | const effects = {
53 | playSource: (model, msg, e) => srcPlayer(e),
54 | playOla: (model, msg, e) => olaPlayer(e)
55 | }
56 |
57 | const subs = [
58 | (_, msg) => fetch('example/amen-mono.wav')
59 | .then(waa.decodeArrayBuffer())
60 | .then(buffer => {
61 | console.log('LOAD', buffer)
62 | srcPlayer.buffer = buffer
63 | msg.loadSource(buffer)
64 | return buffer
65 | })
66 | .then(buffer => {
67 | var bufferOLA = elastica.stretch(1, buffer, { algorithm: 'ola' })
68 | olaPlayer.buffer = bufferOLA
69 | msg.performOla(bufferOLA)
70 | })
71 | ]
72 |
73 | const hooks = {
74 | onAction: (prev, next, data) => console.log('ACTION', prev, next, data)
75 | }
76 |
77 | app({ view, model, subs, update, effects, hooks })
78 |
--------------------------------------------------------------------------------
/packages/elastica/example/lib/decode-array-buffer.js:
--------------------------------------------------------------------------------
1 | var ac = require('audio-context')
2 |
3 | module.exports = function decodeArrayBuffer (context) {
4 | context = context || ac
5 | return function (response) {
6 | const next = typeof response.arrayBuffer === 'function'
7 | ? response.arrayBuffer() : Promise.resolve(response)
8 |
9 | return next.then(arrayBuffer => new Promise(function (resolve, reject) {
10 | context.decodeAudioData(arrayBuffer, resolve, reject)
11 | }))
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/elastica/example/lib/player.js:
--------------------------------------------------------------------------------
1 | var ac = require('audio-context')
2 |
3 | module.exports = function player (buffer, context) {
4 | var player = null
5 | return function (e, el) {
6 | if (player) {
7 | console.log('stop')
8 | player.stop()
9 | player = null
10 | if (el) el.innerText = 'Play'
11 | } else {
12 | console.log('playing...')
13 | if (el) el.innerText = 'Stop'
14 | player = play(buffer, true, context)
15 | }
16 | }
17 | }
18 |
19 | function play (buffer, loop, context = ac) {
20 | var source = context.createBufferSource()
21 | source.buffer = buffer
22 | source.connect(context.destination)
23 | if (loop === true) source.loop = true
24 | source.start()
25 | return source
26 | }
27 |
--------------------------------------------------------------------------------
/packages/elastica/example/phase-vocoder.js:
--------------------------------------------------------------------------------
1 | /* global fetch */
2 | var h = require('h')
3 | var waa = require('dsp-waa')
4 | var elastica = require('..')
5 |
6 | const add = (el) => { document.body.appendChild(el); return el }
7 | const canvas = (opts) => h('canvas', opts)
8 |
9 | Promise.resolve('Phase vocoder elastica example')
10 | .then((title) => {
11 | add(h('h1', 'Phase vocoder elastica example'))
12 | })
13 | .then(() => {
14 | // load buffer
15 | add(h('p', 'Loading sound...'))
16 | return fetch('example/amen-mono.wav').then(waa.decodeArrayBuffer())
17 | })
18 | .then((buffer) => {
19 | // draw buffer
20 | var c = canvas({ width: 600, height: 300 })
21 | c.onclick = waa.player(buffer, true)
22 | waa.drawWaveform(add(c), buffer.getChannelData(0))
23 | return buffer
24 | })
25 | .then((buffer) => {
26 | // perform time stretch
27 | return elastica.vocoder(1.2, buffer)
28 | })
29 | .then((stretched) => {
30 | console.log('joder', stretched.length, stretched.length / 44100)
31 | var c = canvas({ width: 600, height: 300 })
32 | c.onclick = waa.player(stretched, true)
33 | waa.drawWaveform(add(c), stretched.getChannelData(0))
34 | })
35 |
--------------------------------------------------------------------------------
/packages/elastica/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > timestretch audio in the browser
3 | *
4 | * It bridges the web audio api (audio buffers, audio workers) with the
5 | * timestretch functions of dsp-kit
6 | *
7 | * Will be published as an independent module
8 | *
9 | * @module elastica
10 | */
11 |
12 | var pv = require('dsp-phase-vocoder')
13 | var ac = require('audio-context')
14 |
15 | /**
16 | * Perform time-stretch to an audio buffer
17 | *
18 | * @param {Number} factor - the stretch factor (< 1 reduce duration, > 1 expand duration)
19 | * @param {AudioBuffer} buffer - a WebAudio's AudioBuffer
20 | * @param {Object} [options] - An optional object with configuration:
21 | *
22 | * - {String} algorithm = 'phase-vocoder': the algorithm to be use.
23 | * Valid values are: 'phase-vocoder', 'ola', 'paul-stretch'. Default: 'phase-vocoder'
24 | * - {Integer} size = 4096: the frame size
25 | * - {Integer} hop = 1024: the hop size
26 | * - {AudioContext} context: the audio context to use (or use 'audio-context' npm package)
27 | *
28 | * @return {AudioBuffer} a new audio buffer
29 | */
30 | function stretch (factor, buffer, options = {}) {
31 | var stretch = pv.phaseVocoder(options)
32 | var data = buffer.getChannelData(0)
33 | var output = stretch(factor, data)
34 | return toAudioBuffer(output)
35 | }
36 |
37 | function toAudioBuffer (left) {
38 | var len = left.length
39 | var buffer = ac.createBuffer(1, len, ac.sampleRate)
40 | var data = buffer.getChannelData(0)
41 | for (var i = 0; i < len; i++) {
42 | data[i] = left[i]
43 | }
44 | return buffer
45 | }
46 |
47 | module.exports = { stretch }
48 |
--------------------------------------------------------------------------------
/packages/elastica/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elastica",
3 | "description": "Audio timestretch for Web Audio API",
4 | "private": true,
5 | "version": "0.0.1",
6 | "main": "index.js",
7 | "module": "index",
8 | "scripts": {
9 | "pretest": "rollup -f cjs -o build/index.js -- index.js",
10 | "test": "node test/test.js",
11 | "docs": "jsdoc2md index.js > README.md && cp README.md API.md",
12 | "example": "budo --open example/example.js",
13 | "example:ola": "budo --open example/amen-ola.js",
14 | "example:phase": "budo --open example/phase-vocoder.js"
15 | },
16 | "repository": "https://github.com/oramics/dsp-kit/packages/elastica",
17 | "keywords": [
18 | "audio",
19 | "timestretch",
20 | "pitchchange",
21 | "dsp"
22 | ],
23 | "author": "danigb",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/oramics/dsp-kit/issues"
27 | },
28 | "homepage": "https://github.com/oramics/dsp-kit/packages/elastica/#readme",
29 | "devDependencies": {
30 | "dsp-waa": "0.0.1",
31 | "audio-context": "^0.1.0",
32 | "h": "^0.1.0",
33 | "inferno": "^1.1.2",
34 | "inferno-hyperscript": "^1.1.2"
35 | },
36 | "dependencies": {
37 | "dsp-array": "0.0.1",
38 | "dsp-ola": "0.0.1",
39 | "dsp-phase-vocoder": "0.0.1"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/elastica/test/test.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/elastica/test/test.js
--------------------------------------------------------------------------------
/packages/fft-asm/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/fft-asm/README.md
--------------------------------------------------------------------------------
/packages/fft-asm/fft-no-asm.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Checks if a number is a power of two
3 | // https://github.com/mikolalysenko/bit-twiddle/blob/master/twiddle.js#L41
4 | function isPow2 (v) { return !(v & (v - 1)) && (!!v) }
5 |
6 | function fft (size, input, output, dir) {
7 | if (arguments.length > 1) return fft(size)(input, dir, output)
8 | var cached = tables(size)
9 |
10 | return function process (dir, input, output) {
11 | // check direction
12 | if (dir !== 'forward' && dir !== 'inverse') throw Error('Direction must be "forward" or "inverse" but was: ' + dir)
13 | var inverse = dir === 'inverse'
14 |
15 | // check input
16 | var rs = input.real || input
17 | var is = input.imag || cached.zeros
18 | if (rs.length !== size) throw Error('Invalid input real length: ' + rs.length + ' (must be ' + size + ')')
19 | if (is.length !== size) throw Error('Invalid input real length: ' + is.length + ' (must be ' + size + ')')
20 |
21 | // check output
22 | var real = output.real
23 | var imag = output.imag
24 | if (real.length !== size) throw Error('Invalid input real length: ' + real.length + ' (must be ' + size + ')')
25 | if (imag.length !== size) throw Error('Invalid input real length: ' + imag.length + ' (must be ' + size + ')')
26 |
27 | var phaseShiftStepReal, phaseShiftStepImag, currentPhaseShiftReal, currentPhaseShiftImag
28 | var off, tr, ti, tmpReal, rev
29 | var halfSize = 1
30 | var cosTable = cached.cosTable
31 | var sinTable = cached.sinTable
32 | var reverseTable = cached.reverseTable
33 |
34 | for (var i = 0; i < size; i++) {
35 | rev = reverseTable[i]
36 | real[i] = rs[rev]
37 | imag[i] = -1 * is[rev]
38 | }
39 |
40 | while (halfSize < size) {
41 | phaseShiftStepReal = cosTable[halfSize]
42 | phaseShiftStepImag = sinTable[halfSize]
43 | currentPhaseShiftReal = 1
44 | currentPhaseShiftImag = 0
45 |
46 | for (var fftStep = 0; fftStep < halfSize; fftStep++) {
47 | i = fftStep
48 |
49 | while (i < size) {
50 | off = i + halfSize
51 | tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off])
52 | ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off])
53 |
54 | real[off] = real[i] - tr
55 | imag[off] = imag[i] - ti
56 | real[i] += tr
57 | imag[i] += ti
58 |
59 | i += halfSize << 1
60 | }
61 |
62 | tmpReal = currentPhaseShiftReal
63 | currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag)
64 | currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal)
65 | }
66 |
67 | halfSize = halfSize << 1
68 | }
69 |
70 | // normalize
71 | if (inverse) {
72 | for (i = 0; i < size; i++) {
73 | real[i] /= size
74 | imag[i] /= size
75 | }
76 | }
77 | return output
78 | }
79 | }
80 |
81 | function tables (size) {
82 | if (!isPow2(size)) throw Error('Size must be a power of 2, and was: ' + size)
83 | var reverseTable = new Uint32Array(size)
84 | var sinTable = new Float32Array(size)
85 | var cosTable = new Float32Array(size)
86 | var zeros = new Float32Array(size)
87 | var limit = 1
88 | var bit = size >> 1
89 | var i
90 |
91 | while (limit < size) {
92 | for (i = 0; i < limit; i++) {
93 | reverseTable[i + limit] = reverseTable[i] + bit
94 | }
95 | limit = limit << 1
96 | bit = bit >> 1
97 | }
98 |
99 | for (i = 0; i < size; i++) {
100 | sinTable[i] = Math.sin(-Math.PI / i)
101 | cosTable[i] = Math.cos(-Math.PI / i)
102 | }
103 | return { reverseTable, sinTable, cosTable, zeros }
104 | }
105 |
106 | module.exports = { fft: fft }
107 |
--------------------------------------------------------------------------------
/packages/fft-asm/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | /* eslint-disable space-infix-ops */
3 | /* eslint-disable semi */
4 |
5 | // Checks if a number is a power of two
6 | // https://github.com/mikolalysenko/bit-twiddle/blob/master/twiddle.js#L41
7 | function isPow2 (v) { return !(v & (v - 1)) && (!!v) }
8 |
9 | function fft (size) {
10 | if (!isPow2(size)) throw Error('Invalid size: ' + size + ' (must be a power of 2)')
11 |
12 | // init asm.js module
13 | var heap = new ArrayBuffer(0x10000)
14 | var asm = FFTModule({
15 | Float32Array: Float32Array,
16 | Uint32Array: Uint32Array,
17 | Math: Math
18 | }, null, heap)
19 | asm.init(size)
20 |
21 | return function (dir, input, output) {
22 | var inverse = dir === 'inverse' ? 1 : 0
23 | asm.process(inverse)
24 | }
25 | }
26 |
27 | function FFTModule (stdlib, foreign, heap) {
28 | 'use asm'
29 | var data = new stdlib.Float32Array(heap)
30 | var inverse = new stdlib.Uint32Array(heap)
31 | var sin = stdlib.Math.sin
32 | var PI = stdlib.Math.PI
33 | var size = 0;
34 |
35 | function init (sz) {
36 | sz = sz|0;
37 | var i = 0;
38 | var bit = 0;
39 | var limit = 1;
40 | bit = sz >> 1
41 | // var limit = 1;
42 | // var bit = 0;
43 | //
44 | // // build the reverse table
45 | // bit = size >> 1
46 | // while (limit < size) {
47 | // for (i = 0; i < limit; i++) {
48 | // inverse[i + limit] = inverse[i] + bit
49 | // }
50 | // limit = limit << 1
51 | // bit = bit >> 1
52 | // }
53 | // limit = 2 * size
54 | // for (var x = size; (x|0) < (limit|0); x = (x + 1)|0) {
55 | // data[x] = sin(-PI / i)
56 | // }
57 | return 0|0
58 | }
59 |
60 | function process (inverse) {
61 | inverse = inverse | 0
62 | }
63 |
64 | return { init: init, process: process }
65 | }
66 |
67 | module.exports = { fft: fft }
68 |
--------------------------------------------------------------------------------
/packages/fft-asm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fft-asm",
3 | "description": "A FFT algorithm in asm.js",
4 | "version": "0.0.1",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node test/test.js",
8 | "benchmark": "node test/benchmark.js",
9 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/fft-asm.md",
10 | "profile": "budo --open test/profile.js"
11 | },
12 | "repository": "https://github.com/oramics/dsp-kit/packages/fft-asm",
13 | "keywords": [
14 | "fft",
15 | "dsp-kit"
16 | ],
17 | "author": "danigb",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/oramics/dsp-kit/issues"
21 | },
22 | "homepage": "https://github.com/oramics/dsp-kit/packages/fft-asm/#readme",
23 | "devDependencies": {
24 | "dsp-array": "^0.0.1",
25 | "dsp-dft": "^0.0.1",
26 | "dspjs": "../../support/dspjs"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/fft-asm/test/benchmark.js:
--------------------------------------------------------------------------------
1 | var Benchmark = require('Benchmark')
2 | var suite = new Benchmark.Suite()
3 | var dspjs = require('dspjs')
4 | var asm = require('..')
5 | var arr = require('dsp-array')
6 |
7 | var SIZE = 1024
8 | var signal = arr.fill(SIZE, () => Math.random() * 2 - 0.5)
9 | var fftjs = new dspjs.FFT(signal.length, 44100)
10 | var asmfft = asm.fft(SIZE)
11 | var output = { real: signal.slice(), imag: arr.zeros(signal.length) }
12 | var zeros = arr.zeros(SIZE)
13 |
14 | suite
15 | .add('fft dsp-kit (asm)', function () {
16 | asmfft('forward', { real: signal, imag: zeros }, output)
17 | })
18 | .add('fft dsp.js', function () {
19 | fftjs.forward(signal)
20 | })
21 | .on('cycle', function (event) {
22 | console.log(String(event.target))
23 | })
24 | .on('complete', function () {
25 | console.log('Fastest is ', this.filter('fastest').map('name'))
26 | })
27 | .on('error', function (e) {
28 | console.error('ERROR', e)
29 | })
30 | .run({ 'async': true })
31 |
--------------------------------------------------------------------------------
/packages/fft-asm/test/profile.js:
--------------------------------------------------------------------------------
1 | /* global performance */
2 | var dspjs = require('dspjs')
3 | var asm = require('..')
4 | var arr = require('dsp-array')
5 |
6 | var TIMES = 10000
7 | var SIZE = 1024
8 | var signal = arr.fill(SIZE, () => Math.random() * 2 - 0.5)
9 | var fft = new dspjs.FFT(signal.length, 44100)
10 | var asmfft = asm.fft(SIZE)
11 | var output = { real: signal.slice(), imag: arr.zeros(signal.length) }
12 | var zeros = arr.zeros(SIZE)
13 |
14 | function addElement (name, profiler) {
15 | var div = document.createElement('div')
16 | div.innerHTML = name
17 | div.onclick = profiler
18 | document.body.appendChild(div)
19 | }
20 |
21 | function profileDSP () {
22 | console.log('dspjs...')
23 | var t0 = performance.now()
24 | for (var i = 0; i < TIMES; i++) {
25 | fft.forward(signal)
26 | }
27 | var t1 = performance.now()
28 | report('DSP', t0, t1)
29 | }
30 | function profileASM () {
31 | console.log('asm version...')
32 | var t0 = performance.now()
33 | var complex = { real: signal, imag: zeros }
34 | for (var i = 0; i < TIMES; i++) {
35 | asmfft('forward', complex, output)
36 | }
37 | var t1 = performance.now()
38 | report('ASM', t0, t1)
39 | }
40 |
41 | function report (name, t0, t1) {
42 | console.log(name + ' time: ', t1 - t0)
43 | }
44 |
45 | addElement('dsp.js', profileDSP)
46 | addElement('asm.js', profileASM)
47 |
--------------------------------------------------------------------------------
/packages/fft-asm/test/test.js:
--------------------------------------------------------------------------------
1 | var test = require('tst')
2 | var assert = require('assert')
3 | var almost = require('almost-equal')
4 | var arr = require('dsp-array')
5 | var asm = require('..')
6 | var dspjs = require('dspjs')
7 | var dft = require('dsp-dft')
8 |
9 | var SIZE = 1024
10 | var signal = arr.fill(SIZE, () => Math.random() * 2 - 0.5)
11 |
12 | function calcDFT (signal) {
13 | return dft.dft('forward', signal)
14 | }
15 |
16 | function calcFFT (signal) {
17 | var fft = new dspjs.FFT(signal.length, 44100)
18 | fft.forward(signal)
19 | return { real: fft.real, imag: fft.imag }
20 | }
21 |
22 | function calcASM (signal) {
23 | var len = signal.length
24 | var fft = asm.fft(len)
25 | return fft(signal, { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }, 'forward')
26 | }
27 |
28 | test.skip('test inverse', function () {
29 | var fft = new dspjs.FFT(SIZE, 44100)
30 | fft.forward(signal)
31 | var result = fft.inverse(fft.real, fft.imag)
32 | assert.almost(result, signal)
33 |
34 | var asmFFT = asm.fft(SIZE)
35 | var output = { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }
36 | var inverse = { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }
37 | asmFFT(signal, output, 'forward')
38 | asmFFT(output, inverse, 'inverse')
39 | assert.almost(inverse.real, signal)
40 | })
41 |
42 | test.skip('test forward', function () {
43 | var dft = calcDFT(signal)
44 | var fft = calcFFT(signal)
45 | var asm = calcASM(signal)
46 | assert.almost(fft.real, dft.real, 'fft real')
47 | assert.almost(fft.imag, dft.imag, 'fft imag')
48 | assert.almost(asm.real, fft.real, 'asm real')
49 | assert.almost(asm.imag, fft.imag, 'asm imag')
50 | })
51 |
52 | assert.almost = function (x, y, message) {
53 | if (x.length && y.length) {
54 | return x.every(function (x, i) {
55 | return assert.almost(x, y[i], (message || ' : ') + i)
56 | })
57 | }
58 |
59 | var EPSILON = 10e-8
60 | if (!almost(x, y, EPSILON)) assert.fail(x, y, `${x} ≈ ${y}`, '≈ ' + message)
61 | return true
62 | }
63 |
--------------------------------------------------------------------------------
/packages/fft-asm/versions/arduino.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module is a port of [arduinoFFT](https://github.com/kosme/arduinoFFT/)
3 | * source from C++ to Javascript
4 | *
5 | * Copyright (C) 2010 Didier Longueville
6 | * Copyright (C) 2014 Enrique Condes
7 | * Javascript version by danigb
8 | *
9 | * @module fft-asm
10 | */
11 | var sqrt = Math.sqrt
12 |
13 | export function fft (real, imag, dir) {
14 | dir = dir || 'forward'
15 | var len = real.length
16 | if (imag.length !== len) throw Error('Real and imag parts should have same size, but was ' + len + ' and ' + imag.length)
17 | var power = exponent(len)
18 | compute(real, imag, len, power, dir)
19 | }
20 |
21 | // Computes the exponent of a powered 2 value
22 | function exponent (value) {
23 | var result = 0
24 | while (((value >> result) & 1) !== 1) result++
25 | return result
26 | }
27 |
28 | function compute (real, imag, samples, power, dir) {
29 | /* Computes in-place complex-to-complex FFT */
30 | /* Reverse bits */
31 | var j = 0
32 | for (var i = 0; i < (samples - 1); i++) {
33 | if (i < j) {
34 | swap(real[i], real[j])
35 | swap(imag[i], imag[j])
36 | }
37 | var k = (samples >> 1)
38 | while (k <= j) {
39 | j -= k
40 | k >>= 1
41 | }
42 | j += k
43 | }
44 | /* Compute the FFT */
45 | var c1 = -1.0
46 | var c2 = 0.0
47 | var l2 = 1
48 | for (var l = 0; (l < power); l++) {
49 | var l1 = l2
50 | l2 <<= 1
51 | var u1 = 1.0
52 | var u2 = 0.0
53 | for (j = 0; j < l1; j++) {
54 | for (i = j; i < samples; i += l2) {
55 | var i1 = i + l1
56 | var t1 = u1 * real[i1] - u2 * imag[i1]
57 | var t2 = u1 * imag[i1] + u2 * real[i1]
58 | real[i1] = real[i] - t1
59 | imag[i1] = imag[i] - t2
60 | real[i] += t1
61 | imag[i] += t2
62 | }
63 | var z = ((u1 * c1) - (u2 * c2))
64 | u2 = ((u1 * c2) + (u2 * c1))
65 | u1 = z
66 | }
67 | c2 = sqrt((1.0 - c1) / 2.0)
68 | if (dir !== 'inverse') {
69 | c2 = -c2
70 | }
71 | c1 = sqrt((1.0 + c1) / 2.0)
72 | }
73 | /* Scaling for reverse transform */
74 | if (dir === 'inverse') {
75 | for (i = 0; i < samples; i++) {
76 | real[i] /= samples
77 | imag[i] /= samples
78 | }
79 | }
80 | }
81 |
82 | function swap (x, y) {
83 | var temp = x
84 | x = y
85 | y = temp
86 | }
87 |
--------------------------------------------------------------------------------
/packages/fft-asm/versions/fftc.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | /**
3 | * fft.c
4 | * Douglas L. Jones
5 | * University of Illinois at Urbana-Champaign
6 | * January 19, 1992
7 | * http://cnx.rice.edu/content/m12016/latest/
8 | *
9 | * fft: in-place radix-2 DIT DFT of a complex input
10 | *
11 | * input:
12 | * n: length of FFT: must be a power of two
13 | * m: n = 2**m
14 | * input/output
15 | * x: double array of length n with real part of data
16 | * y: double array of length n with imag part of data
17 | *
18 | * Permission to copy and use this program is granted
19 | * as long as this header is included.
20 | *
21 | * @module fft-asm
22 | */
23 | var sin = Math.sin
24 | var cos = Math.cos
25 |
26 | export function fft (real, imag, dir) {
27 | dir = dir || 'forward'
28 | var n = real.length
29 | if (imag.length !== n) throw Error('Real and imag parts should have same size, but was ' + n + ' and ' + imag.length)
30 | var m = Math.log(n) / Math.log(2)
31 | compute(real, imag, n, m, dir)
32 | }
33 |
34 | function compute (x, y, n, m) {
35 | var i = 0, j = 0, k = 0, n1 = 0, n2 = 0, a = 0;
36 | var c = 0, s = 0, t1 = 0, t2 = 0;
37 |
38 | // Bit-reverse
39 | j = 0;
40 | n2 = n / 2;
41 | for (i = 1; i < n - 1; i++) {
42 | n1 = n2;
43 | while (j >= n1) {
44 | j = j - n1;
45 | n1 = n1 / 2;
46 | }
47 | j = j + n1;
48 |
49 | if (i < j) {
50 | t1 = x[i];
51 | x[i] = x[j];
52 | x[j] = t1;
53 | t1 = y[i];
54 | y[i] = y[j];
55 | y[j] = t1;
56 | }
57 | }
58 |
59 | // FFT
60 | n1 = 0;
61 | n2 = 1;
62 |
63 | for (i = 0; i < m; i++) {
64 | n1 = n2;
65 | n2 = n2 + n2;
66 | a = 0;
67 |
68 | for (j = 0; j < n1; j++) {
69 | c = cos(a);
70 | s = sin(a);
71 | a += 1 << (m - i - 1);
72 | for (k = j; k < n; k = k + n2) {
73 | t1 = c * x[k + n1] - s * y[k + n1];
74 | t2 = s * x[k + n1] + c * y[k + n1];
75 | x[k + n1] = x[k] - t1;
76 | y[k + n1] = y[k] - t2;
77 | x[k] = x[k] + t1;
78 | y[k] = y[k] + t2;
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/fft-asm/versions/mini-fft.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable semi */
2 | /**
3 | * https://gist.github.com/antimatter15/0349ca7d479236fdcdbb
4 | * IT WORKS!
5 | * @module fft-asm
6 | */
7 | var sin = Math.sin
8 | var cos = Math.cos
9 |
10 | export function fft (real, imag, dir) {
11 | dir = dir || 'forward'
12 | var n = real.length
13 | if (imag.length !== n) throw Error('Real and imag parts should have same size, but was ' + n + ' and ' + imag.length)
14 | var m = Math.log(n) / Math.log(2)
15 | process(real, imag, n, m, dir)
16 | }
17 |
18 | function process (re, im, N) {
19 | var c, s, tre, tim
20 | for (var i = 0; i < N; i++) {
21 | for (var j = 0, h = i, k = N; (k >>= 1); h >>= 1) {
22 | j = (j << 1) | (h & 1);
23 | }
24 | if (j > i) {
25 | re[j] = [re[i], re[i] = re[j]][0]
26 | im[j] = [im[i], im[i] = im[j]][0]
27 | }
28 | }
29 | for (var hN = 1; hN * 2 <= N; hN *= 2) {
30 | for (i = 0; i < N; i += hN * 2) {
31 | for (j = i; j < i + hN; j++) {
32 | c = cos(Math.PI * (j - i) / hN)
33 | s = sin(Math.PI * (j - i) / hN)
34 | tre = re[j + hN] * c + im[j + hN] * s;
35 | tim = -re[j + hN] * s + im[j + hN] * c;
36 | re[j + hN] = re[j] - tre; im[j + hN] = im[j] - tim;
37 | re[j] += tre; im[j] += tim;
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/fft-asm/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | dspjs@../../support/dspjs:
6 | version "1.0.0"
7 |
--------------------------------------------------------------------------------
/packages/fft-radix2/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## fft-radix2
4 | > Fast fourier transform using radix-2 Cooley-Tukey algorithm
5 |
6 | [](https://npmjs.org/package/dsp-fft-radix2/)
7 |
8 | This module have functions to compute a Fast Fourier transform either
9 | in forward and inverse versions. The code is adapted from the unmaintained
10 | [dsp.js](https://github.com/corbanbrook/dsp.js) library.
11 |
12 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
13 |
14 | **Example**
15 | ```js
16 | var fftRadix2 = require('dsp-fft-radix2')
17 | var ft = fftRadix2(1024)
18 | ft.forward(signal)
19 | ft.inverse(signal)
20 | ```
21 |
22 |
23 | ### fft-radix2.fftRadix2(size) ⇒ Object.<forward, inverse>
24 | Create a Fast Fourier Transform functions
25 |
26 | It returns an object with two funtions: forward and inverse.
27 | Both accepts a signal and (optionally) an output buffer to store the
28 | results (to reduce memory allocation).
29 |
30 | **Kind**: static method of [fft-radix2](#module_fft-radix2)
31 | **Returns**: Object.<forward, inverse>
- fourier transform functions
32 |
33 | | Param | Type | Description |
34 | | --- | --- | --- |
35 | | size | Integer
| the FFT size |
36 |
37 | **Example**
38 | ```js
39 | var fftRadix2 = require('dsp-fft-radix2')
40 | var ft = fftRadix2(1024)
41 | // Given a signal (a Float32Array) ...
42 | output = { real: new Float32Array(1024), imag: new Float32Array(1024) }
43 | ft.forward(signal, output)
44 | // it's invertible
45 | ft.inverse(output).real === signal
46 | ```
47 |
--------------------------------------------------------------------------------
/packages/fft-radix2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-fft-radix2",
3 | "description": "Fast fourier transform using radix-2 Cooley-Tukey algorithm",
4 | "version": "0.0.1",
5 | "main": "build/index.js",
6 | "module": "index",
7 | "scripts": {
8 | "pretest": "rollup -c ../../rollup.config.js",
9 | "test": "node test/test.js",
10 | "benchmark": "npm run pretest && node test/benchmark.js",
11 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/fft.md"
12 | },
13 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-fft-radix2",
14 | "keywords": [
15 | "fast",
16 | "fourier",
17 | "transform",
18 | "dsp",
19 | "dsp-kit"
20 | ],
21 | "author": "danigb",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/oramics/dsp-kit/issues"
25 | },
26 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-fft-radix2/#readme",
27 | "devDependencies": {
28 | "easy-benchmark": "../../support/easy-benchmark",
29 | "dsp-array": "^0.0.1",
30 | "dsp-noise": "^0.0.1",
31 | "dsp-dft": "^0.0.1",
32 | "dspjs": "../../support/dspjs"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/fft-radix2/test/benchmark.js:
--------------------------------------------------------------------------------
1 | var benchmark = require('easy-benchmark')
2 | var dspjs = require('dspjs')
3 | var fftRadix2 = require('..').fftRadix2
4 | var arr = require('dsp-array')
5 |
6 | var signal = arr.fill(SIZE, () => Math.random() * 2 - 0.5)
7 | var fftjs = new dspjs.FFT(signal.length, 44100)
8 | var fftkit = fftRadix2(SIZE)
9 | var output = { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }
10 | var output2 = { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }
11 |
12 | benchmark('FORWARD: dsp-fft vs dsp.js FFT', {
13 | 'dsp-kit fft': () => fftkit.forward(signal, output),
14 | 'dsp.js fft': () => fftjs.forward(signal)
15 | })
16 |
17 | benchmark('INVERSE: dsp-fft vs dsp.js FFT', {
18 | 'dsp-kit fft': () => fftkit.inverse(output, output2),
19 | 'dsp.js fft': () => fftjs.inverse(output.real, output.imag)
20 | })
21 |
--------------------------------------------------------------------------------
/packages/fft-radix2/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | benchmark@^2.1.3:
6 | version "2.1.4"
7 | resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
8 | dependencies:
9 | lodash "^4.17.4"
10 | platform "^1.3.3"
11 |
12 | dspjs@../../support/dspjs:
13 | version "1.0.0"
14 |
15 | easy-benchmark@../../support/easy-benchmark:
16 | version "0.0.1"
17 | dependencies:
18 | benchmark "^2.1.3"
19 |
20 | lodash@^4.17.4:
21 | version "4.17.4"
22 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
23 |
24 | platform@^1.3.3:
25 | version "1.3.4"
26 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd"
27 |
--------------------------------------------------------------------------------
/packages/fft/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/fft/README.md
--------------------------------------------------------------------------------
/packages/fft/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the default (and fastest) fourier implementation. An alias of `fft-radix2`
3 | *
4 | * This module is not published into npm (mostly because the name is taken)
5 | * You can require one of the existing implementations: `fft-radix2`
6 | *
7 | * `dsp-kit` contains several fourier transform algorithms. This is the one
8 | * you can use by default.
9 | *
10 | * @name fft
11 | * @function
12 | * @memberof module:dsp
13 | */
14 | export { fftRadix2 as fft } from 'dsp-fft-radix2'
15 |
--------------------------------------------------------------------------------
/packages/fft/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-fft",
3 | "description": "Fast fourier transform",
4 | "version": "0.0.1",
5 | "private": true,
6 | "main": "build/index.js",
7 | "module": "index",
8 | "scripts": {
9 | "pretest": "rollup -c ../../rollup.config.js",
10 | "test": "node test/test",
11 | "benchmark": "",
12 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/fft.md"
13 | },
14 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-fft",
15 | "keywords": [
16 | "fast",
17 | "fourier",
18 | "transform",
19 | "dsp",
20 | "dsp-kit"
21 | ],
22 | "author": "danigb",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/oramics/dsp-kit/issues"
26 | },
27 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-fft/#readme",
28 | "dependencies": {
29 | "dsp-fft-radix2": "0.0.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/fft/test/test.js:
--------------------------------------------------------------------------------
1 | var test = require('tst')
2 | var assert = require('assert')
3 | var fft = require('..')
4 |
5 | test('export fft', () => {
6 | assert(typeof fft.fft === 'function')
7 | var ft = fft.fft(1024)
8 | assert(typeof ft.forward === 'function')
9 | assert(typeof ft.inverse === 'function')
10 | })
11 |
--------------------------------------------------------------------------------
/packages/fft2/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/fft2/README.md
--------------------------------------------------------------------------------
/packages/fft2/index.js:
--------------------------------------------------------------------------------
1 | var sqrt = Math.sqrt
2 |
3 | /*
4 | This computes an in-place complex-to-complex FFT
5 | x and y are the real and imaginary arrays of 2^m points.
6 | dir = 1 gives forward transform
7 | dir = -1 gives reverse transform
8 | */
9 | module.exports = function fft (dir, m, x, y) {
10 | var n, i, i1, j, k, i2, l, l1, l2
11 | var c1, c2, tx, ty, t1, t2, u1, u2, z
12 |
13 | /* Calculate the number of points */
14 | n = 1
15 | for (i = 0; i < m; i++) {
16 | n *= 2
17 | }
18 |
19 | /* Do the bit reversal */
20 | i2 = n >> 1
21 | j = 0
22 | for (i = 0; i < n - 1; i++) {
23 | if (i < j) {
24 | tx = x[i]
25 | ty = y[i]
26 | x[i] = x[j]
27 | y[i] = y[j]
28 | x[j] = tx
29 | y[j] = ty
30 | }
31 | k = i2
32 | while (k <= j) {
33 | j -= k
34 | k >>= 1
35 | }
36 | j += k
37 | }
38 |
39 | /* Compute the FFT */
40 | c1 = -1.0
41 | c2 = 0.0
42 | l2 = 1
43 | for (l = 0; l < m; l++) {
44 | l1 = l2
45 | l2 <<= 1
46 | u1 = 1.0
47 | u2 = 0.0
48 | for (j = 0; j < l1; j++) {
49 | for (i = j; i < n; i += l2) {
50 | i1 = i + l1
51 | t1 = u1 * x[i1] - u2 * y[i1]
52 | t2 = u1 * y[i1] + u2 * x[i1]
53 | x[i1] = x[i] - t1
54 | y[i1] = y[i] - t2
55 | x[i] += t1
56 | y[i] += t2
57 | }
58 | z = u1 * c1 - u2 * c2
59 | u2 = u1 * c2 + u2 * c1
60 | u1 = z
61 | }
62 | c2 = sqrt((1.0 - c1) / 2.0)
63 | if (dir === 1) {
64 | c2 = -c2
65 | }
66 | c1 = sqrt((1.0 + c1) / 2.0)
67 | }
68 |
69 | /* Scaling for forward transform */
70 | if (dir === 1) {
71 | for (i = 0; i < n; i++) {
72 | x[i] /= n
73 | y[i] /= n
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/packages/fft2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-fft2",
3 | "description": "Fast fourier transform using radix-2 Cooley-Tukey algorithm",
4 | "version": "0.0.1",
5 | "main": "index.js",
6 | "module": "index",
7 | "scripts": {
8 | "pretest": "rollup -c ../../rollup.config.js",
9 | "test": "node test/test.js",
10 | "benchmark": "npm run pretest && node test/benchmark.js",
11 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/fft.md"
12 | },
13 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-fft2",
14 | "keywords": [
15 | "fast",
16 | "fourier",
17 | "transform",
18 | "dsp",
19 | "dsp-kit"
20 | ],
21 | "author": "danigb",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/oramics/dsp-kit/issues"
25 | },
26 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-fft2/#readme",
27 | "devDependencies": {
28 | "easy-benchmark": "../../support/easy-benchmark",
29 | "dsp-array": "^0.0.1",
30 | "dsp-noise": "^0.0.1",
31 | "dsp-dft": "^0.0.1",
32 | "dspjs": "../../support/dspjs"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/fft2/test/benchmark.js:
--------------------------------------------------------------------------------
1 | var benchmark = require('easy-benchmark')
2 | var dspjs = require('dspjs')
3 | var fft = require('..')
4 | var arr = require('dsp-array')
5 |
6 | var m = 10
7 | var SIZE = Math.pow(2, m)
8 | var signal = arr.fill(SIZE, () => Math.random() * 2 - 0.5)
9 | var imaginary = arr.zeros(SIZE)
10 | var fftjs = new dspjs.FFT(signal.length, 44100)
11 | var output = { real: arr.zeros(SIZE), imag: arr.zeros(SIZE) }
12 |
13 | benchmark('FORWARD: dsp-fft vs dsp.js FFT', {
14 | 'dsp-kit fft2': () => fft(1, m, signal, imaginary),
15 | 'dsp.js fft': () => fftjs.forward(signal)
16 | })
17 |
18 | benchmark('INVERSE: dsp-fft vs dsp.js FFT', {
19 | 'dsp-kit fft2': () => fft(-1, m, signal, imaginary),
20 | 'dsp.js fft': () => fftjs.inverse(output.real, output.imag)
21 | })
22 |
--------------------------------------------------------------------------------
/packages/fft2/test/test.js:
--------------------------------------------------------------------------------
1 | var almost = require('almost-equal')
2 | const test = require('tst')
3 | const assert = require('assert')
4 | const fft = require('..')
5 | const arr = require('dsp-array')
6 | const noise = require('dsp-noise')
7 | var dspjs = require('dspjs')
8 |
9 | var m = 10
10 | var size = Math.pow(2, m)
11 | var FFT = new dspjs.FFT(size, 44100)
12 |
13 | test('simple', () => {
14 | var signal = arr.fill(size, noise.white())
15 | var real = arr.zeros(size)
16 | real.set(signal)
17 | assert.deepEqual(real, signal)
18 | var imag = arr.zeros(size)
19 | fft(1, m, real, imag)
20 | FFT.forward(real)
21 | assert.deepEqual(real, FFT.real)
22 | })
23 |
--------------------------------------------------------------------------------
/packages/fft2/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | benchmark@^2.1.3:
6 | version "2.1.4"
7 | resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
8 | dependencies:
9 | lodash "^4.17.4"
10 | platform "^1.3.3"
11 |
12 | dspjs@../../support/dspjs:
13 | version "1.0.0"
14 |
15 | easy-benchmark@../../support/easy-benchmark:
16 | version "0.0.1"
17 | dependencies:
18 | benchmark "^2.1.3"
19 |
20 | lodash@^4.17.4:
21 | version "4.17.4"
22 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
23 |
24 | platform@^1.3.3:
25 | version "1.3.4"
26 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd"
27 |
--------------------------------------------------------------------------------
/packages/fftshift/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## fftshift
4 | > Cyclic rotation for phase-zero windowing
5 |
6 | [](https://npmjs.org/package/dsp-fftshift/)
7 |
8 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
9 |
10 | **Example**
11 | ```js
12 | var shift = require('dsp-fftshift')
13 | shift.fftshift(signal)
14 | shift.ifftshift(signal)
15 | ```
16 | **Example**
17 | ```js
18 | // ES6 syntax
19 | import { fftshift, ifftshift } from 'dsp-fftshift'
20 | fftshift(signal)
21 | ```
22 | **Example**
23 | ```js
24 | // included in dsp-kit package
25 | var dsp = require('dsp-kit')
26 | dsp.fftshift(signal)
27 | dsp.ifftshift(signal)
28 | ```
29 |
30 | * [fftshift](#module_fftshift)
31 | * [.fftshift(buffer)](#module_fftshift.fftshift) ⇒ Array
32 | * [.ifftshift(buffer)](#module_fftshift.ifftshift) ⇒ Array
33 |
34 |
35 |
36 | ### fftshift.fftshift(buffer) ⇒ Array
37 | Zero-phase windowing alignment
38 |
39 | __CAUTION__: this function mutates the array
40 |
41 | Perform a cyclic shifting (rotation) to set the first sample at the middle
42 | of the buffer (it reorder buffer samples from (0:N-1) to [(N/2:N-1) (0:(N/2-1))])
43 |
44 | Named by the same function in mathlab: `fftshift`
45 |
46 | **Kind**: static method of [fftshift](#module_fftshift)
47 | **Returns**: Array
- the same buffer (with the data rotated)
48 |
49 | | Param | Type |
50 | | --- | --- |
51 | | buffer | Array
|
52 |
53 |
54 |
55 | ### fftshift.ifftshift(buffer) ⇒ Array
56 | Inverse of zero-phase windowing alignment
57 |
58 | __CAUTION__: this function mutates the array
59 |
60 | **Kind**: static method of [fftshift](#module_fftshift)
61 | **Returns**: Array
- the same buffer (with the data rotated)
62 | **See**: fftshift
63 |
64 | | Param | Type |
65 | | --- | --- |
66 | | buffer | Array
|
67 |
68 |
--------------------------------------------------------------------------------
/packages/fftshift/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Cyclic rotation for phase-zero windowing
3 | *
4 | * [](https://npmjs.org/package/dsp-fftshift/)
5 | *
6 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
7 | *
8 | * @example
9 | * var shift = require('dsp-fftshift')
10 | * shift.fftshift(signal)
11 | * shift.ifftshift(signal)
12 | *
13 | * @example
14 | * // ES6 syntax
15 | * import { fftshift, ifftshift } from 'dsp-fftshift'
16 | * fftshift(signal)
17 | *
18 | * @example
19 | * // included in dsp-kit package
20 | * var dsp = require('dsp-kit')
21 | * dsp.fftshift(signal)
22 | * dsp.ifftshift(signal)
23 | *
24 | * @module fftshift
25 | */
26 |
27 | /**
28 | * Rotate a buffer in place
29 | *
30 | * from: http://stackoverflow.com/questions/876293/fastest-algorithm-for-circle-shift-n-sized-array-for-m-position
31 | *
32 | * @param {Array} source - the buffer to rotate
33 | * @param {Number} rotations - the number of rotations
34 | * @private
35 | */
36 | function rotate (src, n) {
37 | var len = src.length
38 | reverse(src, 0, len)
39 | reverse(src, 0, n)
40 | reverse(src, n, len)
41 | return src
42 | }
43 | function reverse (src, from, to) {
44 | --from
45 | while (++from < --to) {
46 | var tmp = src[from]
47 | src[from] = src[to]
48 | src[to] = tmp
49 | }
50 | }
51 |
52 | /**
53 | * Zero-phase windowing alignment
54 | *
55 | * __CAUTION__: this function mutates the array
56 | *
57 | * Perform a cyclic shifting (rotation) to set the first sample at the middle
58 | * of the buffer (it reorder buffer samples from (0:N-1) to [(N/2:N-1) (0:(N/2-1))])
59 | *
60 | * Named by the same function in mathlab: `fftshift`
61 | *
62 | * @param {Array} buffer
63 | * @return {Array} the same buffer (with the data rotated)
64 | */
65 | export function fftshift (src) {
66 | const len = src.length
67 | return rotate(src, Math.floor(len / 2))
68 | }
69 |
70 | /**
71 | * Inverse of zero-phase windowing alignment
72 | *
73 | * __CAUTION__: this function mutates the array
74 | *
75 | * @see fftshift
76 | * @param {Array} buffer
77 | * @return {Array} the same buffer (with the data rotated)
78 | */
79 | export function ifftshift (src) {
80 | const len = src.length
81 | return rotate(src, Math.floor((len + 1) / 2))
82 | }
83 |
--------------------------------------------------------------------------------
/packages/fftshift/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-fftshift",
3 | "description": "Functions to create and manipulate Float64Array buffers",
4 | "version": "0.0.1",
5 | "main": "build/index.js",
6 | "module": "index.js",
7 | "scripts": {
8 | "pretest": "rollup -c ../../rollup.config.js",
9 | "test": "node test/test.js",
10 | "start": "npm run pretest && budo example/example.js",
11 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/fftshift.md"
12 | },
13 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-fftshift",
14 | "keywords": [
15 | "fast",
16 | "fourier",
17 | "transform",
18 | "dsp",
19 | "dsp-kit"
20 | ],
21 | "author": "danigb",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/oramics/dsp-kit/issues"
25 | },
26 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-fftshift/#readme",
27 | "dependencies": {
28 | },
29 | "devDependencies": {
30 | "dsp-array": "^0.0.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/fftshift/test/test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable comma-spacing */
2 | const test = require('tst')
3 | const assert = require('assert')
4 | const arr = require('dsp-array')
5 | const shift = require('..')
6 |
7 | const from = (x) => Float64Array.from(x || [])
8 |
9 | test('fftshift', () => {
10 | const even = shift.fftshift([1, 2, 3, 4, 5, 6])
11 | assert.deepEqual(even, from([4, 5, 6, 1, 2, 3]))
12 | const odd = shift.fftshift([1, 2, 3, 4, 5])
13 | assert.deepEqual(odd, from([4, 5, 1, 2, 3]))
14 |
15 | const N = 1000
16 | const signal = arr.fill(N + 1, x => x)
17 | const shifted = arr.fill(N + 1, x => x < N / 2 ? N / 2 + x + 1 : x - N / 2)
18 | assert.deepEqual(shift.fftshift(signal.slice()), shifted)
19 | assert.deepEqual(shift.ifftshift(shifted.slice()), signal)
20 | })
21 |
22 | test('inverse fftshift', () => {
23 | const even = arr.fill(100, x => x)
24 | assert.deepEqual(shift.ifftshift(shift.fftshift(even)), even)
25 | const odd = arr.fill(101, x => x)
26 | assert.deepEqual(shift.ifftshift(shift.fftshift(odd)), odd)
27 | })
28 |
--------------------------------------------------------------------------------
/packages/noise/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## noise
4 | > Generate noise
5 |
6 | [](https://npmjs.org/package/dsp-noise/)
7 |
8 | **Example**
9 | ```js
10 | const dsp = require('dsp-kit')
11 | dsp.fill(1024, dsp.white())
12 | ```
13 |
14 | * [noise](#module_noise)
15 | * [.white([compensate])](#module_noise.white) ⇒ function
16 | * [.pink([compensate])](#module_noise.pink) ⇒ function
17 | * [.brown([compensate])](#module_noise.brown) ⇒ function
18 |
19 |
20 |
21 | ### noise.white([compensate]) ⇒ function
22 | Create a function that generates white noise
23 |
24 | **Kind**: static method of [noise](#module_noise)
25 | **Returns**: function
- the white noise generator function
26 |
27 | | Param | Type | Default | Description |
28 | | --- | --- | --- | --- |
29 | | [compensate] | Number
| 1
| the gain compensation |
30 |
31 | **Example**
32 | ```js
33 | var dsp = require('dsp-kit')
34 | dsp.fill(1024, dsp.white())
35 | ```
36 |
37 |
38 | ### noise.pink([compensate]) ⇒ function
39 | Create a function that generates pink noise
40 |
41 | Pink noise has an even distribution of power if the frequency is mapped in a
42 | logarithmic scale.
43 |
44 | If 'white' consists of uniform random numbers, such as those generated by the
45 | rand() function, 'pink' will have an almost gaussian level distribution.
46 |
47 | This is an approximation to a -10dB/decade filter using a weighted sum of
48 | first order filters. It is accurate to within +/-0.05dB above 9.2Hz (44100Hz
49 | sampling rate). Unity gain is at Nyquist, but can be adjusted by scaling the
50 | numbers at the end of each line.
51 |
52 | It uses the Paul Kellet’s (refined) method: has smooth spectrum, but goes up
53 | slightly at the far high end
54 |
55 | #### References
56 | - https://github.com/csound/csound/blob/develop/Opcodes/pitch.c#L1338
57 | - http://www.musicdsp.org/files/pink.txt
58 |
59 | **Kind**: static method of [noise](#module_noise)
60 | **Returns**: function
- the pink noise generator function
61 |
62 | | Param | Type | Default | Description |
63 | | --- | --- | --- | --- |
64 | | [compensate] | Number
| 0.11
| The result will be multiplied by this constant in order to compensate the gain output |
65 |
66 | **Example**
67 | ```js
68 | var dsp = require('dsp-kit')
69 | dsp.fill(1024, dsp.pink())
70 | ```
71 |
72 |
73 | ### noise.brown([compensate]) ⇒ function
74 | Create a function that generates brown noise
75 |
76 | **Kind**: static method of [noise](#module_noise)
77 | **Returns**: function
- the brown noise generator function
78 |
79 | | Param | Type | Default | Description |
80 | | --- | --- | --- | --- |
81 | | [compensate] | Number
| 3.5
| the gain compensation value |
82 |
83 | **Example**
84 | ```js
85 | var dsp = require('dsp-kit')
86 | dsp.fill(1024, dsp.brown())
87 | ```
88 |
--------------------------------------------------------------------------------
/packages/noise/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Generate noise
3 | *
4 | * [](https://npmjs.org/package/dsp-noise/)
5 | *
6 | * @example
7 | * const dsp = require('dsp-kit')
8 | * dsp.fill(1024, dsp.white())
9 | *
10 | * @module noise
11 | */
12 | const { random } = Math
13 |
14 | /**
15 | * Create a function that generates white noise
16 | *
17 | * @param {Number} [compensate = 1] - the gain compensation
18 | * @return {Function} the white noise generator function
19 | * @example
20 | * var dsp = require('dsp-kit')
21 | * dsp.fill(1024, dsp.white())
22 | */
23 | export function white (compensate = 1) {
24 | return compensate === 1 ? _white : () => compensate * _white()
25 | }
26 | function _white () { return 2 * random() - 1 }
27 |
28 | /**
29 | * Create a function that generates pink noise
30 | *
31 | * Pink noise has an even distribution of power if the frequency is mapped in a
32 | * logarithmic scale.
33 | *
34 | * If 'white' consists of uniform random numbers, such as those generated by the
35 | * rand() function, 'pink' will have an almost gaussian level distribution.
36 | *
37 | * This is an approximation to a -10dB/decade filter using a weighted sum of
38 | * first order filters. It is accurate to within +/-0.05dB above 9.2Hz (44100Hz
39 | * sampling rate). Unity gain is at Nyquist, but can be adjusted by scaling the
40 | * numbers at the end of each line.
41 | *
42 | * It uses the Paul Kellet’s (refined) method: has smooth spectrum, but goes up
43 | * slightly at the far high end
44 | *
45 | * #### References
46 | * - https://github.com/csound/csound/blob/develop/Opcodes/pitch.c#L1338
47 | * - http://www.musicdsp.org/files/pink.txt
48 | *
49 | * @param {Number} [compensate = 0.11] - The result will be multiplied by
50 | * this constant in order to compensate the gain output
51 | * @return {Function} the pink noise generator function
52 | * @example
53 | * var dsp = require('dsp-kit')
54 | * dsp.fill(1024, dsp.pink())
55 | */
56 | export function pink (compensate = 0.11) {
57 | var b0, b1, b2, b3, b4, b5, b6
58 | b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0
59 | return function () {
60 | var w = _white()
61 | b0 = 0.99886 * b0 + w * 0.0555179
62 | b1 = 0.99332 * b1 + w * 0.0750759
63 | b2 = 0.96900 * b2 + w * 0.1538520
64 | b3 = 0.86650 * b3 + w * 0.3104856
65 | b4 = 0.55000 * b4 + w * 0.5329522
66 | b5 = -0.7616 * b5 - w * 0.0168980
67 | var out = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362
68 | b6 = w * 0.115926
69 | return compensate * out
70 | }
71 | }
72 |
73 | /**
74 | * Create a function that generates brown noise
75 | *
76 | * @param {Number} [compensate = 3.5] - the gain compensation value
77 | * @return {Function} the brown noise generator function
78 | * @example
79 | * var dsp = require('dsp-kit')
80 | * dsp.fill(1024, dsp.brown())
81 | */
82 | export function brown (compensate = 3.5) {
83 | var out = 0
84 | return function () {
85 | out = (out + 0.02 * _white()) / 1.02
86 | return compensate * out
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/packages/noise/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-noise",
3 | "description": "Noise generator for digital signal processing",
4 | "version": "0.0.1",
5 | "main": "build/index.js",
6 | "module": "index.js",
7 | "scripts": {
8 | "pretest": "rollup -c ../../rollup.config.js",
9 | "test": "node test/test.js",
10 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/noise.md"
11 | },
12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-noise",
13 | "keywords": [
14 | "fast",
15 | "fourier",
16 | "transform",
17 | "dsp",
18 | "dsp-kit"
19 | ],
20 | "author": "danigb",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/oramics/dsp-kit/issues"
24 | },
25 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-noise/#readme",
26 | "devDependencies": {
27 | "dsp-array": "0.0.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/noise/test/test.js:
--------------------------------------------------------------------------------
1 | var test = require('tst')
2 | var assert = require('assert')
3 | var noise = require('..')
4 |
5 | test('white noise', () => {
6 | assert(typeof noise.white() === 'function')
7 | })
8 |
9 | test('pink noise', () => {
10 | assert(typeof noise.pink() === 'function')
11 | })
12 |
13 | test('brown noise', () => {
14 | assert(typeof noise.pink() === 'function')
15 | })
16 |
--------------------------------------------------------------------------------
/packages/ola/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## ola
4 | > Add and overlap timestretch algorithm
5 |
6 | The overlap and add is the simplest, cheaper (in terms of computation) and
7 | less quality timestretch algorithm. It changes the length of a buffer without
8 | changing it's pitch.
9 |
10 | [](https://npmjs.org/package/dsp-ola/)
11 |
12 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
13 |
14 | **Example**
15 | ```js
16 | var ola = require('dsp-ola')
17 | const stretch = ola.overlapAdd({ size: 1024 })
18 | const halfSize = stretch(0.5, audioBuffer)
19 | ```
20 | **Example**
21 | ```js
22 | var dsp = require('dsp-kit')
23 | ```
24 |
25 |
26 | ### ola.overlapAdd(options) ⇒ function
27 | Create a timestretch function using an overlap and add algorithm
28 |
29 | **Kind**: static method of [ola](#module_ola)
30 | **Returns**: function
- the timestretch function
31 |
32 | | Param | Type |
33 | | --- | --- |
34 | | options | Object
|
35 |
36 | **Example**
37 | ```js
38 | const stretch = ola.overlapAdd()
39 | stretch(0.5, audio) // => a new audio buffer half of the length
40 | ```
41 |
--------------------------------------------------------------------------------
/packages/ola/example/example.js:
--------------------------------------------------------------------------------
1 | const draw = require('draw-waveform')
2 | const addCanvas = require('add-canvas')
3 | const buffer = require('dsp-array')
4 | const ola = require('..').overlapAdd
5 |
6 | print('Add and overlap examples', 'h3')
7 |
8 | print('Example signal: a constant')
9 | const constant = buffer.gen(400, (x) => 0.5)
10 | draw(addCanvas(400), constant)
11 |
12 | print('Stretch to double, with hop size factor 0.5')
13 | var stretch = ola({ size: 20, hop: 10 })
14 | const doubled = stretch(2, constant)
15 | console.log('double', doubled.length)
16 | draw(addCanvas(800), doubled)
17 |
18 | print('Stretch to half')
19 | const half = stretch(0.5, constant)
20 | console.log('half', half.length)
21 | draw(addCanvas(200), half)
22 |
23 | function print (text, tag = 'p', parent = document.body) {
24 | var el = document.createElement(tag)
25 | el.innerText = text
26 | parent.append(el)
27 | return el
28 | }
29 |
--------------------------------------------------------------------------------
/packages/ola/history/olaV1.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Add and overlap timestretch algorithm
3 | *
4 | * The overlap and add is the simplest, cheaper (in terms of computation) and
5 | * less quality timestretch algorithm. It changes the length of a buffer without
6 | * changing it's pitch.
7 | *
8 | * [](https://npmjs.org/package/dsp-ola/)
9 | *
10 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
11 | *
12 | * @example
13 | * var ola = require('dsp-ola')
14 | * const stretch = ola.overlapAdd({ size: 1024 })
15 | * const halfSize = stretch(0.5, audioBuffer)
16 | *
17 | * @example
18 | * var dsp = require('dsp-kit')
19 | *
20 | * @module ola
21 | */
22 | import { zeros, gen, add, mult } from 'dsp-array'
23 | const cos = Math.cos
24 | const PI2 = 2 * Math.PI
25 |
26 | const hamming = () => (n, N) => 0.54 - 0.46 * cos(PI2 * n / (N - 1))
27 |
28 | /**
29 | * Create a timestretch function using an overlap and add algorithm
30 | *
31 | * @param {Object} options
32 | * @return {Function} the timestretch function
33 | * @example
34 | * const stretch = ola.overlapAdd()
35 | * stretch(0.5, audio) // => a new audio buffer half of the length
36 | */
37 | export function overlapAdd (options = {}) {
38 | const { size = 1024, hop = size / 2 } = options
39 | const window = gen(size, options.window || hamming())
40 | const frame = zeros(size)
41 |
42 | return function (factor, src, dest) {
43 | var frames
44 | // we change hop size in source to change the size of the dest
45 | const srcHopSize = Math.floor(hop / factor)
46 | const srcLen = src.length
47 | if (!dest) {
48 | // if not dest, create one
49 | frames = Math.floor(srcLen / srcHopSize)
50 | dest = zeros(frames * hop)
51 | } else {
52 | frames = Math.floor(dest.length / hop)
53 | }
54 |
55 | let read, write
56 | for (var i = 0; i < frames; i++) {
57 | read = src.subarray(i * srcHopSize, i * srcHopSize + size)
58 | write = src.subarray(i * hop, i * hop + size)
59 | mult(size, read, window, frame)
60 | add(size, frame, write, write)
61 | }
62 | return dest
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/ola/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Add and overlap timestretch algorithm
3 | *
4 | * The overlap and add is the simplest, cheaper (in terms of computation) and
5 | * less quality timestretch algorithm. It changes the length of a buffer without
6 | * changing it's pitch.
7 | *
8 | * [](https://npmjs.org/package/dsp-ola/)
9 | *
10 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
11 | *
12 | * @example
13 | * var ola = require('dsp-ola')
14 | * const stretch = ola.overlapAdd({ size: 1024 })
15 | * const halfSize = stretch(0.5, audioBuffer)
16 | *
17 | * @example
18 | * var dsp = require('dsp-kit')
19 | *
20 | * @module ola
21 | */
22 | import { zeros, fill, add, mult } from 'dsp-array'
23 | const cos = Math.cos
24 | const PI2 = 2 * Math.PI
25 |
26 | const hamming = () => (n, N) => 0.54 - 0.46 * cos(PI2 * n / (N - 1))
27 |
28 | /**
29 | * Create a timestretch function using an overlap and add algorithm
30 | *
31 | * @param {Object} options
32 | * @return {Function} the timestretch function
33 | * @example
34 | * const stretch = ola.overlapAdd()
35 | * stretch(0.5, audio) // => a new audio buffer half of the length
36 | */
37 | export function overlapAdd (options = {}) {
38 | const { size = 1024, hop = size / 2 } = options
39 | const window = fill(size, options.window || hamming())
40 | const frame = zeros(size)
41 |
42 | return function (factor, src, dest) {
43 | var frames
44 | // we change hop size in source to change the size of the dest
45 | const srcHopSize = Math.floor(hop / factor)
46 | const srcLen = src.length
47 | if (!dest) {
48 | // if not dest, create one
49 | frames = Math.floor(srcLen / srcHopSize)
50 | dest = zeros(frames * hop)
51 | } else {
52 | frames = Math.floor(dest.length / hop)
53 | }
54 |
55 | let read, write
56 | for (var i = 0; i < frames; i++) {
57 | read = src.subarray(i * srcHopSize, i * srcHopSize + size)
58 | write = src.subarray(i * hop, i * hop + size)
59 | mult(size, read, window, frame)
60 | add(size, frame, write, write)
61 | }
62 | return dest
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/packages/ola/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-ola",
3 | "description": "Overlap and add timestretch algorithm",
4 | "version": "0.0.1",
5 | "main": "build/index.js",
6 | "module": "index",
7 | "scripts": {
8 | "pretest": "rollup -c ../../rollup.config.js",
9 | "test": "node test/test.js",
10 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/ola.md",
11 | "example": "npm run pretest && budo example/example.js"
12 | },
13 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-ola",
14 | "keywords": [
15 | "timestretch",
16 | "ola",
17 | "dsp",
18 | "dsp-kit"
19 | ],
20 | "author": "danigb",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/oramics/dsp-kit/issues"
24 | },
25 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-ola/#readme",
26 | "devDependencies": {
27 | "dsp-array": "^0.0.1",
28 | "dsp-window": "^0.0.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/ola/test/test.js:
--------------------------------------------------------------------------------
1 | const test = require('tst')
2 | const assert = require('assert')
3 | const buffer = require('dsp-array')
4 | const overlapAdd = require('..').overlapAdd
5 |
6 | const average = (arr) => arr.reduce((a, b) => a + b) / arr.length
7 |
8 | test.skip('micro overlap', function () {
9 | const size = 10
10 | const length = 20
11 | const stretch = overlapAdd({ size: size })
12 | const source = buffer.gen(length, (x) => 1)
13 | const result = stretch(2, source)
14 | assert.equal(result.length, 50)
15 | assert.deepEqual(result)
16 | })
17 |
18 | test.skip('overlap and add', function () {
19 | const len = 10000
20 | const win = 1024
21 | var signal = buffer.gen(len, (x) => 1)
22 | var stretch = overlapAdd()
23 | var result = stretch(0.5, signal)
24 | assert.equal(result.length, 4608)
25 | // remove the extremes (window fade in and fadout)
26 | var stable = result.slice(win, len - win)
27 | var deviation = Math.abs(1 - average(stable))
28 | assert.equal(deviation)
29 | })
30 |
--------------------------------------------------------------------------------
/packages/oscillator/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## oscillator
4 | > Wavetable oscillators
5 |
6 | [](https://npmjs.org/package/dsp-oscillator/)
7 |
8 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
9 |
10 | ### References
11 |
12 | - http://www.earlevel.com/main/2012/05/09/a-wavetable-oscillator%E2%80%94part-3/
13 | - http://www.earlevel.com/main/2012/05/25/a-wavetable-oscillator%E2%80%94the-code/
14 | - https://github.com/OpenDAWN/wavetable
15 |
16 | **Example**
17 | ```js
18 | const oscillator = require('dsp-oscillator')
19 | ```
20 |
21 |
22 | ### oscillator.oscillator(params)
23 | Create an oscillator. Returns a function that generates the oscillator
24 | signal
25 |
26 | **Kind**: static method of [oscillator](#module_oscillator)
27 |
28 | | Param | Type | Description |
29 | | --- | --- | --- |
30 | | params | Object
| oscillator parameters: - type: one of 'sine' - sampleRate: 44100 by default - defaultSize: the length of the generated buffer |
31 |
32 |
--------------------------------------------------------------------------------
/packages/oscillator/example/audio/saw-2048-lin-20-20k-20s.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/oscillator/example/audio/saw-2048-lin-20-20k-20s.mp3
--------------------------------------------------------------------------------
/packages/oscillator/example/audio/saw-sweep-100-sample-table-20-20k.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/oscillator/example/audio/saw-sweep-100-sample-table-20-20k.mp3
--------------------------------------------------------------------------------
/packages/oscillator/example/example.js:
--------------------------------------------------------------------------------
1 | var waa = require('dsp-waa')
2 | var h = require('h')
3 | var dsp = require('..')
4 | var buffer = require('dsp-array')
5 |
6 | const add = (el) => { document.body.appendChild(el); return el }
7 | const canvas = ({ width = 1024, height = 200 } = {}) => h('canvas', { width, height })
8 |
9 | add(h('h1', 'Oscillator example'))
10 |
11 | var generate = dsp.oscillator({ type: 'saw', defaultSize: 1024 })
12 |
13 | waa.drawWaveform(add(canvas()), generate())
14 | waa.drawWaveform(add(canvas()), generate({ frequency: 880 }))
15 | const concat = buffer.concat(generate({ size: 512 }), generate({ size: 512 }))
16 | waa.drawWaveform(add(canvas()), concat)
17 | var c = waa.drawWaveform(add(canvas()), generate({ frequency: 1600, size: 44100 }))
18 | c.onclick = waa.player(waa.toAudioBuffer(c.waveData), false)
19 |
--------------------------------------------------------------------------------
/packages/oscillator/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Wavetable oscillators
3 | *
4 | * [](https://npmjs.org/package/dsp-oscillator/)
5 | *
6 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
7 | *
8 | * ### References
9 | *
10 | * - http://www.earlevel.com/main/2012/05/09/a-wavetable-oscillator%E2%80%94part-3/
11 | * - http://www.earlevel.com/main/2012/05/25/a-wavetable-oscillator%E2%80%94the-code/
12 | * - https://github.com/OpenDAWN/wavetable
13 | *
14 | * @example
15 | * const oscillator = require('dsp-oscillator')
16 | *
17 | * @module oscillator
18 | */
19 | import { zeros } from 'dsp-array'
20 | const { round, PI, sin } = Math
21 | const PI2 = 2 * PI
22 |
23 | /*
24 | * oscillator module TODO:
25 | * - other generators
26 | * - phase parameter
27 | */
28 |
29 | const generators = {
30 | sine: (x) => sin(PI2 * x),
31 | saw: (x) => 2 * (x - Math.round(x))
32 | }
33 |
34 | /**
35 | * Create an oscillator. Returns a function that generates the oscillator
36 | * signal
37 | * @param {Object} params - oscillator parameters:
38 | *
39 | * - type: one of 'sine'
40 | * - sampleRate: 44100 by default
41 | * - defaultSize: the length of the generated buffer
42 | */
43 | export function oscillator ({ type = 'sine', sampleRate = 44100, defaultSize = 1024 } = {}) {
44 | let frameCount = 0
45 | let tableLen = 2048
46 | let table = generateWaveTable(tableLen, generators[type], sampleRate)
47 |
48 | /**
49 | * Generate the oscillator data
50 | */
51 | return function ({ frequency = 440, size = 0 } = {}, output) {
52 | if (!output) output = zeros(size || defaultSize)
53 | size = output.length
54 | let frameOffset = frameCount * size
55 | let step = tableLen * frequency / sampleRate
56 | let offset
57 |
58 | for (let i = 0; i < size; i++) {
59 | offset = round((frameOffset + i) * step)
60 | output[i] = table[offset % tableLen]
61 | }
62 | frameCount++
63 |
64 | return output
65 | }
66 | }
67 |
68 | function generateWaveTable (size, gen, sampleRate) {
69 | let table = zeros(size)
70 | var waveTableTime = size / sampleRate
71 | var waveTableHz = 1 / waveTableTime
72 |
73 | for (var i = 0; i < size; i++) {
74 | table[i] = gen(i * waveTableHz / sampleRate)
75 | }
76 | return table
77 | }
78 |
--------------------------------------------------------------------------------
/packages/oscillator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dsp-oscillator",
3 | "private": true,
4 | "description": "Wavetable oscillators",
5 | "version": "0.0.1",
6 | "main": "build/index.js",
7 | "module": "index",
8 | "scripts": {
9 | "pretest": "rollup -c ../../rollup.config.js",
10 | "test": "node test/test.js",
11 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/oscillator.md",
12 | "start": "budo --open example/example.js"
13 | },
14 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-oscillator",
15 | "keywords": [
16 | "oscillator",
17 | "dsp",
18 | "dsp-kit"
19 | ],
20 | "author": "danigb",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/oramics/dsp-kit/issues"
24 | },
25 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-oscillator/#readme",
26 | "dependencies": {
27 | "dsp-array": "^0.0.1"
28 | },
29 | "devDependencies": {
30 | "audio-context": "^0.1.0",
31 | "dsp-waa": "^0.0.1",
32 | "h": "^0.1.0",
33 | "dspjs": "../../support/dspjs"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/oscillator/test/test.js:
--------------------------------------------------------------------------------
1 | var test = require('tst')
2 | var assert = require('assert')
3 | var dspjs = require('dspjs')
4 | var osc = require('..')
5 |
6 | test('simple sin oscillator', function () {
7 | var sinejs = new dspjs.Oscillator(dspjs.SINE, 440, 1, 256, 44100)
8 | var sine = osc.oscillator({ type: 'sine', sampleRate: 44100, defaultSize: 256 })
9 | assert.deepEqual(sinejs.generate(), sine({ frequency: 440 }))
10 | assert.deepEqual(sinejs.generate(), sine({ frequency: 440 }))
11 | assert.deepEqual(sinejs.generate(), sine({ frequency: 440 }))
12 | })
13 |
--------------------------------------------------------------------------------
/packages/oscillator/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | audio-context@^0.1.0:
6 | version "0.1.0"
7 | resolved "https://registry.yarnpkg.com/audio-context/-/audio-context-0.1.0.tgz#116ee83d566e10e7e845f29d9b1e1bc2ea3520d2"
8 | dependencies:
9 | global "~4.2.1"
10 |
11 | dom-walk@^0.1.0:
12 | version "0.1.1"
13 | resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
14 |
15 | dspjs@../../support/dspjs:
16 | version "1.0.0"
17 |
18 | global@~4.2.1:
19 | version "4.2.1"
20 | resolved "https://registry.yarnpkg.com/global/-/global-4.2.1.tgz#c16801e9a47f0b0b847a156d419dc143b5a4e8b3"
21 | dependencies:
22 | min-document "^2.6.1"
23 | process "~0.5.1"
24 |
25 | h@^0.1.0:
26 | version "0.1.0"
27 | resolved "https://registry.yarnpkg.com/h/-/h-0.1.0.tgz#24211fe1d9cef2b36cae8ff8255606ea12ecdfb5"
28 |
29 | min-document@^2.6.1:
30 | version "2.19.0"
31 | resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
32 | dependencies:
33 | dom-walk "^0.1.0"
34 |
35 | process@~0.5.1:
36 | version "0.5.2"
37 | resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
38 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## phase-vocoder
4 | > Phase-vocoder timestretch algorithm
5 |
6 | Time stretching means altering the duration of a signal without changing its pitch
7 |
8 | [](https://npmjs.org/package/dsp-phase-vocoder/)
9 |
10 | A short-time Fourier transform (STFT) is performed on a windowed time-domain
11 | real signal to obtain a succession of overlapped spectral frames with minimal
12 | side-band effects (analysis stage). The time delay at which every spectral
13 | frame is picked up from the signal is called the hop size.
14 |
15 | The timedomain signal may be rebuilt by performing an inverse FastFourier
16 | transform on all frames followed by a successive accumulation of all frames
17 | (an operation termed overlap-add)
18 |
19 | Knowing the modulus of every bin is not enough: the phase information is
20 | necessary for a perfect recovery of a signal without modification.
21 | Furthermore the phase information allows an evaluation of ’instantaneous
22 | frequencies’ by the measure of phases between two frames
23 |
24 | The essential idea is to build two functions (analyze and
25 | synthesize) which are intended to work as a tightly coupled set. Between
26 | these two function calls, however, any number of manipulations can be
27 | performed to obtain the desired effects
28 |
29 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
30 |
31 | ### References
32 |
33 | - https://github.com/echo66/time-stretch-wac-article/blob/master/ts-ps-wac.pdf
34 | - https://www.spsc.tugraz.at/sites/default/files/Bachelor%20Thesis%20Gruenwald.pdf
35 | - http://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf
36 |
37 | **Example**
38 | ```js
39 | var dsp = require('dsp-kit')
40 | ```
41 |
42 | * [phase-vocoder](#module_phase-vocoder)
43 | * [.phaseVocoder()](#module_phase-vocoder.phaseVocoder)
44 | * [.paulStretch()](#module_phase-vocoder.paulStretch)
45 |
46 |
47 |
48 | ### phase-vocoder.phaseVocoder()
49 | Implements a standard phase vocoder timestretch algorithm. It returns a
50 | function that process the data.
51 |
52 | **Kind**: static method of [phase-vocoder](#module_phase-vocoder)
53 |
54 |
55 | ### phase-vocoder.paulStretch()
56 | Implements the paul stretch algorithm for extreme timestretching
57 |
58 | **Kind**: static method of [phase-vocoder](#module_phase-vocoder)
59 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * > Phase-vocoder timestretch algorithm
3 | *
4 | * Time stretching means altering the duration of a signal without changing its pitch
5 | *
6 | * [](https://npmjs.org/package/dsp-phase-vocoder/)
7 | *
8 | * A short-time Fourier transform (STFT) is performed on a windowed time-domain
9 | * real signal to obtain a succession of overlapped spectral frames with minimal
10 | * side-band effects (analysis stage). The time delay at which every spectral
11 | * frame is picked up from the signal is called the hop size.
12 | *
13 | * The timedomain signal may be rebuilt by performing an inverse FastFourier
14 | * transform on all frames followed by a successive accumulation of all frames
15 | * (an operation termed overlap-add)
16 | *
17 | * Knowing the modulus of every bin is not enough: the phase information is
18 | * necessary for a perfect recovery of a signal without modification.
19 | * Furthermore the phase information allows an evaluation of ’instantaneous
20 | * frequencies’ by the measure of phases between two frames
21 | *
22 | * The essential idea is to build two functions (analyze and
23 | * synthesize) which are intended to work as a tightly coupled set. Between
24 | * these two function calls, however, any number of manipulations can be
25 | * performed to obtain the desired effects
26 | *
27 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit)
28 |
29 | *
30 | * ### References
31 | *
32 | * - https://github.com/echo66/time-stretch-wac-article/blob/master/ts-ps-wac.pdf
33 | * - https://www.spsc.tugraz.at/sites/default/files/Bachelor%20Thesis%20Gruenwald.pdf
34 | * - http://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf
35 | *
36 | * @example
37 | * var dsp = require('dsp-kit')
38 | *
39 | *
40 | * @module phase-vocoder
41 | */
42 | import analysis from './lib/analysis'
43 | import synthesis from './lib/synthesis'
44 | import recalcPhases from './lib/recalcPhases'
45 | import randomPhases from './lib/randomPhases'
46 | import { fill } from 'dsp-array'
47 | import { bandFrequency } from 'dsp-spectrum'
48 | import { hanning } from 'dsp-window'
49 | import { fft } from 'dsp-fft'
50 | export { analysis, synthesis }
51 | // var dspjs = require('dspjs')
52 |
53 | /**
54 | * Implements a standard phase vocoder timestretch algorithm. It returns a
55 | * function that process the data.
56 | */
57 | export function phaseVocoder ({
58 | algorithm = 'phase-vocoder',
59 | size = 4096,
60 | hop = size * 0.5,
61 | sampleRate = 44100,
62 | windowFn = hanning()
63 | } = {}) {
64 | // a lookup table of bin center frecuencies
65 | var omega = fill(size, (x) => bandFrequency(x, size, sampleRate))
66 | var ft = fft(size)
67 | console.log('PHASE VOCODER', algorithm, size, hop, ft)
68 |
69 | return function stretch (factor, signal, output, timeFreqProccessing) {
70 | var frames = analysis(signal, { ft, size, hop, windowFn })
71 | if (timeFreqProccessing) timeFreqProccessing(frames, { size, hop, sampleRate })
72 | if (algorithm === 'phase-vocoder') recalcPhases(frames, { size, factor, hop }, omega)
73 | else if (algorithm === 'paul-stretch') randomPhases(frames, size)
74 | return synthesis(frames, { ft, size, hop, factor, sampleRate }, output)
75 | }
76 | }
77 |
78 | /**
79 | * Implements the paul stretch algorithm for extreme timestretching
80 | */
81 | export function paulStretch ({ size = 512, hop = 125, sampleRate = 44100 } = {}) {
82 | return function stretch (factor, signal) {
83 | var frames = analysis(signal, { size, hop })
84 | randomPhases(frames, size)
85 | return synthesis(frames, { size, hop, factor, sampleRate })
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/lib/analysis.js:
--------------------------------------------------------------------------------
1 | import { zeros, fill, mult } from 'dsp-array'
2 | import { polar } from 'dsp-spectrum'
3 | import { fftshift } from 'dsp-fftshift'
4 | import { fft } from 'dsp-fft'
5 |
6 | const rectangular = () => 1
7 |
8 | /**
9 | *
10 | */
11 | export default function analysis (signal, { size, hop, windowFn = rectangular, ft = fft(size) }) {
12 | var numFrames = Math.floor((signal.length - size) / hop)
13 | var window = fill(size, windowFn)
14 |
15 | // create an array to store all frames
16 | var frames = new Array(numFrames)
17 |
18 | // create some intermediate buffers (frame and frame in freq domain)
19 | var frame = zeros(size)
20 | var fdFrame = { real: zeros(size), imag: zeros(size) }
21 | for (var i = 0; i < numFrames; i++) {
22 | frame.set(signal.subarray(i * hop, i * hop + size))
23 | // 1. place a window into the signal
24 | mult(size, window, frame, frame)
25 | // 3. Cyclic shift to phase zero windowing
26 | fftshift(frame) // => centered
27 | // 4. Perform the forward fft
28 | ft.forward(frame, fdFrame)
29 | // 5. Convert to polar form in a new frame
30 | frames[i] = polar(fdFrame)
31 | }
32 | return frames
33 | }
34 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/lib/randomPhases.js:
--------------------------------------------------------------------------------
1 | const random = Math.random
2 | const PI2 = 2 * Math.PI
3 |
4 | /**
5 | * Set random phases of a collection of frames
6 | * @private
7 | */
8 | export default function randomPhases (frames, { size }) {
9 | for (var n = 0; n < frames.length; n++) {
10 | var phases = frames[n].phases
11 | for (var i = 0; i < size; i++) {
12 | phases[i] = PI2 * random()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/lib/recalcPhases.js:
--------------------------------------------------------------------------------
1 | const PI = Math.PI
2 | const PI2 = 2 * PI
3 |
4 | export default function recalcPhases (frames, { size, hop, factor, sampleRate }, omega) {
5 | const original = hop / sampleRate
6 | const modified = (hop * factor) / sampleRate
7 |
8 | const numFrames = frames.length
9 | for (let i = 2; i < numFrames; i++) {
10 | const prev = frames[i - 1]
11 | const current = frames[i]
12 | // for each frame, update each bin
13 | for (let bin = 0; bin < size; bin++) {
14 | // calculate the difference between phases
15 | const deltaPhi = current.phases[bin] - prev.phases[bin]
16 | // get the current band frequency
17 | const freq = omega[bin]
18 | // calculate the frequency deviation with the given hop size
19 | const deltaFreq = (deltaPhi / original) - freq
20 | // wrap the deviation
21 | var wrappedDeltaFreq = ((deltaFreq + PI) % PI2) - PI
22 | // and calculate the real frequency
23 | var realFreq = freq + wrappedDeltaFreq
24 | // update the phase
25 | current.phases[bin] = prev.phases[bin] + modified * realFreq
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/lib/recalcPhasesV1.js:
--------------------------------------------------------------------------------
1 | const PI = Math.PI
2 | const PI2 = 2 * PI
3 |
4 | export default function recalcPhases (frames, { size, hop, factor, sampleRate }, omega) {
5 | const original = hop / sampleRate
6 | const modified = (hop * factor) / sampleRate
7 |
8 | const numFrames = frames.length
9 | for (let i = 2; i < numFrames; i++) {
10 | const prev = frames[i - 1]
11 | const current = frames[i]
12 | // for each frame, update each bin
13 | for (let bin = 0; bin < size; bin++) {
14 | // calculate the difference between phases
15 | const deltaPhi = current.phases[bin] - prev.phases[bin]
16 | // get the current band frequency
17 | const freq = omega[bin]
18 | // calculate the frequency deviation with the given hop size
19 | const deltaFreq = (deltaPhi / original) - freq
20 | // wrap the deviation
21 | var wrappedDeltaFreq = ((deltaFreq + PI) % PI2) - PI
22 | // and calculate the real frequency
23 | var realFreq = freq + wrappedDeltaFreq
24 | // update the phase
25 | current.phases[bin] = prev.phases[bin] + modified * realFreq
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/phase-vocoder/lib/recalcPhasesV2.js:
--------------------------------------------------------------------------------
1 | const round = Math.round
2 | const PI2 = 2 * Math.PI
3 |
4 | /**
5 | * If the phase vocoder is utilized to perform effects that involve the
6 | * inequality of input hop size and synthesis hop
7 | * size, it is advisable to perform some phase adjustments
8 | *
9 | * It uses tha algorithm described in https://github.com/echo66/time-stretch-wac-article/blob/master/ts-ps-wac.pdf
10 | *
11 | * @private
12 | * @param {Array