Cabbage Logo
Back to Cabbage Site

Using Faust programs in Csound/Cabbage

Hi,

One of my current projects involves modeling a linear circuit, and thanks to Faust’s WDF filter it can be done easily in Faust. There is a way to use Faust code in Csound thanks to the faust plugin.

Before going further I have two questions:

  • Are they ways to use the UI parameters (knobs, sliders…) in the Faust code?
    Based on my understanding of the code below, the faust code would need to be re-compiled everytime we’d like to pass a new parameter. And I’m not really sure the syntax allow us to add a variable in the faust code. https://csound.com/docs/manual/faustaudio.html
  • Is it realistic to ship the Faust plugin (opcodes) with my VST? There are no compiled binaries so I would need to do it myself for each architecture. I think it is as straightforward as installing Csound but maybe I’m missing something…

If it’s something that wouldn’t be performant and too hard to implement I’ll skip this project, but I wanted to check if someone tried it first and if it was potentially feasible.

Thanks!

Julien

I don’t think you can update parameters in realtime but I’m awaiting confirmation from the author. You can output your faust code to c++. In which case you could build your own opcode, or I can look into doing it for you if you like. I think that would be the simplest, and then I could embed that opcode into your pro build so you don’t need to worry about including dlls and what not.

You can control parameters using faustctl. And you can also output Csound opcode code from Faust directly. I think that might be the best approach.

Thanks Rory!

I’ll take a look at faustctl, I totally missed it while doing my research. And I’ll see if I can output a Csound opcode, it can definitely be a great way to implement it!

I’ll keep you updated. FYI I’m trying to implement features of the Pultec EQP-1A passive EQ and doing it in Csound only looks very hard, which is why I’m trying to use Faust.

I’ll post the code here once I manage to make it work.

Julien

1 Like

Hi @rorywalsh,

Thanks for your help, here is a guide and a working example:

This is how you can use the “osc.dsp” example from Faust in Cabbage. The controls can be done with Cabbage, and control the freq and gain parameters in Faust.

1/ Go here: https://faustide.grame.fr/

2/ Export your Faust code as an opcode:

3/ Import the opcode in Cabbage and use it

<Cabbage>
form caption("Faust Opcode") size(400, 300), guiMode("queue") pluginId("def1") colour(0,0,0)
rslider bounds(30, 98, 75, 72), channel("opcodegain"), range(-96, 0, 0, 1, 0.01), text("OpcodeGain")
rslider  bounds(32, 16, 70, 70), channel("opcodefreq"), text("OpcodeFreq"), range(20, 20000, 1000, 0.333)
rslider bounds(266, 178, 100, 100), channel("gain"), range(0, 1, 0, 1, 0.01), text("Gain")

</Cabbage>
<CsoundSynthesizer>

<CsOptions>
-n -d --opcode-lib=full_path_to_opcode/osc.dylib
</CsOptions>

<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1
kGain cabbageGetValue "gain"
kOpcodeGain cabbageGetValue "opcodegain"
kOpcodeFreq cabbageGetValue "opcodefreq"

; Generate osc with custom opcode 
asig   osc  kOpcodeFreq, kOpcodeGain
outs asig*kGain, asig*kGain
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

And that’s it!

I still need to figure out a few things:

  • Loading the .dylib file wasn’t working because of security issues. I codesigned the file but the error was the same. I had to manually open the dylib to be able to load the file in Cabbage
  • You need one export per platform (osx, windows…). I’m not sure this is compatible with Intel and ARMS macs for example. I need to test it.

But is is encouraging! Next step for me: convert and use the Pultec .dsp code.

1 Like

Wow, I didn’t think it would compile it for you too. That’s neat. The simplest way around the signing issues is for me to build the opcode into your pro build of Cabbage. Then you don’t have to worry about compiling for different platforms. At least that’s how I would approach it.

I agree, this is impressive!

I’m worried about the maintenance cost/time it would imply. It means that everytime the opcode needs to be updated, I would need to send it to you and it would be time consuming for you (even though I imagine these opcodes would be rarely updated…)

I will first test the Pultec code and see if I want to release it as a VST and get back to you if needed! Maybe I could also try to build it in my Azure Pipeline so I can do it on my own. I never did that so I don’t know how hard it is though.

I would just need to understand how to use faust2csound in my pipeline: https://faustdoc.grame.fr/manual/tools/

Thank you very much for your help!

I wouldn’t imagine that I would not have to build them in until you are almost ready to release your plugin and by that stage the opcode will be pretty much finished? Anyway, keep me posted on your progress, it’s definitely a cool way of harnessing the power of FAUST in a plugin.

I’m working through Lazarini’s 2017 book, which incorporates Faust, Csound, and Python. Faust is new to me and looks interesting.

That said, I’d like to better understand the pros/cons of incorporating Faust into a project.

Programmer’s time - probably depends on the user. The Faust syntax looks pretty wild at first glance. But it looks particularly tailored to some tasks, making it efficient.

Performance - I have no idea. I’d love to understand how to benchmark performance for the same task being handled by Faust vs a UDO in Csound vs “normal” Csound.

Usability/distribution - It sounds like Faust adds a layer to this. It might not be a big deal. But it is another thing to maintain.

I’m really just dipping my toes into this stuff, so I’d love to hear opinions.

I’ve never used Faust, but it’s well respected in the audio developer community. Using the faust opcodes does come with some overhead, I’m not sure about performance, but the opcodes themselves are quite large, and distributing them could be troublesome. As @nymano has shown, it’s quite simple to create a Csound opcodes from your faust code, and then include that. For me this is a better option, as you end up with a smaller opcode binary. Distributing this will not be without its challenges either, but should be possible. As for performance, I’d expect that a compiled plugin opcode will always out perform a UDO, or a Csound instrument. Csound code generally runs about a 3rd of the speed of natively compiled code, but that’s quite a generalisation.

p.s. I have no hard evidence of this, it’s just something I’ve heard anecdotally from Csound devs over the years :slight_smile:

1 Like

Thanks for the insights.

What I’m going back and forth on is how to handle the dry signal in my looper plug-in. If I open that audio up, then I have to deal with the latency issues that arise. A justification for opening it up would be to do things like filter it, which then leads me down the path of deciding what the most efficient way to filter would be (csound opcode, my own csound code, my own UDO, Faust DSP, something else I don’t know about, …).

For filters on the loops the performance isn’t a big deal. But I’m trying to minimize latency on the (mostly) dry audio.

Cabbage plugins use PDC ( plugin delay compensation ) that will match the ksmps, so latency shouldn’t be that huge a deal. If you really want 0 latency, use latency(-1) in your form declaration. It will cause Cabbage to do processing in place, but it may cause your plugin to perform poorly in terms of CPU cycles. It’s a constant battle :rofl:

I made a plugin with a lot of latency, and as Rory said with PDC it shouldn’t be a problem for most users, unless the latency is too high for live performance.

Based on a soundonsound article I read recently, Soothe 2 has a ~45ms latency, if it can help you feel better about that…

It is the live use that will make/break it for me. Right now, I’m bussing the audio to the plug-in and keeping it 100% wet. Things that make it feel less like a physical instrument (audio latency, control latency) are genuine concerns for me.

I’m trying to get a Faust-based opcode to work. I feel like I’m very close, but I’m missing one piece of the puzzle.

I’m trying to use this in Csound: https://faustdoc.grame.fr/examples/pitchShifting/#pitchshifter . I think I’ve done all of the pieces correctly (exporting for use in Csound, including the .dylib in my code, and setting up the sliders in Cabbage).

So now it is time to call the opcode. Two big questions:

  1. What is the right name for the function? I see pitchshifter and pitchShifter in the Faust code, plus the dylib is called pitch_shift.dylib. Confusing!
  2. How do I know what parameters to pass to the opcode? Clearly, it needs window, xfade, and shift … but I also want to specify the audio input. Does that go first? Last? Is it implicit in some way?

Thank you!

Here’s my code:

