All About Digital Synthesis
On this site, I aim to provide you with the fundamentals of Csound. If you are new to Csound and have never programmed with sound in mind, this is the right place to start. For additional information, you can consult the Floss Manual, or feel free to contact me—I also offer private lessons in sound synthesis.
What is Csound?
Csound is a powerful and flexible programming language specifically designed for sound synthesis and signal processing. Originally developed at MIT by Barry Vercoe in the 1980s, Csound has evolved into one of the most widely used tools in computer music. It allows users to generate a vast range of sounds, from simple tones to complex textures, through a modular system of opcodes—predefined functions that perform various synthesis and processing tasks. With Csound, you can create music, experiment with sound design, and explore advanced audio concepts.
Where do i get Csound?
A good way to start with Csound is in using CsoundQT. CsoundQT is a front end for Csound that includes a text editor designed for writing Csound code, quick access to the manual, and options for creating a graphical user interface.
"Hello World" - Csound Style
Opcodes
Opcodes are the building blocks of Csound. An opcode is a small operator that performs a predefined task. In Max or Pure Data, they are called "objects," while in SuperCollider, they’re referred to as "UGens." For example, there are opcodes that handle the task of an sine oscillator. ‘poscil’ is one such opcode in Csound. The name stands for precise oscillator. And if there is a very precise oscillator, there are apparently different levels of quality among various opcodes. This variation is due to Csound’s long history — Csound is quite old, with development evolving from then to now — and to considerations of CPU load. When highly precise signals aren’t necessary, one can use less precise and therefore less CPU-intensive opcodes.
An opcode generally needs information to know how to perform its task. The opcode expects so-called arguments, which provide the information or input it needs. The input to an opcode is almost always given on the right side of the opcode and follows a fixed order established during the opcode's development. If multiple arguments are expected, they are separated by commas. The result of the opcode, or the output, is then placed on the left side of the opcode. If you’re unsure which arguments an opcode requires or in what order they should be entered, consult the manual. The manual is your best friend, and it’s a good habit to check it for any missing information. No Csound user can remember everything by heart.
Here's an example with 'poscil', which requires an amplitude value as the first argument—simply put, the volume at which the oscillator should operate. The second argument is a frequency value, which defines the speed at which the oscillator should oscillate.
aResult poscil 0.8, 440
The opcode 'poscil' is given an amplitude argument of '0.8' and a frequency argument of '440'. This means it should generate a waveform with a maximum amplitude of 0.8 and oscillate at 440 Hz. The result is stored in the variable 'aResult'.
Variables and Audio Signals
In Csound, a variable consists of two elements: signifier and name.
The signifier indicates the rate at which information is written into the variable. It appears at the beginning of the variable, and is written in lowercase. Here, it's an 'a' for the audio rate, which corresponds to the sampling rate currently set in Csound. If SR (sampling rate) = 44100, then 44100 new values per second are written into our 'aResult' variable from 'poscil'.
The name of the variable makes it specific and unique. In our example, the variable is called 'aResult', which isn't very descriptive. A better name might be 'aSine', as 'poscil' generates a sine wave. The variable name should describe what is stored in it. This is actually one of Csound's strengths compared to PD or Max. With well-chosen variable names, even very complex Csound code remains readable. Once a variable name is chosen, it’s important to remember that this name is now reserved. This name should not be used again for a declaration within the same instrument; otherwise, the previous variable will be overwritten. So, if you have two 'poscil' instances in an instrument, choose distinct variable names for the results:
aSine1 poscil 0.7, 440 aSine2 poscil 0.3, 80
If I were to do this:
aSine poscil 0.7, 440 aSine poscil 0.3, 80
only the sine wave with an amplitude of 0.3 and a frequency of 80 Hz would remain. Csound reads its instructions from top to bottom. So, the variable 'aSine' is first defined and filled with data from 'poscil' with an amplitude of 0.7 and a frequency of 440. In the next line, however, the variable is redefined and filled with different values from another poscil opcode. Also note that Csound is case-sensitive. Csound distinguishes between 'asine' and 'aSine'.
Another role of variables is to transport data. Variables are like containers that hold information and can be passed along. If we fill the variable 'aSine' with data from the 'poscil' opcode, we can pass this data on. Maybe we want to hear our nice sine wave too? For this, we need another opcode to establish a connection to our sound card. We can use, for example, 'out' for this purpose. This opcode expects only one input as an argument and provides no output.
aSine poscil 0.7, 440 out aSine
The output of 'poscil' is written into the variable 'aSine'. The opcode 'out' receives 'aSine' as an input argument. Simply mentioning the variable allows the output from 'poscil' to be passed along.
Csound Template, Orchestra, Score, and Instrument Definition
We don’t need more opcodes to produce sound in Csound. However, we do need to place these two lines appropriately within a Csound file.
A Csound file can generally be divided into three sections:
- the Options section
- the Orchestra
- the Score
A Csound file is enclosed within <CsoundSynthesizer> tags:
<CsoundSynthesizer> </CsoundSynthesizer>
Everything in between is recognized as Csound code. This is where the three sections mentioned earlier are located.
First comes the Options section, which is enclosed within <CsOptions> tags:
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> </CsoundSynthesizer>
In the Options section, settings for Csound are made. For example, the input and output sound cards can be specified there. It can also be set in which format a sound file is rendered from Csound or how many and which messages should be printed in the console. Here, I have entered the flag '-odac'. This flag indicates that we have an *o*utput that should go to the '*d*igital *a*nalog *c*onverter'. Flags always start with a hyphen followed by additional options.
The next section is the Orchestra. Here, all instruments are defined, and further settings for Csound are made. Confusingly, the Orchestra is enclosed within a <CsInstruments> tag. But since we need instruments in an orchestra, we might just live with that:
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1.0 </CsInstruments> </CsoundSynthesizer>
By default, we need the following entries in the orchestra:
sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1.0
Here, the sampling rate (sr) or audio rate is defined. The ksmps (vector size, for experienced DSP wizards) determines the control rate. More on that later. The nchnls defines the number of audio outputs we need from our selected sound card. And the sample value corresponding to 0dBFS. More on that later as well.
Now, we haven't defined an instrument yet. An instrument definition always starts with the prefix instr, followed by an instrument number, and ends with endin. Everything between these two prefixes is our instrument, which is associated with its instrument number.
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1.0 instr 1 aSine poscil 0.7, 440 out aSine endin </CsInstruments> </CsoundSynthesizer>
Lastly, the score is missing. In the score, we give Csound the information about which instrument should be triggered, when, and for how long. The score is enclosed in the <CsScore> brackets.
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1.0 instr 1 aSine poscil 0.7, 440 out aSine endin </CsInstruments> <CsScore> </CsScore> </CsoundSynthesizer>
There, the score statements are written, which consist of so-called p-fields. At least three p-fields are always required. p1 stands for the instrument that should be triggered. The first p-field in a score statement also requires a letter that defines the type of the score statement. In the score, not only instruments can be called, but other data can also be initialized, which is not relevant here. An instrument call starts with an 'i'. p2 stands for the time of the call. If I write 0 for p2, the instrument starts after 0 seconds, i.e., immediately. If I write 3.45, the instrument starts after 3.45 seconds. p3 stands for the duration of the call.
i1 0 4
With this score statement, instrument 1 is triggered after 0 seconds and has a duration of 4 seconds.
And there you have it, here is our first sound from Csound, your "Hello World!" Code:
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1.0 instr 1 aSine poscil 0.7, 440 out aSine endin </CsInstruments> <CsScore> i1 0 4 </CsScore> </CsoundSynthesizer>