Using Cabbage 3
Csound 7
Cabbage 3 is built on Csound 7, the latest major release of the Csound audio programming language. The improvements it brings make it a much stronger foundation for building audio plugins and instruments. This page highlights the Csound 7 features most relevant to Cabbage users.
Explicit Variable Types
Section titled “Explicit Variable Types”Csound 7 introduces explicit type declarations, moving away from the traditional requirement of prefixing variables with i, k, or a to define their rate. You can now use descriptive, custom names for your variables, provided you declare their type upon initialization:
count:i = 0freq:k = 440arr:k[] init 1, 2, 3, 4
print countFor Cabbage instrument authors, this makes code considerably easier to read and maintain, especially in larger instruments or when sharing code with other users.
User-Defined Structs
Section titled “User-Defined Structs”Csound 7 adds support for user-defined struct types, allowing related data to be grouped together in a meaningful way:
struct MyType val0:i, val1:i
instr 1 testVal:MyType init 8, 88 print(testVal.val0) print(testVal.val1)endinFor Cabbage users building complex instruments structs provide a clean way to organise state that previously required verbose naming conventions or unwieldy arrays. Most often the struct you will reach for is one of the two built-in trigger types described below.
Built-in Trigger Structs
Section titled “Built-in Trigger Structs”Many Cabbage get opcodes return a value plus a trigger that fires for one k-cycle whenever that value changes. Cabbage registers two struct types so both outputs can live in a single variable:
| Struct type | Members | Use with |
|---|---|---|
CabbageNumTrig |
val:k, trig:k |
numeric channels |
CabbageStrTrig |
val:S, trig:k |
string channels and string properties |
The following opcode forms accept them:
cabbageGetValue— both typescabbageGet—CabbageStrTrigcabbageJsonGet—CabbageStrTrigcabbageAraGetUpdateEvent—CabbageStrTrig
instr Effect ; two output variables ... kGain, kTrig cabbageGetValue "gain" ; ... or one struct variable gainRes:CabbageNumTrig cabbageGetValue "gain"
if gainRes.trig == 1 then printf "gain changed to %f\n", 1, gainRes.val endif
statusRes:CabbageStrTrig cabbageGet "status", "text" if statusRes.trig == 1 then printf "status is now %s\n", 1, statusRes.val endifendinTwo things to keep in mind:
- Never declare these types yourself. Cabbage registers
CabbageNumTrigandCabbageStrTrigbefore the orchestra compiles; a localstruct CabbageNumTrig ...line will collide with the built-in registration. - They require a Csound 7 build with struct opcode output support (Csound PR #3356). On older builds Cabbage detects the missing API, skips the struct overloads, and prints a notice to the console — the legacy two-output forms above keep working unchanged, so code can use either style safely.
Improved User-Defined Opcodes (UDOs) with Pass-by-Reference
Section titled “Improved User-Defined Opcodes (UDOs) with Pass-by-Reference”In Csound 7, UDOs support pass-by-reference for array and struct arguments. Previously, passing an array into a UDO made a copy of the entire array on every k-cycle, which was costly and made in-place processing awkward. To avail of this new pass-by-reference you need to use the updated UDO syntax, shown below:
opcode MyCustomOp(inval:i):(i) xout(inval+1)endopThis is especially useful in Cabbage instruments that use UDOs as reusable DSP modules (filters, envelope stages, effects chains). It reduces memory overhead, makes the intent of the code clearer, and enables patterns that simply weren’t practical before.
Oversampling
Section titled “Oversampling”Csound 7 provides built-in oversampling support, allowing sections of your signal chain to run at a higher sample rate than the host. This directly addresses two common problems in audio plugin development:
- Aliasing in non-linear processing — distortion, waveshaping, and saturation all generate harmonics that can fold back into the audible range at standard sample rates. Running the non-linear stage at 2× or 4× eliminates this.
- High-frequency accuracy in modulation and synthesis — oscillators and filters behave more accurately at higher internal rates, which is audible on material with significant high-frequency content.
The following User-Defined Opcode (UDO) performs 8x oversampling, significantly reducing the risk of aliasing caused by wavetable inaccuracies.
opcode oversampleOscil, a,kki amp:k, freq:k, table:i xin oversample(8, 4) sig:a = oscili(amp, freq, table) filteredSig:a = tone(sig, 22000) xout filteredSigendopNote that this UDO utilises traditional syntax. In this context, Csound employs a pass-by-copy mechanism; however, since we are only passing a few scalar arguments, the performance overhead is negligible and not a cause for concern.
For Cabbage users building guitar effects, synthesizers, or any instrument involving distortion or aggressive filtering, oversampling is a straightforward way to achieve more professional-sounding results without changing the host’s sample rate setting.
Parallel Opcode Execution
Section titled “Parallel Opcode Execution”Csound 7 introduces the ability to instantiate and run arrays of opcodes in parallel. This addresses a fundamental limitation in Csound: the inability to use standard loops for multi-signal processing.
The Problem: Statefulness
Section titled “The Problem: Statefulness”Most opcodes in Csound are stateful; they rely on internal memory (like filter coefficients or delay buffers) that must persist across k-cycles. If you try to “re-use” a single opcode inside a while loop to process an array of signals, the internal state is overwritten every iteration, resulting in noise or errors.
In Csound 6, the most effective, albeit a little inaccessible way of having a bank of opcodes was through a recursive UDO. In Csound 7, the new Opcode[] handles this by managing a dedicated internal state for every individual instance in the opcode array.
instr FilterBank ; 1. Create an array of frequencies for our filters freq:i[] fillarray p5*0.75, p5, p5*1.333, p5*1.666
; 2. The 'create' opcode constructs a bank of 'reson' filters. ; The number of filters is determined by the length of the frequency array. obj:Opcode[] create reson, lenarray(freq)
src:a rand linenr(p4, 0.1, 0.1, 0.01)
; 3. The 'run' opcode processes the entire filter bank. ; It takes the opcode array and an argument list matching the 'reson' signature. sig:a[] run obj, src, freq, freq/p6, 2
out sumarray(sig) / lenarray(sig)endinBy replacing complex recursive logic with a single Opcode[] declaration and the run command, Csound 7 provides a much more accessible path for Cabbage developers to build high-density synthesis engines and parallel processing chains.
These features collectively make Csound 7 a significantly more capable host environment for Cabbage than Csound 6 was. The language changes in particular — explicit types, structs, and improved UDOs — reward writing clean, well-structured instrument code, and that structure maps well onto the way Cabbage manages UI state and plugin parameters.