UNPKG

reactive-synth-envelope-generator

Version:

WASM implementation of an envelope generator audio processing node compatible with the web audio API. Created for reactive-synth.

220 lines (212 loc) 5.45 kB
const bytesPerMemorySlot = Float32Array.BYTES_PER_ELEMENT; const renderQuantumSampleCount = 128; const stageNames = { 0: "rest", 1: "attack", 2: "hold", 3: "decay", 4: "sustain", 5: "release", }; registerProcessor( "reactive-synth-envelope-generator", class EnvelopeGenerator extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: "trigger", defaultValue: 0, automationRate: "a-rate", }, { name: "attackValue", defaultValue: 1, minValue: 0, maxValue: 1, automationRate: "a-rate", }, { name: "attackTime", defaultValue: 0.001, minValue: 0, maxValue: 10, automationRate: "a-rate", }, { name: "holdTime", minValue: 0, defaultValue: 0.0625, maxValue: 10, automationRate: "a-rate", }, { name: "decayTime", defaultValue: 0.125, minValue: 0, maxValue: 10, automationRate: "a-rate", }, { name: "sustainValue", defaultValue: 0.25, minValue: 0, maxValue: 1, automationRate: "a-rate", }, { name: "releaseTime", defaultValue: 0.25, minValue: 0, maxValue: 10, }, ]; } constructor(options) { super(options); this.port.onmessage = this.handleMessage.bind(this); this.sampleRate = options.sampleRate || 44100; this.triggerChangeMessage = { type: "trigger-change", value: false }; this.stateMessage = { type: "state", state: { stage: "rest", stageProgress: 0, outputValue: 0, }, }; this.manualTriggerOn = false; this.manualTriggerOnParameter = [1]; } handleMessage(event) { if (event.data && event.data.type === "get-state") { if (this.wasmModule){ this.wasmModule.exports.publish_state(this.internalProcessorPtr); } } if (event.data && event.data.type === "manual-trigger") { this.manualTriggerOn = event.data.value; } if (event.data && event.data.type === "wasm") { this.initWasmModule(event.data.wasmModule).then(() => this.port.postMessage({ type: "module-ready", value: true }) ); } } async initWasmModule(wasmModule) { wasmModule = await WebAssembly.compile(wasmModule); this.wasmModule = await WebAssembly.instantiate(wasmModule, { imports: { triggerChange: (t) => { this.triggerChangeMessage.value = t; this.port.postMessage(this.triggerChangeMessage); }, shareState: ( stage, stageProgress, outputValue, attackValue, attackTime, holdTime, decayTime, sustainValue, releaseTime ) => { this.stateMessage.state.stage = stageNames[stage]; this.stateMessage.state.stageProgress = stageProgress; this.stateMessage.state.outputValue = outputValue; this.stateMessage.state.parameters = { attackValue, attackTime, holdTime, decayTime, sustainValue, releaseTime, }; this.port.postMessage(this.stateMessage); }, }, }); this.internalProcessorPtr = this.wasmModule.exports.init( renderQuantumSampleCount, this.sampleRate ); this.float32WasmMemory = new Float32Array( this.wasmModule.exports.memory.buffer ); } process(_inputs, outputs, parameters) { if (this.wasmModule) { this.float32WasmMemory.set( this.manualTriggerOn ? this.manualTriggerOnParameter : parameters.trigger, this.wasmModule.exports.get_input_gate_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.attackValue, this.wasmModule.exports.get_attack_value_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.attackTime, this.wasmModule.exports.get_attack_time_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.holdTime, this.wasmModule.exports.get_hold_time_ptr(this.internalProcessorPtr) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.decayTime, this.wasmModule.exports.get_decay_time_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.sustainValue, this.wasmModule.exports.get_sustain_value_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); this.float32WasmMemory.set( parameters.releaseTime, this.wasmModule.exports.get_release_time_ptr( this.internalProcessorPtr ) / bytesPerMemorySlot ); const outputPointer = this.wasmModule.exports.process_quantum( this.internalProcessorPtr, this.manualTriggerOn ? this.manualTriggerOnParameter.length : parameters.trigger.length, parameters.attackValue.length, parameters.attackTime.length, parameters.holdTime.length, parameters.decayTime.length, parameters.sustainValue.length, parameters.releaseTime.length ) / bytesPerMemorySlot; for ( let channelIndex = 0; channelIndex < outputs[0].length; channelIndex++ ) { for ( let sample = 0; sample < outputs[0][channelIndex].length; sample++ ) { outputs[0][channelIndex][sample] = this.float32WasmMemory[ outputPointer + sample ]; } } } return true; } } );