Keith Hamel's Csound Course

Week 11

In this tutorial:


Index Next Week Previous Week

Instrument Design in CSound

As you have no doubt discovered by now, it is possible to create Csound orchestras with a small number of sophisticated, flexible instruments OR to create an orchestra with a large number of relatively simple instruments (usually with different instruments being copies of others, with slight modifications). Either approach works - lots of simple instruments makes the score clearer but may not always let you control the sound to the degree that you may want, while complex instruments allow lots of flexibility, but usually require lots of score parameters, and the score can be more confusing. Generally, I feel that a compromise between the two extremes is the best solution. Instruments should be designed with enough flexibility that they will operate effectively over an extended range, for a variety of durations, and for some timbral variety. However, it is usually overkill to continue modifying an instrument so that it does everything. (In terms of memory use and speed of computation, small simple instuments are more efficient.)

goto top Index


Steps in Designing a CSound Instrument

At times, composers have a clear idea of what sound they want; at others times composers simply experiment until they develop something they like. In both cases, the basic procedure for designing a sound is:
  1. Create the basic timbre and main envelope.
  2. Create any secondary timbres (noise, wind, additional audio generators) and their envelopes.
  3. Add the important controls and modifiers (vibrato, tremolo, glissando, pan etc.)
  4. Add secondary and tertiary controls to the above controls to produce more dynamic effects and subtlety. (randh and randi are useful here).
  5. Add filters and reverberation effects if desired. (Conditional statements can be used to bypass the effects)
  6. Clean up the instrument by linking relevant control values to the pitch, and by reassigning p3 if necessary. The instrument should work properly in a range of registers and durations.
When designing an instrument, you should keep your score simple, and gradually increase the complexity of the score (durations, pitches and volumes) as the instrument gets refined.

The first two steps are quite straight-forward. Each audio component should have its own envelope, and the signals should be properly balanced in the final output.

When the important controls and modifiers are added, you need to decide whether the values should be hard-wired into the instrument or passed as parameters from the score. Parameters passed from the score provide more flexibility, but require that the correct range of values be remembered by the composer. (Ranges for the parameters should be commented in your code).

The secondary and tertiary controls are used to add an extra dimension of subtlety to a sound. They are usually deal with slight alterations to existing controls. A simple vibrato:

ivibw  =   cpspch(p5) * .01       ; vibrato width
ksig   oscil   ivibw, 4, 1        ; a vibrato 4 * per second

can be modified with a linseg on the vibrato speed:

ivibw  =   cpspch(p5) * .01                      ; vibrato width
kline  linseg  5, p3*.6, 4, p3*.3, 4.5, p3*.1    ; adjust vibrato speed
ksig   oscil   ivibw, kline, 1                   ; a vibrato

and with a random addition ( up to 1/5 of a cycle) to the vibrato width:
ivibw  =   cpspch(p5) * .01                      ; vibrato width
krand  randh   .2, 3                             ; range of .2 Hz .
kline  linseg  5, p3*.6, 4, p3*.3, 4.5, p3*.1    ; adjust vibrato speed
ksig   oscil   ivibw + krand, kline, 1           ; a vibrato 

and the speed of the randh can be adjusted so that there are more random values towards the end of the sound:

ivibw   =   cpspch(p5) * .01                     ; vibrato width
kspeed  line    3, p3 * .7, 3, p3 * .3, 6        ; increase from 3 to 6
krand   randh   .2, kspeed                       ; range of .2 Hz.
kline   linseg  5, p3*.6, 4, p3*.3, 4.5, p3*.1   ; adjust vibrato speed
ksig    oscil   ivibw + krand, kline, 1          ; a vibrato 

and so on: Filters and reverb can be added to colour the sound. Filters can be added to your instrument, but it is generally better to create separate reverb instrument and pass a global parameter to this instrument. You may want to use a parameter in the score as a flag to determine whether or not reverb should be used. In the example below, reverb is added if p6 is equal to 1 - if it is 0 there is no reverb.

if     (p6 == 0)     goto dry
wet:
ga1    reverb asig, 2.5           ; send to global reverb
dry:
outs   asig, asig                 ; output audio signal

goto top Index


Associating parameters to the pitch

Once an instrument has been designed, it is useful to ensure that certain aspects of the sound be linked to the pitch being passed from the score. Aspects such as tremolo or vibrato speed and depth, noise band width, filter frequencies etc. should be linked to the pitch. In the example below, an instrument has been designed with a noise bandwith of 20 Hz - the pitch used to test the instrument was 8.00.

kband  randh   20, kr                            ; band width 20 Hz
asig   oscil   kenv * p4, cpspch(p5)+kband, 1    ; makes noise 
The band of 20 will work at 8.00, but with other pitches, the fixed 20 Hz band will be perceived as being either larger or smaller than at 8.00. The bandwidth should be associated with p5:

iwidth =   cpspch(p5)/cpspch(8.00) * 10          ; calculate the width
kband  randh  iwidth kr                          ; band width 20 Hz
asig   oscil  kenv * p4, cpspch(p5)+kband, 1     ; makes noise 
The scaling of one parameter by another need not be linear. It could involve a more complex arithmetic expression, or it could index a function table (perhaps containing exponential segments).

iwidth  =   sqrt(cpspch(p5))         ; width is square root of pitch
OR

iwidth  table  cpspch(p5), 4         ; get width from table 4

goto top Index


Reassigning p3

At times it is useful to reassign p3 within the instrument. The reasons for doing this are:
  1. to ensure the minimum duration required by the instrument.
  2. to limit the maximum duration of the instrument
  3. to adjust the p3 to the duration required by a table read (discussed last week).
The reassignment of p3 can be done through a conditional statement, or simply through an assignment statement:

p3   =   (p3 < .25 ? .25 : p3)
OR

p3   =   11.888*(cpspch(p5)/cpspch(8.00)

goto top Index


Designing an instrument step-by-step

Begin with one foscil and a two part envelope

instr 1
ipitch =   cpspch(p5)                                ; convert the pitch
kenv   linseg   0, .05, 1.2, .03, 1, p3-.08, 2.4     ; a two part envelope
kmult  expseg   1, .08, 1, p3 -.08, .0001
kenv   =   kenv * kmult
asig   foscili  kenv * p4, ipitch, 1, 1.005, 1, 1    ;slightly inharmonic
outs   asig, asig
endin

Use three slightly out of sync foscils:

instr 2
ipitch =   cpspch(p5)                               ; convert the pitch
kenv   linseg   0, .05, 1.2, .03, 1, p3-.08, 2.4    ; a two part envelope
kmult  expseg   1, .08, 1, p3 -.08, .0001
kenv   =   kenv * kmult
asig1  foscili  kenv*p4, ipitch, 1, 1.005, 1, 1     ;slightly inharmonic
asig2  foscili  kenv*p4, ipitch, 1, 1.002, 1, 1     ;slightly inharmonic
asig3  foscili  kenv*p4, ipitch, 1, 1.009, 1, 1     ;slightly inharmonic
asig   =   (asig1 + asig2 + asig3)/3
outs asig, asig
endin

Add some white noise on the pitch:

instr 3
ipitch =   cpspch(p5)     ; convert the pitch
kenv   linseg   0, .05, 1.2, .03, 1, p3-.08, 2.4     ; a two part envelope
kmult  expseg   1, .08, 1, p3 -.08, .0001
kenv   =   kenv * kmult
asig1  foscili  kenv * p4, ipitch, 1, 1.005, 1, 1    ;slightly inharmonic
asig2  foscili  kenv * p4, ipitch, 1, 1.002, 1, 1    ;slightly inharmonic
asig3  foscili  kenv * p4, ipitch, 1, 1.009, 1, 1    ;slightly inharmonic
knenv  linseg   .032, .06, .022, p3-.06, .022        ; bandwidth multiplier
knoise randi    knenv*ipitch, kr                     ; generate rand freq
anoise oscili   kenv*p4, knoise+ipitch, 1            ; generate noise
asig   =   (asig1 + asig2 + asig3 + anoise) / 4      ; balance outputs
outs   asig, asig
endin

Add some random frequencies to the foscils:

instr 4
ipitch =   cpspch(p5)                              ; convert the pitch
kenv   linseg  0, .05, 1.2, .03, 1, p3-.08, 2.4    ; a two part envelope
kmult  expseg  1, .08, 1, p3 -.08, .0001
kenv   =   kenv * kmult
krand1 randi   ipitch *.007, 8, .1                 ; randi with seed
krand2 randi   ipitch *.008, 9, .2                 ; randi with seed
krand3 randi   ipitch *.009, 6, .3                 ; randi with seed

asig1  foscili kenv * p4, ipitch+krand1, 1, 1.005, 1, 1
asig2  foscili kenv * p4, ipitch+krand2, 1, 1.002, 1, 1
asig3  foscili kenv * p4, ipitch+krand3, 1, 1.009, 1, 1

knenv  linseg .032, .06, .022, p3-.06, .022         ; bandwidth multiplier
knoise randi knenv*ipitch, kr                       ; generate rand freq
anoise oscili kenv*p4, knoise+ipitch, 1             ; generate noise
asig   =   (asig1 + asig2 + asig3 + anoise) / 4     ; balance outputs
outs   asig, asig
endin

Add line segments to the random frequencies:

instr 5
ipitch =   cpspch(p5)                               ; convert the pitch
kenv   linseg  0, .05, 1.2, .03, 1, p3-.08, 2.4     ; a two part envelope
kmult  expseg  1, .08, 1, p3 -.08, .0001
kenv   =   kenv * kmult

kline1 linseg  .08, .07, .0109, p3-.07, .006        ; line for rand1
kline2 linseg  .09, .07, .0095, p3-.07, .0055       ; line for rand2
kline3 linseg  .1, .07, .0086, p3-.07, .0035        ; line for rand3
kline4 linseg  .1, .07, .0086, p3-.07, .0055        ; line for rand speed
kline4 =   kline4 * ipitch                          ; set rand speed

krand1 randi  ipitch*kline1, kline4, .1             ; randi with seed
krand2 randi  ipitch*kline2, kline4+1, .2           ; randi with seed
krand3 randi  ipitch*kline3, kline4-1, .3           ; randi with seed

asig1 foscili kenv*p4, ipitch+krand1, 1, 1.005, 1, 1
asig2 foscili kenv*p4, ipitch+krand2, 1, 1.002, 1, 1
asig3 foscili kenv*p4, ipitch+krand3, 1, 1.009, 1, 1

knenv  linseg .032, .06, .022, p3-.06, .022         ; bandwidth multiplier
knoise randi  knenv*ipitch, kr                      ; generate rand freq
anoise oscili kenv*p4, knoise+ipitch, 1             ; generate noise

asig  =   (asig1+asig2+asig3+anoise)/4              ; balance outputs
outs asig, asig
endin

Add individual amplitude envelopes:

instr 6
ipitch =   cpspch(p5)     ; convert the pitch
kenv1  linseg  0, .05, 1.2, .03, 1, p3-.08, 2.4    ; a two part envelope
kenv2  linseg  0, .045, 1.2, .032, 1, p3-.077, 2.4
kenv3  linseg  0, .052, 1.2, .032, 1, p3-.084, 2.4
kmult  expseg  1, .08, 1, p3 -.88, .0001

kenv1  =   kenv1 * kmult
kenv2  =   kenv2 * kmult
kenv3  =   kenv3 * kmult

kline1 linseg  .08, .07, .0109, p3-.07, .006       ; line for rand1
kline2 linseg  .09, .07, .0095, p3-.07, .0055      ; line for rand2
kline3 linseg  .1, .07, .0086, p3-.07, .0035       ; line for rand3
kline4 linseg  .1, .07, .0086, p3-.07, .0055       ; line for rand speed
kline4 =   kline4*ipitch                           ; set rand speed

krand1 randi  ipitch*kline1, kline4, .1            ; randi with seed
krand2 randi  ipitch*kline2, kline4+1, .2          ; randi with seed
krand3 randi  ipitch*kline3, kline4-1, .3          ; randi with seed

asig1  foscili kenv1*p4, ipitch+krand1, 1, 1.005, 1, 1
asig2  foscili kenv2*p4, ipitch+krand2, 1, 1.002, 1, 1
asig3  foscili kenv3*p4, ipitch+krand3, 1, 1.009, 1, 1

knenv  linseg .032, .06, .022, p3-.06, .022        ; bandwidth multiplier
knoise randi  knenv*ipitch, kr                     ; generate rand freq
anoise oscili kenv1*p4, knoise+ipitch, 1           ; generate noise
asig   =   (asig1+asig2+asig3+anoise)/4            ; balance outputs
outs asig, asig
endin

Add Reverb:

instr 7
ipitch =   cpspch(p5)                              ; convert the pitch
kenv1  linseg  0, .05, 1.2, .03, 1, p3-.08, 2.4    ; a two part envelope
kenv2  linseg  0, .045, 1.2, .032, 1, p3-.077, 2.4
kenv3  linseg  0, .052, 1.2, .032, 1, p3-.084, 2.4
kmult  expseg  1, .08, 1, p3 -.08, .0001
kenv1  =   kenv1*kmult
kenv2  =   kenv2*kmult
kenv3  =   kenv3*kmult

kline1 linseg  .08, .07, .0109, p3-.07, .006    ; line for rand1
kline2 linseg  .09, .07, .0095, p3-.07, .0055   ; line for rand2
kline3 linseg  .1, .07, .0086, p3-.07, .0035    ; line for rand3
kline4 linseg  .1, .07, .0086, p3-.07, .0055    ; line for rand speed
kline4 =   kline4*ipitch                        ; set rand speed
krand1 randi  ipitch*kline1, kline4, .1         ; randi with seed
krand2 randi  ipitch*kline2, kline4+1, .2       ; randi with seed
krand3 randi  ipitch*kline3, kline4-1, .3       ; randi with seed

asig1  foscili  kenv1*p4, ipitch+krand1, 1, 1.005, 1, 1
asig2  foscili  kenv2*p4, ipitch+krand2, 1, 1.002, 1, 1
asig3  foscili  kenv3*p4, ipitch+krand3, 1, 1.009, 1, 1

knenv  linseg  .032, .06, .022, p3-.06, .022    ; bandwidth multiplier
knoise randi   knenv*ipitch, kr                 ; generate rand freq
anoise oscili  kenv*p4, knoise+ipitch, 1        ; generate noise

asig   =   (asig1+asig2+asig3+anoise)/4         ; balance outputs
ga1    =   ga1 + asig                           ; send to global reverb
outs asig, asig
endin

instr 8 ; REVERB
ga1    init   0
asig   reverb ga1, 0.8
outs   asig, asig
ga1    =     0
endin


goto top Index Next Week Previous Week