-- Back --

Working with Csound scores

The "score" fields of the event scene
Manipulating p-fields within a score
Score length
High-level functions
Alternate score format
Converting pitch values
The EditScore and EditEvent plug-ins
The CScore array
Playing with both MIDI and CSound
Extending the function library
Seamless score processing through Emacs

This section is not intended to be an introduction to Csound; it will only make sense to people already familiar with this program. See the Csound Front Page for more.

You will find in tutorial 6 a detailed example of basic Csound composition with GeoMaestro.

for some functions to work properly, the variable CSOUNDBINARY
must locate your Csound executable.
By default its value is "csound".

The "score" fields of the event scene

We can generate Csound scores very much like we generate MIDI files: events may contain a "score" field analogous to the "nodur" one. After projection, the resulting ligne will also have a "score" field.

A score field is actually an array of strings, each string being a valid line for a score (it can be a comment, or an empty line); the element 0 of the array must contain the number of lines. The strings should not contain double quotes.

For example, we could have:
Ev[4][17]["score"] = [ 0= 2, 1= "i2 0 3 6.08 6.08", 2 =  "i2 0.5 2.5 7.00 6.08"]
... this is a two-line score (refering to instrument 2 of the orchestra) associated to event 17 on channel 4

Instead of writing explicitly a score array, you can simply provide the lines to the Score() function.
So this is equivalent to the previous statement:
Ev[4][17]["score"] = Score("i2 0 3 6.08 6.08", "i2 0.5 2.5 7.00 6.08")

Now, a Csound score usually starts with a few lines defining functions and tables: let's call this "header". The header can be stored in Ev["score"]; a bit like the "PAC" fields for MIDI, this array will be written at the begining of the score, before anything else.

Ev["score"] = [0=1, 1="f37 0 8192 1 \"mandpluk.wav\" 0 0 0"]
... as you can see in this example, we can have double quotes in a string here (this is the only score array in GeoMaestro where double quotes are allowed, because they can be necessary, in this case to include a sample file in the score)

Here are a few basic I/O function for scores:
PrintScore(V)		# display V["score"] at the console

ExScore(V, fname)	# write V["score"] in the file fname,
			# after an optional post processing (see here for details)
 			# and with the header Ev["score"] added

ShowScore(V)		# calls ExScore(V, CS_SnarfFile)
			# and display CS_SnarfFile in a text editor

ReadScore(fname)	# return the score from file fname

WriteScore(V, fname)	# directly write V["score"] in the file fname, 
			# without any modifications (unlike ExScore())

