Macros

clock mechanism

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.

Example of a Macro and its realisation upon score
 load

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.



  1. A recursive macro definition will lead to an infinite expansion. 

  2. 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 into 12345678. 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. 

  3. See the side note argument passing strategies for more details.