i- versus k- versus a-

As you’ve probably noticed, the names of most Csound variables begin with the letters i-, k-, or a-. The time has come to explain what those letters mean. The meanings are simple enough, but in order to make their implications clear, we’ll have to look at what happens when Csound is running.


image

Note The symbol i in the score has a completely different meaning from the i- variable prefix in instrument code.


When an i-statement in the score starts an instrument (or when the instrument is started from another instrument using an opcode such as event), Csound pauses for a moment to initialize the instrument. During the initialization process, the instrument is set up so that it’s ready to play. This is called the initialization pass, or “i-time” for short. The details of the process are a bit complicated internally. At the user level, what you mainly need to know is that Csound goes through the instrument code, looking for variables that begin with i-. When it finds them, it substitutes actual numerical values. For example, consider these three lines:


  idur = p3
  iamp = p4
  kampenv linen iamp, 0.01, idur, 0.01

As the instrument is being initialized, Csound finds the numerical value in the score for this event’s p3 and p4. These are constants; that is, they’re not going to change during the event. We’ll suppose that p3 is 2.0 and p4 is 0.5. In that case, the linen opcode is initialized like this:


  kampenv linen 0.5, 0.01, 2.0, 0.01

Notice that the value 0.5 has been substituted for iamp and 2.0 for idur.

Unless the instrument is later reinitialized from its own code using the reinit opcode (as explained in the “Instrument Control” section in Chapter 7), the initialization pass happens only once—when the instrument is loaded by Csound and made ready to play a given event. This happens not just once for the instrument, but separately for each event that uses the instrument, because the values in the p-fields may be different for each event.

The important thing to understand is that i-time variables cannot change their values while the instrument is playing a given event. They can only be set to a value at the very beginning of the event (or when the instrument is being reinitialized by reinit.)

This might be considered something of a limitation, but it has an important positive consequence in terms of efficiency. If you’re trying to reduce the CPU load on your computer in order to get Csound to run better in a live performance situation, you should use an i-time variable when possible, in preference to a k-rate variable, because as far as Csound is concerned, i-time variables are “set it and forget it” values. k-rate values, on the other hand, can change continuously while an instrument is playing, so Csound will need to evaluate them during every k-period, even if the value hasn’t changed.


image

Used before Defined One of the more common error messages encountered by newcomers to Csound is the statement that a variable was used before being defined. Several different syntax errors can cause this:

image A comma inserted or omitted at the wrong point in a line that should be defining the variable.

image A variable defined in the orchestra header, but without the g- prefix (for instance, giBaseFrequency) that causes it to be available for use in all instruments.

image A typo in the name of the variable.

The basic rule is that a variable must first appear at the left end of a line. Only after it has appeared at the left end of an earlier line can it later be used on the right side of an opcode or equals sign.


k- is an abbreviation for “control,” and a- is an abbreviation for “audio.” Variables that are used to control the sound, such as, perhaps, kampenv, will typically begin with a k-, while audio variables, such as asignal, begin with an a-. In order to understand the distinction, you need to know a bit about how Csound produces sound.

Once an instrument has been initialized and starts playing, Csound runs through all of the instrument’s code once during each k-period. The instrument’s code is processed over and over, hundreds or thousands of times per second, for as long as the event lasts.

Let’s suppose your orchestra header says this:


  sr = 44100
  ksmps = 100

The value for sr means that the sample rate is 44,100 samples per second—the CD audio standard. The value for ksmps means that in each k-period, 100 samples of audio will be generated. This concept is absolutely key. Csound audio signals are not, generally speaking, computed one at a time. Instead, during a k-period, Csound computes an entire vector of audio. (If you think of a vector as a chunk of data, you won’t be far wrong.) Given the orchestra header above, on every pass through every instrument that is currently playing, Csound will compute a vector containing 100 samples.

Given that 44,100 samples have to be computed for each second of audio, it’s easy to see that given the settings above, Csound will need to run through the instrument’s code 441 times in every second. This value is the k-rate, or kr. In older versions of Csound, you would also have had to state the value for kr, which is the control rate, explicitly in the orchestra header. kr still exists as a system variable, but today Csound computes it automatically using the formula:


  kr = sr / ksmps

What this means is that the control rate (in repetitions per second, or Hz) is equal to the sampling rate (also in repetitions per second) divided by the number of samples in each k-period. When ksmps=100 and sr=44100, as above, kr=441.

The reason Csound operates this way is because it’s efficient in terms of usage of the computer’s CPU. The larger the value of ksmps, the fewer k-time passes Csound will have to make through your instrument code during each second.

The value of a k-rate variable can change only once per k-period. Depending on how the instrument is designed, this can sometimes have an effect on the audio quality. Given a value of 100 for ksmps, as above, the output of an amplitude envelope like this:


  kampenv linseg 0, 0.1, 1, p3 – 0.2, 1, 0.1, 0

