Sc_faust - Faust compiler for SuperCollider

Download it via Releases · capital-G/sc_faust · GitHub

sc_faust is a “meta UGen” which brings the Faust compiler to the SuperCollider server. This allows to write Faust code within sclang, send it to the server for compilation and use it immediately within a Synth. Rinse and repeat.

I don’t think I need to tell you how awesome and vast Faust is - if you haven’t used it before, there is a great tutorial on their website: Quick Start - Faust Documentation

I have tested it with macOS and Linux (Fedora 42), but haven’t tested if it works with Windows.
Currently there is no Supernova support and also no “select-audio-buffer” support via Faust.

Quickstart

In order to setup the Faust compiler, we need to tell it where the provided faust .lib files are - this is done via the init method

// start the server
s.boot;

// send a message to the server to tell the server the path of our faustlibs
// this "inits" the compiler
FaustDef.init;

Before a Faust UGen can be used, it needs to be registered and compiled on the server.
Here is an example in which the input will be fed into a JPVerb and it’s t60 parameter will be exposed and controllable

// define a faust snippet 
(
FaustDef(\jp, "
import(\"stdfaust.lib\");
re = library(\"reverbs.lib\");

t60 = hslider(\"t60\", 3.0, 0, 10.0, 0.01);

process = re.jpverb(t60, 0.2, 1.0, 0.8, 0.3, 0.4, 0.9, 0.8, 0.7, 500, 10000);
").send;
)

Once the faust code has been compiled, it can be used via the Faust UGen.

(
Ndef(\jp, {
    var sig = SinOscFB.ar(SinOscFB.kr(0.03*[1.0, 1.01], 2.3).exprange(100, 10000), [1.3, 1.4]) * SinOscFB.kr([1.4, 1.3], 1.3);
    sig = sig * Env.perc(releaseTime: LFDNoise3.kr(0.5).exprange(0.01, 0.5)).kr(gate: Impulse.kr(2.0, [0, 1/2]));
    Faust.ar(
        numOutputs: 2,
        script: \jp,
        inputs: sig,
        params: [
            t60: \t60.kr(2.0, spec: [0.01, 10.0]
        ),
    ]) * \amp.kr(0.2);
}).play.gui;
)

Acknowledgement

Big thanks to GRAME, especially to @Stephane_Letz, for sponsoring the development of this extension! <3

Much thanks also to @madskjeldgaard who provided the idea and a good starting point with GitHub - madskjeldgaard/faustgen-supercollider: Livecode Faust in SuperCollider using an embedded Faust compiler. - yet, in fact, sc_faust does not share any source code with this implementation and was coded from scratch. Instead of using the VSTplugin based approach, it follows the DynGen approach, which is IMO more suitable for passing parameters to short running synths (within e.g. patterns).

16 Likes

Thanks @dscheiba for this first release. The Release v0.1.1 · capital-G/sc_faust · GitHub still have files with 0.1.0 version number ?

Ah, thats a typo - thanks for pointing this out, it is fixed now.

1 Like

Great work on this. Very cool!

Sam

1 Like

New bugfix release: Release v0.1.2 · capital-G/sc_faust · GitHub

Also added spec extraction of parameters via paramMap:

(
~brass = FaustDef(\brass, "
import(\"stdfaust.lib\");

process = pm.brass_ui <: _,_;
").send;
)

(
Ndef(\brass, {
    Faust.ar(2, ~brass, params: ~brass.paramMap) * 0.2;
}).play.gui;
)
2 Likes

Thanks @dscheiba for this implementation ! Nice work !!

I have a small question regarding dynamic behavior in sc_faust:
Why is it not possible to change the Faust script dynamically while the synth is running, as is possible for example in DynGen?
If I remember well, in FaustGen for Max this is possible, which would suggest that, at least theoretically, it should also be feasible in sc_faust ? :slight_smile:

Many thanks!

José

The problem are the parameters of a Faust script in that case - some background from the engine room of DynGen/sc_faust:

DynGen can extract the existing parameters of a script using some regex and “cache” them within the language - this makes the language responsible of which parameter name corresponds to which parameter number (or offset) since we don’t want to (or can’t) transfer strings when creating a Unit, but instead want to pass something like “parameter 5 has this signal”.
This is important since when we add or remove parameters in a DynGen script, the e.g. “amp” parameter will always use the same offset, such that e.g. “amp” will always hold the same offset and no other parameter can claim this offset anymore. So when we update a running DynGen script, the parameter-to-offset mapping is persistent!

With faust, we have to have to rely on the server to extract the parameters of a script and use this as an offset since we don’t have access to the faust compiler on a language level.
So in case we change parameters within a Faust script, this could result in a wrong wiring of parameters which we definitely want to avoid.
It would be possible to solve this by passing the already existing parameter order we have in the language to the server when compiling the faust script, so the server can take the existing order into account when calculating the offsets.
Since this is a bit more involved (and faust scripts take a bit longer to compile than DynGen scripts) I did not include this in a v0.1.0.
I currently focus more on fine-tuning DynGen and work on a SC package manager, but if there is enough demand for this “live drop-in-replacement of faust code” I’ll look into it, but most likely not before april. If someone wants to take a look into it, I am happy to review the PR!

BTW: Since max/msp can pass parameters to units using their names, they have it a bit easier :wink:

2 Likes