How to make a synth using Cabbage, Pt. 1
What is this?
As I’ve been learning more about audio programming, one of the tools I’ve been experimenting with a lot is Cabbage. Created by Rory Walsh, Cabbage is a front-end for Csound, an audio programming language that’s been around for quite a while, and allows you to easily make VSTs that use Csound to do all of the audio processing. If you’ve ever wanted to make your own plugins, Cabbage feels like a good stepping stone to get used to audio programming concepts before making the jump to using C++ and JUCE. Cabbage also has a great community that is quick to answer questions, which is great when you're just starting out. Since Csound can be a bit tough to learn, I wanted to create a tutorial go over the basics of the language and how it integrates with Cabbage. Once you’ve finished, you’ll have made your own synthesizer with a GUI that you can use in your DAW!
Before getting started, make sure that you've downloaded the latest version of Csound, which you can find here.
Intro to Csound
All Csound files (using the extension .csd) have two parts: an orchestra and a score. The orchestra, the upper part of the file, is where we create any instruments and effects that we want to use. We create those instruments and effects by using opcodes, which we'll talk about more in-depth later. The score, the bottom part, is where we put more information about what what sounds the instruments should play back, and to write the notes that we want the instruments to play. The different parts of the .csd file are defined through the use of XML < > tags. When you create a new .csd, the default tags included are usually:
<CsoundSynthesizer> ; Tells Csound that this is the beginning of the .csd
<CsOptions> ; Not always used, but can put information here about what MIDI and audio outputs/inputs to use
</CsOptions> ; End of options section
<CsInstruments> ; Beginning of the section where we create our instruments and effects.
<CsScore> ; Beginning of the section where we write what notes we want to hear.
Opcodes and Variables
All instruments and effects in Csound are created by using opcodes. If you've ever used MaxMSP or PureData, you can think of opcodes like objects, just without the visual inputs and outputs. A better music analogy would be to think of opcodes like pieces of gear in a rack that you can patch in to each other. Every opcode has an output that you specify, and takes a set number of arguments (numbers or variables that affect different parameters of the opcode). Types of opcodes include oscillators, effects, random number generators - anything you would need to create or affect a signal. When using an opcode in Csound, the most basic syntax is:
aOutput opcodeName argument1, argument2, ...
Before we do anything else, let's talk about that aOutput and variables for a second. Variables in Csound work a lot differently than they do in most programming languages. There are three types of variables in Csound: a-rate, k-rate, and i-rate. The three types of variables don't hold different information; rather, they determine how frequently the variable updates and outputs new information. I-rate variables are calculated once when the program is started, and stay the same as long as the program runs. A-rate and k-rate variables update themselves while the program is running, with a-rate variables constantly updating, and k-rate variables updating roughly 1/10 as often as a-rate variables when using default settings. Why is this important? Mostly for reasons of efficiency. While this usually isn't a problem on most modern computers, efficiency is still something we want to strive for. A-rate variables update at the same rate as the sample rate that we choose, so all audio signals need to be sent through a-rate variables. K-rate variables are often used for control signals like LFOs, since we typically don't need as high of a fidelity to get the effect we're looking for. I-rate variables are often used for parameters for opcodes. It should also be noted that certain opcodes only take certain variable types as parameters.
Going back to aOutput, what this really means is that our opcode is outputting a signal to an a-rate variable called Output. You can think of the variable in this case as being a name for a patch cable that we can connect to other pieces of gear (othe opcodes). Generally speaking, all outputs should be a-rate variables.
So now, let's put all of this into context. Since we want to build a very basic synth that outputs a sine wave, we're going to need an opcode to generate that signal.
aout oscil p5, p4, 1