... here V can be an event, a ligne or the Ev array (although for ExScore() it's supposed to be an event or a ligne, since the Ev["score"] will be written at the beginning of the file). Note that it can also be a plain score...

The global variable CS_SnarfFile is defined in intialisations.k: it should be the full name for a temporary score file.

When you [snarf] a ligne from the GUI, its score wil be written in CS_SnarfFile. You can have the text editor open at this point if you set the parameter ShowScoreWhenSnarfed to 1 (this is the default value)

With the [operation], [active] and [inactive] buttons of the GUI, you can use the O_ keyword to refer to the "score" field of an event, in the same way as you use N_ for the nodur.

For example, [inactive] with the test:
O_[0] == 0 
...will only leave active the events having a score.

Or else, [operation] and:
O_[1] = SCpn(O_[1], 4, 1000*X_)
... will set p4 to 1000 time the event x-position in the first line of score for each selected event (see next chapter for SCpn() and other functions)

Manipulating p-fields within a score

Direct access to the p-fields inside a score is given by the following low-level functions:
SCpn(sco[n], pn, valpn)
SCgetpn(sco[n], pn) 

SCpn() gives the value valpn to the parameter pn in line n of the score sco. It returns the new value for line n; valpn can be either a float, an integer or a string (like "+", "." or "np3"). The parameter pn or pn-1 must already be present in the score, otherwise SCpn() will have no effect.

SCgetpn() returns the value of the parameter pn in line n of sco: SCi() returns the instrument number for line n (as does SCgetpn(sco[n], 1) , since p1 is the instrument number); if sco[n] is a comment line, it returns -1

SCnump() returns the number of p-fields in line n (it returns 0 if the line is not an i-statement)

SCgetallp() returns at once all parameters in a single array. While it is faster than SCgetpn(), especially if you want all parameters, it has the following limitations: it only works if all arguments are numbers; it will return as many dummy parameters with 0 value as they are words in a comment, if present.

SCsetallp() does the opposite: given an array of parameters (with numeric values), it returns a score line.

The number of digits for float parameters is set by the global variable CS_DIGITS in lib/initialisations.k; by default it is 2

Others useful functions are:
SCp2Clicks(sco, t)
SCpnAdd(sco[n], pn, add)
SCp2Clicks() has a dumb name, but here is what it does: it adds t (a time value expressed in clicks, which is internally converted in seconds) to the p2 field for each line in the score sco. If p2 was "+", it is untouched (which makes sense)

SCpnAdd() adds add to the parameter pn in line n in sco.

Score length

The length of a score has the same behavior as the .length attribute of a phrase. It is not necessarily equal to to the difference between the end time of the last note and the beginning of the first one...

Here is how it is calculated: The following functions make it easy to handle length:
ScoreLength, ScoreLengthClicks, SetScoreF0, ScoreF0, ScoreF0Clicks

High-level functions

There is a lot of those... check this page for the most current list of autodocumented Csound-related functions
The code for most functions lives either in lib/csound_utils.k or in lib/lib_csound.k

If you have CMask installed, you can call it from KeyKit and directly have the score returned by the function
... where fname is the parameter file name (do not give its full path: it must be located in DATA)

You will need to set CALLCMASK in lib/cmask_utils.k for this to work. Read the comments in InitCMaskUtils().

The CScore array

The scores from the events are modified by a distortion function when the event is projected: the name of the distortion function associated to channel ch must be stored in CScore[ch], which you can access in the GUI under the name Sco in the Vol/Pit/.. toggle menu

CScore functions can play with all p-fields, so unlike the MIDI ones, they are not dedicated to a specific attribute (duration, volume or pitch): there is only one distortion function for each channel.

The arguments of a CScore function are the same as the arguments of the Volume, Pit, Dur, Pan and Mer functions, with one more as the first argument: the event's score. There is also another extra argument cloid, the last one, which will be discussed later on.

So this is the template:
function CScoreExample(sco, ph, s, d, ch, theta, t, cloid, ...)
	# sco: the event score (an array of strings, sco[0] being the number of strings)
	# ph: the event nodur
	# s: the side of the event (-1 for left, 1 for right)
	# d: the distance of the event
	# ch: the event channel
	# theta: the projection angle
	# t: the local time (see here for details)
	# cloid: the closure id, or $0
	# ...: optional arguments, to be stored in CScoreArgs[ch]

	a_score = arraycopy(sco)

	# or

	a_score = ...

	return (a_score)
The function must return a score. It can be created from scratch, or be a simple modification of sco; note in that case the use of arraycopy, because sco is an array. If you do not copy it beforehand, any change to sco will actually appear in the event "score" field, which is probably not what you want.

This single distortion function can be pretty complicated, since all parameters in the score (for each line of the array, which may include several instruments) can be changed in their own way according to the arguments.

The following functions can be helpful (they're defined in lib/lib_csound.k):
UseMIDI_dur(sco[n], s, d, ch, fdur)
UseMIDI_vol(sco[n], pn, min, max, s, d, ch, fvol)
UseMIDI_pit(sco[n], pn, min, max, s, d, ch, fpit)
UseMIDI_pan(sco[n], pn, min, max, s, d, ch, fpan)
You can use them to "translate" a distortion function from MIDI to Csound format.

For example, if p4 is a volume parameter of your Csound instrument, and you have a distortion function VolTruc() that you use for volume modifications of events nodurs, you can use it to play on p4 in line n of sco:
sco[n] = UseMIDI_vol(sco[n], 4, 20000, 50000, s, d, ch, VolTruc)
... the min (20000) and max (50000) values are used to scale the effect of VolTruc(): a MIDI volume/velocity is always a value from 0 to 127, while a Csound instrument volume/amplitude can have any range of values. In this example it goes from 20000 to 50000.

The way it works is the following: a MIDI note is created from scratch, with its volume set to the value of p4 (after scaling). For example, if p4 is 50000, the note will be 'av127'. This will be used as nodur for VolTruc:
dvol = VolTruc('a127', s, d, ch)
... VolTruc returns a dvol value that is added to the nodur's value: this is done and the resulting volume is scaled back in the instrument range so we have the new value for p4.

So remember that the MIDI distortion function is called on an arbitrary nodur, having only one meaningful attribute (volume, duration or pitch, depending on the case). While you could have a MIDI distortion using both volume and pitch values to calculate a new pitch, this will not work through UseMIDI_pit(): only the pitch will be taken into account, the volume in that case being the default one in KeyKit, that is 63.

UseMIDI_dur() is a special case, since the duration parameter is always p3, and no scaling is needed; so it takes less arguments than the other ones.

Note that for all UseMIDI... functions, you can skip the last argument:
sco[n] = UseMIDI_vol(sco[n], 4, 20000, 50000, s, d, ch)
... in this example the distortion function is implicitely the current one for channel ch, that is the one stored in Volume[ch]

Note: If the projection is happening within a closure, you can use the cloid argument to get the distortion functions in the closure, through its getDF method. Here is an example code for pitch:
pitfunction = cloid.getDF()["Pit"][ch]
piffarguments = cloid.getDF()["PitArgs"][ch]

Here is a comprehensive and fully commented example of distortion function for CScore:
(also look at the examples in tutorial 6)
function CSmandol(sco,nodur,pan,dist,ch)	
	sco2 = ArCopy(sco)
	for (l=1; l<= sco2[0]; l++)		# for each line of the score
		lsco = sco2[l]
		inst = SCi(lsco) 	# instrument number for line l

		if (inst == -1)		# if this is a comment line...
			continue	#  ... we skip it and go to the next one

		else if (inst == 2)	# settings for instrument 2 (i2):
			# p3 is changed according to Dur[ch]
			lsco = UseMIDI_dur(lsco, pan, dist, ch)
			# p4 is changed according to Pit[ch]				
			lsco = UseMIDI_pit(lsco, 4, 4, 10, pan, dist, ch)	
			# p5 is changed according to DopplerLateral()	
			lsco = UseMIDI_pit(lsco, 5, 4, 10, pan, dist, ch, DopplerLateral)

		else if (inst == 5)	# settings for instrument 5 (i5):
			# p4 is set to 0.5 if the event is on the left
			if (pan == -1) lsco = SCpn(lsco, 4, 0.5)
			# p4 is set to 0.9 if the event is on the right
			else lsco = SCpn(lsco, 4, 0.9)
			# p3 (duration) is set from the distance of the event
			lsco = SCpn(lsco, 3, dist)
			# p5 is set to 10 times the value of p4
			lsco = SCpn(lsco, 5, SCgetpn(lsco, 4)*10)

		sco2[l] = lsco
	return (sco2)

Insertion of raw projection data

As you can see, writing a projection function for score lines is not such an easy task. Since Csound orchestras can perfom a lot of computing themselves, it may be easier to let them handle this task.

To do so, select RawInsert as the distortion function for CScore. This will insert the 5 parameters pan, dist, ch, tht, t directly in the score, just after the p3 field.

So if for example an event score is
"i1 0 1 1000 2000"
...then after projection it will become something like
"i1 2.1 1 -1 0.256 2.485 5240 1 1000 2000"
...where: (see the projection page for the meaning of theses parameters).
The parameters 1000 and 2000 have been pushed at the end of the line.

No projection is actually defined here. It is up to intrument 1 in the orchestra to deal with the fields p4 to p8 and decide what they mean.

Note that p3 is not affected by the projection: it keeps the value it has in the event.

Although in this example the score was 1-line long, RawInsert can handle any score: the insertion will happen for each i-statement in the score.

Alternate score format

An alternate format is possible if the score only contains i-statements with numeric parameters, and does not embed comments. Example:
score = [ 0= 2,
          1= [1= 2, 2= 0, 3= 3, 4= 6.08, 5= 6.08], 
          2= [1= 2, 2= 0.5, 3= 2.5, 4= 7, 5= 6.08]]
Here you directly have the p3 in line 2 in score[2][3]

Use the function GetScoreFormat() to query for the score format. It returns "string" or "array" depending on the case.

The standard format is the "string" one. Use the "array" format only when you want to perform fast calculations, then return to "string"; the "array" format is usually not supported by the functions handling scores, except the ones whose name contains "NumScore" instead of "Score".

... translates sco from the "string" format to the "array" one

... translates sco from the "array" format to the "string" (standard) one

Converting pitch values

The following functions (whose names are directly copied from Csound) make it possible to convert pitch from a csound format to another:
midipch(pch)	# returns a midi note: midipch(8.08) = 68
pchmidi(midi)	# returns an octave-point-pitch-class: pchmidi(68) = 8.08
midicps(cps)	# midi from cycles-per-second: midicps(440) = 69
cpsmidi(midi)	# cycles-per-seconds from midi: cpsmidi(69) = 440
cpspch(pch)	#	.. etc
Note that only midicps() and midioct() round their result to an integer. With the other functions, you can use quarter tones or other microtunings: for example, pchmidi(68.5) returns 8.085

These functions are not very accurate, though (KeyKit is not a langage for mathematics).

The ...midi() functions can also take a phrase as argument: pchmidi('g+') returns 8.08

The EditScore and EditEvent plug-ins

1) EditScore

The EditScore plug-in provides very basic facilities for editing the scores in events (more ambitious editing is best done in a text editor or a spreadsheet). It presents a set of buttons, some of them having an effect only if you clicked on an event symbol (or alias). Here they are:

The two buttons at the top and right ( [-> L..] and [-> p..]) display the current line number in the score and the current parameter in the line. This is what other buttons are acting on. To change the current line or parameter, simply use the corresponding button: a pop-up window will allow you to set the new one.

[(kill)] kills the current line in the score (the next lines are moved down so that there is no hole in the array)

[(init)] lets you give a new value the current line, through a pop-up window. You can also overrides the current line number and set the value for any other line: to choose line number k, start the text with k£; for example:
5£i2 0 3 6.08 6.08
... sets line 5 of the score to "i2 0 6.08 6.08"

[MidiCS] maps the event nodur into its score; the previous score, if any, is deleted. The actual function call performed by this button is, for each note n in the nodur:
score line for n = MidiCS(n, EditScore_map, EditScore_sco) 
... use the buttons [-> map] and [-> sco] to set the values of EditScore_map and EditScore_sco (of course you can also do it at the console)
See the chapter about MIDI and Csound for details about MidiCS().

[(set)] lets you set the value for the current parameter in the current line. You can NOT enter a formula here, only a float. To set a parameter with a formula, use the [(p=f..)] button

[(p=)] is almost the same as [(set)], with an extra: you can also directly set another parameter by entering its number followed by ";". For example, if current parameter is p3 in line 1,
... will set p3 to 5.6, while:
4; 5.6
... will set p4 (in line 1) to 5.6

[(p=f..)] is the same as [(p=)], but here you can use any KeyKit valid formula to define the parameter:
4; sin(2)
[(p+=)] adds the value (float) you enter to the parameter

[(p*=)] multiply the parameter with the value (float) you enter

[(!del!)] deletes the whole score for the event.

[>-IND.-<]/[>-SEL.-<] toggles between individual event and whole selection mode. In the last one, most operations performed by the plug-in will effect all the selected events (while they will not necessarily effect the event you clicked on in the first place !)

[(save)] and [(read)] saves or reads the event score into a KeyKit variable or a .sco file. The pop-up windows first proposes you to use a variable, then if you press RETURN, it asks for a file name (just give a name without path nor extension; it will be DATA+name+".sco"). These buttons can be used to copy a score from an event to another (although this is best done with the EditEvent plug-in), or to import a score edited elsewhere and saved in a .sco file

[(header)] reads the score header (stored in Ev["score"]) from a variable or a file, as previously.

[(orc)] lets you set the filename of the associated orchestra. Do not give the path: this *.orc file should be located in the DATA directory. This information is used by the Compositor tool when rendering [Audio]. It will be stored in Ev["orc"]

The other buttons are used to directly get the parameters values (at the console). For example, [p4] will display the value for parameter 4 (in the current line) and also set the current parameter to p4.

p1, p2 and p3 have a "!" to remind you that they have a specific meaning in Csound: instrument number, starting time and duration. If you set the value of p2, remember it will behave as an offset, since p2 depends on the projection.

2) Using the EditEvent plug-in

