├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .travis.yml ├── clausen.js ├── fourier.js ├── img ├── clausen.png ├── cosine.png ├── fourier.png ├── interpolate.png ├── noise.png ├── pulse.png ├── sawtooth.png ├── sine.png ├── square.png ├── step.png └── triangle.png ├── index.js ├── interpolate.js ├── noise.js ├── package.json ├── pulse.js ├── readme.md ├── sawtooth.js ├── sine.js ├── square.js ├── step.js ├── test.js └── triangle.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "strict": 2, 11 | "indent": 0, 12 | "linebreak-style": 0, 13 | "quotes": 0, 14 | "semi": 0, 15 | "no-cond-assign": 1, 16 | "no-constant-condition": 1, 17 | "no-duplicate-case": 1, 18 | "no-empty": 1, 19 | "no-ex-assign": 1, 20 | "no-extra-boolean-cast": 1, 21 | "no-extra-semi": 1, 22 | "no-fallthrough": 1, 23 | "no-func-assign": 1, 24 | "no-global-assign": 1, 25 | "no-implicit-globals": 2, 26 | "no-inner-declarations": ["error", "functions"], 27 | "no-irregular-whitespace": 2, 28 | "no-loop-func": 1, 29 | "no-multi-str": 1, 30 | "no-mixed-spaces-and-tabs": 1, 31 | "no-proto": 1, 32 | "no-sequences": 1, 33 | "no-throw-literal": 1, 34 | "no-unmodified-loop-condition": 1, 35 | "no-useless-call": 1, 36 | "no-void": 1, 37 | "no-with": 2, 38 | "wrap-iife": 1, 39 | "no-redeclare": 1, 40 | "no-unused-vars": ["error", { "vars": "all", "args": "none" }], 41 | "no-sparse-arrays": 1 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | 3 | node_modules 4 | # Compiled source # 5 | ################### 6 | *.com 7 | *.class 8 | *.dll 9 | *.exe 10 | *.o 11 | *.so 12 | *.bin 13 | 14 | # Packages # 15 | ############ 16 | # it's better to unpack these files and commit the raw source 17 | # git has its own built in compression methods 18 | *.7z 19 | *.dmg 20 | *.gz 21 | *.iso 22 | *.jar 23 | *.rar 24 | *.tar 25 | *.zip 26 | 27 | # Logs and databases # 28 | ###################### 29 | *.log 30 | *.sql 31 | *.sqlite 32 | 33 | # OS generated files # 34 | ###################### 35 | .DS_Store 36 | .DS_Store? 37 | *._* 38 | .Spotlight-V100 39 | .Trashes 40 | Icon? 41 | ehthumbs.db 42 | Thumbs.db 43 | 44 | # My extension # 45 | ################ 46 | *.lock 47 | *.bak 48 | lsn 49 | *.dump 50 | *.beam 51 | *.[0-9] 52 | *._[0-9] 53 | *.ns 54 | Scripting_* 55 | docs 56 | *.pdf 57 | *.pak 58 | 59 | demo 60 | design 61 | instances 62 | *node_modules 63 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.png 2 | img/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | - "5" 6 | matrix: 7 | fast_finish: true 8 | allow_failures: 9 | - node_js: "5" 10 | -------------------------------------------------------------------------------- /clausen.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var τ = Math.PI * 2 4 | module.exports = function clausen (t, limit) { 5 | if (limit == null) limit = 10 6 | 7 | t %= 1 8 | if (t < 0) t += 1 9 | 10 | var result = 0 11 | for (var k = 1; k <= limit; k++) { 12 | result += Math.sin(k*τ*t) / (k*k) 13 | } 14 | 15 | return result 16 | }; 17 | -------------------------------------------------------------------------------- /fourier.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var τ = Math.PI*2 4 | module.exports = function fourier (t, real, imag, normalize) { 5 | var res = 0; 6 | var sumReal = 0, sumImag = 0; 7 | 8 | t %= 1 9 | if (t < 0) t += 1 10 | 11 | if (imag === true) { 12 | normalize = imag 13 | imag = null 14 | } 15 | var N 16 | if (real) { 17 | N = real.length 18 | for (var harmonic = 0; harmonic < N; harmonic++) { 19 | res += real[harmonic] * Math.cos(τ * t * harmonic) 20 | sumReal += real[harmonic]; 21 | } 22 | } 23 | 24 | if (imag) { 25 | N = imag.length 26 | for (var harmonic = 0; harmonic < N; harmonic++) { 27 | res += imag[harmonic] * Math.sin(τ * t * harmonic); 28 | sumImag += imag[harmonic]; 29 | } 30 | } 31 | 32 | return normalize ? res / (sumReal + sumImag) : res; 33 | }; 34 | -------------------------------------------------------------------------------- /img/clausen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/clausen.png -------------------------------------------------------------------------------- /img/cosine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/cosine.png -------------------------------------------------------------------------------- /img/fourier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/fourier.png -------------------------------------------------------------------------------- /img/interpolate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/interpolate.png -------------------------------------------------------------------------------- /img/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/noise.png -------------------------------------------------------------------------------- /img/pulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/pulse.png -------------------------------------------------------------------------------- /img/sawtooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/sawtooth.png -------------------------------------------------------------------------------- /img/sine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/sine.png -------------------------------------------------------------------------------- /img/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/square.png -------------------------------------------------------------------------------- /img/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/step.png -------------------------------------------------------------------------------- /img/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/periodic-function/ee94803eb0b7ec4dc18ee6ee1e22c6e064760b58/img/triangle.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module periodic-function 3 | */ 4 | 'use strict' 5 | 6 | module.exports = { 7 | noise: require('./noise'), 8 | sine: require('./sine'), 9 | triangle: require('./triangle'), 10 | sawtooth: require('./sawtooth'), 11 | square: require('./square'), 12 | pulse: require('./pulse'), 13 | fourier: require('./fourier'), 14 | clausen: require('./clausen'), 15 | interpolate: require('./interpolate'), 16 | step: require('./step') 17 | } 18 | -------------------------------------------------------------------------------- /interpolate.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function interpolate(t, samples) { 4 | t%=1 5 | if (t < 0) t += 1 6 | 7 | var ptr = t*samples.length 8 | var left = Math.floor(ptr) 9 | var right = Math.ceil(ptr) 10 | if (left === right) right++ 11 | right %= samples.length 12 | var ratio = ptr - left 13 | 14 | return samples[right] * ratio + samples[left] * (1 - ratio) 15 | } 16 | -------------------------------------------------------------------------------- /noise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var len = 5*44100 4 | var samples = Array.from({length:len}, function () {return Math.random()*2-1}) 5 | 6 | module.exports = function noise (t) { 7 | t%=1 8 | if (t < 0) t += 1 9 | return samples[Math.floor(t*44100)%len] 10 | }; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "periodic-function", 3 | "version": "1.1.4", 4 | "description": "Collection of periodic functions", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test --harmony" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/dfcreative/periodic-function.git" 12 | }, 13 | "keywords": [ 14 | "periodic", 15 | "math", 16 | "sine", 17 | "cosine", 18 | "sawtooth", 19 | "dsp" 20 | ], 21 | "author": "Dima Yv ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/dfcreative/periodic-function/issues" 25 | }, 26 | "homepage": "https://github.com/dfcreative/periodic-function#readme", 27 | "devDependencies": { 28 | "almost-equal": "^1.1.0", 29 | "tape": "^4.6.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pulse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function pulse (t, tlr) { 4 | t%=1 5 | if (t < 0) t += 1 6 | if (tlr == null) tlr = 0 7 | if (t <= tlr) return 1 8 | return 0 9 | }; 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # periodic-function [![unstable](https://img.shields.io/badge/stability-unstable-green.svg)](http://github.com/badges/stability-badges) [![Build Status](https://img.shields.io/travis/dfcreative/periodic-function.svg)](https://travis-ci.org/dfcreative/periodic-function) 2 | 3 | Collection of periodic functions with period normalized to turns. 4 | 5 | ## Usage 6 | 7 | [![npm install periodic-function](https://nodei.co/npm/periodic-function.png?mini=true)](https://npmjs.org/package/periodic-function/) 8 | 9 | ```js 10 | const fn = require('periodic-function/') 11 | 12 | //π radians 13 | let halfTurn = fn(.5) 14 | 15 | //2π radians 16 | let fullTurn = fn(1) 17 | ``` 18 | 19 | ## API 20 | 21 | ### let fn = require('periodic-function/\') 22 | 23 | The `fn` takes the amount of turn `t` as the first argument and optional parameters. The list of available functions: 24 | 25 | | Signature | Waveform | Meaning | 26 | ---|:---:|---| 27 | | sine(t, phase=0) | ![sine](img/sine.png) | `Math.sin` normalized to `0..1` rather than radians `0..2π`. To turn into cos, set `phase=.25`. | 28 | | triangle(t, ratio=0.5) | ![triangle](img/triangle.png) | Triangular waveform with regulated ratio. To turn into sawtooth set `ratio=0` or `ratio=1`. | 29 | | sawtooth(t, inverse=false) | ![sawtooth](img/sawtooth.png) | Edge case of triangular waveform, whether descending or ascending. | 30 | | square(t, ratio=0.5) | ![square](img/square.png) | Rectangular waveform with regulated ratio. To turn into pulse set `ratio=0`. | 31 | | pulse(t, tlr=0) | ![pulse](img/pulse.png) | Delta-pulse, which is `1` at `0` and `0` anywhere else. Pass `tlr` as a precision tolerance, ie. `1e-5`. | 32 | | fourier(t, real, imag?, normalize=false) | ![fourier](img/fourier.png) | [Fourier Series](https://en.wikipedia.org/wiki/Fourier_series) coefficients, ie. harmonics. `0` harmonic is static level, `1`st is base frequency, `2`nd is double base frequency, `3`rd is triple etc. Set `normalize=true` to bring max harmonic to `1`. | 33 | | clausen(t, limit=10) | ![clausen](img/clausen.png) | [Clausen function](https://en.wikipedia.org/wiki/Clausen_function). Pass `limit` to indicate number of iterations, precision/performance tradeoff. | 34 | | step(t, samples) | ![step](img/step.png) | [Step function](https://en.wikipedia.org/wiki/Step_function), picks closest sample value out of a set. | 35 | | interpolate(t, samples) | ![interpolate](img/interpolate.png) | Interpolates between closest values in a sample set. | 36 | | noise(t) | ![noise](img/noise.png) | Repeated sample of noise. | 37 | 38 | If you feel like it is not complete list of you know example of a good periodic function, suitable for dsp, welcome to [contribute](https://github.com/dfcreative/periodic-function/issues). 39 | 40 | ## Related 41 | 42 | * [audio-oscillator](https://github.com/audiojs/audio-oscillator) 43 | * [createPeriodicWave](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createPeriodicWave) 44 | * [List of periodic functions](https://en.wikipedia.org/wiki/List_of_periodic_functions) 45 | 46 | ## Credits 47 | 48 | © 2017 Dima Yv. MIT License 49 | -------------------------------------------------------------------------------- /sawtooth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function sawtooth (t, inversed) { 4 | t%=1 5 | if (t < 0) t += 1 6 | if (inversed) return -1 + 2*t 7 | return 1 - 2*t 8 | }; 9 | -------------------------------------------------------------------------------- /sine.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var τ = Math.PI * 2 4 | module.exports = function sine (t, phase) { 5 | t%=1 6 | if (t < 0) t += 1 7 | if (!phase) phase = 0 8 | return Math.sin(τ * (t + phase)); 9 | }; 10 | -------------------------------------------------------------------------------- /square.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function square (t, ratio) { 4 | t%=1 5 | if (t < 0) t += 1 6 | if (ratio == null) ratio = 0.5 7 | if (t >= ratio) return -1; 8 | return 1; 9 | }; 10 | -------------------------------------------------------------------------------- /step.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function step (t, samples) { 4 | t%=1 5 | if (t < 0) t += 1 6 | 7 | return samples[Math.floor(t*samples.length)] 8 | } 9 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tape') 4 | const fn = require('./') 5 | const almost = require('almost-equal') 6 | 7 | function draw(fn, ...args) { 8 | if (typeof document === 'undefined') return 9 | 10 | let arr = populate(fn, 32, ...args) 11 | 12 | let canvas = document.body.appendChild(document.createElement('canvas')) 13 | let ctx = canvas.getContext('2d') 14 | canvas.width = arr.length 15 | canvas.height = 32 16 | let w = canvas.width * .75, h = canvas.height*.75 17 | 18 | ctx.beginPath() 19 | // ctx.moveTo(canvas.width*.125,canvas.height*.625) 20 | for (let i = 0, l = arr.length; i < l; i++) { 21 | // ctx.fillRect(w*i/l, h*.5, 1, -(arr[i]*.5)*h ) 22 | ctx.lineTo(w*i/l + canvas.width*.125, canvas.height*.5-(arr[i]*.5)*h ) 23 | } 24 | // ctx.lineTo(canvas.width*.875,canvas.height*.625) 25 | ctx.lineWidth = 2; 26 | ctx.stroke() 27 | ctx.closePath() 28 | } 29 | 30 | function populate(fn, N, ...args) { 31 | return Array.from({length: N}, (v, i) => fn(i/N, ...args)) 32 | } 33 | 34 | 35 | t('sin', t => { 36 | draw(fn.sine) 37 | var sin = populate(fn.sine, 1024); 38 | t.equal(sin[0], 0); 39 | t.equal(sin[~~(1024/4)], 1); 40 | t.ok(almost(sin[~~(1024/2)], 0, 0.0001, 0.0001)); 41 | t.equal(fn.sine(-.75), fn.sine(.25)) 42 | t.end() 43 | }); 44 | 45 | t('cos', t => { 46 | draw(fn.sine, .25) 47 | var cos = populate(fn.sine, 4, .25); 48 | t.equal(cos[0], 1); 49 | t.ok(almost(cos[1], 0, 0.0001, 0.0001)); 50 | t.equal(cos[2], -1); 51 | t.ok(almost(cos[3], 0, 0.0001, 0.0001)); 52 | t.end() 53 | }); 54 | 55 | t('delta', t => { 56 | draw(fn.pulse) 57 | var delta = populate(fn.pulse, 4); 58 | t.equal(delta[0], 1); 59 | t.equal(delta[1], 0); 60 | t.equal(delta[2], 0); 61 | t.equal(delta[3], 0); 62 | t.end() 63 | }); 64 | 65 | t('pulse', t => { 66 | var pulse = populate(fn.pulse, 10, 0); 67 | t.equal(pulse[0], 1); 68 | t.equal(pulse[1], 0); 69 | t.equal(pulse[9], 0); 70 | t.equal(fn.pulse(-.75), fn.pulse(.25)) 71 | t.end() 72 | }); 73 | 74 | t('square', t => { 75 | draw(fn.square) 76 | var square = populate(fn.square, 10); 77 | t.equal(square[0], 1); 78 | t.equal(square[4], 1); 79 | t.equal(square[5], -1); 80 | t.equal(square[9], -1); 81 | 82 | draw(fn.square, .1) 83 | var square = populate(fn.square, 10, .1); 84 | t.equal(square[0], 1); 85 | t.equal(square[4], -1); 86 | t.equal(square[5], -1); 87 | t.equal(square[9], -1); 88 | t.equal(fn.square(-.75), fn.square(.25)) 89 | t.end() 90 | }); 91 | 92 | 93 | t('triangle', t => { 94 | draw(fn.triangle) 95 | var triangle = populate(fn.triangle, 8); 96 | t.equal(triangle[0], 1); 97 | t.equal(triangle[1], 0.5); 98 | t.equal(triangle[2], 0); 99 | t.equal(triangle[3], -.5); 100 | t.equal(triangle[4], -1); 101 | t.equal(triangle[6], 0); 102 | t.equal(triangle[7], 0.5); 103 | t.equal(fn.triangle(-.75), fn.triangle(.25)) 104 | t.end() 105 | }); 106 | 107 | t('triangle ratio', t => { 108 | draw(fn.triangle, .25) 109 | var triangle = populate(fn.triangle, 8, .25); 110 | t.equal(triangle[2], -1) 111 | t.end() 112 | }); 113 | 114 | t('saw', t => { 115 | draw(fn.sawtooth) 116 | var saw = populate(fn.sawtooth, 8); 117 | t.equal(saw[0], 1); 118 | t.equal(saw[7], -.75); 119 | 120 | draw(fn.sawtooth, true) 121 | var sawi = populate(fn.sawtooth, 8, true); 122 | t.equal(sawi[0], -1); 123 | t.equal(sawi[7], .75); 124 | 125 | t.equal(fn.sawtooth(-.75), fn.sawtooth(.25)) 126 | t.end() 127 | }); 128 | 129 | 130 | t('fourier', t => { 131 | draw(fn.fourier, [0, 1, 0, .5], [0, .5, 0, .1], true) 132 | var series = populate(fn.fourier, 8, [1, .5, .25, .125], true); 133 | t.equal(series[0], 1) 134 | 135 | populate(fn.fourier, 8, null, [1, .5, .25, .125], true); 136 | populate(fn.fourier, 8, [1, .5, .25, .125], null, true); 137 | t.end() 138 | }); 139 | 140 | 141 | t('noise', t => { 142 | draw(fn.noise) 143 | t.end() 144 | }) 145 | 146 | t('clausen', t => { 147 | draw(fn.clausen) 148 | var clausen = populate(fn.clausen, 10); 149 | t.ok(almost(clausen[5], 0)) 150 | t.equal(fn.clausen(-.75), fn.clausen(.25)) 151 | t.end() 152 | }); 153 | 154 | t('interpolate', t => { 155 | let set = Array.from({length: 6}, (v, i) => Math.random()*2 - 1) 156 | draw(fn.interpolate, set) 157 | 158 | var int = populate(fn.interpolate, 10, [0, .5, 1, .5, 0]); 159 | t.equal(int[0], 0) 160 | t.equal(int[1], 0.25) 161 | t.equal(int[2], 0.5) 162 | t.equal(int[9], 0) 163 | 164 | t.end() 165 | }) 166 | 167 | t('step', t => { 168 | let set = Array.from({length: 6}, (v, i) => Math.random()*2 - 1) 169 | draw(fn.step, set) 170 | t.end() 171 | }) 172 | -------------------------------------------------------------------------------- /triangle.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function triangle(t, ratio) { 4 | t%=1 5 | if (t < 0) t += 1 6 | 7 | if (ratio == 0.5 || ratio == null) { 8 | if (t > 0.5) t = 1 - t; 9 | return 1 - 4 * t; 10 | } 11 | 12 | if (ratio == 1) { 13 | return 1 - 2*t 14 | } 15 | 16 | if (ratio == 0) { 17 | return -1 + 2*t 18 | } 19 | 20 | if (t < ratio) { 21 | return 1 - 2*t/ratio 22 | } 23 | else { 24 | return -1 + 2*(t-ratio)/(1-ratio) 25 | } 26 | }; 27 | --------------------------------------------------------------------------------