Macros¶
Functions are used to abstract some variables over an expression and to repeatedly evaluate this expression with a change to the value referenced by these abstracted variables. Processes play a similar role, with a group of actions instead of an expression.
Macros can play both roles because the abstracted object is a text without a priori semantics. The mechanisms is then more primitive but may have some advantages. Refer to the side page Macro versus Function versus Process for a comparison of the three constructs. Usually, if a process or a function can do the same job as a macro, there are advantages to using them over macros and you should give them priority.
Macro Definition and Usage¶
Macro definitions are introduced by the @macro_def
keyword. Macros are called by their @-name followed by their arguments
between parentheses.
@macro_def @b2sec($beat) { $beat / (60. * $RT_TEMPO) }
A call to a macro is simply replaced by its definitions and given arguments in the text of the score: this process is called macro-expansion and is performed before program execution. The macro-expansion is a syntactic replacement that occurs during the parsing and before any evaluation. Macros are thus evaluated at score load and are NOT dynamic. The body of a macro can call other macros but macros cannot be recursive1.
Macro names are @-identifiers. For backwards compatibility reasons, a simple identifier can be used in the definition but the @-form must be used to call the macro. Macro arguments are formal parameters using $-identifiers (but they are not variable!). The body of the macro is between braces. The white spaces, tabulation and carriage-returns immediately after the open brace and immediately before the closing brace are not part of the macro body.
The following code shows a convenient macro called @makenote
that simulates the Makenote objects in Max/Pd. It creates a
group that contains a note-on with pitch $p
,
velocity $vel
sent to a receive object $name
, and triggers the note-off after duration $d
. The two lines inside the group are Max/PD messages and the group
puts them in a single unit and enables polyphony or concurrency.
@macro_def @makenote($name, $p, $vel, $dur)
{
group myMakenote
{
$name $p $vel
$dur $name $p 0
}
}
The figure below shows the above definition with its realization in a score as shown in AscoGraph. The call to the macro can be seen in the text window on the right, and its realization on the graphical representation on the left. Since Macros are expanded upon the loading of the score, you can only see the expansion results on the graphical end of AscoGraph and not the call.
Notice that in a macro-call, the white-spaces and carriage-returns surrounding an argument are removed. But “inside” the argument, one can use it:
@macro_def @delay_five($x)
{
5 group {
$x
}
}
@delay_five(
1 print One
2 print Two
)
results in the following code after score is loaded:
5 group {
1 print One
2 print Two
}
Macros can accept zero arguments. In this case, there is no list of arguments at all:
@macro_def @PI { 3.1415926535 }
let $x := @sin($t * @PI)
Expansion Sequence¶
The body of a macro @m
can contain calls to other macros,
but they will be expanded after the expansion of @m
. Similarly, the arguments of a macro may contain calls to other
macros, but beware that their expansion takes place only after the
expansion of the enclosing call. So one can write:
@macro_def apply1($f,$arg) { $f($arg) }
@macro_def concat($x, $y) { $x$y }
let $x := @apply1(@sin, @PI)
print @concat(@concat(12, 34), @concat(56, 78))
which results in
let $x := @sin(3.1415926535)
print 1234 5678
The expression @sin(3.1415926535)
results from the
expansion of @sin(@PI)
while 234 5678
results from the expansion of @concat(12, 34)@concat(56,78)
. In the later case, we don’t have 12345678
because
after the expansion the first of the two remaining macro calls, we have
the text 1234@concat(56, 78)
which is analyzed as a
number followed by a macro call, hence two distinct tokens2.
When a syntax error occurs in the expansion of a macro, the location given refers to the text of the macro and is completed by the location of the macro-call site (which can be a file or the site of another macro-expansion).
Generating New Names¶
The use of macro often requires the generation of new names. As an alternative, consider using local variables that can be introduced in groups. Local variables enable the reuse of identifier names and are visible only within their scope.
Howevever, local variables are not always a solution. In this case, there are two special macro constructs that can be used to generate fresh identifiers:
@UID(id)
is substituted by a unique identifier of the form idxxx
where xxx
is
a fresh number (unique at each invocation). id
can be a simple
identifier, a $-identifier or an @-identifier. The token
@LID(id)
is replaced by the idxxx
where xxx
is the number generated by the
last call to @UID(id)
. For instance
loop 2 @name := @UID(loop)
{
let @LID($var) := 0
; ...
superVP speed @LID($var) @name := @LID(action)
}
; ...
kill @LID(action) of @LID(loop)
; ...
kill @LID(loop)
is expanded in (the number used here is for the sake of the example):
loop 2 @name := loop33
{
let $var33 := 0
; ...
superVP speed $var33 @name := action33
}
; ...
kill action33 of loop33
; ...
kill loop33
The special constructs @UID
and @LID
can
be used everywhere (even outside a macro body).
If the previous constructions are not enough, there are some tricks that can be used to concatenate text. For example, consider the following macro definition:
@macro_def @Gen($x, $d, $action)
{
group @name := Gengroup$x
{
$d $action
$d $action
}
}
Note that the character $
cannot be part of a simple identifier. So
the text Gengroup$x
is analyzed as a simple identifier immediately
followed by a $-identifier. During macro-expansion, the text
Gengroup$x
will be replaced by a token obtained by concatenating the
actual value of the parameter $x
to Gengroup
. For instance
@Gen(one, 5, print Ok)
will expand into
group @name := Gengroupone
{
5 print Ok
5 print Ok
}
Another trick is to know that comments are removed during the macro-expansion, so you can use comment to concatenate text after an argument, as with the C preprocessor:
@macro_def @adsuffix($x) { $x/**/suffix }
@macro_def @concat($x, $y) { $x$y }
With these definitions,
@addsuffix($yyy)
@concat( 3.1415 , 9265 )
is replaced by
$yyysuffix
3.14159265
What to choose between macro, functions and processes¶
Capitalizing some code fragment to reuse it several times raises the recurring questions: should we use a macro, a function or a process? The side page Macro versus Function versus Process compares the three mechanisms. If the purpose of the code fragment is to produce a value, in the same instant as the call, then a function should be considered. If it is to perform actions, especially actions that take time, then a process should be considered. In other cases, such as if the code fragment must be parameterized by a variable name, then a macro must be considered.
The last point deserves some development. Consider the following process definition:
let $myVar := 0
@proc_def ::P($x)
{
whenever ($x) {
; do something
}
}
::P($myVar)
; ...
let $myVar := 1
If the intention of the programmer is to activate the whenever
in the process ::P
each time the variable
$myVar
is set, the previous approach is incorrect: the
whenever
in ::P
is activated each time the
local variable $x
is set. When the process is called, the
argument is evaluated and it is the value of $myVar
which
is passed to the process, not the variable itself3. This
is why a process can be called with constant arguments:
::P(0)
With this call, it is apparent that the whenever
in
::P
watches the local variable $x
because
there is no other variable involved.
To achieve the intended behavior, the name of the variable to watch must
explicitly appear in the condition of the whenever
. Macros are handy for that, because they are expanded literally:
let $myVar := 0
@macro_def @P($x)
{
whenever ($x) {
; do something
}
}
@P($myVar)
; ...
let $myVar := 1
is expanded into
let $myVar := 0
whenever ($myVar) {
; do something
}
; ...
let $myVar := 1 ; this time, "do something" will be triggered
which achieves the desired behavior.
-
A recursive macro definition will lead to an infinite expansion. ↩
-
This behavior differs, for instance, from the behavior of the macro-processor used for C or C++ where
1234@concat(56,78)
would have been expansed into12345678
. The difference is that cpp-macro expansion takes place before any parsing, at the raw level of the stream of characters, while Antescofo macro-expansion take place during the parsing, as a phase of the lexical analysis at the level of the stream of tokens. ↩ -
See the side note argument passing strategies for more details. ↩