Patterns¶
Patterns are a simple way to define complex logical conditions to be
used in a whenever. A pattern is a sequence of atomic patterns that
describe the evolution through time of logical conditions. There is
three kinds of atomic patterns: Note
, Event
and State
.
Such a sequence is defined and then used as the condition of a whenever to trigger some actions every time the pattern matches. It can represent a neume, that is a melodic schema defining a general shape but not necessarily the exact notes or rhythms involved. It can also be used in broader contexts involving not only the pitch detected by the listening machine, but also arbitrary variables.
*Warning: * The notion of pattern used here is very specific and the recognition algorithm departs from the recognition achieved by the listening machine. Patterns define an exact pattern of variation in time (variation of variable's values) while the listening machine recognizes the most probable variation (of the audio signal) from a given dictionary of musical events. The latter relies on signal processing probabilistic methods. The former relies on algorithms like those used for recognizing regular expressions in string matching. So the pattern matching available here is not relevant for the audio signal, even if it can have some applications.
Note
: Patterns on Score¶
The basic idea is to react to the recognition of a musical phrase defined in a manner similar to event’s specification. For example, the statement:
@pattern_def pattern::P
{
Note C4 0.5
Note D4 1.0
}
defines a pattern that can be used later as the argument of a whenever:
whenever pattern::P
{
print "found pattern P"
}
The pattern Note
is an atomic pattern and the
@pattern_def
defines and gives a name to a sequence of
atomic patterns.
In the current version, the only events recognized are Note
: chords, trill, etc. cannot be used (see however the other kinds
of atomic patterns below). Contrary to the notes in the score, the
duration may be omitted to specify that any duration is acceptable.
Pattern Variables¶
To be more flexible, patterns can be specified using local variables that act as wildcards:
@pattern_def pattern::Q
{
@local $h
Note $h
Note $h
}
The pattern Note
matches a note in the input followed by
the listening machine. The pattern pattern::Q
defines a
repetition of two successive notes with the same pitch (their respective
duration do not matter).
The wildcard, or pattern variable $h
, is specified in
the clause at the beginning of the pattern definition using a
@local
declaration. Every occurrence of a pattern
variable must refer to the same value. Here, this value is the pitch of
the detected note (given in midicents).
Pattern variables are really local variables and their scope extends to
the body of the whenever
that uses this pattern. So they
can be used to parametrize the actions to be triggered. For example:
whenever pattern::Q
{
print "detection of the repetition of pitch " $h
}
Specifying Duration¶
A pattern variable can also be used to restrict durations in the same manner. The value of a duration is the value given in the score (and not the actual duration played by the musician).
Specifying Constraints¶
There are two parameters for a note: its pitch and its duration. These parameters may be specified by
-
a constant
-
a pattern variable specifying an unknown value determined at matching time
-
an (ordinary) variable specifying a specific value (the value of the variable at matching time)
For pitches, the constant is an integer or the ratio of two integers, or a symbolic note. These constants specify the expected pitch in midicents.
For duration, the constant specifies the expected duration in raltive
time (in beat) or in absolute time (in second with the s
unit appended or in millisecond with ms
appended). Notice
however that such specification is probably useless: there is few chance
that the actual note duration is exactly equal to the specified one.
A pattern variable can be used as a kind of wildcard. Declared using an
@local
declaration, the variable takes its actual value
with the matching of its first occurrence. The following occurrence of
the variable constraint the corresponding matching to take the same
value.
An ordinary Antescofo variable can be used to specify a pitch or a
duration. In this case, only a note with the specified pitch or duration
is matched by the note
pattern. The specified value is
the value of the variable at the time of matching and this value can be
changed dynamically (with an assignment). This mechanism can be used to
adapt a pattern to a given context.
Here is an example involving ordinary variables in a pattern :
@pattern_def pattern::R
{
Note $X
Note C4 $Y
}
specifies a sequence of two notes. The first one must have a pitch equal
to the value of the variable $X
(at the time where the
pattern is checked). The pitch of the second one is C4
, and the duration of the first is irrelevant while the duration of
the second must be equal to the value of $Y
. As for
$X
, this variable is updated elsewhere and the value
considered is its value at the time where the pattern is checked.
These two variables are recognized as ordinary variables and not as
pattern variables, because they are not declared with a @local
in the scope of the pattern.
Additional constraints on the matching can be specified through a
where
clause which specifies a logical expression which
must be true for the matching to succeed:
@pattern_def pattern::R
{
@local $h, $dur1, $dur2
Note $h $dur1 where $h > 6300
Note $h $dur2 where $dur2 < $dur1
}
specifies a sequence of two successive notes such that:
-
their pitch is equal and this value in midicents is the value of the local variable
$h
; -
$h
in midicents is higher than 6300; -
and the duration
$dur2
of the second note must be lower than the duration$dur1
of the first note.
The logical expression after the where
is an arbitrary
expression. If it involves variables, the value of these variables is the
value of the variable at the time of matching.
Pattern Causality¶
In a clause, all pattern variables used must have been set before. For
example, it is not possible to refer to $dur2
in the
where
clause of the first note: the pattern recognition
is causal which means that the sequence of pattern is recognized
“on-line” in time from the first to the last without guessing the
future.
However, it is easy to postpone a antescofo::: where
clause to
an event where all pattern variables have been set. For example, writing:
@pattern_def pattern::R
{
@local $h, $dur1, $dur2
Note $h $dur1 where ($h > 6300) && ($dur2 > 0.5)
Note $h $dur2 where $dur2 < $dur1
}
One can also write
@pattern_def pattern::R
{
@local $h, $dur1, $dur2
Note $h $dur1 where $h > 6300
Note $h $dur2 where ($dur2 < $dur1) && ($dur2 > 0.5)
}
A Complete Example¶
The pattern
@pattern_def pattern::M
{
@local $h, $dur
Note $X $dur
Note $h $dur where $dur > $Y
Note C4
}
defines a sequence of 3 notes. The first note has a pitch equal to
$X
(at the moment where the pattern is checked); the
second note has an unknown pitch referred to by $h
and a
duration $dur
which is the same as the duration of the
first note. In addition, this duration must be greater than the current
value of the ordinary variable $Y
; and finally, the third
note as a pitch equal to C4
.
Event
on Arbitrary Variables¶
From the listening machine perspective, a ::atescofo Note
is a complex
event to detect in the sequence of samples of the audio input. But from
the pattern matching perspective, a Note
is an atomic
event that can be detected looking only on the system variables
$PITCH
and $DURATION
managed by the
listening machine.
It is then natural to extend the pattern-matching mechanism to look
after any variable. This generalization from any variable is achieved
using the pattern Event
:
@pattern_def pattern::Gong
{
@local $x, $y, $s, $z
Event $S value $x
Event $S value $y at $s where $s > 110
Before [4]
Event $S value $z where [$x < $z < $y]
}
The keyword Event
is used here to specify that the event
we are looking for is an update in the value of the variable
$S
1. We say that $S
is the watched
variable of the pattern.
An Event
pattern is another kind of atomic pattern.
Note
and Event
patterns can be freely
mixed in a @pattern_def
definition.
Four optional clauses can be used to constrain an Event
pattern:
-
The
before
clause is used to specify a temporal scope for looking at the pattern. -
The
value
clause is used to give a name or to constrain the value of the variable specified in the at matching time. -
The
at
clause can be used to refer elsewhere to the time at which the pattern occurs. -
The
where
clause can be used to specify additional logical constraint.
The Before
clause must be given before the Event
keyword. The last three clauses can be given in any order after
the specification of the watched variable.
Contrary to the Note
pattern, there is no “duration”
clause because an event is point wise in time: it detects the update of
a variable, which is instantaneous.
The value
Clause¶
The value
clause used in an Event
is more
general than that used in a Note
pattern: it
accepts a pattern variable or an arbitrary expression. An arbitrary
expression constrains the value of the watched variable to be equal to
the value of this expression2. A pattern variable is bound
to the value of the watched variable. This pattern variable can be used
elsewhere in the pattern.
The at
Clause.¶
An at
clause is used to bind a local variable to the
value of the $NOW
variable when the match occurs. This
variable can then be used in another clause, e.g. to assert some
properties about the time elapsed between two events or in the body of
the whenever.
Unlike in a value
clause, it is not possible to directly
specify a value for the clause but this value can be tested in the
clause:
@pattern_def pattern::S
{
@local $s, $x, $y
Event $S at $s where $s==5 ; cannot write directly: Event $S at 5
Event $S at $x
Event $S at $y where ($y - $x) < 2
}
Note that it is very unluckily that the matching time of a pattern is exactly “5”. Notice also that the date is expressed in absolute time.
The where
Clause¶
As for patterns, a clause is used to constraint the parameters of an event (value and occurrence time). It can also be used to check any property that must hold at the time of matching. For example: in the clause:
@pattern_def pattern::S
{
Event $S where $ok
}
will match an update of $S
only when $ok
is true.
The before
Clause¶
For a pattern q that follows a pattern p, the before
clause can be used to relax the temporal scope on which q is
looked for.
When Antescofo is waiting to match the pattern q =Event $X
, it starts to watch the variable right after the match of the
previous pattern p. Then, at the first value change of
$X
, Antescofo checks the various constraints on q. If
the constraints are not met, the matching fails.
The before
clause can be used to shrink or to extend the
temporal interval on which the pattern is matched beyond the first value
change. For instance, the pattern
@pattern_def pattern::twice
{
@local $x
Event $V value $x
Before [3s] Event $V value $x
}
is looking for two updates of variable $:::antescofo $V
for the same
value $x
in less than 3 seconds. Nota bene that other
updates may occur but $V
must be updated for the same
value before 3 seconds have elapsed for the pattern to match.
If we replace the temporal scope [3s]
by a logical count
[3#]
, we are looking for an update for the same value
that occurs in the next 3 updates of the watched variable. The temporal
scope can also be specified in relative time [3]
.
Notice that a before
clause cannot be achieved using an
at
clause with a where
clause; pattern twice
@pattern_def pattern::twice2[$x]
{
@local $x, $s1 $s2
Event $V value $x at $s1
Event $V value $x at $s2 where ($s2 - $s1) <= 3
}
pattern::twice2
does not match the same thing as
pattern::twice
because for pattern::twice2
the two matched events are two successive updates of $V
.
When the temporal scope of a pattern is extended beyond the first value change, it is possible that several updates occurring within the temporal scope satisfy the various patterns’ constraints3. However, the pattern matching stops looking for further occurrences in the same temporal scope after having found the first one. This behavior is called the single match property.
For instance, if the variable $V
takes the same value
three times within 3 seconds, say at the dates t_1 < t_2 < t_3, then
pattern::twice
occurs three times as (t_1, t_2), (t_1,
t_3), and (t_2, t_3). Because Antescofo stops to look for further
occurrences when a match starting at a given date is found, only the two
matches (t_1, t_2) and (t_2, t_3) are reported.
Finally, notice that the temporal scope defined in an event starts with
the preceding event. So a before
clause on the first of a
pattern sequence is meaningless and actually forbidden by the syntax.
Watching Multiple Variables Simultaneously¶
It is possible to watch several variables simultaneously: the event occurs when one of the watched variable is updated (and if the constraints are fulfilled). For instance:
@pattern_def pattern::T
{
@local $s1, $s2
Event $X, $Y at $s1
Event $X, $Y at $s2 where ($s2 - $s1) < 1
}
is a pattern looking for two successive updates of either $X
or $Y
in less than one second.
Notice that when watching multiple variables, it is not possible to use
a value
clause.
A Complex Example¶
As mentioned, it is possible to freely mix and patterns, for example to watch some variables after the occurrence of a musical event:
@pattern_def pattern::T
{
@local $d, $s1, $s2, $z
Note D4 $d
Before [2.5] Event $X, $Y at $s1
Event $Z value $z at $s2 where ($z > $d) && $d > ($s2 - $s1)
}
Note that different variables are watched after the occurrence of a note
D4
(6400 midicents). This pattern is waiting for an
assignment to variable $X
or $Y
in an
interval of2.5 beats after a note D4
, followed by a
change in variable $Z
for a value such that the duration
of D4
is greater than the interval between the changes in
$X
or $Y
, and such that the value of
$Z
is also greater than this interval.
State
Patterns¶
The Event
pattern corresponds to a logic of signal:
each variable update is meaningful and a property is checked
instantaneously on a given point in time. This contrasts with a logic
of state where a property is looked on an interval of time. The
State
pattern can be used to face such case.
A Motivating Example¶
Suppose we want to trigger an action when a variable takes the value 0 for at least 2 beats. The following pattern:
Event $X value 0
does not work because the constraint “at least 2 beats” is not taken
into account. The pattern matches every time $X
takes
the value 0.
The pattern sequence
@local $start, $stop
Event $X value 0 at $start
Event $X value 0 at $stop where ($stop - $start) >= 2
is no better: it matches two successive updates of $X
that span over 2 seconds. It would not match three consecutive updates
of for the same value 0, one at each beat, a configuration that should
be recognized. In addition, converting the absolute duration into relative
time is difficult because it would require tracking every tempo change
in the interval.
This example shows that is not an easy task to translate the
specification of a state that lasts over an interval into a sequence of
instantaneous events. This is why, a new kind of atomic pattern has been
introduced to match states. Using a state
pattern, the
specification of the previous problem is easy:
State $X where $X == 0 during 2
matches an interval of 2 beats where the variable constantly has the value 0 (irrespectively of the variable updates).
Five optional clauses can be used to constrain a state
pattern:
-
The
before
clause is used to specify a temporal scope for looking the pattern. -
The
start
clause can be used to refer elsewhere to the time at which the matching of the pattern has started. -
The
stop
clause can be used to refer elsewhere to the time at which the matching of the pattern stops. -
The
where
clause can be used to specify additional logical constraints. -
The
during
clause can be used to specify the duration of the state.
The before
clause must be given before the event
keyword. The others can be given in any order after the
specification of the watched variable. There is no value
clause because the value of the watched variable may change during the
matching of the pattern, for instance when the state is defined as
“being above some threshold”.
The first three clauses are similar to those described for an
event
pattern, except that the at
is split
into the start
and the stop
clauses
because here the pattern is not instantaneous; it spans over an interval
of time.
The initiation of a state
Pattern¶
Contrary to note
and event
, the pattern is
not driven solely by the updates of the watched variables. So the
matching of a state
is initiated immediately after the
end of the previous matching.
The during
Clause¶
The optional during
clause is used to specify the time
interval on which the various constraints of the pattern must hold. If
this clause is not provided, the state
finishes to match
as soon as the constraint becomes false.
The figure below illustrates the behavior of the pattern
@Refractory r
State $X during d where $X > a
Before [s]
State $X where $X > b
The schema assumes that variable $X
is sampling a
continuous variation.
The first pattern is looking for an interval of length d
where $X
is constantly greater than a
.
The second pattern must start to match before s
beats have
elapsed since the end of the previous pattern (the allowed time zone is
in green). The match starts as soon as it is greater than b
.
The second pattern finishes its matching as soon as it becomes smaller than
b
because there is no specification of a duration.
With the sketched curve, there are many other possible matches
corresponding to postponing the start of the first state
while still maintaining $X > b
. Because the start time of
these matches are all different, they are not ruled out by the single
match property. A refractory
period is used to restrict
the number of successful (reported) matches.
Limiting the Number of Matches of a Pattern¶
The refractory period is defined for a pattern sequence, not for an
atomic pattern. The clause must be specified at the beginning of the
pattern sequence just before or after an eventual @local
clause.
This clause specifies the period after a successful match (of the whole pattern) during which no other matches may occur. This period is given in absolute time and counted starting from the end of the successful match. The refractory period is represented in red in the above figure. The effect of a refractory period is to restrict the number of matching per time interval.
Pattern Compilation¶
Patterns are not a core feature of the language: internally they are compiled in a nest of whenever, conditionals and local variables. If verbosity is greater than zero, the [printfwd] command reveals the result of the pattern compilation in the printed score.
Two properties of the generated code must be kept in mind:
-
Causality: The pattern compiler assumes that the various constraints expressed in a pattern are free of side-effects and the pattern matching is achieved on-line, that is, sequentially in time and without assumptions about the future.
-
Single match property: When a pattern sequence occurs several times starting at the same time t, only one pattern occurrence is reported4.
Pattern semantics and pattern compilation are detailed in Real-Time Matching of Antescofo Temporal Patterns.
-
A variable may be updated while keeping the same value, as for instance when evaluating
let $S := $S
. Why$S
is updated or what it represents does not matter here. For example,$S
can be the result of some computation in Antescofo to record a rhythmic structure. Or$S
is computed in the environment using a pitch detector or a gesture follower and its value is notified to Antescofo using asetvar
message. ↩ -
To achieve the same effect in a
Note
pattern, you need to use thewhere
clause: a pattern variable is used to bind the pitchor the duration value and then the logical expression is checked in thewhere
clause. ↩ -
If there is no before clause, the temporal scope is “the first value change” which implies that there is at most one match. ↩
-
Alternative behaviors may be considered in the future. ↩