This chapter is in three parts. Part I provides a general introduction to the concepts behind random numbers and how to work with them in Csound. Part II focusses on a more mathematical approach. Part III introduces a number of opcodes for generating random numbers, functions and distributions and demonstrates their use in musical examples.
The term random derives from the idea of a horse that is running so fast it becomes 'out of control' or 'beyond predictability'.1 Yet there are different ways in which to run fast and to be out of control; therefore there are different types of randomness.
We can divide types of randomness into two classes. The first contains random events that are independent of previous events. The most common example for this is throwing a die. Even if you have just thrown three '1's in a row, when thrown again, a '1' has the same probability as before (and as any other number). The second class of random number involves random events which depend in some way upon previous numbers or states. Examples here are Markov chains and random walks.
The use of randomness in electronic music is widespread. In this chapter, we shall try to explain how the different random horses are moving, and how you can create and modify them on your own. Moreover, there are many pre-built random opcodes in Csound which can be used out of the box (see the overview in the Csound Manual). The final section of this chapter introduces some musically interesting applications of them.
A computer is typically only capable of computation. Computations are deterministic processes: one input will always generate the same output, but a random event is not predictable. To generate something which looks like a random event, the computer uses a pseudo-random generator.
The pseudo-random generator takes one number as input, and generates another number as output. This output is then the input for the next generation. For a huge amount of numbers, they look as if they are randomly distributed, although everything depends on the first input: the seed. For one given seed, the next values can be predicted.
The output of a classical pseudo-random generator is uniformly distributed: each value in a given range has the same likelihood of occurence. The first example shows the influence of a fixed seed (using the same chain of numbers and beginning from the same location in the chain each time) in contrast to a seed being taken from the system clock (the usual way of imitating unpredictability). The first three groups of four notes will always be the same because of the use of the same seed whereas the last three groups should always have a different pitch.
EXAMPLE 01D01_different_seed.csd
<CsoundSynthesizer> <CsOptions> -d -odac -m0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 instr generate ;get seed: 0 = seeding from system clock ; otherwise = fixed seed seed p4 ;generate four notes to be played from subinstrument iNoteCount = 0 until iNoteCount == 4 do iFreq random 400, 800 event_i "i", "play", iNoteCount, 2, iFreq iNoteCount += 1 ;increase note count enduntil endin instr play iFreq = p4 print iFreq aImp mpulse .5, p3 aMode mode aImp, iFreq, 1000 aEnv linen aMode, 0.01, p3, p3-0.01 outs aEnv, aEnv endin </CsInstruments> <CsScore> ;repeat three times with fixed seed r 3 i "generate" 0 2 1 ;repeat three times with seed from the system clock r 3 i "generate" 0 1 0 </CsScore> </CsoundSynthesizer> ;example by joachim heintz
Note that a pseudo-random generator will repeat its series of numbers after as many steps as are given by the size of the generator. If a 16-bit number is generated, the series will be repeated after 65536 steps. If you listen carefully to the following example, you will hear a repetition in the structure of the white noise (which is the result of uniformly distributed amplitudes) after about 1.5 seconds in the first note.2 In the second note, there is no perceivable repetition as the random generator now works with a 31-bit number.
EXAMPLE 01D02_white_noises.csd
<CsoundSynthesizer> <CsOptions> -d -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 instr white_noise iBit = p4 ;0 = 16 bit, 1 = 31 bit ;input of rand: amplitude, fixed seed (0.5), bit size aNoise rand .1, 0.5, iBit outs aNoise, aNoise endin </CsInstruments> <CsScore> i "white_noise" 0 10 0 i "white_noise" 11 10 1 </CsScore> </CsoundSynthesizer> ;example by joachim heintz
Two more general notes about this:
<CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed = 0 ;seeding from current time
...
1) ires random imin, imax 2) kres random kmin, kmax 3) ares random kmin, kmaxIn the first case, a random value is generated only once, when an instrument is called, at initialisation. The generated value is then stored in the variable ires. In the second case, a random value is generated at each k-cycle, and stored in kres. In the third case, in each k-cycle as many random values are stored as the audio vector has in size, and stored in the variable ares. Have a look at example 03A12_Random_at_ika.csd to see this at work. Chapter 03A tries to explain the background of the different rates in depth, and how to work with them.
The uniform distribution is the one each computer can output via its pseudo-random generator. But there are many situations you will not want a uniformly distributed random, but any other shape. Some of these shapes are quite common, but you can actually build your own shapes quite easily in Csound. The next examples demonstrate how to do this. They are based on the chapter in Dodge/Jerse3 which also served as a model for many random number generator opcodes in Csound.4
A linear distribution means that either lower or higher values in a given range are more likely:
To get this behaviour, two uniform random numbers are generated, and the lower is taken for the first shape. If the second shape with the precedence of higher values is needed, the higher one of the two generated numbers is taken. The next example implements these random generators as User Defined Opcodes. First we hear a uniform distribution, then a linear distribution with precedence of lower pitches (but longer durations), at least a linear distribution with precedence of higher pitches (but shorter durations).
EXAMPLE 01D03_linrand.csd
<CsoundSynthesizer> <CsOptions> -d -odac -m0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0 ;****DEFINE OPCODES FOR LINEAR DISTRIBUTION**** opcode linrnd_low, i, ii ;linear random with precedence of lower values iMin, iMax xin ;generate two random values with the random opcode iOne random iMin, iMax iTwo random iMin, iMax ;compare and get the lower one iRnd = iOne < iTwo ? iOne : iTwo xout iRnd endop opcode linrnd_high, i, ii ;linear random with precedence of higher values iMin, iMax xin ;generate two random values with the random opcode iOne random iMin, iMax iTwo random iMin, iMax ;compare and get the higher one iRnd = iOne > iTwo ? iOne : iTwo xout iRnd endop ;****INSTRUMENTS FOR THE DIFFERENT DISTRIBUTIONS**** instr notes_uniform prints "... instr notes_uniform playing:\n" prints "EQUAL LIKELINESS OF ALL PITCHES AND DURATIONS\n" ;how many notes to be played iHowMany = p4 ;trigger as many instances of instr play as needed iThisNote = 0 iStart = 0 until iThisNote == iHowMany do iMidiPch random 36, 84 ;midi note iDur random .5, 1 ;duration event_i "i", "play", iStart, iDur, int(iMidiPch) iStart += iDur ;increase start iThisNote += 1 ;increase counter enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;trigger next instrument two seconds after the last note event_i "i", "notes_linrnd_low", p3, 1, iHowMany endin instr notes_linrnd_low prints "... instr notes_linrnd_low playing:\n" prints "LOWER NOTES AND LONGER DURATIONS PREFERRED\n" iHowMany = p4 iThisNote = 0 iStart = 0 until iThisNote == iHowMany do iMidiPch linrnd_low 36, 84 ;lower pitches preferred iDur linrnd_high .5, 1 ;longer durations preferred event_i "i", "play", iStart, iDur, int(iMidiPch) iStart += iDur iThisNote += 1 enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;trigger next instrument two seconds after the last note event_i "i", "notes_linrnd_high", p3, 1, iHowMany endin instr notes_linrnd_high prints "... instr notes_linrnd_high playing:\n" prints "HIGHER NOTES AND SHORTER DURATIONS PREFERRED\n" iHowMany = p4 iThisNote = 0 iStart = 0 until iThisNote == iHowMany do iMidiPch linrnd_high 36, 84 ;higher pitches preferred iDur linrnd_low .3, 1.2 ;shorter durations preferred event_i "i", "play", iStart, iDur, int(iMidiPch) iStart += iDur iThisNote += 1 enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;call instr to exit csound event_i "i", "exit", p3+1, 1 endin ;****INSTRUMENTS TO PLAY THE SOUNDS AND TO EXIT CSOUND**** instr play ;increase duration in random range iDur random p3, p3*1.5 p3 = iDur ;get midi note and convert to frequency iMidiNote = p4 iFreq cpsmidinn iMidiNote ;generate note with karplus-strong algorithm aPluck pluck .2, iFreq, iFreq, 0, 1 aPluck linen aPluck, 0, p3, p3 ;filter aFilter mode aPluck, iFreq, .1 ;mix aPluck and aFilter according to MidiNote ;(high notes will be filtered more) aMix ntrpol aPluck, aFilter, iMidiNote, 36, 84 ;panning also according to MidiNote ;(low = left, high = right) iPan = (iMidiNote-36) / 48 aL, aR pan2 aMix, iPan outs aL, aR endin instr exit exitnow endin </CsInstruments> <CsScore> i "notes_uniform" 0 1 23 ;set number of notes per instr here ;instruments linrnd_low and linrnd_high are triggered automatically e 99999 ;make possible to perform long (exit will be automatically) </CsScore> </CsoundSynthesizer> ;example by joachim heintz
In a triangular distribution the values in the middle of the given range are more likely than those at the borders. The probability transition between the middle and the extrema are linear:
The algorithm for getting this distribution is very simple as well. Generate two uniform random numbers and take the mean of them. The next example shows the difference between uniform and triangular distribution in the same environment as the previous example.
EXAMPLE 01D04_trirand.csd
<CsoundSynthesizer> <CsOptions> -d -odac -m0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0 ;****UDO FOR TRIANGULAR DISTRIBUTION**** opcode trirnd, i, ii iMin, iMax xin ;generate two random values with the random opcode iOne random iMin, iMax iTwo random iMin, iMax ;get the mean and output iRnd = (iOne+iTwo) / 2 xout iRnd endop ;****INSTRUMENTS FOR UNIFORM AND TRIANGULAR DISTRIBUTION**** instr notes_uniform prints "... instr notes_uniform playing:\n" prints "EQUAL LIKELINESS OF ALL PITCHES AND DURATIONS\n" ;how many notes to be played iHowMany = p4 ;trigger as many instances of instr play as needed iThisNote = 0 iStart = 0 until iThisNote == iHowMany do iMidiPch random 36, 84 ;midi note iDur random .25, 1.75 ;duration event_i "i", "play", iStart, iDur, int(iMidiPch) iStart += iDur ;increase start iThisNote += 1 ;increase counter enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;trigger next instrument two seconds after the last note event_i "i", "notes_trirnd", p3, 1, iHowMany endin instr notes_trirnd prints "... instr notes_trirnd playing:\n" prints "MEDIUM NOTES AND DURATIONS PREFERRED\n" iHowMany = p4 iThisNote = 0 iStart = 0 until iThisNote == iHowMany do iMidiPch trirnd 36, 84 ;medium pitches preferred iDur trirnd .25, 1.75 ;medium durations preferred event_i "i", "play", iStart, iDur, int(iMidiPch) iStart += iDur iThisNote += 1 enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;call instr to exit csound event_i "i", "exit", p3+1, 1 endin ;****INSTRUMENTS TO PLAY THE SOUNDS AND EXIT CSOUND**** instr play ;increase duration in random range iDur random p3, p3*1.5 p3 = iDur ;get midi note and convert to frequency iMidiNote = p4 iFreq cpsmidinn iMidiNote ;generate note with karplus-strong algorithm aPluck pluck .2, iFreq, iFreq, 0, 1 aPluck linen aPluck, 0, p3, p3 ;filter aFilter mode aPluck, iFreq, .1 ;mix aPluck and aFilter according to MidiNote ;(high notes will be filtered more) aMix ntrpol aPluck, aFilter, iMidiNote, 36, 84 ;panning also according to MidiNote ;(low = left, high = right) iPan = (iMidiNote-36) / 48 aL, aR pan2 aMix, iPan outs aL, aR endin instr exit exitnow endin </CsInstruments> <CsScore> i "notes_uniform" 0 1 23 ;set number of notes per instr here ;instr trirnd will be triggered automatically e 99999 ;make possible to perform long (exit will be automatically) </CsScore> </CsoundSynthesizer> ;example by joachim heintz
Having written this with some very simple UDOs, it is easy to emphasise the probability peaks of the distributions by generating more than two random numbers. If you generate three numbers and choose the smallest of them, you will get many more numbers near the minimum in total for the linear distribution. If you generate three random numbers and take the mean of them, you will end up with more numbers near the middle in total for the triangular distribution.
If we want to write UDOs with a flexible number of sub-generated numbers, we have to write the code in a slightly different way. Instead of having one line of code for each random generator, we will use a loop, which calls the generator as many times as we wish to have units. A variable will store the results of the accumulation. Re-writing the above code for the UDO trirnd would lead to this formulation:
opcode trirnd, i, ii iMin, iMax xin ;set a counter and a maximum count iCount = 0 iMaxCount = 2 ;set the accumulator to zero as initial value iAccum = 0 ;perform loop and accumulate until iCount == iMaxCount do iUniRnd random iMin, iMax iAccum += iUniRnd iCount += 1 enduntil ;get the mean and output iRnd = iAccum / 2 xout iRnd endop
To get this completely flexible, you only have to get iMaxCount as input argument. The code for the linear distribution UDOs is quite similar. -- The next example shows these steps:
Rather than using different instruments for the different distributions, the next example combines all possibilities in one single instrument. Inside the loop which generates as many notes as desired by the iHowMany argument, an if-branch calculates the pitch and duration of one note depending on the distribution type and the number of sub-units used. The whole sequence (which type first, which next, etc) is stored in the global array giSequence. Each instance of instrument "notes" increases the pointer giSeqIndx, so that for the next run the next element in the array is being read. If the pointer has reached the end of the array, the instrument which exits Csound is called instead of a new instance of "notes".
EXAMPLE 01D05_more_lin_tri_units.csd
<CsoundSynthesizer> <CsOptions> -d -odac -m0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0 ;****SEQUENCE OF UNITS AS ARRAY****/ giSequence[] array 0, 1.2, 1.4, 2.2, 2.4, 3.2, 3.6 giSeqIndx = 0 ;startindex ;****UDO DEFINITIONS**** opcode linrnd_low, i, iii ;linear random with precedence of lower values iMin, iMax, iMaxCount xin ;set counter and initial (absurd) result iCount = 0 iRnd = iMax ;loop and reset iRnd until iCount == iMaxCount do iUniRnd random iMin, iMax iRnd = iUniRnd < iRnd ? iUniRnd : iRnd iCount += 1 enduntil xout iRnd endop opcode linrnd_high, i, iii ;linear random with precedence of higher values iMin, iMax, iMaxCount xin ;set counter and initial (absurd) result iCount = 0 iRnd = iMin ;loop and reset iRnd until iCount == iMaxCount do iUniRnd random iMin, iMax iRnd = iUniRnd > iRnd ? iUniRnd : iRnd iCount += 1 enduntil xout iRnd endop opcode trirnd, i, iii iMin, iMax, iMaxCount xin ;set a counter and accumulator iCount = 0 iAccum = 0 ;perform loop and accumulate until iCount == iMaxCount do iUniRnd random iMin, iMax iAccum += iUniRnd iCount += 1 enduntil ;get the mean and output iRnd = iAccum / iMaxCount xout iRnd endop ;****ONE INSTRUMENT TO PERFORM ALL DISTRIBUTIONS**** ;0 = uniform, 1 = linrnd_low, 2 = linrnd_high, 3 = trirnd ;the fractional part denotes the number of units, e.g. ;3.4 = triangular distribution with four sub-units instr notes ;how many notes to be played iHowMany = p4 ;by which distribution with how many units iWhich = giSequence[giSeqIndx] iDistrib = int(iWhich) iUnits = round(frac(iWhich) * 10) ;set min and max duration iMinDur = .1 iMaxDur = 2 ;set min and max pitch iMinPch = 36 iMaxPch = 84 ;trigger as many instances of instr play as needed iThisNote = 0 iStart = 0 iPrint = 1 ;for each note to be played until iThisNote == iHowMany do ;calculate iMidiPch and iDur depending on type if iDistrib == 0 then printf_i "%s", iPrint, "... uniform distribution:\n" printf_i "%s", iPrint, "EQUAL LIKELIHOOD OF ALL PITCHES AND DURATIONS\n" iMidiPch random iMinPch, iMaxPch ;midi note iDur random iMinDur, iMaxDur ;duration elseif iDistrib == 1 then printf_i "... linear low distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "LOWER NOTES AND LONGER DURATIONS PREFERRED\n" iMidiPch linrnd_low iMinPch, iMaxPch, iUnits iDur linrnd_high iMinDur, iMaxDur, iUnits elseif iDistrib == 2 then printf_i "... linear high distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "HIGHER NOTES AND SHORTER DURATIONS PREFERRED\n" iMidiPch linrnd_high iMinPch, iMaxPch, iUnits iDur linrnd_low iMinDur, iMaxDur, iUnits else printf_i "... triangular distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "MEDIUM NOTES AND DURATIONS PREFERRED\n" iMidiPch trirnd iMinPch, iMaxPch, iUnits iDur trirnd iMinDur, iMaxDur, iUnits endif ;call subinstrument to play note event_i "i", "play", iStart, iDur, int(iMidiPch) ;increase start tim and counter iStart += iDur iThisNote += 1 ;avoid continuous printing iPrint = 0 enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;increase index for sequence giSeqIndx += 1 ;call instr again if sequence has not been ended if giSeqIndx < lenarray(giSequence) then event_i "i", "notes", p3, 1, iHowMany ;or exit else event_i "i", "exit", p3, 1 endif endin ;****INSTRUMENTS TO PLAY THE SOUNDS AND EXIT CSOUND**** instr play ;increase duration in random range iDur random p3, p3*1.5 p3 = iDur ;get midi note and convert to frequency iMidiNote = p4 iFreq cpsmidinn iMidiNote ;generate note with karplus-strong algorithm aPluck pluck .2, iFreq, iFreq, 0, 1 aPluck linen aPluck, 0, p3, p3 ;filter aFilter mode aPluck, iFreq, .1 ;mix aPluck and aFilter according to MidiNote ;(high notes will be filtered more) aMix ntrpol aPluck, aFilter, iMidiNote, 36, 84 ;panning also according to MidiNote ;(low = left, high = right) iPan = (iMidiNote-36) / 48 aL, aR pan2 aMix, iPan outs aL, aR endin instr exit exitnow endin </CsInstruments> <CsScore> i "notes" 0 1 23 ;set number of notes per instr here e 99999 ;make possible to perform long (exit will be automatically) </CsScore> </CsoundSynthesizer> ;example by joachim heintz
With this method we can build probability distributions which are very similar to exponential or gaussian distributions.5 Their shape can easily be formed by the number of sub-units used.
Random is a complex and sensible context. There are so many ways to let the horse go, run, or dance -- the conditions you set for this 'way of moving' are much more important than the fact that one single move is not predictable. What are the conditions of this randomness?
In the example above we used two implicit scalings. The pitches have been scaled to the keys of a piano or keyboard. Why? We do not play piano here obviously ... -- What other possibilities might have been instead? One would be: no scaling at all. This is the easiest way to go -- whether it is really the best, or simple laziness, can only be decided by the composer or the listener.
Instead of using the equal tempered chromatic scale, or no scale at all, you can use any other ways of selecting or quantising pitches. Be it any which has been, or is still, used in any part of the world, or be it your own invention, by whatever fantasy or invention or system.
As regards the durations, the example above has shown no scaling at all. This was definitely laziness...
The next example is essentially the same as the previous one, but it uses a pitch scale which represents the overtone scale, starting at the second partial extending upwards to the 32nd partial. This scale is written into an array by a statement in instrument 0. The durations have fixed possible values which are written into an array (from the longest to the shortest) by hand. The values in both arrays are then called according to their position in the array.
EXAMPLE 01D06_scalings.csd
<CsoundSynthesizer> <CsOptions> -d -odac -m0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0 ;****POSSIBLE DURATIONS AS ARRAY**** giDurs[] array 3/2, 1, 2/3, 1/2, 1/3, 1/4 giLenDurs lenarray giDurs ;****POSSIBLE PITCHES AS ARRAY**** ;initialize array with 31 steps giScale[] init 31 giLenScale lenarray giScale ;iterate to fill from 65 hz onwards iStart = 65 iDenom = 3 ;start with 3/2 iCnt = 0 until iCnt = giLenScale do giScale[iCnt] = iStart iStart = iStart * iDenom / (iDenom-1) iDenom += 1 ;next proportion is 4/3 etc iCnt += 1 enduntil ;****SEQUENCE OF UNITS AS ARRAY**** giSequence[] array 0, 1.2, 1.4, 2.2, 2.4, 3.2, 3.6 giSeqIndx = 0 ;startindex ;****UDO DEFINITIONS**** opcode linrnd_low, i, iii ;linear random with precedence of lower values iMin, iMax, iMaxCount xin ;set counter and initial (absurd) result iCount = 0 iRnd = iMax ;loop and reset iRnd until iCount == iMaxCount do iUniRnd random iMin, iMax iRnd = iUniRnd < iRnd ? iUniRnd : iRnd iCount += 1 enduntil xout iRnd endop opcode linrnd_high, i, iii ;linear random with precedence of higher values iMin, iMax, iMaxCount xin ;set counter and initial (absurd) result iCount = 0 iRnd = iMin ;loop and reset iRnd until iCount == iMaxCount do iUniRnd random iMin, iMax iRnd = iUniRnd > iRnd ? iUniRnd : iRnd iCount += 1 enduntil xout iRnd endop opcode trirnd, i, iii iMin, iMax, iMaxCount xin ;set a counter and accumulator iCount = 0 iAccum = 0 ;perform loop and accumulate until iCount == iMaxCount do iUniRnd random iMin, iMax iAccum += iUniRnd iCount += 1 enduntil ;get the mean and output iRnd = iAccum / iMaxCount xout iRnd endop ;****ONE INSTRUMENT TO PERFORM ALL DISTRIBUTIONS**** ;0 = uniform, 1 = linrnd_low, 2 = linrnd_high, 3 = trirnd ;the fractional part denotes the number of units, e.g. ;3.4 = triangular distribution with four sub-units instr notes ;how many notes to be played iHowMany = p4 ;by which distribution with how many units iWhich = giSequence[giSeqIndx] iDistrib = int(iWhich) iUnits = round(frac(iWhich) * 10) ;trigger as many instances of instr play as needed iThisNote = 0 iStart = 0 iPrint = 1 ;for each note to be played until iThisNote == iHowMany do ;calculate iMidiPch and iDur depending on type if iDistrib == 0 then printf_i "%s", iPrint, "... uniform distribution:\n" printf_i "%s", iPrint, "EQUAL LIKELINESS OF ALL PITCHES AND DURATIONS\n" iScaleIndx random 0, giLenScale-.0001 ;midi note iDurIndx random 0, giLenDurs-.0001 ;duration elseif iDistrib == 1 then printf_i "... linear low distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "LOWER NOTES AND LONGER DURATIONS PREFERRED\n" iScaleIndx linrnd_low 0, giLenScale-.0001, iUnits iDurIndx linrnd_low 0, giLenDurs-.0001, iUnits elseif iDistrib == 2 then printf_i "... linear high distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "HIGHER NOTES AND SHORTER DURATIONS PREFERRED\n" iScaleIndx linrnd_high 0, giLenScale-.0001, iUnits iDurIndx linrnd_high 0, giLenDurs-.0001, iUnits else printf_i "... triangular distribution with %d units:\n", iPrint, iUnits printf_i "%s", iPrint, "MEDIUM NOTES AND DURATIONS PREFERRED\n" iScaleIndx trirnd 0, giLenScale-.0001, iUnits iDurIndx trirnd 0, giLenDurs-.0001, iUnits endif ;call subinstrument to play note iDur = giDurs[int(iDurIndx)] iPch = giScale[int(iScaleIndx)] event_i "i", "play", iStart, iDur, iPch ;increase start time and counter iStart += iDur iThisNote += 1 ;avoid continuous printing iPrint = 0 enduntil ;reset the duration of this instr to make all events happen p3 = iStart + 2 ;increase index for sequence giSeqIndx += 1 ;call instr again if sequence has not been ended if giSeqIndx < lenarray(giSequence) then event_i "i", "notes", p3, 1, iHowMany ;or exit else event_i "i", "exit", p3, 1 endif endin ;****INSTRUMENTS TO PLAY THE SOUNDS AND EXIT CSOUND**** instr play ;increase duration in random range iDur random p3*2, p3*5 p3 = iDur ;get frequency iFreq = p4 ;generate note with karplus-strong algorithm aPluck pluck .2, iFreq, iFreq, 0, 1 aPluck linen aPluck, 0, p3, p3 ;filter aFilter mode aPluck, iFreq, .1 ;mix aPluck and aFilter according to freq ;(high notes will be filtered more) aMix ntrpol aPluck, aFilter, iFreq, 65, 65*16 ;panning also according to freq ;(low = left, high = right) iPan = (iFreq-65) / (65*16) aL, aR pan2 aMix, iPan outs aL, aR endin instr exit exitnow endin </CsInstruments> <CsScore> i "notes" 0 1 23 ;set number of notes per instr here e 99999 ;make possible to perform long (exit will be automatically) </CsScore> </CsoundSynthesizer> ;example by joachim heintz
There are many ways a current value in a random number progression can influence the next. Two of them are used frequently. A Markov chain is based on a number of possible states, and defines a different probability for each of these states. A random walk looks at the last state as a position in a range or field, and allows only certain deviations from this position.
A typical case for a Markov chain in music is a sequence of certain pitches or notes. For each note, the probability of the following note is written in a table like this:
This means: the probability that element a is repeated, is 0.2; the probability that b follows a is 0.5; the probability that c follows a is 0.3. The sum of all probabilities must, by convention, add up to 1. The following example shows the basic algorithm which evaluates the first line of the Markov table above, in the case, the previous element has been 'a'.
EXAMPLE 01D07_markov_basics.csd
<CsoundSynthesizer> <CsOptions> -ndm0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 0dbfs = 1 nchnls = 1 seed 0 instr 1 iLine[] array .2, .5, .3 iVal random 0, 1 iAccum = iLine[0] iIndex = 0 until iAccum >= iVal do iIndex += 1 iAccum += iLine[iIndex] enduntil printf_i "Random number = %.3f, next element = %c!\n", 1, iVal, iIndex+97 endin </CsInstruments> <CsScore> r 10 i 1 0 0 </CsScore> </CsoundSynthesizer> ;example by joachim heintz
The probabilities are 0.2 0.5 0.3. First a uniformly distributed random number between 0 and 1 is generated. An acculumator is set to the first element of the line (here 0.2). It is interrogated as to whether it is larger than the random number. If so then the index is returned, if not, the second element is added (0.2+0.5=0.7), and the process is repeated, until the accumulator is greater or equal the random value. The output of the example should show something like this:
Random number = 0.850, next element = c!
Random number = 0.010, next element = a!
Random number = 0.805, next element = c!
Random number = 0.696, next element = b!
Random number = 0.626, next element = b!
Random number = 0.476, next element = b!
Random number = 0.420, next element = b!
Random number = 0.627, next element = b!
Random number = 0.065, next element = a!
Random number = 0.782, next element = c!
The next example puts this algorithm in an User Defined Opcode. Its input is a Markov table as a two-dimensional array, and the previous line as index (starting with 0). Its output is the next element, also as index. -- There are two Markov chains in this example: seven pitches, and three durations. Both are defined in two-dimensional arrays: giProbNotes and giProbDurs. Both Markov chains are running independently from each other.
EXAMPLE 01D08_markov_music.csd
<CsoundSynthesizer> <CsOptions> -dnm128 -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 0dbfs = 1 nchnls = 2 seed 0 ;****USER DEFINED OPCODES FOR MARKOV CHAINS**** opcode Markov, i, i[][]i iMarkovTable[][], iPrevEl xin iRandom random 0, 1 iNextEl = 0 iAccum = iMarkovTable[iPrevEl][iNextEl] until iAccum >= iRandom do iNextEl += 1 iAccum += iMarkovTable[iPrevEl][iNextEl] enduntil xout iNextEl endop opcode Markovk, k, k[][]k kMarkovTable[][], kPrevEl xin kRandom random 0, 1 kNextEl = 0 kAccum = kMarkovTable[kPrevEl][kNextEl] until kAccum >= kRandom do kNextEl += 1 kAccum += kMarkovTable[kPrevEl][kNextEl] enduntil xout kNextEl endop ;****DEFINITIONS FOR NOTES**** ;notes as proportions and a base frequency giNotes[] array 1, 9/8, 6/5, 5/4, 4/3, 3/2, 5/3 giBasFreq = 330 ;probability of notes as markov matrix: ;first -> only to third and fourth ;second -> anywhere without self ;third -> strong probability for repetitions ;fourth -> idem ;fifth -> anywhere without third and fourth ;sixth -> mostly to seventh ;seventh -> mostly to sixth giProbNotes[][] init 7, 7 giProbNotes array 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.2, 0.0, 0.2, 0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.5, 0.1, 0.1, 0.1, 0.0, 0.0, 0.1, 0.1, 0.5, 0.1, 0.1, 0.1, 0.2, 0.2, 0.0, 0.0, 0.2, 0.2, 0.2, 0.1, 0.1, 0.0, 0.0, 0.1, 0.1, 0.6, 0.1, 0.1, 0.0, 0.0, 0.1, 0.6, 0.1 ;****DEFINITIONS FOR DURATIONS**** ;possible durations gkDurs[] array 1, 1/2, 1/3 ;probability of durations as markov matrix: ;first -> anything ;second -> mostly self ;third -> mostly second gkProbDurs[][] init 3, 3 gkProbDurs array 1/3, 1/3, 1/3, 0.2, 0.6, 0.3, 0.1, 0.5, 0.4 ;****SET FIRST NOTE AND DURATION FOR MARKOV PROCESS**** giPrevNote init 1 gkPrevDur init 1 ;****INSTRUMENT FOR DURATIONS**** instr trigger_note kTrig metro 1/gkDurs[gkPrevDur] if kTrig == 1 then event "i", "select_note", 0, 1 gkPrevDur Markovk gkProbDurs, gkPrevDur endif endin ;****INSTRUMENT FOR PITCHES**** instr select_note ;choose next note according to markov matrix and previous note ;and write it to the global variable for (next) previous note giPrevNote Markov giProbNotes, giPrevNote ;call instr to play this note event_i "i", "play_note", 0, 2, giPrevNote ;turn off this instrument turnoff endin ;****INSTRUMENT TO PERFORM ONE NOTE**** instr play_note ;get note as index in ginotes array and calculate frequency iNote = p4 iFreq = giBasFreq * giNotes[iNote] ;random choice for mode filter quality and panning iQ random 10, 200 iPan random 0.1, .9 ;generate tone and put out aImp mpulse 1, p3 aOut mode aImp, iFreq, iQ aL, aR pan2 aOut, iPan outs aL, aR endin </CsInstruments> <CsScore> i "trigger_note" 0 100 </CsScore> </CsoundSynthesizer> ;example by joachim heintz
In the context of movement between random values, 'walk' can be thought of as the opposite of 'jump'. If you jump within the boundaries A and B, you can end up anywhere between these boundaries, but if you walk between A and B you will be limited by the extent of your step - each step applies a deviation to the previous one. If the deviation range is slightly more positive (say from -0.1 to +0.2), the general trajectory of your walk will be in the positive direction (but individual steps will not necessarily be in the positive direction). If the deviation range is weighted negative (say from -0.2 to 0.1), then the walk will express a generally negative trajectory.
One way of implementing a random walk will be to take the current state, derive a random deviation, and derive the next state by adding this deviation to the current state. The next example shows two ways of doing this.
The pitch random walk starts at pitch 8 in octave notation. The general pitch deviation gkPitchDev is set to 0.2, so that the next pitch could be between 7.8 and 8.2. But there is also a pitch direction gkPitchDir which is set to 0.1 as initial value. This means that the upper limit of the next random pitch is 8.3 instead of 8.2, so that the pitch will move upwards in a greater number of steps. When the upper limit giHighestPitch has been crossed, the gkPitchDir variable changes from +0.1 to -0.1, so after a number of steps, the pitch will have become lower. Whenever such a direction change happens, the console reports this with a message printed to the terminal.
The density of the notes is defined as notes per second, and is applied as frequency to the metro opcode in instrument 'walk'. The lowest possible density giLowestDens is set to 1, the highest to 8 notes per second, and the first density giStartDens is set to 3. The possible random deviation for the next density is defined in a range from zero to one: zero means no deviation at all, one means that the next density can alter the current density in a range from half the current value to twice the current value. For instance, if the current density is 4, for gkDensDev=1 you would get a density between 2 and 8. The direction of the densities gkDensDir in this random walk follows the same range 0..1. Assumed you have no deviation of densities at all (gkDensDev=0), gkDensDir=0 will produce ticks in always the same speed, whilst gkDensDir=1 will produce a very rapid increase in speed. Similar to the pitch walk, the direction parameter changes from plus to minus if the upper border has crossed, and vice versa.
EXAMPLE 01D09_random_walk.csd
<CsoundSynthesizer> <CsOptions> -dnm128 -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 0dbfs = 1 nchnls = 2 seed 1 ;change to zero for always changing results ;****SETTINGS FOR PITCHES**** ;define the pitch street in octave notation giLowestPitch = 7 giHighestPitch = 9 ;set pitch startpoint, deviation range and the first direction giStartPitch = 8 gkPitchDev init 0.2 ;random range for next pitch gkPitchDir init 0.1 ;positive = upwards ;****SETTINGS FOR DENSITY**** ;define the maximum and minimum density (notes per second) giLowestDens = 1 giHighestDens = 8 ;set first density giStartDens = 3 ;set possible deviation in range 0..1 ;0 = no deviation at all ;1 = possible deviation is between half and twice the current density gkDensDev init 0.5 ;set direction in the same range 0..1 ;(positive = more dense, shorter notes) gkDensDir init 0.1 ;****INSTRUMENT FOR RANDOM WALK**** instr walk ;set initial values kPitch init giStartPitch kDens init giStartDens ;trigger impulses according to density kTrig metro kDens ;if the metro ticks if kTrig == 1 then ;1) play current note event "i", "play", 0, 1.5/kDens, kPitch ;2) calculate next pitch ;define boundaries according to direction kLowPchBound = gkPitchDir < 0 ? -gkPitchDev+gkPitchDir : -gkPitchDev kHighPchBound = gkPitchDir > 0 ? gkPitchDev+gkPitchDir : gkPitchDev ;get random value in these boundaries kPchRnd random kLowPchBound, kHighPchBound ;add to current pitch kPitch += kPchRnd ;change direction if maxima are crossed, and report if kPitch > giHighestPitch && gkPitchDir > 0 then gkPitchDir = -gkPitchDir printks " Pitch touched maximum - now moving down.\n", 0 elseif kPitch < giLowestPitch && gkPitchDir < 0 then gkPitchDir = -gkPitchDir printks "Pitch touched minimum - now moving up.\n", 0 endif ;3) calculate next density (= metro frequency) ;define boundaries according to direction kLowDensBound = gkDensDir < 0 ? -gkDensDev+gkDensDir : -gkDensDev kHighDensBound = gkDensDir > 0 ? gkDensDev+gkDensDir : gkDensDev ;get random value in these boundaries kDensRnd random kLowDensBound, kHighDensBound ;get multiplier (so that kDensRnd=1 yields to 2, and kDens=-1 to 1/2) kDensMult = 2 ^ kDensRnd ;multiply with current duration kDens *= kDensMult ;avoid too high values and too low values kDens = kDens > giHighestDens*1.5 ? giHighestDens*1.5 : kDens kDens = kDens < giLowestDens/1.5 ? giLowestDens/1.5 : kDens ;change direction if maxima are crossed if (kDens > giHighestDens && gkDensDir > 0) || (kDens < giLowestDens && gkDensDir < 0) then gkDensDir = -gkDensDir if kDens > giHighestDens then printks " Density touched upper border - now becoming less dense.\n", 0 else printks " Density touched lower border - now becoming more dense.\n", 0 endif endif endif endin ;****INSTRUMENT TO PLAY ONE NOTE**** instr play ;get note as octave and calculate frequency and panning iOct = p4 iFreq = cpsoct(iOct) iPan ntrpol 0, 1, iOct, giLowestPitch, giHighestPitch ;calculate mode filter quality according to duration iQ ntrpol 10, 400, p3, .15, 1.5 ;generate tone and throw out aImp mpulse 1, p3 aMode mode aImp, iFreq, iQ aOut linen aMode, 0, p3, p3/4 aL, aR pan2 aOut, iPan outs aL, aR endin </CsInstruments> <CsScore> i "walk" 0 999 </CsScore> </CsoundSynthesizer> ;example by joachim heintz
The relative frequency of occurrence of a random variable can be described by a probability function (for discrete random variables) or by density functions (for continuous random variables).
When two dice are thrown simultaneously, the sum x of their numbers can be 2, 3, ...12. The following figure shows the probability function p(x) of these possible outcomes. p(x) is always less than or equal to 1. The sum of the probabilities of all possible outcomes is 1.
For continuous random variables the probability of getting a specific value x is 0. But the probability of getting a value within a certain interval can be indicated by an area that corresponds to this probability. The function f(x) over these areas is called the density function. With the following density the chance of getting a number smaller than 0 is 0, to get a number between 0 and 0.5 is 0.5, to get a number between 0.5 and 1 is 0.5 etc. Density functions f(x) can reach values greater than 1 but the area under the function is 1.
Csound provides opcodes for some specific densities but no means to produce random number with user defined probability or density functions. The opcodes rand_density and rand_probability (see below) generate random numbers with probabilities or densities given by tables. They are realized by using the so-called rejection sampling method.
The principle of rejection sampling is to first generate uniformly distributed random numbers in the range required and to then accept these values corresponding to a given density function (or otherwise reject them). Let us demonstrate this method using the density function shown in the next figure. (Since the rejection sampling method uses only the "shape" of the function, the area under the function need not be 1). We first generate uniformly distributed random numbers rnd1 over the interval [0, 1]. Of these we accept a proportion corresponding to f(rnd1). For example, the value 0.32 will only be accepted in the proportion of f(0.32) = 0.82. We do this by generating a new random number rand2 between 0 and 1 and accept rnd1 only if rand2 < f(rnd1); otherwise we reject it. (see Signals, Systems and Sound Synthesis chapter 10.1.4.4)
EXAMPLE 01D10_Rejection_Sampling.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> ;example by martin neukom sr = 44100 ksmps = 10 nchnls = 1 0dbfs = 1 ; random number generator to a given density function ; kout random number; k_minimum,k_maximum,i_fn for a density function opcode rand_density, k, kki kmin,kmax,ifn xin loop: krnd1 random 0,1 krnd2 random 0,1 k2 table krnd1,ifn,1 if krnd2 > k2 kgoto loop xout kmin+krnd1*(kmax-kmin) endop ; random number generator to a given probability function ; kout random number ; in: i_nr number of possible values ; i_fn1 function for random values ; i_fn2 probability functionExponential: Generate a uniformly distributed number between 0 and 1 and take its natural logarithm. opcode rand_probability, k, iii inr,ifn1,ifn2 xin loop: krnd1 random 0,inr krnd2 random 0,1 k2 table int(krnd1),ifn2,0 if krnd2 > k2 kgoto loop kout table krnd1,ifn1,0 xout kout endop instr 1 krnd rand_density 400,800,2 aout poscil .1,krnd,1 out aout endin instr 2 krnd rand_probability p4,p5,p6 aout poscil .1,krnd,1 out aout endin </CsInstruments> <CsScore> ;sine f1 0 32768 10 1 ;density function f2 0 1024 6 1 112 0 800 0 112 1 ;random values and their relative probability (two dice) f3 0 16 -2 2 3 4 5 6 7 8 9 10 11 12 f4 0 16 2 1 2 3 4 5 6 5 4 3 2 1 ;random values and their relative probability f5 0 8 -2 400 500 600 800 f6 0 8 2 .3 .8 .3 .1 i1 0 10 i2 0 10 4 5 6 </CsScore> </CsoundSynthesizer>
In a series of random numbers the single numbers are independent upon each other. Parameter (left figure) or paths in the room (two-dimensional trajectory in the right figure) created by random numbers wildly jump around.
Example 1
Table[RandomReal[{-1, 1}], {100}];
We get a smoother path, a so-called random walk, by adding at every time step a random number r to the actual position x (x += r).
Example 2
x = 0; walk = Table[x += RandomReal[{-.2, .2}], {300}];
The path becomes even smoother by adding a random number r to the actual velocity v.
v += r
x += v
The path can be bounded to an area (figure to the right) by inverting the velocity if the path exceeds the limits (min, max):
vif(x < min || x > max) v *= -1
The movement can be damped by decreasing the velocity at every time step by a small factor d
v *= (1-d)
Example 3
x = 0; v = 0; walk = Table[x += v += RandomReal[{-.01, .01}], {300}];
The path becomes again smoother by adding a random number r to the actual acelleration a, the change of the aceleration, etc.
a += r
v += a
x += v
Example 4
x = 0; v = 0; a = 0;
Table[x += v += a += RandomReal[{-.0001, .0001}], {300}];
(see Martin Neukom, Signals, Systems and Sound Synthesis chapter 10.2.3.2)
EXAMPLE 01D11_Random_Walk2.csd
<CsoundSynthesizer> <CsInstruments> ;example by martin neukom sr = 44100 ksmps = 128 nchnls = 1 0dbfs = 1 ; random frequency instr 1 kx random -p6, p6 kfreq = p5*2^kx aout oscil p4, kfreq, 1 out aout endin ; random change of frequency instr 2 kx init .5 kfreq = p5*2^kx kv random -p6, p6 kv = kv*(1 - p7) kx = kx + kv aout oscil p4, kfreq, 1 out aout endin ; random change of change of frequency instr 3 kv init 0 kx init .5 kfreq = p5*2^kx ka random -p7, p7 kv = kv + ka kv = kv*(1 - p8) kx = kx + kv kv = (kx < -p6 || kx > p6?-kv : kv) aout oscili p4, kfreq, 1 out aout endin </CsInstruments> <CsScore> f1 0 32768 10 1 ; i1 p4 p5 p6 ; i2 p4 p5 p6 p7 ; amp c_fr rand damp ; i2 0 20 .1 600 0.01 0.001 ; amp c_fr d_fr rand damp ; amp c_fr rand ; i1 0 20 .1 600 0.5 ; i3 p4 p5 p6 p7 p8 i3 0 20 .1 600 1 0.001 0.001 </CsScore> </CsoundSynthesizer>
Csound has a range of opcodes and GEN routine for the creation of various random functions and distributions. Perhaps the simplest of these is random which simply generates a random value within user defined minimum and maximum limit and at i-time, k-rate or a-rate accroding to the variable type of its output:
ires random imin, imax kres random kmin, kmax ares random kmin, kmax
Values are generated according to a uniform random distribution, meaning that any value within the limits has equal chance of occurence. Non-uniform distributions in which certain values have greater chance of occurence over others are often more useful and musical. For these purposes, Csound includes the betarand, bexprand, cauchy, exprand, gauss, linrand, pcauchy, poisson, trirand, unirand and weibull random number generator opcodes. The distributions generated by several of these opcodes are illustrated below.
In addition to these so called 'x-class noise generators' Csound provides random function generators, providing values that change over time a various ways.
randomh generates new random numbers at a user defined rate. The previous value is held until a new value is generated, and then the output immediately assumes that value.
The instruction:
kmin = -1 kmax = 1 kfreq = 2 kout randomh kmin,kmax,kfreq
will produce and output something like:
randomi is an interpolating version of randomh. Rather than jump to new values when they are generated, randomi interpolates linearly to the new value, reaching it just as a new random value is generated. Replacing randomh with randomi in the above code snippet would result in the following output:
In practice randomi's angular changes in direction as new random values are generated might be audible depending on the how it is used. rsplsine allows us to specify not just a single frequency but a minimum and a maximum frequency, and the resulting function is a smooth spline between the minimum and maximum values and these minimum and maximum frequencies. The following input:
kmin = -0.95 kmax = 0.95 kminfrq = 1 kmaxfrq = 4 asig jspline kmin, kmax, kminfrq, kmaxfrq
would generate an output something like:
We need to be careful with what we do with rspline's output as it can exceed the limits set by kmin and kmax. Minimum and maximum values can be set conservatively or the limit opcode could be used to prevent out of range values that could cause problems.
The following example uses rspline to 'humanise' a simple synthesiser. A short melody is played, first without any humanising and then with humanising. rspline random variation is added to the amplitude and pitch of each note in addition to an i-time random offset.
EXAMPLE 01D12_humanising.csd
<CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 seed 0 giWave ftgen 0, 0, 2^10, 10, 1,0,1/4,0,1/16,0,1/64,0,1/256,0,1/1024 instr 1 ; an instrument with no 'humanising' inote = p4 aEnv linen 0.1,0.01,p3,0.01 aSig poscil aEnv,cpsmidinn(inote),giWave outs aSig,aSig endin instr 2 ; an instrument with 'humanising' inote = p4 ; generate some i-time 'static' random paramters iRndAmp random -3,3 ; amp. will be offset by a random number of decibels iRndNte random -5,5 ; note will be offset by a random number of cents ; generate some k-rate random functions kAmpWob rspline -1,1,1,10 ; amplitude 'wobble' (in decibels) kNteWob rspline -5,5,0.3,10 ; note 'wobble' (in cents) ; calculate final note function (in CPS) kcps = cpsmidinn(inote+(iRndNte*0.01)+(kNteWob*0.01)) ; amplitude envelope (randomisation of attack time) aEnv linen 0.1*ampdb(iRndAmp+kAmpWob),0.01+rnd(0.03),p3,0.01 aSig poscil aEnv,kcps,giWave outs aSig,aSig endin </CsInstruments> <CsScore> t 0 80 #define SCORE(i) # i $i 0 1 60 i . + 2.5 69 i . + 0.5 67 i . + 0.5 65 i . + 0.5 64 i . + 3 62 i . + 1 62 i . + 2.5 70 i . + 0.5 69 i . + 0.5 67 i . + 0.5 65 i . + 3 64 # $SCORE(1) ; play melody without humanising b 17 $SCORE(2) ; play melody with humanising e </CsScore> </CsoundSynthesizer> ;example by Iain McCurdy
The final example implements a simple algorithmic note generator. It makes use of GEN17 to generate histograms which define the probabilities of certain notes and certain rhythmic gaps occuring.
EXAMPLE 01D13_simple_algorithmic_note_generator.csd
<CsoundSynthesizer> <CsOptions> -odac -dm0 </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 1 0dbfs = 1 giNotes ftgen 0,0,-100,-17,0,48, 15,53, 30,55, 40,60, 50,63, 60,65, 79,67, 85,70, 90,72, 96,75 giDurs ftgen 0,0,-100,-17,0,2, 30,0.5, 75,1, 90,1.5 instr 1 kDur init 0.5 ; initial rhythmic duration kTrig metro 2/kDur ; metronome freq. 2 times inverse of duration kNdx trandom kTrig,0,1 ; create a random index upon each metro 'click' kDur table kNdx,giDurs,1 ; read a note duration value schedkwhen kTrig,0,0,2,0,1 ; trigger a note! endin instr 2 iNote table rnd(1),giNotes,1 ; read a random value from the function table aEnv linsegr 0, 0.005, 1, p3-0.105, 1, 0.1, 0 ; amplitude envelope iPlk random 0.1, 0.3 ; point at which to pluck the string iDtn random -0.05, 0.05 ; random detune aSig wgpluck2 0.98, 0.2, cpsmidinn(iNote+iDtn), iPlk, 0.06 out aSig * aEnv endin </CsInstruments> <CsScore> i 1 0 300 ; start 3 long notes close after one another i 1 0.01 300 i 1 0.02 300 e </CsScore> </CsoundSynthesizer> ;example by Iain McCurdy
There has been error in communication with Booktype server. Not sure right now where is the problem.
You should refresh this page.