will be computed 441 times per second. Because its attack time (the first time value in the arguments to linseg) is 0.1 second, there will be only 44 different values computed and stored in the variable kampenv while the output rises from a level of 0 to a level of 1 during the attack ramp of the envelope. If you know a little about digital audio, you may suspect that this is less than ideal. Depending on how an envelope like this is used, you may possibly hear zipper noise, a sort of grainy quality, as the envelope signal rises from 0 to 1.

How do you avoid zipper noise? Lower the value of ksmps. Many Csound users set ksmps rather high while developing a piece, because they gain a tiny bit in the time required to produce an audio output, but when they’re ready to render a finished piece to an audio file, they reset ksmps to 1 for the smoothest possible sound.

When ksmps is 1, kr=sr. The k-rate is the same as the sampling rate. Consequently (though this fact is not generally too important to the user), the size of an audio vector will be one sample.

Some opcodes require input arguments in a particular form—i-time arguments, or k-rate arguments, or a-rate arguments. (The term “a-rate” is somewhat misleading, because everything in Csound happens at k-rate.) With other opcodes, you may have a choice of what type of argument to use. In the latter case, the prototype will show a variable whose name has the x- prefix. An x- variable can’t be used in your own code, because Csound always needs to know whether a given value is to be computed at i-time, at k-rate, or as an audio vector. This prefix is used only in prototypes in the manual.

The same thing is true of output variable types. Some opcodes always output a-rate values, some k-rate values, and some can output at either rate depending on the type of output variable you specify.

The init Opcode

Like many other computer programming languages, Csound insists that variables be declared before they’re used. If you try to use a variable as an input argument for an opcode before it has been defined in the instrument, your orchestra will fail to compile, and you’ll see an error message. (You can use a new variable as an output for an opcode, however. That usage, in effect, declares it.)

This fact could give rise to a problem of circularity, however. Suppose we want to create two LFOs and have each of them modulate the frequency of the other. (This is a patch I used to use a lot on an analog modular synthesizer.) This won’t work:


  klfo1 lfo 3, klfo2
  klfo2 lfo 3, klfo1

It won’t work because the value klfo2 is being used before it’s defined. Let’s suppose we want the frequency of LFO 1 to begin at 2 Hz when the instrument starts. We might first think to do this:


  klfo2 = 2
  klfo1 lfo 3, klfo2
  klfo2 lfo 3, klfo1

But this won’t work either. Can you see why? On every k-rate pass through the instrument’s code, the value of klfo2 will be set to 2. It won’t matter what value is created for that variable by the second LFO, because the value will always be reset to 2 on the next pass.

The solution is the init opcode. init is unique in that it operates on k-rate (and a-rate) variables, but runs at i-time, during the initialization pass. The correct way to set up our two cross-modulating LFOs is to use init like this:


  klfo2 init 2
  klfo1 lfo 3, klfo2
  klfo2 lfo 3, klfo1

Now the variable klfo2 has been properly declared, so it can be used as an argument to the first LFO, but it will be given a value of 2 by init only on the first k-rate pass. After that, it will have whatever value is output by the second LFO.

As a footnote, here’s a simple instrument that uses this type of patch to produce an animated burbling tone. The two LFOs are used to control amplitude, pitch, FM index, and panning.


  klfo2 init 1
  klfo1 lfo 2, klfo2, 1
  klfo1 = klfo1 + 1.2
  klfo2 lfo 2, klfo1, 1
  klfo2 = klfo2 + 1.1
  asig foscil 0.1 + (klfo2 * 0.2), 220 + (55 * klfo1), 1, 1, 
    klfo1 - klfo2, giSine
  aoutL, aoutR pan2 asig, klfo2 - klfo1
  outs aoutL, aoutR


image

Caution To make a long line of Csound code easier to view, you can break it up into two or more short lines by ending each line (except the last one) with a backslash character (). This method is used in the code examples in Csound Power! Note, however, that the backslash character is not currently supported by CsoundQt. If you’re using CsoundQt as a front end to enter the examples in this book, you must type backslash-interrupted lines as single long lines.


Converting from a- to k-

Occasionally you may need to convert an audio signal to k-rate. This is necessary, for instance, if you want to use the signal as an argument to an opcode that won’t accept an a-rate input. The tool for this is downsamp. The downsamp opcode down-samples the audio signal, producing one value in every k-period. It takes an optional argument specifying the number of audio samples to be averaged in producing the k-rate output. Especially with high-frequency audio signals, averaging is useful, because it smooths out the signal.

Converting in the other direction is easy:


  asig = ksig

However, the manual suggests that the upsamp opcode performs the process a little more efficiently. This conversion produces an audio signal that repeats the most recent value of ksig for an entire k-period. With high values of ksmps, the audio signal created by up-sampling will be very distorted. The interp opcode performs linear interpolation while up-sampling, so it’s a better choice than upsamp when the value of ksmps is higher than 4 or thereabouts.

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

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