The basic concept of Csound from the early days of the program is still valent and fertile because it is a familiar musical one. You create a set of instruments and instruct them to play at various times. These calls of instrument instances, and their execution, are called "instrument events".
This scheme of instruments and events can be instigated in a number of ways. In the classical approach you think of an "orchestra" with a number of musicians playing from a "score", but you can also trigger instruments using any kind of live input: from MIDI, from OSC, from the command line, from a GUI (such as Csound's FLTK widgets or QuteCsound's widgets), from the API (also used in QuteCsound's Live Event Sheet). Or you can create a kind of "master instrument", which is always on, and triggers other instruments using opcodes designed for this task, perhaps under certain conditions: if the live audio input from a singer has been detected to have a base frequency greater than 1043 Hz, then start an instrument which plays a soundfile of broken glass...
This chapter is about the various ways to trigger instrument events whether that be from the score, by using MIDI, by using widgets, through using conditionals or by using loops.
Whatever you do in Csound with instrument events, you must bear in mind the order of execution that has been explained in chapter 03 under the initialization and performance pass: instruments are executed one by one, both in the initialization pass and in each control cycle, and the order is determined by the instrument number. So if you have an instrument which triggers another instrument, it should usually have the lower number. If, for instance, instrument 10 calls instrument 20 in a certain control cycle, instrument 20 will execute the event in the same control cycle. But if instrument 20 calls instrument 10, then instrument 10 will execute the event only in the next control cycle.
This is the classical way of triggering instrument events: you write a list in the score section of a .csd file. Each line which begins with an "i", is an instrument event. As this is very simple, and examples can be found easily, let us focus instead on some additional features which can be useful when you work in this way. Documentation for these features can be found in the Score Statements section of the Canonical Csound Reference Manual. Here are some examples:
EXAMPLE 03E01.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giWav ftgen 0, 0, 2^10, 10, 1, .5, .3, .1 instr 1 kFadout init 1 krel release ;returns "1" if last k-cycle if krel == 1 && p3 < 0 then ;if so, and negative p3: xtratim .5 ;give 0.5 extra seconds kFadout linseg 1, .5, 0 ;and make fade out endif kEnv linseg 0, .01, p4, abs(p3)-.1, p4, .09, 0; normal fade out aSig poscil kEnv*kFadout, p5, giWav outs aSig, aSig endin </CsInstruments> <CsScore> t 0 120 ;set tempo to 120 beats per minute i 1 0 1 .2 400 ;play instr 1 for one second i 1 2 -10 .5 500 ;play instr 1 indefinetely (negative p3) i -1 5 0 ;turn it off (negative p1) i 1.1 ^+1 -10 .2 600 ;turn on instance 1 of instr 1 one sec after the previous start i 1.2 ^+2 -10 .2 700 ;another instance of instr 1 i -1.2 ^+2 0 ;turn off 1.2 i -1.1 ^+1 . ;turn off 1.1 (dot = same as the same p-field above) s ;end of a section, so time begins from new at zero i 1 1 1 .2 800 r 5 ;repeats the following line (until the next "s") i 1 .25 .25 .2 900 s v 2 ;lets time be double as long i 1 0 2 .2 1000 i 1 1 1 .2 1100 s v 0.5 ;lets time be half as long i 1 0 2 .2 1200 i 1 1 1 .2 1300 s ;time is normal now again i 1 0 2 .2 1000 i 1 1 1 .2 900 s {4 LOOP ;make a score loop (4 times) with the variable "LOOP" i 1 [0 + 4 * $LOOP.] 3 .2 [1200 - $LOOP. * 100] i 1 [1 + 4 * $LOOP.] 2 . [1200 - $LOOP. * 200] i 1 [2 + 4 * $LOOP.] 1 . [1200 - $LOOP. * 300] } e </CsScore> </CsoundSynthesizer>
Triggering an instrument with an indefinite duration by setting p3 to any negative value, and stopping it by a negative p1 value, can be an important feature for live events. If you turn instruments off in this way you may have to add a fade out segment. One method of doing this is shown in the instrument above with a combination of the release and the xtratim opcodes. Also note that you can start and stop certain instances of an instrument with a floating point number as p1.
Csound has a particular feature which makes it very simple to trigger instrument events from a MIDI keyboard. Each MIDI Note-On event can trigger an instrument, and the related Note-Off event of the same key stops the related instrument instance. This is explained more in detail in chapter 07 in the MIDI section of this manual. Here, just a small example is shown. Simply connect your MIDI keyboard and it should work.
EXAMPLE 03E02.csd
<CsoundSynthesizer> <CsOptions> -Ma -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giSine ftgen 0, 0, 2^10, 10, 1 massign 0, 1; assigns all midi channels to instr 1 instr 1 iFreq cpsmidi ;gets frequency of a pressed key iAmp ampmidi 8 ;gets amplitude and scales 0-8 iRatio random .9, 1.1 ;ratio randomly between 0.9 and 1.1 aTone foscili .1, iFreq, 1, iRatio/5, iAmp+1, giSine ;fm aEnv linenr aTone, 0, .01, .01 ;for avoiding clicks at the end of a note outs aEnv, aEnv endin </CsInstruments> <CsScore> f 0 36000; play for 10 hours e </CsScore> </CsoundSynthesizer>
If you want to trigger an instrument event in realtime with a Graphical User Interface, it is usually a "Button" widget which will do this job. We will see here a simple example; first implemented using Csound's FLTK widgets, and then using QuteCsound's widgets.
This is a very simple example demonstrating how to trigger an instrument using an FLTK button. A more extended example can be found here.
EXAMPLE 03E03.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 FLpanel "Trigger By FLTK Button", 300, 100, 100, 100; creates an FLTK panel k1, ih1 FLbutton "Push me!", 0, 0, 1, 150, 40, 10, 25, 0, 1, 0, 1; trigger instr 1 (equivalent to the score line "i 1 0 1") k2, ih2 FLbutton "Quit", 0, 0, 1, 80, 40, 200, 25, 0, 2, 0, 1; trigger instr 2 FLpanelEnd; end of the FLTK panel section FLrun ; run FLTK seed 0; random seed different each time instr 1 idur random .5, 3; recalculate instrument duration p3 = idur; reset instrument duration ioct random 8, 11; random values between 8th and 11th octave idb random -18, -6; random values between -6 and -18 dB aSig oscils ampdb(idb), cpsoct(ioct), 0 aEnv transeg 1, p3, -10, 0 outs aSig*aEnv, aSig*aEnv endin instr 2 exitnow endin </CsInstruments> <CsScore> f 0 36000 e </CsScore> </CsoundSynthesizer>
Note that in this example the duration of an instrument event is recalculated when the instrument is inititalized. This is done using the statement "p3 = i...". This can be a useful technique if you want the duration that an instrument plays for to be different each time it is called. In this example duration is the result of a random function'. The duration defined by the FLTK button will be overwriten by any other calculation within the instrument itself at i-time.
In QuteCsound, a button can be created easily from the submenu in a widget panel:
In the Properties Dialog of the button widget, make sure you have selected "event" as Type. Insert a Channel name, and at the bottom type in the event you want to trigger - as you would if writing a line in the score.
In your Csound code, you need nothing more than the instrument you want to trigger:
For more information about QuteCsound, read chapter 11 (QuteCsound) in this manual.
If you use any .csd with the option "-L stdin" (and the -odac option for realtime output), you can type any score line in realtime (sorry, this does not work for Windows). For instance, save this .csd anywhere and run it from the command line:
EXAMPLE 03E04.csd
<CsoundSynthesizer> <CsOptions> -L stdin -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0; random seed different each time instr 1 idur random .5, 3; calculate instrument duration p3 = idur; reset instrument duration ioct random 8, 11; random values between 8th and 11th octave idb random -18, -6; random values between -6 and -18 dB aSig oscils ampdb(idb), cpsoct(ioct), 0 aEnv transeg 1, p3, -10, 0 outs aSig*aEnv, aSig*aEnv endin </CsInstruments> <CsScore> f 0 36000 e </CsScore> </CsoundSynthesizer>
If you run it by typing and returning a commandline like this ...
... you should get a prompt at the end of the Csound messages:
If you now type the line "i 1 0 1" and press return, you should hear that instrument 1 has been executed. After three times your messages may look like this:
In general, this is the method that QuteCsound uses and it is made available to the user in a flexible environment called the Live Event Sheet. This is just a screenshot of the current (QuteCsound 0.6.0) example of the Live Event Sheet in QuteCsound:
Have a look in the QuteCsound frontend to see more of the possibilities of "firing" live instrument events using the Live Event Sheet.
We have discussed first the classical method of triggering instrument events from the score section of a .csd file, then we went on to look at different methods of triggering real time events using MIDI, by using widgets, and by using score lines inserted live. We will now look at the Csound orchestra itself and to some methods by which an instrument can internally trigger another instrument. The pattern of triggering could be governed by conditionals, or by different kinds of loops. As this "master" instrument can itself be triggered by a realtime event, you have unlimited options available for combining the different methods.
Let's start with conditionals. If we have a realtime input, we may want to define a threshold, and trigger an event
In Csound, this could be implemented using an orchestra of three instruments. The first instrument is the master instrument. It receives the input signal and investigates whether that signal is crossing the threshold and if it does whether it is crossing from low to high or from high to low. If it crosses the threshold from low ot high the second instrument is triggered, if it crosses from high to low the third instrument is triggered.
EXAMPLE 03E05.csd
<CsoundSynthesizer> <CsOptions> -iadc -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0; random seed different each time instr 1; master instrument ichoose = p4; 1 = real time audio, 2 = random amplitude movement ithresh = -12; threshold in dB kstat init 1; 1 = under the threshold, 2 = over the threshold ;;CHOOSE INPUT SIGNAL if ichoose == 1 then ain inch 1 else kdB randomi -18, -6, 1 ain pinkish ampdb(kdB) endif ;;MEASURE AMPLITUDE AND TRIGGER SUBINSTRUMENTS IF THRESHOLD IS CROSSED afoll follow ain, .1; measure mean amplitude each 1/10 second kfoll downsamp afoll if kstat == 1 && dbamp(kfoll) > ithresh then; transition down->up event "i", 2, 0, 1; call instr 2 printks "Amplitude = %.3f dB%n", 0, dbamp(kfoll) kstat = 2; change status to "up" elseif kstat == 2 && dbamp(kfoll) < ithresh then; transition up->down event "i", 3, 0, 1; call instr 3 printks "Amplitude = %.3f dB%n", 0, dbamp(kfoll) kstat = 1; change status to "down" endif endin instr 2; triggered if threshold has been crossed from down to up asig oscils .2, 500, 0 aenv transeg 1, p3, -10, 0 outs asig*aenv, asig*aenv endin instr 3; triggered if threshold has been crossed from up to down asig oscils .2, 400, 0 aenv transeg 1, p3, -10, 0 outs asig*aenv, asig*aenv endin </CsInstruments> <CsScore> i 1 0 1000 2 ;change p4 to "1" for live input e </CsScore> </CsoundSynthesizer>
You can perform a number of calculations at init-time which lead to a list of instrument events. In this way you are producing a score, but inside an instrument. The score events are then executed later.
Using this opportunity we can introduce the scoreline / scoreline_i opcode. It is quite similar to the event / event_i opcode but has two major benefits:
Let's look at a simple example for executing score events from an instrument using the scoreline opcode:
EXAMPLE 03E06.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0; random seed different each time instr 1 ;master instrument with event pool scoreline_i {{i 2 0 2 7.09 i 2 2 2 8.04 i 2 4 2 8.03 i 2 6 1 8.04}} endin instr 2 ;plays the notes asig pluck .2, cpspch(p4), cpspch(p4), 0, 1 aenv transeg 1, p3, 0, 0 outs asig*aenv, asig*aenv endin </CsInstruments> <CsScore> i 1 0 7 e </CsScore> </CsoundSynthesizer>
With good right, you might say: "OK, that's nice, but I can also write scorelines in the score itself!" That's right, but the advantage with the scoreline_i method is that you can render the score events in an instrument, and then send them out to one or more instruments to execute them. This can be done with the sprintf opcode, which produces the string for scoreline in an i-time loop (see the chapter about control structures).
EXAMPLE 03E07.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giPch ftgen 0, 0, 4, -2, 7.09, 8.04, 8.03, 8.04 seed 0; random seed different each time instr 1 ; master instrument with event pool itimes = 7 ;number of events to produce icnt = 0 ;counter istart = 0 Slines = "" loop: ;start of the i-time loop idur random 1, 2.9999 ;duration of each note: idur = int(idur) ;either 1 or 2 itabndx random 0, 3.9999 ;index for the giPch table: itabndx = int(itabndx) ;0-3 ipch table itabndx, giPch ;random pitch value from the table Sline sprintf "i 2 %d %d %.2f\n", istart, idur, ipch ;new scoreline Slines strcat Slines, Sline ;append to previous scorelines istart = istart + idur ;recalculate start for next scoreline loop_lt icnt, 1, itimes, loop ;end of the i-time loop puts Slines, 1 ;print the scorelines scoreline_i Slines ;execute them iend = istart + idur ;calculate the total duration p3 = iend ;set p3 to the sum of all durations print p3 ;print it endin instr 2 ;plays the notes asig pluck .2, cpspch(p4), cpspch(p4), 0, 1 aenv transeg 1, p3, 0, 0 outs asig*aenv, asig*aenv endin </CsInstruments> <CsScore> i 1 0 1 ;p3 is automatically set to the total duration e </CsScore> </CsoundSynthesizer>
In this example, seven events have been rendered in an i-time loop in instrument 1. The result is stored in the string variable Slines. This string is given at i-time to scoreline_i, which executes them then one by one according to their starting times (p2), durations (p3) and other parameters.
If you have many scorelines which are added in this way, you may run to Csound's maximal string length. By default, it is 255 characters. It can be extended by adding the option "-+max_str_len=10000" to Csound's maximum string length of 9999 characters. Instead of collecting all score lines in a single string, you can also execute them inside the i-time loop. Also in this way all the single score lines are added to Csound's event pool. The next example shows an alternative version of the previous one by adding the instrument events one by one in the i-time loop, either with event_i (instr 1) or with scoreline_i (instr 2):
EXAMPLE 03E08.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giPch ftgen 0, 0, 4, -2, 7.09, 8.04, 8.03, 8.04 seed 0; random seed different each time instr 1; master instrument with event_i itimes = 7; number of events to produce icnt = 0; counter istart = 0 loop: ;start of the i-time loop idur random 1, 2.9999; duration of each note: idur = int(idur); either 1 or 2 itabndx random 0, 3.9999; index for the giPch table: itabndx = int(itabndx); 0-3 ipch table itabndx, giPch; random pitch value from the table event_i "i", 3, istart, idur, ipch; new instrument event istart = istart + idur; recalculate start for next scoreline loop_lt icnt, 1, itimes, loop; end of the i-time loop iend = istart + idur; calculate the total duration p3 = iend; set p3 to the sum of all durations print p3; print it endin instr 2; master instrument with scoreline_i itimes = 7; number of events to produce icnt = 0; counter istart = 0 loop: ;start of the i-time loop idur random 1, 2.9999; duration of each note: idur = int(idur); either 1 or 2 itabndx random 0, 3.9999; index for the giPch table: itabndx = int(itabndx); 0-3 ipch table itabndx, giPch; random pitch value from the table Sline sprintf "i 3 %d %d %.2f", istart, idur, ipch; new scoreline scoreline_i Sline; execute it puts Sline, 1; print it istart = istart + idur; recalculate start for next scoreline loop_lt icnt, 1, itimes, loop; end of the i-time loop iend = istart + idur; calculate the total duration p3 = iend; set p3 to the sum of all durations print p3; print it endin instr 3; plays the notes asig pluck .2, cpspch(p4), cpspch(p4), 0, 1 aenv transeg 1, p3, 0, 0 outs asig*aenv, asig*aenv endin </CsInstruments> <CsScore> i 1 0 1 i 2 14 1 e </CsScore> </CsoundSynthesizer>
As discussed above in the chapter about control structures, a time loop can be built in Csound either with the timout opcode or with the metro opcode. There were also simple examples for triggering instrument events using both methods. Here, a more complex example is given: A master instrument performs a time loop (choose either instr 1 for the timout method or instr 2 for the metro method) and triggers once in a loop a subinstrument. The subinstrument itself (instr 10) performs an i-time loop and triggers several instances of a sub-subinstrument (instr 100). Each instance performs a partial with an independent envelope for a bell-like additive synthesis.
EXAMPLE 03E09.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;Example by Joachim Heintz sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 giSine ftgen 0, 0, 2^10, 10, 1 seed 0 instr 1; time loop with timout. events are triggered by event_i (i-rate) loop: idurloop random 1, 4; duration of each loop timout 0, idurloop, play reinit loop play: idurins random 1, 5; duration of the triggered instrument event_i "i", 10, 0, idurins; triggers instrument 10 endin instr 2; time loop with metro. events are triggered by event (k-rate) kfreq init 1; give a start value for the trigger frequency kTrig metro kfreq if kTrig == 1 then ;if trigger impulse: kdur random 1, 5; random duration for instr 10 event "i", 10, 0, kdur; call instr 10 kfreq random .25, 1; set new value for trigger frequency endif endin instr 10; triggers 8-13 partials inumparts random 8, 14 inumparts = int(inumparts); 8-13 as integer ibasoct random 5, 10; base pitch in octave values ibasfreq = cpsoct(ibasoct) ipan random .2, .8; random panning between left (0) and right (1) icnt = 0; counter loop: event_i "i", 100, 0, p3, ibasfreq, icnt+1, inumparts, ipan loop_lt icnt, 1, inumparts, loop endin instr 100; plays one partial ibasfreq = p4; base frequency of sound mixture ipartnum = p5; which partial is this (1 - N) inumparts = p6; total number of partials ipan = p7; panning ifreqgen = ibasfreq * ipartnum; general frequency of this partial ifreqdev random -10, 10; frequency deviation between -10% and +10% ifreq = ifreqgen + (ifreqdev*ifreqgen)/100; real frequency regarding deviation ixtratim random 0, p3; calculate additional time for this partial p3 = p3 + ixtratim; new duration of this partial imaxamp = 1/inumparts; maximum amplitude idbdev random -6, 0; random deviation in dB for this partial iamp = imaxamp * ampdb(idbdev-ipartnum); higher partials are softer ipandev random -.1, .1; panning deviation ipan = ipan + ipandev aEnv transeg 0, .005, 0, iamp, p3-.005, -10, 0 aSine poscil aEnv, ifreq, giSine aL, aR pan2 aSine, ipan outs aL, aR prints "ibasfreq = %d, ipartial = %d, ifreq = %d%n", ibasfreq, ipartnum, ifreq endin </CsInstruments> <CsScore> i 1 0 300 ;try this, or the next line (or both) ;i 2 0 300 </CsScore> </CsoundSynthesizer>
A great collection of interactive examples with FLTK widgets by Iain McCurdy can be found here. See particularily the "Realtime Score Generation" section.
An extended example for calculating score events at i-time can be found in the Re-Generation of Stockhausen's "Studie II" by Joachim Heintz (also included in the QuteCsound Examples menu).
event_i / event: Generate an instrument event at i-time (event_i) or at k-time (event). Easy to use, but you cannot send a string to the subinstrument.
scoreline_i / scoreline: Generate an instrument at i-time (scoreline_i) or at k-time (scoreline). Like event_i/event, but you can send to more than one instrument but unlike event_i/event you can send strings. On the other hand, you must usually preformat your scoreline-string using sprintf.
sprintf / sprintfk: Generate a formatted string at i-time (sprintf) or k-time (sprintfk), and store it as a string-variable.
-+max_str_len=10000: Option in the "CsOptions" tag of a .csd file which extend the maximum string length to 9999 characters.
massign: Assigns the incoming MIDI events to a particular instrument. It is also possible to prevent any assigment by this opcode.
cpsmidi / ampmidi: Returns the frequency / velocity of a pressed MIDI key.
release: Returns "1" if the last k-cycle of an instrument has begun.
xtratim: Adds an additional time to the duration (p3) of an instrument.
turnoff / turnoff2: Turns an instrument off; either by the instrument itself (turnoff), or from another instrument and with several options (turnoff2).
-p3 / -p1: A negative duration (p3) turns an instrument on "indefinitely"; a negative instrument number (p1) turns this instrument off. See the examples at the beginning of this chapter.
-L stdin: Option in the "CsOptions" tag of a .csd file which lets you type in realtime score events.
timout: Allows you to perform time loops at i-time with reinitalization passes.
metro: Outputs momentary 1s with a definable (and variable) frequency. Can be used to perform a time loop at k-rate.
follow: Envelope follower.