Event's datation¶
Usually, delays are used to sequence actions. However, it is also possible to trigger
actions at specific dates (or beat positions). See date delays for details. Dates are indicated using the syntax
§xxx
, where xxx
is a numerical value.
A similar mechanism allows specifying musical events by date rather than duration. This makes it possible to define scores as timelines, which can be very convenient.
Musical events specified by dates are internally rewritten by the compiler into events with durations. The transformed score can be inspected using the commands printscore or printfwd. If unexpected behaviors occur, referring to this normalized version of the score can be useful. Ultimately, this normalized score is what Antescofo actually executes.
Important considerations:
-
While delays and dates for actions can be arbitrary expressions, dates and durations for events must be numeric. These values are computed at compile time, and no runtime computation is performed.
-
Specifying an event by date implicitly defines the event's duration according to specific rules (see below). Sometimes, implicit silences may be inserted, which will be explicitly shown in the normalized score.
Events with Durations¶
An explicit date (beat position) or duration can be set when defining musical events (e.g., NOTE, CHORD, TRILL).
Dates use the syntax §10
, indicating a position in beats relative to the current section's start.
Events defined with a date or with a duration can be freely intermixed.
Sections¶
Sections are introduced with the directive:
§new_section §3.5 section_name
Here, §3.5
is a date that specifies the start beat of the new section relative to the previous section's beginning. If there are no previous sections, dates are relative to the start of the score. If no sections are defined, all event dates are relative to the start of the score.
Internally, sections are converted into abstract events:
EVENT 7 section_name
7 is the duration if this event. See below for the computation of its duration.
Sequentiality¶
The order of events in the score determines their chronological order. Antescofo computes the dates sequentially, and if an event is found out of chronological order, it generates an error. For instance:
NOTE C1 §3
CHORD (A3 B3) §1
This raises an error because events are out of chronological order.
Calculating Event Durations from Dates¶
For events or sections specified by date §D
, their durations are calculated as follows:
- If the next event is specified by duration, the current event’s duration is
0
. - If the next event is specified by date
§D'
, the current event’s duration is§D' - §D
.
Filling Gaps with Silence¶
Consider the following example:
NOTE D2 §0 "first event"
NOTE C3 2 "second event"
NOTE E5 $3 "third event"
Following the previous rules, this score introduces a gap between the second event (ending at beat 2) and the third event (starting at beat 3). Antescofo automatically inserts a silence to fill this gap, issuing a warning about this implicit insertion.
Sections' Effects on Durations¶
When an event with a date precedes a §new_section §D
, it is treated as if followed by an event with date §D
(sections are abstract event).
If the score ends with an event specified by date, its duration defaults to zero. To explicitly mark the end of the section, use:
§end §33
Here, §33
specifies the end beat position of the section. The duration of the previous event is adjusted accordingly. If necessary, an implicit silence fills the remaining time.
Internally, all scores with dates are converted to scores with explicit durations, as required by the listening engine.
Example: Automatic Timeline with Interruptions¶
The companion file exemple_event_date.asco.txt provides an example using events specified by dates to represent a timeline.
In performances, score progression may be automatic (as in "play" mode), but sometimes must pause at defined points, waiting for user input to resume. While playto
and playfrom
commands can simulate this behavior, they depend on fast-forwarding to specific points, potentially interrupting ongoing computations.
The proposed solution uses a custom process ::PlayUpTo(stop)
to emulate smooth progression from the current position to a designated stopping point without fast-forwarding interruptions. The companion file includes comments detailing the implementation of this approach.
We comment the implementation of this process.
// The score following is not used
antescofo::suivi 0
// There is no tempo inference. It means that the tempo used
// at run time comes directly from the BPM specification in
// this score. However it is always possible to alter this tempo
// at run time, using the command 'antescofo::tempo' in an action.
tempo 0
// The processus ::PlayUpTo() will issue a series of nextevent to
// make the computation proceeds, up to the event with label $stop
// included.
//
// The @exclusive means that only one such process can be run at
// the same time. This is a security to avoid an overlap betwwen two
// occurence of this process (the former is killed before launching the new one).
@proc_def PlayUpTo($stop) @exclusive
{
// If we are runing using the play mode, or in fastforward mode,
// do nothing: there is already a 'player' that takes care of the
// progression.
if (@is_playing_on() || @is_fastforward()) { abort $MYSELF }
// Function @event_label_position("label") returns the position of the
// (start of the) event labelled with label". This function works also
// for the label used for the sections (sections are abstract events).
//
// Function @make_all_events_tab() returns a description of the score
// in the form of TAB (a vector). Each entry is a TAB that describes
// one musical event in the score. An entry is a 10-element TAB listing
// in this order:
// (0) the position of the event in beat (its onset)
// (1) the kind of the event, coded as an integer:
// 0 = Silence, 1 = Note, 2 = Chords, 3 = Trill, 4 = Multi,
// 5 = Event, (i.e. abstract event introduced by the keyword EVENT)
// 6 = Section (which is a special kind of Event)
// (2) the duration in beat of the event
// (3) the current tempo for this event in SPB (seconds per beat), i.e. a
// tempo of one means 60 BPM and a tempo of 0.5 means 120 BPM.
// (4) the list of pitches specified by the event
// (5) its label
// (6) its rank in the sequence of musical event. The is an implicit event
// at rank 0, a silence, that starts every score. This event is the
// anchor of the actions that precedes the first event specified by the
// programmer
// (7) a boolean which is true only if this event is an abstract event used
// to represent the start of a section.
// (8) boolean which is true only if the event is an implicit silence used
// to fill a gap between two events
// (9) a boolean which is true only if the event is an implicit silence used
// to fill gap between and event and a section.
//
// The arguments of @make_all_events_tab() are used to describe an
// interval of the score. In the form used here:
// @make_all_events_tab(start, false, stop, true)
// argument start represents the index of the event starting the sequence
// of musical events in the description; and stop is given as a position.
// Index start is included and stop is excluded.
// The use of an index is necessary because several musical event can share
// the same position in beats (if they have a zero duration).
//
@local
$endpos := @event_label_position($stop),
$part := @make_all_events_tab(@current_event(), false, $endpos, true)
// if we pass a bad agument, $part is undef and we leave.
if (!$part.is_tab())
{
@print("====>> ERROR unknown label", $stop, "<<====")
abort $MYSELF;
}
// Function @print is used to write someting on the (Max, PD) terminal
// without relying on a message to the 'print' object. They are two
// advantages:
// - all antescofo values can be printed
// - the print is direct and does not use the message dispatch mechanism.
// So they are delivered in the right order, irrespectively of a
// message priority. The downside is that yoru are not supposed to
// write huge message that takes times while you are perhaps runing
// in the timer thread. But for informative message, there is no problem.
//
@print("•• PlayUpTo from beatpos ", $RNOW, " = ", @event_name(),
" to beatpos ", $part.last()[0], "=", $part.last()[5], " included")
// We extract the duration from the score par description and we weight
// with the tempo to have duration in seconds.
@local
$dur := [$e[2] * $e[3] | $e in $part],
$i := 0
// This loop will issue the 'nextevent' making the progression in the score.
// There is two subtleties:
//
// - When the loop is launched, the body is immediatly launched and the
// next iteration will take place after the duration of the current event.
// So we have to issue the nextevent command after the duration of this
// event. Hence the delay (in seconds) between the nextevent command.
//
// - The nextevent command doe not always ump to the nextevent: it jumps
// to the next event which is not a grave note, not a silence and not
// an abstract event.
// The transport command that really goes to the the immediately following
// event in the score is 'nexteventany'.
//
// Thanks to the during clause, the loop iterates for all the elment in the
// selected part.
// When the loop terlinates, the action in the continuation ==> prints a
// message on the console to warn that a manual nexteventany is needed
// to unblock the computation.
loop $dur[$i] s
{
@local $k := $i
$i := $k + 1
($dur[$k]) s
antescofo::nexteventany
} during [($dur.size() - 1)#]
==> if ($stop != "end")
{
($dur.last()) s @print("•• Frozen at beatpos ", $RNOW, " ",
$LAST_EVENT_LABEL, ", waiting a clic to go to ",
@event_name(1 + @current_event()))
}
}
// Here is an example of usage:
// - A first nexteventany in answer to the message
// "wait clic to start new section"
// will run the program up to the section "NS2" (excluded)
// - A second nexteventany runs the actions linked to the section NS2.
// - A 3rd nexteventany runs the actions linked to "c2".
// The lmast of these action recall PlayUpTo unti the end of teh score.
//
// The $dateSection variable is used to record the date just before a section
// or before the launch of ::PlayUpTo().It makes the comparaison of the
// date of each event more easier compared to the play mode.
//
// Beware: here nexteventany is used by the operator to manually progress
// when needed. It is important to be sure to go from one event to another
// which is not always the case using nextevent, if they are silence or grace
// event in the score.
// But it means also that sometimes more than one nexteventany is needed.
// This can be elucidated by looking to the normalized score produced by the
// message printfwd or printsocre.
// By looking to the normalized score, you may determine is nextevent is usable
// in place of nexteventany.
BPM 60
print Origin at $NOW
print "wait clic to start new section"
§new_section §0 DEBUT ; ----------------
$dateSection := $NOW
print section DEBUT at ($NOW - $dateSection)
::PlayUpTo("NS2")
NOTE A2 §0 rrr
print rrr at ($NOW - $dateSection)
BPM 120
CHORD (D3 C3 A7) §1 "chord"
print chord at ($NOW - $dateSection)
Trill (B1 B4 C2) §4 "trill"
print trill at ($NOW - $dateSection)
§new_section §6 NS1 ; ------------------
$dateSection := $NOW
print section NS1 at ($NOW - $dateSection)
NOTE C1 §0 "c1"
print C1 at ($NOW - $dateSection)
BPM 90
§new_section §2 NS2 ; ------------------
print section NS2 at ($NOW - $dateSection)
NOTE C2 §0 "c2"
$dateSection := $NOW
print C2 at ($NOW - $dateSection)
print "play to end from C2 (not a section)"
::PlayUpTo("end")
NOTE C3 §3 "c3"
print C3 at ($NOW - $dateSection)
print DONE
§end §4
/*
antescofo~: Sequence playback from beat position 0.000000...
print: Origin at 0.
print: "wait clic to start new section"
print: section DEBUT at 0.
print: rrr at 0.
print: chord at 1.
print: trill at 2.5
print: section NS1 at 0.
print: C1 at 0.
print: section NS2 at 0.
print: C2 at 0.
print: "play to end from C2 (not a section)"
print: C3 at 2.
print: DONE
*/