MidicaPL Tutorial, Chapter 4: Blocks

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

In this chapter we will focus on nestable blocks that can be used for different things like loops or conditional executions.

Nestable Blocks

Functions are useful to group a number of commands and apply them later using a self-invented name. But sometimes you may want to group commands without inventing a name and execute them directly instead of calling them later. That's where nestable blocks are used.

Programming languages also have nestable blocks. Typically they are used for if or else blocks or loops. In MidicaPL a nestable block is opened by a line beginning with { and closed by a line beginning with }.

We switch back again to "Another one bites the dust" by "Queen" (see chapter 3). We rewrite the drums and bass of that song using nestable blocks:

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

We formed a block for the bass part and a second one for the drums part. Both blocks are nested inside of another block. The commands are played directly without the need of a function call.

But hey... The Bass part is played only once! One quick and dirty way to fix this problem is to use the drums function from chapter 3:

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 |
	}
	{
		CALL drums q=4
	}
}
FUNCTION drums
	p:  (v=127) hhc,bd1:8   (v=80) hhc
	p:  (v=127) hhc,bd1,sd1 (v=80) hhc
END
Result (Score)

So you see, a function can be called from inside a block as well.

On the other hand you can also use blocks inside of a FUNCTION. Let's rewrite the function drum-and-bass using blocks:

MidicaPL
FUNCTION drum-and-bass
	{
		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 |
	}
	{
		CALL drums q=4
	}
END
MidicaPL
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 drum-and-bass
	CALL bassline
	CALL drums q=4
END

These examples are equivalent. The first one uses blocks, the second one doesn't.

The version with blocks doesn't need a function for the bass part. However the drums part is needed in both versions. And of cause we could have written the same thing completely without blocks. This was just a demonstration that blocks can be nested inside of functions.

Blocks become much more useful when using block options.

Block Options

Just like CALL commands, nestable blocks can have options as well. The block options table shows the available options. As you can see, the following options are also available as block options:

  • quantity (short version: q)
  • multiple (short version: m)
  • shift (short version: s)
  • if (no short version available)

Moreover there are also some new options, only available for blocks:

  • tuplet (short version: t)
  • elsif (no short version available)
  • else (no short version available)

Options can be attached to the opening { or closing } brace, separated by whitespaces.

Block Options
Long Name Short Name Type Min Value Max Value
quantity q integer 1
multiple m none - -
shift s integer -127 127
tuplet t none or string - -
if - condition - -
elsif - condition - -
else - - - -

If you want (or must) pass a value to an option, the value must be appended to the option name, separated by a = symbol or whitespace(s).

If more than one option is used, they have to be separated by a , symbol.

Quantity

The quantity option plays the according block as many times as its value defines. Eventually we are able to rewrite the function drum-and-bass correctly and without needing to define the functions drums and bassline:

MidicaPL
FUNCTION drum-and-bass
	{
		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 |
	}
	{ quantity 4
		p:  (v=127) hhc,bd1:8   (v=80) hhc
		p:  (v=127) hhc,bd1,sd1 (v=80) hhc
	}
END
MidicaPL
FUNCTION drum-and-bass
	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
	} q=4
END

These two examples are equivalent. The first example uses the long form quantity and adds it to the opening brace. The second example uses the short form q for the closing brace.

Another difference is that the second example omits the block around the bass part. This block is optional because without block options it does not do anything special.

Multiple

The multiple option (short form m) can be used to implement different voices playing in the same channel in the same time. It plays the block content and then moves the end markers of each involved channel to the position they had before the block has been opened.

The following example is equivalent to the example we used to explain the multiple option in a function call (in chapter 3). But this time we do it completely without functions, only with blocks.

MidicaPL
* time 1/8
5: | (d=30%) a-2:16 g-2 |
* time 4/4
{ m
	5: | -:1... a-2:16 g-2 |
}
{ q=3
	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 |
	{ q=4
		p:  (v=127) hhc,bd1:8   (v=80) hhc
		p:  (v=127) hhc,bd1,sd1 (v=80) hhc
	}
}
Result (Score)

The red part is the anacrusis (upbeat) again. The blue part defines the two notes at the end of the second measure. This is the part with the multiple option.

Because of this option, the following black part starts at the same time like the blue block. This black part is repeated three times.

The multiple option is especially useful for music with complex percussion patterns, like in african or latin music. The following example shows a typical Mambo section with different percussion instruments playing different patterns in the same time.

MidicaPL
* tempo 180
{ q=2
	// tom tom
	{
		p: | -:4 t3:8 t3 -:4 t6:8 t6 |
	} m, q=2
	
	// bass drum
	{
		p: | -:4 bd1:2. | - bd1:4 |
	} multiple

	// bell pattern
	{
		p: | (q=2) rb:4 (q=4) rb:8
		p: | - (q=3) rb rb:4 (q=2) rb:8
	} m

	// clave
	{
		p: | -:4 cla cla - | cla:4. cla cla:4 |
	}
}
Result (Score)

