Curve (continuous action)¶
Many computer music controls are by nature continuous. Curves in Antescofo allow users to define such actions and to delegate the rest of the hard work to Antescofo, which takes care of correct arrival and interpolations between parameters. The construction allows for the definition of continuously sampled actions on break points and detailed control of the interpolation between them. Curves are defined by a sequence of break points and their interpolation methods along with specific attributes. As time passes, the curve is traversed and the corresponding action fired at the sampling point. Curves can be scalar (onedimensional) or vectorial (multidimensional).
We introduce Curves^{1} starting with a simplified and familiar syntax of linear interpolation and move on to the complete syntax and showcase details of Curve construction.
Simplified Curve Syntax¶
The simplest continuous action to imagine is the linear interpolation of a scalar value between a starting and ending point with a duration, similar to line objects in Max or Pd. The timestep for interpolation in the simplified curve is 30 milliseconds and hardcoded. This can be achieved using the simplified syntax as shown in
Curve level 0.0, 1.0 2.0 s
In this example, the action constructs a line starting at 0.0, going
to 1.0 in 2.0 seconds and sending the results to the receiver object
level
. The initial point 0.0 is separated by a comma from the
destination point. The destination point consists of a destination value
(here 1.0) and the time to achieve it (2.0s in this case). At each
sampling point x, a message level x
is sent to the
environment.
Another facility of Simplified Curves is their ability to be chained.
The score excerpt below shows the score where a second call to
curve level
is added on the third note. This new call
does not have a starting point and only has a destination value:
Curve level 0.5 1.0
Both Curves also act on the same receiver level
. This means that
during performance, the second curve will take on from whatever value of
the prior curve and arrives to its destination (here 0.5) at the given
time (here 1.0 beat).
Note that the second curve in the figure above cannot be visualised by Ascograph. This is because its starting point is a variable whose value is unknown and depends on where and when the prior curve arrives during performance. Moreover, by calling simplified curves as above you can make sure that the first curve does not continue while the second is running. This is because of the way Simplified Curves are hardcoded. A new call on the same receiver/action will cancel the previous one before taking over.
The reason for the malleability of Simplified Curves is because they store their value as a variable. A new call on the same receiver aborts prior calls and takes the latest stored value as departing point if no initial point is given. You can program this yourself using the complete curve syntax.
The simplified curve is thus very similar to line
object in Max or PD.
That said, it is important (and vital) that the first call to
Simplified Curve have an initial value. Otherwise, the departing point is
unknown and you risk receiving NaN values (notanumber) until the
first destination point!
To summarize, starting a simplified curve is written:
curve max_receiver starting_point , final_point duration
and chaining a simplified curve is written:
curve max_receiver final_point duration
where cexp
are closed expressions.
The action fired at the sampling point of a simplified curve is
restricted to be a message with only one argument: the sampled
value. Replacing the receiver with a bracketed @command
construct, it is possible to have more general messages and even to
compute the receiver of the message:
curve @command { receiver arg₀ arg₁ } starting_point , final_point duration
where receiver
and the argᵢ
are closed
expressions, will send the message:
@command(receiver) arg₀ arg₁ x
for each sampling point x
of the curve (see the
@command keyword for computing the receiver of a message). The chained
version is similar:
curve @command { receiver arg₀ arg₁ } final_point duration
The simplified command hides several important properties of Curves from users and are there to simplify calls for simple linear and scalar interpolation. For example, the timestep for interpolation in the above curve is 30 milliseconds and hardcoded. A complete curve allows for adjustment of such parameters, several kinds of multidimensional interpolations, complex actions, and more. From here we will detail the complete curve syntax.
Full Curve Syntax¶
A curve iterates a sequence of actions (specified with the @action attribute) on each sampling point (defined by the @grain attribute) of a piecewise function. The piecewise function is defined by multiple subfunctions, each subfunction applying to a certain interval defined by breakpoints. Between two breakpoints, the function is defined by an interpolation type, the delay between the breakpoint and the value of the general function at the breakpoints.
Piecewise functions defined this way are also called breakpoint functions or BPF. They are implemented in Antescofo as nim. Nim offers a powerful data structure to compute Piecewise functions and the curve construction acts as a nim player by sampling the nim in time.
In this section, we mainly discuss the curve construction that directly embeds the specification of the underlying BPF.
Scalar Curve¶
The example below shows a simple curve defined in two pieces. The
Curve
has the name C
and starts at a value
of 0
. Two beats later, the curve reaches 2
and ends on 4
after 8
additional
beats. Between the breakpoints, the interpolation is linear, as
indicated by the string "linear"
after the keyword
@type
. Linear interpolation is the default behaviour of
a curve (hence it can be dismissed).
curve C @action := { level $a }, @grain := 0.1 { $a { { 0 } @type "linear" 2 { 2 } @type "linear" 8 { 4 } } }
In the above example, variable $a
, called the curve
parameter, ranges over the curve. Its value is updated at a timerate
defined by attribute @grain
which can be specified in
absolute time or in relative time. Each time $a
is
updated, the @action
sequence, which can refer to
$a
, is triggered.
Vectorial Curve¶
It is easy to apply curves on multidimensional vectors as shown in the following example:
curve C { $x, $y, $z { { 0, 1, 1 } 4 { 2, 1, 0 } 4 { 1, 2, 1 } } }
curve C { $x { { 0 } 2 { 0 } 0 { 1 } 3 { 1 } } $y { { 1 } 3 { 2 } } }
In the above example, curve parameters $x
and
$y
have different breakpoints. The breakpoint definition
on $x
shows how to define a sudden change on a
stepfunction with a zerodelay value. Incidentally, note that the
result is not a continuous function on [0, 5]. The parameter is
defined by only one pair of breakpoints. The last breakpoint has its
time coordinate equal to 3, which ends the function before the end of
$x
.
The figure [1] below shows a simple 2dimensional vector curve on
Ascograph. Here, two variables $x
and $y
are used to sample the curve and are referenced in the action. They
share the same breakpoints but can be split within the same curve. The
curve is also aborted at event2
when the abort command
is called with the curve's name.
Editing Curve with Ascograph¶
Using Ascograph, you can graphically interact with curves, as long as the curve parameters are constant. If this is the case, it is possible to move breakpoints vertically (changing their values) and horizontally (time position) by mouse, assigning new interpolation schemes graphically (controlclick on breakpoint), splitting multidimensional curves and more.
For many of these operations on multidimensional curves, each coordinate should be represented separately. This can be done by pressing the button on the Curve box in which will automatically generate the corresponding text in the score. Each time you make graphical modifications on a curve in Ascograph, you need to press APPLY to regenerate the corresponding text.
The figure below shows the curve of the previous example [1] embedded on the event score, split and in the process of being modified by a user.
In the following sections we will get into details of Curve attributes: namely @action, timing @grain, and interpolation methods [@type]. But before that, we will describe the textual syntax. Knowing the textual syntax is important when defining curve whose parameters are defined by full expressions.
Textual Definition of a Full Curve¶
Curve Attributes¶
NIM player¶
Breakpoints¶
Curve Interpolation Type¶
Actions Fired by a Curve¶
Each time a parameter is assigned, the action specified by the attribute @action is also fired. The value of the attribute is a sequence of actions. Usually, it is a simple message but arbitrary actions are allowed, for instance :
curve C @action := { print $y 2 action₁ $y 1 action₂ $y } { ... }
At each sampling point, the value of $y
is immediately
sent to the receiver print
. Two beats later action₁
will be fired and one beat after that action₂
will be fired.
This sequence of actions is an implicit group and cannot have attributes, but a group can be nested for that end:
curve C @action := { Group @tempo := 120 { print $y 2 action₁ $y 1 action₂ $y } } { ... }
If the @action attribute is absent, the curve simply assigns the parameters specified in its body. This can be useful in conjunction with other parts of the code if the parameters are refered in expressions or in other actions. In the next example, a curve is used to dynamically change the tempo of a loop
Curve @grain := 5ms { $x { {60} 10 {120} } } Loop 1 @tempo := $x { print loop $NOW } until ($x >= 105)
will print:
0.0 0.954669 1.83255 2.64963 3.41704 4.14287 4.83321 5.49282 6.12547 6.73421 7.32156
Grain, Duration and Breakpoints Specifications¶
In the previous example, the time step (the sampling rate of the curve)
called grain size and specified with the @grain
attribute, is expressed in absolute time while breakpoints' durations
are expressed in relative time. However, durations and grains can be
freely expressed in absolute or relative time.
The grain size can be as small as needed to achieve perceptual continuity. However, in the MAX/PD environments, one cannot go below 1ms (the temporal precision of the host).
The grain specifies only a maximal duration between two sampling points. This freedom is used by Antescofo to ensure that the actions fired by the curve will be fired for each breakpoints boundaries.
Grain size and duration, as well as the values at breakpoints, can be closed expressions too. Grain size is evaluated at each sampling point, which makes it possible to change dynamically the time step.
The values of the breakpoint are evaluated once: when the curve is fired.
Curve Playing a NIM¶
A single value can be used as an argument of the curve parameter. In this case, the expression is expected to evaluate to a NIM, allowing the user to dynamically build breakpoints and their values as a result of computation. The syntax has already been described:
Curve ... { $x : e }
defines a curve where the breakpoints are taken from the value of the
expression e
. This expression is evaluated when the curve
is triggered and must return a nim value. The NIM is used as a
specification of the breakpoints of the curve. Notice that, when a NIM
is “played” by a curve, the first breakpoint of the NIM coincides with
the start of the curve.
For example
$nim := NIM { ... } ; ... Curve @tempo := 30, @grain := 0.1s, @action := { print $x } { $x : $nim }
Any expression can be used which evaluates to a NIM. So, the following code plays a random NIM taken in a vector of 10 NIMs:
$nim1 := NIM { ...} $nim2 := NIM { ...} ; ... $nim10 := NIM { ...} $tab := [ $nim1, $nim2, ..., $nim10 ] ; ... Curve @tempo := 30, @grain := 0.1s, @action := { print $x } { $x : $tab[@rand(11)] }
A typical situation is to play a NIM chosen from a repertoire of NIMs in
a specified time interval $dur
. In this case, directly
playing the NIM with the curve is not appropriate, because the NIM will
be played with its natural length. Fortunately, processes like
NIMplayer
make the desired behavior easy to code.
$Nim1 := NIM { 0. 0.,0.05 1 "quad", 0.1 0.2 "quad_out", 0.85 0. "cubic" } $Nim2:= NIM { 0. 0.,0.05 1., 0.9 1., 0.05 0. } @proc_def ::NIMplayer($NIM, $dur) { curve readNIM @grain := 0.02s, @action := { print ($NIM($x)) } { $x { { (@min_key($nim)) } $dur { (@max_key($nim)) } } } } NOTE 69 4 ::NIMplayer($Nim1, 4)
The playing of the NIM is controlled by a curve. The functions
@min_key and @max_key are used to get the definition interval of the
nim $nim
.
Interpolation Methods¶
The specification of the interpolation between two breakpoints is given
by an optional string. The keyword @type
is mandatory
only when a variable is used to specify the interpolation type. Using a
variable makes it possible to compute the interpolation type, e.g.
when a curve is embedded in a process and there is a need to
parameterize the interpolation type.
A linear interpolation is used by default. Antescofo offers a rich set of interpolation methods, mimicking the standard tweeners used in flash animation^{2}. There are 10 different types:

