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).

19 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 ?

1 Like

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

Hello, unearthing this thread as I have been trying to play with sc_faust and I do not have a GitHub account to post a report in the repository.

The examples in the schelp file trigger crashes and the failures point to places in the FAUST library.

I am running the 0.1.2 release version compiled on my machine and linked with libraries from FAUST 2.88, with SC 3.14.1 running on a Debian 13 machine. I rebuilt the extension both with the FAUST GitHub releases, a version compiled on my machine and the Debian packages, to no avail.

First issue:

Running FaustDef(\x, "process = 0.5;").send; results in the following stack trace:

#0  0x00007f04f1823721 in llvm_dsp_factory::getJSON[abi:cxx11]() ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#1  0x00007f04f1816348 in compileScript(World*, void*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#2  0x00007f04f1816776 in compileScriptFromString(World*, void*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#3  0x00007f0501ce6c60 in ?? () from /lib/libscsynth.so.1
#4  0x00007f0501cc72ea in ?? () from /lib/libscsynth.so.1
#5  0x00007f0501ae1224 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007f0501871b7b in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#7  0x00007f05018ef7f8 in ?? () from /lib/x86_64-linux-gnu/libc.so.6

While this crash occurs with FAUST 2.88, it doesn’t happen anymore when using a prior version of the FAUST libs (2.83.1).

Second issue:

Running the jp60 reverb code results in this error:

scsynth: /home/runner/work/faust/faust/compiler/interval/intervalPow.cpp:89: itv::interval itv::interval_algebra::fPow(const itv::interval&, const itv::interval&): Assertion `x.lo() > 0' failed. Server 'localhost' exited with exit code 0. server 'localhost' disconnected shared memory interface

and the follwing stacktrace:

#0  0x00007f3968ea095c in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f3968e4bcc2 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f3968e344ac in abort () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007f3968e34420 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x00007f39596e423d in itv::interval_algebra::fPow(itv::interval const&, itv::interval const&) const [clone .cold] ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#5  0x00007f395c7c1a47 in itv::interval_algebra::Pow(itv::interval const&, itv::interval const&) const ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#6  0x00007f395c823574 in non-virtual thunk to itv::AffineOps<SignalAlgebra<itv::AffItv> >::Pow(itv::AffItv const&, itv::AffItv const&) const ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#7  0x00007f395c82f6e0 in SignalDispatch<itv::AffItv>::xtendedOp(xtended*, std::vector<itv::AffItv, std::allocator<itv::AffItv> > const&) const ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#8  0x00007f395c82c4f2 in SignalDispatch<itv::AffItv>::combine(CTree*, std::vector<itv::AffItv, std::allocator<itv::AffItv> > const&, FixPointEvaluator<itv::Af
fItv>&) const () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#9  0x00007f395c82d077 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::alloca
tor<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#10 0x00007f395c82d3f8 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#11 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::alloca
tor<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#12 0x00007f395c82d3f8 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#13 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::alloca
tor<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#14 0x00007f395c82d3f8 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#15 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::alloca
tor<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#16 0x00007f395c82d3f8 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#17 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::allocator<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#18 0x00007f395c82d4c5 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#19 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::allocator<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#20 0x00007f395c82d4c5 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#21 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::allocator<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#22 0x00007f395c82d4c5 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
--Type <RET> for more, q to quit, c to continue without paging--
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#23 0x00007f395c82d272 in FixPointIterator<itv::AffItv>::memoized(std::unordered_map<CTree*, itv::AffItv, std::hash<CTree*>, std::equal_to<CTree*>, std::allocator<std::pair<CTree* const, itv::AffItv> > >&, CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#24 0x00007f395c82d4c5 in FixPointIterator<itv::AffItv>::eval(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#25 0x00007f395c82a7ed in FixPointIterator<itv::AffItv>::probeComponent(int) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#26 0x00007f395c81f11a in HorizonReader::at(CTree*) const () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#27 0x00007f395b52afa6 in TypeSolver::type(CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#28 0x00007f395b52a57e in typeAnnotation(CTree*, bool) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#29 0x00007f395b499e6d in simplifyToNormalFormAux(CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#30 0x00007f395b49b5a6 in simplifyToNormalForm(CTree*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#31 0x00007f395c792a7e in InstructionsCompiler::prepare(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#32 0x00007f395c7a0a51 in InstructionsCompiler::compileMultiSignal(CTree*) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#33 0x00007f395b367159 in generateCode(CTree*, int, int, bool) ()
   from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#34 0x00007f395b369927 in createFactoryAux1(void*) () from /home/jmbto/.local/share/SuperCollider/Extensions/native-linux/compiled/sc_faust/sc_faust.so
#35 0x00007f3968e9eb7b in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#36 0x00007f3968f1c7f8 in ?? () from /lib/x86_64-linux-gnu/libc.so.6

This occurs both with FAUST 2.83 and 2.88.

The brass example code triggers the same assertion.

A PR is already opened on FAUST’s repository side regarding the assertion, with a potential solution here.
However, I am not sure about how to integrate such a change in theexisting codebase.

Thats intereststing, thanks for reporting - and also thanks for giving a hint for a solution!

Does the problem also appear if you don’t compile it yourself but use the provided artifacts?

With the GitHub artifacts, first issue does not happen anymore (probably because it’s linked against FAUST 2.83) but second issue about the assertion still occurs.