@libraz/libcantus 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +4 -0
  2. package/README_ja.md +4 -0
  3. package/dist/analyze/index.cjs +28 -4
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +151 -5
  6. package/dist/analyze/index.d.ts +151 -5
  7. package/dist/analyze/index.js +34 -10
  8. package/dist/{chunk-DSMJRXCZ.cjs → chunk-4R2LWV7E.cjs} +384 -107
  9. package/dist/chunk-4R2LWV7E.cjs.map +1 -0
  10. package/dist/{chunk-J7FHVZII.cjs → chunk-B2PW6EHN.cjs} +2000 -30
  11. package/dist/chunk-B2PW6EHN.cjs.map +1 -0
  12. package/dist/{chunk-Q6LFSAKV.js → chunk-CPMB7P75.js} +1973 -3
  13. package/dist/chunk-CPMB7P75.js.map +1 -0
  14. package/dist/{chunk-H2KJHBW4.js → chunk-GNVN4PPJ.js} +1871 -5
  15. package/dist/chunk-GNVN4PPJ.js.map +1 -0
  16. package/dist/{chunk-SGLEQVJH.cjs → chunk-IFZDWIKM.cjs} +1868 -2
  17. package/dist/chunk-IFZDWIKM.cjs.map +1 -0
  18. package/dist/{chunk-FBH75DAY.js → chunk-IPEF6NWB.js} +287 -10
  19. package/dist/chunk-IPEF6NWB.js.map +1 -0
  20. package/dist/{chunk-HYJJ2V2I.js → chunk-MLBPXA4Z.js} +300 -720
  21. package/dist/chunk-MLBPXA4Z.js.map +1 -0
  22. package/dist/{chunk-HWHGOVO6.cjs → chunk-PWC4CRWT.cjs} +1 -1
  23. package/dist/{chunk-HWHGOVO6.cjs.map → chunk-PWC4CRWT.cjs.map} +1 -1
  24. package/dist/{chunk-HPVSH7SF.cjs → chunk-YFA7X2F6.cjs} +350 -770
  25. package/dist/chunk-YFA7X2F6.cjs.map +1 -0
  26. package/dist/{chunk-NFJ4P6ZZ.js → chunk-ZBUROTJQ.js} +1 -1
  27. package/dist/generate/index.cjs +15 -3
  28. package/dist/generate/index.cjs.map +1 -1
  29. package/dist/generate/index.d.cts +84 -6
  30. package/dist/generate/index.d.ts +84 -6
  31. package/dist/generate/index.js +14 -2
  32. package/dist/{index-BNSM-DSk.d.cts → index-CY-3NVfy.d.cts} +484 -3
  33. package/dist/{index-BJgP97N1.d.ts → index-zaZdmnU4.d.ts} +484 -3
  34. package/dist/index.cjs +43 -7
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -5
  37. package/dist/index.d.ts +5 -5
  38. package/dist/index.js +48 -12
  39. package/dist/model/index.cjs +5 -5
  40. package/dist/model/index.d.cts +203 -7
  41. package/dist/model/index.d.ts +203 -7
  42. package/dist/model/index.js +4 -4
  43. package/dist/{index-DGCNKAUr.d.cts → profile-CQMEqdH9.d.cts} +65 -598
  44. package/dist/{index-BRMbPkHE.d.ts → profile-DLdlIpS9.d.ts} +65 -598
  45. package/dist/{index-BBWNZS0f.d.cts → types-D-XgxGvp.d.cts} +1169 -13
  46. package/dist/{index-CJBBiAmU.d.ts → types-Dh7C064C.d.ts} +1169 -13
  47. package/package.json +5 -5
  48. package/dist/chunk-DSMJRXCZ.cjs.map +0 -1
  49. package/dist/chunk-FBH75DAY.js.map +0 -1
  50. package/dist/chunk-H2KJHBW4.js.map +0 -1
  51. package/dist/chunk-HPVSH7SF.cjs.map +0 -1
  52. package/dist/chunk-HYJJ2V2I.js.map +0 -1
  53. package/dist/chunk-J7FHVZII.cjs.map +0 -1
  54. package/dist/chunk-Q6LFSAKV.js.map +0 -1
  55. package/dist/chunk-SGLEQVJH.cjs.map +0 -1
  56. /package/dist/{chunk-NFJ4P6ZZ.js.map → chunk-ZBUROTJQ.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/analyze/detect/modes.ts","../src/analyze/adjacency.ts","../src/analyze/histogram.ts","../src/analyze/detect/profiles.ts","../src/analyze/detect/index.ts","../src/analyze/functional/internal.ts","../src/analyze/functional/augmented-sixth.ts","../src/analyze/functional/cadence.ts","../src/analyze/functional/borrowed.ts","../src/analyze/functional/tonicization.ts","../src/analyze/functional/roman.ts","../src/analyze/functional/rationale.ts","../src/analyze/functional/function.ts","../src/analyze/functional/pivot.ts","../src/analyze/form/internal.ts","../src/analyze/grid.ts","../src/analyze/keys/index.ts","../src/analyze/voice/index.ts","../src/analyze/timeline/index.ts"],"sourcesContent":["/**\n * Church-mode candidates for {@link detectKey}.\n *\n * A mode is not a key with a different scale bolted on: it is the same tonal\n * hierarchy — tonic strongest, dominant next, the remaining diatonic degrees\n * behind them — heard over a scale in which one degree has been replaced. Its\n * profile is therefore derived from the profile of the major or minor key it\n * sits closest to, rather than being a table of its own.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport {\n DORIAN_MASK,\n LOCRIAN_MASK,\n LYDIAN_MASK,\n MIXOLYDIAN_MASK,\n PHRYGIAN_MASK,\n} from '../../theory/scale/index.js';\nimport type { KeyProfilePair } from './profiles.js';\n\n/**\n * A church mode {@link detectKey} can rank alongside the major and minor keys.\n *\n * Ionian and Aeolian are absent because they are the major and the natural minor\n * scale: a piece in either is already reported by the plain major or minor\n * candidate, and adding a second candidate over the same pitch classes and the\n * same profile would only produce a tie with itself.\n *\n * @category Recognition\n */\nexport type ModalScaleName = 'dorian' | 'phrygian' | 'lydian' | 'mixolydian' | 'locrian';\n\n/**\n * How a modal candidate is built out of its parallel major or minor key.\n *\n * @category Recognition\n */\nexport type ModalCandidate = {\n /** The name reported as {@link KeyMatch.scaleName} when this candidate wins. */\n scaleName: ModalScaleName;\n /** The major or minor key this mode is closest to, reported as `mode`. */\n mode: 'major' | 'minor';\n /** The 12-bit scale mask of the mode. */\n mask: number;\n /**\n * Degree pairs the mode substitutes, as `[replaced, replacing]` semitone\n * offsets above the tonic. Applying them to the parallel scale yields `mask`,\n * and applying them to the parallel profile yields this candidate's profile.\n */\n substitutions: readonly (readonly [number, number])[];\n};\n\n/**\n * The modal candidates, in the order they break a tie among themselves.\n *\n * Each mode is described as its parallel major or minor key with one or two\n * degrees substituted — exactly the substitution that names the mode in\n * teaching: dorian is minor with a natural sixth, phrygian minor with a flat\n * second, lydian major with a raised fourth, mixolydian major with a flat\n * seventh, locrian minor with both a flat second and a flat fifth. Because the\n * same substitution defines both the scale and the profile, the two can never\n * drift apart: a degree that is not in the scale never carries the weight the\n * profile expected on the degree it replaced.\n */\nexport const MODAL_CANDIDATES: readonly ModalCandidate[] = Object.freeze([\n { scaleName: 'dorian', mode: 'minor', mask: DORIAN_MASK, substitutions: [[8, 9]] },\n { scaleName: 'phrygian', mode: 'minor', mask: PHRYGIAN_MASK, substitutions: [[2, 1]] },\n { scaleName: 'lydian', mode: 'major', mask: LYDIAN_MASK, substitutions: [[5, 6]] },\n { scaleName: 'mixolydian', mode: 'major', mask: MIXOLYDIAN_MASK, substitutions: [[11, 10]] },\n {\n scaleName: 'locrian',\n mode: 'minor',\n mask: LOCRIAN_MASK,\n substitutions: [\n [2, 1],\n [7, 6],\n ],\n },\n] satisfies readonly ModalCandidate[]);\n\n/**\n * Every mode {@link DetectKeyOptions.modes} accepts, in candidate order.\n *\n * @category Recognition\n */\nexport const MODAL_SCALE_NAMES: readonly ModalScaleName[] = Object.freeze(\n MODAL_CANDIDATES.map((candidate) => candidate.scaleName),\n);\n\n/**\n * Derive the key profile of a mode from the profile of its parallel key.\n *\n * The mode keeps the tonal hierarchy of the major or minor key it is closest to,\n * so its profile starts as that key's profile; the weight the parallel key\n * expected on each replaced degree then moves to the degree that replaced it.\n * Dorian therefore expects on its natural sixth the weight minor expects on its\n * flat sixth, and mixolydian expects on its flat seventh the weight major\n * expects on its leading tone, while both keep their tonic peak — which is what\n * separates a mode from its parent scale, since D dorian and C major hold the\n * same seven pitch classes and differ only in where the weight is expected to\n * fall.\n *\n * Moving weight rather than adding it makes every modal vector a permutation of\n * a published one, so it has the same mean and the same variance as the major\n * and minor vectors it competes with. Pearson correlations against them are then\n * directly comparable, and a mode can neither win nor lose a ranking through an\n * artefact of scaling.\n *\n * @param profile The profile pair the ranking is running on.\n * @param candidate The mode to derive a vector for.\n * @returns A 12-entry vector indexed from the modal tonic.\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * // D dorian and C major hold the same seven pitch classes; the weight on the\n * // natural sixth is what tells them apart.\n * const dorian = [62, 64, 65, 67, 69, 71, 62, 69, 62];\n * detectKey(dorian, { modes: ['dorian'] })[0]?.scaleName; // 'dorian'\n * ```\n */\nexport function modalProfileVector(\n profile: KeyProfilePair,\n candidate: ModalCandidate,\n): readonly number[] {\n const parent = candidate.mode === 'major' ? profile.major : profile.minor;\n const vector = [...parent];\n for (const [replaced, replacing] of candidate.substitutions) {\n vector[replacing] = parent[replaced] ?? 0;\n vector[replaced] = parent[replacing] ?? 0;\n }\n return vector;\n}\n\n/**\n * Resolve the `modes` option of {@link detectKey} to the candidates to rank.\n *\n * @param modes True for every mode, a list of mode names for a chosen few, or\n * false/undefined for none (the default, which ranks the 24 keys alone).\n * @returns The modal candidates, always in {@link MODAL_CANDIDATES} order so a\n * caller cannot reorder the ranking by reordering the request.\n * @throws {InvalidInputError} When `modes` is neither a boolean nor an array, or\n * names a mode that is not a church mode.\n * @example\n * ```ts\n * import { detectKey, MODAL_SCALE_NAMES } from '@libraz/libcantus';\n * const dorian = [62, 64, 65, 67, 69, 71, 62, 69, 62];\n * // Without the option the 24 keys are ranked alone, so the nearest major or\n * // minor key wins; naming a mode puts it in the running.\n * detectKey(dorian)[0]?.scaleName; // 'major'\n * detectKey(dorian, { modes: ['dorian'] })[0]?.scaleName; // 'dorian'\n * MODAL_SCALE_NAMES.includes('dorian'); // true\n * ```\n */\nexport function resolveModalCandidates(\n modes: boolean | readonly ModalScaleName[] | undefined,\n): readonly ModalCandidate[] {\n if (modes === undefined || modes === false) {\n return [];\n }\n if (modes === true) {\n return MODAL_CANDIDATES;\n }\n if (!Array.isArray(modes)) {\n throw new InvalidInputError('modes must be a boolean or an array of mode names');\n }\n const requested = new Set<string>(modes);\n for (const name of requested) {\n if (!MODAL_SCALE_NAMES.some((known) => known === name)) {\n throw new InvalidInputError(\n `unknown mode '${String(name)}'; expected one of ${MODAL_SCALE_NAMES.join(', ')}`,\n );\n }\n }\n return MODAL_CANDIDATES.filter((candidate) => requested.has(candidate.scaleName));\n}\n","/**\n * Whether two beats are the same instant, and whether one note follows another.\n *\n * Two questions are asked of the beat axis all over the analysis layer: \"is this\n * the same beat as that one\" and \"does this note come straight after that one\".\n * They take different tolerances — a beat boundary is exact arithmetic, while a\n * note played rather than entered on a grid lands a fraction of a beat off — and\n * asking either of them with the other's tolerance is what lets one pass see a\n * predecessor where another sees none. Both are answered here, as distances\n * between beats: a bucket index would put two beats a hair either side of a\n * bucket edge in different buckets and answer that they differ, which no\n * distance can.\n */\n\nimport { BEAT_EPS } from '../core/meter/index.js';\n\n/**\n * Largest difference between two beats that are the same beat.\n *\n * Float residue rather than playing: what arithmetic over beat positions leaves\n * behind — a bar length times a bar count, a grid origin plus a whole number of\n * steps — and nothing musical is measured in it.\n *\n * Re-exported rather than declared, from the meter layer that has to compare\n * beats below every reader here. Two numbers that happen to be equal are not one\n * tolerance: a correction to either would leave the bar boundaries the meter\n * computes and the same instants the analysis reads a hair apart, and nothing\n * would report it.\n */\nexport { BEAT_EPS } from '../core/meter/index.js';\n\n/**\n * Largest gap or overlap, in beats, at which one note still follows another.\n *\n * A part played in rather than entered on a grid — or one run through\n * humanization — leaves a few hundredths of a beat of silence between notes that\n * are heard as consecutive, and leaves an overlap of the same size where a\n * legato line is held past its successor's onset. Inside this bound the two are\n * adjacent; outside it there is a rest between them.\n */\nexport const HUMANIZE_ADJACENCY = 0.05;\n\n/**\n * Whether two beats name the same instant.\n *\n * @param beat One beat.\n * @param other The beat to compare it with.\n * @returns True when the two are the same beat.\n */\nexport function sameInstant(beat: number, other: number): boolean {\n return Math.abs(beat - other) <= BEAT_EPS;\n}\n\n/**\n * Whether a note ending at `prevEnd` is heard as the one before `nextStart`.\n *\n * @param prevEnd Beat the earlier note ends on.\n * @param nextStart Beat the later note starts on.\n * @returns True when no audible rest separates them.\n */\nexport function adjacent(prevEnd: number, nextStart: number): boolean {\n return Math.abs(nextStart - prevEnd) <= HUMANIZE_ADJACENCY;\n}\n\n/**\n * Whether a note ending at `prevEnd` has finished by `beat`.\n *\n * The same tolerance as {@link adjacent}, asked one-sidedly: a line held a\n * hundredth of a beat past the next onset is one line, not two sounding at once.\n *\n * @param prevEnd Beat the note ends on.\n * @param beat The beat to ask about.\n * @returns True when the note no longer sounds there.\n */\nexport function hasEnded(prevEnd: number, beat: number): boolean {\n return prevEnd <= beat + HUMANIZE_ADJACENCY;\n}\n","/**\n * Pitch-class weight histograms over a span of notes.\n *\n * Chord inference and key inference both ask the same question of a stretch of\n * music — how much of each pitch class is sounding, and how prominently — so\n * they read it from one place. Sharing the histogram is what lets a chord\n * boundary and a key boundary be compared on the same scale.\n */\n\nimport type { MeterLike } from '../core/meter/index.js';\nimport { metricWeight } from '../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../core/pitch/index.js';\nimport type { NoteEvent } from '../core/types.js';\nimport { assertGenerationBudget } from '../core/validation/index.js';\nimport { BEAT_EPS } from './adjacency.js';\n\n/** Loudest a MIDI velocity goes, which is what a velocity is read against. */\nconst MAX_VELOCITY = 127;\n\n/** How much a metric accent may add to a note's weight, as a divisor of it. */\nconst ACCENT_DIVISOR = 3;\n\n/**\n * How much one note counts towards a pitch-class histogram.\n *\n * The one definition of prominence the chord route and the key route both\n * weigh their notes by, so the two never read the same music as emphasising\n * different pitch classes. A note counts for the beats it sounds, scaled by how\n * hard it is struck and, where a meter is in hand, by the accent of the beat it\n * starts on. A note carrying no velocity is counted at full weight: an import\n * that records velocity for one part and not another would otherwise have the\n * silent part outweigh the played one.\n *\n * @param note The note to weigh.\n * @param beats How many beats of it are being counted: its whole duration, or\n * its overlap with the window being weighed.\n * @param meter The meter supplying the metric accent; omit it where the note is\n * counted without one, which weighs every onset alike.\n * @returns The weight, in the same units for either caller.\n */\nexport function noteWeight(note: NoteEvent, beats: number, meter?: MeterLike): number {\n const velocityFactor = note.velocity !== undefined ? note.velocity / MAX_VELOCITY : 1;\n const accent = meter === undefined ? 1 : 1 + metricWeight(note.startBeat, meter) / ACCENT_DIVISOR;\n return beats * velocityFactor * accent;\n}\n\n/** An equal-slot grid: slot `i` covers `[origin + i * slotBeats, + slotBeats)`. */\nexport type SlotGrid = {\n /** First beat of slot 0. */\n origin: number;\n /** Length of one slot in beats. */\n slotBeats: number;\n};\n\n/**\n * Sort notes into the slots of an equal grid they overlap.\n *\n * A per-slot histogram asks one question once per slot, so the notes are\n * grouped once rather than rescanned for every slot: scanning them per slot\n * costs the note count times the slot count, and at a fixed note density the\n * slot count grows with the piece, so the total grows with its square. Both the\n * chord search and the key search cut a piece into slots, and they group their\n * notes here so neither can drift back to rescanning.\n *\n * Notes are kept by identity, so a caller may deduplicate a note spanning\n * several slots with a `Set`.\n *\n * @param notes The notes to group; the parts of one falling outside the grid\n * are dropped.\n * @param grid The grid to group them on.\n * @param slotCount How many slots the grid holds.\n * @param opts `name` labels the budget error, `budget` bounds the total number\n * of note-to-slot memberships.\n * @returns One array per slot, each holding the notes overlapping that slot in\n * input order.\n * @throws If the memberships exceed `budget`.\n */\nexport function bucketNotesBySlot(\n notes: readonly NoteEvent[],\n grid: SlotGrid,\n slotCount: number,\n opts: { name: string; budget?: number },\n): NoteEvent[][] {\n const buckets: NoteEvent[][] = Array.from({ length: slotCount }, () => []);\n let memberships = 0;\n for (const note of notes) {\n const first = Math.max(0, Math.floor((note.startBeat - grid.origin) / grid.slotBeats));\n const lastExclusive = Math.min(\n slotCount,\n Math.ceil((note.startBeat + note.durationBeat - grid.origin) / grid.slotBeats),\n );\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, opts.name, opts.budget);\n for (let slot = first; slot < lastExclusive; slot += 1) {\n buckets[slot]?.push(note);\n }\n }\n return buckets;\n}\n\n/** A window's pitch-class weight histogram and the summaries drawn from it. */\nexport type WindowWeights = {\n /** Weight per pitch class, indexed 0-11. */\n weights: number[];\n /** Sum of every pitch class's weight. */\n totalWeight: number;\n /** The single largest pitch-class weight. */\n maxWeight: number;\n /** Lowest sounding MIDI pitch, or +Infinity when the window is empty. */\n lowestPitch: number;\n};\n\n/**\n * Weigh the pitch classes sounding across `[windowStart, windowEnd)`.\n *\n * A note contributes its overlap with the window, scaled by velocity and, when\n * its onset falls inside the window, by a metric-accent bonus. Weighing by\n * overlap rather than by full duration is what makes the result additive over\n * adjacent windows: a note held across a boundary counts in each window only\n * for the part of it that sounds there.\n *\n * The accents are read from the meter itself rather than from the signature in\n * force at the window's start, so an onset under a meter change is weighed\n * against the bar it actually falls in and a pickup at a negative beat is\n * weighed as the upbeat it is.\n *\n * @param notes The notes to weigh; those not overlapping the window are ignored.\n * @param windowStart First beat of the window.\n * @param windowEnd End of the window, exclusive.\n * @param meter A single signature, or the piece's meter map, supplying the\n * metric accents.\n * @returns The histogram and its summaries.\n */\nexport function windowWeights(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n meter: MeterLike,\n): WindowWeights {\n const weights = new Array<number>(12).fill(0);\n let lowestPitch = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);\n if (overlap <= BEAT_EPS) {\n continue;\n }\n const onsetInWindow =\n note.startBeat >= windowStart - BEAT_EPS && note.startBeat < windowEnd - BEAT_EPS;\n const pc = pitchClass(note.pitch);\n // The accent is asked for only where the onset falls in the window: a note\n // held across the boundary is already counted for its part of it.\n const weight = noteWeight(note, overlap, onsetInWindow ? meter : undefined);\n weights[pc] = (weights[pc] ?? 0) + weight;\n // A note the histogram does not count cannot be the bass either. A velocity\n // of zero is a note event the library accepts — a note-off written as one,\n // a muted ghost layer — and it weighs nothing, so a lowest note taken from\n // it named a pitch class the histogram had never heard of: the reading that\n // follows then found no bass among the pitches it selected and dropped the\n // inversion, the slash bass and the augmented-sixth accounting with it.\n if (weight > BEAT_EPS && note.pitch < lowestPitch) {\n lowestPitch = note.pitch;\n }\n }\n let totalWeight = 0;\n let maxWeight = 0;\n for (const w of weights) {\n totalWeight += w;\n maxWeight = Math.max(maxWeight, w);\n }\n return { weights, totalWeight, maxWeight, lowestPitch };\n}\n","/**\n * Key-profile vectors for {@link detectKey}.\n *\n * A key profile is a 12-entry vector indexed from the tonic (0 = tonic, 1 = flat\n * second, ... 11 = major seventh) giving how strongly each chromatic degree is\n * expected to sound in that key. Ranking a candidate key means rotating its\n * profile onto the candidate tonic and correlating it with the pitch-class\n * distribution of the music, which is what separates a key from its relative:\n * C major and A minor contain the same seven pitch classes, but they expect the\n * weight to fall on different degrees.\n *\n * The published vectors below are transcribed from their sources and are not\n * tuned for this library; a citation accompanies each so a reader can check the\n * digits.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\n\n/**\n * A pair of 12-entry key profiles, one per mode, each indexed from its own\n * tonic (index 0 = tonic, 1 = flat second, ... 11 = major seventh).\n *\n * @category Recognition\n */\nexport type KeyProfilePair = {\n /** Degree weights for a major candidate, indexed from the major tonic. */\n major: readonly number[];\n /** Degree weights for a minor candidate, indexed from the minor tonic. */\n minor: readonly number[];\n};\n\n/**\n * Name of a built-in key profile accepted by {@link DetectKeyOptions.profile}.\n *\n * @category Recognition\n */\nexport type KeyProfileName = 'krumhansl' | 'temperley' | 'flat';\n\n/**\n * Krumhansl–Kessler probe-tone profiles, the vectors the Krumhansl–Schmuckler\n * key-finding algorithm was defined over.\n *\n * Source: Krumhansl, Carol L. *Cognitive Foundations of Musical Pitch*, Oxford\n * Psychology Series No. 17, Oxford University Press, 1990, p. 37 (the averaged\n * probe-tone ratings of Krumhansl & Kessler 1982); the key-finding algorithm\n * itself is Chapter 4. These are the digits key-finding tools carry as the\n * Krumhansl–Schmuckler weight set.\n *\n * Each entry is a mean rating on a 1..7 scale, so the tonic (6.35 major, 6.33\n * minor) and the dominant (5.19 major) dominate, and the raised submediant of\n * minor is deliberately weaker than its flat form.\n */\nexport const KRUMHANSL_KESSLER_PROFILE: KeyProfilePair = {\n major: [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88],\n minor: [6.33, 2.68, 3.52, 5.38, 2.6, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17],\n};\n\n/**\n * Temperley's Kostka–Payne profiles: the proportion of segments of a key in\n * which each scale degree appears, counted over the Kostka–Payne harmony\n * textbook corpus.\n *\n * Source: Temperley, David. *Music and Probability*, MIT Press, 2007, p. 85.\n * These are the digits key-finding tools carry as the Temperley–Kostka–Payne\n * weight set.\n *\n * These are presence proportions rather than listener ratings, so chromatic\n * degrees fall much closer to zero than in {@link KRUMHANSL_KESSLER_PROFILE};\n * that makes the profile decisive on diatonic music and blunt on chromatic\n * music. Note the corpus quirk that the dominant outweighs the tonic in minor.\n */\nexport const TEMPERLEY_KOSTKA_PAYNE_PROFILE: KeyProfilePair = {\n major: [0.748, 0.06, 0.488, 0.082, 0.67, 0.46, 0.096, 0.715, 0.104, 0.366, 0.057, 0.4],\n minor: [0.712, 0.084, 0.474, 0.618, 0.049, 0.46, 0.105, 0.747, 0.404, 0.067, 0.133, 0.33],\n};\n\n/**\n * A flat profile over the scale, reproducing plain scale-membership scoring.\n *\n * Every scale degree carries the same weight and every chromatic degree carries\n * none, so correlating against this vector ranks candidates by the share of the\n * input weight that lands inside the scale — the behaviour key detection had\n * before profile correlation. The tonic carries 1.5 rather than 1, which is the\n * same half-a-count tonic bonus that scoring used to add on top of the\n * membership sum.\n *\n * The major and minor vectors are permutations of one another (both are seven\n * ones-with-a-1.5-tonic in a field of five zeros), which is what keeps the two\n * modes commensurable: correlation against either is then a strictly increasing\n * function of the in-scale weight, so the ranking matches the membership sum\n * exactly rather than approximately.\n *\n * The minor vector is the natural minor. Membership scoring used to rank a\n * minor candidate by whichever of the three minor variants scored best; profile\n * ranking picks the variant after the fact instead (see {@link detectKey}), so\n * this profile reproduces the membership ranking, not that per-candidate\n * maximum.\n */\nexport const FLAT_SCALE_PROFILE: KeyProfilePair = {\n major: [1.5, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],\n minor: [1.5, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0],\n};\n\n/** Number of entries every key profile must have, one per chromatic degree. */\nconst PROFILE_LENGTH = 12;\n\n/**\n * Reciprocal of the quantum {@link profileScore} rounds to.\n *\n * Rotating a profile sums the same twelve products in twelve different orders,\n * so candidates that are mathematically equal — every rotation of a chromatic\n * input, for instance — come out differing by a unit in the last place. Left\n * alone that noise decides the ranking, which would make the order an artefact\n * of summation order rather than of the music. Rounding to 1e-12, eleven orders\n * of magnitude coarser than the noise and far finer than any musically\n * meaningful difference, makes equal candidates compare equal so the documented\n * tie-break can do its job.\n */\nconst SCORE_QUANTUM = 1e12;\n\n/** Built-in profiles addressable by name. */\nconst NAMED_PROFILES: Record<KeyProfileName, KeyProfilePair> = {\n krumhansl: KRUMHANSL_KESSLER_PROFILE,\n temperley: TEMPERLEY_KOSTKA_PAYNE_PROFILE,\n flat: FLAT_SCALE_PROFILE,\n};\n\n/** Reject a vector that cannot be correlated against a distribution. */\nfunction assertProfileVector(vector: readonly number[], name: string): void {\n if (vector.length !== PROFILE_LENGTH) {\n throw new InvalidInputError(`${name} must have ${PROFILE_LENGTH} entries`);\n }\n for (let index = 0; index < vector.length; index += 1) {\n const value = vector[index];\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidInputError(`${name}[${index}] must be a finite number`);\n }\n }\n}\n\n/**\n * Resolve the `profile` option of {@link detectKey} to a validated profile pair.\n *\n * @param profile A built-in profile name, an explicit pair of 12-entry vectors,\n * or undefined for the default ({@link KRUMHANSL_KESSLER_PROFILE}).\n * @returns The profile pair to rank candidates with.\n * @throws {InvalidInputError} When a custom pair is missing a vector, a vector\n * is not 12 entries long, or an entry is not a finite number.\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * const pitches = [60, 62, 64, 65, 67, 69, 71, 60];\n * // The named default, spelled out and left out, rank the same way.\n * detectKey(pitches, { profile: 'krumhansl' })[0]?.score ===\n * detectKey(pitches)[0]?.score; // true\n * ```\n */\nexport function resolveKeyProfile(\n profile: KeyProfileName | KeyProfilePair | undefined,\n): KeyProfilePair {\n if (profile === undefined) {\n return KRUMHANSL_KESSLER_PROFILE;\n }\n if (typeof profile === 'string') {\n const named = NAMED_PROFILES[profile];\n if (named === undefined) {\n throw new InvalidInputError(\n `unknown key profile '${profile}'; expected one of ${Object.keys(NAMED_PROFILES).join(', ')}`,\n );\n }\n return named;\n }\n if (typeof profile !== 'object' || profile === null) {\n throw new InvalidInputError('profile must be a profile name or a { major, minor } pair');\n }\n const { major, minor } = profile;\n if (!Array.isArray(major) || !Array.isArray(minor)) {\n throw new InvalidInputError('profile must carry a major and a minor vector');\n }\n assertProfileVector(major, 'profile.major');\n assertProfileVector(minor, 'profile.minor');\n return { major, minor };\n}\n\n/**\n * Cosine similarity of a distribution with a rotated profile, the fallback used\n * when Pearson correlation is undefined.\n *\n * @param dist Weighted pitch-class distribution, 12 entries indexed by pitch class.\n * @param vector Key profile indexed from its own tonic.\n * @param tonic Pitch class the profile is rotated onto.\n * @returns The normalized dot product, 0 when either vector is all zeros.\n */\nfunction normalizedDot(dist: readonly number[], vector: readonly number[], tonic: number): number {\n let dot = 0;\n let distSquares = 0;\n let profileSquares = 0;\n for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {\n const d = dist[pc] ?? 0;\n const p = vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH] ?? 0;\n dot += d * p;\n distSquares += d * d;\n profileSquares += p * p;\n }\n const norm = Math.sqrt(distSquares * profileSquares);\n return norm > 0 ? dot / norm : 0;\n}\n\n/**\n * Score one key candidate: the Pearson correlation between a pitch-class\n * distribution and a profile rotated onto the candidate tonic.\n *\n * Pearson r is undefined when either side has zero variance — a distribution\n * that spreads its weight perfectly evenly across all twelve pitch classes, or\n * a caller-supplied profile whose entries are all equal. Rather than emit NaN,\n * such a candidate falls back to the cosine similarity of the same two vectors,\n * which stays in [0, 1], preserves the relative ordering of the candidates that\n * do differ, and is deterministic.\n *\n * The result is rounded to a quantum of 1e-12 so that candidates which differ\n * only by the order their products were summed in compare exactly equal, and\n * the caller's tie-break rather than float noise decides between them.\n *\n * @param dist Weighted pitch-class distribution, 12 entries indexed by pitch class.\n * @param vector Key profile indexed from its own tonic.\n * @param tonic Pitch class the profile is rotated onto.\n * @returns A finite score in [-1, 1]; never NaN.\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * // Weight on the tonic, the fifth and the third, spread as a major key\n * // expects it: the score is the correlation this returns.\n * const best = detectKey([60, 60, 60, 60, 64, 64, 64, 67, 67, 65, 69, 62, 71])[0];\n * best?.score !== undefined && best.score > 0.8; // true\n * ```\n */\nexport function profileScore(\n dist: readonly number[],\n vector: readonly number[],\n tonic: number,\n): number {\n let distSum = 0;\n let profileSum = 0;\n for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {\n distSum += dist[pc] ?? 0;\n profileSum += vector[pc] ?? 0;\n }\n const distMean = distSum / PROFILE_LENGTH;\n const profileMean = profileSum / PROFILE_LENGTH;\n let covariance = 0;\n let distVariance = 0;\n let profileVariance = 0;\n for (let pc = 0; pc < PROFILE_LENGTH; pc += 1) {\n const d = (dist[pc] ?? 0) - distMean;\n const p = (vector[(pc - tonic + PROFILE_LENGTH) % PROFILE_LENGTH] ?? 0) - profileMean;\n covariance += d * p;\n distVariance += d * d;\n profileVariance += p * p;\n }\n const denominator = Math.sqrt(distVariance * profileVariance);\n const raw = denominator > 0 ? covariance / denominator : normalizedDot(dist, vector, tonic);\n if (!Number.isFinite(raw)) {\n return 0;\n }\n const clamped = Math.min(1, Math.max(-1, raw));\n return Math.round(clamped * SCORE_QUANTUM) / SCORE_QUANTUM;\n}\n","/**\n * Recognition: infer a chord name from a set of pitches, or a key from a set of\n * pitch classes. This is the inverse direction of the chord/scale builders,\n * which only go name -> notes.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport { formatNote, pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertArray,\n assertFiniteNumber,\n assertGenerationBudget,\n assertMidiPitch,\n assertNoteEvents,\n assertOptions,\n assertRecord,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, chordQualities, makeChord } from '../../theory/chord/index.js';\nimport type { KeyVariant, ScaleName } from '../../theory/scale/index.js';\nimport {\n HARMONIC_MINOR_MASK,\n MAJOR_MASK,\n MELODIC_MINOR_MASK,\n NATURAL_MINOR_MASK,\n spelledKeyOf,\n} from '../../theory/scale/index.js';\nimport type { RejectedCandidate } from '../functional/rationale.js';\nimport { noteWeight } from '../histogram.js';\nimport type { ModalScaleName } from './modes.js';\nimport { modalProfileVector, resolveModalCandidates } from './modes.js';\nimport type { KeyProfileName, KeyProfilePair } from './profiles.js';\nimport { profileScore, resolveKeyProfile } from './profiles.js';\n\nexport type { ModalCandidate, ModalScaleName } from './modes.js';\nexport { MODAL_SCALE_NAMES } from './modes.js';\nexport type { KeyProfileName, KeyProfilePair } from './profiles.js';\n\n/**\n * A candidate chord interpretation of a pitch set.\n *\n * @category Recognition\n */\nexport type ChordMatch = {\n rootPc: number;\n quality: ChordQuality;\n /** Chord tones absent from the input (an incomplete voicing). */\n missingPcs: number[];\n /** Input pitch classes not belonging to the chord. */\n extraPcs: number[];\n /** True when the input pitch-class set equals the chord exactly. */\n exact: boolean;\n /**\n * Inversion implied by a known bass: 0 for root position, 1..n for an\n * inversion. Null when no inversion can be named — either the input is an\n * unordered pitch-class set with no bass, or the bass is not a chord tone (a\n * pedal or passing bass), in which case `bassPc` still reports it.\n */\n inversion: number | null;\n /** Bass pitch class when the lowest note is not the root. */\n bassPc?: number;\n};\n\n/**\n * Input interpretation for {@link detectChord} and {@link detectChordBest}.\n *\n * @category Recognition\n */\nexport type DetectChordOptions = {\n /**\n * `midi` uses the numerically lowest pitch as bass; `pitchClass` treats the\n * input as unordered. `auto` (default) selects pitch-class mode only when all\n * values lie in 0..11.\n */\n input?: 'auto' | 'midi' | 'pitchClass';\n /** Explicit bass pitch class, including for an unordered pitch-class set. */\n bassPc?: number;\n};\n\n/**\n * A candidate key interpretation of a pitch-class set.\n *\n * @category Recognition\n */\nexport type KeyMatch = {\n /** The scale that scored best for this tonic and mode, `variant` included. */\n key: KeyScale;\n /**\n * The major or minor key this match is closest to. A modal match reports the\n * mode it leans on — dorian, phrygian and locrian are minor, lydian and\n * mixolydian major — so a caller that reads nothing but `mode` still gets a\n * musically sane answer; `scaleName` is where the mode itself is named.\n */\n mode: 'major' | 'minor';\n /**\n * Which scale form `key` uses. Minor keys report whichever of the natural,\n * harmonic and melodic masks covers the most input weight; the variant does\n * not take part in ranking. A church mode reports `'modal'`, since its scale\n * is neither of the major nor one of the three minor forms.\n */\n variant: KeyVariant;\n /**\n * The entry of `NAMED_SCALES` whose mask is `key.modeMask12`, naming the scale\n * exactly where `mode` and `variant` only place it: `'major'`,\n * `'naturalMinor'`, `'harmonicMinor'`, `'melodicMinor'`, or one of the church\n * modes when {@link DetectKeyOptions.modes} put them in the running. Ionian\n * and Aeolian are reported as `'major'` and `'naturalMinor'`, the names of the\n * candidates they are the same scale as.\n */\n scaleName: ScaleName;\n /**\n * Fraction of the distinct input pitch classes that belong to `key`, in\n * [0, 1]. Measured against the returned scale, so it always agrees with\n * `isScaleTone(pc, match.key)`. This is a coverage figure, not the ranking:\n * see `score`.\n */\n fit: number;\n /**\n * The value the results are ranked by, in [-1, 1]: the Pearson correlation\n * between the weighted pitch-class distribution of the input and this\n * candidate's key profile rotated onto its tonic. 1 is a distribution shaped\n * exactly like the profile, 0 no relationship, negative an anti-correlation.\n * Unlike `fit` it is a distribution measure, not a membership count, so where\n * the weight falls among the scale degrees is what separates a key from its\n * relative. Always a finite number; see {@link DetectKeyOptions.profile} for\n * the degenerate case.\n */\n score: number;\n /**\n * Why this candidate scored what it did, in the phrasing\n * {@link analyzeVoice} uses for a note.\n *\n * Present only when {@link DetectKeyOptions.explain} asked for it: a ranking\n * is 24 candidates before the modes are counted, and most callers read the\n * first one or two.\n */\n rationale?: string;\n /**\n * The candidates that were considered and rejected, and why each ranked\n * below this one.\n *\n * Carried by the top-ranked match alone — the rivals of a runner-up are the\n * rest of the list, which the caller already holds — and only when\n * {@link DetectKeyOptions.explain} asked for it.\n */\n alternatives?: RejectedCandidate[];\n};\n\n/**\n * Which form of a scale a {@link KeyMatch} settled on.\n *\n * Defined with the rest of a key's identity, and re-exported here because a\n * detection result is where most callers first meet it.\n *\n * @category Recognition\n */\nexport type { KeyVariant };\n\n/**\n * Input weighting for {@link detectKey}.\n *\n * @category Recognition\n */\nexport type DetectKeyOptions = {\n /**\n * How much each pitch counts toward the histogram, one entry per pitch.\n * Defaults to one per pitch, which weighs a thirty-second-note ornament as\n * heavily as the whole note under it. {@link detectKeyFromNotes} supplies\n * duration times velocity, matching how chord inference weighs its own\n * histogram.\n */\n weights?: readonly number[];\n /**\n * Which key profile ranks the candidates: the 12 degree weights a key is\n * expected to distribute its music over, correlated against the distribution\n * actually observed.\n *\n * `'krumhansl'` (the default) uses the Krumhansl–Kessler probe-tone ratings,\n * `'temperley'` the Kostka–Payne corpus proportions, and `'flat'` a flat\n * profile over the scale, which reduces the ranking to plain scale membership\n * — the behaviour this detector had before profile correlation, kept as a way\n * back for a caller that depends on it. A custom pair supplies two 12-entry\n * vectors indexed from the tonic (index 0 = tonic, 1 = flat second, and so on\n * to 11 = major seventh).\n *\n * When either the distribution or the rotated profile has zero variance the\n * correlation is undefined; such a candidate is scored by the normalized dot\n * product (cosine similarity) of the same two vectors instead, so a result\n * never carries NaN.\n *\n * @defaultValue `'krumhansl'`\n */\n profile?: KeyProfileName | KeyProfilePair;\n /**\n * Whether the church modes join the 24 major and minor keys as candidates.\n *\n * Off by default, because a modal candidate can only change a ranking by\n * outranking something: a caller working in common-practice repertoire, where\n * a raised sixth over a minor tonic is a melodic-minor inflection rather than\n * a dorian tonality, should keep reading the answer it reads today. Turn it on\n * (`true` for all five, or a list to name the modes a repertoire actually\n * uses) and each enabled mode adds one candidate per tonic, ranked on a\n * profile derived from its parallel major or minor key.\n *\n * Ionian and Aeolian are not on the list: they are the major and natural minor\n * candidates, which already carry `scaleName` `'major'` and `'naturalMinor'`.\n *\n * @defaultValue `false`\n */\n modes?: boolean | readonly ModalScaleName[];\n /**\n * Attach a {@link KeyMatch.rationale} to every candidate, and\n * {@link KeyMatch.alternatives} to the top-ranked one.\n *\n * Off by default, and the one explanation in the library that is: the other\n * entry points explain a single reading, while a ranking explains 24 of them\n * (36 or more with the modes on), each needing a phrase built for a candidate\n * nobody asked about. That cost outweighs the detection itself, so it is paid\n * only on request.\n *\n * @defaultValue false\n */\n explain?: boolean;\n /**\n * Upper bound on the input pitches processed by this detection call.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/** Unique pitch classes of the input, sorted ascending. */\nfunction uniquePitchClasses(pitches: readonly number[]): number[] {\n return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);\n}\n\nfunction assertPitches(\n pitches: readonly number[],\n name: string,\n budget: number | undefined = undefined,\n): void {\n assertArray(pitches, name);\n assertGenerationBudget(pitches.length, name, budget);\n for (let index = 0; index < pitches.length; index += 1) {\n assertMidiPitch(pitches[index] ?? Number.NaN, `pitches[${index}]`);\n }\n}\n\n/**\n * Identify chords matching a set of pitches.\n *\n * Every input pitch class is tried as a root against every known chord quality.\n * A match is reported when all of the chord's tones are present in the input, or\n * when the only absent tone is the perfect fifth (a common omission in shell\n * voicings); matches are ranked best-first by fewest extra notes, then fewest\n * missing notes, then root position over an inversion, then most specific\n * (largest) chord. An exact match (no extras, no missing) is flagged and ranked\n * first.\n *\n * The root-position step decides between readings of the same pitches that are\n * otherwise equally good: `[55, 59, 62, 64]` is read as G6 rather than Em7,\n * because G is in the bass.\n *\n * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).\n * @returns Ranked chord interpretations (may be empty).\n * @example\n * ```ts\n * import { detectChord } from '@libraz/libcantus';\n * const matches = detectChord([60, 64, 67]); // C E G\n * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }\n * ```\n * @category Recognition\n */\nexport function detectChord(\n pitches: readonly number[],\n opts: DetectChordOptions = {},\n): ChordMatch[] {\n const asked = assertOptions(opts, 'opts');\n assertPitches(pitches, 'chord detection pitches');\n if (asked.bassPc !== undefined) assertFiniteNumber(asked.bassPc, 'bassPc');\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const inputKind =\n asked.input === undefined || asked.input === 'auto'\n ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11)\n ? 'pitchClass'\n : 'midi'\n : asked.input;\n const bassPc =\n asked.bassPc !== undefined\n ? pitchClass(asked.bassPc)\n : inputKind === 'midi'\n ? pitchClass(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity))\n : undefined;\n const inputSet = new Set(input);\n const matches: ChordMatch[] = [];\n const qualities = chordQualities();\n const toneCounts = new Map(\n qualities.map((quality) => [quality, chordPitchClasses(makeChord(0, quality)).length]),\n );\n const qualityRank = new Map(qualities.map((quality, index) => [quality, index]));\n for (const rootPc of input) {\n for (const quality of qualities) {\n const chord = makeChord(rootPc, quality);\n const tones = chordPitchClasses(chord);\n const toneSet = new Set(tones);\n const missingPcs = tones.filter((pc) => !inputSet.has(pc));\n // Accept an exact-tone match, or one whose only absent tone is the perfect\n // fifth; any other missing tone (third, sus tone, seventh, ...) rejects.\n const fifthPc = pitchClass(rootPc + 7);\n if (missingPcs.length > 1 || (missingPcs.length === 1 && missingPcs[0] !== fifthPc)) {\n continue;\n }\n const extraPcs = input.filter((pc) => !toneSet.has(pc));\n const bassIndex =\n bassPc === undefined\n ? -1\n : chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);\n // A bass that is not a chord tone names no inversion; reporting it as 0\n // would be indistinguishable from root position for a caller that reads\n // `inversion === 0` as \"no slash needed\".\n const inversion = bassPc === undefined || bassIndex < 0 ? null : bassIndex;\n const match: ChordMatch = {\n rootPc,\n quality,\n missingPcs,\n extraPcs,\n exact: extraPcs.length === 0 && missingPcs.length === 0,\n inversion,\n };\n if (bassPc !== undefined && bassPc !== rootPc) {\n match.bassPc = bassPc;\n }\n matches.push(match);\n }\n }\n matches.sort((a, b) => {\n if (a.extraPcs.length !== b.extraPcs.length) {\n return a.extraPcs.length - b.extraPcs.length;\n }\n if (a.missingPcs.length !== b.missingPcs.length) {\n return a.missingPcs.length - b.missingPcs.length;\n }\n // Prefer root position (the bass is the chord root) on a tie.\n if ((a.inversion === 0) !== (b.inversion === 0)) {\n return a.inversion === 0 ? -1 : 1;\n }\n const aSize = toneCounts.get(a.quality) ?? 0;\n const bSize = toneCounts.get(b.quality) ?? 0;\n if (aSize !== bSize) {\n return bSize - aSize;\n }\n // Past size, two readings of the same pitches are told apart by the chord\n // they name and by nothing else: leaving it to the sort's stability would\n // hand the decision to the order the pitch classes were pushed in, which is\n // their numeric order, so the same set of intervals would be named one\n // chord here and another a fourth higher. The canonical quality order\n // decides instead, and only a chord symmetrical under transposition — a\n // diminished seventh, which has no one root — reaches the root below it.\n const aRank = qualityRank.get(a.quality) ?? qualities.length;\n const bRank = qualityRank.get(b.quality) ?? qualities.length;\n if (aRank !== bRank) {\n return aRank - bRank;\n }\n return a.rootPc - b.rootPc;\n });\n return matches;\n}\n\n/**\n * The single best chord interpretation of a pitch set, or null if none.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns The top-ranked chord, or null when nothing matches.\n * @example\n * ```ts\n * import { detectChordBest } from '@libraz/libcantus';\n * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }\n * ```\n * @category Recognition\n */\nexport function detectChordBest(\n pitches: readonly number[],\n opts: DetectChordOptions = {},\n): Chord | null {\n const best = detectChord(pitches, opts)[0];\n if (!best) {\n return null;\n }\n return makeChord(best.rootPc, best.quality, best.bassPc);\n}\n\n/**\n * Minor-scale variants a minor-key candidate may report. Ranking does not\n * consult them: the minor profile already expects weight on the raised sixth\n * and seventh, so the variant is chosen afterwards as the mask that covers the\n * most input weight. Natural minor comes first so it wins a tie as the most\n * diatonic reading.\n */\nconst MINOR_VARIANTS = [\n { variant: 'natural', mask: NATURAL_MINOR_MASK, scaleName: 'naturalMinor' },\n { variant: 'harmonic', mask: HARMONIC_MINOR_MASK, scaleName: 'harmonicMinor' },\n { variant: 'melodic', mask: MELODIC_MINOR_MASK, scaleName: 'melodicMinor' },\n] as const satisfies readonly { variant: KeyVariant; mask: number; scaleName: ScaleName }[];\n\n/** The single major form, kept in the same shape as the minor variants. */\nconst MAJOR_VARIANTS = [\n { variant: 'major', mask: MAJOR_MASK, scaleName: 'major' },\n] as const satisfies readonly { variant: KeyVariant; mask: number; scaleName: ScaleName }[];\n\n/**\n * Tie-break order over the scales a candidate can report, applied last so that\n * two candidates on the same tonic which score and fit identically — as every\n * candidate does on a perfectly even distribution — still rank in an order the\n * input alone decides. Major before the minor forms reproduces the order the 24\n * keys have always ranked in; the modes follow, in candidate order.\n */\nconst SCALE_NAME_ORDER: readonly ScaleName[] = [\n 'major',\n 'naturalMinor',\n 'harmonicMinor',\n 'melodicMinor',\n 'dorian',\n 'phrygian',\n 'lydian',\n 'mixolydian',\n 'locrian',\n];\n\n/** Rank of a reported scale in {@link SCALE_NAME_ORDER}. */\nfunction scaleNameRank(name: ScaleName): number {\n return SCALE_NAME_ORDER.indexOf(name);\n}\n\n/**\n * How many runners-up the winning match names in its `alternatives`.\n *\n * Every candidate below the winner was rejected, so the list has to stop\n * somewhere; three is the neighbourhood a disagreement actually lives in — the\n * relative, the parallel and the neighbouring fifth — and the caller holding\n * the full ranking can read further itself.\n */\nconst EXPLAINED_RIVALS = 3;\n\n/** Decimal places a correlation is quoted to in a rationale. */\nconst SCORE_DIGITS = 2;\n\n/**\n * Decimal places a comparison of two correlations may grow to.\n *\n * Two candidates can be separated by a margin the two quoted decimals hide,\n * which would read as \"ranked below (0.68 against 0.68)\" — the very thing a\n * rationale exists to prevent. The comparison quotes further digits until the\n * two differ, up to the point where the difference stops being a musical fact.\n */\nconst MAX_SCORE_DIGITS = 6;\n\n/** Two correlations, quoted to enough digits to tell them apart. */\nfunction quoteScores(rival: number, winner: number): string {\n let digits = SCORE_DIGITS;\n while (digits < MAX_SCORE_DIGITS && rival.toFixed(digits) === winner.toFixed(digits)) {\n digits += 1;\n }\n return `${rival.toFixed(digits)} against ${winner.toFixed(digits)}`;\n}\n\n/** The key a match names, spelled the way its signature would be written. */\nfunction keyLabel(match: KeyMatch): string {\n const tonic = formatNote(spelledKeyOf(match.key).tonic);\n // Scale names are camelCase identifiers; a rationale reads them as prose.\n const scale = match.scaleName.replace(/([A-Z])/g, ' $1').toLowerCase();\n return `${tonic} ${scale}`;\n}\n\n/** Why a candidate scored what it did: the correlation and the coverage. */\nfunction describeKeyMatch(match: KeyMatch, inputSize: number): string {\n // `fit` is the coverage share; the count behind it is what a reader checks.\n const inScale = Math.round(match.fit * inputSize);\n return `${keyLabel(match)}: profile correlation ${match.score.toFixed(SCORE_DIGITS)}, with ${inScale} of ${inputSize} input pitch classes in the scale`;\n}\n\n/** Which link of the documented tie-break chain put a rival below the winner. */\nfunction keyRivalReason(winner: KeyMatch, rival: KeyMatch): string {\n if (rival.score !== winner.score) {\n return `Ranked below on profile correlation (${quoteScores(rival.score, winner.score)})`;\n }\n if (rival.fit !== winner.fit) {\n return 'Tied on correlation, ranked below on the share of the input its scale covers';\n }\n if (rival.key.rootPc !== winner.key.rootPc) {\n return 'Tied on correlation and coverage, ranked below by tonic pitch class';\n }\n return 'Tied on correlation and coverage on the same tonic, ranked below by scale, which takes major before the minor forms and the modes last';\n}\n\n/** Attach the rationale to every candidate and the rivals to the winner. */\nfunction explainMatches(results: KeyMatch[], inputSize: number): void {\n for (const match of results) {\n match.rationale = describeKeyMatch(match, inputSize);\n }\n const winner = results[0];\n if (winner === undefined) {\n return;\n }\n winner.alternatives = results\n .slice(1, EXPLAINED_RIVALS + 1)\n .map((rival) => ({ label: keyLabel(rival), reason: keyRivalReason(winner, rival) }));\n}\n\n/**\n * Rank major and minor keys by how well a set of pitch classes is distributed\n * like each key.\n *\n * The input becomes a weighted pitch-class distribution, and every one of the\n * 24 candidates is scored by the Pearson correlation between that distribution\n * and its key profile rotated onto the candidate tonic (see\n * {@link DetectKeyOptions.profile}). Correlating against a profile rather than\n * counting scale members is what tells a key apart from its relative: C major\n * and A minor contain the same seven pitch classes, so a membership count can\n * only separate them by a tie-break, while their profiles expect the weight on\n * different degrees.\n *\n * Choosing the minor variant is deliberately not part of the ranking. A minor\n * candidate is ranked once, on the minor profile, which already expects some\n * weight on the raised sixth and seventh; only then does it report whichever of\n * the natural, harmonic and melodic masks covers the most input weight, and\n * `fit` is measured against that mask. That split is why a harmonic-minor\n * cadence ranks its own tonic first without the harmonic variant having to win\n * a scoring contest against the natural one.\n *\n * {@link DetectKeyOptions.modes} adds the church modes to the same contest, one\n * candidate per mode per tonic, each ranked on a profile derived from its\n * parallel major or minor key. A modal winner reports the mode in `scaleName`\n * and still reports the key it leans on in `mode`. The option is off by default,\n * so a caller that does not ask for modes gets the ranking of the 24 keys it has\n * always got.\n *\n * Ties are broken by `fit` descending, then tonic pitch class ascending, then by\n * scale — major, the minor forms, then the modes — so the order is fully\n * determined by the input rather than by the order candidates happen to be built\n * in. Returns all 24 keys ranked best-first, plus 12 more per enabled mode, or\n * an empty array for an empty input (mirroring {@link detectChord}).\n *\n * {@link DetectKeyOptions.explain} adds a `rationale` to every candidate and,\n * to the winner, the runners-up it beat with the link of that tie-break chain\n * that separated each. It is off by default; see the option for why.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @param opts How to weigh the input, which profile to rank with, and whether\n * the modes take part; see {@link DetectKeyOptions}.\n * @returns Ranked key interpretations (empty for an empty input), each carrying\n * a finite `score` in [-1, 1].\n * @throws {InvalidInputError} When `weights` does not have one entry per pitch,\n * `profile` is not a known name or a valid pair of 12-entry vectors, or\n * `modes` names something that is not a church mode.\n * @throws {RangeError} When a pitch is outside the MIDI domain or the input\n * exceeds the budget.\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale\n * keys[0].mode; // 'major', with keys[0].key.rootPc === 0\n * keys[1].mode; // 'minor' — on 9: A minor, the relative, ranked just below\n * const riff = detectKey([62, 62, 65, 69, 71, 69, 65, 62], { modes: true });\n * riff[0].scaleName; // 'dorian', on rootPc 2, still reported as mode 'minor'\n * ```\n * @category Recognition\n */\nexport function detectKey(pitches: readonly number[], opts: DetectKeyOptions = {}): KeyMatch[] {\n // The default fills in only for an absent argument, so an explicit `null`\n // reaches the first field read as itself; a caller working in JavaScript, or\n // one passing on an options object it never built, arrives here with one.\n const asked = assertRecord<DetectKeyOptions>(opts, 'opts');\n assertPitches(pitches, 'key detection pitches', asked.budget);\n const weights = asked.weights;\n if (weights !== undefined && weights.length !== pitches.length) {\n throw new InvalidInputError('weights must have one entry per pitch');\n }\n const profile = resolveKeyProfile(opts.profile);\n // Derived once per call: a mode's vector depends on the profile, not on which\n // tonic it is being rotated onto.\n const modalCandidates = resolveModalCandidates(opts.modes).map((candidate) => ({\n candidate,\n vector: modalProfileVector(profile, candidate),\n }));\n const counts = new Map<number, number>();\n let total = 0;\n for (let index = 0; index < pitches.length; index += 1) {\n const weight = weights === undefined ? 1 : (weights[index] ?? 0);\n assertFiniteNumber(weight, `weights[${index}]`);\n if (weight <= 0) {\n continue;\n }\n const pc = pitchClass(pitches[index] ?? 0);\n counts.set(pc, (counts.get(pc) ?? 0) + weight);\n total += weight;\n }\n if (total === 0) {\n return [];\n }\n const input = [...counts.keys()];\n // The distribution the profiles are correlated against: one bin per pitch\n // class, holding the total weight that landed on it.\n const dist = new Array<number>(12).fill(0);\n for (const [pc, count] of counts) {\n dist[pc] = count;\n }\n const results: KeyMatch[] = [];\n for (let tonic = 0; tonic < 12; tonic += 1) {\n for (const mode of ['major', 'minor'] as const) {\n const vector = mode === 'major' ? profile.major : profile.minor;\n const score = profileScore(dist, vector, tonic);\n // Ranking is settled; the variant only decides which scale the caller is\n // handed, so it is the mask covering the most input weight. Ties keep the\n // earlier (more diatonic) variant, and `fit` is measured against the very\n // scale that wins here.\n const variants = mode === 'major' ? MAJOR_VARIANTS : MINOR_VARIANTS;\n let covered = Number.NEGATIVE_INFINITY;\n let inScale = 0;\n let best: { variant: KeyVariant; mask: number; scaleName: ScaleName } = variants[0];\n for (const candidate of variants) {\n let candidateCovered = 0;\n let candidateInScale = 0;\n for (const [pc, count] of counts) {\n const offset = (pc - tonic + 12) % 12;\n if ((candidate.mask >> offset) & 1) {\n candidateCovered += count;\n candidateInScale += 1;\n }\n }\n if (candidateCovered > covered) {\n covered = candidateCovered;\n inScale = candidateInScale;\n best = candidate;\n }\n }\n results.push({\n key: { rootPc: tonic, modeMask12: best.mask },\n mode,\n variant: best.variant,\n scaleName: best.scaleName,\n fit: inScale / input.length,\n score,\n });\n }\n // A mode has one scale, so there is no variant to choose: it is ranked on\n // its derived profile and reports the mask it was ranked with.\n for (const { candidate, vector } of modalCandidates) {\n let inScale = 0;\n for (const pc of counts.keys()) {\n if ((candidate.mask >> ((pc - tonic + 12) % 12)) & 1) {\n inScale += 1;\n }\n }\n results.push({\n key: { rootPc: tonic, modeMask12: candidate.mask },\n mode: candidate.mode,\n variant: 'modal',\n scaleName: candidate.scaleName,\n fit: inScale / input.length,\n score: profileScore(dist, vector, tonic),\n });\n }\n }\n // Floating-point scores tie often enough — a chromatic input scores every\n // rotation of one mode the same, so the twelve major keys tie with each other\n // and the twelve minor keys tie with each other — that leaving the order to\n // the sort's stability would make construction order the contract. Rank on\n // the reported fields instead.\n results.sort((a, b) => {\n if (a.score !== b.score) {\n return b.score - a.score;\n }\n if (a.fit !== b.fit) {\n return b.fit - a.fit;\n }\n if (a.key.rootPc !== b.key.rootPc) {\n return a.key.rootPc - b.key.rootPc;\n }\n return scaleNameRank(a.scaleName) - scaleNameRank(b.scaleName);\n });\n if (opts.explain === true) {\n // After the sort, so a rival's reason can name the link of the tie-break\n // chain that actually separated it from the winner.\n explainMatches(results, input.length);\n }\n return results;\n}\n\n/**\n * Rank keys by how well they contain a set of note events, weighting each note\n * by how much of the music it actually occupies.\n *\n * {@link detectKey} counts every pitch once, which lets a run of fast ornamental\n * notes outvote the sustained harmony that establishes the key. This weighs\n * each note by the same measure chord inference weighs its own histogram by, so\n * the two agree on what the music emphasises: duration times\n * velocity, with a note that carries no velocity counted at full weight rather\n * than at an assumed one. Notes that never sound (zero or negative duration)\n * are ignored. The weighted distribution is then ranked exactly as\n * {@link detectKey} ranks it, by correlation against a key profile, so `score`\n * is a finite correlation in [-1, 1].\n *\n * The one term of that measure this cannot share is the metric accent, which\n * chord inference adds to an onset by the beat it falls on: the notes arrive\n * here without the meter they are counted in, and reading them against an\n * assumed one would rank the same notes differently under a signature the\n * caller never named. Ranking a whole piece is the coarser question, and the\n * accents are what separate one window from the next rather than one key from\n * another.\n *\n * @param notes The note events to weigh.\n * @param opts Which profile to rank with, whether the modes take part, and the\n * budget for processing imported note events; the weights are derived from the\n * notes themselves.\n * @returns Ranked key interpretations (empty when nothing sounds).\n * @throws {InvalidInputError} When `profile` is not a known name or a valid\n * pair of 12-entry vectors, or `modes` names something that is not a church\n * mode.\n * @throws {RangeError} When a note event is malformed or the input exceeds the\n * budget.\n * @example\n * ```ts\n * import { detectKeyFromNotes } from '@libraz/libcantus';\n * detectKeyFromNotes([{ pitch: 60, startBeat: 0, durationBeat: 4 }])[0]?.key.rootPc; // 0\n * ```\n * @category Recognition\n */\nexport function detectKeyFromNotes(\n notes: readonly NoteEvent[],\n opts: Omit<DetectKeyOptions, 'weights'> = {},\n): KeyMatch[] {\n const asked = assertOptions(opts, 'opts');\n assertNoteEvents(notes, 'key detection notes', {\n allowNonPositiveDuration: true,\n budget: asked.budget,\n });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n return detectKey(\n sounding.map((note) => note.pitch),\n {\n weights: sounding.map((note) => noteWeight(note, note.durationBeat)),\n profile: asked.profile,\n modes: asked.modes,\n explain: asked.explain,\n budget: asked.budget,\n },\n );\n}\n\n/**\n * The single best key interpretation of a pitch set.\n *\n * The counterpart of {@link detectChordBest}: the ranked list is the general\n * answer, but a caller that just wants \"what key is this\" should not have to\n * index into it and assert the result is there. The winner is the candidate\n * whose key profile correlates best with the input distribution, with the same\n * deterministic tie-break {@link detectKey} applies, so its `score` is a finite\n * correlation in [-1, 1] rather than a membership share.\n *\n * This is where {@link DetectKeyOptions.explain} earns its keep: the winner is\n * the only match a caller of this function sees, and with `explain` it carries\n * both its own rationale and the candidates it beat.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @param opts How to weigh the input, which profile to rank with, and whether\n * the modes take part; see {@link DetectKeyOptions}.\n * @returns The top-ranked key, or null when nothing sounds.\n * @throws {InvalidInputError} When `weights` does not have one entry per pitch,\n * `profile` is not a known name or a valid pair of 12-entry vectors, or\n * `modes` names something that is not a church mode.\n * @throws {RangeError} When a pitch is outside the MIDI domain or the input\n * exceeds the budget.\n * @example\n * ```ts\n * import { detectKeyBest } from '@libraz/libcantus';\n * detectKeyBest([0, 2, 4, 5, 7, 9, 11])?.mode; // 'major'\n * ```\n * @category Recognition\n */\nexport function detectKeyBest(\n pitches: readonly number[],\n opts: DetectKeyOptions = {},\n): KeyMatch | null {\n return detectKey(pitches, opts)[0] ?? null;\n}\n","/**\n * Shared helpers for the functional-harmony modules.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport { pitchClassOf as mod12 } from '../../core/pitch/index.js';\n\nexport { pitchClassOf as mod12 } from '../../core/pitch/index.js';\n\nimport type { KeyScale } from '../../core/types.js';\nimport {\n MAJOR_MASK,\n NATURAL_MINOR_MASK,\n scaleTonesInDegreeOrder,\n} from '../../theory/scale/index.js';\n// The mode-mask predicate is not part of the package surface: `isMinorKey` is\n// the public way to ask, and it is this same test one layer up.\nimport { isMinorMask } from '../../theory/scale/masks.js';\n\n/**\n * The chord-and-key predicates this layer shares with the part-writing and\n * voicing layers, re-exported so a sibling here reaches them by the same import\n * as everything else in the module.\n */\nexport {\n hasDominantSonority,\n hasMajorThird,\n isDiatonicChord,\n isNeapolitanChordOf,\n soundsDominantSeventh,\n} from '../../theory/tendency/index.js';\n\nimport { heptatonicFrameOf } from '../../theory/tendency/index.js';\n\n/** Whether a scale has a minor third and no major third (a minor key). */\nexport function isMinorScale(key: KeyScale): boolean {\n return isMinorMask(key.modeMask12);\n}\n\n/** The parallel key of a scale: same tonic, opposite mode. */\nexport function parallelScale(key: KeyScale): KeyScale {\n return {\n rootPc: mod12(key.rootPc),\n modeMask12: isMinorScale(key) ? MAJOR_MASK : NATURAL_MINOR_MASK,\n };\n}\n\n/** Diatonic pitch class of a 1-based scale degree in a key. */\nexport function degreeRootPc(degreeNumber: number, key: KeyScale): number {\n const tones = scaleTonesInDegreeOrder(key);\n if (tones.length === 0) {\n return mod12(key.rootPc);\n }\n // A plain `%` yields a negative index for a degree below the first, which\n // reads as undefined and collapses to the tonic; wrap into range instead.\n const index = (((degreeNumber - 1) % tones.length) + tones.length) % tones.length;\n return tones[index] ?? mod12(key.rootPc);\n}\n\n/**\n * The 1-based scale degrees that already sit a semitone below their parallel\n * major counterpart — the third, sixth and seventh of a natural minor key, the\n * third and seventh of dorian, and so on.\n *\n * Roman-numeral spelling and parsing both need this: a flat sign on such a\n * degree is the degree itself rather than a further lowering, and a chromatic\n * root a semitone above one of them is that degree raised (`#III` in a minor\n * key), not a flattened version of the degree above it.\n *\n * Only heptatonic keys have a degree-for-degree correspondence with a major\n * scale, so anything else reports no lowered degrees.\n */\nexport function loweredDegrees(key: KeyScale): Set<number> {\n const tones = scaleTonesInDegreeOrder(key);\n const out = new Set<number>();\n if (tones.length !== 7) {\n return out;\n }\n const major = scaleTonesInDegreeOrder({ rootPc: mod12(key.rootPc), modeMask12: MAJOR_MASK });\n for (let index = 0; index < 7; index += 1) {\n const tone = tones[index];\n const majorTone = major[index];\n if (tone !== undefined && majorTone !== undefined && mod12(majorTone - tone) === 1) {\n out.add(index + 1);\n }\n }\n return out;\n}\n\n/**\n * The heptatonic frame a Roman numeral is measured against for a key.\n *\n * Roman numerals only have seven glyphs, so a scale with any other number of\n * tones has no degree-for-degree numeral of its own: a pentatonic key would\n * make `VI` wrap around to the tonic and an octatonic or chromatic key would\n * have degrees no numeral can name. Such keys are read against their parallel\n * major instead — the same frame the chromatic fallback spelling already uses —\n * so `chordToRoman` and `romanToChord` stay mutual inverses for every key.\n *\n * It is the frame degrees are counted in generally, so the numerals and the\n * degrees a chord may tonicize are measured against one and the same scale.\n */\nexport function romanReference(key: KeyScale): KeyScale {\n return heptatonicFrameOf(key);\n}\n","/**\n * Augmented-sixth chords: the Italian, French, and German sixths.\n *\n * All three stand on the lowered submediant (b6) in the bass and carry an\n * augmented sixth above it, and all three are predominants whose two outer\n * voices resolve outward onto the dominant. The family is defined by that bass\n * and that interval rather than by a stack of thirds, so a chord is recognized\n * here from the tones sounding over that bass rather than through the\n * Roman-numeral degree tables.\n *\n * The interval is spelled, so recognition reads letters and not only pitch\n * classes: over an Ab bass the augmented sixth is F# and the minor seventh of\n * the bVI7 it otherwise sounds exactly like is Gb. A caller holding pitch\n * classes alone — an analysis of a MIDI track, which names no accidentals —\n * makes that reading with {@link augmentedSixthFromPitchClasses}, and the chord\n * it hands back carries the spelling that decided it.\n */\n\nimport type { Note, NoteLike, SpelledInterval } from '../../core/pitch/index.js';\nimport {\n diatonicLetterOf,\n parseInterval,\n toNoteData,\n transposeByInterval,\n} from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { assertArray, assertOneOf } from '../../core/validation/index.js';\nimport type { Chord, ChordQuality, PitchSpelling } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport {\n type KeyLike,\n type ResolvedKey,\n resolveKey,\n type SpelledKeyLike,\n} from '../../theory/scale/index.js';\nimport { spellChord } from '../../theory/spelling/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { mod12 } from './internal.js';\n\n/**\n * The three augmented-sixth chords, under the nationalities convention gives\n * them.\n *\n * Over an Ab bass in C: the Italian sixth is Ab C F#, the French sixth adds the\n * supertonic (Ab C D F#), and the German sixth adds the lowered mediant\n * (Ab C Eb F#).\n *\n * @category Functional Harmony\n */\nexport type AugmentedSixthKind = 'italian' | 'french' | 'german';\n\n/** Every kind, in the order a sounding chord is matched against them. */\nconst KINDS: readonly AugmentedSixthKind[] = ['italian', 'french', 'german'];\n\n/** Semitones from the tonic up to the lowered submediant, the family's bass. */\nconst BASS_ABOVE_TONIC = 8;\n\n/** Semitone offsets above the bass for each kind's tones. */\nconst TONES_ABOVE_BASS: Record<AugmentedSixthKind, readonly number[]> = {\n italian: [0, 4, 10],\n french: [0, 4, 6, 10],\n german: [0, 4, 7, 10],\n};\n\n/**\n * Spelled intervals above the bass for each kind, bass first.\n *\n * The top note is an augmented sixth and never a minor seventh: over an Ab bass\n * it is F#, which resolves outward to G, rather than the Gb a dominant seventh\n * would spell. Naming the intervals rather than the semitones is what keeps\n * that true in every key, at the cost of the double flats a far flat-side key\n * genuinely needs (the German sixth of Db major is Bbb Db Fb G).\n */\nconst SPELLED_ABOVE_BASS: Record<AugmentedSixthKind, readonly SpelledInterval[]> = {\n italian: ['P1', 'M3', 'A6'].map((name) => parseInterval(name)),\n french: ['P1', 'M3', 'A4', 'A6'].map((name) => parseInterval(name)),\n german: ['P1', 'M3', 'P5', 'A6'].map((name) => parseInterval(name)),\n};\n\n/** The minor sixth from the tonic up to the bass every kind stands on. */\nconst BASS_INTERVAL = parseInterval('m6');\n\n/**\n * How each kind is carried by the pitch-class {@link Chord} type: the root it\n * is measured from, the nearest quality label, and its interval template.\n *\n * None of the three stacks in thirds over its own bass, so the `quality` field\n * can only name the closest tertian sonority: the Italian and German sixths are\n * a dominant seventh without and with its fifth. The French sixth does stack in\n * thirds one rotation away — it is the supertonic seventh with a lowered fifth\n * — so it is rooted there, which is the conventional derivation and the only\n * rooting whose letters a stack of thirds gets right on its own. The other two\n * carry their letters as tone spellings instead.\n *\n * The interval template always names the tones that actually sound, so the\n * pitch classes are exact whatever the quality label says.\n */\nconst CHORD_SHAPE: Record<\n AugmentedSixthKind,\n { rootAboveBass: number; quality: ChordQuality; intervals: readonly number[] }\n> = {\n italian: { rootAboveBass: 0, quality: 'dom7', intervals: [0, 4, 10] },\n french: { rootAboveBass: 6, quality: '7b5', intervals: [0, 4, 6, 10] },\n german: { rootAboveBass: 0, quality: 'dom7', intervals: [0, 4, 7, 10] },\n};\n\n/** Roman-numeral symbol accepted for each kind, including the figured variant. */\nconst KIND_BY_SYMBOL: ReadonlyMap<string, AugmentedSixthKind> = new Map([\n ['It6', 'italian'],\n ['Fr6', 'french'],\n ['Ger6', 'german'],\n // The German sixth's figures over its bass are 6/5; both spellings are in use.\n ['Ger65', 'german'],\n]);\n\n/** The symbol emitted for each kind, one per kind so rendering is canonical. */\nconst SYMBOL_BY_KIND: Record<AugmentedSixthKind, string> = {\n italian: 'It6',\n french: 'Fr6',\n german: 'Ger6',\n};\n\n/** The pitch class of the lowered submediant, the bass every kind stands on. */\nfunction bassPcOf(key: KeyScale): number {\n return mod12(key.rootPc + BASS_ABOVE_TONIC);\n}\n\n/** Reduce a spelled note to the bare letter/alter a chord records as a hint. */\nfunction bareSpelling(note: Note): PitchSpelling {\n return { letter: note.letter, alter: note.alter };\n}\n\n/**\n * The kind's spelled tones in the order its own interval template names them.\n *\n * {@link spellAugmentedSixth} spells upward from the bass, which is the order\n * the chord sounds in but not the order it is measured in. Rotating that\n * spelling onto the tone the template is rooted from puts the letters back in\n * step with the intervals, so both orderings stay derived from one table.\n */\nfunction spelledInChordOrder(kind: AugmentedSixthKind, tonic: Note): Note[] {\n const tones = spelledFromTonic(kind, tonic);\n const rootIndex = TONES_ABOVE_BASS[kind].indexOf(CHORD_SHAPE[kind].rootAboveBass);\n return [...tones.slice(rootIndex), ...tones.slice(0, rootIndex)];\n}\n\n/**\n * Build an augmented-sixth chord in a key.\n *\n * The chord always carries an explicit `bassPc` on the lowered submediant,\n * which is what makes it an augmented sixth rather than the dominant seventh or\n * the altered supertonic seventh it shares pitch classes with, and an explicit\n * interval template naming exactly the tones that sound.\n *\n * It also carries its spelling: the tones as `toneSpellings`, and the root and\n * bass as the usual hints. Those are what let the ordinary chord speller and\n * the symbol formatter name the chord — `Ab C Eb F#` and `Ab7` for the German\n * sixth of C major — since a stack of thirds over that root would put a Gb\n * where the augmented sixth belongs. The spelling is the one the key is written\n * on, so a flat-side key takes the double flats it genuinely needs.\n *\n * The lowered submediant is taken as eight semitones above the tonic in every\n * mode, since the chord is a chromatic alteration rather than a degree of the\n * prevailing scale.\n *\n * @param kind Which of the three augmented sixths to build.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The chord, with its bass on the lowered submediant.\n * @throws If `kind` is not one of the three, or the key's root is not finite.\n * @see {@link spellAugmentedSixth} to spell the family from a tonic without\n * building a chord at all.\n * @example\n * ```ts\n * import { augmentedSixthChord, majorKey } from '@libraz/libcantus';\n * augmentedSixthChord('german', majorKey(0));\n * // Ab C Eb F# in C: { rootPc: 8, quality: 'dom7', intervals: [0, 4, 7, 10], bassPc: 8, ... }\n * ```\n * @category Functional Harmony\n */\nexport function augmentedSixthChord(kind: AugmentedSixthKind, key: SpelledKeyLike): Chord {\n return augmentedSixthOn(kind, key);\n}\n\n/**\n * {@link augmentedSixthChord} for the callers inside the analysis layer.\n *\n * The key is read whole, so the letters come from the key the caller named: the\n * German sixth of Ab minor is Fb Ab Cb D and not the E G# B C## the same pitch\n * classes spell once the key has been reduced and respelled as G# minor.\n */\nfunction augmentedSixthOn(kind: AugmentedSixthKind, keyLike: KeyLike): Chord {\n const checked = assertOneOf(kind, KINDS, 'augmented sixth kind');\n const shape = CHORD_SHAPE[checked];\n const { scale: key, tonic } = resolveKey(keyLike);\n const bassPc = bassPcOf(key);\n const chord = makeChord(mod12(bassPc + shape.rootAboveBass), shape.quality, bassPc);\n chord.intervals = [...shape.intervals];\n const bass = transposeByInterval(tonic, BASS_INTERVAL);\n const tones = spelledInChordOrder(checked, tonic);\n // The rotation starts on the tone the template is measured from, so the first\n // spelling is the root's own; the three kinds all sound, so it is never empty.\n const [root = bass] = tones;\n chord.rootSpelling = bareSpelling(root);\n chord.bassSpelling = bareSpelling(bass);\n chord.toneSpellings = tones.map(bareSpelling);\n return chord;\n}\n\n/** The kind whose tones over `bass` are exactly `pcs`, or null for none. */\nfunction kindOfPitchClasses(pcs: ReadonlySet<number>, bass: number): AugmentedSixthKind | null {\n for (const kind of KINDS) {\n const tones = TONES_ABOVE_BASS[kind];\n if (pcs.size === tones.length && tones.every((tone) => pcs.has(mod12(bass + tone)))) {\n return kind;\n }\n }\n return null;\n}\n\n/** A spelled note or hint reduced to the letter/alter pair spellings compare by. */\nfunction spellingId(note: Note | PitchSpelling): string {\n return `${diatonicLetterOf(note.letter)}:${note.alter}`;\n}\n\n/**\n * Whether a chord's own letters are the kind's.\n *\n * The chord is spelled the way the rest of the library spells it, which is by\n * its tone spellings when it carries them and by stacking thirds when it does\n * not. That is the whole test: a stack of thirds writes the top tone as a minor\n * seventh above the bass, and only a chord that says otherwise sounds the\n * augmented sixth the family is named for.\n */\nfunction spellsAugmentedSixth(chord: Chord, kind: AugmentedSixthKind, key: ResolvedKey): boolean {\n const tonic = key.tonic;\n const written = new Set(spellChord(chord, tonic, key.scale).map(spellingId));\n const tones = spelledFromTonic(kind, tonic).map(spellingId);\n return written.size === tones.length && tones.every((tone) => written.has(tone));\n}\n\n/**\n * Identify a chord as one of the augmented sixths, or null when it is none.\n *\n * A chord qualifies when it sounds the lowered submediant of `key` in the bass,\n * its pitch classes are exactly one of the three sets built over that bass, and\n * its own letters spell the augmented sixth above it. All three are needed: the\n * same pitch classes over the same bass are an ordinary bVI7 when the tone ten\n * semitones above the bass is written as a minor seventh, and the letters are\n * the only place that distinction lives.\n *\n * A chord naming no `bassPc` sounds its root lowest, which is how the two\n * conventions for that field — the sounding bass always, or only a bass that\n * differs from the root — are read alike here.\n *\n * The root the chord is measured from is not consulted, so a chord voiced or\n * rebuilt around any of its own tones still identifies as long as it keeps its\n * spelling.\n *\n * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The kind of augmented sixth, or null.\n * @see {@link augmentedSixthFromPitchClasses} to make the reading from pitch\n * classes that carry no spelling at all.\n * @example\n * ```ts\n * import { augmentedSixthKind, majorKey, romanToChord } from '@libraz/libcantus';\n * augmentedSixthKind(romanToChord('Fr6', majorKey(0)), majorKey(0)); // 'french'\n * ```\n * @category Functional Harmony\n */\nexport function augmentedSixthKind(chord: ChordLike, key: KeyLike): AugmentedSixthKind | null {\n return augmentedSixthKindOf(toChordData(chord), key);\n}\n\n/** {@link augmentedSixthKind} on chord data the analysis layer already holds. */\nexport function augmentedSixthKindOf(chord: Chord, keyLike: KeyLike): AugmentedSixthKind | null {\n const resolved = resolveKey(keyLike);\n const bass = bassPcOf(resolved.scale);\n if (mod12(chord.bassPc ?? chord.rootPc) !== bass) {\n return null;\n }\n const kind = kindOfPitchClasses(new Set(chordPitchClasses(chord)), bass);\n if (kind === null || !spellsAugmentedSixth(chord, kind, resolved)) {\n return null;\n }\n return kind;\n}\n\n/**\n * Read a set of sounding pitch classes over a sounding bass as an augmented\n * sixth, or null when they are none.\n *\n * The reading a caller holding no spelling has to make for itself: pitch\n * classes name no accidentals, so nothing downstream can tell the German sixth\n * from the bVI7 it sounds like once the letters are gone. The chord returned is\n * the one {@link augmentedSixthChord} builds, spelling and all, so it keeps\n * identifying as an augmented sixth everywhere it travels.\n *\n * Not every b6-1-b3-#4 over a b6 bass is an augmented sixth, and this cannot\n * know which one is: it answers what the tones would spell, and the caller\n * decides whether that reading is the one the music supports.\n *\n * @param pcs The sounding pitch classes; duplicates and unreduced values are\n * fine.\n * @param bassPc The pitch class sounding in the bass.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The augmented-sixth chord, or null.\n * @see {@link augmentedSixthKind} to read a chord that already carries its\n * spelling.\n * @example\n * ```ts\n * import { augmentedSixthFromPitchClasses, majorKey } from '@libraz/libcantus';\n * // Ab C Eb Gb sounding over Ab in C: the German sixth, spelled with its F#.\n * augmentedSixthFromPitchClasses([8, 0, 3, 6], 8, majorKey(0))?.quality; // 'dom7'\n * ```\n * @category Functional Harmony\n */\nexport function augmentedSixthFromPitchClasses(\n pcs: readonly number[],\n bassPc: number,\n key: SpelledKeyLike,\n): Chord | null {\n // Null means \"these pitch classes are no augmented sixth\", which is a\n // statement about a set of pitch classes; a value that is not one is refused.\n return augmentedSixthOverBass(assertArray<number>(pcs, 'pcs'), bassPc, key);\n}\n\n/**\n * {@link augmentedSixthFromPitchClasses} for the callers inside the analysis\n * layer.\n */\nexport function augmentedSixthOverBass(\n pcs: readonly number[],\n bassPc: number,\n keyLike: KeyLike,\n): Chord | null {\n const resolved = resolveKey(keyLike);\n const bass = bassPcOf(resolved.scale);\n if (mod12(bassPc) !== bass) {\n return null;\n }\n const kind = kindOfPitchClasses(new Set(pcs.map(mod12)), bass);\n return kind === null ? null : augmentedSixthOn(kind, resolved);\n}\n\n/**\n * Spell an augmented-sixth chord from a spelled tonic, lowest note first.\n *\n * This is the spelled counterpart of {@link augmentedSixthChord}, and the table\n * the chord's own spelling is built from: every tone is placed by a named\n * interval above the bass, so the outer interval is an augmented sixth in every\n * key rather than the minor seventh its ten semitones would otherwise imply.\n * Reach for it when a spelled tonic is all there is; spelling the chord itself\n * with {@link spellChord} names the same notes, in the chord's own order.\n *\n * A tonic carrying an octave yields notes carrying octaves, ascending from the\n * bass; an octave-less tonic yields octave-less notes.\n *\n * @param kind Which of the three augmented sixths to spell.\n * @param tonic The spelled tonic of the key, as a note name, a MIDI number,\n * note data, or a `Note`.\n * @returns The spelled tones, bass first and ascending.\n * @throws If `kind` is not one of the three, or `tonic` is not a note.\n * @example\n * ```ts\n * import { noteNames, parseNote, spellAugmentedSixth } from '@libraz/libcantus';\n * noteNames(spellAugmentedSixth('german', parseNote('C'))); // ['Ab', 'C', 'Eb', 'F#']\n * noteNames(spellAugmentedSixth('italian', 'E')); // ['C', 'E', 'A#']\n * ```\n * @category Functional Harmony\n */\nexport function spellAugmentedSixth(kind: AugmentedSixthKind, tonic: NoteLike): Note[] {\n return spelledFromTonic(kind, toNoteData(tonic));\n}\n\n/** {@link spellAugmentedSixth} on a tonic already read into its narrow form. */\nfunction spelledFromTonic(kind: AugmentedSixthKind, tonic: Note): Note[] {\n const checked = assertOneOf(kind, KINDS, 'augmented sixth kind');\n const bass = transposeByInterval(tonic, BASS_INTERVAL);\n return SPELLED_ABOVE_BASS[checked].map((interval) => transposeByInterval(bass, interval));\n}\n\n/** The chord an augmented-sixth symbol names in a key, or null for other text. */\nexport function augmentedSixthFromSymbol(symbol: string, key: KeyLike): Chord | null {\n const kind = KIND_BY_SYMBOL.get(symbol);\n return kind === undefined ? null : augmentedSixthOn(kind, key);\n}\n\n/** The symbol a chord renders as when it is an augmented sixth, else null. */\nexport function augmentedSixthSymbol(chord: Chord, key: KeyLike): string | null {\n const kind = augmentedSixthKindOf(chord, key);\n return kind === null ? null : SYMBOL_BY_KIND[kind];\n}\n","/**\n * Cadence detection between two successive chords.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport { assertOptions } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole } from '../../theory/chord/index.js';\nimport { isScaleTone, type KeyLike, scaleSystemOf, toKeyScale } from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { isDominantChordOf, isLeadingToneChordOf } from '../../theory/tendency/index.js';\nimport {\n degreeRootPc,\n hasMajorThird,\n isMinorScale,\n mod12,\n parallelScale,\n romanReference,\n} from './internal.js';\nimport type { RejectedCandidate } from './rationale.js';\n\n/** The scale degrees the cadence rules are stated in. */\nconst SUBDOMINANT_DEGREE = 4;\nconst DOMINANT_DEGREE = 5;\nconst SUBMEDIANT_DEGREE = 6;\n\n/**\n * How far above the tonic a scale degree lies, read in the heptatonic frame the\n * key's degrees are numbered in.\n *\n * Which semitone a degree lands on is the mode's business — the submediant is a\n * major sixth in dorian and a minor sixth in aeolian — so a cadence stated in\n * semitones would hold for two modes and miss the rest. A key with no\n * degree-for-degree numbering of its own is read against its parallel major,\n * the same frame its numerals use.\n */\nfunction degreeOffset(degreeNumber: number, key: KeyScale): number {\n return mod12(degreeRootPc(degreeNumber, romanReference(key)) - key.rootPc);\n}\n\n/** Whether the chord's own third — major or minor — is a tone of the key. */\nfunction thirdBelongsToKey(chord: Chord, key: KeyScale): boolean {\n return chord.intervals.some(\n (interval) =>\n (mod12(interval) === 3 || mod12(interval) === 4) &&\n isScaleTone(mod12(chord.rootPc + interval), key),\n );\n}\n\n/**\n * The offsets a deceptive cadence may land on: the key's own submediant, and in\n * a major key the borrowed lowered submediant as well, since the flat-side\n * arrival evades the tonic the same way.\n */\nfunction deceptiveTargets(key: KeyScale): number[] {\n const submediant = degreeOffset(SUBMEDIANT_DEGREE, key);\n if (isMinorScale(key)) {\n return [submediant];\n }\n const borrowed = degreeOffset(SUBMEDIANT_DEGREE, parallelScale(key));\n return borrowed === submediant ? [submediant] : [submediant, borrowed];\n}\n\n/**\n * Whether an arrival on the dominant degree rests there as a half cadence.\n *\n * The dominant of a key functional harmony was built on carries the leading\n * tone, and that major third is what the cadence is heard through, so a\n * borrowed minor `v` is not the chord the ear was left on. A mode is held\n * together by something other than a dominant-to-tonic cadence and has only the\n * dominant it does have, so there the arrival's own third serves, provided the\n * key contains it.\n *\n * The relaxation follows the scale system rather than the mask alone. A natural\n * minor key writes no raised seventh in its signature either, yet it cadences\n * through one: reading the bare mask would let every minor `i - v` be reported\n * as a half cadence and put a phrase boundary where the music has none.\n */\nfunction restsOnDominant(to: Chord, key: KeyScale): boolean {\n // The shared reading first: whether a chord is the key's own dominant is one\n // question, and the arrival side of a cadence is not entitled to a narrower\n // answer than the approach side two functions below. Asking only for the\n // major third here refused the `V7sus4` of gospel, pop and modal jazz — which\n // this file's own six-four reader accepts — and left a half cadence reporting\n // that its arrival stood on the fifth degree and sounded no third the key\n // rests on, about a chord it had just called the dominant.\n if (hasMajorThird(to) || isDominantChordOf(to, key)) {\n return true;\n }\n if (scaleSystemOf(key) !== 'modal') {\n return false;\n }\n const dominantPc = mod12(key.rootPc + degreeOffset(DOMINANT_DEGREE, key));\n const leadingToneDominant = isScaleTone(mod12(dominantPc + 4), key);\n return !leadingToneDominant && thirdBelongsToKey(to, key);\n}\n\n/** Whether a chord is the key's tonic triad standing on its own fifth. */\nfunction isTonicSixFour(chord: Chord, key: KeyScale): boolean {\n if (mod12(chord.rootPc - key.rootPc) !== 0 || chord.bassPc === undefined) {\n return false;\n }\n const isTriad =\n chord.intervals.length === 3 && chord.intervals.some((interval) => mod12(interval) === 7);\n return isTriad && mod12(chord.bassPc - chord.rootPc) === 7;\n}\n\n/**\n * Whether a chord is the cadential six-four of the chord that follows it.\n *\n * The cadential six-four is not an inverted tonic. Its bass has already\n * arrived on the dominant, and the tonic and third sounding above it are\n * appoggiaturas that resolve down onto the dominant's own leading tone and\n * fifth — so the harmony is the dominant from the moment the six-four sounds,\n * and a cadence it opens begins there rather than at the dominant's arrival.\n *\n * The reading rests on the bass: the tonic triad has to stand on the same\n * pitch class the dominant does. A six-four the bass leaves — the passing\n * `IV-I64-IV`, a neighbouring one over the tonic — is an ordinary inverted\n * tonic and answers false, which is what keeps this from swallowing every\n * second-inversion tonic in sight.\n *\n * The chord pair is enough to answer, so the layers that see time — cadence\n * detection over a timeline, harmonic reduction — can all ask the same\n * question of the same two chords instead of each deciding for itself.\n *\n * @param chord The candidate six-four.\n * @param next The chord it moves to.\n * @param key The prevailing key.\n * @returns True when `chord` is the cadential six-four of `next`.\n */\nexport function isCadentialSixFour(chord: Chord, next: Chord, key: KeyScale): boolean {\n return (\n isTonicSixFour(chord, key) &&\n isDominantChordOf(next, key) &&\n mod12(chord.bassPc ?? chord.rootPc) === mod12(next.bassPc ?? next.rootPc)\n );\n}\n\n/**\n * The outer voices of a voicing: its lowest and highest sounding pitches.\n *\n * Non-finite entries name no pitch, so they are skipped rather than dragging\n * the bass or the soprano to a value nothing sounds.\n */\nfunction outerPitches(voicing: readonly number[]): { bass?: number; soprano?: number } {\n let bass: number | undefined;\n let soprano: number | undefined;\n for (const pitch of voicing) {\n if (!Number.isFinite(pitch)) {\n continue;\n }\n if (bass === undefined || pitch < bass) {\n bass = pitch;\n }\n if (soprano === undefined || pitch > soprano) {\n soprano = pitch;\n }\n }\n return { bass, soprano };\n}\n\n/**\n * The pitch class sounding in a chord's bass.\n *\n * A voicing settles it outright: whatever is written, the lowest sounding pitch\n * is the bass. Without one the chord's own `bassPc` answers, and a chord naming\n * no bass stands on its root.\n */\nfunction bassPcOf(chord: Chord, voicedBass: number | undefined): number {\n return mod12(voicedBass ?? chord.bassPc ?? chord.rootPc);\n}\n\n/**\n * A recognized cadence, with the voice-leading facts its reading rests on.\n *\n * @category Functional Harmony\n */\nexport type CadenceResult = {\n /**\n * The cadence the chord pair forms, or null when it forms none.\n *\n * `'phrygian'` and `'modal'` are the two named species: the first is a\n * specific half cadence and is reported in place of `'half'`, the second is\n * the bVII-to-I arrival that no common-practice type covers. Code counting\n * half cadences has to count `'phrygian'` alongside `'half'`.\n */\n type: 'authentic' | 'plagal' | 'half' | 'deceptive' | 'phrygian' | 'modal' | null;\n /**\n * How conclusive an authentic cadence is: `'perfect'` when it is a PAC,\n * `'imperfect'` when it is an IAC.\n *\n * Null for every other cadence type — the distinction is only defined for the\n * authentic cadence — and null for an authentic cadence whose soprano is\n * unknown, which is the case whenever no `voicing` is given.\n */\n strength: 'perfect' | 'imperfect' | null;\n /**\n * The role the final chord's highest sounding pitch plays in that chord.\n *\n * Omitted when no `voicing` was given, and when the top voice is a tension\n * with no basic chord-tone role.\n */\n soprano?: ChordToneRole;\n /**\n * True when both chords stand on their own root — the inversion condition a\n * perfect authentic cadence has to meet.\n */\n rootPosition: boolean;\n /**\n * True when the dominant resolved onto an inverted tonic (V to I6 and its\n * relatives) instead of the root-position arrival it prepared, so the\n * cadential weight is withheld even though the type still holds.\n */\n evaded: boolean;\n /**\n * Why the pair reads this way: the motion the type rests on, what graded an\n * authentic cadence, and what a null type failed to be. Always present, in\n * the phrasing {@link analyzeVoice} uses for a note.\n */\n rationale: string;\n /**\n * The cadences that were considered and rejected, empty unless\n * {@link DetectCadenceOptions.alternatives} asked for them.\n *\n * These are the near misses — the readings a condition or two away from\n * holding — rather than every type that did not fire, which for most pairs\n * would be all of them.\n */\n alternatives: RejectedCandidate[];\n};\n\n/**\n * Options for {@link detectCadence}.\n *\n * @category Functional Harmony\n */\nexport type DetectCadenceOptions = {\n /**\n * The pitches actually sounding, as MIDI note numbers: the penultimate\n * chord's voicing first, then the final chord's.\n *\n * The lowest pitch of each is its bass and so decides inversion, overriding\n * whatever the chords' own `bassPc` says; the highest pitch of the final\n * chord is the soprano. Without a voicing the soprano is unknown, and a\n * perfect authentic cadence cannot be told from an imperfect one.\n */\n voicing?: [number[], number[]];\n /**\n * Report the cadences this pair came close to forming and why each was\n * turned down.\n *\n * Off by default: the rationale is built from facts the classification\n * already established, while a rival has to be phrased against conditions\n * that did not fire, and most callers read the type only.\n *\n * @defaultValue false\n */\n alternatives?: boolean;\n /**\n * The chord before `from`, which is what tells a cadential six-four from an\n * inverted tonic.\n *\n * A dominant reached from the tonic triad standing on its own bass was\n * already sounding when that six-four did, so the cadence began there. Only\n * the chord before the dominant settles that, and a caller reading a pair at\n * a time has it to hand; without it the pair is classified on its own, as\n * before.\n *\n * Accepted as a chord symbol, chord data, or a `Chord`.\n */\n approach?: ChordLike;\n};\n\n/** {@link DetectCadenceOptions} with its approach chord already coerced. */\ntype CadenceOptions = Omit<DetectCadenceOptions, 'approach'> & { approach?: Chord };\n\n/**\n * Classify the motion between two chords, before inversion is weighed.\n *\n * The bass pitch classes are needed here for one case only: the Phrygian\n * cadence is defined by its bass line, not by its roots.\n */\nfunction cadenceType(\n from: Chord,\n to: Chord,\n key: KeyScale,\n fromBassPc: number,\n toBassPc: number,\n): CadenceResult['type'] {\n const tonic = mod12(key.rootPc);\n const fromOffset = mod12(from.rootPc - tonic);\n const toOffset = mod12(to.rootPc - tonic);\n const dominant = isDominantChordOf(from, key);\n if ((dominant || isLeadingToneChordOf(from, key)) && toOffset === 0) {\n return 'authentic';\n }\n // The subdominant is the chord on the key's own fourth degree, which lydian\n // puts a tritone above the tonic. A fixed five semitones would miss that\n // arrival and name a chord the key does not contain in its place.\n if (fromOffset === degreeOffset(SUBDOMINANT_DEGREE, key) && toOffset === 0) {\n return 'plagal';\n }\n // bVII to I: the modal cadence of rock and pop, which has no leading tone at\n // all and so is neither authentic nor plagal. The subtonic carries subdominant\n // function (see functionOf) whether or not it cadences; what makes this one a\n // cadence is the arrival on the tonic.\n if (fromOffset === 10 && toOffset === 0 && hasMajorThird(from)) {\n return 'modal';\n }\n if (dominant && deceptiveTargets(key).includes(toOffset)) {\n return 'deceptive';\n }\n // A move to the dominant is a half cadence, but a no-root-motion V-to-V repeat\n // is not a cadence at all.\n const dominantOffset = degreeOffset(DOMINANT_DEGREE, key);\n const half =\n toOffset === dominantOffset && restsOnDominant(to, key) && fromOffset !== dominantOffset;\n // The Phrygian cadence is that half cadence approached by a semitone descent\n // in the bass, b6 to 5, which is what iv6 to V sounds in a minor key.\n if (\n half &&\n isMinorScale(key) &&\n fromOffset === 5 &&\n mod12(fromBassPc - tonic) === 8 &&\n mod12(toBassPc - tonic) === 7\n ) {\n return 'phrygian';\n }\n return half ? 'half' : null;\n}\n\n/**\n * Perfect or imperfect, for a cadence already known to be authentic.\n *\n * A perfect authentic cadence needs all three of: the dominant itself rather\n * than a leading-tone chord standing in for it, both chords on their roots, and\n * the tonic in the soprano. The first two are answered by the chords; the third\n * needs a voicing, and without one the answer is unknown rather than assumed.\n */\nfunction authenticStrength(\n leadingTone: boolean,\n rootPosition: boolean,\n voiced: boolean,\n soprano: ChordToneRole | null,\n): CadenceResult['strength'] {\n if (leadingTone || !rootPosition) {\n return 'imperfect';\n }\n if (!voiced) {\n return null;\n }\n return soprano === 'root' ? 'perfect' : 'imperfect';\n}\n\n/**\n * The facts a rationale and its rejected rivals are phrased from: the reading\n * that was reached, plus the conditions the rules were testing when they did.\n */\ntype CadenceFacts = {\n type: CadenceResult['type'];\n strength: CadenceResult['strength'];\n rootPosition: boolean;\n evaded: boolean;\n /** The approach is a leading-tone chord standing in for the dominant. */\n leadingTone: boolean;\n fromOffset: number;\n toOffset: number;\n minor: boolean;\n /** The arrival stands on the fifth degree, whatever its third. */\n onFifthDegree: boolean;\n /** The dominant was reached as a cadential six-four over its own bass. */\n sixFour: boolean;\n /** The pair is that six-four resolving inside the dominant it stands on. */\n withinSixFour: boolean;\n};\n\n/** Which of the three conditions of a perfect authentic cadence went unmet. */\nfunction imperfectReason(facts: CadenceFacts): string {\n if (facts.leadingTone) {\n return 'the dominant root never sounds under the leading-tone chord';\n }\n if (!facts.rootPosition) {\n return 'a chord stands on something other than its own root';\n }\n return 'the soprano does not land on the tonic';\n}\n\n/** The rationale for a pair that forms no cadence at all. */\nfunction describeNonCadence(facts: CadenceFacts): string {\n if (facts.fromOffset === facts.toOffset) {\n return 'No cadence: the harmony repeats, so there is no root motion to cadence with';\n }\n if (facts.toOffset === 0) {\n return 'No cadence: the tonic is approached by none of the chords that cadence onto it';\n }\n if (facts.onFifthDegree) {\n return 'No cadence: the arrival stands on the fifth degree but sounds no third the key rests on, so it is not the dominant a half cadence comes to';\n }\n return 'No cadence: the motion arrives on neither the tonic nor the dominant';\n}\n\n/** Build the rationale for a classified pair. */\nfunction describeCadence(facts: CadenceFacts): string {\n let text: string;\n switch (facts.type) {\n case 'authentic': {\n const approach = facts.leadingTone ? 'leading-tone chord' : 'dominant';\n const head = `Authentic cadence: the ${approach} resolves onto the tonic`;\n text =\n facts.strength === 'perfect'\n ? `${head}, perfect with both chords on their roots and the tonic in the soprano`\n : facts.strength === 'imperfect'\n ? `${head}, imperfect because ${imperfectReason(facts)}`\n : `${head}; no voicing names the soprano, so it is graded neither perfect nor imperfect`;\n break;\n }\n case 'plagal':\n text = 'Plagal cadence: the subdominant falls onto the tonic';\n break;\n case 'modal':\n text =\n 'Modal cadence: the major triad on the subtonic rises onto the tonic with no leading tone';\n break;\n case 'deceptive':\n text = 'Deceptive cadence: the dominant resolves onto the submediant in place of the tonic';\n break;\n case 'phrygian':\n text =\n 'Phrygian cadence: the bass falls a semitone from the lowered submediant onto the dominant, and the motion rests there';\n break;\n case 'half':\n text = facts.withinSixFour\n ? 'Half cadence: the motion comes to rest on the dominant, which the tonic six-four standing on its bass was already sounding'\n : 'Half cadence: the motion comes to rest on the dominant';\n break;\n case null:\n text = describeNonCadence(facts);\n break;\n }\n if (facts.evaded) {\n text = `${text}; the tonic arrives inverted, so the arrival the ear was promised is withheld`;\n }\n // The six-four is the dominant already, so the cadence is one event that\n // started when it sounded — the numeral I64 names the chord, not the harmony.\n return facts.sixFour\n ? `${text}; the dominant was reached as a cadential six-four, a double appoggiatura over the same bass, so the cadence begins there rather than at the dominant's own arrival`\n : text;\n}\n\n/** The cadences the pair came close to forming, and why each was turned down. */\nfunction cadenceAlternatives(facts: CadenceFacts): RejectedCandidate[] {\n const out: RejectedCandidate[] = [];\n if (facts.type === 'deceptive') {\n out.push({\n label: 'authentic',\n reason: 'The dominant was prepared, but the arrival is the submediant rather than the tonic',\n });\n } else if (facts.type !== 'authentic' && facts.toOffset === 0) {\n out.push({\n label: 'authentic',\n reason:\n 'The arrival is the tonic, but the chord before it is neither the dominant nor a leading-tone chord',\n });\n }\n if (facts.type === 'authentic' && facts.strength !== 'perfect') {\n out.push({\n label: 'perfect authentic',\n reason:\n facts.strength === null\n ? 'The chords meet every condition a voicing can be judged without, but no voicing names the soprano'\n : `The cadence is authentic, but ${imperfectReason(facts)}`,\n });\n }\n if (facts.type === 'half' && facts.minor && facts.fromOffset === 5) {\n out.push({\n label: 'phrygian',\n reason:\n 'The dominant is approached from the subdominant, but the bass does not fall a semitone from the lowered submediant onto it',\n });\n }\n if (facts.type === null && facts.onFifthDegree && facts.fromOffset !== facts.toOffset) {\n out.push({\n label: 'half',\n reason:\n \"The arrival stands on the fifth degree, but a half cadence rests on the dominant the key has: the major third, or the mode's own third where that dominant carries no leading tone\",\n });\n }\n return out;\n}\n\n/**\n * Classify the cadence formed by moving from one chord to the next.\n *\n * - authentic: V (dominant a fifth above the tonic) to I\n * - plagal: IV, the chord on the key's own fourth degree, to I\n * - modal: bVII to I, the cadence of rock and modal writing\n * - deceptive: V to the key's own submediant, plus the borrowed bVI of a major\n * key\n * - phrygian: iv6 to V of a minor key, the half cadence whose bass falls a\n * semitone from b6 to 5\n * - half: any other chord than the dominant itself to V, the cadential six-four\n * standing on the dominant's own bass included\n *\n * These are read as scale degrees rather than as semitone distances, so a mode\n * cadences from and onto the degrees it actually has: the deceptive arrival of\n * dorian is a major sixth above the tonic and that of phrygian a minor sixth,\n * and the subdominant a plagal cadence falls from is the tritone above the\n * tonic in lydian. The two borrowed types are the exception, and are stated as\n * the fixed offsets they name: `modal` is the flattened seventh whatever the\n * key writes on that degree, and the Phrygian bass falls from the lowered\n * submediant. A half cadence normally rests on the major third that carries\n * the leading tone, but where the key's own dominant has no leading tone — the\n * modal `v` of dorian, mixolydian and the pop writing built on them — the\n * arrival's own third suffices as long as the key contains it. That relaxation\n * belongs to the modes alone: a borrowed minor `v` in a major key is no half\n * cadence, and neither is the `v` of a minor key, which cadences through the\n * raised seventh it writes as an accidental rather than in its scale.\n *\n * A static V-to-V repeat with no root motion is not a cadence and yields a null\n * type. The tonic six-four resolving onto the dominant it stands on is a half\n * cadence, and the most ordinary one there is: `I - I64 - V` closes the\n * antecedent of a period, and the six-four is a double appoggiatura over the\n * dominant's own bass rather than a chord that keeps the motion going. Where\n * that dominant goes on to resolve, the pair and the resolution are one event —\n * passing the six-four as `approach` when the dominant is the `from` chord is\n * what lets it be reported as one, with the type and the beat still the\n * dominant's resolution while the `rationale` says the cadence began at the\n * six-four. A six-four the bass leaves instead (`IV-I64-IV`, or a neighbouring\n * one over the tonic) is an ordinary inverted tonic and is read as one.\n *\n * An authentic cadence is graded perfect or imperfect. Perfect takes the\n * dominant to the tonic with both chords in root position and the tonic in the\n * soprano; anything else authentic is imperfect, including the leading-tone\n * cadence, which has no dominant root to stand on. Only a `voicing` names the\n * soprano, so without one the grade is null — the pair could still be either,\n * and reporting perfect would be a guess.\n *\n * The `rationale` says which motion the reading rests on, including for a pair\n * that cadences not at all; `alternatives` is empty unless asked for, and then\n * names the cadences the pair came close to forming.\n *\n * @param from The penultimate chord, as a chord symbol, chord data, or a\n * `Chord`.\n * @param to The final chord, in any of the same forms.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @param opts Voice-leading detail, the chord before `from`, and whether to\n * collect the rejected readings; see {@link DetectCadenceOptions}.\n * @returns The cadence, its strength, and the facts behind them.\n * @example\n * ```ts\n * import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';\n * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0));\n * // { type: 'authentic', strength: null, rootPosition: true, evaded: false, ... }\n * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0), {\n * voicing: [[55, 62, 71], [48, 64, 72]],\n * });\n * // { type: 'authentic', strength: 'perfect', soprano: 'root', ... }\n * const sixFour = detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0), {\n * approach: makeChord(0, 'maj', 7), // C/G, the cadential six-four\n * });\n * sixFour.type; // 'authentic'\n * // The rationale says where the cadence began.\n * sixFour.rationale?.includes('cadential six-four'); // true\n * ```\n * @category Functional Harmony\n */\nexport function detectCadence(\n from: ChordLike,\n to: ChordLike,\n key: KeyLike,\n opts: DetectCadenceOptions = {},\n): CadenceResult {\n const { approach, ...rest } = assertOptions(opts, 'opts');\n return cadenceBetween(\n toChordData(from),\n toChordData(to),\n toKeyScale(key),\n approach === undefined ? rest : { ...rest, approach: toChordData(approach) },\n );\n}\n\n/** {@link detectCadence} on data already read into its narrow form. */\nexport function cadenceBetween(\n from: Chord,\n to: Chord,\n key: KeyScale,\n opts: CadenceOptions = {},\n): CadenceResult {\n const [fromVoicing, toVoicing] = opts.voicing ?? [];\n const fromOuter = outerPitches(fromVoicing ?? []);\n const toOuter = outerPitches(toVoicing ?? []);\n const fromBassPc = bassPcOf(from, fromOuter.bass);\n const toBassPc = bassPcOf(to, toOuter.bass);\n const toRootPosition = toBassPc === mod12(to.rootPc);\n const rootPosition = fromBassPc === mod12(from.rootPc) && toRootPosition;\n const soprano = toOuter.soprano === undefined ? null : chordToneRole(toOuter.soprano, to);\n\n const type = cadenceType(from, to, key, fromBassPc, toBassPc);\n const leadingTone = isLeadingToneChordOf(from, key);\n const facts: CadenceFacts = {\n type,\n strength:\n type === 'authentic'\n ? authenticStrength(leadingTone, rootPosition, toOuter.soprano !== undefined, soprano)\n : null,\n rootPosition,\n // The dominant resolved, but onto an inverted tonic: the arrival the ear was\n // promised is the one it does not get.\n evaded: type === 'authentic' && !toRootPosition,\n leadingTone,\n fromOffset: mod12(from.rootPc - key.rootPc),\n toOffset: mod12(to.rootPc - key.rootPc),\n minor: isMinorScale(key),\n onFifthDegree: mod12(to.rootPc - key.rootPc) === degreeOffset(DOMINANT_DEGREE, key),\n sixFour: opts.approach !== undefined && isCadentialSixFour(opts.approach, from, key),\n withinSixFour: isCadentialSixFour(from, to, key),\n };\n const result: CadenceResult = {\n type: facts.type,\n strength: facts.strength,\n rootPosition: facts.rootPosition,\n evaded: facts.evaded,\n rationale: describeCadence(facts),\n alternatives: opts.alternatives === true ? cadenceAlternatives(facts) : [],\n };\n if (soprano !== null) {\n result.soprano = soprano;\n }\n return result;\n}\n","/**\n * Borrowed chords (modal interchange) and the Neapolitan.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport {\n HARMONIC_MINOR_MASK,\n type KeyLike,\n MELODIC_MINOR_MASK,\n toKeyScale,\n} from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport {\n isDiatonicChord,\n isMinorScale,\n isNeapolitanChordOf,\n mod12,\n parallelScale,\n} from './internal.js';\n\n/**\n * The origin of a recognized non-diatonic chord, or null when none applies.\n *\n * @category Functional Harmony\n */\nexport type BorrowedSource = 'parallelMinor' | 'parallelMajor' | 'neapolitan' | null;\n\n/**\n * In a minor key, whether a non-diatonic chord is explained by the harmonic\n * minor scale on the same tonic (major V, V7, raised-leading-tone vii°), or\n * by the melodic-minor raised-leading-tone half-diminished seventh. Such\n * chords are in-key chromatic alterations, not modal interchange.\n */\nfunction isMinorScaleAlteration(chord: Chord, key: KeyScale): boolean {\n if (!isMinorScale(key)) return false;\n const alteredKey = { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK };\n if (isDiatonicChord(chord, alteredKey)) return true;\n // Melodic minor contributes the raised-leading-tone half-diminished seventh,\n // but treating every chord in its ascending collection as a non-borrowed\n // alteration would hide ordinary modal interchange such as minor-key IV.\n return (\n mod12(chord.rootPc - key.rootPc) === 11 &&\n isDiatonicChord(chord, { rootPc: mod12(key.rootPc), modeMask12: MELODIC_MINOR_MASK })\n );\n}\n\n/** {@link isBorrowedChord} on data already read into its narrow form. */\nfunction isBorrowed(chord: Chord, key: KeyScale): boolean {\n if (isDiatonicChord(chord, key)) {\n return false;\n }\n if (isNeapolitanChordOf(chord, key)) {\n return true;\n }\n if (isMinorScaleAlteration(chord, key)) {\n return false;\n }\n return isDiatonicChord(chord, parallelScale(key));\n}\n\n/** {@link borrowedSource} on data already read into its narrow form. */\nexport function borrowedSourceOf(chord: Chord, key: KeyScale): BorrowedSource {\n if (isDiatonicChord(chord, key)) {\n return null;\n }\n if (isNeapolitanChordOf(chord, key)) return 'neapolitan';\n if (isBorrowed(chord, key)) {\n return isMinorScale(key) ? 'parallelMajor' : 'parallelMinor';\n }\n return null;\n}\n\n/**\n * Whether a chord is borrowed from the parallel mode (modal interchange).\n *\n * True when the chord is not diatonic to `key` but is diatonic to its\n * {@link parallelKeyOf | parallel key} — e.g. iv, bVI, or bVII in a major key, or the Picardy\n * tonic and major IV in a minor key — and true for the Neapolitan, which is\n * counted as a borrowing of its own even though it belongs to neither parallel\n * mode. Two non-diatonic families are excluded: any other chord diatonic to\n * neither mode (those are chromatic, not borrowed), and, in a minor key, chords\n * explained by the altered minor forms (the major dominant and\n * raised-leading-tone chords), which are in-key alterations rather than\n * interchange even though they happen to fit the parallel major.\n *\n * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns True if the chord is borrowed from the parallel mode.\n * @category Functional Harmony\n */\nexport function isBorrowedChord(chord: ChordLike, key: KeyLike): boolean {\n return isBorrowed(toChordData(chord), toKeyScale(key));\n}\n\n/**\n * Identify where a non-diatonic chord comes from.\n *\n * The Neapolitan (major triad on b2 of a key that does not have that degree\n * already) is recognized first, since it sits outside both parallel modes;\n * parallel-mode borrowing follows the {@link isBorrowedChord} rules. Diatonic\n * chords and altered-minor chords, and unrecognized chromatic chords all yield\n * null.\n *\n * @param chord The chord to classify, as a chord symbol, chord data, or a\n * `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The borrowing source, or null.\n * @category Functional Harmony\n */\nexport function borrowedSource(chord: ChordLike, key: KeyLike): BorrowedSource {\n return borrowedSourceOf(toChordData(chord), toKeyScale(key));\n}\n","/**\n * Tonicization: which degrees a key can make a local tonic, which chords point\n * at one, and which seventh qualities the minor-key leading-tone numeral\n * convention covers.\n *\n * The function layer, the numeral layer and the part-writing checker all have\n * to answer \"can this degree be tonicized?\", and they answered it differently\n * for long enough that a diminished seventh could read as an applied dominant\n * in the same analysis object whose numeral refused to name a target for it.\n * The degrees themselves are derived one layer down, in `theory/tendency`,\n * which needs nothing but the key and which the checker can reach; what is\n * written here is what only this layer knows — the sonorities that point at a\n * degree, and the semitone fall a tritone substitute makes, which is the one\n * reading allowed to reach the tonic.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport type { TonicizableDegree } from '../../theory/tendency/index.js';\nimport {\n appliedDominantTarget,\n LEADING_TONE_QUALITIES,\n tonicizableDegrees,\n} from '../../theory/tendency/index.js';\nimport { borrowedSourceOf } from './borrowed.js';\nimport { hasDominantSonority, isDiatonicChord, mod12, soundsDominantSeventh } from './internal.js';\n\n/**\n * Which degrees of a key can be made a local tonic, and the type naming one.\n *\n * The derivation belongs to the theory layer, since it needs nothing but the\n * key: the part-writing checker asks it too, and cannot import this layer. It\n * is re-exported here for the layers above — the numerals, the harmonizer and\n * the progression generator reach it through this module — rather than for the\n * package surface, which has never carried it.\n */\nexport type { TonicizableDegree };\n/**\n * Diminished-family qualities.\n *\n * All three tonicize from a semitone below, and all three are what the bare\n * `viio` family of numerals means in a key whose seventh degree is lowered: the\n * fully diminished seventh and the half-diminished one stand on the same raised\n * leading tone and differ only in the colour of the sixth degree above it.\n */\nexport { LEADING_TONE_QUALITIES, tonicizableDegrees };\n\n/** Whether a pitch class is a degree of `key` that can be made a local tonic. */\nfunction isTonicizableRoot(rootPc: number, key: KeyScale): boolean {\n return tonicizableDegrees(key).some((degree) => degree.rootPc === mod12(rootPc));\n}\n\n/**\n * The scale degree a chromatic chord tonicizes as a numeral names it, or null\n * when it tonicizes nothing.\n *\n * A dominant sonority points a fifth below itself and a diminished one a\n * semitone above itself; which degrees may be pointed at is\n * {@link appliedDominantTarget}, so the tonic is not among them — a chord\n * pointing there is the key's own dominant, and `V/I` is a numeral no harmony\n * text writes.\n */\nexport function appliedTarget(chord: Chord, key: KeyScale): TonicizableDegree | null {\n const dominant = hasDominantSonority(chord);\n const leadingTone = LEADING_TONE_QUALITIES.has(chord.quality);\n if (!dominant && !leadingTone) {\n return null;\n }\n return appliedDominantTarget(chord.rootPc + (dominant ? 5 : 1), key);\n}\n\n/**\n * Whether a chord is an applied dominant: a chromatic chord whose sonority and\n * resolution give it dominant function whatever degree it stands on.\n *\n * Three things have to hold. The chord must be chromatic to the key, since a\n * diatonic chord keeps the function of its degree. It must not be modal\n * interchange: the Picardy tonic and the major IV of a minor key are borrowed\n * from the parallel major and sound their own degrees, and reading either as an\n * applied dominant would give the tonic chord dominant function. And it must\n * point at a degree that can be a local tonic.\n *\n * What counts as pointing is {@link appliedTarget}, the same predicate the\n * numeral layer names its target with, so a chord read as applied here always\n * has a numeral to be applied to. The tonic is not among those targets: a chord\n * pointing at it is the key's own dominant, which is why the raised `V7` of a\n * minor key and the altered dominants and leading-tone sevenths of a major one\n * take the function of their degree rather than tonicizing anything.\n *\n * The tritone substitute is the one reading that reaches the tonic. It is a\n * dominant seventh falling a semitone rather than a fifth, and nothing else\n * resolves that way, so it is asked for separately instead of loosening the\n * targets every other sonority is held to.\n */\nexport function isAppliedDominant(chord: Chord, key: KeyScale): boolean {\n if (isDiatonicChord(chord, key)) {\n return false;\n }\n const source = borrowedSourceOf(chord, key);\n if (source === 'parallelMajor' || source === 'parallelMinor') {\n return false;\n }\n return pointsAtTonicizableDegree(chord, key);\n}\n\n/**\n * Whether a chord points at a degree the key can tonicize.\n *\n * The sonority half of {@link isAppliedDominant}, without the chromaticism the\n * reading also needs. A caller reporting the applied reading a chord did *not*\n * get has to ask this too: a chord that points at nothing was never a rival, so\n * naming it one describes a choice the analysis never made, and states the\n * wrong reason for the outcome. Both readings ask here, so inverting the reason\n * one of them gives is what would make the other take the chord.\n */\nexport function pointsAtTonicizableDegree(chord: Chord, key: KeyScale): boolean {\n if (appliedTarget(chord, key) !== null) {\n return true;\n }\n return soundsDominantSeventh(chord) && isTonicizableRoot(mod12(chord.rootPc - 1), key);\n}\n","/**\n * Roman-numeral parsing and formatting.\n *\n * The {@link ROMAN_STYLE} table is the single source of truth for both\n * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual\n * inverses by construction. The chromatic chords no numeral can spell — the\n * augmented sixths, and the Neapolitan under its figured name — are matched as\n * whole symbols beside that table.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { assertOptions } from '../../core/validation/index.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordToneSpellings, makeChord } from '../../theory/chord/index.js';\nimport {\n type KeyLike,\n majorKey,\n type ResolvedKey,\n resolveKey,\n type SpelledKeyLike,\n scaleTonesInDegreeOrder,\n} from '../../theory/scale/index.js';\nimport { spellPitchClass } from '../../theory/spelling/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { augmentedSixthFromSymbol, augmentedSixthSymbol } from './augmented-sixth.js';\nimport {\n degreeRootPc,\n isDiatonicChord,\n isNeapolitanChordOf,\n loweredDegrees,\n mod12,\n romanReference,\n} from './internal.js';\nimport type { RejectedCandidate } from './rationale.js';\nimport {\n appliedTarget,\n LEADING_TONE_QUALITIES,\n type TonicizableDegree,\n tonicizableDegrees,\n} from './tonicization.js';\n\n/** Roman numeral glyphs indexed by degree number - 1. */\nconst ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] as const;\n\n/** Roman glyph -> degree number (1..7). */\nconst ROMAN_TO_DEGREE: Record<string, number> = {\n I: 1,\n II: 2,\n III: 3,\n IV: 4,\n V: 5,\n VI: 6,\n VII: 7,\n};\n\n/** Major-key Roman spelling for each semitone offset above the tonic. */\nconst OFFSET_SPELLING: readonly [number, string][] = [\n [1, ''],\n [2, 'b'],\n [2, ''],\n [3, 'b'],\n [3, ''],\n [4, ''],\n [4, '#'],\n [5, ''],\n [6, 'b'],\n [6, ''],\n [7, 'b'],\n [7, ''],\n];\n\n/** Choose a seventh-chord quality from a triad label and the numeral case. */\nfunction seventhQuality(base: 'maj' | 'min' | 'dim' | 'aug', halfDim: boolean): ChordQuality {\n if (halfDim) {\n return 'm7b5';\n }\n if (base === 'dim') {\n return 'dim7';\n }\n if (base === 'aug') {\n return 'aug7';\n }\n return base === 'maj' ? 'dom7' : 'min7';\n}\n\n/** Figure strings that denote a recognized figured-bass inversion. */\nconst INVERSION_FIGURES = new Set(['6', '64', '65', '43', '42', '2', '7']);\n\n/**\n * Canonical Roman-numeral rendering (numeral case and quality suffix) for every\n * supported chord quality. The table is the single source of truth for both\n * directions: {@link chordToRoman} renders from it and {@link romanToChord}\n * recognizes its exact case-sensitive suffixes, so the two stay mutual\n * inverses by construction. Suffixes are chosen to never collide with\n * figured-bass inversion digits (`6`, `64`, `65`, `43`, `42`, `2`) — the added\n * sixth is `add6`, the six-nine chord is `69` — and each (case, suffix) pair is\n * unique.\n */\nconst ROMAN_STYLE: Record<ChordQuality, { lower: boolean; suffix: string }> = {\n maj: { lower: false, suffix: '' },\n min: { lower: true, suffix: '' },\n dim: { lower: true, suffix: 'o' },\n aug: { lower: false, suffix: '+' },\n maj7: { lower: false, suffix: 'maj7' },\n min7: { lower: true, suffix: '7' },\n dom7: { lower: false, suffix: '7' },\n dim7: { lower: true, suffix: 'o7' },\n m7b5: { lower: true, suffix: 'ø7' },\n minMaj7: { lower: true, suffix: 'maj7' },\n minMaj9: { lower: true, suffix: 'maj9' },\n minMaj11: { lower: true, suffix: 'maj11' },\n minMaj13: { lower: true, suffix: 'maj13' },\n aug7: { lower: false, suffix: '+7' },\n augMaj7: { lower: false, suffix: '+maj7' },\n majb5: { lower: false, suffix: 'b5' },\n '6': { lower: false, suffix: 'add6' },\n min6: { lower: true, suffix: 'add6' },\n '6/9': { lower: false, suffix: '69' },\n sus2: { lower: false, suffix: 'sus2' },\n sus4: { lower: false, suffix: 'sus4' },\n add9: { lower: false, suffix: 'add9' },\n add11: { lower: false, suffix: 'add11' },\n maj9: { lower: false, suffix: 'maj9' },\n min9: { lower: true, suffix: '9' },\n dom9: { lower: false, suffix: '9' },\n '7b9': { lower: false, suffix: '7b9' },\n '7#9': { lower: false, suffix: '7#9' },\n '7#11': { lower: false, suffix: '7#11' },\n '7b13': { lower: false, suffix: '7b13' },\n '11': { lower: false, suffix: '11' },\n '13': { lower: false, suffix: '13' },\n '5': { lower: false, suffix: '5' },\n '7sus4': { lower: false, suffix: '7sus4' },\n '7b5': { lower: false, suffix: '7b5' },\n '7alt': { lower: false, suffix: '7alt' },\n '13b9': { lower: false, suffix: '13b9' },\n maj13: { lower: false, suffix: 'maj13' },\n 'maj7#11': { lower: false, suffix: 'maj7#11' },\n min11: { lower: true, suffix: '11' },\n min13: { lower: true, suffix: '13' },\n minAdd9: { lower: true, suffix: 'add9' },\n 'min6/9': { lower: true, suffix: '69' },\n};\n\n/** Reverse lookup of {@link ROMAN_STYLE}: `(case, exact suffix) -> quality`. */\nconst SUFFIX_QUALITY: ReadonlyMap<string, ChordQuality> = new Map(\n (Object.entries(ROMAN_STYLE) as [ChordQuality, { lower: boolean; suffix: string }][]).map(\n ([quality, style]) => [`${style.lower ? 'l' : 'u'}:${style.suffix}`, quality],\n ),\n);\n\n/** Read figured-bass digits into a chord inversion and whether a seventh is implied. */\nfunction parseInversion(figures: string): { inversion: number; seventh: boolean } {\n if (figures === '65') {\n return { inversion: 1, seventh: true };\n }\n if (figures === '43') {\n return { inversion: 2, seventh: true };\n }\n if (figures === '42' || figures === '2') {\n return { inversion: 3, seventh: true };\n }\n if (figures === '7') {\n return { inversion: 0, seventh: true };\n }\n if (figures === '64') {\n return { inversion: 2, seventh: false };\n }\n if (figures === '6') {\n return { inversion: 1, seventh: false };\n }\n return { inversion: 0, seventh: false };\n}\n\n/** Parse a Roman numeral (no secondary '/') into a root, quality, and inversion. */\nfunction parseSimpleRoman(\n text: string,\n key: KeyScale,\n): { rootPc: number; quality: ChordQuality; inversion: number } {\n const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());\n if (!match) {\n throw new InvalidInputError(`Invalid Roman numeral: ${text}`);\n }\n const glyphText = match[2] ?? '';\n if (glyphText !== glyphText.toUpperCase() && glyphText !== glyphText.toLowerCase()) {\n throw new InvalidInputError(`Roman numeral must use one case: ${text}`);\n }\n const accidental = match[1] === 'b' ? -1 : match[1] === '#' ? 1 : 0;\n const glyph = glyphText.toUpperCase();\n const degreeNumber = ROMAN_TO_DEGREE[glyph];\n if (degreeNumber === undefined) {\n throw new InvalidInputError(`Invalid Roman numeral: ${text}`);\n }\n const isUpper = glyphText[0] === glyphText[0]?.toUpperCase();\n const suffix = match[3] ?? '';\n // Degrees are read in the key's heptatonic frame, so a numeral means the same\n // root in both directions even when the key itself is not heptatonic.\n const frame = romanReference(key);\n // A flat on a degree the mode already lowers is that degree, not a further\n // lowering: `bVII` in A minor is G, the pop reading, rather than F#.\n const redundantFlat = accidental === -1 && loweredDegrees(frame).has(degreeNumber);\n const diatonicRootPc = mod12(\n degreeRootPc(degreeNumber, frame) + (redundantFlat ? 0 : accidental),\n );\n\n // In a minor key, an unaltered diminished-family seventh-degree numeral\n // conventionally denotes the harmonic-minor leading tone: `viio`, `viio7` and\n // `viiø7` in A minor all stand on G#, not G. An explicit accidental remains\n // literal, so callers can still write `#viio` (or `bviio`) when that\n // distinction is meaningful to them.\n const rootForQuality = (quality: ChordQuality): number =>\n accidental === 0 &&\n degreeNumber === 7 &&\n loweredDegrees(frame).has(7) &&\n LEADING_TONE_QUALITIES.has(quality)\n ? mod12(diatonicRootPc + 1)\n : diatonicRootPc;\n\n // Canonical quality suffixes (exact match, case-sensitive on the numeral)\n // come first so every chordToRoman rendering re-parses to the same quality:\n // `V9` -> dom9 but `ii9` -> min9 and `Imaj9` -> maj9, and added-tone suffixes\n // whose digits would otherwise be misread as figured bass (`Iadd6`, `I69`,\n // `Isus2`, `Iadd11`) resolve to their qualities in root position.\n const canonical = SUFFIX_QUALITY.get(`${isUpper ? 'u' : 'l'}:${suffix}`);\n if (canonical !== undefined) {\n return { rootPc: rootForQuality(canonical), quality: canonical, inversion: 0 };\n }\n\n // Non-canonical spellings are limited to a complete quality marker followed\n // by an optional major-seventh marker and a recognized figured bass. Never\n // search substrings: `Vfoo` must not become diminished merely because it\n // contains an \"o\", and unknown trailing text must be rejected in full.\n const parsedSuffix = /^(o|°|dim|ø|\\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);\n if (!parsedSuffix) {\n throw new InvalidInputError(`Unsupported suffix \"${suffix}\" in Roman numeral: ${text}`);\n }\n const qualityMarker = parsedSuffix[1] ?? '';\n const isDim = qualityMarker === 'o' || qualityMarker === '°' || qualityMarker === 'dim';\n const isAug = qualityMarker === '+' || qualityMarker === 'aug';\n const isHalfDim = qualityMarker === 'ø';\n const explicitMaj7 = parsedSuffix[2] !== undefined;\n const figures = parsedSuffix[3] ?? '';\n\n // Reject figure strings that are neither a known figured-bass inversion nor a\n // canonical quality suffix rather than silently degrading to a root triad.\n if (figures !== '' && !INVERSION_FIGURES.has(figures)) {\n throw new InvalidInputError(\n `Unsupported figured-bass or extension \"${figures}\" in Roman numeral: ${text}`,\n );\n }\n\n const { inversion, seventh } = parseInversion(figures);\n // The ø glyph is the half-diminished seventh by definition (there is no\n // half-diminished triad), so it always implies a seventh.\n const hasSeventh = explicitMaj7 || seventh || isHalfDim;\n\n let base: 'maj' | 'min' | 'dim' | 'aug';\n if (isDim || isHalfDim) {\n base = 'dim';\n } else if (isAug) {\n base = 'aug';\n } else {\n base = isUpper ? 'maj' : 'min';\n }\n\n let quality: ChordQuality;\n if (explicitMaj7) {\n quality = base === 'min' ? 'minMaj7' : base === 'aug' ? 'augMaj7' : 'maj7';\n } else if (hasSeventh) {\n quality = seventhQuality(base, isHalfDim);\n } else if (base === 'dim') {\n quality = 'dim';\n } else if (base === 'aug') {\n quality = 'aug';\n } else {\n quality = base;\n }\n return { rootPc: rootForQuality(quality), quality, inversion };\n}\n\n/** The conventional figured name for the Neapolitan in first inversion. */\nconst NEAPOLITAN_SIXTH = 'N6';\n\n/**\n * The chord a whole-token chromatic symbol names, or null when the text is an\n * ordinary numeral.\n *\n * `It6`, `Fr6`, `Ger6` (also written `Ger65`) and `N6` are names rather than\n * numerals: the degree-plus-figure grammar has no way to spell an augmented\n * sixth, and `N6` is a name for a chord that grammar already spells as `bII6`.\n * Both build the same chords their numeral counterparts would, so the two\n * notations stay interchangeable.\n *\n * The key is taken whole rather than as a scale, because an augmented sixth is\n * spelled from the tonic the key is written on: the German sixth of Ab minor is\n * Fb Ab Cb D, and reducing the key here would build the E G# B C## the same\n * pitch classes read as once they have been respelled in G# minor.\n */\nfunction chromaticSymbolChord(text: string, key: ResolvedKey): Chord | null {\n if (text === NEAPOLITAN_SIXTH) {\n // Exactly the chord `bII6` builds: the major triad on b2, third in the bass.\n const rootPc = mod12(key.scale.rootPc + 1);\n return makeChord(rootPc, 'maj', mod12(rootPc + 4));\n }\n return augmentedSixthFromSymbol(text, key);\n}\n\n/**\n * The local major key an applied numeral is read in.\n *\n * The degree being tonicized is spelled the way the prevailing key writes that\n * pitch class, so the local key is written on a letter the caller's key would\n * use: `Ger6/V` in Ab minor is built on Eb, not on the D# the bare pitch class\n * would otherwise be named.\n */\nfunction tonicizedKey(targetRoot: number, key: ResolvedKey): ResolvedKey {\n return resolveKey({\n scale: majorKey(targetRoot),\n tonic: spellPitchClass(targetRoot, key.tonic, key.scale),\n });\n}\n\n/**\n * The degree an augmented sixth is applied to, or null when the chord is the\n * augmented sixth of no degree at all.\n *\n * The reading is made by building the local key {@link tonicizedKey} builds for\n * the parse direction and asking whether the chord is an augmented sixth there,\n * so the two directions stay each other's inverse by sharing one derivation:\n * whatever `Ger6/V` builds in a key is exactly what is recognized here.\n *\n * The tonic is skipped, for the reason it is no target for an applied dominant\n * either — the augmented sixth of the home key is that key's own chord, which\n * the unconditional reading has already named, and `Ger6/I` is a numeral no\n * harmony text writes.\n *\n * Only a chord carrying its own tone spellings is read this way, which is the\n * one place this reading is stricter than the home-key one. A chord that brings\n * no letters is spelled by stacking thirds, and that stack writes the French\n * sixth correctly by accident, since the French sixth is the one kind that is a\n * genuine stack of thirds over its own root. Over the single lowered submediant\n * of the prevailing key that inference is safe, but there are six candidate\n * degrees here, and half of all `7b5` chords with the fifth in the bass stand on\n * the lowered submediant of one of them — reading those as French sixths would\n * take an altered dominant nobody spelled and call it an exotic chord. A caller\n * holding pitch classes alone makes the reading it means with\n * {@link augmentedSixthFromPitchClasses}, whose chord carries the letters.\n */\nfunction augmentedSixthTarget(chord: Chord, key: ResolvedKey): TonicizableDegree | null {\n if (chordToneSpellings(chord) === undefined) {\n return null;\n }\n for (const degree of tonicizableDegrees(key.scale)) {\n if (degree.degreeNumber === 1) {\n continue;\n }\n if (augmentedSixthSymbol(chord, tonicizedKey(degree.rootPc, key)) !== null) {\n return degree;\n }\n }\n return null;\n}\n\n/** Build a chord from a parsed Roman numeral, attaching a bass for inversions. */\nfunction chordFromParsed(parsed: {\n rootPc: number;\n quality: ChordQuality;\n inversion: number;\n}): Chord {\n const chord = makeChord(parsed.rootPc, parsed.quality);\n if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {\n chord.bassPc = mod12(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));\n }\n return chord;\n}\n\n/**\n * Build the chord denoted by a Roman numeral in a key.\n *\n * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`\n * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions\n * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash\n * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree\n * whose root becomes a local major tonic for the applied chord. Inverted chords\n * carry a `bassPc`.\n *\n * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==\n * `m7b5`), since no half-diminished triad exists. Extension and added-tone\n * suffixes are the canonical case-sensitive forms emitted by\n * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->\n * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->\n * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->\n * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.\n * A figure string that is neither a recognized inversion nor a supported\n * quality suffix throws rather than silently degrading to a triad.\n *\n * Four chromatic chords are named rather than numbered, and are accepted as\n * whole symbols: the augmented sixths `It6`, `Fr6` and `Ger6` (also written\n * `Ger65`), all three on the lowered submediant in the bass, and `N6` for the\n * first-inversion Neapolitan, which builds exactly the chord `bII6` does.\n * Each may be applied to a degree like any other numeral, so `Ger6/V` is the\n * German sixth of the dominant.\n *\n * @param text The Roman numeral.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The chord.\n * @example\n * ```ts\n * import { romanToChord, majorKey } from '@libraz/libcantus';\n * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }\n * romanToChord('Ger6', majorKey(0)); // Ab C Eb F#, bass Ab\n * ```\n * @category Functional Harmony\n */\nexport function romanToChord(text: string, key: SpelledKeyLike): Chord {\n // The key is kept whole: the chromatic chords named as whole symbols are\n // spelled from its tonic, and a key reduced here would spell them from\n // whichever side of the circle its pitch classes read best as.\n const resolved = resolveKey(key);\n const scale = resolved.scale;\n // Accept the conventional slashes in figured bass (`V6/4`, `V6/5`) while\n // retaining `/` as the separator for applied dominants (`V7/V`).\n const trimmed = text.trim().replace(/(\\d)\\/(?=\\d)/g, '$1');\n const slash = trimmed.indexOf('/');\n if (slash >= 0) {\n const applied = trimmed.slice(0, slash);\n const target = trimmed.slice(slash + 1);\n const targetRoot = parseSimpleRoman(target, scale).rootPc;\n const localKey = tonicizedKey(targetRoot, resolved);\n return (\n chromaticSymbolChord(applied, localKey) ??\n chordFromParsed(parseSimpleRoman(applied, localKey.scale))\n );\n }\n return (\n chromaticSymbolChord(trimmed, resolved) ?? chordFromParsed(parseSimpleRoman(trimmed, scale))\n );\n}\n\n/** Case and suffix for rendering a chord quality as a Roman numeral. */\nfunction romanStyle(quality: ChordQuality): { lower: boolean; suffix: string } {\n return ROMAN_STYLE[quality];\n}\n\n/** Choose the degree number and accidental to spell a root as a Roman numeral. */\nfunction romanSpelling(\n rootPc: number,\n key: KeyScale,\n): { degreeNumber: number; accidental: string } {\n const tones = scaleTonesInDegreeOrder(romanReference(key));\n const diatonic = tones.indexOf(mod12(rootPc));\n if (diatonic >= 0) {\n return { degreeNumber: diatonic + 1, accidental: '' };\n }\n // A chromatic root a semitone above a degree the mode already lowers is that\n // degree raised: C# in A minor is `#III`, not `bIV`.\n const lowered = loweredDegrees(romanReference(key));\n for (let i = 0; i < tones.length; i += 1) {\n if (lowered.has(i + 1) && mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {\n return { degreeNumber: i + 1, accidental: '#' };\n }\n }\n // Otherwise prefer a flat of the diatonic degree a semitone above it,\n // then a sharp of the degree a semitone below. Degree 1 (the tonic) and\n // degree 5 (the dominant) are skipped in the flat pass, so a raised leading\n // tone spells as `#vii` rather than a flat tonic `bI`, and the tritone above\n // the tonic spells as `#iv` rather than a flat fifth `bV` (matching the\n // conventional `OFFSET_SPELLING` for offset 6).\n for (let i = 0; i < tones.length; i += 1) {\n const degreeNumber = i + 1;\n if (degreeNumber === 1 || degreeNumber === 5) {\n continue;\n }\n if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {\n return { degreeNumber, accidental: 'b' };\n }\n }\n for (let i = 0; i < tones.length; i += 1) {\n if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {\n return { degreeNumber: i + 1, accidental: '#' };\n }\n }\n const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ''];\n return { degreeNumber, accidental };\n}\n\n/**\n * The accidental a seventh-degree numeral is written with, which is the one\n * place the spelling is not the literal one.\n *\n * A bare `viio`/`viio7`/`viiø7` conventionally names the raised leading tone in\n * a key whose seventh degree is lowered, so the raised root drops its sharp —\n * and the lowered root, which that numeral therefore no longer names, has to\n * take a flat. Both halves are needed: this is the exact inverse of the reading\n * `parseSimpleRoman` applies, and without the second one the two roots collapse\n * onto a single numeral. The convention covers the whole diminished family, so\n * the half-diminished seventh on the leading tone is not left pointing at a\n * different root than the fully diminished one beside it.\n */\nfunction seventhDegreeAccidental(\n accidental: string,\n degreeNumber: number,\n quality: ChordQuality,\n key: KeyScale,\n): string {\n if (\n degreeNumber !== 7 ||\n !LEADING_TONE_QUALITIES.has(quality) ||\n !loweredDegrees(romanReference(key)).has(7)\n ) {\n return accidental;\n }\n if (accidental === '#') {\n return '';\n }\n return accidental === '' ? 'b' : accidental;\n}\n\n/** Quality marker (without the seventh digit) used when a figure carries the 7. */\nfunction baseMarker(quality: ChordQuality): string {\n if (quality === 'dim' || quality === 'dim7') {\n return 'o';\n }\n if (quality === 'm7b5') {\n return 'ø';\n }\n if (quality === 'aug' || quality === 'aug7') {\n return '+';\n }\n if (quality === 'augMaj7') {\n return '+maj7';\n }\n if (quality === 'maj7' || quality === 'minMaj7') {\n return 'maj7';\n }\n return '';\n}\n\nconst TRIAD_FIGURES: Record<number, string> = { 1: '6', 2: '64' };\nconst SEVENTH_FIGURES: Record<number, string> = { 1: '65', 2: '43', 3: '42' };\n\n/** Qualities whose inversions render as lossless triad figures (6, 64). */\nconst TRIAD_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set(['maj', 'min', 'dim', 'aug']);\n\n/**\n * Qualities carrying a chordal seventh, whose inversions render as seventh\n * figures (65, 43, 42). Membership is decided by the quality itself — a 7th\n * above the root (10 or 11 semitones, or the diminished 7th of `dim7`) — never\n * by interval count, which would turn added-tone chords into false sevenths.\n */\nconst SEVENTH_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set([\n 'maj7',\n 'min7',\n 'dom7',\n 'dim7',\n 'm7b5',\n 'minMaj7',\n 'aug7',\n 'augMaj7',\n]);\n\n/**\n * The numeral glyph for a degree number, cased for the chord quality.\n *\n * Only the seven glyphs exist, so a degree outside 1..7 is a contract violation\n * rather than something to paper over with a default numeral.\n */\nfunction numeralFor(degreeNumber: number, lower: boolean): string {\n const glyph = ROMAN[degreeNumber - 1];\n if (glyph === undefined) {\n throw new InvalidInputError(`no Roman numeral for scale degree ${degreeNumber}`);\n }\n return lower ? glyph.toLowerCase() : glyph;\n}\n\n/**\n * Options for {@link chordToRoman}.\n *\n * @category Functional Harmony\n */\nexport type ChordToRomanOptions = {\n /**\n * Render a tonicizing chord as an applied numeral (`V7/V`, `viio7/ii`)\n * instead of naming its root against the home key (`II7`, `#ivo7`).\n *\n * Off by default: naming the root is always a correct spelling, whereas\n * whether a chromatic dominant is genuinely applied is a reading only the\n * caller can make. Turning it on names the chords {@link secondaryDominant}\n * builds as the applied numerals they are, except in three cases, where the\n * numeral names the root against the home key as it does with this off:\n *\n * - The target degree carries no perfect fifth: nothing tonicizes a\n * diminished triad, so a chord over the seventh degree of a major key stays\n * `#IV7` rather than becoming a numeral no harmony text writes.\n * - The target is the tonic, which its own dominant points at: `V7` is the\n * key's dominant and `V/I` is a numeral no harmony text writes either.\n * - The chord is diatonic to the key, which keeps the function of its degree\n * rather than tonicizing another. `G7` is `VII7` in A minor and `I7` in C\n * mixolydian, not `V7/III` and `V7/IV`.\n */\n applied?: boolean;\n /**\n * Render a first-inversion Neapolitan under its figured name `N6` instead of\n * the numeral `bII6`.\n *\n * Off by default, for the opposite reason to `applied`: both spellings name\n * the same chord and `bII6` is already correct, so which one to write is a\n * house-style choice rather than an analysis. `romanToChord` accepts either\n * whatever this is set to. The augmented sixths need no such switch — no\n * numeral spells them at all, so they always render as `It6`, `Fr6`\n * and `Ger6`.\n */\n neapolitan?: boolean;\n};\n\n/**\n * Render a chord as a Roman numeral relative to a key.\n *\n * Diatonic roots take their scale-degree numeral directly, so numerals are\n * correct in both major and minor keys (and any custom scale). The quality\n * selects the case and suffix; chromatic roots receive a flat/sharp spelling by\n * convention. A key that is not heptatonic has no numeral of its own for every\n * degree, so its roots are named against its parallel major.\n *\n * When the chord carries a `bassPc` on a chord tone, a figured-bass inversion\n * (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true seventh\n * chords; added-tone and extended qualities have no lossless figure and render\n * in root position instead (the bass is dropped, pitch classes are preserved).\n *\n * A `bassPc` that is *not* a chord tone — a pedal or passing bass such as\n * `C/D` — is likewise dropped, and the numeral names the upper chord alone.\n * Roman-numeral notation spends its slash on applied chords (`V7/V`), so there\n * is no unambiguous way to write such a bass; a caller that must keep it should\n * read `chord.bassPc` alongside the numeral.\n *\n * A chord sounding one of the augmented sixths over an explicit bass on the\n * lowered submediant renders as `It6`, `Fr6` or `Ger6`, since no numeral names\n * those chords; see {@link augmentedSixthKind} for exactly when that applies.\n * That reading is made against the key as written, because what separates an\n * augmented sixth from the seventh chord it sounds like is its letters: the\n * German sixth of Ab minor is named from the Fb Ab Cb D it is written with, not\n * from the letters those pitch classes take once the key has been respelled as\n * a G# minor.\n *\n * @param chord The chord to name, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @param opts `applied` renders tonicizing chords as `V7/V`-style numerals;\n * `neapolitan` renders a first-inversion Neapolitan as `N6`.\n * @returns The Roman numeral string.\n * @example\n * ```ts\n * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';\n * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)\n * chordToRoman(makeChord(2, 'dom7'), majorKey(0), { applied: true }); // => 'V7/V'\n * ```\n * @see {@link explainRoman} for the same numeral with the reasoning behind it.\n * @category Functional Harmony\n */\nexport function chordToRoman(\n chord: ChordLike,\n key: KeyLike,\n opts: ChordToRomanOptions = {},\n): string {\n return renderRoman(toChordData(chord), key, assertOptions(opts, 'opts')).roman;\n}\n\n/**\n * Where a chord's bass ended up in the numeral.\n *\n * `'figured'` is an inversion a figure spells losslessly; the last two are the\n * cases where a bass exists and the numeral cannot carry it, which is what a\n * reader wondering where the slash chord went needs told.\n */\ntype RomanBass = 'root' | 'figured' | 'unfigurable' | 'foreign';\n\n/**\n * How a numeral was arrived at, as the facts themselves rather than as prose.\n *\n * {@link chordToRoman} is called once per chord by arrangement analysis, so the\n * rendering path carries data and leaves the phrasing to {@link explainRoman};\n * deriving both from one pass is what keeps the explanation from drifting away\n * from the numeral it explains.\n */\ntype RomanDerivation =\n | { kind: 'augmentedSixth' }\n | { kind: 'applied'; target: string }\n | { kind: 'neapolitan' }\n | {\n kind: 'degree';\n degreeNumber: number;\n /** The accidental the numeral is printed with. */\n accidental: string;\n /**\n * The accidental the root literally stands at above the degree, which is\n * the printed one everywhere except the seventh degree of a key that\n * lowers it. Keeping both is what lets the explanation state where the\n * root actually is instead of reading the display convention back as a\n * fact about the key.\n */\n literalAccidental: string;\n quality: ChordQuality;\n bass: RomanBass;\n figure?: string;\n };\n\n/** Render a chord as a numeral, keeping the facts that decided it. */\nexport function renderRoman(\n chord: Chord,\n keyLike: KeyLike,\n opts: ChordToRomanOptions,\n): { roman: string; derivation: RomanDerivation } {\n // The key is resolved once here and kept whole for the reading that needs its\n // letters. Naming an augmented sixth is that reading: the chord is one only\n // while its tones spell the sixth above the bass, which is a question about\n // the key as written — the German sixth of Ab minor is Fb Ab Cb D, and a key\n // reduced here would measure those letters against a G# minor and answer that\n // the chord is an ordinary VI7.\n const resolved = resolveKey(keyLike);\n const key = resolved.scale;\n // The augmented sixths come first and unconditionally: their pitch classes\n // also spell a dominant seventh or an altered supertonic seventh, but only\n // this reading survives the bass they are standing on.\n const augmented = augmentedSixthSymbol(chord, resolved);\n if (augmented !== null) {\n return { roman: augmented, derivation: { kind: 'augmentedSixth' } };\n }\n if (opts.applied === true) {\n // The augmented sixth of a degree is asked for first, and is not held to the\n // chromatic test the ordinary applied reading is. It has to be asked\n // separately because nothing about where a dominant resolves finds it: the\n // German sixth of the dominant stands a tritone from its target rather than\n // a fifth above it, so the sonority test would hand back a different degree\n // for the same chord. Ordering it first pulls no ordinary chord into the\n // exotic reading, because a chord is an augmented sixth only while its own\n // letters spell that sixth over its bass — the same pitch classes written\n // with a minor seventh on top stay the bIII7 they are.\n const target =\n augmentedSixthTarget(chord, resolved) ??\n (isDiatonicChord(chord, key) ? null : appliedTarget(chord, key));\n if (target !== null) {\n const local = renderRoman(chord, tonicizedKey(target.rootPc, resolved), {}).roman;\n const numeral = numeralFor(target.degreeNumber, target.lower);\n return {\n roman: `${local}/${numeral}`,\n derivation: { kind: 'applied', target: numeral },\n };\n }\n }\n const { degreeNumber, accidental: spelledAccidental } = romanSpelling(chord.rootPc, key);\n const { lower, suffix } = romanStyle(chord.quality);\n const cased = numeralFor(degreeNumber, lower);\n // Render the conventional unaltered `viio`/`viio7` for the raised leading\n // tone in a minor key, and `bviio`/`bviio7` for the subtonic below it.\n // Keeping the parser and formatter aligned makes the common harmonic-minor\n // progression a true round trip.\n const accidental = seventhDegreeAccidental(spelledAccidental, degreeNumber, chord.quality, key);\n\n let inversion = 0;\n let bass: RomanBass = 'root';\n if (chord.bassPc !== undefined) {\n const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);\n if (idx > 0) {\n inversion = idx;\n } else if (idx < 0) {\n bass = 'foreign';\n }\n }\n if (opts.neapolitan === true && inversion === 1 && isNeapolitanChordOf(chord, key)) {\n return { roman: NEAPOLITAN_SIXTH, derivation: { kind: 'neapolitan' } };\n }\n if (inversion > 0) {\n const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality)\n ? SEVENTH_FIGURES[inversion]\n : TRIAD_FIGURE_QUALITIES.has(chord.quality)\n ? TRIAD_FIGURES[inversion]\n : undefined;\n if (figure !== undefined) {\n return {\n roman: `${accidental}${cased}${baseMarker(chord.quality)}${figure}`,\n derivation: {\n kind: 'degree',\n degreeNumber,\n accidental,\n literalAccidental: spelledAccidental,\n quality: chord.quality,\n bass: 'figured',\n figure,\n },\n };\n }\n // No lossless figured-bass symbol exists — the quality is an added-tone or\n // extended chord, or the bass falls on a tension beyond the seventh — so\n // fall back to root-position rendering with the quality suffix rather than\n // emitting a figure that would re-parse as a different chord.\n bass = 'unfigurable';\n }\n return {\n roman: `${accidental}${cased}${suffix}`,\n derivation: {\n kind: 'degree',\n degreeNumber,\n accidental,\n literalAccidental: spelledAccidental,\n quality: chord.quality,\n bass,\n },\n };\n}\n\n/** Ordinal names of the seven scale degrees, indexed by degree number - 1. */\nconst DEGREE_NAMES = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh'] as const;\n\n/**\n * The phrase naming where a numeral's root came from.\n *\n * The claim is made about the root itself, so it reads the accidental the root\n * literally stands at and not the one the numeral is printed with. The two part\n * company on the seventh degree of a key that lowers it, where the bare numeral\n * is reserved for the raised leading tone; saying the root is chromatic because\n * the numeral wears a flat would state the opposite of where the root is, and\n * would make the same root chromatic or diatonic according to the chord's\n * quality.\n */\nfunction describeDegree(degreeNumber: number, accidental: string, literal: string): string {\n const name = DEGREE_NAMES[degreeNumber - 1] ?? `${degreeNumber}th`;\n if (accidental !== literal) {\n return literal === ''\n ? `the root is the ${name} degree of the key, written with a flat because the bare numeral is reserved by convention for the raised leading tone above it`\n : `the root is the raised leading tone, a semitone above the ${name} degree of the key, which the bare numeral denotes by convention`;\n }\n if (literal === '') {\n return `the root is the ${name} degree of the key`;\n }\n const direction = literal === 'b' ? 'lowered' : 'raised';\n return `the root is chromatic, spelled as the ${name} degree ${direction} a semitone`;\n}\n\n/** The phrase naming what became of the chord's bass. */\nfunction describeBass(bass: RomanBass, figure: string | undefined): string {\n switch (bass) {\n case 'root':\n return 'and the chord stands on its own root';\n case 'figured':\n return figure === undefined\n ? 'and the bass is an inversion'\n : `and the bass is an inversion, figured ${figure}`;\n case 'unfigurable':\n return 'and the bass names no lossless figure for this quality, so the numeral is written in root position';\n case 'foreign':\n return 'and the bass is not a chord tone, so the numeral names the upper chord alone';\n }\n}\n\n/**\n * The note a tonic six-four carries, which the numeral alone cannot give.\n *\n * `I64` is the right spelling for the chord, and the harmony it names is a\n * question the chords around it answer: standing on the dominant's bass and\n * resolving onto it, the same notes are a double appoggiatura over the dominant\n * rather than a tonic of their own.\n */\nconst SIX_FOUR_NOTE =\n '; on the dominant bass this chord is heard as a double appoggiatura over the dominant rather than as a tonic of its own, which only the chords around it settle';\n\n/** Build the rationale for a rendered numeral. */\nfunction describeRoman(roman: string, derivation: RomanDerivation): string {\n switch (derivation.kind) {\n case 'augmentedSixth':\n return `${roman}: an augmented sixth, which no numeral spells, standing on the lowered submediant`;\n case 'applied':\n return `${roman}: an applied chord, named in the local key its target degree ${derivation.target} makes a tonic`;\n case 'neapolitan':\n return `${roman}: the first-inversion Neapolitan under its figured name, the chord bII6 also spells`;\n case 'degree': {\n const text = `${roman}: ${describeDegree(derivation.degreeNumber, derivation.accidental, derivation.literalAccidental)}, the case and suffix come from the ${derivation.quality} quality, ${describeBass(derivation.bass, derivation.figure)}`;\n const tonicSixFour =\n derivation.degreeNumber === 1 && derivation.accidental === '' && derivation.figure === '64';\n return tonicSixFour ? `${text}${SIX_FOUR_NOTE}` : text;\n }\n }\n}\n\n/**\n * The numerals the options turned down for this chord.\n *\n * Both switches of {@link ChordToRomanOptions} name a spelling the caller could\n * have had instead, so the rival is exactly what the other setting renders —\n * derived by rendering it rather than by describing it, so the two can never\n * disagree. A setting that changes nothing for this chord produces no rival.\n */\nexport function romanAlternatives(\n chord: Chord,\n key: KeyLike,\n opts: ChordToRomanOptions,\n): RejectedCandidate[] {\n const emitted = renderRoman(chord, key, opts).roman;\n const out: RejectedCandidate[] = [];\n const flippedApplied = renderRoman(chord, key, {\n ...opts,\n applied: opts.applied !== true,\n }).roman;\n if (flippedApplied !== emitted) {\n out.push({\n label: flippedApplied,\n reason:\n opts.applied === true\n ? 'Naming the root against the home key is always a correct spelling; `applied` asked for the tonicizing reading instead'\n : 'Whether a chromatic dominant is genuinely applied is a reading only the caller can make, so the root is named against the home key unless `applied` asks otherwise',\n });\n }\n const flippedNeapolitan = renderRoman(chord, key, {\n ...opts,\n neapolitan: opts.neapolitan !== true,\n }).roman;\n if (flippedNeapolitan !== emitted) {\n out.push({\n label: flippedNeapolitan,\n reason:\n 'Both spellings name the same chord, so which one to write is a house-style choice `neapolitan` settles',\n });\n }\n return out;\n}\n\n/**\n * A numeral with the reasoning behind it.\n *\n * @category Functional Harmony\n */\nexport type RomanExplanation = {\n /** The numeral, exactly as {@link chordToRoman} renders it. */\n roman: string;\n /** How the numeral was arrived at: the degree, the quality, and the bass. */\n rationale: string;\n /**\n * The spellings the options turned down, empty unless the `alternatives`\n * option of {@link ExplainRomanOptions} asked for them.\n */\n alternatives: RejectedCandidate[];\n};\n\n/**\n * Options for {@link explainRoman}: {@link ChordToRomanOptions} plus the switch\n * for the rival spellings.\n *\n * @category Functional Harmony\n */\nexport type ExplainRomanOptions = ChordToRomanOptions & {\n /**\n * Report the numerals the other option settings would have rendered, and why\n * this one was rendered instead.\n *\n * Off by default: each rival costs a further rendering pass, and a caller\n * that only wants the numeral explained should not pay for the ones it did\n * not get.\n *\n * @defaultValue false\n */\n alternatives?: boolean;\n};\n\n/**\n * Render a chord as a Roman numeral and say how the numeral was arrived at.\n *\n * The numeral is exactly what {@link chordToRoman} returns for the same\n * arguments — this is that function with its reasoning attached, for teaching\n * material and for any interface whose users argue with the analysis. The\n * rationale names the degree the root was spelled as, the quality that set the\n * numeral's case and suffix, and what became of the bass, including the two\n * cases where a bass exists and no numeral can carry it.\n *\n * @param chord The chord to name, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @param opts The rendering options of {@link chordToRoman}, plus\n * `alternatives` to collect the spellings they turned down.\n * @returns The numeral, its rationale, and the rejected spellings.\n * @example\n * ```ts\n * import { explainRoman, makeChord, majorKey } from '@libraz/libcantus';\n * explainRoman(makeChord(7, 'dom7'), majorKey(0)).rationale;\n * // 'V7: the root is the fifth degree of the key, the case and suffix come from the dom7 quality, and the chord stands on its own root'\n * const applied = explainRoman(makeChord(2, 'dom7'), majorKey(0), { alternatives: true });\n * applied.alternatives?.map((rejected) => rejected.label); // ['V7/V']\n * ```\n * @category Functional Harmony\n */\nexport function explainRoman(\n chord: ChordLike,\n key: KeyLike,\n opts: ExplainRomanOptions = {},\n): RomanExplanation {\n const asked = assertOptions(opts, 'opts');\n const data = toChordData(chord);\n const { roman, derivation } = renderRoman(data, key, asked);\n return {\n roman,\n rationale: describeRoman(roman, derivation),\n alternatives: asked.alternatives === true ? romanAlternatives(data, key, asked) : [],\n };\n}\n","/**\n * The vocabulary the analyze layer explains itself in.\n *\n * `analyzeVoice` has always returned a short `rationale` beside the labels it\n * assigns; the chord, numeral, cadence and key readings say the same kind of\n * thing about a larger unit. One vocabulary keeps a rationale readable wherever\n * it comes from: the conclusion first, a colon, then the fact it rests on, as a\n * phrase rather than a sentence.\n *\n * The reading that was turned down is worth as much to a reader arguing with the\n * result as the one that was kept, so every entry point that ranks readings can\n * report them in the one shape below.\n */\n\n/**\n * A reading that was considered and rejected, and why it lost.\n *\n * @category Arrangement & Analysis\n */\nexport type RejectedCandidate = {\n /** The rejected reading, named the way the accepted one is named. */\n label: string;\n /** Why the accepted reading was preferred over this one. */\n reason: string;\n};\n\n/**\n * Sentence-case a lower-case term standing at the head of a rationale.\n *\n * The vocabulary the labels come in — `'tonic'`, `'authentic'` — is lower case\n * because it is machine-readable; the same word opening a rationale is prose.\n */\nexport function capitalize(text: string): string {\n return text.charAt(0).toUpperCase() + text.slice(1);\n}\n","/**\n * Harmonic function (tonic / subdominant / dominant), diatonicity, parallel-key\n * mirroring, and full chord analysis.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport { transposeNote } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport { assertFiniteNumber, assertInteger, assertOptions } from '../../core/validation/index.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { makeChord } from '../../theory/chord/index.js';\nimport { type KeyLike, scaleTonesInDegreeOrder, toKeyScale } from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { augmentedSixthKindOf } from './augmented-sixth.js';\nimport { type BorrowedSource, borrowedSourceOf } from './borrowed.js';\nimport {\n degreeRootPc,\n hasMajorThird,\n isDiatonicChord,\n isMinorScale,\n isNeapolitanChordOf,\n mod12,\n parallelScale,\n} from './internal.js';\nimport { capitalize, type RejectedCandidate } from './rationale.js';\nimport { type ChordToRomanOptions, renderRoman, romanAlternatives } from './roman.js';\nimport { isAppliedDominant, pointsAtTonicizableDegree } from './tonicization.js';\n\n/**\n * The three broad harmonic functions of tonal music.\n *\n * @category Functional Harmony\n */\nexport type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';\n\n/** Harmonic function of each semitone offset above the tonic (major context). */\nconst FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [\n 'tonic', // 0 I\n 'subdominant', // 1 bII (Neapolitan)\n 'subdominant', // 2 ii\n 'tonic', // 3 bIII\n 'tonic', // 4 iii\n 'subdominant', // 5 IV\n 'dominant', // 6 #IV / bV\n 'dominant', // 7 V\n 'subdominant', // 8 bVI\n 'tonic', // 9 vi\n 'subdominant', // 10 bVII\n 'dominant', // 11 vii\n];\n\n/**\n * Harmonic function of each root offset in a natural-minor context.\n *\n * The diatonic degrees take the function the textbooks give them; a chromatic\n * offset takes the function its role as a displaced neighbour of a diatonic\n * degree implies. The raised mediant is the case that shows the difference: it\n * carries neither a dominant sonority nor a dominant resolution, so as a\n * chromatic mediant it prolongs the tonic through the tones it shares with it.\n */\nconst MINOR_FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [\n 'tonic', // 0 i\n 'subdominant', // 1 bII (Neapolitan)\n 'subdominant', // 2 iio\n 'tonic', // 3 III\n 'tonic', // 4 #III, the chromatic mediant\n 'subdominant', // 5 iv\n 'dominant', // 6 #iv / bv\n 'dominant', // 7 V\n 'tonic', // 8 VI\n 'tonic', // 9 #VI\n 'subdominant', // 10 VII\n 'dominant', // 11 viio\n];\n\n/**\n * Whether a key's scale has a minor third and no major third (a minor key).\n *\n * @param key The key to test, as a key name, a key/scale, or a `Key`.\n * @returns True if the key's scale is a minor one.\n * @category Functional Harmony\n */\nexport function isMinorKey(key: KeyLike): boolean {\n return isMinorScale(toKeyScale(key));\n}\n\n/**\n * The harmonic function of a chord in a key.\n *\n * The root's offset above the tonic gives the baseline mapping, which follows\n * common-practice major-key function and is a useful approximation in minor and\n * for borrowed chords. Chord quality then refines it where the sonority settles\n * an ambiguity the root offset cannot:\n *\n * - A chord that *sounds* like a dominant — a major third with a minor seventh,\n * or a diminished-family chord — and is neither diatonic to the key nor\n * borrowed from its parallel mode has dominant function when its root\n * resolves onto a degree that can be made a local tonic. This is what makes an\n * applied dominant (`A7` in C, which tonicizes ii) and its tritone substitute\n * (`Db7`) read as dominant rather than inheriting the function of the degree\n * they happen to sit on. A bare major triad carries no tritone, so it only\n * tonicizes by falling a fifth onto a degree other than the tonic: the Picardy\n * third of a minor key and its borrowed major IV sound their own degrees.\n * - The Neapolitan is subdominant.\n * - An augmented sixth is subdominant. All three are altered predominants that\n * resolve outward onto the dominant, so they take subdominant function even\n * though the German sixth sounds a dominant seventh and would otherwise read\n * as the tritone substitute it shares its pitch classes with.\n * - A major triad on bVI or bVII of a major key is subdominant — the borrowed\n * pop cadence chord, distinct from the bVII7 above, which has a seventh and\n * is a dominant sonority.\n *\n * @param chord The chord, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns The harmonic function.\n * @example\n * ```ts\n * import { functionOf, makeChord, majorKey } from '@libraz/libcantus';\n * functionOf(makeChord(9, 'dom7'), majorKey(0)); // 'dominant' — A7 tonicizes ii\n * functionOf('Bb', 'C major'); // 'subdominant' — borrowed bVII\n * ```\n * @category Functional Harmony\n */\nexport function functionOf(chord: ChordLike, key: KeyLike): HarmonicFunction {\n return functionWithReason(toChordData(chord), toKeyScale(key)).function;\n}\n\n/**\n * Which of the rules in {@link functionOf} settled the function.\n *\n * `'degree'` is the baseline mapping from the root's offset above the tonic;\n * the other four are the sonority-aware rules that override it, and naming\n * which one fired is what lets the analysis say why it disagreed with the\n * degree the chord stands on.\n */\ntype FunctionReason = 'neapolitan' | 'augmentedSixth' | 'applied' | 'flatSideMajor' | 'degree';\n\n/** The function the root's offset above the tonic maps to, before any override. */\nfunction degreeFunction(chord: Chord, key: KeyScale): HarmonicFunction {\n const functions = isMinorScale(key) ? MINOR_FUNCTION_BY_OFFSET : FUNCTION_BY_OFFSET;\n return functions[mod12(chord.rootPc - key.rootPc)] ?? 'tonic';\n}\n\n/** {@link functionOf}, keeping the rule that decided the answer. */\nexport function functionWithReason(\n chord: Chord,\n key: KeyScale,\n): { function: HarmonicFunction; reason: FunctionReason } {\n const offset = mod12(chord.rootPc - key.rootPc);\n if (isNeapolitanChordOf(chord, key)) {\n return { function: 'subdominant', reason: 'neapolitan' };\n }\n if (augmentedSixthKindOf(chord, key) !== null) {\n return { function: 'subdominant', reason: 'augmentedSixth' };\n }\n if (isAppliedDominant(chord, key)) {\n return { function: 'dominant', reason: 'applied' };\n }\n // A chord the key already contains is its own degree, however flat that degree\n // lies: the major triad on the subtonic of mixolydian is the mode's own VII,\n // and describing it as borrowed would contradict the source the analysis\n // reports alongside it.\n if (\n !isMinorScale(key) &&\n !isDiatonicChord(chord, key) &&\n hasMajorThird(chord) &&\n (offset === 8 || offset === 10)\n ) {\n return { function: 'subdominant', reason: 'flatSideMajor' };\n }\n return { function: degreeFunction(chord, key), reason: 'degree' };\n}\n\n/**\n * The result of {@link analyzeChord}: function, borrowing, and Roman numeral.\n *\n * @category Functional Harmony\n */\nexport type ChordAnalysis = {\n function: HarmonicFunction;\n borrowed: boolean;\n source: BorrowedSource;\n roman: string;\n /**\n * Why the chord reads this way: the rule that settled its function, and where\n * it was borrowed from when it was borrowed. Always present, in the phrasing\n * {@link analyzeVoice} uses for a note.\n */\n rationale: string;\n /**\n * The readings that were considered and rejected, empty unless the\n * `alternatives` option of {@link AnalyzeChordOptions} asked for them.\n */\n alternatives: RejectedCandidate[];\n};\n\n/**\n * Options for {@link analyzeChord}: {@link ChordToRomanOptions} plus the switch\n * for the rival readings.\n *\n * @category Functional Harmony\n */\nexport type AnalyzeChordOptions = ChordToRomanOptions & {\n /**\n * Report the readings this analysis turned down: the function the chord's\n * degree alone would have carried, the tonicizing reading a dominant sonority\n * could have had, and the numerals the other rendering options would emit.\n *\n * Off by default. The rationale is built from facts the analysis already\n * established, but a rival costs work nothing else needs — a second and third\n * numeral rendering among them — and most callers read the conclusion only.\n *\n * @defaultValue false\n */\n alternatives?: boolean;\n};\n\n/** What a rule overriding the degree table found, phrased for a rationale. */\nconst FUNCTION_REASON_PHRASE: Record<Exclude<FunctionReason, 'degree'>, string> = {\n neapolitan: 'the Neapolitan, an altered predominant on the lowered second degree',\n augmentedSixth: 'an augmented sixth, an altered predominant resolving outward onto the dominant',\n applied: 'an applied dominant sonority resolving onto a diatonic degree',\n flatSideMajor: 'a major triad on the flat side, the borrowed cadence chord',\n};\n\n/** Where a borrowed chord came from, phrased for a rationale. */\nconst BORROWED_SOURCE_PHRASE: Record<Exclude<BorrowedSource, null>, string> = {\n parallelMinor: 'borrowed from the parallel minor',\n parallelMajor: 'borrowed from the parallel major',\n neapolitan: 'belonging to neither parallel mode',\n};\n\n/** Build the rationale for a chord's function and borrowing. */\nfunction describeChord(\n fn: HarmonicFunction,\n reason: FunctionReason,\n roman: string,\n source: BorrowedSource,\n): string {\n const head =\n reason === 'degree'\n ? `${capitalize(fn)}: ${roman} takes the ${fn} function of its degree in the key`\n : `${capitalize(fn)}: ${roman} is ${FUNCTION_REASON_PHRASE[reason]}`;\n // The Neapolitan rule already says where the chord comes from; repeating the\n // source would make the rationale name it twice.\n return source === null || reason === 'neapolitan'\n ? head\n : `${head}, ${BORROWED_SOURCE_PHRASE[source]}`;\n}\n\n/** The readings of the chord's function that were considered and rejected. */\nfunction functionAlternatives(\n chord: Chord,\n key: KeyScale,\n reason: FunctionReason,\n accepted: HarmonicFunction,\n): RejectedCandidate[] {\n const out: RejectedCandidate[] = [];\n if (reason === 'degree') {\n // The sonority could tonicize, and only its being in the key stopped it.\n // Both halves are needed: a reading nothing could have been is not a\n // reading that was turned down. The subdominant of a major key sounds a\n // dominant and is diatonic, but points at no degree the key can tonicize,\n // so reporting it as a rejected applied dominant named a rival that never\n // existed and gave the wrong reason for its absence. What counts as\n // pointing is the predicate the accepting side applies, so inverting the\n // reason stated here is exactly what would make the analysis take it.\n if (pointsAtTonicizableDegree(chord, key) && isDiatonicChord(chord, key)) {\n out.push({\n label: 'applied dominant',\n reason:\n 'The chord is diatonic to the key, so it keeps the function of its degree rather than tonicizing another',\n });\n }\n } else {\n const byDegree = degreeFunction(chord, key);\n if (byDegree !== accepted) {\n out.push({\n label: `${byDegree} by scale degree`,\n reason: `The degree the root stands on reads as ${byDegree}, but the chord is ${FUNCTION_REASON_PHRASE[reason]}`,\n });\n }\n }\n if (augmentedSixthKindOf(chord, key) === 'german') {\n out.push({\n label: 'tritone substitute',\n reason:\n 'The German sixth sounds a dominant seventh, but the lowered submediant under it fixes the reading as an augmented sixth',\n });\n }\n return out;\n}\n\n/**\n * Whether every pitch class of a chord belongs to the key's scale.\n *\n * The test is strict against the key's own mode mask: in a natural-minor key\n * the harmonic-minor dominant (major V) is *not* diatonic, since the raised\n * leading tone lies outside the mask. Borrowing predicates treat that case as\n * an in-key alteration separately (see {@link isBorrowedChord}).\n *\n * @param chord The chord to test, as a chord symbol, chord data, or a `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns True if all chord pitch classes are scale tones.\n * @category Functional Harmony\n */\nexport function isDiatonic(chord: ChordLike, key: KeyLike): boolean {\n return isDiatonicChord(toChordData(chord), toKeyScale(key));\n}\n\n/**\n * The parallel key: same tonic, opposite mode.\n *\n * A key with a minor third (natural/harmonic/melodic minor, dorian, phrygian)\n * maps to the parallel major; any other key maps to the parallel natural minor.\n *\n * Internal to this layer, and not part of the public API. This is the\n * pitch-class form, taking and returning a `KeyScale`, which has nowhere to\n * put a spelling: the parallel of a key named as C is a key whose root is\n * pitch class 0, and whether that is written C or B# is lost. That is fine for\n * the callers here, which go straight on to compare pitch classes, and wrong\n * for a caller who wanted a key to show. Published alongside `parallelKeyOf`\n * it was a trap — the shorter name, the simpler signature, and the silent loss.\n *\n * `parallelKeyOf` is the public answer, and `Key.parallel()` the class one;\n * both keep the tonic spelling, and both sit beside `relativeKeyOf`,\n * `dominantKeyOf` and `subdominantKeyOf`, which take a tonic the same way.\n *\n * @param key The key to mirror, as a key name, a key/scale, or a `Key`.\n * @returns The parallel major or natural-minor key on the same tonic.\n * @see {@link parallelKeyOf} for the spelled form.\n */\nexport function parallelKey(key: KeyLike): KeyScale {\n return parallelScale(toKeyScale(key));\n}\n\n/**\n * Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.\n *\n * The function is quality-aware (see the predicates behind it), the source\n * follows {@link borrowedSource}, and the numeral comes from\n * {@link chordToRoman}. `borrowed` is true whenever a source is identified,\n * which includes the Neapolitan: it belongs to neither parallel mode, and both\n * this and {@link isBorrowedChord} count it as a borrowing all the same.\n *\n * The `rationale` says which of those rules settled the function, so a reader\n * who disagrees can see what the reading rests on. `alternatives` is empty\n * unless asked for, and then names the readings that were turned down.\n *\n * @param chord The chord to analyze, as a chord symbol, chord data, or a\n * `Chord`.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @param opts Options used when rendering the Roman numeral, plus\n * `alternatives` to collect the rejected readings.\n * @returns The chord analysis.\n * @example\n * ```ts\n * import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';\n * analyzeChord(makeChord(7, 'dom7'), majorKey(0));\n * // { function: 'dominant', borrowed: false, source: null, roman: 'V7', ... }\n * analyzeChord(makeChord(9, 'dom7'), majorKey(0), { alternatives: true }).alternatives;\n * // [{ label: 'tonic by scale degree', reason: '...' }, ...]\n * ```\n * @category Functional Harmony\n */\nexport function analyzeChord(\n chord: ChordLike,\n key: KeyLike,\n opts: AnalyzeChordOptions = {},\n): ChordAnalysis {\n const asked = assertOptions(opts, 'opts');\n const data = toChordData(chord);\n const scale = toKeyScale(key);\n const source = borrowedSourceOf(data, scale);\n const { function: fn, reason } = functionWithReason(data, scale);\n const roman = renderRoman(data, scale, asked).roman;\n return {\n function: fn,\n borrowed: source !== null,\n source,\n roman,\n rationale: describeChord(fn, reason, roman, source),\n alternatives:\n asked.alternatives === true\n ? [\n ...functionAlternatives(data, scale, reason, fn),\n ...romanAlternatives(data, scale, asked),\n ]\n : [],\n };\n}\n\n/**\n * The secondary dominant (V7) that tonicizes a scale degree.\n *\n * @param targetDegree 1-based scale degree to tonicize: 5 is the dominant, so\n * `secondaryDominant(5, majorKey(0))` is the V of V.\n * @param key The prevailing key, as a key name, a key/scale, or a `Key`.\n * @returns A dominant-seventh chord a fifth above the target's root.\n * @throws If `targetDegree` is not an integer naming a degree in `key`.\n * @see {@link secondaryDominantOf} to tonicize a chord that has no degree in\n * the key, or when no key is at hand.\n * @category Functional Harmony\n */\nexport function secondaryDominant(targetDegree: number, key: KeyLike): Chord {\n const scale = toKeyScale(key);\n // A degree outside the scale is a caller error, not a wrap-around: silently\n // tonicizing some other degree produces a chord that reads as intentional.\n const degreeCount = scaleTonesInDegreeOrder(scale).length;\n assertInteger(targetDegree, 'targetDegree', 1, degreeCount);\n const targetRoot = degreeRootPc(targetDegree, scale);\n return makeChord(mod12(targetRoot + 7), 'dom7');\n}\n\n/**\n * The secondary dominant (V7) that tonicizes a chord.\n *\n * {@link secondaryDominant} names its target by scale degree, so it only\n * reaches the degrees a key actually has. This takes the target chord itself\n * and needs no key at all, which is what lets a borrowed or chromatic target —\n * bVI of a major key, a Neapolitan, any chord a modulation left behind — have\n * its dominant built without first inventing a degree for it.\n *\n * A `rootSpelling` hint on the target moves with the root, so a flat-named\n * target yields a flat-named dominant (Eb gives Bb7, not A#7).\n *\n * @param target The chord to tonicize, as a chord symbol, chord data, or a\n * `Chord`.\n * @returns A dominant-seventh chord a perfect fifth above the target's root.\n * @throws If the target's root pitch class is not a finite number.\n * @example\n * ```ts\n * import { makeChord, secondaryDominantOf } from '@libraz/libcantus';\n * secondaryDominantOf(makeChord(8, 'maj')).rootPc; // 3 — Eb7 tonicizes the\n * // borrowed bVI of C major\n * ```\n * @category Functional Harmony\n */\nexport function secondaryDominantOf(target: ChordLike): Chord {\n const data = toChordData(target);\n assertFiniteNumber(data.rootPc, 'target chord rootPc');\n const dominant = makeChord(mod12(data.rootPc + 7), 'dom7');\n // Move an explicit spelling hint rather than deriving one: the dominant of a\n // flat-named target is spelled flat, whatever key it is later read in.\n if (data.rootSpelling !== undefined) {\n const moved = transposeNote(data.rootSpelling, 7);\n dominant.rootSpelling = { letter: moved.letter, alter: moved.alter };\n }\n return dominant;\n}\n","/**\n * Pivot chords: the common ground two keys share for a modulation.\n *\n * Roots are pitch classes measured against the key tonic, so the two readings\n * of a pivot follow from the chord's degree in each key rather than requiring a\n * spelled key signature.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { diatonicTriad } from '../../theory/chord/index.js';\nimport { type KeyLike, scaleTonesInDegreeOrder, toKeyScale } from '../../theory/scale/index.js';\nimport { isDiatonicChord } from './internal.js';\nimport { renderRoman } from './roman.js';\n\n/** Degree count of a scale that stacks thirds into triads. */\nconst HEPTATONIC_DEGREES = 7;\n\n/**\n * A chord that is diatonic to two keys at once, with its reading in each.\n *\n * @category Functional Harmony\n */\nexport type PivotChord = {\n /** The shared chord. */\n chord: Chord;\n /** Roman numeral in the key being left. */\n romanFrom: string;\n /** Roman numeral in the key being entered. */\n romanTo: string;\n};\n\n/**\n * List the chords a modulation from `from` to `to` can pivot on.\n *\n * A pivot chord belongs to both keys at once, so it can be heard as a degree of\n * the key being left and reinterpreted as a degree of the key being entered —\n * the seamless way into the new key. Candidates are the diatonic triads of\n * `from`, degrees 1 through 7, kept when every one of their pitch classes is\n * also a scale tone of `to`; the result is ordered by their degree in `from`.\n *\n * Only triads are candidates. The textbook pivot is a triad: a seventh narrows\n * the shared ground by a whole tone for no analytical gain, and the chord that\n * turns the modulation is heard as a triad whether or not a seventh is played\n * over it.\n *\n * A key that is not heptatonic has no diatonic triads at all — stacking scale\n * thirds only lands on a triad over seven degrees — so such a `from` key yields\n * an empty list rather than an error. A `to` key is only tested for membership,\n * so it may be any scale; its numerals then follow the {@link chordToRoman}\n * fallback of naming roots against the parallel major.\n *\n * @param from The key being left, as a key name, a key/scale, or a `Key`.\n * @param to The key being entered, in any of the same forms.\n * @returns The shared triads, ascending by their degree in `from`.\n * @example\n * ```ts\n * import { majorKey, pivotChords } from '@libraz/libcantus';\n * pivotChords(majorKey(0), majorKey(7)).map((p) => `${p.romanFrom}=${p.romanTo}`);\n * // ['I=IV', 'iii=vi', 'V=I', 'vi=ii'] — C major into G major\n * ```\n * @category Functional Harmony\n */\nexport function pivotChords(from: KeyLike, to: KeyLike): PivotChord[] {\n return pivotsBetween(toKeyScale(from), toKeyScale(to));\n}\n\n/** {@link pivotChords} on keys already read into their narrow form. */\nexport function pivotsBetween(from: KeyScale, to: KeyScale): PivotChord[] {\n // Testing the degree count keeps a key with no diatonic triads an empty\n // result rather than letting the stacking throw.\n if (scaleTonesInDegreeOrder(from).length !== HEPTATONIC_DEGREES) {\n return [];\n }\n const pivots: PivotChord[] = [];\n const seen = new Set<string>();\n for (let degree = 1; degree <= HEPTATONIC_DEGREES; degree += 1) {\n const chord = stackedTriad(degree, from);\n if (chord === null || !isDiatonicChord(chord, to)) {\n continue;\n }\n // Candidates are identified by sonority, so a scale whose degrees repeat a\n // triad offers that pivot once.\n const identity = `${chord.rootPc}:${chord.quality}`;\n if (seen.has(identity)) {\n continue;\n }\n seen.add(identity);\n pivots.push({\n chord,\n romanFrom: renderRoman(chord, from, {}).roman,\n romanTo: renderRoman(chord, to, {}).roman,\n });\n }\n return pivots;\n}\n\n/**\n * The diatonic triad on a scale degree, or null where the scale stacks none.\n *\n * A heptatonic scale with uneven steps can stack thirds into an interval set no\n * triad quality describes, which {@link diatonicTriad} rejects. That degree\n * offers nothing to pivot on, so it is skipped rather than failing the query\n * for the six degrees that may well be pivots.\n */\nfunction stackedTriad(degree: number, key: KeyScale): Chord | null {\n try {\n return diatonicTriad(degree, key);\n } catch (error) {\n if (error instanceof InvalidInputError) {\n return null;\n }\n throw error;\n }\n}\n","/**\n * Bar-level evidence shared by the form analyses.\n *\n * Phrases, hypermeter and sections all ask the same two questions of a bar —\n * what sounds in it, and how far that is from what sounded in the bar before —\n * so they read the answer from one place. Everything here works in absolute\n * quarter-note beats and 0-based bar indices, so a pickup is bar -1 rather than\n * a special case.\n */\n\nimport type { MeterLike } from '../../core/meter/index.js';\nimport { barIndexAt, barPositionToBeat } from '../../core/meter/index.js';\nimport type { NoteEvent } from '../../core/types.js';\nimport { assertGenerationBudget } from '../../core/validation/index.js';\nimport { BEAT_EPS } from '../adjacency.js';\nimport { windowWeights } from '../histogram.js';\n\n/** Hold a score inside the [0, 1] range every confidence in this module reports. */\nexport function clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Remainder carrying the sign of the divisor.\n *\n * Bar indices run negative through a pickup, so the phase of a hypermetric\n * group has to be asked with a modulo that answers 3 for bar -1 of a four-bar\n * group rather than -1.\n */\nexport function floorMod(value: number, modulus: number): number {\n return ((value % modulus) + modulus) % modulus;\n}\n\n/**\n * Where the analysed span starts: the earliest beat that sounds.\n *\n * Seeding this with 0 instead would anchor every analysis to bar 0 — an excerpt\n * lifted from bar 9 would be reported as starting at beat 0 and as holding\n * eight bars of silence it never had. The span runs back past beat 0 only for a\n * genuine pickup, which sounds before the first downbeat.\n *\n * @param notes The sounding notes.\n * @param fallback The beat to report when nothing sounds.\n * @returns The earliest onset, or `fallback` for an empty span.\n */\nexport function firstSoundingBeat(notes: readonly NoteEvent[], fallback = 0): number {\n let first = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n first = Math.min(first, note.startBeat);\n }\n return Number.isFinite(first) ? first : fallback;\n}\n\n/**\n * Index of the last bar a span occupies.\n *\n * A span ending exactly on a bar line ends in the bar before it: the bar its\n * end beat falls in has not begun. Asking for the bar of `spanEnd` minus an\n * epsilon does not answer this — bar arithmetic carries a tolerance of its own,\n * measured in bars rather than beats, and rounds such a beat back up — so the\n * empty trailing bar is dropped explicitly.\n *\n * @param meter A single signature, or the piece's meter map.\n * @param spanStart First beat of the span.\n * @param spanEnd End of the span, exclusive.\n * @returns The last bar index, never before the span's first bar.\n */\nexport function lastBarOf(meter: MeterLike, spanStart: number, spanEnd: number): number {\n const firstBar = barIndexAt(spanStart, meter);\n const bar = barIndexAt(Math.max(spanStart, spanEnd), meter);\n if (bar > firstBar && barPositionToBeat({ bar, beat: 0 }, meter) >= spanEnd - BEAT_EPS) {\n return bar - 1;\n }\n return bar;\n}\n\n/** One bar of the analysed span, with what sounds in it. */\nexport type BarSlice = {\n /** 0-based bar index; negative inside a pickup. */\n index: number;\n /** First beat of the bar. */\n startBeat: number;\n /** End of the bar, exclusive. */\n endBeat: number;\n /** Every note overlapping the bar, in input order. */\n notes: NoteEvent[];\n /** Pitch-class weights over the bar, indexed 0-11. */\n weights: number[];\n /** Sum of those weights; zero for a silent bar. */\n totalWeight: number;\n};\n\n/**\n * Cut a span into its bars and weigh what sounds in each.\n *\n * Notes are bucketed into the bars they overlap before any histogram is built,\n * so the cost is one pass over the notes plus one per bar-membership rather\n * than a scan of the whole piece per bar.\n *\n * @param notes The sounding notes; those outside the span are ignored.\n * @param meter A single signature, or the piece's meter map.\n * @param spanStart First beat of the span, negative inside a pickup.\n * @param spanEnd End of the span, exclusive.\n * @param budget Upper bound on the work this may do.\n * @returns One slice per bar, in bar order.\n */\nexport function sliceBars(\n notes: readonly NoteEvent[],\n meter: MeterLike,\n spanStart: number,\n spanEnd: number,\n budget?: number,\n): BarSlice[] {\n const firstBar = barIndexAt(spanStart, meter);\n const lastBar = lastBarOf(meter, spanStart, spanEnd);\n const barCount = lastBar - firstBar + 1;\n assertGenerationBudget(barCount, 'form bars', budget);\n\n const buckets: NoteEvent[][] = Array.from({ length: barCount }, () => []);\n let memberships = 0;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const from = Math.max(firstBar, barIndexAt(note.startBeat, meter));\n const to = Math.min(lastBar, barIndexAt(Math.max(note.startBeat, noteEnd - BEAT_EPS), meter));\n memberships += Math.max(0, to - from + 1);\n assertGenerationBudget(memberships, 'form note-to-bar memberships', budget);\n for (let bar = from; bar <= to; bar += 1) {\n buckets[bar - firstBar]?.push(note);\n }\n }\n\n const slices: BarSlice[] = [];\n for (let bar = firstBar; bar <= lastBar; bar += 1) {\n const startBeat = barPositionToBeat({ bar, beat: 0 }, meter);\n const endBeat = barPositionToBeat({ bar: bar + 1, beat: 0 }, meter);\n const barNotes = buckets[bar - firstBar] ?? [];\n const { weights, totalWeight } = windowWeights(barNotes, startBeat, endBeat, meter);\n slices.push({ index: bar, startBeat, endBeat, notes: barNotes, weights, totalWeight });\n }\n return slices;\n}\n\n/**\n * Cosine similarity of two non-negative weight vectors, in [0, 1].\n *\n * Cosine rather than a difference of weights, because two statements of the\n * same harmony are the same harmony whether they are played loud or soft, long\n * or short. Two silent spans are alike; a silent one and a sounding one are not.\n */\nexport function weightSimilarity(a: readonly number[], b: readonly number[]): number {\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i] ?? 0;\n const y = b[i] ?? 0;\n dot += x * y;\n normA += x * x;\n normB += y * y;\n }\n if (normA <= BEAT_EPS && normB <= BEAT_EPS) {\n return 1;\n }\n if (normA <= BEAT_EPS || normB <= BEAT_EPS) {\n return 0;\n }\n return clamp01(dot / Math.sqrt(normA * normB));\n}\n\n/**\n * How much the harmony changes at the start of slice `index`, in [0, 1].\n *\n * The opening of the music counts as a change outright: nothing precedes it to\n * be the same as. A pickup bar does not — it is an upbeat into the first bar,\n * and treating it as an opening would pull the hypermetric phase onto it.\n */\nexport function harmonicNovelty(slices: readonly BarSlice[], index: number): number {\n const current = slices[index];\n if (current === undefined) {\n return 0;\n }\n const previous = slices[index - 1];\n if (previous === undefined) {\n // The first slice of the analysed span, wherever the span was cut from: an\n // excerpt taken from bar 9 opens on bar 9, and nothing precedes it there\n // either. Only a pickup, which is bar -1, is not an opening.\n return current.index >= 0 ? 1 : 0;\n }\n return 1 - weightSimilarity(previous.weights, current.weights);\n}\n\n/**\n * Sum independent evidence into one strength in [0, 1].\n *\n * Two signals pointing at the same boundary reinforce each other without ever\n * summing past certainty, and neither can drag the other down — which a plain\n * sum or a mean would both get wrong.\n */\nexport function combineEvidence(strengths: Iterable<number>): number {\n let remaining = 1;\n for (const strength of strengths) {\n remaining *= 1 - clamp01(strength);\n }\n return clamp01(1 - remaining);\n}\n","/**\n * Where a slot grid starts: the beat the music itself begins on.\n *\n * Chord inference, key inference and the tension curve slice the same piece\n * into slots of different lengths, so they have to agree about where the piece\n * starts or the three answers describe different spans. They read the origin\n * from here for that reason: the same input can only have one first beat,\n * whether it arrives before the downbeat as a pickup or well after it as an\n * excerpt lifted from the middle of a piece.\n */\n\nimport type { MeterLike } from '../core/meter/index.js';\nimport { beatsPerBarAt } from '../core/meter/index.js';\nimport type { NoteEvent } from '../core/types.js';\nimport { BEAT_EPS, HUMANIZE_ADJACENCY } from './adjacency.js';\nimport { firstSoundingBeat } from './form/internal.js';\n\n/**\n * The grid a span is analyzed in: equal slots of `slotBeats` from `origin`, and\n * the beat the music itself starts on.\n */\nexport type GridOrigin = {\n /**\n * First beat of slot 0. Always a whole number of slots from beat 0, so the\n * grid's boundaries keep falling on the bar lines rather than being pushed\n * off them by the length of the upbeat or by where the excerpt was cut.\n */\n origin: number;\n /**\n * First beat that sounds. Never earlier than `origin` and never a whole slot\n * later than it, so a span whose first note falls inside a slot rather than\n * on its boundary is analyzed in a slot that begins before it without being\n * reported as sounding there.\n */\n startBeat: number;\n};\n\n/**\n * Place the slot grid for a span whose earliest onset is `firstOnset`.\n *\n * @param firstOnset The earliest onset of a sounding note.\n * @param slotBeats Length of one slot in beats.\n * @returns The grid origin and the beat the music starts on.\n */\nexport function gridOriginOf(firstOnset: number, slotBeats: number): GridOrigin {\n // A note a millibeat before the downbeat is playing it rather than\n // anticipating it, and reading it as a pickup would hand the analysis a whole\n // silent slot ahead of the music.\n const startBeat = firstOnset < 0 && firstOnset > -HUMANIZE_ADJACENCY ? 0 : firstOnset;\n // The grid starts where the music does — but a whole number of slots from\n // beat 0, or every slot boundary would sit off the bar lines by however far\n // into a slot the first note falls. Dropping a pickup instead would throw\n // away the very bar that establishes the key.\n return { origin: Math.floor(startBeat / slotBeats + BEAT_EPS) * slotBeats, startBeat };\n}\n\n/**\n * Place the slot grid for the notes themselves.\n *\n * This is the one derivation of an analysed span's origin. Seeding a minimum\n * with 0 instead — the shape each reader would otherwise write for itself —\n * anchors every analysis to beat 0, so an excerpt lifted from bar 9 is reported\n * as starting there and as holding eight bars of silence it never had. The form\n * analyses already answer from {@link firstSoundingBeat}, and this is the same\n * answer placed on a slot grid.\n *\n * @param notes The sounding notes of the span. Notes that never sound are the\n * caller's to drop; a span with none starts at beat 0.\n * @param slotBeats Length of one slot in beats.\n * @returns The grid origin and the beat the music starts on.\n */\nexport function gridForNotes(notes: readonly NoteEvent[], slotBeats: number): GridOrigin {\n return gridOriginOf(firstSoundingBeat(notes), slotBeats);\n}\n\n/**\n * First beat of a span read in bars, with the pickup rule applied.\n *\n * The bar is the slot the form analyses work in, so phrases, sections and\n * hypermeter answer the pickup question from here rather than from a raw\n * minimum of their own. Three readings of one piece that disagree about where\n * bar 1 begins are not three opinions: an export that puts the first note a\n * millibeat early would give one of them a pickup the other two never saw.\n *\n * @param firstOnset The earliest onset of the span, from\n * {@link firstSoundingBeat}.\n * @param meter A single signature, or the piece's meter map.\n * @returns The beat the span's bars are counted from.\n */\nexport function barGridStart(firstOnset: number, meter: MeterLike): number {\n return gridOriginOf(firstOnset, beatsPerBarAt(firstOnset, meter)).startBeat;\n}\n","/**\n * The key as it changes over time.\n *\n * A piece that modulates has no single key, so a whole-piece answer is wrong\n * for every bar after the modulation. This module answers instead with a run of\n * regions, each carrying the key in force over one span, how it relates to the\n * key before it, and — when chords are available — the chord the modulation\n * turned on.\n */\n\nimport type { MeterLike } from '../../core/meter/index.js';\nimport { beatsPerBarAt, metricWeight, resolveMeters } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n allocateCandidateTable,\n allocateChoiceTable,\n assertArray,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertRecord,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSegment } from '../../theory/chord/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport type { KeyRelation, ResolvedKey } from '../../theory/scale/index.js';\nimport {\n isScaleTone,\n keyRelationBetween,\n majorKey,\n minorKey,\n resolveKey,\n} from '../../theory/scale/index.js';\nimport { soundsDominantSeventh } from '../../theory/tendency/index.js';\nimport { BEAT_EPS } from '../adjacency.js';\nimport type { KeyProfileName, KeyProfilePair } from '../detect/index.js';\nimport { profileScore, resolveKeyProfile } from '../detect/profiles.js';\nimport type { PivotChord } from '../functional/index.js';\nimport { pivotChords } from '../functional/index.js';\nimport { gridForNotes } from '../grid.js';\nimport type { WindowWeights } from '../histogram.js';\nimport { bucketNotesBySlot, windowWeights } from '../histogram.js';\n\n/** How many pitch classes a histogram is indexed by. */\nconst PITCH_CLASSES = 12;\n\n/** The histogram a slot outside the grid contributes: nothing at all. */\nconst EMPTY_WEIGHTS: WindowWeights = {\n weights: new Array<number>(PITCH_CLASSES).fill(0),\n totalWeight: 0,\n maxWeight: 0,\n lowestPitch: Number.POSITIVE_INFINITY,\n};\n\n/** The largest value {@link metricWeight} returns (a downbeat). */\nconst MAX_METRIC_WEIGHT = 3;\n\n/**\n * A span of the piece over which one key is in force.\n *\n * @category Arrangement & Analysis\n */\nexport type KeyRegion = {\n /** First beat of the region. */\n startBeat: number;\n /** End of the region, exclusive. */\n endBeat: number;\n /**\n * The key in force across the region, owned by it: every entry point hands\n * back a key of the region's own, so writing to one changes neither the other\n * regions of the same run nor what a later call reports.\n *\n * Whole, whichever way the region was built. A caller who named the key hands\n * its spelling and scale form down, and a key the analysis found is spelled\n * and formed the way {@link resolveKey} reads a bare scale — the reading every\n * other layer would have made of it. There is no third state to guess at: a\n * key/scale alone says nothing about how it is written, so a region carrying\n * one would report whichever spelling those pitch classes read best from, and\n * an Ab minor handed in would come back out a G# minor.\n */\n key: ResolvedKey;\n /**\n * How well the region's music fits the key, in [0, 1]: the correlation\n * between the span's pitch-class distribution and the key's profile, with\n * negative correlations reported as 0.\n *\n * One statistic whichever entry point built the region, so a threshold or a\n * comparison means the same thing across them. Where the region came from\n * chords rather than notes, the distribution is each chord's tones weighted\n * by how long the chord holds.\n */\n confidence: number;\n /**\n * How this key relates to the previous region's — `'dominant'` for a move to\n * the dominant, `'relative'` for a move to the relative minor, and so on.\n * Absent on the first region, and absent when the two keys stand in none of\n * the named relations.\n *\n * Never `'same'`: a region is the maximal span of one key, so consecutive\n * regions never share one and a region carrying this field is a place the\n * piece moved.\n */\n modulation?: KeyRelation;\n /**\n * The chord the modulation turned on, when one was identified. Only the\n * chord-aware entry points fill this in: a pivot is a chord that reads in\n * both keys, so it cannot be named without knowing which chord sounded.\n */\n pivot?: PivotChord;\n};\n\n/**\n * Options shared by the key-region entry points.\n *\n * @category Arrangement & Analysis\n */\nexport type KeyTimelineOptions = {\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: MeterLike;\n /**\n * The meter as it changes over the span. Bar lines, downbeats and the metric\n * accents the search discounts a modulation by all follow the signature in\n * force at the beat in question, so a piece that changes meter is not read in\n * the one it opened in.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterLike;\n /**\n * Expected length of one key area in beats; defaults to four bars of the\n * opening meter.\n *\n * This is a prior, not a window: the longer a key is expected to hold, the\n * more evidence a modulation needs before the search will report one. Keys\n * change far more slowly than chords do, which is why the default is measured\n * in bars rather than beats.\n *\n * @defaultValue four bars of the opening meter\n */\n expectedKeyBeats?: number;\n /**\n * Shortest key area the search may report, in beats; defaults to one bar of\n * the opening meter. Values above `expectedKeyBeats` are clamped to it.\n *\n * @defaultValue one bar of the opening meter\n */\n minKeyBeats?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note or\n * chord segment.\n *\n * @defaultValue the end of the input\n */\n totalBeats?: number;\n /**\n * Key profile the regions are scored against; defaults to `'krumhansl'`.\n * Same meaning as the option of the same name on the key detectors.\n *\n * Every entry point reports `confidence` as the correlation between a\n * region's pitch-class distribution and this profile, so the option is what\n * that number is measured against. Which keys {@link detectModulations}\n * reports is settled by how each chord reads in a key rather than by a\n * pitch-class profile, so there the profile scores the answer without\n * choosing it.\n *\n * @defaultValue `'krumhansl'`\n */\n profile?: KeyProfileName | KeyProfilePair;\n /**\n * Upper bound on the work this call may do.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Cost of one modulation, as a fraction of one expected key area's evidence.\n *\n * The scores this is weighed against are correlations, and neighbouring keys\n * correlate strongly with the same music — a bar of plain C major still scores\n * 0.45 as G major. The evidence that actually separates two keys is therefore a\n * fraction of the raw score, and the cost is set against that margin rather\n * than against the score itself. Charging the full score would make no\n * modulation between related keys ever worth reporting, which is precisely the\n * modulation tonal music spends most of its time making.\n */\nconst MODULATION_COST = 0.15;\n\n/**\n * How loudly a slot hears the slot before it when the key is read.\n *\n * Key moves more slowly than harmony, so a single bar is not enough to settle\n * one. Below 1 the bar still speaks for itself, which is what keeps a boundary\n * on the bar the music turns on rather than a bar either side of it.\n */\nconst PAST_CONTEXT_SHARE = 0.5;\n\n/**\n * How loudly a slot hears the slot after it.\n *\n * Quieter than the past, because a key is established by what has already\n * sounded and only confirmed by what follows. Hearing the future as loudly as\n * the past makes the search hand the closing chord of one key area to the next,\n * which reads a cadence's own resolution as the first bar of the key it was\n * cadencing away from.\n */\nconst FUTURE_CONTEXT_SHARE = 0.25;\n\n/** How much of the modulation cost a maximally strong beat waives. */\nconst STRONG_BEAT_DISCOUNT = 0.4;\n\n/**\n * How much each step around the circle of fifths adds to the modulation cost.\n *\n * A move to the dominant or the relative is what tonal music mostly does; a\n * move to the tritone is what it mostly does not. Charging by distance is what\n * stops a passing chromatic bar from being read as a remote modulation.\n */\nconst FIFTHS_DISTANCE_COST = 0.18;\n\n/** The 24 major and minor keys, in the order the search indexes them. */\nconst KEY_CANDIDATES: readonly { key: KeyScale; rootPc: number; minor: boolean }[] = (() => {\n const candidates: { key: KeyScale; rootPc: number; minor: boolean }[] = [];\n for (let rootPc = 0; rootPc < 12; rootPc += 1) {\n candidates.push({ key: majorKey(rootPc), rootPc, minor: false });\n candidates.push({ key: minorKey(rootPc), rootPc, minor: true });\n }\n return candidates;\n})();\n\n/** Position of a key on the circle of fifths, as a signed count of accidentals. */\nfunction fifthsOf(rootPc: number, minor: boolean): number {\n // A minor key carries its relative major's signature, three semitones up.\n const majorRoot = pitchClass(minor ? rootPc + 3 : rootPc);\n const raw = (majorRoot * 7) % 12;\n return raw > 6 ? raw - 12 : raw;\n}\n\n/** Steps around the circle of fifths between two candidates, 0 to 6. */\nfunction fifthsDistance(a: number, b: number): number {\n const diff = Math.abs(a - b) % 12;\n return Math.min(diff, 12 - diff);\n}\n\n/** Circle-of-fifths distance between every ordered pair of candidates. */\nconst CANDIDATE_DISTANCE: readonly (readonly number[])[] = KEY_CANDIDATES.map((from) =>\n KEY_CANDIDATES.map((to) =>\n fifthsDistance(fifthsOf(from.rootPc, from.minor), fifthsOf(to.rootPc, to.minor)),\n ),\n);\n\n/** A slot of the search: what sounds in it, and how strongly. */\ntype KeySlot = {\n startBeat: number;\n endBeat: number;\n /** Score of each candidate over this slot, already weighted by prominence. */\n scores: number[];\n /** Total prominence of the slot, used to calibrate the modulation cost. */\n weight: number;\n};\n\n/**\n * Choose one key per slot, trading each slot's fit against a cost per\n * modulation.\n *\n * Unlike the chord search, the cost here depends on which key is left as well\n * as which is entered — a modulation to the dominant is ordinary and one to the\n * tritone is not — so the previous stage cannot collapse to a single cheapest\n * predecessor and every pair is considered. With 24 candidates that is 576\n * comparisons per slot, which is nothing next to building the slots.\n */\nfunction chooseKeys(\n slots: readonly KeySlot[],\n changeCost: number,\n meter: MeterLike,\n budget?: number,\n): number[] {\n const count = KEY_CANDIDATES.length;\n if (slots.length === 0) {\n return [];\n }\n // One back-pointer row per slot, charged where it is allocated, and two cost\n // rows the recurrence alternates between: only the previous slot's costs are\n // ever read, so keeping a row per slot would allocate the piece twice over.\n const back = allocateChoiceTable(slots.length, count, 'key search choices', budget);\n let costs = allocateCandidateTable(1, count, 'key search costs', budget);\n let spare = allocateCandidateTable(1, count, 'key search costs', budget);\n const opening = slots[0]?.scores ?? [];\n for (let c = 0; c < count; c += 1) {\n costs[c] = -(opening[c] ?? 0);\n }\n for (let i = 1; i < slots.length; i += 1) {\n const slot = slots[i];\n const previous = costs;\n const next = spare;\n const accent = metricWeight(slot?.startBeat ?? 0, meter);\n const metricFactor = 1 - (STRONG_BEAT_DISCOUNT * accent) / MAX_METRIC_WEIGHT;\n const rowBase = i * count;\n for (let to = 0; to < count; to += 1) {\n // Holding the key is free; every other predecessor pays by distance.\n let best = previous[to] ?? Number.POSITIVE_INFINITY;\n let bestFrom = to;\n for (let from = 0; from < count; from += 1) {\n if (from === to) {\n continue;\n }\n const distance = CANDIDATE_DISTANCE[from]?.[to] ?? 0;\n const penalty = changeCost * metricFactor * (1 + FIFTHS_DISTANCE_COST * distance);\n const cost = (previous[from] ?? Number.POSITIVE_INFINITY) + penalty;\n if (cost < best) {\n best = cost;\n bestFrom = from;\n }\n }\n back[rowBase + to] = bestFrom;\n next[to] = best - (slot?.scores[to] ?? 0);\n }\n costs = next;\n spare = previous;\n }\n\n let best = 0;\n for (let c = 1; c < count; c += 1) {\n if ((costs[c] ?? Number.POSITIVE_INFINITY) < (costs[best] ?? Number.POSITIVE_INFINITY)) {\n best = c;\n }\n }\n const path = new Array<number>(slots.length);\n let current = best;\n for (let i = slots.length - 1; i >= 0; i -= 1) {\n path[i] = current;\n if (i > 0) {\n current = back[i * count + current] ?? current;\n }\n }\n return path;\n}\n\n/** A run of slots the search gave one key, before relations and pivots. */\ntype SlotRegion = {\n startBeat: number;\n endBeat: number;\n candidate: number;\n /** First slot of the run. */\n from: number;\n /** One past its last slot. */\n toExclusive: number;\n};\n\n/**\n * Group a per-slot key choice into regions, without relations or pivots.\n *\n * A region is the maximal span over which one key is in force, so two\n * consecutive runs of the same key are one region whether or not anything\n * sounded between them: silence changes no key, and a region ending at a rest\n * would report the key returning to itself as a modulation. The earlier region\n * therefore reaches the later one rather than stopping at the last slot that\n * sounded — the same reading that lets the last region run to the end of the\n * analyzed span.\n */\nfunction regionsFromPath(slots: readonly KeySlot[], path: readonly number[]): SlotRegion[] {\n const regions: SlotRegion[] = [];\n for (let i = 0; i < slots.length; i += 1) {\n const last = regions[regions.length - 1];\n const slot = slots[i];\n if (slot === undefined) {\n continue;\n }\n if (last !== undefined && last.candidate === path[i]) {\n last.endBeat = slot.endBeat;\n last.toExclusive = i + 1;\n continue;\n }\n regions.push({\n startBeat: slot.startBeat,\n endBeat: slot.endBeat,\n candidate: path[i] ?? 0,\n from: i,\n toExclusive: i + 1,\n });\n }\n return regions;\n}\n\n/**\n * Fold every region shorter than `minBeats` into the neighbour that explains it\n * best, and return the amended per-slot choice.\n *\n * The note path gets this for its inner regions: its slots are `minKeyBeats`\n * long, so no region it groups from whole slots can come out shorter than one.\n * Its first region is clipped to where the music starts and is folded into its\n * neighbour there rather than here. The chord path takes its\n * slots from the chords instead, and a chord is as short as the harmony is —\n * without this a single passing chord is reported as a key area of its own,\n * which is exactly what raising `minKeyBeats` is asked to stop.\n *\n * A region with no neighbour adjoining it is left alone: nothing can absorb a\n * run that stands across a gap in the chords, and shortening the reported span\n * would claim a key holds where no chord sounded.\n */\nfunction enforceMinimumRegion(\n slots: readonly KeySlot[],\n path: readonly number[],\n minBeats: number,\n budget?: number,\n): number[] {\n const chosen = [...path];\n const initial = regionsFromPath(slots, chosen);\n if (initial.length < 2) {\n return chosen;\n }\n const candidates = KEY_CANDIDATES.length;\n // Running totals of every slot's candidate scores. The evidence a region\n // gives a neighbour's key is the difference of two of these, so it is read in\n // constant time however many slots the region has grown to hold — summing the\n // slots afresh cost the slot count once per merge, and a piece whose chords\n // are all shorter than one key area merges once per chord.\n const totals = allocateCandidateTable(slots.length, candidates, 'key region scores', budget);\n for (let i = 0; i < slots.length; i += 1) {\n const scores = slots[i]?.scores ?? [];\n const base = i * candidates;\n const previous = base - candidates;\n for (let c = 0; c < candidates; c += 1) {\n totals[base + c] = (i === 0 ? 0 : (totals[previous + c] ?? 0)) + (scores[c] ?? 0);\n }\n }\n const scoreOver = (region: MergeNode, candidate: number): number =>\n (totals[(region.toExclusive - 1) * candidates + candidate] ?? 0) -\n (region.from === 0 ? 0 : (totals[(region.from - 1) * candidates + candidate] ?? 0));\n\n const nodes = initial.map(\n (region): MergeNode => ({ ...region, previous: undefined, next: undefined, merged: false }),\n );\n for (let i = 0; i < nodes.length; i += 1) {\n const node = nodes[i];\n if (node === undefined) {\n continue;\n }\n node.previous = nodes[i - 1];\n node.next = nodes[i + 1];\n }\n\n // A region only ever grows, so a region already too long to merge stays too\n // long, and the neighbour a region adjoins keeps the edge it adjoins on until\n // one of the two is absorbed. That is what lets the shortest region be taken\n // from a heap instead of by rescanning every region on every merge: an entry\n // whose region has since grown is recognised by its length and dropped.\n const pending = new RegionHeap();\n for (const node of nodes) {\n pending.push(node);\n }\n for (;;) {\n const region = pending.pop();\n if (region === undefined) {\n return chosen;\n }\n const length = region.endBeat - region.startBeat;\n if (region.merged || length >= minBeats - BEAT_EPS) {\n continue;\n }\n const previous = region.previous;\n const next = region.next;\n const before =\n previous !== undefined && Math.abs(previous.endBeat - region.startBeat) < BEAT_EPS\n ? previous\n : undefined;\n const after =\n next !== undefined && Math.abs(next.startBeat - region.endBeat) < BEAT_EPS ? next : undefined;\n if (before === undefined && after === undefined) {\n continue;\n }\n // The shorter region joins the neighbouring key that reads its chords best,\n // so a tonicization is absorbed by the key it decorates rather than by\n // whichever side happens to come first.\n const left = before?.candidate;\n const right = after?.candidate;\n const winner =\n left === undefined\n ? (right ?? region.candidate)\n : right === undefined || scoreOver(region, left) >= scoreOver(region, right)\n ? left\n : right;\n for (let i = region.from; i < region.toExclusive; i += 1) {\n chosen[i] = winner;\n }\n region.candidate = winner;\n // Two consecutive runs of one key are one region whether or not they adjoin,\n // which is the grouping {@link regionsFromPath} would give the amended path.\n let grown = region;\n if (previous !== undefined && previous.candidate === winner) {\n grown = absorbNext(previous);\n }\n if (grown.next?.candidate === winner) {\n grown = absorbNext(grown);\n }\n pending.push(grown);\n }\n}\n\n/** A region of the merge pass, in a list that shrinks as regions absorb others. */\ntype MergeNode = SlotRegion & {\n previous: MergeNode | undefined;\n next: MergeNode | undefined;\n /** Set when the region has been absorbed, so its heap entries are spent. */\n merged: boolean;\n};\n\n/** Fold a region's successor into it, and return the region that remains. */\nfunction absorbNext(region: MergeNode): MergeNode {\n const next = region.next;\n if (next === undefined) {\n return region;\n }\n region.endBeat = next.endBeat;\n region.toExclusive = next.toExclusive;\n region.next = next.next;\n if (next.next !== undefined) {\n next.next.previous = region;\n }\n next.merged = true;\n return region;\n}\n\n/**\n * The shortest region first, and the earlier of two of one length.\n *\n * Regions are taken shortest first because the shortest is the one least able\n * to stand as a key area of its own. Ties go to the earlier region so that a\n * piece of evenly cut chords is read left to right rather than in whatever\n * order the search happened to leave them in.\n */\nclass RegionHeap {\n readonly #entries: { region: MergeNode; length: number }[] = [];\n\n push(region: MergeNode): void {\n const entries = this.#entries;\n entries.push({ region, length: region.endBeat - region.startBeat });\n let index = entries.length - 1;\n while (index > 0) {\n const parent = (index - 1) >> 1;\n if (!RegionHeap.#before(entries[index], entries[parent])) {\n break;\n }\n RegionHeap.#swap(entries, index, parent);\n index = parent;\n }\n }\n\n /** The shortest region still worth merging, skipping entries since spent. */\n pop(): MergeNode | undefined {\n const entries = this.#entries;\n for (;;) {\n const top = entries[0];\n if (top === undefined) {\n return undefined;\n }\n const last = entries.pop();\n if (entries.length > 0 && last !== undefined) {\n entries[0] = last;\n this.#sink();\n }\n // An entry whose region has since absorbed a neighbour describes a length\n // the region no longer has; the merge that grew it pushed the new one.\n if (!top.region.merged && top.region.endBeat - top.region.startBeat === top.length) {\n return top.region;\n }\n }\n }\n\n #sink(): void {\n const entries = this.#entries;\n let index = 0;\n for (;;) {\n const left = index * 2 + 1;\n const right = left + 1;\n let smallest = index;\n if (left < entries.length && RegionHeap.#before(entries[left], entries[smallest])) {\n smallest = left;\n }\n if (right < entries.length && RegionHeap.#before(entries[right], entries[smallest])) {\n smallest = right;\n }\n if (smallest === index) {\n return;\n }\n RegionHeap.#swap(entries, index, smallest);\n index = smallest;\n }\n }\n\n static #before(\n a: { region: MergeNode; length: number } | undefined,\n b: { region: MergeNode; length: number } | undefined,\n ): boolean {\n if (a === undefined || b === undefined) {\n return a !== undefined;\n }\n return a.length === b.length ? a.region.from < b.region.from : a.length < b.length;\n }\n\n static #swap(entries: { region: MergeNode; length: number }[], a: number, b: number): void {\n const left = entries[a];\n const right = entries[b];\n if (left !== undefined && right !== undefined) {\n entries[a] = right;\n entries[b] = left;\n }\n }\n}\n\n/**\n * Attach each region's relation to the one before it.\n *\n * The relation is asked of the conventionally spelled keys, not of raw pitch\n * classes, because that is the only way `'enharmonic'` can be told from\n * `'same'` — and telling them apart is the whole point of naming the relation.\n *\n * A key standing in the `'same'` relation to the one before it is no\n * modulation, so it is not written: a region boundary marks a change of key,\n * and a caller filtering on `modulation` is asking where the piece moved.\n */\nfunction withRelations(regions: KeyRegion[]): KeyRegion[] {\n for (let i = 1; i < regions.length; i += 1) {\n const previous = regions[i - 1];\n const current = regions[i];\n if (previous === undefined || current === undefined) {\n continue;\n }\n // The regions' own keys, not a spelling derived back from their pitch\n // classes: the relation between two keys is read off their tonics, so\n // re-spelling them here would decide it from the side of the circle the\n // pitch classes read best from rather than from the side they are written\n // on.\n const relation = keyRelationBetween(previous.key, current.key);\n if (relation !== null && relation !== 'same') {\n current.modulation = relation;\n }\n }\n return regions;\n}\n\n/**\n * Infer where the key changes from raw notes.\n *\n * The span is examined in `minKeyBeats` slots. Each slot's pitch-class\n * distribution is correlated against all 24 major and minor key profiles, and\n * the run of keys that best explains the piece is chosen, paying a cost per\n * modulation that rises with the distance travelled around the circle of\n * fifths and falls on strong beats. Adjacent slots agreeing on a key become one\n * region.\n *\n * The metric accents that discount a modulation follow the signature in force\n * at the beat in question, so a piece that changes meter is read in the meter\n * it is actually in. The first downbeat is beat 0, so a pickup is written at\n * negative beats and its notes argue for the key like any others rather than\n * falling outside the span.\n *\n * Pivot chords are not reported here: naming one requires knowing which chord\n * sounded at the boundary, which raw notes do not say. Use\n * {@link detectModulations} when a chord timeline is in hand.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link KeyTimelineOptions}.\n * @returns The key regions in time order; empty when nothing sounds.\n * @example\n * ```ts\n * import { keyTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 64, startBeat: 4, durationBeat: 4 },\n * ];\n * const regions = keyTimelineFromNotes(notes);\n * regions[0]?.key; // the key in force at the start\n * ```\n * @category Arrangement & Analysis\n */\nexport function keyTimelineFromNotes(\n notes: readonly NoteEvent[],\n opts: KeyTimelineOptions = {},\n): KeyRegion[] {\n const meters = resolveMeters(opts, 'key timeline meters');\n const bar = beatsPerBarAt(0, meters);\n const expectedKeyBeats = opts.expectedKeyBeats ?? bar * 4;\n assertRange(expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'expectedKeyBeats');\n const minKeyBeats = opts.minKeyBeats ?? bar;\n assertRange(minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minKeyBeats');\n const budget = opts.budget;\n assertNoteEvents(notes, 'key timeline notes', { allowNonPositiveDuration: true, budget });\n const sounding = notes.filter((note) => note.durationBeat > 0);\n const lastEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'key timeline totalBeats');\n\n const slotBeats = Math.min(minKeyBeats, expectedKeyBeats);\n // The key slots are longer than the chord slots, so the two grids start on\n // different beats — but the music starts on one beat, and that is the beat\n // both report, so a chord timeline and its keys cover the same span.\n const { origin, startBeat: musicStart } = gridForNotes(sounding, slotBeats);\n const slotCount = Math.max(0, Math.ceil((totalBeats - origin) / slotBeats - BEAT_EPS));\n // Every slot is scored against the whole candidate list and keeps that row,\n // so what the budget has to bound is the table about to be built rather than\n // the slot count alone: a slot count inside the budget stands for a table\n // twenty-four times that. The chord timeline is charged the same way.\n assertGenerationBudget(slotCount * KEY_CANDIDATES.length, 'key timeline slots', budget);\n if (slotCount === 0 || sounding.length === 0) {\n return [];\n }\n\n const profile = resolveKeyProfile(opts.profile);\n // Each note is sorted into its slots once. Weighing every slot against the\n // whole note list instead would cost the note count times the slot count,\n // and the slot count grows with the piece.\n const slotNotes = bucketNotesBySlot(sounding, { origin, slotBeats }, slotCount, {\n name: 'key timeline note-to-slot memberships',\n budget,\n });\n // A slot's own histogram, which is also what its neighbours hear of it: the\n // window a slot looks back or forward over is exactly the adjacent slot.\n const slotWeights: WindowWeights[] = [];\n for (let i = 0; i < slotCount; i += 1) {\n const startBeat = origin + i * slotBeats;\n slotWeights.push(\n windowWeights(\n slotNotes[i] ?? [],\n startBeat,\n Math.min(startBeat + slotBeats, totalBeats),\n meters,\n ),\n );\n }\n const slots: KeySlot[] = [];\n let weightSum = 0;\n let soundingSlots = 0;\n for (let i = 0; i < slotCount; i += 1) {\n const startBeat = origin + i * slotBeats;\n const endBeat = Math.min(startBeat + slotBeats, totalBeats);\n // One bar in isolation says very little — a bar of G major triad inside C\n // major looks exactly like a bar of tonic G — so the neighbouring bars are\n // heard too. They are heard more quietly than the bar itself, which is what\n // keeps the boundary where the music actually turns instead of smearing it\n // across the bars either side.\n const own = slotWeights[i] ?? EMPTY_WEIGHTS;\n const before = slotWeights[i - 1] ?? EMPTY_WEIGHTS;\n const after = slotWeights[i + 1] ?? EMPTY_WEIGHTS;\n const weights = own.weights.map(\n (weight, pc) =>\n weight +\n PAST_CONTEXT_SHARE * (before.weights[pc] ?? 0) +\n FUTURE_CONTEXT_SHARE * (after.weights[pc] ?? 0),\n );\n // Prominence comes from the slot itself, so a sparse slot does not inherit\n // the weight of its loud neighbours when the modulation cost is calibrated.\n const totalWeight = own.totalWeight;\n // A correlation is dimensionless; multiplying by the slot's prominence puts\n // the fit and the modulation cost into the same units, so a loud, long slot\n // carries more of the argument than a sparse one.\n const scores = KEY_CANDIDATES.map(\n (candidate) =>\n profileScore(weights, candidate.minor ? profile.minor : profile.major, candidate.rootPc) *\n totalWeight,\n );\n slots.push({ startBeat, endBeat, scores, weight: totalWeight });\n if (totalWeight > BEAT_EPS) {\n weightSum += totalWeight;\n soundingSlots += 1;\n }\n }\n if (soundingSlots === 0) {\n return [];\n }\n\n const meanSlotWeight = weightSum / soundingSlots;\n const changeCost = MODULATION_COST * meanSlotWeight * (expectedKeyBeats / slotBeats);\n const path = chooseKeys(slots, changeCost, meters, budget);\n const grouped = regionsFromPath(slots, path);\n // The reported span of the first region is clipped to where the music starts,\n // and the slot holding the first onset begins a whole slot before it wherever\n // that onset is not on a slot boundary. What the search chose was a region a\n // slot long; what a caller was handed could be a sliver of one — a key band a\n // beat wide in front of the piece, which the documented minimum says is only\n // ever left at the end of the analysed span. It is folded into the region\n // after it, which then covers those slots and is read from them.\n\n const head = grouped[0];\n const next = grouped[1];\n if (\n head !== undefined &&\n next !== undefined &&\n head.endBeat - Math.max(head.startBeat, musicStart) < slotBeats - BEAT_EPS\n ) {\n next.startBeat = head.startBeat;\n next.from = head.from;\n grouped.shift();\n }\n const regions = grouped.map((region) => {\n const candidate = KEY_CANDIDATES[region.candidate];\n // A fresh key per region: the candidate table is built once for the process\n // and its distances are precomputed from it, so handing a region the table's\n // own key would let a caller writing to `region.key` rewrite what every\n // later search scores against — and let two regions of one piece share the\n // key that one of them edits.\n const key = resolveKey(candidate?.key ?? majorKey(0));\n // Only the first region can start before the music: the slot holding the\n // pickup begins a whole slot before beat 0, and nothing sounds in the part\n // of it that precedes the upbeat.\n const startBeat = Math.max(region.startBeat, musicStart);\n // The histogram over a region is the histogram over its slots: a note's\n // weight is its overlap with the window, so the parts add up. Nothing\n // sounds before the first onset, so clipping the reported start to it\n // removes no weight.\n const weights = new Array<number>(PITCH_CLASSES).fill(0);\n for (let i = region.from; i < region.toExclusive; i += 1) {\n const slot = slotWeights[i];\n if (slot === undefined) {\n continue;\n }\n for (let pc = 0; pc < PITCH_CLASSES; pc += 1) {\n weights[pc] = (weights[pc] ?? 0) + (slot.weights[pc] ?? 0);\n }\n }\n const correlation = profileScore(\n weights,\n candidate?.minor ? profile.minor : profile.major,\n candidate?.rootPc ?? 0,\n );\n return {\n startBeat,\n endBeat: region.endBeat,\n key: resolveKey(key),\n confidence: Math.max(0, correlation),\n };\n });\n return withRelations(regions);\n}\n\n/** How much of a chord's weight a key must explain before it counts as fitting. */\nconst CHORD_TONE_CREDIT = 1;\n\n/** Extra credit for a chord that is the key's dominant — the strongest signal. */\nconst DOMINANT_CREDIT = 0.7;\n\n/** Extra credit for a chord that is the key's tonic. */\nconst TONIC_CREDIT = 0.4;\n\n/** Extra credit for a tonic that its own dominant resolved onto. */\nconst CADENCE_CREDIT = 0.6;\n\n/**\n * Cost of one modulation on the chord path, in beats of chord evidence.\n *\n * Higher than {@link MODULATION_COST} because chord fits are not correlations:\n * a chord either belongs to a key or does not, so the margin between two\n * readings is a large fraction of the score rather than a small one. Most of a\n * diatonic progression reads plausibly in its subdominant as well as its tonic\n * — C F G C is I IV V I in C and V I II V in F — so without a cost of this size\n * the search reports a modulation every time the harmony touches IV.\n */\nconst CHORD_MODULATION_COST = 0.6;\n\n/** Score how strongly one chord argues for one key. */\nfunction chordFitsKey(chord: Chord, candidate: { key: KeyScale; rootPc: number }): number {\n const tones = chordPitchClasses(chord);\n if (tones.length === 0) {\n return 0;\n }\n let inKey = 0;\n for (const pc of tones) {\n if (isScaleTone(pc, candidate.key)) {\n inKey += 1;\n }\n }\n // A tone outside the key counts against it rather than merely failing to\n // count for it: the F# in a D major triad is the reason to hear G rather\n // than C, and a rule that only rewarded the D and the A would miss it.\n let score = CHORD_TONE_CREDIT * ((inKey - (tones.length - inKey)) / tones.length);\n // A dominant seventh names its key almost by itself, which is what makes a\n // secondary dominant readable as a modulation rather than as a wrong note. It\n // is the tritone inside it that does so, not the name the chord is written\n // under, so every extended and altered spelling of the sonority earns the same\n // credit; a bare major triad a fifth up has no tritone and earns half of it.\n const rootDegree = pitchClass(chord.rootPc - candidate.rootPc);\n if (rootDegree === 7) {\n if (soundsDominantSeventh(chord)) {\n score += DOMINANT_CREDIT;\n } else if (chord.quality === 'maj') {\n score += DOMINANT_CREDIT * 0.5;\n }\n }\n if (rootDegree === 0) {\n score += TONIC_CREDIT;\n }\n return score;\n}\n\n/**\n * Infer where the key changes from a chord sequence, naming the pivot chords.\n *\n * Each chord segment is one slot of the same search {@link keyTimelineFromNotes}\n * runs, scored by how well the chord belongs to each key rather than by a\n * pitch-class profile — a dominant seventh argues for its key far more strongly\n * than its three or four pitch classes alone would suggest. Where the key\n * changes, the chord sounding into the boundary is reported as the pivot if it\n * reads in both keys.\n *\n * The chords supply the slots, so `minKeyBeats` cannot size them as it does in\n * the note path; a region shorter than it is folded into the neighbouring key\n * that reads its chords best instead, which is the same guarantee — a shorter\n * region is reported only where the analyzed span itself ends, or across a gap\n * in the chords that no neighbour adjoins. `profile` scores each region's\n * confidence, as it does in the note path, but takes no part in choosing which\n * keys are reported: that is settled by how the chords read.\n *\n * A region's evidence is the run of segments it was grouped from, which is the\n * same reading the search itself makes of them. Where the segments given\n * overlap each other — a chart is a sequence, so a chord timeline's are not —\n * a chord held past the segment after it argues for its own segment's key\n * rather than for both.\n *\n * @param chords The chord segments to analyze, in time order; pass a chord\n * timeline's `segments`.\n * @param opts Analysis options; see {@link KeyTimelineOptions}.\n * @returns The key regions in time order; empty when no chords were given.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectModulations } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 4 },\n * { pitch: 64, startBeat: 0, durationBeat: 4 },\n * { pitch: 67, startBeat: 0, durationBeat: 4 },\n * ];\n * const { timeline } = chordTimelineFromNotes(notes);\n * const regions = detectModulations(timeline.segments);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectModulations(\n chords: readonly ChordSegment[],\n opts: KeyTimelineOptions = {},\n): KeyRegion[] {\n assertArray(chords, 'modulation chord segments');\n chords.forEach((segment, index) => {\n assertRecord<ChordSegment>(segment, `modulation chord segments[${index}]`);\n });\n const meters = resolveMeters(opts, 'modulation meters');\n const bar = beatsPerBarAt(0, meters);\n const expectedKeyBeats = opts.expectedKeyBeats ?? bar * 4;\n assertRange(expectedKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'expectedKeyBeats');\n const minKeyBeats = opts.minKeyBeats ?? bar;\n assertRange(minKeyBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minKeyBeats');\n // One slot per chord, each holding a row of the candidate list: the search's\n // table, not the chord count, is what the budget stands for.\n assertGenerationBudget(\n chords.length * KEY_CANDIDATES.length,\n 'modulation chord segments',\n opts.budget,\n );\n const sorted = [...chords]\n .filter((segment) => segment.endBeat > segment.startBeat)\n .sort((a, b) => a.startBeat - b.startBeat);\n const lastChordEnd = sorted.reduce((end, segment) => Math.max(end, segment.endBeat), 0);\n const totalBeats = opts.totalBeats ?? lastChordEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'modulation totalBeats');\n // The analyzed span ends where the caller says it does, so a chord held past\n // the end of the piece argues for its key only over the part that sounds.\n const ordered = sorted\n .map((segment) => ({ ...segment, endBeat: Math.min(segment.endBeat, totalBeats) }))\n .filter((segment) => segment.endBeat > segment.startBeat);\n if (ordered.length === 0) {\n return [];\n }\n\n const slots: KeySlot[] = ordered.map((segment) => {\n const length = segment.endBeat - segment.startBeat;\n return {\n startBeat: segment.startBeat,\n endBeat: segment.endBeat,\n // A chord that holds for two bars argues twice as hard as one that\n // passes in half a bar, exactly as a heavier slot does in the note path.\n scores: KEY_CANDIDATES.map((candidate) => chordFitsKey(segment.chord, candidate) * length),\n weight: length,\n };\n });\n // A chord preceded by its own dominant is the clearest statement a key gets,\n // and it is the pair that makes it — so the cadential credit is folded into\n // the arriving chord, where the search can read it as ordinary slot evidence.\n for (let i = 1; i < slots.length; i += 1) {\n const arriving = ordered[i]?.chord;\n const approaching = ordered[i - 1]?.chord;\n const slot = slots[i];\n if (arriving === undefined || approaching === undefined || slot === undefined) {\n continue;\n }\n // Only a dominant seventh earns this. A plain major triad a fifth above is\n // just as readily heard as the chord it moves to being the subdominant —\n // C to F says nothing about whether the key is C or F. What makes a cadence\n // name its key is the tritone, so the sonority is what is asked for: `G13`\n // and `G7b9` cadence exactly as `G7` does.\n const isDominantMotion =\n pitchClass(approaching.rootPc - arriving.rootPc) === 7 && soundsDominantSeventh(approaching);\n if (!isDominantMotion) {\n continue;\n }\n const length = slot.endBeat - slot.startBeat;\n for (let c = 0; c < KEY_CANDIDATES.length; c += 1) {\n const candidate = KEY_CANDIDATES[c];\n if (candidate !== undefined && pitchClass(arriving.rootPc - candidate.rootPc) === 0) {\n slot.scores[c] = (slot.scores[c] ?? 0) + CADENCE_CREDIT * length;\n }\n }\n }\n // Here a slot's weight is its length in beats outright, so one expected key\n // area's worth of evidence is just `expectedKeyBeats` and no mean is needed.\n const changeCost = CHORD_MODULATION_COST * expectedKeyBeats;\n const path = chooseKeys(slots, changeCost, meters, opts.budget);\n // A shortest region longer than a key is expected to hold is a contradiction,\n // and the prior is the one to keep — the note path clamps it the same way.\n const grouped = regionsFromPath(\n slots,\n enforceMinimumRegion(slots, path, Math.min(minKeyBeats, expectedKeyBeats), opts.budget),\n );\n\n const profile = resolveKeyProfile(opts.profile);\n const regions: KeyRegion[] = grouped.map((region) => {\n const candidate = KEY_CANDIDATES[region.candidate];\n // Owned by the region, for the reason the note path spells out: the\n // candidate table outlives the call, and a region's key is writable.\n const key = resolveKey(candidate?.key ?? majorKey(0));\n // The confidence is the statistic the note path reports, not the chord fit\n // the search ran on: a chord fit says which key won, and a correlation says\n // how much of the region the winner explains. Reporting the search's own\n // score here pegged every diatonic passage at 1. A chord sounds all of its\n // tones for all of its length, so the distribution is its pitch classes\n // weighted by how much of the region it holds.\n const weights = new Array<number>(PITCH_CLASSES).fill(0);\n // A region is exactly the run of slots it was grouped from, and a slot is\n // exactly one chord, so the chords sounding over it are the run its bounds\n // already name. Asking that of the whole chord list once per region cost the\n // two counts multiplied, and read every chord's pitch classes again per\n // region for the sake of the ones that contribute nothing.\n for (let i = region.from; i < region.toExclusive; i += 1) {\n const segment = ordered[i];\n if (segment === undefined) {\n continue;\n }\n const overlap = segment.endBeat - segment.startBeat;\n if (overlap <= BEAT_EPS) {\n continue;\n }\n for (const pc of chordPitchClasses(segment.chord)) {\n weights[pc] = (weights[pc] ?? 0) + overlap;\n }\n }\n const correlation = profileScore(\n weights,\n candidate?.minor ? profile.minor : profile.major,\n candidate?.rootPc ?? 0,\n );\n return {\n startBeat: region.startBeat,\n endBeat: region.endBeat,\n key,\n confidence: Math.max(0, correlation),\n };\n });\n // The analyzed span runs to `totalBeats`, so the last key holds to the end of\n // it rather than to the last chord: silence changes no key, and the note path\n // covers its own silent tail the same way.\n const last = regions[regions.length - 1];\n if (last !== undefined && totalBeats > last.endBeat) {\n last.endBeat = totalBeats;\n }\n\n return attachPivots(withRelations(regions), ordered);\n}\n\n/** Semitones above the root a third may sit at: minor, then major. */\nconst THIRD_SEMITONES = [3, 4];\n\n/** Semitones above the root a fifth may sit at: diminished, perfect, augmented. */\nconst FIFTH_SEMITONES = [6, 7, 8];\n\n/**\n * The triad a chord is heard as, or null when it is heard as none.\n *\n * A pivot is a triad, and a chart writes that triad with whatever the players\n * put over it: the `Am7` sounding into a boundary is the A minor triad the\n * modulation turns on, and so is the `Am` and the `Am9`. Only the tones inside\n * the octave are read, so an extension never stands in for a chord member, and\n * the slash bass is not read at all, so an inversion is the same triad. A chord\n * whose third or fifth is replaced rather than played — a suspension, a power\n * chord — is heard as no triad and pivots on nothing.\n *\n * @param chord The chord that sounded.\n * @returns Its root, third and fifth as pitch classes, ascending, or null.\n */\nfunction triadCore(chord: Chord): number[] | null {\n const root = pitchClass(chord.rootPc);\n let third: number | undefined;\n let fifth: number | undefined;\n for (const interval of chord.intervals) {\n if (interval < 0 || interval >= 12) {\n continue;\n }\n if (third === undefined && THIRD_SEMITONES.includes(interval)) {\n third = interval;\n } else if (fifth === undefined && FIFTH_SEMITONES.includes(interval)) {\n fifth = interval;\n }\n }\n if (third === undefined || fifth === undefined) {\n return null;\n }\n return [root, pitchClass(root + third), pitchClass(root + fifth)].sort((a, b) => a - b);\n}\n\n/**\n * Build a `chordFinishedBy(beat)` lookup: the last chord to have stopped by it.\n *\n * \"Last\" is last in the order the chords were given, which is the chord a scan\n * of the whole list would have ended on. Scanning the list again for every\n * boundary cost the two counts multiplied — a piece of ten thousand chords\n * modulating every eight bars scans a hundred times over. Reading the chords in\n * the order they finish instead answers every boundary from one table: the\n * chords that have stopped by a beat are a prefix of that order, and the one to\n * report is the latest-given of the prefix.\n *\n * @param chords The chords that sounded.\n * @returns The lookup, which answers `undefined` before the first chord ends.\n */\nfunction lastChordFinishedBy(\n chords: readonly ChordSegment[],\n): (beat: number) => ChordSegment | undefined {\n const byEnd = chords\n .map((_, index) => index)\n .sort((a, b) => (chords[a]?.endBeat ?? 0) - (chords[b]?.endBeat ?? 0));\n const latest = new Int32Array(byEnd.length);\n let running = 0;\n for (let i = 0; i < byEnd.length; i += 1) {\n running = Math.max(running, byEnd[i] ?? 0);\n latest[i] = running;\n }\n return (beat) => {\n let low = 0;\n let high = byEnd.length;\n while (low < high) {\n const mid = (low + high) >> 1;\n if ((chords[byEnd[mid] ?? 0]?.endBeat ?? 0) <= beat) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low === 0 ? undefined : chords[latest[low - 1] ?? 0];\n };\n}\n\n/** Whether two ascending pitch-class lists name the same tones. */\nfunction sameTones(a: readonly number[], b: readonly number[]): boolean {\n return a.length === b.length && a.every((pc, index) => pc === b[index]);\n}\n\n/**\n * Name the chord each modulation turned on, where the chords support one.\n *\n * The pivot is the last chord to sound before the boundary, reported only when\n * it is diatonic to both keys — which is exactly what makes it a pivot rather\n * than simply the chord that happened to be playing.\n *\n * The chord is matched by the triad it is heard as rather than by its quality\n * name, so a seventh, an added tone or an inversion over the boundary chord\n * names the same pivot the bare triad does. That is what the candidate list\n * means by offering triads alone: see {@link pivotChords}.\n *\n * Not part of the public surface: it takes regions that some other pass already\n * decided, so exposing it would invite callers to pair regions and chords that\n * were never analysed together.\n *\n * @param regions The regions to annotate, modified in place and returned.\n * @param chords The chords that sounded, in time order.\n * @returns The same regions.\n */\nexport function attachPivots(regions: KeyRegion[], chords: readonly ChordSegment[]): KeyRegion[] {\n if (regions.length < 2 || chords.length === 0) {\n return regions;\n }\n const finished = lastChordFinishedBy(chords);\n for (let i = 1; i < regions.length; i += 1) {\n const previous = regions[i - 1];\n const current = regions[i];\n if (previous === undefined || current === undefined) {\n continue;\n }\n const last = finished(current.startBeat + BEAT_EPS);\n if (last === undefined) {\n continue;\n }\n const core = triadCore(last.chord);\n if (core === null) {\n continue;\n }\n const root = pitchClass(last.chord.rootPc);\n const pivot = pivotChords(previous.key, current.key).find(\n (entry) => entry.chord.rootPc === root && sameTones(chordPitchClasses(entry.chord), core),\n );\n if (pivot !== undefined) {\n current.pivot = pivot;\n }\n }\n return regions;\n}\n\n/**\n * Build a `keyAt(beat)` lookup over a run of regions.\n *\n * Regions are disjoint and in beat order, so the covering one is found by\n * binary search. Beats outside every region — before the first note, or inside\n * a rest long enough to have ended a region — answer with the fallback rather\n * than with nothing, because every beat of an analysis has to be read against\n * some key.\n *\n * Not part of the public surface: it is an internal convenience for passes that\n * already hold both the regions and the beats.\n *\n * @param regions The regions to search, in time order.\n * @param fallback The key to answer with outside every region.\n * @returns The lookup.\n */\nexport function keyLookup(\n regions: readonly KeyRegion[],\n fallback: ResolvedKey,\n): (beat: number) => ResolvedKey {\n return (beat) => {\n let low = 0;\n let high = regions.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((regions[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = regions[low - 1];\n return candidate !== undefined && beat < candidate.endBeat ? candidate.key : fallback;\n };\n}\n\n/**\n * The single key that holds for most of a run of regions.\n *\n * Callers that only want one key — a key signature to print, a scale to offer —\n * need the one the piece spends the most time in, not the one it happens to\n * start on.\n *\n * @param regions The regions to weigh, as returned by the key-region entry points.\n * @returns The longest-held key, or null when there are no regions.\n * @example\n * ```ts\n * import { keyTimelineFromNotes, prevailingKeyOf } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const key = prevailingKeyOf(keyTimelineFromNotes(notes));\n * ```\n * @category Arrangement & Analysis\n */\nexport function prevailingKeyOf(regions: readonly KeyRegion[]): ResolvedKey | null {\n assertArray(regions, 'key regions');\n // Held time is summed per key, not per region: a key returned to after a\n // digression prevails over one stated once at length.\n const totals = new Map<string, { key: ResolvedKey; length: number }>();\n for (const [index, given] of regions.entries()) {\n const region = assertRecord<KeyRegion>(given, `key regions[${index}]`);\n assertRecord<ResolvedKey>(region.key, `key regions[${index}].key`);\n const id = `${pitchClass(region.key.scale.rootPc)}:${region.key.scale.modeMask12}`;\n const entry = totals.get(id);\n const length = Math.max(0, region.endBeat - region.startBeat);\n if (entry === undefined) {\n totals.set(id, { key: region.key, length });\n } else {\n entry.length += length;\n }\n }\n let best: ResolvedKey | null = null;\n let bestLength = -1;\n for (const entry of totals.values()) {\n // Ties keep the earlier key, which is the one the piece established first.\n if (entry.length > bestLength) {\n bestLength = entry.length;\n best = entry.key;\n }\n }\n return best;\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertFunction, assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole, intervalAboveRoot, isChordMember } from '../../theory/chord/index.js';\nimport {\n createsVerticalDissonance,\n isLeadingToneResolution,\n} from '../../theory/counterpoint/index.js';\nimport type { VoiceSnapshot } from '../../theory/safety/index.js';\nimport type { KeyLike } from '../../theory/scale/index.js';\nimport { toKeyScale } from '../../theory/scale/index.js';\nimport { adjacent } from '../adjacency.js';\n\n/**\n * A suspension figure, named by the interval above the bass and the interval it\n * resolves to.\n *\n * A figure is reported only when the suspended note forms the interval the\n * figure names. A dissonance that resolves by step from anywhere else — a\n * retardation rising a semitone from the seventh to the octave, say — is left\n * unfigured rather than named after an interval it does not form.\n *\n * @category Arrangement & Analysis\n */\nexport type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n\n/**\n * A theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: ChordToneRole }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: SuspensionFigure; resolveTo: number }\n /**\n * A note leant on: reached by leap and given up by step the other way, with\n * `resolveTo` naming the pitch it gives way to. The word is the one\n * {@link MelodyToneRole} uses for the same figure, so a melody read through\n * this layer and through the harmonizer comes back under one vocabulary\n * rather than under two names for one note.\n */\n | { kind: 'appoggiatura'; resolveTo: number }\n | { kind: 'anticipation' }\n | { kind: 'escape' }\n | { kind: 'needsResolution'; resolveTo: number }\n | { kind: 'leadingTone'; resolveTo: number };\n\n/**\n * A note with its theory labels and a short rationale.\n *\n * The labels describe the note against the chord sounding at its **onset**;\n * they are not re-derived for the later chords a long note sustains across.\n * `analyzeArrangement` does re-check every crossed chord change, and reports\n * what it finds as conflicts, so a note labelled a chord tone here can still\n * appear in `conflicts` at a later beat.\n *\n * The identity fields make an annotation mappable back to the caller's input:\n * `originalIndex` is the note's position in the array that was passed in, and\n * `trackIndex` is set by {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: number;\n /** The note's index in the caller's own array, when the caller supplied one. */\n originalIndex?: number;\n /** The note's track, set by `analyzeArrangement`. */\n trackIndex?: number;\n pitch: number;\n startBeat: number;\n durationBeat: number;\n labels: TheoryLabel[];\n rationale?: string;\n};\n\n/**\n * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,\n * optionally, its index in the caller's original array.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id?: number; originalIndex?: number };\n\n/**\n * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and\n * {@link analyzeArrangement} works in.\n *\n * @category Arrangement & Analysis\n */\nexport type IdentifiedVoiceNote = VoiceNote & { id: number };\n\n/**\n * A key that applies to the whole voice, or the key in force at a given beat.\n *\n * The callback form is what a piece that modulates needs: it is asked for the\n * key at a specific beat, the same way a chord is asked for with `chordAtBeat`.\n *\n * @example\n * ```ts\n * import { majorKey } from '@libraz/libcantus';\n * const wholePiece = majorKey(0); // C major throughout\n * const modulating = (beat: number) => (beat < 8 ? majorKey(5) : majorKey(0));\n * ```\n * @category Arrangement & Analysis\n */\nexport type KeyContext = KeyLike | ((beat: number) => KeyScale);\n\n/**\n * The scale in force at a beat, whichever form the context was given in.\n *\n * One key is read once, here, so an entry point may take a key in any form a\n * caller holds one without the reading being repeated per note. The per-beat\n * form is handed back as it stands: it is called once per note, and resolving\n * inside that loop would put a key parse under every note of the piece.\n */\nexport function keyScaleAt(key: KeyContext): (beat: number) => KeyScale {\n if (typeof key === 'function') {\n return key;\n }\n const scale = toKeyScale(key);\n return () => scale;\n}\n\n/**\n * Give plain note events the ids {@link analyzeVoice} reports back.\n *\n * The id is only an identity handle, so the array position serves; this exists\n * so the mapping is a named call rather than a `map` every caller writes.\n *\n * @param events The note events, in time order.\n * @returns The same notes as voice notes, each carrying its index as its id.\n * @category Arrangement & Analysis\n */\nexport function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[] {\n assertNoteEvents(events, 'voice notes', { allowNonPositiveDuration: true });\n return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));\n}\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * The extension each interval class above the root forms: a flat, natural or\n * raised ninth (1, 2 or 3) to 9, an eleventh (5 or 6) to 11, and a flat or\n * natural thirteenth (8 or 9) to 13.\n *\n * The interval classes left out are the ones a chord states as its own root,\n * third, fifth and seventh, which {@link chordToneRole} names instead.\n */\nconst TENSION_DEGREE_BY_INTERVAL: Readonly<Record<number, 9 | 11 | 13>> = {\n 1: 9,\n 2: 9,\n 3: 9,\n 5: 11,\n 6: 11,\n 8: 13,\n 9: 13,\n};\n\n/**\n * The interval classes forming an extension every chord takes: the natural\n * ninth, eleventh and thirteenth.\n *\n * The rest of the table is the altered extensions, which are alterations of a\n * seventh chord's upper structure and are heard as such only where the chord\n * states a seventh.\n */\nconst NATURAL_TENSION_INTERVALS: ReadonlySet<number> = new Set([2, 5, 9]);\n\n/**\n * The extension an interval class above the root names, or undefined when it\n * names none.\n *\n * Answering undefined rather than a default is what keeps a raised ninth — the\n * interval class three semitones above the root — from being reported as the\n * thirteenth that a catch-all would name it.\n */\nfunction tensionDegree(ic: number): 9 | 11 | 13 | undefined {\n return TENSION_DEGREE_BY_INTERVAL[ic];\n}\n\n/**\n * A chord with the bass it is written over among its own tones.\n *\n * Only for reading a tone the template leaves out: a slash chord states a bass\n * its stack of thirds does not reach, and the sonority the two make together is\n * what a listener hears. Everything else reads the template, which is what the\n * chord is rather than what it happens to sound under.\n */\nfunction asSounded(chord: Chord): Chord {\n if (chord.bassPc === undefined) {\n return chord;\n }\n return { ...chord, intervals: [...chord.intervals, intervalAboveRoot(chord.bassPc, chord)] };\n}\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d === 1 || d === 2;\n}\n\n/** Narrowest interval heard as a leap: anything wider than a step. */\nconst LEAP_SEMITONES = 3;\n\n/**\n * Classify a suspension figure from the interval class above the sounding bass\n * (`ic`) and the resolution direction (`delta`, positive when resolving upward).\n */\nfunction suspensionType(ic: number, delta: number): SuspensionFigure | null {\n if (delta > 0) {\n // Resolving upward is the 2-3 only from a second above the bass. A seventh\n // above it rising to the octave is a retardation, and the figure 2-3 would\n // name it after an interval it does not form.\n return ic === 1 || ic === 2 ? 'sus2-3' : null;\n }\n if (ic === 2) {\n return 'sus9-8';\n }\n if (ic === 10 || ic === 11) {\n return 'sus7-6';\n }\n if (ic === 5 || ic === 6) {\n return 'sus4-3';\n }\n if (ic === 8 || ic === 9) {\n return 'sus6-5';\n }\n // Any other interval above the bass is a downward-resolving dissonance with\n // no standard figure; a catch-all `sus4-3` would name it after an interval it\n // does not form.\n return null;\n}\n\n/** Widest interval, in semitones, a resolution by step may cover. */\nconst STEP_RESOLUTION = 2;\n\n/**\n * How far the fallback search for a resolution target reaches, in semitones.\n * A chord sounds its root, so a member is always found inside an octave.\n */\nconst NEAREST_CHORD_TONE_REACH = 12;\n\n/**\n * The nearest chord member to a pitch, searched outward from it and downward\n * first at equal distance, within `maxDistance` semitones.\n *\n * A dissonance resolves by step wherever a chord tone lies a step away, which\n * is what a `maxDistance` of {@link STEP_RESOLUTION} asks for. The wider search\n * names the closest chord tone there is, so a dissonance with no stepwise exit\n * — a minor third or a major seventh over a bare triad — still reports where it\n * wants to go instead of going unlabelled.\n */\nfunction nearestChordTone(pitch: number, chord: Chord, maxDistance: number): number | undefined {\n for (let delta = 1; delta <= maxDistance; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n if (isChordMember(pitch + delta, chord)) {\n return pitch + delta;\n }\n }\n return undefined;\n}\n\n/**\n * Label every note of a voice with its theory roles.\n *\n * Each note is classified against the chord sounding at its beat. Chord tones\n * get a role label; non-chord tones are matched, in order, as suspensions\n * (prepared by an identical consonant pitch and resolving by step), passing\n * tones, neighbors, anticipations, appoggiaturas (approached by leap and\n * resolving by step the other way), and escape tones, then fall back to\n * tension, avoid, or an unresolved-dissonance label. The figures are named as\n * {@link classifyMelodyTones} names them, so a caller reading a melody through\n * both layers gets one vocabulary. The two answer different questions and\n * neither refines the other: that classifier weighs the metre, which this one is\n * not given, so it can call ornamental a note this one reads as a chord tone,\n * and the reverse. Leading-tone resolutions are noted\n * additionally, judged against the key in force at the beat the resolution\n * lands on, so a modulation is heard from its new tonic.\n *\n * The voice is expected to be monophonic (one note at a time). Notes sharing an\n * onset are treated as simultaneous cluster members, not melodic neighbors, so\n * they receive no melodic labels (suspension, passing, neighbor, anticipation,\n * escape) — only harmonic ones. Callers with truly polyphonic material should\n * split it into monophonic sub-voices first, as `analyzeArrangement` does.\n *\n * @param voice The monophonic voice, in time order. A note without an `id` is\n * identified by its position, so plain note events can be passed straight in.\n * @param chordAtBeat Chord sounding at a given beat, or null.\n * @param key Key context for leading-tone detection: a single {@link KeyScale}\n * covering the whole voice, or a callback giving the key in force at a given\n * beat, for music that modulates.\n * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to\n * none, which is the whole story for a solo line.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * const cMajor = makeChord(0, 'maj');\n * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));\n * labels; // one AnalyzedNote per input note, in the same order\n * // A voice that modulates to C major at beat 2:\n * analyzeVoice(voice, () => cMajor, (beat) => (beat < 2 ? majorKey(5) : majorKey(0)));\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeVoice(\n voice: readonly VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyContext,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[] = () => [],\n): AnalyzedNote[] {\n assertNoteEvents(voice, 'voice notes', { allowNonPositiveDuration: true });\n assertFunction(chordAtBeat, 'chordAtBeat');\n assertFunction(otherVoicesAtBeat, 'otherVoicesAtBeat');\n const keyAt = keyScaleAt(key);\n const result: AnalyzedNote[] = [];\n\n for (let i = 0; i < voice.length; i += 1) {\n const note = voice[i];\n if (!note) {\n continue;\n }\n const prevNote = i > 0 ? voice[i - 1] : undefined;\n const nextNote = i + 1 < voice.length ? voice[i + 1] : undefined;\n // A neighboring note sharing this note's onset is a simultaneous cluster\n // member, not a melodic predecessor/successor; treating it as one would\n // fabricate passing/suspension figures, so it is dropped here.\n const prevEnd =\n prevNote === undefined\n ? Number.NEGATIVE_INFINITY\n : prevNote.startBeat + prevNote.durationBeat;\n const noteEnd = note.startBeat + note.durationBeat;\n const prev = prevNote !== undefined && adjacent(prevEnd, note.startBeat) ? prevNote : undefined;\n const next =\n nextNote !== undefined && adjacent(noteEnd, nextNote.startBeat) ? nextNote : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n const degree =\n role === null ? tensionDegree(intervalAboveRoot(note.pitch, chord)) : undefined;\n if (role) {\n labels.push({ kind: 'chordTone', role });\n handled = true;\n } else if (degree !== undefined) {\n labels.push({ kind: 'tension', degree });\n handled = true;\n } else {\n // A slash bass the chord's own template does not contain falls between\n // two readings: the membership test counts the bass, the role test\n // reads the template, and every branch below is closed by `!member`.\n // It is the one tone a chord names without stacking it, so it is read\n // against the chord as it sounds — the B flat under a C triad is the\n // seventh that sonority has, and carries that tone's obligation.\n const bassRole = chordToneRole(note.pitch, asSounded(chord));\n if (bassRole !== null) {\n labels.push({ kind: 'chordTone', role: bassRole });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevChord = chordAtBeat(prev.startBeat);\n const prepared = prev.pitch === note.pitch && isChordMember(prev.pitch, prevChord);\n const resolves = isStep(next.pitch, note.pitch);\n const others = otherVoicesAtBeat(note.startBeat);\n const verticallyDissonant = others.some((ov) =>\n createsVerticalDissonance(note.pitch, ov.pitch, true),\n );\n if (prepared && verticallyDissonant && resolves) {\n const ic = intervalAboveBass(note.pitch, others, chord);\n const type = suspensionType(ic, next.pitch - note.pitch);\n if (type !== null) {\n labels.push({ kind: 'suspension', type, resolveTo: next.pitch });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const up = note.pitch - prev.pitch > 0;\n const contInto = next.pitch - note.pitch > 0;\n const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;\n if (\n prevMember &&\n nextMember &&\n !member &&\n isStep(note.pitch, prev.pitch) &&\n isStep(next.pitch, note.pitch) &&\n sameDir\n ) {\n labels.push({ kind: 'passing' });\n handled = true;\n }\n }\n\n if (\n !handled &&\n chord &&\n prev &&\n next &&\n !member &&\n prev.pitch === next.pitch &&\n isChordMember(prev.pitch, chordAtBeat(prev.startBeat)) &&\n isStep(note.pitch, prev.pitch)\n ) {\n labels.push({ kind: 'neighbor' });\n handled = true;\n }\n\n if (!handled && chord && next && !member && note.pitch === next.pitch) {\n const nextChord = chordAtBeat(next.startBeat);\n if (isChordMember(next.pitch, nextChord)) {\n labels.push({ kind: 'anticipation' });\n handled = true;\n }\n }\n\n // The appoggiatura and the escape tone are each other's mirror: one leans in\n // by leap and gives way by step, the other steps out of the harmony and\n // leaves by leap. Both turn back on themselves, which is what separates them\n // from a passing tone travelling the same way throughout.\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const leapFromPrev = Math.abs(note.pitch - prev.pitch) >= LEAP_SEMITONES;\n const stepToNext = isStep(next.pitch, note.pitch);\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && leapFromPrev && stepToNext && opposite) {\n labels.push({ kind: 'appoggiatura', resolveTo: next.pitch });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const stepFromPrev = isStep(note.pitch, prev.pitch);\n const leapToNext = Math.abs(next.pitch - note.pitch) >= LEAP_SEMITONES;\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {\n labels.push({ kind: 'escape' });\n handled = true;\n }\n }\n\n if (!handled && chord && !member) {\n const ic = intervalAboveRoot(note.pitch, chord);\n // Which extension a note forms is a matter of its interval above the\n // root, so an altered ninth is heard as one whether or not the chord\n // symbol writes it: the same A flat must not read as a tension over G7b9\n // and as a dissonance needing resolution over G7. An alteration is an\n // alteration of a seventh chord's upper structure, though, so where the\n // chord states no seventh a chromatic note is the dissonance it sounds\n // like rather than a colour the harmony implies.\n const degree = tensionDegree(ic);\n const colours =\n NATURAL_TENSION_INTERVALS.has(ic) || chord.intervals.some((i) => i === 10 || i === 11);\n const avoid =\n (ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5)) ||\n (ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10));\n if (avoid) {\n labels.push({ kind: 'avoid' });\n const resolveTo = nearestChordTone(note.pitch, chord, STEP_RESOLUTION);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n } else if (degree !== undefined && colours) {\n labels.push({ kind: 'tension', degree });\n } else {\n // Every note sounding against a chord is classified, so this branch\n // labels the dissonance whether or not it has a stepwise exit: an empty\n // label list would be indistinguishable from a consonance.\n const resolveTo = nearestChordTone(note.pitch, chord, NEAREST_CHORD_TONE_REACH);\n labels.push(\n resolveTo === undefined ? { kind: 'avoid' } : { kind: 'needsResolution', resolveTo },\n );\n }\n }\n\n // A leading tone is only one because of the tonic it arrives on, so the\n // key is read at the beat of that arrival rather than at this note's own\n // beat: a modulation places its key change on the new tonic, and the\n // leading tone that carries the voice into it still sits in the old key.\n if (next && isLeadingToneResolution(note.pitch, next.pitch, keyAt(next.startBeat))) {\n labels.push({ kind: 'leadingTone', resolveTo: next.pitch });\n }\n\n const analyzed: AnalyzedNote = {\n noteId: note.id ?? i,\n pitch: note.pitch,\n startBeat: note.startBeat,\n durationBeat: note.durationBeat,\n labels,\n rationale: describe(labels),\n };\n if (note.originalIndex !== undefined) {\n analyzed.originalIndex = note.originalIndex;\n }\n result.push(analyzed);\n }\n\n return result;\n}\n\n/** Build a short rationale from a note's primary label. */\nfunction describe(labels: TheoryLabel[]): string {\n const primary = labels[0];\n if (!primary) {\n return 'Unclassified note';\n }\n switch (primary.kind) {\n case 'chordTone':\n return `Chord ${primary.role}`;\n case 'tension':\n return `${primary.degree}th tension`;\n case 'avoid':\n return 'Avoid note';\n case 'passing':\n return 'Passing tone between chord tones';\n case 'neighbor':\n return 'Neighbor tone';\n case 'suspension':\n return `Suspension (${primary.type})`;\n case 'appoggiatura':\n return 'Appoggiatura leaning on the harmony, resolving by step';\n case 'anticipation':\n return 'Anticipation of the next chord';\n case 'escape':\n return 'Escape tone';\n case 'needsResolution':\n return 'Unresolved dissonance';\n case 'leadingTone':\n return 'Leading tone resolving to the tonic';\n }\n}\n","import { sortedNoteEvents } from '../../core/event-index/index.js';\nimport type { MeterLike, MeterMap } from '../../core/meter/index.js';\nimport {\n beatsPerBarAt,\n metricGridUnit,\n metricWeight,\n resolveMeters,\n} from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport type { NoteEventAssertOptions } from '../../core/validation/index.js';\nimport {\n allocateCandidateTable,\n allocateChoiceTable,\n assertArray,\n assertFiniteNumber,\n assertFunction,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertRecord,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordQuality, ChordSpan } from '../../theory/chord/index.js';\nimport { chordFromSpan, chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport type { KeyLike } from '../../theory/scale/index.js';\nimport {\n isScaleTone,\n majorKey,\n type ResolvedKey,\n resolveKey,\n scaleOf,\n} from '../../theory/scale/index.js';\nimport { BEAT_EPS } from '../adjacency.js';\nimport type { ChordMatch } from '../detect/index.js';\nimport { detectChord } from '../detect/index.js';\nimport { augmentedSixthFromPitchClasses } from '../functional/augmented-sixth.js';\nimport { isCadentialSixFour } from '../functional/cadence.js';\nimport type { CadenceResult } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\nimport { gridForNotes } from '../grid.js';\nimport type { SlotGrid, WindowWeights } from '../histogram.js';\nimport { bucketNotesBySlot, windowWeights } from '../histogram.js';\nimport type { KeyRegion } from '../keys/index.js';\nimport { attachPivots, keyLookup, keyTimelineFromNotes, prevailingKeyOf } from '../keys/index.js';\nimport type { KeyContext } from '../voice/index.js';\nimport { keyScaleAt } from '../voice/index.js';\n\nexport type { ChordSegment } from '../../theory/chord/index.js';\n\nimport type { ChordSegment } from '../../theory/chord/index.js';\n\n/**\n * A beat-indexed sequence of chord segments.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimeline = {\n /** The chord sounding at a beat, or null when no segment covers it. */\n at: (beat: number) => Chord | null;\n segments: ChordSegment[];\n};\n\n/**\n * Reject a value that is not a chord timeline.\n *\n * A timeline is the one argument a host is most likely to hold from somewhere\n * else — an in-memory project value, a previous run restored to a timeline, or\n * another library's output — and\n * it is read field by field deep inside an analysis. Checking it once at the\n * entrance names the argument that is wrong instead of reporting the library's\n * own `TypeError` from wherever the first `segments` lookup happened to be.\n *\n * The segments are checked too: every reader of a timeline compares beats, and\n * a segment whose bounds are missing compares as neither before nor after, so\n * it disappears from the analysis rather than being reported.\n *\n * @param timeline The timeline as the caller passed it.\n * @param name What the argument is called in an error message.\n * @returns The same timeline.\n * @throws If the value is not a timeline of chord segments.\n * @example\n * ```ts\n * import { assertChordTimeline, chordTimelineFromChords } from '@libraz/libcantus';\n * assertChordTimeline(chordTimelineFromChords([], 4)); // the timeline, unchanged\n * ```\n * @category Arrangement & Analysis\n */\nexport function assertChordTimeline(timeline: ChordTimeline, name = 'timeline'): ChordTimeline {\n const read = assertRecord<ChordTimeline>(timeline, name);\n assertFunction(read.at, `${name}.at`);\n const segments = assertArray<ChordSegment>(read.segments, `${name}.segments`);\n for (let index = 0; index < segments.length; index += 1) {\n const segment = assertRecord<ChordSegment>(segments[index], `${name}.segments[${index}]`);\n assertFiniteNumber(segment.startBeat, `${name}.segments[${index}].startBeat`);\n assertFiniteNumber(segment.endBeat, `${name}.segments[${index}].endBeat`);\n }\n return read;\n}\n\n/**\n * Build a chord timeline from placed chords.\n *\n * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last\n * chord runs to `totalBeats`. Segments with no positive length (from duplicate\n * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns\n * the covering segment's chord, or null when the beat lies outside every segment.\n *\n * @param chords Placed chords in time order.\n * @param totalBeats End of the timeline in beats.\n * @returns A queryable chord timeline.\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromChords(\n chords: readonly ChordSpan[],\n totalBeats: number,\n): ChordTimeline {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n assertArray<ChordSpan>(chords, 'chords');\n assertGenerationBudget(chords.length, 'timeline chords');\n for (let index = 0; index < chords.length; index += 1) {\n // Unbounded below, like a note onset: a chord may sound in the pickup.\n assertRange(\n chords[index]?.startBeat ?? Number.NaN,\n -Number.MAX_SAFE_INTEGER,\n Number.MAX_SAFE_INTEGER,\n `chords[${index}].startBeat`,\n );\n }\n const sorted = [...chords]\n .filter((chord) => chord.startBeat < totalBeats)\n .sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const nextBeat = next ? next.startBeat : totalBeats;\n const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));\n return {\n startBeat: gc.startBeat,\n endBeat,\n chord: chordFromSpan(gc),\n };\n })\n .filter((seg) => seg.endBeat > seg.startBeat);\n\n return { at: segmentLookup(segments), segments };\n}\n\n/**\n * The chord sounding at a beat, or null where nothing is.\n *\n * A segment covers `[startBeat, endBeat)`, so the beat a chord gives way on\n * answers with the chord arriving rather than the one leaving. Segments are\n * disjoint and in beat order, so the covering one is found by binary search\n * rather than by a scan: this is the inner loop of arrangement analysis, which\n * queries it once per note per chord change.\n *\n * The class-side timeline answers `at(beat)` through this same search, so the\n * function and the class cannot come to differ over where a chord starts.\n *\n * @param segments The segments to search, disjoint and in beat order.\n * @param beat The beat to read.\n * @returns The covering segment's chord, or null outside every segment.\n * @throws If `beat` is not finite.\n */\nexport function chordAtBeat(segments: readonly ChordSegment[], beat: number): Chord | null {\n assertFiniteNumber(beat, 'timeline query beat');\n const candidate = segments[firstAfter(segments, beat) - 1];\n return candidate !== undefined && beat >= candidate.startBeat && beat < candidate.endBeat\n ? candidate.chord\n : null;\n}\n\n/** Index of the first segment beginning after `beat`, by binary search. */\nfunction firstAfter(segments: readonly ChordSegment[], beat: number): number {\n let low = 0;\n let high = segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * The segment a chord arrives on at a beat, if one begins there.\n *\n * The other question a caller asks of a timeline: {@link chordAtBeat} answers\n * what is sounding at a beat, and this answers what starts on it — which is how\n * long the chord a cadence arrives on holds. Segments are disjoint and in beat\n * order, so it is the same binary search rather than a scan: a passage where\n * most chord changes are cadential — a ii-V-I loop, which is what the\n * generators write — asks it once per chord, and a scan made that the square of\n * the piece.\n *\n * @param segments The segments to search, disjoint and in beat order.\n * @param beat The beat a chord may arrive on.\n * @returns The segment beginning there, or undefined when none does.\n */\nexport function segmentStartingAt(\n segments: readonly ChordSegment[],\n beat: number,\n): ChordSegment | undefined {\n const index = firstAfter(segments, beat);\n for (const candidate of [segments[index - 1], segments[index]]) {\n if (candidate !== undefined && Math.abs(candidate.startBeat - beat) < BEAT_EPS) {\n return candidate;\n }\n }\n return undefined;\n}\n\n/** Build the `at(beat)` lookup a {@link ChordTimeline} carries. */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => chordAtBeat(segments, beat);\n}\n\n/** Fraction of the strongest pitch-class weight below which a pc is noise. */\nconst NOISE_THRESHOLD_RATIO = 0.2;\n\n/** Maximum number of pitch classes fed to chord detection per window. */\nconst MAX_DETECTION_PCS = 6;\n\n/** Confidence multiplier applied when the chosen chord match is inexact. */\nconst INEXACT_CONFIDENCE_FACTOR = 0.85;\n\n/** Score bonus for a match whose tones are all in the key. */\nconst DIATONIC_BONUS = 0.5;\n\n/** Score bonus for an exact match (no extra and no missing tones). */\nconst EXACT_BONUS = 0.5;\n\n/**\n * Score penalty per sounding pitch class a match leaves unexplained.\n *\n * Larger than {@link DIATONIC_BONUS} deliberately: key membership may separate\n * two readings that account for the same notes, but it may not buy the\n * discarding of a note that sounded. Were the two the same size, a dominant\n * voiced without its fifth would read as the triad the key happens to contain —\n * C E Bb in C major as `C`, which drops the tritone the chord exists for and\n * asserts a G nobody played.\n */\nconst EXTRA_TONE_PENALTY = 0.6;\n\n/**\n * Score penalty per chord tone the voicing omits.\n *\n * {@link detectChord} admits one absent tone and only the perfect fifth, so this\n * is the cost of a shell voicing rather than of a misreading — small, because\n * dropping the fifth is ordinary practice, and non-zero, so a complete voicing\n * still outranks an incomplete reading of the same notes.\n */\nconst OMITTED_TONE_PENALTY = 0.15;\n\n/**\n * Standing a match's root earns from being the sounding bass, on the 0..1 scale\n * a root's share of the window's weight is measured on.\n *\n * The bass is the strongest single indicator of a root there is, so a candidate\n * it puts in root position is not judged on how loudly that root happens to be\n * sounding: a shell voicing whose seventh is the loudest thing in the window\n * still has its root underneath it, and reading it from the loudest pitch class\n * instead re-roots the chord onto a tone that is merely prominent.\n *\n * Short of 1 because the bass is evidence rather than proof — a pitch class that\n * actually carries the window's weight still outranks a bass that does not,\n * which is what keeps a chord voiced in inversion from being re-rooted onto its\n * own bass note.\n */\nconst BASS_ROOT_STANDING = 0.95;\n\n/**\n * Semitones in an octave, as the boundary between a chord's core tones and the\n * extensions written above them: a ninth is a second an octave up.\n */\nconst OCTAVE = 12;\n\n/**\n * How {@link chordTimelineFromNotes} decides where one chord ends and the next\n * begins.\n *\n * `'dynamic'` searches for the boundaries the notes actually imply, so a bar\n * holding two chords yields two segments and a bar holding one yields one.\n * `'grid'` cuts a segment every `harmonicRhythm` beats regardless of what\n * sounds, which is only right when the harmonic rhythm is known to be fixed.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordSegmentation = 'dynamic' | 'grid';\n\n/**\n * Options controlling {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineOptions = {\n /**\n * Key context held across the whole span. Omit it to have the key searched\n * for over time with {@link keyTimelineFromNotes}, which is what lets a piece\n * that modulates be analysed against the key actually in force.\n *\n * Taken in whatever form the caller holds a key, and kept whole: the region\n * built from it carries the spelling and the scale form it was named with, so\n * an Ab minor stated here is an Ab minor in the result.\n */\n key?: KeyLike;\n /**\n * A single time signature held across the whole span, as sugar for a\n * one-element `meters`; defaults to 4/4. Giving both is an input error.\n *\n * @defaultValue `4/4`\n */\n ts?: MeterLike;\n /**\n * The meter as it changes over the span. Bar lines, downbeats and metric\n * weight all follow the signature in force at the beat in question, so a\n * piece that changes meter is not read in the one it opened in.\n *\n * @defaultValue 4/4 throughout\n */\n meters?: MeterLike;\n /**\n * Length of the pickup in beats, when the piece starts with one.\n *\n * The first downbeat is beat 0 whatever precedes it, so an upbeat is written\n * at negative beats — a one-beat pickup in 4/4 sounds at beat -1. Declaring\n * its length rejects a note that starts before the pickup does; leave it\n * unset to accept any finite onset.\n *\n * @defaultValue no declared pickup\n */\n pickupBeats?: number;\n /**\n * Where chord boundaries may fall; defaults to `'dynamic'`.\n *\n * @defaultValue `'dynamic'`\n */\n segmentation?: ChordSegmentation;\n /**\n * Expected chord length in beats; defaults to the length of the opening bar.\n *\n * Under `'grid'` segmentation this is the exact window length. Under\n * `'dynamic'` it is a prior: the longer a chord is expected to last, the more\n * evidence a change needs before the search will place one.\n *\n * @defaultValue the length of the opening bar\n */\n harmonicRhythm?: number;\n /**\n * Resolution the `'dynamic'` search looks for changes at, in beats; defaults\n * to one main pulse of the meter — a quarter in 4/4, a dotted quarter in 6/8.\n * Lower it to catch changes on off-beats, at the cost of proportionally more\n * work. Values above `harmonicRhythm` are clamped to it, and the option is\n * ignored under `'grid'` segmentation.\n *\n * The search reads it as a resolution rather than as an exact slot length: it\n * rounds down to a whole division of the meter's pulse, so `0.6` in 4/4 is\n * read in half-beats. That keeps every pulse of the meter, and the bar lines\n * with them, on the grid whatever resolution is asked for, and with them every\n * change a coarser setting found. A resolution coarser than one pulse is\n * therefore read as one pulse rather than stepping over the beats a change is\n * heard on.\n *\n * @defaultValue one main pulse of the meter\n */\n minChordBeats?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * The result of {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineResult = {\n /** The inferred timeline, with adjacent identical chords merged. */\n timeline: ChordTimeline;\n /**\n * The key regions the analysis ran against, in time order. A piece that does\n * not modulate yields one region covering the whole span; a given `key`\n * yields exactly one region too, since the caller has already answered the\n * question. Either way the first region starts where the analysis does, which\n * is before beat 0 when the piece opens with a pickup. Pivot chords are filled\n * in where the chords support one.\n */\n keys: KeyRegion[];\n /**\n * The key held longest across {@link ChordTimelineResult.keys} — the one to\n * print on a key signature or hand to a generator that takes a single key.\n */\n prevailingKey: ResolvedKey;\n /** One confidence value in [0, 1] per segment, in segment order. */\n segmentConfidence: number[];\n};\n\n/** A window's inferred chord and its confidence, or null for an empty window. */\ntype WindowChord = {\n chord: Chord;\n confidence: number;\n /**\n * The augmented-sixth reading of the very same tones, when they spell one over\n * the sounding bass.\n *\n * A window cannot tell whether that reading is the one the music supports,\n * because what decides it is where the harmony goes next. So both readings are\n * carried out of the window and {@link resolvesOntoDominant} settles which one\n * the segment reports.\n */\n augmented?: { chord: Chord; confidence: number };\n};\n\n/**\n * Score a chord match against a window's pitch-class weights: a root that\n * carries weight or stands in the bass, in-key tones, and exactness all raise\n * the score; a sounding pitch class the chord does not name and a chord tone the\n * voicing omits both lower it.\n *\n * The two costs are not the same size. What sounded is the evidence, so leaving\n * a pitch class out of the reading costs more than the key can make up, while\n * the absent perfect fifth {@link detectChord} allows costs little — that is\n * what keeps a shell voicing named by the chord it plays rather than by the\n * triad its key prefers.\n */\nfunction scoreMatch(\n match: ChordMatch,\n weights: number[],\n maxWeight: number,\n key: KeyScale,\n pedalPc: number | null,\n): number {\n const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;\n // `inversion === 0` is exactly \"the sounding bass is this candidate's root\".\n // It is null when the window offered no usable bass, so a bass that was\n // filtered out as noise lifts nothing.\n const rootStanding =\n match.inversion === 0 ? Math.max(rootWeight, BASS_ROOT_STANDING) : rootWeight;\n const chord = makeChord(match.rootPc, match.quality);\n const account = matchAccount(match, chord, pedalPc);\n let score = rootStanding;\n if (chordPitchClasses(chord).every((pc) => isScaleTone(pc, key))) {\n score += DIATONIC_BONUS;\n }\n if (account.exact) {\n score += EXACT_BONUS;\n }\n score -= EXTRA_TONE_PENALTY * account.extras;\n score -= OMITTED_TONE_PENALTY * account.missing;\n return score;\n}\n\n/**\n * The qualities a slash chord's upper structure is written in.\n *\n * A complete plain triad over a foreign bass is what the notation exists for —\n * `F/G`, `Dm/G`, `C/D` — and it is how such a bar is written on a chart. An\n * altered or extended upper structure over a foreign bass is not: whatever those\n * tones spell, they are read as a chord of their own rather than as a triad\n * standing on someone else's bass.\n */\nconst SLASH_UPPER_QUALITIES: ReadonlySet<ChordQuality> = new Set<ChordQuality>(['maj', 'min']);\n\n/**\n * Whether a candidate reads the window as a triad standing on a foreign bass.\n *\n * Every tone of the triad sounds, the bass is the one thing outside it, and the\n * bass is heard nowhere above it. That is a slash chord, and the bass is part of\n * the reading rather than a pitch class it failed to name.\n */\nfunction isSlashReading(match: ChordMatch, pedalPc: number | null): boolean {\n return (\n pedalPc !== null &&\n match.inversion === null &&\n match.missingPcs.length === 0 &&\n match.extraPcs.length === 1 &&\n match.extraPcs[0] === pedalPc &&\n SLASH_UPPER_QUALITIES.has(match.quality)\n );\n}\n\n/** How much of a window a candidate accounts for, and what it costs to. */\ntype MatchAccount = {\n /** Sounding pitch classes the candidate does not name. */\n extras: number;\n /** Chord tones nothing voices. */\n missing: number;\n /** Whether the candidate accounts for the window exactly. */\n exact: boolean;\n};\n\n/**\n * Whether a candidate names a pitch class as an upper extension — a tone voiced\n * an octave or more above the root, which is what a ninth, an eleventh or a\n * thirteenth is — rather than as one of its core tones.\n */\nfunction namesAsExtension(chord: Chord, pc: number): boolean {\n return chord.intervals.some((iv) => iv >= OCTAVE && pitchClass(chord.rootPc + iv) === pc);\n}\n\n/**\n * Weigh a candidate against the window with the pedal bass set aside.\n *\n * A ninth, an eleventh or a thirteenth is a tone voiced above the chord. When the\n * only thing sounding that pitch class is the bass, the reading naming it an\n * extension has not explained the bass at all: it is short the tone it claims,\n * and the bass it was supposed to account for is still unaccounted for. Both are\n * charged, so the reading loses to the slash chord that says what the input\n * played — otherwise the two readings of `G2 + F4 A4 C5` are separated by the\n * exactness bonus alone and the ninth nobody played wins every time.\n *\n * The slash reading, for its part, is not charged for the bass at all: it names\n * it, in the only place a chord symbol can. It earns no exactness either, since\n * the chord proper does not contain the bass, so a reading that does contain\n * every sounding tone still outranks it.\n */\nfunction matchAccount(match: ChordMatch, chord: Chord, pedalPc: number | null): MatchAccount {\n const unvoiced = pedalPc !== null && namesAsExtension(chord, pedalPc) ? 1 : 0;\n const named = isSlashReading(match, pedalPc) ? 1 : 0;\n return {\n extras: match.extraPcs.length - named + unvoiced,\n missing: match.missingPcs.length + unvoiced,\n exact: match.exact && unvoiced === 0,\n };\n}\n\n/**\n * Whether the window's bass pitch class is heard only underneath the harmony.\n *\n * The upper structure is everything sounding above the lowest note that is not\n * the bass pitch class itself; a bass doubled at the octave is still a bass. When\n * the pitch class never reaches into that structure, no voice above the chord\n * sounds it, and the extended readings that would name it a ninth or an eleventh\n * have nothing to name.\n */\nfunction bassSoundsOnlyBelow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n bassPc: number,\n): boolean {\n let upperFloor = Number.POSITIVE_INFINITY;\n let highestBass = Number.NEGATIVE_INFINITY;\n for (const note of notes) {\n const overlap =\n Math.min(note.startBeat + note.durationBeat, windowEnd) -\n Math.max(note.startBeat, windowStart);\n if (overlap <= BEAT_EPS) {\n continue;\n }\n if (pitchClass(note.pitch) === bassPc) {\n highestBass = Math.max(highestBass, note.pitch);\n } else {\n upperFloor = Math.min(upperFloor, note.pitch);\n }\n }\n return highestBass <= upperFloor;\n}\n\n/** Confidence of a chord for a window: chord-tone weight over total weight. */\nfunction chordConfidence(\n chord: Chord,\n weights: number[],\n totalWeight: number,\n exact: boolean,\n): number {\n const tones = new Set(chordPitchClasses(chord));\n let chordWeight = 0;\n for (let pc = 0; pc < 12; pc += 1) {\n if (tones.has(pc)) {\n chordWeight += weights[pc] ?? 0;\n }\n }\n const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;\n}\n\n/**\n * Infer the chord sounding in one window from the notes overlapping it.\n *\n * Builds a pitch-class weight histogram (overlap duration x velocity x\n * metric-accent bonus for onsets inside the window), keeps the significantly\n * weighted pitch classes, and picks the best {@link detectChord} match by the\n * standing of its root — the weight it carries, or the bass it stands in — plus\n * key membership, exactness, and how much of the window it accounts for.\n */\nfunction analyzeWindow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n meters: MeterMap,\n key: ResolvedKey,\n): WindowChord | null {\n const { weights, totalWeight, maxWeight, lowestPitch } = windowWeights(\n notes,\n windowStart,\n windowEnd,\n meters,\n );\n if (totalWeight <= BEAT_EPS) {\n return null;\n }\n\n // Keep pitch classes with meaningful weight, strongest first.\n const ranked = weights\n .map((weight, pc) => ({ pc, weight }))\n .filter(({ weight }) => weight > 0)\n .sort((a, b) => b.weight - a.weight || a.pc - b.pc);\n const selected = ranked\n .filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO)\n .slice(0, MAX_DETECTION_PCS)\n .map(({ pc }) => pc);\n\n // Feed detection with the window's true bass as the lowest pitch so inversion\n // detection works. If that pitch class was removed as noise, detection must\n // become unordered instead: promoting the strongest remaining tone to bass\n // manufactures a slash chord that the input never established.\n const bassPc = pitchClass(lowestPitch);\n const hasBass = selected.includes(bassPc);\n const detectionPitches = hasBass\n ? selected.map((pc) => (pc === bassPc ? pc : pc + 12))\n : selected;\n\n // A bass heard nowhere above the harmony is a bass, whatever pitch class it\n // carries, so no reading may spend it twice — once as the slash the input\n // played and once as an extension it did not.\n const pedalPc =\n hasBass && bassSoundsOnlyBelow(notes, windowStart, windowEnd, bassPc) ? bassPc : null;\n\n const matches = detectChord(detectionPitches, { input: hasBass ? 'midi' : 'pitchClass' });\n let bestMatch: ChordMatch | null = null;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const match of matches) {\n const score = scoreMatch(match, weights, maxWeight, scaleOf(key), pedalPc);\n if (score > bestScore) {\n bestScore = score;\n bestMatch = match;\n }\n }\n if (bestMatch) {\n const chord = makeChord(bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);\n const account = matchAccount(\n bestMatch,\n makeChord(bestMatch.rootPc, bestMatch.quality),\n pedalPc,\n );\n const reading: WindowChord = {\n chord: shared(chord),\n confidence: chordConfidence(chord, weights, totalWeight, account.exact),\n };\n // An augmented sixth is a bass and an interval rather than a stack of\n // thirds, so no tertian match can carry one: over the lowered submediant\n // the same pitch classes are read as bVI7, whose seventh is spelled a\n // semitone below where the augmented sixth is. What those tones could spell\n // is worked out here, where the sounding bass and the key are both known;\n // whether they do is not a question this window can answer, so the reading\n // is offered alongside the tertian one rather than taken over it.\n const augmented = hasBass ? augmentedSixthFromPitchClasses(selected, bassPc, key) : null;\n if (augmented !== null) {\n // Its tones are exactly the window's, so it explains them exactly.\n reading.augmented = {\n chord: shared(augmented),\n confidence: chordConfidence(augmented, weights, totalWeight, true),\n };\n }\n return reading;\n }\n\n return null;\n}\n\n/**\n * A chord that more than one analysis holds at once.\n *\n * A window's reading is cached and handed to the next analysis of the same\n * span, so one object reaches both the analysis made before an edit and the one\n * made after it. The earlier analysis is what a host keeps in order to undo, and\n * an undo state that the next edit can rewrite is not a snapshot: renaming a\n * root for display in the current timeline would silently rename it in the one\n * being held. Freezing says so where the object is made, rather than leaving\n * every reader to copy defensively.\n */\nfunction shared(chord: Chord): Chord {\n Object.freeze(chord.intervals);\n return Object.freeze(chord);\n}\n\n/** Semitones from a tonic up to its dominant. */\nconst DOMINANT_ABOVE_TONIC = 7;\n\n/**\n * Whether the augmented-sixth reading offered at `index` is the one the music\n * supports.\n *\n * The family is defined by an outward resolution onto the dominant, so the\n * reading is accepted only where the harmony gets there: straight onto V or V7,\n * or through a dominant preparation already standing on the dominant in the bass\n * — the cadential six-four, which is the dominant arriving under a suspension\n * rather than another chord in the way. Anything else the same tones move to,\n * the tonic above all, is an ordinary bVI7 acting as a backdoor dominant or a\n * chromatic mediant, and its seventh is a minor seventh.\n *\n * A candidate with no harmony sounding after it ends the piece with its\n * resolution still to come, which is silence rather than evidence against it.\n * A rest between the two chords is read the same way, and a window repeating the\n * candidate's own chord is the same harmony held on across a boundary.\n */\nfunction resolvesOntoDominant(\n readings: readonly (WindowChord | null)[],\n index: number,\n key: KeyScale,\n): boolean {\n const dominantPc = pitchClass(key.rootPc + DOMINANT_ABOVE_TONIC);\n const candidate = readings[index]?.chord;\n for (let i = index + 1; i < readings.length; i += 1) {\n const next = readings[i];\n if (!next) {\n continue;\n }\n const chord = next.chord;\n if (candidate !== undefined && sameChord(chord, candidate)) {\n continue;\n }\n if (pitchClass(chord.bassPc ?? chord.rootPc) !== dominantPc) {\n return false;\n }\n if (pitchClass(chord.rootPc) === dominantPc) {\n return true;\n }\n // Standing on the dominant bass without being the dominant is the\n // preparation the dominant arrives through, so the dominant it prepares is\n // what the next window still has to hold.\n }\n return true;\n}\n\n/** Whether two chords are the same root, quality, and bass. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;\n}\n\n/**\n * Chord qualities the boundary search considers.\n *\n * This lexicon only has to be rich enough to tell \"the harmony changed here\"\n * from \"it did not\": the chord a segment finally reports comes from\n * {@link analyzeWindow} over the settled span, which searches the full quality\n * table. Adding rarer qualities here would slow every slot down without\n * changing where the boundaries land.\n */\nconst SEGMENTATION_QUALITIES: readonly ChordQuality[] = [\n 'maj',\n 'min',\n 'dim',\n 'aug',\n 'dom7',\n 'maj7',\n 'min7',\n 'm7b5',\n 'dim7',\n 'sus4',\n '6',\n 'min6',\n];\n\n/**\n * The 12 x {@link SEGMENTATION_QUALITIES} lexicon, built once and held flat.\n *\n * Candidate `c` has root `LEXICON_ROOTS[c]` and sounds the pitch classes in\n * `LEXICON_TONES` from `LEXICON_TONE_START[c]` up to `LEXICON_TONE_START[c + 1]`.\n * The search scores every candidate against every slot of the piece, so the\n * inner loop reads a flat table rather than chasing one object and one array per\n * candidate.\n */\nconst LEXICON_SIZE = 12 * SEGMENTATION_QUALITIES.length;\nconst LEXICON_ROOTS = new Int32Array(LEXICON_SIZE);\nconst LEXICON_TONE_START = new Int32Array(LEXICON_SIZE + 1);\nconst LEXICON_TONES: Int32Array = (() => {\n const tones: number[] = [];\n let candidate = 0;\n for (let rootPc = 0; rootPc < 12; rootPc += 1) {\n for (const quality of SEGMENTATION_QUALITIES) {\n LEXICON_ROOTS[candidate] = rootPc;\n LEXICON_TONE_START[candidate] = tones.length;\n tones.push(...chordPitchClasses(makeChord(rootPc, quality)));\n candidate += 1;\n }\n }\n LEXICON_TONE_START[candidate] = tones.length;\n return Int32Array.from(tones);\n})();\n\n/** Weight of a slot's non-chord tones, relative to its chord tones. */\nconst OUTSIDE_TONE_PENALTY = 1;\n\n/** Penalty per absent chord tone, in units of the candidate's average tone weight. */\nconst MISSING_TONE_PENALTY = 0.5;\n\n/** Extra credit for the candidate's root carrying weight of its own. */\nconst ROOT_PRESENCE_BONUS = 0.3;\n\n/**\n * Cost of one chord change, as a fraction of the evidence an expected-length\n * chord carries. A change has to improve the fit by at least this much to be\n * worth making, which is what keeps passing tones and appoggiaturas from\n * splitting a segment.\n */\nconst CHANGE_COST = 0.35;\n\n/** How much of the change cost a maximally strong beat waives. */\nconst STRONG_BEAT_DISCOUNT = 0.5;\n\n/** The largest value {@link metricWeight} returns (a downbeat). */\nconst MAX_METRIC_WEIGHT = 3;\n\n/**\n * Score how well one lexicon candidate explains a slot's weights.\n *\n * Chord tones earn their weight, everything else sounding costs it, and each\n * chord tone that never sounds costs a share of what the candidate would have\n * earned had it been complete — so a triad is not rewarded for the two thirds\n * of itself that are missing.\n */\nfunction candidateScore(\n candidate: number,\n weights: readonly number[],\n totalWeight: number,\n): number {\n const first = LEXICON_TONE_START[candidate] ?? 0;\n const last = LEXICON_TONE_START[candidate + 1] ?? first;\n let covered = 0;\n let missing = 0;\n for (let index = first; index < last; index += 1) {\n const weight = weights[LEXICON_TONES[index] ?? 0] ?? 0;\n covered += weight;\n if (weight <= BEAT_EPS) {\n missing += 1;\n }\n }\n const outside = totalWeight - covered;\n const averageToneWeight = totalWeight / (last - first);\n return (\n covered -\n OUTSIDE_TONE_PENALTY * outside -\n MISSING_TONE_PENALTY * missing * averageToneWeight +\n ROOT_PRESENCE_BONUS * (weights[LEXICON_ROOTS[candidate] ?? 0] ?? 0)\n );\n}\n\n/** A half-open span of slots the boundary search settled on. */\ntype BoundarySpan = { startBeat: number; endBeat: number };\n\n/**\n * The grid the span is examined in is the shared {@link SlotGrid}: equal slots\n * of `slotBeats`, starting at `origin`. The origin is 0 for a piece that starts\n * on the downbeat, negative for one that starts with a pickup, and the excerpt's\n * own first slot for one lifted from later in a piece; {@link gridForNotes}\n * places it, and the key search cuts its own slots on a grid of the same shape.\n */\n\n/** First beat of slot `index`. */\nfunction beatOfSlot(grid: SlotGrid, index: number): number {\n return grid.origin + index * grid.slotBeats;\n}\n\n/** Index of the slot containing `beat`, unclamped. */\nfunction slotOfBeat(grid: SlotGrid, beat: number): number {\n return (beat - grid.origin) / grid.slotBeats;\n}\n\n/**\n * The tables the boundary search runs on, sized once for the whole piece.\n *\n * Every field is indexed by absolute slot, so a run of slots is searched by\n * bounds rather than by being copied out — and a cell of the recurrence is a\n * pair of numbers in a typed array rather than an object. The score table is\n * also the only part of the search that depends on what the notes are, which is\n * what lets an unchanged slot's row be carried over from a previous analysis.\n */\nexport type BoundaryTables = {\n /** Fit of slot `i` to candidate `c`, at `scores[i * LEXICON_SIZE + c]`. */\n scores: Float64Array;\n /** Chosen predecessor of slot `i`, candidate `c`, at the same index. */\n back: Int32Array;\n /** The two rolling cost rows the recurrence alternates between. */\n costs: Float64Array;\n spare: Float64Array;\n /** The winning candidate of each slot, filled by the backward pass. */\n path: Int32Array;\n};\n\n/**\n * Allocate the boundary-search tables for a piece of `slotCount` slots.\n *\n * @param slotCount Number of slots the search runs over.\n * @param budget Upper bound the tables' size is charged against.\n */\nexport function createBoundaryTables(slotCount: number, budget?: number): BoundaryTables {\n return {\n scores: allocateCandidateTable(slotCount, LEXICON_SIZE, 'timeline window scores', budget),\n back: allocateChoiceTable(slotCount, LEXICON_SIZE, 'timeline window choices', budget),\n costs: allocateCandidateTable(1, LEXICON_SIZE, 'timeline window costs', budget),\n spare: allocateCandidateTable(1, LEXICON_SIZE, 'timeline window costs', budget),\n path: allocateChoiceTable(slotCount, 1, 'timeline window path', budget),\n };\n}\n\n/**\n * Score one slot against the whole lexicon, into its row of the score table.\n *\n * A slot carrying no weight scores zero everywhere, which is what the row\n * already holds, so silence costs nothing to score.\n */\nexport function scoreSlot(tables: BoundaryTables, slot: WindowWeights, index: number): void {\n const base = index * LEXICON_SIZE;\n if (slot.totalWeight <= BEAT_EPS) {\n tables.scores.fill(0, base, base + LEXICON_SIZE);\n return;\n }\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n tables.scores[base + c] = candidateScore(c, slot.weights, slot.totalWeight);\n }\n}\n\n/**\n * Choose chord boundaries over one run of slots by dynamic programming.\n *\n * Every slot is scored against the whole lexicon; the optimal path trades the\n * per-slot fit against a fixed cost per chord change, discounted on strong\n * beats so that a change lands on the bar line rather than a beat either side\n * of it. Because the change cost is the same whichever candidate is left\n * behind, the previous stage collapses to \"stay on this candidate\" versus\n * \"come from the cheapest one\", which keeps the search linear in the lexicon\n * rather than quadratic.\n */\nfunction chooseBoundaries(\n tables: BoundaryTables,\n from: number,\n to: number,\n grid: SlotGrid,\n meters: MeterMap,\n changeCost: number,\n): BoundarySpan[] {\n const width = to - from;\n if (width <= 0) {\n return [];\n }\n const { scores, back, path } = tables;\n // Costs are negated scores, so the search minimises.\n let costs = tables.costs;\n let next = tables.spare;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n costs[c] = -(scores[from * LEXICON_SIZE + c] ?? 0);\n }\n for (let i = 1; i < width; i += 1) {\n let cheapest = Number.POSITIVE_INFINITY;\n let cheapestIndex = 0;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n const cost = costs[c] ?? Number.POSITIVE_INFINITY;\n if (cost < cheapest) {\n cheapest = cost;\n cheapestIndex = c;\n }\n }\n const slotStart = beatOfSlot(grid, from + i);\n const accent = metricWeight(slotStart, meters);\n const penalty = changeCost * (1 - (STRONG_BEAT_DISCOUNT * accent) / MAX_METRIC_WEIGHT);\n const rowBase = (from + i) * LEXICON_SIZE;\n const change = cheapest + penalty;\n for (let c = 0; c < LEXICON_SIZE; c += 1) {\n const stay = costs[c] ?? Number.POSITIVE_INFINITY;\n // Ties hold the current chord: a change has to be strictly better.\n const carried = stay <= change ? stay : change;\n back[rowBase + c] = stay <= change ? c : cheapestIndex;\n next[c] = carried - (scores[rowBase + c] ?? 0);\n }\n const previous = costs;\n costs = next;\n next = previous;\n }\n\n let best = 0;\n for (let c = 1; c < LEXICON_SIZE; c += 1) {\n if ((costs[c] ?? Number.POSITIVE_INFINITY) < (costs[best] ?? Number.POSITIVE_INFINITY)) {\n best = c;\n }\n }\n let current = best;\n for (let i = width - 1; i >= 0; i -= 1) {\n path[from + i] = current;\n if (i > 0) {\n current = back[(from + i) * LEXICON_SIZE + current] ?? current;\n }\n }\n\n const spans: BoundarySpan[] = [];\n let runStart = 0;\n for (let i = 1; i <= width; i += 1) {\n if (i === width || path[from + i] !== path[from + runStart]) {\n spans.push({\n startBeat: beatOfSlot(grid, from + runStart),\n endBeat: beatOfSlot(grid, from + i),\n });\n runStart = i;\n }\n }\n return spans;\n}\n\n/**\n * The slot length a requested resolution is read at: a whole division of the\n * meter's own grid, never coarser than the caller asked for.\n *\n * A change is discounted on strong beats, and the strongest of them are the main\n * pulses of the meter. A grid that steps over those beats never earns the\n * discount, so the search reports no change anywhere and a run of alternating\n * harmony comes back as one segment naming a chord that never sounded — which is\n * what a grid counted in quarters does to a compound meter, where the pulse is a\n * dotted quarter and the quarters fall between the beats a player feels.\n * Dividing the pulse keeps every pulse in the span, and the bar lines with them,\n * on the grid at every resolution: that is what makes the resolution a knob\n * rather than a trap, since every setting finds the changes the coarser settings\n * found.\n *\n * @param gridUnit The meter's grid step in beats, from {@link metricGridUnit}.\n * @param minChordBeats Shortest chord the caller will accept, in beats.\n * @param harmonicRhythm Expected chord length in beats; a resolution coarser\n * than one expected chord is read as that chord, since a search cannot report\n * a boundary it never looks for.\n * @returns The slot length, which divides the meter's grid step exactly.\n */\nfunction slotBeatsWithin(gridUnit: number, minChordBeats: number, harmonicRhythm: number): number {\n const resolution = Math.min(minChordBeats, harmonicRhythm);\n const divisions = Math.max(1, Math.ceil(gridUnit / resolution - BEAT_EPS));\n return gridUnit / divisions;\n}\n\n/** One span per slot: the fixed grid, unchanged. */\nfunction gridSpans(slotCount: number, grid: SlotGrid): BoundarySpan[] {\n return Array.from({ length: slotCount }, (_, i) => ({\n startBeat: beatOfSlot(grid, i),\n endBeat: beatOfSlot(grid, i + 1),\n }));\n}\n\n/**\n * Place chord boundaries where the notes imply them.\n *\n * Silence long enough to be a chord of its own ends the run: nothing sounds\n * across it, so nothing may be merged across it either, and `at(beat)` answers\n * null there. A shorter rest is absorbed — a chord is not over because the\n * players took a breath.\n */\nfunction dynamicSpans(\n slotWeights: readonly WindowWeights[],\n tables: BoundaryTables,\n grid: SlotGrid,\n harmonicRhythm: number,\n meters: MeterMap,\n): BoundarySpan[] {\n const slotBeats = grid.slotBeats;\n const slotCount = slotWeights.length;\n let soundingSlots = 0;\n let weightSum = 0;\n for (let i = 0; i < slotCount; i += 1) {\n const weight = slotWeights[i]?.totalWeight ?? 0;\n if (weight > BEAT_EPS) {\n soundingSlots += 1;\n weightSum += weight;\n }\n }\n if (soundingSlots === 0) {\n return [];\n }\n // The change cost is expressed in the same weight units the slot scores are,\n // scaled to one expected chord's worth of evidence: a change must be worth\n // that much of the fit before the search will make it, whatever the absolute\n // loudness or note lengths of the piece happen to be.\n //\n // A slot's weight is proportional to its length, so the mean cancels the\n // slots-per-chord factor and the cost depends on `harmonicRhythm` alone. That\n // is what makes `minChordBeats` a resolution knob rather than a sensitivity\n // one: asking for finer slots finds a boundary in more places, but does not\n // change how much evidence placing one takes.\n const meanSlotWeight = weightSum / soundingSlots;\n const changeCost = CHANGE_COST * meanSlotWeight * (harmonicRhythm / slotBeats);\n\n const silent = slotWeights.map((slot) => slot.totalWeight <= BEAT_EPS);\n const breaks = new Array<boolean>(slotCount).fill(false);\n for (let i = 0; i < slotCount; ) {\n if (!silent[i]) {\n i += 1;\n continue;\n }\n let end = i;\n while (end < slotCount && silent[end]) {\n end += 1;\n }\n if ((end - i) * slotBeats >= harmonicRhythm - BEAT_EPS) {\n for (let k = i; k < end; k += 1) {\n breaks[k] = true;\n }\n }\n i = end;\n }\n\n const spans: BoundarySpan[] = [];\n for (let i = 0; i < slotCount; ) {\n if (breaks[i]) {\n i += 1;\n continue;\n }\n let end = i;\n while (end < slotCount && !breaks[end]) {\n end += 1;\n }\n spans.push(...chooseBoundaries(tables, i, end, grid, meters, changeCost));\n i = end;\n }\n return spans;\n}\n\n/** The distinct notes overlapping a span, gathered from the slots it covers. */\nfunction notesOfSpan(\n slotNotes: readonly NoteEvent[][],\n span: BoundarySpan,\n grid: SlotGrid,\n): NoteEvent[] {\n const first = Math.round(slotOfBeat(grid, span.startBeat));\n const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));\n if (lastExclusive - first === 1) {\n return [...(slotNotes[first] ?? [])];\n }\n // A note held across several slots appears in each of them; the histogram\n // weighs by overlap, so counting it twice would double its influence.\n const seen = new Set<NoteEvent>();\n for (let i = first; i < lastExclusive; i += 1) {\n for (const note of slotNotes[i] ?? []) {\n seen.add(note);\n }\n }\n return [...seen];\n}\n\n/**\n * Infer a chord timeline from raw multi-track notes.\n *\n * Chord boundaries are searched for rather than assumed: the span is examined\n * in slots of one main pulse, subdivided as finely as `minChordBeats` asks for,\n * and the change points that best explain the notes\n * are chosen, trading each slot's harmonic fit against a cost per chord change\n * that a strong beat discounts. A bar holding two chords therefore yields two\n * segments and a bar holding one yields one, without the caller having to know\n * the harmonic rhythm in advance. Pass `segmentation: 'grid'` to cut a segment\n * every `harmonicRhythm` beats instead.\n *\n * Each settled segment's chord is inferred from a pitch-class weight histogram\n * of the notes overlapping it (weight = overlap duration x velocity x\n * metric-accent bonus for onsets in the segment). Adjacent segments carrying\n * the identical chord are merged; a stretch with no notes produces no segment,\n * so `at(beat)` returns null there. Each segment carries a confidence in [0, 1]\n * — the fraction of its weight explained by chord tones, reduced when the match\n * is inexact, and duration-weighted across anything merged into it.\n *\n * Notes with a zero or negative duration never sound, so they are dropped at\n * ingest: they contribute to neither the key inference, the span, nor any\n * window's histogram.\n *\n * Bar lines, downbeats and the metric accents the boundary search discounts by\n * all follow the signature in force at the beat in question, so a piece that\n * changes meter is read in the meter it is actually in. The first downbeat is\n * beat 0, so a pickup is written at negative beats and the search still finds\n * the bar lines where the score has them.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link ChordTimelineOptions}.\n * @returns The inferred timeline, the key regions it ran against, the key that\n * prevails across them, and per-segment confidences.\n * @throws If `harmonicRhythm` is not positive.\n * @example\n * ```ts\n * import { chordTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C\n * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E\n * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G\n * ];\n * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);\n * timeline.at(0); // the chord inferred over beat 0, or null\n * prevailingKey; // the key held longest across the piece\n * ```\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromNotes(\n notes: readonly NoteEvent[],\n opts: ChordTimelineOptions = {},\n): ChordTimelineResult {\n return analyzeTimeline(notes, opts).result;\n}\n\n/**\n * The slot-level evidence one chord-timeline analysis drew from the notes.\n *\n * Everything here is a pure function of the notes overlapping one slot, so a\n * slot the edit cannot reach carries over to the next analysis unchanged. The\n * grid itself is recorded alongside, because a carried-over row only means the\n * same thing if the slot it indexes still covers the same beats.\n *\n * Not part of the public surface: it is the private state of an incremental\n * re-analysis, and a caller holding it could pair it with notes it was never\n * derived from.\n */\nexport type TimelineEvidence = {\n /** The grid the slots were cut on. */\n grid: SlotGrid;\n slotCount: number;\n totalBeats: number;\n segmentation: ChordSegmentation;\n harmonicRhythm: number;\n /** Whether the key was given rather than searched for. */\n keyGiven: boolean;\n slotWeights: WindowWeights[];\n /**\n * The score table the analysis ran on, or undefined once a later analysis has\n * taken it over.\n *\n * Only one re-analysis may carry a given table forward: the carrying pass\n * writes the edited slots straight into it rather than reallocating a table\n * the size of the piece per edit, which leaves the evidence it came from with\n * rows that no longer describe its own notes. An evidence whose table has\n * moved on therefore lends nothing, and an analysis carrying it re-derives\n * every slot — the same answer, over the work a first analysis does.\n */\n tables: BoundaryTables | undefined;\n /**\n * The chord inferred over each settled span, keyed by the span's bounds and\n * the key it was read in — the two things besides the span's notes that\n * {@link analyzeWindow} depends on.\n */\n windows: Map<string, WindowChord | null>;\n};\n\n/** A half-open run of slots whose contents an edit may have changed. */\nexport type DirtySlots = { from: number; to: number };\n\n/**\n * How many slots either side of an edit are treated as dirty on top of the\n * slots it actually overlaps.\n *\n * The evidence itself needs none: a slot's histogram sees exactly the notes\n * overlapping it, so an edit confined to `[startBeat, endBeat)` cannot change a\n * slot it does not touch. The extra slot is there so that an onset landing a\n * floating-point hair either side of a slot boundary cannot leave a stale row\n * behind, which would be wrong output rather than merely slow output.\n */\nconst DIRTY_SLOT_MARGIN = 1;\n\n/**\n * The slots an edit over `[startBeat, endBeat)` may have changed.\n *\n * @param evidence Evidence from the analysis being updated, whose grid the\n * slots are numbered on.\n * @param startBeat First beat the edit touched.\n * @param endBeat End of the beats the edit touched, exclusive.\n * @returns The half-open run of slots to recompute.\n */\nexport function dirtySlotsFor(\n evidence: TimelineEvidence,\n startBeat: number,\n endBeat: number,\n): DirtySlots {\n const first = Math.floor(slotOfBeat(evidence.grid, startBeat)) - DIRTY_SLOT_MARGIN;\n const lastExclusive = Math.ceil(slotOfBeat(evidence.grid, endBeat)) + DIRTY_SLOT_MARGIN;\n return {\n from: Math.max(0, Math.min(evidence.slotCount, first)),\n to: Math.max(0, Math.min(evidence.slotCount, lastExclusive)),\n };\n}\n\n/** Whether every slot a span covers lies outside the dirty run. */\nfunction spanIsClean(\n grid: SlotGrid,\n span: BoundarySpan,\n dirtyFrom: number,\n dirtyTo: number,\n): boolean {\n const first = Math.round(slotOfBeat(grid, span.startBeat));\n const lastExclusive = Math.round(slotOfBeat(grid, span.endBeat));\n return lastExclusive <= dirtyFrom || first >= dirtyTo;\n}\n\n/** Identity of a key, for keying the per-span chord cache. */\nfunction keyId(key: KeyScale): string {\n return `${key.rootPc}:${key.modeMask12}`;\n}\n\n/** Cache key of one settled span read in one key. */\nfunction windowId(startBeat: number, endBeat: number, key: KeyScale): string {\n return `${startBeat}|${endBeat}|${keyId(key)}`;\n}\n\n/**\n * Whether previous evidence describes the same grid as this analysis, and so\n * may have rows carried over from it. A different grid means slot `i` no longer\n * covers the beats it did, which would silently pair a row with the wrong slot.\n */\nfunction evidenceFits(\n previous: TimelineEvidence,\n grid: SlotGrid,\n slotCount: number,\n totalBeats: number,\n segmentation: ChordSegmentation,\n harmonicRhythm: number,\n keyGiven: boolean,\n): boolean {\n return (\n previous.grid.origin === grid.origin &&\n previous.grid.slotBeats === grid.slotBeats &&\n previous.slotCount === slotCount &&\n previous.totalBeats === totalBeats &&\n previous.segmentation === segmentation &&\n previous.harmonicRhythm === harmonicRhythm &&\n previous.keyGiven === keyGiven\n );\n}\n\n/**\n * Infer a chord timeline, carrying over every slot the edit cannot reach.\n *\n * The dependency margin is worked out per stage rather than guessed at once:\n *\n * - A slot's histogram sees exactly the notes overlapping it, so the slots\n * whose evidence an edit can change are the slots overlapping the edited\n * beats — margin zero, rounded outward to whole slots.\n * - The boundary search has no finite margin inside a run. It is a Viterbi over\n * the run's slots, so the winning path at any slot depends on every other\n * slot of the run; and its change cost is calibrated on the mean weight of\n * every sounding slot in the piece, which couples the runs to each other.\n * The recurrence is therefore re-run over the whole piece — pure arithmetic\n * over the carried-over score table, with no per-cell allocation.\n * - A settled span's chord depends only on the notes overlapping it, so it is\n * carried over when the span's bounds, its key, and its slots are all\n * unchanged. Bounds may move anywhere within a run, so which spans qualify is\n * read off the freshly chosen boundaries rather than assumed.\n *\n * @param notes The notes to analyze.\n * @param opts Analysis options.\n * @param previous Evidence from the analysis being updated, when there is one.\n * @param dirty The slots the edit may have changed; every slot when omitted.\n * @returns The analysis and the evidence behind it.\n */\nexport function analyzeTimeline(\n notes: readonly NoteEvent[],\n opts: ChordTimelineOptions = {},\n previous?: TimelineEvidence,\n dirty?: DirtySlots,\n): { result: ChordTimelineResult; evidence: TimelineEvidence } {\n const meters = resolveMeters(opts, 'timeline meters');\n const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBarAt(0, meters);\n assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'harmonic rhythm');\n const segmentation = opts.segmentation ?? 'dynamic';\n // One pulse by default, whatever the pulse of this meter is: a chord is not\n // expected to change faster than the meter is felt, and a resolution written\n // in quarters would step over the pulses of every compound meter.\n const gridUnit = metricGridUnit(meters);\n const minChordBeats = opts.minChordBeats ?? gridUnit;\n assertRange(minChordBeats, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'minChordBeats');\n const budget = opts.budget;\n const noteOptions: NoteEventAssertOptions = { allowNonPositiveDuration: true, budget };\n if (opts.pickupBeats !== undefined) {\n assertRange(opts.pickupBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline pickupBeats');\n noteOptions.minStartBeat = -opts.pickupBeats;\n }\n assertNoteEvents(notes, 'timeline notes', noteOptions);\n // Zero/negative-length notes never sound; drop them before any inference.\n // What the passes below read is the sequence in onset order, so that is what\n // is built: none of them asks which note sounds at a beat, and an index would\n // charge every analysis for lookups it never makes.\n const sounding = sortedNoteEvents(notes.filter((note) => note.durationBeat > 0));\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n const slotBeats =\n segmentation === 'grid'\n ? harmonicRhythm\n : slotBeatsWithin(gridUnit, minChordBeats, harmonicRhythm);\n const { origin, startBeat: musicStart } = gridForNotes(sounding, slotBeats);\n const grid: SlotGrid = { origin, slotBeats };\n const slotCount = Math.max(0, Math.ceil((totalBeats - grid.origin) / slotBeats - BEAT_EPS));\n const dynamic = segmentation === 'dynamic';\n // The boundary search allocates and fills one lexicon row per slot, so what\n // the budget has to bound is the table about to be built rather than the slot\n // count alone: a slot count inside the budget stands for a table the whole\n // lexicon wider. A grid cuts its segments from the grid itself and builds no\n // table, so it is charged for its slots and nothing more. This is asked before\n // the key search rather than after it, so a request too large to be answered\n // is refused before the passes it pays for begin.\n assertGenerationBudget(\n dynamic ? slotCount * LEXICON_SIZE : slotCount,\n 'timeline windows',\n budget,\n );\n // A given key is taken as read across the whole span: the caller has already\n // answered the question, and second-guessing it would make the option mean\n // \"a hint\" when it reads as an instruction. Its one region starts where the\n // music does rather than at beat 0, so a piece opening with a pickup is\n // covered from its first note however the key was arrived at — and the two\n // paths name the same beat, since the search reads its origin from the same\n // place. Otherwise the key is searched for over time, so a piece that\n // modulates is not analysed against the wrong key for every bar after it does.\n const keys: KeyRegion[] =\n opts.key !== undefined\n ? [\n {\n startBeat: musicStart,\n endBeat: totalBeats,\n key: resolveKey(opts.key),\n confidence: 1,\n },\n ]\n : keyTimelineFromNotes(sounding, { meters, totalBeats, budget });\n const prevailingKey = prevailingKeyOf(keys) ?? resolveKey(majorKey(0));\n const keyAt = keyLookup(keys, prevailingKey);\n // The pitch classes of the key in force. A window's chord is chosen by how\n // its notes score against those, and the cache is keyed on them; the\n // spelling the key carries reaches the caller through the regions, which\n // hold it whole.\n const scaleAt = (beat: number) => scaleOf(keyAt(beat));\n\n const segments: ChordSegment[] = [];\n const segmentConfidence: number[] = [];\n const slotNotes = bucketNotesBySlot(sounding, grid, slotCount, {\n name: 'timeline note-to-window memberships',\n budget,\n });\n\n const carried =\n previous !== undefined &&\n dirty !== undefined &&\n previous.tables !== undefined &&\n evidenceFits(\n previous,\n grid,\n slotCount,\n totalBeats,\n segmentation,\n harmonicRhythm,\n opts.key !== undefined,\n )\n ? previous\n : undefined;\n const dirtyFrom = carried === undefined ? 0 : Math.max(0, dirty?.from ?? 0);\n const dirtyTo = carried === undefined ? slotCount : Math.min(slotCount, dirty?.to ?? slotCount);\n\n // A grid cuts its segments from the grid alone, so it consults no candidate\n // and needs no row per slot: an empty table keeps the evidence's shape without\n // the megabytes a search would have filled. A carried-over table is taken over\n // rather than copied — the rows the edit cannot reach are already what this\n // analysis would write into them, and reallocating the table would cost one\n // the size of the piece per edit.\n const tables = carried?.tables ?? createBoundaryTables(dynamic ? slotCount : 0, budget);\n if (carried !== undefined) {\n // The evidence lending the table gives it up: the edited rows are about to\n // say what these notes say rather than what its own did.\n carried.tables = undefined;\n }\n const slotWeights: WindowWeights[] = new Array<WindowWeights>(slotCount);\n for (let i = 0; i < slotCount; i += 1) {\n const reusable = i < dirtyFrom || i >= dirtyTo ? carried?.slotWeights[i] : undefined;\n if (reusable !== undefined) {\n slotWeights[i] = reusable;\n continue;\n }\n const start = beatOfSlot(grid, i);\n const weights = windowWeights(\n slotNotes[i] ?? [],\n start,\n Math.min(start + slotBeats, totalBeats),\n meters,\n );\n slotWeights[i] = weights;\n if (dynamic) {\n scoreSlot(tables, weights, i);\n }\n }\n\n const spans =\n segmentation === 'grid'\n ? gridSpans(slotCount, grid)\n : dynamicSpans(slotWeights, tables, grid, harmonicRhythm, meters);\n\n const windows = new Map<string, WindowChord | null>();\n // The chord of every settled span, read before any of them is reported: an\n // augmented sixth is told from the bVI7 it sounds like by where the harmony\n // goes next, which is a question about the span after this one.\n const readings: (WindowChord | null)[] = [];\n const readingKeys: KeyScale[] = [];\n const readingBounds: { start: number; end: number }[] = [];\n for (const span of spans) {\n // The slot before the pickup is analyzed — its notes are the pickup's — but\n // the stretch of it that precedes the first note holds nothing, so the\n // segment is reported from where the music starts, as the last one is\n // reported to where it ends.\n const start = Math.max(span.startBeat, musicStart);\n const end = Math.min(span.endBeat, totalBeats);\n const spanKey = scaleAt(start);\n // The window is analysed against the key whole. Its pitch classes decide\n // which chord the notes are read as, but an augmented sixth is spelled\n // rather than scored, and a key reduced on the way in would spell one from\n // whichever side of the circle those pitch classes read best as: the Ab\n // minor the caller named would hand back the sharps of a G# minor.\n const spanKeyWhole = keyAt(start);\n const id = windowId(start, end, spanKey);\n // A span whose slots are all clean holds exactly the notes it held before,\n // so its chord and confidence are the same numbers over the same input.\n const cached =\n carried !== undefined && spanIsClean(grid, span, dirtyFrom, dirtyTo)\n ? carried.windows.get(id)\n : undefined;\n const inferred =\n cached !== undefined\n ? cached\n : analyzeWindow(notesOfSpan(slotNotes, span, grid), start, end, meters, spanKeyWhole);\n // What is cached is the window's own reading, both spellings and all: an\n // edit elsewhere can change which of them the music supports without\n // changing a note this span holds.\n windows.set(id, inferred);\n readings.push(inferred);\n readingKeys.push(spanKey);\n readingBounds.push({ start, end });\n }\n\n // Settle the augmented sixths against what follows them, in the key each was\n // read in — the resolution belongs to the key the chord stands in.\n for (let i = 0; i < readings.length; i += 1) {\n const offered = readings[i]?.augmented;\n const spanKey = readingKeys[i];\n if (\n offered !== undefined &&\n spanKey !== undefined &&\n resolvesOntoDominant(readings, i, spanKey)\n ) {\n readings[i] = offered;\n }\n }\n\n for (let i = 0; i < readings.length; i += 1) {\n const inferred = readings[i];\n const bounds = readingBounds[i];\n if (!inferred || bounds === undefined) {\n continue;\n }\n const { start, end } = bounds;\n const last = segments[segments.length - 1];\n if (\n last &&\n sameChord(last.chord, inferred.chord) &&\n Math.abs(last.endBeat - start) < BEAT_EPS\n ) {\n // Merge into the previous segment, blending confidence by duration.\n const lastLength = last.endBeat - last.startBeat;\n const length = end - start;\n const lastConfidence = segmentConfidence[segmentConfidence.length - 1] ?? 0;\n segmentConfidence[segmentConfidence.length - 1] =\n (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);\n last.endBeat = end;\n } else {\n segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });\n segmentConfidence.push(inferred.confidence);\n }\n }\n\n return {\n result: {\n timeline: { at: segmentLookup(segments), segments },\n keys: attachPivots(keys, segments),\n prevailingKey,\n segmentConfidence,\n },\n evidence: {\n grid,\n slotCount,\n totalBeats,\n segmentation,\n harmonicRhythm,\n keyGiven: opts.key !== undefined,\n slotWeights,\n tables,\n windows,\n },\n };\n}\n\n/**\n * A cadence found between two consecutive timeline segments.\n *\n * @category Arrangement & Analysis\n */\nexport type CadenceHit = {\n /** The beat where the cadence arrives (the second chord's onset). */\n atBeat: number;\n /**\n * The cadence formed by the pair. Its `type` is never null: a pair forming no\n * cadence is not a hit. A timeline names no voicing, so `strength` is null for\n * an authentic cadence whose chords are both in root position.\n */\n cadence: CadenceResult;\n from: Chord;\n to: Chord;\n};\n\n/**\n * Find cadences between consecutive segments of a chord timeline.\n *\n * Each temporally adjacent segment pair is classified with\n * {@link detectCadence}; pairs forming no cadence are skipped. Segments\n * separated by a gap (a rest in the timeline) are not a chord-to-chord\n * progression, so they are never paired.\n *\n * The segment before the pair is passed as the approach, which is what tells a\n * cadential six-four from an inverted tonic: `I I64 V I` is one cadence\n * arriving on the final tonic, described from the six-four the dominant was\n * already sounding under, rather than a half cadence onto the dominant\n * followed by an authentic one. A passing six-four — one whose dominant is not\n * what follows it — is unaffected, and so is a pair whose predecessor is\n * separated from it by a rest.\n *\n * @param timeline The chord timeline to scan.\n * @param key The prevailing key, or the key in force at a given beat.\n * @returns The cadences found, in time order.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant\n * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic\n * ];\n * const { timeline, prevailingKey } = chordTimelineFromNotes(notes);\n * const cadences = detectCadences(timeline, prevailingKey);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectCadences(timeline: ChordTimeline, key: KeyContext): CadenceHit[] {\n assertChordTimeline(timeline);\n // A cadence belongs to the key it arrives in, so a modulating piece is asked\n // for the key at the arrival beat rather than for one key for the whole span.\n const keyAt = keyScaleAt(key);\n const hits: CadenceHit[] = [];\n for (let i = 1; i < timeline.segments.length; i += 1) {\n const prev = timeline.segments[i - 1];\n const cur = timeline.segments[i];\n if (!prev || !cur) {\n continue;\n }\n if (Math.abs(cur.startBeat - prev.endBeat) > BEAT_EPS) {\n continue; // A rest separates the chords; no cadential motion across it.\n }\n // The chord before the pair, when it sounded straight into it: a six-four\n // across a rest was not still sounding when the dominant arrived.\n const before = timeline.segments[i - 2];\n const approach =\n before !== undefined && Math.abs(prev.startBeat - before.endBeat) <= BEAT_EPS\n ? before.chord\n : undefined;\n const key = keyAt(cur.startBeat);\n const cadence = detectCadence(prev.chord, cur.chord, key, {\n ...(approach === undefined ? {} : { approach }),\n });\n if (cadence.type === null) {\n continue;\n }\n // A cadential six-four comes to rest on the dominant it stands on, which is\n // the half cadence that closes an antecedent phrase. Where that dominant\n // goes on to resolve, the six-four opened the cadence the resolution names,\n // so the pair is reported once, at the arrival — the reading the approach\n // chord already gives that resolution.\n if (cadence.type === 'half' && isCadentialSixFour(prev.chord, cur.chord, key)) {\n const next = timeline.segments[i + 1];\n if (next !== undefined && Math.abs(next.startBeat - cur.endBeat) <= BEAT_EPS) {\n const onward = detectCadence(cur.chord, next.chord, keyAt(next.startBeat)).type;\n if (onward === 'authentic' || onward === 'deceptive') {\n continue;\n }\n }\n }\n hits.push({ atBeat: cur.startBeat, cadence, from: prev.chord, to: cur.chord });\n }\n return hits;\n}\n"],"mappings":"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","names":["bassPcOf","quality","degreeNumber","best","chordAtBeat","i","STRONG_BEAT_DISCOUNT","MAX_METRIC_WEIGHT","key"]}