Reacting to logical events

The whenever statement allows the launching of actions conditionally on the occurrence of a logical condition:

          whenever optional_label ( boolean_expression )
          {
                actions_list
          }

The behavior of this construction is the following: The whenever is active from its firing until its end. In absence of an end clause or of an abort command, the whenever will be active until the end of the program execution.

When a whenever statement is active, each time a variable referred to by boolean_expression is updated, the expression is re-evaluated. If the condition evaluates to true, the body of the whenever is launched.

We stress the fact that only the variables that appear explicitly in the boolean condition are tracked. We say that these variables are watched by the whenever.

The boolean expression can be replaced by temporal patterns which ease the specification of complex events over time.

Nota Bene: multiple occurrences of the body of the same whenever may be active simultaneously, as shown by the following example:

              let $cpt := 0
              0.5 
              loop 1 { 
                 let $cpt := $cpt + 1
              }
              whenever ($cpt > 0) {
                 0.5 a₁
                 0.5 a₂
                 0.5 a₃
              } while ($cpt <= 3)

This example will produce the following schedule:

whenever schedule


Difference between conditional actions and whenever

Notice the difference between a conditional action and a whenever: a conditional action is evaluated once when the flow of control reaches the action while the whenever is evaluated as many times as needed to track the changes of the variables appearing in the condition, between its firing and its end.

The whenever is a way to reduce and simplify the specification of the score, specifically when actions have to be executed each time some condition is satisfied. It also escapes the sequential nature of traditional scores. The actions resulting from a whenever statement are not statically associated to an event of the performer but dynamically at some point in time where a predicate becomes true.

The @immediate attribute

Note that the boolean condition is usually not evaluated when the whenever is fired because the variables that appears in the whenever are usually not assigned in the same instant.

To force the evaluation of the boolean expression when the whenever is fired, one can use the @immediate attribute. This attribute forces the evaluation of the boolean condition when the whenever is fired, in addition to the evaluation caused by an update of the watched variables.

Notice that if a watched variable is set in the same instant as the whenever is fired, it does not necessarily trigger the whenever's body, even if the condition is true: The watched variable must be set after the activation of the whenever. For instance

         whenever W1 ($y) { print "OK whenever 1 at " $NOW }
         let $y := true
         whenever W2 ($y) { print "OK whenever 2 at " $NOW }
         1s
         let $y := true

will print

    OK whenever 1 at 0
    OK whenever 1 at 1
    OK whenever 2 at 1

because whenever W1 is triggered two times (at time 0 and time 1) while W2 is triggered only once: at time 0, it is activated after the first assignment of $yand so cannot be triggered.

Instantaneous action that takes place in the same instant are executed sequentially: this is the synchrony hypothesis. The order of execution within an instant depends on the action priority.

Synchronization Attributes

Because the actions in the body of a whenever statement are not bound to an event or another action, synchronization and error handling attributes of the body's instances are the those of the whenever's activation.

Avoiding Overlapping Instances of a Body

The activation of a whenever fires a new group and two such groups may overlap in time. Sometimes it is necessary to avoid this behavior. It can be done using an explicit abort:

          $last_activation := 0
          whenever (...)
          {
              abort $last_activation
              $last_activation := $MYSELF
              ; ...
          }

which kills the previous instance of the body, if any. The same behavior can be obtained using the @exclusive attribute:

          whenever (...) @exclusive
          {
              ; ...
          }

This attribute may also be used on other compound actions. See also section priority for the management of actions that takes place at the same date.

Stopping a Whenever

