From 5a7c4bd78f33d0cd036d61e9f6d4a74df1676383 Mon Sep 17 00:00:00 2001 From: Eugene Cherny <146974283+eu-ch@users.noreply.github.com> Date: Mon, 11 Dec 2023 07:35:59 +0000 Subject: [PATCH 01/22] fix incorrect context deallocation test_signal.c (#147) Co-authored-by: ech --- tests/src/signal/test_signal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/signal/test_signal.c b/tests/src/signal/test_signal.c index c9cbb88e..a20655bd 100644 --- a/tests/src/signal/test_signal.c +++ b/tests/src/signal/test_signal.c @@ -51,7 +51,7 @@ int main(int argc, const char *argv[]) { } tinywav_close(&tw); - hv_delete(context); + hv_heavy_free(context); free(outBuffers); return 0; } From bc3a9e4c5601faa3584db74405cb09637f9d991e Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 12 Dec 2023 11:14:57 +0100 Subject: [PATCH 02/22] add web to emcc ENVIRONMENT --- hvcc/generators/c2js/c2js.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hvcc/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index 7ce9d699..06883800 100644 --- a/hvcc/generators/c2js/c2js.py +++ b/hvcc/generators/c2js/c2js.py @@ -242,7 +242,7 @@ def compile( output_name=f"{patch_name}_AudioLibWorklet", post_js_path=post_js_path, should_modularize=0, - environment="worker", + environment="web,worker", pre_js_path=pre_js_path, binaryen_async=0) From 4f073c3f3ff191c48cfb291700643751b286e517 Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 12 Dec 2023 11:17:44 +0100 Subject: [PATCH 03/22] shell not web --- hvcc/generators/c2js/c2js.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hvcc/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index 06883800..0235be5f 100644 --- a/hvcc/generators/c2js/c2js.py +++ b/hvcc/generators/c2js/c2js.py @@ -242,7 +242,7 @@ def compile( output_name=f"{patch_name}_AudioLibWorklet", post_js_path=post_js_path, should_modularize=0, - environment="web,worker", + environment="shell,worker", pre_js_path=pre_js_path, binaryen_async=0) From 8ad6cd614be46ffd557e4723d86ebe5fff0f9e6f Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 12 Dec 2023 14:43:27 +0100 Subject: [PATCH 04/22] add warning about broken Emscripten versions in the docs --- docs/03.gen.javascript.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/03.gen.javascript.md b/docs/03.gen.javascript.md index 183354ac..04fad9d6 100644 --- a/docs/03.gen.javascript.md +++ b/docs/03.gen.javascript.md @@ -1,5 +1,9 @@ # Javascript +> Emscripten versions 3.1.42 until 3.1.47 contain a bug that breaks our builds. +> +> Use a version earlier or later with this Generator for functional output. + ## Getting Started Heavy can provide a Javascript (JS) implementation of your patch using WebAssembly. The library provides a basic interface for processing audio, handling playback and sending or receiving messages. Both `AudioWorklet` and `ScriptProcessorNode` are supported. The old `asm.js` implementation has been deprecated. From 7bd3c734da7dc373be5a492442a698824b2ca1ba Mon Sep 17 00:00:00 2001 From: dreamer Date: Tue, 12 Dec 2023 15:22:56 +0100 Subject: [PATCH 05/22] make sure ScriptProcessorNode works --- hvcc/generators/c2js/template/hv_wrapper.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hvcc/generators/c2js/template/hv_wrapper.js b/hvcc/generators/c2js/template/hv_wrapper.js index 2d997ce7..167e0004 100644 --- a/hvcc/generators/c2js/template/hv_wrapper.js +++ b/hvcc/generators/c2js/template/hv_wrapper.js @@ -65,12 +65,20 @@ AudioLibLoader.prototype.init = function(options) { } AudioLibLoader.prototype.start = function() { - this.webAudioContext.resume(); + if (this.audiolib) { + this.webAudioProcessor.connect(this.webAudioContext.destination); + } else { + this.webAudioContext.resume(); + } this.isPlaying = true; } AudioLibLoader.prototype.stop = function() { - this.webAudioContext.suspend(); + if (this.audiolib) { + this.webAudioProcessor.disconnect(this.webAudioContext.destination); + } else { + this.webAudioContext.suspend(); + } this.isPlaying = false; } From 94a00f41b850b0d872391d2c4cd52cb02b6ca725 Mon Sep 17 00:00:00 2001 From: dreamer Date: Wed, 13 Dec 2023 08:46:13 +0100 Subject: [PATCH 06/22] use port.postMessage instead of callbacks --- hvcc/generators/c2js/template/hv_worklet.js | 61 ++++++++++----------- hvcc/generators/c2js/template/hv_wrapper.js | 10 +++- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/hvcc/generators/c2js/template/hv_worklet.js b/hvcc/generators/c2js/template/hv_worklet.js index 0feaa235..929af0fa 100644 --- a/hvcc/generators/c2js/template/hv_worklet.js +++ b/hvcc/generators/c2js/template/hv_worklet.js @@ -17,13 +17,8 @@ class {{name}}_AudioLibWorklet extends AudioWorkletProcessor { // instantiate heavy context this.heavyContext = _hv_{{name}}_new_with_options(this.sampleRate, {{pool_sizes_kb.internal}}, {{pool_sizes_kb.inputQueue}}, {{pool_sizes_kb.outputQueue}}); - if (processorOptions.printHook) { - this.setPrintHook(new Function(processorOptions.printHook)); - } - - if(processorOptions.sendHook) { - this.setSendHook(new Function(processorOptions.sendHook)); - } + this.setPrintHook(); + this.setSendHook(); // allocate temporary buffers (pointer size is 4 bytes in javascript) var lengthInSamples = this.blockSize * this.getNumOutputChannels(); @@ -77,42 +72,46 @@ class {{name}}_AudioLibWorklet extends AudioWorkletProcessor { return (this.heavyContext) ? _hv_getNumOutputChannels(this.heavyContext) : -1; } - setPrintHook(hook) { + setPrintHook() { if (!this.heavyContext) { console.error("heavy: Can't set Print Hook, no Heavy Context instantiated"); return; } - if (hook) { - // typedef void (HvPrintHook_t) (HeavyContextInterface *context, const char *printName, const char *str, const HvMessage *msg); - var printHook = addFunction(function(context, printName, str, msg) { - // Converts Heavy print callback to a printable message - var timeInSecs =_hv_samplesToMilliseconds(context, _hv_msg_getTimestamp(msg)) / 1000.0; - var m = UTF8ToString(printName) + " [" + timeInSecs.toFixed(3) + "]: " + UTF8ToString(str); - hook(m); - }, - "viiii" - ); - _hv_setPrintHook(this.heavyContext, printHook); - } + var self = this; + // typedef void (HvPrintHook_t) (HeavyContextInterface *context, const char *printName, const char *str, const HvMessage *msg); + var printHook = addFunction(function(context, printName, str, msg) { + // Converts Heavy print callback to a printable message + var timeInSecs =_hv_samplesToMilliseconds(context, _hv_msg_getTimestamp(msg)) / 1000.0; + var m = UTF8ToString(printName) + " [" + timeInSecs.toFixed(3) + "]: " + UTF8ToString(str); + self.port.postMessage({ + type: 'printHook', + payload: m + }); + }, + "viiii" + ); + _hv_setPrintHook(this.heavyContext, printHook); } - setSendHook(hook) { + setSendHook() { if (!this.heavyContext) { console.error("heavy: Can't set Send Hook, no Heavy Context instantiated"); return; } - if (hook) { - // typedef void (HvSendHook_t) (HeavyContextInterface *context, const char *sendName, hv_uint32_t sendHash, const HvMessage *msg); - var sendHook = addFunction(function(context, sendName, sendHash, msg) { - // Converts sendhook callback to (sendName, float) message - hook(UTF8ToString(sendName), _hv_msg_getFloat(msg, 0)); - }, - "viiii" - ); - _hv_setSendHook(this.heavyContext, sendHook); - } + var self = this; + // typedef void (HvSendHook_t) (HeavyContextInterface *context, const char *sendName, hv_uint32_t sendHash, const HvMessage *msg); + var sendHook = addFunction(function(context, sendName, sendHash, msg) { + // Converts sendhook callback to (sendName, float) message + self.port.postMessage({ + type: 'sendHook', + payload: [UTF8ToString(sendName), _hv_msg_getFloat(msg, 0)] + }); + }, + "viiii" + ); + _hv_setSendHook(this.heavyContext, sendHook); } sendEvent(name) { diff --git a/hvcc/generators/c2js/template/hv_wrapper.js b/hvcc/generators/c2js/template/hv_wrapper.js index 167e0004..44abeef6 100644 --- a/hvcc/generators/c2js/template/hv_wrapper.js +++ b/hvcc/generators/c2js/template/hv_wrapper.js @@ -36,12 +36,16 @@ AudioLibLoader.prototype.init = function(options) { processorOptions: { sampleRate: this.webAudioContext.sampleRate, blockSize, - printHook: options.printHook && options.printHook.toString(), - sendHook: options.sendHook && options.sendHook.toString() } }); this.webAudioWorklet.port.onmessage = (event) => { - console.log('Message from {{name}}_AudioLibWorklet:', event.data); + if (event.data.type === 'printHook') { + options.printHook(event.data.payload); + } else if (event.data.type === 'sendHook') { + options.sendHook(event.data.payload[0], event.data.payload[1]) + } else { + console.log('Unhandled message from {{name}}_AudioLibWorklet:', event.data); + } }; this.webAudioWorklet.connect(this.webAudioContext.destination); } else { From c4aa48f45e27d216751a3875181593de8d75a430 Mon Sep 17 00:00:00 2001 From: dreamer Date: Wed, 13 Dec 2023 08:52:24 +0100 Subject: [PATCH 07/22] add check for print/sendHook validity --- hvcc/generators/c2js/template/hv_wrapper.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hvcc/generators/c2js/template/hv_wrapper.js b/hvcc/generators/c2js/template/hv_wrapper.js index 44abeef6..0eeef43f 100644 --- a/hvcc/generators/c2js/template/hv_wrapper.js +++ b/hvcc/generators/c2js/template/hv_wrapper.js @@ -39,10 +39,10 @@ AudioLibLoader.prototype.init = function(options) { } }); this.webAudioWorklet.port.onmessage = (event) => { - if (event.data.type === 'printHook') { + if (event.data.type === 'printHook' && options.printHook) { options.printHook(event.data.payload); - } else if (event.data.type === 'sendHook') { - options.sendHook(event.data.payload[0], event.data.payload[1]) + } else if (event.data.type === 'sendHook' && options.sendHook) { + options.sendHook(event.data.payload[0], event.data.payload[1]); } else { console.log('Unhandled message from {{name}}_AudioLibWorklet:', event.data); } From 627ea276c57811a3bd0d07edc0add199455af995 Mon Sep 17 00:00:00 2001 From: dreamer Date: Wed, 13 Dec 2023 08:54:22 +0100 Subject: [PATCH 08/22] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cae21eef..ed362313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ===== +Next Release +----- + +* JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs + 0.10.0 ----- From c52798cf0ae56289280fae7e98f6522f774cc587 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Sun, 17 Dec 2023 13:16:42 +0100 Subject: [PATCH 09/22] Bugfix/stripnote (#150) * enhance stripnote with second inlet functionality; improve test cases for stripnote * changelog * expand stripnote tests and use-cases * use == 0 instead --- CHANGELOG.md | 1 + hvcc/interpreters/pd2hv/libs/pd/stripnote.pd | 50 ++++++++++++++++---- tests/pd/control/test-stripnote.golden.txt | 6 +++ tests/pd/control/test-stripnote.pd | 46 +++++++++++++----- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed362313..89ab15b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Next Release ----- * JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs +* Bugfix: stripnote missing right inlet 0.10.0 ----- diff --git a/hvcc/interpreters/pd2hv/libs/pd/stripnote.pd b/hvcc/interpreters/pd2hv/libs/pd/stripnote.pd index a3b411da..737752a8 100644 --- a/hvcc/interpreters/pd2hv/libs/pd/stripnote.pd +++ b/hvcc/interpreters/pd2hv/libs/pd/stripnote.pd @@ -1,16 +1,46 @@ #N canvas 648 404 450 300 12; -#X obj 47 31 inlet; -#X obj 47 185 outlet; -#X obj 47 57 unpack f f; -#X obj 96 184 outlet; -#X obj 47 142 spigot; -#X obj 86 99 != 0; -#X obj 96 141 spigot; -#X connect 0 0 2 0; +#X obj 47 10 inlet; +#X obj 47 503 outlet; +#X obj 47 335 unpack f f; +#X obj 96 503 outlet; +#X obj 47 460 spigot; +#X obj 107 408 != 0; +#X obj 96 460 spigot; +#X obj 248 10 inlet; +#X obj 216 289 spigot; +#X obj 96 365 t f f; +#X obj 96 87 unpack f f; +#X obj 47 41 t a a; +#X obj 111 199 f; +#X msg 96 159 0; +#X msg 144 159 1; +#X obj 47 289 spigot; +#X obj 216 199 pack f f; +#X obj 96 117 t b f b; +#X obj 241 258 == 0; +#X connect 0 0 11 0; #X connect 2 0 4 0; -#X connect 2 1 5 0; -#X connect 2 1 6 0; +#X connect 2 1 9 0; #X connect 4 0 1 0; #X connect 5 0 4 1; #X connect 5 0 6 1; #X connect 6 0 3 0; +#X connect 6 0 16 1; +#X connect 7 0 16 1; +#X connect 8 0 2 0; +#X connect 9 0 6 0; +#X connect 9 1 5 0; +#X connect 10 0 17 0; +#X connect 10 1 14 0; +#X connect 11 0 15 0; +#X connect 11 1 10 0; +#X connect 12 0 15 1; +#X connect 12 0 18 0; +#X connect 13 0 12 1; +#X connect 14 0 12 1; +#X connect 15 0 2 0; +#X connect 16 0 8 0; +#X connect 17 0 13 0; +#X connect 17 1 16 0; +#X connect 17 2 12 0; +#X connect 18 0 8 1; diff --git a/tests/pd/control/test-stripnote.golden.txt b/tests/pd/control/test-stripnote.golden.txt index deb1db92..8650a3c9 100644 --- a/tests/pd/control/test-stripnote.golden.txt +++ b/tests/pd/control/test-stripnote.golden.txt @@ -1,2 +1,8 @@ +[@ 0.000] print: 20 +[@ 0.000] print: 10 +[@ 0.000] print: 50 +[@ 0.000] print: 40 +[@ 0.000] print: 50 +[@ 0.000] print: 15 [@ 0.000] print: 67.8 [@ 0.000] print: 34.5 diff --git a/tests/pd/control/test-stripnote.pd b/tests/pd/control/test-stripnote.pd index b4f82a8b..8165e0a5 100644 --- a/tests/pd/control/test-stripnote.pd +++ b/tests/pd/control/test-stripnote.pd @@ -1,14 +1,36 @@ -#N canvas 722 308 249 251 12; +#N canvas 479 256 559 429 12; #X obj 58 25 loadbang; -#X obj 58 50 t b b; -#X msg 58 85 23 0; -#X msg 101 85 34.5 67.8; -#X obj 58 120 stripnote; -#X obj 58 152 print; -#X connect 0 0 1 0; -#X connect 1 0 2 0; -#X connect 1 1 3 0; -#X connect 2 0 4 0; +#X msg 58 83 23 0; +#X msg 112 83 34.5 67.8; +#X obj 58 284 stripnote; +#X obj 58 316 print; +#X obj 274 114 t b b; +#X msg 274 139 10; +#X msg 306 139 20; +#X obj 341 113 t b b; +#X msg 373 138 0; +#X msg 341 138 9; +#X msg 220 83 40 50 60; +#X msg 166 112 15; +#X obj 58 50 t b b b b b b, f 39; +#X connect 0 0 13 0; +#X connect 1 0 3 0; +#X connect 2 0 3 0; #X connect 3 0 4 0; -#X connect 4 0 5 0; -#X connect 4 1 5 0; +#X connect 3 1 4 0; +#X connect 5 0 6 0; +#X connect 5 1 7 0; +#X connect 6 0 3 0; +#X connect 7 0 3 1; +#X connect 8 0 10 0; +#X connect 8 1 9 0; +#X connect 9 0 3 1; +#X connect 10 0 3 0; +#X connect 11 0 3 0; +#X connect 12 0 3 0; +#X connect 13 0 1 0; +#X connect 13 1 2 0; +#X connect 13 2 12 0; +#X connect 13 3 11 0; +#X connect 13 4 5 0; +#X connect 13 5 8 0; From 6e5e6548ae9c71236b723de380ad7eea1d8e78bd Mon Sep 17 00:00:00 2001 From: dreamer Date: Thu, 21 Dec 2023 11:22:55 +0100 Subject: [PATCH 10/22] correct doc about @hv_event --- docs/02.getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02.getting_started.md b/docs/02.getting_started.md index 9d9c65ea..91d74fe3 100644 --- a/docs/02.getting_started.md +++ b/docs/02.getting_started.md @@ -40,7 +40,7 @@ See the specific framework details for more information on output parameter supp ## Exposing Events -All (control) `[receive]` and `[r]` objects annotated with `@hv_event` will be exposed as events in the Unity target only. +All (control) `[receive]` and `[r]` objects annotated with `@hv_event` will be exposed as events in the Unity and Javascript targets only. ![events](img/docs_exposed_events.png) From 04515d7a6be0fa2a9f54207d4a2a209d121c4b3f Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Mon, 25 Dec 2023 01:03:27 +0100 Subject: [PATCH 11/22] use tinywav submodule; move all source files to src dir (#154) --- .gitmodules | 3 + tests/framework/base_control.py | 2 +- tests/framework/base_midi.py | 2 +- tests/framework/base_signal.py | 5 +- tests/framework/base_speed.py | 2 +- tests/src/signal/tinywav.c | 148 --------------------------- tests/src/signal/tinywav.h | 62 ----------- tests/{ => src}/test_control.c | 0 tests/{ => src}/test_midi.cpp | 0 tests/src/{signal => }/test_signal.c | 4 +- tests/{ => src}/test_speed.c | 0 tests/src/tinywav | 1 + 12 files changed, 12 insertions(+), 217 deletions(-) delete mode 100644 tests/src/signal/tinywav.c delete mode 100644 tests/src/signal/tinywav.h rename tests/{ => src}/test_control.c (100%) rename tests/{ => src}/test_midi.cpp (100%) rename tests/src/{signal => }/test_signal.c (97%) rename tests/{ => src}/test_speed.c (100%) create mode 160000 tests/src/tinywav diff --git a/.gitmodules b/.gitmodules index b1afe5b4..a6f90b24 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "tests/src/midifile"] path = tests/src/midifile url = https://github.com/craigsapp/midifile.git +[submodule "tests/src/tinywav"] + path = tests/src/tinywav + url = https://github.com/mhroth/tinywav.git diff --git a/tests/framework/base_control.py b/tests/framework/base_control.py index 3fbcac69..2bc15bf9 100644 --- a/tests/framework/base_control.py +++ b/tests/framework/base_control.py @@ -81,7 +81,7 @@ def _test_control_patch(self, pd_file, num_iterations=1, allow_warnings=True, fa # copy over additional C assets c_src_dir = os.path.join(out_dir, "c") - shutil.copy2(os.path.join(self.SCRIPT_DIR, "test_control.c"), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/test_control.c"), c_src_dir) # prepare the clang command c_sources = os.listdir(c_src_dir) diff --git a/tests/framework/base_midi.py b/tests/framework/base_midi.py index 00b1542d..c5bd2156 100644 --- a/tests/framework/base_midi.py +++ b/tests/framework/base_midi.py @@ -82,7 +82,7 @@ def _test_midi_patch(self, pd_file, num_iterations=1, allow_warnings=True, fail_ # copy over additional C assets c_src_dir = os.path.join(out_dir, "c") - shutil.copy2(os.path.join(self.SCRIPT_DIR, "test_midi.cpp"), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/test_midi.cpp"), c_src_dir) shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/midifile/include/MidiFile.h"), c_src_dir) shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/midifile/include/MidiEventList.h"), c_src_dir) shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/midifile/include/MidiEvent.h"), c_src_dir) diff --git a/tests/framework/base_signal.py b/tests/framework/base_signal.py index 368874cf..70c767c3 100644 --- a/tests/framework/base_signal.py +++ b/tests/framework/base_signal.py @@ -87,8 +87,9 @@ def _test_signal_patch(self, pd_file): # copy over additional C assets c_src_dir = os.path.join(out_dir, "c") - for c in os.listdir(os.path.join(self.SCRIPT_DIR, "src", "signal")): - shutil.copy2(os.path.join(self.SCRIPT_DIR, "src", "signal", c), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/test_signal.c"), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/tinywav/tinywav.h"), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/tinywav/tinywav.c"), c_src_dir) # prepare the clang command source_files = os.listdir(c_src_dir) diff --git a/tests/framework/base_speed.py b/tests/framework/base_speed.py index ff544f42..3eae87cb 100644 --- a/tests/framework/base_speed.py +++ b/tests/framework/base_speed.py @@ -63,7 +63,7 @@ def _test_speed_patch(self, pd_file): c_src_dir = os.path.join(out_dir, "c") # copy additional source - shutil.copy2(os.path.join(self.SCRIPT_DIR, "test_speed.c"), c_src_dir) + shutil.copy2(os.path.join(self.SCRIPT_DIR, "src/test_speed.c"), c_src_dir) c_sources = [os.path.join(c_src_dir, c) for c in os.listdir(c_src_dir) if c.endswith(".c")] diff --git a/tests/src/signal/tinywav.c b/tests/src/signal/tinywav.c deleted file mode 100644 index af495521..00000000 --- a/tests/src/signal/tinywav.c +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright (c) 2015, Martin Roth (mhroth@gmail.com) - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - - -#include -#include "tinywav.h" - -typedef struct TinyWavHeader { - uint32_t ChunkID; - uint32_t ChunkSize; - uint32_t Format; - uint32_t Subchunk1ID; - uint32_t Subchunk1Size; - uint16_t AudioFormat; - uint16_t NumChannels; - uint32_t SampleRate; - uint32_t ByteRate; - uint16_t BlockAlign; - uint16_t BitsPerSample; - uint32_t Subchunk2ID; - uint32_t Subchunk2Size; -} TinyWavHeader; - -int tinywav_new(TinyWav *tw, - int16_t numChannels, int32_t samplerate, - TinyWavSampleFormat sampFmt, TinyWavChannelFormat chanFmt, - const char *path) { - tw->f = fopen(path, "w"); - tw->numChannels = numChannels; - tw->totalFramesWritten = 0; - tw->sampFmt = sampFmt; - tw->chanFmt = chanFmt; - - // prepare WAV header - TinyWavHeader h; - h.ChunkID = htonl(0x52494646); // "RIFF" - h.ChunkSize = 0; // fill this in on file-close - h.Format = htonl(0x57415645); // "WAVE" - h.Subchunk1ID = htonl(0x666d7420); // "fmt " - h.Subchunk1Size = 16; // PCM - h.AudioFormat = (tw->sampFmt-1); // 1 PCM, 3 IEEE float - h.NumChannels = numChannels; - h.SampleRate = samplerate; - h.ByteRate = samplerate * numChannels * tw->sampFmt; - h.BlockAlign = numChannels * tw->sampFmt; - h.BitsPerSample = 8*tw->sampFmt; - h.Subchunk2ID = htonl(0x64617461); // "data" - h.Subchunk2Size = 0; // fill this in on file-close - - // write WAV header - fwrite(&h, sizeof(TinyWavHeader), 1, tw->f); - - return 0; -} - -size_t tinywav_write_f(TinyWav *tw, void *f, int len) { - switch (tw->sampFmt) { - case TW_INT16: { - int16_t z[tw->numChannels*len]; - switch (tw->chanFmt) { - case TW_INTERLEAVED: { - return 0; - } - case TW_INLINE: { - const float *const x = (const float *const) f; - for (int i = 0, k = 0; i < len; ++i) { - for (int j = 0; j < tw->numChannels; ++j) { - z[k++] = (int16_t) (x[j*len+i] * 32767.0f); - } - } - break; - } - case TW_SPLIT: { - const float **const x = (const float **const) f; - for (int i = 0, k = 0; i < len; ++i) { - for (int j = 0; j < tw->numChannels; ++j) { - z[k++] = (int16_t) (x[j][i] * 32767.0f); - } - } - break; - } - default: return 0; - } - - tw->totalFramesWritten += len; - return fwrite(z, sizeof(int16_t), tw->numChannels*len, tw->f); - break; - } - case TW_FLOAT32: { - float z[tw->numChannels*len]; - switch (tw->chanFmt) { - case TW_INTERLEAVED: { - return 0; - } - case TW_INLINE: { - const float *const x = (const float *const) f; - for (int i = 0, k = 0; i < len; ++i) { - for (int j = 0; j < tw->numChannels; ++j) { - z[k++] = x[j*len+i]; - } - } - break; - } - case TW_SPLIT: { - const float **const x = (const float **const) f; - for (int i = 0, k = 0; i < len; ++i) { - for (int j = 0; j < tw->numChannels; ++j) { - z[k++] = x[j][i]; - } - } - break; - } - default: return 0; - } - - tw->totalFramesWritten += len; - return fwrite(z, sizeof(float), tw->numChannels*len, tw->f); - } - default: return 0; - } -} - -void tinywav_close(TinyWav *tw) { - uint32_t data_len = tw->totalFramesWritten * tw->numChannels * tw->sampFmt; - - // set length of data - fseek(tw->f, 4, SEEK_SET); - uint32_t chunkSize_len = 36 + data_len; - fwrite(&chunkSize_len, sizeof(uint32_t), 1, tw->f); - - fseek(tw->f, 40, SEEK_SET); - fwrite(&data_len, sizeof(uint32_t), 1, tw->f); - - fclose(tw->f); -} diff --git a/tests/src/signal/tinywav.h b/tests/src/signal/tinywav.h deleted file mode 100644 index 337d343a..00000000 --- a/tests/src/signal/tinywav.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2015, Martin Roth (mhroth@gmail.com) - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - - #ifndef _TINY_WAV_ - #define _TINY_WAV_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// http://soundfile.sapp.org/doc/WaveFormat/ -// http://www-mmsp.ece.mcgill.ca/documents/AudioFormats/WAVE/WAVE.html - -typedef enum TinyWavChannelFormat { - TW_INTERLEAVED, // channel buffer is interleaved e.g. [LRLRLRLR] - TW_INLINE, // channel buffer is inlined e.g. [LLLLRRRR] - TW_SPLIT // channel buffer is split e.g. [[LLLL],[RRRR]] -} TinyWavChannelFormat; - -typedef enum TinyWavSampleFormat { - TW_INT16 = 2, // two byte signed integer - TW_FLOAT32 = 4 // four byte IEEE float -} TinyWavSampleFormat; - -typedef struct TinyWav { - FILE *f; - int16_t numChannels; - uint32_t totalFramesWritten; - TinyWavChannelFormat chanFmt; - TinyWavSampleFormat sampFmt; -} TinyWav; - -int tinywav_new(TinyWav *tw, - int16_t numChannels, int32_t samplerate, - TinyWavSampleFormat sampFmt, TinyWavChannelFormat chanFmt, - const char *path); - -size_t tinywav_write_f(TinyWav *tw, void *f, int len); - -void tinywav_close(TinyWav *tw); - -#ifdef __cplusplus -} -#endif - -#endif // _TINY_WAV_ diff --git a/tests/test_control.c b/tests/src/test_control.c similarity index 100% rename from tests/test_control.c rename to tests/src/test_control.c diff --git a/tests/test_midi.cpp b/tests/src/test_midi.cpp similarity index 100% rename from tests/test_midi.cpp rename to tests/src/test_midi.cpp diff --git a/tests/src/signal/test_signal.c b/tests/src/test_signal.c similarity index 97% rename from tests/src/signal/test_signal.c rename to tests/src/test_signal.c index a20655bd..bd28d6e4 100644 --- a/tests/src/signal/test_signal.c +++ b/tests/src/test_signal.c @@ -35,7 +35,7 @@ int main(int argc, const char *argv[]) { hv_getNumOutputChannels(context) == 2); TinyWav tw; - tinywav_new(&tw, + tinywav_open_write(&tw, hv_getNumOutputChannels(context), (int32_t) hv_getSampleRate(context), TW_FLOAT32, TW_INLINE, outputPath); @@ -50,7 +50,7 @@ int main(int argc, const char *argv[]) { tinywav_write_f(&tw, outBuffers, blockSize); } - tinywav_close(&tw); + tinywav_close_write(&tw); hv_heavy_free(context); free(outBuffers); return 0; diff --git a/tests/test_speed.c b/tests/src/test_speed.c similarity index 100% rename from tests/test_speed.c rename to tests/src/test_speed.c diff --git a/tests/src/tinywav b/tests/src/tinywav new file mode 160000 index 00000000..3cbf9661 --- /dev/null +++ b/tests/src/tinywav @@ -0,0 +1 @@ +Subproject commit 3cbf9661d3c6bd14e59dba4ea5dd4d48c5b9a72e From d5ee155e2bf8ae8973d974335c5d0e44c0c47f18 Mon Sep 17 00:00:00 2001 From: dreamer Date: Mon, 25 Dec 2023 13:02:37 +0100 Subject: [PATCH 12/22] update readme --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b991d17c..2e2f2cc1 100644 --- a/README.md +++ b/README.md @@ -36,21 +36,25 @@ It has since then been expanded to provide further support for many different pl hvcc has been integrated into several projects and services. This allows to easily compile patches without having to install hvcc manually. -* [plugdata](https://plugdata.org/) - a new way to use Pure Data. Includes a full toolchain and targets Daisy, DPF and pd externals. -* [mod-cloud-builder](https://github.com/moddevices/mod-cloud-builder) - An online service for building LV2 plugins for the MOD platform. -* [OWL Patch Library](https://www.rebeltech.org/patch-library) - An online service for building OWL plugins (uses an old fork). +* [plugdata](https://plugdata.org/) - Modern interface for Pure Data. Includes a full cross-platform toolchain and targets Daisy, DPF and PD Externals. +* [mod-cloud-builder](https://github.com/moddevices/mod-cloud-builder) - Online service for building LV2 plugins for the MOD platform. +* [OWL Patch Library](https://www.rebeltech.org/patch-library) - Online service for building OWL plugins (uses an old fork). ## Requirements -python 3.8 until 3.12 +Python 3.8 up to 3.12 - * `jinja2` (for generator templating) - * `importlib_resources` (for reading static resources) - * `json2daisy` (for daisy integration) - * `tox` (for tests, optional) - * `numpy/scipy` (for tests, optional) - * `midifile` (for tests, optional) - * `clang/clang++` (for building tests, optional) +* `jinja2` (for generator templating) +* `importlib_resources` (for reading static resources) +* `json2daisy` (for daisy integration) + +For tests: + +* `tox` +* `numpy/scipy` +* `midifile` +* `tiniwav` +* `clang/clang++` ## Installation From 6e5e4842ab36dc008fb26094a12f3c2f4938d008 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Fri, 29 Dec 2023 02:24:28 +0100 Subject: [PATCH 13/22] Output parameters in DPF (#155) * proof of concept for output parameters in DPF * fix typing and sends * some cleanup * some cleanup. use hash and switchcase * add send type; add changelog --- CHANGELOG.md | 2 + hvcc/core/hv2ir/HIrSend.py | 2 + hvcc/generators/c2dpf/c2dpf.py | 7 +- hvcc/generators/c2dpf/templates/HeavyDPF.cpp | 84 ++++++------------- hvcc/generators/c2dpf/templates/HeavyDPF.hpp | 8 +- .../c2dpf/templates/HeavyDPF_UI.cpp | 16 ++-- .../c2dpf/templates/initParameter.cpp | 49 +++++++++++ ...{HeavyDPF_MIDI_Input.cpp => midiInput.cpp} | 0 ...eavyDPF_MIDI_Output.cpp => midiOutput.cpp} | 0 ...HeavyDPF_PortGroups.cpp => portGroups.cpp} | 0 hvcc/interpreters/pd2hv/PdSendObject.py | 33 +++++++- 11 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 hvcc/generators/c2dpf/templates/initParameter.cpp rename hvcc/generators/c2dpf/templates/{HeavyDPF_MIDI_Input.cpp => midiInput.cpp} (100%) rename hvcc/generators/c2dpf/templates/{HeavyDPF_MIDI_Output.cpp => midiOutput.cpp} (100%) rename hvcc/generators/c2dpf/templates/{HeavyDPF_PortGroups.cpp => portGroups.cpp} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89ab15b6..f6a4885e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG Next Release ----- +* Core: add attributes and send type to send params +* DPF: add "read only" outputParameter type based on send params * JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs * Bugfix: stripnote missing right inlet diff --git a/hvcc/core/hv2ir/HIrSend.py b/hvcc/core/hv2ir/HIrSend.py index 71449fc7..a71b63a6 100644 --- a/hvcc/core/hv2ir/HIrSend.py +++ b/hvcc/core/hv2ir/HIrSend.py @@ -46,8 +46,10 @@ def get_ir_control_list(self) -> List: on_message_list = [x for o in receive_objs for x in o.get_ir_on_message(inlet_index=0)] return [{ "id": self.id, + "type": "send", "onMessage": [on_message_list], "extern": self.args["extern"], + "attributes": self.args["attributes"], "hash": self.args["hash"], "display": self.args["name"], "name": ((f"_{self.args['name']}") if re.match(r"\d", self.args["name"]) else self.args["name"]) diff --git a/hvcc/generators/c2dpf/c2dpf.py b/hvcc/generators/c2dpf/c2dpf.py index 2041d21d..d5ffb195 100644 --- a/hvcc/generators/c2dpf/c2dpf.py +++ b/hvcc/generators/c2dpf/c2dpf.py @@ -45,6 +45,7 @@ def compile( out_dir = os.path.join(out_dir, "plugin") receiver_list = externs['parameters']['in'] + sender_list = externs["parameters"]["out"] if patch_meta: patch_name = patch_meta.get("name", patch_name) @@ -87,6 +88,7 @@ def compile( num_input_channels=num_input_channels, num_output_channels=num_output_channels, receivers=receiver_list, + senders=sender_list, copyright=copyright_c)) dpf_cpp_path = os.path.join(source_dir, f"HeavyDPF_{patch_name}.cpp") with open(dpf_cpp_path, "w") as f: @@ -97,6 +99,7 @@ def compile( num_input_channels=num_input_channels, num_output_channels=num_output_channels, receivers=receiver_list, + senders=sender_list, pool_sizes_kb=externs["memoryPoolSizesKb"], copyright=copyright_c)) if dpf_meta.get("enable_ui"): @@ -106,9 +109,8 @@ def compile( name=patch_name, meta=dpf_meta, class_name=f"HeavyDPF_{patch_name}", - num_input_channels=num_input_channels, - num_output_channels=num_output_channels, receivers=receiver_list, + senders=sender_list, copyright=copyright_c)) dpf_h_path = os.path.join(source_dir, "DistrhoPluginInfo.h") with open(dpf_h_path, "w") as f: @@ -118,7 +120,6 @@ def compile( class_name=f"HeavyDPF_{patch_name}", num_input_channels=num_input_channels, num_output_channels=num_output_channels, - receivers=receiver_list, pool_sizes_kb=externs["memoryPoolSizesKb"], copyright=copyright_c)) diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF.cpp b/hvcc/generators/c2dpf/templates/HeavyDPF.cpp index a942d9b8..d7f69a7d 100644 --- a/hvcc/generators/c2dpf/templates/HeavyDPF.cpp +++ b/hvcc/generators/c2dpf/templates/HeavyDPF.cpp @@ -5,7 +5,7 @@ #include -#define HV_LV2_NUM_PARAMETERS {{receivers|length}} +#define HV_DPF_NUM_PARAMETER {{receivers|length + senders|length}} #define HV_HASH_NOTEIN 0x67E37CA3 #define HV_HASH_CTLIN 0x41BE0f9C @@ -55,6 +55,7 @@ static void hvSendHookFunc(HeavyContextInterface *c, const char *sendName, uint3 {{class_name}}* plugin = ({{class_name}}*)c->getUserData(); if (plugin != nullptr) { + plugin->setOutputParameter(sendHash, m); {%- if meta.midi_output is defined and meta.midi_output == 1 %} #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT plugin->handleMidiSend(sendHash, m); @@ -78,9 +79,9 @@ static void hvPrintHookFunc(HeavyContextInterface *c, const char *printLabel, co // Main DPF plugin class {{class_name}}::{{class_name}}() - : Plugin(HV_LV2_NUM_PARAMETERS, 0, 0) + : Plugin(HV_DPF_NUM_PARAMETER, 0, 0) { - {% for k, v in receivers -%} + {% for k, v in receivers + senders -%} _parameters[{{loop.index-1}}] = {{v.attributes.default}}f; {% endfor %} @@ -91,7 +92,7 @@ static void hvPrintHookFunc(HeavyContextInterface *c, const char *printLabel, co {% if receivers|length > 0 %} // ensure that the new context has the current parameters - for (int i = 0; i < HV_LV2_NUM_PARAMETERS; ++i) { + for (int i = 0; i < HV_DPF_NUM_PARAMETER; ++i) { setParameterValue(i, _parameters[i]); } {%- endif %} @@ -102,62 +103,18 @@ static void hvPrintHookFunc(HeavyContextInterface *c, const char *printLabel, co } {%- if meta.port_groups is defined %} -{% include 'HeavyDPF_PortGroups.cpp' %} +{% include 'portGroups.cpp' %} {%- endif %} void {{class_name}}::initParameter(uint32_t index, Parameter& parameter) { - {%- if receivers|length > 0 -%} + {%- if (receivers|length > 0) or (senders|length > 0) -%} // initialise parameters with defaults switch (index) { - {% for k, v in receivers -%} - case param{{v.display}}: - parameter.name = "{{v.display.replace('_', ' ')}}"; - parameter.symbol = "{{v.display|lower}}"; - {%- if v.attributes.type == 'db': %} - parameter.unit = "dB"; - {%- elif v.attributes.type in ['hz', 'log_hz']: %} - parameter.unit = "Hz"; - {%- endif %} - parameter.hints = kParameterIsAutomatable - {%- if v.attributes.type == 'bool': %} - | kParameterIsBoolean - {%- elif v.attributes.type == 'trig': -%} - | kParameterIsTrigger - {%- elif v.attributes.type == 'int': -%} - | kParameterIsInteger - {%- elif v.attributes.type in ['log', 'log_hz']: -%} - | kParameterIsLogarithmic - {%- endif %}; - parameter.ranges.min = {{v.attributes.min}}f; - parameter.ranges.max = {{v.attributes.max}}f; - parameter.ranges.def = {{v.attributes.default}}f; - {%- if v.attributes.type == 'db' and not (meta.enumerators is defined and meta.enumerators[v.display] is defined): %} - { - ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[1]; - enumValues[0].value = {{v.attributes.min}}f; - enumValues[0].label = "-inf"; - parameter.enumValues.count = 1; - parameter.enumValues.values = enumValues; - } - {%- endif %} - {%- if meta.enumerators is defined and meta.enumerators[v.display] is defined %} - {% set enums = meta.enumerators[v.display] %} - {% set enumlen = enums|length %} - if (ParameterEnumerationValue *values = new ParameterEnumerationValue[{{enumlen}}]) - { - parameter.enumValues.restrictedMode = true; - {% for i in enums -%} - values[{{loop.index - 1}}].value = {{loop.index - 1}}.0f; - values[{{loop.index - 1}}].label = "{{i}}"; - {% endfor -%} - parameter.enumValues.count = {{enumlen}}; - parameter.enumValues.values = values; - } - {%- endif %} - break; - {% endfor %} + {% for k, v in receivers + senders %} +{% include 'initParameter.cpp' %} + {% endfor -%} } {% endif %} } @@ -167,7 +124,7 @@ void {{class_name}}::initParameter(uint32_t index, Parameter& parameter) float {{class_name}}::getParameterValue(uint32_t index) const { - {%- if receivers|length > 0 %} + {%- if (receivers|length > 0) or (senders|length > 0) %} return _parameters[index]; {% else %} return 0.0f; @@ -194,6 +151,19 @@ void {{class_name}}::setParameterValue(uint32_t index, float value) {%- endif %} } +void {{class_name}}::setOutputParameter(uint32_t sendHash, const HvMessage *m) +{ + {%- if senders|length > 0 %} + switch (sendHash) { + {% for k, v in senders -%} + case {{v.hash}}: // {{v.display}} + _parameters[param{{v.display}}] = hv_msg_getFloat(m, 0); + break; + {% endfor %} + } + {%- endif %} +} + // ------------------------------------------------------------------- // Process @@ -210,13 +180,13 @@ void {{class_name}}::setParameterValue(uint32_t index, float value) {%- if meta.midi_input is defined and meta.midi_input == 1 %} #if DISTRHO_PLUGIN_WANT_MIDI_INPUT -{% include 'HeavyDPF_MIDI_Input.cpp' %} +{% include 'midiInput.cpp' %} #endif {% endif %} {%- if meta.midi_output is defined and meta.midi_output == 1 %} #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT -{% include 'HeavyDPF_MIDI_Output.cpp' %} +{% include 'midiOutput.cpp' %} #endif {% endif %} @@ -254,7 +224,7 @@ void {{class_name}}::sampleRateChanged(double newSampleRate) {% if receivers|length > 0 -%} // ensure that the new context has the current parameters - for (int i = 0; i < HV_LV2_NUM_PARAMETERS; ++i) { + for (int i = 0; i < HV_DPF_NUM_PARAMETER; ++i) { setParameterValue(i, _parameters[i]); } {%- endif %} diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF.hpp b/hvcc/generators/c2dpf/templates/HeavyDPF.hpp index df6480cd..8094ebb5 100644 --- a/hvcc/generators/c2dpf/templates/HeavyDPF.hpp +++ b/hvcc/generators/c2dpf/templates/HeavyDPF.hpp @@ -20,6 +20,9 @@ class {{class_name}} : public Plugin {% for k, v in receivers -%} param{{v.display}}, {% endfor %} + {% for k, v in senders -%} + param{{v.display}}, + {% endfor %} }; {% if meta.port_groups is defined %} @@ -44,6 +47,7 @@ class {{class_name}} : public Plugin void handleMidiInput(uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount); void handleMidiSend(uint32_t sendHash, const HvMessage *m); + void setOutputParameter(uint32_t sendHash, const HvMessage *m); protected: // ------------------------------------------------------------------- @@ -135,9 +139,9 @@ class {{class_name}} : public Plugin // ------------------------------------------------------------------- private: - {%- if receivers|length > 0 %} + {%- if (receivers|length > 0) or senders|length > 0 %} // parameters - float _parameters[{{receivers|length}}]; // in range of [0,1] + float _parameters[{{receivers|length + senders|length}}]; // in range of [0,1] {%- endif %} // transport values diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF_UI.cpp b/hvcc/generators/c2dpf/templates/HeavyDPF_UI.cpp index 8ab79302..0aa2982d 100644 --- a/hvcc/generators/c2dpf/templates/HeavyDPF_UI.cpp +++ b/hvcc/generators/c2dpf/templates/HeavyDPF_UI.cpp @@ -6,9 +6,9 @@ START_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- -{%- if receivers|length > 0 %} +{%- if (receivers|length > 0) or (senders|length > 0) %} enum HeavyParams { - {%- for k, v in receivers %} + {%- for k, v in receivers + senders -%} {{v.display|upper}}, {%- endfor %} }; @@ -16,7 +16,7 @@ enum HeavyParams { class ImGuiPluginUI : public UI { - {% for k, v in receivers -%} + {% for k, v in receivers + senders -%} {%- if v.attributes.type == 'bool': %} bool f{{v.display|lower}} = {{v.attributes.default}}f != 0.0f; {%- elif v.attributes.type == 'int': %} @@ -56,9 +56,9 @@ class ImGuiPluginUI : public UI */ void parameterChanged(uint32_t index, float value) override { - {%- if receivers|length > 0 %} + {%- if (receivers|length > 0) or (senders|length > 0) %} switch (index) { - {% for k, v in receivers -%} + {% for k, v in receivers + senders -%} case {{v.display|upper}}: {%- if v.attributes.type == 'bool': %} f{{v.display|lower}} = value != 0.0f; @@ -92,7 +92,7 @@ class ImGuiPluginUI : public UI if (ImGui::Begin("{{name.replace('_', ' ')}}", nullptr, ImGuiWindowFlags_NoResize + ImGuiWindowFlags_NoCollapse)) { - {%- for k, v in receivers %} + {%- for k, v in receivers + senders %} {%- set v_display = v.display|lower %} {%- if meta.enumerators is defined and meta.enumerators[v.display] is defined -%} {%- set enums = meta.enumerators[v.display] -%} @@ -130,17 +130,19 @@ class ImGuiPluginUI : public UI if (ImGui::SliderFloat("{{v.display.replace('_', ' ')}}", &f{{v_display}}, {{v.attributes.min}}f, {{v.attributes.max}}f)) {%- endif %} { + {%- if not v.type == "send" %} if (ImGui::IsItemActivated()) { editParameter({{v.display|upper}}, true); } setParameterValue({{v.display|upper}}, f{{v_display}}); + {%- endif %} } {%- endif %} {% endfor %} if (ImGui::IsItemDeactivated()) { - {%- for k, v in receivers -%} + {% for k, v in receivers + senders -%} editParameter({{v.display|upper}}, false); {% endfor -%} } diff --git a/hvcc/generators/c2dpf/templates/initParameter.cpp b/hvcc/generators/c2dpf/templates/initParameter.cpp new file mode 100644 index 00000000..47628021 --- /dev/null +++ b/hvcc/generators/c2dpf/templates/initParameter.cpp @@ -0,0 +1,49 @@ + case param{{v.display}}: + parameter.name = "{{v.display.replace('_', ' ')}}"; + parameter.symbol = "{{v.display|lower}}"; + {%- if v.attributes.type == 'db': %} + parameter.unit = "dB"; + {%- elif v.attributes.type in ['hz', 'log_hz']: %} + parameter.unit = "Hz"; + {%- endif %} + {%- if v.type == "send" %} + parameter.hints = kParameterIsOutput + {%- else %} + parameter.hints = kParameterIsAutomatable + {%- endif %} + {%- if v.attributes.type == 'bool': %} + | kParameterIsBoolean + {%- elif v.attributes.type == 'trig': -%} + | kParameterIsTrigger + {%- elif v.attributes.type == 'int': -%} + | kParameterIsInteger + {%- elif v.attributes.type in ['log', 'log_hz']: -%} + | kParameterIsLogarithmic + {%- endif %}; + parameter.ranges.min = {{v.attributes.min}}f; + parameter.ranges.max = {{v.attributes.max}}f; + parameter.ranges.def = {{v.attributes.default}}f; + {%- if v.attributes.type == 'db' and not (meta.enumerators is defined and meta.enumerators[v.display] is defined): %} + { + ParameterEnumerationValue* const enumValues = new ParameterEnumerationValue[1]; + enumValues[0].value = {{v.attributes.min}}f; + enumValues[0].label = "-inf"; + parameter.enumValues.count = 1; + parameter.enumValues.values = enumValues; + } + {%- endif %} + {%- if meta.enumerators is defined and meta.enumerators[v.display] is defined %} + {% set enums = meta.enumerators[v.display] %} + {% set enumlen = enums|length %} + if (ParameterEnumerationValue *values = new ParameterEnumerationValue[{{enumlen}}]) + { + parameter.enumValues.restrictedMode = true; + {% for i in enums -%} + values[{{loop.index - 1}}].value = {{loop.index - 1}}.0f; + values[{{loop.index - 1}}].label = "{{i}}"; + {% endfor -%} + parameter.enumValues.count = {{enumlen}}; + parameter.enumValues.values = values; + } + {%- endif %} + break; diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF_MIDI_Input.cpp b/hvcc/generators/c2dpf/templates/midiInput.cpp similarity index 100% rename from hvcc/generators/c2dpf/templates/HeavyDPF_MIDI_Input.cpp rename to hvcc/generators/c2dpf/templates/midiInput.cpp diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF_MIDI_Output.cpp b/hvcc/generators/c2dpf/templates/midiOutput.cpp similarity index 100% rename from hvcc/generators/c2dpf/templates/HeavyDPF_MIDI_Output.cpp rename to hvcc/generators/c2dpf/templates/midiOutput.cpp diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF_PortGroups.cpp b/hvcc/generators/c2dpf/templates/portGroups.cpp similarity index 100% rename from hvcc/generators/c2dpf/templates/HeavyDPF_PortGroups.cpp rename to hvcc/generators/c2dpf/templates/portGroups.cpp diff --git a/hvcc/interpreters/pd2hv/PdSendObject.py b/hvcc/interpreters/pd2hv/PdSendObject.py index 2f9af518..8772d2d8 100644 --- a/hvcc/interpreters/pd2hv/PdSendObject.py +++ b/hvcc/interpreters/pd2hv/PdSendObject.py @@ -34,7 +34,7 @@ def __init__( self.__send_name = "" self.__extern_type = None - self.__attributes = {} + self.__attributes: Dict = {} try: # send objects don't necessarily need to have a name @@ -49,6 +49,37 @@ def __init__( except Exception: pass + if self.__extern_type == "param": + try: + self.__attributes = { + "min": 0.0, + "max": 1.0, + "default": 0.5, + "type": "float" + } + self.__attributes["min"] = float(self.obj_args[2]) + self.__attributes["max"] = float(self.obj_args[3]) + self.__attributes["default"] = float(self.obj_args[4]) + self.__attributes["type"] = str(self.obj_args[5]) + except ValueError: + self.add_warning( + f"Minimum, maximum, and default values for Parameter {self.__send_name} must be numbers.") + except Exception: + pass + + if not (self.__attributes["min"] <= self.__attributes["default"]): + self.add_error("Default parameter value is less than the minimum. " + "Send will not be exported: {0:g} < {1:g}".format( + self.__attributes["default"], + self.__attributes["min"])) + self.__extern_type = None + if not (self.__attributes["default"] <= self.__attributes["max"]): + self.add_error("Default parameter value is greater than the maximum. " + "Send will not be exported: {0:g} > {1:g}".format( + self.__attributes["default"], + self.__attributes["max"])) + self.__extern_type = None + if '@raw' in self.obj_args or '@owl' in self.obj_args: # TODO(dromer): deprecate @owl on next stable release try: pd_raw_args = parse_pd_raw_args(self.obj_args) From b803b35119e02cca67b3719473c812f1754baa59 Mon Sep 17 00:00:00 2001 From: Eugene Cherny <146974283+eu-ch@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:59:16 +0000 Subject: [PATCH 14/22] Add quotes to Wwise examples to prevent common user mistakes (#161) --- docs/03.gen.wwise.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/03.gen.wwise.md b/docs/03.gen.wwise.md index e4db9337..6766eb37 100644 --- a/docs/03.gen.wwise.md +++ b/docs/03.gen.wwise.md @@ -101,15 +101,15 @@ cd Hvcc_Out_Dir\wwise Generate Visual Studio project files; note, WWISEROOT environment variable can be set from Wwise Launcher by clicking on *Set Environment Variables* button in front of an installed Wwise entry: ```cmd -python %WWISEROOT%\Scripts\Build\Plugins\wp.py premake Authoring -python %WWISEROOT%\Scripts\Build\Plugins\wp.py premake Windows_vc160 +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" premake Authoring +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" premake Windows_vc160 ``` Build Authoring and Engine plugins in Release configurations; for Visual Studio 2022 replace vc160 with vc170: ```cmd -python %WWISEROOT%\Scripts\Build\Plugins\wp.py build -c Release -x x64 -t vc160 Authoring -python %WWISEROOT%\Scripts\Build\Plugins\wp.py build -c Release -x x64 -t vc160 Windows_vc160 +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" build -c Release -x x64 -t vc160 Authoring +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" build -c Release -x x64 -t vc160 Windows_vc160 ``` At this point, the plugins should be placed in correct SDK directories and be ready for use in the Authoring app. @@ -119,9 +119,9 @@ At this point, the plugins should be placed in correct SDK directories and be re We can go a step further and package the plugins into a bundle that can be installed conveniently from Wwise Launcher. ```cmd -python %WWISEROOT%\Scripts\Build\Plugins\wp.py package --version 2022.1.0.1 Authoring -python %WWISEROOT%\Scripts\Build\Plugins\wp.py package --version 2022.1.0.1 Windows_vc160 -python %WWISEROOT%\Scripts\Build\Plugins\wp.py generate-bundle --version 2022.1.0.1 +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" package --version 2022.1.0.1 Authoring +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" package --version 2022.1.0.1 Windows_vc160 +python "%WWISEROOT%\Scripts\Build\Plugins\wp.py" generate-bundle --version 2022.1.0.1 mkdir Bundle copy /y bundle.json Bundle copy /y *.tar.xz Bundle From 99343d68c7c35f7c262848258cdb02b62a6c683e Mon Sep 17 00:00:00 2001 From: dreamer Date: Thu, 25 Jan 2024 16:04:02 +0100 Subject: [PATCH 15/22] expand docs --- README.md | 10 +++++----- docs/02.getting_started.md | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2e2f2cc1..0f56c2c7 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,11 @@ Python 3.8 up to 3.12 For tests: -* `tox` -* `numpy/scipy` -* `midifile` -* `tiniwav` -* `clang/clang++` +* `tox` (python install) +* `numpy/scipy` (requirements-dev) +* `midifile` (git submodule) +* `tinywav` (git submodule) +* `clang/clang++` (system install) ## Installation diff --git a/docs/02.getting_started.md b/docs/02.getting_started.md index 91d74fe3..5e0496f2 100644 --- a/docs/02.getting_started.md +++ b/docs/02.getting_started.md @@ -8,6 +8,8 @@ To send audio output add a `[dac~]` The number of channels can be configured by Note that top-level graphs (e.g. `_main.pd`) should not have any `[inlet~]` or `[outlet~]` objects. These are reserved only for abstractions. +> NOTE: Currently if your main patch does not have at least an `adc~` or `dac~` configured signal rate objects will not be evaluated in the graph! + ## Exposing Parameters ### Input Parameters From 8bd8cd494ebe7b563eb7c4e47be386cb1a52e0b2 Mon Sep 17 00:00:00 2001 From: dreamer Date: Sat, 10 Feb 2024 19:20:43 +0100 Subject: [PATCH 16/22] initialize transport values --- hvcc/generators/c2dpf/templates/HeavyDPF.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hvcc/generators/c2dpf/templates/HeavyDPF.hpp b/hvcc/generators/c2dpf/templates/HeavyDPF.hpp index 8094ebb5..cd4f6610 100644 --- a/hvcc/generators/c2dpf/templates/HeavyDPF.hpp +++ b/hvcc/generators/c2dpf/templates/HeavyDPF.hpp @@ -145,10 +145,9 @@ class {{class_name}} : public Plugin {%- endif %} // transport values - bool wasPlaying; - float samplesProcessed; - double nextClockTick; - double sampleAtCycleStart; + bool wasPlaying = false; + double nextClockTick = 0.0; + double sampleAtCycleStart = 0.0; // midi out buffer int midiOutCount; From 0e2cffb9a6c0c5dd5cb73a6310ba548300b6db08 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Sun, 11 Feb 2024 19:41:04 +0100 Subject: [PATCH 17/22] pass displayprocess to template; use newer wst2daisy (#162) * pass displayprocess to template; use newer wst2daisy * update changelog --- CHANGELOG.md | 1 + hvcc/generators/c2daisy/c2daisy.py | 1 + setup.cfg | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a4885e..1d53404c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Next Release * Core: add attributes and send type to send params * DPF: add "read only" outputParameter type based on send params +* Daisy: update wstd2daisy and allow for setting `displayprocess` code into the template * JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs * Bugfix: stripnote missing right inlet diff --git a/hvcc/generators/c2daisy/c2daisy.py b/hvcc/generators/c2daisy/c2daisy.py index 490f5009..f09747f5 100644 --- a/hvcc/generators/c2daisy/c2daisy.py +++ b/hvcc/generators/c2daisy/c2daisy.py @@ -86,6 +86,7 @@ def compile( component_glue['max_channels'] = board_info['channels'] component_glue['num_output_channels'] = num_output_channels component_glue['has_midi'] = board_info['has_midi'] + component_glue['displayprocess'] = board_info['displayprocess'] component_glue['debug_printing'] = daisy_meta.get('debug_printing', False) component_glue['usb_midi'] = daisy_meta.get('usb_midi', False) diff --git a/setup.cfg b/setup.cfg index d8cef533..1acd1fe9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,7 @@ python_requires = >= 3.8 install_requires = Jinja2>=2.11 importlib_resources>=5.1 - wstd2daisy>=0.5.1 + wstd2daisy>=0.5.2 [options.entry_points] console_scripts = From a7d11661062fa7a9e388163861eeb52e8e697fb7 Mon Sep 17 00:00:00 2001 From: Alexander Chalikiopoulos Date: Mon, 12 Feb 2024 17:52:30 +0100 Subject: [PATCH 18/22] provide default name --- hvcc/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hvcc/__init__.py b/hvcc/__init__.py index 2321142a..ae9e6c90 100644 --- a/hvcc/__init__.py +++ b/hvcc/__init__.py @@ -316,6 +316,7 @@ def main() -> bool: parser.add_argument( "-n", "--name", + default="heavy", help="Provides a name for the generated Heavy context.") parser.add_argument( "-m", From 6a025b082b6d96751ee75110730b66d9b73c4688 Mon Sep 17 00:00:00 2001 From: Winry Litwa-Vulcu <19890187+vulcu@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:22:02 -0500 Subject: [PATCH 19/22] fix: if os is Windows call emcc via batch not shell script (#151) * fix: if os is Windows call emcc via batch not shell script * fix: check OS to get correct path to script instead of appending extension --- hvcc/generators/c2js/c2js.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hvcc/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index 0235be5f..d7c916da 100644 --- a/hvcc/generators/c2js/c2js.py +++ b/hvcc/generators/c2js/c2js.py @@ -74,7 +74,10 @@ def run_emscripten( """Run the emcc command to compile C source files to a javascript library. """ - emcc_path = which("emcc") + if os.name == 'nt': + emcc_path = which("emcc.bat") + else: + emcc_path = which("emcc") if emcc_path is None: raise HeavyException("emcc is not in the PATH") From 6b02e977748aa171cf9f942bb8849a82df06a460 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Fri, 16 Feb 2024 04:39:38 +0100 Subject: [PATCH 20/22] handle output events and parameters (#166) --- CHANGELOG.md | 1 + hvcc/generators/c2js/c2js.py | 4 ++ hvcc/generators/c2js/template/index.html | 74 +++++++++++++++++++++--- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d53404c..0f003c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Next Release * Core: add attributes and send type to send params * DPF: add "read only" outputParameter type based on send params +* JS: add output Parameter and Event to generator and html template * Daisy: update wstd2daisy and allow for setting `displayprocess` code into the template * JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs * Bugfix: stripnote missing right inlet diff --git a/hvcc/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index d7c916da..6f8a80d6 100644 --- a/hvcc/generators/c2js/c2js.py +++ b/hvcc/generators/c2js/c2js.py @@ -168,7 +168,9 @@ def compile( tick = time.time() parameter_list = externs["parameters"]["in"] + parameter_out_list = externs["parameters"]["out"] event_list = externs["events"]["in"] + event_out_list = externs["events"]["out"] out_dir = os.path.join(out_dir, "js") patch_name = patch_name or "heavy" @@ -217,7 +219,9 @@ def compile( name=patch_name, includes=[f"./{js_out_file}"], parameters=parameter_list, + parameters_out=parameter_out_list, events=event_list, + events_out=event_out_list, copyright=copyright_html)) # generate heavy js worklet from template diff --git a/hvcc/generators/c2js/template/index.html b/hvcc/generators/c2js/template/index.html index 5bf25431..9eb0b5b2 100644 --- a/hvcc/generators/c2js/template/index.html +++ b/hvcc/generators/c2js/template/index.html @@ -13,6 +13,7 @@ .widget { max-width: 900px; margin: auto; } .row { width: 100%; margin-bottom: 10px; } .col { display: inline-block; } + .events { vertical-align: top; margin: 10px; } .title { width: 80%; margin: 0 auto; } .transport { padding-left: 10px; } .parameter-name { width: 35%; text-align: right; } @@ -74,11 +75,30 @@ } function onFloatMessage(sendName, floatValue) { - console.log(sendName, floatValue); + + switch (sendName) { + {%- if parameters_out | length %} + {%- for k, v in parameters_out %} + case "{{k}}": + document.getElementById("parameter_{{k}}").value = floatValue; + document.getElementById("value_{{k}}").textContent = Number(floatValue).toFixed(2); + break; + {%- endfor %} + {%- endif %} + {%- if events_out | length %} + {%- for k, v in events_out %} + case "{{k}}": + {{k}}_counter += 1; + document.getElementById("{{k}}_counter").innerHTML = {{k}}_counter; + break; + {%- endfor %} + {%- endif %} + default: + console.log(sendName, floatValue); + } } {%- if events | length %} - // Generated Event Update Methods {%- for k, v in events %} function sendEvent_{{k}}() { @@ -91,6 +111,13 @@ {% endfor %} {%- endif %} + {%- if events_out | length %} + {%- for k, v in events_out %} + var {{k}}_counter = 0; + {%- endfor %} + {%- endif %} + + // randomizer function randomiseParameters() { {%- for k, v in parameters %} @@ -127,16 +154,31 @@ + +
{%- if events | length %} -
- {%- for k, v in events %} -
- +
+ Input Events:
+ {%- for k, v in events %} +
+ {%- endfor %} +
+ {%- endif %} + + {%- if events_out | length %} +
+ Output Events:
+ {%- for k, v in events_out %} + {{k}}: 0
+ {%- endfor %}
- {%- endfor %} -
{%- endif %} +
+ {%- if parameters | length %} +
+ Input Parameters: +
{%- for k, v in parameters %}
{{k}}
@@ -147,6 +189,22 @@
{%- endfor %} {%- endif %} + + {%- if parameters_out | length %} +
+ Output Parameters: +
+ {%- for k, v in parameters_out %} +
+
{{k}}
+
+ +
+
{{v.attributes.default}}
+
+ {%- endfor %} + {%- endif %} +
powered by heavy
From 20966f2f2a27cb1fce9f8a406ee93e046d130410 Mon Sep 17 00:00:00 2001 From: dreamer Date: Fri, 16 Feb 2024 04:48:11 +0100 Subject: [PATCH 21/22] update changelog --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f003c36..5d43802c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,16 @@ Next Release * Core: add attributes and send type to send params * DPF: add "read only" outputParameter type based on send params -* JS: add output Parameter and Event to generator and html template -* Daisy: update wstd2daisy and allow for setting `displayprocess` code into the template +* JS: add output Parameter and output Event to generator and html template +* Daisy: update `wstd2daisy` and allow for setting `displayprocess` code into the template +* Testing: move `tinywav` to git submodule * JS Bugfix: printHook and sendHook for AudioWorklet; mention emsdk limitations in docs -* Bugfix: stripnote missing right inlet +* Object Bugfix: `[stripnote]` missing right inlet +* Small Bugfixes: + * set default name argument + * `emcc` call on Windows - thanks to @vulcu + * deallocation in test_signal - thanks to @eu-ch + * quotes around WWISE paths - thanks to @eu-ch 0.10.0 ----- From f12dfa38dd51fea928fc4d7367db42a40d0bfa8a Mon Sep 17 00:00:00 2001 From: dreamer Date: Fri, 16 Feb 2024 04:50:26 +0100 Subject: [PATCH 22/22] up version --- .bumpversion.cfg | 2 +- CHANGELOG.md | 2 +- setup.cfg | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9bbddeae..ccbf75a2 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.0 +current_version = 0.11.0 [bumpversion:file:setup.cfg] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d43802c..eb7d185c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ CHANGELOG ===== -Next Release +0.11.0 ----- * Core: add attributes and send type to send params diff --git a/setup.cfg b/setup.cfg index 1acd1fe9..02dc854d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] name = hvcc -version = 0.10.0 +version = 0.11.0 license = GPLv3 author = Enzien Audio, Wasted Audio description = `hvcc` is a python-based dataflow audio programming language compiler that generates C/C++ code and a variety of specific framework wrappers. url = https://github.com/Wasted-Audio/hvcc -download_url = https://github.com/Wasted-Audio/hvcc/archive/refs/tags/v0.10.0.tar.gz +download_url = https://github.com/Wasted-Audio/hvcc/archive/refs/tags/v0.11.0.tar.gz classifiers = Development Status :: 4 - Beta Intended Audience :: Developers