The EditEvent plug-in provides three items making it easy to deal with simple scores in events:

Playing with both MIDI and CSound

MIDI and Csound score rendering happen in parallel within GeoMaestro, so you can do both at the same time. More, you can make them work together in several different ways:

The MidiCS() function provides an easy way of mapping MIDI notes into a Csound score. It is notably used by the EditScore plug-in.

A MIDI note n is transformed into a single score line lsco with one of the following commands:
lsco = MidiCS(n)
lsco = MidiCS(n, map)
lsco = MidiCS(n, map, lsco)

The first one uses basic default values: the volume of the note goes in p4 and keeps its MIDI value (0 to 127), the pitch goes in p5 and is converted in pch format. Time is converted from clicks to seconds. Instrument number is 1. For example:
"i 1 0.05 2.6 127 8.09"

The argument map allows a full customization of the score line format: it sets the instrument number, the p-fields associated to volume and pitch, the range of volumes and the format of the pitch. It also allows other p-fields to have default values.

map is an array with a specific format, returned by the CSMap() function:
map = CSMap(inum, pvol, volmin, volmax, ppitch, format {, template})
... where: example:
map = CSMap(705, 8, -1000, 1000, 4, "oct", "i0 0 0 0 100 200 300 0 -0.05 1223")
lsco = MidiCS('av127d500t10', map)
will return the following lsco:
"i 705 0.05 2.6 8.75 100 200 300 1000 -0.05 1223"

