@libraz/libcantus 0.9.4 → 0.9.5
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 -18
- package/README_ja.md +268 -0
- package/dist/analyze/index.cjs +7 -7
- package/dist/analyze/index.d.cts +9 -99
- package/dist/analyze/index.d.ts +9 -99
- package/dist/analyze/index.js +6 -6
- package/dist/{chunk-36N73KZ3.cjs → chunk-2A2QMA74.cjs} +80 -65
- package/dist/chunk-2A2QMA74.cjs.map +1 -0
- package/dist/{chunk-YLEMGEFH.js → chunk-2PTFGVD7.js} +50 -35
- package/dist/chunk-2PTFGVD7.js.map +1 -0
- package/dist/{chunk-ZEJR4TKL.cjs → chunk-357ZDPVU.cjs} +36 -47
- package/dist/chunk-357ZDPVU.cjs.map +1 -0
- package/dist/{chunk-EHVEBWM5.js → chunk-3KX76RLG.js} +136 -37
- package/dist/chunk-3KX76RLG.js.map +1 -0
- package/dist/{chunk-RC6GBMCD.cjs → chunk-4VZ5MZAM.cjs} +155 -111
- package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
- package/dist/{chunk-RK35RIN7.js → chunk-7EONYVI5.js} +115 -28
- package/dist/chunk-7EONYVI5.js.map +1 -0
- package/dist/{chunk-GQSGS2N4.js → chunk-7JEP3MFY.js} +5 -16
- package/dist/chunk-7JEP3MFY.js.map +1 -0
- package/dist/{chunk-7NKDPF74.js → chunk-AID75RN3.js} +203 -104
- package/dist/chunk-AID75RN3.js.map +1 -0
- package/dist/{chunk-FNDWXRRI.cjs → chunk-BSLTLGM3.cjs} +55 -37
- package/dist/chunk-BSLTLGM3.cjs.map +1 -0
- package/dist/{chunk-NP4NA2FB.cjs → chunk-CU6VG2YM.cjs} +39 -36
- package/dist/chunk-CU6VG2YM.cjs.map +1 -0
- package/dist/{chunk-NYYWMV2C.js → chunk-DZMHIGI4.js} +27 -17
- package/dist/chunk-DZMHIGI4.js.map +1 -0
- package/dist/{chunk-I24MV65U.js → chunk-EDOTEABK.js} +18 -15
- package/dist/{chunk-I24MV65U.js.map → chunk-EDOTEABK.js.map} +1 -1
- package/dist/{chunk-NKIBQ6PT.js → chunk-HN2OEUN2.js} +7 -4
- package/dist/chunk-HN2OEUN2.js.map +1 -0
- package/dist/{chunk-D2FAJBYL.js → chunk-KBTTEX7Q.js} +30 -12
- package/dist/chunk-KBTTEX7Q.js.map +1 -0
- package/dist/{chunk-CAN76NCU.cjs → chunk-KQN2ZYND.cjs} +147 -102
- package/dist/chunk-KQN2ZYND.cjs.map +1 -0
- package/dist/{chunk-4HLLZPXT.cjs → chunk-MPTVJIVG.cjs} +350 -251
- package/dist/chunk-MPTVJIVG.cjs.map +1 -0
- package/dist/{chunk-RUMU3UA6.cjs → chunk-MTMZTMWM.cjs} +12 -12
- package/dist/{chunk-RUMU3UA6.cjs.map → chunk-MTMZTMWM.cjs.map} +1 -1
- package/dist/{chunk-KMHOICIY.js → chunk-NNB67XA2.js} +90 -45
- package/dist/chunk-NNB67XA2.js.map +1 -0
- package/dist/{chunk-LW53AHQU.cjs → chunk-OL3IHHSJ.cjs} +228 -141
- package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
- package/dist/{chunk-7MGZ7D62.cjs → chunk-PI3UXRWK.cjs} +43 -13
- package/dist/chunk-PI3UXRWK.cjs.map +1 -0
- package/dist/{chunk-XEBG3G53.js → chunk-R2VVFAIN.js} +42 -12
- package/dist/chunk-R2VVFAIN.js.map +1 -0
- package/dist/{chunk-Q6OP4U43.cjs → chunk-WFDXOLUB.cjs} +50 -40
- package/dist/chunk-WFDXOLUB.cjs.map +1 -0
- package/dist/{chunk-NVE34YGZ.cjs → chunk-WTAN4ACG.cjs} +212 -113
- package/dist/chunk-WTAN4ACG.cjs.map +1 -0
- package/dist/{chunk-FCANXBAT.cjs → chunk-YFJXS3AU.cjs} +8 -5
- package/dist/chunk-YFJXS3AU.cjs.map +1 -0
- package/dist/{chunk-Q2XUP227.js → chunk-YUE6TJ4M.js} +89 -45
- package/dist/chunk-YUE6TJ4M.js.map +1 -0
- package/dist/{chunk-HKGYCQ34.js → chunk-ZGLJM4A4.js} +2 -2
- package/dist/core/index.cjs +15 -5
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +14 -4
- package/dist/core/index.d.ts +14 -4
- package/dist/core/index.js +14 -4
- package/dist/{function-DNugAOrt.d.cts → function-CxixTB02.d.cts} +98 -7
- package/dist/{function-BtiZb9i9.d.ts → function-DTCt675z.d.ts} +98 -7
- package/dist/generate/index.cjs +10 -10
- package/dist/generate/index.d.cts +21 -21
- package/dist/generate/index.d.ts +21 -21
- package/dist/generate/index.js +9 -9
- package/dist/{index-tJhkmQgE.d.cts → index-B2Ulxxpo.d.cts} +3 -2
- package/dist/{index-Be74mcon.d.cts → index-BkYJ0xMq.d.cts} +2 -2
- package/dist/{index-6VR8-kit.d.ts → index-BuaB-ypt.d.ts} +3 -2
- package/dist/{index-70p168VM.d.ts → index-CAN7v-kw.d.ts} +9 -2
- package/dist/{index-C83mZI1P.d.ts → index-COw1Ky9g.d.ts} +1 -1
- package/dist/{index-tSQh-xwY.d.ts → index-Cfx8xgH0.d.ts} +1 -1
- package/dist/{index-C5UBbQ4G.d.ts → index-DRtIhlJj.d.ts} +2 -2
- package/dist/{index-BtXT6UQ0.d.cts → index-alrRninU.d.cts} +9 -2
- package/dist/{index-y_ak9_Hi.d.cts → index-oTQbYYIs.d.cts} +1 -1
- package/dist/{index-OG65ocOr.d.ts → index-tR9jTn26.d.cts} +4 -1
- package/dist/{index-OG65ocOr.d.cts → index-tR9jTn26.d.ts} +4 -1
- package/dist/{index-D6b6na69.d.cts → index-x2vcsduE.d.cts} +1 -1
- package/dist/index.cjs +24 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +23 -13
- package/dist/model/index.cjs +7 -7
- package/dist/model/index.d.cts +48 -16
- package/dist/model/index.d.ts +48 -16
- package/dist/model/index.js +6 -6
- package/dist/{styled-DIRkk9Uz.d.ts → styled-Bl0ZK28h.d.ts} +40 -3
- package/dist/{styled-DJbkJQfj.d.cts → styled-Crv6SA4S.d.cts} +40 -3
- package/dist/theory/index.cjs +6 -6
- package/dist/theory/index.d.cts +9 -9
- package/dist/theory/index.d.ts +9 -9
- package/dist/theory/index.js +5 -5
- package/package.json +12 -9
- package/dist/chunk-36N73KZ3.cjs.map +0 -1
- package/dist/chunk-4HLLZPXT.cjs.map +0 -1
- package/dist/chunk-7MGZ7D62.cjs.map +0 -1
- package/dist/chunk-7NKDPF74.js.map +0 -1
- package/dist/chunk-CAN76NCU.cjs.map +0 -1
- package/dist/chunk-D2FAJBYL.js.map +0 -1
- package/dist/chunk-EHVEBWM5.js.map +0 -1
- package/dist/chunk-FCANXBAT.cjs.map +0 -1
- package/dist/chunk-FNDWXRRI.cjs.map +0 -1
- package/dist/chunk-GQSGS2N4.js.map +0 -1
- package/dist/chunk-KMHOICIY.js.map +0 -1
- package/dist/chunk-LW53AHQU.cjs.map +0 -1
- package/dist/chunk-NKIBQ6PT.js.map +0 -1
- package/dist/chunk-NP4NA2FB.cjs.map +0 -1
- package/dist/chunk-NVE34YGZ.cjs.map +0 -1
- package/dist/chunk-NYYWMV2C.js.map +0 -1
- package/dist/chunk-Q2XUP227.js.map +0 -1
- package/dist/chunk-Q6OP4U43.cjs.map +0 -1
- package/dist/chunk-RC6GBMCD.cjs.map +0 -1
- package/dist/chunk-RK35RIN7.js.map +0 -1
- package/dist/chunk-XEBG3G53.js.map +0 -1
- package/dist/chunk-YLEMGEFH.js.map +0 -1
- package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
- /package/dist/{chunk-HKGYCQ34.js.map → chunk-ZGLJM4A4.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-OL3IHHSJ.cjs","../src/theory/chordscale/index.ts","../src/theory/spelling/index.ts","../src/theory/symbol/index.ts","../src/theory/voicing/internal.ts","../src/theory/voicing/leading.ts","../src/theory/voicing/satb.ts","../src/theory/voicing/styled.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-OL3IHHSJ.cjs","sourcesContent":[null,"import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport { assertGenerationBudget, assertPositiveInt } from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport type { ScaleNameInput } from '../scale/index.js';\nimport { NAMED_SCALES, namedScaleMask, requireScaleMask } from '../scale/index.js';\n\n/** Count the set bits (scale tones) in a 12-bit mode mask. */\nfunction popcount12(mask: number): number {\n let count = 0;\n for (let n = 0; n < 12; n += 1) {\n count += (mask >> n) & 1;\n }\n return count;\n}\n\n/** Test whether a pitch class belongs to a mode mask rooted on `scaleRootPc`. */\nfunction maskHasPitchClass(mask: number, scaleRootPc: number, pc: number): boolean {\n const offset = pitchClass(pc - scaleRootPc);\n return ((mask >> offset) & 1) === 1;\n}\n\n/**\n * Test whether every chord pitch class is contained in a scale.\n *\n * @param chordPcs The chord's pitch classes (0..11).\n * @param scaleMask The scale's 12-bit mode mask.\n * @param scaleRootPc The pitch class the mask is rooted on.\n * @returns True if the scale is a superset of the chord.\n *\n * @category Scales\n */\nexport function scaleMatchesChord(\n chordPcs: number[],\n scaleMask: number,\n scaleRootPc: number,\n): boolean {\n for (const pc of chordPcs) {\n if (!maskHasPitchClass(scaleMask, scaleRootPc, pc)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A named scale rooted on a pitch class that fits over a chord.\n *\n * @category Scales\n */\nexport type ChordScaleMatch = {\n name: string;\n rootPc: number;\n};\n\n/**\n * Scale names whose mask duplicates another {@link NAMED_SCALES} entry\n * (major/ionian, naturalMinor/aeolian). Chord-scale analysis conventionally\n * speaks in mode names, so these aliases are skipped and only the modal name\n * is reported.\n */\nconst ALIASED_SCALE_NAMES = new Set(['major', 'naturalMinor']);\n\n/** Number of tones in a heptatonic (seven-note) scale. */\nconst HEPTATONIC_SIZE = 7;\n\n/**\n * Conventional preference among equally-fitting scales, brightest first.\n *\n * Several modes fit a chord that states no third or sixth — a power chord, a\n * suspension — equally well. Alphabetical order made `aeolian` the answer for\n * those, naming a minor mode as the best fit for a mode-neutral chord.\n */\nconst SCALE_PREFERENCE = [\n 'ionian',\n 'major',\n 'mixolydian',\n 'dorian',\n 'lydian',\n 'aeolian',\n 'naturalMinor',\n 'phrygian',\n 'locrian',\n] as const;\n\n/** Conventional first choices that pure set-fit ranking cannot express. */\nconst IDIOMATIC_SCALES: Partial<Record<Chord['quality'], readonly string[]>> = {\n aug: ['wholeTone'],\n m7b5: ['locrian'],\n};\n\n/** Position of a scale in {@link SCALE_PREFERENCE}, or last when unlisted. */\nfunction conventionalRank(name: string): number {\n const index = (SCALE_PREFERENCE as readonly string[]).indexOf(name);\n return index < 0 ? SCALE_PREFERENCE.length : index;\n}\n\n/** Whether this scale is an idiomatic color choice for the chord quality. */\nfunction idiomaticRank(chord: Chord, name: string): number {\n const choices = IDIOMATIC_SCALES[chord.quality];\n if (choices === undefined) {\n return 0;\n }\n const index = choices.indexOf(name);\n return index < 0 ? choices.length + 1 : index;\n}\n\n/**\n * An ordinary dominant seventh may use altered colors while retaining its\n * perfect fifth in the voicing; the `7alt` quality makes that alteration\n * explicit, but lead sheets commonly write the shorter `7` here.\n */\nfunction isAlteredDominant(chord: Chord, scaleName: string): boolean {\n return chord.quality === 'dom7' && scaleName === 'altered';\n}\n\n/**\n * List the scales that fit over a chord, best fit first.\n *\n * Only the chord root is considered as the scale root, matching the\n * conventional chord-scale relationship. Every entry of {@link NAMED_SCALES}\n * whose pitch-class set is a superset of the chord's is returned once per\n * distinct pitch-class set (aliased masks such as major/ionian report only the\n * modal name), ranked by fewest extra scale tones beyond the chord and then by\n * conventional\n * preference. For bare triads and smaller chords, heptatonic scales rank before\n * pentatonics and other sizes: a pentatonic adds no modal color over a triad,\n * so the seven-note modes are the more useful answer. The chromatic scale is\n * only returned as a fallback when no other scale contains the chord.\n *\n * The ranking is by fit, so the first entry is the tightest-fitting scale\n * rather than the idiomatic choice: a four-note chord that a pentatonic covers\n * exactly puts that pentatonic ahead of the mode a player would name. Read the\n * whole list, or pick by name, when the idiomatic scale is what is wanted;\n * {@link scalesForChanges} makes that choice for a progression.\n *\n * @param chord The chord to fit scales over.\n * @returns The matching scales rooted on the chord root, best fit first.\n *\n * @example\n * ```ts\n * import { makeChord, chordScales } from '@libraz/libcantus';\n * const scales = chordScales(makeChord(0, 'maj7'));\n * // scales for Cmaj7, best fit first; each is { name, rootPc: 0 }\n * ```\n *\n * @category Scales\n */\nexport function chordScales(chord: Chord): ChordScaleMatch[] {\n const chordPcs = chordPitchClasses(chord);\n const rootPc = pitchClass(chord.rootPc);\n const ranked: { name: string; extra: number; size: number }[] = [];\n const seenMasks = new Set<number>();\n for (const name of Object.keys(NAMED_SCALES)) {\n if (name === 'chromatic' || ALIASED_SCALE_NAMES.has(name)) {\n continue;\n }\n const mask = namedScaleMask(name);\n if (mask === undefined || seenMasks.has(mask)) {\n continue;\n }\n seenMasks.add(mask);\n if (scaleMatchesChord(chordPcs, mask, rootPc) || isAlteredDominant(chord, name)) {\n const size = popcount12(mask);\n ranked.push({ name, extra: size - chordPcs.length, size });\n }\n }\n if (ranked.length === 0) {\n // No diatonic-style scale contains the chord; fall back to the chromatic\n // scale, which trivially contains every pitch class.\n if (NAMED_SCALES.chromatic !== undefined) {\n return [{ name: 'chromatic', rootPc }];\n }\n return [];\n }\n const preferHeptatonic = chordPcs.length <= 3;\n ranked.sort((a, b) => {\n const idiomaticA = idiomaticRank(chord, a.name);\n const idiomaticB = idiomaticRank(chord, b.name);\n if (idiomaticA !== idiomaticB) {\n return idiomaticA - idiomaticB;\n }\n if (preferHeptatonic && (a.size === HEPTATONIC_SIZE) !== (b.size === HEPTATONIC_SIZE)) {\n return a.size === HEPTATONIC_SIZE ? -1 : 1;\n }\n if (a.extra !== b.extra) {\n return a.extra - b.extra;\n }\n const rankA = conventionalRank(a.name);\n const rankB = conventionalRank(b.name);\n if (rankA !== rankB) {\n return rankA - rankB;\n }\n return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n return ranked.map((entry) => ({ name: entry.name, rootPc }));\n}\n\n/**\n * List a scale's avoid notes over a chord.\n *\n * An avoid note is a non-chord scale tone that lies a semitone directly above a\n * chord tone; sounding it against the chord clashes. The scale is rooted on the\n * chord root. If the scale does not contain the chord, no avoid notes exist.\n *\n * @param chord The chord providing the chord tones.\n * @param scaleName A key of {@link NAMED_SCALES}.\n * @returns The avoid-note pitch classes, sorted ascending in [0, 11].\n * @throws If `scaleName` is not a built-in scale. An empty result already\n * means \"this scale has no avoid notes over this chord\"; answering a typo\n * the same way would make the two indistinguishable.\n *\n * @category Scales\n */\nexport function avoidNotes(chord: Chord, scaleName: ScaleNameInput): number[] {\n const mask = requireScaleMask(scaleName);\n const rootPc = pitchClass(chord.rootPc);\n const chordPcs = chordPitchClasses(chord);\n const alteredDominant = isAlteredDominant(chord, scaleName);\n if (!scaleMatchesChord(chordPcs, mask, rootPc) && !alteredDominant) {\n return [];\n }\n const chordSet = new Set(chordPcs);\n const avoid: number[] = [];\n for (let pc = 0; pc < 12; pc += 1) {\n if (!maskHasPitchClass(mask, rootPc, pc) || chordSet.has(pc)) {\n continue;\n }\n const suspendedThird =\n chord.quality === 'sus4' || chord.quality === '7sus4' ? pc === pitchClass(rootPc + 4) : false;\n if (!alteredDominant && (chordSet.has(pitchClass(pc - 1)) || suspendedThird)) {\n avoid.push(pc);\n }\n }\n return avoid.sort((a, b) => a - b);\n}\n\n/**\n * List a scale's available tensions over a chord.\n *\n * Available tensions are scale tones that are neither chord tones nor avoid\n * notes, i.e. the usable color tones (typically the 9/11/13 region). The scale\n * is rooted on the chord root. If the scale does not contain the chord, there\n * are no available tensions.\n *\n * @param chord The chord providing the chord tones.\n * @param scaleName A key of {@link NAMED_SCALES}.\n * @returns The available-tension pitch classes, sorted ascending in [0, 11].\n * @throws If `scaleName` is not a built-in scale, as {@link avoidNotes} does.\n *\n * @example\n * ```ts\n * import { makeChord, availableTensions } from '@libraz/libcantus';\n * availableTensions(makeChord(0, 'maj7'), 'ionian'); // [2, 9] — color tones over Cmaj7\n * ```\n *\n * @category Scales\n */\nexport function availableTensions(chord: Chord, scaleName: ScaleNameInput): number[] {\n const mask = requireScaleMask(scaleName);\n const rootPc = pitchClass(chord.rootPc);\n const chordPcs = chordPitchClasses(chord);\n const alteredDominant = isAlteredDominant(chord, scaleName);\n if (!scaleMatchesChord(chordPcs, mask, rootPc) && !alteredDominant) {\n return [];\n }\n const chordSet = new Set(chordPcs);\n const avoidSet = alteredDominant ? new Set<number>() : new Set(avoidNotes(chord, scaleName));\n const tensions: number[] = [];\n for (let pc = 0; pc < 12; pc += 1) {\n if (!maskHasPitchClass(mask, rootPc, pc) || chordSet.has(pc) || avoidSet.has(pc)) {\n continue;\n }\n tensions.push(pc);\n }\n return tensions.sort((a, b) => a - b);\n}\n\n/**\n * A scale fit over a chord together with its avoid notes and tensions.\n *\n * @category Scales\n */\nexport type ChordScaleReportEntry = {\n name: string;\n rootPc: number;\n avoid: number[];\n tensions: number[];\n};\n\n/**\n * Report the best-fitting scales for a chord with their avoid notes and tensions.\n *\n * Combines {@link chordScales}, {@link avoidNotes}, and {@link availableTensions}\n * into a single ergonomic result, ordered best fit first.\n *\n * @param chord The chord to analyze.\n * @param limit Optional maximum number of scales to report; all by default.\n * @returns One entry per reported scale, best fit first.\n *\n * @category Scales\n */\nexport function chordScaleReport(chord: Chord, limit?: number): ChordScaleReportEntry[] {\n if (limit !== undefined) {\n assertPositiveInt(limit, 'chord-scale report limit');\n }\n const matches = chordScales(chord);\n const chosen = limit === undefined ? matches : matches.slice(0, limit);\n return chosen.map((match) => ({\n name: match.name,\n rootPc: match.rootPc,\n avoid: avoidNotes(chord, match.name),\n tensions: availableTensions(chord, match.name),\n }));\n}\n\n/**\n * A chord paired with the scale chosen for it by {@link scalesForChanges}.\n *\n * @category Scales\n */\nexport type ScaleChoice = {\n chord: Chord;\n scale: ChordScaleMatch;\n};\n\n/**\n * Small per-candidate penalty added in {@link scalesForChanges} so that, among\n * choices of otherwise-equal transition cost, the tighter best-fit scale wins.\n */\nconst RANK_PENALTY = 0.01;\n\n/** Build the pitch-class set of a named scale rooted on `rootPc`. */\nfunction scalePitchClassSet(name: string, rootPc: number): Set<number> {\n const mask = namedScaleMask(name);\n const pcs = new Set<number>();\n if (mask === undefined) {\n return pcs;\n }\n for (let n = 0; n < 12; n += 1) {\n if (((mask >> n) & 1) === 1) {\n pcs.add(pitchClass(rootPc + n));\n }\n }\n return pcs;\n}\n\n/** Count pitch classes that belong to exactly one of two sets. */\nfunction symmetricDifferenceSize(a: Set<number>, b: Set<number>): number {\n let count = 0;\n for (const pc of a) {\n if (!b.has(pc)) {\n count += 1;\n }\n }\n for (const pc of b) {\n if (!a.has(pc)) {\n count += 1;\n }\n }\n return count;\n}\n\n/**\n * Choose one scale per chord across a progression, favoring continuity.\n *\n * Each chord's {@link chordScales} candidates form a stage in a Viterbi-style\n * dynamic program. The transition cost between adjacent choices is the number\n * of pitch classes that differ between their pitch-class sets (the symmetric\n * difference), plus a small penalty for straying from a chord's best-fit scale\n * so that ties break toward the tighter fit. The minimum-total-cost path is\n * returned, one {@link ScaleChoice} per input chord in the original order.\n *\n * @param chords The chord sequence to choose scales for.\n * @returns One scale choice per chord, in input order.\n *\n * @category Scales\n */\nexport function scalesForChanges(chords: Chord[]): ScaleChoice[] {\n assertGenerationBudget(chords.length, 'chord-scale changes');\n if (chords.length === 0) {\n return [];\n }\n const candidateLists = chords.map((chord) => chordScales(chord));\n const pcSets = candidateLists.map((candidates) =>\n candidates.map((match) => scalePitchClassSet(match.name, match.rootPc)),\n );\n\n const first = candidateLists[0] ?? [];\n const dp: number[][] = [first.map((_, j) => j * RANK_PENALTY)];\n const back: number[][] = [first.map(() => -1)];\n\n for (let i = 1; i < candidateLists.length; i += 1) {\n const candidates = candidateLists[i] ?? [];\n const prevCosts = dp[i - 1] ?? [];\n const prevSets = pcSets[i - 1] ?? [];\n const curSets = pcSets[i] ?? [];\n const stageCosts: number[] = [];\n const stageBack: number[] = [];\n for (let j = 0; j < candidates.length; j += 1) {\n let bestCost = Number.POSITIVE_INFINITY;\n let bestPrev = -1;\n for (let k = 0; k < prevCosts.length; k += 1) {\n const transition = symmetricDifferenceSize(\n prevSets[k] ?? new Set(),\n curSets[j] ?? new Set(),\n );\n const cost = (prevCosts[k] ?? 0) + transition;\n if (cost < bestCost) {\n bestCost = cost;\n bestPrev = k;\n }\n }\n stageCosts.push(bestCost + j * RANK_PENALTY);\n stageBack.push(bestPrev);\n }\n dp.push(stageCosts);\n back.push(stageBack);\n }\n\n const lastCosts = dp[dp.length - 1] ?? [];\n let bestFinal = 0;\n for (let j = 1; j < lastCosts.length; j += 1) {\n if ((lastCosts[j] ?? Number.POSITIVE_INFINITY) < (lastCosts[bestFinal] ?? 0)) {\n bestFinal = j;\n }\n }\n\n const chosenIndices: number[] = new Array(candidateLists.length);\n let current = bestFinal;\n for (let i = candidateLists.length - 1; i >= 0; i -= 1) {\n chosenIndices[i] = current;\n current = back[i]?.[current] ?? -1;\n }\n\n return chords.map((chord, i) => {\n const candidates = candidateLists[i] ?? [];\n const scale = candidates[chosenIndices[i] ?? 0] ?? { name: 'chromatic', rootPc: chord.rootPc };\n return { chord, scale };\n });\n}\n","/**\n * Spelling: derive letter-name notes for scales and chords from a spelled tonic.\n *\n * The theory core works in pitch classes, which cannot choose between (say) a\n * G# and an Ab. Given a spelled tonic, this module assigns diatonic letters to\n * scale degrees and chord tones so a C major scale spells as C D E F G A B and\n * A harmonic minor spells its seventh as G#.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport type { Note } from '../../core/pitch/index.js';\nimport {\n formatNote,\n diatonicLetterOf as mod7,\n pitchClassOf as mod12,\n naturalPitchClassOf as naturalPc,\n} from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../scale/index.js';\n\n/**\n * Conventional degree assignment for each chromatic offset above the tonic.\n *\n * The letter is derived from the tonic's letter plus `degreeOffset` (so the\n * spelling follows the key's diatonic letters), and the accidental is recomputed\n * for that letter. The table covers the whole octave, so a pitch class that is\n * not a degree of the scale — a chromatic tone in a heptatonic key, or any tone\n * outside a pentatonic or blues scale — still spells from the tonic's letter\n * rather than falling back to a fixed sharp table.\n *\n * Offsets follow the conventional ascending chromatic spelling: b2, M2, b3, M3,\n * P4, #4, P5, b6, #6, b7, #7. The raised sixth and leading tone keep sharp-side\n * minor keys spelling their raised degrees with the key-correct letter (e.g. F#\n * minor spells its leading tone E#, not F).\n */\nconst CHROMATIC_SPELLING: Record<number, number> = {\n 1: 1, // b2\n 2: 1, // M2\n 3: 2, // b3\n 4: 2, // M3\n 5: 3, // P4\n 6: 3, // #4\n 7: 4, // P5\n 8: 5, // b6\n 9: 5, // #6 (raised sixth of a minor key)\n 10: 6, // b7\n 11: 6, // #7 (raised leading tone of a minor key)\n};\n\n/**\n * Flat-side table for scales that are not heptatonic.\n *\n * Identical to {@link CHROMATIC_SPELLING} except at the tritone, which a\n * flat-side scale spells as a diminished fifth rather than the augmented fourth\n * a lydian mode wants: C blues spells C Eb F Gb G Bb.\n */\nconst CHROMATIC_SPELLING_FLAT: Record<number, number> = {\n ...CHROMATIC_SPELLING,\n 6: 4, // b5\n};\n\n/**\n * Sharp-side counterpart, used for scales that are not heptatonic and lean\n * sharp: C whole-tone spells C D E F# G# A#.\n */\nconst CHROMATIC_SPELLING_SHARP: Record<number, number> = {\n 1: 0, // #1\n 2: 1, // M2\n 3: 1, // #2\n 4: 2, // M3\n 5: 3, // P4\n 6: 3, // #4\n 7: 4, // P5\n 8: 4, // #5\n 9: 5, // M6\n 10: 5, // #6\n 11: 6, // M7\n};\n\n/** Shortest signed alteration (in [-6, 6]) taking a letter's natural pc to `pc`. */\nfunction alterFor(letter: number, pc: number): number {\n let d = mod12(pc - naturalPc(letter));\n if (d > 6) {\n d -= 12;\n }\n return d;\n}\n\n/** Spell `pc` on the letter `degreeOffset` steps above the tonic's letter. */\nfunction letterFor(tonic: Note, degreeOffset: number | undefined, pc: number): Note | undefined {\n if (degreeOffset === undefined) {\n return undefined;\n }\n const letter = mod7(tonic.letter + degreeOffset);\n return { letter, alter: alterFor(letter, pc) };\n}\n\n/** The lighter-accidental of two candidate spellings; ties go to `preferFlat`. */\nfunction pickSpelling(\n flat: Note | undefined,\n sharp: Note | undefined,\n preferFlat: boolean,\n): Note | undefined {\n if (flat === undefined || sharp === undefined) {\n return flat ?? sharp;\n }\n if (Math.abs(flat.alter) !== Math.abs(sharp.alter)) {\n return Math.abs(flat.alter) < Math.abs(sharp.alter) ? flat : sharp;\n }\n return preferFlat ? flat : sharp;\n}\n\n/**\n * Keep a conventional chromatic degree unless its key-letter spelling costs\n * two or more accidentals than the enharmonic alternative. This preserves Db\n * for C-major bII while avoiding Bbb in Ab major, where A is more readable.\n */\nfunction capChromaticAccidentals(preferred: Note, alternative: Note | undefined): Note {\n return alternative !== undefined && Math.abs(preferred.alter) - Math.abs(alternative.alter) >= 2\n ? alternative\n : preferred;\n}\n\n/** Whether the key's scale is a seven-note (heptatonic) scale. */\nfunction isHeptatonic(key: KeyScale): boolean {\n return scaleTonesInDegreeOrder(key).length === 7;\n}\n\n/** Whether the scale has a minor third and no major third — it leans flat. */\nfunction hasMinorThird(key: KeyScale): boolean {\n const tones = scaleTonesInDegreeOrder(key);\n const root = mod12(key.rootPc);\n return tones.includes(mod12(root + 3)) && !tones.includes(mod12(root + 4));\n}\n\n/**\n * Spell a single pitch class relative to a spelled tonic and key.\n *\n * The tonic always spells as itself. In a heptatonic key each scale degree takes\n * the next letter above the tonic, so the scale spells with one letter per\n * degree. Every other pitch class — a chromatic tone in a heptatonic key, or any\n * tone of a scale that is not heptatonic (pentatonic, blues, octatonic) — takes\n * the conventional interval spelling above the tonic's letter, so a flat-side\n * key keeps flat-side names.\n *\n * @param pc The pitch class to spell.\n * @param tonic The spelled tonic (its letter anchors the spelling).\n * @param key The key/scale.\n * @returns The spelled note (without octave).\n * @category Pitch & Intervals\n */\nexport function spellPitchClass(pc: number, tonic: Note, key: KeyScale): Note {\n const tonicPc = mod12(naturalPc(tonic.letter) + tonic.alter);\n const offset = mod12(pc - tonicPc);\n\n // The tonic spells as itself, whatever the scale: a caller that asked for Eb\n // major pentatonic must not be handed back a D#.\n if (offset === 0) {\n return { letter: mod7(tonic.letter), alter: tonic.alter };\n }\n\n const heptatonic = isHeptatonic(key);\n if (heptatonic) {\n const degree = scaleTonesInDegreeOrder(key).indexOf(mod12(pc));\n if (degree >= 0) {\n const letter = mod7(tonic.letter + degree);\n return { letter, alter: alterFor(letter, pc) };\n }\n const degreeOffset = CHROMATIC_SPELLING[offset];\n const preferred = letterFor(tonic, degreeOffset, pc);\n if (preferred !== undefined) {\n const flat = letterFor(tonic, CHROMATIC_SPELLING_FLAT[offset], pc);\n const sharp = letterFor(tonic, CHROMATIC_SPELLING_SHARP[offset], pc);\n // The conventional heptatonic degree is usually the flat-side candidate;\n // use the other side only when it avoids a double (or worse) accidental.\n const alternative =\n flat?.letter === preferred.letter\n ? sharp\n : sharp?.letter === preferred.letter\n ? flat\n : undefined;\n return capChromaticAccidentals(preferred, alternative);\n }\n } else {\n // A scale that is not heptatonic has no letter-per-degree spelling to\n // follow, so take whichever conventional letter needs the smaller\n // accidental. Ties go to the side the scale itself leans: a flat tonic or a\n // minor third both mean flats (C blues spells Gb and Bb, not F# and A#).\n const flat = letterFor(tonic, CHROMATIC_SPELLING_FLAT[offset], pc);\n const sharp = letterFor(tonic, CHROMATIC_SPELLING_SHARP[offset], pc);\n const best = pickSpelling(flat, sharp, tonic.alter < 0 || hasMinorThird(key));\n if (best !== undefined) {\n return best;\n }\n }\n\n // Unreachable for a pitch class, since both tables cover offsets 1..11 and\n // offset 0 returns above; kept so a non-integral input still names something.\n const belowLetter = mod7([0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][mod12(pc)] ?? 0);\n return { letter: belowLetter, alter: alterFor(belowLetter, pc) };\n}\n\n/**\n * Spell every pitch class of a scale, in ascending scale-degree order.\n *\n * Correct for heptatonic scales (each degree gets the next letter). A scale that\n * is not heptatonic is spelled tone by tone, on the accidental side the scale\n * leans towards.\n *\n * @param tonic The spelled tonic.\n * @param key The key/scale.\n * @returns Spelled notes, one per scale degree.\n * @example\n * ```ts\n * import { spellScale, noteNames, parseNote, majorKey } from '@libraz/libcantus';\n * noteNames(spellScale(parseNote('C'), majorKey(0)));\n * // ['C', 'D', 'E', 'F', 'G', 'A', 'B']\n * ```\n * @category Pitch & Intervals\n */\nexport function spellScale(tonic: Note, key: KeyScale): Note[] {\n return scaleTonesInDegreeOrder(key).map((pc) => spellPitchClass(pc, tonic, key));\n}\n\n/**\n * Spell an arbitrary list of pitch classes relative to a key.\n *\n * @param pcs The pitch classes.\n * @param tonic The spelled tonic.\n * @param key The key/scale.\n * @returns Spelled notes, in input order.\n * @category Pitch & Intervals\n */\nexport function spellPitchClasses(pcs: number[], tonic: Note, key: KeyScale): Note[] {\n return pcs.map((pc) => spellPitchClass(pc, tonic, key));\n}\n\n/**\n * Diatonic letter distance implied by a chord interval.\n *\n * Chord intervals are not merely pitch-class distances: 6 is a diminished\n * fifth in the basic chord templates while 18 is an augmented eleventh, and 9\n * is a diminished seventh in `dim7` but a sixth elsewhere. Keeping that\n * distinction is what prevents functional chord tones from being respelled by\n * their nearest key pitch (for example G# becoming Ab in E7).\n */\nfunction chordLetterOffset(interval: number, chord: Chord): number {\n switch (interval) {\n case 0:\n case 12:\n return 0;\n case 1:\n case 2:\n case 13:\n case 14:\n return 1;\n case 3:\n case 4:\n case 16:\n return 2;\n case 15:\n // A raised ninth over a chord that already has a third is a ninth, not a\n // second third: C7#9 spells D#, not a duplicate E.\n return chord.intervals.some((i) => i === 3 || i === 4) ? 1 : 2;\n case 5:\n case 17:\n case 18:\n return 3;\n case 6:\n case 7:\n case 8:\n case 19:\n return 4;\n case 9:\n return chord.quality === 'dim7' ? 6 : 5;\n case 20:\n case 21:\n return 5;\n case 10:\n case 11:\n case 22:\n case 23:\n return 6;\n default: {\n // Unknown/custom interval data gets the nearest conventional diatonic\n // distance. Public builders use the explicit cases above.\n const octaves = Math.floor(Math.max(0, interval) / 12);\n const simple = mod12(interval);\n const simpleOffset = [0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 6][simple] ?? 0;\n return octaves * 7 + simpleOffset;\n }\n }\n}\n\n/**\n * Spell chord tones from an already chosen root spelling.\n *\n * This is public for callers that already know the desired root spelling;\n * {@link spellChord} instead derives that spelling from the chord and key.\n */\nexport function spellChordFromRoot(chord: Chord, root: Note): Note[] {\n if (mod12(naturalPc(root.letter) + root.alter) !== mod12(chord.rootPc)) {\n throw new InvalidInputError('chord root spelling must match chord.rootPc');\n }\n return chord.intervals.map((interval) => {\n const letter = mod7(root.letter + chordLetterOffset(interval, chord));\n const pc = mod12(chord.rootPc + interval);\n return { letter, alter: alterFor(letter, pc) };\n });\n}\n\n/**\n * Spell a chord's tones, in the chord's own (tertian) order, relative to a key.\n *\n * Diatonic chords spell exactly (e.g. G7 in C major -> G B D F). Chromatic chord\n * tones take their conventional spelling; enharmonically ambiguous altered\n * tensions may be spelled by the general convention rather than by chord\n * function.\n *\n * @param chord The chord.\n * @param tonic The spelled tonic of the key.\n * @param key The key/scale.\n * @returns Spelled chord tones, root first.\n * @example\n * ```ts\n * import { spellChord, noteNames, parseNote, majorKey, makeChord } from '@libraz/libcantus';\n * // G7 in C major\n * noteNames(spellChord(makeChord(7, 'dom7'), parseNote('C'), majorKey(0)));\n * // ['G', 'B', 'D', 'F']\n * ```\n * @category Pitch & Intervals\n */\nexport function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[] {\n return spellChordFromRoot(chord, chordRootSpelling(chord, tonic, key));\n}\n\n/**\n * The root spelling to use for a chord: the chord's own hint when it still\n * matches the root pitch class, otherwise the key's spelling of that pitch\n * class.\n *\n * Honouring the hint is what keeps a chord's spelled tones agreeing with the\n * symbol it renders as — `parseChordSymbol('C#7')` must spell C# E# G# B even\n * when analysed in a flat-side key.\n */\nfunction chordRootSpelling(chord: Chord, tonic: Note, key: KeyScale): Note {\n const hint = chord.rootSpelling;\n if (hint !== undefined && mod12(naturalPc(hint.letter) + hint.alter) === mod12(chord.rootPc)) {\n return { letter: mod7(hint.letter), alter: hint.alter };\n }\n return spellPitchClass(chord.rootPc, tonic, key);\n}\n\n/**\n * Spell a MIDI pitch relative to a key, keeping its octave.\n *\n * The octave-less sibling of this function, {@link spellPitchClass}, is enough\n * for chord and scale spelling; this one is for naming actual sounding pitches\n * (a generated line, an imported track) without losing the register.\n *\n * @param pitch The MIDI pitch; rounded to the nearest integer.\n * @param tonic The spelled tonic of the key.\n * @param key The key/scale.\n * @returns The spelled note, carrying the octave that reproduces `pitch`.\n * @example\n * ```ts\n * import { spellPitch, formatNote, parseNote, majorKey } from '@libraz/libcantus';\n * formatNote(spellPitch(70, parseNote('Eb'), majorKey(3))); // 'Bb4'\n * ```\n * @category Pitch & Intervals\n */\nexport function spellPitch(pitch: number, tonic: Note, key: KeyScale): Note {\n const rounded = Math.round(pitch);\n const spelled = spellPitchClass(mod12(rounded), tonic, key);\n const octave = (rounded - naturalPc(spelled.letter) - spelled.alter) / 12 - 1;\n return { letter: spelled.letter, alter: spelled.alter, octave };\n}\n\n/**\n * Convenience: render spelled notes as letter-name strings.\n *\n * @param notes The notes.\n * @returns Their formatted names.\n * @category Pitch & Intervals\n */\nexport function noteNames(notes: Note[]): string[] {\n return notes.map(formatNote);\n}\n","/**\n * Lead-sheet chord symbol parsing and formatting.\n *\n * Bridges the informal `<root><quality>[/<bass>]` notation musicians write\n * (`Cmaj7`, `F#m7b5`, `Bb7`, `C/G`) and the library's structured {@link Chord}\n * type, so callers can accept/emit plain text at the edges of the API.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport {\n formatNote,\n midiToNote,\n type Note,\n noteToPitchClass,\n parseNote,\n pitchClassOf as pitchClass,\n transposeNote,\n} from '../../core/pitch/index.js';\nimport { assertFiniteNumber, assertFiniteSemitones } from '../../core/validation/index.js';\nimport { type Chord, type ChordQuality, makeChord, type PitchSpelling } from '../chord/index.js';\n\n/**\n * Matches a chord root: a letter A-G (case-insensitive, like {@link parseNote})\n * with an optional single-direction accidental.\n */\nconst ROOT_RE = /^[A-Ga-g](?:#{1,2}|b{1,2}|x)?/;\n\n/** Matches a bare note usable as a slash-chord bass (no octave). */\nconst BASS_RE = /^[A-Ga-g](?:#{1,2}|b{1,2}|x)?$/;\n\n/** Recognized quality suffixes, keyed by their exact lead-sheet spelling. */\nconst QUALITY_MAP: Record<string, ChordQuality> = {\n '': 'maj',\n maj: 'maj',\n m: 'min',\n min: 'min',\n '-': 'min',\n dim: 'dim',\n '°': 'dim',\n aug: 'aug',\n '+': 'aug',\n maj7: 'maj7',\n M7: 'maj7',\n Δ: 'maj7',\n Δ7: 'maj7',\n m7: 'min7',\n min7: 'min7',\n '-7': 'min7',\n '7': 'dom7',\n dim7: 'dim7',\n '°7': 'dim7',\n m7b5: 'm7b5',\n ø: 'm7b5',\n ø7: 'm7b5',\n min7b5: 'm7b5',\n mMaj7: 'minMaj7',\n mM7: 'minMaj7',\n minMaj7: 'minMaj7',\n mMaj9: 'minMaj9',\n mM9: 'minMaj9',\n minMaj9: 'minMaj9',\n mMaj11: 'minMaj11',\n mM11: 'minMaj11',\n minMaj11: 'minMaj11',\n mMaj13: 'minMaj13',\n mM13: 'minMaj13',\n minMaj13: 'minMaj13',\n aug7: 'aug7',\n '+7': 'aug7',\n '7#5': 'aug7',\n augMaj7: 'augMaj7',\n augM7: 'augMaj7',\n '+maj7': 'augMaj7',\n '+M7': 'augMaj7',\n majb5: 'majb5',\n 'maj(b5)': 'majb5',\n '(b5)': 'majb5',\n '6': '6',\n m6: 'min6',\n min6: 'min6',\n '6/9': '6/9',\n '69': '6/9',\n sus2: 'sus2',\n sus: 'sus4',\n sus4: 'sus4',\n add9: 'add9',\n add11: 'add11',\n maj9: 'maj9',\n M9: 'maj9',\n m9: 'min9',\n min9: 'min9',\n '9': 'dom9',\n '7b9': '7b9',\n '7#9': '7#9',\n '7#11': '7#11',\n '7b13': '7b13',\n '11': '11',\n '13': '13',\n '5': '5',\n '7sus4': '7sus4',\n '7sus': '7sus4',\n '9sus4': '11',\n '9sus': '11',\n '7b5': '7b5',\n '7alt': '7alt',\n alt: '7alt',\n '13b9': '13b9',\n maj13: 'maj13',\n M13: 'maj13',\n 'maj7#11': 'maj7#11',\n 'M7#11': 'maj7#11',\n m11: 'min11',\n min11: 'min11',\n m13: 'min13',\n min13: 'min13',\n madd9: 'minAdd9',\n 'min(add9)': 'minAdd9',\n 'm(add9)': 'minAdd9',\n '6add9': '6/9',\n 'm6/9': 'min6/9',\n m69: 'min6/9',\n 'min6/9': 'min6/9',\n // ASCII stand-ins for the degree and half-diminished glyphs, which most\n // lead sheets and chord-chart text files use instead of the typographic ones.\n o: 'dim',\n o7: 'dim7',\n h: 'm7b5',\n h7: 'm7b5',\n};\n\n/** Canonical lead-sheet suffix emitted for each chord quality when formatting. */\nconst CANONICAL_SUFFIX: Record<ChordQuality, string> = {\n maj: '',\n min: 'm',\n dim: 'dim',\n aug: 'aug',\n maj7: 'maj7',\n min7: 'm7',\n dom7: '7',\n dim7: 'dim7',\n m7b5: 'm7b5',\n minMaj7: 'mMaj7',\n minMaj9: 'mMaj9',\n minMaj11: 'mMaj11',\n minMaj13: 'mMaj13',\n aug7: 'aug7',\n augMaj7: 'augMaj7',\n majb5: 'maj(b5)',\n '6': '6',\n min6: 'm6',\n '6/9': '6/9',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n add11: 'add11',\n maj9: 'maj9',\n min9: 'm9',\n dom9: '9',\n '7b9': '7b9',\n '7#9': '7#9',\n '7#11': '7#11',\n '7b13': '7b13',\n '11': '11',\n '13': '13',\n '5': '5',\n '7sus4': '7sus4',\n '7b5': '7b5',\n '7alt': '7alt',\n '13b9': '13b9',\n maj13: 'maj13',\n 'maj7#11': 'maj7#11',\n min11: 'm11',\n min13: 'm13',\n minAdd9: 'madd9',\n 'min6/9': 'm6/9',\n};\n\n/**\n * The quality a suffix names, or undefined when it names none.\n *\n * The own-property check is what stops `'constructor'` and `'toString'` from\n * resolving through `Object.prototype` and reaching the chord builder with a\n * quality it has no interval template for.\n */\nfunction knownQuality(suffix: string): ChordQuality | undefined {\n return Object.hasOwn(QUALITY_MAP, suffix) ? QUALITY_MAP[suffix] : undefined;\n}\n\n/** Build a chord and record the parsed spellings as enharmonic hints. */\nfunction makeSpelledChord(root: Note, quality: ChordQuality, bass?: Note): Chord {\n const chord = makeChord(\n noteToPitchClass(root),\n quality,\n bass === undefined ? undefined : noteToPitchClass(bass),\n );\n chord.rootSpelling = { letter: root.letter, alter: root.alter };\n if (bass !== undefined) {\n chord.bassSpelling = { letter: bass.letter, alter: bass.alter };\n }\n return chord;\n}\n\n/**\n * Parse a lead-sheet chord symbol into a {@link Chord}.\n *\n * Accepts `<root><quality>[/<bass>]`, e.g. `Cmaj7`, `F#m7b5`, `Bb7`, `C/G`,\n * `C6/9`, `C6/9/E`. Root and bass letters are case-insensitive, matching\n * {@link parseNote}. A `/` is ambiguous between a slash bass and the `6/9`\n * quality; the text splits at the last `/` whose right-hand side is a valid\n * bare note preceded by a recognized quality, so `6/9` (with or without a\n * further slash bass) parses as a quality.\n *\n * The parsed chord carries the root/bass spellings as enharmonic hints\n * (`rootSpelling`/`bassSpelling`) so {@link formatChordSymbol} can reproduce\n * flat spellings such as `Bbmaj7` instead of respelling them with sharps.\n *\n * @param text The chord symbol text.\n * @returns The parsed chord.\n * @throws If the root or quality is not recognized.\n * @example\n * ```ts\n * import { parseChordSymbol, formatChordSymbol } from '@libraz/libcantus';\n * const c = parseChordSymbol('Bbmaj7');\n * formatChordSymbol(c); // 'Bbmaj7' — flat spelling is preserved on round-trip\n * ```\n * @category Chords\n */\nexport function parseChordSymbol(text: string): Chord {\n const trimmed = text\n .trim()\n .replaceAll('♯', '#')\n .replaceAll('♭', 'b')\n .replaceAll('𝄪', 'x')\n .replaceAll('𝄫', 'bb');\n const rootMatch = ROOT_RE.exec(trimmed);\n if (!rootMatch) {\n throw new InvalidInputError(`Invalid chord symbol: ${text}`);\n }\n const rootToken = rootMatch[0];\n const rootNote = parseNote(rootToken);\n const rest = trimmed.slice(rootToken.length);\n\n // Split at the last '/' so the '6/9' quality's own slash never masks a\n // trailing slash bass (e.g. 'C6/9/E').\n const slashIndex = rest.lastIndexOf('/');\n if (slashIndex !== -1) {\n const before = rest.slice(0, slashIndex);\n const after = rest.slice(slashIndex + 1);\n const beforeQuality = knownQuality(before);\n if (beforeQuality !== undefined && BASS_RE.test(after)) {\n return makeSpelledChord(rootNote, beforeQuality, parseNote(after));\n }\n }\n\n const quality = knownQuality(rest);\n if (quality === undefined) {\n throw new InvalidInputError(`Unrecognized chord quality: ${text}`);\n }\n return makeSpelledChord(rootNote, quality);\n}\n\n/**\n * Name a pitch class, preferring a spelling hint when it is still valid.\n *\n * A hint is used only when no explicit sharp/flat preference was given and the\n * hint still resolves to `pc` (a stale hint left over after transposition is\n * ignored). Otherwise the pitch class is respelled from the requested table,\n * falling back to `inheritFlats` so an unhinted slash bass follows the side its\n * root was spelled on rather than flipping to sharps inside one symbol.\n */\nfunction pitchClassName(\n pc: number,\n hint: PitchSpelling | undefined,\n flats?: boolean,\n inheritFlats?: boolean,\n): string {\n if (flats === undefined && hint !== undefined && noteToPitchClass(hint) === pc) {\n return formatNote(hint);\n }\n const note = midiToNote(60 + pc, (flats ?? inheritFlats) ? 'flat' : 'sharp');\n return formatNote({ letter: note.letter, alter: note.alter });\n}\n\n/**\n * Format a {@link Chord} as a lead-sheet chord symbol.\n *\n * The inverse of {@link parseChordSymbol}: each quality maps to one canonical\n * suffix spelling, and a slash bass is appended only when it differs from the\n * root. When the chord carries `rootSpelling`/`bassSpelling` hints (as chords\n * from {@link parseChordSymbol} do) and no explicit `flats` preference is\n * given, the hints are reused so flat symbols round-trip unchanged.\n *\n * @param chord The chord to format.\n * @param opts Formatting options.\n * @param opts.flats Prefer flat spellings over sharps for altered roots/basses;\n * passing either `true` or `false` overrides any spelling hints.\n * @returns The chord symbol text.\n * @example\n * ```ts\n * import { makeChord, formatChordSymbol } from '@libraz/libcantus';\n * formatChordSymbol(makeChord(0, 'min7')); // 'Cm7'\n * ```\n * @category Chords\n */\nexport function formatChordSymbol(chord: Chord, opts?: { flats?: boolean }): string {\n // A chord rebuilt from JSON or arrived at through arithmetic can carry a\n // non-finite root or a quality this module has no suffix for; formatting\n // those produces 'C' and 'Cundefined', which read as real symbols.\n assertFiniteNumber(chord.rootPc, 'chord rootPc');\n if (chord.bassPc !== undefined) {\n assertFiniteNumber(chord.bassPc, 'chord bassPc');\n }\n if (!Object.hasOwn(CANONICAL_SUFFIX, chord.quality)) {\n throw new InvalidInputError(`Unknown chord quality: ${String(chord.quality)}`);\n }\n const rootPc = pitchClass(chord.rootPc);\n const rootHint = chord.rootSpelling;\n const rootName = pitchClassName(rootPc, rootHint, opts?.flats);\n const suffix = CANONICAL_SUFFIX[chord.quality];\n let symbol = `${rootName}${suffix}`;\n if (chord.bassPc !== undefined && pitchClass(chord.bassPc) !== rootPc) {\n const inheritFlats =\n rootHint !== undefined && noteToPitchClass(rootHint) === rootPc\n ? rootHint.alter < 0\n : undefined;\n symbol += `/${pitchClassName(pitchClass(chord.bassPc), chord.bassSpelling, opts?.flats, inheritFlats)}`;\n }\n return symbol;\n}\n\n/**\n * Transpose a chord symbol by a number of semitones.\n *\n * @param text The chord symbol text.\n * @param semitones Signed semitone offset to apply to the root and bass.\n * @param opts Formatting options for the result; see {@link formatChordSymbol}.\n * @returns The transposed chord symbol text.\n * @throws If `text` does not parse as a chord symbol.\n * @example\n * ```ts\n * import { transposeChordSymbol } from '@libraz/libcantus';\n * transposeChordSymbol('C/G', 2); // 'D/A'\n * ```\n * @category Chords\n */\nexport function transposeChordSymbol(\n text: string,\n semitones: number,\n opts?: { flats?: boolean },\n): string {\n assertFiniteSemitones(semitones);\n const chord = parseChordSymbol(text);\n const transposed: Chord = { ...chord, rootPc: pitchClass(chord.rootPc + semitones) };\n if (chord.rootSpelling !== undefined) {\n const hint = transposeNote(chord.rootSpelling, semitones);\n transposed.rootSpelling = { letter: hint.letter, alter: hint.alter };\n }\n if (chord.bassPc !== undefined) {\n transposed.bassPc = pitchClass(chord.bassPc + semitones);\n }\n if (chord.bassSpelling !== undefined) {\n const hint = transposeNote(chord.bassSpelling, semitones);\n transposed.bassSpelling = { letter: hint.letter, alter: hint.alter };\n }\n return formatChordSymbol(transposed, opts);\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\n\nexport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\n\nimport { NoSolutionError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses, chordToneRole } from '../chord/index.js';\nimport { createsParallelPerfect, createsVoiceOverlap } from '../counterpoint/index.js';\nimport type { VoiceRange } from './satb.js';\n\n/** Default maximum spacing between adjacent upper voices (one octave). */\nexport const DEFAULT_MAX_SPACING = 12;\n\n/** Score penalty per counterpoint violation between consecutive voicings. */\nexport const VIOLATION_PENALTY = 1000;\n/** Score penalty per chord tone absent from a voicing. */\nconst MISSING_TONE_PENALTY = 500;\n/**\n * Score penalty for omitting the chord's fifth. Far milder than any other\n * missing tone: the fifth carries no identity, and the textbook resolution of a\n * complete dominant seventh onto a tonic triad — every tendency tone resolving\n * inward — leaves the tonic without its fifth. Weighted below\n * {@link RESOLUTION_PENALTY} so a resolution can buy the omission, and above\n * {@link POOR_DOUBLING_PENALTY} so nothing else can.\n */\nconst MISSING_FIFTH_PENALTY = 20;\n/** Score penalty per doubled tone that is neither the root nor the fifth. */\nconst POOR_DOUBLING_PENALTY = 4;\n/** Penalty for adjacent voices sharing exactly the same MIDI pitch. */\nconst UNISON_PENALTY = 100;\n/**\n * Score penalty for doubling the key's leading tone. Doubling it guarantees\n * parallel octaves on its resolution, so it is a rule violation rather than a\n * matter of taste — weighted far above {@link POOR_DOUBLING_PENALTY}, but below\n * a missing chord tone, which damages the chord itself.\n */\nconst LEADING_TONE_DOUBLING_PENALTY = 200;\n/**\n * Score penalty per tendency tone that fails to resolve. Well above the few\n * semitones of extra motion a correct resolution usually costs, so smoothness\n * can never buy an unresolved seventh, and well below\n * {@link VIOLATION_PENALTY}, so avoiding parallels still comes first.\n */\nexport const RESOLUTION_PENALTY = 200;\n/** Hard cap on candidate voicings evaluated per chord, keeping the search bounded. */\nconst DEFAULT_MAX_CANDIDATES = 4000;\n/**\n * Hard cap on search-tree nodes visited per chord. The candidate cap alone only\n * counts completed voicings, so a search whose upper voices admit no chord tone\n * would expand the whole cartesian product without ever reaching a leaf.\n */\nconst SEARCH_NODES_PER_CANDIDATE = 16;\n\n/**\n * All MIDI pitches of a pitch class inside an inclusive range, ordered from the\n * centre of the range outward (ties break toward the lower pitch). Enumerating\n * centre-outward keeps the candidate set balanced around the register when it\n * is truncated at {@link MAX_CANDIDATES}, instead of skewing to the low octaves\n * that a plain ascending scan would visit first.\n */\nfunction pitchesForPc(pc: number, range: VoiceRange): number[] {\n const result: number[] = [];\n for (let midi = Math.ceil(range.min); midi <= range.max; midi += 1) {\n if (pitchClass(midi) === pc) {\n result.push(midi);\n }\n }\n const center = (range.min + range.max) / 2;\n return result.sort((a, b) => {\n const da = Math.abs(a - center);\n const db = Math.abs(b - center);\n return da === db ? a - b : da - db;\n });\n}\n\n/**\n * Enumerate candidate voicings for a chord: the bass takes the chord's bass\n * (or root) pitch class at each available octave, and each upper voice takes\n * any chord pitch class within its range. Candidates are built in ascending\n * voice order and pruned to exclude voice crossings and over-wide adjacent\n * spacing (the bass–tenor pair is allowed an extra octave, per convention).\n * Enumeration is deterministic and capped at `maxCandidates` completed\n * voicings and a proportional number of visited nodes.\n *\n * Each voice's admissible pitches are resolved once up front rather than\n * recomputed at every node, and a voice that admits no chord tone at all fails\n * immediately: continuing would explore every combination of the voices below\n * it only to reach no leaf.\n *\n * @throws If any voice's range contains no pitch of any chord tone.\n */\nexport function enumerateVoicings(\n chord: Chord,\n ranges: VoiceRange[],\n maxSpacing: number,\n maxCandidates = DEFAULT_MAX_CANDIDATES,\n): number[][] {\n const chordPcs = chordPitchClasses(chord);\n const bassPc = pitchClass(chord.bassPc ?? chord.rootPc);\n // Pitch-class-major order, matching the enumeration order of the search.\n const byVoice = ranges.map((range, voice) =>\n (voice === 0 ? [bassPc] : chordPcs).flatMap((pc) => pitchesForPc(pc, range)),\n );\n for (let voice = 0; voice < byVoice.length; voice += 1) {\n if ((byVoice[voice] ?? []).length === 0) {\n throw new NoSolutionError('no voicing satisfies the given ranges');\n }\n }\n const results: number[][] = [];\n const current: number[] = [];\n let nodes = 0;\n const maxSearchNodes = maxCandidates * SEARCH_NODES_PER_CANDIDATE;\n const build = (voice: number): void => {\n nodes += 1;\n if (results.length >= maxCandidates || nodes > maxSearchNodes) {\n return;\n }\n if (voice === ranges.length) {\n results.push([...current]);\n return;\n }\n const prev = current[voice - 1];\n for (const pitch of byVoice[voice] ?? []) {\n if (prev !== undefined) {\n if (pitch < prev) {\n continue; // would cross below the next lower voice\n }\n const spacingLimit = voice === 1 ? maxSpacing + 12 : maxSpacing;\n if (pitch - prev > spacingLimit) {\n continue;\n }\n }\n current.push(pitch);\n build(voice + 1);\n current.pop();\n }\n };\n build(0);\n return results;\n}\n\n/**\n * Structural quality penalty of a single voicing: heavily penalize missing\n * chord tones (mildly for the fifth, which carries no identity), mildly\n * penalize doubling anything other than the root or fifth, and — when a key is\n * known — heavily penalize doubling its leading tone.\n *\n * The exempt fifth is the chord's own fifth, whatever its size: a diminished,\n * augmented, or absent fifth would leave the root as the only freely doubled\n * tone, which is what forces a doubled leading tone in a `viio` chord.\n */\nexport function structuralPenalty(pitches: number[], chord: Chord, key?: KeyScale): number {\n const counts = new Map<number, number>();\n for (const pitch of pitches) {\n const pc = pitchClass(pitch);\n counts.set(pc, (counts.get(pc) ?? 0) + 1);\n }\n let penalty = 0;\n for (let index = 1; index < pitches.length; index += 1) {\n if (pitches[index] === pitches[index - 1]) {\n penalty += UNISON_PENALTY;\n }\n }\n for (const pc of chordPitchClasses(chord)) {\n if (!counts.has(pc)) {\n penalty +=\n chordToneRole(pc, chord) === 'fifth' ? MISSING_FIFTH_PENALTY : MISSING_TONE_PENALTY;\n }\n }\n const rootPc = pitchClass(chord.rootPc);\n const leadingTonePc = key === undefined ? undefined : pitchClass(key.rootPc - 1);\n for (const [pc, count] of counts) {\n if (count <= 1) {\n continue;\n }\n if (pc === leadingTonePc) {\n penalty += (count - 1) * LEADING_TONE_DOUBLING_PENALTY;\n continue;\n }\n if (pc !== rootPc && chordToneRole(pc, chord) !== 'fifth') {\n penalty += (count - 1) * POOR_DOUBLING_PENALTY;\n }\n }\n return penalty;\n}\n\n/** The chord's own seventh as a pitch class, or undefined when it has none. */\nfunction seventhPcOf(chord: Chord): number | undefined {\n for (const interval of chord.intervals) {\n const pc = pitchClass(chord.rootPc + interval);\n if (chordToneRole(pc, chord) === 'seventh') {\n return pc;\n }\n }\n return undefined;\n}\n\n/**\n * Count the tendency tones that fail to resolve between two consecutive\n * voicings of the same progression.\n *\n * A chordal seventh is a dissonance: unless the next chord holds it as a common\n * tone, the voice carrying it must fall by step. A leading tone must rise to\n * the tonic whenever the next chord contains one; without a key there is no\n * leading tone to speak of, so that half of the rule is skipped.\n */\nexport function resolutionViolations(\n prev: number[],\n cur: number[],\n prevChord: Chord,\n nextChord: Chord,\n key?: KeyScale,\n): number {\n const seventhPc = seventhPcOf(prevChord);\n const leadingTonePc = key === undefined ? undefined : pitchClass(key.rootPc - 1);\n const tonicPc = key === undefined ? undefined : pitchClass(key.rootPc);\n const nextPcs = new Set(chordPitchClasses(nextChord));\n let count = 0;\n for (let voice = 0; voice < prev.length && voice < cur.length; voice += 1) {\n const from = prev[voice];\n const to = cur[voice];\n if (from === undefined || to === undefined) {\n continue;\n }\n const fromPc = pitchClass(from);\n const motion = to - from;\n if (fromPc === seventhPc && !nextPcs.has(fromPc) && motion !== -1 && motion !== -2) {\n count += 1;\n }\n if (\n fromPc === leadingTonePc &&\n tonicPc !== undefined &&\n nextPcs.has(tonicPc) &&\n !nextPcs.has(fromPc) &&\n motion !== 1\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n/**\n * Count counterpoint violations between two consecutive voicings of equal\n * length: parallel perfects (fifths and octaves alike, since octaves are the\n * perfect-class-zero case of {@link createsParallelPerfect}) on every voice\n * pair, and voice overlaps on adjacent pairs.\n *\n * Voice crossing and over-wide spacing are not counted: {@link\n * enumerateVoicings} rejects both while building a candidate, so a candidate\n * that reaches here cannot exhibit either. Overlap involves the previous\n * voicing and so is still live.\n */\nexport function violationCount(prev: number[], cur: number[]): number {\n let count = 0;\n for (let lower = 0; lower < cur.length; lower += 1) {\n for (let upper = lower + 1; upper < cur.length; upper += 1) {\n const prevLower = prev[lower];\n const prevUpper = prev[upper];\n const curLower = cur[lower];\n const curUpper = cur[upper];\n if (\n prevLower === undefined ||\n prevUpper === undefined ||\n curLower === undefined ||\n curUpper === undefined\n ) {\n continue;\n }\n if (createsParallelPerfect(prevUpper, curUpper, prevLower, curLower)) {\n count += 1;\n }\n if (upper === lower + 1) {\n if (createsVoiceOverlap(prevUpper, curUpper, prevLower, curLower)) {\n count += 1;\n }\n }\n }\n }\n return count;\n}\n","import { InvalidInputError, NoSolutionError } from '../../core/errors/index.js';\nimport { assertFiniteNumber } from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { createsHiddenParallelPerfect } from '../counterpoint/index.js';\nimport {\n enumerateVoicings,\n RESOLUTION_PENALTY,\n resolutionViolations,\n structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport type { VoicingOptions } from './satb.js';\nimport { resolveMaxCandidates, resolveMaxSpacing, resolveRanges } from './satb.js';\n\n/**\n * Moderate penalty for a hidden/direct perfect fifth or octave reached on the\n * outer-voice (bass–soprano) pair. Unlike a true parallel perfect it is\n * discouraged rather than forbidden, so the weight sits alongside voice-leading\n * motion rather than the hard {@link VIOLATION_PENALTY}.\n */\nconst HIDDEN_PERFECT_PENALTY = 6;\n\n/**\n * Total voice-leading cost between two voicings: the sum of absolute semitone\n * motion across voices, plus a moderate `HIDDEN_PERFECT_PENALTY` when the\n * outer-voice (bass–soprano) pair reaches a hidden/direct perfect fifth or\n * octave by similar motion. The arrays must be the same length; when they\n * differ the voicings are not comparable and the cost is `Infinity`.\n *\n * @param from The previous voicing, one MIDI pitch per voice.\n * @param to The next voicing, one MIDI pitch per voice.\n * @returns The summed absolute motion (plus any hidden-perfect penalty), or\n * `Infinity` when lengths differ.\n * @category Voicing & Counterpoint\n */\nexport function voiceLeadingCost(from: number[], to: number[]): number {\n if (from.length !== to.length) {\n return Number.POSITIVE_INFINITY;\n }\n let total = 0;\n for (let i = 0; i < from.length; i += 1) {\n const a = from[i];\n const b = to[i];\n if (a === undefined || b === undefined) {\n continue;\n }\n total += Math.abs(b - a);\n }\n // Discourage hidden/direct perfects between the outermost voices, where they\n // are most audible. True parallels are handled (and forbidden) elsewhere.\n if (from.length >= 2) {\n const bassPrev = from[0];\n const bassCur = to[0];\n const sopPrev = from[from.length - 1];\n const sopCur = to[to.length - 1];\n if (\n bassPrev !== undefined &&\n bassCur !== undefined &&\n sopPrev !== undefined &&\n sopCur !== undefined &&\n createsHiddenParallelPerfect(bassPrev, bassCur, sopPrev, sopCur)\n ) {\n total += HIDDEN_PERFECT_PENALTY;\n }\n }\n return total;\n}\n\n/**\n * Voice a single chord to follow smoothly from an arbitrary current voicing.\n *\n * Candidate voicings of `chord` are enumerated within ranges taken from `opts`,\n * or — when neither `voices` nor `ranges` is given — from a one-octave window\n * around each pitch of `current` (so the result matches `current`'s voice\n * count). Each candidate is scored by structural quality, voice-leading motion\n * from `current`, and a large penalty per counterpoint violation; the lowest\n * scoring candidate is returned, ascending.\n *\n * @param current The current voicing to lead from, ascending (index 0 = lowest).\n * @param chord The next chord to voice.\n * @param opts Voicing options; when omitted, ranges follow `current`'s span.\n * @returns The chosen voicing, ascending with one MIDI pitch per voice.\n * @throws If `current` is empty or holds a non-finite pitch while the ranges\n * are derived from it, or if no voicing fits the ranges.\n * @category Voicing & Counterpoint\n */\nexport function nextVoicing(current: number[], chord: Chord, opts?: VoicingOptions): number[] {\n const derived = opts?.ranges === undefined && opts?.voices === undefined;\n if (derived) {\n // The ranges are read off `current`, so an empty voicing would describe a\n // zero-voice chord and return an empty result — the same input that throws\n // when written as an explicit `{ ranges: [] }`.\n if (current.length === 0) {\n throw new InvalidInputError('current must contain at least one pitch');\n }\n for (let index = 0; index < current.length; index += 1) {\n assertFiniteNumber(current[index] ?? Number.NaN, `current[${index}]`);\n }\n }\n const ranges = derived\n ? current.map((pitch) => {\n // Window one octave around each current pitch, clamped so extreme-low\n // or extreme-high input can never yield MIDI outside [0, 127].\n const centre = Math.min(127, Math.max(0, pitch));\n return { min: Math.max(0, centre - 12), max: Math.min(127, centre + 12) };\n })\n : resolveRanges(opts);\n for (let index = 0; index < current.length; index += 1) {\n assertFiniteNumber(current[index] ?? Number.NaN, `current[${index}]`);\n }\n if (current.length < ranges.length) {\n throw new InvalidInputError(\n `current has ${current.length} voices but the requested ranges require ${ranges.length}`,\n );\n }\n // Thinning an existing texture is a normal use case (SATB to three voices,\n // for example). Keep the outer voices and select evenly spaced inner voices\n // so motion is still measured between like voice roles.\n const source =\n current.length === ranges.length\n ? current\n : ranges.length === 1\n ? [current[0] ?? Number.NaN]\n : ranges.map((_, index) => {\n const sourceIndex = Math.round((index * (current.length - 1)) / (ranges.length - 1));\n return current[sourceIndex] ?? Number.NaN;\n });\n const maxSpacing = resolveMaxSpacing(opts);\n const candidates = enumerateVoicings(chord, ranges, maxSpacing, resolveMaxCandidates(opts));\n const previousChord = opts?.previousChord;\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n const score =\n structuralPenalty(candidate, chord, opts?.key) +\n voiceLeadingCost(source, candidate) +\n VIOLATION_PENALTY * violationCount(source, candidate) +\n RESOLUTION_PENALTY *\n (previousChord === undefined\n ? 0\n : resolutionViolations(source, candidate, previousChord, chord, opts?.key));\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new NoSolutionError('no voicing satisfies the given ranges');\n }\n return best;\n}\n","import { InvalidInputError, NoSolutionError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertPositiveInt,\n} from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { formatChordSymbol } from '../symbol/index.js';\nimport {\n DEFAULT_MAX_SPACING,\n enumerateVoicings,\n RESOLUTION_PENALTY,\n resolutionViolations,\n structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport { voiceLeadingCost } from './leading.js';\n\n/**\n * An inclusive MIDI pitch range for a single voice.\n *\n * @category Voicing & Counterpoint\n */\nexport type VoiceRange = {\n readonly min: number;\n readonly max: number;\n};\n\n/**\n * Default four-voice SATB ranges, ascending (index 0 = lowest):\n * bass E2–C4 (40–60), tenor C3–G4 (48–67), alto G3–D5 (55–74),\n * soprano C4–G5 (60–79).\n *\n * @category Voicing & Counterpoint\n */\nexport const SATB_RANGES: readonly Readonly<VoiceRange>[] = Object.freeze([\n Object.freeze({ min: 40, max: 60 }),\n Object.freeze({ min: 48, max: 67 }),\n Object.freeze({ min: 55, max: 74 }),\n Object.freeze({ min: 60, max: 79 }),\n]);\n\n/**\n * Options controlling {@link voiceChord} and {@link voiceProgression}.\n *\n * @category Voicing & Counterpoint\n */\nexport type VoicingOptions = {\n /**\n * Number of voices to realize. Ignored when `ranges` is given.\n *\n * @defaultValue 4\n */\n voices?: number;\n /** Explicit per-voice ranges, ascending (index 0 = lowest). Takes precedence over `voices`. */\n ranges?: VoiceRange[];\n /**\n * Maximum spacing in semitones between adjacent upper voices.\n *\n * @defaultValue 12\n */\n maxSpacing?: number;\n /**\n * Maximum candidate voicings evaluated for one chord.\n *\n * Raise this for large voicings when exact optimum matters more than bounded\n * search time.\n *\n * @defaultValue 4000\n */\n maxCandidates?: number;\n /**\n * Maximum number of chords {@link voiceProgression} will voice. The search\n * per chord is bounded internally, so the cost of a progression is linear in\n * its length; this is the guard against an unbounded caller, not a limit on\n * the search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n /**\n * The prevailing key. Supplying it enables the rules that only make sense\n * relative to a tonic: the leading tone is neither doubled nor left\n * unresolved. Without it, voicings are chosen from chord structure and\n * voice-leading distance alone.\n */\n key?: KeyScale;\n /**\n * The chord that produced the current voicing passed to {@link nextVoicing}.\n * When supplied, chordal-seventh resolution is scored exactly as it is by\n * {@link voiceProgression}.\n */\n previousChord?: Chord;\n};\n\n/** Overall pitch floor/ceiling used when deriving ranges for arbitrary voice counts. */\nconst DERIVED_LOW = 40;\nconst DERIVED_HIGH = 79;\n/** Span of each derived per-voice range in semitones. */\nconst DERIVED_SPAN = 19;\n\n/**\n * Resolve the per-voice ranges implied by the options: explicit `ranges` win,\n * four voices use {@link SATB_RANGES}, and other counts get evenly spaced\n * ranges spanning roughly the bass-to-soprano compass.\n */\nexport function resolveRanges(opts?: VoicingOptions): VoiceRange[] {\n if (opts?.ranges !== undefined) {\n if (opts.ranges.length === 0) {\n throw new InvalidInputError('ranges must contain at least one voice range');\n }\n assertGenerationBudget(opts.ranges.length, 'voice ranges', 128);\n return opts.ranges.map((range, index) => {\n assertFiniteNumber(range.min, `ranges[${index}].min`);\n assertFiniteNumber(range.max, `ranges[${index}].max`);\n if (range.min > range.max) {\n throw new InvalidInputError(`ranges[${index}].min must not exceed max`);\n }\n assertGenerationBudget(\n Math.floor(range.max) - Math.ceil(range.min) + 1,\n `ranges[${index}] span`,\n 4096,\n );\n return { ...range };\n });\n }\n const voices = opts?.voices ?? 4;\n assertPositiveInt(voices, 'voices', 128);\n if (voices === 4) {\n return SATB_RANGES.map((range) => ({ ...range }));\n }\n if (voices === 1) {\n return [{ min: DERIVED_LOW, max: DERIVED_HIGH }];\n }\n const ranges: VoiceRange[] = [];\n for (let i = 0; i < voices; i += 1) {\n const min = Math.round(\n DERIVED_LOW + (i * (DERIVED_HIGH - DERIVED_SPAN - DERIVED_LOW)) / (voices - 1),\n );\n ranges.push({ min, max: min + DERIVED_SPAN });\n }\n return ranges;\n}\n\n/**\n * Resolve and validate the adjacent-voice spacing limit. Shared by every entry\n * point so `nextVoicing` cannot accept a NaN that silently disables the spacing\n * constraint, nor a negative value that reports itself as an impossible range.\n */\nexport function resolveMaxSpacing(opts?: VoicingOptions): number {\n const maxSpacing = opts?.maxSpacing ?? DEFAULT_MAX_SPACING;\n assertFiniteNumber(maxSpacing, 'maxSpacing');\n if (maxSpacing < 0) {\n throw new InvalidInputError('maxSpacing must be non-negative');\n }\n return maxSpacing;\n}\n\n/** Resolve and validate the per-chord candidate-search cap. */\nexport function resolveMaxCandidates(opts?: VoicingOptions): number | undefined {\n if (opts?.maxCandidates === undefined) return undefined;\n assertPositiveInt(opts.maxCandidates, 'maxCandidates', 1_000_000);\n return opts.maxCandidates;\n}\n\n/**\n * Realize a single chord as one MIDI pitch per voice, ascending (index 0 =\n * lowest). The bass voice takes the chord's `bassPc` when set, otherwise the\n * root; upper voices take chord pitch classes, doubling the root or fifth as\n * needed to fill all voices. The result stays inside each voice's range, keeps\n * adjacent upper voices within `maxSpacing`, avoids voice crossing, and is\n * deterministic: a compact close-position voicing centered in the ranges.\n *\n * @param chord The chord to voice.\n * @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.\n * @returns MIDI pitches, ascending, one per voice.\n * @throws If no voicing fits the given ranges.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceChord } from '@libraz/libcantus';\n * const chord = parseChordSymbol('Cmaj7');\n * voiceChord(chord); // four ascending MIDI pitches within the SATB ranges\n * ```\n * @category Voicing & Counterpoint\n */\nexport function voiceChord(chord: Chord, opts?: VoicingOptions): number[] {\n const ranges = resolveRanges(opts);\n const maxSpacing = resolveMaxSpacing(opts);\n const candidates = enumerateVoicings(chord, ranges, maxSpacing, resolveMaxCandidates(opts));\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n let score = structuralPenalty(candidate, chord, opts?.key);\n for (let i = 0; i < candidate.length; i += 1) {\n const pitch = candidate[i];\n const range = ranges[i];\n if (pitch === undefined || range === undefined) {\n continue;\n }\n // Prefer pitches near the middle of each voice's range for a centered,\n // compact default voicing.\n score += Math.abs(pitch - (range.min + range.max) / 2);\n }\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new NoSolutionError(\n `no voicing satisfies the given ranges for ${formatChordSymbol(chord)}`,\n );\n }\n return best;\n}\n\n/**\n * Voice a chord progression with smooth voice leading. The first chord is\n * voiced with {@link voiceChord}; each subsequent chord picks, from a bounded\n * deterministic candidate set, the voicing minimizing the voice-leading cost\n * from the previous voicing plus a large penalty per counterpoint violation\n * (parallel perfects/octaves, voice crossing, voice overlap, and over-wide\n * upper-voice spacing).\n *\n * Tendency tones are resolved rather than merely moved economically: the voice\n * holding a chordal seventh falls by step unless the next chord keeps that tone,\n * and — when `opts.key` is given — the leading tone rises to the tonic and is\n * never doubled. Without a key the leading-tone rules cannot apply, since\n * nothing identifies which pitch class is the leading tone.\n *\n * @param chords The chords to voice in order.\n * @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.\n * @returns One voicing per chord, each ascending with one MIDI pitch per voice.\n * @throws If any chord admits no voicing within the given ranges, or if the\n * progression is longer than `opts.budget` chords.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceProgression } from '@libraz/libcantus';\n * const chords = ['C', 'Am', 'F', 'G'].map((s) => parseChordSymbol(s));\n * voiceProgression(chords); // one four-voice voicing per chord, smoothly led\n * ```\n * @category Voicing & Counterpoint\n */\n/** The unsatisfiable-constraints error for one chord of a progression. */\nfunction noSolutionAt(index: number, chord: Chord): NoSolutionError {\n return new NoSolutionError(\n `no voicing satisfies the given ranges for ${formatChordSymbol(chord)} at index ${index}`,\n { at: index },\n );\n}\n\n/**\n * Run one chord's work, re-raising an unsatisfiable-constraints failure with\n * the chord it happened on. Voicing a whole lead sheet is otherwise told only\n * that some chord did not fit.\n */\nfunction locate<T>(index: number, chord: Chord, work: () => T): T {\n try {\n return work();\n } catch (error) {\n if (error instanceof NoSolutionError) {\n throw noSolutionAt(index, chord);\n }\n throw error;\n }\n}\n\n/**\n * Voice a chord progression with smooth, bounded SATB-style leading.\n *\n * Each chord is chosen from a deterministic candidate set, minimizing motion\n * while penalizing counterpoint violations. A supplied key additionally\n * resolves leading tones and chordal sevenths.\n *\n * @param chords The chords to voice in order.\n * @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.\n * @returns One ascending MIDI voicing per chord.\n * @throws {@link NoSolutionError} with the failing chord index when a chord\n * cannot fit the requested ranges.\n * @category Voicing & Counterpoint\n */\nexport function voiceProgression(chords: readonly Chord[], opts?: VoicingOptions): number[][] {\n assertGenerationBudget(chords.length, 'voiced progression chords', opts?.budget);\n const ranges = resolveRanges(opts);\n const maxSpacing = resolveMaxSpacing(opts);\n const maxCandidates = resolveMaxCandidates(opts);\n const result: number[][] = [];\n let prev: number[] | undefined;\n let prevChord: Chord | undefined;\n for (let index = 0; index < chords.length; index += 1) {\n const chord = chords[index];\n if (chord === undefined) {\n continue;\n }\n if (prev === undefined) {\n prev = locate(index, chord, () => voiceChord(chord, opts));\n prevChord = chord;\n result.push(prev);\n continue;\n }\n const candidates = locate(index, chord, () =>\n enumerateVoicings(chord, ranges, maxSpacing, maxCandidates),\n );\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n const unresolved =\n prevChord === undefined\n ? 0\n : resolutionViolations(prev, candidate, prevChord, chord, opts?.key);\n const score =\n structuralPenalty(candidate, chord, opts?.key) +\n voiceLeadingCost(prev, candidate) +\n VIOLATION_PENALTY * violationCount(prev, candidate) +\n RESOLUTION_PENALTY * unresolved;\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw noSolutionAt(index, chord);\n }\n result.push(best);\n prev = best;\n prevChord = chord;\n }\n return result;\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { assertFiniteNumber, assertInteger } from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordToneRole } from '../chord/index.js';\nimport { pitchClass } from './internal.js';\n\n/**\n * A tertian voicing style for {@link voiceChordStyled}.\n *\n * - `close`: the plain close-position tertian stack.\n * - `drop2`: the second voice from the top dropped an octave (drop-2 voicing).\n * - `drop3`: the third voice from the top dropped an octave (drop-3 voicing).\n * - `shell`: root plus guide tones (third and seventh) for seventh chords, or\n * root/third/fifth for triads; the fifth and tensions are omitted.\n * - `rootless`: the root omitted, keeping third/fifth/seventh and tensions\n * (a typical left-hand jazz voicing).\n *\n * @category Voicing & Counterpoint\n */\nexport type VoicingStyle = 'close' | 'drop2' | 'drop3' | 'shell' | 'rootless';\n\n/**\n * Options controlling {@link voiceChordStyled}.\n *\n * @category Voicing & Counterpoint\n */\nexport type StyledVoicingOptions = {\n /**\n * Voicing style to build.\n *\n * @defaultValue 'close'\n */\n style?: VoicingStyle;\n /** Constrain the highest voice to this pitch class (0..11) when given. */\n topNote?: number;\n /**\n * Base octave in scientific pitch notation: octave 4 starts the stack near\n * middle C (MIDI 60), i.e. near `12 * (octave + 1)`.\n *\n * @defaultValue 4\n */\n octave?: number;\n /**\n * Omit the root regardless of style (relevant for jazz voicings).\n *\n * @defaultValue false\n */\n rootless?: boolean;\n};\n\n/** Default base octave for a styled close-position stack. */\nconst DEFAULT_STYLE_OCTAVE = 4;\n\n/** Return the pitch classes of a list in order, without duplicates. */\nfunction dedupePcs(pcs: number[]): number[] {\n const seen = new Set<number>();\n const result: number[] = [];\n for (const pc of pcs) {\n if (!seen.has(pc)) {\n seen.add(pc);\n result.push(pc);\n }\n }\n return result;\n}\n\n/** The lowest MIDI pitch of a pitch class at or above a floor. */\nfunction lowestPitchAtOrAbove(pc: number, floor: number): number {\n return floor + ((((pc - pitchClass(floor)) % 12) + 12) % 12);\n}\n\n/** The pitch class in `pcs` whose circular distance to `target` is smallest. */\nfunction nearestPc(target: number, pcs: number[]): number {\n let best = pcs[0] ?? target;\n let bestDist = 12;\n for (const pc of pcs) {\n const dist = Math.min((((pc - target) % 12) + 12) % 12, (((target - pc) % 12) + 12) % 12);\n if (dist < bestDist) {\n bestDist = dist;\n best = pc;\n }\n }\n return best;\n}\n\n/**\n * Realize a single chord as an explicit tertian voicing in a chosen style,\n * independent of the SATB range search used by {@link voiceChord}.\n *\n * The chord tones are stacked in close position from the bass (`bassPc`, else\n * `rootPc`) in the given scientific-pitch `octave` upward (octave 4 starts the\n * bass near C4 = MIDI 60, matching {@link midiToNote} and the bass module).\n * When `topNote` is given the stack is rotated so the highest voice's pitch\n * class equals that note (or the nearest chord tone). The chosen\n * {@link VoicingStyle} then transforms the stack, and the result is returned as\n * ascending MIDI pitches (index 0 = lowest).\n *\n * Every returned pitch is a valid MIDI number: an octave whose stack would run\n * off either end of the 0..127 range is rejected rather than voiced out of\n * range, matching {@link nextVoicing}, which clamps its derived ranges.\n *\n * @param chord The chord to voice.\n * @param opts Styled voicing options; defaults to a close voicing at octave 4.\n * @returns MIDI pitches, ascending, one per retained voice.\n * @throws If the voicing would not fit inside MIDI 0..127 at the given octave.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceChordStyled } from '@libraz/libcantus';\n * const chord = parseChordSymbol('Dm7');\n * voiceChordStyled(chord, { style: 'drop2' }); // ascending MIDI pitches, drop-2 voicing\n * ```\n * @category Voicing & Counterpoint\n */\nexport function voiceChordStyled(chord: Chord, opts?: StyledVoicingOptions): number[] {\n const style = opts?.style ?? 'close';\n const octave = opts?.octave ?? DEFAULT_STYLE_OCTAVE;\n assertInteger(octave, 'styled voicing octave', -10, 10);\n if (opts?.topNote !== undefined) {\n assertFiniteNumber(opts.topNote, 'styled voicing topNote');\n }\n const base = 12 * (octave + 1);\n const rootPc = pitchClass(chord.rootPc);\n const bassPc = pitchClass(chord.bassPc ?? chord.rootPc);\n const omitRoot = style === 'rootless' || opts?.rootless === true;\n\n // Tertian chord tones in order, with the bass tone rotated to the bottom.\n let order = dedupePcs(chord.intervals.map((interval) => pitchClass(chord.rootPc + interval)));\n const bassIndex = order.indexOf(bassPc);\n if (bassIndex > 0) {\n order = [...order.slice(bassIndex), ...order.slice(0, bassIndex)];\n } else if (bassIndex < 0) {\n order = [bassPc, ...order];\n }\n\n if (style === 'shell') {\n const roles = order.map((pc) => chordToneRole(pc, chord));\n // The guide tone is the seventh when present; on a sixth chord the sixth\n // stands in for it, and otherwise the voicing falls back to the fifth.\n const guide = roles.includes('seventh')\n ? 'seventh'\n : roles.includes('sixth')\n ? 'sixth'\n : 'fifth';\n order = order.filter((_pc, i) => {\n const role = roles[i];\n return _pc === bassPc || role === 'root' || role === 'third' || role === guide;\n });\n }\n\n if (omitRoot) {\n order = order.filter((pc) => pc !== rootPc || (chord.bassPc !== undefined && pc === bassPc));\n }\n\n if (order.length === 0) {\n return [];\n }\n\n if (opts?.topNote !== undefined) {\n const target = order.includes(pitchClass(opts.topNote))\n ? pitchClass(opts.topNote)\n : nearestPc(pitchClass(opts.topNote), order);\n const targetIndex = order.indexOf(target);\n order = [...order.slice(targetIndex + 1), ...order.slice(0, targetIndex + 1)];\n }\n\n // Stack the ordered pitch classes upward in close position.\n const stack: number[] = [];\n let prev: number | undefined;\n for (const pc of order) {\n if (prev === undefined) {\n prev = lowestPitchAtOrAbove(pc, base);\n } else {\n let delta = (((pc - pitchClass(prev)) % 12) + 12) % 12;\n if (delta === 0) {\n delta = 12;\n }\n prev += delta;\n }\n stack.push(prev);\n }\n\n if (style === 'drop2' && stack.length >= 2) {\n stack[stack.length - 2] = (stack[stack.length - 2] ?? 0) - 12;\n } else if (style === 'drop3' && stack.length >= 3) {\n stack[stack.length - 3] = (stack[stack.length - 3] ?? 0) - 12;\n }\n\n // An explicit slash bass is a structural requirement, including when it is\n // not a chord member. Drop voicings and top-note rotation may have moved a\n // different tone below it, so lower the retained bass by octaves before the\n // final sort. This gives the bass priority over a conflicting top-note hint.\n if (chord.bassPc !== undefined) {\n const bassIndexInStack = stack.findIndex((pitch) => pitchClass(pitch) === bassPc);\n if (bassIndexInStack >= 0) {\n const otherLowest = Math.min(\n ...stack.filter((_pitch, index) => index !== bassIndexInStack),\n Number.POSITIVE_INFINITY,\n );\n while ((stack[bassIndexInStack] ?? 0) >= otherLowest) {\n stack[bassIndexInStack] = (stack[bassIndexInStack] ?? 0) - 12;\n }\n }\n }\n\n const voicing = stack.sort((a, b) => a - b);\n const lowest = voicing[0] ?? 0;\n const highest = voicing[voicing.length - 1] ?? 0;\n if (lowest < 0 || highest > 127) {\n throw new InvalidInputError(\n `styled voicing at octave ${octave} spans MIDI ${lowest}..${highest}, outside 0..127`,\n );\n }\n return voicing;\n}\n"]}
|
|
@@ -21,7 +21,11 @@ var BudgetExceededError = (_class3 = class extends RangeError {constructor(...ar
|
|
|
21
21
|
__init6() {this.name = "BudgetExceededError"}
|
|
22
22
|
}, _class3);
|
|
23
23
|
function isLibcantusError(value) {
|
|
24
|
-
|
|
24
|
+
if (typeof value !== "object" || value === null || !("code" in value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const code = value.code;
|
|
28
|
+
return code === "INVALID_INPUT" || code === "NO_SOLUTION" || code === "BUDGET_EXCEEDED";
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
// src/core/meter/internal.ts
|
|
@@ -76,9 +80,23 @@ function assertInteger(value, name, min = Number.MIN_SAFE_INTEGER, max = Number.
|
|
|
76
80
|
}
|
|
77
81
|
return value;
|
|
78
82
|
}
|
|
83
|
+
function assertMidiPitch(value, name = "MIDI pitch") {
|
|
84
|
+
return assertInteger(value, name, 0, 127);
|
|
85
|
+
}
|
|
86
|
+
function clampToMidi(value, name = "MIDI pitch") {
|
|
87
|
+
assertInteger(value, name);
|
|
88
|
+
return Math.min(127, Math.max(0, value));
|
|
89
|
+
}
|
|
90
|
+
function assertDegree(value, name = "degree") {
|
|
91
|
+
return assertInteger(value, name, -1e3, 1e3);
|
|
92
|
+
}
|
|
93
|
+
function assertFiniteSemitones(value, name = "semitones") {
|
|
94
|
+
return assertFiniteNumber(value, name);
|
|
95
|
+
}
|
|
79
96
|
function dropSilentNotes(events) {
|
|
80
97
|
return events.filter((event) => event !== void 0 && event.durationBeat > 0);
|
|
81
98
|
}
|
|
99
|
+
var soundingNotesOnly = dropSilentNotes;
|
|
82
100
|
function assertOneOf(value, allowed, name) {
|
|
83
101
|
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
84
102
|
throw new InvalidInputError(
|
|
@@ -131,7 +149,7 @@ function assertTimeSignature(ts, name = "time signature") {
|
|
|
131
149
|
return ts;
|
|
132
150
|
}
|
|
133
151
|
function assertNoteEvent(event, name = "note event", options = {}) {
|
|
134
|
-
|
|
152
|
+
assertMidiPitch(event.pitch, `${name}.pitch`);
|
|
135
153
|
assertRange(event.startBeat, 0, Number.MAX_SAFE_INTEGER, `${name}.startBeat`);
|
|
136
154
|
assertFiniteNumber(event.durationBeat, `${name}.durationBeat`);
|
|
137
155
|
if (!options.allowNonPositiveDuration && event.durationBeat <= 0) {
|
|
@@ -152,7 +170,7 @@ function assertNoteEvent(event, name = "note event", options = {}) {
|
|
|
152
170
|
}
|
|
153
171
|
function assertNoteEvents(events, name = "note events", options = {}) {
|
|
154
172
|
if (!Array.isArray(events)) {
|
|
155
|
-
throw new
|
|
173
|
+
throw new InvalidInputError(`${name} must be an array; received ${typeof events}`);
|
|
156
174
|
}
|
|
157
175
|
assertGenerationBudget(events.length, `${name} count`, options.budget);
|
|
158
176
|
for (let index = 0; index < events.length; index += 1) {
|
|
@@ -183,6 +201,7 @@ function pitchClassOf(value) {
|
|
|
183
201
|
}
|
|
184
202
|
var mod12 = pitchClassOf;
|
|
185
203
|
function diatonicLetterOf(value) {
|
|
204
|
+
assertFiniteNumber(value, "diatonic letter");
|
|
186
205
|
return (Math.round(value) % 7 + 7) % 7;
|
|
187
206
|
}
|
|
188
207
|
function naturalPitchClassOf(letter) {
|
|
@@ -190,6 +209,9 @@ function naturalPitchClassOf(letter) {
|
|
|
190
209
|
}
|
|
191
210
|
var mod7 = diatonicLetterOf;
|
|
192
211
|
function parseNote(text) {
|
|
212
|
+
if (typeof text !== "string") {
|
|
213
|
+
throw new InvalidInputError(`note must be a string; received ${typeof text}`);
|
|
214
|
+
}
|
|
193
215
|
const match = /^([A-Ga-g])([#x]*|b*)(-?\d+)?$/.exec(text.trim());
|
|
194
216
|
if (!match) {
|
|
195
217
|
throw new InvalidInputError(`Invalid note: ${text}`);
|
|
@@ -286,8 +308,11 @@ function transposeNote(note, semitones, opts) {
|
|
|
286
308
|
if (_optionalChain([opts, 'optionalAccess', _ => _.spelling]) !== void 0) {
|
|
287
309
|
return note.octave === void 0 ? bareOf(midiToNote(60 + mod12(noteToPitchClass(note) + steps), opts.spelling)) : midiToNote(noteToMidi(note) + steps, opts.spelling);
|
|
288
310
|
}
|
|
289
|
-
const
|
|
290
|
-
const
|
|
311
|
+
const direction = steps < 0 ? -1 : 1;
|
|
312
|
+
const magnitude = Math.abs(steps);
|
|
313
|
+
const octaves = Math.floor(magnitude / 12);
|
|
314
|
+
const withinOctave = magnitude % 12;
|
|
315
|
+
const letterSteps = direction * ((_nullishCoalesce(LETTER_STEPS_BY_SEMITONE[withinOctave], () => ( 0))) + 7 * octaves);
|
|
291
316
|
const absoluteLetter = mod7(note.letter) + letterSteps;
|
|
292
317
|
const letter = mod7(absoluteLetter);
|
|
293
318
|
const natural = _nullishCoalesce(LETTER_SEMITONES[letter], () => ( 0));
|
|
@@ -354,7 +379,7 @@ function intervalSemitones(numberValue, quality) {
|
|
|
354
379
|
return reference + quality.length;
|
|
355
380
|
}
|
|
356
381
|
if (/^d+$/.test(quality)) {
|
|
357
|
-
return reference - quality.length - (perfect ? 0 : 1);
|
|
382
|
+
return Math.abs(reference - quality.length - (perfect ? 0 : 1));
|
|
358
383
|
}
|
|
359
384
|
throw new InvalidInputError(`unknown interval quality ${JSON.stringify(quality)}`);
|
|
360
385
|
}
|
|
@@ -371,9 +396,9 @@ function parseInterval(name) {
|
|
|
371
396
|
}
|
|
372
397
|
function transposeByInterval(note, interval) {
|
|
373
398
|
assertNote(note, "note");
|
|
374
|
-
assertInteger(interval.number, "interval.number", 1
|
|
399
|
+
assertInteger(interval.number, "interval.number", 1);
|
|
375
400
|
assertFiniteNumber(interval.semitones, "interval.semitones");
|
|
376
|
-
const descending = interval.semitones < 0;
|
|
401
|
+
const descending = _nullishCoalesce(interval.descending, () => ( interval.semitones < 0));
|
|
377
402
|
const letterSteps = (interval.number - 1) * (descending ? -1 : 1);
|
|
378
403
|
const absoluteLetter = mod7(note.letter) + letterSteps;
|
|
379
404
|
const letter = mod7(absoluteLetter);
|
|
@@ -417,7 +442,8 @@ function spelledInterval(a, b) {
|
|
|
417
442
|
const number = absSteps + 1;
|
|
418
443
|
const directedSpan = letterSteps === 0 ? Math.abs(semitones) : letterSteps > 0 ? semitones : -semitones;
|
|
419
444
|
const quality = qualityFromSpan(number, directedSpan);
|
|
420
|
-
|
|
445
|
+
const descending = letterSteps < 0 || letterSteps === 0 && semitones < 0;
|
|
446
|
+
return { number, quality, semitones, descending };
|
|
421
447
|
}
|
|
422
448
|
|
|
423
449
|
// src/core/interval/index.ts
|
|
@@ -428,8 +454,7 @@ var ConsonanceClass = /* @__PURE__ */ ((ConsonanceClass2) => {
|
|
|
428
454
|
return ConsonanceClass2;
|
|
429
455
|
})(ConsonanceClass || {});
|
|
430
456
|
function simpleInterval(semitones) {
|
|
431
|
-
|
|
432
|
-
return Math.abs(Math.round(semitones)) % 12;
|
|
457
|
+
return pitchClassOf(Math.abs(semitones));
|
|
433
458
|
}
|
|
434
459
|
function classifyInterval(semitones, twoVoice = true) {
|
|
435
460
|
const pc = simpleInterval(semitones);
|
|
@@ -494,5 +519,10 @@ function isConsonantInterval(semitones, twoVoice = true) {
|
|
|
494
519
|
|
|
495
520
|
|
|
496
521
|
|
|
497
|
-
|
|
498
|
-
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
exports.InvalidInputError = InvalidInputError; exports.NoSolutionError = NoSolutionError; exports.BudgetExceededError = BudgetExceededError; exports.isLibcantusError = isLibcantusError; exports.isCompoundNumerator = isCompoundNumerator; exports.isAdditiveReading = isAdditiveReading; exports.pulseCountOf = pulseCountOf; exports.pulseBeatsOf = pulseBeatsOf; exports.barBeatsOf = barBeatsOf; exports.DEFAULT_GENERATION_BUDGET = DEFAULT_GENERATION_BUDGET; exports.assertFiniteNumber = assertFiniteNumber; exports.assertInteger = assertInteger; exports.assertMidiPitch = assertMidiPitch; exports.clampToMidi = clampToMidi; exports.assertDegree = assertDegree; exports.assertFiniteSemitones = assertFiniteSemitones; exports.dropSilentNotes = dropSilentNotes; exports.soundingNotesOnly = soundingNotesOnly; exports.assertOneOf = assertOneOf; exports.assertPositiveInt = assertPositiveInt; exports.assertRange = assertRange; exports.assertGenerationBudget = assertGenerationBudget; exports.assertTimeSignature = assertTimeSignature; exports.assertNoteEvent = assertNoteEvent; exports.assertNoteEvents = assertNoteEvents; exports.pitchClassOf = pitchClassOf; exports.diatonicLetterOf = diatonicLetterOf; exports.naturalPitchClassOf = naturalPitchClassOf; exports.parseNote = parseNote; exports.formatNote = formatNote; exports.noteToPitchClass = noteToPitchClass; exports.noteToMidi = noteToMidi; exports.midiToNote = midiToNote; exports.transposeNote = transposeNote; exports.intervalSemitones = intervalSemitones; exports.parseInterval = parseInterval; exports.transposeByInterval = transposeByInterval; exports.spelledInterval = spelledInterval; exports.ConsonanceClass = ConsonanceClass; exports.classifyInterval = classifyInterval; exports.isPerfectInterval = isPerfectInterval; exports.isConsonantInterval = isConsonantInterval;
|
|
528
|
+
//# sourceMappingURL=chunk-PI3UXRWK.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-PI3UXRWK.cjs","../src/core/errors/index.ts","../src/core/meter/internal.ts","../src/core/validation/index.ts","../src/core/pitch/index.ts","../src/core/interval/index.ts"],"names":["ConsonanceClass"],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-PI3UXRWK.cjs","sourcesContent":[null,"/**\n * The library's error taxonomy.\n *\n * Every failure this library raises falls into one of three kinds, and the kind\n * decides what a caller can do about it. Batch-voicing a lead sheet, for\n * instance, has to tell \"fix the input\" apart from \"loosen the constraints\",\n * and a message string is not a contract.\n *\n * The classes extend the built-in error types a caller would otherwise catch —\n * a rejected argument is still a `RangeError` — so the `code` narrows an\n * existing category rather than replacing it.\n */\n\n/**\n * What kind of failure an error reports.\n *\n * - `INVALID_INPUT`: an argument is malformed or out of range. The caller's\n * input must change.\n * - `NO_SOLUTION`: the input is valid but nothing satisfies the constraints.\n * The constraints must change, or the failure must be accepted.\n * - `BUDGET_EXCEEDED`: the requested work is larger than the synchronous\n * generation budget. The request must be smaller, or the budget larger.\n *\n * @category Core\n */\nexport type LibcantusErrorCode = 'INVALID_INPUT' | 'NO_SOLUTION' | 'BUDGET_EXCEEDED';\n\n/**\n * A rejected argument: malformed, out of range, or naming something unknown.\n *\n * @category Core\n */\nexport class InvalidInputError extends RangeError {\n readonly code = 'INVALID_INPUT' as const;\n override readonly name = 'InvalidInputError';\n}\n\n/**\n * A valid request with no answer: no voicing fits the given ranges, no chord\n * matches the pitches, no preset claims the style.\n *\n * @category Core\n */\nexport class NoSolutionError extends Error {\n readonly code = 'NO_SOLUTION' as const;\n override readonly name = 'NoSolutionError';\n\n /**\n * Where in a sequence the failure occurred, when the operation was working\n * through one — the chord index of a progression, say.\n */\n readonly at: number | undefined;\n\n constructor(message: string, options: { at?: number } = {}) {\n super(message);\n this.at = options.at;\n }\n}\n\n/**\n * Work that would exceed the synchronous generation budget.\n *\n * @category Core\n */\nexport class BudgetExceededError extends RangeError {\n readonly code = 'BUDGET_EXCEEDED' as const;\n override readonly name = 'BudgetExceededError';\n}\n\n/**\n * Whether a caught value is one of this library's coded errors.\n *\n * @param value The caught value.\n * @returns True when it carries a {@link LibcantusErrorCode}.\n * @example\n * ```ts\n * import { isLibcantusError, voiceChord, parseChordSymbol } from '@libraz/libcantus';\n * try {\n * voiceChord(parseChordSymbol('C'), { ranges: [] });\n * } catch (error) {\n * if (isLibcantusError(error) && error.code === 'NO_SOLUTION') {\n * // loosen the constraints rather than blaming the input\n * }\n * }\n * ```\n * @category Core\n */\nexport function isLibcantusError(\n value: unknown,\n): value is InvalidInputError | NoSolutionError | BudgetExceededError {\n if (typeof value !== 'object' || value === null || !('code' in value)) {\n return false;\n }\n const code = value.code;\n return code === 'INVALID_INPUT' || code === 'NO_SOLUTION' || code === 'BUDGET_EXCEEDED';\n}\n","/**\n * Meter arithmetic shared by the meter module and the validation guards.\n *\n * These helpers never validate: they are the single definition of how a time\n * signature is read, so the guard that rejects a malformed signature and the\n * functions that use a validated one cannot drift apart. Everything here is\n * integer arithmetic on the numerator, so a bar's pulse count is exact rather\n * than the result of dividing two floats.\n */\n\nimport type { TimeSignature } from './index.js';\n\n/** Length of one denominator unit in quarter-note beats. */\nexport function unitBeatsOf(ts: TimeSignature): number {\n return 4 / ts.denominator;\n}\n\n/**\n * Whether a numerator reads as compound: a multiple of three greater than\n * three, so its main pulses each divide into three.\n */\nexport function isCompoundNumerator(numerator: number): boolean {\n return numerator % 3 === 0 && numerator > 3;\n}\n\n/** Sum of an additive grouping, or undefined when there is none. */\nexport function groupingSumOf(ts: TimeSignature): number | undefined {\n const grouping = ts.grouping;\n if (grouping === undefined) {\n return undefined;\n }\n let sum = 0;\n for (const entry of grouping) {\n sum += entry;\n }\n return sum;\n}\n\n/**\n * Whether the signature's grouping counts denominator units rather than\n * compound pulses.\n *\n * A compound numerator normally groups into pulses of three — 9/8 is three\n * dotted-quarter pulses — but the same signature is also how additive metres\n * are written, where 9/8 means 2+2+2+3 quavers. A grouping that sums to the\n * numerator selects that additive reading.\n */\nexport function isAdditiveReading(ts: TimeSignature): boolean {\n const sum = groupingSumOf(ts);\n return (\n sum !== undefined && isCompoundNumerator(ts.numerator) && sum === ts.numerator && sum !== 0\n );\n}\n\n/** Number of main pulses per bar under the signature's effective reading. */\nexport function pulseCountOf(ts: TimeSignature): number {\n if (isAdditiveReading(ts)) {\n return ts.numerator;\n }\n return isCompoundNumerator(ts.numerator) ? ts.numerator / 3 : ts.numerator;\n}\n\n/** Length of one main pulse in quarter-note beats, under the same reading. */\nexport function pulseBeatsOf(ts: TimeSignature): number {\n return (ts.numerator * unitBeatsOf(ts)) / pulseCountOf(ts);\n}\n\n/** Length of a bar in quarter-note beats. */\nexport function barBeatsOf(ts: TimeSignature): number {\n return ts.numerator * unitBeatsOf(ts);\n}\n","import { BudgetExceededError, InvalidInputError } from '../errors/index.js';\nimport type { TimeSignature } from '../meter/index.js';\nimport { isCompoundNumerator } from '../meter/internal.js';\nimport type { NoteEvent } from '../types.js';\n\n/**\n * Default upper bound for synchronous event/window/candidate generation.\n *\n * @category Core\n */\nexport const DEFAULT_GENERATION_BUDGET = 1_000_000;\n\n/**\n * Require a finite JavaScript number and return it unchanged.\n *\n * @category Core\n */\nexport function assertFiniteNumber(value: number, name: string): number {\n if (!Number.isFinite(value)) {\n throw new InvalidInputError(`${name} must be finite; received ${value}`);\n }\n return value;\n}\n\n/**\n * Require an integer in the inclusive range `[min, max]`.\n *\n * @category Core\n */\nexport function assertInteger(\n value: number,\n name: string,\n min = Number.MIN_SAFE_INTEGER,\n max = Number.MAX_SAFE_INTEGER,\n): number {\n assertFiniteNumber(value, name);\n if (!Number.isSafeInteger(value) || value < min || value > max) {\n throw new InvalidInputError(\n `${name} must be an integer in [${min}, ${max}]; received ${value}`,\n );\n }\n return value;\n}\n\n/** Require an integer MIDI pitch in the inclusive range 0..127. */\nexport function assertMidiPitch(value: number, name = 'MIDI pitch'): number {\n return assertInteger(value, name, 0, 127);\n}\n\n/** Clamp an integer MIDI pitch to the inclusive range 0..127. */\nexport function clampToMidi(value: number, name = 'MIDI pitch'): number {\n assertInteger(value, name);\n return Math.min(127, Math.max(0, value));\n}\n\n/** Require a bounded integer scale/chord degree. */\nexport function assertDegree(value: number, name = 'degree'): number {\n return assertInteger(value, name, -1000, 1000);\n}\n\n/** Require a finite semitone offset before pitch-class reduction. */\nexport function assertFiniteSemitones(value: number, name = 'semitones'): number {\n return assertFiniteNumber(value, name);\n}\n\n/**\n * Drop the notes that never sound.\n *\n * A MIDI or DAW import routinely carries zero-length artefacts. Every entry\n * point in this library ignores them, but a caller holding such an array still\n * has to decide what to do with it before comparing note counts or indices;\n * this is the one filter to apply.\n *\n * @param events The note events to filter.\n * @returns A new array containing only the notes with a positive duration.\n * @example\n * ```ts\n * import { dropSilentNotes } from '@libraz/libcantus';\n * dropSilentNotes([{ pitch: 60, startBeat: 0, durationBeat: 0 }]); // []\n * ```\n * @category Core\n */\nexport function dropSilentNotes(events: readonly NoteEvent[]): NoteEvent[] {\n return events.filter((event) => event !== undefined && event.durationBeat > 0);\n}\n\n/** Alias that makes the shared positive-duration policy explicit at API entrances. */\nexport const soundingNotesOnly = dropSilentNotes;\n\n/**\n * Require a value to be one of a fixed set of names.\n *\n * TypeScript checks string-union options at compile time only, so a value that\n * arrives from JSON, a config file, or a JavaScript caller reaches the engine\n * unchecked and is then read against a table that has no entry for it —\n * producing a silent `undefined`, a NaN velocity, or a crash far from the call.\n *\n * @param value The value to check.\n * @param allowed Every accepted name.\n * @param name What the value is, for the error message.\n * @returns The value, narrowed to the allowed union.\n * @throws If the value is not one of `allowed`.\n *\n * @category Core\n */\nexport function assertOneOf<const T extends string>(\n value: unknown,\n allowed: readonly T[],\n name: string,\n): T {\n if (typeof value !== 'string' || !(allowed as readonly string[]).includes(value)) {\n throw new InvalidInputError(\n `${name} must be one of ${allowed.join(', ')}; received ${JSON.stringify(value)}`,\n );\n }\n return value as T;\n}\n\n/**\n * Require a positive safe integer, optionally capped by `max`.\n *\n * @category Core\n */\nexport function assertPositiveInt(\n value: number,\n name: string,\n max = DEFAULT_GENERATION_BUDGET,\n): number {\n return assertInteger(value, name, 1, max);\n}\n\n/**\n * Require a finite number in the inclusive range `[min, max]`.\n *\n * @category Core\n */\nexport function assertRange(value: number, min: number, max: number, name: string): number {\n assertFiniteNumber(value, name);\n if (value < min || value > max) {\n throw new InvalidInputError(`${name} must be in [${min}, ${max}]; received ${value}`);\n }\n return value;\n}\n\n/**\n * Reject work estimates that would exceed a synchronous generation budget.\n *\n * @category Core\n */\nexport function assertGenerationBudget(\n estimated: number,\n name: string,\n limit: number | undefined = DEFAULT_GENERATION_BUDGET,\n): number {\n const cap = limit ?? DEFAULT_GENERATION_BUDGET;\n assertFiniteNumber(estimated, name);\n assertPositiveInt(cap, `${name} limit`, Number.MAX_SAFE_INTEGER);\n if (estimated < 0) {\n throw new InvalidInputError(`${name} must not be negative; received ${estimated}`);\n }\n if (estimated > cap) {\n throw new BudgetExceededError(\n `${name} exceeds the generation budget ${cap}; received ${estimated}`,\n );\n }\n return estimated;\n}\n\n/**\n * Validate a time signature, including additive grouping, before any early return.\n *\n * @category Core\n */\nexport function assertTimeSignature(ts: TimeSignature, name = 'time signature'): TimeSignature {\n assertPositiveInt(ts.numerator, `${name}.numerator`);\n assertPositiveInt(ts.denominator, `${name}.denominator`);\n if (ts.grouping !== undefined) {\n if (ts.grouping.length === 0) {\n throw new InvalidInputError(`${name}.grouping must not be empty`);\n }\n let sum = 0;\n for (let index = 0; index < ts.grouping.length; index += 1) {\n sum += assertPositiveInt(ts.grouping[index] ?? Number.NaN, `${name}.grouping[${index}]`);\n }\n // A compound numerator accepts either reading: grouping its dotted pulses\n // (9/8 as [1, 1, 1]) or grouping its denominator units additively (9/8 as\n // [2, 2, 2, 3]), which is how aksak and other additive metres are written.\n const pulses = isCompoundNumerator(ts.numerator) ? ts.numerator / 3 : ts.numerator;\n if (sum !== pulses && sum !== ts.numerator) {\n const accepted =\n pulses === ts.numerator ? `${pulses}` : `${pulses} (pulses) or ${ts.numerator} (units)`;\n throw new InvalidInputError(`${name}.grouping must sum to ${accepted}; received ${sum}`);\n }\n }\n return ts;\n}\n\n/**\n * How strictly {@link assertNoteEvent} and {@link assertNoteEvents} read an\n * event array.\n *\n * @category Core\n */\nexport type NoteEventAssertOptions = {\n /**\n * Accept notes that never sound. The analysis side does, so an array from a\n * MIDI import can be validated before the zero-length artefacts are dropped.\n */\n allowNonPositiveDuration?: boolean;\n /** Upper bound on the event count; defaults to the generation budget. */\n budget?: number;\n};\n\n/**\n * Validate the finite fields of one timeline note event.\n *\n * @category Core\n */\nexport function assertNoteEvent(\n event: NoteEvent,\n name = 'note event',\n options: NoteEventAssertOptions = {},\n): NoteEvent {\n assertMidiPitch(event.pitch, `${name}.pitch`);\n assertRange(event.startBeat, 0, Number.MAX_SAFE_INTEGER, `${name}.startBeat`);\n // The positivity check comes first so the common failure — a zero-length note\n // from a MIDI import — reads as such instead of naming a denormal lower bound.\n assertFiniteNumber(event.durationBeat, `${name}.durationBeat`);\n if (!options.allowNonPositiveDuration && event.durationBeat <= 0) {\n throw new InvalidInputError(\n `${name}.durationBeat must be positive; received ${event.durationBeat}`,\n );\n }\n assertRange(\n event.durationBeat,\n -Number.MAX_SAFE_INTEGER,\n Number.MAX_SAFE_INTEGER,\n `${name}.durationBeat`,\n );\n if (event.velocity !== undefined) {\n assertRange(event.velocity, 0, 127, `${name}.velocity`);\n }\n return event;\n}\n\n/**\n * Validate an event array and its allocation budget without copying it.\n *\n * @category Core\n */\nexport function assertNoteEvents(\n events: readonly NoteEvent[],\n name = 'note events',\n options: NoteEventAssertOptions = {},\n): NoteEvent[] {\n if (!Array.isArray(events)) {\n throw new InvalidInputError(`${name} must be an array; received ${typeof events}`);\n }\n assertGenerationBudget(events.length, `${name} count`, options.budget);\n for (let index = 0; index < events.length; index += 1) {\n const event = events[index];\n // A hole in a sparse array and an explicit undefined are both rejected\n // here: letting either through only moves the failure to a later\n // TypeError, or drops the note silently.\n if (event === undefined) {\n throw new InvalidInputError(`${name}[${index}] must be a note event; received undefined`);\n }\n assertNoteEvent(event, `${name}[${index}]`, options);\n }\n return events;\n}\n","/**\n * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled\n * intervals.\n *\n * The rest of the library works in pitch classes (0..11), which cannot tell a\n * G# from an Ab or an augmented fourth from a diminished fifth. This module adds\n * an explicit spelling layer — a note is a diatonic letter plus a chromatic\n * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are\n * preserved.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertFiniteNumber, assertInteger } from '../validation/index.js';\n\n/** Semitone offset of each natural letter above C: C D E F G A B. */\nconst LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/**\n * Widest alteration a note may carry.\n *\n * Six accidentals already exceeds anything common practice writes, and the\n * bound is what keeps an unvalidated number from reaching `String.repeat`.\n */\nconst MAX_ALTER = 6;\n\n/** Validate the fields of a spelled note once, at a public entry point. */\nfunction assertNote(note: Note, name: string): Note {\n assertInteger(note.letter, `${name}.letter`, -1000, 1000);\n assertInteger(note.alter, `${name}.alter`, -MAX_ALTER, MAX_ALTER);\n if (note.octave !== undefined) {\n assertInteger(note.octave, `${name}.octave`, -100, 100);\n }\n return note;\n}\n\n/** Letter glyphs indexed by letter number (0 = C .. 6 = B). */\nconst LETTER_NAMES = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] as const;\n\n/**\n * A spelled note: a diatonic letter, a chromatic alteration, and an optional\n * octave.\n *\n * - `letter` is 0..6 for C..B.\n * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,\n * 0 natural, +1 sharp, +2 double-sharp.\n * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When\n * absent, the note denotes a bare pitch class.\n *\n * @category Pitch & Intervals\n */\nexport type Note = {\n letter: number;\n alter: number;\n octave?: number;\n};\n\n/**\n * The quality label of a spelled interval: perfect, major, minor, or one or\n * more augmentations or diminutions.\n *\n * This names how an interval is *spelled*. It is a different notion from\n * {@link ConsonanceClass}, which classifies how an interval *sounds* against\n * another voice.\n *\n * @see {@link ConsonanceClass}\n * @category Pitch & Intervals\n */\nexport type IntervalQualityLabel = 'P' | 'M' | 'm' | `A${string}` | `d${string}`;\n\n/**\n * A spelled interval: a diatonic number, a quality label, and its semitone span.\n *\n * @category Pitch & Intervals\n */\nexport type SpelledInterval = {\n /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */\n number: number;\n /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */\n quality: IntervalQualityLabel;\n /** Signed semitone distance from the first note to the second. */\n semitones: number;\n /** True when the diatonic letter movement is descending despite a zero span. */\n descending?: boolean;\n};\n\n/**\n * Reduce a MIDI pitch, a bare pitch class, or any signed offset to a pitch\n * class in [0, 11].\n *\n * This is the library's single definition of pitch-class arithmetic: every\n * layer routes through it so a fractional pitch reduces the same way\n * everywhere. A non-integral input is rounded to the nearest semitone first, so\n * a microtonal 60.6 reads as C# rather than C.\n *\n * @param value A MIDI pitch, pitch class, or signed semitone offset.\n * @returns The pitch class in [0, 11].\n * @example\n * ```ts\n * import { pitchClassOf } from '@libraz/libcantus';\n * pitchClassOf(61); // 1\n * pitchClassOf(-1); // 11\n * pitchClassOf(60.6); // 1\n * ```\n * @category Pitch & Intervals\n */\nexport function pitchClassOf(value: number): number {\n assertFiniteNumber(value, 'pitch');\n return ((Math.round(value) % 12) + 12) % 12;\n}\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nconst mod12 = pitchClassOf;\n\n/**\n * Reduce a letter number or letter offset to a diatonic letter in [0, 6]\n * (0 = C .. 6 = B).\n *\n * @param value A letter number or signed letter offset.\n * @returns The diatonic letter in [0, 6].\n * @category Pitch & Intervals\n */\nexport function diatonicLetterOf(value: number): number {\n assertFiniteNumber(value, 'diatonic letter');\n return ((Math.round(value) % 7) + 7) % 7;\n}\n\n/**\n * The pitch class of a diatonic letter with no accidental (C = 0, D = 2, ...).\n *\n * @param letter A letter number or signed letter offset; reduced to [0, 6].\n * @returns The natural pitch class of that letter.\n * @category Pitch & Intervals\n */\nexport function naturalPitchClassOf(letter: number): number {\n return LETTER_SEMITONES[diatonicLetterOf(letter)] ?? 0;\n}\n\n/** Reduce any integer to [0, 7). */\nconst mod7 = diatonicLetterOf;\n\n/**\n * Parse scientific pitch notation into a {@link Note}.\n *\n * Accepts a letter (case-insensitive) followed by any number of same-direction\n * accidentals and an optional octave, e.g. `\"C\"`, `\"C#4\"`, `\"Bb\"`, `\"F##3\"`,\n * `\"Ebb2\"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for\n * flats. Mixing sharps and flats (e.g. `\"C#b\"`) is rejected.\n *\n * @param text The note text.\n * @returns The parsed note.\n * @throws If the text is not a valid note.\n * @example\n * ```ts\n * import { parseNote, noteToMidi } from '@libraz/libcantus';\n * const n = parseNote('C#4');\n * noteToMidi(n); // 61\n * ```\n * @category Pitch & Intervals\n */\nexport function parseNote(text: string): Note {\n if (typeof text !== 'string') {\n throw new InvalidInputError(`note must be a string; received ${typeof text}`);\n }\n const match = /^([A-Ga-g])([#x]*|b*)(-?\\d+)?$/.exec(text.trim());\n if (!match) {\n throw new InvalidInputError(`Invalid note: ${text}`);\n }\n const letterGlyph = (match[1] ?? '').toUpperCase();\n const letter = LETTER_NAMES.indexOf(letterGlyph as (typeof LETTER_NAMES)[number]);\n let alter = 0;\n for (const ch of match[2] ?? '') {\n if (ch === '#') {\n alter += 1;\n } else if (ch === 'x') {\n alter += 2;\n } else if (ch === 'b') {\n alter -= 1;\n }\n }\n const note: Note = { letter, alter };\n if (match[3] !== undefined) {\n note.octave = Number.parseInt(match[3], 10);\n }\n return assertNote(note, `note ${text}`);\n}\n\n/** Render an alteration as accidental glyphs (`##`, `b`, empty for natural). */\nfunction formatAlter(alter: number): string {\n if (alter > 0) {\n return '#'.repeat(alter);\n }\n if (alter < 0) {\n return 'b'.repeat(-alter);\n }\n return '';\n}\n\n/**\n * Render a {@link Note} as scientific pitch notation.\n *\n * @param note The note to format.\n * @returns The note text, including the octave when present.\n * @example\n * ```ts\n * import { formatNote, parseNote } from '@libraz/libcantus';\n * formatNote(parseNote('C#4')); // 'C#4'\n * ```\n * @category Pitch & Intervals\n */\nexport function formatNote(note: Note): string {\n assertNote(note, 'note');\n const glyph = LETTER_NAMES[mod7(note.letter)] ?? 'C';\n const octave = note.octave === undefined ? '' : String(note.octave);\n return `${glyph}${formatAlter(note.alter)}${octave}`;\n}\n\n/**\n * Pitch class (0..11) of a note, ignoring octave.\n *\n * @param note The note.\n * @returns The pitch class.\n * @example\n * ```ts\n * import { noteToPitchClass, parseNote } from '@libraz/libcantus';\n * noteToPitchClass(parseNote('Db')); // 1\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToPitchClass(note: Note): number {\n assertNote(note, 'note');\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return mod12(natural + note.alter);\n}\n\n/**\n * MIDI number of a note (middle C = C4 = 60).\n *\n * @param note The note; must carry an octave.\n * @returns The MIDI number.\n * @throws If the note has no octave.\n * @example\n * ```ts\n * import { noteToMidi, parseNote } from '@libraz/libcantus';\n * noteToMidi(parseNote('A4')); // 69\n * ```\n * @category Pitch & Intervals\n */\nexport function noteToMidi(note: Note): number {\n assertNote(note, 'note');\n if (note.octave === undefined) {\n throw new InvalidInputError('noteToMidi requires an octave');\n }\n const natural = LETTER_SEMITONES[mod7(note.letter)] ?? 0;\n return (note.octave + 1) * 12 + natural + note.alter;\n}\n\n/** Preferred spelling when naming a black key from a bare MIDI number. */\nconst SHARP_SPELLING: readonly [number, number][] = [\n [0, 0],\n [0, 1],\n [1, 0],\n [1, 1],\n [2, 0],\n [3, 0],\n [3, 1],\n [4, 0],\n [4, 1],\n [5, 0],\n [5, 1],\n [6, 0],\n];\n\nconst FLAT_SPELLING: readonly [number, number][] = [\n [0, 0],\n [1, -1],\n [1, 0],\n [2, -1],\n [2, 0],\n [3, 0],\n [4, -1],\n [4, 0],\n [5, -1],\n [5, 0],\n [6, -1],\n [6, 0],\n];\n\n/**\n * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.\n *\n * The input is rounded to the nearest integer but is not clamped to the MIDI\n * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,\n * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.\n *\n * @param midi The MIDI number.\n * @param spelling Whether to prefer sharps or flats for black keys.\n * @returns The spelled note, with octave.\n * @example\n * ```ts\n * import { midiToNote, formatNote } from '@libraz/libcantus';\n * formatNote(midiToNote(61)); // 'C#4'\n * formatNote(midiToNote(61, 'flat')); // 'Db4'\n * ```\n * @category Pitch & Intervals\n */\nexport function midiToNote(midi: number, spelling: 'sharp' | 'flat' = 'sharp'): Note {\n assertFiniteNumber(midi, 'midi');\n const rounded = Math.round(midi);\n const pc = mod12(rounded);\n const octave = Math.floor(rounded / 12) - 1;\n const table = spelling === 'sharp' ? SHARP_SPELLING : FLAT_SPELLING;\n const entry = table[pc] ?? [0, 0];\n return { letter: entry[0], alter: entry[1], octave };\n}\n\n/**\n * Diatonic letter distance of the conventional ascending interval for each\n * semitone step within an octave: P1, m2, M2, m3, M3, P4, A4, P5, m6, M6, m7, M7.\n */\nconst LETTER_STEPS_BY_SEMITONE = [0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6] as const;\n\n/**\n * Transpose a note by a signed number of semitones, keeping its spelling.\n *\n * The letter moves by the diatonic distance of the conventional interval for\n * that many semitones and the accidental is recomputed for the new letter, so\n * enharmonic identity survives: `Ab4` up a major second is `Bb4`, not `A#4`, and\n * the operation is an exact inverse of itself. An octave-less note stays\n * octave-less.\n *\n * A bare semitone count cannot distinguish an augmented fourth from a diminished\n * fifth; pass `spelling` to override the letter distance when the enharmonic\n * choice matters.\n *\n * @param note The note to transpose.\n * @param semitones The signed semitone offset.\n * @param opts `spelling` forces the result onto the sharp or flat side instead\n * of following the source note's letter.\n * @returns The transposed note.\n * @example\n * ```ts\n * import { transposeNote, formatNote, parseNote } from '@libraz/libcantus';\n * formatNote(transposeNote(parseNote('Ab4'), 2)); // 'Bb4'\n * formatNote(transposeNote(parseNote('Ab4'), 2, { spelling: 'sharp' })); // 'A#4'\n * ```\n * @category Pitch & Intervals\n */\nexport function transposeNote(\n note: Note,\n semitones: number,\n opts?: { spelling?: 'sharp' | 'flat' },\n): Note {\n if (!Number.isFinite(semitones)) {\n throw new InvalidInputError(`semitones must be a finite number; received ${semitones}`);\n }\n const steps = Math.round(semitones);\n if (opts?.spelling !== undefined) {\n return note.octave === undefined\n ? bareOf(midiToNote(60 + mod12(noteToPitchClass(note) + steps), opts.spelling))\n : midiToNote(noteToMidi(note) + steps, opts.spelling);\n }\n const direction = steps < 0 ? -1 : 1;\n const magnitude = Math.abs(steps);\n const octaves = Math.floor(magnitude / 12);\n const withinOctave = magnitude % 12;\n // Descending motion reverses the conventional ascending letter distance.\n // Deriving it from floor(negative / 12) treated -6 as a descending fourth,\n // so transpose(+n) followed by transpose(-n) changed the letter spelling.\n const letterSteps = direction * ((LETTER_STEPS_BY_SEMITONE[withinOctave] ?? 0) + 7 * octaves);\n const absoluteLetter = mod7(note.letter) + letterSteps;\n const letter = mod7(absoluteLetter);\n const natural = LETTER_SEMITONES[letter] ?? 0;\n if (note.octave === undefined) {\n return { letter, alter: alterFor(natural, mod12(noteToPitchClass(note) + steps)) };\n }\n const octave = note.octave + Math.floor(absoluteLetter / 7);\n return { letter, alter: noteToMidi(note) + steps - ((octave + 1) * 12 + natural), octave };\n}\n\n/** Drop the octave from a spelled note. */\nfunction bareOf(note: Note): Note {\n return { letter: note.letter, alter: note.alter };\n}\n\n/** Shortest signed alteration taking a letter's natural pitch class to `pc`. */\nfunction alterFor(natural: number, pc: number): number {\n const d = mod12(pc - natural);\n return d > 6 ? d - 12 : d;\n}\n\n/** Reference semitone span of a perfect/major simple interval by diatonic number. */\nconst SIMPLE_REFERENCE = [0, 0, 2, 4, 5, 7, 9, 11] as const;\n\n/** Whether a simple diatonic number (1..7 within an octave) is a perfect kind. */\nfunction isPerfectNumber(simpleNumber: number): boolean {\n return simpleNumber === 1 || simpleNumber === 4 || simpleNumber === 5;\n}\n\n/** Quality label from a diatonic number and its actual semitone span. */\nfunction qualityFromSpan(numberValue: number, semitones: number): IntervalQualityLabel {\n const octaves = Math.floor((numberValue - 1) / 7);\n const simple = numberValue - 7 * octaves;\n const reference = (SIMPLE_REFERENCE[simple] ?? 0) + 12 * octaves;\n const delta = semitones - reference;\n const repeat = (glyph: 'A' | 'd', count: number): IntervalQualityLabel =>\n `${glyph}${glyph.repeat(Math.min(count, MAX_ALTER * 2) - 1)}`;\n if (isPerfectNumber(simple)) {\n if (delta === 0) {\n return 'P';\n }\n return delta > 0 ? repeat('A', delta) : repeat('d', -delta);\n }\n if (delta === 0) {\n return 'M';\n }\n if (delta === -1) {\n return 'm';\n }\n return delta > 0 ? repeat('A', delta) : repeat('d', -delta - 1);\n}\n\n/**\n * The semitone span of the interval named by a diatonic number and a quality.\n *\n * The inverse of the quality derivation in {@link spelledInterval}: it turns a\n * name back into a distance, which is what transposing by a named interval or\n * parsing one from text needs.\n *\n * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.\n * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.\n * @returns The unsigned semitone span.\n * @throws If the quality cannot apply to the number, such as a major fifth.\n * @example\n * ```ts\n * import { intervalSemitones } from '@libraz/libcantus';\n * intervalSemitones(5, 'P'); // 7\n * ```\n * @category Pitch & Intervals\n */\nexport function intervalSemitones(numberValue: number, quality: IntervalQualityLabel): number {\n assertInteger(numberValue, 'interval number', 1, 64);\n const octaves = Math.floor((numberValue - 1) / 7);\n const simple = numberValue - 7 * octaves;\n const reference = (SIMPLE_REFERENCE[simple] ?? 0) + 12 * octaves;\n const perfect = isPerfectNumber(simple);\n if (quality === 'P' || quality === 'M') {\n if (perfect !== (quality === 'P')) {\n throw new InvalidInputError(\n `a ${numberValue === 1 ? 'unison' : `${numberValue}th`} cannot be ${quality === 'P' ? 'perfect' : 'major'}`,\n );\n }\n return reference;\n }\n if (quality === 'm') {\n if (perfect) {\n throw new InvalidInputError(\n `a perfect-class interval cannot be minor; received ${numberValue}`,\n );\n }\n return reference - 1;\n }\n if (/^A+$/.test(quality)) {\n return reference + quality.length;\n }\n if (/^d+$/.test(quality)) {\n return Math.abs(reference - quality.length - (perfect ? 0 : 1));\n }\n throw new InvalidInputError(`unknown interval quality ${JSON.stringify(quality)}`);\n}\n\n/**\n * Parse an interval name such as `'P5'`, `'m3'`, or `'AA4'`.\n *\n * @param name The interval name: a quality label followed by a diatonic number.\n * @returns The spelled interval. Diminished unisons have a descending\n * (negative) semitone span; all other supported names are ascending.\n * @throws If the name is not a quality label followed by a number, or the two\n * cannot describe the same interval.\n * @example\n * ```ts\n * import { parseInterval } from '@libraz/libcantus';\n * parseInterval('m3'); // { number: 3, quality: 'm', semitones: 3 }\n * ```\n * @category Pitch & Intervals\n */\nexport function parseInterval(name: string): SpelledInterval {\n const match = /^(P|M|m|A+|d+)(\\d+)$/.exec(name);\n const quality = match?.[1] as IntervalQualityLabel | undefined;\n const numberValue = Number(match?.[2]);\n if (quality === undefined || !Number.isFinite(numberValue)) {\n throw new InvalidInputError(\n `interval must be a quality followed by a number, such as 'P5'; received ${JSON.stringify(name)}`,\n );\n }\n return { number: numberValue, quality, semitones: intervalSemitones(numberValue, quality) };\n}\n\n/**\n * Transpose a note by a spelled interval, keeping the spelling the interval\n * names.\n *\n * Unlike {@link transposeNote}, which picks a letter from the semitone count,\n * the diatonic number decides the letter here: transposing C by an augmented\n * second gives D#, not Eb.\n *\n * @param note The note to transpose.\n * @param interval The interval to apply; a negative span transposes downward.\n * @returns The transposed note, octave-less if the source was.\n * @example\n * ```ts\n * import { formatNote, parseInterval, parseNote, transposeByInterval } from '@libraz/libcantus';\n * formatNote(transposeByInterval(parseNote('C4'), parseInterval('A2'))); // 'D#4'\n * ```\n * @category Pitch & Intervals\n */\nexport function transposeByInterval(note: Note, interval: SpelledInterval): Note {\n assertNote(note, 'note');\n // `spelledInterval` accepts the full supported octave range, so accepting\n // only 64 here made a value produced by that sibling public function\n // impossible to apply back to its source note.\n assertInteger(interval.number, 'interval.number', 1);\n assertFiniteNumber(interval.semitones, 'interval.semitones');\n const descending = interval.descending ?? interval.semitones < 0;\n const letterSteps = (interval.number - 1) * (descending ? -1 : 1);\n const absoluteLetter = mod7(note.letter) + letterSteps;\n const letter = mod7(absoluteLetter);\n const natural = LETTER_SEMITONES[letter] ?? 0;\n const steps = Math.round(interval.semitones);\n if (note.octave === undefined) {\n return { letter, alter: alterFor(natural, mod12(noteToPitchClass(note) + steps)) };\n }\n const octave = note.octave + Math.floor(absoluteLetter / 7);\n return { letter, alter: noteToMidi(note) + steps - ((octave + 1) * 12 + natural), octave };\n}\n\n/** Diatonic ladder index of a note (letter + 7 * octave when octave-bearing). */\nfunction diatonicIndex(note: Note): number {\n return mod7(note.letter) + 7 * (note.octave ?? 0);\n}\n\n/**\n * The spelled interval from one note to another.\n *\n * When both notes carry octaves, the interval reflects their full signed\n * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the\n * interval is measured within a single ascending octave from `a` to `b`.\n *\n * @param a The lower/first note.\n * @param b The upper/second note.\n * @returns The diatonic number, quality, and semitone span.\n * @example\n * ```ts\n * import { spelledInterval, parseNote } from '@libraz/libcantus';\n * spelledInterval(parseNote('C4'), parseNote('G4'));\n * // { number: 5, quality: 'P', semitones: 7 }\n * ```\n * @category Pitch & Intervals\n */\nexport function spelledInterval(a: Note, b: Note): SpelledInterval {\n assertNote(a, 'a');\n assertNote(b, 'b');\n // Two octave-bearing notes measure their full signed distance; two\n // octave-less ones measure a single ascending octave. One of each has no\n // defined answer, and silently picking either reading gives a number the\n // caller cannot interpret.\n if ((a.octave === undefined) !== (b.octave === undefined)) {\n throw new InvalidInputError(\n 'spelledInterval needs both notes to carry an octave or neither to; received one of each',\n );\n }\n const octaved = a.octave !== undefined && b.octave !== undefined;\n let letterSteps: number;\n let semitones: number;\n if (octaved) {\n letterSteps = diatonicIndex(b) - diatonicIndex(a);\n // Signed pitch distance from a to b, so a note below a yields a negative span.\n semitones = noteToMidi(b) - noteToMidi(a);\n } else {\n letterSteps = mod7(b.letter - a.letter);\n let rawSemis = mod12(noteToPitchClass(b) - noteToPitchClass(a));\n // Lift the chromatic span into the octave nearest the diatonic reference so\n // wraparound intervals stay consistent with the diatonic number instead of\n // collapsing modulo 12. The correction is bidirectional: an upward wrap\n // (e.g. Ab -> G# = augmented seventh) adds an octave, while a same-letter or\n // downward chromatic step (e.g. E -> Eb, F# -> F, C -> Cb = descending\n // diminished/augmented unison) subtracts one so the sign follows pitch\n // direction instead of returning a garbage stack of augmentations.\n const reference = SIMPLE_REFERENCE[letterSteps + 1] ?? 0;\n if (reference - rawSemis > 6) {\n rawSemis += 12;\n } else if (rawSemis - reference > 6) {\n rawSemis -= 12;\n }\n semitones = rawSemis;\n }\n const absSteps = Math.abs(letterSteps);\n const number = absSteps + 1;\n // The span is measured in the direction the letters move, not as a magnitude:\n // C# up to Dbb steps up one letter but down one semitone, which is a doubly\n // diminished second, not a minor one. `semitones` keeps its own sign.\n //\n // A unison is the exception: the letters do not move, so there is no\n // direction to measure against and the quality names the chromatic\n // alteration alone — C down to Cb is a descending augmented unison.\n const directedSpan =\n letterSteps === 0 ? Math.abs(semitones) : letterSteps > 0 ? semitones : -semitones;\n const quality = qualityFromSpan(number, directedSpan);\n const descending = letterSteps < 0 || (letterSteps === 0 && semitones < 0);\n return { number, quality, semitones, descending };\n}\n","import { pitchClassOf } from '../pitch/index.js';\n\n/**\n * Counterpoint classification of a harmonic interval by consonance.\n *\n * Distinct from a spelled interval's quality label — see\n * {@link IntervalQualityLabel}, which names the `'P'` / `'M'` / `'m'` / `'A'` /\n * `'d'` spelling of an interval rather than how it sounds against another\n * voice.\n *\n * @see {@link IntervalQualityLabel}\n * @category Pitch & Intervals\n */\nexport enum ConsonanceClass {\n PerfectConsonance = 0,\n ImperfectConsonance = 1,\n Dissonance = 2,\n}\n\n/**\n * Reduce an interval to a simple interval class in the range [0, 11].\n *\n * Every public predicate in this module funnels through here, so a non-finite\n * interval is rejected once rather than silently classifying as a dissonance.\n */\nfunction simpleInterval(semitones: number): number {\n return pitchClassOf(Math.abs(semitones));\n}\n\n/**\n * Classify an interval for counterpoint evaluation.\n *\n * The interval is reduced modulo 12, so compound intervals classify as their\n * simple equivalents. The perfect fourth (5) is context-dependent: with\n * `twoVoice` (the default) it classifies as a dissonance, matching\n * {@link isConsonantInterval}; otherwise it is an imperfect consonance.\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @param twoVoice When true, the perfect fourth is treated as dissonant.\n * @returns The counterpoint quality of the interval.\n * @example\n * ```ts\n * import { classifyInterval, ConsonanceClass } from '@libraz/libcantus';\n * classifyInterval(7); // ConsonanceClass.PerfectConsonance\n * classifyInterval(5); // ConsonanceClass.Dissonance (two-voice)\n * classifyInterval(5, false); // ConsonanceClass.ImperfectConsonance\n * ```\n * @category Pitch & Intervals\n */\nexport function classifyInterval(semitones: number, twoVoice = true): ConsonanceClass {\n const pc = simpleInterval(semitones);\n if (pc === 0 || pc === 7) {\n return ConsonanceClass.PerfectConsonance;\n }\n if (pc === 3 || pc === 4 || pc === 8 || pc === 9) {\n return ConsonanceClass.ImperfectConsonance;\n }\n if (pc === 5) {\n return twoVoice ? ConsonanceClass.Dissonance : ConsonanceClass.ImperfectConsonance;\n }\n return ConsonanceClass.Dissonance;\n}\n\n/**\n * Test whether an interval is a perfect consonance: the unison/octave or the\n * fifth.\n *\n * The perfect fourth is deliberately excluded. This is the predicate the\n * parallel-motion rules are written against, where the fourth behaves as a\n * dissonance between two voices; {@link classifyInterval} is the general\n * classifier, and it treats the fourth as context-dependent.\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @returns True for the unison/octave (0) and the perfect fifth (7) mod 12.\n * @category Pitch & Intervals\n */\nexport function isPerfectInterval(semitones: number): boolean {\n const pc = simpleInterval(semitones);\n return pc === 0 || pc === 7;\n}\n\n/**\n * Test whether an interval is consonant.\n *\n * The consonant set is {0, 3, 4, 5, 7, 8, 9} mod 12. In two-voice counterpoint\n * the perfect fourth (5) is treated as a dissonance, so `twoVoice` set to true\n * excludes it.\n *\n * @param semitones Interval size in semitones (may be negative or compound).\n * @param twoVoice When true, the perfect fourth is treated as dissonant.\n * @returns True if the interval is consonant in the given context.\n * @category Pitch & Intervals\n */\nexport function isConsonantInterval(semitones: number, twoVoice = true): boolean {\n const pc = simpleInterval(semitones);\n if (pc === 5) {\n return !twoVoice;\n }\n return pc === 0 || pc === 3 || pc === 4 || pc === 7 || pc === 8 || pc === 9;\n}\n"]}
|