GeoMaestro
Tutorial 6: composing Csound scores
basics
setting up a distortion function
processing a score from the compositor
a 1-note-per-event approach
customizing GeoMaestro for Csound
Warning: this tutorial is only aimed at people already somewhat familiar with Csound; it also involves some programming of KeyKit functions.
See this page of the manual for a survey of GeoMaestro Csound-related functions and plug-in
We will define a scene whose events embed Csound score lines for the legato instrument proposed by Richard W. Dobson in the chapter 7 of the Csound Book. Here are the orc and sco from the book: 705.orc, 705.sco
basics
Open a wrapper tool, since we are going to use both the main GUI and the Compositor.
Since we are going to create our own *.sco file, we need to extract the first lines (the "header") of 705.sco in order to have the table initialisations available. So, with a text editor, let's create 705_head.sco in the DATA directory.
Now we can inform GeoMaestro that this will be the beginning of our final score:
Ev["sco"] = ReadScore
(DATA+"705_head.sco"]
... you can check it's correctly stored by typing
PrintScore
(Ev) # also try: ShowScore
(Ev)
The 10 lines of 705_head.sco should be displayed at the console.
Note:
we used an external editor to remove the t-statement in the initial header in 705.sco
when what we want is simply extracting the first statements in a score file, this can be done automatically with ReadScoreHeader()
, so that we would have:
Ev["sco"] = ReadScoreHeader(DATA+"705.sco"]
Now let's create a couple of events. Since we have a legato instrument here, it makes sense (although it is not required) to represent several tied notes as one single event.
Again, it is simpler to edit an external text file: we create 705_3notes.sco in the DATA
directory (these three lines are merely the first notes from 705.sco)
Switch to the main GUI if necessary, then use the "Snarf to event" mouse mode and click anywhere in the graphic area: you just created a new event, the number 1 in channel 1.
At the console, type:
Ev[1][1]["score"] = ReadScore(DATA+"705_3notes.sco")
... now if you click on this event in "infos" mouse mode, you should see the three score lines at the console.
Create another empty event, then select the "plug-in" mouse mode and set the current plug-in to "EditEvent
". Now click on the first event and select the menu item "score -> copy"; then click on the new event and this time select "score -> paste". That's it: the two events have the same score lines.
Let's try a projection: set the mouse mode to "hear ev/seg", then click and drag a segment like this:
Now click on [pj], select the Ecoute
projector, then click on the [->] button for projection and type "-
" as parameter: the two events have been projected on the segment.
Click on [snarf]: the text editor should open and display the resulting six-line score, complete with the header part, with new p2 fields corresponding to the playing time of the events. It is ready to be processed by Csound.
If the text editor doesn't appear, check that the ShowScoreWhenSnarfed
variable is set to 1; also check that the Notepad()
paths are correct.
processing a score from the compositor
Compiling the score can be done from GeoMaestro: swith to the Compositor, and import a box with the command
£1c
... which is a short-cut for RL[1]["score"].
Then click [EVAL]. You should see something like:
Now we need to defined the orchestra to be associated by default to a score. Set
Ev["orc"] = "705.orc"
Now click [AUDIO]... if your audio settings are correct, the score should be compiled and the resulting wav displayed or played (depending on your settings).
setting up a distortion function
A distortion function is a function that tells GeoMaestro how distance and side from the event to the support will effect the p-fields of the resulting projected note(s).
Unlike distortion functions for MIDI events, only a single function (per GeoMaestro channel) has to be defined to take care of all the p-fields we want to make variable.
In our example, the events in channel one embedding three lines of score, each one with about 7 pfields (we ignore p1 and p2), we have to write a function that handles 21 parameters. This function, hand-made and tailored for the 705.orc orchestra, is going to define how our 3 tied notes will sound according to their distance from the projection support. This of course makes the whole interest in using GeoMaestro to generate a Csound score.
Note: this approach supposes that all events in channel 1 will always have a three-line score representing three tied notes. It is of course a limited usage of the 705 instrument; the number of lines could be variable, so that an event would represent any number of tied notes. But this is a tutorial so we'll keep it simple... On the other hand, an event could also only embed one note, held or not, and then the legato phrases would be issued from the projections: we will see how to do this later on
For those of you who have the Csound Book available, it's time to have a look at chapter 7 to see what this is all about. For the other people, I will describe now the meaning of the different parameters:
- the first five parameters are common to many instruments: p1, p2, p3 are as always instrument number, time and duration (the note being held if p3 is negative); p4 is the amplitude of the sound, p5 its pitch (in the pch format: octave-point-pitch-class)
- p-fields 6 and 7 make the "context" part of the score: they inform the instrument of the previous pitch and the next amplitude, so that it can generate the legato ramp.
- p8 is an expression parameter: it sets a dynamic inflection of amplitude from a note to the next (the "expression swell"), while p9 sets the relative position (from 0 to 1) of the "expression swell" peak
- p10 sets the decay length and allow the programming of a diminuendo
Here are the effects we will implement:
- the more an event is far away, the more its notes are short, and the more the second note gets relatively closer to the first one.
- the pitch of the notes increases linearly with the distance if the event is at the left, decreases if it's at the right of the support
- the amplitude of the sound exponentially decreases with the distance
- the expression swell amplitude logarithmically increases with the distance
- its peak is randomly set from 0.2 to 0.8
Here we go (this function, like all others in this tutorial, is already coded in the userlib/tut6_csound.k file from the distribution, so you don't have to actually write it):
#---------------------------------------------------------
function DF705_1(score, nodur, side, dist, ch)
{
sco = arraycopy(score)
# duration (p3) of the notes (developed for clarity):
p3_1 = SCgetpn(sco[1], 3)
p3_2 = SCgetpn(sco[2], 3)
p3_3 = SCgetpn(sco[3], 3)
sco[1] = SCpn(sco[1], 3, p3_1/float(0.5+2*dist))
sco[2] = SCpn(sco[2], 3, p3_2 /float(0.5+0.5*dist))
sco[3] = SCpn(sco[3], 3, p3_3 /float(0.5+dist))
p4f = exp(-dist)
p8f = log10(10+10*dist)
for (i =1; i<=3; i++)
{
# amplitude (p4):
sco[i] = SCpn(sco[i], 4, SCgetpn(sco[i], 4)*p4f)
# pitch (p5):
sco[i] = SCpn(sco[i], 5, pchmidi(midipch(SCgetpn(sco[i], 5))+integer(-side*dist*12)))
# swell amplitude (p8):
sco[i] = SCpn(sco[i], 8, SCgetpn(sco[i], 8)*p8f)
# swell peak position (p9):
sco[i] = SCpn(sco[i], 9, 0.1*rand(6)+0.2)
}
# first line: we always have p5=p6
sco[1] = SCpn(sco[1], 6, SCgetpn(sco[1], 5))
return (sco)
}
#---------------------------------------------------------
At the console, type:
CScore[1]="DF705_1"
... now let's switch back to the main GUI and try these settings on the following projection (all four events are identical and the same as previously, so you just have to select and copy the two events we already have):
Here is my resulting score: Snarf.sco (of course yours will be different)
Another projection, with the same events:
... results in this score: Snarf2.sco
Note that the projections of the two last events being close, the instrument gets confused and the last group of notes sounds kind of weird.
Actually, what we saw so far does not lead to nice-sounding scores. Let´s do a better job now.
a 1-note-per-event approach
The previous set-up we made has obvious limitations: the sounds from two events should not overlap, what we hear are simple variations of the same triplet... but this could be useful in specific case, and it allowed us to see how to write a complex distortion function managing several lines of score.
Now we are going to use more freely the legato instrument. On channel 2, we will set-up one-line events: the legato phrase will arise from the projections.
First, let's define a new event in channel 2 (use the "new event" or the "Snarf to event" mouse mode). When this is done, use the "score -> edit" menu from the "EditEvent" plug-in to set the score as follows:
Score
("i 705 0 -0.125 5000 9.09 pp5 np4 10000 0.5")
... (this is the second note from 705.sco, except that the + in p2 has been replaced with a 0).
Now let's make a few copies of this event: select it ("selection" mouse mode), then use the "copy(m) selection" mouse mode to duplicate it, a bit like this:
For now, we are only interested in what happen in channel 2, so let's get rid of the channel 1 events: click on [ev] then on [inactive] and type:
C_ == 1
... this removes from display (and from projections) the events in channel 1 (they are not deleted, though):
Let's define a distortion function for channel 2, with the following behaviours:
- the distance (measured as a time value) is added to the note duration.
- again, the pitch of the notes increases or decreases linearly with the distance according to the side, only slower than before
- again, the amplitude of the sound exponentially decreases with the distance
- the expression swell amplitude logarithmically increases with the distance, and is proportional to the amplitude p4
- its peak is randomly set from 0.2 to 0.8
- the p10 diminuendo parameter is randomly set from 0.2 to 1
Here is the code (again, already written in userlib/tutorial_6.k):
#---------------------------------------------------------
function DF705_2(score, nodur, side, dist, ch)
{
sco = arraycopy(score)
lsco = sco[1]
if (SCi(lsco) == 705)
{
# duration (p3):
p3 = SCgetpn(lsco, 3)
lsco = SCpn(lsco, 3, Signe(p3)*(abs(p3)+dist))
# amplitude (p4):
p4f = exp(-dist)
lsco = SCpn(lsco, 4, SCgetpn(lsco, 4)*p4f)
p4 = SCgetpn(lsco, 4)
# pitch (p5):
p5 = SCgetpn(lsco, 5)
lsco = SCpn(lsco, 5, pchmidi(midipch(p5)+integer(-side*dist*6)))
# swell amplitude (p8):
p8 = p4*log10(10+10*dist)
lsco = SCpn(lsco, 8, p8)
# p9 and p10:
lsco = SCpn(lsco, 9, 0.1*rand(6)+0.2)
lsco = SCpn(lsco, 10, 0.1*rand(8)+0.2)
# this is actually useless (see below in the tutorial):
p6 = SCgetpn(lsco, 6)
if (p6 != "pp5")
lsco = SCpn(lsco, 6, p5)
sco[1] = lsco
}
return (sco)
}
#---------------------------------------------------------
You may have noticed the main test checking that the score is related to instrument 705. This is related to the way we are going to handle the legato.
For now, if you set
CScore[2]="DF705_2"
and do this kind of projection:
... you will obtain this kind of score: Snarf3.sco
Although the notes are not ordonned, it is quite obvious that there is a problem with the "np4" and "pp5", since they appear in all lines. And the notes durations do not match their time position, making the legato sound quite ugly. Also, all notes are now played within the same breath, so to say.
I invite you now to have a look at the code for the function P2Legato()
, in lib/csound_utils.k (well, you don't have to, you know... only if you're curious). We are going to use it.
This function first orders the events in the score list, then it looks for the first one and sets its pp.. parameter (in our example this is p6), then it looks for the last one and sets its np.. parameter to 0 (here it is p7). Then it recalculates the durations of the notes.
Also, it handles interruptions in the legato. If a score line for a given dummy instrument number appears (let's say i99), it uses it as a reference point to stop the current legato (and it gets rid of the i99 score line by transforming it into a comment line)
So, let's define the function
function T6Sco(rli)
{
return(P2Legato(ArCopy(RL[rli]["score"]), 705, 1, 99))
}
.. rli
is an index in the RL
array.
In there we call P2Legato()
on the score in ligne RL[rli]
, saying that legato is for instrument 705, while dummy instrument 99 is used for interruptions. The third parameter is a flag, here set to 1, saying wheither or not we want the "np?" and "pp?" to be detected and arranged. What's what we want here.
Let´s switch to the Compositor and import the box
T6Sco(4)
(maybe 4 is not the right number: just check what's your current index in the RL
array)
Then click [EVAL] and [AUDIO]
Here is the kind of results you should get: 705_bis.sco... the legato works fine.
Now let's see for the interruptions:
Back to the main GUI, copy two events then use the "EditScore
" plug-in to change their instrument number to 99 (box "p1 !", then box "(set)"and typing "99"); select them and associate them to channel 17 by clicking [operation] and typing
C_=17
... so that we can easily recognize interruption events from note events.
It should look like this:
Now [redo] the projection, then go back to the Compositor and [EVAL] + [AUDIO] the last box again
The result should be something like this: another_705_bis.sco
...listen to it... you can hear how the phrase has been cut twice by the "i99" events.
customizing GeoMaestro for Csound
I will stop this tutorial here, since there are no limits to the complexity of what can be done to produce a Csound score. It's all matter of programming specific functions to complement the basic projection mechanism.
Csound matters are discussed here. The already defined functions are described here, and I hope writing your own will be straightforward with the help of the existing low-level library.
The GUI can be customized in the following ways:
- as we have seen in this tutorial, if the parameter
ShowScoreWhenSnarfed
is 1, clicking [snarf] writes the score file (interally it calls ExScore()
) and displays the score in a text editor. This also happens automatically if you use the "hear ev/seg" mouse mode. The name of the score file is set by the CS_SnarfFile
string parameter.
- If a
PostProcessScore()
function is defined, it is called by ExScore()
before adding the header. This is a hook for you to define any processing you want to be applied to the score before it is exported as a file.
You will find an empty PostProcessScore()
in userlib/GeoPostinit.k
- if the parameter
RenderScore
is set to 1, then ExScore()
also calls the function DoRenderScore()
, if it exists, after having written the score file. So you can use this function to have KeyKit call Csound and actually render the score and play it to you, without having to use the Compositor. At least, this is how I use it, but of course you may define it otherwise... (use KeyKit function system()
to communicate with the shell, also see CallCsound
). Again, you'll find a void DoRenderScore()
in userlib/GeoPostinit.k
-- Back to the tutorials index--
-- Back --