The last optional argument lsco of MidiCS() is used to override the template in the map, which is notably what we want if we are simply changing the values of volume, pitch, time and duration in an already defined line of score.

lsco = "i705 0.05 2.6 8.75 -1 -2 -3 1000"
lsco = MidiCS('av30d96', map, lsco)
"i 705 0 0.5 8.75 -1 -2 -3 -527.56"


MidiCS() only works on a single note, but you can get a Csound score out of a whole phrase ph with the simple loop:
score = [0=0]
for (n in onlynotes(ph))
	score[++score[0]] = MidiCS(n, map)

...and here is how you would map a MIDI phrase on a score for several instruments, by selecting the notes from their MIDI channel:
maps = []
maps[1] = CSMap( ... parameters for channel 1... )
maps[2] = CSMap( ... parameters for channel 2... )
... etc
score = [0=0]
for (n in onlynotes(ph))
	score[++score[0]] = MidiCS(n, maps[n.chan])

This is actually what the function MidiPhCS(ph, maps) does. In the other way round, we have MidiFromCS() and MidiFromScore() which perform a mapping from Csound to MIDI.

Extending the function library

Working with Csound involves writing a lot of new functions, since the score format changes with each instrument, and of course because there's no score functions in KeyKit to start with.

The current GeoMaestro library is documented in the reference page.

You may also use man, functopic and funclist to have on-line help from the keyKit console:
funclist("Score") will give you most of the high-level score functions, functopic("csound") will list all functions whose #topics keywords contain "csound"

The low-level and I/O functions are written in the file lib/csound_utils.k
The library of high-level functions is coded in lib/lib_csound.k
..have a look there to see how to adapt and write functions.

The one tricky point is that, as KeyKit array variables are pointers, changing a score passed as an argument within a function does change the score outside it ! So extensively use arraycopy() or ArCopy() to make sure you created a copy of the score before modifying it.

Seamless score processing through Emacs

(more here later ...)

One last word: if you feel that this overview leaves something obscure or unexplained, feel free to write me so that I can improve the documentation; feedback is always precious.

-- Back --