diff --git a/templates/lv2/src/lv2.c b/templates/lv2/src/lv2.c index a46f40c..8452e39 100644 --- a/templates/lv2/src/lv2.c +++ b/templates/lv2/src/lv2.c @@ -254,7 +254,7 @@ static void run(LV2_Handle instance, uint32_t sample_count) { for (uint32_t j = 0; j < DATA_PRODUCT_CONTROL_OUTPUTS_N; j++) { uint32_t k = param_out_index[j]; if (i->c[k] != NULL) - i->c[k] = plugin_get_parameter(&i->p, k); + *i->c[k] = plugin_get_parameter(&i->p, k); } #endif } diff --git a/templates/web-make/Makefile b/templates/web-make/Makefile new file mode 100644 index 0000000..813009e --- /dev/null +++ b/templates/web-make/Makefile @@ -0,0 +1,32 @@ +include vars.mk + +CC = clang +CFLAGS = --target=wasm32 -flto -fvisibility=hidden -Ofast -Wall -Wextra -Wpedantic +LDFLAGS = \ + -Wl,--allow-undefined \ + -Wl,--no-entry \ + -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,-z,stack-size=$$((8*1024*1024)) \ + -nostdlib + +all: build/${BUNDLE_NAME}.wasm + +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: + mkdir -p $@ + +clean: + rm -fr build + +.PHONY: all clean diff --git a/templates/web-make/tibia-index.js b/templates/web-make/tibia-index.js new file mode 100644 index 0000000..53d79fb --- /dev/null +++ b/templates/web-make/tibia-index.js @@ -0,0 +1,4 @@ +module.exports = function (data, api) { + api.copyFile(`Makefile`, `Makefile`); + api.generateFileFromTemplateFile(`vars.mk`, `vars.mk`, data); +}; diff --git a/templates/web-make/vars.mk b/templates/web-make/vars.mk new file mode 100644 index 0000000..c845be1 --- /dev/null +++ b/templates/web-make/vars.mk @@ -0,0 +1,3 @@ +BUNDLE_NAME := {{=it.product.bundleName}} +CFLAGS_EXTRA := {{=it.make && it.make.cflags ? it.make.cflags : ""}} {{=it.web_make && it.web_make.cflags ? it.web_make.cflags : ""}} +LIBS_EXTRA := {{=it.make && it.make.libs ? it.make.libs : ""}} {{=it.web_make && it.web_make.libs ? it.web_make.libs : ""}} diff --git a/templates/web/src/data.h b/templates/web/src/data.h new file mode 100644 index 0000000..165a6e6 --- /dev/null +++ b/templates/web/src/data.h @@ -0,0 +1,26 @@ +#define DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").reduce((s, x) => s += x.channels == "mono" ? 1 : 2, 0)}} +#define DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").reduce((s, x) => s += x.channels == "mono" ? 1 : 2, 0)}} +#define DATA_PRODUCT_MIDI_INPUTS_N {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}} +#define DATA_PRODUCT_MIDI_OUTPUTS_N {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "output").length}} +#define DATA_PRODUCT_PARAMETERS_N {{=it.product.parameters.length}} +#define DATA_PRODUCT_PARAMETERS_OUTPUT_N {{=it.product.parameters.filter(x => x.direction == "output").length}} + +#if DATA_PRODUCT_PARAMETERS_N > 0 +static struct { + char out; + float def; +} param_data[DATA_PRODUCT_PARAMETERS_N] = { +{{~it.product.parameters :p}} + { + /* .out = */ {{=p.direction == "output" ? 1 : 0}}, + /* .def = */ {{=p.defaultValue.toExponential()}} + }, +{{~}} +}; +#endif + +#if DATA_PRODUCT_PARAMETERS_OUTPUT_N > 0 +static size_t param_out_index[DATA_PRODUCT_PARAMETERS_OUTPUT_N] = { + {{~it.product.parameters :p:i}}{{?p.direction == "output"}}{{=i}}, {{?}}{{~}} +}; +#endif diff --git a/templates/web/src/memset.h b/templates/web/src/memset.h new file mode 100644 index 0000000..0fb33f2 --- /dev/null +++ b/templates/web/src/memset.h @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2023 Orastron Srl unipersonale + */ + +void *memset(void *ptr, int value, size_t num) { + unsigned char *p = (unsigned char *)ptr; + for (size_t i = 0; i < num; i++) + p[i] = (unsigned char)value; + return ptr; +} diff --git a/templates/web/src/walloc.h b/templates/web/src/walloc.h new file mode 100644 index 0000000..cd5b5a7 --- /dev/null +++ b/templates/web/src/walloc.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2021, 2022, 2024 Orastron Srl unipersonale + */ + +void *malloc(size_t size); +void *realloc(void *ptr, size_t size); +void free(void *ptr); + +extern unsigned char __heap_base; + +typedef struct _header { + struct _header *next; + struct _header *prev; + char free; +} header; + +static char inited = 0; + +static size_t get_size(header *h) { + char *n = (char *)h->next; + return (n ? n : (char *)(__builtin_wasm_memory_size(0) << 16)) - (char *)h - sizeof(header); +} + +static void split_if_possible(header *h, size_t s, size_t size) { + if (s <= size + sizeof(header) + sizeof(header)) + return; + + header *hn = (header *)((char *)h + sizeof(header) + size); + hn->prev = h; + hn->next = h->next; + hn->free = 1; + h->next = hn; + if (hn->next) + hn->next->prev = hn; +} + +void *malloc(size_t size) { + if (size == 0) + return NULL; + + header *h = (header *)&__heap_base; + + if (!inited) { + h->next = NULL; + h->prev = NULL; + h->free = 1; + inited = 1; + } + + header *p; + for (; h; p = h, h = h->next) { + if (!h->free) + continue; + + size_t s = get_size(h); + if (s < size) + continue; + + split_if_possible(h, s, size); + + h->free = 0; + return (char *)h + sizeof(header); + } + + int32_t n = __builtin_wasm_memory_grow(0, ((size + sizeof(header) - 1) >> 16) + 1); + if (n < 0) + return NULL; + + if (p->free) + h = p; + else { + h = (header *)(n << 16); + p->next = h; + h->prev = p; + h->next = NULL; + } + + split_if_possible(h, get_size(h), size); + + h->free = 0; + return (char *)h + sizeof(header); +} + +void *realloc(void *ptr, size_t size) { + if (ptr == NULL) + return malloc(size); + + if (size == 0) { + free(ptr); + return NULL; + } + + header *h = (header *)((char *)ptr - sizeof(header)); + size_t s = get_size(h); + if (s >= size) + return ptr; + + void *p = malloc(size); + if (p == NULL) + return NULL; + + char *src = (char *)ptr; + char *dest = (char *)p; + for (size_t i = 0; i < s; i++) + dest[i] = src[i]; + + free(ptr); + + return p; +} + +void free(void *ptr) { + header *h = (header *)((char *)ptr - sizeof(header)); + h->free = 1; + + if (h->next && h->next->free) { + h->next = h->next->next; + if (h->next) + h->next->prev = h; + } + + if (h->prev && h->prev->free) { + h->prev->next = h->next; + if (h->next) + h->next->prev = h->prev; + } +} diff --git a/templates/web/src/wrapper.c b/templates/web/src/wrapper.c new file mode 100644 index 0000000..42934d3 --- /dev/null +++ b/templates/web/src/wrapper.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2022-2024 Orastron Srl unipersonale + */ + +#include +#include + +#include "data.h" +#include "plugin.h" + +#include "memset.h" +#include "walloc.h" + +typedef struct { + plugin p; + void * mem; +#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 + float x_buf[DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N * 128]; + const float * x[DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N]; +#endif +#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0 + float y_buf[DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N * 128]; + float * y[DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N]; +#endif +#if DATA_PRODUCT_PARAMETERS_OUTPUT_N > 0 + float out_params[DATA_PRODUCT_PARAMETERS_OUTPUT_N]; +#endif +} instance; + +instance * wrapper_new(float sample_rate) { + instance * i = malloc(sizeof(instance)); + if (i == NULL) + return NULL; + + plugin_init(&i->p); + +#if DATA_PRODUCT_PARAMETERS_N > 0 + for (size_t j = 0; j < DATA_PRODUCT_PARAMETERS_N; j++) + if (!param_data[j].out) + plugin_set_parameter(&i->p, j, param_data[j].def); +#endif + + plugin_set_sample_rate(&i->p, sample_rate); + size_t req = plugin_mem_req(&i->p); + if (req != 0) { + i->mem = malloc(req); + if (i->mem == NULL) { + plugin_fini(&i->p); + return NULL; + } + plugin_mem_set(&i->p, i->mem); + } else + i->mem = NULL; + + plugin_reset(&i->p); + +#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 + for (size_t j = 0; j < DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N; j++) + i->x[j] = i->x_buf + 128 * j; +#endif +#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0 + for (size_t j = 0; j < DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N; j++) + i->y[j] = i->y_buf + 128 * j; +#endif + + return i; +} + +void wrapper_free(instance * i) { + plugin_fini(&i->p); + if (i->mem) + free(i->mem); + free(i); +} + +float * wrapper_get_x_buf(instance * i) { +#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 + return i->x_buf; +#else + (void *)i; + return NULL; +#endif +} + +float * wrapper_get_y_buf(instance * i) { +#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 + return i->y_buf; +#else + (void *)i; + return NULL; +#endif +} + +float * wrapper_get_out_params(instance *i) { +#if DATA_PRODUCT_PARAMETERS_OUTPUT_N > 0 + return i->out_params; +#else + (void)i; + return NULL; +#endif +} + +void wrapper_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) { +#if DATA_PRODUCT_AUDIO_INPUT_CHANNELS_N > 0 + const float **x = i->x; +#else + const float **x = NULL; +#endif +#if DATA_PRODUCT_AUDIO_OUTPUT_CHANNELS_N > 0 + float **y = i->y; +#else + float **y = NULL; +#endif + + plugin_process(&i->p, x, y, n_samples); + +#if DATA_PRODUCT_PARAMETERS_OUTPUT_N > 0 + for (size_t j = 0; j < DATA_PRODUCT_PARAMETERS_OUTPUT_N; j++) + i->out_params[j] = plugin_get_parameter(&i->p, param_out_index[j]); +#endif +} diff --git a/templates/web/tibia-index.js b/templates/web/tibia-index.js new file mode 100644 index 0000000..f9cf682 --- /dev/null +++ b/templates/web/tibia-index.js @@ -0,0 +1,9 @@ +var path = require("path"); +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.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data); +}; diff --git a/test/make.json b/test/make.json deleted file mode 100644 index 6f2f74c..0000000 --- a/test/make.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "make": { - "libs": "-lm" - } -} diff --git a/test/plugin.h b/test/plugin.h index 3dde9f2..95288db 100644 --- a/test/plugin.h +++ b/test/plugin.h @@ -1,6 +1,3 @@ -#include -#include - typedef struct plugin { float sample_rate; size_t delay_line_length; @@ -14,6 +11,7 @@ typedef struct plugin { size_t delay_line_cur; float z1; float cutoff_k; + float yz1; } plugin; static void plugin_init(plugin *instance) { @@ -24,7 +22,8 @@ static void plugin_fini(plugin *instance) { static void plugin_set_sample_rate(plugin *instance, float sample_rate) { instance->sample_rate = sample_rate; - instance->delay_line_length = ceilf(sample_rate) + 1; + //safe approx instance->delay_line_length = ceilf(sample_rate) + 1; + instance->delay_line_length = (size_t)(sample_rate + 1.f) + 1; } static size_t plugin_mem_req(plugin *instance) { @@ -36,16 +35,19 @@ static void plugin_mem_set(plugin *instance, void *mem) { } static void plugin_reset(plugin *instance) { - memset(instance->delay_line, 0, instance->delay_line_length * sizeof(float)); + for (size_t i = 0; i < instance->delay_line_length; i++) + instance->delay_line[i] = 0.f; instance->delay_line_cur = 0; instance->z1 = 0.f; instance->cutoff_k = 1.f; + instance->yz1 = 0.f; } static void plugin_set_parameter(plugin *instance, size_t index, float value) { switch (index) { case 0: - instance->gain = powf(10.f, 0.05f * value); + //approx instance->gain = powf(10.f, 0.05f * value); + instance->gain = ((2.6039890429412597e-4f * value + 0.032131027163547855f) * value + 1.f) / ((0.0012705124328080768f * value - 0.0666763481312185f) * value + 1.f); break; case 1: instance->delay = 0.001f * value; @@ -60,8 +62,7 @@ static void plugin_set_parameter(plugin *instance, size_t index, float value) { } static float plugin_get_parameter(plugin *instance, size_t index) { - // no output parameters - return 0.f; + return instance->yz1; } static size_t calc_index(size_t cur, size_t delay, size_t len) { @@ -69,7 +70,8 @@ static size_t calc_index(size_t cur, size_t delay, size_t len) { } static void plugin_process(plugin *instance, const float **inputs, float **outputs, size_t n_samples) { - size_t delay = roundf(instance->sample_rate * instance->delay); + //approx size_t delay = roundf(instance->sample_rate * instance->delay); + size_t delay = (size_t)(instance->sample_rate * instance->delay + 0.5f); const float mA1 = instance->sample_rate / (instance->sample_rate + 6.283185307179586f * instance->cutoff * instance->cutoff_k); for (size_t i = 0; i < n_samples; i++) { instance->delay_line[instance->delay_line_cur] = inputs[0][i]; @@ -80,11 +82,13 @@ static void plugin_process(plugin *instance, const float **inputs, float **outpu const float y = x + mA1 * (instance->z1 - x); instance->z1 = y; outputs[0][i] = instance->bypass ? inputs[0][i] : instance->gain * y; + instance->yz1 = outputs[0][i]; } } static void plugin_note_on(plugin *instance, size_t index, uint8_t note, float velocity) { - instance->cutoff_k = powf(2.f, (1.f / 12.f) * (note - 60)); + //approx instance->cutoff_k = powf(2.f, (1.f / 12.f) * (note - 60)); + instance->cutoff_k = note < 64 ? (-0.19558034980097166f * note - 2.361735109225749f) / (note - 75.57552349522389f) : (393.95397927344214f - 7.660826245588588f * note) / (note - 139.0755234952239f); } static void plugin_note_off(plugin *instance, size_t index, uint8_t note, float velocity) { diff --git a/test/product.json b/test/product.json index 3bf1fd1..761c0af 100644 --- a/test/product.json +++ b/test/product.json @@ -108,6 +108,22 @@ "list": true, "unit": "", "map": "linear" + }, + { + "name": "yz1", + "shortName": "yz1", + "direction": "output", + "isBypass": false, + "isLatency": false, + "defaultValue": 0.0, + "minimum": -1.0, + "maximum": 1.0, + "toggled": false, + "optional": false, + "integer": false, + "list": false, + "unit": "", + "map": "linear" } ] } diff --git a/test/run.sh b/test/run.sh index ef694f5..c1350e2 100755 --- a/test/run.sh +++ b/test/run.sh @@ -2,8 +2,13 @@ dir=`dirname $0` $dir/../tibia $dir/product.json,$dir/company.json,$dir/vst3.json $dir/../templates/vst3 $dir/../out/vst3 -$dir/../tibia $dir/product.json,$dir/company.json,$dir/vst3.json,$dir/make.json,$dir/vst3-make.json $dir/../templates/vst3-make $dir/../out/vst3 +$dir/../tibia $dir/product.json,$dir/company.json,$dir/vst3.json,$dir/vst3-make.json $dir/../templates/vst3-make $dir/../out/vst3 cp $dir/plugin.h $dir/../out/vst3/src + $dir/../tibia $dir/product.json,$dir/company.json,$dir/lv2.json $dir/../templates/lv2 $dir/../out/lv2 -$dir/../tibia $dir/product.json,$dir/company.json,$dir/lv2.json,$dir/make.json $dir/../templates/lv2-make $dir/../out/lv2 +$dir/../tibia $dir/product.json,$dir/company.json,$dir/lv2.json $dir/../templates/lv2-make $dir/../out/lv2 cp $dir/plugin.h $dir/../out/lv2/src + +$dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/web $dir/../out/web +$dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/web-make $dir/../out/web +cp $dir/plugin.h $dir/../out/web/src