An end clause can be defined for whenever. These clauses are evaluated each time the logical condition must be evaluated, irrespective of its or value. For example,

          $X := false
          whenever ($X) { print "OK" $X } during [2 #]
          1.0 $X := false
          1.0 $X := true
          1.0 $X := true      

will print only one OK because at (relative) time 1.0 the body of the logical condition is false, at time 2.0 the logical condition is true, the body is launched and then the whenever is stopped because it has been “tested” two times, i.e. [2 #].

Using a duration in relative time or in absolute time gives the a interval of time during which it is active. When the duration is elapsed, the whenever cannot longer fire its body.

The previous example with logical time shows how to stop the whenever after two updates of $X (whatever is the update). It is easy to stop it after a given number of bodies fire, using a counter in the condition:

          $X := false
          $cpt := 0
          whenever (($cpt < 1) && $X)
          {
               $cpt := $cpt + 1
               print "OK" $X
          } 
          1.0 $X := false
          1.0 $X := true
          1.0 $X := true      

This will print only one OK at relative time 2.0. Then the counter is set to 1 and the condition will always be false in the future.

However, the previous program will still leave the whenever active: the boolean condition is still checked at each update of $cpt or $X. So its is better to use a logical end clause to terminate the whenever

          $X := false
          $cpt := 0
          whenever ($X)
          {
               $cpt := $cpt + 1
               print "OK" $X
          } while ($cpt < 1)
          1.0 $X := false
          1.0 $X := true
          1.0 $X := true

One can also use an abort command.

Watching Restrictions

The whenever watches variables, not values. This means that the construction monitors the updates of the variables that appear in the logical condition. When a variable is updated, the logical condition is (re)evaluated to decide (if true) to launch the whenever's body.

Additionally, the set of watched variables is determined by a syntactic analysis of the boolean condition. Some systems' variables are not managed as ordinary variables and cannot be watched.

These constraints have several consequences that are reviewed below. Their rationale is to ensure that Antescofo scores remain causal and efficiently implementable.

Assignment of a tab

In

        whenever ($t) { ... }
        ; ...
        let $t[0] := ...

the assigment of a tab element does not trigger the whenever even if the tab is referred by a variable that appears in the whenever condition. As a matter of fact, the value of $t is mutable, the assignment mutates this value but the variable assignation: $t always refers to the same value.

Reference to a system variable

The three system variables $NOW, $MYSELF and $THISOBJ cannot be watched by a whenever.

The variable $NOW appears as continuously updated (there is no notion of quantum step in time progression, so watching this variable amount to execute infinitely often the whenever's body, in a finite time interval). Notice that this is not the case for $RNOW which is updated by discrete jumps at each musical events and meaningful actions.

Variables $MYSELF and $THISOBJ are not real variables: they are constants that denote some exec linked to the context where they appear.

Reference to a scoped variable

This limitation is rather subtle. Refer to scoped variable to fully appreciate the code below:

        let $g := {
            @local $x
            $x := false
            whenever U ($x) { print "OK 1" }
            10
            print "end of G"

        }
        whenever V ($g.$x) { print "OK 2" }

        2 let $g.$x := true  ; [1]

The evaluation of this program will print

     OK 1
     end of G

because V does not watch the local variable $x in the group denoted by $g. Only U is triggered by the assignment [1].

As a matter of fact, the variable watched by V is restricted to $g: in the expression $g.$x, $x is only a name, not a variable. The determination of the variable denoted by expression $g.$x is dynamically computed and may change when $g is updated. The set of watched variables is statically determined. Dynamically changing this set is considered too costly and is not managed in the current Antescofo version.

One Activation per Instant

The variables watched by a whenever can be updated several times in the same instant. Howevever, the whenever is fired at most once, with the first update that leads the condition to evaluate to true. For example:

        $a := false
        $b := false
        $c := false

        1
        whenever( $a || $b || $c)
        {
             print WHENEVER activated at $NOW $a $b $c
        }

        1
        $a := false
        $b := true
        $c := true

will print only

    WHENEVER activated at 1.0 false true false

because the wehever is activated at most once per instant, as soon as possible. It is possible to override this behaviour, see sect. Automatic Temporal Shortcut Detection below.

Causal Score and Temporal Shortcuts

The actions triggered when the body of a whenever is fired, may fire other whenever, including itself directly or indirectly. Here is an example:

          let $x := 1
          let $y := 1
          whenever W1 ($x > 0) 
          {
               let $y := $y + 1
          }
          whenever W2 ($y > 0) 
          {
               let $x := $x + 1
          }
          let $x := 10 @label Start

When action Start is fired, the body W1 of is fired in turn in the same logical instant, which leads to the firing of the body of W2 which triggers W1 again, etc. So we have an infinite loop of computations that are supposed to take place in the same logical instant:

\qquad\qquad Start \rightarrow W1 \rightarrow W2 \rightarrow W1 \rightarrow W2 \rightarrow W1 \rightarrow W2 \rightarrow \quad\dots

This instantaneous infinite loop is called a temporal shortcut and corresponds to a non causal score. The previous score is non-causal because the variable $y depends instantaneously on the updates of variable $x and variable $x depends instantaneously of the update of the variable $y.

The situation would have been much different if the $y assignments had been made after some delay. For example:

          let $x := 1
          let $y := 1
          whenever W1 ($x > 0) 
          {
               1 let $y := $y + 1
          }
          whenever W2 ($y > 0) 
          {
               1 let $x := $x + 1
          }
          let $x := 10 @label Start

also generates an infinite stream of computations but with a viable schedule in time. If is fired at 0, then is fired at the same date but the assignment of will occurs only at date 2. At this date, the body of is subsequently fired, which leads to the assignment of at date 3, etc.

\qquad \quad 0: Start \rightarrow W1 \rightarrow
\qquad \quad 1: $y := 1+1 \rightarrow W2 \rightarrow
\qquad \quad 2: $x := 10+1 \rightarrow W1 \rightarrow
\qquad \quad 3: $y := 2+1 \rightarrow W2 \rightarrow
\qquad \quad 4: $x := 11+1 \rightarrow W1 \rightarrow
\qquad \quad 5: \dots

Automatic Temporal Shortcut Detection

Antescofo automatically detects temporal shortcuts and stops the infinite regression. This behavior is a consequence of the rule “whenever are activated at most once per instant” and no warning is issued.

Notice that this rule is a sufficient condition to prohibit temporal shortcuts, but it is not a necessary condition. Some times it is desirable to allow several activations of a whenever in the same logical instant, whithout entailing an infinite number of activations. For these (rare) occasion, the attribute @override can be used to bypass the “at most one activation per logical instant” rule. Beware that using this attribute may lead to infinite loops.

For example

         $cpt := 0
         whenever ($x) @override
         {
             $cpt += 1
         }
         $x := true
         $x := true
         print $x         

will print 2. Without the @override attribute, the program prints 1.