Generating an audio signal within an instrument and sending it directly to Csound’s output is often all that’s needed—but sometimes we need to bus signals from one instrument to another. In particular, effects processors such as reverb and delay are usually implemented in Csound as separate instruments.
Tip It’s usually better to make an effect a separate Csound instrument for a couple of reasons. First, effects like reverb and delay typically linger for a few seconds after the last note that they’re processing has stopped. If the effect is written as part of the instrument playing the note, the effect will end when the note ends. It’s possible to work around this limitation by extending the note event, but it’s tricky and not necessary. Second, because reverb can be a fairly CPU-intensive process, it’s usually better to run only one instance of a reverb, by having the reverb instrument run throughout the piece, than to give each note its own reverb processor.
Csound provides four different methods of routing signals from one instrument to another. Each is the best choice in certain situations. The use of global variables was already covered in Chapter 6. A global variable is the easiest choice if you only need one or two signal paths between instruments. Simply declare values in the orchestra header, like this:
gaReverbInL init 0 gaReverbInR init 0
and you’re ready to go. Here’s an example that plays a pitch-swept tone and sends it to a reverb using global variables:
sr = 44100 ksmps = 4 nchnls = 2 0dbfs = 1 gaReverbInL init 0 gaReverbInR init 0 instr 1 iCos ftgen 0, 0, 8192, 11, 1 iamp = 0.7 kfreq line 300, p3, 600 asig gbuzz 1, kfreq, 75, 1, 0.9, iCos ifiltrise = p3 * 0.1 ifiltdecay = p3 * 0.9 kfiltenv linseg 300, ifiltrise, 2500, ifiltdecay, 500 afilt moogladder asig, kfiltenv, 0.2 ashaped linen afilt, 0.01, p3, 0.1 gaReverbInL = gaReverbInL + ashaped gaReverbInR = gaReverbInR + ashaped aout = ashaped * iamp outs aout, aout endin instr 101 ioutlevel = p4 ainL = gaReverbInL ainR = gaReverbInR gaReverbInL = 0 gaReverbInR = 0
aoutL, aoutR reverbsc ainL, ainR, 0.8, 6000 outs aoutL * ioutlevel, aoutR * ioutlevel endin </CsInstruments> <CsScore> i1 0 10 i101 0 12 1.2
A few things about this example are worth pointing out. First, the reverb instrument starts running at the beginning of the score, and runs for a couple of seconds after the last note in the score ends. (In this case, there’s only one note.) Its output level is controlled from p4 in the score. Second, because reverbsc is a stereo reverb opcode, we’re sending left and right signals to it, though in this case the two signals happen to be the same. Finally, the bus signals have been zeroed out in the reverb after being copied into local variables. If you’re mixing the signals from several instruments (or, for that matter, several instances of a single instrument that is being used polyphonically) into the reverb bus, this step is essential. If you fail to zero out the bus, the signal in it will quickly build to an astronomical level.
We’re going to edit this example to illustrate the other possibilities. When your busing needs are more complex, you can turn to the chnset and chnget opcodes, or to the zak family of opcodes. chnset and chnget are convenient when you want to use named channels. The zak family of opcodes is a better choice when you have quite a few buses and want to refer to them (perhaps by selecting the bus from a p-field in the score) by number. They’re also convenient if you want to let an instrument choose which effect send bus to use, either at random or in response to some external control message. You could do the switching to a different bus using either global variables or chnset/chnget, but doing so would be messy. It’s easier with zak.
One advantage of the chnset/chnget mechanism is that the channels don’t need to be declared in the orchestra header. Starting from the code in the example above, delete the declaration of the global audio variables. Then, in instrument 1, replace these two lines:
gaReverbInL = gaReverbInL + ashaped gaReverbInR = gaReverbInR + ashaped
with these two:
chnmix ashaped, “RevInL” chnmix ashaped, “RevInR”
Next, replace these four lines in instrument 101, the reverb:
ainL = gaReverbInL ainR = gaReverbInR
gaReverbInL = 0 gaReverbInR = 0
with these four lines:
ainL chnget “RevInL” ainR chnget “RevInR” chnclear “RevInL” chnclear “RevInR”
The result should sound exactly the same as before. The chnmix opcode mixes an audio signal with whatever is already in the named channel (in this case, “RevInL” and ”RevInR”), so the signals from multiple notes can be sent to the bus at the same time. The chnclear opcode resets the data buffer of the channel to zero on every k-period.
Using the zak opcodes is almost as easy, but it does require that you remember which of the numbered buses are being used for what. Begin by initializing a couple of buffers in the orchestra header:
zakinit 2, 2
The zakinit opcode initializes an arbitrary number of a-rate and k-rate buffers. In this case, we’re not using the k-rate buffers, but zakinit requires a non-zero value for its second argument, so we’ll set it to 2 so as to be symmetrical. Next, use the zawm opcode to send the signal from instrument 1, like this:
zawm ashaped, 0 zawm ashaped, 1
The name of this opcode is terse but readable: The “a” means “audio,” the “w” means “write,” and the “m” means “mix.” Finally, in the reverb, replace the four lines using chnget and chnclear with these three lines:
ainL zar 0 ainR zar 1 zacl 0, 1
The “r” in zar means “read,” and zacl clears all of the audio buffers between the two numbers given as arguments. The last line of code above uses zacl to clear (set to 0) the buffers between 0 and 1, inclusive. Again, the sound should be exactly the same as before.
The mixer opcodes provide yet another way to mix audio signals before sending them to the output. The manual explains these opcodes pretty clearly, but a few points are worth reiterating here:
MixerSetLevel (or MixerSetLevel_i) must be used in a lower-numbered instrument than the instrument using the corresponding send bus.
MixerSend must be used in a lower-numbered instrument than the one using the corresponding MixerReceive.
After using MixerReceive, you must use MixerClear to zero out the signals in all of the busses.
Using MixerSetLevel or MixerSetLevel_i is mandatory, as this creates the bus.
You may find it useful to let the number of the send bus be the same as the number of the instrument, but this is not mandatory. MixerSetLevel_i can conveniently be placed in the orchestra header; MixerSetLevel can accept k-rate signals as inputs, which makes it ideal for fade-outs, fade-ins, and crossfades.
Here is a not-too-convoluted example. It includes two instruments, basically identical, whose outputs are sent to the mixer. Instrument 1 uses a p-field to send the signal either to the 0 (left) or 1 (right) channel of the bus. Instrument 2 sends to both channels and is set to a lower level by the second MixerSetLevel_i line.
giSine ftgen 0, 0, 8192, 10, 1 ; set the send and receive channels for two busses, and their levels: MixerSetLevel_i 1, 101, 1 MixerSetLevel_i 2, 101, 0.4 instr 1 kenv line 0.25, p3, 0 asig foscil kenv, p4, 1, 1, kenv * 3, giSine MixerSend asig, p1, 101, p5 endin instr 2 kenv line 0.35, p3, 0 asig foscil kenv, p4, 1, 1, kenv * 3, giSine MixerSend asig, p1, 101, 0 MixerSend asig, p1, 101, 1 endin
instr 101; mixer aL MixerReceive p1, 0 aR MixerReceive p1, 1 outs aL, aR MixerClear endin </CsInstruments> <CsScore> ; start the mixer instrument: i101 0 5 ; play some notes and send them to the left channel: i1 0 3 300 0 i1 0.5 . 400 0 i1 1 . 500 0 ; play some notes and send them to the right channel: i1 0 3 700 1 i1 0.5 . 800 1 i1 1 . 900 1 ; play some notes that will be sent to both channels: i2 0 0.25 150 i2 + i2 + i2 + i2 + i2 + i2 + i2 + . 175 i2 + i2 + i2 + i2 + i2 + 0.75
MixerSend has four input arguments—the audio signal, the send channel, the receive bus, and a channel number. The term “channel number” may be slightly misleading. If nchnls=2, the final argument to MixerSend should be 0 for the left channel and 1 for the right channel.