From 491ae9ba960e65325f3676bff6dec0c3bc74170b Mon Sep 17 00:00:00 2001 From: Ricardo Wurmus Date: Tue, 30 Apr 2024 06:40:17 +0200 Subject: Initial commit. --- README.org | 31 ++++++++++++ faust-ksoloti-object.c | 65 +++++++++++++++++++++++++ faust2axo.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ manifest.scm | 1 + 4 files changed, 225 insertions(+) create mode 100644 README.org create mode 100644 faust-ksoloti-object.c create mode 100755 faust2axo.py create mode 100644 manifest.scm 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 @@ + + + Moog VCF (moog_vcf_2b) + Ricardo Wurmus (Ksoloti), Julius O. Smith III (Faust) + STK-4.3 + + + math.h + axoloti.h + + + + <> + + + + + + + <> + + + 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 +//---------------------------------------------------------------------------- + +<> + +<> + +mydsp mdsp; +]]> + + + > + +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]); +}]]> + + 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"\n" + params += f"\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 away + # - throw everything after 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(""): + seen_start = True + if not seen_end and line.startswith(""): + seen_end = True + if seen_end: + outfile.write(line) + return + + if seen_start: + if "<>" in line: + outfile.write(line.replace("<>", inlets)) + continue + + if "<>" in line: + outfile.write(line.replace("<>", params)) + continue + + if "<>" in line: + outfile.write(line.replace("<>", 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")) -- cgit v1.2.3