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/.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/CHANGELOG.md b/CHANGELOG.md index cae21eef..eb7d185c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ CHANGELOG ===== +0.11.0 +----- + +* Core: add attributes and send type to send params +* DPF: add "read only" outputParameter type based on send params +* 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 +* 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 ----- diff --git a/README.md b/README.md index b991d17c..0f56c2c7 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` (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 9d9c65ea..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 @@ -40,7 +42,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) 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. 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 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", 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/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/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..cd4f6610 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,16 +139,15 @@ 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 - bool wasPlaying; - float samplesProcessed; - double nextClockTick; - double sampleAtCycleStart; + bool wasPlaying = false; + double nextClockTick = 0.0; + double sampleAtCycleStart = 0.0; // midi out buffer int midiOutCount; 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/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index 7ce9d699..6f8a80d6 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") @@ -165,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" @@ -214,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 @@ -242,7 +249,7 @@ def compile( output_name=f"{patch_name}_AudioLibWorklet", post_js_path=post_js_path, should_modularize=0, - environment="worker", + environment="shell,worker", pre_js_path=pre_js_path, binaryen_async=0) 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 2d997ce7..0eeef43f 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) { + options.printHook(event.data.payload); + } 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); + } }; this.webAudioWorklet.connect(this.webAudioContext.destination); } else { @@ -65,12 +69,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; } 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
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) 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/setup.cfg b/setup.cfg index d8cef533..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 @@ -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 = 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/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; 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 95% rename from tests/src/signal/test_signal.c rename to tests/src/test_signal.c index c9cbb88e..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,8 +50,8 @@ int main(int argc, const char *argv[]) { tinywav_write_f(&tw, outBuffers, blockSize); } - tinywav_close(&tw); - hv_delete(context); + 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