summaryrefslogtreecommitdiff
path: root/faust2axo.py
blob: 21572218e4a1689c395fcffdf606eb6d438fee2b (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3

import sys
import argparse
import subprocess
import xml.etree.ElementTree as ET


def run_faust(dsp_file, output_filename):
    """Run Faust on the DSP_FILE, write the output to OUTPUT_FILENAME, and return the parsed XML tree of the description file."""
    with open(output_filename, "w") as outfile:
        result = subprocess.run(
            [
                "faust",
                "-xml",  # generate description
                "-lang",
                "c",  # C code, please
                "-scal",  # non-vectorized code
                "-light",  # keep it light
                "-a",
                "faust-ksoloti-object.c",  # architecture file
                dsp_file,
            ],
            stdout=outfile,
            check=True,
        )
    return result


def process_sliders(xml):
    """Process sliders and return inlets, params, and conversions."""
    inlets = ""
    params = ""
    output = ""
    sliders = xml.findall(".//ui/activewidgets/widget[@type='hslider']")
    for slider in sliders:
        attributes = slider.attrib
        varname = slider.find("varname").text
        label = slider.find("label").text
        max = slider.find("max").text

        inlets += f"<frac32 name=\"{label}\" />\n"
        params += f"<frac32.u.map name=\"{label}\" />\n"

        # TODO: summation and conversion based on type
        output += f"int32_t {label} = mtof48k_ext_q31(param_{label} + inlet_{label});\n"
        output += f"mdsp.{varname} = fminf(1.0f, q27_to_float({label})) * {max};\n"
    return (inlets, params, output)


def massage_output(oldfile, newfile, xml):
    """
    Process the Faust output.
    """

    # - throw everything before <objdefs> away
    # - throw everything after </objdefs> away
    # - remove extern "C" chunk (wrapped in "#ifdef __cplusplus")
    # - replace scaling factors for sliders
    # - replace metadata

    inlets, params, conversions = process_sliders(xml)
    
    seen_start = False
    seen_end = False
    skip_ifdef = False

    with open(oldfile, "r", encoding="utf-8") as infile, open(
        newfile, "w", encoding="utf-8"
    ) as outfile:
        for line in infile:
            if not seen_start and line.startswith("<objdefs>"):
                seen_start = True
            if not seen_end and line.startswith("</objdefs>"):
                seen_end = True
            if seen_end:
                outfile.write(line)
                return

            if seen_start:
                if "<<INLETS>>" in line:
                    outfile.write(line.replace("<<INLETS>>", inlets))
                    continue

                if "<<PARAMS>>" in line:
                    outfile.write(line.replace("<<PARAMS>>", params))
                    continue

                if "<<SLIDERS>>" in line:
                    outfile.write(line.replace("<<SLIDERS>>", conversions))
                    continue

                if line.startswith("#ifdef __cplusplus"):
                    skip_ifdef = True

                if skip_ifdef:
                    if line.startswith("#endif"):
                        skip_ifdef = False
                    else:
                        continue

                outfile.write(line)
            else:
                continue


def main(faust_input_file, axo_output_file):
    axo_temp_output_file = axo_output_file + ".temp"
    run_faust(faust_input_file, axo_temp_output_file)

    # Parse generated XML description
    dsp_xml = faust_input_file + ".xml"
    xml = ET.parse(dsp_xml).getroot()

    # Modify Axoloti object file
    massage_output(axo_temp_output_file, axo_output_file, xml)


def arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument("-i", "--input", type=str, required=True)
    parser.add_argument("-o", "--output", type=str, required=True)
    return parser.parse_args()


if __name__ == "__main__":
    args = arguments()
    main(args.input, args.output)