Features

hint

Searching by the harmonic interval between a specific pair of voices.

**hint is Humdrum's harmonic-interval representation: a diatonic interval size with its quality (M3, P5, m10, …). It can also be generated with humlib's fb tool via its --hint option. Unlike fb's own default output, which reduces the whole texture to intervals above the bass, hint is computed per pair of voices: one spine for bass/tenor, one for bass/alto, one for tenor/soprano, and so on.

hint-<pair>

chorale-search generates one **hint-<pair> spine for every unordered pair of the four voices: hint-12, hint-13, hint-14, hint-23, hint-24, hint-34. Like fb, each of these is a single spine per chorale, not one per voice, so a lookup against hint-14 always resolves to the same spine regardless of which voices you searched for. Searching hint-14 with "voices": "tenor" reads the exact same bass/soprano data as "voices": "bass".

There's no bare **hint feature (unlike fb, which does exist as its own single spine derived the same way): every hint spine is tied to a specific pair, so feature: "hint" or a bare "hint" key always matches nothing.

Token format

A **hint token is a single interval: an optional quality letter (M, m, P, A, d, …) followed by a diatonic number, e.g. m3 or P5.

The interval is always given as an absolute value, measured from the lower-numbered voice of the pair up to the higher-numbered voice. It's never negative, so the interval size alone can't tell you whether the lower-numbered voice actually sounds below the other at that moment, only how far apart they are.

Just like fb, the quality letter can be omitted to match any quality:

Pattern valueMatches figure 3 as
"M3"only a major 3rd
"3"any 3rd, any quality

A widely spaced pair like bass/soprano routinely produces genuinely compound intervals (M10, P12, M17, and so on) rather than folding them down to M3/P5/M3 within an octave. See hintReduceCompound if you want a pattern value like "3" to also match a 10, 17, etc.

{
    "feature": "hint-14",
    "voices": "bass",
    "pattern": [
        { "hint-14": "P8", "fermata": true }
    ]
}

finds every moment the bass carries a fermata while it's exactly an octave apart from the soprano.

hint-<voice>

A pattern key can also name a pair relative to whichever voice is currently being walked, e.g. hint-2. Searching voices: "1234" with a hint-2 constraint resolves the pair separately for each voice being walked: hint-12 while walking the bass, hint-23 while walking the alto, hint-24 while walking the soprano.

{
    "feature": "kern",
    "voices": "1234",
    "pattern": [
        { "hint-2": "P8", "fermata": true }
    ]
}

finds the fermatas where a voice is an octave above or below the tenor.

hint-<voice> only makes sense as a cross-referenced constraint next to another driving feature (kern, deg, …), since there's no hint-2 spine to walk directly, it's resolved fresh at each onset, relative to whichever voice is being searched.

hint-<voice> is what makes a genuine parallel-motion search possible: both sides of the pair stay fixed to the same two voices across every position in the pattern, because one side is the digit you wrote and the other is whichever voice the search is walking, neither changes from position to position.

{
    "feature": "kern",
    "voices": "alto",
    "pattern": [
        { "hint-2": "P5" },
        { "hint-2": "P5" }
    ]
}

finds consecutive perfect 5ths between the alto and the tenor specifically, not "any voice a 5th above any other voice twice in a row", which a pair wildcard can't express, since it lets the pair itself vary from one position to the next.

Comparing interval sizes

The size of the interval can be compared with a comparison, in hint-<pair>, hint-<voice> and wildcard pairs alike. The value is a diatonic number ("10": the number alone is compared, so every interval of the same number is equally wide) or a number with a quality ("M10": the size in semitones is compared).

{
    "feature": "hint-14",
    "voices": "bass",
    "pattern": [
        { "hint-14": { "operator": "gt", "value": "22" } }
    ]
}

finds the moments the outer voices are more than three octaves apart (a 22nd). The size is the one in the score, so hintReduceCompound does not fold it into the octave first. A tenth is a 10, which is what tells it from a third.

Wildcard pairs

Either digit (or both) in a hint-<pair> key can be *, meaning "any pair matching this digit pattern". hint-*4 means "hint-14, hint-24, or hint-34: any voice's interval to the soprano". The position is satisfied if any one of the matching pairs currently has the pattern value, not all of them at once:

{
    "feature": "kern",
    "pattern": [
        { "hint-*4": "M7" }
    ]
}

finds every onset where at least one lower voice forms a major 7th with the soprano, regardless of which voice it is. Unlike hint-<voice>, a wildcard pair is re-evaluated independently at each position, so it can't pin down "the same two voices" across a multi-position pattern the way hint-<voice> does.

Searching several pairs at once

A query can be driven by a set of voice pairs instead of one pair: hint-** (every pair), or a set with a voice in its place, like hint-*4 (the soprano above another voice: 14, 24 and 34) or hint-1* (the bass below another voice: 12, 13 and 14), see Wildcard pairs. A set needs at least one pair, so hint-4* (the soprano is never the lower voice) can't drive a query. The search runs once for each pair of the set, and every result has the pair it is of as its feature, e.g. hint-24.

Inside such a query:

  • hint-@pair in the pattern is the interval of the pair being searched, so every position is about the same two voices. As with every key, a leading ! negates the position: { "!hint-@pair": "P5" } holds where that pair is not a fifth. hint-@pair and a pair of the set, like hint-24, can't be keys of the same position: while the pair 24 is searched, both would be the key hint-24;
  • every other hint key keeps its usual meaning. A set like hint-** or hint-*4 in the pattern holds if any pair of the set has the value, not only the pair being searched, see Wildcard pairs;
  • @lower and @upper in voices, in the query and in simultaneousWith, are the lower and the upper voice of that pair. The query's own voices is all voices (the default), which reads each pair once, or @lower and @upper.

For a complete query, see Parallel fifths downwards between any two voices.

Using it in a pattern

hint-<pair> is most useful either as the driving feature (walking that specific pair's interval as it changes) or as a secondary constraint alongside kern/deg/mint (checking "what's this specific pair of voices doing when that melodic thing happens"); hint-<voice> is a secondary constraint only.

{
    "feature": "deg",
    "voices": "soprano",
    "pattern": [
        { "deg": "2" },
        { "deg": "1", "hint-14": "P8" }
    ]
}

finds a soprano 2-to-1 resolution where the tonic lands exactly an octave above the bass. See Patterns for OR-lists, wildcards, multi-feature positions, and negation, all of which work the same way for hint keys as for any other feature.