Getting started with Faust for SuperCollider

hello all!
I recently started messing around with the amazing Faust and using it to create SuperCollider plugins. It’s a lot of fun and pretty easy to get started, so I gathered up some of my notes in a handy little quick start guide:

16 Likes

Well done Mads! thank you for sharing, great to see more action in Faust-SuperCollider. I can see that there is some updating in the faust2supercollider code what we can only be happy about.

Something that I have learned recently with Faust is the use of si.bus() to allow audio rate modulations, to illustrate the idea let’s look at this simple example:

import("stdfaust.lib");
decimalpart(x) = x-int(x);
phase(f) = f/ma.SR : (+ : decimalpart) ~_;
osc(f) = phase(f) * 2 * ma.PI : sin;
//out = osc(hslider("freq", 440, 0, 10000, 1):si.smoo); //becomes kr in supercollider
out = osc(si.bus(1) );
process = out <: _,_;

In the supercollider side let’s call Example1 the UGen compiled with faust2supercollider using the hslider version, so no suitable for audio rate modulations

x={arg freq=1, amp=0.5;Example1.ar(SinOsc.ar(freq,0,400,400)!2)*amp}.play
y={arg freq=1, amp=0.5;SinOsc.ar(SinOsc.ar(freq,0,400,400)!2)*amp}.play

now let’s call SimpleOsc the compiled UGen with the si.bus()version, which works as expected


x={arg freq=1, amp=0.5;SimpleOsc.ar(SinOsc.ar(freq,0,400,400))*amp}.play
y={arg freq=1, amp=0.5;SinOsc.ar(SinOsc.ar(freq,0,400,400)!2)*amp}.play

but now scalars have to be embedded into audio rate ugens for example like this

{SimpleOsc.ar(DC.ar(1200),0.1)}.play

volià

2 Likes

I don’t understand what exactly you are saying. Is this only if you use the hslider? I have had no issues with audio rate controls in my faust plugins. The only issue I have is that I can only use audio rate controls, but I put this inside the .sc file:

if(lim.rate != 'audio', {lim = K2A.ar(lim)});
if(mul.rate != 'audio', {mul = K2A.ar(mul)});
etc

I guess that does the same a DC.ar, but it can change.

Sam

1 Like

By the way I’ve somewhat related started working on more faust2supercollider ish tools using lua. There’s a faust2schelp tool here to create sc help files from .dsp files. It’s still work in progress but seems to do the job fairly well for the moment.

Hi Sam,
After many experiments I came to the conclusion that the Faust compiler will make a control rate input when using a GUI element such as a hslider, there is no real workaround (well other than running the control rate at the same rate as the audio in supercollider). So the code in Faust has to include an audio rate input such as si.bus
FM is a good example, compare these two sounds, thisone is done with an UGen compiled with the si.bus and modulated at the audio rate, you can hear the sidebands as expected thisone is compiled with the hslider in the Faust code and modulated with the same UGens the artifacts are obvious and the FM sidebands are not there.
This is the supercollider code:

    {SimpleOsc.ar(SinOsc.ar(1200).range(400,600),0.1)}.play;
    {Example1.ar(SinOsc.ar(1200).range(400,600),0.1)}.play

SimpleOsc.ar was compiled with si.bus
Example1.ar with hslider

Using K2A.ar does not solve the issue, neither adding si.smoo in Faust.

As a side note in Kronos you can define the rate of your inputs before exporting but the online-compiler seems to be off at the moment.

Do you see my point now?

I see your point. I always just take the gui elements out of the faust code when compiling for SC, which would also result in audio rate inputs (I would hope). I was concerned you were saying that what I thought were audio rate inputs were actually control rate.

Do you mind elaborating with what do you replace the GUI elements before compiling for supercollider? for example in a Faust code like this one:
osc(hslider("freq", 440, 0, 10000, 1):si.smoo);
Thanks

