-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from jariseon/master
audioworklet / wasm version
- Loading branch information
Showing
59 changed files
with
1,170 additions
and
17,008 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist/dx7/presets/*.syx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,42 @@ | ||
# webdx7 | ||
virtual Yamaha DX7 synth in a browser | ||
# webdx7 (AudioWorklet/WASM edition) | ||
virtual Yamaha DX7 synth in a browser. | ||
|
||
[demo](https://webaudiomodules.org/wamsynths/dx7) | ||
|
||
other WAM demos at [webaudiomodules.org/wamsynths](https://webaudiomodules.org/wamsynths/) | ||
|
||
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). | ||
|
||
## info | ||
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. | ||
|
||
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. | ||
|
||
## prerequisites | ||
* WASM [toolchain](http://webassembly.org/getting-started/developers-guide/) | ||
* [node.js](https://nodejs.org/en/download/) | ||
|
||
## building | ||
|
||
### #1 wasm compilation | ||
``` | ||
cd build | ||
export PATH=$PATH:/to/emsdk/where/emmake/resides | ||
emmake make | ||
``` | ||
step #1 creates two files, **dx7.wasm** and **dx7.js**. WASM binary cannot currently be loaded into AudioWorkletProcessor (AWP) directly, so let's encode it into a JS Uint8Array in step #2. | ||
|
||
### #2 encoding | ||
``` | ||
node encode-wasm.js dx7.wasm | ||
``` | ||
step #2 produces **dx7.wasm.js** file, which can be loaded into AWP. | ||
|
||
|
||
### done | ||
We have now **dx7.wasm.js** (from step #2) and its loader **dx7.js** (from step #1). Copy these files to `dist/dx7/wasm` folder, and copy some DX7 sysex files into `dist/dx7/presets`. See readme there for instructions. | ||
|
||
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. | ||
|
||
|
||
|
||
more at http://webaudiomodules.org |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Web Audio Modules | ||
# wasm makefile for msfa DX7 | ||
|
||
TARGET = ./dx7.js | ||
API = ../src/wamsdk | ||
MSFA = ../src/c/msfa | ||
|
||
SRC = ../src/c/dx7.cc $(API)/processor.cpp \ | ||
$(MSFA)/synth_unit.cc $(MSFA)/ringbuffer.cc $(MSFA)/patch.cc \ | ||
$(MSFA)/lfo.cc $(MSFA)/dx7note.cc $(MSFA)/freqlut.cc $(MSFA)/sin.cc $(MSFA)/exp2.cc \ | ||
$(MSFA)/fm_core.cc $(MSFA)/pitchenv.cc $(MSFA)/env.cc $(MSFA)/fm_op_kernel.cc | ||
|
||
CFLAGS = -I$(API) -I$(MSFA) -Wno-logical-op-parentheses | ||
LDFLAGS = -O2 | ||
JSFLAGS = -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s BINARYEN_ASYNC_COMPILATION=0 -s EXPORT_NAME="'AudioWorkletGlobalScope.WAM.DX7'" | ||
|
||
$(TARGET): $(OBJECTS) | ||
$(CC) $(CFLAGS) $(LDFLAGS) $(JSFLAGS) -o $@ $(SRC) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
if (process.argv.length != 3) { | ||
console.log("usage: node encode-wasm.js mymodule.wasm"); | ||
return; | ||
} | ||
|
||
let wasmName = process.argv[2]; | ||
let name = wasmName.substr(0, wasmName.length - 5).toUpperCase(); | ||
|
||
// thanks to Steven Yi / Csound | ||
// | ||
fs = require('fs'); | ||
let wasmData = fs.readFileSync(wasmName); | ||
let wasmStr = wasmData.join(","); | ||
let wasmOut = "AudioWorkletGlobalScope.WAM = AudioWorkletGlobalScope.WAM || {}\n"; | ||
wasmOut += "AudioWorkletGlobalScope.WAM." + name + " = { ENVIRONMENT: 'WEB' }\n"; | ||
wasmOut += "AudioWorkletGlobalScope.WAM." + name + ".wasmBinary = new Uint8Array([" + wasmStr + "]);"; | ||
fs.writeFileSync(wasmName + ".js", wasmOut); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// AudioWorklet polyfill | ||
// Jari Kleimola 2017-18 (jari@webaudiomodules.org) | ||
// | ||
var AWGS = { processors:[] } | ||
|
||
// -------------------------------------------------------------------------- | ||
// | ||
// | ||
AWGS.AudioWorkletGlobalScope = function () { | ||
var ctors = {}; // node name to processor definition map | ||
|
||
function registerOnWorker(name, ctor) { | ||
if (!ctors[name]) { | ||
ctors[name] = ctor; | ||
postMessage({ type:"register", name:name, descriptor:ctor.parameterDescriptors }); | ||
} | ||
else { | ||
postMessage({ type:"state", node:nodeID, state:"error" }); | ||
throw new Error("AlreadyRegistered"); | ||
} | ||
}; | ||
|
||
function constructOnWorker (name, port, options) { | ||
if (ctors[name]) { | ||
options = options || {} | ||
options._port = port; | ||
var processor = new ctors[name](options); | ||
if (!(processor instanceof AudioWorkletProcessor)) { | ||
postMessage({ type:"state", node:nodeID, state:"error" }); | ||
throw new Error("InvalidStateError"); | ||
} | ||
return processor; | ||
} | ||
else { | ||
postMessage({ type:"state", node:nodeID, state:"error" }); | ||
throw new Error("NotSupportedException"); | ||
} | ||
} | ||
|
||
class AudioWorkletProcessorPolyfill { | ||
constructor (options) { this.port = options._port; } | ||
process (inputs, outputs, params) {} | ||
} | ||
|
||
return { | ||
'AudioWorkletProcessor': AudioWorkletProcessorPolyfill, | ||
'registerProcessor': registerOnWorker, | ||
'_createProcessor': constructOnWorker | ||
} | ||
} | ||
|
||
|
||
AudioWorkletGlobalScope = AWGS.AudioWorkletGlobalScope(); | ||
AudioWorkletProcessor = AudioWorkletGlobalScope.AudioWorkletProcessor; | ||
registerProcessor = AudioWorkletGlobalScope.registerProcessor; | ||
sampleRate = 44100; | ||
hasSAB = true; | ||
|
||
onmessage = function (e) { | ||
var msg = e.data; | ||
switch (msg.type) { | ||
|
||
case "init": | ||
sampleRate = AudioWorkletGlobalScope.sampleRate = msg.sampleRate; | ||
break; | ||
|
||
case "import": | ||
importScripts(msg.url); | ||
postMessage({ type:"load", url:msg.url }); | ||
break; | ||
|
||
case "createProcessor": | ||
// -- slice io to match with SPN bufferlength | ||
var a = msg.args; | ||
var slices = []; | ||
var buflen = 128; | ||
var numSlices = (a.options.samplesPerBuffer/buflen)|0; | ||
|
||
hasSAB = (a.bufferCount === undefined); | ||
if (hasSAB) { | ||
for (var i=0; i<numSlices; i++) { | ||
var sliceStart = i * buflen; | ||
var sliceEnd = sliceStart + buflen; | ||
|
||
// -- create io buses | ||
function createBus (buffers) { | ||
var ports = []; | ||
for (var iport=0; iport<buffers.length; iport++) { | ||
var port = []; | ||
for (var channel=0; channel<buffers[iport].length; channel++) { | ||
var buf = new Float32Array(buffers[iport][channel]); | ||
port.push(buf.subarray(sliceStart, sliceEnd)); | ||
} | ||
ports.push(port); | ||
} | ||
return ports; | ||
} | ||
var inbus = createBus(a.bus.input); | ||
var outbus = createBus(a.bus.output); | ||
|
||
slices.push({ inbus:inbus, outbus:outbus }); | ||
} | ||
} | ||
|
||
// -- create processor | ||
var processor = AudioWorkletGlobalScope._createProcessor(a.name, e.ports[0], a.options); | ||
processor.node = a.node; | ||
processor.id = AWGS.processors.length; | ||
processor.numSlices = numSlices; | ||
AWGS.processors.push({ awp:processor, slices:slices }); | ||
postMessage({ type:"state", node:a.node, processor:processor.id, state:"running" }); | ||
break; | ||
|
||
case "process": | ||
var processor = AWGS.processors[msg.processor]; | ||
if (processor) { | ||
if (hasSAB) { | ||
for (var i=0; i<processor.slices.length; i++) { | ||
var slice = processor.slices[i]; | ||
processor.awp.process(slice.inbus, slice.outbus, []); | ||
} | ||
} | ||
else if (msg.buf.byteLength) { | ||
var buf = new Float32Array(msg.buf); | ||
var start = 0; | ||
for (var i=0; i<processor.awp.numSlices; i++) { | ||
var arr = buf.subarray(start, start + 128); | ||
processor.awp.process([], [[arr]], []); | ||
start += 128; | ||
} | ||
postMessage({ buf:msg.buf, type:"process", node:processor.awp.node }, [msg.buf]); | ||
} | ||
} | ||
break; | ||
} | ||
} |
Oops, something went wrong.