├── .gitignore ├── .npmrc ├── midi ├── hip.mid ├── sowhat.mid ├── tunisia.mid ├── bluebossa.mid ├── cantaloup.mid ├── chameleon.mid ├── got-a-match.mid ├── minute_waltz.mid └── rachmaninoff-op39-no6.mid ├── algorithms.png ├── roms └── ROM1A.SYX ├── images ├── dx7-logo.png ├── yamaha-dx-7.png ├── 13460FD783A2087D.png ├── dx7-toggle-off.svg ├── dx7-toggle-off-active.svg ├── dx7-toggle-on.svg ├── dx7-slider-rail.svg ├── dx7-toggle-on-active.svg ├── knob-foreground.svg ├── dx7-slider-foreground.svg ├── frequency-display.svg ├── dx7-slider-meter.svg ├── dx7-slider-rail-dashed.svg ├── knob-background.svg ├── lcd-background.svg ├── algorithm-31.svg ├── algorithm-29.svg ├── algorithm-32.svg ├── algorithm-30.svg ├── algorithm-05.svg ├── algorithm-06.svg ├── algorithm-01.svg ├── algorithm-07.svg ├── algorithm-02.svg ├── algorithm-08.svg ├── algorithm-09.svg ├── algorithm-13.svg ├── algorithm-26.svg ├── algorithm-14.svg ├── algorithm-12.svg ├── algorithm-28.svg ├── algorithm-15.svg ├── algorithm-27.svg ├── algorithm-25.svg ├── algorithm-11.svg ├── algorithm-10.svg └── algorithm-16.svg ├── fonts ├── 5x8_lcd_hd44780u_a02.eot ├── 5x8_lcd_hd44780u_a02.ttf ├── 5x8_lcd_hd44780u_a02.woff ├── digital-7__mono_italic_.eot ├── digital-7__mono_italic_.ttf └── digital-7__mono_italic_.woff ├── impulses └── church-saint-laurentius.wav ├── src ├── config.js ├── operator.js ├── envelope-dx7.js ├── default-presets.js ├── sysex-dx7.js ├── midi.js ├── synth.js ├── visualizer.js ├── reverb.js └── lfo-dx7.js ├── webpack.config.js ├── README.md ├── LICENSE.txt └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | roms/* 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-options=--openssl-legacy-provider 2 | 3 | -------------------------------------------------------------------------------- /midi/hip.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/hip.mid -------------------------------------------------------------------------------- /algorithms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/algorithms.png -------------------------------------------------------------------------------- /midi/sowhat.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/sowhat.mid -------------------------------------------------------------------------------- /midi/tunisia.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/tunisia.mid -------------------------------------------------------------------------------- /roms/ROM1A.SYX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/roms/ROM1A.SYX -------------------------------------------------------------------------------- /midi/bluebossa.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/bluebossa.mid -------------------------------------------------------------------------------- /midi/cantaloup.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/cantaloup.mid -------------------------------------------------------------------------------- /midi/chameleon.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/chameleon.mid -------------------------------------------------------------------------------- /images/dx7-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/images/dx7-logo.png -------------------------------------------------------------------------------- /midi/got-a-match.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/got-a-match.mid -------------------------------------------------------------------------------- /midi/minute_waltz.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/minute_waltz.mid -------------------------------------------------------------------------------- /images/yamaha-dx-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/images/yamaha-dx-7.png -------------------------------------------------------------------------------- /images/13460FD783A2087D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/images/13460FD783A2087D.png -------------------------------------------------------------------------------- /fonts/5x8_lcd_hd44780u_a02.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/5x8_lcd_hd44780u_a02.eot -------------------------------------------------------------------------------- /fonts/5x8_lcd_hd44780u_a02.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/5x8_lcd_hd44780u_a02.ttf -------------------------------------------------------------------------------- /fonts/5x8_lcd_hd44780u_a02.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/5x8_lcd_hd44780u_a02.woff -------------------------------------------------------------------------------- /midi/rachmaninoff-op39-no6.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/midi/rachmaninoff-op39-no6.mid -------------------------------------------------------------------------------- /fonts/digital-7__mono_italic_.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/digital-7__mono_italic_.eot -------------------------------------------------------------------------------- /fonts/digital-7__mono_italic_.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/digital-7__mono_italic_.ttf -------------------------------------------------------------------------------- /fonts/digital-7__mono_italic_.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/fonts/digital-7__mono_italic_.woff -------------------------------------------------------------------------------- /impulses/church-saint-laurentius.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmontag/dx7-synth-js/HEAD/impulses/church-saint-laurentius.wav -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | var LFO_SAMPLE_PERIOD = 100; 2 | var BUFFER_SIZE = 1024; 3 | var POLYPHONY = 12; 4 | 5 | if (/iPad|iPhone|iPod|Android/.test(navigator.userAgent)) { 6 | BUFFER_SIZE = 4096; 7 | POLYPHONY = 8; 8 | } 9 | 10 | var Config = { 11 | sampleRate: 44100, // gets updated with audio context rate 12 | lfoSamplePeriod: LFO_SAMPLE_PERIOD, 13 | bufferSize: BUFFER_SIZE, 14 | polyphony: POLYPHONY 15 | }; 16 | 17 | module.exports = Config; 18 | -------------------------------------------------------------------------------- /images/dx7-toggle-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /images/dx7-toggle-off-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /images/dx7-toggle-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: [ 6 | //'webpack-dev-server/client?http://localhost:9090', 7 | //'webpack/hot/dev-server', 8 | './src/app.js' 9 | ], 10 | output: { 11 | path: path.resolve(__dirname, '.'), 12 | filename: 'bundle.js' 13 | }, 14 | devtool: 'inline-source-map', 15 | module: { 16 | rules: [ 17 | { test: /\.css$/, loader: 'style!css' }, 18 | { test: /\.json$/, loader: 'json-loader', type: 'javascript/auto' } 19 | ] 20 | }, 21 | node: { fs: "empty" }, 22 | plugins: [ 23 | //new webpack.optimize.OccurenceOrderPlugin(), 24 | //new webpack.optimize.UglifyJsPlugin({minimize: true}) 25 | ] 26 | }; -------------------------------------------------------------------------------- /images/dx7-slider-rail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /images/dx7-toggle-on-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /images/knob-foreground.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /images/dx7-slider-foreground.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /images/frequency-display.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /images/dx7-slider-meter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DX7 Synth JS 2 | ================= 3 | 4 | ![](images/yamaha-dx-7.png) 5 | 6 | DX7 FM synthesis using the Web Audio and Web MIDI API. Works in Chrome and Firefox. 7 | Use a MIDI or QWERTY keyboard to play the synth. 8 | 9 | [Live demo of DX7 Synth JS](https://mmontag.github.io/dx7-synth-js/) 10 | 11 | This is a high-level emulation of DX7 using FM synthesis principles. It's not super accurate, but good enough to produce most DX7 presets. Stereo panning has been added to the operators. 12 | 13 | Controls: 14 | 15 | 1. **QWERTY keys** to play notes 16 | 1. **Space bar** panic (all notes off) 17 | 1. **Control** hold down to increase QWERTY velocity 18 | 1. **Mouse wheel** over knobs and sliders to increase/decrease value 19 | 1. **Click or touch and drag up/down** on knobs and sliders to increase/decrease value 20 | 1. **Arrow up/down** on knobs and sliders to increase/decrease value 21 | 1. **Tab** moves between controls 22 | 23 | Many thanks to: 24 | 25 | - John Chowning and Yamaha 26 | - Sean Bolton, author of Hexter, a DSSI DX7 modeler 27 | - Phil Cowans, author of Javascript-DX7 music hackday prototype https://github.com/philcowans/Javascript-DX7 28 | - Jamie Bullock, Ewan Macpherson, and other independent engineers who provided specs about the DX7/TX7 implementation 29 | - Propellerhead Software, for the PX7 Reason Rack Extension 30 | - Native Instruments, for the FM7 and FM8 VSTi software instruments 31 | -------------------------------------------------------------------------------- /src/operator.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'); 2 | 3 | // http://www.chipple.net/dx7/fig09-4.gif 4 | var OCTAVE_1024 = 1.0006771307; //Math.exp(Math.log(2)/1024); 5 | var PERIOD = Math.PI * 2; 6 | 7 | function Operator(params, baseFrequency, envelope, lfo) { 8 | this.phase = 0; 9 | this.val = 0; 10 | this.params = params; 11 | this.envelope = envelope; 12 | // TODO: Pitch envelope 13 | // this.pitchEnvelope = pitchEnvelope; 14 | this.lfo = lfo; 15 | this.updateFrequency(baseFrequency); 16 | } 17 | 18 | Operator.prototype.updateFrequency = function(baseFrequency) { 19 | var frequency = this.params.oscMode ? 20 | this.params.freqFixed : 21 | baseFrequency * this.params.freqRatio * Math.pow(OCTAVE_1024, this.params.detune); 22 | this.phaseStep = PERIOD * frequency / config.sampleRate; // radians per sample 23 | }; 24 | 25 | Operator.prototype.render = function(mod) { 26 | this.val = Math.sin(this.phase + mod) * this.envelope.render() * this.lfo.renderAmp(); 27 | // this.phase += this.phaseStep * this.pitchEnvelope.render() * this.lfo.render(); 28 | this.phase += this.phaseStep * this.lfo.render(); 29 | if (this.phase >= PERIOD) { 30 | this.phase -= PERIOD; 31 | } 32 | return this.val; 33 | }; 34 | 35 | Operator.prototype.noteOff = function() { 36 | this.envelope.noteOff(); 37 | }; 38 | 39 | Operator.prototype.isFinished = function() { 40 | return this.envelope.isFinished(); 41 | }; 42 | 43 | module.exports = Operator; 44 | -------------------------------------------------------------------------------- /images/dx7-slider-rail-dashed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | DX7 Synth is provided under MIT License. 2 | Copyright (C) 2014 Matt Montag 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | ----- 25 | 26 | midi.js by Chris Wilson used with modifications under MIT license. 27 | Copyright (c) 2014 Chris Wilson 28 | https://github.com/cwilso/midi-synth 29 | 30 | mml-emitter.js by Nao Yonamine used under MIT license. 31 | Copyright (c) 2014 Nao Yonamine 32 | https://github.com/mohayonao/mml-emitter -------------------------------------------------------------------------------- /images/knob-background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dx7-synth-js", 3 | "version": "0.0.1", 4 | "description": "DX7 FM synthesis using the Web Audio and Web MIDI API.", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "webpack", 8 | "prod": "webpack --optimize-minimize --optimize-dedupe", 9 | "dev": "webpack-dev-server --progress --colors --port 9090 --inline --hot", 10 | "deploy": "git checkout master; git branch -D gh-pages; git checkout -b gh-pages; npm start; git add bundle.js; git commit -m'Deploy'; git push --force --set-upstream origin gh-pages; git checkout master" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mmontag/dx7-synth-js.git" 15 | }, 16 | "keywords": [ 17 | "yamaha", 18 | "dx7", 19 | "synth", 20 | "synthesizer", 21 | "midi", 22 | "audio" 23 | ], 24 | "author": "Matt Montag", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/mmontag/dx7-synth-js/issues" 28 | }, 29 | "homepage": "https://github.com/mmontag/dx7-synth-js#readme", 30 | "devDependencies": { 31 | "babel-core": "^6.26.3", 32 | "babel-loader": "^7.1.5", 33 | "json-loader": "^0.5.2", 34 | "node-libs-browser": "^0.5.2", 35 | "webpack": "^4.16.0", 36 | "webpack-cli": "^3.0.8", 37 | "webpack-dev-server": "^3.1.14" 38 | }, 39 | "dependencies": { 40 | "angular": "^1.4.1", 41 | "axios": "^1.9.0", 42 | "lodash": "^4.17.15", 43 | "midifile": "^1.0.1", 44 | "midiplayer": "0.0.2", 45 | "mml-emitter": "git+https://github.com/mohayonao/mml-emitter.git#edd40ecf42141ca257c6cd8adc5073d570add1ee", 46 | "ngstorage": "^0.3.6", 47 | "pixi.js": "^3.0.6" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/envelope-dx7.js: -------------------------------------------------------------------------------- 1 | // Based on http://wiki.music-synthesizer-for-android.googlecode.com/git/img/env.html 2 | var ENV_OFF = 4; 3 | var outputlevel = [0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 4 | 41, 42, 43, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 5 | 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 6 | 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 7 | 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 8 | 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]; 9 | 10 | var outputLUT = []; 11 | for (var i = 0; i < 4096; i++) { 12 | var dB = (i - 3824) * 0.0235; 13 | outputLUT[i] = Math.pow(20, (dB/20)); 14 | } 15 | 16 | function EnvelopeDX7(levels, rates) { 17 | this.levels = levels; 18 | this.rates = rates; 19 | this.level = 0; // should start here 20 | this.i = 0; 21 | this.down = true; 22 | this.decayIncrement = 0; 23 | this.advance(0); 24 | } 25 | 26 | EnvelopeDX7.prototype.render = function() { 27 | if (this.state < 3 || (this.state < 4 && !this.down)) { 28 | var lev; 29 | lev = this.level; 30 | if (this.rising) { 31 | lev += this.decayIncrement * (2 + (this.targetlevel - lev) / 256); 32 | if (lev >= this.targetlevel) { 33 | lev = this.targetlevel; 34 | this.advance(this.state + 1); 35 | } 36 | } else { 37 | lev -= this.decayIncrement; 38 | if (lev <= this.targetlevel) { 39 | lev = this.targetlevel; 40 | this.advance(this.state + 1); 41 | } 42 | } 43 | this.level = lev; 44 | } 45 | this.i++; 46 | 47 | // Convert DX7 level -> dB -> amplitude 48 | return outputLUT[Math.floor(this.level)]; 49 | // if (this.pitchMode) { 50 | // return Math.pow(pitchModDepth, amp); 51 | // } 52 | }; 53 | 54 | EnvelopeDX7.prototype.advance = function(newstate) { 55 | this.state = newstate; 56 | if (this.state < 4) { 57 | var newlevel = this.levels[this.state]; 58 | this.targetlevel = Math.max(0, (outputlevel[newlevel] << 5) - 224); // 1 -> -192; 99 -> 127 -> 3840 59 | this.rising = (this.targetlevel - this.level) > 0; 60 | var rate_scaling = 0; 61 | this.qr = Math.min(63, rate_scaling + ((this.rates[this.state] * 41) >> 6)); // 5 -> 3; 49 -> 31; 99 -> 63 62 | this.decayIncrement = Math.pow(2, this.qr/4) / 2048; 63 | // console.log("decayIncrement (", this.state, "): ", this.decayIncrement); 64 | } 65 | // console.log("advance state="+this.state+", qr="+this.qr+", target="+this.targetlevel+", rising="+this.rising); 66 | }; 67 | 68 | EnvelopeDX7.prototype.noteOff = function() { 69 | this.down = false; 70 | this.advance(3); 71 | }; 72 | 73 | EnvelopeDX7.prototype.isFinished = function() { 74 | return this.state == ENV_OFF; 75 | }; 76 | 77 | module.exports = EnvelopeDX7; -------------------------------------------------------------------------------- /src/default-presets.js: -------------------------------------------------------------------------------- 1 | var defaultPresets = [ 2 | { 3 | "name": "Init", 4 | "algorithm": 18, 5 | "feedback": 7, 6 | "lfoSpeed": 37, 7 | "lfoDelay": 42, 8 | "lfoPitchModDepth": 0, 9 | "lfoAmpModDepth": 0, 10 | "lfoPitchModSens": 4, 11 | "lfoWaveform": 4, 12 | "lfoSync": 0, 13 | "pitchEnvelope": { 14 | "rates": [0, 0, 0, 0], 15 | "levels": [50, 50, 50, 50] 16 | }, 17 | "controllerModVal": 0, 18 | "aftertouchEnabled": 0, 19 | "operators": [ 20 | { 21 | "idx": 0, 22 | "enabled": true, 23 | "rates": [96, 0, 12, 70], 24 | "levels": [99, 95, 95, 0], 25 | "detune": 1, 26 | "velocitySens": 0, 27 | "lfoAmpModSens": 0, 28 | "volume": 99, 29 | "oscMode": 0, 30 | "freqCoarse": 2, 31 | "freqFine": 0, 32 | "pan": 0, 33 | "outputLevel": 13.12273 34 | }, 35 | { 36 | "idx": 1, 37 | "enabled": true, 38 | "rates": [99, 95, 0, 70], 39 | "levels": [99, 96, 89, 0], 40 | "detune": -1, 41 | "velocitySens": 0, 42 | "lfoAmpModSens": 0, 43 | "volume": 99, 44 | "oscMode": 0, 45 | "freqCoarse": 0, 46 | "freqFine": 0, 47 | "pan": 0, 48 | "outputLevel": 13.12273 49 | }, 50 | { 51 | "idx": 2, 52 | "enabled": true, 53 | "rates": [99, 87, 0, 70], 54 | "levels": [93, 90, 0, 0], 55 | "detune": 0, 56 | "velocitySens": 0, 57 | "lfoAmpModSens": 0, 58 | "volume": 82, 59 | "oscMode": 0, 60 | "freqCoarse": 1, 61 | "freqFine": 0, 62 | "pan": 0, 63 | "outputLevel": 3.008399 64 | }, 65 | { 66 | "idx": 3, 67 | "enabled": true, 68 | "rates": [99, 92, 28, 60], 69 | "levels": [99, 90, 0, 0], 70 | "detune": 2, 71 | "velocitySens": 0, 72 | "lfoAmpModSens": 0, 73 | "volume": 71, 74 | "oscMode": 0, 75 | "freqCoarse": 2, 76 | "freqFine": 0, 77 | "pan": 0, 78 | "outputLevel": 1.159897 79 | }, 80 | { 81 | "idx": 4, 82 | "enabled": true, 83 | "rates": [99, 99, 97, 70], 84 | "levels": [99, 65, 60, 0], 85 | "detune": -2, 86 | "velocitySens": 0, 87 | "lfoAmpModSens": 0, 88 | "volume": 43, 89 | "oscMode": 0, 90 | "freqCoarse": 3, 91 | "freqFine": 0, 92 | "pan": 0, 93 | "outputLevel": 0.102521 94 | }, 95 | { 96 | "idx": 5, 97 | "enabled": true, 98 | "rates": [99, 70, 60, 70], 99 | "levels": [99, 99, 97, 0], 100 | "detune": 0, 101 | "velocitySens": 0, 102 | "lfoAmpModSens": 0, 103 | "volume": 47, 104 | "oscMode": 0, 105 | "freqCoarse": 17, 106 | "freqFine": 0, 107 | "pan": 0, 108 | "outputLevel": 0.144987 109 | } 110 | ] 111 | } 112 | ]; 113 | 114 | module.exports = defaultPresets; -------------------------------------------------------------------------------- /src/sysex-dx7.js: -------------------------------------------------------------------------------- 1 | var SysexDX7 = { 2 | bin2hex: function (s) { 3 | var i, f = s.length, a = []; 4 | for (i = 0; i < f; i++) { 5 | a[i] = ('0' + s.charCodeAt(i).toString(16)).slice(-2); 6 | } 7 | return a.join(' '); 8 | }, 9 | 10 | // Expects bankData to be a DX7 SYSEX Bulk Data for 32 Voices 11 | loadBank: function (bankData) { 12 | var presets = []; 13 | for (var i = 0; i < 32; i++) { 14 | presets.push(this.extractPatchFromRom(bankData, i)); 15 | } 16 | return presets; 17 | }, 18 | 19 | // see http://homepages.abdn.ac.uk/mth192/pages/dx7/sysex-format.txt 20 | // Section F: Data Structure: Bulk Dump Packed Format 21 | extractPatchFromRom: function (bankData, patchId) { 22 | var dataStart = 128 * patchId + 6; 23 | var dataEnd = dataStart + 128; 24 | var voiceData = bankData.substring(dataStart, dataEnd); 25 | var operators = [{},{},{},{},{},{}]; 26 | 27 | for (var i = 5; i >= 0; --i) { 28 | var oscStart = (5 - i) * 17; 29 | var oscEnd = oscStart + 17; 30 | var oscData = voiceData.substring(oscStart, oscEnd); 31 | var operator = operators[i]; 32 | 33 | operator.rates = [oscData.charCodeAt(0), oscData.charCodeAt(1), oscData.charCodeAt(2), oscData.charCodeAt(3)]; 34 | operator.levels = [oscData.charCodeAt(4), oscData.charCodeAt(5), oscData.charCodeAt(6), oscData.charCodeAt(7)]; 35 | operator.keyScaleBreakpoint = oscData.charCodeAt(8); 36 | operator.keyScaleDepthL = oscData.charCodeAt(9); 37 | operator.keyScaleDepthR = oscData.charCodeAt(10); 38 | operator.keyScaleCurveL = oscData.charCodeAt(11) & 3; 39 | operator.keyScaleCurveR = oscData.charCodeAt(11) >> 2; 40 | operator.keyScaleRate = oscData.charCodeAt(12) & 7; 41 | operator.detune = Math.floor(oscData.charCodeAt(12) >> 3) - 7; // range 0 to 14 42 | operator.lfoAmpModSens = oscData.charCodeAt(13) & 3; 43 | operator.velocitySens = oscData.charCodeAt(13) >> 2; 44 | operator.volume = oscData.charCodeAt(14); 45 | operator.oscMode = oscData.charCodeAt(15) & 1; 46 | operator.freqCoarse = Math.floor(oscData.charCodeAt(15) >> 1); 47 | operator.freqFine = oscData.charCodeAt(16); 48 | // Extended/non-standard parameters 49 | operator.pan = ((i + 1)%3 - 1) * 25; // Alternate panning: -25, 0, 25, -25, 0, 25 50 | operator.idx = i; 51 | operator.enabled = true; 52 | } 53 | 54 | return { 55 | algorithm: voiceData.charCodeAt(110) + 1, // start at 1 for readability 56 | feedback: voiceData.charCodeAt(111) & 7, 57 | operators: operators, 58 | name: voiceData.substring(118, 128), 59 | lfoSpeed: voiceData.charCodeAt(112), 60 | lfoDelay: voiceData.charCodeAt(113), 61 | lfoPitchModDepth: voiceData.charCodeAt(114), 62 | lfoAmpModDepth: voiceData.charCodeAt(115), 63 | lfoPitchModSens: voiceData.charCodeAt(116) >> 4, 64 | lfoWaveform: Math.floor(voiceData.charCodeAt(116) >> 1) & 7, 65 | lfoSync: voiceData.charCodeAt(116) & 1, 66 | pitchEnvelope: { 67 | rates: [voiceData.charCodeAt(102), voiceData.charCodeAt(103), voiceData.charCodeAt(104), voiceData.charCodeAt(105)], 68 | levels: [voiceData.charCodeAt(106), voiceData.charCodeAt(107), voiceData.charCodeAt(108), voiceData.charCodeAt(109)] 69 | }, 70 | controllerModVal: 0, 71 | aftertouchEnabled: 0 72 | }; 73 | } 74 | }; 75 | 76 | module.exports = SysexDX7; -------------------------------------------------------------------------------- /images/lcd-background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/midi.js: -------------------------------------------------------------------------------- 1 | var MIDI = function(synth) { 2 | var selectMidi = null; 3 | var midiAccess = null; 4 | var midiIn = null; 5 | 6 | function midiMessageReceived( ev ) { 7 | synth.queueMidiEvent(ev); 8 | return; 9 | 10 | var cmd = ev.data[0] >> 4; 11 | var channel = ev.data[0] & 0xf; 12 | var noteNumber = ev.data[1]; 13 | var velocity = ev.data[2]; 14 | // console.log( "" + ev.data[0] + " " + ev.data[1] + " " + ev.data[2]) 15 | // console.log("midi: ch %d, cmd %d, note %d, vel %d", channel, cmd, noteNumber, velocity); 16 | if (channel == 9) 17 | return; 18 | if (cmd==8 || ((cmd==9)&&(velocity==0))) { // with MIDI, note on with velocity zero is the same as note off 19 | synth.noteOff(noteNumber); 20 | } else if (cmd == 9) { 21 | synth.noteOn(noteNumber, velocity/99.0); // changed 127 to 99 to incorporate "overdrive" 22 | } else if (cmd == 10) { 23 | //synth.polyphonicAftertouch(noteNumber, velocity/127); 24 | } else if (cmd == 11) { 25 | synth.controller(noteNumber, velocity/127); 26 | } else if (cmd == 12) { 27 | //synth.programChange(noteNumber); 28 | } else if (cmd == 13) { 29 | synth.channelAftertouch(noteNumber/127); 30 | } else if (cmd == 14) { 31 | synth.pitchBend( ((velocity * 128.0 + noteNumber)-8192)/8192.0 ); 32 | } 33 | } 34 | 35 | function onSelectMidiChange( ev ) { 36 | var index = ev.target.selectedIndex; 37 | setMidiIn(index); 38 | } 39 | 40 | function setMidiIn(index) { 41 | if (!selectMidi[index]) 42 | return; 43 | var id = selectMidi[index].value; 44 | if (midiIn) 45 | midiIn.onmidimessage = null; 46 | if ((typeof(midiAccess.inputs) == "function")) //Old Skool MIDI inputs() code 47 | midiIn = midiAccess.inputs()[index]; 48 | else 49 | midiIn = midiAccess.inputs.get(id); 50 | if (midiIn) 51 | midiIn.onmidimessage = midiMessageReceived; 52 | } 53 | 54 | function onMidiStarted( midi ) { 55 | var preferredIndex = 0; 56 | midiAccess = midi; 57 | selectMidi=document.getElementById("midiIn"); 58 | if ((typeof(midiAccess.inputs) == "function")) { //Old Skool MIDI inputs() code 59 | var list=midiAccess.inputs(); 60 | 61 | // clear the MIDI input select 62 | selectMidi.options.length = 0; 63 | 64 | for (var i = 0; (i> 4; 28 | var channel = ev.data[0] & 0xf; 29 | var noteNumber = ev.data[1]; 30 | var velocity = ev.data[2]; 31 | // console.log( "" + ev.data[0] + " " + ev.data[1] + " " + ev.data[2]) 32 | // console.log("midi: ch %d, cmd %d, note %d, vel %d", channel, cmd, noteNumber, velocity); 33 | if (channel === 9) // Ignore drum channel 34 | return; 35 | if (cmd === 8 || (cmd === 9 && velocity === 0)) { // with MIDI, note on with velocity zero is the same as note off 36 | this.noteOff(noteNumber); 37 | } else if (cmd === 9) { 38 | this.noteOn(noteNumber, velocity/99.0); // changed 127 to 99 to incorporate "overdrive" 39 | } else if (cmd === 10) { 40 | //this.polyphonicAftertouch(noteNumber, velocity/127); 41 | } else if (cmd === 11) { 42 | this.controller(noteNumber, velocity/127); 43 | } else if (cmd === 12) { 44 | //this.programChange(noteNumber); 45 | } else if (cmd === 13) { 46 | this.channelAftertouch(noteNumber/127); 47 | } else if (cmd === 14) { 48 | this.pitchBend(((velocity * 128.0 + noteNumber) - 8192)/8192.0); 49 | } 50 | 51 | }; 52 | 53 | Synth.prototype.getLatestNoteDown = function() { 54 | var voice = this.voices[this.voices.length - 1] || { note: 64 }; 55 | return voice.note; 56 | }; 57 | 58 | Synth.prototype.controller = function(controlNumber, value) { 59 | // see http://www.midi.org/techspecs/midimessages.php#3 60 | switch (controlNumber) { 61 | case MIDI_CC_MODULATION: 62 | this.voiceClass.modulationWheel(value); 63 | break; 64 | case MIDI_CC_SUSTAIN_PEDAL: 65 | this.sustainPedal(value > 0.5); 66 | break; 67 | } 68 | }; 69 | 70 | Synth.prototype.channelAftertouch = function(value) { 71 | this.voiceClass.channelAftertouch(value); 72 | }; 73 | 74 | Synth.prototype.sustainPedal = function(down) { 75 | if (down) { 76 | this.sustainPedalDown = true; 77 | } else { 78 | this.sustainPedalDown = false; 79 | for (var i = 0, l = this.voices.length; i < l; i++) { 80 | if (this.voices[i] && this.voices[i].down === false) 81 | this.voices[i].noteOff(); 82 | } 83 | } 84 | }; 85 | 86 | Synth.prototype.pitchBend = function(value) { 87 | this.voiceClass.pitchBend(value * PITCH_BEND_RANGE); 88 | for (var i = 0, l = this.voices.length; i < l; i++) { 89 | if (this.voices[i]) 90 | this.voices[i].updatePitchBend(); 91 | } 92 | }; 93 | 94 | Synth.prototype.noteOn = function(note, velocity) { 95 | var voice = new this.voiceClass(note, velocity); 96 | if (this.voices.length >= this.polyphony) { 97 | // TODO: fade out removed voices 98 | this.voices.shift(); // remove first 99 | } 100 | this.voices.push(voice); 101 | }; 102 | 103 | Synth.prototype.noteOff = function(note) { 104 | for (var i = 0, voice; i < this.voices.length, voice = this.voices[i]; i++) { 105 | if (voice && voice.note === note && voice.down === true) { 106 | voice.down = false; 107 | if (this.sustainPedalDown === false) 108 | voice.noteOff(); 109 | break; 110 | } 111 | } 112 | }; 113 | 114 | Synth.prototype.panic = function() { 115 | this.sustainPedalDown = false; 116 | for (var i = 0, l = this.voices.length; i < l; i++) { 117 | if (this.voices[i]) 118 | this.voices[i].noteOff(); 119 | } 120 | this.voices = []; 121 | }; 122 | 123 | Synth.prototype.render = function() { 124 | var output; 125 | var outputL = 0; 126 | var outputR = 0; 127 | 128 | for (var i = 0, length = this.voices.length; i < length; i++) { 129 | var voice = this.voices[i]; 130 | if (voice) { 131 | if (voice.isFinished()) { 132 | // Clear the note after release 133 | this.voices.splice(i, 1); 134 | i--; // undo increment 135 | } else { 136 | output = voice.render(); 137 | outputL += output[0]; 138 | outputR += output[1]; 139 | } 140 | } 141 | } 142 | return [outputL * PER_VOICE_LEVEL, outputR * PER_VOICE_LEVEL]; 143 | }; 144 | 145 | module.exports = Synth; 146 | -------------------------------------------------------------------------------- /src/visualizer.js: -------------------------------------------------------------------------------- 1 | var PIXI = require('pixi.js'); 2 | 3 | var MODE_DISABLED = 0; 4 | var MODE_WAVE = 1; 5 | var MODE_FFT = 2; 6 | var MODE_COUNT = 3; 7 | 8 | var WAVE_PIXELS_PER_SAMPLE = 0.4; 9 | 10 | // getByteTimeDomainData polyfill for Safari 11 | if (global.AnalyserNode && !global.AnalyserNode.prototype.getFloatTimeDomainData) { 12 | var uint8 = new Uint8Array(2048); 13 | global.AnalyserNode.prototype.getFloatTimeDomainData = function(array) { 14 | this.getByteTimeDomainData(uint8); 15 | for (var i = 0, imax = array.length; i < imax; i++) { 16 | array[i] = (uint8[i] - 128) * 0.0078125; 17 | } 18 | }; 19 | } 20 | 21 | function Visualizer(containerId, width, height, backgroundColor, foregroundColor, audioContext) { 22 | this.render = this.render.bind(this); 23 | this.width = width; 24 | this.height = height; 25 | this.backgroundColor = backgroundColor; 26 | this.foregroundColor = foregroundColor; 27 | 28 | // set up audio analyser node 29 | this.analyzer = audioContext.createAnalyser(); 30 | this.analyzer.smoothingTimeConstant = 0.25; 31 | this.setPeriod(this.width); 32 | 33 | // create a pixi stage and renderer instance 34 | this.stage = new PIXI.Container(); 35 | this.renderer = PIXI.autoDetectRenderer(width, height, {backgroundColor : backgroundColor, resolution: 1 }); 36 | // Kill PIXI mouse event listeners (https://github.com/pixijs/pixijs/issues/1672). 37 | this.renderer.plugins.interaction.destroy() 38 | this.el = this.renderer.view; 39 | this.graphics = new PIXI.Graphics(); 40 | this.graphics.lineStyle(1, foregroundColor); 41 | this.stage.addChild(this.graphics); 42 | 43 | // add the renderer view element to the DOM 44 | var containerEl = document.getElementById(containerId); 45 | containerEl.appendChild(this.el); 46 | 47 | // set default mode 48 | this.setModeWave(); 49 | } 50 | 51 | Visualizer.prototype.getAudioNode = function() { 52 | return this.analyzer; 53 | }; 54 | 55 | Visualizer.prototype.enable = function() { 56 | this.enabled = true; 57 | this.el.style.visibility = "visible"; 58 | requestAnimationFrame(this.render); 59 | }; 60 | 61 | Visualizer.prototype.cycleMode = function() { 62 | this.mode = (this.mode + 1) % MODE_COUNT; 63 | switch (this.mode) { 64 | case MODE_DISABLED: 65 | this.setModeDisabled(); 66 | break; 67 | case MODE_WAVE: 68 | this.setModeWave(); 69 | break; 70 | case MODE_FFT: 71 | this.setModeFFT(); 72 | break; 73 | } 74 | }; 75 | 76 | Visualizer.prototype.setModeDisabled = function() { 77 | this.mode = MODE_DISABLED; 78 | this.enabled = false; 79 | this.el.style.visibility = "hidden"; 80 | }; 81 | 82 | Visualizer.prototype.setModeFFT = function() { 83 | this.mode = MODE_FFT; 84 | this.enable(); 85 | try { 86 | this.analyzer.fftSize = Math.pow(2, Math.ceil(Math.log(this.width) / Math.LN2)) * 16; 87 | } catch (e) { 88 | // Probably went over a browser limitation, try 2048... 89 | this.analyzer.fftSize = 2048; 90 | } 91 | this.data = new Uint8Array(this.analyzer.frequencyBinCount); 92 | }; 93 | 94 | Visualizer.prototype.setModeWave = function() { 95 | this.mode = MODE_WAVE; 96 | this.enable(); 97 | // Analyzer needs extra data padding to do phase alignment across frames 98 | this.analyzer.fftSize = 2048; 99 | this.floatData = new Float32Array(this.analyzer.frequencyBinCount); 100 | }; 101 | 102 | Visualizer.prototype.setPeriod = function(samplePeriod) { 103 | // TODO: make WAVE_PIXELS_PER_SAMPLE dynamic so that low freqs don't get cut off 104 | if (this.mode !== MODE_WAVE) return; 105 | this.period = samplePeriod; 106 | }; 107 | 108 | Visualizer.prototype.render = function() { 109 | 110 | var data; 111 | var graphics = this.graphics; 112 | var height = this.height - 1; 113 | var i; 114 | 115 | // The time and data are sometimes duplicated. In this case we can bypass rendering. 116 | var sampleTime = this.analyzer.context.sampleRate * this.analyzer.context.currentTime; 117 | if (sampleTime !== this.lastTime) { 118 | 119 | graphics.clear(); 120 | this.lastTime = sampleTime; 121 | 122 | if (this.mode === MODE_FFT) { 123 | data = this.data; 124 | this.analyzer.getByteFrequencyData(data); 125 | 126 | graphics.lineStyle(1, this.foregroundColor, 0.2); 127 | graphics.moveTo(0, height); 128 | graphics.lineTo(this.width, height); 129 | 130 | graphics.lineStyle(1, this.foregroundColor, 0.66); 131 | for (i = 0, l = data.length; i < l; i++) { 132 | if (data[i] === 0) continue; 133 | graphics.moveTo(i/4, height); 134 | graphics.lineTo(i/4, height - (data[i] >> 3)); 135 | } 136 | } else if (this.mode === MODE_WAVE) { 137 | data = this.floatData; 138 | this.analyzer.getFloatTimeDomainData(data); 139 | 140 | graphics.lineStyle(1, this.foregroundColor, 0.2); 141 | graphics.moveTo(0, height/2); 142 | graphics.lineTo(this.width, height/2); 143 | 144 | // Normalize... 145 | var max = 0; 146 | for (i = 0, l = data.length; i < l; i++) { 147 | var abs = Math.abs(data[i]); 148 | if (abs > max) max = abs; 149 | } 150 | var scale = Math.min(1 / max, 4) * 0.48; 151 | var sampleOffset = (sampleTime) % this.period - this.period; 152 | 153 | graphics.lineStyle(1, this.foregroundColor, 1); 154 | graphics.moveTo(0, height * 1.5 - (data[0] >> 2) - 1); 155 | for (i = 0, l = data.length; i < l; i++) { 156 | var x = (i + sampleOffset) * WAVE_PIXELS_PER_SAMPLE; 157 | if (x > this.width) break; 158 | graphics.lineTo(x, height / 2 + height * data[i] * scale); 159 | } 160 | } 161 | } 162 | 163 | // render the stage 164 | this.renderer.render(this.stage); 165 | if (this.enabled) requestAnimationFrame(this.render); 166 | }; 167 | 168 | module.exports = Visualizer; 169 | -------------------------------------------------------------------------------- /src/reverb.js: -------------------------------------------------------------------------------- 1 | /*global ArrayBuffer, Uint8Array, window, XMLHttpRequest*/ 2 | // LICENSE: 3 | // https://github.com/burnson/Reverb.js/blob/master/COPYING.md 4 | var Reverb = { 5 | extend : function (audioContext) { 6 | function decodeBase64ToArrayBuffer(input) { 7 | function encodedValue(input, index) { 8 | var encodedCharacter, x = input.charCodeAt(index); 9 | if (index < input.length) { 10 | if (x >= 65 && x <= 90) { 11 | encodedCharacter = x - 65; 12 | } else if (x >= 97 && x <= 122) { 13 | encodedCharacter = x - 71; 14 | } else if (x >= 48 && x <= 57) { 15 | encodedCharacter = x + 4; 16 | } else if (x === 43) { 17 | encodedCharacter = 62; 18 | } else if (x === 47) { 19 | encodedCharacter = 63; 20 | } else if (x !== 61) { 21 | console.log('base64 encountered unexpected character code: ' + x); 22 | } 23 | } 24 | return encodedCharacter; 25 | } 26 | 27 | if (input.length === 0 || (input.length % 4) > 0) { 28 | console.log('base64 encountered unexpected length: ' + input.length); 29 | return; 30 | } 31 | 32 | var padding = input.match(/[=]*$/)[0].length, 33 | decodedLength = input.length * 3 / 4 - padding, 34 | buffer = new ArrayBuffer(decodedLength), 35 | bufferView = new Uint8Array(buffer), 36 | encoded = [], 37 | d = 0, 38 | e = 0, 39 | i; 40 | 41 | while (d < decodedLength) { 42 | for (i = 0; i < 4; i += 1) { 43 | encoded[i] = encodedValue(input, e); 44 | e += 1; 45 | } 46 | bufferView[d] = (encoded[0] * 4) + Math.floor(encoded[1] / 16); 47 | d += 1; 48 | if (d < decodedLength) { 49 | bufferView[d] = ((encoded[1] % 16) * 16) + Math.floor(encoded[2] / 4); 50 | d += 1; 51 | } 52 | if (d < decodedLength) { 53 | bufferView[d] = ((encoded[2] % 4) * 64) + encoded[3]; 54 | d += 1; 55 | } 56 | } 57 | return buffer; 58 | } 59 | 60 | function decodeAndSetupBuffer(node, arrayBuffer, callback) { 61 | audioContext.decodeAudioData(arrayBuffer, function (audioBuffer) { 62 | console.log('Finished decoding audio data.'); 63 | node.buffer = audioBuffer; 64 | if (typeof callback === "function" && audioBuffer !== null) { 65 | callback(node); 66 | } 67 | }, function (e) { 68 | console.log('Could not decode audio data: ' + e); 69 | }); 70 | } 71 | 72 | audioContext.createReverbFromBase64 = function (audioBase64, callback) { 73 | var reverbNode = audioContext.createConvolver(); 74 | decodeAndSetupBuffer(reverbNode, decodeBase64ToArrayBuffer(audioBase64), 75 | callback); 76 | return reverbNode; 77 | }; 78 | 79 | audioContext.createSourceFromBase64 = function (audioBase64, callback) { 80 | var sourceNode = audioContext.createBufferSource(); 81 | decodeAndSetupBuffer(sourceNode, decodeBase64ToArrayBuffer(audioBase64), 82 | callback); 83 | return sourceNode; 84 | }; 85 | 86 | audioContext.createReverbFromUrl = function (audioUrl, callback) { 87 | console.log('Downloading impulse response from ' + audioUrl); 88 | var reverbNode = audioContext.createConvolver(), 89 | request = new XMLHttpRequest(); 90 | request.open('GET', audioUrl, true); 91 | request.onreadystatechange = function () { 92 | if (request.readyState === 4 && request.status === 200) { 93 | console.log('Downloaded impulse response'); 94 | decodeAndSetupBuffer(reverbNode, request.response, callback); 95 | } 96 | }; 97 | request.onerror = function (e) { 98 | console.log('There was an error receiving the response: ' + e); 99 | reverbjs.networkError = e; 100 | }; 101 | request.responseType = 'arraybuffer'; 102 | request.send(); 103 | return reverbNode; 104 | }; 105 | 106 | audioContext.createSourceFromUrl = function (audioUrl, callback) { 107 | console.log('Downloading sound from ' + audioUrl); 108 | var sourceNode = audioContext.createBufferSource(), 109 | request = new XMLHttpRequest(); 110 | request.open('GET', audioUrl, true); 111 | request.onreadystatechange = function () { 112 | if (request.readyState === 4 && request.status === 200) { 113 | console.log('Downloaded sound'); 114 | decodeAndSetupBuffer(sourceNode, request.response, callback); 115 | } 116 | }; 117 | request.onerror = function (e) { 118 | console.log('There was an error receiving the response: ' + e); 119 | reverbjs.networkError = e; 120 | }; 121 | request.responseType = 'arraybuffer'; 122 | request.send(); 123 | return sourceNode; 124 | }; 125 | 126 | audioContext.createReverbFromBase64Url = function (audioUrl, callback) { 127 | console.log('Downloading base64 impulse response from ' + audioUrl); 128 | var reverbNode = audioContext.createConvolver(), 129 | request = new XMLHttpRequest(); 130 | request.open('GET', audioUrl, true); 131 | request.onreadystatechange = function () { 132 | if (request.readyState === 4 && request.status === 200) { 133 | console.log('Downloaded impulse response'); 134 | decodeAndSetupBuffer(reverbNode, 135 | decodeBase64ToArrayBuffer(request.response), 136 | callback); 137 | } 138 | }; 139 | request.onerror = function (e) { 140 | console.log('There was an error receiving the response: ' + e); 141 | reverbjs.networkError = e; 142 | }; 143 | request.send(); 144 | return reverbNode; 145 | }; 146 | 147 | audioContext.createSourceFromBase64Url = function (audioUrl, callback) { 148 | console.log('Downloading base64 sound from ' + audioUrl); 149 | var sourceNode = audioContext.createBufferSource(), 150 | request = new XMLHttpRequest(); 151 | request.open('GET', audioUrl, true); 152 | request.onreadystatechange = function () { 153 | if (request.readyState === 4 && request.status === 200) { 154 | console.log('Downloaded sound'); 155 | decodeAndSetupBuffer(sourceNode, 156 | decodeBase64ToArrayBuffer(request.response), 157 | callback); 158 | } 159 | }; 160 | request.onerror = function (e) { 161 | console.log('There was an error receiving the response: ' + e); 162 | reverbjs.networkError = e; 163 | }; 164 | request.send(); 165 | return sourceNode; 166 | }; 167 | } 168 | }; 169 | 170 | module.exports = Reverb; -------------------------------------------------------------------------------- /src/lfo-dx7.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'); 2 | 3 | var PERIOD = Math.PI * 2; 4 | var PERIOD_HALF = PERIOD / 2; 5 | var PERIOD_RECIP = 1 / PERIOD; 6 | var LFO_SAMPLE_PERIOD = config.lfoSamplePeriod; 7 | var LFO_FREQUENCY_TABLE = [ // see https://github.com/smbolton/hexter/tree/master/src/dx7_voice.c#L1002 8 | 0.062506, 0.124815, 0.311474, 0.435381, 0.619784, 9 | 0.744396, 0.930495, 1.116390, 1.284220, 1.496880, 10 | 1.567830, 1.738994, 1.910158, 2.081322, 2.252486, 11 | 2.423650, 2.580668, 2.737686, 2.894704, 3.051722, 12 | 3.208740, 3.366820, 3.524900, 3.682980, 3.841060, 13 | 3.999140, 4.159420, 4.319700, 4.479980, 4.640260, 14 | 4.800540, 4.953584, 5.106628, 5.259672, 5.412716, 15 | 5.565760, 5.724918, 5.884076, 6.043234, 6.202392, 16 | 6.361550, 6.520044, 6.678538, 6.837032, 6.995526, 17 | 7.154020, 7.300500, 7.446980, 7.593460, 7.739940, 18 | 7.886420, 8.020588, 8.154756, 8.288924, 8.423092, 19 | 8.557260, 8.712624, 8.867988, 9.023352, 9.178716, 20 | 9.334080, 9.669644, 10.005208, 10.340772, 10.676336, 21 | 11.011900, 11.963680, 12.915460, 13.867240, 14.819020, 22 | 15.770800, 16.640240, 17.509680, 18.379120, 19.248560, 23 | 20.118000, 21.040700, 21.963400, 22.886100, 23.808800, 24 | 24.731500, 25.759740, 26.787980, 27.816220, 28.844460, 25 | 29.872700, 31.228200, 32.583700, 33.939200, 35.294700, 26 | 36.650200, 37.812480, 38.974760, 40.137040, 41.299320, 27 | 42.461600, 43.639800, 44.818000, 45.996200, 47.174400, 28 | 47.174400, 47.174400, 47.174400, 47.174400, 47.174400, 29 | 47.174400, 47.174400, 47.174400, 47.174400, 47.174400, 30 | 47.174400, 47.174400, 47.174400, 47.174400, 47.174400, 31 | 47.174400, 47.174400, 47.174400, 47.174400, 47.174400, 32 | 47.174400, 47.174400, 47.174400, 47.174400, 47.174400, 33 | 47.174400, 47.174400, 47.174400 34 | ]; 35 | var LFO_AMP_MOD_TABLE = [ // TODO: use lfo amp mod table 36 | 0.00000, 0.00793, 0.00828, 0.00864, 0.00902, 0.00941, 0.00982, 0.01025, 0.01070, 0.01117, 37 | 0.01166, 0.01217, 0.01271, 0.01327, 0.01385, 0.01445, 0.01509, 0.01575, 0.01644, 0.01716, 38 | 0.01791, 0.01870, 0.01952, 0.02037, 0.02126, 0.02220, 0.02317, 0.02418, 0.02524, 0.02635, 39 | 0.02751, 0.02871, 0.02997, 0.03128, 0.03266, 0.03409, 0.03558, 0.03714, 0.03877, 0.04047, 40 | 0.04224, 0.04409, 0.04603, 0.04804, 0.05015, 0.05235, 0.05464, 0.05704, 0.05954, 0.06215, 41 | 0.06487, 0.06772, 0.07068, 0.07378, 0.07702, 0.08039, 0.08392, 0.08759, 0.09143, 0.09544, 42 | 0.09962, 0.10399, 0.10855, 0.11331, 0.11827, 0.12346, 0.12887, 0.13452, 0.14041, 0.14657, 43 | 0.15299, 0.15970, 0.16670, 0.17401, 0.18163, 0.18960, 0.19791, 0.20658, 0.21564, 0.22509, 44 | 0.23495, 0.24525, 0.25600, 0.26722, 0.27894, 0.29116, 0.30393, 0.31725, 0.33115, 0.34567, 45 | 0.36082, 0.37664, 0.39315, 0.41038, 0.42837, 0.44714, 0.46674, 0.48720, 0.50856, 0.53283 46 | ]; 47 | var LFO_PITCH_MOD_TABLE = [ 48 | 0, 0.0264, 0.0534, 0.0889, 0.1612, 0.2769, 0.4967, 1 49 | ]; 50 | var LFO_MODE_TRIANGLE = 0, 51 | LFO_MODE_SAW_DOWN = 1, 52 | LFO_MODE_SAW_UP = 2, 53 | LFO_MODE_SQUARE = 3, 54 | LFO_MODE_SINE = 4, 55 | LFO_MODE_SAMPLE_HOLD = 5; 56 | 57 | var LFO_DELAY_ONSET = 0, 58 | LFO_DELAY_RAMP = 1, 59 | LFO_DELAY_COMPLETE = 2; 60 | 61 | // Private static variables 62 | var phaseStep = 0; 63 | var pitchModDepth = 0; 64 | var ampModDepth = 0; 65 | var sampleHoldRandom = 0; 66 | var delayTimes = [0, 0, 0]; 67 | var delayIncrements = [0, 0, 0]; 68 | var delayVals = [0, 0, 1]; 69 | var params = {}; 70 | 71 | function LfoDX7(opParams) { 72 | this.opParams = opParams; 73 | this.phase = 0; 74 | this.pitchVal = 0; 75 | this.counter = 0; 76 | this.ampVal = 1; 77 | this.ampValTarget = 1; 78 | this.ampIncrement = 0; 79 | this.delayVal = 0; 80 | this.delayState = LFO_DELAY_ONSET; 81 | LfoDX7.update(); 82 | } 83 | 84 | LfoDX7.prototype.render = function() { 85 | var amp; 86 | if (this.counter % LFO_SAMPLE_PERIOD === 0) { 87 | switch (params.lfoWaveform) { 88 | case LFO_MODE_TRIANGLE: 89 | if (this.phase < PERIOD_HALF) 90 | amp = 4 * this.phase * PERIOD_RECIP - 1; 91 | else 92 | amp = 3 - 4 * this.phase * PERIOD_RECIP; 93 | break; 94 | case LFO_MODE_SAW_DOWN: 95 | amp = 1 - 2 * this.phase * PERIOD_RECIP; 96 | break; 97 | case LFO_MODE_SAW_UP: 98 | amp = 2 * this.phase * PERIOD_RECIP - 1; 99 | break; 100 | case LFO_MODE_SQUARE: 101 | amp = (this.phase < PERIOD_HALF) ? -1 : 1; 102 | break; 103 | case LFO_MODE_SINE: 104 | amp = Math.sin(this.phase); 105 | break; 106 | case LFO_MODE_SAMPLE_HOLD: 107 | amp = sampleHoldRandom; 108 | break; 109 | } 110 | 111 | switch (this.delayState) { 112 | case LFO_DELAY_ONSET: 113 | case LFO_DELAY_RAMP: 114 | this.delayVal += delayIncrements[this.delayState]; 115 | if (this.counter / LFO_SAMPLE_PERIOD > delayTimes[this.delayState]) { 116 | this.delayState++; 117 | this.delayVal = delayVals[this.delayState]; 118 | } 119 | break; 120 | case LFO_DELAY_COMPLETE: 121 | break; 122 | } 123 | 124 | // if (this.counter % 10000 == 0 && this.operatorIndex === 0) console.log("lfo amp value", this.ampVal); 125 | amp *= this.delayVal; 126 | pitchModDepth = 1 + 127 | LFO_PITCH_MOD_TABLE[params.lfoPitchModSens] * (params.controllerModVal + params.lfoPitchModDepth / 99); 128 | this.pitchVal = Math.pow(pitchModDepth, amp); 129 | // TODO: Simplify ampValTarget calculation. 130 | // ampValTarget range = 0 to 1. lfoAmpModSens range = -3 to 3. ampModDepth range = 0 to 1. amp range = -1 to 1. 131 | var ampSensDepth = Math.abs(this.opParams.lfoAmpModSens) * 0.333333; 132 | var phase = (this.opParams.lfoAmpModSens > 0) ? 1 : -1; 133 | this.ampValTarget = 1 - ((ampModDepth + params.controllerModVal) * ampSensDepth * (amp * phase + 1) * 0.5); 134 | this.ampIncrement = (this.ampValTarget - this.ampVal) / LFO_SAMPLE_PERIOD; 135 | this.phase += phaseStep; 136 | if (this.phase >= PERIOD) { 137 | sampleHoldRandom = 1 - Math.random() * 2; 138 | this.phase -= PERIOD; 139 | } 140 | } 141 | this.counter++; 142 | return this.pitchVal; 143 | }; 144 | 145 | LfoDX7.prototype.renderAmp = function() { 146 | this.ampVal += this.ampIncrement; 147 | return this.ampVal; 148 | }; 149 | 150 | LfoDX7.setParams = function(globalParams) { 151 | params = globalParams; 152 | }; 153 | 154 | LfoDX7.update = function() { 155 | var frequency = LFO_FREQUENCY_TABLE[params.lfoSpeed]; 156 | var lfoRate = config.sampleRate/LFO_SAMPLE_PERIOD; 157 | phaseStep = PERIOD * frequency/lfoRate; // radians per sample 158 | ampModDepth = params.lfoAmpModDepth * 0.01; 159 | // ignoring amp mod table for now. it seems shallow LFO_AMP_MOD_TABLE[params.lfoAmpModDepth]; 160 | delayTimes[LFO_DELAY_ONSET] = (lfoRate * 0.001753 * Math.pow(params.lfoDelay, 3.10454) + 169.344 - 168) / 1000; 161 | delayTimes[LFO_DELAY_RAMP] = (lfoRate * 0.321877 * Math.pow(params.lfoDelay, 2.01163) + 494.201 - 168) / 1000; 162 | delayIncrements[LFO_DELAY_RAMP] = 1 / (delayTimes[LFO_DELAY_RAMP] - delayTimes[LFO_DELAY_ONSET]); 163 | }; 164 | 165 | module.exports = LfoDX7; 166 | -------------------------------------------------------------------------------- /images/algorithm-31.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 118 | 119 | 120 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /images/algorithm-29.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | 119 | 120 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /images/algorithm-32.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 89 | 96 | 97 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 118 | 119 | 120 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /images/algorithm-30.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 52 | 53 | 54 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 120 | 121 | 122 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /images/algorithm-05.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 45 | 46 | 47 | 50 | 51 | 52 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /images/algorithm-06.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 45 | 46 | 47 | 50 | 51 | 52 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /images/algorithm-01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 56 | 57 | 58 | 61 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /images/algorithm-07.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 56 | 57 | 58 | 61 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /images/algorithm-02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 56 | 57 | 58 | 61 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /images/algorithm-08.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 56 | 57 | 58 | 61 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /images/algorithm-09.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 56 | 57 | 58 | 61 | 62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /images/algorithm-13.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | 49 | 58 | 59 | 60 | 63 | 64 | 65 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /images/algorithm-26.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 103 | 104 | 105 | 106 | 107 | 108 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /images/algorithm-14.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 36 | 37 | 38 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 88 | 89 | 90 | 91 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /images/algorithm-12.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | 49 | 58 | 59 | 60 | 63 | 64 | 65 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /images/algorithm-28.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 42 | 43 | 44 | 47 | 48 | 49 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 123 | 124 | 125 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /images/algorithm-15.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 45 | 46 | 47 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | 100 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /images/algorithm-27.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | 121 | 127 | 128 | 129 | 130 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /images/algorithm-25.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 126 | 127 | 128 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /images/algorithm-11.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /images/algorithm-10.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /images/algorithm-16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | --------------------------------------------------------------------------------