Just make freq an argument:

import("stdfaust.lib");

decimalpart(x) = x-int(x);
phase(f) = f/ma.SR : (+ : decimalpart) ~_;
osc(f) = phase(f) * 2 * ma.PI : sin;

process (freq) = osc(freq) <: _,_;

That should be an audio rate input in SC, once compiled, and you can use an audio rate signal like a SinOsc or whatever to modulate it.

Sam

2 Likes

Coming back to this.
@Sam_Pluta are you adding that code in the method checkInputs of the sc file? or somewhere in the init?

I am trying to add that functionality to my sc class but either the server fails or the K2Areturns a bad input

  checkInputs {

	if (rate == 'audio', {
      3.do({|i|
        if (inputs.at(i).rate != 'audio', {

             inputs.at(i) = K2A.ar(inputs.at(i))

          //^(" input at index " + i + "(" + inputs.at(i) +
           //") is not audio rate");

        });
      });
    });


    ^this.checkValidInputs
  }

Also is there a trick to change the name of the arguments? I am getting all compiled as in1, in2, in3 ...
Thank you
Alejandro

inputs.put(i, K2A.ar(inputs.at(i)))

Or

inputs[i] = K2A.ar(inputs.at(i))

Bracket syntax for array indexing is special syntax handled in the compiler:

  • Assignment array[i] = something translates to array.put(i, something).

  • But no-assignment array[i] translates to array.at[i].

But if you write array.at(i), then this is a normal, simple method call and it is always illegal to use assignment syntax with it: (correction) object.method(argument) = something is always a syntax error. (object.method = something compiles to object.method_(something) – I forgot that case.)

But you could use collect here as well, and avoid the confusion entirely.

hjh

Thank you James for the clarification.
I also figured out that for changing the default names of the arguments is enough to replace them in the arand kr methods. Maybe these two things (automatically remapping the non-audio inputs with K2A.ar and renaming the arguments) could be integrated into the faust2supercollider script, I am going to check with that team.

Yes! This would make things so much better. When I recompile my Faust code, I have to make sure not to overwrite my .sc file, because it will delete all my changes. I would love it if the faust2supercollider would name the arguments as they are in faust and auto add this ar stuff. This is what I always do:

MoogLadderB : UGen
{
  *ar { | in, normFreq, q |
		if(in.rate != 'audio', {in = K2A.ar(in)});
		if(normFreq.rate != 'audio', {normFreq = K2A.ar(normFreq)});
		if(q.rate != 'audio', {q = K2A.ar(q)});
      ^this.multiNew('audio', in, normFreq, q)
  }

  *kr { | in1, in2, in3 |
      ^this.multiNew('control', in1, in2, in3)
  }

  checkInputs {
    if (rate == 'audio', {
      3.do({|i|
        if (inputs.at(i).rate != 'audio', {
          ^(" input at index " + i + "(" + inputs.at(i) +
            ") is not audio rate");
        });
      });
    });
    ^this.checkValidInputs
  }

  name { ^"MoogLadderB" }


  info { ^"Generated with Faust" }
}

It is actually the next step for me to create a script to generate the .sc files as well here: GitHub - madskjeldgaard/faust2supercollider2

The original is written in ruby and I honestly can’t be bothered to learn Ruby.

Adding something like this would be fairly easy I think.

@madskjeldgaard Great initiative, but do I understand correctly that you are proposing to rewrite the faust2supercollider script? wouldn’t be better to just bring these ideas forward to the Faust team for example in a PR? Isn’t it a good moment to move the discussion to the Faust lists/channels?

You’re right Alejandro. Let’s discuss some of these ideas in an issue perhaps.

If you could drop a link here for those of us following I’d appreciate it!

I’ve opened up an issue here which contains some of the ideas discussed here. Please feel free to add to the list !

Thank you, Mads, but it looks like you forgot the link.

whoops, sorry, here it is: