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 (one-dimensional) or vectorial (multi-dimensional).
We introduce Curves1 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 time-step for interpolation in the simplified curve is 30 milli-seconds and hard-coded. 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 hard-coded. 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 (not-anumber) 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 time-step for interpolation in the above curve is 30 milli-seconds and hard-coded. A complete curve allows for adjustment of such parameters, several kinds of multi-dimensional 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 sub-functions, each sub-function 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 time-rate
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 multi-dimensional 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
step-function with a zero-delay 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 2-dimensional 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 (control-click on breakpoint), splitting multi-dimensional curves and more.
For many of these operations on multi-dimensional 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¶
The body of the curve, the part between braces, takes various forms:
-
a sequence of explicit breakpoint specifying the value of the function at the breakpoints and the interpolation type between breakpoints (as exemplified previously);
-
sequence of symbolic breakpoints where the interval bteween breakpoints, the value at each breakpoints and the intrepolation type is not precisely given (only a general pace: increasing, decreasing, etc. is specified);
-
a nim which is value that represents a function (this value can be computed elsewhere);
-
a set of differential equations (this experimental feature is described in the next chapter).
Even if the specification of the body takes several forms, the principe of the curve is the same: the idea is to sample in time a function defined through a way or another.
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