MidicaPL Tutorial, Chapter 3: Functions

This is the third chapter of the MidicaPL tutorial. Please read chapter 1 and chapter 2 first, if you have not yet done that already.

So far you learned a lot about lowlevel commands. In this and the next chapter we will focus on highlevel structures that help to reuse your code. This chapter is about functions.

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".

MidicaPL
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.

MidicaPL
CALL bassline
CALL drums
CALL drums
CALL drums
CALL drums
MidicaPL
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
Result (Score)

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:

MidicaPL
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.

MidicaPL
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:

MidicaPL
CALL bassline
CALL drums quantity 4
MidicaPL
CALL bassline
CALL drums q=4
MidicaPL
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.

MidicaPL
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
Result (Score)

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).

MidicaPL
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
Result (Score)

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:

MidicaPL
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:

MidicaPL
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
Result (Score)

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:

  • CALL foo
  • CALL foo()

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.

MidicaPL
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.

MidicaPL
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.

MidicaPL
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
MidicaPL
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:

MidicaPL
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":

MidicaPL
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
Result (Score)

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.