The previous tutorial introduced
the @
operator to access the attributes of entities. In this tutorial
we will learn to use functions in a similar fashion. Just as @velocity = 0.3
sets the gain for all entities in the working set, the @
prefix
can be used to apply a function to all entities in the working set.
In the end of this tutorial, we will be able to produce a sequence of tones.
Each entity has a span
attribute. It represents the tone duration in seconds.
Next to span
, each entity has a time
property as well.
It determines when the tone will be played.
It is basically an offset from the song start in seconds.
time
is rarely used directly in code. Most of the time,
functions like Split
take care of setting it properly.
Split
is the first function of D♭ you encounter.
It divides an entity into a sequence of shorter entities, so
that in sum they cover the whole span
of the original entity.
Now we finally hear more than a single tone,
but playing a shorter version of the input tone four times is not very exciting.
Then
can be called on the result of Split
in order to directly manipulate the produced entites.
For example, we may shorten them even more.
Then
consumes a transformer function.
It is applied to all entities produced by the Split
.
Note that as a result of the Split
, the span
values
of all four entities equal 1 second at the time of the compound multiplication *=
.
The transformer may be also used with an Indexer
parameter
that allows easy distinction of the produced entities. The indexer
has two basic data properties: Index
and Max
. The latter carries
the highest index number in this Split
call.
The Indexer
also offers a few computed properties that come handy in
common situations: Relative
represents the index progress in the range of [0, 1],
IsFirst
, IsLast
, IsEven
and IsOdd
are self-explanatory.
To keep the syntax simple, Indexer
is implicitly casted to an int
if
used without any of its properties. In the following example, this is demonstrated
by the expression 1 + idx
. It shortens the span
with increasing index. Note
the addition of one to avoid division by zero.
Instead of a regular division resulting in equally sized entities,
Split
can also take an array of relative durations and distribute
the available time span proportionally. Alternatively, the durations
can be also passed as separate method parameters. The following sequence will
sound like a famous Morse code.
There is an even more advanced concept that involves three different sizing types
that can mix together: FILL
, REL
, ABS
. So far the spans were just
relative to each other. This would correspond to the FILL
mode. The FILL
mode involves normalization of the values so that they exactly fit the duration
of the input entity.
REL
has a higher priority than FILL
. The values are relative to the duration of the input entity.
If their sum is > 1.0 (i.e. the input duration), normalization
is applied to the relative items to scale them down so that they exactly cover the input duration
and at the same time all fills are neglected. If the sum of REL
items is < 1.0, the unoccupied
space is assigned to the FILL
items. If there are no FILL
items, then REL
items are scaled up
to cover the whole input duration.
ABS
has the same priority like REL
. The values are in absolute time units, i.e. in seconds.
If the sum of ABS
and REL
items is higher than the available span, normalization
is applied to scale them down and all fills are neglected. Otherwise, either fills are used to
cover the unoccupied space or, if no fills are persent, then ABS
and REL
items are scaled up to match the input duration.
Advanced sizing modes FILL
, REL
and ABS
are useful for higher structuring
of music pieces with complex structures where further layers of splitting follow.
The examples above with single notes are provided just as toy examples for easy demonstration.
D♭ functions are also called batch functions as they process all entities in the working set in a single batch. Without going into any formal details, note that there is a relation to L-Systems.
The following example demonstrates the power of batch processing
of the whole working set. The first Split
divides
the span into parts that could correspond to measures.
In the spirit of batch processing the second Split
divides each of the measures into beats.
We can use both indexes to make the rhythm a bit more interesting. Every second measure will double the third beat.
There are a few more built-in functions which will be introduced in this and in the following tutorials, but most of the functions you will design by yourself as custom functions.
Just like with attributes, you can define custom functions and
apply them to the entities. When calling custom functions they need
by prefixed with @
just like the built-in commands. The folowing
example shows the custom function Bursts
.
The usage of Bursts
can be integrated into the Then
transformer.
We can omit measureIndex
as a helper attribute and make it directly
an argument of 'Bursts`.
The usage of Bursts
can be improved even more. It can be
directly passed as the transformer by replacing int measureIndex
by Indexer idx
. Note that Bursts
is passed as a function
reference, hence the @
prefix must be omitted.
Otherwise, it would be executed right away (i.e. its
result would be expected to be passed to Then
)
which would result in an error.
Watch out for invalid function names. Some identifiers are reserved for other data structures. Using them as custon function names would result in an error. These are names of helper structures like: Melody, Rhythm which will be iontroduced in some of the intermediate tutorials or built-in functions like Split or Rest. The following example demonstrates the situation when a custom function name conflicts with a helper structure resulting in an error.
All the entites we generated so far were actual sounds. But music contains many rests that serve different purposes: they grant time to breate, they convey rhythm, they switch off instruments to produce certain colors.
The Rest
function simply converts entites to rests. They keep all attributes, but one:
chord
, which is discussed in the harmony tutorial, gets
a special value to represent the rest. This is a much better practice
than a full removal of an entity, as only the original chord
information gets lost, but anything else is preserved.
As Split
produces many entities, it is sometimes usefull
to mark some of them terminals. That means that they are considered
to be in their final state and no more D♭ function or
attribute assignemnt can change them. As if they would be deactivated.
In the following example Done
is used to deactivate bursts with
an odd number of tones. Done bursts will not be played by the french horn.
Use Done
with caution, it can easily cause confusion in case you
forget about it or some one else working with your code misses it.
Usually there are better ways to deal with exclusion of a subset of entities.
Local temporary exclusion should be preferred over Done
.
For example, the previous example can be rewritten using a local conditional.
In the next tutorial we will utilize the attributes and functions for producing simple melodies.