Functions
Most programming languages have functions, methods, subroutines or something similar that can be called from different places
inside of your source code. In MidicaPL such a structure is called function.
A function is a bock beginning with the keyword FUNCTION
, followed by one or more whitespaces and a self-given function name.
The block is closed with the keyword END
.
Inside the block you can write as many channel or global commands as you want.
The following example defines two functions for "Another one bites the dust" by "Queen".
INSTRUMENTS
5 E_BASS_FINGER Bass
END
FUNCTION bassline
5: | (d=30%,q=3) e-2:4 -:8. e-2:16
5: | e-2:8 e-2 g-2 e-2:16 a-2 -:2 |
END
FUNCTION drums
p: (v=127) hhc,bd1:8 (v=80) hhc
p: (v=127) hhc,bd1,sd1 (v=80) hhc
END
First we introduce the bass instrument in channel 5 so that we can use it later. Then we define the functions.
The first function is called bassline
. It defines the main bassline and has a length of 8 quarter notes.
The second function is called drums
. It defines the drum beat with a length of 2 quarter notes.
So far no sound is produced at all. The functions have been defined but they have not yet been used.
Later we want to play the bass and the drums together. The bassline
function is 4 times longer
than the drums
function.
That means we need to call drums
4 times more often than bassline
.
A function can be called with the CALL
command, followed by one or more whitespaces and the function name.
You can imagine that a CALL
command is internally replaced by the code inside of the function.
The following example plays the functions that we have defined before. The first source code version uses CALL
commands.
The second version shows how these CALLs would look like if they were replaced by the according function contents.
CALL bassline
CALL drums
CALL drums
CALL drums
CALL drums
5: | (d=30%,q=3) e-2:4 -:8. e-2:16
5: | e-2:8 e-2 g-2 e-2:16 a-2 -:2 |
p: (v=127) hhc,bd1:8 (v=80) hhc
p: (v=127) hhc,bd1,sd1 (v=80) hhc
p: (v=127) hhc,bd1:8 (v=80) hhc
p: (v=127) hhc,bd1,sd1 (v=80) hhc
p: (v=127) hhc,bd1:8 (v=80) hhc
p: (v=127) hhc,bd1,sd1 (v=80) hhc
p: (v=127) hhc,bd1:8 (v=80) hhc
p: (v=127) hhc,bd1,sd1 (v=80) hhc
Both functions play in different channels. So if we call them after each other they play simultaneously.
A function can also be called from another function.
So an alternative for the last example would be the usage of a further function:
FUNCTION drum-and-bass
CALL bassline
CALL drums
CALL drums
CALL drums
CALL drums
END
CALL drum-and-bass
Now we need to handle the different lengths of the bass and drum functions only at one place: inside the function drum-and-bass
.
Later we can call this function as often as we want.
In the last example we defined the function first and called it later. But the other way round is also possible.
CALL drum-and-bass
FUNCTION drum-and-bass
CALL bassline
CALL drums
CALL drums
CALL drums
CALL drums
END
In this example the function is called before it is defined.
Call Options
Do you remember the channel command options from chapter 2?
A CALL
command can also have options.
As you can see in the Call Options table, the following options are also allowed as call options:
quantity
(short version: q
)
multiple
(short version: m
)
shift
(short version: s
)
Besides these, we also have a new option:
if
(no short version available)
Call Options
Long Name |
Short Name |
Type |
Min Value |
Max Value |
quantity |
q |
integer |
1 |
∞ |
multiple |
m |
none |
- |
- |
shift |
s |
integer |
-127 |
127 |
if |
- |
condition |
- |
- |
Call options are attached to the end of the CALL
command line, separated from the rest of the command
by one or more whitespaces.
If an option requires a value, this must be appended to the option name, separated by a =
symbol or whitespace(s).
If different options are used, they have to be separated by a ,
symbol.
Quantity
Probably you already have guessed what the quantity
option does: It repeats the function call as often as defined by
the option value.
So we can rewrite our calls using the quantity option. The following versions are all equivalent:
CALL bassline
CALL drums quantity 4
CALL bassline
CALL drums q=4
CALL bassline
CALL drums
CALL drums
CALL drums
CALL drums
Multiple
The multiple
option can be used to implement different voices playing in the same channel in the same time.
It plays the function and then moves the end markers of each involved channel to the position they had before calling the function.
In other words: After playing a function with this option, the next notes in the same channels are played in the same time as the function.
The next example shows how we can use this option in order to play a small variation.
FUNCTION drum-and-bass
CALL bassline
CALL drums q=4
END
* time 1/8
5: | (d=30%) a-2:16 g-2 |
* time 4/4
CALL drum-and-bass multiple
5: | -:1... a-2:16 g-2 |
*
CALL drum-and-bass q=2
The red part is the anacrusis (upbeat) without drums. The global command (* time 4/4
) synchronizes
the involved channels so that the drums start after the anacrusis. And it also switches to 4/4 beat.
The blue part adds two bass notes to the end of the second measure. Therefore the first CALL
command
is marked as multiple
so that the following code is added to the time where the call started.
As we want to add something to the last eighth of the measure, we need to fill up the beginning with rests in the bass channel.
We need a whole rest with three dots.
After adding the rest and the notes, our bass channel is two measures further than our percussion channel. So we need to synchronize them
again with another *
.
Then we are ready to call drum-and-bass
again a few times.
Shift
The shift
option can be used to transpose a whole function up or down by an arbitrary number of half tone steps.
Use a positive number to shift the function up or a negative value to shift it down.
The following example shows how this works. It shows the first two measures of the harpsichord part of Antonio Vivaldi's
"Four Seasons - Summer" (3rd movement).
INSTRUMENTS
6 HARPSICHORD Harpsichord
END
FUNCTION measure_1-2
6: g:16 (q=11) g- f (q=11) g-
END
CALL measure_1-2 m
CALL measure_1-2 s=-12
The right and left hand play both the same notes, but in a different octave.
First we introduce the Harpsichord in channel 6. Then we define a function for the first two measures.
We call this function for the right hand of the harpsichord first. Therefore we use the m
option because we want to
play the left hand in the same time.
The second call of measure_1-2
is for the left hand. We use the option s=-12
to play the same
notes one octave lower.
Alternatively we can also use variables for the channel. Then we are able to use the same function for different channels.
The next example demonstrates how this works:
INSTRUMENTS
0 VIOLIN Lead Violin
4 CELLO Violoncello
6 HARPSICHORD Harpsichord
END
FUNCTION measure_1-2
$ch: g:16 (q=11) g- f (q=11) g-
END
VAR $ch = 0
CALL measure_1-2
VAR $ch = 4
CALL measure_1-2 shift=-12
VAR $ch = 6
CALL measure_1-2 m
CALL measure_1-2 s=-12
First we introduce our instruments in the channels 0, 4 and 6.
Then we define the function for the first two measures again. But this time we use the variable $ch
instead
of a channel number.
Later we set $ch
to 0 and call the function for the lead violin. This is the red part in the example.
Then we set $ch
to 4 and call the function again. This time for the Violoncello. This is the blue part in the example.
We use shift=-12
because the Violoncello plays the same notes one octave lower. Like the left hand of the Harpsichord.
The last (black) part switches to channel 6 and calls the function once for each hand of the Harpsichord.
Just like in the example before.
If
The if
option can be used to call a function only under a certain condition.
The condition can be a variable having a certain value.
The following example demonstrates how this works:
VAR $part = 1
CALL section
VAR $part = 2
CALL section
FUNCTION section
0: | c:4 d e f |
CALL first-ending if=$part==1
CALL second-ending if $part == 2
END
FUNCTION first-ending
0: | g:2 e |
END
FUNCTION second-ending
0: | e:2 d |
END
The function section
is called twice. When it's called the first time, the variable $part
has a value of
1
, while the second time the value is 2
.
At the end of section
there are two conditional function calls, representing the two volta brackets
(marked with 1.
and 2.
in the score).
The line CALL first-ending if=$part==1
is executed only in the first call, when $part
is 1
.
The according condition is $part==1
. It contains the operator ==
to check if the left and right side of the
operator is equal. Only in this case, the function call is executed.
The line CALL second-ending if $part == 2
is working likewise. It is only executed in the second call.
This time we only omitted the =
between if
and the condition part. So it looks more like in other
programming languages. And we have added whitespaces around ==
, which is allowed in conditions. This makes the
condition more readable.
There are some more operators that can be used inside a condition, apart from ==
. You will learn more about
them in the next chapter.
Call Parameters
One important feature of functions in all major programming languages is the support of parameters.
In MidicaPL this works as well.
Therefore you need to add the symbols (
and )
directly after the function name (and before any options).
Then you add your parameters between the parantheses, separated by ,
.
E.g. you can call the functon foo
with the parameters a
and b
three times and transposed one octave higher with the following command:
CALL foo(a, b) q=3, s=12
A function call without parameters can also be made with an empty list. So the following calls are equivalent:
There are two types of parameters: indexed parameters and named parameters.
Indexed Parameters
An indexed parameter is a parameter without a name. So the order of the parameters in the CALL
command
is relevant.
Let's get back to our "Vivaldi" example and rewrite our function measure_1-2
using indexed parameters
instead of variables.
CALL measure_1-2( 0, 0 )
CALL measure_1-2( 4, -12 )
CALL measure_1-2( 6, 0 ) m
CALL measure_1-2( 6, -12 )
FUNCTION measure_1-2
$[0] g /16 s=$[1]
$[0] g- /16 s=$[1], q=11
$[0] f /16 s=$[1]
$[0] g- /16 s=$[1], q=11
END
Instead of changing a channel variable between the function calls, we pass the channel as the first parameter.
And as the second parameter we pass the transposition value.
Inside the function, we use these parameters with a $
symbol, followed by an index, surrounded by
square braces [
and ]
.
A variable index starts counting from 0. That means, to use the first parameter, we must write $[0]
and for the second one $[1]
. A third one would be $[2]
, and so on.
This time we use lowlevel syntax because in compact syntax the shift
option is not supported.
(This is only for demonstrating how to use more than one parameter.)
Named Parameters
A named parameter is a parameter with a name. The following example is equivalent but uses named parameters instead
of indexed parameters.
CALL measure_1-2(ch=0, transp=0)
CALL measure_1-2(transp=-12,ch=4)
CALL measure_1-2(transp=0, ch=6) m
CALL measure_1-2(ch=6, transp=-12)
FUNCTION measure_1-2
${ch} g /16 s=${transp}
${ch} g- /16 s=${transp}, q=11
${ch} f /16 s=${transp}
${ch} g- /16 s=${transp}, q=11
END
A named parameter inside of a CALL
command consists of a name and a value, separated by =
.
The order of the parameters doesn't matter.
Inside the function, a named parameter is accessed by a $
symbol, followed by the name, surrounded by
{
and }
.
In our example the channel is passed as ch=...
and accessed as ${ch}
.
Mixed Parameters
Indexed and named parameters can be mixed. The following examples are equivalent but use a mix of indexed and named
parameters inside of the same function calls.
CALL measure_1-2( 0, transp=0 )
CALL measure_1-2( 4, transp=-12 )
CALL measure_1-2( 6, transp=0 ) m
CALL measure_1-2( 6, transp=-12 )
FUNCTION measure_1-2
$[0] g /16 s=${transp}
$[0] g- /16 s=${transp}, q=11
$[0] f /16 s=${transp}
$[0] g- /16 s=${transp}, q=11
END
CALL measure_1-2( ch=0, 0 )
CALL measure_1-2( ch=4, -12 )
CALL measure_1-2( ch=6, 0 ) m
CALL measure_1-2( ch=6, -12 )
FUNCTION measure_1-2
${ch} g /16 s=$[1]
${ch} g- /16 s=$[1], q=11
${ch} f /16 s=$[1]
${ch} g- /16 s=$[1], q=11
END
The first example uses an indexed parameter for the channel and a named parameter for the transposition.
The second example uses the opposite approach.
Scope of Parameters
A variable in MidicaPL can be accessed globally, as soon as it is defined.
But a call parameter is only valid inside the called function.
Even if you call a second function from inside the first function, you cannot access the first function's parameters
from the second function. Not directly. But of cause you could pass the parameters over to the second function, like the following
example shows:
CALL measure_1-2(ch=0, 0)
CALL measure_1-2(ch=4, -12)
CALL measure_1-2(ch=6, 0) m
CALL measure_1-2(ch=6, -12)
FUNCTION measure_1-2
CALL measure_1(ch=${ch}, $[1])
CALL measure_2(ch=${ch}, $[1])
END
FUNCTION measure_1
${ch} g /16 s=$[1]
${ch} g- /16 s=$[1], q=11
END
FUNCTION measure_2
${ch} f /16 s=$[1]
${ch} g- /16 s=$[1], q=11
END
The function measure_1-2
uses mixed parameter types. The first parameter is named, while the second one
is indexed. The named parameter is later used as ${ch}
, the second one is used as $[1]
.
measure_1-2
does not directly implement the channel commands any more. Instead, it calls two other functions,
one for each measure: measure_1
and measure_2
.
Inside of measure_1-2
, the parameters must be passed over to the CALL
commands for
measure_1
and measure_2
, so that they can be used there as well.
Lyrics as parameters
Function parameters are especially useful for the lyrics of verses. In modern songs the melodies of different verses are
typically the same. But the lyrics are not. So we can implement the vocal melody in a function and provide the lyrics as parameters.
The following example shows how this can be done in "London Bridge":
INSTRUMENTS
4 LEAD_VOICE Vocals
END
CALL vocals(Lon, don_, Bridge_, is_, fal, ling_, down)
CALL vocals(Build_, it_, up_, with_, wood_, and_, clay)
CALL vocals(Wood_, and_, clay_, will_, wash_, a, way)
FUNCTION vocals
CALL measure_1($[0], $[1], $[2], $[3]) // London bridge is
CALL measure_2($[4], $[5], $[6]\c\r) // falling down,
CALL measure_3($[4], $[5], $[6]\c_) // falling down,
CALL measure_2($[4], $[5], $[6].\r) // falling down.
CALL measure_1($[0], $[1], $[2], $[3]) // London bridge is
CALL measure_2($[4], $[5], $[6].\r) // falling down,
CALL refrain() // My fair lady.
END
FUNCTION measure_1
// Lon- don bridge is
4: (l=$[0]) a:4. (l=$[1]) b:8 (l=$[2]) a:4 (l=$[3]) g
END
FUNCTION measure_2
// fal- ling down
4: (l=$[0]) f#:4 (l=$[1]) g (l=$[2]) a:2
END
FUNCTION measure_3
// fal- ling down
4: (l=$[0]) e:4 (l=$[1]) f# (l=$[2]) g:2
END
FUNCTION refrain
// My fair La- dy
4: (l=My_) e:2 (l=fair_) a (l=La) f#:4 (l=dy.\n) d:2.
END
The vocals
function is called once for each verse, with the syllables as indexed parameters.
Parts of the lyrics are repeated but with different notes. So the lyrics are delegated to further functions
for the individual measures.
The last two measures have always the same lyrics ("My fair Lady."). So the according function refrain
does not need any parameters.