diff --git a/hvcc/core/hv2ir/HIrRFFT.py b/hvcc/core/hv2ir/HIrRFFT.py new file mode 100644 index 00000000..1580e2bb --- /dev/null +++ b/hvcc/core/hv2ir/HIrRFFT.py @@ -0,0 +1,42 @@ +# Copyright (C) 2014-2018 Enzien Audio, Ltd. +# Copyright (C) 2023 Wasted Audio +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from typing import Dict, Optional + +from .HeavyIrObject import HeavyIrObject +from .HeavyGraph import HeavyGraph + + +class HIrRFFT(HeavyIrObject): + """ __rfft~f + """ + + def __init__( + self, + obj_type: str, + args: Optional[Dict] = None, + graph: Optional[HeavyGraph] = None, + annotations: Optional[Dict] = None + ) -> None: + assert obj_type in {"__rfft~f", "__rifft~f"} + super().__init__(obj_type, args=args, graph=graph, annotations=annotations) + + def reduce(self) -> Optional[tuple]: + if self.graph is not None: + self.args["block_size"] = self.graph.args["block_size"] + return ({self}, []) + + return None diff --git a/hvcc/core/hv2ir/HeavyGraph.py b/hvcc/core/hv2ir/HeavyGraph.py index ff60f496..f3e5fff1 100644 --- a/hvcc/core/hv2ir/HeavyGraph.py +++ b/hvcc/core/hv2ir/HeavyGraph.py @@ -301,7 +301,6 @@ def resolve_object_for_name( from this graph. Returns None if no objects are available. This is a convenience method. """ - objs = self.resolve_objects_for_name(name, obj_types, local_graph) return objs[0] if len(objs) > 0 else None diff --git a/hvcc/core/hv2ir/HeavyParser.py b/hvcc/core/hv2ir/HeavyParser.py index 77a10ccc..5a6b2cc0 100644 --- a/hvcc/core/hv2ir/HeavyParser.py +++ b/hvcc/core/hv2ir/HeavyParser.py @@ -26,6 +26,7 @@ from .HIrLorenz import HIrLorenz from .HIrOutlet import HIrOutlet from .HIrPack import HIrPack +from .HIrRFFT import HIrRFFT from .HIrSwitchcase import HIrSwitchcase from .HIrTabhead import HIrTabhead from .HIrTabread import HIrTabread @@ -114,6 +115,14 @@ def graph_from_object( """ # resolve default graph arguments graph_args = graph_args or {} + + # set the block size + try: + if json_heavy["block_size"] is not None: + graph_args["block_size"] = int(json_heavy["block_size"]) + except KeyError: + graph_args["block_size"] = 64 + for a in json_heavy["args"]: if a["name"] not in graph_args: if a["required"]: @@ -307,6 +316,8 @@ def reduce(self) -> Tuple[Set, List]: "__tabwrite~f": HIrTabwrite, "__tabwrite_stoppable~f": HIrTabwrite, "__tabwrite": HIrTabwrite, + "__rfft~f": HIrRFFT, + "__rifft~f": HIrRFFT, "receive": HLangReceive, "send": HLangSend, "__switchcase": HIrSwitchcase, diff --git a/hvcc/core/json/heavy.ir.json b/hvcc/core/json/heavy.ir.json index 741ba904..5596ba2d 100644 --- a/hvcc/core/json/heavy.ir.json +++ b/hvcc/core/json/heavy.ir.json @@ -2271,6 +2271,44 @@ "-->" ] }, + "__rfft~f": { + "inlets": [ + "~f>" + ], + "ir": { + "control": false, + "signal": true, + "init": true + }, + "outlets": [ + "~f>", + "~f>" + ], + "args": [], + "perf": { + "avx": 0, + "sse": 0 + } + }, + "__rifft~f": { + "inlets": [ + "~f>", + "~f>" + ], + "ir": { + "control": false, + "signal": true, + "init": true + }, + "outlets": [ + "~f>" + ], + "args": [], + "perf": { + "avx": 0, + "sse": 0 + } + }, "__rpole~f": { "inlets": [ "~f>", diff --git a/hvcc/generators/ir2c/SignalRFFT.py b/hvcc/generators/ir2c/SignalRFFT.py new file mode 100644 index 00000000..49674b8a --- /dev/null +++ b/hvcc/generators/ir2c/SignalRFFT.py @@ -0,0 +1,109 @@ +# Copyright (C) 2014-2018 Enzien Audio, Ltd. +# Copyright (C) 2023 Wasted Audio +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from typing import Dict, List + +from .HeavyObject import HeavyObject + +from hvcc.types.IR import IRSignalList + + +class SignalRFFT(HeavyObject): + + c_struct = "SignalRFFT" + preamble = "sRFFT" + + @classmethod + def get_C_header_set(cls) -> set: + return {"HvSignalRFFT.h"} + + @classmethod + def get_C_file_set(cls) -> set: + return {"HvSignalRFFT.h", "HvSignalRFFT.c"} + + @classmethod + def get_C_init(cls, obj_type: str, obj_id: str, args: Dict) -> List[str]: + return [ + "sRFFT_init(&sRFFT_{0}, {1});".format( + obj_id, + args["block_size"]) + ] + + @classmethod + def get_C_onMessage(cls, obj_type: str, obj_id: str, inlet_index: int, args: Dict) -> List[str]: + return [ + "sRFFT_onMessage(_c, &Context(_c)->sRFFT_{0}, {1}, m, NULL);".format( + obj_id, + inlet_index) + ] + + @classmethod + def get_C_process(cls, process_dict: IRSignalList, obj_type: str, obj_id: str, args: Dict) -> List[str]: + if obj_type == "__rfft~f": + return [ + "__hv_rfft_f(&sRFFT_{0}, VIf({1}), VOf({2}), VOf({3}));".format( + process_dict.id, + cls._c_buffer(process_dict.inputBuffers[0]), + cls._c_buffer(process_dict.outputBuffers[0]), + cls._c_buffer(process_dict.outputBuffers[1]) + ) + ] + else: + raise Exception + + +class SignalRIFFT(HeavyObject): + + c_struct = "SignalRIFFT" + preamble = "sRIFFT" + + @classmethod + def get_C_header_set(cls) -> set: + return {"HvSignalRFFT.h"} + + @classmethod + def get_C_file_set(cls) -> set: + return {"HvSignalRFFT.h", "HvSignalRFFT.c"} + + @classmethod + def get_C_init(cls, obj_type: str, obj_id: str, args: Dict) -> List[str]: + return [ + "sRIFFT_init(&sRIFFT_{0}, {1});".format( + obj_id, + args["block_size"]) + ] + + @classmethod + def get_C_onMessage(cls, obj_type: str, obj_id: str, inlet_index: int, args: Dict) -> List[str]: + return [ + "sRIFFT_onMessage(_c, &Context(_c)->sRIFFT_{0}, {1}, m, NULL);".format( + obj_id, + inlet_index) + ] + + @classmethod + def get_C_process(cls, process_dict: IRSignalList, obj_type: str, obj_id: str, args: Dict) -> List[str]: + if obj_type == "__rifft~f": + return [ + "__hv_rifft_f(&sRIFFT_{0}, VIf({1}), VIf({2}), VOf({3}));".format( + process_dict.id, + cls._c_buffer(process_dict.inputBuffers[0]), + cls._c_buffer(process_dict.inputBuffers[1]), + cls._c_buffer(process_dict.outputBuffers[0]) + ) + ] + else: + raise Exception diff --git a/hvcc/generators/ir2c/ir2c.py b/hvcc/generators/ir2c/ir2c.py index c90398c9..920a511c 100644 --- a/hvcc/generators/ir2c/ir2c.py +++ b/hvcc/generators/ir2c/ir2c.py @@ -59,6 +59,7 @@ from hvcc.generators.ir2c.SignalLorenz import SignalLorenz from hvcc.generators.ir2c.SignalMath import SignalMath from hvcc.generators.ir2c.SignalPhasor import SignalPhasor +from hvcc.generators.ir2c.SignalRFFT import SignalRFFT, SignalRIFFT from hvcc.generators.ir2c.SignalRPole import SignalRPole from hvcc.generators.ir2c.SignalSample import SignalSample from hvcc.generators.ir2c.SignalSamphold import SignalSamphold @@ -105,6 +106,8 @@ class ir2c: "__tabwrite_stoppable~f": SignalTabwrite, "__phasor~f": SignalPhasor, "__phasor_k~f": SignalPhasor, + "__rfft~f": SignalRFFT, + "__rifft~f": SignalRIFFT, "__sample~f": SignalSample, "__samphold~f": SignalSamphold, "__slice": ControlSlice, diff --git a/hvcc/generators/ir2c/static/HvSignalRFFT.c b/hvcc/generators/ir2c/static/HvSignalRFFT.c new file mode 100644 index 00000000..0b4b2e1e --- /dev/null +++ b/hvcc/generators/ir2c/static/HvSignalRFFT.c @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2023 Wasted Audio + * + * 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 "HvSignalRFFT.h" + +hv_size_t sRFFT_init(SignalRFFT *o, const int size) { + hv_size_t numBytes = hTable_init(&o->input, size); + numBytes += hTable_init(&o->outputReal, size/2+1); + numBytes += hTable_init(&o->outputImagin, size/2+1); + return numBytes; +} + +void sRFFT_free(SignalRFFT *o) { + hTable_free(&o->input); + hTable_free(&o->outputReal); + hTable_free(&o->outputImagin); +} + +void __hv_rfft_f(SignalRFFT *o, hv_bInf_t bIn, hv_bOutf_t bOut0, hv_bOutf_t bOut1) { + // do fft stuff +} + +void sRFFT_onMessage(HeavyContextInterface *_c, SignalRFFT *o, int letIndex, + const HvMessage *m, void *sendMessage) { + switch (letIndex) { + default: return; + } +} + +hv_size_t sRIFFT_init(SignalRIFFT *o, const int size) { + hv_size_t numBytes = hTable_init(&o->inputReal, size/2+1); + numBytes += hTable_init(&o->inputImagin, size/2+1); + numBytes += hTable_init(&o->output, size); + return numBytes; +} + +void sRIFFT_free(SignalRIFFT *o) { + hTable_free(&o->inputReal); + hTable_free(&o->inputImagin); + hTable_free(&o->output); +} + +void __hv_rifft_f(SignalRIFFT *o, hv_bInf_t bIn0, hv_bInf_t bIn1, hv_bOutf_t bOut) { + // do ifft stuff +} + +void sRIFFT_onMessage(HeavyContextInterface *_c, SignalRIFFT *o, int letIndex, + const HvMessage *m, void *sendMessage) { + switch (letIndex) { + default: return; + } +} diff --git a/hvcc/generators/ir2c/static/HvSignalRFFT.h b/hvcc/generators/ir2c/static/HvSignalRFFT.h new file mode 100644 index 00000000..f79b7931 --- /dev/null +++ b/hvcc/generators/ir2c/static/HvSignalRFFT.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2023 Wasted Audio + * + * 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 _SIGNAL_RFFT_H_ +#define _SIGNAL_RFFT_H_ + +#include "HvHeavyInternal.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct SignalRFFT { + struct HvTable input; + struct HvTable outputReal; + struct HvTable outputImagin; +} SignalRFFT; + +hv_size_t sRFFT_init(SignalRFFT *o, const int size); +void sRFFT_free(SignalRFFT *o); +void sRFFT_onMessage(HeavyContextInterface *_c, SignalRFFT *o, int letIndex, const HvMessage *m, void *sendMessage); +void __hv_rfft_f(SignalRFFT *o, hv_bInf_t bIn, hv_bOutf_t bOut0, hv_bOutf_t bOut1); + +typedef struct SignalRIFFT { + struct HvTable inputReal; + struct HvTable inputImagin; + struct HvTable output; +} SignalRIFFT; + +hv_size_t sRIFFT_init(SignalRIFFT *o, const int size); +void sRIFFT_free(SignalRIFFT *o); +void sRIFFT_onMessage(HeavyContextInterface *_c, SignalRIFFT *o, int letIndex, const HvMessage *m, void *sendMessage); +void __hv_rifft_f(SignalRIFFT *o, hv_bInf_t bIn0, hv_bInf_t bIn1, hv_bOutf_t bOut); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _SIGNAL_RFFT_H_ diff --git a/hvcc/interpreters/pd2hv/PdGraph.py b/hvcc/interpreters/pd2hv/PdGraph.py index 1bb17540..f993c26e 100644 --- a/hvcc/interpreters/pd2hv/PdGraph.py +++ b/hvcc/interpreters/pd2hv/PdGraph.py @@ -55,6 +55,9 @@ def __init__( # only used is this graph is actually a subpatch self.subpatch_name: Optional[str] = None + # the block size of this graph (used for rfft window size) + self.block_size: Optional[int] = None + # TODO(dromer) these are virtual attributes that are only instantiated with internal representation self._PdGraph__connections: List[Connection] = [] self._PdGraph__pd_path: str = "" @@ -227,6 +230,7 @@ def to_hv(self, export_args: bool = False) -> Dict: assert all(a is not None for a in self.hv_args), "Graph is missing a @hv_arg." return { "type": "graph", + "block_size": self.block_size, "imports": [], "args": self.hv_args if export_args else [], "objects": {o.obj_id: o.to_hv() for o in self.__objs}, diff --git a/hvcc/interpreters/pd2hv/PdParser.py b/hvcc/interpreters/pd2hv/PdParser.py index e0e6d923..d9f8f46f 100644 --- a/hvcc/interpreters/pd2hv/PdParser.py +++ b/hvcc/interpreters/pd2hv/PdParser.py @@ -356,6 +356,10 @@ def graph_from_canvas( continue if obj_type in ('block~',): + if obj_type == 'block~': + # grab the block size for this graph + g.block_size = obj_args[0] + # we ignore the object and continue g.add_warning( f"This graph contains a {obj_type} object that is ignored.", diff --git a/hvcc/interpreters/pd2hv/libs/pd/rfft~.pd b/hvcc/interpreters/pd2hv/libs/pd/rfft~.pd new file mode 100644 index 00000000..4cc96863 --- /dev/null +++ b/hvcc/interpreters/pd2hv/libs/pd/rfft~.pd @@ -0,0 +1,12 @@ +#N canvas 532 457 436 234 12; +#X obj 59 59 inlet~; +#X obj 59 120 outlet~; +#X obj 190 121 outlet~; +#N canvas 0 22 450 300 @hv_obj 0; +#X obj 55 136 outlet~; +#X obj 55 75 inlet~; +#X obj 167 137 outlet~; +#X restore 59 91 pd @hv_obj __rfft~f; +#X connect 0 0 3 0; +#X connect 3 0 1 0; +#X connect 3 1 2 0; diff --git a/hvcc/interpreters/pd2hv/libs/pd/rifft~.pd b/hvcc/interpreters/pd2hv/libs/pd/rifft~.pd new file mode 100644 index 00000000..124d2c02 --- /dev/null +++ b/hvcc/interpreters/pd2hv/libs/pd/rifft~.pd @@ -0,0 +1,12 @@ +#N canvas 1222 648 486 278 12; +#X obj 74 59 inlet~; +#X obj 74 127 outlet~; +#X obj 211 59 inlet~; +#N canvas 0 22 450 300 @hv_obj 0; +#X obj 55 136 outlet~; +#X obj 55 75 inlet~; +#X obj 117 75 inlet~; +#X restore 74 98 pd @hv_obj __rifft~f; +#X connect 0 0 3 0; +#X connect 2 0 3 1; +#X connect 3 0 1 0; diff --git a/tests/framework/base_signal.py b/tests/framework/base_signal.py index 4079c894..9e10c7de 100644 --- a/tests/framework/base_signal.py +++ b/tests/framework/base_signal.py @@ -90,6 +90,7 @@ def _test_signal_patch(self, pd_file: str): try: out_dir = self._run_hvcc(pd_path) + assert out_dir is not None except Exception as e: self.fail(str(e)) diff --git a/tests/pd/signal_rfft/test-rfft.pd b/tests/pd/signal_rfft/test-rfft.pd new file mode 100644 index 00000000..3d5e1eb9 --- /dev/null +++ b/tests/pd/signal_rfft/test-rfft.pd @@ -0,0 +1,19 @@ +#N canvas 1039 0 450 300 12; +#N canvas 1216 43 450 300 subtest 1; +#X obj 74 84 rfft~; +#X obj 74 146 rifft~; +#X obj 72 231 outlet~; +#X obj 74 43 inlet~; +#X obj 201 58 block~ 256; +#X obj 74 181 /~ 256; +#X connect 0 0 1 0; +#X connect 0 1 1 1; +#X connect 1 0 5 0; +#X connect 3 0 0 0; +#X connect 5 0 2 0; +#X restore 71 104 pd subtest; +#X obj 45 166 dac~; +#X obj 46 44 osc~ 220; +#X connect 0 0 1 1; +#X connect 2 0 0 0; +#X connect 2 0 1 0; diff --git a/tests/test_signal.py b/tests/test_signal.py index 75bd33be..b078d168 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -16,6 +16,7 @@ import argparse import os +import shutil from tests.framework.base_signal import TestPdSignalBase @@ -62,17 +63,23 @@ def main(): action="count") args = parser.parse_args() - out_dir = TestPdSignalPatches._run_hvcc(args.pd_path) + test_patch = TestPdSignalPatches() + test_patch.setUp() + out_dir = test_patch._run_hvcc(args.pd_path) + assert out_dir is not None c_src_dir = os.path.join(out_dir, "c") - c_sources = [os.path.join(c_src_dir, c) for c in os.listdir(c_src_dir) if c.endswith(".c")] + shutil.copy2(os.path.join(test_patch.SCRIPT_DIR, "src/test_signal.c"), c_src_dir) + shutil.copy2(os.path.join(test_patch.SCRIPT_DIR, "src/tinywav/tinywav.h"), c_src_dir) + shutil.copy2(os.path.join(test_patch.SCRIPT_DIR, "src/tinywav/tinywav.c"), c_src_dir) + c_sources = os.listdir(c_src_dir) - wav_path = TestPdSignalPatches.compile_and_run( - out_dir, - c_sources, - args.samplerate, - args.blocksize, - args.numblocks, + wav_path = test_patch.compile_and_run( + source_files=c_sources, + out_dir=out_dir, + sample_rate=args.samplerate, + block_size=args.blocksize, + num_iterations=args.numblocks, flag=args.simd) if args.verbose: