Cabbage Logo
Back to Cabbage Site

Fout opcode suppored for Cabbage

Hi!

I’m currently working with Dr. Richard Boulanger on a research project called CStore, which import AI into the Csound.

We’ve run into an issue when trying to export a short preview “one-shot” of a Cabbage-designed instrument (just a few seconds of audio) using the fout opcode.

The problem:

Many plugin-designed .csd files use performance loops or k-rate logic that essentially run indefinitely. To generate one-shot preview audio, I wrote a Python tool that:

  1. parses the .csd
  2. finds the instrument’s score events
  3. rewrites duration values
  4. inserts a fout opcode to export audio (AIFF format)

But in practice:

  • sometimes Csound creates a massively long file (for example, larger than 1 TB)
  • other times it produces an empty file
  • or the rendering fails completely

It seems that the issue might be related to how Cabbage handles performance loops and internal scheduling, but I’m not entirely sure.

My question:

Does Cabbage 2 officially support the fout opcode?
If not, will Cabbage 3 include support for reliable offline exporting or a safe way to render a bounded-duration audio file from a plugin-style .csd?

Our goal is simple:
Inside a “designed-for-plugin” Cabbage .csd, we want to render a few seconds of audio for machine-learning dataset generation, without depending on real-time output or infinite loops.

Any guidance on:

  • correct usage of fout inside Cabbage
  • whether offline export is expected to work
  • or future plans in Cabbage 3 related to this
  • or any other way I can do that function!
    would be incredibly helpful.

Thanks so much!

— Charlie

Hi @CharlieSL1, always nice to hear from Richard’s students. There are no limitations to using fout in Cabbage. It should work just like it doesn’t in plain old Csound. Do you have a simple example of one of these modified csd files? The key to the issue can probably be found there :thinking:

Hi Rory,

Thanks a lot for getting back to me — and for confirming that fout should work the same way in Cabbage as in plain Csound.

Today Dr. B and I spent about 30 minutes debugging, and we got our test file working as a .csd in Cabbage, so fout itself is definitely functional. However, we’re still seeing a few strange behaviours and I’d love your input.

Using the attached/embedded Trapped09 example (adapted from Dr. B’s Trapped collection), we noticed:

  1. If we place fout directly on the local amix signal inside instr Trapped09 , the rendered file can sound noisy / trashy compared to what we hear from the Monitor output.
  2. If instead we record from a Monitor instrument that uses monitorfout on allL, allR , the bounced allMix file sounds correct.
  3. The duration of the file rendered from Trapped09 (local amix ) does not always match the duration of the file rendered from the full mix, even though we’re using the same ip3 / dur values.
  4. For our use case, we’d ideally like a clean way to:
  • trigger the sound once,
  • write it to disk with fout , and then
  • have Cabbage/Csound stop automatically after the render (so we don’t end up with very long or looping renders).

Here is the test .csd we’ve been working with (the problematic fout line is currently commented in instr Trapped09 , and the working one is in instr Monitor ):

<Cabbage> bounds(0, 0, 0, 0) 
form caption("Trapped09") size(650, 250), guiMode("queue") pluginId("def1")

button  bounds(34, 34, 101, 44) channel("trigger") text("Trigger") textColour("white")

hslider bounds(322, 10, 150, 50) channel("dur") range(2, 9, 4, 1, 0.001) text("Dur") textColour("white")
hslider bounds(250, 68, 150, 50) channel("note") range(20, 80, 50, 1, 0.001) text("MIDI NN") textColour("white")
hslider bounds(408, 68, 150, 50) channel("rndNote") range(0, 8, 3, 1, 0.001) text("Rnd NN") textColour("white")

hslider bounds(484, 10, 150, 50) channel("amp") range(0, 1, .6, 1, 0.001) text("Synth Lvl") textColour("white")

hslider bounds(286, 122, 162, 50) channel("rndRate") range(100, 400, 185, 1, 0.001) text("RndRate") textColour("white")
hslider bounds(452, 122, 150, 50) channel("rndAmp") range(0, 10, 3.3, 1, 0.001) text("RndAmp") textColour("white")

hslider bounds(338, 182, 150, 50) channel("delaySend") range(0, 1, .76, 1, 0.001) text("Delay Send") textColour("white")
hslider bounds(490, 182, 150, 50) channel("delayTime") range(.001, 5, .08, 1, 0.001) text("Delay Time") textColour("white")

hslider bounds(22, 184, 150, 50) channel("rvbSend") range(0, 1, .45, 1, 0.001) text("Rvb Send") textColour("white")
hslider bounds(176, 184, 150, 50) channel("rvbPan") range(0, 6, 4, 1, 0.001) text("Rvb Pan") textColour("white")

hslider bounds(160, 10, 150, 50) channel("masterLvl") range(0, 1, 0.9, 1, 0.001) text("Master Lvl") textColour("white")

combobox bounds(120, 104, 100, 25), populate("*.snaps"), channelType("string") automatable(0) channel("combo31") text("Starting-4note", "More Notes Less Vcomb", "More noise & open filter -pan LFO faster", "faster, less notes, more delay", "Slower, More resonance, and RingMod", "Complex PolyRhythms", "Alll Notes, Spinning, No Delay, All FreeVerb") value("1")
filebutton bounds(58, 104, 60, 25), text("Save", "Save"), populate("*.snaps", "test"), mode("named preset") channel("filebutton32")
filebutton bounds(58, 134, 60, 25), text("Remove", "Remove"), populate("*.snaps", "test"), mode("remove preset") channel("filebutton33")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-odac 
</CsOptions>
<CsInstruments>
ksmps = 32
nchnls = 2
0dbfs = 1

garvb          init      0
gadel          init      0

giSine ftgen 1, 0, 8192, 10, 1
giWav2 ftgen 2, 0, 8192, 10, 10, 8, 0, 6, 0, 4, 0, 1
giWav3 ftgen 3, 0, 8192, 10, 10, 0, 5, 5, 0, 4, 3, 0, 1
giWav4 ftgen 4, 0, 8192, 10, 10, 0, 9, 0, 0, 8, 0, 7, 0, 4, 0, 2, 0, 1

gkRevPan init 4

instr 1
    iDur = chnget:i("dur")
    iNote = chnget:i("note") + rnd(chnget:i("rndNote"))
    iFrq = cpsmidinn(iNote)
    iAmp = chnget:i("amp")
    kTrig chnget "trigger"
    if changed(kTrig) == 1 then
        event "i", "Trapped09", 0, iDur, iFrq, iAmp
    endif
endin

instr Trapped09
ip3    = chnget:i("dur")
iamp   = chnget:i("amp")*.2
kNote  = chnget:k("note")+rnd(chnget:i("rndNote"))
kFrq   = cpsmidinn(kNote)*.25

kRndAmp  = chnget:k("rndAmp")
kRndFrq  = chnget:k("rndRate")

k2             randh     kRndAmp, kRndFrq, .1                     
k3             randh     kRndAmp * .98, kRndFrq * .91, .2             
k4             randh     kRndAmp * 1.2, kRndFrq * .96, .3            
k5             randh     kRndAmp * .9, kRndFrq * 1.3     

kenv           linen    iamp, ip3 *.2, ip3, ip3 * .8  

a1             oscil     kenv, kFrq + k2, giSine, .2             
a2             oscil     kenv * .91, (kFrq + .004) + k3, giWav2, .3
a3             oscil     kenv * .85, (kFrq + .006) + k4, giWav3, .5
a4             oscil     kenv * .95, (kFrq + .009) + k5, giWav4, .8

kgate         transeg   1, ip3, 0, 0 
amix           =        (a1 + a2 + a3 + a4) * kgate
aL             =        a1 + a3
aR             =        a2 + a4

              outs      aL * chnget:k("masterLvl"), aR * chnget:k("masterLvl")
; Problem area when enabled:
;              fout "/Applications/CsoundTest/fout_aif.aiff", 14, amix

garvb          =         garvb + (amix * chnget:k("rvbSend")) 
gadel          =         gadel + (amix * chnget:k("delaySend")) 
endin

instr Delay
denorm    gadel
ip3    = chnget:i("dur")
kgate          expseg    1, ip3*.7, 1, ip3*.3, .0001
asig           delay     gadel, chnget:i("delayTime")
asig = asig*kgate
               outs      asig*chnget:k("masterLvl"), asig*chnget:k("masterLvl")
gadel          =         0
endin

instr Reverb 
denorm    garvb                   
k1             oscil     .5, chnget:k("rvbPan"), 1
k2             =         .5 + k1
k3             =         1 - k2
asig           reverb    garvb, 3.1
               outs      (asig * k2) * chnget:k("masterLvl"), ((asig * k3) * (-1)) * chnget:k("masterLvl")
garvb          =         0
endin
             
instr Monitor
allL, allR monitor
fout "/Applications/CsoundTest/fout_allMix.wav", 14, allL, allR
endin

</CsInstruments>
<CsScore>
i 1 0 15
i "Delay" 0 15
i "Reverb" 0 15
i "Monitor" 0 15
i "Trapped09" 0 3
i "Trapped09" 4 3
i "Trapped09" 8 3
</CsScore>
</CsoundSynthesizer>

If you have any suggestions about:

  • best practice for using fout from within a Cabbage plugin (especially inside a single instrument vs from monitor ), and
  • a clean pattern for “render once then stop” in a plugin context,

that would be incredibly helpful for us and for the larger project we’re working on.

Thanks again for your time and for all your work on Cabbage — it’s been central to this project with Dr. B.

Using fout within an instrument is not a problem, so long as theinstrument isn’t triggered multiple times. This is usually why problems arise with this approach. Also, I see that you have 3 instances of Trapped09. This might also cause some issues as the sound file might not be closed. You should probably call ficlose during the release phase of the instrument to ensure it’s properly closed before trying to write again. Closing the sound file after writing should help in the context of rendering to disk too. You can also render faster than realtime in a k-rate loop if I’m not mistaken. During each k-cycle will process ksmps samples, so if you run things in a k-rate loop you should be able to do a kind of offline bounce, but I’ve not tried it.