Options
Query-level fields that don't belong to pattern itself: toggles that change
how a match's boundaries or comparisons are computed. They never change which
features or values a pattern asks for, only how those asks are judged.
| Option | Type | Default | What it does |
|---|---|---|---|
mintStartAtPreviousToken | boolean | false | Reports a mint match as starting on the note the first interval was measured from, one onset earlier. |
mintAllowIntervalComplementation | string/number or array | — | Lets the listed interval numbers also match their complement, so "-5" can also match +4. |
fbCompareExactChord | boolean | false | Requires an fb chord to have exactly the figures of the pattern value, with no extras. |
kernIgnoreOctave | boolean | false | Matches a kern pitch in every register instead of the one written. |
hintReduceCompound | boolean | false | Folds hint intervals into one octave before comparing, so "3" also matches a 10th. |
durationAllowSplitNotes | boolean | false | Lets a duration also be added up from consecutive repetitions of the same note. |
durationAllowMergedNotes | boolean | false | Lets consecutive durations also be divided out of one single longer note. |
metweightSkipUnclassified | boolean | false | Treats an onset on an unclassified metric position as an ornament and leaves it out of the search. |
simultaneousAlignment | "start", "end", "start-end" | "start" | Picks which positions of a simultaneousWith match must line up with the primary match. |
Every one of them can also be set inside a single entry of
simultaneousWith, overriding the query's own value just
for that group's matching. Leaving one out of a group inherits the query's
top-level value instead.
mintStartAtPreviousToken
Type: boolean · Default: false
Only affects searches driven by mint. A **mint token
describes the interval into a note, so the first onset your pattern checks is
already the second note of the phrase you're matching. The note the interval
starts from isn't itself checked by position 0, and by default it's left out
of the reported startPosition/startLine.
Setting mintStartAtPreviousToken: true shifts the reported start back by one
onset, so the match begins on the note the first interval was measured from, not
the note it lands on.
{
"feature": "mint",
"voices": "soprano",
"mintStartAtPreviousToken": true,
"choraleFilter": { "ids": [1, 5, 6, 8, 9, 29, 39, 103] },
"pattern": [
{ "mint": "-2" },
{ "mint": "-2" }
]
}
The choraleFilter restricts the search to the eight chorales of the test
fixtures. The tables below show the matches in chorale 29.
Without the option, a match's startLine points at the second note of the
descending-2nds run; with it, startLine points at the first note, one note
earlier, so the highlighted range covers all three notes of the run instead of
just the last two:
chorale feature voice start_line end_line start_position end_position
chor029 mint Soprano 26 30 3 4
chor029 mint Soprano 30 32 4 5
chor029 mint Soprano 32 34 5 6
chor029 mint Soprano 34 36 6 7
chor029 mint Soprano 46 48 12 13
chor029 mint Soprano 48 50 13 14
chor029 mint Soprano 57 60 19 20
chor029 mint Soprano 60 61 20 21
chor029 mint Soprano 73 76 27 28
chor029 mint Soprano 76 78 28 29
chor029 mint Soprano 78 80 29 30
chor029 mint Soprano 87 89 34 35
chor029 mint Soprano 89 90 35 35+1/2
chor029 mint Soprano 90 92 35+1/2 36
chor029 mint Soprano 105 107 44 45
chor029 mint Soprano 107 109 45 46
chor029 mint Soprano 113 116 48 50
chorale feature voice start_line end_line start_position end_position
chor029 mint Soprano 25 30 2 4
chor029 mint Soprano 26 32 3 5
chor029 mint Soprano 30 34 4 6
chor029 mint Soprano 32 36 5 7
chor029 mint Soprano 43 48 11 13
chor029 mint Soprano 46 50 12 14
chor029 mint Soprano 56 60 18 20
chor029 mint Soprano 57 61 19 21
chor029 mint Soprano 70 76 26 28
chor029 mint Soprano 73 78 27 29
chor029 mint Soprano 76 80 28 30
chor029 mint Soprano 85 89 33 35
chor029 mint Soprano 87 90 34 35+1/2
chor029 mint Soprano 89 92 35 36
chor029 mint Soprano 103 107 43 45
chor029 mint Soprano 105 109 44 46
chor029 mint Soprano 110 116 47 50
The escape hatch: the shift is skipped when position 0 of your pattern
explicitly sets mint to a wildcard, { "mint": "*" }, so that it doesn't add
one extra step on top of a position you've already accounted for yourself. Any
other position 0 (a specific mint value, or no mint key at all, e.g. only
checking duration or another feature) still gets the shift.
Rests: **mint measures across rests, from the last note that actually
sounded, and the shifted start follows that same rule. If one or more rests sit
between the first matched interval and the note it was measured from, the start
skips over them to that sounding note rather than landing on a rest. When
nothing sounds earlier in the voice at all, there is nothing to shift back to
and the match keeps its own first onset.
mintAllowIntervalComplementation
Type: string/number or array of strings/numbers · Default: none (off)
Affects any mint pattern key, whether mint is the
driving feature or a cross-referenced check alongside another one. It lets a
pattern value also be satisfied by its complementary interval: the
interval that fills the rest of the octave in the opposite direction. A 5th down
and a 4th up land on the same pitch class from the same starting note, an octave
apart, so for many melodic questions they're the same move:
{
"feature": "mint",
"voices": "bass",
"pattern": [
{ "mint": "-5" },
{ "mint": "-5" }
],
"mintStartAtPreviousToken": true,
"mintAllowIntervalComplementation": ["5"]
}
Without the option, this finds only two descending 5ths in a row. With it, it also finds ascending 4ths.
The value is the set of diatonic numbers you're opting in, either a single
value or an array. Entries may be written as strings or as plain numbers (["4", "5"] and [4, 5] are the same thing), and "*" opts in every number at once.
Only simple intervals have a complement within the octave, so entries must be
1–8 (or "*").
How a value is complemented
| Component | Complement |
|---|---|
| number | 9 − the number: 1↔8, 2↔7, 3↔6, 4↔5 |
| direction | flipped: +↔− (a value with no sign stays direction-agnostic) |
| quality | inverted: M↔m, A↔d, P stays P (so AA↔dd) |
So "-P5" also matches +P4, "+M2" also matches -m7, and the sign-less
"5" (a 5th in either direction) also matches any 4th in either direction.
Quality is inverted properly, which means the option never widens quality: a
pattern of "+M2" picks up -m7, never -M7.
It's the number in your pattern that opts in
An entry opts in every pattern value written with that number, not every
value that could complement to it. Listing "5" makes { "mint": "-5" } also
match a +4; it does not make { "mint": "+4" } also match a -5. To have
it both ways, list both numbers:
{
"feature": "mint",
"pattern": [
{ "mint": "+4" },
{ "mint": "+4" }
],
"voices": "bass",
"mintAllowIntervalComplementation": [4, 5]
}
This keeps each direction of the shorthand something the query asks for explicitly, so a pattern position never quietly matches more than the numbers you listed.
Values with nothing to complement are left alone even under "*": a value
without a diatonic number ("+", "-", a bare quality), a compound interval (a
9th and up), and mint's bracketed first-note marker (e.g. "[gg]") all keep
matching exactly what they matched before. Negation composes as usual: with the
option on, { "!mint": "-5" } excludes both descending 5ths and ascending
4ths (see Negating a feature).
Listing 1, 8 or "*" has one effect beyond comparing pattern values: for a
mint-driven search it also lets an octave leap hold a
durationAllowSplitNotes run together, since the
octave is the unison's complement.
fbCompareExactChord
Type: boolean · Default: false
Only affects comparisons against fb. By default, a
multi-figure pattern value like "2 4" is a minimum requirement. It matches
any chord that contains at least those figures, so it also matches an actual
chord of "2 4 6". Setting fbCompareExactChord: true requires the actual
chord to have exactly as many figures as the pattern value, no more:
{
"feature": "fb",
"fbCompareExactChord": true,
"pattern": [
{ "fb": "m6 m3", "fermata": true }
]
}
With the default (false), this also matches a four-note chord that happens to
contain both a minor 6th and a minor 3rd above the bass, alongside other
figures. With fbCompareExactChord: true, it only matches chords that are
exactly a minor 6th and minor 3rd, nothing else sounding above the bass.
kernIgnoreOctave
Type: boolean · Default: false
Affects any kern pattern key, whether kern is the driving feature or a
cross-referenced check alongside another one. By default, a pitch pattern
value's register must match exactly, so "G" only matches a G in that exact
octave, not g, GG, gg, or any other octave of the same pitch class.
Setting kernIgnoreOctave: true matches that pitch class in every register:
{
"feature": "kern",
"kernIgnoreOctave": true,
"pattern": [
{ "kern": "G" },
{ "kern": "A" },
{ "kern": "B" },
{ "kern": "C" }
]
}
Accidentals always count as part of the pitch regardless of the option: "G#"
with kernIgnoreOctave: true matches a G♯ in any octave, never a plain G.
Rhythm and fermata components in the same pattern value (e.g. "4G;") are
unaffected and still have to match exactly.
hintReduceCompound
Type: boolean · Default: false
Affects any hint-<pair>/hint-<voice> pattern key. By
default, a pattern value's interval size must match exactly, so "3" only
matches a genuine 3rd, not a compound 3rd (a 10th, a 17th, …). Setting
hintReduceCompound: true folds both the pattern value and the actual interval
down to within an octave before comparing. A unison and an octave stay distinct
from each other. So "3" also matches a 10th:
{
"feature": "hint-14",
"hintReduceCompound": true,
"pattern": [
{ "hint-14": "M3" },
{ "hint-14": "M3" },
{ "hint-14": "M3" },
{ "hint-14": "M3" }
]
}
Without the option, this only matches an actual major 3rd between bass and soprano. With it, it also matches a major 10th or major 17th between them, since bass/soprano is wide enough that the interval is routinely compound in the raw data (see hint).
durationAllowSplitNotes
Type: boolean · Default: false
Affects any duration pattern key. By default a position's duration is the
sounding duration of the one onset that position is checked against, so a
pattern asking for a half note only finds notes actually written as half notes.
A chorale often spells the same musical note out as repeated shorter notes
instead and those repetitions are missed.
Setting durationAllowSplitNotes: true lets such a position instead be
satisfied by a run of consecutive onsets that re-articulate one and the same
note, whose durations add up to one of the position's allowed values:
{
"feature": "kern",
"voices": "alto",
"durationAllowSplitNotes": true,
"pattern": [
{ "kern": "d", "duration": "2" }
]
}
In chor001 the alto's opening d is a quarter-note upbeat re-attacked as
another quarter in measure 1; A half note's worth of d, never written as one.
Without the option only the genuine half notes are found; with it, the split one
joins them.
What counts as the same note
A run may only grow across onsets that re-attack the note it started on, which depends on the driving feature:
| Driving feature | A run continues while |
|---|---|
kern | the pitch stays the same (rhythm, beams, slurs and ties are ignored) |
mint | the interval into the onset is a perfect unison (P1) — how **mint writes a repetition |
| anything else | the spine repeats its own token (deg, fb, metweight, hint-<pair>) |
A run also stops as soon as the sum overshoots every allowed value: with
"duration": "2", three repeated quarters are a dotted half and match nothing,
while the first two of them are a match.
Octave leaps under mintAllowIntervalComplementation
For a mint-driven search, a perfect octave holds a run together just as a
unison does, provided the query listed the unison-octave pair itself (1, 8
or "*") in
mintAllowIntervalComplementation, since
P8 is P1's complement. chor009's bass cadences that way four times: the
dominant is a half note's worth written as two quarters an octave apart
(quarters 13–15: 4D, 4DD), stepping to the tonic under the fermata (4GG;,
written +P4). Here "*" does two jobs, 8 for the octave re-attack and 5
so that "-P5" reaches that +P4; [5, 8] would do as well:
{
"feature": "mint",
"voices": "bass",
"durationAllowSplitNotes": true,
"mintAllowIntervalComplementation": "*",
"pattern": [
{ "duration": "2" },
{ "mint": "-P5", "fermata": true }
]
}
This is a mint rule only. A kern-driven run always needs the identical
pitch, kernIgnoreOctave included; that option decides how a pattern value is
compared, not what holds a run together.
The rest of the position
Only duration is summed. Every other key of the same position is checked
on every onset of the run, so a deg, fb or hint-<pair> constraint has
to hold for the whole logical note, not just its first onset; a repetition under
changing harmony fails a harmonic constraint that the change breaks. Three keys
are treated specially:
- The driving feature's own key is checked on the run's first onset only. What follows is a re-attack of that same note, which is what the table above already vouches for.
fermatais checked on the run's last onset, the one that closes the note off, since that is where a chorale writes the fermata.phraseis split the same way:"start"is checked on the run's first onset, where the note begins, and"end"on its last onset, which carries the fermata.
A run consumes as many onsets as it needs, and the pattern simply carries on
from the one after it. So positions after a split position line up with the
notes that actually follow the run, and a match's endLine/endPosition report
the last onset it consumed.
durationAllowMergedNotes
Type: boolean · Default: false
The converse of durationAllowSplitNotes, and
independent of it. Affects any duration pattern key. By default every position
of a pattern needs an onset of its own, so a pattern spelling a cadence out as
three quarters only finds voices that actually write three quarters. a voice
that ties the first two together into a half note is missed, even though it is
singing the same thing.
Setting durationAllowMergedNotes: true lets a single onset satisfy several
consecutive positions at once, as long as their durations add up to exactly
that onset's sounding duration:
{
"feature": "kern",
"voices": "soprano",
"pattern": [
{ "kern": "a", "duration": "4" },
{ "kern": "a", "duration": "4" },
{ "kern": "g", "duration": "4", "fermata": "true" }
],
"durationAllowMergedNotes": true
}
In chor009 the soprano's measure 4 is a half note a, then the fermata g,
where alto, tenor and bass move in quarters. Without the option the query finds
nothing there; with it, the half note answers the pattern's first two positions
and the fermata g the third.
How the onset is divided up
The durations of the run's positions must sum to the onset's sounding duration
exactly. A quarter plus a quarter is a half note, a quarter plus a half is
not. When a position offers several values ("duration": ["4", "8"]), the
shortest run of positions that adds up exactly is the one taken. A run stops as
soon as it would overshoot, and it can never reach across a position without a
concrete duration (a missing one, or "*"), since such a position has no
share of the onset to claim.
A run of a single position is just the ordinary case: that position's duration
has to be the onset's own.
The rest of the positions
Every position of the run describes the same one onset, so all of their other
keys are checked against it, including fermata, which that onset either
carries or doesn't. Two kinds of key are treated specially, both for the same
reason: a position after the run's first one describes a re-attack that the
merged note doesn't have, so the onset's own token is the wrong thing to judge
it against.
- A
mintkey on such a position is judged against what that re-attack would have been: a unison, since it re-articulates the note the run started on. Nothing in the score is consulted for it. So{ "mint": "P1" }is the way to spell the repetition out, and something like{ "mint": "-P5" }can never hold: there is no second attack inside a merged note for a fifth to be measured into. An octave ("P8") passes for a re-attack too, on the same opt-in the split option asks for:"1","8"or"*"inmintAllowIntervalComplementation. - A
kernkey on such a position keeps its pitch judged exactly as anywhere else: a re-attack re-articulates the note the run started on, so its pitch is the merged onset's. Only the rhythm written into the value is dropped, since the onset's duration is the whole merged note's rather than this position's share of it. Constrain the share withduration, which is what the run divides the onset up by. So in{ "kern": "4a" }theastill has to hold and the4is ignored.
Both rules are deliberately independent of the driving feature. Asking for
the same repetition means the same thing whether the search runs on kern,
mint, deg or anything else.
Every other key is checked against the onset like any of the run's first
position's: a re-attack of a note has that note's scale degree, its figured
bass, its intervals to the other voices. In particular a later position asking
for a different note than the run started on. Another kern pitch, another
deg simply doesn't match, however neatly the durations would have divided the
onset up.
A run consumes as many positions as it needs from a single onset, and the
pattern carries on from the next onset. A match's endLine/endPosition
therefore report the last onset it actually consumed, which may be fewer onsets
than the pattern has positions.
Combining it with durationAllowSplitNotes
The two options describe opposite mismatches between a pattern and the score (one note written as several, several notes written as one) and never the same one, so switching both on simply allows both. At each position a split run is tried first; only if none is found does the position start a merged run instead.
metweightSkipUnclassified
Type: boolean · Default: false
The two duration options above are about a pattern and a score spelling the
same notes out differently. This one is about the score having notes the
pattern doesn't mention at all: the diminution a chorale decorates a line
with. A neighbour note, an anticipation, a leap away and back: each of them puts
an extra onset between the notes that carry the phrase, and a pattern written
for the plain version then walks straight past it.
Setting metweightSkipUnclassified: true treats an onset the walked voice
attacks on a metrically unclassified position (its
metweight is "u", i.e. between the beats the
meter classifies) as an ornament rather than as a note of its own. The
search then sees the voice with its ornaments folded away:
- the pattern never gets offered that onset at all;
- its duration falls to the note it decorates, the one in front of it;
- the
mintinto the next note is measured across it, from the note before it, rather than out of the ornament as the**mintspine writes it.
{
"feature": "kern",
"voices": "soprano",
"pattern": [
{ "mint": "*", "duration": "2" },
{ "mint": "-M2", "duration": "*", "fermata": true }
],
"metweightSkipUnclassified": true
}
A half note stepping down into the fermata. chor008's soprano writes measure 4
as 4.b- 8a- 2a-;: the eighth a- is an anticipation, already the fermata
note, taking its time from the b- in front of it. Without the option nothing
matches there, since there is no half note and no -M2 out of one. With it, the
b- gets its eighth back and is the half note the phrase is made of, and the
step into the fermata is the -M2 the pattern asked for.
A neighbour note folds away the same way. chor005's soprano reaches its
fermata in measure 4 as 8a 8g 4a 4g;, where the offbeat g is a lower
neighbour: with the ornament gone the two as are adjacent, so the plain
skeleton reaches it, either as a quarter and its repetition, or, adding
durationAllowSplitNotes, as the one half note the
query above asks for.
It is the pattern that gets simpler, not looser
The option doesn't widen what a position matches; it changes which onsets there are to match against. Everything a position asks for is still asked of the notes that remain, and a plain, undiminished passage is unaffected, having no unclassified onsets to fold away. So one pattern finds the plain reading and the diminished one at once:
{
"feature": "kern",
"voices": "bass",
"pattern": [
{ "mint": "*", "duration": "4" },
{ "mint": "+2", "duration": "4" },
{ "mint": "-P5", "duration": "*", "fermata": true }
],
"metweightSkipUnclassified": true,
"mintAllowIntervalComplementation": "5"
}
A scale-degree 4–5–1 bass cadence. chor006 has it twice: measure 8 plainly, and
measure 6 diminished into 4–2–5–1, where an offbeat eighth drops a third away
from the 4 and leaps a fourth back up to the 5. Only the plain one is found
without the option, both with it. Note that what the ornament is shaped like
never enters into it, a step, a leap or a return alike. All that counts is where
it falls.
What is never an ornament
- A rest.
**metweightcalls every rest unclassified wherever it falls, so requiring a sounding note is what keeps rests as the boundaries they are. - A voice's first onset. There is no note in front of it whose duration it could belong to.
- An onset in another voice. Which onsets fold away is decided per voice,
for the voice being walked; a
simultaneousWithgroup does its own folding for its own voice, and can switch the option on or off independently.
The other side of the trade
Since an ornament is not an onset any more, a pattern position can no longer
land on one while the option is on. A pattern that asks for { "metweight": "u" }, or that spells the ornament out itself (8a, 8g, 4a, …), will find
nothing it used to find. That is the same pattern's job with the option off.
Switch it on to search the skeleton, off to search the surface.
Everything else about an onset is read where it always was, from its own line:
its deg, its fb, its hint intervals, its fermata, its kern pitch. Only
its duration grows, and only the mint of a note that directly follows an
ornament is recomputed. A note with no ornament in front of it keeps the
**mint spine's own token.
Combining it with the duration options
This option and the two duration ones can all be on at once, and a fuller
diminution needs more than one of them. Skipping an ornament is what makes the
notes on either side of it neighbours, and that is the precondition for either
duration option to reach across it:
durationAllowSplitNotescan then sum a note the score both split into repetitions and decorated in between, into the one longer note it stands for. chor005's measure 4, above, is the case: the twoas only become a run of repetitions once the neighbour note between them is gone.durationAllowMergedNotescan then spread a note an ornament just handed its time to across several pattern positions. chor008's measure 4 is the case: theb-has to grow to a half note before it can answer two quarter-note positions.
simultaneousAlignment
Type: string · Default: "start" · One of: "start", "end", "start-end"
Only relevant with simultaneousWith. Picks which of a
group's own match positions must line up with the primary match's:
"start"— the group's match must start where the primary match starts."end"— the group's match must end where the primary match ends."start-end"— both at once, i.e. the two matches run for the exact same duration.
{
"feature": "deg",
"voices": "soprano",
"pattern": [
{ "deg": "3" },
{ "deg": "2" },
{ "deg": "1", "fermata": true }
],
"simultaneousWith": [
{
"feature": "deg",
"voices": "bass",
"pattern": [
{ "deg": "5" },
{ "deg": "5" },
{ "deg": "1" }
]
}
],
"simultaneousAlignment": "start-end"
}
Set at the top level it applies to every group; a single group can override it
with its own simultaneousAlignment (see simultaneousWith).