<Cabbage>
form caption("Faust Opcode") size(400, 300), guiMode("queue") pluginId("def1") colour(0,0,0)
rslider bounds(10,50,50,50), channel("window"), range(50, 10000, 1000,1, 1), text("window")
rslider bounds(70, 50, 50, 50), channel("xfade"), range(1, 10000, 10,1, 1), text("xfade")
rslider bounds(130, 50, 50, 50), channel("shift"), range(-12, 12, 0,1, 0.1), text("shift")

rslider bounds(250, 50, 50, 50), channel("gain"), range(0, 1, 0 ,1, 0.01), text("gain")


</Cabbage>

<CsoundSynthesizer>
<CsOptions>
-n -d --opcode-lib=/Users/bryantysinger/Documents/svn_checkouts/loobt_v1/Prototypes/Faust/pitch_shift.dylib
</CsOptions>

<CsInstruments>
; Initialize the global variables. 
ksmps = 32
nchnls = 2
0dbfs = 1

instr 1

a1_in inch 1

kGain cabbageGetValue "gain"
kWindow cabbageGetValue "window"
kXfade cabbageGetValue "xfade"
kShift cabbageGetValue "shift"

; Generate osc with custom opcode 
a_process pitchShifter, a1_in, kWindow, kXfade, kShift
outs a1_in*kGain, a_process*kGain
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
;starts instrument 1 and runs it for a week
i1 0 [60*60*24*7] 
</CsScore>
</CsoundSynthesizer>

And here is the Faust code, if that helps:

declare filename "pitch_shift.dsp";
declare name "pitch_shift";
declare name        "pitchShifter";
declare version     "1.0";
declare author      "Grame";
declare license     "BSD";
declare copyright   "(c)GRAME 2006";

 //--------------------------------------
 // Very simple real time pitch shifter
 //--------------------------------------

import("stdfaust.lib");

pitchshifter = vgroup("Pitch Shifter", ef.transpose(
                                    hslider("window (samples)", 1000, 50, 10000, 1),
                                    hslider("xfade (samples)", 10, 1, 10000, 1),
                                    hslider("shift (semitones) ", 0, -12, +12, 0.1)
                                  )
                );

process = pitchshifter;

I’m going to ping @nymano, as he has recently done something similar with Faust. I’m afraid I’ve never used it :man_facepalming:

I think the issue is that I’ve specified what the function does, but not the input and output.

I think the Faust function needs to be something like

_ : pitch_shift(a,b,c) : _

Maybe …

(EDIT) - nope, that wasn’t the issue.

When I call the opcode in the most likely way I do get a Cabbage error that I don’t have a Cabbage section. I do have a cabbage section, but that makes me think something is happening (and going wrong!) when I run it. Weird.

I think I’m getting somewhere, but I’m not entirely sure how to make sense of what I’m seeing.

nm -gU pitch_shift.dylib

yields this:

00000000000021f0 D __ZTI2UI
0000000000002118 D __ZTI3dsp
0000000000002230 D __ZTI4CSUI
0000000000002130 D __ZTI5mydsp
00000000000021d8 D __ZTI6UIRealIdE
0000000000002210 D __ZTI9GenericUI
0000000000001cfd S __ZTS2UI
0000000000001ce7 S __ZTS3dsp
0000000000001cec S __ZTS4CSUI
0000000000001ce0 S __ZTS5mydsp
0000000000001d01 S __ZTS6UIRealIdE
0000000000001cf2 S __ZTS9GenericUI
00000000000013c0 T _csoundModuleInfo
00000000000013a0 T _csound_opcode_init

Notably absent is anything that looks like “pitch_shift” or “pitchshifter.” So, maybe the export from the Faust website didn’t work correctly?

Hi @Bryan_T

I managed to use a csound opcode exported from Faust, I’ll have a look and see if I can see why your code is not working

Can you tell me which OS you are using?
FYI If you are on MacOS with an M chip the exported opcode from the online IDE won’t work because afaik the runners were for Intel Macs

I’ll try to have a look this week end!