diff options
author | Ricardo Wurmus <rekado@elephly.net> | 2024-04-30 06:40:17 +0200 |
---|---|---|
committer | Ricardo Wurmus <rekado@elephly.net> | 2024-04-30 06:46:47 +0200 |
commit | 491ae9ba960e65325f3676bff6dec0c3bc74170b (patch) | |
tree | cfb915ec7aa022ee4b9e896572b0d0397f220078 |
Initial commit.
-rw-r--r-- | README.org | 31 | ||||
-rw-r--r-- | faust-ksoloti-object.c | 65 | ||||
-rwxr-xr-x | faust2axo.py | 128 | ||||
-rw-r--r-- | manifest.scm | 1 |
4 files changed, 225 insertions, 0 deletions
diff --git a/README.org b/README.org new file mode 100644 index 0000000..2c93887 --- /dev/null +++ b/README.org @@ -0,0 +1,31 @@ +#+TITLE: Convert Faust DSP to Axoloti objects + +Enter a development environment: + +#+begin_example sh +guix shell -m manifest.scm +#+end_example + +Take a Faust DSP program: + +#+begin_src faust +import("stdfaust.lib"); +va = library("vaeffects.lib"); + +freq = hslider("cutoff [unit:Hz]", 10000, 0, 10000, 0.1) : si.smoo; +res = hslider("resonance", 0, 0, 1, 0.01) : si.smoo; + +process = va.moog_vcf_2b(res, freq); +#+end_src + +And convert it to a crude Axoloti object: + +#+begin_src sh +./faust2axo.py -i moog.dsp -o test.axo +#+end_src + + + +* License + +GPLv3+ diff --git a/faust-ksoloti-object.c b/faust-ksoloti-object.c new file mode 100644 index 0000000..82e575d --- /dev/null +++ b/faust-ksoloti-object.c @@ -0,0 +1,65 @@ +<objdefs> + <obj.normal id="moog_vcf_2b" uuid="0009f089dced01ba64f7e22ac77fd78fbec47db9" sha="000d84e43d5934e94f32e1d50318f8feede69769"> + <sDescription>Moog VCF (moog_vcf_2b)</sDescription> + <author>Ricardo Wurmus (Ksoloti), Julius O. Smith III (Faust)</author> + <license>STK-4.3</license> + <helpPatch/> + <includes> + <include>math.h</include> + <include>axoloti.h</include> + </includes> + <inlets> + <frac32buffer name="in" description="filter input"/> + <<INLETS>> + </inlets> + <outlets> + <frac32buffer name="out" description="filter output"/> + </outlets> + <displays/> + <params> + <<PARAMS>> + </params> + <attribs/> + <code.declaration><![CDATA[ +float fmaxf (float a, float b) { + return a > b ? a : b; +} +float fminf (float a, float b) { + return a < b ? a : b; +} +float tanf(float x) { + return sinf(x) / cosf(x); +} + +//---------------------------------------------------------------------------- +// FAUST generated signal processor +//---------------------------------------------------------------------------- + +<<includeIntrinsic>> + +<<includeclass>> + +mydsp mdsp; +]]></code.declaration> + <code.init><![CDATA[initmydsp(&mdsp, 48000.0);]]></code.init> + <code.dispose><![CDATA[//deletemydsp(mdsp);]]></code.dispose> + <code.krate><![CDATA[// Calculate audio +<<SLIDERS>> + +float inf[BUFSIZE] = {}; +float outf[BUFSIZE] = {}; + +for (uint8_t i = 0; i < BUFSIZE; i++) { + inf[i] = q27_to_float(inlet_in[i]); +} + +float* inp = (float*) &inf; +float* outp = (float*) &outf; + +computemydsp(&mdsp, BUFSIZE, *inp, *outp); + +for (uint8_t i = 0; i < BUFSIZE; i++) { + outlet_out[i] = float_to_q27(outf[i]); +}]]></code.krate> + </obj.normal> +</objdefs> diff --git a/faust2axo.py b/faust2axo.py new file mode 100755 index 0000000..2157221 --- /dev/null +++ b/faust2axo.py @@ -0,0 +1,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) diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..9796782 --- /dev/null +++ b/manifest.scm @@ -0,0 +1 @@ +(specifications->manifest (list "python" "python-black" "faust")) |