linear, quad, cubic, quart, quint: which correspond to polynomial of degree respectively one to five;

expo: exponential, i.e. \alpha e^{\beta t + \delta} + \gamma

sine: sinusoidal interpolation \alpha \sin(\beta t + \delta) + \gamma

back: overshooting cubic easing (\alpha+1) t^3  \alpha t^2

circ: circular interpolation \alpha \sqrt{(\beta t + \delta)} + \gamma

bounce: exponentially decaying parabolic bounce

elastic: exponentially decaying sine wave
With the exception of the linear type, all interpolations types come in three “flavors” traditionally called ease:

in (the default) which means that the derivative of the curve is increasing with the time (usually from zero to some value),

out when the derivative of the curve is decreasing (usually to zero),

and in_out when the derivative first increases (until the midpoint of the two breakpoints) and then decreases.
The corresponding interpolation keywords are listed below and illustrated in the next figures. Note that the interpolation can be different for each successive pair of breakpoints. These interpolation methods are also available for NIM (but NIM includes a richer set of interpolation types).
"back" "back_in" "back_in_out" "back_out" "bounce" "bounce_in" "bounce_in_out" "bounce_out" "circ" "circ_in" "circ_in_out" "circ_out" "cubic" "cubic_in" "cubic_in_out" "cubic_out" "elastic" "elastic_in" "elastic_in_out" "elastic_out"
"exp" "exp_in" "exp_in_out" "exp_out" "quad" "quad_in" "quad_in_out" "quad_out" "quart" "quart_in" "quart_in_out" "quart_out" "quint" "quint_in" "quint_in_out" "quint_out" "sine" "sine_in" "sine_in_out" "sine_out"
Programming an Interpolation Method.¶
If your preferred interpolation method is not included in the list above, it can be easily programmed. The idea is to apply a user defined function to the value returned by a simple linear interpolation, as follows:
@fun_def @f($x) { ... } ... curve C @action := { print (@f($x)) }, @grain := 0.1 { $x { { 0 } @linear 1s { 1 } } }
The curve will interpolate function @f
between
0
and 1
after it starts, over the course
of one second and with a sampling rate of 0.1
beats.
Examples of Interpolation Types¶
In the pictures below:

