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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
#!/usr/bin/env python3
import sys
import argparse
import subprocess
import uuid
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 = ""
widgets = xml.findall(".//ui/activewidgets/widget[@type='hslider']")
for widget in widgets:
varname = widget.find("varname").text
label = widget.find("label").text
max = widget.find("max").text
maybe_unit = widget.find("meta[@key='unit']")
pitch = isinstance(maybe_unit, ET.Element) and maybe_unit.text == "Hz"
if pitch:
param_type = "frac32.s.map.pitch"
else:
param_type = "frac32.u.map"
inlets += f'<frac32 name="{label}" />\n'
params += f'<{param_type} name="{label}" />\n'
# TODO: summation and conversion based on type
if pitch:
output += (
f"uint32_t {label} = mtof48k_ext_q31(param_{label} + inlet_{label});\n"
)
else:
output += f"uint32_t {label} = param_{label} + inlet_{label};\n"
output += f"mdsp.{varname} = fminf(1.0f, q27_to_float({label})) * {max};\n"
return (inlets, params, output)
def safe_extract_text(xml, xpath, alternative):
result = xml.find(xpath)
if isinstance(result, ET.Element):
return result.text
else:
return alternative
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")
# - inject params and inlets
# - replace scaling factors for sliders
# - replace metadata
id = uuid.uuid4()
author = safe_extract_text(xml, "./author", "unknown")
name = safe_extract_text(xml, "./name", "unnamed")
license = safe_extract_text(xml, "./license", "CC-SA 4.0")
description = safe_extract_text(
xml, "./description", "Generated from Faust DSP code."
)
inlets, params, conversions = process_sliders(xml)
seen_start = False
seen_end = False
skip_ifdef = False
replacements = {
"UUID": str(id),
"AUTHOR": author,
"NAME": name,
"LICENSE": license,
"DESCRIPTION": description,
"INLETS": inlets,
"PARAMS": params,
"SLIDERS": conversions,
}
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 line.startswith("#ifdef __cplusplus"):
skip_ifdef = True
if skip_ifdef:
if line.startswith("#endif"):
skip_ifdef = False
continue
for key, value in replacements.items():
if "<<" + key + ">>" in line:
line = line.replace("<<" + key + ">>", value)
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)
|