Options

Toggles that change how a match's boundaries and comparisons are computed.

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.

OptionTypeDefaultWhat it does
mintStartAtPreviousTokenbooleanfalseReports a mint match as starting on the note the first interval was measured from, one onset earlier.
mintAllowIntervalComplementationstring/number or array—Lets the listed interval numbers also match their complement, so "-5" can also match +4.
fbCompareExactChordbooleanfalseRequires an fb chord to have exactly the figures of the pattern value, with no extras.
kernIgnoreOctavebooleanfalseMatches a kern pitch in every register instead of the one written.
hintReduceCompoundbooleanfalseFolds hint intervals into one octave before comparing, so "3" also matches a 10th.
durationAllowSplitNotesbooleanfalseLets a duration also be added up from consecutive repetitions of the same note.
durationAllowMergedNotesbooleanfalseLets consecutive durations also be divided out of one single longer note.
metweightSkipUnclassifiedbooleanfalseTreats 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

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

ComponentComplement
number9 − the number: 1↔8, 2↔7, 3↔6, 4↔5
directionflipped: +↔− (a value with no sign stays direction-agnostic)
qualityinverted: 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 featureA run continues while
kernthe pitch stays the same (rhythm, beams, slurs and ties are ignored)
mintthe interval into the onset is a perfect unison (P1) — how **mint writes a repetition
anything elsethe 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.
  • fermata is checked on the run's last onset, the one that closes the note off, since that is where a chorale writes the fermata.
  • phrase is 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 mint key 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 "*" in mintAllowIntervalComplementation.
  • A kern key 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 with duration, which is what the run divides the onset up by. So in { "kern": "4a" } the a still has to hold and the 4 is 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 mint into the next note is measured across it, from the note before it, rather than out of the ornament as the **mint spine 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. **metweight calls 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 simultaneousWith group 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:

  • durationAllowSplitNotes can 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 two as only become a run of repetitions once the neighbour note between them is gone.
  • durationAllowMergedNotes can then spread a note an ornament just handed its time to across several pattern positions. chor008's measure 4 is the case: the b- 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).