From 925403e7e727ee038c3eb58b5f6ef4b83c67cdc7 Mon Sep 17 00:00:00 2001 From: dreamer <1185977+dromer@users.noreply.github.com> Date: Sun, 1 Dec 2024 11:21:44 +0100 Subject: [PATCH] Feature/generator typing (#216) * initial compiler results typing * more compiler results typing * dates and correct exception enum * cleanup --- hvcc/__init__.py | 72 +++++++++-------- hvcc/core/hv2ir/hv2ir.py | 89 +++++++++++---------- hvcc/generators/c2daisy/c2daisy.py | 60 +++++++------- hvcc/generators/c2dpf/c2dpf.py | 64 +++++++-------- hvcc/generators/c2js/c2js.py | 65 +++++++-------- hvcc/generators/c2owl/c2owl.py | 63 +++++++-------- hvcc/generators/c2pdext/c2pdext.py | 65 +++++++-------- hvcc/generators/c2unity/c2unity.py | 64 +++++++-------- hvcc/generators/c2wwise/c2wwise.py | 64 +++++++-------- hvcc/generators/ir2c/ir2c.py | 30 +++---- hvcc/generators/ir2c/ir2c_perf.py | 18 +++-- hvcc/interpreters/pd2hv/NotificationEnum.py | 3 + hvcc/interpreters/pd2hv/pd2hv.py | 83 +++++++++---------- hvcc/types/__.init__.py | 0 hvcc/types/compiler.py | 51 ++++++++++++ tests/framework/base_test.py | 34 ++++---- tests/test_control.py | 3 +- 17 files changed, 424 insertions(+), 404 deletions(-) create mode 100644 hvcc/types/__.init__.py create mode 100644 hvcc/types/compiler.py diff --git a/hvcc/__init__.py b/hvcc/__init__.py index 5c384ad6..c2513eb0 100644 --- a/hvcc/__init__.py +++ b/hvcc/__init__.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -35,6 +35,7 @@ from hvcc.generators.c2wwise import c2wwise from hvcc.generators.c2unity import c2unity from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import CompilerResp, CompilerNotif, CompilerMsg class Colours: @@ -50,19 +51,18 @@ class Colours: end = "\033[0m" -def add_error(results: OrderedDict, error: str) -> OrderedDict: +def add_error( + results: Dict[str, CompilerResp], + error: str +) -> Dict[str, CompilerResp]: if "hvcc" in results: - results["hvcc"]["notifs"]["errors"].append({"message": error}) + results["hvcc"].notifs.errors.append(CompilerMsg(message=error)) else: - results["hvcc"] = { - "stage": "hvcc", - "notifs": { - "has_error": True, - "exception": None, - "errors": [{"message": error}], - "warnings": [] - } - } + results["hvcc"] = CompilerResp(stage="hvcc", + notifs=CompilerNotif( + has_error=True, + errors=[CompilerMsg(message=error)], + )) return results @@ -212,9 +212,9 @@ def compile_dataflow( verbose: bool = False, copyright: Optional[str] = None, nodsp: Optional[bool] = False -) -> OrderedDict: +) -> Dict[str, CompilerResp]: - results: OrderedDict = OrderedDict() # default value, empty dictionary + results: OrderedDict[str, CompilerResp] = OrderedDict() # default value, empty dictionary patch_meta = Meta() # basic error checking on input @@ -250,23 +250,26 @@ def compile_dataflow( verbose=verbose) # check for errors - if list(results.values())[0]["notifs"].get("has_error", False): + + response: CompilerResp = list(results.values())[0] + + if response.notifs.has_error: return results subst_name = re.sub(r'\W', '_', patch_name) results["hv2ir"] = hv2ir.hv2ir.compile( - hv_file=os.path.join(list(results.values())[0]["out_dir"], list(results.values())[0]["out_file"]), + hv_file=os.path.join(response.out_dir, response.out_file), # ensure that the ir filename has no funky characters in it ir_file=os.path.join(out_dir, "ir", f"{subst_name}.heavy.ir.json"), patch_name=patch_name, verbose=verbose) # check for errors - if results["hv2ir"]["notifs"].get("has_error", False): + if results["hv2ir"].notifs.has_error: return results # get the hvir data - hvir = results["hv2ir"]["ir"] + hvir = results["hv2ir"].ir patch_name = hvir["name"]["escaped"] externs = generate_extern_info(hvir, results) @@ -278,7 +281,7 @@ def compile_dataflow( c_src_dir = os.path.join(out_dir, "c") results["ir2c"] = ir2c.ir2c.compile( - hv_ir_path=os.path.join(results["hv2ir"]["out_dir"], results["hv2ir"]["out_file"]), + hv_ir_path=os.path.join(results["hv2ir"].out_dir, results["hv2ir"].out_file), static_dir=os.path.join(application_path, "generators/ir2c/static"), output_dir=c_src_dir, externs=externs, @@ -286,17 +289,16 @@ def compile_dataflow( nodsp=nodsp) # check for errors - if results["ir2c"]["notifs"].get("has_error", False): + if results["ir2c"].notifs.has_error: return results # ir2c_perf - results["ir2c_perf"] = { - "stage": "ir2c_perf", - "obj_counter": ir2c_perf.ir2c_perf.perf(results["hv2ir"]["ir"], verbose=verbose), - "in_dir": results["hv2ir"]["out_dir"], - "in_file": results["hv2ir"]["out_file"], - "notifs": {} - } + results["ir2c_perf"] = CompilerResp( + stage="ir2c_perf", + obj_perf=ir2c_perf.ir2c_perf.perf(results["hv2ir"].ir, verbose=verbose), + in_dir=results["hv2ir"].out_dir, + in_file=results["hv2ir"].out_file, + ) # reconfigure such that next stage is triggered in_path = c_src_dir @@ -442,25 +444,25 @@ def main() -> bool: errorCount = 0 for r in list(results.values()): # print any errors - if r["notifs"].get("has_error", False): - for i, error in enumerate(r["notifs"].get("errors", [])): + if r.notifs.has_error: + for i, error in enumerate(r.notifs.errors): errorCount += 1 print("{4:3d}) {2}Error{3} {0}: {1}".format( - r["stage"], error["message"], Colours.red, Colours.end, i + 1)) + r.stage, error.message, Colours.red, Colours.end, i + 1)) # only print exception if no errors are indicated - if len(r["notifs"].get("errors", [])) == 0 and r["notifs"].get("exception", None) is not None: + if len(r.notifs.errors) == 0 and r.notifs.exception is not None: errorCount += 1 print("{2}Error{3} {0} exception: {1}".format( - r["stage"], r["notifs"]["exception"], Colours.red, Colours.end)) + r.stage, r.notifs.exception, Colours.red, Colours.end)) # clear any exceptions such that results can be JSONified if necessary - r["notifs"]["exception"] = [] + r.notifs.exception = None # print any warnings - for i, warning in enumerate(r["notifs"].get("warnings", [])): + for i, warning in enumerate(r.notifs.warnings): print("{4:3d}) {2}Warning{3} {0}: {1}".format( - r["stage"], warning["message"], Colours.yellow, Colours.end, i + 1)) + r.stage, warning.message, Colours.yellow, Colours.end, i + 1)) if args.results_path: results_path = os.path.realpath(os.path.abspath(args.results_path)) diff --git a/hvcc/core/hv2ir/hv2ir.py b/hvcc/core/hv2ir/hv2ir.py index 8eaf6535..7f4e0b49 100644 --- a/hvcc/core/hv2ir/hv2ir.py +++ b/hvcc/core/hv2ir/hv2ir.py @@ -19,11 +19,13 @@ import os import time -from typing import Optional, Dict +from typing import Optional from .HeavyException import HeavyException from .HeavyParser import HeavyParser +from hvcc.types.compiler import CompilerResp, CompilerNotif, CompilerMsg + class hv2ir: @@ -34,7 +36,7 @@ def compile( ir_file: str, patch_name: Optional[str] = None, verbose: bool = False - ) -> Dict: + ) -> CompilerResp: """ Compiles a HeavyLang file into a HeavyIR file. Returns a tuple of compile time in seconds, a notification dictionary, and a heavy object counter. @@ -50,21 +52,20 @@ def compile( # parse heavy file hv_graph = HeavyParser.graph_from_file(hv_file=hv_file, xname=patch_name) except HeavyException as e: - return { - "stage": "hv2ir", - "compile_time": time.time() - tick, - "notifs": { - "has_error": True, - "exception": e, - "errors": [{"message": e.message}], - "warnings": [] - }, - "obj_counter": None, - "in_file": os.path.basename(hv_file), - "in_dir": os.path.dirname(hv_file), - "out_file": os.path.basename(ir_file), - "out_dir": os.path.dirname(ir_file) - } + return CompilerResp( + stage="hv2ir", + compile_time=time.time() - tick, + notifs=CompilerNotif( + has_error=True, + exception=e, + errors=[CompilerMsg(message=e.message)], + warnings=[] + ), + in_file=os.path.basename(hv_file), + in_dir=os.path.dirname(hv_file), + out_file=os.path.basename(ir_file), + out_dir=os.path.dirname(ir_file) + ) try: # get a counter of all heavy objects @@ -80,21 +81,21 @@ def compile( # generate Heavy.IR ir = hv_graph.to_ir() except HeavyException as e: - return { - "stage": "hv2ir", - "compile_time": time.time() - tick, - "notifs": { - "has_error": True, - "exception": e, - "errors": [{"message": e.message}], - "warnings": [] - }, - "obj_counter": hv_counter, - "in_file": os.path.basename(hv_file), - "in_dir": os.path.dirname(hv_file), - "out_file": os.path.basename(ir_file), - "out_dir": os.path.dirname(ir_file) - } + return CompilerResp( + stage="hv2ir", + compile_time=time.time() - tick, + notifs=CompilerNotif( + has_error=True, + exception=e, + errors=[CompilerMsg(message=e.message)], + warnings=[] + ), + in_file=os.path.basename(hv_file), + in_dir=os.path.dirname(hv_file), + out_file=os.path.basename(ir_file), + out_dir=os.path.dirname(ir_file), + obj_counter=hv_counter + ) # write the hv.ir file with open(ir_file, "w") as f: @@ -121,17 +122,17 @@ def compile( else: print(o["type"]) - return { - "stage": "hv2ir", - "compile_time": time.time() - tick, # record the total compile time - "notifs": hv_graph.get_notices(), - "obj_counter": hv_counter, - "in_file": os.path.basename(hv_file), - "in_dir": os.path.dirname(hv_file), - "out_file": os.path.basename(ir_file), - "out_dir": os.path.dirname(ir_file), - "ir": ir - } + return CompilerResp( + stage="hv2ir", + compile_time=time.time() - tick, # record the total compile time + notifs=CompilerNotif(**hv_graph.get_notices()), + in_file=os.path.basename(hv_file), + in_dir=os.path.dirname(hv_file), + out_file=os.path.basename(ir_file), + out_dir=os.path.dirname(ir_file), + obj_counter=hv_counter, + ir=ir + ) def main() -> None: @@ -158,7 +159,7 @@ def main() -> None: verbose=args.verbose) if args.verbose: - print(f"Total hv2ir time: {(d['compile_time'] * 1000):.2f}ms") + print(f"Total hv2ir time: {(d.compile_time * 1000):.2f}ms") if __name__ == "__main__": diff --git a/hvcc/generators/c2daisy/c2daisy.py b/hvcc/generators/c2daisy/c2daisy.py index 4ef47117..ed6f45fb 100644 --- a/hvcc/generators/c2daisy/c2daisy.py +++ b/hvcc/generators/c2daisy/c2daisy.py @@ -10,6 +10,9 @@ from ..types.meta import Meta, Daisy from . import parameters +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg + hv_midi_messages = { "__hv_noteout", @@ -23,7 +26,7 @@ } -class c2daisy: +class c2daisy(Compiler): """ Generates a Daisy wrapper for a given patch. """ @@ -39,7 +42,7 @@ def compile( num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -138,36 +141,27 @@ def compile( # ====================================================================================== - return { - "stage": "c2daisy", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": os.path.basename(daisy_h_path), - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2daisy", + in_dir=c_src_dir, + out_dir=out_dir, + out_file=os.path.basename(daisy_h_path), + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2daisy", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2daisy", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2dpf/c2dpf.py b/hvcc/generators/c2dpf/c2dpf.py index 90f2b508..558ee379 100644 --- a/hvcc/generators/c2dpf/c2dpf.py +++ b/hvcc/generators/c2dpf/c2dpf.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -21,10 +21,13 @@ from ..copyright import copyright_manager from ..filters import filter_uniqueid -from ..types.meta import Meta, DPF +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta, DPF +from hvcc.types.compiler import Compiler, CompilerResp, CompilerMsg, CompilerNotif -class c2dpf: + +class c2dpf(Compiler): """ Generates a DPF wrapper for a given patch. """ @@ -40,7 +43,7 @@ def compile( num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -133,36 +136,27 @@ def compile( meta=dpf_meta, dpf_path=dpf_path)) - return { - "stage": "c2dpf", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": os.path.basename(dpf_h_path), - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2dpf", + in_dir=c_src_dir, + out_dir=out_dir, + out_file=os.path.basename(dpf_h_path), + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2dpf", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2dpf", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2js/c2js.py b/hvcc/generators/c2js/c2js.py index 75fdd943..daaff852 100644 --- a/hvcc/generators/c2js/c2js.py +++ b/hvcc/generators/c2js/c2js.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -25,8 +25,12 @@ from hvcc.core.hv2ir.HeavyException import HeavyException from ..copyright import copyright_manager +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg -class c2js: + +class c2js(Compiler): """Compiles a directory of C source files into javascript. Requires the emscripten library to be installed - https://github.com/kripken/emscripten """ @@ -159,12 +163,12 @@ def compile( out_dir: str, externs: Dict, patch_name: Optional[str] = None, - patch_meta: Optional[Dict] = None, + patch_meta: Meta = Meta(), num_input_channels: int = 0, num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -263,36 +267,27 @@ def compile( os.remove(post_js_path) os.remove(pre_js_path) - return { - "stage": "c2js", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": js_out_file, - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2js", + in_dir=c_src_dir, + out_dir=out_dir, + out_file=js_out_file, + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2js", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2js", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2owl/c2owl.py b/hvcc/generators/c2owl/c2owl.py index 908938bb..cf96247f 100644 --- a/hvcc/generators/c2owl/c2owl.py +++ b/hvcc/generators/c2owl/c2owl.py @@ -9,12 +9,16 @@ import hvcc.core.hv2ir.HeavyLangObject as HeavyLangObject from ..copyright import copyright_manager +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg + heavy_hash = HeavyLangObject.HeavyLangObject.get_hash OWL_BUTTONS = ['Push', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8'] -class c2owl: +class c2owl(Compiler): """ Generates a OWL wrapper for a given patch. """ @@ -71,12 +75,12 @@ def compile( out_dir: str, externs: Dict, patch_name: Optional[str] = None, - patch_meta: Optional[Dict] = None, + patch_meta: Meta = Meta(), num_input_channels: int = 0, num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -122,36 +126,27 @@ def compile( # ====================================================================================== - return { - "stage": "c2owl", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": os.path.basename(owl_h_path), - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2owl", + in_dir=c_src_dir, + out_dir=out_dir, + out_file=os.path.basename(owl_h_path), + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2owl", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2owl", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2pdext/c2pdext.py b/hvcc/generators/c2pdext/c2pdext.py index 9567a6ae..039da424 100644 --- a/hvcc/generators/c2pdext/c2pdext.py +++ b/hvcc/generators/c2pdext/c2pdext.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -23,8 +23,12 @@ from ..copyright import copyright_manager from ..filters import filter_max +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg -class c2pdext: + +class c2pdext(Compiler): """Generates a Pure Data external wrapper for a given patch. """ @@ -35,12 +39,12 @@ def compile( out_dir: str, externs: Dict, patch_name: Optional[str] = None, - patch_meta: Optional[Dict] = None, + patch_meta: Meta = Meta(), num_input_channels: int = 0, num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -94,36 +98,27 @@ def compile( f.write(env.get_template("Makefile").render( name=patch_name)) - return { - "stage": "c2pdext", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": os.path.basename(pdext_path), - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2pdext", + in_dir=c_src_dir, + out_dir=out_dir, + out_file=os.path.basename(pdext_path), + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2pdext", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2pdext", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2unity/c2unity.py b/hvcc/generators/c2unity/c2unity.py index 09150b3e..21433b3b 100644 --- a/hvcc/generators/c2unity/c2unity.py +++ b/hvcc/generators/c2unity/c2unity.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -23,8 +23,12 @@ from ..copyright import copyright_manager from ..filters import filter_string_cap, filter_templates, filter_xcode_build, filter_xcode_fileref +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg -class c2unity: + +class c2unity(Compiler): """Generates a Audio Native Plugin wrapper for Unity 5. """ @@ -35,12 +39,12 @@ def compile( out_dir: str, externs: Dict, patch_name: Optional[str] = None, - patch_meta: Optional[Dict] = None, + patch_meta: Meta = Meta(), num_input_channels: int = 0, num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -99,36 +103,26 @@ def compile( compile_files=os.listdir(src_out_dir), copyright=copyright)) - return { - "stage": "c2unity", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2unity", + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2unity", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2unity", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/c2wwise/c2wwise.py b/hvcc/generators/c2wwise/c2wwise.py index aa53ab9b..e492a061 100644 --- a/hvcc/generators/c2wwise/c2wwise.py +++ b/hvcc/generators/c2wwise/c2wwise.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2021-2023 Wasted Audio +# Copyright (C) 2021-2024 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 @@ -23,8 +23,12 @@ from ..copyright import copyright_manager from ..filters import filter_plugin_id +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta +from hvcc.types.compiler import Compiler, CompilerResp, CompilerNotif, CompilerMsg -class c2wwise: + +class c2wwise(Compiler): """Generates a plugin wrapper for Audiokinetic's Wwise game audio middleware platform. """ @@ -36,12 +40,12 @@ def compile( out_dir: str, externs: Dict, patch_name: Optional[str] = None, - patch_meta: Optional[Dict] = None, + patch_meta: Meta = Meta(), num_input_channels: int = 0, num_output_channels: int = 0, copyright: Optional[str] = None, verbose: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() in_parameter_list = externs["parameters"]["in"] @@ -125,36 +129,26 @@ def compile( plugin_id=plugin_id, copyright=copyright_xml if file_name.endswith(".xml") else copyright_c)) - return { - "stage": "c2wwise", - "notifs": { - "has_error": False, - "exception": None, - "warnings": [], - "errors": [] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2wwise", + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) except Exception as e: - return { - "stage": "c2wwise", - "notifs": { - "has_error": True, - "exception": e, - "warnings": [], - "errors": [{ - "enum": -1, - "message": str(e) - }] - }, - "in_dir": c_src_dir, - "in_file": "", - "out_dir": out_dir, - "out_file": "", - "compile_time": time.time() - tick - } + return CompilerResp( + stage="c2wwise", + notifs=CompilerNotif( + has_error=True, + exception=e, + warnings=[], + errors=[CompilerMsg( + enum=NotificationEnum.ERROR_EXCEPTION, + message=str(e) + )] + ), + in_dir=c_src_dir, + out_dir=out_dir, + compile_time=time.time() - tick + ) diff --git a/hvcc/generators/ir2c/ir2c.py b/hvcc/generators/ir2c/ir2c.py index e14c3c20..946d40b0 100644 --- a/hvcc/generators/ir2c/ir2c.py +++ b/hvcc/generators/ir2c/ir2c.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2023 Wasted Audio +# Copyright (C) 2023-2024 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 @@ -65,6 +65,8 @@ from hvcc.generators.ir2c.SignalTabwrite import SignalTabwrite from hvcc.generators.ir2c.SignalVar import SignalVar +from hvcc.types.compiler import CompilerResp + class ir2c: @@ -159,7 +161,7 @@ def compile( externs: Dict, copyright: Optional[str] = None, nodsp: Optional[bool] = False - ) -> Dict: + ) -> CompilerResp: """ Compiles a HeavyIR file into a C. Returns a tuple of compile time in seconds, a notification dictionary, and a HeavyIR object counter. @@ -296,20 +298,14 @@ def compile( # generate HeavyIR object counter ir_counter = Counter([obj["type"] for obj in ir["objects"].values()]) - return { - "stage": "ir2c", - "notifs": { - "has_error": False, - "exception": None, - "errors": [] - }, - "in_dir": os.path.dirname(hv_ir_path), - "in_file": os.path.basename(hv_ir_path), - "out_dir": output_dir, - "out_file": "", - "compile_time": (time.time() - tick), - "obj_counter": ir_counter - } + return CompilerResp( + stage="ir2c", + in_dir=os.path.dirname(hv_ir_path), + in_file=os.path.basename(hv_ir_path), + out_dir=output_dir, + compile_time=(time.time() - tick), + obj_counter=ir_counter + ) def main() -> None: @@ -349,7 +345,7 @@ def main() -> None: args.copyright) if args.verbose: - print("Total ir2c time: {0:.2f}ms".format(results["compile_time"] * 1000)) + print("Total ir2c time: {0:.2f}ms".format(results.compile_time * 1000)) if __name__ == "__main__": diff --git a/hvcc/generators/ir2c/ir2c_perf.py b/hvcc/generators/ir2c/ir2c_perf.py index bc8d232e..3e6c7dde 100644 --- a/hvcc/generators/ir2c/ir2c_perf.py +++ b/hvcc/generators/ir2c/ir2c_perf.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2023 Wasted Audio +# Copyright (C) 2023-2024 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 @@ -28,14 +28,20 @@ class ir2c_perf: @classmethod - def perf(cls, ir: Dict, blocksize: int = 512, mhz: int = 1000, verbose: bool = False) -> Dict: + def perf( + cls, + ir: Dict, + blocksize: int = 512, + mhz: int = 1000, + verbose: bool = False + ) -> Dict[str, Counter]: # read the hv.ir.json file with open(os.path.join(os.path.dirname(__file__), "../../core/json/heavy.ir.json"), "r") as f: - HEAVY_IR_JSON = HeavyIRType(json.load(f)).root + HEAVY_IR_JSON = HeavyIRType(**json.load(f)).root objects: Counter = Counter() perf: Counter = Counter() - per_object_perf: Dict = defaultdict(Counter) + per_object_perf: Dict[str, Counter] = defaultdict(Counter) for o in ir["signal"]["processOrder"]: obj_id = o["id"] obj_type = ir["objects"][obj_id]["type"] @@ -54,7 +60,7 @@ def perf(cls, ir: Dict, blocksize: int = 512, mhz: int = 1000, verbose: bool = F mhz, blocksize * perf["avx"] / 8.0 / mhz)) - print # new line + print() # new line print("SSE: {0} cycles / {1} cycles per frame".format(perf["sse"], perf["sse"] / 4.0)) print(" {0} frames @ {1}MHz >= {2:.2f}us".format( @@ -62,7 +68,7 @@ def perf(cls, ir: Dict, blocksize: int = 512, mhz: int = 1000, verbose: bool = F mhz, blocksize * perf["sse"] / 4.0 / mhz)) - print # new line + print() # new line print("{0:<4} {1:<5} {2:<16} {3}".format("CPU%", "#Objs", "Object Type", "Performance")) print("==== ===== ================ ===========") diff --git a/hvcc/interpreters/pd2hv/NotificationEnum.py b/hvcc/interpreters/pd2hv/NotificationEnum.py index 1cde01ee..0f921f78 100644 --- a/hvcc/interpreters/pd2hv/NotificationEnum.py +++ b/hvcc/interpreters/pd2hv/NotificationEnum.py @@ -1,4 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. +# Copyright (C) 2024 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 @@ -22,6 +23,8 @@ class NotificationEnum(Enum): """ These enumerations refer to all possible warnings and errors produced by pd2hv. """ + EMPTY = -1 + # Warnings # an unspecified generic warning diff --git a/hvcc/interpreters/pd2hv/pd2hv.py b/hvcc/interpreters/pd2hv/pd2hv.py index 5e2ce77b..79153712 100644 --- a/hvcc/interpreters/pd2hv/pd2hv.py +++ b/hvcc/interpreters/pd2hv/pd2hv.py @@ -1,5 +1,5 @@ # Copyright (C) 2014-2018 Enzien Audio, Ltd. -# Copyright (C) 2023 Wasted Audio +# Copyright (C) 2023-2024 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 @@ -18,9 +18,10 @@ import json import os import time -from typing import Dict, List, Optional +from typing import List, Optional from hvcc.interpreters.pd2hv.PdParser import PdParser +from hvcc.types.compiler import CompilerResp, CompilerNotif class Colours: @@ -50,7 +51,7 @@ def compile( search_paths: Optional[List] = None, verbose: bool = False, export_args: bool = False - ) -> Dict: + ) -> CompilerResp: tick = time.time() @@ -60,25 +61,22 @@ def compile( parser.add_absolute_search_directory(p) pd_graph = parser.graph_from_file(pd_path) - notices = pd_graph.get_notices() + notices = CompilerNotif(**pd_graph.get_notices()) # TODO: type all get_notices() # check for errors - if len(notices["errors"]) > 0: - return { - "stage": "pd2hv", - "obj_counter": dict(parser.obj_counter), - "notifs": { - "has_error": True, - "exception": None, - "errors": notices["errors"], - "warnings": notices["warnings"] - }, - "in_dir": os.path.dirname(pd_path), - "in_file": os.path.basename(pd_path), - "out_dir": None, - "out_file": None, - "compile_time": (time.time() - tick) - } + if len(notices.errors) > 0: + return CompilerResp( + stage="pd2hv", + obj_counter=parser.obj_counter, + notifs=CompilerNotif( + has_error=True, + errors=notices.errors, + warnings=notices.warnings + ), + in_dir=os.path.dirname(pd_path), + in_file=os.path.basename(pd_path), + compile_time=(time.time() - tick) + ) if not os.path.exists(hv_dir): os.makedirs(hv_dir) @@ -95,21 +93,18 @@ def compile( else: json.dump(pd_graph.to_hv(export_args=export_args), f) - return { - "stage": "pd2hv", - "obj_counter": dict(parser.obj_counter), - "notifs": { - "has_error": False, - "exception": None, - "errors": notices["errors"], - "warnings": notices["warnings"] - }, - "in_dir": os.path.dirname(pd_path), - "in_file": os.path.basename(pd_path), - "out_dir": hv_dir, - "out_file": hv_file, - "compile_time": (time.time() - tick) - } + return CompilerResp( + stage="pd2hv", + obj_counter=parser.obj_counter, + notifs=CompilerNotif( + warnings=notices.warnings + ), + in_dir=os.path.dirname(pd_path), + in_file=os.path.basename(pd_path), + out_dir=hv_dir, + out_file=hv_file, + compile_time=(time.time() - tick) + ) def main() -> None: @@ -142,25 +137,25 @@ def main() -> None: verbose=args.verbose, export_args=args.export) - for i, n in enumerate(result["notifs"]["errors"]): + for i, n in enumerate(result.notifs.errors): print("{0:3d}) {1}Error #{2:4d}:{3} {4}".format( i + 1, Colours.red, - n["enum"], + n.enum, Colours.end, - n["message"])) - for i, n in enumerate(result["notifs"]["warnings"]): + n.message)) + for i, n in enumerate(result.notifs.warnings): print("{0:3d}) {1}Warning #{2:4d}:{3} {4}".format( i + 1, Colours.yellow, - n["enum"], + n.enum, Colours.end, - n["message"])) + n.message)) if args.verbose: - if len(result["notifs"]["errors"]) == 0: - print("Heavy file written to", os.path.join(result["out_dir"], result["out_file"])) - print("Total pd2hv compile time: {0:.2f}ms".format(result["compile_time"] * 1000)) + if len(result.notifs.errors) == 0: + print("Heavy file written to", os.path.join(result.out_dir, result.out_file)) + print("Total pd2hv compile time: {0:.2f}ms".format(result.compile_time * 1000)) if __name__ == "__main__": diff --git a/hvcc/types/__.init__.py b/hvcc/types/__.init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hvcc/types/compiler.py b/hvcc/types/compiler.py new file mode 100644 index 00000000..16e21fa6 --- /dev/null +++ b/hvcc/types/compiler.py @@ -0,0 +1,51 @@ +from abc import ABC, abstractmethod +from collections import Counter +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel + +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum +from hvcc.generators.types.meta import Meta + + +class CompilerMsg(BaseModel): + enum: NotificationEnum = NotificationEnum.EMPTY + message: str + + +class CompilerNotif(BaseModel, arbitrary_types_allowed=True): + has_error: bool = False + exception: Optional[Exception] = None + warnings: List[CompilerMsg] = [] + errors: List[CompilerMsg] = [] + + +class CompilerResp(BaseModel): + stage: str + notifs: CompilerNotif = CompilerNotif() + in_dir: str = "" + in_file: str = "" + out_dir: str = "" + out_file: str = "" + compile_time: float = 0.0 + obj_counter: Counter = Counter() + obj_perf: Dict[str, Counter] = {} + ir: Dict[str, Any] = {} # TODO: improve Any type in Graph objects + + +class Compiler(ABC): + + @abstractmethod + def compile( + cls, + c_src_dir: str, + out_dir: str, + externs: Dict, + patch_name: Optional[str] = None, + patch_meta: Meta = Meta(), + num_input_channels: int = 0, + num_output_channels: int = 0, + copyright: Optional[str] = None, + verbose: Optional[bool] = False + ) -> CompilerResp: + raise NotImplementedError diff --git a/tests/framework/base_test.py b/tests/framework/base_test.py index 070b9029..a455e5ab 100644 --- a/tests/framework/base_test.py +++ b/tests/framework/base_test.py @@ -20,8 +20,12 @@ import subprocess import unittest +from typing import Optional + import hvcc +from hvcc.interpreters.pd2hv.NotificationEnum import NotificationEnum + simd_flags = { "HV_SIMD_NONE": ["-DHV_SIMD_NONE"], @@ -45,10 +49,10 @@ def setUp(self): def _run_hvcc( self, pd_path, - expect_warning: bool = None, - expect_fail: bool = None, - expected_enum: int = None - ): + expect_warning: bool = False, + expect_fail: bool = False, + expected_enum: NotificationEnum = NotificationEnum.EMPTY + ) -> Optional[str]: """Run hvcc on a Pd file. Returns the output directory. """ @@ -63,28 +67,28 @@ def _run_hvcc( if not expect_fail: # if there are any errors from hvcc, fail immediately # TODO(mhroth): standardise how errors and warnings are returned between stages - if r["notifs"].get("has_error", False): - if r["stage"] == "pd2hv": - self.fail(hvcc_results["pd2hv"]["notifs"]["errors"][0]) + if r.notifs.has_error: + if r.stage == "pd2hv": + self.fail(hvcc_results["pd2hv"].notifs.errors[0]) else: - self.fail(str(r["notifs"])) + self.fail(str(r.notifs)) if expect_warning: for r in hvcc_results.values(): - if r["stage"] == "pd2hv": + if r.stage == "pd2hv": self.assertTrue( - expected_enum in [w["enum"] for w in hvcc_results["pd2hv"]["notifs"]["warnings"]] + expected_enum in [w.enum for w in hvcc_results["pd2hv"].notifs.warnings] ) return self.fail(f"Expected warning enum: {expected_enum}") - if expect_fail and r["notifs"].get("has_error", False): - if r["stage"] == "pd2hv": - self.assertTrue(expected_enum in [e["enum"] for e in hvcc_results["pd2hv"]["notifs"]["errors"]]) + if expect_fail and r.notifs.has_error: + if r.stage == "pd2hv": + self.assertTrue(expected_enum in [e.enum for e in hvcc_results["pd2hv"].notifs.errors]) return - elif r["stage"] == "hvcc": - if len(hvcc_results["hvcc"]["notifs"]["errors"]) > 0: + elif r.stage == "hvcc": + if len(hvcc_results["hvcc"].notifs.errors) > 0: return # hvcc isn't using Notification enums so just pass self.fail(f"Expected error enum: {expected_enum}") diff --git a/tests/test_control.py b/tests/test_control.py index 2a2f0054..cdbeb263 100755 --- a/tests/test_control.py +++ b/tests/test_control.py @@ -322,7 +322,8 @@ def main(): help="The path to the Pd file to read.") args = parser.parse_args() if os.path.exists(args.pd_path): - result = TestPdControlPatches._test_control_patch(args.pd_path) + test_control = TestPdControlPatches() + result = test_control._test_control_patch(pd_file=args.pd_path) print(result) else: print(f"Pd file path '{args.pd_path}' doesn't exist")