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:
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 $y
and 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
.