├── .babelrc ├── .codeclimate.yml ├── .eslintrc ├── .gitignore ├── .jsdoc3.json ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── dsp-kit.js └── dsp-kit.min.js ├── docs ├── FOURIER.md ├── README.md ├── api │ ├── array_index.js.html │ ├── benchmark_index.js.html │ ├── delay_index.js.html │ ├── dft_index.js.html │ ├── dsp_index.js.html │ ├── elastica_index.js.html │ ├── fft-asm_versions_arduino.js.html │ ├── fft-asm_versions_fftc.js.html │ ├── fft-asm_versions_mini-fft.js.html │ ├── fft-radix2_index.js.html │ ├── fft_index.js.html │ ├── fft_lib_v1.js.html │ ├── fft_v1.js.html │ ├── fftshift_index.js.html │ ├── fonts │ │ ├── Lato-Regular.ttf │ │ ├── OFL.txt │ │ └── SourceCodePro-Regular.ttf │ ├── global.html │ ├── index.html │ ├── module-array.html │ ├── module-delay.html │ ├── module-dft.html │ ├── module-dsp-kit.html │ ├── module-elastica.html │ ├── module-fft-asm.html │ ├── module-fft-radix2.html │ ├── module-fft.html │ ├── module-fftshift.html │ ├── module-noise.html │ ├── module-ola.html │ ├── module-oscillator.html │ ├── module-phase-vocoder.html │ ├── module-rfft.html │ ├── module-signal.html │ ├── module-signal_arithmetic.html │ ├── module-signal_buffer.html │ ├── module-signal_comparasion.html │ ├── module-signal_integrator.html │ ├── module-signal_logic.html │ ├── module-spectrum.html │ ├── module-stft.html │ ├── module-waa.html │ ├── module-window.html │ ├── noise_index.js.html │ ├── ola_history_olaV1.js.html │ ├── ola_index.js.html │ ├── oscillator_index.js.html │ ├── phase-vocoder_index.js.html │ ├── phase-vocoder_lib_analysis.js.html │ ├── rfft_index.js.html │ ├── rfft_lib_inverse.js.html │ ├── scripts │ │ ├── linenumber.js │ │ └── prettify │ │ │ ├── Apache-License-2.0.txt │ │ │ ├── lang-css.js │ │ │ └── prettify.js │ ├── signal_index.js.html │ ├── signal_lib_buffer.js.html │ ├── signal_lib_comparasion.js.html │ ├── signal_lib_core.js.html │ ├── signal_lib_integrator.js.html │ ├── signal_lib_logic.js.html │ ├── signal_lib_math.js.html │ ├── spectrum_index.js.html │ ├── stft_index.js.html │ ├── styles │ │ ├── ionicons.min.css │ │ ├── jsdoc-default.css │ │ ├── prettify-jsdoc.css │ │ └── prettify-tomorrow.css │ ├── waa_index.js.html │ ├── waa_lib_player.js.html │ └── window_index.js.html ├── dss.md ├── modules │ ├── array.md │ ├── dft.md │ ├── dsp.md │ ├── fft-asm.md │ ├── fft.md │ ├── fftshift.md │ ├── noise.md │ ├── ola.md │ ├── oscillator.md │ ├── phase-vocoder.md │ ├── rfft.md │ ├── signal.md │ ├── spectrum.md │ ├── stft.md │ ├── waa.md │ └── window.md ├── phase-vocoder.md └── research │ ├── beat-detection.md │ ├── dsp.md │ ├── fft.md │ ├── filters.md │ ├── noise.md │ ├── pitch-detection.md │ ├── projects.md │ ├── resampling.md │ ├── reverb.md │ ├── time-stretch-pitch-shifting.md │ └── windows.md ├── lerna.json ├── package.json ├── packages ├── README.md ├── array │ ├── README.md │ ├── example │ │ └── example.js │ ├── index.js │ ├── package.json │ ├── test │ │ ├── benchmark.js │ │ └── test.js │ └── yarn.lock ├── dft │ ├── README.md │ ├── images │ │ ├── correlation.png │ │ ├── dft.png │ │ └── dft2.png │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── dsp │ ├── .babelrc │ ├── README.md │ ├── browser.js │ ├── index.js │ ├── package.json │ ├── rollup.config.js │ └── test │ │ └── test.js ├── elastica │ ├── README.md │ ├── example │ │ ├── amen-mono.wav │ │ ├── amen-ola.js │ │ ├── amen-stereo.wav │ │ ├── app.js │ │ ├── lib │ │ │ ├── decode-array-buffer.js │ │ │ └── player.js │ │ └── phase-vocoder.js │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── fft-asm │ ├── README.md │ ├── fft-no-asm.js │ ├── index.js │ ├── package.json │ ├── test │ │ ├── benchmark.js │ │ ├── profile.js │ │ └── test.js │ ├── versions │ │ ├── arduino.js │ │ ├── fftc.js │ │ └── mini-fft.js │ └── yarn.lock ├── fft-radix2 │ ├── README.md │ ├── index.js │ ├── package.json │ ├── test │ │ ├── benchmark.js │ │ └── test.js │ └── yarn.lock ├── fft │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── fft2 │ ├── README.md │ ├── index.js │ ├── package.json │ ├── test │ │ ├── benchmark.js │ │ └── test.js │ └── yarn.lock ├── fftshift │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── noise │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── ola │ ├── README.md │ ├── example │ │ └── example.js │ ├── history │ │ └── olaV1.js │ ├── index.js │ ├── package.json │ └── test │ │ └── test.js ├── oscillator │ ├── README.md │ ├── example │ │ ├── audio │ │ │ ├── saw-2048-lin-20-20k-20s.mp3 │ │ │ └── saw-sweep-100-sample-table-20-20k.mp3 │ │ └── example.js │ ├── index.js │ ├── package.json │ ├── test │ │ └── test.js │ └── yarn.lock ├── phase-vocoder │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── analysis.js │ │ ├── randomPhases.js │ │ ├── recalcPhases.js │ │ ├── recalcPhasesV1.js │ │ ├── recalcPhasesV2.js │ │ └── synthesis.js │ ├── package.json │ ├── pv-diagram.png │ ├── test │ │ └── test.js │ └── yarn.lock ├── rfft │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── forward.js │ │ ├── inverse.js │ │ ├── reverse-permute.js │ │ └── reverse-table.js │ ├── package.json │ ├── test │ │ └── test.js │ └── yarn.lock ├── signal │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── buffer.js │ │ ├── comparasion.js │ │ ├── control.js │ │ ├── core.js │ │ ├── integrator.js │ │ ├── logic.js │ │ └── math.js │ ├── package.json │ └── test │ │ ├── buffer-test.js │ │ ├── comparasion-test.js │ │ ├── control-test.js │ │ ├── integrator-test.js │ │ ├── logic-test.js │ │ ├── math-test.js │ │ └── test.js ├── spectral-models │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ └── dft.js │ ├── test │ │ └── dft.test.js │ └── yarn.lock ├── spectrum │ ├── README.md │ ├── example │ │ └── unwrap.js │ ├── index.js │ ├── package.json │ ├── test │ │ └── test.js │ └── yarn.lock ├── stft │ ├── README.md │ ├── index.js │ └── package.json ├── waa │ ├── README.md │ ├── index.js │ ├── lib │ │ ├── decode-array-buffer.js │ │ ├── draw-waveform.js │ │ ├── player.js │ │ └── to-audio-buffer.js │ ├── package.json │ └── test │ │ └── test.js └── window │ ├── README.md │ ├── index.js │ ├── package.json │ └── test │ └── test.js ├── rollup.config.js ├── support ├── assert-almost │ ├── index.js │ └── package.json ├── dspjs │ ├── README.md │ ├── dsp.js │ └── package.json └── easy-benchmark │ ├── index.js │ └── package.json ├── test ├── README.md ├── data │ ├── calc.py │ ├── generate.js │ ├── noise4096.json │ ├── noise4096fft-abs.json │ ├── noise4096fft-angle.json │ ├── noise4096fft-imag.json │ ├── noise4096fft-real.json │ ├── noise4096fft-unwrap.json │ ├── noise4096fft.json │ ├── noise4096ifft-imag.json │ └── noise4096ifft-real.json └── test-data.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } 4 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | 4 | engines: 5 | eslint: 6 | enabled: true 7 | 8 | exclude_paths: 9 | - "docs/**" 10 | - "test/**/*" 11 | - "build/*" 12 | - "packages/**/build/*" 13 | - "dist/*" 14 | - "support/*" 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lerna-debug.log 3 | npm-debug* 4 | packages/**/API.md 5 | tmp/ 6 | .DS_Store 7 | build/ 8 | -------------------------------------------------------------------------------- /.jsdoc3.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["README.md", "packages/"], 4 | "excludePattern": "node_modules|build|tmp" 5 | }, 6 | "templates": { 7 | "default": { 8 | "outputSourceFiles": true 9 | }, 10 | "cleverLinks": true, 11 | "monospaceLinks": true, 12 | "systemName": "dsp-kit", 13 | "systemSummary": "A kit for digital signal processing", 14 | "footer": "dsp-kit", 15 | "copyright": "MIT License", 16 | "navType": "vertical", 17 | "inlineNav": true, 18 | "theme": "spacelab", 19 | "linenums": true, 20 | "collapseSymbols": false, 21 | "inverseNav": true, 22 | "showTableOfContents": true 23 | }, 24 | "opts": { 25 | "destination": "./docs/api/", 26 | "recurse": true, 27 | "verbose": true, 28 | "template": "./node_modules/postman-jsdoc-theme" 29 | }, 30 | "plugins": ["plugins/markdown"] 31 | } 32 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "esversion": 6, 5 | 6 | "asi": true, 7 | "curly": true, 8 | "latedef": true, 9 | "quotmark": true, 10 | "undef": true, 11 | "unused": true, 12 | "trailing": true 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | git: 3 | depth: 1 4 | sudo: false 5 | language: node_js 6 | cache: 7 | directories: 8 | 9 | script: npm run test-ci 10 | 11 | node_js: 12 | - stable 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Oramics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/FOURIER.md: -------------------------------------------------------------------------------- 1 | # The Fourier Transform 2 | 3 | Understanding the discrete fourier transform by coding it into javascript. 4 | 5 | > The discrete Fourier transform (DFT) is one of the two most common, and powerful, procedures encountered in the field of digital signal processing. (Digital filtering is the other.) The DFT enables us to analyze, manipulate, and synthesize signals in ways not possible with continuous (analog) signal processing. Even though it's now used in almost every field of engineering, we'll see applications for DFT continue to flourish as its utility becomes more widely understood. Because of this, a solid understanding of the DFT is mandatory for anyone working in the field of digital signal processing. [1] 6 | 7 | The Fourier transform is an algorithm to find cyclic patterns. The mathematical version is quite ugly: 8 | 9 | ![Fourier Transform Equation](https://wikimedia.org/api/rest_v1/media/math/render/svg/b52e5fea739005f88aa2dd14716fef886603a1b5) 10 | 11 | We won't use that, we'll use a version of that one, the _discrete_ fourier transform that allow to apply the transformation to _discrete_ signals. We'll know soon what a discrete signal is but before I want to emphasize a property of the fourier transform: it's inversible. It means that if we transform something with the fourier transform, we can use another mathematical procedure (the _inverse_ fourier transform) to get that something back. 12 | 13 | ## 2. Signal and correlation 14 | 15 | Signals in the physical world are, acording to wikipedia, "any quantity exhibiting variation in time or variation in space". Tipical examples are radio, telephone, radar, television. For example, an electric voltage is a tipical representation (a signal) of a sound used in electric devices like electric guitars or amplifiers. 16 | 17 | Since we are working with computers, we are going to talk about _digital_ signals, basically a succession of numbers. In javascript, and for our educational purposes, a simple array will do the job: `const signal = [1, 0.5, -2, -100, 0]`. 18 | 19 | Computers usually have one or more "analog to digital" signal converters, like the one that takes the sound from the microphone and converts it into a digital signal that can be saved into a file. 20 | 21 | The _correlation_ is a measure of how similar two signals are. If the _absolute_ correlation is high, then the signals are similar, and if the correlation is near zero the signals are not similar. 22 | 23 | Calculate the correlation between two signals is surprisingly easy: 24 | 25 | ![correlation](images/correlation.png) 26 | 27 | It's an operation called _dot product_ that basically multiply one by one the elements of two signals. Here's an [awesome explanation of dot product](http://jackschaedler.github.io/circles-sines-signals/dotproduct.html) [and correlation](http://jackschaedler.github.io/circles-sines-signals/dotproduct2.html) 28 | 29 | ## 3. Discrete fourier transform 30 | 31 | Correlation is very important to the discrete fourier transform because what the transform does is basically _find the correlation (the similarity) between a collection of predefined signals and your signal_. 32 | 33 | Althought the typical equation for the discrete fourier transform is this one: 34 | 35 | We'll deal with an equivalent version: 36 | 37 | Here we should see two things, 38 | 39 | 1. It deals with [comples numbers! aaargg](https://betterexplained.com/articles/a-visual-intuitive-guide-to-imaginary-numbers/) 40 | 2. The real part (from the left) is a _correlation_ between your signal and a cosine signal, and the imaginary parte (the right) is a _correlation_ between your signal and a 41 | 42 | 43 | 44 | 45 | 46 | ## 4. Bibliography 47 | 48 | - `[1]`: [Understanding Digital Signal](https://www.amazon.com/Understanding-Digital-Signal-Processing-3rd/dp/0137027419) by Richard G. Lyons. 49 | - Spectrum and spectral density estimation by the Discrete Fourier transform (DFT), including a comprehensive list of window functions and some new flat-top windows: https://holometer.fnal.gov/GH_FFT.pdf 50 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # `dsp-kit` documentation 2 | 3 | Currently it includes: 4 | 5 | - [API](https://github.com/oramics/dsp-kit/blob/master/docs/API.md): API generated documentation from `dsp-kit` source 6 | - [FOURIER](https://github.com/oramics/dsp-kit/blob/master/docs/FOURIER.md): an explanation about how fourier transform works 7 | - [PHASE-VOCODER](https://github.com/oramics/dsp-kit/blob/master/docs/PHASE-VOCODER.md): some notes on research about phase vocoder 8 | -------------------------------------------------------------------------------- /docs/api/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/docs/api/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /docs/api/fonts/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/docs/api/fonts/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /docs/api/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/api/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/api/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/api/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: hsl(104, 100%, 24%); } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: hsl(240, 100%, 50%); } 17 | 18 | /* a comment */ 19 | .com { 20 | color: hsl(0, 0%, 60%); } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: hsl(240, 100%, 32%); } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: hsl(240, 100%, 40%); } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #000000; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #000000; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #000000; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/dss.md: -------------------------------------------------------------------------------- 1 | # Dynamic Stochastic Synthesis (1977): Method 2 | 3 | - https://github.com/abbernie/gendy 4 | - http://www.sergioluque.com/texts/Luque-Stochastic_Synthesis-Origins_and_Extensions.pdf 5 | 6 | 1. Select the number of breakpoints for the waveform. For example: 3 7 | 2. Select the waveform’s minimum and maximum frequencies and convert them to duration in number of samples. For example: 368-735 Hertz = 120-60 samples 8 | 3. Divide the minimum and maximum number of samples by the number of breakpoints: 9 | - 60/3 = 20 10 | - 120/3 = 40 11 | (These values are the barriers for the duration random walk of each breakpoint) 12 | 4. For the continual generation of steps for all the duration random walks: select a probability distribution, its parameters and the ± number that will be the minimum and maximum size for these steps. 13 | 5. An initial duration is given to each breakpoint: values taken from stochastic or 14 | trigonometric functions, the minimum or the maximum duration, etc. 15 | 6. A maximum amplitude is selected and this ± value is the barrier for the amplitude 16 | random walk of each breakpoint. 17 | 7. For the continual generation of steps for all the amplitude random walks: select a 18 | probability distribution, its parameters and the ± number that will be the minimum 19 | and maximum size for these steps. 20 | 8. An initial amplitude is given to the each breakpoint: values taken from stochastic or 21 | trigonometric functions, zeroes, etc. 22 | 9. Breakpoints are linked by linear interpolation. At each repetition, the last breakpoint 23 | of the current waveform is connected with the first breakpoint of the next variation 24 | of the waveform. 25 | 26 | This technique is described in the chapter “Dynamic Stochastic Synthesis” of Formalized 27 | Music (Xenakis 1992, pp. 289-293) and is often mistaken as the explanation for the dynamic stochastic synthesis algorithm implemented in the beginning of the 1990 28 | -------------------------------------------------------------------------------- /docs/modules/dft.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## dft 4 | > Discrete Fourier Transformation 5 | 6 | [![npm install dsp-dft](https://nodei.co/npm/dsp-dft.png?mini=true)](https://npmjs.org/package/dsp-dft/) 7 | 8 | This module have functions to compute DFT using the correlation algorithm 9 | (the simplest and easy to understand, also the slowest) 10 | 11 | > Various methods are used to obtain DFT for time domain samples including use 12 | of Simultaneous Equations using Gaussian elimination, correlation, and using 13 | the Fast Fourier Transform algorithm. The first option requires massive work 14 | even for a comparitively small number of samples. In actual practice, 15 | correlation is the preferred method if the DFT has less than about 32 points. 16 | 17 | The function of this module is __really slow__, and not intended to be used 18 | in production. It has two goals: 19 | 20 | - Educational: learn how to implement the DFT correlation algorithm 21 | - Testing: test more complex algorithms against this to check outputs 22 | 23 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 24 | 25 | **Example** 26 | ```js 27 | // using dsp-kit 28 | var dsp = require('dsp-kit') 29 | dsp.dft('forward', signal) 30 | dsp.dft('inverse', complexSignal) 31 | ``` 32 | **Example** 33 | ```js 34 | // requiring only this module 35 | var dft = require('dsp-dft') 36 | dft.dft('forward', signal) 37 | ``` 38 | 39 | 40 | ### dft.dft(direction, signal, output) ⇒ Object 41 | Perform a DFT using a _brute force_ correlation algorithm 42 | 43 | It accepts real and complex signals of any size. 44 | 45 | It implements the mathematical function as it, without any kind of optimization, 46 | so it's the slowest algorithm possible. 47 | 48 | This algorithm is not intended to be used in production. It's main use 49 | (apart from the educational purposes) is to check the output of more 50 | complex algorithms 51 | 52 | **Kind**: static method of [dft](#module_dft) 53 | **Returns**: Object - the DFT output 54 | 55 | | Param | Type | Description | 56 | | --- | --- | --- | 57 | | direction | String | Can be 'forward' or 'inverse' | 58 | | signal | Array | Object | The (real) signal array, or the complex signal object `{ imag, real }` | 59 | | output | Object | (Optional) the pair of buffers `{ imag, real }` to store the output (or new buffers are created if not provided) | 60 | 61 | **Example** 62 | ```js 63 | dft('forward', signal) 64 | dft('inverse', { real: ..., imag: .... }) 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/modules/dsp.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## dsp-kit 4 | > Digital Signal Processing 5 | 6 | [![npm install dsp-kit](https://nodei.co/npm/dsp-kit.png?mini=true)](https://npmjs.org/package/dsp-kit/) 7 | 8 | This module is a facade of some of the `dsp-kit` modules. Currently 9 | it exposes: 10 | 11 | - `array`: create and manipulate arrays 12 | - `fft`: fast fourier transform functions 13 | - `spectrum`: manipulate the result of the fourier transform 14 | - `fftshift`: perform zero phase fft shifting 15 | - `noise`: generate noise signals 16 | - `window`: several windowing functions 17 | 18 | -------------------------------------------------------------------------------- /docs/modules/fft-asm.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/docs/modules/fft-asm.md -------------------------------------------------------------------------------- /docs/modules/fft.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/docs/modules/fft.md -------------------------------------------------------------------------------- /docs/modules/fftshift.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## fftshift 4 | > Cyclic rotation for phase-zero windowing 5 | 6 | [![npm install dsp-fftshift](https://nodei.co/npm/dsp-fftshift.png?mini=true)](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 | -------------------------------------------------------------------------------- /docs/modules/noise.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## noise 4 | > Generate noise 5 | 6 | [![npm install dsp-noise](https://nodei.co/npm/dsp-noise.png?mini=true)](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 | -------------------------------------------------------------------------------- /docs/modules/ola.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 | [![npm install dsp-ola](https://nodei.co/npm/dsp-ola.png?mini=true)](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 | -------------------------------------------------------------------------------- /docs/modules/oscillator.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## oscillator 4 | > Wavetable oscillators 5 | 6 | [![npm install dsp-oscillator](https://nodei.co/npm/dsp-oscillator.png?mini=true)](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 | -------------------------------------------------------------------------------- /docs/modules/phase-vocoder.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 | [![npm install dsp-phase-vocoder](https://nodei.co/npm/dsp-phase-vocoder.png?mini=true)](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 | -------------------------------------------------------------------------------- /docs/modules/rfft.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## rfft 4 | > real split radix FFT algorithm 5 | 6 | [![npm install dsp-rfft](https://nodei.co/npm/dsp-rfft.png?mini=true)](https://npmjs.org/package/dsp-rfft/) 7 | 8 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 9 | 10 | Most of the code was adapted from [dsp.js](https://github.com/corbanbrook/dsp.js) 11 | by @corbanbrook and some parts by @Spudd86 12 | 13 | ### References 14 | 15 | - Original C implementation at http://www.jjj.de/fxt/ 16 | 17 | **Example** 18 | ```js 19 | const dsp = require('dsp-kit') 20 | const forward = dsp.rfft(1024) 21 | const result = forward(signal) 22 | ``` 23 | 24 | 25 | ### rfft.rfft(buffer) ⇒ 26 | Performs a forward transform on the sample buffer. 27 | Converts a time domain signal to frequency domain spectra. 28 | 29 | **Kind**: static method of [rfft](#module_rfft) 30 | **Returns**: The frequency spectrum array 31 | 32 | | Param | Type | Description | 33 | | --- | --- | --- | 34 | | buffer | Array | The sample buffer. Buffer Length must be power of 2 | 35 | 36 | -------------------------------------------------------------------------------- /docs/modules/signal.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## signal 4 | > Generate signals 5 | 6 | This is a collection of functions to generate signals using one sample a time. 7 | This is a quite slow implementation and not ready for realtime generation. 8 | The main focus is on easy to read, aka learn-dsp. 9 | 10 | This implementation is largely based on the excellent [genish.sh](https://github.com/charlieroberts/genish.js) 11 | library that has much more performance than this one. 12 | 13 | -------------------------------------------------------------------------------- /docs/modules/stft.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## stft 4 | > Short-Time Fourier Transform: a time-varying method of spectral analysis 5 | 6 | [![npm install dsp-stft](https://nodei.co/npm/dsp-stft.png?mini=true)](https://npmjs.org/package/dsp-stft/) 7 | 8 | **Example** 9 | ```js 10 | import { stft, istft } from 'dsp-sftf' 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/modules/waa.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## waa 4 | > Web Audio API utilities for digital signal processing 5 | 6 | It provides web audio api utilities 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/window.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## window 4 | > Windowing functions for digital signal processing 5 | 6 | [![npm install dsp-window](https://nodei.co/npm/dsp-window.png?mini=true)](https://npmjs.org/package/dsp-window/) 7 | 8 | 9 | All window functions have some extra properties: 10 | 11 | - rov: recommended overlap 12 | 13 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 14 | 15 | ### References 16 | https://www.dsprelated.com/freebooks/sasp/Spectrum_Analysis_Windows.html 17 | 18 | **Example** 19 | ```js 20 | const dsp = require('dsp-kit') 21 | dsp.fill(1024, dsp.window.hanning()) 22 | ``` 23 | 24 | * [window](#module_window) 25 | * [.rectangular](#module_window.rectangular) 26 | * [.hanning](#module_window.hanning) 27 | * [.blackmanHarris](#module_window.blackmanHarris) 28 | 29 | 30 | 31 | ### window.rectangular 32 | The rectangular window, also sometimes called ‘uniform window’, is given by 33 | w = 1, equivalent to using no window at all. 34 | 35 | Although there are some special applications where the rectangular 36 | window is advantageous, it is probably not useful for any of our applications 37 | 38 | - Abrupt transition from 1 to 0 at the window endpoints 39 | - Roll-off is asymptotically -6dB per octave 40 | - First side lobe is -13dB relative to main-lobe peak 41 | 42 | **Kind**: static constant of [window](#module_window) 43 | 44 | 45 | ### window.hanning 46 | The Hanning window (one of a family of ‘raised cosine’ windows) is also known 47 | as ‘Hann window’. Do not confuse it with the ‘Hamming’ window. 48 | 49 | - Smooth transition to zero at window endpoints 50 | - Roll-off is asymptotically -18 dB per octave 51 | - First side lobe is -31dB relative to main-lobe peak 52 | 53 | **Kind**: static constant of [window](#module_window) 54 | 55 | 56 | ### window.blackmanHarris 57 | The Blackman-Harris window is one of a family of window functions given by a 58 | sum of cosine terms. By varying the number and coefficients of the terms 59 | different characteristics can be optimized. 60 | 61 | **Kind**: static constant of [window](#module_window) 62 | -------------------------------------------------------------------------------- /docs/research/beat-detection.md: -------------------------------------------------------------------------------- 1 | # Beat detection 2 | 3 | ## References 4 | 5 | - https://github.com/corbanbrook/pjsaudio/blob/master/modules/beatdetektor.lib.js 6 | -------------------------------------------------------------------------------- /docs/research/dsp.md: -------------------------------------------------------------------------------- 1 | # DSP 2 | 3 | - Introduction to Audio Digital Signal Processing on Linux: http://www.mega-nerd.com/Res/IADSPL/ 4 | - Audio Signal Processing in FAUST: https://ccrma.stanford.edu/~jos/aspf/aspf.pdf 5 | - Computer Sound Transformation by Trevor Wishart (CDP): http://www.composersdesktop.com/trnsform.html 6 | -------------------------------------------------------------------------------- /docs/research/fft.md: -------------------------------------------------------------------------------- 1 | # Fast Fourier Transform 2 | 3 | 4 | - Really nice FFT stuff: http://www.katjaas.nl/fourier/fourier.html 5 | - Incredible explanation of FFT: http://www.katjaas.nl/inverseFFT/inverseFFT.html 6 | - Benchmarked FFT Implementations: http://www.fftw.org/benchfft/ffts.html 7 | - Old FFT fortran programs (so nice!): http://www.dtic.mil/dtic/tr/fulltext/u2/657019.pdf 8 | - https://jakevdp.github.io/blog/2013/08/28/understanding-the-fft/ 9 | 10 | - https://gist.github.com/xypron/9ad954dc65899caea4f4 11 | -------------------------------------------------------------------------------- /docs/research/filters.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | - https://github.com/dnault/libresample4j/blob/master/src/main/java/com/laszlosystems/libresample4j/FilterKit.java 4 | -------------------------------------------------------------------------------- /docs/research/noise.md: -------------------------------------------------------------------------------- 1 | # Noise 2 | 3 | - Generate Noise with Web Audio API: http://noisehack.com/generate-noise-web-audio-api/ 4 | - Pink noise: http://www.firstpr.com.au/dsp/pink-noise/ 5 | 6 | > In the natural world, there are many physical processes which produce noise with what is known as a "pink" distribution of power. "Pink" noise has an even distribution of power if the frequency is mapped in a logarithmic scale. 7 | 8 | 9 | - McCartney algorithm for pink noise: 10 | 11 | - Pink noise generation: https://github.com/PaulBatchelor/Soundpipe/blob/master/modules/pinknoise.c 12 | - http://www.firstpr.com.au/dsp/pink-noise/phil_burk_19990905_patest_pink.c 13 | - https://github.com/falkTX/protrekkr/blob/474637b9e1afbcc87cd4c73b1a0e74c1baf061ab/release/distrib/replay/lib/replay.cpp 14 | -------------------------------------------------------------------------------- /docs/research/pitch-detection.md: -------------------------------------------------------------------------------- 1 | # Pitch detection research 2 | 3 | - http://dsp.stackexchange.com/questions/3652/fft-pitch-detection-methods-autocorrelation-or-other 4 | - https://github.com/cwilso/PitchDetect 5 | -------------------------------------------------------------------------------- /docs/research/projects.md: -------------------------------------------------------------------------------- 1 | # Projects 2 | 3 | A collection of music-dsp related projects and locations: 4 | 5 | ## DSP 6 | 7 | - Soundpipe - A lightweight music DSP library: https://github.com/PaulBatchelor/Soundpipe 8 | - FAUST (Functional AUdio STream) is a domain-specific purely functional programming language for implementing signal processing algorithms 9 | 10 | ## Effects 11 | 12 | - http://guitarix.org/ 13 | -------------------------------------------------------------------------------- /docs/research/resampling.md: -------------------------------------------------------------------------------- 1 | # Resampling 2 | 3 | - https://ccrma.stanford.edu/~jos/resample/resample.pdf 4 | - https://github.com/dnault/libresample4j 5 | - https://android.googlesource.com/platform/external/speex/+/donut-release/libspeex/resample.c 6 | -------------------------------------------------------------------------------- /docs/research/reverb.md: -------------------------------------------------------------------------------- 1 | # Reverb 2 | 3 | - http://tinyrave.com/tracks/76-simple-reverb-test 4 | - https://github.com/adelespinasse/reverbGen 5 | - https://github.com/martineastwood/mverb 6 | 7 | - Dattoro reverb design: https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf 8 | - Reverb Algorithms Tips: http://freeverb3vst.osdn.jp/reverb.shtml 9 | - Lexicon ARU: http://www.10000cows.com/LexiconARU.htm 10 | -------------------------------------------------------------------------------- /docs/research/time-stretch-pitch-shifting.md: -------------------------------------------------------------------------------- 1 | # Time stretch and pitch shifting 2 | 3 | - http://blogs.zynaptiq.com/bernsee/time-pitch-overview/ 4 | - Waveset technique: http://www.composersdesktop.com/trnsform.html 5 | > I defined a waveset as the signal between any pair of zero-crossings. With a simple sine-wave the waveset corresponds to the waveform. But even with a harmonic tone with very strong partials, the waveform may cross the zero more than twice in a complete cycle. In this case the wavesets are shorter than the waveform. 6 | 7 | - A fast implementation for SOLA, a sample time stretching algorithm in C: https://erdgeist.org/gitweb/timestretch/ 8 | -------------------------------------------------------------------------------- /docs/research/windows.md: -------------------------------------------------------------------------------- 1 | # Window functions 2 | 3 | - https://www.dsprelated.com/freebooks/sasp/Spectrum_Analysis_Windows.html 4 | - [Spectrum and spectral density estimation by the Discrete Fourier transform (DFT), including a comprehensive list of window functions and some new flat-top windows](https://holometer.fnal.gov/GH_FFT.pdf) by G. Heinzel and others. 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.4", 3 | "npmClient": "yarn", 4 | "version": "0.0.1" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-kit-tools", 3 | "private": true, 4 | "scripts": { 5 | "precommit": "npm test && npm run docs && npm run dist", 6 | "build": "lerna run pretest", 7 | "test": "lerna run test", 8 | "test:data": "node test/test-data", 9 | "test-ci": "lerna bootstrap && npm test", 10 | "docs": "npm run docs:html && npm run docs:modules", 11 | "docs:html": "jsdoc -c .jsdoc3.json", 12 | "docs:modules": "lerna run docs", 13 | "dist": "npm run dist:pack && npm run dist:minify && ls -hall dist/", 14 | "dist:pack": "browserify packages/dsp/browser.js -o dist/dsp-kit.js", 15 | "dist:minify": "uglifyjs dist/dsp-kit.js > dist/dsp-kit.min.js", 16 | "benchmark": "lerna run benchmark" 17 | }, 18 | "devDependencies": { 19 | "babel-preset-es2015": "^6.24.1", 20 | "babel-preset-es2015-rollup": "^3.0.0", 21 | "babelify": "^7.3.0", 22 | "benchmark": "^2.1.3", 23 | "fft": "^0.2.1", 24 | "jsdoc-to-markdown": "^3.0.0", 25 | "lerna": "2.0.0-rc.4", 26 | "postman-jsdoc-theme": "0.0.2", 27 | "rollup": "^0.41.4", 28 | "rollup-plugin-babel": "^2.7.1", 29 | "rollup-plugin-cleanup": "^1.0.0", 30 | "rollup-plugin-node-resolve": "^3.0.0", 31 | "rollup-watch": "^3.2.2", 32 | "standard": "^10.0.2", 33 | "tst": "^1.3.2" 34 | }, 35 | "standard": { 36 | "ignore": [ 37 | "dist/*", 38 | "*.md" 39 | ] 40 | }, 41 | "dependencies": { 42 | "almost-equal": "^1.1.0", 43 | "hyperapp": "0.0.9" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/README.md: -------------------------------------------------------------------------------- 1 | # dsp-kit packages 2 | 3 | `dsp-kit` is a multimodule package that uses [lerna](https://github.com/lerna/lerna) 4 | 5 | Not all packages are [published into npm yet](https://www.npmjs.com/browse/keyword/dsp-kit) 6 | 7 | Use the [dsp-kit](https://github.com/oramics/dsp-kit/tree/master/packages/dsp) package to rule them all. 8 | -------------------------------------------------------------------------------- /packages/array/example/example.js: -------------------------------------------------------------------------------- 1 | var draw = require('draw-waveform') 2 | var addCanvas = require('add-canvas') 3 | var array = require('..') 4 | 5 | var constant = array.fill(600, (x) => 1) 6 | draw(addCanvas(600), constant) 7 | 8 | var sine = array.fill(600, (n, N) => Math.sin(10 * 2 * Math.PI * n / (N - 1))) 9 | draw(addCanvas(600), sine) 10 | 11 | const hamming = () => (n, N) => 0.54 - 0.46 * Math.cos(2 * Math.PI * n / (N - 1)) 12 | var window = array.fill(600, hamming()) 13 | draw(addCanvas(600), window) 14 | 15 | var mult = array.mult(constant, window) 16 | draw(addCanvas(600), mult) 17 | 18 | var dest = array.zeros(1200) 19 | array.copy(window, dest) 20 | array.add(window, dest, dest, 0, 300, 300) 21 | array.add(window, dest, dest, 0, 600, 600) 22 | draw(addCanvas(600), dest) 23 | -------------------------------------------------------------------------------- /packages/array/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-array", 3 | "description": "Functions to create and manipulate Float64Array arrays", 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 | "benchmark": "npm run pretest && node test/benchmark.js", 11 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/array.md", 12 | "start": "npm run pretest && budo example/example.js" 13 | }, 14 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-array", 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-array/#readme", 28 | "devDependencies": { 29 | "easy-benchmark": "../../support/easy-benchmark", 30 | "add-canvas": "gist:2e32779f41baefc491a370a138767fdf", 31 | "draw-waveform": "gist:ffe0c4e7a06586d4bbb018ca69887ae8" 32 | }, 33 | "dependencies": { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/array/test/benchmark.js: -------------------------------------------------------------------------------- 1 | var benchmark = require('easy-benchmark') 2 | var arr = require('..') 3 | 4 | var SIZE = 1024 * 10 5 | var heap32 = new Float32Array(SIZE) 6 | var heap64 = new Float64Array(SIZE) 7 | 8 | benchmark('Typed array creation', { 9 | 'create Float32Array': () => new Float32Array(SIZE), 10 | 'create Float64Array': () => new Float64Array(SIZE) 11 | }) 12 | 13 | benchmark('Fill arrays', { 14 | 'Fill 32bit': () => arr.fill(heap32, (x) => x), 15 | 'Fill 64bit': () => arr.fill(heap64, (x) => x) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/array/test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const array = require('..') 5 | 6 | const from = (x) => Float64Array.from(x || []) 7 | 8 | test('zeros', () => { 9 | const zeros = array.zeros(10) 10 | assert.equal(zeros.length, 10) 11 | assert.deepEqual(zeros, from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) 12 | }) 13 | 14 | test('add', () => { 15 | const ones = array.fill(10, (x) => 1) 16 | var result = array.add(5, ones, ones) 17 | assert.deepEqual(result, [2,2,2,2,2]) 18 | assert.deepEqual(ones, [1,1,1,1,1,1,1,1,1,1]) 19 | array.add(5, ones, ones, ones) 20 | assert.deepEqual(ones, [2,2,2,2,2,1,1,1,1,1]) 21 | }) 22 | 23 | test('substr', () => { 24 | const a = array.fill(5, (x) => 5) 25 | const b = array.fill(5, (x) => x) 26 | assert.deepEqual(array.substr(5, a, b), [5,4,3,2,1]) 27 | array.substr(3, a, b, a) 28 | assert.deepEqual(a, [5,4,3,5,5]) 29 | }) 30 | 31 | test('mult', () => { 32 | var signal = array.fill(10, (n) => 2) 33 | var result = array.mult(5, signal, signal) 34 | assert.deepEqual(result, [4, 4, 4, 4, 4]) 35 | assert.deepEqual(signal, [2,2,2,2,2,2,2,2,2,2]) 36 | array.mult(5, signal, signal, signal) 37 | assert.deepEqual(signal, [4,4,4,4,4,2,2,2,2,2]) 38 | }) 39 | 40 | test('concat', () => { 41 | const ones = array.fill(5, (x) => 1) 42 | const twos = array.fill(5, (x) => 2) 43 | assert.deepEqual(array.concat(ones, twos), from([1, 1, 1, 1, 1, 2, 2, 2, 2, 2])) 44 | }) 45 | 46 | test('fill', function () { 47 | const zeros = array.fill(100, (x) => 0) 48 | assert.deepStrictEqual(zeros, array.zeros(100)) 49 | const ones = array.fill(10, (x) => 1) 50 | assert.deepEqual(ones, from([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])) 51 | const indices = array.fill(10, (n, N) => n) 52 | assert.deepEqual(indices, from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) 53 | const lens = array.fill(10, (n, N) => N) 54 | assert.deepEqual(lens, from([10, 10, 10, 10, 10, 10, 10, 10, 10, 10])) 55 | }) 56 | 57 | test('generate in-place', function () { 58 | const PI = Math.PI 59 | const sine32 = new Float32Array(10) 60 | array.fill(10, (n, N) => Math.sin(2 * PI * n / (N - 1)), sine32) 61 | assert.deepEqual(sine32, [ 62 | 0, 0.6427876353263855, 0.9848077297210693, 0.8660253882408142, 0.3420201539993286, -0.3420201539993286, -0.8660253882408142, -0.9848077297210693, -0.6427876353263855, -2.4492937051703357e-16 63 | ]) 64 | const sine64 = new Float64Array(10) 65 | array.fill(10, (n, N) => Math.sin(2 * PI * n / (N - 1)), sine64) 66 | assert.deepEqual(sine64, [ 67 | 0, 0.6427876096865393, 0.984807753012208, 0.8660254037844387, 0.3420201433256689, -0.34202014332566866, -0.8660254037844385, -0.9848077530122081, -0.6427876096865396, -2.4492935982947064e-16 68 | ]) 69 | }) 70 | 71 | test('round', function () { 72 | const signal = array.fill(10, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 73 | assert.deepEqual(array.round(signal, 2), from([0,0.64,0.98,0.87,0.34,-0.34,-0.87,-0.98,-0.64,0])) 74 | assert.deepEqual(array.round(signal, 3), from([0,0.643,0.985,0.866,0.342,-0.342,-0.866,-0.985,-0.643,0])) 75 | }) 76 | 77 | test('testAll', function () { 78 | const signal = [1, 1, 1, 2, 2, 2] 79 | assert(array.testAll(3, (x) => x === 1, signal)) 80 | assert(!array.testAll(4, (x) => x === 1, signal)) 81 | }) 82 | -------------------------------------------------------------------------------- /packages/array/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "add-canvas@gist:2e32779f41baefc491a370a138767fdf": 6 | version "1.0.0" 7 | resolved "https://gist.github.com/2e32779f41baefc491a370a138767fdf.git#943a56d94c372be6876b1a8515318f1716d1090e" 8 | 9 | benchmark@^2.1.3: 10 | version "2.1.4" 11 | resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" 12 | dependencies: 13 | lodash "^4.17.4" 14 | platform "^1.3.3" 15 | 16 | "draw-waveform@gist:ffe0c4e7a06586d4bbb018ca69887ae8": 17 | version "1.0.0" 18 | resolved "https://gist.github.com/ffe0c4e7a06586d4bbb018ca69887ae8.git#e3089fa13f16843f38e3b66f6e9e90f53bf9775c" 19 | 20 | easy-benchmark@../../support/easy-benchmark: 21 | version "0.0.1" 22 | dependencies: 23 | benchmark "^2.1.3" 24 | 25 | lodash@^4.17.4: 26 | version "4.17.4" 27 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 28 | 29 | platform@^1.3.3: 30 | version "1.3.4" 31 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" 32 | -------------------------------------------------------------------------------- /packages/dft/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## dft 4 | > Discrete Fourier Transformation 5 | 6 | [![npm install dsp-dft](https://nodei.co/npm/dsp-dft.png?mini=true)](https://npmjs.org/package/dsp-dft/) 7 | 8 | This module have functions to compute DFT using the correlation algorithm 9 | (the simplest and easy to understand, also the slowest) 10 | 11 | > Various methods are used to obtain DFT for time domain samples including use 12 | of Simultaneous Equations using Gaussian elimination, correlation, and using 13 | the Fast Fourier Transform algorithm. The first option requires massive work 14 | even for a comparitively small number of samples. In actual practice, 15 | correlation is the preferred method if the DFT has less than about 32 points. 16 | 17 | The function of this module is __really slow__, and not intended to be used 18 | in production. It has two goals: 19 | 20 | - Educational: learn how to implement the DFT correlation algorithm 21 | - Testing: test more complex algorithms against this to check outputs 22 | 23 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 24 | 25 | **Example** 26 | ```js 27 | // using dsp-kit 28 | var dsp = require('dsp-kit') 29 | dsp.dft('forward', signal) 30 | dsp.dft('inverse', complexSignal) 31 | ``` 32 | **Example** 33 | ```js 34 | // requiring only this module 35 | var dft = require('dsp-dft') 36 | dft.dft('forward', signal) 37 | ``` 38 | 39 | 40 | ### dft.dft(direction, signal, output) ⇒ Object 41 | Perform a DFT using a _brute force_ correlation algorithm 42 | 43 | It accepts real and complex signals of any size. 44 | 45 | It implements the mathematical function as it, without any kind of optimization, 46 | so it's the slowest algorithm possible. 47 | 48 | This algorithm is not intended to be used in production. It's main use 49 | (apart from the educational purposes) is to check the output of more 50 | complex algorithms 51 | 52 | **Kind**: static method of [dft](#module_dft) 53 | **Returns**: Object - the DFT output 54 | 55 | | Param | Type | Description | 56 | | --- | --- | --- | 57 | | direction | String | Can be 'forward' or 'inverse' | 58 | | signal | Array | Object | The (real) signal array, or the complex signal object `{ imag, real }` | 59 | | output | Object | (Optional) the pair of buffers `{ imag, real }` to store the output (or new buffers are created if not provided) | 60 | 61 | **Example** 62 | ```js 63 | dft('forward', signal) 64 | dft('inverse', { real: ..., imag: .... }) 65 | ``` 66 | -------------------------------------------------------------------------------- /packages/dft/images/correlation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/dft/images/correlation.png -------------------------------------------------------------------------------- /packages/dft/images/dft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/dft/images/dft.png -------------------------------------------------------------------------------- /packages/dft/images/dft2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/dft/images/dft2.png -------------------------------------------------------------------------------- /packages/dft/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Discrete Fourier Transformation 3 | * 4 | * [![npm install dsp-dft](https://nodei.co/npm/dsp-dft.png?mini=true)](https://npmjs.org/package/dsp-dft/) 5 | * 6 | * This module have functions to compute DFT using the correlation algorithm 7 | * (the simplest and easy to understand, also the slowest) 8 | * 9 | * > Various methods are used to obtain DFT for time domain samples including use 10 | * of Simultaneous Equations using Gaussian elimination, correlation, and using 11 | * the Fast Fourier Transform algorithm. The first option requires massive work 12 | * even for a comparitively small number of samples. In actual practice, 13 | * correlation is the preferred method if the DFT has less than about 32 points. 14 | * 15 | * The function of this module is __really slow__, and not intended to be used 16 | * in production. It has two goals: 17 | * 18 | * - Educational: learn how to implement the DFT correlation algorithm 19 | * - Testing: test more complex algorithms against this to check outputs 20 | * 21 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 22 | * 23 | * @example 24 | * // using dsp-kit 25 | * var dsp = require('dsp-kit') 26 | * dsp.dft('forward', signal) 27 | * dsp.dft('inverse', complexSignal) 28 | * 29 | * @example 30 | * // requiring only this module 31 | * var dft = require('dsp-dft') 32 | * dft.dft('forward', signal) 33 | * 34 | * @module dft 35 | */ 36 | const { sin, cos, PI } = Math 37 | 38 | /** 39 | * Perform a DFT using a _brute force_ correlation algorithm 40 | * 41 | * It accepts real and complex signals of any size. 42 | * 43 | * It implements the mathematical function as it, without any kind of optimization, 44 | * so it's the slowest algorithm possible. 45 | * 46 | * This algorithm is not intended to be used in production. It's main use 47 | * (apart from the educational purposes) is to check the output of more 48 | * complex algorithms 49 | * 50 | * @param {String} direction - Can be 'forward' or 'inverse' 51 | * @param {Array|Object} signal - The (real) signal array, or the complex signal 52 | * object `{ imag, real }` 53 | * @param {Object} output - (Optional) the pair of buffers `{ imag, real }` to 54 | * store the output (or new buffers are created if not provided) 55 | * @return {Object} the DFT output 56 | * 57 | * @example 58 | * dft('forward', signal) 59 | * dft('inverse', { real: ..., imag: .... }) 60 | */ 61 | export function dft (dir, signal, output) { 62 | if (dir !== 'forward' && dir !== 'inverse') throw Error('Direction must be "forward" or "inverse" but was ' + dir) 63 | var inverse = dir === 'inverse' 64 | signal = toComplex(signal) 65 | output = toComplex(output, signal.real.length) 66 | process(inverse, signal, output) 67 | return output 68 | } 69 | 70 | /** 71 | * Perform the actual DFT correlation 72 | * 73 | * @private 74 | * @param {Boolean} inverse - Perform inverse DFT or not 75 | * @param {Object} signal - A complex ({ real, imag }) input signal 76 | * @param {Object} output - The output ({ real, imag }) output signal 77 | * @return {Object} the output 78 | */ 79 | function process (inverse, signal, output) { 80 | let r, i, theta 81 | const { real, imag } = signal 82 | // we take the size of the output. It can be smaller than the source 83 | const size = output.real.length 84 | for (let k = 0; k < size; k++) { 85 | r = i = 0.0 86 | for (let n = 0; n < size; n++) { 87 | theta = 2 * PI * k * n / size 88 | r += real[n] * cos(theta) - imag[n] * sin(theta) 89 | i -= real[n] * sin(theta) + imag[n] * cos(theta) 90 | } 91 | output.real[k] = inverse ? r / size : r 92 | output.imag[k] = inverse ? i / size : i 93 | } 94 | } 95 | 96 | /** 97 | * Given a signal or a size, create a complex signal. 98 | * @private 99 | */ 100 | function toComplex (signal, size) { 101 | if (!signal) { 102 | if (!size) throw Error('A signal is required') 103 | return { real: new Float32Array(size), imag: new Float32Array(size) } 104 | } else if (signal.length) { 105 | return { real: signal, imag: new Float32Array(signal.length) } 106 | } else if (!signal.real || !signal.imag || signal.real.length !== signal.imag.length) { 107 | throw Error('Not valid signal: ' + signal + ' (must be an object { real: Array, imag: Array })') 108 | } else { 109 | return signal 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/dft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-dft", 3 | "description": "Discrete Fourier Transform", 4 | "version": "0.0.1", 5 | "main": "build/index.js", 6 | "module": "index", 7 | "scripts": { 8 | "pretest": "rollup -f cjs -o build/index.js -- index.js", 9 | "test": "node test/test.js", 10 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/dft.md" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-dft", 13 | "keywords": [ 14 | "fourier", 15 | "transform", 16 | "dsp", 17 | "signal", 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-dft/#readme", 26 | "devDependencies": { 27 | "dsp-array": "^0.0.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/dft/test/test.js: -------------------------------------------------------------------------------- 1 | var almost = require('almost-equal') 2 | const test = require('tst') 3 | const assert = require('assert') 4 | var dft = require('..') 5 | var arr = require('dsp-array') 6 | 7 | const { PI, sin } = Math 8 | const PI2 = 2 * PI 9 | function round (a, n = 6) { return arr.round(a, n) } 10 | 11 | test('Lyons example 1', () => { 12 | const signal = arr.fill(8, (n, N) => 13 | sin(PI2 * n / N) + 0.5 * sin(2 * PI2 * n / N + 3 * PI / 4)) 14 | 15 | // signal 16 | assert.deepEqual(signal.length, 8) 17 | assert.almost(round(signal, 4), [0.3536, 0.3536, 0.6464, 1.0607, 0.3536, -1.0607, -1.3536, -0.3536]) 18 | 19 | // forward 20 | const freqDomain = dft.dft('forward', signal) 21 | assert.deepEqual(arr.round(freqDomain.real, 4), [0, 0, 1.4142, 0, 0, 0, 1.4142, 0]) 22 | assert.deepEqual(arr.round(freqDomain.imag, 4), [0, -4, 1.4142, 0, 0, 0, -1.4142, 4]) 23 | 24 | // inverse 25 | const timeDomain = dft.dft('inverse', freqDomain) 26 | assert.almost(round(timeDomain.real), round(signal)) 27 | }) 28 | 29 | test('forward', () => { 30 | const size = 8 31 | const signal = arr.fill(size, (n, N) => Math.sin(n / N)) 32 | const result = dft.dft('forward', signal) 33 | assert.almost(result.real, [3.2520562955298242, -0.5212520123271656, -0.4496171806736543, -0.4375990648812408, -0.4351197797657045, -0.43759906488124195, -0.44961718067365586, -0.5212520123271627]) 34 | assert.almost(result.imag, [0, 1.0435441269071244, 0.42404402499638455, 0.17507452536950258, -3.6977482267336003e-16, -0.1750745253695032, -0.42404402499638527, -1.0435441269071233]) 35 | }) 36 | 37 | test('inverse', () => { 38 | const size = 1024 39 | const signal = arr.fill(size, (n, N) => Math.sin(n / N)) 40 | const forward = dft.dft('forward', signal) 41 | const inverse = dft.dft('inverse', forward) 42 | assert.almost(inverse.real, signal) 43 | }) 44 | 45 | assert.almost = function (x, y, message) { 46 | if (x.length && y.length) { 47 | return x.every(function (x, i) { 48 | return assert.almost(x, y[i], (message || ' : ') + i) 49 | }) 50 | } 51 | 52 | var EPSILON = 10e-8 53 | if (!almost(x, y, EPSILON)) assert.fail(x, y, `${x} ≈ ${y}`, '≈ ' + message) 54 | return true 55 | } 56 | -------------------------------------------------------------------------------- /packages/dsp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/dsp/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## dsp-kit 4 | > Digital Signal Processing 5 | 6 | [![npm install dsp-kit](https://nodei.co/npm/dsp-kit.png?mini=true)](https://npmjs.org/package/dsp-kit/) 7 | 8 | This module is a facade of some of the `dsp-kit` modules. Currently 9 | it exposes: 10 | 11 | - `array`: create and manipulate arrays 12 | - `fft`: fast fourier transform functions 13 | - `spectrum`: manipulate the result of the fourier transform 14 | - `fftshift`: perform zero phase fft shifting 15 | - `noise`: generate noise signals 16 | - `window`: several windowing functions 17 | 18 | -------------------------------------------------------------------------------- /packages/dsp/browser.js: -------------------------------------------------------------------------------- 1 | window.dsp = require('./build/index.js') 2 | -------------------------------------------------------------------------------- /packages/dsp/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Digital Signal Processing 3 | * 4 | * [![npm install dsp-kit](https://nodei.co/npm/dsp-kit.png?mini=true)](https://npmjs.org/package/dsp-kit/) 5 | * 6 | * This module is a facade of some of the `dsp-kit` modules. Currently 7 | * it exposes: 8 | * 9 | * - `array`: create and manipulate arrays 10 | * - `fft`: fast fourier transform functions 11 | * - `spectrum`: manipulate the result of the fourier transform 12 | * - `fftshift`: perform zero phase fft shifting 13 | * - `noise`: generate noise signals 14 | * - `window`: several windowing functions 15 | * 16 | * @module dsp-kit 17 | */ 18 | export { add, mult, zeros, fill, concat, round, testAll } from 'dsp-array' 19 | export { dft } from 'dsp-dft' 20 | export { fft } from 'dsp-fft' 21 | export { white, pink, brown } from 'dsp-noise' 22 | export { fftshift, ifftshift } from 'dsp-fftshift' 23 | export { bandWidth, bandFrequency, polar, rectangular, unwrap } from 'dsp-spectrum' 24 | 25 | // window is exported into it's own namespace 26 | import * as win from 'dsp-window' 27 | export const window = win 28 | -------------------------------------------------------------------------------- /packages/dsp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-kit", 3 | "description": "Digital Signal Processing kit", 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/dsp.md" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-kit", 13 | "keywords": [ 14 | "fourier", 15 | "transform", 16 | "dsp", 17 | "signal", 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-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 | draw(e, model.buffer)} width="600" height="150"> 19 | Play! 20 |
21 | ` 22 | 23 | const Overlap = (model, msg) => html` 24 |
25 |

