@dlphillips: I’m actually about half done with a source-to-source translator that reads the default Faust C++ output (including UI primitives) and generates a Rack plugin. Only a few substantive steps, and nothing complicated. Baking it into Faust itself would be very easy. I think that once V1 has stabilized it would definitely be worth approaching the Faust team, and I’d be happy to share my translator before that.
@Vortico: Cool! Got it. I’ll fill in the other examples when I get a little time.
The naming thing is interesting in Faust since we’re in high functional territory and since Faust is used to defining its own UIs and interactions with its environment. I’ll explain what I mean and then make a draft proposal for how to handle it.
I’ll try to get a demo of source translation up in a separate thread; when I do, I’ll link it here. It may help clarify the below.
Background: In the attenuator example above, I used formal function parameters to put names in for clarity, but the attenuator could just have well been written process = _ * (max(0, min(_, 5)) / 5);
(which would just grab the first two channels from the environment and match them against the two “wires”).
A typical Faust program, though, wouldn’t use the second full-speed channel as a parameter; instead, you would actually write process = _ * (max(0, min(hslider("gain", 0.5, 0, 1, .01), 5)) / 5);
which sets up a horizontal slider in the execution environment (Faust WASM, JUCE VST, a standalone Jack/Qt application, whatever). Predictably, the slider I defined is labeled “gain”, starts at 0.5, ranges from 0 to 1, and is quantized to steps of 0.01. (Note that despite the single-rate semantics of the language, Faust actually processes parameters at a lower sampling rate; when I convert Faust code to Rack, I strip this out and make everything audio-rate).
Obviously, a good Faust -> Rack module transpiler could create Rack parameters and widgets for these UI elements, handle scaling, etc. But I think you want fixed inputs and outputs in the prototyper (i.e. loading a new script couldn’t and shouldn’t change the panel).
Proposal: For inputs and parameters, here’s a simple way to do the 8/8/8 => 8 model (which would require existing Faust scripts to be adapted, but which would be straightforward for new prototyping):
Require prototype scripts to define a designated function as their entry point (say rack_process
). This function takes between 0 and 24 formal parameters, but their names are restricted to input[1..8] | param[1..8] | gate[1..8]
(using 1-indexing since we don’t have array access syntax in Faust and this isn’t aimed at programmers anyway). Sequence and composition are not specified (so rack_process(input8, gate5, input1)
is perfectly legal). Prototype scripts can also define other functions, but they cannot define a process
function, since that’s the main Faust entry point.
The prototype plugin then wraps its entry point in Faust (process = rack_process;
), compiles (LLVM via libfaust
); uses the names and order of the rack_process
parameters to establish data flow and standards conversion in C++ (== 10 ? 1 : 0
for gates, etc.), and then passes the function pointer (or however you want to do it) to Module.process()
.
There is a way of naming outputs but it’s clunky and limited, and not really needed. I would just let the first eight outputs consume the outputs of the block, and (probably) fail to compile if the prototype Faust code defined more than eight outputs.