The label
xxx[0]
corresponds to the ease in, that is to the type"xxx_in"
or equivalently"xxx"
. 
The label
xxx[1]
corresponds to the ease out, i.e. the interpolation type"xxx_out"
. 
And the label
xxx[2]
corresponds to the ease in_out, i.e. interpolation method"xxx_in_out"
.
Open the imge on another window to enlarge the plot.
Curve Synchronization¶
Synchronization attributes apply to curve. To understand the effect of synchronization strategies on curve, it is usefull to understand the curve as a group whose actions are the curve's action iterated at each sampling point:
curve C @grain := d @action := { ... actionᵢ ... } { $x { {start} ... {end} } }
is really a shorthand for^{3}:
Group G { $x := start { ... actionᵢ ... } d $x := ... { ... actionᵢ ... } d $x := ... { ... actionᵢ ... } ... d $x := end { ... actionᵢ ... } }
The synchronization attributes simply apply to this group.

Curves can be edited graphically using the Ascograph editor. ↩

Inbetweening or tweening is the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image. The page Tweeners illustrates the standard tweens to control the successive positions of a point, illustrating the use of tweens to control the apparent speed and to achieve different qualities of movement. ↩

The equivalent group given here is only an approximation because the grain
d
is dynamically computed and adjusted so that curve's action is executed for each breakpoint boundaries (breakpoint's duration are not necessary a multiple of the grain size). ↩