Aborting and Cancelling an Action

An atomic action takes “no time” to be processed. So, aborting an atomic action is irrelevant: the action is either already fired or not fired. On the other hand, Compound Actions act as containers for others actions and thus span over a duration. Only compound actions can be aborted; however, the action of aborting is atomic (occurs in an instant)1.

We say that a compound action A is active when it has been fired and some of its nested actions are still waiting to be fired. Notice that the notion of activity encompass an action and its children: a compound action A can have finished its own work but can be still active because some of its child actions are still performing their tasks. Aborting an action may have a visible effect only while it is active.

Cancelling an action refers to another notion: the dynamic suppression of an action from the score. This feature is deprecated since version 0.9: a conditional action can often be used instead and is at the same time more expressive and more efficient.

Aborting an Action

After a compound action has been launched, it can be aborted, meaning that the nested atomic actions that remain will not be fired.

There is no harm to abort an non-active compound action. So an abort command can be issued multiple times and there is no need to check that the target action is till active.

There are two forms of termination:

          abort name 
          abort expression

where name is the label of an action and expression evaluates to an Exec.

Termination through a label

If the abort's argument is a label, then active actions with that label (and their children) will be aborted. The command has no effect on atomic actions or compound actions with no children.

Above, the plural is used ("active actions") because one label can be shared by several distinct actions: in this case, all active actions labeled by name are aborted together. One action can also occur several times (e.g. the body of loop, or the body of a whenever statement). All occurrences of an action labeled by name are aborted.

The command also accepts the name of a process as argument. In this case, all active instances of this process are aborted.

Termination through an exec

The argument of abort can be an expression. In this case the expression is evaluated and must return an Exec. Only this exec will be terminated by the command.

Abort and the hierarchical structure of compound actions

By default, the abort command applies recursively on the whole hierarchical structure of actions (cf. section Compound actions), even if some actions in the path are not active anymore.

Notice that the actions launched by a process call in a context C are considered as descendants of C.

The attribute @norec can be used to abort only the top level actions of the compound. Here is an example:

      group G1 {
            1 a₁
            1 group G2 {
                    0.2 b₁
                    0.5 b₂
                    0.5 b₃
            1 a₂
            1 a₃
      2.5 abort G1

The action abort takes place at 2.5 beats after the firing of G1. At this date, actions a₁ and b₁ have already been fired. The results of the abort is to suppress the future firing of a₂, a₃, b₂ and b₃.

If line 11 is replaced by

          2.5 abort G1 @norec      

then, actions a₂ and a₃ are aborted but not actions b₂ and b₃ which will be performed.

Abort handler

Abort commands can be issued from eveywhere in the code, making difficult to express some dedicated actions to do when the compound action is terminated, actions that are not needed when the compound action reaches its natural end2.

A direct implementation of this behavior is provided by abort handlers. An abort handler is a group of actions triggered when a compound action is terminated by an abort. Abort handlers are specified using an @abort clause with a syntax similar to the syntax of the @action clause of a curve:

      CompoundAction ... @abort := { ... }

An handler can be defined for all compound actions. The scope of the handler is the scope introduced by the compound actions (if any): local variables introduced eventually by the compound action are accessible in the handler.

When a handler associated to a compound action is spanned by an abort command, the handler cannot be killed by further abort command (in other words, abort handler cannot be aborted).

Notice that abort commands are usually recursive, also killing the children spanned by the abort target. If these children have themselves handlers, they will be triggered when terminating the target. However, the order of activation is not specified and can differ from one execution to another.

A Paradigmatic Example

A good example of the use of abort handlers is given by a curve that samples some parameter controlling the synthesis of a sound. On some event, the synthesis must be stopped, but this cannot be done abruptly: the parameter must go from its current value to some predetermined value, e.g. 0, in one beat. This is easily written:

          Curve C 
             @grain := 0.5
             @action := { print "curve: " $x }
             @abort := { 
                  print "Curve C aborted at " $x
                  Curve AH
                     @grain := 0.2 
                     @action := { print "handler curve: " $x }
                    $x { { $x } 1 { 0.0 } }
            $x { { 0.0 } 10 { 10.0 } 10 { 0.0 } }

When an abort is issued, the curve is stopped and the actions associated to the abort handler are launched. These actions create a new curve with the same control variable $x, starting from the current value of $x to 0.0 in one beat. A typical trace is (the command is issued at 1.5 beats):

      print: curve:  0.
      print: curve:  0.5
      print: curve:  1.
      print: curve:  1.5
      print: Curve Aborted at  1.5
      print: handler curve:  1.5
      print: handler curve:  1.2
      print: handler curve:  0.9
      print: handler curve:  0.6
      print: handler curve:  0.3
      print: handler curve:  0.

Controlling the activation of abort handler

Issuing an abort does not always trigger the abort handler:

  • If the action target of the abort has not been fired, its termination does nothing and does not trigger the execution of its abort handler.

  • If the action A has been fired and is still running (e.g. waiting some delay before triggering some sub-actions), the abort command will stop the execution of A and triggers the abort handler. By default, the abort is propagated to the childs and their abort handlers are eventually triggered. If the @norec attribute is specified on the abort command, the abort is not propagated to the childs (and so their abort handler are not triggered).

  • If the action A has been fired, has finished its work but is still active (because some of its childs are still runing), then its abort handler is triggered unless the attribute @rec_if_alive is specified on the abort command. With this attribute, the handler of a finished but active action is not triggered. However, if the abort is recursive (the default), the abort signal is correctly passed to the (active and non active) childs, until reaching the leaves of the hirerachy.

  1. Because termination is an atomic action, the abort command is presented here but its understanding presumes some knowledge on compound actions

  2. This effect can be achieved without abort handlers by wrapping the actions to perform in case of termination in a whenever that watches a dedicated variable. The whenever is then triggered by setting this variable to the boolean value true immediately after the abort command. This approach becomes cumbersome when the actions launched on termination have to access variables that are local to the aborted action, when dealing with multiple compound actions and, furthermore, scatter the code in several places.