diff --git a/.gitignore b/.gitignore
index 16d04f22..f2b60df4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -175,3 +175,7 @@ DerivedData
generators/vs2015/Heavy/generated
generators/c2js/emsdk_portable/
generators/c2js/emsdk/
+
+## added by dgbillotte to ignore my dev stuff, feel free to remove
+test/pd/*/dgb*
+
diff --git a/hvcc/core/hv2ir/HIrExpr.py b/hvcc/core/hv2ir/HIrExpr.py
new file mode 100644
index 00000000..288230b4
--- /dev/null
+++ b/hvcc/core/hv2ir/HIrExpr.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2014-2018 Enzien Audio, Ltd.
+#
+# 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 .HeavyIrObject import HeavyIrObject
+from .HeavyException import HeavyException
+
+
+class HIrExpr(HeavyIrObject):
+ """Just a stub to get it going..."""
+
+ def __init__(self, obj_type, args=None, graph=None, annotations=None):
+ HeavyIrObject.__init__(self, obj_type, args=args, graph=graph,
+ num_inlets=args["num_inlets"],
+ num_outlets=1,
+ annotations=annotations)
+
+ def inlet_requires_signal(self, inlet_index=0):
+ """ Overriding HeavyIrObject to deal with variable number of inlets
+ """
+ if inlet_index >= self.num_inlets:
+ raise HeavyException(f"inlet_index: {inlet_index} is greater than number of inlets")
+ return True
diff --git a/hvcc/core/hv2ir/HeavyLangObject.py b/hvcc/core/hv2ir/HeavyLangObject.py
index f8d3b40f..023dde2f 100644
--- a/hvcc/core/hv2ir/HeavyLangObject.py
+++ b/hvcc/core/hv2ir/HeavyLangObject.py
@@ -75,6 +75,7 @@ def __init__(
# the list of connections at each inlet
num_inlets = num_inlets if num_inlets >= 0 else len(self._obj_desc["inlets"])
+ # print("=-=-=-=- HeavyLangObject::__init__", self.type, self._obj_desc)
self.inlet_connections: List = [[] for _ in range(num_inlets)]
# the list of connections at each outlet
@@ -224,6 +225,8 @@ def add_connection(self, c: Connection) -> None:
"""
try:
if c.to_object is self:
+ print("-------- c props", c)
+ print("-------- inlet cons", self.inlet_connections)
self.inlet_connections[c.inlet_index].append(c)
elif c.from_object is self:
self.outlet_connections[c.outlet_index].append(c)
diff --git a/hvcc/core/hv2ir/HeavyParser.py b/hvcc/core/hv2ir/HeavyParser.py
index 180de62d..20a24218 100644
--- a/hvcc/core/hv2ir/HeavyParser.py
+++ b/hvcc/core/hv2ir/HeavyParser.py
@@ -21,6 +21,7 @@
from typing import Any, Dict, Optional
from .HIrConvolution import HIrConvolution
+from .HIrExpr import HIrExpr
from .HIrInlet import HIrInlet
from .HIrLorenz import HIrLorenz
from .HIrOutlet import HIrOutlet
@@ -310,5 +311,7 @@ def reduce(self) -> tuple:
"send": HLangSend,
"__switchcase": HIrSwitchcase,
"switchcase": HIrSwitchcase,
- "__pack": HIrPack
+ "__pack": HIrPack,
+ "__expr": HIrExpr,
+ "__expr~": HIrExpr,
}
diff --git a/hvcc/core/json/heavy.ir.json b/hvcc/core/json/heavy.ir.json
index ff36a44d..fb0db498 100644
--- a/hvcc/core/json/heavy.ir.json
+++ b/hvcc/core/json/heavy.ir.json
@@ -1037,6 +1037,56 @@
]
},
+ "__expr": {
+ "inlets": [],
+ "ir": {
+ "control": true,
+ "signal": false,
+ "init": true
+ },
+ "outlets": [
+ "-->"
+ ],
+ "args": [{
+ "name": "expressions",
+ "value_type": "stringarray",
+ "description": "The expression to evaluate",
+ "default": "",
+ "required": true
+ },
+ {
+ "name": "num_inlets",
+ "value_type": "int",
+ "description": "Number of inlets for this instance",
+ "default": "",
+ "required": true
+ }]
+ },
+ "__expr~": {
+ "inlets": [],
+ "ir": {
+ "control": false,
+ "signal": true,
+ "init": false
+ },
+ "outlets": [
+ "~f>"
+ ],
+ "args": [{
+ "name": "expressions",
+ "value_type": "stringarray",
+ "description": "The expression to evaluate",
+ "default": "",
+ "required": true
+ },
+ {
+ "name": "num_inlets",
+ "value_type": "int",
+ "description": "Number of inlets for this instance",
+ "default": "",
+ "required": true
+ }]
+ },
"__floor": {
"inlets": [
"-->"
@@ -1049,9 +1099,7 @@
"outlets": [
"-->"
],
- "args": [
-
- ]
+ "args": []
},
"__floor~f": {
"args": [
diff --git a/hvcc/generators/ir2c/ControlDelay.py b/hvcc/generators/ir2c/ControlDelay.py
index 591f6438..e9929ddc 100644
--- a/hvcc/generators/ir2c/ControlDelay.py
+++ b/hvcc/generators/ir2c/ControlDelay.py
@@ -62,7 +62,8 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
send_message_list = [
f"cDelay_{obj_id}_sendMessage(HeavyContextInterface *_c, int letIn, const HvMessage *const m) {{"
diff --git a/hvcc/generators/ir2c/ControlExpr.py b/hvcc/generators/ir2c/ControlExpr.py
new file mode 100644
index 00000000..60181215
--- /dev/null
+++ b/hvcc/generators/ir2c/ControlExpr.py
@@ -0,0 +1,127 @@
+# Copyright (C) 2014-2018 Enzien Audio, Ltd.
+#
+# 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 .HeavyObject import HeavyObject
+import re
+from typing import Callable, Dict, List
+
+
+class ControlExpr(HeavyObject):
+ """Just a stub to get the thing working"""
+
+ c_struct = "ControlExpr"
+ preamble = "cExpr"
+
+ @classmethod
+ def get_C_header_set(self) -> set:
+ return {"HvControlExpr.h"}
+
+ @classmethod
+ def get_C_file_set(self) -> set:
+ return {"HvControlExpr.h", "HvControlExpr.c"}
+
+ @classmethod
+ def get_C_init(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ """(Per object) code that gets inserted into from the Heavy_heavy ctor.
+ Only if "ir[init]" == true
+ """
+
+ eval_f = f"&Heavy_heavy::{cls.preamble}_{obj_id}_evaluate"
+ return [f"cExpr_init(&cExpr_{obj_id}, {eval_f});"]
+
+ @classmethod
+ def get_C_def(cls, obj_type: str, obj_id: int) -> List[str]:
+ """(Per object) code that gets inserted into the header file
+ Only if "ir[init]" == true
+ """
+
+ lines = super().get_C_def(obj_type, obj_id)
+ lines.append("// --------------- big ol' comment ------------")
+ lines.append(f"static float {cls.preamble}_{obj_id}_evaluate(float* args);")
+ return lines
+
+ @classmethod
+ def get_C_onMessage(cls, obj_type: str, obj_id: int, inlet_index: int, args: Dict) -> List[str]:
+ """
+ (Per object) code that gets inserted into the c__sendMessage
+ method in the .cpp file
+ """
+
+ return [
+ "cExpr_onMessage(_c, &Context(_c)->cExpr_{0}, {1}, m, &cExpr_{0}_sendMessage);".format(
+ obj_id,
+ inlet_index)
+ ]
+ """
+ The get_C_onMessage method returns the code that will get inserted into
+ the cReceive__sendMessage method
+ """
+
+ # @classmethod
+ # def get_C_process(cls, process_dict: Dict, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ # return [
+ # "printf(\"hello world\")"
+ # ]
+ """
+ The get_C_process method seems to only get called by Signal IR objects, it does
+ not get called for Control IR objects
+ """
+
+ @classmethod
+ def get_C_impl(
+ cls,
+ obj_type: str,
+ obj_id: int,
+ on_message_list: List,
+ get_obj_class: Callable,
+ objects: Dict,
+ args: Dict
+ ) -> List[str]:
+ """
+ (Per object) this creates the _sendMessage function that other objects use to
+ send messages to this object.
+ """
+
+ lines = super().get_C_impl(obj_type, obj_id, on_message_list, get_obj_class, objects, args)
+ expr = args["expressions"][0]
+ bound_expr = bind_expr(expr, "args")
+ lines.extend([
+ "",
+ f"float Heavy_heavy::{cls.preamble}_{obj_id}_evaluate(float* args) {{",
+ f"\treturn {bound_expr};",
+ "}",
+ ])
+ return lines
+
+
+"""
+Below is code to rewrite the input expression into one that uses local variables
+that have been cast to either float or int
+"""
+
+
+# todo(dgb): need to handle the 's' type
+def var_n(a_name, var):
+ parts = re.match(r"\$([fi])(\d)", var)
+ type = "float" if parts[1] == "f" else "int"
+ return f"(({type})({a_name}[{int(parts[2])-1}]))"
+
+
+def bind_expr(exp="$f1+2", a_name="a"):
+ vars = re.findall(r"\$[fis]\d", exp)
+ new_exp = exp
+ for var in vars:
+ new_exp = new_exp.replace(var, var_n(a_name, var))
+ return new_exp
diff --git a/hvcc/generators/ir2c/ControlMessage.py b/hvcc/generators/ir2c/ControlMessage.py
index ae946bb3..745c13d7 100644
--- a/hvcc/generators/ir2c/ControlMessage.py
+++ b/hvcc/generators/ir2c/ControlMessage.py
@@ -35,7 +35,8 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
send_message_list = [
f"cMsg_{obj_id}_sendMessage(HeavyContextInterface *_c, int letIn, const HvMessage *const n) {{"
diff --git a/hvcc/generators/ir2c/ControlPrint.py b/hvcc/generators/ir2c/ControlPrint.py
index 62d43dfb..81a7ee41 100644
--- a/hvcc/generators/ir2c/ControlPrint.py
+++ b/hvcc/generators/ir2c/ControlPrint.py
@@ -55,6 +55,7 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
return []
diff --git a/hvcc/generators/ir2c/ControlSend.py b/hvcc/generators/ir2c/ControlSend.py
index 39e82ce6..ee3b5a12 100644
--- a/hvcc/generators/ir2c/ControlSend.py
+++ b/hvcc/generators/ir2c/ControlSend.py
@@ -35,7 +35,8 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
# Note(joe): if no corresponding receivers exist and there's no extern indicator
# then there is not much need to generate code stub
diff --git a/hvcc/generators/ir2c/ControlSwitchcase.py b/hvcc/generators/ir2c/ControlSwitchcase.py
index c1d1b05d..27582b35 100644
--- a/hvcc/generators/ir2c/ControlSwitchcase.py
+++ b/hvcc/generators/ir2c/ControlSwitchcase.py
@@ -49,7 +49,8 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
# generate the onMessage implementation
out_list = [
diff --git a/hvcc/generators/ir2c/HeavyObject.py b/hvcc/generators/ir2c/HeavyObject.py
index 4845a819..a32122e7 100644
--- a/hvcc/generators/ir2c/HeavyObject.py
+++ b/hvcc/generators/ir2c/HeavyObject.py
@@ -47,6 +47,10 @@ def get_C_header_set(self) -> set:
def get_C_file_set(self) -> set:
return set()
+ @classmethod
+ def get_C_init(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ return [f"{cls.preamble}_init(&{cls.preamble}_{obj_id})"]
+
@classmethod
def get_C_def(cls, obj_type: str, obj_id: int) -> List[str]:
return ["{0} {1}_{2};".format(
@@ -73,7 +77,8 @@ def get_C_impl(
obj_id: int,
on_message_list: List,
get_obj_class: Callable,
- objects: Dict
+ objects: Dict,
+ args: Dict
) -> List[str]:
send_message_list = [
"{0}_{1}_sendMessage(HeavyContextInterface *_c, int letIn, const HvMessage *m) {{".format(
@@ -97,12 +102,24 @@ def get_C_impl(
return send_message_list
@classmethod
- def get_C_init(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
- raise NotImplementedError("method get_C_init not implemented")
+ def get_C_onMessage(cls, obj_type: str, obj_id: int, inlet_index: int, args: Dict) -> List[str]:
+ raise NotImplementedError("method get_C_onMessage not implemented", cls, obj_type)
@classmethod
- def get_C_onMessage(cls, obj_type: str, obj_id: int, inlet_index: int, args: Dict) -> List[str]:
- raise NotImplementedError("method get_C_onMessage not implemented")
+ def get_C_obj_header_code(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ return []
+
+ @classmethod
+ def get_C_obj_impl_code(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ return []
+
+ @classmethod
+ def get_C_class_header_code(cls, obj_type: str, args: Dict) -> List[str]:
+ return []
+
+ @classmethod
+ def get_C_class_impl_code(cls, obj_type: str, args: Dict) -> List[str]:
+ return []
@classmethod
def get_C_process(cls, process_dict: Dict, obj_type: str, obj_id: int, args: Dict) -> List[str]:
diff --git a/hvcc/generators/ir2c/SignalExpr.py b/hvcc/generators/ir2c/SignalExpr.py
new file mode 100644
index 00000000..6e477917
--- /dev/null
+++ b/hvcc/generators/ir2c/SignalExpr.py
@@ -0,0 +1,110 @@
+# Copyright (C) 2014-2018 Enzien Audio, Ltd.
+#
+# 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 .expr_c_writer import ExprCWriter
+from .HeavyObject import HeavyObject
+
+
+class SignalExpr(HeavyObject):
+ """Handles the math objects.
+ """
+
+ preamble = "cExprSig"
+
+ obj_eval_functions: Dict = {}
+
+ @classmethod
+ def handles_type(cls, obj_type: str) -> bool:
+ """Returns true if the object type can be handled by this class
+ """
+ return obj_type == "_expr~"
+
+ @classmethod
+ def get_C_header_set(self) -> set:
+ return {"HvMath.h"}
+
+ @classmethod
+ def get_C_class_header_code(cls, obj_type: str, args: Dict) -> List[str]:
+ eval_funcs = ", ".join(cls.obj_eval_functions.values())
+ fptr_type = f"{cls.preamble}_evaluator"
+ lines = [
+ f"typedef void(*{fptr_type})(hv_bInf_t*, hv_bOutf_t);",
+ f"{fptr_type} {cls.preamble}_evaluators[{len(cls.obj_eval_functions)}] = {{{eval_funcs}}};",
+ ]
+ return lines
+
+ @classmethod
+ def get_C_obj_header_code(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ lines = super().get_C_obj_header_code(obj_type, obj_id, args)
+ func_name = f"{cls.preamble}_{obj_id}_evaluate"
+ cls.obj_eval_functions[obj_id] = func_name
+ lines.extend([
+ f"static inline void {func_name}(hv_bInf_t* bIns, hv_bOutf_t bOut);",
+ ])
+ return lines
+
+ @classmethod
+ def get_C_obj_impl_code(cls, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ """
+ (Per object) this creates the _sendMessage function that other objects use to
+ send messages to this object.
+ """
+
+ lines = super().get_C_obj_impl_code(obj_type, obj_id, args)
+
+ expr = args["expressions"][0]
+ expr_parser = ExprCWriter(expr)
+ expr_lines = expr_parser.to_c_simd("bIns", "bOut")[:-1]
+ expr_lines = "\n".join(
+ [f"\t{line}" for line in expr_lines]
+ )
+ num_buffers = expr_parser.num_simd_buffers()
+ buffer_declaration = "\t// no extra buffers needed"
+ if num_buffers > 0:
+ buffers = ", ".join([f"Bf{i}" for i in range(0, num_buffers)])
+ buffer_declaration = f"\thv_bufferf_t {buffers};"
+
+ func_name = f"Heavy_heavy::{cls.preamble}_{obj_id}_evaluate"
+ lines.extend([
+ "",
+ f"void {func_name}(hv_bInf_t* bIns, hv_bOutf_t bOut) {{",
+
+ buffer_declaration,
+ expr_lines,
+ "}",
+
+ ])
+ return lines
+
+ @classmethod
+ def get_C_process(cls, process_dict: Dict, obj_type: str, obj_id: int, args: Dict) -> List[str]:
+ input_args = []
+ for b in process_dict["inputBuffers"]:
+ buf = HeavyObject._c_buffer(b)
+ input_args.append(f"VIf({buf})")
+ out_buf = HeavyObject._c_buffer(process_dict["outputBuffers"][0])
+ out_buf = f"VOf({out_buf})"
+
+ call = [
+ "",
+ "\t// !!! declare this buffer once outside the loop",
+ f"\thv_bInf_t input_args_{obj_id}[{args['num_inlets']}] = {{{', '.join(input_args)}}};",
+ f"\t{cls.preamble}_evaluators[{len(cls.obj_eval_functions)}](input_args_{obj_id}, {out_buf});"
+ "",
+ ]
+
+ return call
diff --git a/hvcc/generators/ir2c/expr_arpeggio_grammar.py b/hvcc/generators/ir2c/expr_arpeggio_grammar.py
new file mode 100644
index 00000000..77e1b6e3
--- /dev/null
+++ b/hvcc/generators/ir2c/expr_arpeggio_grammar.py
@@ -0,0 +1,47 @@
+from arpeggio import ParserPython, StrMatch
+from arpeggio import ZeroOrMore
+from arpeggio import RegExMatch as regex
+
+
+class SuppressStrMatch(StrMatch):
+ suppress = True
+
+
+hide = SuppressStrMatch
+
+
+def expr(): return lor # EOF # noqa
+def lor(): return land, ZeroOrMore("||", land) # noqa
+def land(): return bor, ZeroOrMore("&&", bor) # noqa
+def bor(): return xor, ZeroOrMore("|", xor) # noqa
+def xor(): return band, ZeroOrMore("^", band) # noqa
+def band(): return eq, ZeroOrMore("&", eq) # noqa
+def eq(): return gtlt, ZeroOrMore(["==","!="], gtlt) # noqa
+def gtlt(): return shift, ZeroOrMore(["<","<=",">",">="], shift) # noqa
+def shift(): return term, ZeroOrMore(["<<",">>"], term) # noqa
+def term(): return factor, ZeroOrMore(["+","-"], factor) # RtoL # noqa
+def factor(): return unary, ZeroOrMore(["*","/","%"], unary) # noqa
+def unary(): return [(["-","~","!"], unary), primary] # RtoL # noqa
+def primary(): return [number, var, func, group] # noqa
+def number(): return [num_f, num_i] # noqa
+def var(): return regex(r"\$[fisv]\d+") # noqa
+def func(): return [ # noqa
+ (f_name, hide("("), expr, hide(")")), # noqa
+ (f_name, hide("("), expr, hide(","), expr, hide(")")), # noqa
+ (f_name, hide("("), expr, hide(","), expr, hide(","), expr, hide(")")) # noqa
+ ] # noqa
+def group(): return hide("("), expr, hide(")") # noqa
+def num_f(): return regex(r"\d+\.\d+") # prob need to cover exponential syntax, others? # noqa
+def num_i(): return regex(r"\d+") # need to cover bin/octal/hex? # noqa
+def f_name(): return [ # noqa
+ "abs", "acos", "acosh", "asin", "asinh", "atan", "atan2", # noqa
+ "cbrt", "ceil", "copysign", "cos", "cosh", "drem", "erf", # noqa
+ "erfc", "exp", "expm1", "fact", "finite", "float", "floor", # noqa
+ "fmod", "ldexp", "if", "imodf", "int", "isinf", "isnan", # noqa
+ "ln", "log", "log10", "log1p", "max", "min", "modf", "pow", # noqa
+ "rint", "sin", "sinh", "size", "sqrt", "sum", "Sum", # noqa
+ "tan", "tanh", # noqa
+ ] # noqa
+
+
+expr_grammar = ParserPython(expr, reduce_tree=True)
diff --git a/hvcc/generators/ir2c/expr_arpeggio_parser.py b/hvcc/generators/ir2c/expr_arpeggio_parser.py
new file mode 100644
index 00000000..397ccfdb
--- /dev/null
+++ b/hvcc/generators/ir2c/expr_arpeggio_parser.py
@@ -0,0 +1,77 @@
+from .expr_arpeggio_grammar import expr_grammar
+
+
+class ExprArpeggioParser():
+ """
+ These two methods are the "public" API
+ """
+ @classmethod
+ def parse(cls, expr):
+ """Parse the input expression and return a parse tree"""
+ return expr_grammar.parse(expr)
+
+ @classmethod
+ def parse_to_ast(cls, expr):
+ """Parse the input expression and return an AST"""
+ return cls.to_expr_tree(cls.parse(expr))
+
+ """
+ Helper methods below
+ """
+ @classmethod
+ def to_expr_tree(cls, expr):
+ if expr.rule_name == "num_i":
+ return ExprNode("num_i", expr.value)
+ elif expr.rule_name == "num_f":
+ return ExprNode("num_f", expr.value)
+ elif expr.rule_name == "var":
+ return ExprNode("var", expr.value)
+ elif expr.rule_name == "func":
+ return ExprNode("func", expr[0].value, [cls.to_expr_tree(p) for p in expr[1:]])
+ elif expr.rule_name == "unary":
+ return ExprNode("unary", expr[0].value, [cls.to_expr_tree(expr[1])])
+ elif expr.rule_name == "term":
+ # this is RtoL associativity
+ val = None
+ tmp = None
+ subtree = None
+ for i in range(len(expr)):
+ if i % 2 == 0:
+ val = cls.to_expr_tree(expr[i])
+ else:
+ foo = ExprNode("binary", str(expr[i]), [val])
+ if not subtree:
+ subtree = foo
+ else:
+ tmp.nodes.append(foo)
+ tmp = foo
+ tmp.nodes.append(val)
+ return subtree
+
+ elif expr.rule_name in (
+ "lor", "land", "bor", "xor", "band", "eq", "gtlt", "shift", "factor"
+ ):
+ # this is LtoR associativity
+ subtree = None
+ for i in range(len(expr)):
+ if i % 2 == 0:
+ if not subtree:
+ subtree = cls.to_expr_tree(expr[i])
+ else:
+ subtree.nodes.append(cls.to_expr_tree(expr[i]))
+ else:
+ subtree = ExprNode("binary", str(expr[i]), [subtree])
+ return subtree
+
+
+class ExprNode:
+ def __init__(self, node_type, value, nodes=[]):
+ self.type = node_type
+ self.value = value
+ self.nodes = nodes
+
+ def __str__(self, depth=0):
+ this = f"{' '*depth}{self.type}:{self.value}\n"
+ for node in self.nodes:
+ this += node.__str__(depth + 1)
+ return this
diff --git a/hvcc/generators/ir2c/expr_c_writer.py b/hvcc/generators/ir2c/expr_c_writer.py
new file mode 100644
index 00000000..dde038a1
--- /dev/null
+++ b/hvcc/generators/ir2c/expr_c_writer.py
@@ -0,0 +1,219 @@
+from .expr_arpeggio_parser import ExprArpeggioParser, ExprNode
+import re
+
+
+class ExprCWriter:
+ def __init__(self, expression):
+ self.expression = expression
+ self.expr_tree = ExprArpeggioParser.parse_to_ast(expression)
+ self.parse_tree = ExprArpeggioParser.parse(expression)
+ self.num_buffers = None
+
+ def to_ast(self):
+ return self.expr_tree
+
+ def to_parse_tree(self):
+ return self.parse_tree
+
+ def to_c_simd(self, vv_in, v_out):
+ self._simd_bind_variables(vv_in)
+ self._simd_replace_constants()
+ return self._to_c_simd(v_out)
+
+ def num_simd_buffers(self):
+ if self.num_buffers is None:
+ self.to_c_simd()
+ return self.num_buffers
+
+ def to_c_nested(self):
+ return self._to_c_nested()
+
+ def _simd_replace_constants(self):
+ self._simd_replace_constants_R(self.expr_tree)
+
+ def _simd_replace_constants_R(self, tree):
+ if tree.type == "func" and tree.value.startswith("_load_"):
+ return # don't re-replace anything
+
+ for i in range(len(tree.nodes)):
+ node = tree.nodes[i]
+ if node.type == "num_i":
+ tree.nodes[i] = ExprNode("func", "_load_i", [node])
+ elif node.type == "num_f":
+ tree.nodes[i] = ExprNode("func", "_load_f", [node])
+ else:
+ self._simd_replace_constants_R(node)
+
+ def _simd_bind_variables(self, a_name="A"):
+ self._bind_vars_R(self.expr_tree, a_name)
+
+ def _bind_vars_R(self, tree, a_name):
+ if tree.type == "var":
+ self._bind_var_node(tree, a_name)
+ else:
+ for node in tree.nodes:
+ self._bind_vars_R(node, a_name)
+
+ def _bind_var_node(self, node, a_name):
+ if node.type != "var":
+ print("called on non-var node: ", node)
+ return
+ parts = re.match(r"\$(v|f)(\d+)", node.value)
+ node.value = f"{a_name}[{int(parts[2])-1}]"
+ node.type = "bound_var"
+
+ class BufferAllocator:
+ """Inner class for managing the swapping of buffers from
+ output to input in successive calls.
+ """
+ def __init__(self):
+ self._avail = set()
+ self._next = 0
+
+ def next(self):
+ """If a buffer is available return it, otherwise
+ allocate a new one and return it. """
+
+ if len(self._avail) > 0:
+ return self._avail.pop()
+ nxt = self._next
+ self._next += 1
+ return nxt
+
+ def free(self, n):
+ """Return a buffer back to the pool to be reused"""
+ self._avail.add(n)
+
+ def num_allocated(self):
+ """Return the buffers allocated in so far."""
+ return self._next
+
+ def _to_c_simd(self, v_out):
+ ba = ExprCWriter.BufferAllocator()
+ lines = []
+
+ def _to_c_simd_R(expr_tree, r_vec=None):
+ if expr_tree.type in ("num_i", "num_f", "var", "bound_var"):
+ return expr_tree.value
+
+ args = []
+ buffers = []
+
+ if (
+ expr_tree.type == "func"
+ and expr_tree.value.startswith("_load_")
+ ):
+ next_buf = f"Bf{ba.next()}"
+ args.append("&" + next_buf)
+ const_value = _to_c_simd_R(expr_tree.nodes[0])
+ for i in range(8):
+ args.append(const_value)
+ else:
+ for node in expr_tree.nodes:
+ val = _to_c_simd_R(node)
+ args.append(val)
+ if type(val) == str and val.startswith("Bf"):
+ buffers.append(val)
+ if r_vec:
+ next_buf = r_vec
+ args.append(next_buf)
+ else:
+ next_buf = f"Bf{ba.next()}"
+ args.append("&" + next_buf)
+
+ f_name = ExprOpMap.get_hv_func_simd(expr_tree.value)
+ lines.append(f"{f_name}({', '.join(args)});")
+ [ba.free(int(b[2])) for b in buffers]
+
+ return next_buf
+
+ lines.append(_to_c_simd_R(self.expr_tree, v_out))
+ self.num_buffers = ba.num_allocated()
+ return lines
+
+ def _to_c_nested(self):
+ """Output C-code as nested function calls"""
+
+ def _to_c_nested_R(expr_tree):
+ if expr_tree.type in ("num_i", "num_f", "var"):
+ return expr_tree.value
+ else:
+ f_name = ExprOpMap.get_hv_func(expr_tree.value)
+ args = [_to_c_nested_R(p) for p in expr_tree.nodes]
+ return f"{f_name}({', '.join(args)})"
+
+ return _to_c_nested_R(self.expr_tree) + ";"
+
+
+class ExprOpMap:
+ op_map = {
+ "~": "hv_?_f",
+ # "-": "hv_neg_f",
+ "*": "hv_mul_f",
+ "/": "hv_div_f",
+ "%": "hv_?_f",
+ "+": "hv_add_f",
+ "-": "hv_sub_f",
+ "<": "hv_lt_f",
+ "<=": "hv_lte_f",
+ ">": "hv_gt_f",
+ ">=": "hv_gte_f",
+ "!=": "hv_neq_f",
+ "&&": "hv_and_f",
+ "||": "hv_or_f",
+ "abs": "hv_abs_f",
+ "acos": "hv_acos_f",
+ "acosh": "hv_acosh_f",
+ "asin": "hv_asin_f",
+ "asinh": "hv_asinh_f",
+ "atan": "hv_atan_f",
+ "atan2": "hv_atan2_f",
+ "cbrt": "hv_?_f",
+ "ceil": "hv_ceil_f",
+ "copysign": "hv_?_f", # does this just return +/- 1? It doesn't come up in pd...
+ "cos": "hv_cos_f",
+ "cosh": "hv_cosh_f",
+ "drem": "hv_?_f",
+ "erf": "hv_?_f",
+ "erfc": "hv_?_f",
+ "exp": "hv_exp_f",
+ "expm1": "hv_?_f",
+ "fact": "hv_?_f",
+ "finite": "hv_?_f",
+ "float": "hv_cast_if",
+ "floor": "hv_floor_f",
+ "fmod": "hv_?_f",
+ "ldexp": "hv_?_f",
+ "if": "hv_?_f",
+ "imodf": "hv_?_f",
+ "int": "hv_cast_fi",
+ "isinf": "hv_?_f",
+ "isnan": "hv_?_f",
+ "ln": "hv_?_f",
+ "log": "hv_?_f",
+ "log10": "hv_?_f",
+ "log1p": "hv_?_f",
+ "max": "hv_max_f",
+ "min": "hv_min_f",
+ "modf": "hv_?_f",
+ "pow": "hv_pow_f",
+ "rint": "hv_?_f", # round to nearest int
+ "sin": "hv_sin_f",
+ "sinh": "hv_sinh_f",
+ "size": "hv_?_f",
+ "sqrt": "hv_sqrt_f",
+ "sum": "hv_?_f", # sum of all elements of a table
+ "Sum": "hv_?_f", # sum of elemnets of a specified boundary of a table???
+ "tan": "hv_tan_f",
+ "tanh": "hv_tanh_f",
+ "_load_f": "hv_var_k_f",
+ "_load_i": "hv_var_k_i",
+ }
+
+ @classmethod
+ def get_hv_func(cls, symbol):
+ return cls.op_map[symbol]
+
+ @classmethod
+ def get_hv_func_simd(cls, symbol):
+ return "__" + cls.op_map[symbol]
diff --git a/hvcc/generators/ir2c/ir2c.py b/hvcc/generators/ir2c/ir2c.py
index c822c007..0363c923 100644
--- a/hvcc/generators/ir2c/ir2c.py
+++ b/hvcc/generators/ir2c/ir2c.py
@@ -31,6 +31,7 @@
from hvcc.generators.ir2c.ControlBinop import ControlBinop
from hvcc.generators.ir2c.ControlCast import ControlCast
from hvcc.generators.ir2c.ControlDelay import ControlDelay
+from hvcc.generators.ir2c.ControlExpr import ControlExpr
from hvcc.generators.ir2c.ControlIf import ControlIf
from hvcc.generators.ir2c.ControlMessage import ControlMessage
from hvcc.generators.ir2c.ControlPack import ControlPack
@@ -53,6 +54,7 @@
from hvcc.generators.ir2c.SignalCPole import SignalCPole
from hvcc.generators.ir2c.SignalDel1 import SignalDel1
from hvcc.generators.ir2c.SignalEnvelope import SignalEnvelope
+from hvcc.generators.ir2c.SignalExpr import SignalExpr
from hvcc.generators.ir2c.SignalLine import SignalLine
from hvcc.generators.ir2c.SignalLorenz import SignalLorenz
from hvcc.generators.ir2c.SignalMath import SignalMath
@@ -78,6 +80,8 @@ class ir2c:
"__cast_b": ControlCast,
"__cast_f": ControlCast,
"__cast_s": ControlCast,
+ "__expr": ControlExpr,
+ "__expr~": SignalExpr,
"__message": ControlMessage,
"__system": ControlSystem,
"__receive": ControlReceive,
@@ -197,8 +201,13 @@ def compile(
free_list = []
def_list = []
decl_list = []
+ obj_header_lines = []
+ obj_impl_lines = []
+ class_header_lines = []
+ class_impl_lines = []
for obj_id in ir["init"]["order"]:
o = ir["objects"][obj_id]
+ print("init objects:", o["type"])
obj_class = ir2c.get_class(o["type"])
init_list.extend(obj_class.get_C_init(o["type"], obj_id, o["args"]))
def_list.extend(obj_class.get_C_def(o["type"], obj_id))
@@ -208,13 +217,15 @@ def compile(
for x in ir["control"]["sendMessage"]:
obj_id = x["id"]
o = ir["objects"][obj_id]
+ print("control objects:", o["type"])
obj_class = ir2c.get_class(o["type"])
impl = obj_class.get_C_impl(
o["type"],
obj_id,
x["onMessage"],
ir2c.get_class,
- ir["objects"])
+ ir["objects"],
+ o["args"])
impl_list.append("\n".join(PrettyfyC.prettyfy_list(impl)))
decl_list.extend(obj_class.get_C_decl(o["type"], obj_id, o["args"]))
@@ -230,15 +241,36 @@ def compile(
# generate the list of functions to process
process_list: List = []
+ # print("--------------- for each signal in order")
+ process_classes = set()
for x in ir["signal"]["processOrder"]:
+ # print("--- signal", x["id"], o["type"], ir2c.get_class(o["type"]))
obj_id = x["id"]
o = ir["objects"][obj_id]
- process_list.extend(ir2c.get_class(o["type"]).get_C_process(
+ print("process objects:", o["type"])
+ obj_cls = ir2c.get_class(o["type"])
+ process_classes.add(obj_cls)
+ process_list.extend(obj_cls.get_C_process(
x,
o["type"],
obj_id,
o["args"]))
+ # begin experiment for expr~
+ obj_header_lines.extend(obj_cls.get_C_obj_header_code(
+ o["type"], obj_id, o["args"]
+ ))
+ obj_impl_lines.extend(obj_cls.get_C_obj_impl_code(
+ o["type"], obj_id, o["args"]
+ ))
+ # once for each class
+ for prc_cls in process_classes:
+ class_header_lines.extend(prc_cls.get_C_class_header_code(
+ o["type"], o["args"]
+ ))
+ class_impl_lines.extend(prc_cls.get_C_class_impl_code(
+ o["type"], o["args"]
+ ))
#
# Load the C-language template files and use the parsed strings to fill them in.
#
@@ -262,7 +294,9 @@ def compile(
def_list=def_list,
signal=ir["signal"],
copyright=copyright,
- externs=externs))
+ externs=externs,
+ class_header_lines=class_header_lines,
+ obj_header_lines=obj_header_lines))
# write C++ implementation
with open(os.path.join(output_dir, f"Heavy_{name}.cpp"), "w") as f:
@@ -276,7 +310,9 @@ def compile(
send_table=ir["tables"],
process_list=process_list,
table_data_list=table_data_list,
- copyright=copyright))
+ copyright=copyright,
+ class_impl_lines=class_impl_lines,
+ obj_impl_lines=obj_impl_lines))
# write C API, hv_NAME.h
with open(os.path.join(output_dir, f"Heavy_{name}.h"), "w") as f:
diff --git a/hvcc/generators/ir2c/static/HvControlExpr.c b/hvcc/generators/ir2c/static/HvControlExpr.c
new file mode 100644
index 00000000..b15f05f9
--- /dev/null
+++ b/hvcc/generators/ir2c/static/HvControlExpr.c
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2014-2018 Enzien Audio Ltd.
+ *
+ * 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 "HvControlExpr.h"
+
+hv_size_t cExpr_init(ControlExpr *o, float(*eval_fptr)(float*)) {
+ o->eval_fptr = eval_fptr;
+ for(int i=0; i < MAX_EXPR_ARGS; i++) {
+ o->args[i] = 0.0f;
+ }
+ return 0;
+}
+
+void cExpr_free(ControlExpr *o) {
+ ;
+}
+
+
+void cExpr_onMessage(HeavyContextInterface *_c, ControlExpr *o, int letIn, const HvMessage *m,
+ void (*sendMessage)(HeavyContextInterface *, int, const HvMessage *)) {
+
+ int numElements = msg_getNumElements(m);
+ switch (letIn) {
+ case 0: { // first inlet stores all values of input msg and triggers an output
+ if (msg_isBang(m,0)) {
+ ; // pass through to sending the msg below
+
+ } else if (! msg_isFloat(m,0)) {
+ printf("Got value other than bang or float\n");
+ break;
+
+ } else {
+ for (int i = hv_min_i(numElements, msg_getNumElements(m))-1; i >= 0; --i) {
+ if (msg_isFloat(m, i)) {
+ o->args[i] = msg_getFloat(m, i);
+ } else {
+ printf("Got value other than float\n");
+ }
+ }
+ }
+
+ // send result of expression
+ HvMessage *n = HV_MESSAGE_ON_STACK(1);
+ float f = o->eval_fptr(o->args);
+ msg_initWithFloat(n, msg_getTimestamp(m), f);
+ sendMessage(_c, 0, n);
+ break;
+ }
+ default: { // rest of inlets just store values
+ if (msg_isFloat(m,0)) {
+ o->args[letIn] = msg_getFloat(m, 0);
+ } else {
+ printf("Got value other than float\n");
+ }
+ break;
+ }
+ }
+}
diff --git a/hvcc/generators/ir2c/static/HvControlExpr.h b/hvcc/generators/ir2c/static/HvControlExpr.h
new file mode 100644
index 00000000..7c062f33
--- /dev/null
+++ b/hvcc/generators/ir2c/static/HvControlExpr.h
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2014-2018 Enzien Audio Ltd.
+ *
+ * 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 _HEAVY_CONTROL_EXPR_H_
+#define _HEAVY_CONTROL_EXPR_H_
+
+#include "HvHeavyInternal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX_EXPR_ARGS 10
+
+typedef struct ControlExpr {
+ float args[MAX_EXPR_ARGS];
+ float(*eval_fptr)(float*);
+} ControlExpr;
+
+hv_size_t cExpr_init(ControlExpr *o, float(*eval_fptr)(float*));
+
+void cExpr_free(ControlExpr *o);
+
+void cExpr_onMessage(HeavyContextInterface *_c, ControlExpr *o, int letIn, const HvMessage *m,
+ void (*sendMessage)(HeavyContextInterface *, int, const HvMessage *));
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // _HEAVY_CONTROL_EXPR_H_
diff --git a/hvcc/generators/ir2c/static/HvControlPack.c b/hvcc/generators/ir2c/static/HvControlPack.c
index 7ae73430..edfa575c 100644
--- a/hvcc/generators/ir2c/static/HvControlPack.c
+++ b/hvcc/generators/ir2c/static/HvControlPack.c
@@ -22,14 +22,15 @@ hv_size_t cPack_init(ControlPack *o, int nargs, ...) {
hv_assert(o->msg != NULL);
msg_init(o->msg, nargs, 0);
- // variable arguments are used as float initialisers for the pack elements
+ // variable arguments are used as float initialisers for the Expr elements
va_list ap;
va_start(ap, nargs);
for (int i = 0; i < nargs; ++i) {
msg_setFloat(o->msg, i, (float) va_arg(ap, double));
}
va_end(ap);
- return numBytes;
+ return numBytes;
+
}
void cPack_free(ControlPack *o) {
diff --git a/hvcc/generators/ir2c/templates/Heavy_NAME.cpp b/hvcc/generators/ir2c/templates/Heavy_NAME.cpp
index a9e0a33c..c7d8a8fc 100644
--- a/hvcc/generators/ir2c/templates/Heavy_NAME.cpp
+++ b/hvcc/generators/ir2c/templates/Heavy_NAME.cpp
@@ -155,6 +155,21 @@ void Heavy_{{name}}::{{x}}
{%- endfor %}
+/*
+ * Experimental code for expr/expr~ implementation
+ * Write out the generic header code
+ */
+
+ // per class code
+ {%- for line in class_impl_lines %}
+ {{line}}
+ {%- endfor %}
+
+ // per object code
+ {%- for line in obj_impl_lines %}
+ {{line}}
+ {%- endfor %}
+
/*
* Context Process Implementation
diff --git a/hvcc/generators/ir2c/templates/Heavy_NAME.hpp b/hvcc/generators/ir2c/templates/Heavy_NAME.hpp
index 75777a7c..d03df5a1 100644
--- a/hvcc/generators/ir2c/templates/Heavy_NAME.hpp
+++ b/hvcc/generators/ir2c/templates/Heavy_NAME.hpp
@@ -85,6 +85,23 @@ class Heavy_{{name}} : public HeavyContext {
HvTable *getTableForHash(hv_uint32_t tableHash) override;
void scheduleMessageForReceiver(hv_uint32_t receiverHash, HvMessage *m) override;
+
+ /*
+ * Experimental code for expr/expr~ implementation
+ * Write out the generic header code
+ */
+
+ // per class code
+ {%- for line in class_header_lines %}
+ {{line}}
+ {%- endfor %}
+
+ // per object code
+ {%- for line in obj_header_lines %}
+ {{line}}
+ {%- endfor %}
+
+
// static sendMessage functions
{%- for d in decl_list %}
static void {{d}}
diff --git a/hvcc/interpreters/pd2hv/PdExprObject.py b/hvcc/interpreters/pd2hv/PdExprObject.py
new file mode 100644
index 00000000..f449597a
--- /dev/null
+++ b/hvcc/interpreters/pd2hv/PdExprObject.py
@@ -0,0 +1,90 @@
+# Copyright (C) 2014-2018 Enzien Audio, Ltd.
+#
+# 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 .
+import re
+
+from .PdObject import PdObject
+
+from typing import Optional, List
+
+
+class PdExprObject(PdObject):
+ """
+ Limitations (compared to vanilla pd):
+ - only supports a single expression
+ - pd docs say expr support 9 variables, experiments show that
+ it supports at least 20... This version currently supports up
+ to 10 variables as defined in HvControlExpr.h
+ - I don't know what pd-expr does with strings, haven't experimented
+ and haven't given it any thought yet here
+
+ Bugs:
+ - skipped variables cause crash, like "$f1 + $f3"
+ """
+
+ def __init__(
+ self,
+ obj_type: str,
+ obj_args: Optional[List] = None,
+ pos_x: int = 0,
+ pos_y: int = 0
+ ) -> None:
+ """
+ Validate the expr object and any heavy restrictions, then
+ convert it directly into a HeavyIR object.
+ """
+ # self.obj_type = obj_type
+
+ print("In Pd expr Obj")
+ assert obj_type in ["expr", "expr~"]
+ super().__init__(obj_type, obj_args, pos_x, pos_y)
+
+ # turn the arguments into a list of expressions, but only one for now
+ if len(self.obj_args) == 0:
+ self.add_error("Empty expression")
+
+ expressions = [e.strip() for e in " ".join(self.obj_args).split("\\;")]
+ if len(expressions) > 1:
+ self.add_error("Heavy expr does not support multiple expressions")
+ else:
+ self.expressions = expressions
+
+ # count the number of inlets
+ var_nums = {
+ int(var[2:]) for var in
+ # this should be checked separately for control vs. signal instances
+ # fis for control, v for signal
+ re.findall(r"\$[fisv]\d+", self.expressions[0])
+ }
+ self.num_inlets = max(var_nums) if len(var_nums) > 0 else 1
+ if self.num_inlets > 10:
+ self.add_error("Heavy expr supports upto 10 variables")
+
+ def validate_configuration(self):
+ # things that could be validated:
+ # - inlet count/types match variables in the expression(s)
+ pass
+
+ def to_hv(self):
+ return {
+ "type": f"__{self.obj_type}",
+ "args": {
+ "expressions": self.expressions,
+ "num_inlets": self.num_inlets
+ },
+ "properties": {
+ "x": self.pos_x,
+ "y": self.pos_y
+ }
+ }
diff --git a/hvcc/interpreters/pd2hv/PdParser.py b/hvcc/interpreters/pd2hv/PdParser.py
index f0818de4..2647a64a 100644
--- a/hvcc/interpreters/pd2hv/PdParser.py
+++ b/hvcc/interpreters/pd2hv/PdParser.py
@@ -26,6 +26,7 @@
from .HvSwitchcase import HvSwitchcase # __switchcase
from .PdAudioIoObject import PdAudioIoObject # adc~/dac~
from .PdBinopObject import PdBinopObject # binary arithmatic operators
+from .PdExprObject import PdExprObject # expr/expr~
from .PdGraph import PdGraph # canvas
from .PdLetObject import PdLetObject # inlet/inlet~/outlet/outlet~
from .PdMessageObject import PdMessageObject # msg
@@ -344,6 +345,7 @@ def graph_from_canvas(
# do we have an abstraction for this object?
abs_path = self.find_abstraction_path(os.path.dirname(pd_path), obj_type)
if abs_path is not None and not g.is_abstraction_on_call_stack(abs_path):
+ # print("PdParser: find object - abstraction")
# ensure that infinite recursion into abstractions is not possible
x = self.graph_from_file(
file_path=abs_path,
@@ -353,6 +355,7 @@ def graph_from_canvas(
# is this object in lib/pd_converted?
elif os.path.isfile(os.path.join(self.__PDLIB_CONVERTED_DIR, f"{obj_type}.hv.json")):
+ # print("PdParser: find object - pd_converted")
self.obj_counter[obj_type] += 1
hv_path = os.path.join(self.__PDLIB_CONVERTED_DIR, f"{obj_type}.hv.json")
x = HeavyGraph(
@@ -362,6 +365,7 @@ def graph_from_canvas(
# is this object in lib/heavy_converted?
elif os.path.isfile(os.path.join(self.__HVLIB_CONVERTED_DIR, f"{obj_type}.hv.json")):
+ # print("PdParser: find object - heavy_converted")
self.obj_counter[obj_type] += 1
hv_path = os.path.join(self.__HVLIB_CONVERTED_DIR, f"{obj_type}.hv.json")
x = HeavyGraph(
@@ -371,6 +375,7 @@ def graph_from_canvas(
# is this object in lib/pd?
elif os.path.isfile(os.path.join(self.__PDLIB_DIR, f"{obj_type}.pd")):
+ # print("PdParser: find object - pd")
self.obj_counter[obj_type] += 1
pdlib_path = os.path.join(self.__PDLIB_DIR, f"{obj_type}.pd")
@@ -411,6 +416,7 @@ def graph_from_canvas(
# is this object in lib/heavy?
elif os.path.isfile(os.path.join(self.__HVLIB_DIR, f"{obj_type}.pd")):
+ # print("PdParser: find object - heavy")
self.obj_counter[obj_type] += 1
hvlib_path = os.path.join(self.__HVLIB_DIR, f"{obj_type}.pd")
x = self.graph_from_file(
@@ -441,6 +447,7 @@ def graph_from_canvas(
# is this an object that must be programatically parsed?
elif obj_type in self.__PD_CLASSES:
+ # print("PdParser: find object - prog-parse")
self.obj_counter[obj_type] += 1
obj_class = self.__PD_CLASSES[obj_type]
x = obj_class(
@@ -449,6 +456,7 @@ def graph_from_canvas(
pos_x=int(line[2]), pos_y=int(line[3]))
elif self.__is_float(obj_type):
+ # print("PdParser: find object - float")
# parse float literals
self.obj_counter["float"] += 1
x = HeavyObject(
@@ -648,6 +656,8 @@ def __resolve_object_args(
__PD_CLASSES = {
"adc~": PdAudioIoObject,
"dac~": PdAudioIoObject,
+ "expr": PdExprObject,
+ "expr~": PdExprObject,
"inlet": PdLetObject,
"inlet~": PdLetObject,
"outlet": PdLetObject,
diff --git a/setup.cfg b/setup.cfg
index 6b8e2844..4c77be9b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -22,6 +22,7 @@ include_package_data = True
packages = find:
python_requires = >= 3.8
install_requires =
+ Arpeggio>=2.0.0
Jinja2>=2.11
importlib_resources>=5.1
wstd2daisy>=0.5.2
diff --git a/tests/pd/control_expr/test-div.golden.txt b/tests/pd/control_expr/test-div.golden.txt
new file mode 100644
index 00000000..659e01a3
--- /dev/null
+++ b/tests/pd/control_expr/test-div.golden.txt
@@ -0,0 +1 @@
+[@ 0.000] print: 0.148387
diff --git a/tests/pd/control_expr/test-div.pd b/tests/pd/control_expr/test-div.pd
new file mode 100644
index 00000000..a6ff55a3
--- /dev/null
+++ b/tests/pd/control_expr/test-div.pd
@@ -0,0 +1,16 @@
+#N canvas 670 156 450 300 12;
+#X obj 55 17 loadbang;
+#X obj 55 152 print;
+#X msg 116 77 3.1;
+#X msg 55 77 2.3;
+#X obj 55 42 t b b b;
+#X msg 178 77 5.5;
+#X obj 55 119 expr ($f1/$f2)/$i3;
+#X connect 0 0 4 0;
+#X connect 2 0 6 1;
+#X connect 3 0 6 0;
+#X connect 4 0 3 0;
+#X connect 4 1 2 0;
+#X connect 4 2 5 0;
+#X connect 5 0 6 2;
+#X connect 6 0 1 0;
diff --git a/tests/pd/control_expr/test-mult.golden.txt b/tests/pd/control_expr/test-mult.golden.txt
new file mode 100644
index 00000000..46f3126d
--- /dev/null
+++ b/tests/pd/control_expr/test-mult.golden.txt
@@ -0,0 +1 @@
+[@ 0.000] print: 35.65
diff --git a/tests/pd/control_expr/test-mult.pd b/tests/pd/control_expr/test-mult.pd
new file mode 100644
index 00000000..40e521c5
--- /dev/null
+++ b/tests/pd/control_expr/test-mult.pd
@@ -0,0 +1,16 @@
+#N canvas 670 156 450 300 12;
+#X obj 55 17 loadbang;
+#X obj 55 152 print;
+#X msg 109 77 3.1;
+#X msg 55 77 2.3;
+#X obj 55 119 expr $f1*$f2*$i3;
+#X obj 55 42 t b b b;
+#X msg 164 77 5.5;
+#X connect 0 0 5 0;
+#X connect 2 0 4 1;
+#X connect 3 0 4 0;
+#X connect 4 0 1 0;
+#X connect 5 0 3 0;
+#X connect 5 1 2 0;
+#X connect 5 2 6 0;
+#X connect 6 0 4 2;
diff --git a/tests/test_control_expr.py b/tests/test_control_expr.py
new file mode 100755
index 00000000..c38a4eb7
--- /dev/null
+++ b/tests/test_control_expr.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2014-2018 Enzien Audio, Ltd.
+# Copyright (C) 2022 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 .
+
+import argparse
+import os
+
+from tests.framework.base_control import TestPdControlBase
+
+
+class TestPdControlExprPatches(TestPdControlBase):
+ SCRIPT_DIR = os.path.dirname(__file__)
+ TEST_DIR = os.path.join(os.path.dirname(__file__), "pd", "control_expr")
+
+ def test_mult(self):
+ self._test_control_patch("test-mult.pd")
+
+ def test_div(self):
+ self._test_control_patch("test-div.pd")
+
+
+def main():
+ # TODO(mhroth): make this work
+ parser = argparse.ArgumentParser(
+ description="Compile a specific pd patch.")
+ parser.add_argument(
+ "pd_path",
+ help="The path to the Pd file to read.")
+ args = parser.parse_args()
+ if os.path.exists(args.pd_path):
+ result = TestPdControlExprPatches._test_control_patch(args.pd_path)
+ print(result)
+ else:
+ print(f"Pd file path '{args.pd_path}' doesn't exist")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tox.ini b/tox.ini
index 812a253b..bd892768 100644
--- a/tox.ini
+++ b/tox.ini
@@ -35,7 +35,7 @@ deps =
basepython =
python3
commands =
- mypy hvcc
+ mypy --ignore-missing-imports hvcc
[run]
ignore = examples/*
@@ -44,4 +44,5 @@ omit = .tox/*,venv/*,tests/*,examples/*,setup.py
[flake8]
max-line-length = 120
-exclude = .tox,venv,build,examples
+exclude =
+ .tox,venv,build,examples