├── .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 |
11 |
--------------------------------------------------------------------------------
/images/dx7-toggle-off-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/images/dx7-toggle-on.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
11 |
--------------------------------------------------------------------------------
/images/dx7-toggle-on-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/images/knob-foreground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
40 |
--------------------------------------------------------------------------------
/images/dx7-slider-foreground.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
40 |
--------------------------------------------------------------------------------
/images/frequency-display.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
43 |
--------------------------------------------------------------------------------
/images/dx7-slider-meter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DX7 Synth JS
2 | =================
3 |
4 | 
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 |
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 |
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 |
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 |
129 |
--------------------------------------------------------------------------------
/images/algorithm-29.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
129 |
--------------------------------------------------------------------------------
/images/algorithm-32.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
131 |
--------------------------------------------------------------------------------
/images/algorithm-30.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
133 |
--------------------------------------------------------------------------------
/images/algorithm-05.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
131 |
--------------------------------------------------------------------------------
/images/algorithm-06.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
131 |
--------------------------------------------------------------------------------
/images/algorithm-01.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
133 |
--------------------------------------------------------------------------------
/images/algorithm-07.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
133 |
--------------------------------------------------------------------------------
/images/algorithm-02.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
134 |
--------------------------------------------------------------------------------
/images/algorithm-08.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
134 |
--------------------------------------------------------------------------------
/images/algorithm-09.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
134 |
--------------------------------------------------------------------------------
/images/algorithm-13.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
135 |
--------------------------------------------------------------------------------
/images/algorithm-26.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
137 |
--------------------------------------------------------------------------------
/images/algorithm-14.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
135 |
--------------------------------------------------------------------------------
/images/algorithm-12.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
136 |
--------------------------------------------------------------------------------
/images/algorithm-28.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
136 |
--------------------------------------------------------------------------------
/images/algorithm-15.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
136 |
--------------------------------------------------------------------------------
/images/algorithm-27.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
139 |
--------------------------------------------------------------------------------
/images/algorithm-25.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
137 |
--------------------------------------------------------------------------------
/images/algorithm-11.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
139 |
--------------------------------------------------------------------------------
/images/algorithm-10.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
140 |
--------------------------------------------------------------------------------
/images/algorithm-16.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
141 |
--------------------------------------------------------------------------------