Overlap

26 |
${model.duration}
27 | draw(e, model.buffer)} width="600" height="150"> 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 | [![npm install dsp-fft-radix2](https://nodei.co/npm/dsp-fft-radix2.png?mini=true)](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 | [![npm install dsp-fftshift](https://nodei.co/npm/dsp-fftshift.png?mini=true)](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 | * [![npm install dsp-fftshift](https://nodei.co/npm/dsp-fftshift.png?mini=true)](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 | [![npm install dsp-noise](https://nodei.co/npm/dsp-noise.png?mini=true)](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 | * [![npm install dsp-noise](https://nodei.co/npm/dsp-noise.png?mini=true)](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 | [![npm install dsp-ola](https://nodei.co/npm/dsp-ola.png?mini=true)](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 | * [![npm install dsp-ola](https://nodei.co/npm/dsp-ola.png?mini=true)](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 | * [![npm install dsp-ola](https://nodei.co/npm/dsp-ola.png?mini=true)](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 | [![npm install dsp-oscillator](https://nodei.co/npm/dsp-oscillator.png?mini=true)](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 | * [![npm install dsp-oscillator](https://nodei.co/npm/dsp-oscillator.png?mini=true)](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 | [![npm install dsp-phase-vocoder](https://nodei.co/npm/dsp-phase-vocoder.png?mini=true)](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 | * [![npm install dsp-phase-vocoder](https://nodei.co/npm/dsp-phase-vocoder.png?mini=true)](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} frames - and array of frames. Each frame is 13 | * a `{ magnitudes, phases }` object 14 | * @param {Object} parameters 15 | * @param {Array} omega - a pre-calculated center frequency of each bin 16 | */ 17 | export default function recalcPhases (frames, { size, factor, hop }, omega) { 18 | var ha = hop // hop analysis 19 | var hs = hop * factor // hop synthesis 20 | var numFrames = frames.length 21 | 22 | var prev = frames[0].phases 23 | for (var f = 1; f < numFrames; f += 1) { 24 | var current = frames[f].phases 25 | 26 | for (var i = 0; i < size; i += 1) { 27 | // Calculate the difference between current and previous phase spectra 28 | // and, then, the sample-wise difference with the frequency centres 29 | var centerFreq = omega[i] 30 | // var expectedPhaseAdv = ha * centerFreq 31 | var phaseIncr = current[i] - prev[i] - ha * centerFreq 32 | // Due to the fact that the phase values are given in modulo 2π and, as 33 | // such, phase ‘jumps’ will occur, we need to unwrap the phase in order to 34 | // obtain a continuous phase function 35 | var unwrapped = phaseIncr - PI2 * round(phaseIncr / PI2) 36 | if (f === 547 && i > 200) { 37 | console.log(i, centerFreq, phaseIncr, unwrapped) 38 | console.log(current[i], prev[i], ha) 39 | } 40 | // Compute the instantaneous frequency ω for each freq. bin k 41 | // var realFreq = centerFreq + unwrapped / ha 42 | // Now, we can use ωk (realFreq) to compute the output phase spectra ∠Yi by 43 | // advancing the previous output ∠Yi−1 according to the synthesis hop 44 | // size Hs 45 | if (isNaN(unwrapped)) { 46 | console.log('NaN', numFrames, f, size, i) 47 | console.log('joder', prev[i], prev) 48 | throw Error('NaN') 49 | } 50 | current[i] = prev[i] + hs * (centerFreq + unwrapped / ha) 51 | } 52 | prev = current 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/phase-vocoder/lib/synthesis.js: -------------------------------------------------------------------------------- 1 | import { zeros, add } from 'dsp-array' 2 | import { rectangular } from 'dsp-spectrum' 3 | import { ifftshift } from 'dsp-fftshift' 4 | 5 | /** 6 | * Synthesize a signal from a collection of frames 7 | * @private 8 | * @param {Array} frames - an array of frames (`{ magnitudes, phases }`) 9 | * @param {Object} options - All required: size, hop, sampeRate, factor 10 | * @param {Array} output - (Optional) the output array 11 | */ 12 | export default function synthesis (frames, { ft, size, hop, sampleRate, factor }, output) { 13 | if (!frames || !frames.length) throw Error('"frames" parameter is required in synthesis') 14 | 15 | var len = frames.length 16 | var hopS = hop * factor 17 | console.log('SYNTHESIS', hop, hopS, len, len * hopS, output) 18 | if (!output) output = zeros(len * hopS + size) 19 | var position = 0 20 | 21 | // create some intermediate buffers (and reuse it for performance) 22 | var rectFD = { real: zeros(size), imag: zeros(size) } 23 | var timeDomain = { real: zeros(size), imag: zeros(size) } 24 | for (var i = 0; i < len; i++) { 25 | // 1. Convert freq-domain from polar to rectangular 26 | rectangular(frames[i], rectFD) 27 | // 2. Convert from freq-domain in rectangular to time-domain 28 | var signal = ft.inverse(rectFD, timeDomain).real 29 | // 3. Unshift the previous cycling shift 30 | ifftshift(signal) 31 | // 4. Overlap add 32 | var write = output.subarray(position, position + hopS) 33 | add(hopS, signal, write, write) 34 | position += hopS 35 | } 36 | return output 37 | } 38 | -------------------------------------------------------------------------------- /packages/phase-vocoder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-phase-vocoder", 3 | "description": "Phase vocoder algorithm in javascript", 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 | "watch": "rollup --watch -c ../../rollup.config.js", 11 | "test": "node test/test.js", 12 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/phase-vocoder.md" 13 | }, 14 | "repository": "https://github.com/oramics/dsp-kit/packages/phase-vocoder", 15 | "keywords": [ 16 | "timestretch", 17 | "pitchshift", 18 | "phase-vocoder", 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/phase-vocoder/#readme", 28 | "dependencies": { 29 | "dsp-array": "^0.0.1", 30 | "dsp-window": "^0.0.1", 31 | "dsp-spectrum": "^0.0.1", 32 | "dsp-fft": "^0.0.1", 33 | "dsp-fftshift": "^0.0.1" 34 | }, 35 | "devDependencies": { 36 | "assert-almost": "file:///Users/Dani/Code/Oramics/dsp-kit/support/assert-almost", 37 | "dspjs": "../../support/dspjs" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/phase-vocoder/pv-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/phase-vocoder/pv-diagram.png -------------------------------------------------------------------------------- /packages/phase-vocoder/test/test.js: -------------------------------------------------------------------------------- 1 | const test = require('tst') 2 | const assert = require('assert') 3 | require('assert-almost')(assert) 4 | const { analysis, synthesis, phaseVocoder } = require('..') 5 | const { fft } = require('dsp-fft') 6 | const { polar, rectangular } = require('dsp-spectrum') 7 | const { fftshift, ifftshift } = require('dsp-fftshift') 8 | const arr = require('dsp-array') 9 | 10 | test.only('a->s', () => { 11 | var SIZE = 1024 12 | var ft = fft(SIZE) 13 | var signal = arr.fill(SIZE, (x) => Math.random() * 2 - 1) 14 | var shifted = fftshift(signal.slice()) 15 | var freqDomain = ft.forward(shifted) 16 | var polarFD = polar(freqDomain) 17 | var rectFD = rectangular(polarFD) 18 | var timeDomain = ft.inverse(rectFD) 19 | var unshifted = ifftshift(timeDomain.real) 20 | assert.almost(unshifted, signal) 21 | }) 22 | 23 | test('analysis', () => { 24 | var signal = arr.fill(320, (x) => x) 25 | var frames = analysis(signal, { size: 64, hop: 32 }) 26 | assert.equal(frames.length, 8) 27 | }) 28 | 29 | test('synthesis', () => { 30 | 31 | }) 32 | 33 | test('phaseVocoder', function () { 34 | var signal = arr.fill(10000, (x) => 2 * Math.random() - 1) 35 | var stretch = phaseVocoder() 36 | var result = stretch(0.5, signal) 37 | assert.equal(result.length, 6144) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/phase-vocoder/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | almost-equal@^1.1.0: 6 | version "1.1.0" 7 | resolved "https://registry.yarnpkg.com/almost-equal/-/almost-equal-1.1.0.tgz#f851c631138757994276aa2efbe8dfa3066cccdd" 8 | 9 | "assert-almost@file:///Users/Dani/Code/Oramics/dsp-kit/support/assert-almost": 10 | version "0.0.1" 11 | dependencies: 12 | almost-equal "^1.1.0" 13 | benchmark "^2.1.3" 14 | 15 | benchmark@^2.1.3: 16 | version "2.1.4" 17 | resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" 18 | dependencies: 19 | lodash "^4.17.4" 20 | platform "^1.3.3" 21 | 22 | dspjs@../../support/dspjs: 23 | version "1.0.0" 24 | 25 | lodash@^4.17.4: 26 | version "4.17.4" 27 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 28 | 29 | platform@^1.3.3: 30 | version "1.3.4" 31 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" 32 | -------------------------------------------------------------------------------- /packages/rfft/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## rfft 4 | > real split radix FFT algorithm 5 | 6 | [![npm install dsp-rfft](https://nodei.co/npm/dsp-rfft.png?mini=true)](https://npmjs.org/package/dsp-rfft/) 7 | 8 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 9 | 10 | Most of the code was adapted from [dsp.js](https://github.com/corbanbrook/dsp.js) 11 | by @corbanbrook and some parts by @Spudd86 12 | 13 | ### References 14 | 15 | - Original C implementation at http://www.jjj.de/fxt/ 16 | 17 | **Example** 18 | ```js 19 | const dsp = require('dsp-kit') 20 | const forward = dsp.rfft(1024) 21 | const result = forward(signal) 22 | ``` 23 | 24 | 25 | ### rfft.rfft(buffer) ⇒ 26 | Performs a forward transform on the sample buffer. 27 | Converts a time domain signal to frequency domain spectra. 28 | 29 | **Kind**: static method of [rfft](#module_rfft) 30 | **Returns**: The frequency spectrum array 31 | 32 | | Param | Type | Description | 33 | | --- | --- | --- | 34 | | buffer | Array | The sample buffer. Buffer Length must be power of 2 | 35 | 36 | -------------------------------------------------------------------------------- /packages/rfft/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > real split radix FFT algorithm 3 | * 4 | * [![npm install dsp-rfft](https://nodei.co/npm/dsp-rfft.png?mini=true)](https://npmjs.org/package/dsp-rfft/) 5 | * 6 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 7 | * 8 | * Most of the code was adapted from [dsp.js](https://github.com/corbanbrook/dsp.js) 9 | * by @corbanbrook and some parts by @Spudd86 10 | * 11 | * ### References 12 | * 13 | * - Original C implementation at http://www.jjj.de/fxt/ 14 | * 15 | * @example 16 | * const dsp = require('dsp-kit') 17 | * const forward = dsp.rfft(1024) 18 | * const result = forward(signal) 19 | * 20 | * @module rfft 21 | */ 22 | import generateReverseTable from './lib/reverse-table' 23 | import forward from './lib/forward' 24 | import inverse from './lib/inverse' 25 | const { sqrt } = Math 26 | 27 | /** 28 | * Performs a forward transform on the sample buffer. 29 | * Converts a time domain signal to frequency domain spectra. 30 | * 31 | * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 32 | * 33 | * @returns The frequency spectrum array 34 | */ 35 | export function rfft (bufferSize) { 36 | var trans = new Float64Array(bufferSize) 37 | var spectrum = new Float64Array(bufferSize / 2) 38 | var table = generateReverseTable(bufferSize) 39 | 40 | return function (buffer) { 41 | forward(bufferSize, buffer, trans, spectrum, table) 42 | return trans 43 | } 44 | } 45 | 46 | export function irfft (bufferSize) { 47 | return function (trans, output) { 48 | return inverse(bufferSize, trans, output) 49 | } 50 | } 51 | 52 | export function rfftSpectrum (x, spectrum) { 53 | var n = x.length 54 | var i = n / 2 55 | var bSi = 2 / n 56 | var rval, ival, mag 57 | if (!spectrum) spectrum = new Float64Array(i) 58 | while (--i) { 59 | rval = x[i] 60 | ival = x[n - i - 1] 61 | mag = bSi * sqrt(rval * rval + ival * ival) 62 | spectrum[i] = mag 63 | } 64 | spectrum[0] = Math.abs(bSi * x[0]) 65 | return spectrum 66 | } 67 | -------------------------------------------------------------------------------- /packages/rfft/lib/reverse-permute.js: -------------------------------------------------------------------------------- 1 | export default function reverseBinPermute (bufferSize, dest, source) { 2 | var halfSize = bufferSize >>> 1 3 | var nm1 = bufferSize - 1 4 | var i = 1 5 | var r = 0 6 | var h 7 | 8 | dest[0] = source[0] 9 | 10 | do { 11 | r += halfSize 12 | dest[i] = source[r] 13 | dest[r] = source[i] 14 | 15 | i++ 16 | 17 | h = halfSize << 1 18 | while (h = h >> 1, !((r ^= h) & h)) ; 19 | 20 | if (r >= i) { 21 | dest[i] = source[r] 22 | dest[r] = source[i] 23 | 24 | dest[nm1 - i] = source[nm1 - r] 25 | dest[nm1 - r] = source[nm1 - i] 26 | } 27 | i++ 28 | } while (i < halfSize) 29 | dest[nm1] = source[nm1] 30 | } 31 | -------------------------------------------------------------------------------- /packages/rfft/lib/reverse-table.js: -------------------------------------------------------------------------------- 1 | export default function generateReverseTable (bufferSize) { 2 | var reverseTable = new Uint32Array(bufferSize) 3 | var halfSize = bufferSize >>> 1 4 | var nm1 = bufferSize - 1 5 | var i = 1 6 | var r = 0 7 | var h 8 | 9 | reverseTable[0] = 0 10 | 11 | do { 12 | r += halfSize 13 | 14 | reverseTable[i] = r 15 | reverseTable[r] = i 16 | 17 | i++ 18 | 19 | h = halfSize << 1 20 | while (h = h >> 1, !((r ^= h) & h)) ; 21 | 22 | if (r >= i) { 23 | reverseTable[i] = r 24 | reverseTable[r] = i 25 | 26 | reverseTable[nm1 - i] = nm1 - r 27 | reverseTable[nm1 - r] = nm1 - i 28 | } 29 | i++ 30 | } while (i < halfSize) 31 | 32 | reverseTable[nm1] = nm1 33 | return reverseTable 34 | } 35 | -------------------------------------------------------------------------------- /packages/rfft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-rfft", 3 | "description": "Real split radix FFT 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/*.js", 10 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/rfft.md" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-rfft", 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/dsp-rfft/#readme", 23 | "devDependencies": { 24 | "dsp-array": "^0.0.1", 25 | "dspjs": "../../support/dspjs" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/rfft/test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tst') 2 | var assert = require('assert') 3 | var arr = require('dsp-array') 4 | var dspjs = require('dspjs') 5 | var dsp = require('..') 6 | 7 | test.skip('fft and rfft returns the same spectrum', function () { 8 | var signal = arr.fill(1024, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 9 | var rfft = new dspjs.RFFT(1024, 44100) 10 | var fft = new dspjs.FFT(1024, 44100) 11 | rfft.forward(signal) 12 | fft.forward(signal) 13 | assert.deepEqual(arr.round(rfft.spectrum), arr.round(fft.spectrum)) 14 | // var result = { real: arr.zeros(1024), imag: arr.zeros(1024) } 15 | }) 16 | 17 | test('legacy rfft implementation and new one gives same result', function () { 18 | var signal = arr.fill(1024, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 19 | var rfft = new dspjs.RFFT(1024, 44100) 20 | var forward = dsp.rfft(1024) 21 | rfft.forward(signal) 22 | assert.deepEqual(dsp.rfftSpectrum(forward(signal)), rfft.spectrum) 23 | }) 24 | 25 | test.skip('inverse rfft restores the signal', function () { 26 | var signal = arr.fill(1024, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 27 | var forward = dsp.rfft(1024) 28 | var inverse = dsp.irfft(1024) 29 | assert.deepEqual(inverse(forward(signal)), signal) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/rfft/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/signal/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## signal 4 | > Generate signals 5 | 6 | This is a collection of functions to generate signals using one sample a time. 7 | This is a quite slow implementation and not ready for realtime generation. 8 | The main focus is on easy to read, aka learn-dsp. 9 | 10 | This implementation is largely based on the excellent [genish.sh](https://github.com/charlieroberts/genish.js) 11 | library that has much more performance than this one. 12 | 13 | -------------------------------------------------------------------------------- /packages/signal/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Generate signals 3 | * 4 | * This is a collection of functions to generate signals using one sample a time. 5 | * This is a quite slow implementation and not ready for realtime generation. 6 | * The main focus is on easy to read, aka learn-dsp. 7 | * 8 | * This implementation is largely based on the excellent [genish.sh](https://github.com/charlieroberts/genish.js) 9 | * library that has much more performance than this one. 10 | * 11 | * @module signal 12 | */ 13 | 14 | export { 15 | abs, add, div, mod, mul, pow, sub, 16 | sin, cos, tan, asin, acos, atan, 17 | floor, ceil, round 18 | } from './lib/math' 19 | export { and, or } from './lib/logic' 20 | export { eq, lt, lte, ltp, gt, gte, gtp } from './lib/comparasion' 21 | export { accum, clamp } from './lib/integrator' 22 | export { bang, ifelse } from './lib/control' 23 | export { loop } from './lib/buffer' 24 | -------------------------------------------------------------------------------- /packages/signal/lib/buffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module signal/buffer 3 | */ 4 | 5 | /** 6 | * @name data 7 | * @function 8 | */ 9 | export const data = (data) => () => data 10 | 11 | export function loop (data) { 12 | const len = data.length 13 | let index = 0 14 | return function () { 15 | const val = data[index] 16 | index = (index + 1) % len 17 | return val 18 | } 19 | } 20 | 21 | export const peek = (data, index, opts) => { 22 | return () => { 23 | const val = data[index] 24 | index++ 25 | return val 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/signal/lib/comparasion.js: -------------------------------------------------------------------------------- 1 | import { val } from './core' 2 | 3 | /** 4 | * > Comparasion operation on signals 5 | * 6 | * @module signal/comparasion 7 | */ 8 | 9 | /** 10 | * Returns 1 if two inputs are __equal__, otherwise returns 0. 11 | * @name eq 12 | * @function 13 | * @memberof module:signal/comparasion 14 | */ 15 | export const eq = (a, b) => () => val(a) === val(b) ? 1 : 0 16 | 17 | /** 18 | * Returns 1 if a is __greater__ than b, otherwise returns 0. 19 | * @name gt 20 | * @function 21 | * @memberof module:signal/comparasion 22 | */ 23 | export const gt = (a, b) => () => val(a) > val(b) ? 1 : 0 24 | 25 | /** 26 | * Returns 1 if a is __greater or equal__ than b, otherwise returns 0. 27 | * @name gte 28 | * @function 29 | * @memberof module:signal/comparasion 30 | */ 31 | export const gte = (a, b) => () => val(a) >= val(b) ? 1 : 0 32 | 33 | /** 34 | * Returns `a` if `a` is __greater__ than `b`, otherwise returns 0. 35 | * @name gtp 36 | * @function 37 | * @memberof module:signal/comparasion 38 | */ 39 | export const gtp = (a, b) => () => { 40 | const a = val(a) 41 | return a > val(b) ? a : 0 42 | } 43 | 44 | /** 45 | * Returns 1 if a is __less__ than b, otherwise returns 0. 46 | * @name lt 47 | * @function 48 | * @memberof module:signal/comparasion 49 | */ 50 | export const lt = (a, b) => () => val(a) < val(b) ? 1 : 0 51 | 52 | /** 53 | * Returns 1 if a is __less or equal__ than b, otherwise returns 0. 54 | * @name lte 55 | * @function 56 | * @memberof module:signal/comparasion 57 | */ 58 | export const lte = (a, b) => () => val(a) <= val(b) ? 1 : 0 59 | 60 | /** 61 | * Returns `a` if `a` is __less__ than `b`, otherwise returns 0. 62 | * @name ltp 63 | * @function 64 | * @memberof module:signal/comparasion 65 | */ 66 | export const ltp = (a, b) => () => { 67 | const a = val(a) 68 | return a < val(b) ? a : 0 69 | } 70 | -------------------------------------------------------------------------------- /packages/signal/lib/control.js: -------------------------------------------------------------------------------- 1 | import { val } from './core' 2 | 3 | export function bang () { 4 | let value = 0 5 | const gen = function () { 6 | if (value === 0) return 0 7 | value = 0 8 | return 1 9 | } 10 | gen.trigger = function () { 11 | value = 1 12 | } 13 | return gen 14 | } 15 | 16 | export function ifelse (cond, trueSt, falseSt) { 17 | return function () { 18 | return val(cond) ? val(trueSt) : val(falseSt) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/signal/lib/core.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Get value of a signal 4 | * @param {Function|Number} signal 5 | * @return {Number} 6 | * @private 7 | */ 8 | export function val (s) { 9 | return typeof s === 'function' ? s() : parseFloat(s) 10 | } 11 | -------------------------------------------------------------------------------- /packages/signal/lib/integrator.js: -------------------------------------------------------------------------------- 1 | import { val } from './core' 2 | import { mul } from './math' 3 | 4 | /** 5 | * @module signal/integrator 6 | */ 7 | 8 | /** 9 | * Increment a stored value between a provided range 10 | * @name accum 11 | * @function 12 | * @memberof module:signal/integrator 13 | */ 14 | export function accum (step = 0.1, reset = 0, { min = 0, max = 1, init = 0 } = {}) { 15 | var next = init 16 | return function () { 17 | const current = val(reset) === 1 ? init : next 18 | next += val(step) 19 | if (next > max) next = min 20 | else if (next < min) next = max 21 | return current 22 | } 23 | } 24 | 25 | /** 26 | * A phasor accumulates phase, as determined by its frequency, and wraps between 27 | * 0 and 1. This creates a sawtooth wave, but with a dc offset of 1 (no negative 28 | * numbers) 29 | */ 30 | export function phasor (frequency, reset, props = {}, sampleRate = 44100) { 31 | let range = (props.max || 1) - (props.min || 0) 32 | return accum(mul(frequency, range / sampleRate), reset, props) 33 | } 34 | 35 | /** 36 | * Clamp constricts an signal to a particular range. If signal exceeds the 37 | * maximum, the maximum is returned. If signal is less than the minimum, the 38 | * minimum is returned. 39 | * 40 | * @name clamp 41 | * @function 42 | * @param {(Signal|Number)} signal 43 | * @param {(Signal|Number)} [min = -1] 44 | * @param {(Signal|Number)} [max = 1] 45 | */ 46 | export function clamp (signal, min = -1, max = 1) { 47 | return function () { 48 | var s = val(signal) 49 | var M = val(max) 50 | var m = val(min) 51 | return s > M ? M : s < m ? m : s 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/signal/lib/logic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Logic functions for signal processing 3 | * @module signal/logic 4 | */ 5 | import { val } from './core' 6 | 7 | /** 8 | * Returns 1 if both signals are not 0 9 | * 10 | * @function 11 | * @param {(Signal|Number)} a - input signal 12 | * @param {(Signal|Number)} b - input signal 13 | * @return {Signal} 14 | */ 15 | export const and = (a, b) => () => val(a) !== 0 && val(b) !== 0 ? 1 : 0 16 | 17 | /** 18 | * Returns 0 if both signals are 0, 1 otherwise 19 | * 20 | * @function 21 | * @param {(Signal|Number)} a - input signal 22 | * @param {(Signal|Number)} b - input signal 23 | * @return {Signal} 24 | */ 25 | export const or = (a, b) => () => val(a) !== 0 || val(b) !== 0 ? 1 : 0 26 | -------------------------------------------------------------------------------- /packages/signal/lib/math.js: -------------------------------------------------------------------------------- 1 | import { val } from './core' 2 | 3 | /** 4 | * > Mathematical functions and constants 5 | * 6 | * Note that a lot of the math functions have a precision that's implementation-dependent. 7 | * 8 | * @see Math 9 | * @module signal/arithmetic 10 | */ 11 | 12 | /** 13 | * Get absolute value of a signal 14 | * @name abs 15 | * @function 16 | * @memberof module:signal/arithmetic 17 | */ 18 | export const abs = (a) => () => Math.abs(val(a)) 19 | 20 | /** 21 | * Add signals 22 | * @name add 23 | * @function 24 | * @memberof module:signal/arithmetic 25 | */ 26 | export const add = (a, b) => () => val(a) + val(b) 27 | 28 | /** 29 | * Subtract signals 30 | * @name sub 31 | * @function 32 | * @memberof module:signal/arithmetic 33 | */ 34 | export const sub = (a, b) => () => val(a) - val(b) 35 | 36 | /** 37 | * Subtract signals 38 | * @name div 39 | * @function 40 | * @memberof module:signal/arithmetic 41 | */ 42 | export const div = (a, b) => val(a) / val(b) 43 | 44 | /** 45 | * Subtract signals 46 | * @name mod 47 | * @function 48 | * @memberof module:signal/arithmetic 49 | */ 50 | export const mod = (a, b) => val(a) % val(b) 51 | 52 | /** 53 | * Subtract signals 54 | * @name mul 55 | * @function 56 | * @memberof module:signal/arithmetic 57 | */ 58 | export const mul = (a, b) => val(a) * val(b) 59 | /** 60 | * Subtract signals 61 | * @name pow 62 | * @function 63 | * @memberof module:signal/arithmetic 64 | */ 65 | export const pow = (a, b) => Math.pow(val(a), val(b)) 66 | 67 | /** 68 | * Returns the arcosine (in radians) of a number 69 | * @param {Signal|Number} number 70 | * @return {Signal} 71 | */ 72 | export const acos = (number) => () => Math.acos(val(number)) 73 | 74 | /** 75 | * Returns the arcsine (in radians) of a number 76 | * @param {Signal|Number} number 77 | * @return {Signal} 78 | */ 79 | export const asin = (number) => () => Math.asin(val(number)) 80 | 81 | /** 82 | * Returns the arctangent (in radians) of a number 83 | * @param {Signal|Number} number 84 | * @return {Signal} 85 | */ 86 | export const atan = (number) => () => Math.atan(val(number)) 87 | 88 | /** 89 | * Returns the cosine of the input (interpreted as radians) 90 | * @param {Signal|Number} radians 91 | * @return {Signal} 92 | */ 93 | export const cos = (radians) => () => Math.cos(val(radians)) 94 | 95 | /** 96 | * Returns the sine of the input (interpreted as radians) 97 | * @param {Signal|Number} radians 98 | * @return {Signal} 99 | */ 100 | export const sin = (radians) => () => Math.sin(val(radians)) 101 | 102 | /** 103 | * Returns the tangent of the input (interpreted as radians) 104 | * @param {Signal|Number} radians 105 | * @return {Signal} 106 | */ 107 | export const tan = (radians) => () => Math.tan(val(radians)) 108 | 109 | /** 110 | * Returns the smallest integer greater than or equal to a given number 111 | * 112 | * @param {Signal|Number} number 113 | * @return {Signal} 114 | * @see Math.ceil 115 | */ 116 | export const ceil = (num) => () => Math.ceil(val(num)) 117 | 118 | /** 119 | * Returns the largest integer less than or equal to a given number 120 | * 121 | * @param {Signal|Number} number 122 | * @return {Signal} 123 | * @see Math.floor 124 | */ 125 | export const floor = (num) => () => Math.floor(val(num)) 126 | 127 | /** 128 | * Rounds the signal to a number of decimals 129 | */ 130 | export function round (signal, dec = 0) { 131 | var f = Math.pow(10, dec) 132 | return () => Math.round(val(signal) * f) / f 133 | } 134 | -------------------------------------------------------------------------------- /packages/signal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-signal", 3 | "description": "Create and manipulate signals, one sample at time", 4 | "private": true, 5 | "version": "0.0.1", 6 | "main": "build/index.js", 7 | "module": "index.js", 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/signal.md", 12 | "start": "npm run pretest && budo example/example.js" 13 | }, 14 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-signal", 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-signal/#readme", 28 | "devDependencies": { 29 | "dsp-array": "^0.0.1", 30 | "genish.js": "^0.1.0", 31 | "memory-helper": "^1.1.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/signal/test/buffer-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | const { fill } = require('dsp-array') 6 | 7 | test('buffer', () => { 8 | test('loop', () => { 9 | var signal = _.loop([0, 1, 3]) 10 | assert.deepEqual(fill(10, signal), [0,1,3,0,1,3,0,1,3,0]) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/signal/test/comparasion-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | const { fill } = require('dsp-array') 6 | 7 | test('comparasion', () => { 8 | test('eq', () => { 9 | var eq = _.eq(() => 10, () => 10) 10 | assert.deepEqual(fill(10, eq), [1,1,1,1,1,1,1,1,1,1]) 11 | var neq = _.eq(() => 10, () => 9) 12 | assert.deepEqual(fill(10, neq), [0,0,0,0,0,0,0,0,0,0]) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/signal/test/control-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | const { fill } = require('dsp-array') 6 | 7 | test('control', () => { 8 | test('bang', () => { 9 | var bang = _.bang() 10 | assert.deepEqual(fill(10, bang), [0,0,0,0,0,0,0,0,0,0]) 11 | bang.trigger() 12 | assert.deepEqual(fill(10, bang), [1,0,0,0,0,0,0,0,0,0]) 13 | }) 14 | 15 | test('ifelse', () => { 16 | var signal = _.ifelse(_.loop([1, 0, 0]), 10, 20) 17 | assert.deepEqual(fill(10, signal), [10,20,20,10,20,20,10,20,20,10]) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/signal/test/integrator-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | const { fill, roundTo } = require('dsp-array') 6 | const $ = require('genish.js') 7 | 8 | const gen = $.gen.createCallback.bind($.gen) 9 | const round = roundTo(4) 10 | 11 | test('integrator', () => { 12 | test('accum', () => { 13 | var signal = _.accum(0.4) 14 | assert.deepEqual(fill(10, signal), [0,0.4,0.8,0,0.4,0.8,0,0.4,0.8,0]) 15 | }) 16 | 17 | test('accum min max', () => { 18 | var sig = _.accum(1, 0, { min: -2, max: 2 }) 19 | assert.deepEqual(fill(10, sig), [0,1,2,-2,-1,0,1,2,-2,-1]) 20 | }) 21 | 22 | test('accum genish', () => { 23 | var sig = _.accum(0.2, 0, { max: 0.9999 }) 24 | var sigRef = gen($.accum(0.2)) 25 | assert.deepEqual(round(fill(10, sig)), round(fill(10, sigRef))) 26 | }) 27 | 28 | test('clamp', () => { 29 | var signal = _.clamp(_.accum(0.5, false, { min: -2, max: 2 })) 30 | assert.deepEqual(fill(10, signal), [0,0.5,1,1,1,-1,-1,-1,-0.5,0]) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/signal/test/logic-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | 6 | test('logic', () => { 7 | test('and', () => { 8 | assert.equal(_.and(1, 1)(), 1) 9 | assert.equal(_.and(1, 0)(), 0) 10 | assert.equal(_.and(0, 1)(), 0) 11 | assert.equal(_.and(0, 0)(), 0) 12 | }) 13 | 14 | test('or', () => { 15 | assert.equal(_.or(1, 1)(), 1) 16 | assert.equal(_.or(1, 0)(), 1) 17 | assert.equal(_.or(0, 1)(), 1) 18 | assert.equal(_.or(0, 0)(), 0) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/signal/test/math-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const _ = require('..') 5 | const { fill } = require('dsp-array') 6 | 7 | test('trigonometry', () => { 8 | test('sin', () => { 9 | var signal = _.round(_.sin(_.accum(0.1)), 3) 10 | assert.deepEqual(fill(10, signal), [0,0.1,0.199,0.296,0.389,0.479,0.565,0.644,0.717,0.783]) 11 | }) 12 | }) 13 | 14 | test('number', () => { 15 | test('floor', () => { 16 | var sig = _.floor(_.accum(0.4, false, { min: -2, max: 2 })) 17 | assert.deepEqual(fill(14, sig), [0,0,0,1,1,2,-2,-2,-2,-1,-1,-1,0,0]) 18 | }) 19 | }) 20 | 21 | test('arithmetic', () => { 22 | test('add', () => { 23 | var s = _.add(() => 1, () => 2) 24 | assert.deepEqual(s(), 3) 25 | }) 26 | 27 | test('sub', () => { 28 | var sig = _.sub(() => 10, () => 5) 29 | assert.deepEqual(fill(10, sig), [5,5,5,5,5,5,5,5,5,5]) 30 | }) 31 | 32 | test('abs', () => { 33 | var sig = _.abs(_.accum(1, 0, { min: -2, max: 2 })) 34 | assert.deepEqual(fill(10, sig), [0,1,2,2,1,0,1,2,2,1]) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/signal/test/test.js: -------------------------------------------------------------------------------- 1 | 2 | require('./math-test') 3 | require('./logic-test') 4 | require('./buffer-test') 5 | require('./integrator-test') 6 | require('./comparasion-test') 7 | require('./control-test') 8 | -------------------------------------------------------------------------------- /packages/spectral-models/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 danigb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/spectral-models/README.md: -------------------------------------------------------------------------------- 1 | # spectral-models -------------------------------------------------------------------------------- /packages/spectral-models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spectral-models", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest test/*", 8 | "docs": "jsdoc2md src/*.js > API.md" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/danigb/spectral-models.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/danigb/spectral-models/issues" 19 | }, 20 | "homepage": "https://github.com/danigb/spectral-models#readme", 21 | "dependencies": { 22 | "fft.js": "^4.0.3", 23 | "fftshift": "^1.0.1", 24 | "filled-array": "^1.1.0", 25 | "scijs-window-functions": "^2.0.2", 26 | "unwrap-phases": "^1.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/spectral-models/src/dft.js: -------------------------------------------------------------------------------- 1 | // # DFT model 2 | const FFT = require("fft.js"); 3 | const { fftshift } = require("fftshift"); 4 | const winFn = require("scijs-window-functions/hamming"); 5 | const { sqrt, atan2 } = Math; 6 | const fill = require("filled-array"); 7 | const unwrap = require("unwrap-phases"); 8 | const { sin, cos } = Math; 9 | 10 | /** 11 | * Create a DFT model. A DFT model allows to perform dft analysis of a signal 12 | * using a fast fourier function. 13 | * 14 | * @param {number} size - the size of the signal 15 | * @param {window} [window] - the window to use (or a hamming window if not specified) 16 | * @return {Object} an object with two functions: analisys and synthesis 17 | * @example 18 | * import DFT from 'spectral-models' 19 | * const dft = DFT(512) 20 | * dft.analisys(signal) 21 | */ 22 | function DFT(size, window) { 23 | if (!window) window = fill(winFn, size); 24 | if (window.length !== size) 25 | throw Error( 26 | "Window size must be " + size + " length but is " + window.length 27 | ); 28 | 29 | const fft = new FFT(size); 30 | const signal = new Array(size); 31 | const output = fft.createComplexArray(); 32 | const inversed = fft.createComplexArray(); 33 | const spectrumSize = size / 2 + 1; 34 | const magnitudes = new Array(spectrumSize); 35 | const phases = new Array(spectrumSize); 36 | 37 | /** 38 | * A DFT instance 39 | */ 40 | return { 41 | /** 42 | * Given a real signal, create a { magnitudes, phases } object. 43 | * The size of the input must be the same as specified in the constructor. 44 | * 45 | * @memberof DFT 46 | * @param {Array} input - the input signal 47 | * @return {Object} a `{ magnitudes, phases }` object 48 | */ 49 | analisys(input) { 50 | // apply the window to the signal 51 | for (let i = 0; i < size; i++) { 52 | signal[i] = input[i] * window[i]; 53 | } 54 | // rotate the array to phase-zero 55 | fftshift(signal); 56 | // FFT forward 57 | fft.realTransform(output, signal); 58 | for (let i = 0, p = 0; i < spectrumSize; i++, (p += 2)) { 59 | const real = output[p]; 60 | const imag = output[p + 1]; 61 | magnitudes[i] = sqrt(real * real + imag * imag); 62 | phases[i] = atan2(imag, real); 63 | } 64 | unwrap(phases); 65 | return { magnitudes, phases }; 66 | }, 67 | 68 | /** 69 | * Given a { magnitudes, phases } object, return the initial signal. 70 | * Notice that the signal will have a window applied. 71 | * @memberof DFT 72 | * @param {Object} analisys - the analysis object 73 | * @return {Array} the signal 74 | */ 75 | synthesis ({ magnitudes, phases } = {}) { 76 | for (let i = 0, p = 0; i < spectrumSize; i++, (p += 2)) { 77 | // real 78 | output[p] = magnitudes[i] * cos(phases[i]); 79 | // imaginary 80 | output[p + 1] = magnitudes[i] * sin(phases[i]); 81 | } 82 | fft.completeSpectrum(output); 83 | fft.inverseTransform(inversed, output); 84 | fft.fromComplexArray(inversed, signal) 85 | fftshift(signal) 86 | return signal 87 | } 88 | }; 89 | } 90 | 91 | module.exports = DFT; 92 | -------------------------------------------------------------------------------- /packages/spectral-models/test/dft.test.js: -------------------------------------------------------------------------------- 1 | /* global describe test expect */ 2 | var DFT = require('../src/dft') 3 | var fill = require('filled-array') 4 | 5 | const round = (arr, n = 1000) => arr.map(i => Math.floor(i * n) / n) 6 | 7 | describe('DFT', () => { 8 | test('analisys', () => { 9 | // use a rectangular window (ie. no window) 10 | var rect = fill(1, 16) 11 | var dft = new DFT(16, rect) 12 | var signal = fill((n, N) => Math.sin(2 * Math.PI * n / (N - 1)), 16) 13 | var result = dft.analisys(signal) 14 | expect(round(result.magnitudes)).toEqual([ 15 | 0, 7.678, 0.753, 0.425, 0.314, 0.26, 0.231, 0.217, 0.212 16 | ]) 17 | expect(round(result.phases)).toEqual([ 18 | 3.141, 1.767, 1.963, -0.982, -3.927, -6.873, -9.818, -12.763, -15.708 19 | ]) 20 | var synth = dft.synthesis(result) 21 | expect(round(synth)).toEqual(round(signal)) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/spectrum/example/unwrap.js: -------------------------------------------------------------------------------- 1 | var h = require('h') 2 | var spectrum = require('..') 3 | var arr = require('dsp-array') 4 | var waa = require('dsp-waa') 5 | var fft = require('dsp-fft') 6 | 7 | const add = (el) => { document.body.appendChild(el); return el } 8 | const canvas = ({ width = 1024, height = 150 } = {}) => h('canvas', { width, height }) 9 | 10 | add(h('h1', 'Unwrap phase examples')) 11 | 12 | 13 | var signal = arr.fill(1024, (n, N) => Math.sin(10 * 2 * Math.PI * n / (N - 1))) 14 | 15 | add(h('h3', 'Signal')) 16 | waa.drawWaveform(add(canvas()), signal) 17 | 18 | var { magnitudes, phases } = spectrum.polar(fft.fft(1024, signal)) 19 | 20 | add(h('h3', 'Magnitudes')) 21 | waa.drawWaveform(add(canvas()), magnitudes) 22 | add(h('h3', 'Wrapped phases')) 23 | waa.drawWaveform(add(canvas()), phases) 24 | add(h('h3', 'Unwrapped phases')) 25 | waa.drawWaveform(add(canvas()), spectrum.unwrap(phases)) 26 | -------------------------------------------------------------------------------- /packages/spectrum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-spectrum", 3 | "description": "Functions to work with frequency domain signal representations", 4 | "version": "0.0.1", 5 | "main": "build/index.js", 6 | "module": "index", 7 | "scripts": { 8 | "pretest": "rollup -f cjs -o build/index.js -- index.js", 9 | "test": "node test/test.js", 10 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/spectrum.md", 11 | "start": "budo --open example/unwrap.js" 12 | }, 13 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-spectrum", 14 | "keywords": [ 15 | "fourier", 16 | "transform", 17 | "dsp", 18 | "signal", 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-spectrum/#readme", 27 | "devDependencies": { 28 | "dsp-array": "0.0.1", 29 | "dsp-fft": "0.0.1", 30 | "dsp-waa": "0.0.1", 31 | "dspjs": "../../support/dspjs", 32 | "h": "^0.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/spectrum/test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-spacing */ 2 | const test = require('tst') 3 | const assert = require('assert') 4 | const dspjs = require('dspjs') 5 | const fft = require('dsp-fft') 6 | const spectrum = require('..') 7 | const { fill, round } = require('dsp-array') 8 | 9 | function from (...v) { return Float64Array.from(v) } 10 | 11 | test('band width', function () { 12 | const reference = new dspjs.FFT(2048, 44100) 13 | assert.equal(spectrum.bandWidth(2048, 44100), reference.bandwidth) 14 | }) 15 | 16 | test('center frequency', function () { 17 | const reference = new dspjs.FFT(2048, 44100) 18 | for (let i = 0; i < 100; i += 10) { 19 | assert.equal(spectrum.bandFrequency(i, 2048, 44100), reference.getBandFrequency(i)) 20 | } 21 | }) 22 | 23 | test('spectrum', function () { 24 | const size = 64 25 | const signal = fill(size, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 26 | const reference = new dspjs.FFT(size, 44100) 27 | reference.forward(signal) 28 | // the result from the dsp.js is divided by `2 / size` so we have to denormalize 29 | const magnitudes = reference.spectrum.map((n) => n * size / 2) 30 | const polar = spectrum.polar(fft.fft(size).forward(signal)) 31 | // by default the length of magnitures array is the same length as signal 32 | // although some simmetry can be found 33 | assert.deepEqual(polar.magnitudes.length, size) 34 | assert.deepEqual(round(polar.magnitudes.slice(0, magnitudes.length), 5), round(magnitudes, 5)) 35 | }) 36 | 37 | test('rectangular form', function () { 38 | const size = 8 39 | const signal = fill(size, (n, N) => Math.sin(2 * Math.PI * n / (N - 1))) 40 | const complex = fft.fft(size).forward(signal) 41 | const polar = spectrum.polar(complex) 42 | const rect = spectrum.rectangular(polar) 43 | assert.deepEqual(round(rect.real), round(complex.real)) 44 | }) 45 | 46 | test('phase unwrap', function () { 47 | const positive = fill(10, (i) => i % (2 * Math.PI)) 48 | assert.deepEqual(positive, from(0,1,2,3,4,5,6,0.7168146928204138,1.7168146928204138,2.7168146928204138)) 49 | assert.deepEqual(spectrum.unwrap(positive), from(0,1,2,3,4,5,6,7,8,9)) 50 | const negative = fill(10, (i) => -i % (2 * Math.PI)) 51 | assert.deepEqual(negative, from(0,-1,-2,-3,-4,-5,-6,-0.7168146928204138,-1.7168146928204138,-2.7168146928204138)) 52 | assert.deepEqual(spectrum.unwrap(negative), from(0,-1,-2,-3,-4,-5,-6,-7,-8,-9)) 53 | }) 54 | 55 | test('phase unwrap in place', function () { 56 | var signal = fill(1024, (i) => i % (2 * Math.PI)) 57 | var unwrapped = spectrum.unwrap(signal) 58 | spectrum.unwrap(signal, signal) 59 | assert.deepEqual(signal, unwrapped) 60 | }) 61 | -------------------------------------------------------------------------------- /packages/spectrum/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 | 8 | h@^0.1.0: 9 | version "0.1.0" 10 | resolved "https://registry.yarnpkg.com/h/-/h-0.1.0.tgz#24211fe1d9cef2b36cae8ff8255606ea12ecdfb5" 11 | -------------------------------------------------------------------------------- /packages/stft/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## stft 4 | > Short-Time Fourier Transform: a time-varying method of spectral analysis 5 | 6 | [![npm install dsp-stft](https://nodei.co/npm/dsp-stft.png?mini=true)](https://npmjs.org/package/dsp-stft/) 7 | 8 | **Example** 9 | ```js 10 | import { stft, istft } from 'dsp-sftf' 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/stft/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Short-Time Fourier Transform: a time-varying method of spectral analysis 3 | * 4 | * [![npm install dsp-stft](https://nodei.co/npm/dsp-stft.png?mini=true)](https://npmjs.org/package/dsp-stft/) 5 | * 6 | * @example 7 | * import { stft, istft } from 'dsp-sftf' 8 | * 9 | * @module stft 10 | */ 11 | import FFT from 'fft.js' 12 | 13 | export function stft (input, window, output, inputSize, fftSize, hopSize) { 14 | const fft = new FFT(fftSize) 15 | const fftOut = fft.createComplexArray() 16 | const frame = new Array(fftSize) 17 | 18 | for (let pIn = 0, pOut = 0; pIn < inputSize; pIn += hopSize) { 19 | // Create a new windowed frame 20 | for (let i = 0; i < fftSize; i++) { 21 | frame[i] = pIn + i < inputSize ? input[pIn + 1] * window[i] : 0 22 | } 23 | // Transform it 24 | fft.realTransform(fftOut, frame) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/stft/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-stft", 3 | "description": "Short-Time Fourier Transform", 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/stft.md" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-stft", 13 | "keywords": [ 14 | "short-time fourier", 15 | "stft", 16 | "dsp", 17 | "audio", 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-stft/#readme", 26 | "devDependencies": { 27 | }, 28 | "dependencies": { 29 | "fft.js": "^4.0.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/waa/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## waa 4 | > Web Audio API utilities for digital signal processing 5 | 6 | It provides web audio api utilities 7 | 8 | -------------------------------------------------------------------------------- /packages/waa/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Web Audio API utilities for digital signal processing 3 | * 4 | * It provides web audio api utilities 5 | * 6 | * @module waa 7 | */ 8 | 9 | module.exports = { 10 | decodeArrayBuffer: require('./lib/decode-array-buffer'), 11 | player: require('./lib/player'), 12 | drawWaveform: require('./lib/draw-waveform'), 13 | toAudioBuffer: require('./lib/to-audio-buffer') 14 | } 15 | -------------------------------------------------------------------------------- /packages/waa/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/waa/lib/draw-waveform.js: -------------------------------------------------------------------------------- 1 | module.exports = function (canvas, data, color, maxStep = 1000) { 2 | var ctx = canvas.getContext('2d') 3 | if (color) ctx.fillStyle = color 4 | var width = canvas.width 5 | var height = canvas.height 6 | var step = Math.ceil(data.length / width) 7 | var amp = height / 2 8 | for (var i = 0; i < width; i++) { 9 | var neg = 0 10 | var pos = 0 11 | var max = Math.min(step, maxStep) 12 | for (var j = 0; j < max; j++) { 13 | var val = data[(i * step) + j] 14 | if (val < 0) neg += val 15 | else pos += val 16 | } 17 | neg = neg / max 18 | pos = pos / max 19 | ctx.fillRect(i, amp - pos * amp, 1, amp * (pos - neg)) 20 | } 21 | canvas.waveData = data 22 | return canvas 23 | } 24 | -------------------------------------------------------------------------------- /packages/waa/lib/player.js: -------------------------------------------------------------------------------- 1 | var ac = require('audio-context') 2 | 3 | /** 4 | * A play function to be attached to a dom element 5 | * 6 | * @name player 7 | * @function 8 | * @memberof module:dsp-waa 9 | * @example 10 | * document.getElementById('#play').onclick = player(buffer, { loop: true }) 11 | */ 12 | module.exports = function player ({ 13 | buffer, 14 | loop = false, 15 | context = ac, 16 | labels = ['Play', 'Stop'], 17 | gain = false 18 | } = {}) { 19 | function player (e) { 20 | if (player.source) { 21 | player.source.stop() 22 | player.source = null 23 | if (e && labels) e.target.innerText = labels[0] 24 | } else { 25 | if (e && labels) e.target.innerText = labels[1] 26 | player.source = play(player.buffer, loop, gain, context) 27 | } 28 | } 29 | player.buffer = buffer 30 | return player 31 | } 32 | 33 | function play (buffer, loop, gain, context) { 34 | var source = context.createBufferSource() 35 | source.buffer = buffer 36 | if (loop === true) source.loop = true 37 | 38 | if (gain) { 39 | var g = context.createGain() 40 | g.gain.value = gain 41 | g.connect(context.destination) 42 | source.connect(g) 43 | } else { 44 | source.connect(context.destination) 45 | } 46 | 47 | source.start() 48 | return source 49 | } 50 | -------------------------------------------------------------------------------- /packages/waa/lib/to-audio-buffer.js: -------------------------------------------------------------------------------- 1 | var ac = require('audio-context') 2 | 3 | module.exports = function toAudioBuffer (left, right, context) { 4 | var len = left.length 5 | context = context || ac 6 | var buffer = ac.createBuffer(1, len, context.sampleRate) 7 | var data = buffer.getChannelData(0) 8 | for (var i = 0; i < len; i++) { 9 | data[i] = left[i] 10 | } 11 | return buffer 12 | } 13 | -------------------------------------------------------------------------------- /packages/waa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-waa", 3 | "description": "Audio timestretch for Web Audio API", 4 | "private": true, 5 | "version": "0.0.1", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "node test/test.js", 9 | "docs": "jsdoc2md index.js > README.md && cp README.md ../../docs/modules/waa.md", 10 | "start": "budo --open example/example.js" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-waa", 13 | "keywords": [ 14 | "dsp", 15 | "web audio api", 16 | "dsp-kit" 17 | ], 18 | "author": "danigb", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/oramics/dsp-kit/issues" 22 | }, 23 | "homepage": "https://github.com/oramics/dsp-kit/packages/dsp-waa/#readme", 24 | "dependencies": { 25 | }, 26 | "devDependencies": { 27 | "audio-context": "^0.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/waa/test/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oramics/dsp-kit/fea1c79ff873d3d8ce5cbcdec7e8bea364b3b3ca/packages/waa/test/test.js -------------------------------------------------------------------------------- /packages/window/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## window 4 | > Windowing functions for digital signal processing 5 | 6 | [![npm install dsp-window](https://nodei.co/npm/dsp-window.png?mini=true)](https://npmjs.org/package/dsp-window/) 7 | 8 | 9 | All window functions have some extra properties: 10 | 11 | - rov: recommended overlap 12 | 13 | This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 14 | 15 | ### References 16 | https://www.dsprelated.com/freebooks/sasp/Spectrum_Analysis_Windows.html 17 | 18 | **Example** 19 | ```js 20 | const dsp = require('dsp-kit') 21 | dsp.fill(1024, dsp.window.hanning()) 22 | ``` 23 | 24 | * [window](#module_window) 25 | * [.rectangular](#module_window.rectangular) 26 | * [.hanning](#module_window.hanning) 27 | * [.blackmanHarris](#module_window.blackmanHarris) 28 | 29 | 30 | 31 | ### window.rectangular 32 | The rectangular window, also sometimes called ‘uniform window’, is given by 33 | w = 1, equivalent to using no window at all. 34 | 35 | Although there are some special applications where the rectangular 36 | window is advantageous, it is probably not useful for any of our applications 37 | 38 | - Abrupt transition from 1 to 0 at the window endpoints 39 | - Roll-off is asymptotically -6dB per octave 40 | - First side lobe is -13dB relative to main-lobe peak 41 | 42 | **Kind**: static constant of [window](#module_window) 43 | 44 | 45 | ### window.hanning 46 | The Hanning window (one of a family of ‘raised cosine’ windows) is also known 47 | as ‘Hann window’. Do not confuse it with the ‘Hamming’ window. 48 | 49 | - Smooth transition to zero at window endpoints 50 | - Roll-off is asymptotically -18 dB per octave 51 | - First side lobe is -31dB relative to main-lobe peak 52 | 53 | **Kind**: static constant of [window](#module_window) 54 | 55 | 56 | ### window.blackmanHarris 57 | The Blackman-Harris window is one of a family of window functions given by a 58 | sum of cosine terms. By varying the number and coefficients of the terms 59 | different characteristics can be optimized. 60 | 61 | **Kind**: static constant of [window](#module_window) 62 | -------------------------------------------------------------------------------- /packages/window/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * > Windowing functions for digital signal processing 3 | * 4 | * [![npm install dsp-window](https://nodei.co/npm/dsp-window.png?mini=true)](https://npmjs.org/package/dsp-window/) 5 | * 6 | * 7 | * All window functions have some extra properties: 8 | * 9 | * - rov: recommended overlap 10 | * 11 | * This is part of [dsp-kit](https://github.com/oramics/dsp-kit) 12 | * 13 | * ### References 14 | * https://www.dsprelated.com/freebooks/sasp/Spectrum_Analysis_Windows.html 15 | * 16 | * @example 17 | * const dsp = require('dsp-kit') 18 | * dsp.fill(1024, dsp.window.hanning()) 19 | * 20 | * @module window 21 | */ 22 | const { PI, sin, cos } = Math 23 | const PI2 = PI * 2 24 | 25 | /** 26 | * The rectangular window, also sometimes called ‘uniform window’, is given by 27 | * w = 1, equivalent to using no window at all. 28 | * 29 | * Although there are some special applications where the rectangular 30 | * window is advantageous, it is probably not useful for any of our applications 31 | * 32 | * - Abrupt transition from 1 to 0 at the window endpoints 33 | * - Roll-off is asymptotically -6dB per octave 34 | * - First side lobe is -13dB relative to main-lobe peak 35 | */ 36 | export const rectangular = () => (n, N) => { return 1 } 37 | rectangular.rov = 0.5 38 | export const none = rectangular 39 | 40 | /** 41 | * The Hanning window (one of a family of ‘raised cosine’ windows) is also known 42 | * as ‘Hann window’. Do not confuse it with the ‘Hamming’ window. 43 | * 44 | * - Smooth transition to zero at window endpoints 45 | * - Roll-off is asymptotically -18 dB per octave 46 | * - First side lobe is -31dB relative to main-lobe peak 47 | */ 48 | export const hanning = () => (n, N) => { 49 | const z = (PI2 * n) / (N - 1) 50 | return 0.5 * (1 - cos(z)) 51 | } 52 | 53 | /* 54 | * The Hamming window is the simplest example of a family of windows that are 55 | * constructed as a weighted sum of a constant term and some cosine terms. Do 56 | * not confuse it with the ‘Hanning’ window. 57 | * 58 | * - Discontinuous ``slam to zero'' at endpoints 59 | * - Roll-off is asymptotically -6 dB per octave 60 | * - Side lobes are closer to ``equal ripple'' 61 | * - First side lobe is 41dB down = 10dB better than Hann 62 | */ 63 | export const hamming = () => (n, N) => { 64 | const z = (PI2 * n) / (N - 1) 65 | return 0.54 - 0.46 * cos(z) 66 | } 67 | 68 | export const blackman = (a) => (n, N) => { 69 | const z = (PI2 * n) / (N - 1) 70 | return (1 - a) / 2 - 0.5 * cos(z) + a * cos(2 * z) / 2 71 | } 72 | 73 | /** 74 | * The Blackman-Harris window is one of a family of window functions given by a 75 | * sum of cosine terms. By varying the number and coefficients of the terms 76 | * different characteristics can be optimized. 77 | */ 78 | export const blackmanHarris = () => (n, N) => { 79 | var z = (PI2 * n) / (N - 1) 80 | return 0.35875 - 0.48829 * cos(z) + 0.14128 * cos(2 * z) - 0.01168 * cos(3 * z) 81 | } 82 | -------------------------------------------------------------------------------- /packages/window/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp-window", 3 | "description": "Windowing functions 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/window.md" 11 | }, 12 | "repository": "https://github.com/oramics/dsp-kit/packages/dsp-window", 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-window/#readme", 26 | "devDependencies": { 27 | "dsp-array": "0.0.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/window/test/test.js: -------------------------------------------------------------------------------- 1 | const test = require('tst') 2 | const assert = require('assert') 3 | const windows = require('..') 4 | 5 | test('rectangular', function () { 6 | assert(windows.rectangular) 7 | assert(windows.none === windows.rectangular) 8 | }) 9 | 10 | test('hanning', function () { 11 | assert(windows.hanning) 12 | }) 13 | -------------------------------------------------------------------------------- /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: [ babel(), nodeResolve(), cleanup({ maxEmptyLines: 1 }) ], 9 | dest: 'build/index.js' 10 | } 11 | -------------------------------------------------------------------------------- /support/assert-almost/index.js: -------------------------------------------------------------------------------- 1 | var almost = require('almost-equal') 2 | 3 | module.exports = function (assert) { 4 | var EPSILON = 10e-4 5 | assert.almost = function (x, y, epsilon, message) { 6 | epsilon = epsilon || EPSILON 7 | if (x.length && y.length) { 8 | return x.every(function (x, i) { 9 | return assert.almost(x, y[i], epsilon, (message || '') + ' - index: ' + i) 10 | }) 11 | } 12 | 13 | if (!almost(x, y, epsilon)) assert.fail(x, y, `${x} ≈ ${y}`, '≈ ' + message + '(' + epsilon + ')') 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /support/assert-almost/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assert-almost", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "Extend assert with almost-equal", 6 | "main": "index.js", 7 | "scripts": {}, 8 | "license": "MIT", 9 | "dependencies": { 10 | "almost-equal": "^1.1.0", 11 | "benchmark": "^2.1.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /support/dspjs/README.md: -------------------------------------------------------------------------------- 1 | # dsp.js 2 | 3 | This is the code from [dsp.js](https://github.com/corbanbrook/dsp.js) by Corban Brook, because the npm published version doesn't work. 4 | 5 | It's used in this library to test against to (both behaviour and performance). 6 | -------------------------------------------------------------------------------- /support/dspjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dspjs", 3 | "version": "1.0.0", 4 | "description": "The unmantained dsp.js library. Used for testing (both results and performance)", 5 | "main": "dsp.js", 6 | "scripts": { 7 | "test": "echo 'See : https://github.com/corbanbrook/dsp.js'" 8 | }, 9 | "author": "Corban Brook", 10 | "license": "MIT" 11 | } 12 | -------------------------------------------------------------------------------- /support/easy-benchmark/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A small layer over Benchmark to reduce boilerplate 3 | * This is an internal package (not published) 4 | */ 5 | var Benchmark = require('Benchmark') 6 | 7 | /* 8 | * @example 9 | * var benchmark = require('dsp-benchmark') 10 | * benchmark({ 11 | * one: () => someExpensiveOperation(), 12 | * two: () => someOtherExpensiveOperation() 13 | * }) 14 | */ 15 | function benchmark (title, suite, { run = true, out = console } = {}) { 16 | out.log('Benchmark -- ', title) 17 | var bench = new Benchmark.Suite() 18 | Object.keys(suite).forEach((k) => { 19 | bench.add(k, suite[k]) 20 | }) 21 | bench.on('cycle', function (event) { 22 | out.log(String(event.target)) 23 | }) 24 | .on('complete', function () { 25 | out.log('Fastest is ', this.filter('fastest').map('name')) 26 | }) 27 | .on('error', function (e) { 28 | out.error('ERROR', e) 29 | }) 30 | 31 | if (run) bench.run({ 'async': false }) 32 | } 33 | 34 | benchmark.skip = function (title, suite, { out = console } = {}) { 35 | out.log('Skip -- ', title) 36 | } 37 | 38 | module.exports = benchmark 39 | -------------------------------------------------------------------------------- /support/easy-benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-benchmark", 3 | "version": "0.0.1", 4 | "private": true, 5 | "description": "A simple wrapper over benchmark.js to reduce boilerplate. Private module.", 6 | "main": "index.js", 7 | "scripts": {}, 8 | "license": "MIT", 9 | "dependencies": { 10 | "benchmark": "^2.1.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # dsp-kit data tests 2 | 3 | Test dsp-kit function against Python's numpy module. Data is generated and serialized in json files using python. The tests are against that data. 4 | -------------------------------------------------------------------------------- /test/data/calc.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | 4 | def write(name, data): 5 | with open(name, 'w') as outfile: 6 | json.dump(data.tolist(), outfile) 7 | 8 | with open('noise4096.json') as json_data: 9 | d = json.load(json_data) 10 | arr = np.fromiter(d, np.float32) 11 | fft = np.fft.fft(arr) 12 | 13 | with open('noise4096fft-real.json', 'w') as outfile: 14 | json.dump(fft.real.tolist(), outfile) 15 | with open('noise4096fft-imag.json', 'w') as outfile: 16 | json.dump(fft.imag.tolist(), outfile) 17 | with open('noise4096fft-abs.json', 'w') as outfile: 18 | json.dump(abs(fft).tolist(), outfile) 19 | with open('noise4096fft-angle.json', 'w') as outfile: 20 | json.dump(np.angle(fft).tolist(), outfile) 21 | with open('noise4096fft-unwrap.json', 'w') as outfile: 22 | json.dump(np.unwrap(np.angle(fft)).tolist(), outfile) 23 | write('noise4096ifft-real.json', np.fft.ifft(fft).real) 24 | write('noise4096ifft-imag.json', np.fft.ifft(fft).imag) 25 | -------------------------------------------------------------------------------- /test/data/generate.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | var arr = require('dsp-array') 4 | var noise = require('dsp-noise') 5 | 6 | var SIZE = 4096 7 | 8 | var signal = arr.fill(SIZE, noise.white()) 9 | fs.writeFileSync('noise4096.json', JSON.stringify(Array.from(signal))) 10 | -------------------------------------------------------------------------------- /test/data/noise4096fft.json: -------------------------------------------------------------------------------- 1 | [ -------------------------------------------------------------------------------- /test/test-data.js: -------------------------------------------------------------------------------- 1 | var test = require('tst') 2 | var assert = require('assert') 3 | var almost = require('almost-equal') 4 | var dsp = require('../packages/dsp') 5 | 6 | var data = { 7 | signal: require('./data/noise4096.json'), 8 | fft: { 9 | real: require('./data/noise4096fft-real.json'), 10 | imag: require('./data/noise4096fft-imag.json') 11 | }, 12 | ifft: { 13 | real: require('./data/noise4096ifft-real.json'), 14 | imag: require('./data/noise4096ifft-imag.json') 15 | }, 16 | polar: { 17 | magnitudes: require('./data/noise4096fft-abs.json'), 18 | phases: require('./data/noise4096fft-angle.json') 19 | }, 20 | unwrap: require('./data/noise4096fft-unwrap.json') 21 | } 22 | 23 | test('fft', () => { 24 | test('fft forward', () => { 25 | var result = dsp.fft(4096).forward(data.signal) 26 | assert.almost(result.real, data.fft.real) 27 | assert.almost(result.imag, data.fft.imag) 28 | }) 29 | 30 | test('fft inverse', () => { 31 | var result = dsp.fft(4096).inverse(data.fft) 32 | assert.almost(result.real, data.ifft.real) 33 | assert.almost(result.imag, data.ifft.imag) 34 | }) 35 | }) 36 | 37 | test('spectrum', () => { 38 | test('magnitudes and phases', () => { 39 | var result = dsp.polar(data.fft) 40 | assert.almost(result.magnitudes, data.polar.magnitudes) 41 | assert.almost(result.phases, data.polar.phases) 42 | }) 43 | 44 | test('phase unwrap', () => { 45 | var result = dsp.unwrap(data.polar.phases) 46 | assert.almost(result, data.unwrap) 47 | }) 48 | }) 49 | 50 | var EPSILON = 10e-6 51 | assert.almost = function (x, y, epsilon, message) { 52 | epsilon = epsilon || EPSILON 53 | if (x.length && y.length) { 54 | return x.every(function (x, i) { 55 | return assert.almost(x, y[i], epsilon, (message || '') + ' - index: ' + i) 56 | }) 57 | } 58 | 59 | if (!almost(x, y, epsilon)) assert.fail(x, y, `${x} ≈ ${y}`, '≈ ' + message + '(' + epsilon + ')') 60 | return true 61 | } 62 | --------------------------------------------------------------------------------