WORKING WITH CONTROLLERS

Scanning MIDI Continuous Controllers

The most useful opcode for reading in midi continuous controllers is ctrl7. ctrl7's input arguments allow us to specify midi channel and controller number of the controller to be scanned in addition to giving us the option to rescale the received midi values between a new minimum and maximum value as defined by the 3rd and 4th input arguments.

The following example scans midi controller 1 on channel 1 and prints values received to the console. The minimum and maximum values are rescaled between 0 and 127 therefore they are not actually rescaled at all. Note that controller 1 is also the modulation wheel on a midi keyboard.

  EXAMPLE 07C01.csd

<CsoundSynthesizer>
<CsOptions>
-Ma
</CsOptions>
<CsInstruments>
;Example by Iain McCurdy

;this example does not use audio so 'sr' and 'nchnls' have been omitted
ksmps = 32

  instr 1
kCtrl    ctrl7    1,1,0,127; read in midi controller #1 on channel 1
kTrigger changed  kCtrl; if 'kCtrl' changes generate a trigger ('bang')
 if kTrigger=1 then
printks "Controller Value: %d%n", 0, kCtrl; print kCtrl to console only when its value changes
 endif
  endin

</CsInstruments>
<CsScore>
i 1 0 300
e
</CsScore>
<CsoundSynthesizer>

There are also 14 bit and 21 bit versions of ctrl7 (ctrl14 and ctrl21) which improve upon the 7 bit resolution of ctrl7 but hardware that outputs 14 or 21 bit controller information is rare so these opcodes are probably rarely used.

Scanning Pitch Bend and Aftertouch

We can scan pitch bend and aftertouch in a similar way using the opcodes pchbend and aftouch. Once again we can specify minimum and maximum values with which to re-range the output but these input arguments are optional and the following example uses the default values of -3 to 1 for pitch bend and 0 to 127 for aftertouch. The next example merely prints out values for pitch bend and aftertouch received to the console as the previous example did for continuous controllers but one thing to bear in mind this time is that for the pchbend opcode to function the Csound instrument that contains it needs to have been activated by a MIDI event. You will need to play a midi note on your keyboard and then move the pitch bend wheel.

  EXAMPLE 07C02.csd

<CsoundSynthesizer>
<CsOptions>
-Ma
</CsOptions>
<CsInstruments>
;Example by Iain McCurdy

;this example does not use audio so 'sr' and 'nchnls' have been omitted
ksmps = 32

  instr 1
kPchBnd pchbend; read in pitch bend information
kTrig1  changed kPchBnd; if 'kPchBnd' changes generate a trigger ('bang')
 if kTrig1=1 then
printks "Pitch Bend Value: %f%n", 0, kPchBnd; print kPchBnd to console only when its value changes
 endif

kAfttch aftouch; read in aftertouch information
kTrig2  changed kAfttch; if 'kAfttch' changes generate a trigger ('bang')
 if kTrig2=1 then
printks "Aftertouch Value: %d%n", 0, kAfttch; print kAfttch to console only when its value changes
 endif
  endin

</CsInstruments>
<CsScore>
f 0 300
e
</CsScore>
<CsoundSynthesizer>

Initializing MIDI Controllers

It may be useful to be able to define the beginning value of a midi controller that will be used in an orchestra - that is, the value it will adopt until its corresponding hardware control has been moved. Until a controller has been moved its value in Csound defaults to its minimum setting unless additional initialization has been carried out. It is important to be aware that midi controllers only send out information when they are moved, when lying idle they send out no information. As an example, if we imagine we have an Csound instrument in which the output volume is controlled by a midi controller it might prove to be slightly frustrating that this instrument will, each time the orchestra is launched, remain silent until the volume control is moved. This frustration might become greater when many midi controllers are begin utilized. It would be more useful to be able to define the starting value of each of these controllers. The initc7 opcode allows us to define the starting value of a midi controller until its hardware control has been moved. If initc7 is placed within the instrument itself it will be re-initialized each time the instrument is called, if it is placed in instrument 0 (just after the header statements) the it will only be initialized when the orchestra is first launched. The latter case is probably most useful.

In the following example a simple synthesizer is implemented. Midi controller 1 controls the output volume of this instrument but the initc7 statement near the top of the orchestra ensures that this control does not default to its minimum setting. The arguments that initc7 takes are for midi channel, controller number and intitial value. Initial value is defined within the range 0-1, therefore value of 1 set this controller to its maximum value (midi value 127), and value of 0.5 sets it to its halfway value (midi value 64) and so on.

Additionally this example uses the cpsmidi opcode to scan in midi pitch and the ampmidi opcode to scan in note velocity.

  EXAMPLE 07C03.csd

<CsoundSynthesizer>
<CsOptions>
-Ma -odac
</CsOptions>
<CsInstruments>
;Example by Iain McCurdy

sr = 44100
ksmps = 32
nchnls = 1
0dbfs = 1

giSine ftgen 0,0,2^12,10,1
initc7 1,1,1; initialize controller 1 on midi channel 1 to its maximum level

  instr 1
iCps cpsmidi; read in midi pitch in cycles-per-second
iAmp ampmidi 1; read in note velocity - re-range to be from 0 to 1
kVol ctrl7   1,1,0,1; read in controller 1, channel 1. Re-range to be from 0 to 1
aSig poscil  iAmp*kVol, iCps, giSine
     out     aSig
  endin

</CsInstruments>
<CsScore>
f 0 300
e
</CsScore>
<CsoundSynthesizer>

Smoothing 7-bit Quantization in MIDI Controllers

A problem we encounter with 7 bit midi controllers is the poor resolution that they offer us. 7 bit means that we have 2 to the power of 7 possible values; therefore 128 possible values, which is rather inadequate for defining the frequency of an oscillator over a number of octaves, the cutoff frequency of a filter or a volume control. We quickly become aware of the parameter that is being controlled moving up in steps - not so much of a 'continuous' control after all. We may also experience clicking artefacts, sometimes called 'zipper noise', as the value changes. There are some things we can do to address this problem however. We can filter the controller signal within Csound so that the sudden changes that occur between steps along the controller's travel are smoothed using additional interpolating values - we must be careful not to smooth excessively otherwise the response of the controller will become sluggish. Any k-rate compatible lowpass filter can be used for this task but the portk opcode is particularly useful as it allows us to define the amount of smoothing as a time taken to glide to half the required value rather than having to deal with a cutoff frequency. Additionally this 'half time' value can be varied as a k-rate value which provides an advantage availed of in the following example.

This example takes the simple synthesizer of the previous example as its starting point. The volume control which is controlled by midi controller 1 on channel 1 is passed through a portk filter. The 'half time' for portk ramps quickly up to its required value of 0.01 through the use of a linseg statement in the previous line. This is done so that when a new note begins the volume control jumps immediately to its required value rather than gliding up from zero due to the effect of the portk filter. Try this example with the portk half time defined as a constant to hear the difference. To further smooth the volume control, it is converted to an a-rate variable through the use of the interp opcode which, as well as performing this conversion, interpolates values in the gaps between k-cycles.

  EXAMPLE 07C04.csd

<CsoundSynthesizer>
<CsOptions>
-Ma -odac
</CsOptions>
<CsInstruments>
;Example by Iain McCurdy

sr = 44100
ksmps = 32
nchnls = 1
0dbfs = 1

giSine   ftgen    0,0,2^12,10,1                      
         initc7   1,1,1; initialize controller 1 on midi channel 1 to its maximum level

  instr 1
iCps      cpsmidi ;read in midi pitch in cycles-per-second
iAmp      ampmidi 1; read in note velocity - re-range to be from 0 to 1
kVol      ctrl7   1,1,0,1; read in controller 1, channel 1. Re-range to be from 0 to 1
kPortTime linseg  0,0.001,0.01; create a value that quickly ramps up to 0.01
kVol      portk   kVol, kPortTime; create a new version of kVol that has been filtered (smoothed) using portk
aVol      interp  kVol; create an a-rate version of kVol. Use intepolation to smooth this signal even further
aSig      poscil  iAmp*aVol, iCps, giSine     
          out     aSig
  endin

</CsInstruments>
<CsScore>
f 0 300
e
</CsScore>
<CsoundSynthesizer>

All of the techniques introduced in this section are combined in the final example which includes a 2-semitone pitch bend and tone control which is controlled by aftertouch. For tone generation this example uses the gbuzz opcode.

  EXAMPLE 07C05.csd

<CsoundSynthesizer>
<CsOptions>
-Ma -odac
</CsOptions>
<CsInstruments>
;Example by Iain McCurdy

sr = 44100
ksmps = 32
nchnls = 1
0dbfs = 1

giSine   ftgen    0,0,2^12,10,1
         initc7   1,1,1; initialize controller 1 on midi channel 1 to its maximum level

  instr 1
iOct      octmidi; read in midi pitch in Csound's 'oct' format
iAmp      ampmidi 0.1; read in note velocity - re-range to be from 0 to 0.2
kVol      ctrl7   1,1,0,1; read in controller 1, channel 1. Re-range to be from 0 to 1
kPortTime linseg  0,0.001,0.01; create a value that quickly ramps up to 0.01
kVol      portk   kVol, kPortTime; create a new version of kVol that has been filtered (smoothed) using portk
aVol      interp  kVol; create an a-rate version of kVol. Use intepolation to smooth this signal even further
iBndRange =       2; pitch bend range in semitones
imin      =       0; equilibrium position
imax      =       iBndRange * 1/12; max pitch displacement (in oct format)
kPchBnd	  pchbend imin, imax; pitch bend variable (in oct format)
kPchBnd   portk   kPchBnd, kPortTime; create a new version of kPchBnd that has been filtered (smoothed) using portk
aEnv      linsegr 0,0.005,1,0.1,0; amplitude envelope with release stage
kMul      aftouch 0.4,0.85; read in a value that will be used with gbuzz as a kind of tone control
kMul      portk   kMul,kPortTime; create a new version of kPchBnd that has been filtered (smoothed) using portk
aSig      gbuzz   iAmp*aVol*aEnv, cpsoct(iOct+kPchBnd), 70,0,kMul,giSine
          out     aSig
  endin

</CsInstruments>
<CsScore>
f 0 300
e
</CsScore>
<CsoundSynthesizer>