@libraz/libcantus 1.0.1 → 1.2.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.
- package/README.md +27 -65
- package/README_ja.md +28 -66
- package/dist/analyze/index.cjs +11 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +79 -144
- package/dist/analyze/index.d.ts +79 -144
- package/dist/analyze/index.js +20 -16
- package/dist/chunk-35NBV2FT.cjs +145 -0
- package/dist/chunk-35NBV2FT.cjs.map +1 -0
- package/dist/{chunk-UADYAYAT.cjs → chunk-3R3QVKY5.cjs} +1353 -580
- package/dist/chunk-3R3QVKY5.cjs.map +1 -0
- package/dist/{chunk-WWURE4XT.cjs → chunk-76ZBTRG2.cjs} +624 -729
- package/dist/chunk-76ZBTRG2.cjs.map +1 -0
- package/dist/{chunk-FVSI36UV.js → chunk-7KG3RTYS.js} +197 -110
- package/dist/chunk-7KG3RTYS.js.map +1 -0
- package/dist/{chunk-VTI6FOLB.js → chunk-AGOVW7U6.js} +709 -133
- package/dist/chunk-AGOVW7U6.js.map +1 -0
- package/dist/{chunk-E3F4AHAH.cjs → chunk-CGBADIO5.cjs} +221 -134
- package/dist/chunk-CGBADIO5.cjs.map +1 -0
- package/dist/{chunk-6XH6QZYQ.cjs → chunk-DSMJRXCZ.cjs} +1455 -849
- package/dist/chunk-DSMJRXCZ.cjs.map +1 -0
- package/dist/{chunk-PKFNUHDY.js → chunk-FBH75DAY.js} +1218 -612
- package/dist/chunk-FBH75DAY.js.map +1 -0
- package/dist/chunk-H2KJHBW4.js +3329 -0
- package/dist/chunk-H2KJHBW4.js.map +1 -0
- package/dist/chunk-HPVSH7SF.cjs +2294 -0
- package/dist/chunk-HPVSH7SF.cjs.map +1 -0
- package/dist/{chunk-MXO72PLY.cjs → chunk-HWHGOVO6.cjs} +21 -19
- package/dist/chunk-HWHGOVO6.cjs.map +1 -0
- package/dist/chunk-HYJJ2V2I.js +2294 -0
- package/dist/chunk-HYJJ2V2I.js.map +1 -0
- package/dist/{chunk-AFM2ULJH.cjs → chunk-J7FHVZII.cjs} +1567 -1067
- package/dist/chunk-J7FHVZII.cjs.map +1 -0
- package/dist/chunk-K6OZXIR6.js +1 -0
- package/dist/{chunk-H3VORETI.js → chunk-M5RYMAFI.js} +487 -592
- package/dist/chunk-M5RYMAFI.js.map +1 -0
- package/dist/chunk-NB25P3EH.js +363 -0
- package/dist/chunk-NB25P3EH.js.map +1 -0
- package/dist/{chunk-TTCLJX6D.js → chunk-NFJ4P6ZZ.js} +10 -8
- package/dist/chunk-NFJ4P6ZZ.js.map +1 -0
- package/dist/chunk-NM7UAMC3.cjs +457 -0
- package/dist/chunk-NM7UAMC3.cjs.map +1 -0
- package/dist/chunk-PM4XJE2Y.cjs +363 -0
- package/dist/chunk-PM4XJE2Y.cjs.map +1 -0
- package/dist/chunk-PUKJP2Z5.js +145 -0
- package/dist/chunk-PUKJP2Z5.js.map +1 -0
- package/dist/{chunk-VOHZB45S.js → chunk-Q6LFSAKV.js} +1321 -821
- package/dist/chunk-Q6LFSAKV.js.map +1 -0
- package/dist/{chunk-INHVXP6Z.js → chunk-RFIBF2RR.js} +1203 -430
- package/dist/chunk-RFIBF2RR.js.map +1 -0
- package/dist/{chunk-Q2WGKJNN.cjs → chunk-S5BKZZGM.cjs} +699 -123
- package/dist/chunk-S5BKZZGM.cjs.map +1 -0
- package/dist/chunk-SGLEQVJH.cjs +3329 -0
- package/dist/chunk-SGLEQVJH.cjs.map +1 -0
- package/dist/chunk-UGLFDG7L.cjs +1 -0
- package/dist/chunk-UGLFDG7L.cjs.map +1 -0
- package/dist/{chunk-YH3BIKHH.js → chunk-UMWZ4ZET.js} +146 -49
- package/dist/chunk-UMWZ4ZET.js.map +1 -0
- package/dist/{index-szBv3496.d.ts → coerce-DP0vYu-x.d.ts} +286 -199
- package/dist/{index-C_8o--VE.d.cts → coerce-DRERzdve.d.cts} +286 -199
- package/dist/core/index.cjs +9 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +122 -108
- package/dist/core/index.d.ts +122 -108
- package/dist/core/index.js +32 -28
- package/dist/generate/index.cjs +8 -8
- package/dist/generate/index.d.cts +168 -84
- package/dist/generate/index.d.ts +168 -84
- package/dist/generate/index.js +7 -7
- package/dist/{index-CGpRv5sl.d.cts → index-9eoQs2C1.d.ts} +34 -19
- package/dist/{index-CCNJRG-P.d.cts → index-B9QLziDe.d.cts} +293 -56
- package/dist/{index-Dzr13q3d.d.cts → index-BBWNZS0f.d.cts} +135 -58
- package/dist/{index-9wcMhG73.d.ts → index-BHIUyjhZ.d.ts} +293 -56
- package/dist/{index-BGFW-8P2.d.cts → index-BJgP97N1.d.ts} +196 -114
- package/dist/{index-CunYKLiu.d.ts → index-BL5N2Oyz.d.cts} +1 -1
- package/dist/{index-Cj49F892.d.ts → index-BMweUlm5.d.cts} +63 -23
- package/dist/{index-CgHqxdps.d.ts → index-BNSM-DSk.d.cts} +196 -114
- package/dist/{index-D57QRbVn.d.ts → index-BRMbPkHE.d.ts} +480 -254
- package/dist/{index-Chbst3Jx.d.ts → index-CJBBiAmU.d.ts} +135 -58
- package/dist/{index-BkGmlyI3.d.cts → index-CL4b4gUQ.d.ts} +63 -23
- package/dist/{index-B9MNZboQ.d.ts → index-Cy15oTvh.d.cts} +52 -22
- package/dist/{index-B9MNZboQ.d.cts → index-Cy15oTvh.d.ts} +52 -22
- package/dist/{index-BO6xE9YU.d.cts → index-DGCNKAUr.d.cts} +480 -254
- package/dist/{index-B4o55NDr.d.cts → index-Dw2DP7vm.d.ts} +1 -1
- package/dist/{index-G1uwGGWQ.d.ts → index-cHvbYgJI.d.cts} +34 -19
- package/dist/index.cjs +42 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +82 -54
- package/dist/{masks-DYbE485J.d.cts → masks-Cc7-MzzY.d.ts} +49 -2
- package/dist/{masks-DYbE485J.d.ts → masks-pADfTOiD.d.cts} +49 -2
- package/dist/model/index.cjs +19 -12
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +504 -182
- package/dist/model/index.d.ts +504 -182
- package/dist/model/index.js +18 -11
- package/dist/{positional-Cptlj-xs.d.ts → positional-DJgs6ltD.d.ts} +85 -8
- package/dist/{positional-DvH_sd8u.d.cts → positional-DMZer-FM.d.cts} +85 -8
- package/dist/theory/index.cjs +35 -6
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +257 -62
- package/dist/theory/index.d.ts +257 -62
- package/dist/theory/index.js +41 -12
- package/package.json +7 -5
- package/dist/chunk-2LCPM3EG.js +0 -3224
- package/dist/chunk-2LCPM3EG.js.map +0 -1
- package/dist/chunk-6XH6QZYQ.cjs.map +0 -1
- package/dist/chunk-6XPTBE7Y.cjs +0 -49
- package/dist/chunk-6XPTBE7Y.cjs.map +0 -1
- package/dist/chunk-AFM2ULJH.cjs.map +0 -1
- package/dist/chunk-B6XNKWZV.cjs +0 -3224
- package/dist/chunk-B6XNKWZV.cjs.map +0 -1
- package/dist/chunk-E3F4AHAH.cjs.map +0 -1
- package/dist/chunk-ELCTZUJJ.js +0 -379
- package/dist/chunk-ELCTZUJJ.js.map +0 -1
- package/dist/chunk-FVSI36UV.js.map +0 -1
- package/dist/chunk-H3VORETI.js.map +0 -1
- package/dist/chunk-H42UHPYW.js +0 -1577
- package/dist/chunk-H42UHPYW.js.map +0 -1
- package/dist/chunk-HFPS7JFD.js +0 -1
- package/dist/chunk-INHVXP6Z.js.map +0 -1
- package/dist/chunk-K4ZJUGF4.cjs +0 -379
- package/dist/chunk-K4ZJUGF4.cjs.map +0 -1
- package/dist/chunk-KNJEKLKV.js +0 -49
- package/dist/chunk-KNJEKLKV.js.map +0 -1
- package/dist/chunk-MXO72PLY.cjs.map +0 -1
- package/dist/chunk-NLEU4QSE.cjs +0 -360
- package/dist/chunk-NLEU4QSE.cjs.map +0 -1
- package/dist/chunk-OE6WK2Y2.cjs +0 -1577
- package/dist/chunk-OE6WK2Y2.cjs.map +0 -1
- package/dist/chunk-PKFNUHDY.js.map +0 -1
- package/dist/chunk-Q2WGKJNN.cjs.map +0 -1
- package/dist/chunk-RHIJ3S4I.cjs +0 -1
- package/dist/chunk-RHIJ3S4I.cjs.map +0 -1
- package/dist/chunk-TTCLJX6D.js.map +0 -1
- package/dist/chunk-UADYAYAT.cjs.map +0 -1
- package/dist/chunk-VOHZB45S.js.map +0 -1
- package/dist/chunk-VTI6FOLB.js.map +0 -1
- package/dist/chunk-WWURE4XT.cjs.map +0 -1
- package/dist/chunk-YH3BIKHH.js.map +0 -1
- /package/dist/{chunk-HFPS7JFD.js.map → chunk-K6OZXIR6.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyze/detect/modes.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/borrowed.ts","../src/analyze/functional/cadence.ts","../src/analyze/functional/roman.ts","../src/analyze/functional/rationale.ts","../src/analyze/functional/function.ts","../src/analyze/functional/pivot.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 * 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 assertFiniteNumber,\n assertGenerationBudget,\n assertMidiPitch,\n assertNoteEvents,\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 { 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 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 * @category Recognition\n */\nexport type KeyVariant = 'major' | 'natural' | 'harmonic' | 'melodic' | 'modal';\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 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 most specific (largest) chord. An exact match (no extras,\n * no missing) is flagged and ranked first.\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 assertPitches(pitches, 'chord detection pitches');\n if (opts.bassPc !== undefined) assertFiniteNumber(opts.bassPc, 'bassPc');\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const inputKind =\n opts.input === undefined || opts.input === 'auto'\n ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11)\n ? 'pitchClass'\n : 'midi'\n : opts.input;\n const bassPc =\n opts.bassPc !== undefined\n ? pitchClass(opts.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 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 return bSize - aSize;\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 assertPitches(pitches, 'key detection pitches', opts.budget);\n const weights = opts.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 ties all 24 —\n // that leaving the order to the sort's stability would make construction\n // order the contract. Rank on 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/** Default MIDI velocity assumed when a note event does not carry one. */\nconst DEFAULT_VELOCITY = 100;\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 duration times velocity, the same measure chord inference uses,\n * so the two agree on what the music emphasises. Notes that never sound (zero\n * or negative duration) are ignored. The weighted distribution is then ranked\n * exactly as {@link detectKey} ranks it, by correlation against a key profile,\n * so `score` is a finite correlation in [-1, 1].\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 assertNoteEvents(notes, 'key detection notes', {\n allowNonPositiveDuration: true,\n budget: opts.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) => note.durationBeat * (note.velocity ?? DEFAULT_VELOCITY)),\n profile: opts.profile,\n modes: opts.modes,\n explain: opts.explain,\n budget: opts.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 type { Chord } from '../../theory/chord/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport {\n isScaleTone,\n MAJOR_MASK,\n majorKey,\n NATURAL_MINOR_MASK,\n scaleTonesInDegreeOrder,\n} from '../../theory/scale/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 // Named for the mask bits they read, so neither shadows the chord predicate\n // of the same idea that the shared helpers export.\n const minorThirdBit = (key.modeMask12 >> 3) & 1;\n const majorThirdBit = (key.modeMask12 >> 4) & 1;\n return Boolean(minorThirdBit) && !majorThirdBit;\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 */\nexport function romanReference(key: KeyScale): KeyScale {\n return scaleTonesInDegreeOrder(key).length === 7 ? key : majorKey(mod12(key.rootPc));\n}\n\n/** Whether every pitch class of a chord belongs to the key's scale. */\nexport function isDiatonicChord(chord: Chord, key: KeyScale): boolean {\n return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));\n}\n\n/**\n * Whether a chord's interval template carries a major third above its root.\n *\n * The interval is reduced first, so a third voiced as a tenth still counts: it\n * is the same chord tone, and a dominant spread across two octaves is heard\n * through the same leading tone as a close one.\n */\nexport function hasMajorThird(chord: Chord): boolean {\n return chord.intervals.some((interval) => mod12(interval) === 4);\n}\n\n/**\n * Whether a chord is the Neapolitan: a major triad on the flat second degree of\n * a key that does not have that degree already.\n *\n * The Neapolitan is an altered predominant, so it has to be an alteration. A\n * mode carrying a lowered second of its own — phrygian, locrian, and the\n * phrygian-dominant scales of flamenco and modal jazz — sounds that triad as a\n * native chord of the key, and calling it chromatic there would make the key's\n * own II a borrowing every time it appeared.\n */\nexport function isNeapolitan(chord: Chord, key: KeyScale): boolean {\n return (\n mod12(chord.rootPc - key.rootPc) === 1 &&\n chord.quality === 'maj' &&\n !isDiatonicChord(chord, key)\n );\n}\n\n/** Whether a chord has the sonority that can tonicize another scale degree. */\nexport function isAppliedDominantSonority(chord: Chord): boolean {\n const has = (semitones: number): boolean => chord.intervals.some((i) => mod12(i) === semitones);\n return chord.quality === 'maj' || (has(4) && has(10));\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 { 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 { type KeyLike, spelledKeyOf, toKeyScale } 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: KeyLike): Chord {\n return augmentedSixthOn(kind, toKeyScale(key));\n}\n\n/** {@link augmentedSixthChord} on a key already read into its narrow form. */\nfunction augmentedSixthOn(kind: AugmentedSixthKind, key: KeyScale): Chord {\n const checked = assertOneOf(kind, KINDS, 'augmented sixth kind');\n const shape = CHORD_SHAPE[checked];\n const bassPc = bassPcOf(key);\n const chord = makeChord(mod12(bassPc + shape.rootAboveBass), shape.quality, bassPc);\n chord.intervals = [...shape.intervals];\n const tonic = spelledKeyOf(key).tonic;\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: KeyScale): boolean {\n const tonic = spelledKeyOf(key).tonic;\n const written = new Set(spellChord(chord, tonic, key).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), toKeyScale(key));\n}\n\n/** {@link augmentedSixthKind} on data already read into its narrow form. */\nexport function augmentedSixthKindOf(chord: Chord, key: KeyScale): AugmentedSixthKind | null {\n const bass = bassPcOf(key);\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, key)) {\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: KeyLike,\n): Chord | null {\n return augmentedSixthOverBass(pcs, bassPc, toKeyScale(key));\n}\n\n/**\n * {@link augmentedSixthFromPitchClasses} on a key already read into its narrow\n * form.\n */\nexport function augmentedSixthOverBass(\n pcs: readonly number[],\n bassPc: number,\n key: KeyScale,\n): Chord | null {\n const bass = bassPcOf(key);\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, key);\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: KeyScale): 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: KeyScale): string | null {\n const kind = augmentedSixthKindOf(chord, key);\n return kind === null ? null : SYMBOL_BY_KIND[kind];\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 { isDiatonicChord, isMinorScale, isNeapolitan, mod12, parallelScale } 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 (isNeapolitan(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 (isNeapolitan(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 * 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 type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole } from '../../theory/chord/index.js';\nimport { isScaleTone, type KeyLike, toKeyScale } from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/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 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 major or harmonic-minor key carries the leading tone, and\n * that major third is what the cadence is heard through, so a borrowed minor\n * `v` is not the chord the ear was left on. A mode whose own dominant has no\n * leading tone — aeolian, dorian, the modal writing this is routine in — has\n * only the dominant it does have, so there the arrival's own third serves,\n * provided the key contains it.\n */\nfunction restsOnDominant(to: Chord, key: KeyScale): boolean {\n if (hasMajorThird(to)) {\n return true;\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 leading-tone chord is a diminished-family resolution chord. */\nfunction isLeadingToneDiminished(chord: Chord): boolean {\n return chord.quality === 'dim' || chord.quality === 'dim7' || chord.quality === 'm7b5';\n}\n\n/** Whether a chord is the key's dominant, heard through its major third. */\nfunction isDominantOf(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 7 && hasMajorThird(chord);\n}\n\n/** Whether a chord is a leading-tone chord standing in for the dominant. */\nfunction isLeadingToneOf(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 11 && isLeadingToneDiminished(chord);\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 isDominantOf(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 // The cadential six-four resolving onto its own dominant is one harmony\n // moving inside itself, so there is no root motion to cadence with — the same\n // reason a repeated dominant is no cadence.\n if (isCadentialSixFour(from, to, key)) {\n return null;\n }\n const dominant = isDominantOf(from, key);\n if ((dominant || isLeadingToneOf(from, key)) && toOffset === 0) {\n return 'authentic';\n }\n if (fromOffset === 5 && 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.withinSixFour) {\n return 'No cadence: the tonic six-four stands on the dominant already, so its resolution is one harmony moving inside itself rather than a motion between two';\n }\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 = '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 (a fourth above the tonic) 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\n *\n * Both are read as scale degrees rather than as semitone distances, so a mode\n * cadences onto the submediant and the dominant it actually has: the deceptive\n * arrival of dorian is a major sixth above the tonic and that of phrygian a\n * minor sixth. 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 —\n * aeolian and dorian, the modal `v` of pop writing — the arrival's own third\n * suffices as long as the key contains it. That relaxation never applies to a\n * key whose dominant does carry the leading tone, so a borrowed minor `v` in a\n * major key is no half cadence.\n *\n * A static V-to-V repeat with no root motion is not a cadence and yields a null\n * type, and so is the tonic six-four resolving onto the dominant it already\n * stands on: that pair is one harmony moving inside itself. Passing that\n * six-four as `approach` when the dominant is the `from` chord is what lets the\n * cadence be reported as the single event it is — the type and the beat are\n * still the dominant's resolution, while the `rationale` says the cadence began\n * at the six-four. A six-four the bass leaves instead (`IV-I64-IV`, or a\n * neighbouring one over the tonic) is an ordinary inverted tonic and is read as\n * 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 } = 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 = isLeadingToneOf(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 * 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 type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { makeChord } from '../../theory/chord/index.js';\nimport {\n type KeyLike,\n majorKey,\n scaleTonesInDegreeOrder,\n toKeyScale,\n} from '../../theory/scale/index.js';\nimport { type ChordLike, toChordData } from '../../theory/symbol/index.js';\nimport { augmentedSixthFromSymbol, augmentedSixthSymbol } from './augmented-sixth.js';\nimport {\n degreeRootPc,\n isAppliedDominantSonority,\n isDiatonicChord,\n isNeapolitan,\n loweredDegrees,\n mod12,\n romanReference,\n} from './internal.js';\nimport type { RejectedCandidate } from './rationale.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 seventh-degree numeral conventionally\n // denotes the harmonic-minor leading tone: `viio` in A minor is G#, not G.\n // An explicit accidental remains literal, so callers can still write `#viio`\n // (or `bviio`) when that distinction is meaningful to them.\n const rootForQuality = (quality: ChordQuality): number =>\n accidental === 0 &&\n degreeNumber === 7 &&\n loweredDegrees(frame).has(7) &&\n (quality === 'dim' || quality === 'dim7')\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 */\nfunction chromaticSymbolChord(text: string, key: KeyScale): 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.rootPc + 1);\n return makeChord(rootPc, 'maj', mod12(rootPc + 4));\n }\n return augmentedSixthFromSymbol(text, key);\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: KeyLike): Chord {\n const scale = toKeyScale(key);\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 = majorKey(targetRoot);\n return (\n chromaticSymbolChord(applied, localKey) ??\n chordFromParsed(parseSimpleRoman(applied, localKey))\n );\n }\n return chromaticSymbolChord(trimmed, scale) ?? chordFromParsed(parseSimpleRoman(trimmed, scale));\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` conventionally names the raised leading tone in a key\n * whose seventh degree is lowered, so the raised root drops its sharp — and the\n * lowered root, which that numeral therefore no longer names, has to take a\n * 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.\n */\nfunction seventhDegreeAccidental(\n accidental: string,\n degreeNumber: number,\n quality: ChordQuality,\n key: KeyScale,\n): string {\n if (\n degreeNumber !== 7 ||\n (quality !== 'dim' && quality !== 'dim7') ||\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/** Diminished-family qualities, which tonicize from a semitone below. */\nconst LEADING_TONE_QUALITIES: ReadonlySet<ChordQuality> = new Set(['dim', 'dim7', 'm7b5']);\n\n/** The third and fifth above a scale degree, measured within its own scale. */\nfunction degreeTriad(tones: readonly number[], index: number): { third: number; fifth: number } {\n const root = tones[index] ?? 0;\n return {\n third: mod12((tones[(index + 2) % tones.length] ?? 0) - root),\n fifth: mod12((tones[(index + 4) % tones.length] ?? 0) - root),\n };\n}\n\n/**\n * The scale degree a chromatic chord tonicizes, or null when it tonicizes\n * nothing. A dominant sonority points a fifth below itself, a diminished one a\n * semitone above itself; the tonic is never a target (that chord is the key's own\n * dominant) and neither is a degree whose triad has no perfect fifth — a\n * tonicized degree has to be a major or minor triad to be a tonic at all, so\n * the diminished seventh degree of a major key is no target and a chord over it\n * is named against the home key instead (`F#7` in C major is `#IV7`).\n */\nfunction appliedTarget(\n chord: Chord,\n key: KeyScale,\n): { degreeNumber: number; rootPc: number; lower: boolean } | null {\n const dominant = isAppliedDominantSonority(chord);\n const leadingTone = LEADING_TONE_QUALITIES.has(chord.quality);\n if (!dominant && !leadingTone) {\n return null;\n }\n const tones = scaleTonesInDegreeOrder(romanReference(key));\n for (let index = 1; index < tones.length; index += 1) {\n const targetRoot = tones[index];\n if (targetRoot === undefined) {\n continue;\n }\n const triad = degreeTriad(tones, index);\n if (triad.fifth !== 7) {\n continue;\n }\n const expectedRoot = mod12(targetRoot + (dominant ? 7 : -1));\n if (expectedRoot === mod12(chord.rootPc)) {\n return { degreeNumber: index + 1, rootPc: targetRoot, lower: triad.third === 3 };\n }\n }\n return null;\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 where the target degree\n * carries no perfect fifth: nothing tonicizes a diminished triad, so a chord\n * over the seventh degree of a major key stays `#IV7` rather than becoming a\n * numeral no harmony text writes.\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 *\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), toKeyScale(key), 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 accidental: 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 key: KeyScale,\n opts: ChordToRomanOptions,\n): { roman: string; derivation: RomanDerivation } {\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, key);\n if (augmented !== null) {\n return { roman: augmented, derivation: { kind: 'augmentedSixth' } };\n }\n if (opts.applied === true && !isDiatonicChord(chord, key)) {\n const target = appliedTarget(chord, key);\n if (target !== null) {\n const local = renderRoman(chord, majorKey(target.rootPc), {}).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 && isNeapolitan(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 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: { kind: 'degree', degreeNumber, accidental, quality: chord.quality, bass },\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/** The phrase naming where a numeral's root came from. */\nfunction describeDegree(degreeNumber: number, accidental: string): string {\n const name = DEGREE_NAMES[degreeNumber - 1] ?? `${degreeNumber}th`;\n if (accidental === '') {\n return `the root is the ${name} degree of the key`;\n }\n const direction = accidental === '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)}, 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: KeyScale,\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 data = toChordData(chord);\n const scale = toKeyScale(key);\n const { roman, derivation } = renderRoman(data, scale, opts);\n return {\n roman,\n rationale: describeRoman(roman, derivation),\n alternatives: opts.alternatives === true ? romanAlternatives(data, scale, opts) : [],\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 } from '../../core/validation/index.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { makeChord } from '../../theory/chord/index.js';\nimport {\n isScaleTone,\n type KeyLike,\n scaleTonesInDegreeOrder,\n toKeyScale,\n} 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 isAppliedDominantSonority,\n isDiatonicChord,\n isMinorScale,\n isNeapolitan,\n mod12,\n parallelScale,\n} from './internal.js';\nimport { capitalize, type RejectedCandidate } from './rationale.js';\nimport { type ChordToRomanOptions, renderRoman, romanAlternatives } from './roman.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/** Harmonic function of each root offset in a natural-minor context. */\nconst MINOR_FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [\n 'tonic',\n 'subdominant',\n 'subdominant',\n 'tonic',\n 'dominant',\n 'subdominant',\n 'dominant',\n 'dominant',\n 'tonic',\n 'tonic',\n 'subdominant',\n 'dominant',\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 not diatonic to the key has dominant\n * function when its root resolves down a fifth or by a semitone onto a\n * diatonic degree. This is what makes an applied dominant (`A7` in C, which\n * tonicizes ii) and its tritone substitute (`Db7`) read as dominant rather\n * than inheriting the function of the degree they happen to sit on.\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 (isNeapolitan(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 if (!isMinorScale(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {\n return { function: 'subdominant', reason: 'flatSideMajor' };\n }\n return { function: degreeFunction(chord, key), reason: 'degree' };\n}\n\n/**\n * Whether the chord is a dominant sonority pointing at a diatonic degree.\n *\n * A chord already diatonic to the key keeps the offset table's reading; what is\n * classified here is the chromatic chord whose sonority and resolution give it\n * dominant function regardless of the degree it sits on.\n *\n * The resolution required depends on the sonority. A dominant seventh (major\n * third plus minor seventh) must fall a perfect fifth — the applied dominants —\n * or a semitone, which is the same motion its tritone substitute makes. A\n * diminished-family chord must rise a semitone, the leading-tone resolution;\n * requiring that is what keeps a borrowed `iiø7`, which falls a fifth like any\n * other supertonic chord, reading as a predominant rather than a dominant.\n */\nfunction isAppliedDominant(chord: Chord, key: KeyScale): boolean {\n if (isDiatonicChord(chord, key)) {\n return false;\n }\n const root = mod12(chord.rootPc);\n const resolvesTo = (step: number) => isScaleTone(mod12(root + step), key);\n if (isAppliedDominantSonority(chord)) {\n return chord.quality === 'maj' ? resolvesTo(5) : resolvesTo(5) || resolvesTo(11);\n }\n return isDiminishedQuality(chord.quality) && resolvesTo(1);\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 if (isAppliedDominantSonority(chord) && 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/** Diminished-family qualities: diminished triad, dim7, half-diminished. */\nfunction isDiminishedQuality(quality: ChordQuality): boolean {\n return quality === 'dim' || quality === 'dim7' || quality === 'm7b5';\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 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, opts).roman;\n return {\n function: fn,\n borrowed: source !== null,\n source,\n roman,\n rationale: describeChord(fn, reason, roman, source),\n alternatives:\n opts.alternatives === true\n ? [\n ...functionAlternatives(data, scale, reason, fn),\n ...romanAlternatives(data, scale, opts),\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'));\n * // { rootPc: 3, quality: 'dom7' } — Eb7 tonicizes the 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"],"mappings":"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","names":["bassPcOf","quality","degreeNumber"]}
|
package/dist/chunk-HFPS7JFD.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=chunk-HFPS7JFD.js.map
|