Global Signal Routing

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.


image

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.

The zak Opcodes

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

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:

image MixerSetLevel (or MixerSetLevel_i) must be used in a lower-numbered instrument than the instrument using the corresponding send bus.

image MixerSend must be used in a lower-numbered instrument than the one using the corresponding MixerReceive.

image After using MixerReceive, you must use MixerClear to zero out the signals in all of the busses.

image 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.197.213