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: