├── .gitignore ├── LICENSE ├── README.md ├── build ├── Makefile └── encode-wasm.js ├── dist ├── audioworker.js ├── audioworklet.js ├── dx7.html ├── dx7 │ ├── dx7-awn.js │ ├── dx7-awp.js │ ├── dx7-lib.js │ ├── gui │ │ ├── dx7.css │ │ ├── gui.js │ │ └── keys.js │ ├── presets │ │ └── README.md │ └── wasm │ │ ├── dx7.js │ │ └── dx7.wasm.js └── wamsdk │ ├── wam-controller.js │ └── wam-processor.js └── src ├── c ├── dx7.cc ├── dx7.h ├── msfa │ ├── COPYING │ ├── aligned_buf.h │ ├── controllers.h │ ├── dx7note.cc │ ├── dx7note.h │ ├── env.cc │ ├── env.h │ ├── exp2.cc │ ├── exp2.h │ ├── fm_core.cc │ ├── fm_core.h │ ├── fm_op_kernel.cc │ ├── fm_op_kernel.h │ ├── freqlut.cc │ ├── freqlut.h │ ├── lfo.cc │ ├── lfo.h │ ├── module.h │ ├── patch.cc │ ├── patch.h │ ├── pitchenv.cc │ ├── pitchenv.h │ ├── ringbuffer.cc │ ├── ringbuffer.h │ ├── sin.cc │ ├── sin.h │ ├── synth.h │ ├── synth_unit.cc │ ├── synth_unit.h │ ├── tuning.cc │ └── tuning.h └── tuning-library │ ├── .gitignore │ ├── LICENSE.md │ ├── Makefile │ ├── README.md │ ├── azure-pipelines.yml │ ├── commands │ └── showmapping.cpp │ ├── include │ ├── Tunings.h │ └── TuningsImpl.h │ ├── libs │ └── catch2 │ │ └── catch2.hpp │ ├── scripts │ └── release-notes.sh │ └── tests │ ├── alltests.cpp │ ├── data │ ├── 12-intune-dosle.scl │ ├── 12-intune.scl │ ├── 12-shuffled.scl │ ├── 31edo.scl │ ├── 6-exact.scl │ ├── ED3-17.scl │ ├── ED4-17.scl │ ├── bad │ │ ├── badnote.scl │ │ ├── blank-line.kbm │ │ ├── blanknote.scl │ │ ├── empty-bad.kbm │ │ ├── empty-extra.kbm │ │ ├── extraline-long.kbm │ │ ├── extraline.scl │ │ ├── garbage-key.kbm │ │ ├── missing-note.kbm │ │ └── missingnote.scl │ ├── empty-note61.kbm │ ├── empty-note69-dosle.kbm │ ├── empty-note69.kbm │ ├── mapping-a440-constant.kbm │ ├── mapping-a442-7-to-12.kbm │ ├── mapping-note53-to-430-408.kbm │ ├── mapping-note54-to-259-6.kbm │ ├── mapping-whitekeys-a440.kbm │ ├── mapping-whitekeys-c261.kbm │ ├── marvel12.scl │ ├── shuffle-a440-constant.kbm │ └── zeus22.scl │ ├── symbolcheck1.cpp │ └── symbolcheck2.cpp ├── dx7-awn.js ├── dx7-awp.js ├── dx7-lib.js └── wamsdk ├── processor.cpp └── processor.h /.gitignore: -------------------------------------------------------------------------------- 1 | dist/dx7/presets/*.syx 2 | build/dx7* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Jari Kleimola & others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webdx7 (AudioWorklet/WASM edition) 2 | virtual Yamaha DX7 synth in a browser. 3 | 4 | [demo](https://webaudiomodules.org/wamsynths/dx7) 5 | 6 | other WAM demos at [webaudiomodules.org/wamsynths](https://webaudiomodules.org/wamsynths/) 7 | 8 | Please note that low latency AudioWorklets require [Chrome Canary 64](https://www.google.com/chrome/browser/canary.html) (or later) and setting a flag as explained [here](https://googlechromelabs.github.io/web-audio-samples/audio-worklet/). Other stable browsers are enabled with this [polyfill](https://github.com/jariseon/audioworklet-polyfill). 9 | 10 | ## info 11 | This repo contains a work-in-progress implementation of webdx7 in WebAssembly. The binary runs in AudioWorklet. webdx7 is built on top of [Web Audio Modules (WAMs) API](https://webaudiomodules.org), which is currently extended to support AudioWorklets and WebAssembly. 12 | 13 | The code here includes pure hacks to work around limitations in current AudioWorklet browser implementation, and should definitely not be considered best practice :) WAMs API will be updated as AudioWorklets mature. 14 | 15 | ## prerequisites 16 | * WASM [toolchain](http://webassembly.org/getting-started/developers-guide/) 17 | * [node.js](https://nodejs.org/en/download/) 18 | 19 | ## building, distribution and demo usage 20 | 21 | ### #1 wasm compilation and encoding 22 | ``` 23 | cd build 24 | export PATH=$PATH:/to/emsdk/where/emmake/resides 25 | emmake make 26 | ``` 27 | step #1 creates three files: 28 | - the loader: **dx7.js**. 29 | - the raw WASM binary: **dx7.wasm** 30 | - a JavaScript embedding of the WASM binary: **dx7.wasm.js** 31 | 32 | ### #2 copy built files to `dist/dx7/wasm` 33 | Next copy the built files to `dist/dx7/wasm` by running: 34 | ``` 35 | make dist 36 | ``` 37 | 38 | ### #3 try it out 39 | Now that you've built webdx7 and put the files in place, copy some DX7 sysex files into `dist/dx7/presets`. See readme there for instructions. 40 | 41 | Finally open `dist/dx7.html` in a WASM-enabled browser and enjoy cool authentic FM sounds straight in browser. Works with MIDI and embedded virtual keyboard. 42 | 43 | ## licenses 44 | - webdx7 code is MIT licensed 45 | - MSFA is Apache 2.0 licensed 46 | - tuning-library is [permissively licensed](/src/c/tuning-library/LICENSE.md) 47 | See [here](https://github.com/webaudiomodules/webdx7/issues/7#issuecomment-809681630) 48 | -------------------------------------------------------------------------------- /build/Makefile: -------------------------------------------------------------------------------- 1 | # Web Audio Modules 2 | # wasm makefile for msfa DX7 3 | 4 | TARGET = ./dx7.js 5 | API = ../src/wamsdk 6 | MSFA = ../src/c/msfa 7 | 8 | SRC = ../src/c/dx7.cc $(API)/processor.cpp \ 9 | $(MSFA)/synth_unit.cc $(MSFA)/ringbuffer.cc $(MSFA)/patch.cc \ 10 | $(MSFA)/lfo.cc $(MSFA)/dx7note.cc $(MSFA)/freqlut.cc $(MSFA)/sin.cc $(MSFA)/exp2.cc \ 11 | $(MSFA)/fm_core.cc $(MSFA)/pitchenv.cc $(MSFA)/env.cc $(MSFA)/fm_op_kernel.cc $(MSFA)/tuning.cc 12 | 13 | CFLAGS = -I$(API) -I$(MSFA) -Wno-logical-op-parentheses 14 | LDFLAGS = -O2 15 | JSFLAGS = -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s BINARYEN_ASYNC_COMPILATION=0 -s EXPORT_NAME="'AudioWorkletGlobalScope_WAM_DX7'" -s "EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap', 'setValue']" -s "EXPORTED_FUNCTIONS=['_malloc', '_free']" -s EMULATE_FUNCTION_POINTER_CASTS=1 16 | 17 | # DEBUGFLAGS = -g4 --source-map-base "http://localhost:8080/path/to/location/with/wasm/sourcemap/" 18 | 19 | $(TARGET): $(OBJECTS) 20 | $(CC) $(CFLAGS) $(LDFLAGS) $(JSFLAGS) -o $@ $(SRC) 21 | # emmake complains about `.` characters in the `EXPORT_NAME`, so we add them manually... 22 | sed -i 's/AudioWorkletGlobalScope_WAM_DX7/AudioWorkletGlobalScope\.WAM\.DX7/g' $(TARGET) 23 | node encode-wasm.js dx7.wasm 24 | 25 | dist: 26 | cp dx7.js ../dist/dx7/wasm/ 27 | cp dx7.wasm.js ../dist/dx7/wasm/ 28 | -------------------------------------------------------------------------------- /build/encode-wasm.js: -------------------------------------------------------------------------------- 1 | if (process.argv.length != 3) { 2 | console.log("usage: node encode-wasm.js mymodule.wasm"); 3 | return; 4 | } 5 | 6 | let wasmName = process.argv[2]; 7 | let name = wasmName.substr(0, wasmName.length - 5).toUpperCase(); 8 | 9 | // thanks to Steven Yi / Csound 10 | // 11 | fs = require('fs'); 12 | let wasmData = fs.readFileSync(wasmName); 13 | let wasmStr = wasmData.join(","); 14 | let wasmOut = "AudioWorkletGlobalScope.WAM = AudioWorkletGlobalScope.WAM || {}\n"; 15 | wasmOut += "AudioWorkletGlobalScope.WAM." + name + " = { ENVIRONMENT: 'WEB' }\n"; 16 | wasmOut += "AudioWorkletGlobalScope.WAM." + name + ".wasmBinary = new Uint8Array([" + wasmStr + "]);"; 17 | fs.writeFileSync(wasmName + ".js", wasmOut); 18 | -------------------------------------------------------------------------------- /dist/audioworker.js: -------------------------------------------------------------------------------- 1 | // AudioWorklet polyfill 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | // 4 | var AWGS = { processors:[] } 5 | 6 | // -------------------------------------------------------------------------- 7 | // 8 | // 9 | AWGS.AudioWorkletGlobalScope = function () { 10 | var ctors = {}; // node name to processor definition map 11 | 12 | function registerOnWorker(name, ctor) { 13 | if (!ctors[name]) { 14 | ctors[name] = ctor; 15 | postMessage({ type:"register", name:name, descriptor:ctor.parameterDescriptors }); 16 | } 17 | else { 18 | postMessage({ type:"state", node:nodeID, state:"error" }); 19 | throw new Error("AlreadyRegistered"); 20 | } 21 | }; 22 | 23 | function constructOnWorker (name, port, options) { 24 | if (ctors[name]) { 25 | options = options || {} 26 | options._port = port; 27 | var processor = new ctors[name](options); 28 | if (!(processor instanceof AudioWorkletProcessor)) { 29 | postMessage({ type:"state", node:nodeID, state:"error" }); 30 | throw new Error("InvalidStateError"); 31 | } 32 | return processor; 33 | } 34 | else { 35 | postMessage({ type:"state", node:nodeID, state:"error" }); 36 | throw new Error("NotSupportedException"); 37 | } 38 | } 39 | 40 | class AudioWorkletProcessorPolyfill { 41 | constructor (options) { this.port = options._port; } 42 | process (inputs, outputs, params) {} 43 | } 44 | 45 | return { 46 | 'AudioWorkletProcessor': AudioWorkletProcessorPolyfill, 47 | 'registerProcessor': registerOnWorker, 48 | '_createProcessor': constructOnWorker 49 | } 50 | } 51 | 52 | 53 | AudioWorkletGlobalScope = AWGS.AudioWorkletGlobalScope(); 54 | AudioWorkletProcessor = AudioWorkletGlobalScope.AudioWorkletProcessor; 55 | registerProcessor = AudioWorkletGlobalScope.registerProcessor; 56 | sampleRate = 44100; 57 | hasSAB = true; 58 | 59 | onmessage = function (e) { 60 | var msg = e.data; 61 | switch (msg.type) { 62 | 63 | case "init": 64 | sampleRate = AudioWorkletGlobalScope.sampleRate = msg.sampleRate; 65 | break; 66 | 67 | case "import": 68 | importScripts(msg.url); 69 | postMessage({ type:"load", url:msg.url }); 70 | break; 71 | 72 | case "createProcessor": 73 | // -- slice io to match with SPN bufferlength 74 | var a = msg.args; 75 | var slices = []; 76 | var buflen = 128; 77 | var numSlices = (a.options.samplesPerBuffer/buflen)|0; 78 | 79 | hasSAB = (a.bufferCount === undefined); 80 | if (hasSAB) { 81 | for (var i=0; i 0) { 125 | if (numPorts > 1) numPorts = 1; // SPN restriction 126 | var bus = new Array(numPorts); 127 | for (var i=0; i 2 | 3 | WAM webDX7 4 | 5 | 6 | 7 | 8 | 9 | 31 | 32 | 33 | 34 |
35 |

Yamaha DX7 web audio module

36 | powered by AudioWorklet and WebAssembly 37 |

synth engine by Raph Levien (ported from msfa) 38 |
wasm/audioworklet implementation 2017-18 by jari@webaudiomodules.org 39 |

40 |
41 |
42 |

AudioWorklet is unsupported in this browser version, using experimental AudioWorklet polyfill as fallback 43 |
more info here

44 |
45 |
46 |
47 |
48 |
webDX7 //
49 |
50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | 60 | 61 |
62 |
63 |
64 |
65 |
66 | 69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /dist/dx7/dx7-awn.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | 4 | class DX7 extends WAMController 5 | { 6 | constructor (actx, options) { 7 | options = options || {}; 8 | options.numberOfInputs = 0; 9 | options.numberOfOutputs = 1; 10 | options.outputChannelCount = [1]; 11 | 12 | super(actx, "DX7", options); 13 | } 14 | 15 | static importScripts (actx) { 16 | var origin = location.origin + "/"; 17 | return new Promise( (resolve) => { 18 | actx.audioWorklet.addModule(origin + "dx7/wasm/dx7.wasm.js").then(() => { 19 | actx.audioWorklet.addModule(origin + "dx7/wasm/dx7.js").then(() => { 20 | actx.audioWorklet.addModule(origin + "../wamsdk/wam-processor.js").then(() => { 21 | actx.audioWorklet.addModule(origin + "dx7/dx7-awp.js").then(() => { 22 | resolve(); 23 | }) }) }) }); 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/dx7/dx7-awp.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | 4 | class DX7AWP extends AudioWorkletGlobalScope.WAMProcessor 5 | { 6 | constructor(options) { 7 | options = options || {} 8 | options.mod = AudioWorkletGlobalScope.WAM.DX7; 9 | super(options); 10 | this.numOutChannels = [1]; 11 | } 12 | } 13 | 14 | registerProcessor("DX7", DX7AWP); 15 | -------------------------------------------------------------------------------- /dist/dx7/dx7-lib.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // bank and patch handling 3 | // Jari Kleimola 2017 (jari@webaudiomodules.org) 4 | 5 | var DX7Library = function () 6 | { 7 | this.banks = banklist; 8 | this.patches = []; 9 | this.bank = []; 10 | var self = this; 11 | 12 | this.load = filename => { 13 | var url = "dx7/presets/" + filename; 14 | return new Promise( (resolve,reject) => { 15 | fetch(url).then(resp => { 16 | resp.arrayBuffer().then(data => { 17 | 18 | // -- packed bank with sysex frame (32 patches) 19 | if (data.byteLength != 4104) reject(); 20 | self.patches = []; 21 | self.bank = []; 22 | data = new Uint8Array(data); 23 | data = data.subarray(6,4102); 24 | for (var i=0; i<32; i++) { 25 | var offset = i*128; 26 | var voice = data.subarray(offset,offset+128); 27 | var name = extractName(voice); 28 | self.patches.push(name); 29 | self.bank.push(voice); 30 | } 31 | resolve(self.patches); 32 | }) }) }); 33 | } 34 | 35 | function extractName (data,offset) { 36 | var offset = offset || 118; // 118 for packed, 145 for unpacked 37 | var name = ""; 38 | for (var n = 0; n < 10; n++) { 39 | var c = data[n + offset]; 40 | switch (c) { 41 | case 92: c = 'Y'; break; // yen 42 | case 126: c = '>'; break; // >> 43 | case 127: c = '<'; break; // << 44 | default: if (c < 32 || c > 127) c = 32; break; 45 | } 46 | name += String.fromCharCode(c); 47 | } 48 | return name; 49 | } 50 | } 51 | 52 | var banklist = [ 53 | "rom1A.syx", 54 | "steinber.syx", 55 | "SynprezFM_03.syx", 56 | "weird1.syx", 57 | "solange2.syx", 58 | "analog1.syx", 59 | "Dexed_01.syx" 60 | ]; 61 | -------------------------------------------------------------------------------- /dist/dx7/gui/dx7.css: -------------------------------------------------------------------------------- 1 | body { font-family:sans-serif; font-weight:100; font-size:14px; user-select:none; cursor:default; padding:5px 0 0 10px; } 2 | 3 | header { margin-bottom:20px; } 4 | h1 { font-weight:100; margin:0; font-size:20px; } 5 | header span { font-size:12px; } 6 | 7 | #unsupported { margin-bottom:30px; display:none; } 8 | #unsupported a { text-decoration:underline; } 9 | #content { width:800px; background:#000; padding:5px; } 10 | 11 | #topbar { display:flex; background:#222; color:#eee; height:40px; line-height:40px; padding-left:10px; border-bottom:5px solid darkgreen; } 12 | .right { display:flex; margin-left:auto; } 13 | .control { margin:0 10px; } 14 | label { color:#bbb; font-size:12px; } 15 | select { background:#222; color:#eee; height:20px; margin-top:10px; cursor:pointer; } 16 | li { cursor:pointer; } 17 | 18 | footer { width:810px; display:flex; justify-content:flex-end; font-size:12px; color:#444; margin-top:5px;} 19 | a, a:visited { color:#000; text-decoration:none; } 20 | a:hover { text-decoration:underline; color:darkgreen; } 21 | 22 | select.moz { 23 | padding-right: 25px; 24 | background-image: url("data:image/svg+xml,\ 25 | \ 27 | \ 28 | "); 29 | background-repeat: no-repeat; 30 | background-position: calc(100% - 7px) 50%; 31 | -moz-appearance: none; 32 | appearance: none; 33 | 34 | border:1px solid #fff; 35 | border-radius:6px; 36 | } -------------------------------------------------------------------------------- /dist/dx7/gui/gui.js: -------------------------------------------------------------------------------- 1 | function initGUI (lib) { 2 | 3 | // -- populate comboboxes 4 | var inited = false; 5 | var banks = document.getElementById("banks"); 6 | var patches = document.getElementById("patches"); 7 | lib.banks.forEach( name => { banks.appendChild(new Option(name)); }); 8 | 9 | // -- change bank 10 | banks.onchange = function (e) { 11 | lib.load(e.target.value).then(bank => { 12 | patches.innerHTML = ""; 13 | bank.forEach( name => { patches.appendChild(new Option(name)); }); 14 | if (!inited) { inited = true; patches.selectedIndex = 10; } 15 | else patches.onchange({ target:patches }); 16 | }) 17 | } 18 | banks.onchange({ target:banks }); 19 | 20 | // -- change patch 21 | patches.onchange = function (e) { 22 | var patch = lib.bank[e.target.selectedIndex]; 23 | dx7.setPatch(patch); 24 | } 25 | 26 | // -- midi keyboard 27 | var velo = 80; 28 | var midikeys = new QwertyHancock({ 29 | container: document.getElementById("keys"), width: this.width, height: 60, margin:0, 30 | octaves: 6, startNote: 'C2', oct:4, 31 | whiteNotesColour: 'white', blackNotesColour: 'black', activeColour:'orange' 32 | }); 33 | midikeys.keyDown = (note, name) => dx7.onMidi([0x90, note, velo]); 34 | midikeys.keyUp = (note, name) => dx7.onMidi([0x80, note, velo]); 35 | } 36 | 37 | function initMidi () { 38 | let combo = document.querySelector("#midiIn"); 39 | navigator.requestMIDIAccess().then((midiIF) => { 40 | for (let input of midiIF.inputs.values()) { 41 | let option = new Option(input.name); 42 | option.port = input; 43 | combo.appendChild(option); 44 | } 45 | combo.onchange = e => { 46 | dx7.midiIn = e.target.options[e.target.selectedIndex].port; 47 | } 48 | if (combo.options.length > 0) 49 | combo.onchange({ target:combo }); 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /dist/dx7/presets/README.md: -------------------------------------------------------------------------------- 1 | webDX7 can read native DX7 sysex dumps (currently only standard 4104 byte sysex files containing a single 32 voice bank). Put your preset banks here and edit **dist/dx7-lib.js** `banklist` object accordingly. -------------------------------------------------------------------------------- /dist/wamsdk/wam-controller.js: -------------------------------------------------------------------------------- 1 | // WAM AudioWorkletController 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | // work in progress 4 | 5 | class WAMController extends AudioWorkletNode 6 | { 7 | constructor(context, processorName, options) { 8 | super(context, processorName, options); 9 | } 10 | 11 | setParam(key,value) { 12 | this.port.postMessage({ type:"param", key:key, value:value }); 13 | } 14 | 15 | setPatch(patch) { 16 | this.port.postMessage({ type:"patch", data:patch }); 17 | } 18 | 19 | setSysex(sysex) { 20 | this.port.postMessage({ type:"sysex", data:sysex }); 21 | } 22 | 23 | onMidi(msg) { 24 | this.port.postMessage({ type:"midi", data:msg }); 25 | } 26 | 27 | set midiIn (port) { 28 | if (this._midiInPort) { 29 | this._midiInPort.close(); 30 | this._midiInPort.onmidimessage = null; 31 | } 32 | this._midiInPort = port; 33 | this._midiInPort.onmidimessage = function (msg) { 34 | this.port.postMessage({ type:"midi", data:msg.data }); 35 | }.bind(this); 36 | } 37 | 38 | sendMessage(verb, prop, data) { 39 | this.port.postMessage({ type:"msg", verb:verb, prop:prop, data:data }); 40 | } 41 | 42 | get gui () { return null; } 43 | } 44 | -------------------------------------------------------------------------------- /dist/wamsdk/wam-processor.js: -------------------------------------------------------------------------------- 1 | // WAM AudioWorkletProcessor 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | // 4 | // work in progress 5 | // helper class for WASM data marshalling and C function call binding 6 | // also provides midi, patch data, parameter and arbitrary message support 7 | 8 | if (!AudioWorkletGlobalScope.WAMProcessor) { 9 | 10 | class WAMProcessor extends AudioWorkletProcessor 11 | { 12 | static get parameterDescriptors() { 13 | return []; 14 | } 15 | 16 | constructor(options) { 17 | options = options || {} 18 | if (options.numberOfInputs === undefined) options.numberOfInputs = 0; 19 | if (options.numberOfOutputs === undefined) options.numberOfOutputs = 1; 20 | if (options.inputChannelCount === undefined) options.inputChannelCount = []; 21 | if (options.outputChannelCount === undefined) options.outputChannelCount = [1]; 22 | if (options.inputChannelCount.length != options.numberOfInputs) throw new Error("InvalidArgumentException"); 23 | if (options.outputChannelCount.length != options.numberOfOutputs) throw new Error("InvalidArgumentException"); 24 | 25 | super(options); 26 | this.bufsize = 128; 27 | this.sr = AudioWorkletGlobalScope.sampleRate || sampleRate; 28 | this.audiobufs = [[],[]]; 29 | 30 | var WAM = options.mod; 31 | this.WAM = WAM; 32 | 33 | // -- construction C function wrappers 34 | var wam_ctor = WAM.cwrap("createModule", 'number', []); 35 | var wam_init = WAM.cwrap("wam_init", null, ['number','number','number','string']); 36 | 37 | // -- runtime C function wrappers 38 | this.wam_terminate = WAM.cwrap("wam_terminate", null, ['number']); 39 | this.wam_onmidi = WAM.cwrap("wam_onmidi", null, ['number','number','number','number']); 40 | this.wam_onpatch = WAM.cwrap("wam_onpatch", null, ['number','number','number']); 41 | this.wam_onprocess = WAM.cwrap("wam_onprocess", 'number', ['number','number','number']); 42 | this.wam_onparam = WAM.cwrap("wam_onparam", null, ['number','number','number']); 43 | this.wam_onsysex = WAM.cwrap("wam_onsysex", null, ['number','number','number']); 44 | this.wam_onmessageN = WAM.cwrap("wam_onmessageN", null, ['number','string','string','number']); 45 | this.wam_onmessageS = WAM.cwrap("wam_onmessageS", null, ['number','string','string','string']); 46 | 47 | // -- supress warnings for older WAMs 48 | if (WAM["_wam_onmessageA"]) 49 | this.wam_onmessageA = WAM.cwrap("wam_onmessageA", null, ['number','string','string','number','number']); 50 | 51 | this.inst = wam_ctor(); 52 | var desc = wam_init(this.inst, this.bufsize, this.sr, ""); 53 | 54 | // -- audio io configuration 55 | this.numInputs = options.numberOfInputs; 56 | this.numOutputs = options.numberOfOutputs; 57 | this.numInChannels = options.inputChannelCount; 58 | this.numOutChannels = options.outputChannelCount; 59 | 60 | var ibufs = this.numInputs > 0 ? WAM._malloc(this.numInputs) : 0; 61 | var obufs = this.numOutputs > 0 ? WAM._malloc(this.numOutputs) : 0; 62 | this.audiobus = WAM._malloc(2*4); 63 | WAM.setValue(this.audiobus, ibufs, 'i32'); 64 | WAM.setValue(this.audiobus+4, obufs, 'i32'); 65 | 66 | for (var i=0; ionPatch((WAM::byte*)patch, size); 59 | } 60 | 61 | void DX7::onParam(uint32_t idparam, double value) 62 | { 63 | synth_unit_->onParam(idparam, (char)value); 64 | } 65 | 66 | void DX7::onProcess(WAM::AudioBus* audio, void* data) 67 | { 68 | // mono 16-bit signed ints 69 | synth_unit_->GetSamples(bufsize_, outbuf16_); 70 | 71 | static const float scaler = 0.00003051757813; 72 | float* outbuf = audio->outputs[0]; 73 | for (uint32_t n=0; n 25 | 26 | template 27 | class AlignedBuf { 28 | public: 29 | T *get() { 30 | return (T *)((((intptr_t)storage_) + alignment - 1) & -alignment); 31 | } 32 | private: 33 | unsigned char storage_[size * sizeof(T) + alignment]; 34 | }; 35 | 36 | #endif // __ALIGNED_BUF_H 37 | -------------------------------------------------------------------------------- /src/c/msfa/controllers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __CONTROLLERS_H 18 | #define __CONTROLLERS_H 19 | 20 | #include "synth.h" 21 | // #include "../Dexed.h" 22 | #include 23 | #include 24 | 25 | #ifdef _WIN32 26 | #define snprintf _snprintf 27 | #endif 28 | 29 | // State of MIDI controllers 30 | const int kControllerPitch = 128; 31 | const int kControllerPitchRangeUp = 129; 32 | const int kControllerPitchStep = 130; 33 | const int kControllerPitchRangeDn = 131; 34 | 35 | class FmCore; 36 | 37 | struct FmMod { 38 | int range; 39 | bool pitch; 40 | bool amp; 41 | bool eg; 42 | 43 | FmMod() { 44 | range = 0; 45 | pitch = false; 46 | amp = false; 47 | eg = false; 48 | } 49 | 50 | void parseConfig(const char *cfg) { 51 | int r = 0, p = 0, a = 0, e = 0; 52 | sscanf(cfg, "%d %d %d %d", &r, &p, &a, &e); 53 | 54 | range = r < 0 || r > 127 ? 0 : r; 55 | pitch = p != 0; 56 | amp = a != 0; 57 | eg = e != 0; 58 | } 59 | 60 | void setConfig(char *cfg) { 61 | snprintf(cfg, 13, "%d %d %d %d", range, pitch, amp, eg); 62 | } 63 | }; 64 | 65 | class Controllers { 66 | void applyMod(int cc, FmMod &mod) { 67 | float range = 0.01 * mod.range; 68 | int total = cc * range; 69 | if ( mod.amp ) 70 | amp_mod = max(amp_mod, total); 71 | 72 | if ( mod.pitch ) 73 | pitch_mod = max(pitch_mod, total); 74 | 75 | if ( mod.eg ) 76 | eg_mod = max(eg_mod, total); 77 | } 78 | 79 | public: 80 | int values_[132]; 81 | 82 | char opSwitch[7]; 83 | 84 | int amp_mod; 85 | int pitch_mod; 86 | int eg_mod; 87 | 88 | int aftertouch_cc; 89 | int breath_cc; 90 | int foot_cc; 91 | int modwheel_cc; 92 | 93 | int masterTune; 94 | 95 | bool transpose12AsScale = true; 96 | 97 | // MPE configuration. FIXME - make this switchable 98 | bool mpeEnabled = true; 99 | int mpePitchBendRange = 24; 100 | 101 | FmMod wheel; 102 | FmMod foot; 103 | FmMod breath; 104 | FmMod at; 105 | 106 | Controllers() { 107 | amp_mod = 0; 108 | pitch_mod = 0; 109 | eg_mod = 0; 110 | strcpy(opSwitch, "111111"); 111 | } 112 | 113 | void refresh() { 114 | amp_mod = 0; 115 | pitch_mod = 0; 116 | eg_mod = 0; 117 | 118 | applyMod(modwheel_cc, wheel); 119 | applyMod(breath_cc, breath); 120 | applyMod(foot_cc, foot); 121 | applyMod(aftertouch_cc, at); 122 | 123 | if ( ! ((wheel.eg || foot.eg) || (breath.eg || at.eg)) ) 124 | eg_mod = 127; 125 | 126 | } 127 | 128 | FmCore *core; 129 | }; 130 | 131 | #endif // __CONTROLLERS_H 132 | 133 | -------------------------------------------------------------------------------- /src/c/msfa/dx7note.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 Pascal Gauthier. 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "synth.h" 21 | #include "freqlut.h" 22 | #include "exp2.h" 23 | #include "controllers.h" 24 | #include "dx7note.h" 25 | #include 26 | #include 27 | 28 | const int FEEDBACK_BITDEPTH = 8; 29 | 30 | const int32_t coarsemul[] = { 31 | -16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, 32 | 50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, 33 | 65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858, 34 | 74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032, 35 | 81503396, 82323963, 83117622 36 | }; 37 | 38 | int32_t Dx7Note::osc_freq(int midinote, int mode, int coarse, int fine, int detune) { 39 | // TODO: pitch randomization 40 | int32_t logfreq; 41 | if (mode == 0) { 42 | logfreq = tuning_state_->midinote_to_logfreq(midinote); 43 | 44 | // could use more precision, closer enough for now. those numbers comes from my DX7 45 | double detuneRatio = 0.0209 * exp(-0.396 * (((float)logfreq)/(1<<24))) / 7; 46 | logfreq += detuneRatio * logfreq * (detune - 7); 47 | 48 | logfreq += coarsemul[coarse & 31]; 49 | if (fine) { 50 | // (1 << 24) / log(2) 51 | logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); 52 | } 53 | 54 | // // This was measured at 7.213Hz per count at 9600Hz, but the exact 55 | // // value is somewhat dependent on midinote. Close enough for now. 56 | // //logfreq += 12606 * (detune -7); 57 | } else { 58 | // ((1 << 24) * log(10) / log(2) * .01) << 3 59 | logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; 60 | logfreq += detune > 7 ? 13457 * (detune - 7) : 0; 61 | } 62 | return logfreq; 63 | } 64 | 65 | const uint8_t velocity_data[64] = { 66 | 0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163, 67 | 166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202, 68 | 205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232, 69 | 233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251, 70 | 252, 253, 254 71 | }; 72 | 73 | // See "velocity" section of notes. Returns velocity delta in microsteps. 74 | int ScaleVelocity(int velocity, int sensitivity) { 75 | int clamped_vel = max(0, min(127, velocity)); 76 | int vel_value = velocity_data[clamped_vel >> 1] - 239; 77 | int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4; 78 | return scaled_vel; 79 | } 80 | 81 | int ScaleRate(int midinote, int sensitivity) { 82 | int x = min(31, max(0, midinote / 3 - 7)); 83 | int qratedelta = (sensitivity * x) >> 3; 84 | #ifdef SUPER_PRECISE 85 | int rem = x & 7; 86 | if (sensitivity == 3 && rem == 3) { 87 | qratedelta -= 1; 88 | } else if (sensitivity == 7 && rem > 0 && rem < 4) { 89 | qratedelta += 1; 90 | } 91 | #endif 92 | return qratedelta; 93 | } 94 | 95 | const uint8_t exp_scale_data[] = { 96 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66, 97 | 80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250 98 | }; 99 | 100 | int ScaleCurve(int group, int depth, int curve) { 101 | int scale; 102 | if (curve == 0 || curve == 3) { 103 | // linear 104 | scale = (group * depth * 329) >> 12; 105 | } else { 106 | // exponential 107 | int n_scale_data = sizeof(exp_scale_data); 108 | int raw_exp = exp_scale_data[min(group, n_scale_data - 1)]; 109 | scale = (raw_exp * depth * 329) >> 15; 110 | } 111 | if (curve < 2) { 112 | scale = -scale; 113 | } 114 | return scale; 115 | } 116 | 117 | int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth, 118 | int left_curve, int right_curve) { 119 | int offset = midinote - break_pt - 17; 120 | if (offset >= 0) { 121 | return ScaleCurve((offset+1) / 3, right_depth, right_curve); 122 | } else { 123 | return ScaleCurve(-(offset-1) / 3, left_depth, left_curve); 124 | } 125 | } 126 | 127 | static const uint8_t pitchmodsenstab[] = { 128 | 0, 10, 20, 33, 55, 92, 153, 255 129 | }; 130 | 131 | // 0, 66, 109, 255 132 | static const uint32_t ampmodsenstab[] = { 133 | 0, 4342338, 7171437, 16777216 134 | }; 135 | 136 | Dx7Note::Dx7Note(std::shared_ptr ts) : tuning_state_(ts) { 137 | for(int op=0;op<6;op++) { 138 | params_[op].phase = 0; 139 | params_[op].gain_out = 0; 140 | } 141 | } 142 | 143 | void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity) { 144 | int rates[4]; 145 | int levels[4]; 146 | playingMidiNote = midinote; 147 | for (int op = 0; op < 6; op++) { 148 | int off = op * 21; 149 | for (int i = 0; i < 4; i++) { 150 | rates[i] = patch[off + i]; 151 | levels[i] = patch[off + 4 + i]; 152 | } 153 | int outlevel = patch[off + 16]; 154 | outlevel = Env::scaleoutlevel(outlevel); 155 | int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], 156 | patch[off + 10], patch[off + 11], patch[off + 12]); 157 | outlevel += level_scaling; 158 | outlevel = min(127, outlevel); 159 | outlevel = outlevel << 5; 160 | outlevel += ScaleVelocity(velocity, patch[off + 15]); 161 | outlevel = max(0, outlevel); 162 | int rate_scaling = ScaleRate(midinote, patch[off + 13]); 163 | env_[op].init(rates, levels, outlevel, rate_scaling); 164 | 165 | int mode = patch[off + 17]; 166 | int coarse = patch[off + 18]; 167 | int fine = patch[off + 19]; 168 | int detune = patch[off + 20]; 169 | int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); 170 | opMode[op] = mode; 171 | basepitch_[op] = freq; 172 | ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; 173 | } 174 | for (int i = 0; i < 4; i++) { 175 | rates[i] = patch[126 + i]; 176 | levels[i] = patch[130 + i]; 177 | } 178 | pitchenv_.set(rates, levels); 179 | algorithm_ = patch[134]; 180 | int feedback = patch[135]; 181 | fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; 182 | pitchmoddepth_ = (patch[139] * 165) >> 6; 183 | pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; 184 | ampmoddepth_ = (patch[140] * 165) >> 6; 185 | 186 | // MPE default valeus 187 | mpePitchBend = 8192; 188 | mpeTimbre = 0; 189 | mpePressure = 0; 190 | } 191 | 192 | void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls) { 193 | // ==== PITCH ==== 194 | uint32_t pmd = pitchmoddepth_ * lfo_delay; // Q32 195 | int32_t senslfo = pitchmodsens_ * (lfo_val - (1 << 23)); 196 | int32_t pmod_1 = (((int64_t) pmd) * (int64_t) senslfo) >> 39; 197 | pmod_1 = abs(pmod_1); 198 | int32_t pmod_2 = (int32_t)(((int64_t)ctrls->pitch_mod * (int64_t)senslfo) >> 14); 199 | pmod_2 = abs(pmod_2); 200 | int32_t pitch_mod = max(pmod_1, pmod_2); 201 | pitch_mod = pitchenv_.getsample() + (pitch_mod * (senslfo < 0 ? -1 : 1)); 202 | 203 | // ---- PITCH BEND ---- 204 | int pitchbend = ctrls->values_[kControllerPitch]; 205 | int32_t pb = (pitchbend - 0x2000); 206 | if (pb != 0) { 207 | if (ctrls->values_[kControllerPitchStep] == 0) { 208 | if( pb >= 0 ) 209 | pb = ((float) (pb << 11)) * ((float) ctrls->values_[kControllerPitchRangeUp]) / 12.0; 210 | else 211 | pb = ((float) (pb << 11)) * ((float) ctrls->values_[kControllerPitchRangeDn]) / 12.0; 212 | } else { 213 | int stp = 12 / ctrls->values_[kControllerPitchStep]; 214 | pb = pb * stp / 8191; 215 | pb = (pb * (8191 / stp)) << 11; 216 | } 217 | } 218 | 219 | if( ctrls->mpeEnabled ) 220 | { 221 | int d = ((float)( (mpePitchBend-0x2000) << 11 )) * ctrls->mpePitchBendRange / 12.0; 222 | // std::cout << mpePitchBend << " " << 0x2000 << " " << d << std::endl; 223 | pb += d; 224 | } 225 | 226 | if( ! tuning_state_->is_standard_tuning() && pb != 0 ) 227 | { 228 | // If we have a scale we want PB to be in scale space so we sort of need to 229 | // unwind the combinations above and re-interpolate 230 | 231 | float notesTuned = ( pb >> 11 ) * 12.0 / 8192; // How many steps you tuned 232 | int floorNote = std::floor(notesTuned); 233 | float frac = notesTuned - floorNote; 234 | float targetLog = tuning_state_->midinote_to_logfreq(playingMidiNote + floorNote) * ( 1.0 - frac ) + 235 | tuning_state_->midinote_to_logfreq(playingMidiNote + floorNote + 1) * frac; // the interpolated log freq 236 | float newpb = targetLog - tuning_state_->midinote_to_logfreq(playingMidiNote); // and the resulting bend 237 | pb = newpb; 238 | } 239 | 240 | int32_t pitch_base = pb + ctrls->masterTune; 241 | pitch_mod += pitch_base; 242 | 243 | // ==== AMP MOD ==== 244 | lfo_val = (1<<24) - lfo_val; 245 | uint32_t amod_1 = (uint32_t)(((int64_t) ampmoddepth_ * (int64_t) lfo_delay) >> 8); // Q24 :D 246 | amod_1 = (uint32_t)(((int64_t) amod_1 * (int64_t) lfo_val) >> 24); 247 | uint32_t amod_2 = (uint32_t)(((int64_t) ctrls->amp_mod * (int64_t) lfo_val) >> 7); // Q?? :| 248 | uint32_t amd_mod = max(amod_1, amod_2); 249 | 250 | // ==== EG AMP MOD ==== 251 | uint32_t amod_3 = (ctrls->eg_mod+1) << 17; 252 | amd_mod = max((1<<24) - amod_3, amd_mod); 253 | 254 | // ==== OP RENDER ==== 255 | for (int op = 0; op < 6; op++) { 256 | if ( ctrls->opSwitch[op] == '0' ) { 257 | env_[op].getsample(); // advance the envelop even if it is not playing 258 | params_[op].level_in = 0; 259 | } else { 260 | //int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); 261 | 262 | if ( opMode[op] ) 263 | params_[op].freq = Freqlut::lookup(basepitch_[op] + pitch_base); 264 | else 265 | params_[op].freq = Freqlut::lookup(basepitch_[op] + pitch_mod); 266 | 267 | int32_t level = env_[op].getsample(); 268 | if (ampmodsens_[op] != 0) { 269 | uint32_t sensamp = (uint32_t)(((uint64_t) amd_mod) * ((uint64_t) ampmodsens_[op]) >> 24); 270 | 271 | // TODO: mehhh.. this needs some real tuning. 272 | uint32_t pt = exp(((float)sensamp)/262144 * 0.07 + 12.2); 273 | uint32_t ldiff = (uint32_t)(((uint64_t)level) * (((uint64_t)pt<<4)) >> 28); 274 | level -= ldiff; 275 | } 276 | params_[op].level_in = level; 277 | } 278 | } 279 | ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_); 280 | } 281 | 282 | void Dx7Note::keyup() { 283 | for (int op = 0; op < 6; op++) { 284 | env_[op].keydown(false); 285 | } 286 | pitchenv_.keydown(false); 287 | } 288 | 289 | void Dx7Note::update(const uint8_t patch[156], int midinote, int velocity) { 290 | int rates[4]; 291 | int levels[4]; 292 | playingMidiNote = midinote; 293 | for (int op = 0; op < 6; op++) { 294 | int off = op * 21; 295 | int mode = patch[off + 17]; 296 | int coarse = patch[off + 18]; 297 | int fine = patch[off + 19]; 298 | int detune = patch[off + 20]; 299 | basepitch_[op] = osc_freq(midinote, mode, coarse, fine, detune); 300 | ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; 301 | opMode[op] = mode; 302 | 303 | for (int i = 0; i < 4; i++) { 304 | rates[i] = patch[off + i]; 305 | levels[i] = patch[off + 4 + i]; 306 | } 307 | int outlevel = patch[off + 16]; 308 | outlevel = Env::scaleoutlevel(outlevel); 309 | int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], 310 | patch[off + 10], patch[off + 11], patch[off + 12]); 311 | outlevel += level_scaling; 312 | outlevel = min(127, outlevel); 313 | outlevel = outlevel << 5; 314 | outlevel += ScaleVelocity(velocity, patch[off + 15]); 315 | outlevel = max(0, outlevel); 316 | int rate_scaling = ScaleRate(midinote, patch[off + 13]); 317 | env_[op].update(rates, levels, outlevel, rate_scaling); 318 | } 319 | algorithm_ = patch[134]; 320 | int feedback = patch[135]; 321 | fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; 322 | pitchmoddepth_ = (patch[139] * 165) >> 6; 323 | pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; 324 | ampmoddepth_ = (patch[140] * 165) >> 6; 325 | } 326 | 327 | void Dx7Note::peekVoiceStatus(VoiceStatus &status) { 328 | for(int i=0;i<6;i++) { 329 | status.amp[i] = Exp2::lookup(params_[i].level_in - (14 * (1 << 24))); 330 | env_[i].getPosition(&status.ampStep[i]); 331 | } 332 | pitchenv_.getPosition(&status.pitchStep); 333 | } 334 | 335 | /** 336 | * Used in monophonic mode to transfer voice state from different notes 337 | */ 338 | void Dx7Note::transferState(Dx7Note &src) { 339 | for (int i=0;i<6;i++) { 340 | env_[i].transfer(src.env_[i]); 341 | params_[i].gain_out = src.params_[i].gain_out; 342 | params_[i].phase = src.params_[i].phase; 343 | } 344 | } 345 | 346 | void Dx7Note::transferSignal(Dx7Note &src) { 347 | for (int i=0;i<6;i++) { 348 | params_[i].gain_out = src.params_[i].gain_out; 349 | params_[i].phase = src.params_[i].phase; 350 | } 351 | } 352 | 353 | void Dx7Note::oscSync() { 354 | for (int i=0;i<6;i++) { 355 | params_[i].gain_out = 0; 356 | params_[i].phase = 0; 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/c/msfa/dx7note.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2017 Pascal Gauthier. 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef SYNTH_DX7NOTE_H_ 19 | #define SYNTH_DX7NOTE_H_ 20 | 21 | // This is the logic to put together a note from the MIDI description 22 | // and run the low-level modules. 23 | 24 | // It will continue to evolve a bit, as note-stealing logic, scaling, 25 | // and real-time control of parameters live here. 26 | 27 | #include "env.h" 28 | #include "pitchenv.h" 29 | #include "fm_core.h" 30 | #include "tuning.h" 31 | #include 32 | 33 | struct VoiceStatus { 34 | uint32_t amp[6]; 35 | char ampStep[6]; 36 | char pitchStep; 37 | }; 38 | 39 | class Dx7Note { 40 | public: 41 | Dx7Note(std::shared_ptr ts); 42 | void init(const uint8_t patch[156], int midinote, int velocity); 43 | 44 | // Note: this _adds_ to the buffer. Interesting question whether it's 45 | // worth it... 46 | void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, 47 | const Controllers *ctrls); 48 | 49 | void keyup(); 50 | 51 | // TODO: some way of indicating end-of-note. Maybe should be a return 52 | // value from the compute method? (Having a count return from keyup 53 | // is also tempting, but if there's a dynamic parameter change after 54 | // keyup, that won't work. 55 | 56 | // PG:add the update 57 | void update(const uint8_t patch[156], int midinote, int velocity); 58 | void peekVoiceStatus(VoiceStatus &status); 59 | void transferState(Dx7Note& src); 60 | void transferSignal(Dx7Note &src); 61 | void oscSync(); 62 | 63 | int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune); 64 | 65 | std::shared_ptr tuning_state_; 66 | 67 | int mpePitchBend = 8192; 68 | int mpePressure = 0; 69 | int mpeTimbre = 0; 70 | 71 | private: 72 | FmCore core_; 73 | Env env_[6]; 74 | FmOpParams params_[6]; 75 | PitchEnv pitchenv_; 76 | int32_t basepitch_[6]; 77 | int32_t fb_buf_[2]; 78 | int32_t fb_shift_; 79 | int32_t ampmodsens_[6]; 80 | int32_t opMode[6]; 81 | 82 | uint8_t playingMidiNote; // We need this for scale aware pitch bend 83 | 84 | int ampmoddepth_; 85 | int algorithm_; 86 | int pitchmoddepth_; 87 | int pitchmodsens_; 88 | 89 | }; 90 | 91 | #endif // SYNTH_DX7NOTE_H_ 92 | -------------------------------------------------------------------------------- /src/c/msfa/env.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Pascal Gauthier. 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | #include "synth.h" 21 | #include "env.h" 22 | 23 | // #include "../Dexed.h" 24 | //using namespace std; 25 | 26 | uint32_t Env::sr_multiplier = (1<<24); 27 | 28 | const int levellut[] = { 29 | 0, 5, 9, 13, 17, 20, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 42, 43, 45, 46 30 | }; 31 | 32 | #ifdef ACCURATE_ENVELOPE 33 | const int statics[] = { 34 | 1764000, 1764000, 1411200, 1411200, 1190700, 1014300, 992250, 35 | 882000, 705600, 705600, 584325, 507150, 502740, 441000, 418950, 36 | 352800, 308700, 286650, 253575, 220500, 220500, 176400, 145530, 37 | 145530, 125685, 110250, 110250, 88200, 88200, 74970, 61740, 38 | 61740, 55125, 48510, 44100, 37485, 31311, 30870, 27562, 27562, 39 | 22050, 18522, 17640, 15435, 14112, 13230, 11025, 9261, 9261, 7717, 40 | 6615, 6615, 5512, 5512, 4410, 3969, 3969, 3439, 2866, 2690, 2249, 41 | 1984, 1896, 1808, 1411, 1367, 1234, 1146, 926, 837, 837, 705, 42 | 573, 573, 529, 441, 441 43 | // and so on, I stopped measuring after R=76 (needs to be double-checked anyway) 44 | }; 45 | #endif 46 | 47 | void Env::init_sr(double sampleRate) { 48 | sr_multiplier = (44100.0 / sampleRate) * (1<<24); 49 | } 50 | 51 | void Env::init(const int r[4], const int l[4], int ol, int rate_scaling) { 52 | for (int i = 0; i < 4; i++) { 53 | rates_[i] = r[i]; 54 | levels_[i] = l[i]; 55 | } 56 | outlevel_ = ol; 57 | rate_scaling_ = rate_scaling; 58 | level_ = 0; 59 | down_ = true; 60 | advance(0); 61 | } 62 | 63 | int32_t Env::getsample() { 64 | #ifdef ACCURATE_ENVELOPE 65 | if (staticcount_) { 66 | staticcount_ -= N; 67 | if (staticcount_ <= 0) { 68 | staticcount_ = 0; 69 | advance(ix_ + 1); 70 | } 71 | } 72 | #endif 73 | 74 | if (ix_ < 3 || ((ix_ < 4) && !down_)) { 75 | if (staticcount_) { 76 | ; 77 | } 78 | else if (rising_) { 79 | const int jumptarget = 1716; 80 | if (level_ < (jumptarget << 16)) { 81 | level_ = jumptarget << 16; 82 | } 83 | level_ += (((17 << 24) - level_) >> 24) * inc_; 84 | // TODO: should probably be more accurate when inc is large 85 | if (level_ >= targetlevel_) { 86 | level_ = targetlevel_; 87 | advance(ix_ + 1); 88 | } 89 | } 90 | else { // !rising 91 | level_ -= inc_; 92 | if (level_ <= targetlevel_) { 93 | level_ = targetlevel_; 94 | advance(ix_ + 1); 95 | } 96 | } 97 | } 98 | // TODO: this would be a good place to set level to 0 when under threshold 99 | return level_; 100 | } 101 | 102 | void Env::keydown(bool d) { 103 | if (down_ != d) { 104 | down_ = d; 105 | advance(d ? 0 : 3); 106 | } 107 | } 108 | 109 | int Env::scaleoutlevel(int outlevel) { 110 | return outlevel >= 20 ? 28 + outlevel : levellut[outlevel]; 111 | } 112 | 113 | void Env::advance(int newix) { 114 | ix_ = newix; 115 | if (ix_ < 4) { 116 | int newlevel = levels_[ix_]; 117 | int actuallevel = scaleoutlevel(newlevel) >> 1; 118 | actuallevel = (actuallevel << 6) + outlevel_ - 4256; 119 | actuallevel = actuallevel < 16 ? 16 : actuallevel; 120 | // level here is same as Java impl 121 | targetlevel_ = actuallevel << 16; 122 | rising_ = (targetlevel_ > level_); 123 | 124 | // rate 125 | int qrate = (rates_[ix_] * 41) >> 6; 126 | qrate += rate_scaling_; 127 | qrate = min(qrate, 63); 128 | 129 | #ifdef ACCURATE_ENVELOPE 130 | if (targetlevel_ == level_ || (ix_ == 0 && newlevel == 0)) { 131 | // approximate number of samples at 44.100 kHz to achieve the time 132 | // empirically gathered using 2 TF1s, could probably use some double-checking 133 | // and cleanup, but it's pretty close for now. 134 | int staticrate = rates_[ix_]; 135 | staticrate += rate_scaling_; // needs to be checked, as well, but seems correct 136 | staticrate = min(staticrate, 99); 137 | staticcount_ = staticrate < 77 ? statics[staticrate] : 20 * (99 - staticrate); 138 | if (staticrate < 77 && (ix_ == 0 && newlevel == 0)) { 139 | staticcount_ /= 20; // attack is scaled faster 140 | } 141 | staticcount_ = (int)(((int64_t)staticcount_ * (int64_t)sr_multiplier) >> 24); 142 | } 143 | else { 144 | staticcount_ = 0; 145 | } 146 | #endif 147 | inc_ = (4 + (qrate & 3)) << (2 + LG_N + (qrate >> 2)); 148 | // meh, this should be fixed elsewhere 149 | inc_ = (int)(((int64_t)inc_ * (int64_t)sr_multiplier) >> 24); 150 | } 151 | } 152 | 153 | void Env::update(const int r[4], const int l[4], int ol, int rate_scaling) { 154 | for (int i = 0; i < 4; i++) { 155 | rates_[i] = r[i]; 156 | levels_[i] = l[i]; 157 | } 158 | outlevel_ = ol; 159 | rate_scaling_ = rate_scaling; 160 | if ( down_ ) { 161 | // for now we simply reset ourselves at level 3 162 | int newlevel = levels_[2]; 163 | int actuallevel = scaleoutlevel(newlevel) >> 1; 164 | actuallevel = (actuallevel << 6) - 4256; 165 | actuallevel = actuallevel < 16 ? 16 : actuallevel; 166 | targetlevel_ = actuallevel << 16; 167 | advance(2); 168 | } 169 | } 170 | 171 | void Env::getPosition(char *step) { 172 | *step = ix_; 173 | } 174 | 175 | void Env::transfer(Env &src) { 176 | for(int i=0;i<4;i++) { 177 | rates_[i] = src.rates_[i]; 178 | levels_[i] = src.levels_[i]; 179 | } 180 | outlevel_ = src.outlevel_; 181 | rate_scaling_ = src.rate_scaling_; 182 | level_ = src.level_; 183 | targetlevel_ = src.targetlevel_; 184 | rising_= src.rising_; 185 | ix_ = src.ix_; 186 | down_ = src.down_; 187 | #ifdef ACCURATE_ENVELOPE 188 | staticcount_ = src.staticcount_; 189 | #endif 190 | inc_ = src.inc_; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/c/msfa/env.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Pascal Gauthier. 3 | * Copyright 2012 Google Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef __ENV_H 19 | #define __ENV_H 20 | 21 | #include "synth.h" 22 | 23 | // DX7 envelope generation 24 | 25 | #define ACCURATE_ENVELOPE 26 | 27 | class Env { 28 | public: 29 | 30 | // The rates and levels arrays are calibrated to match the Dx7 parameters 31 | // (ie, value 0..99). The outlevel parameter is calibrated in microsteps 32 | // (ie units of approx .023 dB), with 99 * 32 = nominal full scale. The 33 | // rate_scaling parameter is in qRate units (ie 0..63). 34 | void init(const int rates[4], const int levels[4], int outlevel, 35 | int rate_scaling); 36 | 37 | void update(const int rates[4], const int levels[4], int outlevel, 38 | int rate_scaling); 39 | // Result is in Q24/doubling log format. Also, result is subsampled 40 | // for every N samples. 41 | // A couple more things need to happen for this to be used as a gain 42 | // value. First, the # of outputs scaling needs to be applied. Also, 43 | // modulation. 44 | // Then, of course, log to linear. 45 | int32_t getsample(); 46 | 47 | void keydown(bool down); 48 | static int scaleoutlevel(int outlevel); 49 | void getPosition(char *step); 50 | 51 | static void init_sr(double sample_rate); 52 | void transfer(Env &src); 53 | 54 | private: 55 | 56 | // PG: This code is normalized to 44100, need to put a multiplier 57 | // if we are not using 44100. 58 | static uint32_t sr_multiplier; 59 | 60 | int rates_[4]; 61 | int levels_[4]; 62 | int outlevel_; 63 | int rate_scaling_; 64 | // Level is stored so that 2^24 is one doubling, ie 16 more bits than 65 | // the DX7 itself (fraction is stored in level rather than separate 66 | // counter) 67 | int32_t level_; 68 | int targetlevel_; 69 | bool rising_; 70 | int ix_; 71 | int inc_; 72 | #ifdef ACCURATE_ENVELOPE 73 | int staticcount_; 74 | #endif 75 | 76 | bool down_; 77 | 78 | void advance(int newix); 79 | }; 80 | 81 | #endif // __ENV_H 82 | 83 | -------------------------------------------------------------------------------- /src/c/msfa/exp2.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #define _USE_MATH_DEFINES 18 | #include 19 | 20 | #include "synth.h" 21 | #include "exp2.h" 22 | 23 | #include 24 | 25 | #ifdef _MSC_VER 26 | #define exp2(arg) pow(2.0, arg) 27 | #endif 28 | 29 | 30 | 31 | int32_t exp2tab[EXP2_N_SAMPLES << 1]; 32 | 33 | void Exp2::init() { 34 | double inc = exp2(1.0 / EXP2_N_SAMPLES); 35 | double y = 1 << 30; 36 | for (int i = 0; i < EXP2_N_SAMPLES; i++) { 37 | exp2tab[(i << 1) + 1] = (int32_t)floor(y + 0.5); 38 | y *= inc; 39 | } 40 | for (int i = 0; i < EXP2_N_SAMPLES - 1; i++) { 41 | exp2tab[i << 1] = exp2tab[(i << 1) + 3] - exp2tab[(i << 1) + 1]; 42 | } 43 | exp2tab[(EXP2_N_SAMPLES << 1) - 2] = (1U << 31) - exp2tab[(EXP2_N_SAMPLES << 1) - 1]; 44 | } 45 | 46 | int32_t tanhtab[TANH_N_SAMPLES << 1]; 47 | 48 | static double dtanh(double y) { 49 | return 1 - y * y; 50 | } 51 | 52 | void Tanh::init() { 53 | double step = 4.0 / TANH_N_SAMPLES; 54 | double y = 0; 55 | for (int i = 0; i < TANH_N_SAMPLES; i++) { 56 | tanhtab[(i << 1) + 1] = (1 << 24) * y + 0.5; 57 | //printf("%d\n", tanhtab[(i << 1) + 1]); 58 | // Use a basic 4th order Runge-Kutte to compute tanh from its 59 | // differential equation. 60 | double k1 = dtanh(y); 61 | double k2 = dtanh(y + 0.5 * step * k1); 62 | double k3 = dtanh(y + 0.5 * step * k2); 63 | double k4 = dtanh(y + step * k3); 64 | double dy = (step / 6) * (k1 + k4 + 2 * (k2 + k3)); 65 | y += dy; 66 | } 67 | for (int i = 0; i < TANH_N_SAMPLES - 1; i++) { 68 | tanhtab[i << 1] = tanhtab[(i << 1) + 3] - tanhtab[(i << 1) + 1]; 69 | } 70 | int32_t lasty = (1 << 24) * y + 0.5; 71 | tanhtab[(TANH_N_SAMPLES << 1) - 2] = lasty - tanhtab[(TANH_N_SAMPLES << 1) - 1]; 72 | } 73 | -------------------------------------------------------------------------------- /src/c/msfa/exp2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | class Exp2 { 18 | public: 19 | Exp2(); 20 | 21 | static void init(); 22 | 23 | // Q24 in, Q24 out 24 | static int32_t lookup(int32_t x); 25 | }; 26 | 27 | #define EXP2_LG_N_SAMPLES 10 28 | #define EXP2_N_SAMPLES (1 << EXP2_LG_N_SAMPLES) 29 | 30 | #define EXP2_INLINE 31 | 32 | extern int32_t exp2tab[EXP2_N_SAMPLES << 1]; 33 | 34 | #ifdef EXP2_INLINE 35 | inline 36 | int32_t Exp2::lookup(int32_t x) { 37 | const int SHIFT = 24 - EXP2_LG_N_SAMPLES; 38 | int lowbits = x & ((1 << SHIFT) - 1); 39 | int x_int = (x >> (SHIFT - 1)) & ((EXP2_N_SAMPLES - 1) << 1); 40 | int dy = exp2tab[x_int]; 41 | int y0 = exp2tab[x_int + 1]; 42 | 43 | int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 44 | return y >> (6 - (x >> 24)); 45 | } 46 | #endif 47 | 48 | class Tanh { 49 | public: 50 | static void init(); 51 | 52 | // Q24 in, Q24 out 53 | static int32_t lookup(int32_t x); 54 | }; 55 | 56 | #define TANH_LG_N_SAMPLES 10 57 | #define TANH_N_SAMPLES (1 << TANH_LG_N_SAMPLES) 58 | 59 | extern int32_t tanhtab[TANH_N_SAMPLES << 1]; 60 | 61 | inline 62 | int32_t Tanh::lookup(int32_t x) { 63 | int32_t signum = x >> 31; 64 | x ^= signum; 65 | if (x >= (4 << 24)) { 66 | if (x >= (17 << 23)) { 67 | return signum ^ (1 << 24); 68 | } 69 | int32_t sx = ((int64_t)-48408812 * (int64_t)x) >> 24; 70 | return signum ^ ((1 << 24) - 2 * Exp2::lookup(sx)); 71 | } else { 72 | const int SHIFT = 26 - TANH_LG_N_SAMPLES; 73 | int lowbits = x & ((1 << SHIFT) - 1); 74 | int x_int = (x >> (SHIFT - 1)) & ((TANH_N_SAMPLES - 1) << 1); 75 | int dy = tanhtab[x_int]; 76 | int y0 = tanhtab[x_int + 1]; 77 | int y = y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 78 | return y ^ signum; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/c/msfa/fm_core.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifdef VERBOSE 18 | #include 19 | #endif 20 | 21 | #include "synth.h" 22 | #include "exp2.h" 23 | #include "fm_op_kernel.h" 24 | #include "fm_core.h" 25 | 26 | 27 | //using namespace std; 28 | 29 | const FmAlgorithm FmCore::algorithms[32] = { 30 | { { 0xc1, 0x11, 0x11, 0x14, 0x01, 0x14 } }, // 1 31 | { { 0x01, 0x11, 0x11, 0x14, 0xc1, 0x14 } }, // 2 32 | { { 0xc1, 0x11, 0x14, 0x01, 0x11, 0x14 } }, // 3 33 | { { 0xc1, 0x11, 0x94, 0x01, 0x11, 0x14 } }, // 4 34 | { { 0xc1, 0x14, 0x01, 0x14, 0x01, 0x14 } }, // 5 35 | { { 0xc1, 0x94, 0x01, 0x14, 0x01, 0x14 } }, // 6 36 | { { 0xc1, 0x11, 0x05, 0x14, 0x01, 0x14 } }, // 7 37 | { { 0x01, 0x11, 0xc5, 0x14, 0x01, 0x14 } }, // 8 38 | { { 0x01, 0x11, 0x05, 0x14, 0xc1, 0x14 } }, // 9 39 | { { 0x01, 0x05, 0x14, 0xc1, 0x11, 0x14 } }, // 10 40 | { { 0xc1, 0x05, 0x14, 0x01, 0x11, 0x14 } }, // 11 41 | { { 0x01, 0x05, 0x05, 0x14, 0xc1, 0x14 } }, // 12 42 | { { 0xc1, 0x05, 0x05, 0x14, 0x01, 0x14 } }, // 13 43 | { { 0xc1, 0x05, 0x11, 0x14, 0x01, 0x14 } }, // 14 44 | { { 0x01, 0x05, 0x11, 0x14, 0xc1, 0x14 } }, // 15 45 | { { 0xc1, 0x11, 0x02, 0x25, 0x05, 0x14 } }, // 16 46 | { { 0x01, 0x11, 0x02, 0x25, 0xc5, 0x14 } }, // 17 47 | { { 0x01, 0x11, 0x11, 0xc5, 0x05, 0x14 } }, // 18 48 | { { 0xc1, 0x14, 0x14, 0x01, 0x11, 0x14 } }, // 19 49 | { { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x14 } }, // 20 50 | { { 0x01, 0x14, 0x14, 0xc1, 0x14, 0x14 } }, // 21 51 | { { 0xc1, 0x14, 0x14, 0x14, 0x01, 0x14 } }, // 22 52 | { { 0xc1, 0x14, 0x14, 0x01, 0x14, 0x04 } }, // 23 53 | { { 0xc1, 0x14, 0x14, 0x14, 0x04, 0x04 } }, // 24 54 | { { 0xc1, 0x14, 0x14, 0x04, 0x04, 0x04 } }, // 25 55 | { { 0xc1, 0x05, 0x14, 0x01, 0x14, 0x04 } }, // 26 56 | { { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x04 } }, // 27 57 | { { 0x04, 0xc1, 0x11, 0x14, 0x01, 0x14 } }, // 28 58 | { { 0xc1, 0x14, 0x01, 0x14, 0x04, 0x04 } }, // 29 59 | { { 0x04, 0xc1, 0x11, 0x14, 0x04, 0x04 } }, // 30 60 | { { 0xc1, 0x14, 0x04, 0x04, 0x04, 0x04 } }, // 31 61 | { { 0xc4, 0x04, 0x04, 0x04, 0x04, 0x04 } }, // 32 62 | }; 63 | 64 | int n_out(const FmAlgorithm &alg) { 65 | int count = 0; 66 | for (int i = 0; i < 6; i++) { 67 | if ((alg.ops[i] & 7) == OUT_BUS_ADD) count++; 68 | } 69 | return count; 70 | } 71 | 72 | void FmCore::dump() { 73 | #ifdef VERBOSE 74 | for (int i = 0; i < 32; i++) { 75 | cout << (i + 1) << ":"; 76 | const FmAlgorithm &alg = algorithms[i]; 77 | for (int j = 0; j < 6; j++) { 78 | int flags = alg.ops[j]; 79 | cout << " "; 80 | if (flags & FB_IN) cout << "["; 81 | cout << (flags & IN_BUS_ONE ? "1" : flags & IN_BUS_TWO ? "2" : "0") << "->"; 82 | cout << (flags & OUT_BUS_ONE ? "1" : flags & OUT_BUS_TWO ? "2" : "0"); 83 | if (flags & OUT_BUS_ADD) cout << "+"; 84 | //cout << alg.ops[j].in << "->" << alg.ops[j].out; 85 | if (flags & FB_OUT) cout << "]"; 86 | } 87 | cout << " " << n_out(alg); 88 | cout << endl; 89 | } 90 | #endif 91 | } 92 | 93 | void FmCore::render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int feedback_shift) { 94 | const int kLevelThresh = 1120; 95 | const FmAlgorithm alg = algorithms[algorithm]; 96 | bool has_contents[3] = { true, false, false }; 97 | for (int op = 0; op < 6; op++) { 98 | int flags = alg.ops[op]; 99 | bool add = (flags & OUT_BUS_ADD) != 0; 100 | FmOpParams ¶m = params[op]; 101 | int inbus = (flags >> 4) & 3; 102 | int outbus = flags & 3; 103 | int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1].get(); 104 | int32_t gain1 = param.gain_out; 105 | int32_t gain2 = Exp2::lookup(param.level_in - (14 * (1 << 24))); 106 | param.gain_out = gain2; 107 | 108 | if (gain1 >= kLevelThresh || gain2 >= kLevelThresh) { 109 | if (!has_contents[outbus]) { 110 | add = false; 111 | } 112 | if (inbus == 0 || !has_contents[inbus]) { 113 | // todo: more than one op in a feedback loop 114 | if ((flags & 0xc0) == 0xc0 && feedback_shift < 16) { 115 | // cout << op << " fb " << inbus << outbus << add << endl; 116 | FmOpKernel::compute_fb(outptr, param.phase, param.freq, 117 | gain1, gain2, 118 | fb_buf, feedback_shift, add); 119 | } else { 120 | // cout << op << " pure " << inbus << outbus << add << endl; 121 | FmOpKernel::compute_pure(outptr, param.phase, param.freq, 122 | gain1, gain2, add); 123 | } 124 | } else { 125 | // cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl; 126 | FmOpKernel::compute(outptr, buf_[inbus - 1].get(), 127 | param.phase, param.freq, gain1, gain2, add); 128 | } 129 | has_contents[outbus] = true; 130 | } else if (!add) { 131 | has_contents[outbus] = false; 132 | } 133 | param.phase += param.freq << LG_N; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/c/msfa/fm_core.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __FM_CORE_H 18 | #define __FM_CORE_H 19 | 20 | #include "aligned_buf.h" 21 | #include "fm_op_kernel.h" 22 | #include "synth.h" 23 | #include "controllers.h" 24 | 25 | 26 | class FmOperatorInfo { 27 | public: 28 | int in; 29 | int out; 30 | }; 31 | 32 | enum FmOperatorFlags { 33 | OUT_BUS_ONE = 1 << 0, 34 | OUT_BUS_TWO = 1 << 1, 35 | OUT_BUS_ADD = 1 << 2, 36 | IN_BUS_ONE = 1 << 4, 37 | IN_BUS_TWO = 1 << 5, 38 | FB_IN = 1 << 6, 39 | FB_OUT = 1 << 7 40 | }; 41 | 42 | class FmAlgorithm { 43 | public: 44 | int ops[6]; 45 | }; 46 | 47 | class FmCore { 48 | public: 49 | virtual ~FmCore() {}; 50 | static void dump(); 51 | void render(int32_t *output, FmOpParams *params, int algorithm, int32_t *fb_buf, int32_t feedback_gain); 52 | protected: 53 | AlignedBufbuf_[2]; 54 | const static FmAlgorithm algorithms[32]; 55 | }; 56 | 57 | #endif // __FM_CORE_H 58 | -------------------------------------------------------------------------------- /src/c/msfa/fm_op_kernel.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include 20 | 21 | #ifdef HAVE_NEON 22 | #include 23 | #endif 24 | 25 | #include "synth.h" 26 | #include "sin.h" 27 | #include "fm_op_kernel.h" 28 | 29 | #ifdef HAVE_NEONx 30 | static bool hasNeon() { 31 | return true; 32 | return (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0; 33 | } 34 | 35 | extern "C" 36 | void neon_fm_kernel(const int *in, const int *busin, int *out, int count, 37 | int32_t phase0, int32_t freq, int32_t gain1, int32_t dgain); 38 | 39 | const int32_t __attribute__ ((aligned(16))) zeros[N] = {0}; 40 | 41 | #else 42 | static bool hasNeon() { 43 | return false; 44 | } 45 | #endif 46 | 47 | void FmOpKernel::compute(int32_t *output, const int32_t *input, 48 | int32_t phase0, int32_t freq, 49 | int32_t gain1, int32_t gain2, bool add) { 50 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 51 | int32_t gain = gain1; 52 | int32_t phase = phase0; 53 | if (hasNeon()) { 54 | #ifdef HAVE_NEON 55 | neon_fm_kernel(input, add ? output : zeros, output, N, 56 | phase0, freq, gain, dgain); 57 | #endif 58 | } else { 59 | if (add) { 60 | for (int i = 0; i < N; i++) { 61 | gain += dgain; 62 | int32_t y = Sin::lookup(phase + input[i]); 63 | int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; 64 | output[i] += y1; 65 | phase += freq; 66 | } 67 | } else { 68 | for (int i = 0; i < N; i++) { 69 | gain += dgain; 70 | int32_t y = Sin::lookup(phase + input[i]); 71 | int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; 72 | output[i] = y1; 73 | phase += freq; 74 | } 75 | } 76 | } 77 | } 78 | 79 | void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, 80 | int32_t gain1, int32_t gain2, bool add) { 81 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 82 | int32_t gain = gain1; 83 | int32_t phase = phase0; 84 | if (hasNeon()) { 85 | #ifdef HAVE_NEON 86 | neon_fm_kernel(zeros, add ? output : zeros, output, N, 87 | phase0, freq, gain, dgain); 88 | #endif 89 | } else { 90 | if (add) { 91 | for (int i = 0; i < N; i++) { 92 | gain += dgain; 93 | int32_t y = Sin::lookup(phase); 94 | int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; 95 | output[i] += y1; 96 | phase += freq; 97 | } 98 | } else { 99 | for (int i = 0; i < N; i++) { 100 | gain += dgain; 101 | int32_t y = Sin::lookup(phase); 102 | int32_t y1 = ((int64_t)y * (int64_t)gain) >> 24; 103 | output[i] = y1; 104 | phase += freq; 105 | } 106 | } 107 | } 108 | } 109 | 110 | #define noDOUBLE_ACCURACY 111 | #define HIGH_ACCURACY 112 | 113 | void FmOpKernel::compute_fb(int32_t *output, int32_t phase0, int32_t freq, 114 | int32_t gain1, int32_t gain2, 115 | int32_t *fb_buf, int fb_shift, bool add) { 116 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 117 | int32_t gain = gain1; 118 | int32_t phase = phase0; 119 | int32_t y0 = fb_buf[0]; 120 | int32_t y = fb_buf[1]; 121 | if (add) { 122 | for (int i = 0; i < N; i++) { 123 | gain += dgain; 124 | int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); 125 | y0 = y; 126 | y = Sin::lookup(phase + scaled_fb); 127 | y = ((int64_t)y * (int64_t)gain) >> 24; 128 | output[i] += y; 129 | phase += freq; 130 | } 131 | } else { 132 | for (int i = 0; i < N; i++) { 133 | gain += dgain; 134 | int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); 135 | y0 = y; 136 | y = Sin::lookup(phase + scaled_fb); 137 | y = ((int64_t)y * (int64_t)gain) >> 24; 138 | output[i] = y; 139 | phase += freq; 140 | } 141 | } 142 | fb_buf[0] = y0; 143 | fb_buf[1] = y; 144 | } 145 | 146 | //////////////////////////////////////////////////////////////////////////////////// 147 | //////////////////////////////////////////////////////////////////////////////////// 148 | //////////////////////////////////////////////////////////////////////////////////// 149 | //////////////////////////////////////////////////////////////////////////////////// 150 | 151 | // Experimental sine wave generators below 152 | #if 0 153 | // Results: accuracy 64.3 mean, 170 worst case 154 | // high accuracy: 5.0 mean, 49 worst case 155 | void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, 156 | int32_t gain1, int32_t gain2, bool add) { 157 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 158 | int32_t gain = gain1; 159 | int32_t phase = phase0; 160 | #ifdef HIGH_ACCURACY 161 | int32_t u = Sin::compute10(phase << 6); 162 | u = ((int64_t)u * gain) >> 30; 163 | int32_t v = Sin::compute10((phase << 6) + (1 << 28)); // quarter cycle 164 | v = ((int64_t)v * gain) >> 30; 165 | int32_t s = Sin::compute10(freq << 6); 166 | int32_t c = Sin::compute10((freq << 6) + (1 << 28)); 167 | #else 168 | int32_t u = Sin::compute(phase); 169 | u = ((int64_t)u * gain) >> 24; 170 | int32_t v = Sin::compute(phase + (1 << 22)); // quarter cycle 171 | v = ((int64_t)v * gain) >> 24; 172 | int32_t s = Sin::compute(freq) << 6; 173 | int32_t c = Sin::compute(freq + (1 << 22)) << 6; 174 | #endif 175 | for (int i = 0; i < N; i++) { 176 | output[i] = u; 177 | int32_t t = ((int64_t)v * (int64_t)c - (int64_t)u * (int64_t)s) >> 30; 178 | u = ((int64_t)u * (int64_t)c + (int64_t)v * (int64_t)s) >> 30; 179 | v = t; 180 | } 181 | } 182 | #endif 183 | 184 | #if 0 185 | // Results: accuracy 392.3 mean, 15190 worst case (near freq = 0.5) 186 | // for freq < 0.25, 275.2 mean, 716 worst 187 | // high accuracy: 57.4 mean, 7559 worst 188 | // freq < 0.25: 17.9 mean, 78 worst 189 | void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, 190 | int32_t gain1, int32_t gain2, bool add) { 191 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 192 | int32_t gain = gain1; 193 | int32_t phase = phase0; 194 | #ifdef HIGH_ACCURACY 195 | int32_t u = floor(gain * sin(phase * (M_PI / (1 << 23))) + 0.5); 196 | int32_t v = floor(gain * cos((phase - freq * 0.5) * (M_PI / (1 << 23))) + 0.5); 197 | int32_t a = floor((1 << 25) * sin(freq * (M_PI / (1 << 24))) + 0.5); 198 | #else 199 | int32_t u = Sin::compute(phase); 200 | u = ((int64_t)u * gain) >> 24; 201 | int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1)); 202 | v = ((int64_t)v * gain) >> 24; 203 | int32_t a = Sin::compute(freq >> 1) << 1; 204 | #endif 205 | for (int i = 0; i < N; i++) { 206 | output[i] = u; 207 | v -= ((int64_t)a * (int64_t)u) >> 24; 208 | u += ((int64_t)a * (int64_t)v) >> 24; 209 | } 210 | } 211 | #endif 212 | 213 | #if 0 214 | // Results: accuracy 370.0 mean, 15480 worst case (near freq = 0.5) 215 | // with double accuracy initialization: mean 1.55, worst 58 (near freq = 0) 216 | // with high accuracy: mean 4.2, worst 292 (near freq = 0.5) 217 | void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, 218 | int32_t gain1, int32_t gain2, bool add) { 219 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 220 | int32_t gain = gain1; 221 | int32_t phase = phase0; 222 | #ifdef DOUBLE_ACCURACY 223 | int32_t u = floor((1 << 30) * sin(phase * (M_PI / (1 << 23))) + 0.5); 224 | double a_d = sin(freq * (M_PI / (1 << 24))); 225 | int32_t v = floor((1LL << 31) * a_d * cos((phase - freq * 0.5) * 226 | (M_PI / (1 << 23))) + 0.5); 227 | int32_t aa = floor((1LL << 31) * a_d * a_d + 0.5); 228 | #else 229 | #ifdef HIGH_ACCURACY 230 | int32_t u = Sin::compute10(phase << 6); 231 | int32_t v = Sin::compute10((phase << 6) + (1 << 28) - (freq << 5)); 232 | int32_t a = Sin::compute10(freq << 5); 233 | v = ((int64_t)v * (int64_t)a) >> 29; 234 | int32_t aa = ((int64_t)a * (int64_t)a) >> 29; 235 | #else 236 | int32_t u = Sin::compute(phase) << 6; 237 | int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1)); 238 | int32_t a = Sin::compute(freq >> 1); 239 | v = ((int64_t)v * (int64_t)a) >> 17; 240 | int32_t aa = ((int64_t)a * (int64_t)a) >> 17; 241 | #endif 242 | #endif 243 | 244 | if (aa < 0) aa = (1 << 31) - 1; 245 | for (int i = 0; i < N; i++) { 246 | gain += dgain; 247 | output[i] = ((int64_t)u * (int64_t)gain) >> 30; 248 | v -= ((int64_t)aa * (int64_t)u) >> 29; 249 | u += v; 250 | } 251 | } 252 | #endif 253 | 254 | #if 0 255 | // Results:: accuracy 112.3 mean, 4262 worst (near freq = 0.5) 256 | // high accuracy 2.9 mean, 143 worst 257 | void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, 258 | int32_t gain1, int32_t gain2, bool add) { 259 | int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; 260 | int32_t gain = gain1; 261 | int32_t phase = phase0; 262 | #ifdef HIGH_ACCURACY 263 | int32_t u = Sin::compute10(phase << 6); 264 | int32_t lastu = Sin::compute10((phase - freq) << 6); 265 | int32_t a = Sin::compute10((freq << 6) + (1 << 28)) << 1; 266 | #else 267 | int32_t u = Sin::compute(phase) << 6; 268 | int32_t lastu = Sin::compute(phase - freq) << 6; 269 | int32_t a = Sin::compute(freq + (1 << 22)) << 7; 270 | #endif 271 | if (a < 0 && freq < 256) a = (1 << 31) - 1; 272 | if (a > 0 && freq > 0x7fff00) a = -(1 << 31); 273 | for (int i = 0; i < N; i++) { 274 | gain += dgain; 275 | output[i] = ((int64_t)u * (int64_t)gain) >> 30; 276 | //output[i] = u; 277 | int32_t newu = (((int64_t)u * (int64_t)a) >> 30) - lastu; 278 | lastu = u; 279 | u = newu; 280 | } 281 | } 282 | #endif 283 | 284 | -------------------------------------------------------------------------------- /src/c/msfa/fm_op_kernel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __FM_OP_KERNEL_H 18 | #define __FM_OP_KERNEL_H 19 | 20 | struct FmOpParams { 21 | int32_t level_in; // value to be computed (from level to gain[0]) 22 | int32_t gain_out; // computed value (gain[1] to gain[0]) 23 | int32_t freq; 24 | int32_t phase; 25 | }; 26 | 27 | class FmOpKernel { 28 | public: 29 | // gain1 and gain2 represent linear step: gain for sample i is 30 | // gain1 + (1 + i) / 64 * (gain2 - gain1) 31 | 32 | // This is the basic FM operator. No feedback. 33 | static void compute(int32_t *output, const int32_t *input, 34 | int32_t phase0, int32_t freq, 35 | int32_t gain1, int32_t gain2, bool add); 36 | 37 | // This is a sine generator, no feedback. 38 | static void compute_pure(int32_t *output, int32_t phase0, int32_t freq, 39 | int32_t gain1, int32_t gain2, bool add); 40 | 41 | // One op with feedback, no add. 42 | static void compute_fb(int32_t *output, int32_t phase0, int32_t freq, 43 | int32_t gain1, int32_t gain2, 44 | int32_t *fb_buf, int fb_gain, bool add); 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/c/msfa/freqlut.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Resolve frequency signal (1.0 in Q24 format = 1 octave) to phase delta. 18 | 19 | // The LUT is just a global, and we'll need the init function to be called before 20 | // use. 21 | 22 | #include 23 | #include 24 | 25 | #include "freqlut.h" 26 | 27 | #define LG_N_SAMPLES 10 28 | #define N_SAMPLES (1 << LG_N_SAMPLES) 29 | #define SAMPLE_SHIFT (24 - LG_N_SAMPLES) 30 | 31 | #define MAX_LOGFREQ_INT 20 32 | 33 | int32_t lut[N_SAMPLES + 1]; 34 | 35 | void Freqlut::init(double sample_rate) { 36 | double y = (1LL << (24 + MAX_LOGFREQ_INT)) / sample_rate; 37 | double inc = pow(2, 1.0 / N_SAMPLES); 38 | for (int i = 0; i < N_SAMPLES + 1; i++) { 39 | lut[i] = (int32_t)floor(y + 0.5); 40 | y *= inc; 41 | } 42 | } 43 | 44 | // Note: if logfreq is more than 20.0, the results will be inaccurate. However, 45 | // that will be many times the Nyquist rate. 46 | int32_t Freqlut::lookup(int32_t logfreq) { 47 | int ix = (logfreq & 0xffffff) >> SAMPLE_SHIFT; 48 | 49 | int32_t y0 = lut[ix]; 50 | int32_t y1 = lut[ix + 1]; 51 | int lowbits = logfreq & ((1 << SAMPLE_SHIFT) - 1); 52 | int32_t y = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> SAMPLE_SHIFT); 53 | int hibits = logfreq >> 24; 54 | return y >> (MAX_LOGFREQ_INT - hibits); 55 | } 56 | -------------------------------------------------------------------------------- /src/c/msfa/freqlut.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | class Freqlut { 18 | public: 19 | static void init(double sample_rate); 20 | static int32_t lookup(int32_t logfreq); 21 | }; 22 | -------------------------------------------------------------------------------- /src/c/msfa/lfo.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Low frequency oscillator, compatible with DX7 18 | 19 | #include "synth.h" 20 | 21 | #include "sin.h" 22 | #include "lfo.h" 23 | 24 | uint32_t Lfo::unit_; 25 | 26 | void Lfo::init(double sample_rate) { 27 | // constant is 1 << 32 / 15.5s / 11 28 | Lfo::unit_ = (int32_t)(N * 25190424 / sample_rate + 0.5); 29 | } 30 | 31 | void Lfo::reset(const char params[6]) { 32 | int rate = params[0]; // 0..99 33 | int sr = rate == 0 ? 1 : (165 * rate) >> 6; 34 | sr *= sr < 160 ? 11 : (11 + ((sr - 160) >> 4)); 35 | delta_ = unit_ * sr; 36 | int a = 99 - params[1]; // LFO delay 37 | if (a == 99) { 38 | delayinc_ = ~0u; 39 | delayinc2_ = ~0u; 40 | } else { 41 | a = (16 + (a & 15)) << (1 + (a >> 4)); 42 | delayinc_ = unit_ * a; 43 | a &= 0xff80; 44 | a = max(0x80, a); 45 | delayinc2_ = unit_ * a; 46 | } 47 | waveform_ = params[5]; 48 | sync_ = params[4] != 0; 49 | } 50 | 51 | int32_t Lfo::getsample() { 52 | phase_ += delta_; 53 | int32_t x; 54 | switch (waveform_) { 55 | case 0: // triangle 56 | x = phase_ >> 7; 57 | x ^= -(phase_ >> 31); 58 | x &= (1 << 24) - 1; 59 | return x; 60 | case 1: // sawtooth down 61 | return (~phase_ ^ (1U << 31)) >> 8; 62 | case 2: // sawtooth up 63 | return (phase_ ^ (1U << 31)) >> 8; 64 | case 3: // square 65 | return ((~phase_) >> 7) & (1 << 24); 66 | case 4: // sine 67 | return (1 << 23) + (Sin::lookup(phase_ >> 8) >> 1); 68 | case 5: // s&h 69 | if (phase_ < delta_) { 70 | randstate_ = (randstate_ * 179 + 17) & 0xff; 71 | } 72 | x = randstate_ ^ 0x80; 73 | return (x + 1) << 16; 74 | } 75 | return 1 << 23; 76 | } 77 | 78 | int32_t Lfo::getdelay() { 79 | uint32_t delta = delaystate_ < (1U << 31) ? delayinc_ : delayinc2_; 80 | uint64_t d = ((uint64_t)delaystate_) + delta; 81 | if (d > ~0u) { 82 | return 1 << 24; 83 | } 84 | delaystate_ = d; 85 | if (d < (1U << 31)) { 86 | return 0; 87 | } else { 88 | return (d >> 7) & ((1 << 24) - 1); 89 | } 90 | } 91 | 92 | void Lfo::keydown() { 93 | if (sync_) { 94 | phase_ = (1U << 31) - 1; 95 | } 96 | delaystate_ = 0; 97 | } 98 | -------------------------------------------------------------------------------- /src/c/msfa/lfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Low frequency oscillator, compatible with DX7 18 | 19 | class Lfo { 20 | public: 21 | static void init(double sample_rate); 22 | void reset(const char params[6]); 23 | 24 | // result is 0..1 in Q24 25 | int32_t getsample(); 26 | 27 | // result is 0..1 in Q24 28 | int32_t getdelay(); 29 | 30 | void keydown(); 31 | private: 32 | static uint32_t unit_; 33 | 34 | uint32_t phase_; // Q32 35 | uint32_t delta_; 36 | uint8_t waveform_; 37 | uint8_t randstate_; 38 | bool sync_; 39 | 40 | uint32_t delaystate_; 41 | uint32_t delayinc_; 42 | uint32_t delayinc2_; 43 | }; 44 | -------------------------------------------------------------------------------- /src/c/msfa/module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SYNTH_MODULE_H 18 | #define SYNTH_MODULE_H 19 | 20 | #include 21 | 22 | class Module { 23 | public: 24 | static const int lg_n = 6; 25 | static const int n = 1 << lg_n; 26 | virtual void process(const int32_t **inbufs, const int32_t *control_in, 27 | const int32_t *control_last, int32_t **outbufs) = 0; 28 | }; 29 | 30 | #endif // SYNTH_MODULE_H 31 | 32 | -------------------------------------------------------------------------------- /src/c/msfa/patch.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include "patch.h" 20 | 21 | void UnpackPatch(const char bulk[128], char patch[156]) { 22 | for (int op = 0; op < 6; op++) { 23 | // eg rate and level, brk pt, depth, scaling 24 | memcpy(patch + op * 21, bulk + op * 17, 11); 25 | char leftrightcurves = bulk[op * 17 + 11]; 26 | patch[op * 21 + 11] = leftrightcurves & 3; 27 | patch[op * 21 + 12] = (leftrightcurves >> 2) & 3; 28 | char detune_rs = bulk[op * 17 + 12]; 29 | patch[op * 21 + 13] = detune_rs & 7; 30 | patch[op * 21 + 20] = detune_rs >> 3; 31 | char kvs_ams = bulk[op * 17 + 13]; 32 | patch[op * 21 + 14] = kvs_ams & 3; 33 | patch[op * 21 + 15] = kvs_ams >> 2; 34 | patch[op * 21 + 16] = bulk[op * 17 + 14]; // output level 35 | char fcoarse_mode = bulk[op * 17 + 15]; 36 | patch[op * 21 + 17] = fcoarse_mode & 1; 37 | patch[op * 21 + 18] = fcoarse_mode >> 1; 38 | patch[op * 21 + 19] = bulk[op * 17 + 16]; // fine freq 39 | } 40 | memcpy(patch + 126, bulk + 102, 9); // pitch env, algo 41 | char oks_fb = bulk[111]; 42 | patch[135] = oks_fb & 7; 43 | patch[136] = oks_fb >> 3; 44 | memcpy(patch + 137, bulk + 112, 4); // lfo 45 | char lpms_lfw_lks = bulk[116]; 46 | patch[141] = lpms_lfw_lks & 1; 47 | patch[142] = (lpms_lfw_lks >> 1) & 7; 48 | patch[143] = lpms_lfw_lks >> 4; 49 | memcpy(patch + 144, bulk + 117, 11); // transpose, name 50 | patch[155] = 0x3f; // operator on/off 51 | } 52 | -------------------------------------------------------------------------------- /src/c/msfa/patch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SYNTH_PATCH_H_ 18 | #define SYNTH_PATCH_H_ 19 | 20 | void UnpackPatch(const char bulk[128], char patch[156]); 21 | 22 | #endif -------------------------------------------------------------------------------- /src/c/msfa/pitchenv.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "synth.h" 18 | #include "pitchenv.h" 19 | 20 | int PitchEnv::unit_; 21 | 22 | void PitchEnv::init(double sample_rate) { 23 | unit_ = N * (1 << 24) / (21.3 * sample_rate) + 0.5; 24 | } 25 | 26 | const uint8_t pitchenv_rate[] = { 27 | 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 28 | 12, 13, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 29 | 25, 26, 27, 28, 30, 31, 33, 34, 36, 37, 38, 39, 41, 42, 44, 46, 47, 30 | 49, 51, 53, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 79, 82, 31 | 85, 88, 91, 94, 98, 102, 106, 110, 115, 120, 125, 130, 135, 141, 147, 32 | 153, 159, 165, 171, 178, 185, 193, 202, 211, 232, 243, 254, 255 33 | }; 34 | 35 | const int8_t pitchenv_tab[] = { 36 | -128, -116, -104, -95, -85, -76, -68, -61, -56, -52, -49, -46, -43, 37 | -41, -39, -37, -35, -33, -32, -31, -30, -29, -28, -27, -26, -25, -24, 38 | -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, 39 | -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 40 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 41 | 28, 29, 30, 31, 32, 33, 34, 35, 38, 40, 43, 46, 49, 53, 58, 65, 73, 42 | 82, 92, 103, 115, 127 43 | }; 44 | 45 | void PitchEnv::set(const int r[4], const int l[4]) { 46 | for (int i = 0; i < 4; i++) { 47 | rates_[i] = r[i]; 48 | levels_[i] = l[i]; 49 | } 50 | level_ = pitchenv_tab[l[3]] << 19; 51 | down_ = true; 52 | advance(0); 53 | } 54 | 55 | int32_t PitchEnv::getsample() { 56 | if (ix_ < 3 || ((ix_ < 4) && !down_)) { 57 | if (rising_) { 58 | level_ += inc_; 59 | if (level_ >= targetlevel_) { 60 | level_ = targetlevel_; 61 | advance(ix_ + 1); 62 | } 63 | } else { // !rising 64 | level_ -= inc_; 65 | if (level_ <= targetlevel_) { 66 | level_ = targetlevel_; 67 | advance(ix_ + 1); 68 | } 69 | } 70 | } 71 | return level_; 72 | } 73 | 74 | void PitchEnv::keydown(bool d) { 75 | if (down_ != d) { 76 | down_ = d; 77 | advance(d ? 0 : 3); 78 | } 79 | } 80 | 81 | void PitchEnv::advance(int newix) { 82 | ix_ = newix; 83 | if (ix_ < 4) { 84 | int newlevel = levels_[ix_]; 85 | targetlevel_ = pitchenv_tab[newlevel] << 19; 86 | rising_ = (targetlevel_ > level_); 87 | inc_ = pitchenv_rate[rates_[ix_]] * unit_; 88 | } 89 | } 90 | 91 | void PitchEnv::getPosition(char *step) { 92 | *step = ix_; 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/c/msfa/pitchenv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __PITCHENV_H 18 | #define __PITCHENV_H 19 | 20 | // Computation of the DX7 pitch envelope 21 | 22 | class PitchEnv { 23 | public: 24 | static void init(double sample_rate); 25 | 26 | // The rates and levels arrays are calibrated to match the Dx7 parameters 27 | // (ie, value 0..99). 28 | void set(const int rates[4], const int levels[4]); 29 | 30 | // Result is in Q24/octave 31 | int32_t getsample(); 32 | void keydown(bool down); 33 | void getPosition(char *step); 34 | private: 35 | static int unit_; 36 | int rates_[4]; 37 | int levels_[4]; 38 | int32_t level_; 39 | int targetlevel_; 40 | bool rising_; 41 | int ix_; 42 | int inc_; 43 | 44 | bool down_; 45 | 46 | void advance(int newix); 47 | }; 48 | 49 | extern const uint8_t pitchenv_rate[]; 50 | extern const int8_t pitchenv_tab[]; 51 | 52 | #endif // __PITCHENV_H 53 | 54 | -------------------------------------------------------------------------------- /src/c/msfa/ringbuffer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include "synth.h" 21 | #include "ringbuffer.h" 22 | 23 | RingBuffer::RingBuffer() { 24 | rd_ix_ = 0; 25 | wr_ix_ = 0; 26 | } 27 | 28 | int RingBuffer::BytesAvailable() { 29 | return (wr_ix_ - rd_ix_) & (kBufSize - 1); 30 | } 31 | 32 | int RingBuffer::WriteBytesAvailable() { 33 | return (rd_ix_ - wr_ix_ - 1) & (kBufSize - 1); 34 | } 35 | 36 | int RingBuffer::Read(int size, uint8_t *bytes) { 37 | int rd_ix = rd_ix_; 38 | SynthMemoryBarrier(); // read barrier, make sure data is committed before ix 39 | int fragment_size = min(size, kBufSize - rd_ix); 40 | memcpy(bytes, buf_ + rd_ix, fragment_size); 41 | if (size > fragment_size) { 42 | memcpy(bytes + fragment_size, buf_, size - fragment_size); 43 | } 44 | SynthMemoryBarrier(); // full barrier, make sure read commits before updating 45 | rd_ix_ = (rd_ix + size) & (kBufSize - 1); 46 | return size; 47 | } 48 | 49 | int RingBuffer::Write(const uint8_t *bytes, int size) { 50 | int remaining = size; 51 | while (remaining > 0) { 52 | int rd_ix = rd_ix_; 53 | int wr_ix = wr_ix_; 54 | int space_available = (rd_ix - wr_ix - 1) & (kBufSize - 1); 55 | if (space_available == 0) { 56 | struct timespec sleepTime; 57 | sleepTime.tv_sec = 0; 58 | sleepTime.tv_nsec = 1000000; 59 | nanosleep(&sleepTime, NULL); 60 | } else { 61 | int wr_size = min(remaining, space_available); 62 | int fragment_size = min(wr_size, kBufSize - wr_ix); 63 | memcpy(buf_ + wr_ix, bytes, fragment_size); 64 | if (wr_size > fragment_size) { 65 | memcpy(buf_, bytes + fragment_size, wr_size - fragment_size); 66 | } 67 | SynthMemoryBarrier(); // write barrier, make sure data commits 68 | wr_ix_ = (wr_ix + wr_size) & (kBufSize - 1); 69 | remaining -= wr_size; 70 | bytes += wr_size; 71 | } 72 | } 73 | // JJK : defined as returning int 74 | return 0; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/c/msfa/ringbuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef SYNTH_RINGBUFFER_H_ 18 | #define SYNTH_RINGBUFFER_H_ 19 | 20 | class RingBuffer { 21 | public: 22 | RingBuffer(); 23 | 24 | // Returns number of bytes available for reading. 25 | int BytesAvailable(); 26 | 27 | // Returns number of bytes that can be written without blocking. 28 | int WriteBytesAvailable(); 29 | 30 | // Reads bytes. It is the caller's responsibility to make sure that 31 | // size <= a previous value of BytesAvailable(). 32 | int Read(int size, uint8_t *bytes); 33 | 34 | // Writes bytes into the buffer. If the buffer is full, the method will 35 | // block until space is available. 36 | int Write(const uint8_t *bytes, int size); 37 | private: 38 | static const int kBufSize = 8192; 39 | uint8_t buf_[kBufSize]; 40 | volatile unsigned int rd_ix_; 41 | volatile unsigned int wr_ix_; 42 | }; 43 | 44 | #endif // SYNTH_RINGBUFFER_H_ 45 | -------------------------------------------------------------------------------- /src/c/msfa/sin.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #define _USE_MATH_DEFINES 18 | #include 19 | 20 | #include "synth.h" 21 | #include "sin.h" 22 | 23 | #define R (1 << 29) 24 | 25 | #ifdef SIN_DELTA 26 | int32_t sintab[SIN_N_SAMPLES << 1]; 27 | #else 28 | int32_t sintab[SIN_N_SAMPLES + 1]; 29 | #endif 30 | 31 | void Sin::init() { 32 | double dphase = 2 * M_PI / SIN_N_SAMPLES; 33 | int32_t c = (int32_t)floor(cos(dphase) * (1 << 30) + 0.5); 34 | int32_t s = (int32_t)floor(sin(dphase) * (1 << 30) + 0.5); 35 | int32_t u = 1 << 30; 36 | int32_t v = 0; 37 | for (int i = 0; i < SIN_N_SAMPLES / 2; i++) { 38 | #ifdef SIN_DELTA 39 | sintab[(i << 1) + 1] = (v + 32) >> 6; 40 | sintab[((i + SIN_N_SAMPLES / 2) << 1) + 1] = -((v + 32) >> 6); 41 | #else 42 | sintab[i] = (v + 32) >> 6; 43 | sintab[i + SIN_N_SAMPLES / 2] = -((v + 32) >> 6); 44 | #endif 45 | int32_t t = ((int64_t)u * (int64_t)s + (int64_t)v * (int64_t)c + R) >> 30; 46 | u = ((int64_t)u * (int64_t)c - (int64_t)v * (int64_t)s + R) >> 30; 47 | v = t; 48 | } 49 | #ifdef SIN_DELTA 50 | for (int i = 0; i < SIN_N_SAMPLES - 1; i++) { 51 | sintab[i << 1] = sintab[(i << 1) + 3] - sintab[(i << 1) + 1]; 52 | } 53 | sintab[(SIN_N_SAMPLES << 1) - 2] = -sintab[(SIN_N_SAMPLES << 1) - 1]; 54 | #else 55 | sintab[SIN_N_SAMPLES] = 0; 56 | #endif 57 | } 58 | 59 | #ifndef SIN_INLINE 60 | int32_t Sin::lookup(int32_t phase) { 61 | const int SHIFT = 24 - SIN_LG_N_SAMPLES; 62 | int lowbits = phase & ((1 << SHIFT) - 1); 63 | #ifdef SIN_DELTA 64 | int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1); 65 | int dy = sintab[phase_int]; 66 | int y0 = sintab[phase_int + 1]; 67 | 68 | return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 69 | #else 70 | int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1); 71 | int y0 = sintab[phase_int]; 72 | int y1 = sintab[phase_int + 1]; 73 | 74 | return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT); 75 | #endif 76 | } 77 | #endif 78 | 79 | 80 | #if 0 81 | // The following is an implementation designed not to use any lookup tables, 82 | // based on the following implementation by Basile Graf: 83 | // http://www.rossbencina.com/static/code/sinusoids/even_polynomial_sin_approximation.txt 84 | 85 | #define C0 (1 << 24) 86 | #define C1 (331121857 >> 2) 87 | #define C2 (1084885537 >> 4) 88 | #define C3 (1310449902 >> 6) 89 | 90 | int32_t Sin::compute(int32_t phase) { 91 | int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22); 92 | int32_t x2 = ((int64_t)x * (int64_t)x) >> 22; 93 | int32_t x4 = ((int64_t)x2 * (int64_t)x2) >> 24; 94 | int32_t x6 = ((int64_t)x2 * (int64_t)x4) >> 24; 95 | int32_t y = C0 - 96 | (((int64_t)C1 * (int64_t)x2) >> 24) + 97 | (((int64_t)C2 * (int64_t)x4) >> 24) - 98 | (((int64_t)C3 * (int64_t)x6) >> 24); 99 | y ^= -((phase >> 23) & 1); 100 | return y; 101 | } 102 | #endif 103 | 104 | #if 1 105 | // coefficients are Chebyshev polynomial, computed by compute_cos_poly.py 106 | #define C8_0 16777216 107 | #define C8_2 -331168742 108 | #define C8_4 1089453524 109 | #define C8_6 -1430910663 110 | #define C8_8 950108533 111 | 112 | int32_t Sin::compute(int32_t phase) { 113 | int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22); 114 | int32_t x2 = ((int64_t)x * (int64_t)x) >> 16; 115 | int32_t y = (((((((((((((int64_t)C8_8 116 | * (int64_t)x2) >> 32) + C8_6) 117 | * (int64_t)x2) >> 32) + C8_4) 118 | * (int64_t)x2) >> 32) + C8_2) 119 | * (int64_t)x2) >> 32) + C8_0); 120 | y ^= -((phase >> 23) & 1); 121 | return y; 122 | } 123 | #endif 124 | 125 | #define C10_0 (1 << 30) 126 | #define C10_2 -1324675874 // scaled * 4 127 | #define C10_4 1089501821 128 | #define C10_6 -1433689867 129 | #define C10_8 1009356886 130 | #define C10_10 -421101352 131 | int32_t Sin::compute10(int32_t phase) { 132 | int32_t x = (phase & ((1 << 29) - 1)) - (1 << 28); 133 | int32_t x2 = ((int64_t)x * (int64_t)x) >> 26; 134 | int32_t y = ((((((((((((((((int64_t)C10_10 135 | * (int64_t)x2) >> 34) + C10_8) 136 | * (int64_t)x2) >> 34) + C10_6) 137 | * (int64_t)x2) >> 34) + C10_4) 138 | * (int64_t)x2) >> 32) + C10_2) 139 | * (int64_t)x2) >> 30) + C10_0); 140 | y ^= -((phase >> 29) & 1); 141 | return y; 142 | } 143 | -------------------------------------------------------------------------------- /src/c/msfa/sin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | class Sin { 18 | public: 19 | Sin(); 20 | 21 | static void init(); 22 | static int32_t lookup(int32_t phase); 23 | static int32_t compute(int32_t phase); 24 | 25 | // A more accurate sine, both input and output Q30 26 | static int32_t compute10(int32_t phase); 27 | }; 28 | 29 | #define SIN_LG_N_SAMPLES 10 30 | #define SIN_N_SAMPLES (1 << SIN_LG_N_SAMPLES) 31 | 32 | #define SIN_INLINE 33 | 34 | // Use twice as much RAM for the LUT but avoid a little computation 35 | #define SIN_DELTA 36 | 37 | #ifdef SIN_DELTA 38 | extern int32_t sintab[SIN_N_SAMPLES << 1]; 39 | #else 40 | extern int32_t sintab[SIN_N_SAMPLES + 1]; 41 | #endif 42 | 43 | #ifdef SIN_INLINE 44 | inline 45 | int32_t Sin::lookup(int32_t phase) { 46 | const int SHIFT = 24 - SIN_LG_N_SAMPLES; 47 | int lowbits = phase & ((1 << SHIFT) - 1); 48 | #ifdef SIN_DELTA 49 | int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1); 50 | int dy = sintab[phase_int]; 51 | int y0 = sintab[phase_int + 1]; 52 | 53 | return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); 54 | #else 55 | int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1); 56 | int y0 = sintab[phase_int]; 57 | int y1 = sintab[phase_int + 1]; 58 | 59 | return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT); 60 | #endif 61 | } 62 | #endif 63 | -------------------------------------------------------------------------------- /src/c/msfa/synth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __SYNTH_H 18 | #define __SYNTH_H 19 | 20 | // This IS not be present on MSVC. 21 | // See http://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio 22 | #include 23 | #ifdef _MSC_VER 24 | typedef __int32 int32_t; 25 | typedef unsigned __int32 uint32_t; 26 | typedef __int16 SInt16; 27 | #endif 28 | 29 | const static int LG_N = 6; 30 | const static int N = (1 << LG_N); 31 | 32 | #if defined(__APPLE__) 33 | #include 34 | #define SynthMemoryBarrier() OSMemoryBarrier() 35 | #elif defined(__GNUC__) 36 | #if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) 37 | #define SynthMemoryBarrier() __sync_synchronize() 38 | #endif 39 | #endif 40 | 41 | 42 | // #undef SynthMemoryBarrier() 43 | 44 | #ifndef SynthMemoryBarrier 45 | // need to understand why this must be defined 46 | // #warning Memory barrier is not enabled 47 | #define SynthMemoryBarrier() 48 | #endif 49 | 50 | template 51 | inline static T min(const T& a, const T& b) { 52 | return a < b ? a : b; 53 | } 54 | 55 | template 56 | inline static T max(const T& a, const T& b) { 57 | return a > b ? a : b; 58 | } 59 | 60 | void dexed_trace(const char *source, const char *fmt, ...); 61 | 62 | #define QER(n,b) ( ((float)n)/(1< 19 | #endif 20 | 21 | #ifdef __ANDROID__ 22 | #include 23 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "synth", __VA_ARGS__) 24 | #endif 25 | 26 | #include 27 | 28 | #include "synth.h" 29 | #include "freqlut.h" 30 | #include "sin.h" 31 | #include "exp2.h" 32 | #include "pitchenv.h" 33 | #include "env.h" 34 | #include "patch.h" 35 | #include "synth_unit.h" 36 | #include "aligned_buf.h" 37 | #include "tuning.h" 38 | 39 | char epiano[] = { 40 | 95, 29, 20, 50, 99, 95, 0, 0, 41, 0, 19, 0, 115, 24, 79, 2, 0, 41 | 95, 20, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 3, 0, 99, 2, 0, 42 | 95, 29, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 59, 24, 89, 2, 0, 43 | 95, 20, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 59, 8, 99, 2, 0, 44 | 95, 50, 35, 78, 99, 75, 0, 0, 0, 0, 0, 0, 59, 28, 58, 28, 0, 45 | 96, 25, 25, 67, 99, 75, 0, 0, 0, 0, 0, 0, 83, 8, 99, 2, 0, 46 | 47 | 94, 67, 95, 60, 50, 50, 50, 50, 4, 6, 34, 33, 0, 0, 56, 24, 48 | 69, 46, 80, 73, 65, 78, 79, 32, 49, 32 49 | }; 50 | 51 | void SynthUnit::Init(double sample_rate) { 52 | Freqlut::init(sample_rate); 53 | Exp2::init(); 54 | Tanh::init(); 55 | Sin::init(); 56 | Lfo::init(sample_rate); 57 | PitchEnv::init(sample_rate); 58 | Env::init_sr(sample_rate); 59 | } 60 | 61 | SynthUnit::SynthUnit(RingBuffer *ring_buffer) { 62 | ring_buffer_ = ring_buffer; 63 | 64 | for (int note = 0; note < max_active_notes; ++note) { 65 | active_note_[note].dx7_note = new Dx7Note(createStandardTuning()); 66 | active_note_[note].keydown = false; 67 | active_note_[note].sustained = false; 68 | active_note_[note].live = false; 69 | } 70 | input_buffer_index_ = 0; 71 | memcpy(patch_data_, epiano, sizeof(epiano)); 72 | ProgramChange(0); 73 | current_note_ = 0; 74 | 75 | controllers_.values_[kControllerPitch] = 0x2000; 76 | controllers_.modwheel_cc = 0; 77 | controllers_.foot_cc = 0; 78 | controllers_.breath_cc = 0; 79 | controllers_.aftertouch_cc = 0; 80 | controllers_.refresh(); 81 | 82 | sustain_ = false; 83 | extra_buf_size_ = 0; 84 | } 85 | 86 | // Transfer as many bytes as possible from ring buffer to input buffer. 87 | // Note that this implementation has a fair amount of copying - we'd probably 88 | // do it a bit differently if it were bulk data, but in this case we're 89 | // optimizing for simplicity of implementation. 90 | void SynthUnit::TransferInput() { 91 | size_t bytes_available = ring_buffer_->BytesAvailable(); 92 | int bytes_to_read = min(bytes_available, 93 | sizeof(input_buffer_) - input_buffer_index_); 94 | if (bytes_to_read > 0) { 95 | ring_buffer_->Read(bytes_to_read, input_buffer_ + input_buffer_index_); 96 | input_buffer_index_ += bytes_to_read; 97 | } 98 | } 99 | 100 | void SynthUnit::ConsumeInput(int n_input_bytes) { 101 | if (n_input_bytes < input_buffer_index_) { 102 | memmove(input_buffer_, input_buffer_ + n_input_bytes, 103 | input_buffer_index_ - n_input_bytes); 104 | } 105 | input_buffer_index_ -= n_input_bytes; 106 | } 107 | 108 | int SynthUnit::AllocateNote() { 109 | int note = current_note_; 110 | for (int i = 0; i < max_active_notes; i++) { 111 | if (!active_note_[note].keydown) { 112 | current_note_ = (note + 1) % max_active_notes; 113 | return note; 114 | } 115 | note = (note + 1) % max_active_notes; 116 | } 117 | return -1; 118 | } 119 | 120 | void SynthUnit::ProgramChange(int p) { 121 | current_patch_ = p; 122 | const uint8_t *patch = patch_data_ + 128 * current_patch_; 123 | UnpackPatch((const char *)patch, unpacked_patch_); 124 | lfo_.reset(unpacked_patch_ + 137); 125 | } 126 | 127 | void SynthUnit::SetController(int controller, int value) { 128 | controllers_.values_[controller] = value; 129 | } 130 | 131 | int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) { 132 | uint8_t cmd = buf[0]; 133 | uint8_t cmd_type = cmd & 0xf0; 134 | //LOGI("got %d midi: %02x %02x %02x", buf_size, buf[0], buf[1], buf[2]); 135 | if (cmd_type == 0x80 || (cmd_type == 0x90 && buf[2] == 0)) { 136 | if (buf_size >= 3) { 137 | // note off 138 | for (int note = 0; note < max_active_notes; ++note) { 139 | if (active_note_[note].midi_note == buf[1] && 140 | active_note_[note].keydown) { 141 | if (sustain_) { 142 | active_note_[note].sustained = true; 143 | } else { 144 | active_note_[note].dx7_note->keyup(); 145 | } 146 | active_note_[note].keydown = false; 147 | } 148 | } 149 | return 3; 150 | } 151 | return 0; 152 | } else if (cmd_type == 0x90) { 153 | if (buf_size >= 3) { 154 | // note on 155 | int note_ix = AllocateNote(); 156 | if (note_ix >= 0) { 157 | lfo_.keydown(); // TODO: should only do this if # keys down was 0 158 | active_note_[note_ix].midi_note = buf[1]; 159 | active_note_[note_ix].keydown = true; 160 | active_note_[note_ix].sustained = sustain_; 161 | active_note_[note_ix].live = true; 162 | active_note_[note_ix].dx7_note->init((uint8_t *) unpacked_patch_, buf[1], buf[2]); 163 | } 164 | return 3; 165 | } 166 | return 0; 167 | } else if (cmd_type == 0xb0) { 168 | if (buf_size >= 3) { 169 | // controller 170 | // TODO: move more logic into SetController 171 | int controller = buf[1]; 172 | int value = buf[2]; 173 | 174 | if (controller == 1) { 175 | controllers_.modwheel_cc = value; 176 | controllers_.refresh(); 177 | } else if (controller == 2) { 178 | controllers_.breath_cc = value; 179 | controllers_.refresh(); 180 | } else if (controller == 3) { 181 | controllers_.foot_cc = value; 182 | controllers_.refresh(); 183 | } else if (controller == 64) { 184 | sustain_ = value != 0; 185 | if (!sustain_) { 186 | for (int note = 0; note < max_active_notes; note++) { 187 | if (active_note_[note].sustained && !active_note_[note].keydown) { 188 | active_note_[note].dx7_note->keyup(); 189 | active_note_[note].sustained = false; 190 | } 191 | } 192 | } 193 | } 194 | return 3; 195 | } return 0; 196 | } else if (cmd_type == 0xd0) { 197 | controllers_.aftertouch_cc = buf[1]; 198 | controllers_.refresh(); 199 | return 0; 200 | } else if (cmd_type == 0xc0) { 201 | if (buf_size >= 2) { 202 | // program change 203 | int program_number = buf[1]; 204 | ProgramChange(min(program_number, 31)); 205 | char name[11]; 206 | memcpy(name, unpacked_patch_ + 145, 10); 207 | name[10] = 0; 208 | #ifdef VERBOSE 209 | std::cout << "Loaded patch " << current_patch_ << ": " << name << "\r"; 210 | std::cout.flush(); 211 | #endif 212 | return 2; 213 | } 214 | return 0; 215 | } else if (cmd == 0xe0) { 216 | // pitch bend 217 | SetController(kControllerPitch, buf[1] | (buf[2] << 7)); 218 | return 3; 219 | } else if (cmd == 0xf0) { 220 | // sysex 221 | if (buf_size >= 6 && buf[1] == 0x43 && buf[2] == 0x00 && buf[3] == 0x09 && 222 | buf[4] == 0x20 && buf[5] == 0x00) { 223 | if (buf_size >= 4104) { 224 | // TODO: check checksum? 225 | memcpy(patch_data_, buf + 6, 4096); 226 | ProgramChange(current_patch_); 227 | return 4104; 228 | } 229 | return 0; 230 | } 231 | } 232 | 233 | // TODO: more robust handling 234 | #ifdef VERBOSE 235 | std::cout << "Unknown message " << std::hex << (int)cmd << 236 | ", skipping " << std::dec << buf_size << " bytes" << std::endl; 237 | #endif 238 | return buf_size; 239 | } 240 | 241 | void SynthUnit::GetSamples(int n_samples, int16_t *buffer) { 242 | TransferInput(); 243 | size_t input_offset; 244 | for (input_offset = 0; input_offset < input_buffer_index_; ) { 245 | int bytes_available = input_buffer_index_ - input_offset; 246 | int bytes_consumed = ProcessMidiMessage(input_buffer_ + input_offset, 247 | bytes_available); 248 | if (bytes_consumed == 0) { 249 | break; 250 | } 251 | input_offset += bytes_consumed; 252 | } 253 | ConsumeInput(input_offset); 254 | 255 | int i; 256 | for (i = 0; i < n_samples && i < extra_buf_size_; i++) { 257 | buffer[i] = extra_buf_[i]; 258 | } 259 | if (extra_buf_size_ > n_samples) { 260 | for (int j = 0; j < extra_buf_size_ - n_samples; j++) { 261 | extra_buf_[j] = extra_buf_[j + n_samples]; 262 | } 263 | extra_buf_size_ -= n_samples; 264 | return; 265 | } 266 | 267 | for (; i < n_samples; i += N) { 268 | AlignedBuf audiobuf; 269 | AlignedBuf audiobuf2; 270 | for (int j = 0; j < N; ++j) { 271 | audiobuf.get()[j] = 0; 272 | } 273 | int32_t lfovalue = lfo_.getsample(); 274 | int32_t lfodelay = lfo_.getdelay(); 275 | for (int note = 0; note < max_active_notes; ++note) { 276 | if (active_note_[note].live) { 277 | active_note_[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, 278 | &controllers_); 279 | } 280 | } 281 | int32_t *bufs[] = { audiobuf.get() }; 282 | /* JJK int32_t *bufs2[] = { audiobuf2.get() }; 283 | filter_.process(bufs, filter_control_, filter_control_, bufs2); */ 284 | int jmax = n_samples - i; 285 | for (int j = 0; j < N; ++j) { 286 | // JJK int32_t val = audiobuf2.get()[j] >> 4; 287 | int32_t val = audiobuf.get()[j] >> 4; 288 | int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9; 289 | // TODO: maybe some dithering? 290 | if (j < jmax) { 291 | buffer[i + j] = clip_val; 292 | } else { 293 | extra_buf_[j - jmax] = clip_val; 294 | } 295 | } 296 | } 297 | extra_buf_size_ = i - n_samples; 298 | } 299 | 300 | // JJK 301 | extern "C" { void wam_logi(int i); } 302 | void SynthUnit::onPatch(const uint8_t* patch, uint32_t size) 303 | { 304 | if (size == 128) UnpackPatch((const char *)patch, unpacked_patch_); 305 | else if (size == 145) 306 | { 307 | memcpy(unpacked_patch_, patch, size); 308 | unpacked_patch_[155] = 0x3f; // operator on/off 309 | } 310 | else return; 311 | lfo_.reset((const char *)unpacked_patch_); 312 | } 313 | 314 | void SynthUnit::onParam(uint32_t id, char value) 315 | { 316 | unpacked_patch_[id] = value; 317 | } 318 | -------------------------------------------------------------------------------- /src/c/msfa/synth_unit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "controllers.h" 18 | #include "dx7note.h" 19 | #include "lfo.h" 20 | #include "ringbuffer.h" 21 | #include "tuning.h" 22 | // JJK #include "resofilter.h" 23 | 24 | struct ActiveNote { 25 | int midi_note; 26 | bool keydown; 27 | bool sustained; 28 | bool live; 29 | Dx7Note *dx7_note; 30 | }; 31 | 32 | class SynthUnit { 33 | public: 34 | static void Init(double sample_rate); 35 | 36 | explicit SynthUnit(RingBuffer *ring_buffer); 37 | 38 | void GetSamples(int n_samples, int16_t *buffer); 39 | private: 40 | void TransferInput(); 41 | 42 | void ConsumeInput(int n_input_bytes); 43 | 44 | // Choose a note for a new key-down, returns note number, or -1 if 45 | // none available. 46 | int AllocateNote(); 47 | 48 | // zero-based 49 | void ProgramChange(int p); 50 | 51 | void SetController(int controller, int value); 52 | 53 | int ProcessMidiMessage(const uint8_t *buf, int buf_size); 54 | 55 | RingBuffer *ring_buffer_; 56 | static const int max_active_notes = 16; 57 | ActiveNote active_note_[max_active_notes]; 58 | int current_note_; 59 | uint8_t input_buffer_[8192]; 60 | size_t input_buffer_index_; 61 | 62 | uint8_t patch_data_[4096]; 63 | int current_patch_; 64 | 65 | char unpacked_patch_[156]; 66 | 67 | // The original DX7 had one single LFO. Later units had an LFO per note. 68 | Lfo lfo_; 69 | 70 | // in MIDI units (0x4000 is neutral) 71 | Controllers controllers_; 72 | 73 | /* JJK ResoFilter filter_; 74 | int32_t filter_control_[3]; */ 75 | bool sustain_; 76 | 77 | // Extra buffering for when GetSamples wants a buffer not a multiple of N 78 | int16_t extra_buf_[N]; 79 | int extra_buf_size_; 80 | 81 | // JJK 82 | public: 83 | void onPatch(const uint8_t* patch, uint32_t size); 84 | void onParam(uint32_t id, char value); 85 | }; 86 | -------------------------------------------------------------------------------- /src/c/msfa/tuning.cc: -------------------------------------------------------------------------------- 1 | #include "tuning.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | struct StandardTuning : public TuningState { 13 | StandardTuning() { 14 | const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) 15 | const int step = (1 << 24) / 12; 16 | for( int mn = 0; mn < 128; ++mn ) 17 | { 18 | auto res = base + step * mn; 19 | current_logfreq_table_[mn] = res; 20 | } 21 | } 22 | 23 | virtual int32_t midinote_to_logfreq(int midinote) override { 24 | return current_logfreq_table_[midinote]; 25 | } 26 | 27 | int current_logfreq_table_[128]; 28 | }; 29 | 30 | 31 | struct SCLAndKBMTuningState : public TuningState { 32 | virtual bool is_standard_tuning() override { 33 | return false; 34 | } 35 | 36 | virtual int32_t midinote_to_logfreq(int midinote) override { 37 | const int base = 50857777; 38 | const int step = ( 1 << 24 ); 39 | return tuning.logScaledFrequencyForMidiNote( midinote ) * step + base; 40 | } 41 | 42 | virtual int scale_length() { return tuning.scale.count; } 43 | virtual std::string display_tuning_str() { return "SCL KBM Tuning"; } 44 | 45 | virtual Tunings::Tuning &getTuning() override { return tuning; } 46 | 47 | Tunings::Tuning tuning; 48 | 49 | }; 50 | 51 | std::shared_ptr createStandardTuning() 52 | { 53 | return std::make_shared(); 54 | } 55 | 56 | std::shared_ptr createTuningFromSCLData( const std::string &scl ) 57 | { 58 | auto s = Tunings::parseSCLData(scl); 59 | auto res = std::make_shared(); 60 | res->tuning = Tunings::Tuning( s ); 61 | return res; 62 | } 63 | 64 | std::shared_ptr createTuningFromKBMData( const std::string &kbm ) 65 | { 66 | auto k = Tunings::parseKBMData(kbm); 67 | auto res = std::make_shared(); 68 | res->tuning = Tunings::Tuning( k ); 69 | return res; 70 | } 71 | 72 | std::shared_ptr createTuningFromSCLAndKBMData( const std::string &sclData, const std::string &kbmData ) 73 | { 74 | auto s = Tunings::parseSCLData(sclData); 75 | auto k = Tunings::parseKBMData(kbmData); 76 | auto res = std::make_shared(); 77 | res->tuning = Tunings::Tuning( s, k ); 78 | return res; 79 | } 80 | -------------------------------------------------------------------------------- /src/c/msfa/tuning.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYNTH_TUNING_H 2 | #define __SYNTH_TUNING_H 3 | 4 | #include "synth.h" 5 | #include 6 | #include 7 | #include "../tuning-library/include/Tunings.h" 8 | 9 | class TuningState { 10 | public: 11 | virtual ~TuningState() { } 12 | 13 | virtual int32_t midinote_to_logfreq(int midinote) = 0; 14 | virtual bool is_standard_tuning() { return true; } 15 | virtual int scale_length() { return 12; } 16 | virtual std::string display_tuning_str() { return "Standard Tuning"; } 17 | 18 | virtual Tunings::Tuning &getTuning() { 19 | static Tunings::Tuning t; 20 | return t; 21 | } 22 | }; 23 | 24 | std::shared_ptr createStandardTuning(); 25 | 26 | std::shared_ptr createTuningFromSCLData( const std::string &sclData ); 27 | std::shared_ptr createTuningFromKBMData( const std::string &kbmData ); 28 | std::shared_ptr createTuningFromSCLAndKBMData( const std::string &sclData, const std::string &kbmData ); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/c/tuning-library/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/c/tuning-library/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019-2020, Paul Walker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/c/tuning-library/Makefile: -------------------------------------------------------------------------------- 1 | # YOU DO NOT NEED TO USE THIS MAKEFILE TO USE THIS LIBRARY 2 | # 3 | # Tunings is a header-only library. This Makefile is just used 4 | # to build tests and the standalone utilities 5 | # 6 | 7 | ifeq ($(OS),Windows_NT) 8 | CC = g++ 9 | CCFLAGS = -Wall -Werror -std=c++14 -Iinclude -Ilibs/catch2 10 | BLD = build/windows 11 | else 12 | UNAME_S := $(shell uname -s) 13 | ifeq ($(UNAME_S),Linux) 14 | CC = g++ 15 | CCFLAGS = -Wall -Werror -std=c++14 -Iinclude -Ilibs/catch2 16 | BLD = build/linux 17 | endif 18 | ifeq ($(UNAME_S),Darwin) 19 | CC = clang++ 20 | CCFLAGS = -Wall -Werror -std=c++14 -Iinclude -Ilibs/catch2 21 | BLD = build/macos 22 | endif 23 | endif 24 | 25 | TUNING=include/Tunings.h include/TuningsImpl.h 26 | 27 | all: $(BLD)/alltests $(BLD)/showmapping $(BLD)/symbolcheck 28 | 29 | runtests: all 30 | @$(BLD)/symbolcheck 31 | 32 | @$(BLD)/alltests 33 | 34 | @LANG=`locale -a | grep es_ES | grep -v "\." | head -1` $(BLD)/alltests 35 | @LANG=`locale -a | grep fr_FR | grep -v "\." | head -1` $(BLD)/alltests 36 | @LANG=`locale -a | grep ja_JP | head -1` $(BLD)/alltests 37 | @LANG=`locale -a | grep zh_CN | head -1` $(BLD)/alltests 38 | @LANG=`locale -a | grep MAKE_SURE_NULL_IS_OK | grep -v "\." | head -1` $(BLD)/alltests 39 | 40 | $(BLD)/symbolcheck: tests/symbolcheck1.cpp tests/symbolcheck2.cpp $(TUNING) $(BLD) 41 | @echo If this build fails, you forgot an inline 42 | $(CC) $(CCFLAGS) -c tests/symbolcheck1.cpp -o $(BLD)/symbolcheck1.o 43 | $(CC) $(CCFLAGS) -c tests/symbolcheck2.cpp -o $(BLD)/symbolcheck2.o 44 | $(CC) $(CCFLAGS) $(BLD)/symbolcheck1.o $(BLD)/symbolcheck2.o -o $(BLD)/symbolcheck 45 | 46 | $(BLD)/alltests: tests/alltests.cpp $(TUNING) $(BLD) 47 | $(CC) $(CCFLAGS) $< -o $@ 48 | 49 | $(BLD)/alltests_cov: tests/alltests.cpp $(TUNING) $(BLD) 50 | $(CC) $(CCFLAGS) --coverage $< -o $@ 51 | 52 | $(BLD)/showmapping: commands/showmapping.cpp $(TUNING) $(BLD) 53 | $(CC) $(CCFLAGS) $< -o $@ 54 | 55 | $(BLD): 56 | mkdir -p $(BLD) 57 | 58 | clean: 59 | rm -rf build 60 | 61 | coverage: $(BLD)/alltests_cov 62 | $(BLD)/alltests_cov 63 | mkdir -p $(BLD)/coverage 64 | mv alltests* $(BLD)/coverage 65 | cd $(BLD)/coverage && gcov alltests.gcna 66 | -------------------------------------------------------------------------------- /src/c/tuning-library/README.md: -------------------------------------------------------------------------------- 1 | # Surge Synth Team Tuning Library 2 | 3 | **WORK IN PROGRESS. PLEASE CHECK BACK IN A BIT** 4 | 5 | In [Surge](https://surge-synthesizer.github.io), we added microtuning 6 | and spent a lot of time making sure our 7 | SCL/KBM implementation was properly calibrated and available for C++ 8 | programs. We then added that same implementation to [dexed] through a copy. 9 | 10 | But we realized we could make the functions available as standalone C++ header 11 | only library and get three benefits. 12 | 13 | 1. Share more code between Surge and our Dexed fork 14 | 2. Make the code available to other soft synths where we or others may add microtuning 15 | 3. Have a set of standalone comand line utilities and well documented tests 16 | 17 | So we took the code and re-factored it here under an MIT license. 18 | 19 | Although Surge and Dexed are GPL3, the copyright holders and authors of the original 20 | Surge microtuning implementation (and only that implementation) were happy to relicense. 21 | 22 | ## Using the library in your C++ project 23 | 24 | The C++ library is a standalone header only C++-11 library. There are a variety of ways 25 | to use it in your project but our approach is generally: 26 | 27 | 1. Make this github repo a submodule of your project 28 | 2. Add the "include/" directory to your compiler include path 29 | 3. `#include "Tunings.h"` 30 | 31 | The code is organized such that Tunings.h is the API and TuningsImpl.h is the header with more 32 | involved implementation bodies. Tunings.h includes TuningsImpl.h automatically. 33 | 34 | ## Building the comand line tools and test suite 35 | 36 | Building the command line tools is really simple. 37 | 38 | 1. Start a terminal 39 | 2. Type `make` 40 | 41 | That's it! Now, for make to work, you need a unix-like environment on windows. 42 | We followed exactly the VCV Rack dev setup and uses mysys, but really anything 43 | which has a working g++ in your path will be fine. 44 | 45 | The build ejects two assets. `alltests` which runs all the tests and `showmapping` 46 | 47 | ## Using the showmapping command 48 | 49 | `showmapping` takes one or two arguments. It either takes an .scl file, in which 50 | case it dumps the frequency table for that .scl file with midi note 60 being the 51 | scale start tuned to 261hz, or it takes an .scl and .kbm file, in which case it 52 | prints the entire internal tuning table for the combination. 53 | 54 | ## Bugs, Problems, etc 55 | 56 | If you find bugs, please open a github issue and we will fix it right away! 57 | 58 | If you have a non-bug problem, you can do the same or you can hop on the slack as 59 | detailed at https://surge-synth-team.org/ 60 | 61 | If you would like to expand our test cases, we are always thrilled for you to do 62 | so. Drop in a pull request. 63 | 64 | If you choose to use the software in your synth, you can go right ahead of course. 65 | That's the point of the MIT license! But if you want to let us know, again pop open 66 | a github or drop in our slack. Always glad to hear from you. 67 | 68 | Enjoy! 69 | -------------------------------------------------------------------------------- /src/c/tuning-library/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Build dexed with JUCE buidls 2 | 3 | trigger: 4 | - master 5 | 6 | pr: 7 | - master 8 | 9 | jobs: 10 | 11 | - job: Build 12 | strategy: 13 | matrix: 14 | mac: 15 | imageName: 'macos-10.14' 16 | isMac: True 17 | win: 18 | imageName: 'vs2017-win2016' 19 | isWindows: True 20 | lin: 21 | imageName: 'ubuntu-18.04' 22 | isLinux: True 23 | 24 | pool: 25 | vmImage: $(imageName) 26 | 27 | steps: 28 | - checkout: self 29 | fetchDepth: 1 30 | # submodules: recursive # can't do submodules here b'cuz depth=1 fails with Github 31 | 32 | - bash: | 33 | make all 34 | displayName: Build with make 35 | 36 | - bash: | 37 | sudo locale-gen es_ES 38 | sudo locale-gen fr_FR 39 | sudo locale-gen ja_JP 40 | sudo locale-gen zh_CN 41 | sudo update-locale 42 | displayName: Install Locales on Linux 43 | condition: variables.isLinux 44 | 45 | - bash: | 46 | make runtests 47 | displayName: Run tests with make 48 | 49 | - bash: | 50 | mkdir products/ 51 | cd build/ 52 | 53 | GIT_TAG=`git rev-parse --short HEAD` 54 | BUILDDATE=`date +%Y%m%d` 55 | ZIPV="${GIT_TAG}-${BUILDDATE}" 56 | 57 | 7z.exe a ../products/Tuning-Library-Utils-Win64bit-${ZIPV}.zip windows 58 | displayName: Build Windows Zip 59 | condition: variables.isWindows 60 | 61 | - task: PublishPipelineArtifact@0 62 | inputs: 63 | artifactName: 'WINDOWS_BUILD' 64 | targetPath: 'products/' 65 | displayName: Publish Windows Zip 66 | condition: variables.isWindows 67 | 68 | - bash: | 69 | mkdir products/ 70 | cd build/ 71 | 72 | GIT_TAG=`git rev-parse --short HEAD` 73 | BUILDDATE=`date +%Y%m%d` 74 | ZIPV="${GIT_TAG}-${BUILDDATE}" 75 | 76 | tar cvzf ../products/Tuning-Library-Linux-Utils-${ZIPV}.tgz linux 77 | displayName: Build Linux TGZ 78 | condition: variables.isLinux 79 | 80 | - task: PublishPipelineArtifact@0 81 | inputs: 82 | artifactName: 'LINUX_BUILD' 83 | targetPath: 'products/' 84 | displayName: Publish Linux Zip 85 | condition: variables.isLinux 86 | 87 | 88 | - bash: | 89 | mkdir products/ 90 | cd build/ 91 | 92 | GIT_TAG=`git rev-parse --short HEAD` 93 | BUILDDATE=`date +%Y%m%d` 94 | ZIPV="${GIT_TAG}-${BUILDDATE}" 95 | 96 | tar cvzf ../products/Tuning-Library-Utils-MacOS-${ZIPV}.tgz macos 97 | displayName: Build Mac TGZ 98 | condition: variables.isMac 99 | 100 | - task: PublishPipelineArtifact@0 101 | inputs: 102 | artifactName: 'MACOS_BUILD' 103 | targetPath: 'products/' 104 | displayName: Publish Mac Zip 105 | condition: variables.isMac 106 | 107 | 108 | - job: UpdateGithubRelease 109 | dependsOn: Build 110 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) 111 | 112 | steps: 113 | - task: DownloadPipelineArtifact@0 114 | inputs: 115 | artifactName: 'MACOS_BUILD' 116 | targetPath: $(Build.ArtifactStagingDirectory) 117 | 118 | - task: DownloadPipelineArtifact@0 119 | inputs: 120 | artifactName: 'WINDOWS_BUILD' 121 | targetPath: $(Build.ArtifactStagingDirectory) 122 | 123 | - task: DownloadPipelineArtifact@0 124 | inputs: 125 | artifactName: 'LINUX_BUILD' 126 | targetPath: $(Build.ArtifactStagingDirectory) 127 | 128 | - bash: | 129 | scripts/release-notes.sh > $(Build.ArtifactStagingDirectory)/ReleaseNotes.md 130 | ls $(Build.ArtifactStagingDirectory) 131 | md5sum $(Build.ArtifactStagingDirectory)/* 132 | 133 | displayName: Fake up release notes and tag release 134 | 135 | - task: GitHubRelease@0 136 | displayName: "Create New Github Release" 137 | inputs: 138 | gitHubConnection: surge-rackupdater 139 | repositoryName: surge-synthesizer/tuning-library 140 | action: 'edit' 141 | tag: Nightly 142 | target: '$(Build.SourceVersion)' 143 | addChangeLog: false 144 | assetUploadMode: 'delete' 145 | releaseNotesFile: $(Build.ArtifactStagingDirectory)/ReleaseNotes.md 146 | assets: $(Build.ArtifactStagingDirectory)/*.* 147 | 148 | -------------------------------------------------------------------------------- /src/c/tuning-library/commands/showmapping.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Tunings.h" 4 | 5 | using namespace Tunings; 6 | 7 | int main( int argc, char **argv ) 8 | { 9 | if( argc == 1 ) 10 | { 11 | std::cout << "Usage: " << argv[0] << " scl-file [kbm-file]\n\n" 12 | << "Will show the frequency mapping across the midi keyboard for the scl/kbm combo" << std::endl; 13 | exit(1); 14 | } 15 | 16 | auto s = readSCLFile(argv[1]); 17 | KeyboardMapping k; 18 | 19 | if( argc == 3 ) 20 | { 21 | k = readKBMFile(argv[2] ); 22 | } 23 | 24 | Tuning t(s, k); 25 | std::cout << "Note ," 26 | << " Freq (Hz) , " 27 | << " ScaledFrq , " 28 | << " logScaled " << std::endl; 29 | 30 | for( int i=0; i<128; ++i ) 31 | { 32 | std::cout << std::setw(4) << i << ", " 33 | << std::setw(10) << std::setprecision(10) << std::fixed << t.frequencyForMidiNote(i) << ", " 34 | << std::setw(10) << std::setprecision(10) << std::fixed << t.frequencyForMidiNoteScaledByMidi0(i) << ", " 35 | << std::setw(10) << std::setprecision(10) << std::fixed << t.logScaledFrequencyForMidiNote(i) << std::endl; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/c/tuning-library/include/Tunings.h: -------------------------------------------------------------------------------- 1 | // -*-c++-*- 2 | 3 | /** 4 | * Tunings.h 5 | * Copyright Paul Walker, 2019-2020 6 | * Released under the MIT License. See LICENSE.md 7 | * 8 | * Tunings.h contains the public API required to determine full keyboard frequency maps 9 | * for a scala SCL and KBM file in standalone, tested, open licensed C++ header only library. 10 | * 11 | * An example of using the API is 12 | * 13 | * ``` 14 | * auto s = Tunings::readSCLFile( "./my-scale.scl" ); 15 | * auto k = Tunings::readKBMFile( "./my-mapping.kbm" ); 16 | * 17 | * Tunings::Tuning t( s, k ); 18 | * 19 | * std::cout << "The frequency of C4 and A4 are " 20 | * << t.frequencyForMidiNote( 60 ) << " and " 21 | * << t.frequencyForMidiNote( 69 ) << std::endl; 22 | * ``` 23 | * 24 | * The API provides several other points, such as access to the structure of the SCL and KBM, 25 | * the ability to create several prototype SCL and KBM files wthout SCL or KBM content, 26 | * a frequency measure which is normalized by the frequency of standard tuning midi note 0 27 | * and the logarithmic frequency scale, with a doubling per frequency doubling. 28 | * 29 | * Documentation is in the class header below; tests are in `tests/all_tests.cpp` and 30 | * a variety of command line tools accompany the header. 31 | */ 32 | 33 | #ifndef __INCLUDE_TUNINGS_H 34 | #define __INCLUDE_TUNINGS_H 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | namespace Tunings 43 | { 44 | const double MIDI_0_FREQ=8.17579891564371; // or 440.0 * pow( 2.0, - (69.0/12.0 ) ) 45 | 46 | /** 47 | * A Tone is a single entry in an SCL file. It is expressed either in cents or in 48 | * a ratio, as described in the SCL documentation. 49 | * 50 | * In most normal use, you will not use this class, and it will be internal to a Scale 51 | */ 52 | struct Tone 53 | { 54 | typedef enum Type 55 | { 56 | kToneCents, // An SCL representation like "133.0" 57 | kToneRatio // An SCL representation like "3/7" 58 | } Type; 59 | 60 | Type type; 61 | double cents; 62 | int ratio_d, ratio_n; 63 | std::string stringRep; 64 | double floatValue; // cents / 1200 + 1. 65 | 66 | Tone() : type(kToneRatio), 67 | cents(0), 68 | ratio_d(1), 69 | ratio_n(1), 70 | stringRep("1/1"), 71 | floatValue(1.0) 72 | { 73 | } 74 | }; 75 | 76 | /** 77 | * Given an SCL string like "100.231" or "3/7" set up a Tone 78 | */ 79 | inline Tone toneFromString(const std::string &t, int lineno=-1); 80 | 81 | /** 82 | * The Scale is the representation of the SCL file. It contains several key 83 | * features. Most importantly it has a count and a vector of Tones. 84 | * 85 | * In most normal use, you will simply pass around instances of this class 86 | * to a Tunings::Tuning instance, but in some cases you may want to create 87 | * or inspect this class yourself. Especially if you are displaying this 88 | * class to your end users, you may want to use the rawText or count methods. 89 | */ 90 | struct Scale 91 | { 92 | std::string name; // The name in the SCL file. Informational only 93 | std::string description; // The description in the SCL file. Informational only 94 | std::string rawText; // The raw text of the SCL file used to create this Scale 95 | int count; // The number of tones. 96 | std::vector tones; // The tones 97 | 98 | Scale() : name("empty scale"), 99 | description(""), 100 | rawText(""), 101 | count(0) 102 | { 103 | } 104 | }; 105 | 106 | /** 107 | * The KeyboardMapping class represents a KBM file. In most cases, the salient 108 | * features are the tuningConstantNote and tuningFrequency, which allow you to 109 | * pick a fixed note in the midi keyboard when retuning. The KBM file can also 110 | * remap individual keys to individual points in a scale, which kere is done with the 111 | * keys vector. 112 | * 113 | * Just as with Scale, the rawText member contains the text of the KBM file used. 114 | */ 115 | 116 | struct KeyboardMapping 117 | { 118 | int count; 119 | int firstMidi, lastMidi; 120 | int middleNote; 121 | int tuningConstantNote; 122 | double tuningFrequency, tuningPitch; // pitch = frequency / MIDI_0_FREQ 123 | int octaveDegrees; 124 | std::vector keys; // rather than an 'x' we use a '-1' for skipped keys 125 | 126 | std::string rawText; 127 | std::string name; 128 | 129 | KeyboardMapping() : count(0), 130 | firstMidi(0), 131 | lastMidi(127), 132 | middleNote(60), 133 | tuningConstantNote(60), 134 | tuningFrequency(MIDI_0_FREQ * 32.0), 135 | tuningPitch(32.0), 136 | octaveDegrees(12), 137 | rawText( "" ), 138 | name( "" ) 139 | { 140 | } 141 | 142 | }; 143 | 144 | /** 145 | * In some failure states, the tuning library will throw an exception of 146 | * type TuningError with a descriptive message. 147 | */ 148 | class TuningError : public std::exception { 149 | public: 150 | TuningError(std::string m) : whatv(m) { } 151 | virtual const char* what() const noexcept override { return whatv.c_str(); } 152 | private: 153 | std::string whatv; 154 | }; 155 | 156 | /** 157 | * readSCLFile returns a Scale from the SCL File in fname 158 | */ 159 | Scale readSCLFile(std::string fname); 160 | 161 | /** 162 | * parseSCLData returns a scale from the SCL file contents in memory 163 | */ 164 | Scale parseSCLData(const std::string &sclContents); 165 | 166 | /** 167 | * evenTemperament12NoteScale provides a utility scale which is 168 | * the "standard tuning" scale 169 | */ 170 | Scale evenTemperament12NoteScale(); 171 | 172 | /** 173 | * evenDivisionOfSpanByM provides a scale referd to as "ED2-17" or 174 | * "ED3-24" by dividing the Span into M points. eventDivisionOfSpanByM(2,12) 175 | * should be the evenTemperament12NoteScale 176 | */ 177 | Scale evenDivisionOfSpanByM( int Span, int M ); 178 | 179 | /** 180 | * readKBMFile returns a KeyboardMapping from a KBM file name 181 | */ 182 | KeyboardMapping readKBMFile(std::string fname); 183 | 184 | /** 185 | * parseKBMData returns a KeyboardMapping from a KBM data in memory 186 | */ 187 | KeyboardMapping parseKBMData(const std::string &kbmContents); 188 | 189 | /** 190 | * tuneA69To creates a KeyboardMapping which keeps the midi note 69 (A4) set 191 | * to a constant frequency, given 192 | */ 193 | KeyboardMapping tuneA69To(double freq); 194 | 195 | /** 196 | * tuneNoteTo creates a KeyboardMapping which keeps the midi note given is set 197 | * to a constant frequency, given 198 | */ 199 | KeyboardMapping tuneNoteTo(int midiNote, double freq); 200 | 201 | /** 202 | * startScaleOnAndTuneNoteTo generates a KBM where scaleStart is the note 0 203 | * of the scale, where midiNote is the tuned note, and where feq is the frequency 204 | */ 205 | KeyboardMapping startScaleOnAndTuneNoteTo(int scaleStart, int midiNote, double freq); 206 | 207 | /** 208 | * The Tuning class is the primary place where you will interact with this library. 209 | * It is constructed for a scale and mapping and then gives you the ability to 210 | * determine frequencies across and beyond the midi keyboard. Since modulation 211 | * can force key number well outside the [0,127] range in some of our synths we 212 | * support a midi note range from -256 to + 256 spanning more than the entire frequency 213 | * space reasonable. 214 | * 215 | * To use this class, you construct a fresh instance every time you want to use a 216 | * different Scale and Keyboard. If you want to tune to a different scale or mapping, 217 | * just construct a new instance. 218 | */ 219 | class Tuning { 220 | public: 221 | // The number of notes we pre-compute 222 | constexpr static int N = 512; 223 | 224 | // Construct a tuning with even temperament and standard mapping 225 | Tuning(); 226 | 227 | /** 228 | * Construct a tuning for a particular scale, mapping, or for both. 229 | */ 230 | Tuning( const Scale &s ); 231 | Tuning( const KeyboardMapping &k ); 232 | Tuning( const Scale &s, const KeyboardMapping &k ); 233 | 234 | /** 235 | * These three related functions provide you the information you 236 | * need to use this tuning. 237 | * 238 | * frequencyForMidiNote returns the Frequency in HZ for a given midi 239 | * note. In standard tuning, FrequencyForMidiNote(69) will be 440 240 | * and frequencyForMidiNote(60) will be 261.62 - the standard frequencies 241 | * for A and middle C. 242 | * 243 | * frequencyForMidiNoteScaledByMidi0 returns the frequency but with the 244 | * standard frequency of midi note 0 divided out. So in standard tuning 245 | * frequencyForMidiNoteScaledByMidi0(0) = 1 and frequencyForMidiNoteScaledByMidi0(60) = 32 246 | * 247 | * Finally logScaledFrequencyForMidiNote returns the log base 2 of the scaled frequency. 248 | * So logScaledFrequencyForMidiNote(0) = 0 and logScaledFrequencyForMidiNote(60) = 5. 249 | * 250 | * Both the frequency measures have the feature of doubling when frequency doubles 251 | * (or when a standard octave is spanned), whereas the log one increase by 1 per frequency double. 252 | * 253 | * Depending on your internal pitch model, one of these three methods should allow you 254 | * to calibrate your oscillators to the appropriate frequency based on the midi note 255 | * at hand. 256 | * 257 | * The scalePositionForMidiNote returns the space in the logical scale. Note 0 is the root. 258 | * It has a maxiumum value of count-1. Note that SCL files omit the root internally and so 259 | * this logical scale position is off by 1 from the index in the tones array of the Scale data. 260 | */ 261 | double frequencyForMidiNote( int mn ) const; 262 | double frequencyForMidiNoteScaledByMidi0( int mn ) const; 263 | double logScaledFrequencyForMidiNote( int mn ) const; 264 | int scalePositionForMidiNote( int mn ) const; 265 | 266 | // For convenience, the scale and mapping used to construct this are kept as public copies 267 | Scale scale; 268 | KeyboardMapping keyboardMapping; 269 | private: 270 | std::array ptable, lptable; 271 | std::array scalepositiontable; 272 | }; 273 | 274 | } // namespace Tunings 275 | 276 | #include "TuningsImpl.h" 277 | 278 | #endif 279 | -------------------------------------------------------------------------------- /src/c/tuning-library/scripts/release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat <<- EOH 4 | # Automated build of surge-synth-team tuning library executables 5 | 6 | Look, the tuning library is a C++ library which you use inside other synths. 7 | Your most likely usage of it is as a developer implemeenting tuning. But we 8 | do make a binary of our command line exe and test exe available at the back 9 | of our pipeline. If that's something you'd like to download, you can do so here! 10 | 11 | EOH 12 | date 13 | echo "" 14 | echo "Most recent commits:" 15 | echo "" 16 | git log --pretty=oneline | head -5 17 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/12-intune-dosle.scl: -------------------------------------------------------------------------------- 1 | ! C:\Users\green\Music\VST\Scales\12-22_Dorian.scl 2 | ! 3 | 22-edo, mode 3 1 1 3 1 3 1 3 1 1 3 1 4 | 12 5 | ! 6 | 100.0 7 | 200.0 8 | 300.0 9 | 400.0 10 | 500.0 11 | 600.0 12 | 700.0 13 | 800.0 14 | 900.0 15 | 1000.0 16 | 1100.0 17 | 2/1 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/12-intune.scl: -------------------------------------------------------------------------------- 1 | ! C:\Users\green\Music\VST\Scales\12-22_Dorian.scl 2 | ! 3 | 22-edo, mode 3 1 1 3 1 3 1 3 1 1 3 1 4 | 12 5 | ! 6 | 100.0 7 | 200.0 8 | 300.0 9 | 400.0 10 | 500.0 11 | 600.0 12 | 700.0 13 | 800.0 14 | 900.0 15 | 1000.0 16 | 1100.0 17 | 2/1 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/12-shuffled.scl: -------------------------------------------------------------------------------- 1 | ! C:\Users\green\Music\VST\Scales\12-22_Dorian.scl 2 | ! 3 | 22-edo, mode 3 1 1 3 1 3 1 3 1 1 3 1 4 | 12 5 | ! 6 | 200.0 7 | 100.0 8 | 300.0 9 | 500.0 10 | 400.0 11 | 600.0 12 | 700.0 13 | 800.0 14 | 1000.0 15 | 900.0 16 | 1100.0 17 | 2/1 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/31edo.scl: -------------------------------------------------------------------------------- 1 | ! C:\Users\green\Documents\Music_Production\Scales\31edo.scl 2 | ! 3 | 31 equal divisions of octave 4 | 31 5 | ! 6 | 38.70968 7 | 77.41935 8 | 116.12903 9 | 154.83871 10 | 193.54839 11 | 232.25806 12 | 270.96774 13 | 309.67742 14 | 348.38710 15 | 387.09677 16 | 425.80645 17 | 464.51613 18 | 503.22581 19 | 541.93548 20 | 580.64516 21 | 619.35484 22 | 658.06452 23 | 696.77419 24 | 735.48387 25 | 774.19355 26 | 812.90323 27 | 851.61290 28 | 890.32258 29 | 929.03226 30 | 967.74194 31 | 1006.45161 32 | 1045.16129 33 | 1083.87097 34 | 1122.58065 35 | 1161.29032 36 | 2/1 37 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/6-exact.scl: -------------------------------------------------------------------------------- 1 | ! 6 exact 2 | ! 3 | HD2 06-12 - Harmonic division of 2: Harmonics 06-12 4 | 6 5 | ! 6 | 7/6 7 | 4/3 8 | 3/2 9 | 5/3 10 | 11/6 11 | 2/1 12 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/ED3-17.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED3-17.scl 2 | ! 3 | ED3-17 - Equal division of harmonic 3 into 17 parts 4 | 17 5 | ! 6 | 111.87971 7 | 223.75941 8 | 335.63912 9 | 447.51882 10 | 559.39853 11 | 671.27824 12 | 783.15794 13 | 895.03765 14 | 1006.91735 15 | 1118.79706 16 | 1230.67677 17 | 1342.55647 18 | 1454.43618 19 | 1566.31588 20 | 1678.19559 21 | 1790.07529 22 | 3/1 23 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/ED4-17.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED4-17.scl 2 | ! 3 | ED4-17 - Equal division of harmonic 4 into 17 parts 4 | 17 5 | ! 6 | 141.17647 7 | 282.35294 8 | 423.52941 9 | 564.70588 10 | 705.88235 11 | 847.05882 12 | 988.23529 13 | 1129.41176 14 | 1270.58824 15 | 1411.76471 16 | 1552.94118 17 | 1694.11765 18 | 1835.29412 19 | 1976.47059 20 | 2117.64706 21 | 2258.82353 22 | 4/1 23 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/badnote.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED3-17.scl 2 | ! 3 | ED3-17 - Equal division of harmonic 3 into 17 parts 4 | 17 5 | ! 6 | 111.87971 7 | 223.75941 8 | 335.63912 9 | 447.51882 10 | 559.39853 11 | 671.27824 12 | What is this 13 | 895.03765 14 | 1006.91735 15 | 1118.79706 16 | 1230.67677 17 | 1342.55647 18 | 1454.43618 19 | 1566.31588 20 | 1678.19559 21 | 1790.07529 22 | 3/1 23 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/blank-line.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | 2 25 | 3 26 | 4 27 | 28 | 6 29 | 7 30 | 8 31 | 9 32 | 10 33 | 11 34 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/blanknote.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED3-17.scl 2 | ! 3 | ED3-17 - Equal division of harmonic 3 into 17 parts 4 | 17 5 | ! 6 | 111.87971 7 | 223.75941 8 | 335.63912 9 | 447.51882 10 | 559.39853 11 | 671.27824 12 | 13 | 895.03765 14 | 1006.91735 15 | 1118.79706 16 | 1230.67677 17 | 1342.55647 18 | 1454.43618 19 | 1566.31588 20 | 1678.19559 21 | 1790.07529 22 | 3/1 23 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/empty-bad.kbm: -------------------------------------------------------------------------------- 1 | ! 61-277-61 Concert C#, Db.kbm 2 | ! 3 | ! Size of map: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry in the mapping is mapped to: 10 | 61 11 | ! Reference note for which frequency is given: 12 | 61 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 280.0 15 | ! Scale degree to consider as formal octave: 16 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/empty-extra.kbm: -------------------------------------------------------------------------------- 1 | ! 61-277-61 Concert C#, Db.kbm 2 | ! 3 | ! Size of map: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry in the mapping is mapped to: 10 | 61 11 | ! Reference note for which frequency is given: 12 | 61 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 280.0 15 | ! Scale degree to consider as formal octave: 16 | 0 17 | ! Mapping. 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/extraline-long.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | 2 25 | 3 26 | 4 27 | 5 28 | 6 29 | 7 30 | 8 31 | 9 32 | 10 33 | 11 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/extraline.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED3-17.scl 2 | ! 3 | ED3-17 - Equal division of harmonic 3 into 17 parts 4 | 17 5 | ! 6 | 111.87971 7 | 223.75941 8 | 335.63912 9 | 447.51882 10 | 559.39853 11 | 671.27824 12 | 783.15794 13 | 895.03765 14 | 1006.91735 15 | 1118.79706 16 | 1230.67677 17 | 1342.55647 18 | 1454.43618 19 | 1566.31588 20 | 1678.19559 21 | 1790.07529 22 | 3/1 23 | 24 | All this extra garbage after 17 notes is ignored by my parser 25 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/garbage-key.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | 2 25 | 3 26 | garbage 27 | 5 28 | 6 29 | 7 30 | 8 31 | 9 32 | 10 33 | 11 34 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/missing-note.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | 2 25 | 3 26 | 4 27 | 5 28 | 6 29 | 8 30 | 9 31 | 10 32 | 11 33 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/bad/missingnote.scl: -------------------------------------------------------------------------------- 1 | ! D:\Scala Batch\ED3-17.scl 2 | ! 3 | ED3-17 - Equal division of harmonic 3 into 17 parts 4 | 17 5 | ! 6 | 111.87971 7 | 223.75941 8 | 335.63912 9 | 447.51882 10 | 559.39853 11 | 671.27824 12 | 783.15794 13 | 895.03765 14 | 1118.79706 15 | 1230.67677 16 | 1342.55647 17 | 1454.43618 18 | 1566.31588 19 | 1678.19559 20 | 1790.07529 21 | 3/1 22 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/empty-note61.kbm: -------------------------------------------------------------------------------- 1 | ! 61-277-61 Concert C#, Db.kbm 2 | ! 3 | ! Size of map: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry in the mapping is mapped to: 10 | 61 11 | ! Reference note for which frequency is given: 12 | 61 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 280.0 15 | ! Scale degree to consider as formal octave: 16 | 0 17 | ! Mapping. 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/empty-note69-dosle.kbm: -------------------------------------------------------------------------------- 1 | ! 61-277-61 Concert C#, Db.kbm 2 | ! 3 | ! Size of map: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry in the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 452 15 | ! Scale degree to consider as formal octave: 16 | 0 17 | ! Mapping. 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/empty-note69.kbm: -------------------------------------------------------------------------------- 1 | ! 61-277-61 Concert C#, Db.kbm 2 | ! 3 | ! Size of map: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry in the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 452 15 | ! Scale degree to consider as formal octave: 16 | 0 17 | ! Mapping. 18 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-a440-constant.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | 2 25 | 3 26 | 4 27 | 5 28 | 6 29 | 7 30 | 8 31 | 9 32 | 10 33 | 11 -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-a442-7-to-12.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to. Tune on b 10 | 59 11 | ! Reference note for which frequency is given. g# is 442 here 12 | 68 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 442.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 7 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 1 24 | x 25 | 2 26 | x 27 | 3 28 | 4 29 | x 30 | 5 31 | x 32 | 6 33 | x 34 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-note53-to-430-408.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 53 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 430.408 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 0 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-note54-to-259-6.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 0 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 54 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 259.6 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 0 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-whitekeys-a440.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 7 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | x 24 | 1 25 | x 26 | 2 27 | 3 28 | x 29 | 4 30 | x 31 | 5 32 | x 33 | 6 34 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/mapping-whitekeys-c261.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 60 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 261.625565280 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 7 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | x 24 | 1 25 | x 26 | 2 27 | 3 28 | x 29 | 4 30 | x 31 | 5 32 | x 33 | 6 -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/marvel12.scl: -------------------------------------------------------------------------------- 1 | ! marvel12.scl 2 | Marvel[12] hobbit in 197-tET 3 | 12 4 | ! 5 | 115.73604 6 | 201.01523 7 | 316.75127 8 | 383.75635 9 | 499.49239 10 | 584.77157 11 | 700.50761 12 | 816.24365 13 | 931.97970 14 | 968.52792 15 | 1084.26396 16 | 2/1 17 | ! 18 | ! ! premarvel12.scl 19 | ! ! 20 | ! Premarvel[12] hobbit 5-limit transversal = diadie2 = pump9 21 | ! 12 22 | ! ! 23 | ! 16/15 24 | ! 9/8 25 | ! 6/5 26 | ! 5/4 27 | ! 4/3 28 | ! 45/32 29 | ! 3/2 30 | ! 8/5 31 | ! 128/75 32 | ! 225/128 33 | ! 15/8 34 | ! 2/1 35 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/shuffle-a440-constant.kbm: -------------------------------------------------------------------------------- 1 | ! Template for a keyboard mapping 2 | ! 3 | ! Size of map. The pattern repeats every so many keys: 4 | 12 5 | ! First MIDI note number to retune: 6 | 0 7 | ! Last MIDI note number to retune: 8 | 127 9 | ! Middle note where the first entry of the mapping is mapped to: 10 | 60 11 | ! Reference note for which frequency is given: 12 | 69 13 | ! Frequency to tune the above note to (floating point e.g. 440.0): 14 | 440.0 15 | ! Scale degree to consider as formal octave (determines difference in pitch 16 | ! between adjacent mapping patterns): 17 | 12 18 | ! Mapping. 19 | ! The numbers represent scale degrees mapped to keys. The first entry is for 20 | ! the given middle note, the next for subsequent higher keys. 21 | ! For an unmapped key, put in an "x". At the end, unmapped keys may be left out. 22 | 0 23 | 2 24 | 1 25 | 3 26 | 4 27 | 6 28 | 5 29 | 7 30 | 8 31 | 9 32 | 11 33 | 10 34 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/data/zeus22.scl: -------------------------------------------------------------------------------- 1 | ! zeus22.scl 2 | Zeus[22] hobbit (121/120&176/175) in POTE tuning 3 | 22 4 | ! 5 | 47.21796 6 | 109.87010 7 | 157.08806 8 | 230.88883 9 | 266.95816 10 | 314.17612 11 | 387.97689 12 | 424.04622 13 | 497.84699 14 | 545.06495 15 | 592.28291 16 | 654.93505 17 | 702.15301 18 | 775.95378 19 | 812.02311 20 | 885.82388 21 | 933.04184 22 | 969.11117 23 | 1042.91194 24 | 1090.12990 25 | 1152.78204 26 | 2/1 27 | ! 28 | !! prezeus22.scl 29 | ! Zeus[22] transversal 30 | ! 22 31 | !! 32 | ! 33/32 33 | ! 16/15 34 | ! 11/10 35 | ! 8/7 36 | ! 64/55 37 | ! 77/64 38 | ! 5/4 39 | ! 14/11 40 | ! 4/3 41 | ! 11/8 42 | ! 45/32 43 | ! 16/11 44 | ! 3/2 45 | ! 11/7 46 | ! 8/5 47 | ! 5/3 48 | ! 55/32 49 | ! 7/4 50 | ! 11/6 51 | ! 15/8 52 | ! 64/33 53 | ! 2/1 54 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/symbolcheck1.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Make sure we don't have duplicated symbols by compiling a pair of objects 3 | ** and linking them 4 | */ 5 | 6 | #include "Tunings.h" 7 | 8 | double symbolcheck1() { 9 | auto k = Tunings::tuneNoteTo( 60, 100 ); 10 | return k.tuningFrequency; 11 | } 12 | -------------------------------------------------------------------------------- /src/c/tuning-library/tests/symbolcheck2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ** Make sure we don't have duplicated symbols by compiling a pair of objects 3 | ** and linking them 4 | */ 5 | 6 | #include 7 | #include 8 | #include "Tunings.h" 9 | 10 | double symbolcheck2() { 11 | auto k = Tunings::tuneNoteTo( 60, 200 ); 12 | return k.tuningFrequency; 13 | } 14 | 15 | int main( int argc, char **argv ) 16 | { 17 | extern double symbolcheck1(); 18 | std::cout << "100 and 200 are " << symbolcheck1() << " and " << symbolcheck2 () << std::endl; 19 | } 20 | -------------------------------------------------------------------------------- /src/dx7-awn.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | 4 | class DX7 extends WAMController 5 | { 6 | constructor (actx, options) { 7 | options = options || {}; 8 | options.numberOfInputs = 0; 9 | options.numberOfOutputs = 1; 10 | options.outputChannelCount = [1]; 11 | 12 | super(actx, "DX7", options); 13 | } 14 | 15 | static importScripts (actx) { 16 | var origin = location.origin + "/"; 17 | return new Promise( (resolve) => { 18 | actx.audioWorklet.addModule(origin + "dx7/wasm/dx7.wasm.js").then(() => { 19 | actx.audioWorklet.addModule(origin + "dx7/wasm/dx7.js").then(() => { 20 | actx.audioWorklet.addModule(origin + "../wamsdk/wam-processor.js").then(() => { 21 | actx.audioWorklet.addModule(origin + "dx7/dx7-awp.js").then(() => { 22 | resolve(); 23 | }) }) }) }); 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/dx7-awp.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // Jari Kleimola 2017-18 (jari@webaudiomodules.org) 3 | 4 | class DX7AWP extends AudioWorkletGlobalScope.WAMProcessor 5 | { 6 | constructor(options) { 7 | options = options || {} 8 | options.mod = AudioWorkletGlobalScope.WAM.DX7; 9 | super(options); 10 | this.numOutChannels = [1]; 11 | } 12 | } 13 | 14 | registerProcessor("DX7", DX7AWP); 15 | -------------------------------------------------------------------------------- /src/dx7-lib.js: -------------------------------------------------------------------------------- 1 | // webDX7 (WAM) 2 | // bank and patch handling 3 | // Jari Kleimola 2017 (jari@webaudiomodules.org) 4 | 5 | var DX7Library = function () 6 | { 7 | this.banks = banklist; 8 | this.patches = []; 9 | this.bank = []; 10 | var self = this; 11 | 12 | this.load = filename => { 13 | var url = "dx7/presets/" + filename; 14 | return new Promise( (resolve,reject) => { 15 | fetch(url).then(resp => { 16 | resp.arrayBuffer().then(data => { 17 | 18 | // -- packed bank with sysex frame (32 patches) 19 | if (data.byteLength != 4104) reject(); 20 | self.patches = []; 21 | self.bank = []; 22 | data = new Uint8Array(data); 23 | data = data.subarray(6,4102); 24 | for (var i=0; i<32; i++) { 25 | var offset = i*128; 26 | var voice = data.subarray(offset,offset+128); 27 | var name = extractName(voice); 28 | self.patches.push(name); 29 | self.bank.push(voice); 30 | } 31 | resolve(self.patches); 32 | }) }) }); 33 | } 34 | 35 | function extractName (data,offset) { 36 | var offset = offset || 118; // 118 for packed, 145 for unpacked 37 | var name = ""; 38 | for (var n = 0; n < 10; n++) { 39 | var c = data[n + offset]; 40 | switch (c) { 41 | case 92: c = 'Y'; break; // yen 42 | case 126: c = '>'; break; // >> 43 | case 127: c = '<'; break; // << 44 | default: if (c < 32 || c > 127) c = 32; break; 45 | } 46 | name += String.fromCharCode(c); 47 | } 48 | return name; 49 | } 50 | } 51 | 52 | var banklist = [ 53 | "rom1A.syx", 54 | "steinber.syx", 55 | "SynprezFM_03.syx", 56 | "weird1.syx", 57 | "solange2.syx", 58 | "analog1.syx", 59 | "Dexed_01.syx" 60 | ]; 61 | -------------------------------------------------------------------------------- /src/wamsdk/processor.cpp: -------------------------------------------------------------------------------- 1 | // Web Audio Modules (WAMs) 2 | // WAM::Processor 3 | // 4 | // jari kleimola and oli larkin 2015-2017 5 | // jari@webaudiomodules.org 6 | // 7 | #include "processor.h" 8 | using namespace WAM; 9 | 10 | EMSCRIPTEN_KEEPALIVE const char* Processor::init(uint32_t bufsize, uint32_t sr, void* desc) 11 | { 12 | m_bufsize = bufsize; 13 | m_sr = sr; 14 | return 0; // custom descriptor not defined at DSP side 15 | } 16 | 17 | // JavaScript glue 18 | extern "C" 19 | { 20 | EMSCRIPTEN_KEEPALIVE const char* wam_init(Processor* proc, uint32_t bufsize, uint32_t sr, void* desc) { return proc->init(bufsize,sr,desc); } 21 | EMSCRIPTEN_KEEPALIVE void wam_terminate(Processor* proc) { proc->terminate(); } 22 | EMSCRIPTEN_KEEPALIVE void wam_resize(Processor* proc, uint32_t bufsize) { proc->resize(bufsize); } 23 | EMSCRIPTEN_KEEPALIVE void wam_onparam(Processor* proc, uint32_t idparam, float value) { proc->onParam(idparam, value); } 24 | EMSCRIPTEN_KEEPALIVE void wam_onmidi(Processor* proc, byte status, byte data1, byte data2) { proc->onMidi(status, data1, data2); } 25 | EMSCRIPTEN_KEEPALIVE void wam_onsysex(Processor* proc, byte* msg, uint32_t size) { proc->onSysex(msg, size); } 26 | EMSCRIPTEN_KEEPALIVE void wam_onprocess(Processor* proc, AudioBus* audio, void* data) { proc->onProcess(audio, data); } 27 | EMSCRIPTEN_KEEPALIVE void wam_onpatch(Processor* proc, void* data, uint32_t size) { proc->onPatch(data, size); } 28 | EMSCRIPTEN_KEEPALIVE void wam_onmessageN(Processor* proc, char* verb, char* res, double data) { proc->onMessage(verb, res, data); } 29 | EMSCRIPTEN_KEEPALIVE void wam_onmessageS(Processor* proc, char* verb, char* res, char* data) { proc->onMessage(verb, res, data); } 30 | EMSCRIPTEN_KEEPALIVE void wam_onmessageA(Processor* proc, char* verb, char* res, void* data, uint32_t size) { proc->onMessage(verb, res, data, size); } 31 | } 32 | 33 | // for debugging 34 | extern "C" 35 | { 36 | void wam_logs(const char* s) { EM_ASM_INT(Module.print(Pointer_stringify($0)), s); } 37 | void wam_logi(int i) { EM_ASM_INT(Module.print($0), i); } 38 | } 39 | -------------------------------------------------------------------------------- /src/wamsdk/processor.h: -------------------------------------------------------------------------------- 1 | // Web Audio Modules (WAMs) 2 | // WAM::Processor 3 | // 4 | // jari kleimola and oli larkin 2015-2017 5 | // jari@webaudiomodules.org 6 | // 7 | #ifndef _WAM_processor_h_ 8 | #define _WAM_processor_h_ 9 | 10 | #include 11 | #include 12 | 13 | namespace WAM { 14 | 15 | typedef unsigned char byte; 16 | typedef struct 17 | { 18 | float** inputs; 19 | float** outputs; 20 | } AudioBus; 21 | 22 | 23 | class Processor 24 | { 25 | // -- lifecycle 26 | public: 27 | Processor() {} 28 | virtual const char* init(uint32_t bufsize, uint32_t sr, void* desc); 29 | virtual void terminate() {} 30 | virtual void resize(uint32_t bufsize) {} 31 | virtual ~Processor() {} 32 | 33 | // -- audio and data streams 34 | public: 35 | virtual void onProcess(WAM::AudioBus* audio, void* data) = 0; 36 | virtual void onMidi(byte status, byte data1, byte data2) {} 37 | virtual void onSysex(byte* msg, uint32_t size) {} 38 | virtual void onMessage(char* verb, char* res, double data) {} 39 | virtual void onMessage(char* verb, char* res, char* data) {} 40 | virtual void onMessage(char* verb, char* res, void* data, uint32_t size) {} 41 | virtual void onParam(uint32_t idparam, double value) {} // todo: other datatypes 42 | 43 | // -- patches 44 | public: 45 | virtual void onPatch(void* data, uint32_t size) {} 46 | 47 | // -- controller interface 48 | protected: 49 | void postMessage(const char* verb, const char* resource, void* data) {} 50 | 51 | protected: 52 | uint32_t m_bufsize; 53 | uint32_t m_sr; 54 | int m_inChannels; 55 | int m_outChannels; 56 | }; 57 | 58 | // for debugging 59 | extern "C" 60 | { 61 | void wam_logs(const char* s); 62 | void wam_logi(int i); 63 | } 64 | 65 | } // namespace WAM 66 | #endif 67 | --------------------------------------------------------------------------------