< Back to IRCAM Forum

Macros with arguments

Bonne année à tous!

Curves are a great feature in Antescofo (and I love the built-in interpolations). However, a score becomes easily unreadable with a lot of curves. So I tried to use a macro. It seems that I actually don’t understand how arguments are expanded in the score and how macro expansion affects the scope of local variables. The curves from my macro, used to drive the levels of harmonizer voices, sound harsh and rippled; and Ascograph plots them differently on every reload, without any change in the score, as if there is a secret randomisation in play. Aren’t curve variables local to the scope of the curve? Is it thus necessary to use the @UID @LID construct for curve variables within a macro?

Hello Kyl.

Macros do not introduce local variable per se: it depends of what is expanded. And Curve do not introduce new variables. So, if you write a macro:

@macro_def MyCurve($start, $end, $dur)   
{  
     Curve @grain (($end - $start)/100)  
     @action = {   
          ; ... some action involving $x  
     }  
     {  $x { {$start} ($dur) { $end } } }  
}

the $x variable will refer to the $x which is in the scope of the macro call. For instance

@MyCurve(0, 1, 10)  
Group {  
   @local $x  
   @MyCurve(10, 20, 2)  
}

In the first invocation, $x will refer to a global variable (global variable are implicitly defined), and in the second invocation, the curve refers the local variable $x introduced by the group.

If two curves overlap in time and use the same sampling variable, this variable will be updated by both curve, which usually results in unexpected results (for instance, the action of one curve may be evaluated with the value given by the other curve, if the two curve are sampled at the same instant).

If you want to uses a variable only for sampling a curve, put the curve in a group that introduces this local variable:

@macro_def MyCurve($start, $end, $dur)   
{  
     Group   
     {  
          @local $x  
          Curve @grain (($end - $start)/100)  
          @action = {   
             ; ... some action involving $x  
          }  
         {  $x { {$start} ($dur) { $end } } }  
     }  
}

In this way you are assured that each macro-expansion refers to a local $x. You can use a @UID and @LID to generate new names and achieve the same effect, but it is more cumbersome.

You can also uses processes instead of macros.

Merci, JLG, for the explanation. I rewrote the macro declaring all variables local, yet the graphical representation in Ascograph still didn’t match the breakpoints specified in the macro invocation (and also given in the pop-up pane when the mouse is over the white bulb which stands for the group state). The curves drawn by Ascograph also still changed randomly on every reload. Beside the variable used to get the curve value I used some other variables in the surrounding group. These variables obviously confused Ascograph (I believe to be up-to-date with version 0.2). Actually, there wasn’t a real purpose for the use of variables here – I had introduced them while debugging. The problem disappeared when I removed all local variables except for the curve variable, i.e. replaced them by expressions in the curve definition. It seems problematic to store the result of a computation on an argument to a macro in a variable which is used in a curve definition, be it global or local.

Hi! At the moment, whenever you use a variable AscoGraph is unable to obtain its value (that would require at least a partial execution/simulation of the code – that’s on the “to do” list). The solution (at least for now) is, as you noticed, to generate a random value, indicating that we’re dealing with a dynamic value.

Otherwise, with earlier versions of AscoGraph, such a curve wouldn’t have been drawn at all.

(all of this of course applies just to the visualization; the curve should be correctly executed at run-time)

Hello Kyl,
Grig outlines that the visualization of a curve is meaningful only in the case where the curve parameter are statically known (i.e. the parameter are given as constant in the code). You mention that your problem disappear when

I removed all local variables except for the curve variable, i.e. replaced them by expressions in the curve definition. It seems problematic to store the result of a computation on an argument to a macro in a variable which is used in a curve definition, be it global or local.

This gives me the occasion to clarify two technical points.

The first is the status of the variables denoting a macro argument: these ‘variables’ are not antescofo variables. They do not exists at execution time. They are just aliases used to denote the text fragment used in the macro-expansion. So, you do not store the result of a computation in a macro argument: rather the expression in argument is evaluated at every occurence of the parameter in the macro body. For instance, in

@macro_def twice ($x) { $x + $x }  
$a := @twice(2*$b)

The right hand side of the assignment will be expanded into 2*$b + 2*$b. Notice that the expression 2*$b is then evaluated two times. This mechanism is very different from the argument passing mechanism in macro or function where the actual argument is evaluated once and its value is stored in the parameter. For processes, process parameters are local variables. For function, parameters are not Antescofo variable: Antescofo variables are persistent in time and they can be watched by a whenever, while a function parameter is ‘instantaneous’.

Notice that these considerations do not prevent to have arbitrary complex expressions in the parameter of a curve. The breakpoints of a curve are evaluated only once, when first needed, so there is no difference with storing the breakpoints parameters in variables and using these variables in the curve specification instead of the expressions.

The second point is that Antescofo is good in constant propagation. This name refers to a mechanism which evaluates at load time (i.e. when the score is read) all expressions that are constant (i.e. expressions whose values do not change at run-time). For example, with the previous definition of @twice, the assignemnt:
$c := @twice(23)
will be expanded into
$c := 12
because the expression 23 + 2*3 is a constant expression and can be safely evaluated at score-loading time. This mechanism is used in Antescofo to optimize the computations and to minimize the impact of expression evaluation at run-time.

Constant propagation explains that you can have an expression as a parameter of the curve and yet the curve can be statically known (if the involved expressions are constant expressions).

Determining if an expression is constant can be hard (that is to say, costly or even impossible). For example, if the expression involves a variable, it is necessary to known statically the value of the variable at the date of the expression evaluation. So Antescofo detects only a subset of the constant expressions of the program, relying on simple syntactic constraints. An expression involving a variable is not considered as ‘constant’.

Thank you very much for this clarification of the differences between constants, expressions, variables and arguments/parameters. A real-time system like Antescofo and its language resembles in many aspects concurrent computing. Here and there it is essential to understand the timing of “memory objects”…