The tom-tom part uses 2 different tom-tom drums. This pattern is the same in both measures, so the block is repeated by the block option q=2 at the end of the block. Besides, the option m (multiple) is used to reset the time, so that the next block begins in the same time. So you see how both options can be combined.

The bass drum part, marked in blue, only uses the multiple option, here in its long form. That's needed again because other parts are following that begin in the same time and in the same channel.

The bell part defines a mambo bell pattern which is very common in afro-cuban music. Here we use the ride bell but it can also be used with a cow bell or other percussion instruments. This block ends with a multiple option (m) as well because there is still another drum part to follow with the same start time.

The last block defines the clave rhythm which is also very typical for latin music like Mambo or Salsa. This block does not contain a multiple option because there is nothing more to follow.

All these blocks are nested inside another block with the option q=2, so that everything is repeated.

Shift

The shift option (short form s) works exactly like the shift option for function calls. It transposes the whole block up or down by an arbitrary number of half tone steps. Nested blocks can have different shift options. In this case, the option values for all surrounding blocks are added up to calculate the resulting shift value.

the following example demonstrates this, first with a lowlevel version and then with a compact version of the same sequence:

MidicaPL
{ shift=-24
	0  c  /4
	0  d  /4
	0  e  /4
	{ s=12
		0  c  /4
		0  d  /4
		0  e  /4
		0  c  /4  s=12
	}
}
MidicaPL
{ shift=-24
	0: c d e
	{ s=12
		0: c d e c+
	}
}
Result (Score)

The first three notes are played 2 octaves lower because the outer block has a shift option of -24.

The next three notes are played only one octave lower because the -24 of the outer block and the +12 of the inner block are added, and the result is -12.

In the lowlevel version the last note has its own shift value of +12 which is added as well. The result for this note is 0, so the note is not shifted any more.

As the shift option doesn't exist for compact syntax, we use c+ here instead of a shifted c in order to get the same sequence.

Tuplet

The tuplet option (short form t) is used to define that the block content is played as a tuplet. The option can be used without any value. In this case the tuplet is interpreted as a triplet.

the following example shows the same triplets that we saw already in chapter 2, but this time defined as blocks.

MidicaPL
1: (q=8) c-:4
{ tuplet
	0:  c:8 c c c c:8. c:16 c:8. -:16 c:8 c
	{ t
		0:  c:16 c c
	}
	0:  c:8 |1 c:2 c,e c
}
Result (Score)

The blue part is again a nested triplet.

We use a bar line after the nested triplet, marked in red. As we already know, we need to add a tolerance value.

Alternatively a value can be passed to the tuplet option. The value consists of two numbers, separated by a :. A triplet could also be defined as tuplet=3:2 (or t=3:2).

3:2 means that the length of each note inside the block is multiplied with 2/3.

Or, more generally, X:Y means that each note length inside the block is multiplied with Y/X.

The following example shows the same tuplets we have seen in chapter 2, but this time using block options.

MidicaPL
1: (q=8) c-:4

{ tuplet=5:2
	0:  c:8 c c - c:16 c
}
0:  c:4
{ t=3:4
	0:  c
	{ t=3:2
		0:  c:16 c c
	}
}
{ t=7:4
	0: |1 c:1 c,g:2 c:4
}
Result (Score)

Again we must add a tolerance to the bar line after the nested triplet, marked in red.

If

The if option can be used to execute a block only under a certain condition. This transforms the block into something like an if block in other programming languages. The following example is equivalent to the example we used for if as a call option. But this time we don't need any further function for the volta brackets (like first-ending or second-ending):

MidicaPL
CALL section(part=1)
CALL section(part=2)

FUNCTION section
	0: | c:4 d e f |
	{ if=${part}==1
		0: | g:2 e |
	}
	{ if ${part} == 2
		0: | e:2 d |
	}
END
Result (Score)

Another difference is that now we use parameters instead of normal variables. We could not do that while explaining if as a CALL option because we didn't learn how to use parameters at that time.

Elsif

The elsif option can be used for a block after another block with the if option. It has its own condition. The elsif block is only executed, if:

  • The preceeding block is not executed (because its if condition if false); and
  • The condition of the elsif option is true.

Here's a rewrite of the previous example with an elsif:

MidicaPL
CALL section(part=1)
CALL section(part=2)

FUNCTION section
	0: | c:4 d e f |
	{ if=${part}==1
		0: | g:2 e |
	}
	{ elsif ${part} > 0
		0: | e:2 d |
	}
END

The if option in the second block is now replaced by an elsif. The condition of the elsif is now ${part} > 0. That means, the condition is true, if the parameter part is a number higher than 0.

In the first call, the if block is executed. The elsif condition is also true, but as the first block is executed, the second one is skipped anyway.

In the second call, the if block is skipped. So the elsif condition is evaluated, and because it's true, the second block is executed.

You can also add more elsif blocks. Each block is only executed if:

  • None of the preceeding if or elsif blocks is executed (because their conditions are all false); and
  • The condition of the block's elsif option is true.

Here's an example of such an if-elsif chain:

MidicaPL
CALL section(part=1)
CALL section(part=2)
CALL section(part=3)

FUNCTION section
	0: | c:4 d e f |
	{ if=${part} == 1
		0: | g:2 e |
	}
	{ elsif ${part} == 2
		0: | e:2 d |
	}
	{ elsif ${part} == 3
		0: | d:2 e |
	}
END
Result (Score)

Else

After a nestable block with an if or elsif option, you can add another block with the option else. This option does not have its own condition. The else block is only executed if none of the preceeding if or elsif blocks is executed.

This enables us to rewrite the if example with only one condition check:

MidicaPL
CALL section(part=1)
CALL section(part=2)

FUNCTION section
	0: | c:4 d e f |
	{ if ${part} == 1
		0: | g:2 e |
	}
	{ else
		0: | e:2 d |
	}
END

The if block is executed in the first call, while in the second call the else block is executed.

Conditions

The if or elsif options require a condition, as you learned before. There are several types of conditions, like the Conditions table shows.

All conditions use an operator, apart from the defined condition.

Most of the operators expect two values, one on the left side and one on the right side of the operator.

Some operators expect only one value.

Conditions
Name Operator Left value Right value Type
equal == string or number string or number binary
not equal != string or number string or number binary
lower < integer integer binary
greater > integer integer binary
lower or equal <= integer integer binary
greater or equal >= integer integer binary
defined none none parameter unary
not defined ! none parameter unary
in in string or number list binary

equal condition (==)

The equal condition uses the operator ==. It compares the left value with the the right one. If both values are equal, the condition is true. Otherwise it's false.

not equal condition (!=)

The not equal condition uses the operator !=. It compares the left value with the the right one. If both values are equal, the condition is false. Otherwise it's true.

lower condition (<)

The lower condition uses the operator <. It expects both values to be numbers. If the left number is lower than the right one, the condition is true. Otherwise, it's false.

greater condition (>)

The greater condition uses the operator >. It expects both values to be numbers. If the left number is higher than the right one, the condition is true. Otherwise, it's false.

lower or equal condition (<=)

The lower or equal condition uses the operator <=. It expects both values to be numbers. If the left number is lower than or equal to the right one, the condition is true. Otherwise, it's false.

greater or equal condition (>=)

The greater or equal condition uses the operator >=. It expects both values to be numbers. If the left number is higher than or equal to the right one, the condition is true. Otherwise, it's false.

defined condition

The defined condition does not use any operator. It expects a call parameter as the only value. If the parameter has been provided with the CALL command, the condition is true. Otherwise it's false.

Here's an example:

MidicaPL
CALL section(foo=0)
CALL section()

FUNCTION section
	0: | c:4 d e f |
	{ if ${foo}
		0: | g:2 e |
	}
	{ else
		0: | e:2 d |
	}
END
Result (Score)

The parameter foo is only provided in the first call. Then the if block is executed. In the second call ${foo} is undefined, and so the else block is executed.

not defined condition (!)

The not defined condition uses the operator !. It expects a call parameter as the only value, on the right side of the operator. If the parameter has been provided with the CALL command, the condition is false. Otherwise it's true.

Summing up, it's the opposite of the defined condition.

Example: if ! ${foo}

This would be executed if the containing function has been called without a parameter foo=...

in condition (in)

The in condition uses the operator in. It expects a simple value or variable or parameter on the left side. On the right side it expects a list of values, variables or parameters, separated by ;. Additional whitespaces are allowed but not required.

The operator compares the left value with all values of the right side. If the left value is equal to one of the list's elements, the condition is true. Otherwise it's false.

Here's an example:

MidicaPL
CALL section(foo=0)
CALL section(foo=2)
CALL section(foo=4)

FUNCTION section
	0: | c:4 d e f |
	{ if ${foo} in 0;1;5;10
		0: | g:2 e |
	}
	{ elsif ${foo} in 2 ; 3
		0: | e:2 d |
	}
	{ else
		0: | d:2 e |
	}
END
Result (Score)

The if block is executed in the first call, because then ${foo} is 0, which is in the list 0;1;5;10.

The elsif block is executed in the second call, when ${foo} is 2, which is not in the first list. But 2 is an element of the list 2 ; 3, so the elsif condition is true.

In the third call, ${foo} is 4, which is in neither of the lists. So the else block is executed.