diff --git a/TODO b/TODO index ff62f05..d8d645a 100644 --- a/TODO +++ b/TODO @@ -19,3 +19,5 @@ * LV2 (and raw midi): uneven midi pitch bend, what to do? * VST3: noteOn, noteOff tuning, what to do? (https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1NoteOnEvent.html, https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1NoteOffEvent.html) * midi out +* vst3 tail, web process return true/false +* error checking etc. web, especially processor.js diff --git a/templates/web-make/Makefile b/templates/web-make/Makefile index 813009e..c918f25 100644 --- a/templates/web-make/Makefile +++ b/templates/web-make/Makefile @@ -8,20 +8,23 @@ LDFLAGS = \ -Wl,--lto-O3 \ -Wl,-strip-all \ -Wl,--export-table \ - -Wl,--export=wrapper_new \ - -Wl,--export=wrapper_free \ - -Wl,--export=wrapper_get_ins \ - -Wl,--export=wrapper_get_outs \ - -Wl,--export=wrapper_get_param_values \ - -Wl,--export=wrapper_process \ - -Wl,--export=wrapper_set_parameter \ + -Wl,--export=processor_new \ + -Wl,--export=processor_free \ + -Wl,--export=processor_get_ins \ + -Wl,--export=processor_get_outs \ + -Wl,--export=processor_get_param_values \ + -Wl,--export=processor_process \ + -Wl,--export=processor_set_parameter \ -Wl,-z,stack-size=$$((8*1024*1024)) \ -nostdlib -all: build/${BUNDLE_NAME}.wasm +all: build/${BUNDLE_NAME}.wasm build/${BUNDLE_NAME}_processor.js -build/${BUNDLE_NAME}.wasm: src/data.h src/memset.h src/plugin.h src/walloc.h src/wrapper.c | build - ${CC} src/wrapper.c -o $@ ${CFLAGS} ${LDFLAGS} ${CFLAGS_EXTRA} ${LIBS_EXTRA} +build/${BUNDLE_NAME}.wasm: src/data.h src/memset.h src/plugin.h src/walloc.h src/processor.c | build + ${CC} src/processor.c -o $@ ${CFLAGS} ${LDFLAGS} ${CFLAGS_EXTRA} ${LIBS_EXTRA} + +build/${BUNDLE_NAME}_processor.js: src/processor.js | build + cp $^ $@ build: mkdir -p $@ diff --git a/templates/web/src/wrapper.c b/templates/web/src/processor.c similarity index 86% rename from templates/web/src/wrapper.c rename to templates/web/src/processor.c index 42934d3..67fa829 100644 --- a/templates/web/src/wrapper.c +++ b/templates/web/src/processor.c @@ -27,7 +27,7 @@ typedef struct { #endif } instance; -instance * wrapper_new(float sample_rate) { +instance * processor_new(float sample_rate) { instance * i = malloc(sizeof(instance)); if (i == NULL) return NULL; @@ -66,32 +66,32 @@ instance * wrapper_new(float sample_rate) { return i; } -void wrapper_free(instance * i) { +void processor_free(instance * i) { plugin_fini(&i->p); if (i->mem) free(i->mem); free(i); } -float * wrapper_get_x_buf(instance * i) { +float * processor_get_x_buf(instance * i) { #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 return i->x_buf; #else - (void *)i; + (void)i; return NULL; #endif } -float * wrapper_get_y_buf(instance * i) { +float * processor_get_y_buf(instance * i) { #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 return i->y_buf; #else - (void *)i; + (void)i; return NULL; #endif } -float * wrapper_get_out_params(instance *i) { +float * processor_get_out_params(instance *i) { #if DATA_PRODUCT_PARAMETERS_OUTPUT_N > 0 return i->out_params; #else @@ -100,11 +100,11 @@ float * wrapper_get_out_params(instance *i) { #endif } -void wrapper_set_parameter(instance *i, int32_t index, float value) { +void processor_set_parameter(instance *i, int32_t index, float value) { plugin_set_parameter(&i->p, index, value); } -void wrapper_process(instance *i, int32_t n_samples) { +void processor_process(instance *i, int32_t n_samples) { #if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 const float **x = i->x; #else diff --git a/templates/web/src/processor.js b/templates/web/src/processor.js new file mode 100644 index 0000000..0e36b5a --- /dev/null +++ b/templates/web/src/processor.js @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022, 2024 Orastron Srl unipersonale + */ + +var buses = {{=JSON.stringify(it.product.buses, null, 2)}}; +var parameters = {{=JSON.stringify(it.product.parameters, null, 2)}}; + +class Processor extends AudioWorkletProcessor { + throwError(msg) { + this.port.postMessage({ type: "error", msg: msg); + throw msg; + } + + constructor(options) { + super(); + + var module = new WebAssembly.Module(options.processorOptions.wasmBytes); + var instance = new WebAssembly.Instance(module, { env: {} }); + this.module = instance.exports; + + this.instance = this.module.processor_new(sampleRate); + if (!this.instance) + this.throwError("Could not instantiate processor module"); + + function getBuffers(p, output) { + var ret = []; + for (var i = 0; i < buses.length; i++) { + if ((output && buses[i].direction == "input") || (!output && buses[i].direction == "output")) + continue; + if (buses[i].channels == "mono") { + ret.push([ new Float32Array(this.module.memory.buffer, p, 128) ]); + p += 128 * 4; + } else { + ret.push([ + new Float32Array(this.module.memory.buffer, p, 128), + new Float32Array(this.module.memory.buffer, p + 128 * 4, 128) + ]); + p += 2 * 128 * 4; + } + } + return ret; + } + this.x = getBuffers.call(this, this.module.processor_get_x_buf(this.instance), false); + this.y = getBuffers.call(this, this.module.processor_get_y_buf(this.instance), true); + + this.parametersIn = []; + for (var i = 0; i < parameters.length; i++) + if (parameters[i].direction == "input") + this.parametersIn.push({ index: i, value: NaN }); + + this.parametersOut = []; + for (var i = 0; i < parameters.length; i++) + if (parameters[i].direction == "output") + this.parametersOut.push({ index: i, value: NaN }); + + if (this.parametersOut.length > 0) + this.parametersOutValues = new Float32Array(this.module.memory.buffer, this.module.processor_get_out_params(this.instance), this.parametersOut.length); + + this.paramOutChangeMsg = { type: "paramOutChange", index: NaN, value: NaN }; + } + + process(input, outputs, params) { + for (var i = 0; i < this.parametersIn.length; i++) { + var index = this.parametersIn[i].index; + var parameter = parameters[index]; + var name = parameter.name; + var value = params[p.name][0]; + if (value != this.parametersIn[i].value) { + if (parameter.isBypass || parameter.toggled) + value = value > 0 ? 0 : 1; + else if (parameter.integer) + value = Math.round(value); + value = Math.min(Math.max(value, parameter.minimum), parameter.maximum); + if (value != this.parametersIn[i].value) { + this.module.processor_set_parameter(this.instance, index, value); + this.parametersIn[i].value = value; + } + } + } + + var n = outputs[0][0].length; + var i = 0; + while (i < n) { + var s = Math.min(n - i, 128); + + for (var j = 0; j < this.x.length; j++) { + var input = inputs[j]; + if (!input.length) { + for (var k = 0; k < this.x[j].length; k++) + this.x[j][k].fill(0); + } else { + for (var k = 0; k < this.x[j].length; k++) + if (k <= input.length) + this.x[j][k].set(input[k].subarray(i, s)); + else + this.x[j][k].fill(0); + } + } + + this.module.processor_process(this.instance, s); + + for (var j = 0; j < this.y.length; j++) { + var output = outputs[j]; + for (var k = 0; k < this.y[j].length; k++) + this.y[j][k].set(output[k].subarray(i, s)); + } + + i += s; + } + + for (var i = 0; i < this.parametersOut.length; i++) { + var index = this.parametersOut[i].index; + var value = this.parametersOutValues[i]; + if (value != this.parametersOut[i].value) { + this.paramOutChangeMsg.index = index; + this.paramOutChangeMsg.value = value; + this.port.postMessage(this.paramOutChangeMsg); + this.parametersOut[i].value = value; + } + } + + return false; + } + + static get parameterDescriptors() { + var ret = []; + for (var i = 0; i < this.parametersIn.length; i++) { + var p = parameters[this.parametersIn[i].index]; + ret.push({ name: p.name, minValue: p.minimum, maxValue: p.maximum, defaultValue: p.defaultValue, automationRate: "k-rate" }); + } + return ret; + } +} + +registerProcessor("{{=it.product.bundleName}}", Processor); diff --git a/templates/web/tibia-index.js b/templates/web/tibia-index.js index f9cf682..ccad3af 100644 --- a/templates/web/tibia-index.js +++ b/templates/web/tibia-index.js @@ -4,6 +4,7 @@ var sep = path.sep; module.exports = function (data, api) { api.copyFile(`src${sep}memset.h`, `src${sep}memset.h`); api.copyFile(`src${sep}walloc.h`, `src${sep}walloc.h`); - api.copyFile(`src${sep}wrapper.c`, `src${sep}wrapper.c`); + api.copyFile(`src${sep}processor.c`, `src${sep}processor.c`); api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data); + api.generateFileFromTemplateFile(`src${sep}processor.js`, `src${sep}processor.js`, data); };