@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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-RC6GBMCD.cjs","../src/analyze/timeline/index.ts","../src/analyze/arrange/internal.ts","../src/analyze/arrange/tension.ts","../src/analyze/voice/index.ts","../src/analyze/arrange/tracks.ts"],"names":["EPS"],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-RC6GBMCD.cjs","sourcesContent":[null,"import { createNoteEventIndex } from '../../core/event-index/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar, metricWeight, parseTimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSpan } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, majorKey } from '../../theory/scale/index.js';\nimport type { ChordMatch } from '../detect/index.js';\nimport { detectChord, detectChordBest, detectKeyFromNotes } from '../detect/index.js';\nimport type { Cadence } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\n\nexport type { ChordSegment } from '../../theory/chord/index.js';\n\nimport type { ChordSegment } from '../../theory/chord/index.js';\n\n/**\n * A beat-indexed sequence of chord segments.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimeline = {\n /** The chord sounding at a beat, or null when no segment covers it. */\n at: (beat: number) => Chord | null;\n segments: ChordSegment[];\n};\n\n/**\n * Build a chord timeline from placed chords.\n *\n * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last\n * chord runs to `totalBeats`. Segments with no positive length (from duplicate\n * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns\n * the covering segment's chord, or null when the beat lies outside every segment.\n *\n * @param chords Placed chords in time order.\n * @param totalBeats End of the timeline in beats.\n * @returns A queryable chord timeline.\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromChords(\n chords: readonly ChordSpan[],\n totalBeats: number,\n): ChordTimeline {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n assertGenerationBudget(chords.length, 'timeline chords');\n for (let index = 0; index < chords.length; index += 1) {\n assertRange(\n chords[index]?.startBeat ?? Number.NaN,\n 0,\n Number.MAX_SAFE_INTEGER,\n `chords[${index}].startBeat`,\n );\n }\n const sorted = [...chords]\n .filter((chord) => chord.startBeat < totalBeats)\n .sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const nextBeat = next ? next.startBeat : totalBeats;\n const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));\n return {\n startBeat: gc.startBeat,\n endBeat,\n chord: makeChord(gc.rootPc, gc.quality, gc.bassPc),\n };\n })\n .filter((seg) => seg.endBeat > seg.startBeat);\n\n return { at: segmentLookup(segments), segments };\n}\n\n/**\n * Build the `at(beat)` lookup over a segment list.\n *\n * Segments are disjoint and in beat order, so the covering one is found by\n * binary search. A linear scan here is the inner loop of arrangement analysis,\n * which queries it once per note per chord change.\n */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => {\n assertFiniteNumber(beat, 'timeline query beat');\n let low = 0;\n let high = segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = segments[low - 1];\n return candidate !== undefined && beat >= candidate.startBeat && beat < candidate.endBeat\n ? candidate.chord\n : null;\n };\n}\n\nconst EPS = 1e-9;\n\n/** Fraction of the strongest pitch-class weight below which a pc is noise. */\nconst NOISE_THRESHOLD_RATIO = 0.2;\n\n/** Maximum number of pitch classes fed to chord detection per window. */\nconst MAX_DETECTION_PCS = 6;\n\n/** Number of top-weighted pitch classes tried in the detection fallback. */\nconst FALLBACK_PCS = 3;\n\n/** Confidence multiplier applied when the chosen chord match is inexact. */\nconst INEXACT_CONFIDENCE_FACTOR = 0.85;\n\n/** Score bonus for a match whose tones are all in the key. */\nconst DIATONIC_BONUS = 0.5;\n\n/** Score bonus for an exact match (no extra and no missing tones). */\nconst EXACT_BONUS = 0.5;\n\n/** Score penalty per extra or missing tone in a match. */\nconst MISMATCH_PENALTY = 0.3;\n\n/**\n * Options controlling {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineOptions = {\n /** Key context; inferred from the notes with {@link detectKeyFromNotes} when omitted. */\n key?: KeyScale;\n /**\n * Time signature used for metric accents; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Window length in beats per chord slot; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * The result of {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineResult = {\n /** The inferred timeline, with adjacent identical chords merged. */\n timeline: ChordTimeline;\n /** The key used for the analysis (given or inferred). */\n key: KeyScale;\n /** One confidence value in [0, 1] per segment, in segment order. */\n segmentConfidence: number[];\n};\n\n/** A window's inferred chord and its confidence, or null for an empty window. */\ntype WindowChord = {\n chord: Chord;\n confidence: number;\n};\n\n/**\n * Score a chord match against a window's pitch-class weights: a heavily\n * weighted root, in-key tones, and exactness all raise the score; extra or\n * missing tones lower it.\n */\nfunction scoreMatch(\n match: ChordMatch,\n weights: number[],\n maxWeight: number,\n key: KeyScale,\n): number {\n const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;\n const tones = chordPitchClasses(makeChord(match.rootPc, match.quality));\n let score = rootWeight;\n if (tones.every((pc) => isScaleTone(pc, key))) {\n score += DIATONIC_BONUS;\n }\n if (match.exact) {\n score += EXACT_BONUS;\n }\n score -= MISMATCH_PENALTY * (match.extraPcs.length + match.missingPcs.length);\n return score;\n}\n\n/** Confidence of a chord for a window: chord-tone weight over total weight. */\nfunction chordConfidence(\n chord: Chord,\n weights: number[],\n totalWeight: number,\n exact: boolean,\n): number {\n const tones = new Set(chordPitchClasses(chord));\n let chordWeight = 0;\n for (let pc = 0; pc < 12; pc += 1) {\n if (tones.has(pc)) {\n chordWeight += weights[pc] ?? 0;\n }\n }\n const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;\n}\n\n/**\n * Infer the chord sounding in one window from the notes overlapping it.\n *\n * Builds a pitch-class weight histogram (overlap duration x velocity x\n * metric-accent bonus for onsets inside the window), keeps the significantly\n * weighted pitch classes, and picks the best {@link detectChord} match by\n * root weight, key membership, and exactness.\n */\nfunction analyzeWindow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n ts: TimeSignature,\n key: KeyScale,\n): WindowChord | null {\n const weights = new Array<number>(12).fill(0);\n let lowestPitch = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);\n if (overlap <= EPS) {\n continue;\n }\n const velocityFactor = note.velocity !== undefined ? note.velocity / 127 : 1;\n const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;\n const accent = onsetInWindow ? 1 + metricWeight(note.startBeat, ts) / 3 : 1;\n const pc = pitchClass(note.pitch);\n weights[pc] = (weights[pc] ?? 0) + overlap * velocityFactor * accent;\n if (note.pitch < lowestPitch) {\n lowestPitch = note.pitch;\n }\n }\n let totalWeight = 0;\n let maxWeight = 0;\n for (const w of weights) {\n totalWeight += w;\n maxWeight = Math.max(maxWeight, w);\n }\n if (totalWeight <= EPS) {\n return null;\n }\n\n // Keep pitch classes with meaningful weight, strongest first.\n const ranked = weights\n .map((weight, pc) => ({ pc, weight }))\n .filter(({ weight }) => weight > 0)\n .sort((a, b) => b.weight - a.weight || a.pc - b.pc);\n const selected = ranked\n .filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO)\n .slice(0, MAX_DETECTION_PCS)\n .map(({ pc }) => pc);\n\n // Feed detection with the window's true bass as the lowest pitch so inversion\n // detection works; when the bass pc was filtered out as noise, anchor on the\n // heaviest pc instead so no spurious inversion is reported.\n const bassPc = pitchClass(lowestPitch);\n const anchorPc = selected.includes(bassPc) ? bassPc : (selected[0] ?? bassPc);\n const detectionPitches = selected.map((pc) => (pc === anchorPc ? pc : pc + 12));\n\n const matches = detectChord(detectionPitches);\n let bestMatch: ChordMatch | null = null;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const match of matches) {\n const score = scoreMatch(match, weights, maxWeight, key);\n if (score > bestScore) {\n bestScore = score;\n bestMatch = match;\n }\n }\n if (bestMatch) {\n const chord = makeChord(bestMatch.rootPc, bestMatch.quality, bestMatch.bassPc);\n return { chord, confidence: chordConfidence(chord, weights, totalWeight, bestMatch.exact) };\n }\n\n // Fallback: retry on only the top-weighted pitch classes.\n const fallback = detectChordBest(selected.slice(0, FALLBACK_PCS));\n if (fallback) {\n return { chord: fallback, confidence: chordConfidence(fallback, weights, totalWeight, false) };\n }\n return null;\n}\n\n/** Whether two chords are the same root, quality, and bass. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;\n}\n\n/**\n * Infer a chord timeline from raw multi-track notes.\n *\n * The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each\n * window's chord is inferred from a pitch-class weight histogram of the notes\n * overlapping it (weight = overlap duration x velocity x metric-accent bonus\n * for onsets in the window). Adjacent windows carrying the identical chord are\n * merged into one segment; windows with no notes produce no segment, so\n * `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:\n * the fraction of the window weight explained by chord tones, reduced when the\n * match is inexact, and duration-weighted across merged windows.\n *\n * Notes with a zero or negative duration never sound, so they are dropped at\n * ingest: they contribute to neither the key inference, the span, nor any\n * window's histogram.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link ChordTimelineOptions}.\n * @returns The inferred timeline, the key used, and per-segment confidences.\n * @throws If `harmonicRhythm` is not positive.\n * @example\n * ```ts\n * import { chordTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C\n * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E\n * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * timeline.at(0); // the chord inferred over beat 0, or null\n * ```\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromNotes(\n notes: NoteEvent[],\n opts: ChordTimelineOptions = {},\n): ChordTimelineResult {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBar(ts);\n assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'harmonic rhythm');\n const budget = opts.budget;\n assertNoteEvents(notes, 'timeline notes', { allowNonPositiveDuration: true, budget });\n // Zero/negative-length notes never sound; drop them before any inference.\n const soundingIndex = createNoteEventIndex(\n notes.filter((note) => note.durationBeat > 0),\n { name: 'timeline notes', budget },\n );\n const sounding = soundingIndex.notes.map(({ note }) => note);\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n // Weighted by duration and velocity, exactly as the per-window chord\n // histogram below is: a busy ornamental figure must not outvote the sustained\n // harmony that establishes the key.\n const key = opts.key ?? detectKeyFromNotes(sounding)[0]?.key ?? majorKey(0);\n\n const segments: ChordSegment[] = [];\n const segmentConfidence: number[] = [];\n const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));\n assertGenerationBudget(windowCount, 'timeline windows', budget);\n const windowNotes: NoteEvent[][] = Array.from({ length: windowCount }, () => []);\n let memberships = 0;\n for (const indexed of soundingIndex.notes) {\n const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));\n const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, 'timeline note-to-window memberships', budget);\n for (let window = first; window < lastExclusive; window += 1) {\n windowNotes[window]?.push(indexed.note);\n }\n }\n for (let i = 0; i < windowCount; i += 1) {\n const start = i * harmonicRhythm;\n const end = Math.min(start + harmonicRhythm, totalBeats);\n const inferred = analyzeWindow(windowNotes[i] ?? [], start, end, ts, key);\n if (!inferred) {\n continue;\n }\n const last = segments[segments.length - 1];\n if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < EPS) {\n // Merge into the previous segment, blending confidence by duration.\n const lastLength = last.endBeat - last.startBeat;\n const length = end - start;\n const lastConfidence = segmentConfidence[segmentConfidence.length - 1] ?? 0;\n segmentConfidence[segmentConfidence.length - 1] =\n (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);\n last.endBeat = end;\n } else {\n segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });\n segmentConfidence.push(inferred.confidence);\n }\n }\n\n return {\n timeline: { at: segmentLookup(segments), segments },\n key,\n segmentConfidence,\n };\n}\n\n/**\n * A cadence found between two consecutive timeline segments.\n *\n * @category Arrangement & Analysis\n */\nexport type CadenceHit = {\n /** The beat where the cadence arrives (the second chord's onset). */\n atBeat: number;\n type: Exclude<Cadence, null>;\n from: Chord;\n to: Chord;\n};\n\n/**\n * Find cadences between consecutive segments of a chord timeline.\n *\n * Each temporally adjacent segment pair is classified with\n * {@link detectCadence}; pairs forming no cadence are skipped. Segments\n * separated by a gap (a rest in the timeline) are not a chord-to-chord\n * progression, so they are never paired.\n *\n * @param timeline The chord timeline to scan.\n * @param key The prevailing key.\n * @returns The cadences found, in time order.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant\n * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * const cadences = detectCadences(timeline, key);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[] {\n const hits: CadenceHit[] = [];\n for (let i = 1; i < timeline.segments.length; i += 1) {\n const prev = timeline.segments[i - 1];\n const cur = timeline.segments[i];\n if (!prev || !cur) {\n continue;\n }\n if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {\n continue; // A rest separates the chords; no cadential motion across it.\n }\n const type = detectCadence(prev.chord, cur.chord, key);\n if (type !== null) {\n hits.push({ atBeat: cur.startBeat, type, from: prev.chord, to: cur.chord });\n }\n }\n return hits;\n}\n","/**\n * Shared helpers for the arrangement-analysis modules: pooling every track's\n * sounding notes and partitioning a track into monophonic sub-voices, plus the\n * small geometric primitives both the track-role and tension passes rely on.\n */\n\nimport type { NoteEvent } from '../../core/types.js';\nimport type { IdentifiedVoiceNote } from '../voice/index.js';\nimport type { ArrangementTrack, TrackRole } from './tracks.js';\n\n/** Float tolerance for beat boundary comparisons. */\nexport const EPS = 1e-9;\n\n/**\n * Largest interval, in semitones, a sub-voice will span between consecutive\n * notes. Beyond two octaves the connection is not a melodic step or leap but an\n * artefact of lane packing, so a new sub-voice is opened instead.\n */\nconst MAX_LANE_LEAP = 24;\n\n/** A note with the pitch that immediately preceded it in the same sub-voice. */\nexport type PreparedNote = {\n pitch: number;\n prevPitch?: number;\n startBeat: number;\n endBeat: number;\n};\n\n/** One monophonic sub-voice of a track: ordered notes plus sounding spans. */\nexport type PreparedVoice = {\n /** The sub-voice's notes in time order, each with a stable id. */\n voice: IdentifiedVoiceNote[];\n /** The same notes as sounding spans, each carrying its predecessor's pitch. */\n sounding: PreparedNote[];\n /**\n * The span sounding at a beat, or undefined. A sub-voice is monophonic by\n * construction, so at most one span covers any beat and the lookup is a\n * single binary search rather than a scan.\n */\n at: (beat: number) => PreparedNote | undefined;\n};\n\n/** A track prepared for analysis: resolved metadata plus monophonic sub-voices. */\nexport type PreparedTrack = {\n name: string;\n role: TrackRole;\n /** The track's index in the caller's input array. */\n trackIndex: number;\n /**\n * The track's notes partitioned into monophonic sub-voices. Voice analysis\n * assumes one note at a time, so a polyphonic track (e.g. a 'harmony' track\n * playing block chords) is split here; a monophonic track yields exactly one\n * sub-voice.\n */\n voices: PreparedVoice[];\n};\n\n/** Whether a sounding span `[start, end)` covers a beat. */\nexport function covers(note: PreparedNote, beat: number): boolean {\n return beat >= note.startBeat - EPS && beat < note.endBeat - EPS;\n}\n\n/** Whether a track's role means it carries no harmonic content. */\nexport function isPercussion(role: TrackRole | undefined): boolean {\n return role === 'drums';\n}\n\n/**\n * Every sounding note of every pitched track pooled into one flat list. Zero-\n * and negative-length notes never sound, so they are dropped here (matching the\n * chord-inference ingest in the timeline module), and percussion tracks are\n * skipped entirely: their pitches are instrument selections, not harmony.\n */\nexport function poolNotes(tracks: ArrangementTrack[], only?: ReadonlySet<number>): NoteEvent[] {\n const notes: NoteEvent[] = [];\n for (let index = 0; index < tracks.length; index += 1) {\n const track = tracks[index];\n if (track === undefined || isPercussion(track.role)) {\n continue;\n }\n if (only !== undefined && !only.has(index)) {\n continue;\n }\n for (const note of track.notes) {\n if (note.durationBeat > 0) {\n notes.push(note);\n }\n }\n }\n return notes;\n}\n\n/**\n * Split a track's ordered notes into monophonic sub-voices.\n *\n * Each note joins the free sub-voice — one whose last note has ended — whose\n * last pitch is nearest its own, so a track whose voices cross keeps each voice\n * on its own lane instead of handing a low note to whichever lane happened to\n * be created first. Ties go to the lane that ended latest, which is the one the\n * note actually follows. A connection wider than {@link MAX_LANE_LEAP} is not a\n * melodic one, so it opens a new sub-voice instead.\n *\n * The predecessor pitch this produces feeds leap and parallel detection, so a\n * mis-assigned lane invents voice-leading faults that are not in the music.\n */\nfunction splitIntoSubVoices(ordered: IdentifiedVoiceNote[]): IdentifiedVoiceNote[][] {\n const lanes: IdentifiedVoiceNote[][] = [];\n for (const note of ordered) {\n let best = -1;\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEnd = Number.NEGATIVE_INFINITY;\n for (let lane = 0; lane < lanes.length; lane += 1) {\n const last = lanes[lane]?.[(lanes[lane]?.length ?? 0) - 1];\n if (last === undefined) {\n continue;\n }\n const end = last.startBeat + last.durationBeat;\n if (end > note.startBeat + EPS) {\n continue; // still sounding, so the lane is not free\n }\n const distance = Math.abs(last.pitch - note.pitch);\n if (distance > MAX_LANE_LEAP) {\n continue;\n }\n if (distance < bestDistance || (distance === bestDistance && end > bestEnd)) {\n best = lane;\n bestDistance = distance;\n bestEnd = end;\n }\n }\n if (best >= 0) {\n lanes[best]?.push(note);\n } else {\n lanes.push([note]);\n }\n }\n return lanes;\n}\n\n/** Index of the last span starting at or before a beat, or -1. */\nfunction lastStartingAtOrBefore(sounding: PreparedNote[], beat: number): number {\n let low = 0;\n let high = sounding.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((sounding[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low - 1;\n}\n\n/**\n * Resolve each track's metadata and derive its monophonic sub-voices.\n *\n * Notes are sorted by onset, then pitch, so ordering is deterministic, and ids\n * are assigned in that order; each note keeps the index it had in the caller's\n * array so annotations can be mapped back. Zero- and negative-length notes never\n * sound and are dropped at ingest. Each track is then partitioned into\n * monophonic sub-voices (see {@link splitIntoSubVoices}); the predecessor pitch\n * used for suspension and parallel detection is the previous note of the same\n * sub-voice, never a simultaneous chord member.\n */\nexport function prepareTracks(tracks: ArrangementTrack[]): PreparedTrack[] {\n let nextId = 0;\n return tracks.map((track, index) => {\n const ordered: IdentifiedVoiceNote[] = track.notes\n .map((note, originalIndex) => ({ note, originalIndex }))\n .filter(({ note }) => note.durationBeat > 0)\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch)\n .map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));\n const voices: PreparedVoice[] = splitIntoSubVoices(ordered).map((voice) => {\n const sounding: PreparedNote[] = voice.map((note, i) => {\n const prev = i > 0 ? voice[i - 1] : undefined;\n const prevEnd = prev === undefined ? undefined : prev.startBeat + prev.durationBeat;\n const contiguous = prevEnd !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS;\n return {\n pitch: note.pitch,\n prevPitch: contiguous ? prev?.pitch : undefined,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n };\n });\n return {\n voice,\n sounding,\n at: (beat: number) => {\n const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];\n return candidate !== undefined && covers(candidate, beat) ? candidate : undefined;\n },\n };\n });\n return {\n name: track.name ?? `track ${index + 1}`,\n role: track.role ?? 'other',\n trackIndex: index,\n voices,\n };\n });\n}\n","/**\n * Arrangement tension analysis: a coarse harmonic-tension curve sampled at\n * regular beats across a whole multi-track piece, combining the sounding\n * chord's harmonic function, the dissonance of the sounding notes, and their\n * registral span into a single normalized reading.\n */\n\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport { evaluateSafety, NoteSafety, type SafetyProfile } from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport { functionOf } from '../functional/index.js';\nimport { type ChordTimeline, chordTimelineFromNotes } from '../timeline/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\nimport type { ArrangementAnalysis, ArrangementOptions, ArrangementTrack } from './tracks.js';\n\n/**\n * A tension reading sampled at a beat.\n *\n * @category Arrangement & Analysis\n */\nexport type TensionPoint = {\n beat: number;\n /** Combined tension in [0, 1]. */\n tension: number;\n};\n\n/** Tension contributed by the sounding chord's harmonic function. */\nconst FUNCTION_TENSION: Record<'tonic' | 'subdominant' | 'dominant', number> = {\n tonic: 0,\n subdominant: 0.5,\n dominant: 1,\n};\n\n/** Weight of the harmonic-function term in the combined tension score. */\nconst FUNCTION_WEIGHT = 0.5;\n/** Weight of the non-chord-tone / dissonance term. */\nconst DISSONANCE_WEIGHT = 0.35;\n/** Weight of the registral-span term. */\nconst SPAN_WEIGHT = 0.15;\n/** Pitch span, in semitones, that saturates the span term. */\nconst SPAN_SATURATION = 24;\n\n/** Clamp a value into [0, 1]. */\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Sample the harmonic tension of an arrangement at regular beats.\n *\n * At each sample beat the tension combines three normalized terms:\n * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,\n * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;\n * - the dissonance of the sounding notes — the larger of the share of sounding\n * pitches that are not chord tones and the share that {@link evaluateSafety}\n * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;\n * - the registral span of the sounding notes, saturating at\n * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.\n *\n * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks\n * pooled together, so the result is deterministic and self-contained.\n *\n * @param tracks The tracks to sample.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @throws If `step` is not positive.\n * @example\n * ```ts\n * import { tensionCurve } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const curve = tensionCurve([{ notes }], { step: 1 });\n * curve; // one { beat, tension } sample per beat, in beat order\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurve(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n for (let index = 0; index < tracks.length; index += 1) {\n assertNoteEvents(tracks[index]?.notes ?? [], `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n }\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const step = opts.step ?? 1;\n assertRange(step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'tension sampling step');\n\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n const totalBeats = poolNotes(tracks).reduce(\n (end, n) => Math.max(end, n.startBeat + n.durationBeat),\n 0,\n );\n const { timeline, key } =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0),\n };\n\n const prepared = prepareTracks(tracks);\n const points: TensionPoint[] = [];\n const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS));\n assertGenerationBudget(sampleCount, 'tension samples', budget);\n // The per-sample cost is one lookup per sub-voice, so it is their product —\n // not either dimension alone — that has to stay inside the budget.\n const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);\n assertGenerationBudget(sampleCount * voiceCount, 'tension sample-voice lookups', budget);\n for (let i = 0; i < sampleCount; i += 1) {\n const beat = i * step;\n points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });\n }\n return points;\n}\n\n/**\n * Sample the tension of an arrangement already analysed by\n * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.\n *\n * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference\n * twice over the same notes. This takes the analysis it already produced, so\n * the curve and the annotations describe the same harmony by construction.\n *\n * @param tracks The same tracks that were analysed.\n * @param analysis The result of {@link analyzeArrangement} for those tracks.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @example\n * ```ts\n * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';\n * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];\n * const analysis = analyzeArrangement(tracks);\n * tensionCurveFrom(tracks, analysis, { step: 1 });\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurveFrom(\n tracks: ArrangementTrack[],\n analysis: ArrangementAnalysis,\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n return tensionCurve(tracks, { ...opts, timeline: analysis.timeline, key: analysis.key });\n}\n\n/**\n * All sounding pitched notes across every track at a beat, tagged with their\n * track. Percussion carries no harmony, so it is skipped; each sub-voice is\n * monophonic, so its contribution is one binary search rather than a scan.\n */\nfunction allSounding(prepared: PreparedTrack[], beat: number): { pitch: number; track: number }[] {\n const out: { pitch: number; track: number }[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (const subVoice of track.voices) {\n const note = subVoice.at(beat);\n if (note !== undefined) {\n out.push({ pitch: note.pitch, track: t });\n }\n }\n }\n return out;\n}\n\n/** Combine the harmonic, dissonance, and span terms at one sample beat. */\nfunction sampleTension(\n prepared: PreparedTrack[],\n timeline: ChordTimeline,\n key: KeyScale,\n ts: TimeSignature,\n profile: SafetyProfile,\n beat: number,\n): number {\n const sounding = allSounding(prepared, beat);\n if (sounding.length === 0) {\n return 0;\n }\n const chord = timeline.at(beat);\n const functionScore = chord ? FUNCTION_TENSION[functionOf(chord, key)] : 0;\n\n const chordPcs = chord ? new Set(chordPitchClasses(chord)) : null;\n const strongBeat = isStrongBeat(beat, ts);\n let nonChord = 0;\n let dissonant = 0;\n for (let i = 0; i < sounding.length; i += 1) {\n const voice = sounding[i];\n if (!voice) {\n continue;\n }\n // Non-chord-tone share only applies when a chord is sounding; at a timeline\n // gap there is no reference harmony, so vertical dissonance alone drives the\n // dissonance term (the chord function term is already 0 there).\n if (chordPcs && !chordPcs.has(pitchClass(voice.pitch))) {\n nonChord += 1;\n }\n const others = sounding.filter((_, j) => j !== i).map((s) => ({ pitch: s.pitch }));\n // Only the verdict is read, so the replacement-pitch search is skipped.\n const result = evaluateSafety(\n {\n profile,\n candidatePitch: voice.pitch,\n chord,\n key,\n otherVoices: others,\n strongBeat,\n },\n { suggestions: false },\n );\n if (result.safety === NoteSafety.Dissonant) {\n dissonant += 1;\n }\n }\n const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;\n\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const voice of sounding) {\n low = Math.min(low, voice.pitch);\n high = Math.max(high, voice.pitch);\n }\n const spanScore = Math.min(1, (high - low) / SPAN_SATURATION);\n\n return clamp01(\n FUNCTION_WEIGHT * functionScore + DISSONANCE_WEIGHT * dissonanceScore + SPAN_WEIGHT * spanScore,\n );\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole, intervalAboveRoot, isChordMember } from '../../theory/chord/index.js';\nimport {\n createsVerticalDissonance,\n isLeadingToneResolution,\n} from '../../theory/counterpoint/index.js';\nimport type { VoiceSnapshot } from '../../theory/safety/index.js';\n\n/**\n * A suspension figure, named by the interval above the bass and the interval it\n * resolves to.\n *\n * @category Arrangement & Analysis\n */\nexport type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n\n/**\n * A theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: ChordToneRole }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: SuspensionFigure; resolveTo: number }\n | { kind: 'anticipation' }\n | { kind: 'escape' }\n | { kind: 'needsResolution'; resolveTo: number }\n | { kind: 'leadingTone'; resolveTo: number };\n\n/**\n * A note with its theory labels and a short rationale.\n *\n * The labels describe the note against the chord sounding at its **onset**;\n * they are not re-derived for the later chords a long note sustains across.\n * `analyzeArrangement` does re-check every crossed chord change, and reports\n * what it finds as conflicts, so a note labelled a chord tone here can still\n * appear in `conflicts` at a later beat.\n *\n * The identity fields make an annotation mappable back to the caller's input:\n * `originalIndex` is the note's position in the array that was passed in, and\n * `trackIndex` is set by {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: number;\n /** The note's index in the caller's own array, when the caller supplied one. */\n originalIndex?: number;\n /** The note's track, set by `analyzeArrangement`. */\n trackIndex?: number;\n pitch: number;\n startBeat: number;\n durationBeat: number;\n labels: TheoryLabel[];\n rationale?: string;\n};\n\n/**\n * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,\n * optionally, its index in the caller's original array.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id?: number; originalIndex?: number };\n\n/**\n * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and\n * {@link analyzeArrangement} works in.\n *\n * @category Arrangement & Analysis\n */\nexport type IdentifiedVoiceNote = VoiceNote & { id: number };\n\n/**\n * Give plain note events the ids {@link analyzeVoice} reports back.\n *\n * The id is only an identity handle, so the array position serves; this exists\n * so the mapping is a named call rather than a `map` every caller writes.\n *\n * @param events The note events, in time order.\n * @returns The same notes as voice notes, each carrying its index as its id.\n * @category Arrangement & Analysis\n */\nexport function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[] {\n return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));\n}\n\n/** Float tolerance for beat comparisons. */\nconst EPS = 1e-9;\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * Map an interval class above the root to its extended-tension scale degree:\n * a flat/natural ninth (1 or 2) to 9, an eleventh (5 or 6) to 11, and a\n * thirteenth (8 or 9) to 13.\n */\nfunction tensionDegree(ic: number): 9 | 11 | 13 {\n if (ic === 1 || ic === 2) {\n return 9;\n }\n if (ic === 5 || ic === 6) {\n return 11;\n }\n return 13;\n}\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d === 1 || d === 2;\n}\n\n/**\n * Classify a suspension figure from the interval class above the sounding bass\n * (`ic`) and the resolution direction (`delta`, positive when resolving upward).\n */\nfunction suspensionType(ic: number, delta: number): SuspensionFigure | null {\n if (delta > 0) {\n return 'sus2-3';\n }\n if (ic === 2) {\n return 'sus9-8';\n }\n if (ic === 10 || ic === 11) {\n return 'sus7-6';\n }\n if (ic === 5 || ic === 6) {\n return 'sus4-3';\n }\n if (ic === 8 || ic === 9) {\n return 'sus6-5';\n }\n // Any other interval above the bass is a downward-resolving dissonance with\n // no standard figure; a catch-all `sus4-3` would name it after an interval it\n // does not form.\n return null;\n}\n\nfunction stepResolution(pitch: number, chord: Chord): number | undefined {\n for (let delta = 1; delta <= 2; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n }\n return undefined;\n}\n\n/**\n * Label every note of a voice with its theory roles.\n *\n * Each note is classified against the chord sounding at its beat. Chord tones\n * get a role label; non-chord tones are matched, in order, as suspensions\n * (prepared by an identical consonant pitch and resolving by step), passing\n * tones, neighbors, anticipations, and escape tones, then fall back to tension,\n * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted\n * additionally.\n *\n * The voice is expected to be monophonic (one note at a time). Notes sharing an\n * onset are treated as simultaneous cluster members, not melodic neighbors, so\n * they receive no melodic labels (suspension, passing, neighbor, anticipation,\n * escape) — only harmonic ones. Callers with truly polyphonic material should\n * split it into monophonic sub-voices first, as `analyzeArrangement` does.\n *\n * @param voice The monophonic voice, in time order. A note without an `id` is\n * identified by its position, so plain note events can be passed straight in.\n * @param chordAtBeat Chord sounding at a given beat, or null.\n * @param key Key context for leading-tone detection.\n * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to\n * none, which is the whole story for a solo line.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * const cMajor = makeChord(0, 'maj');\n * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));\n * labels; // one AnalyzedNote per input note, in the same order\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeVoice(\n voice: readonly VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyScale,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[] = () => [],\n): AnalyzedNote[] {\n assertNoteEvents(voice, 'voice notes', { allowNonPositiveDuration: true });\n const result: AnalyzedNote[] = [];\n\n for (let i = 0; i < voice.length; i += 1) {\n const note = voice[i];\n if (!note) {\n continue;\n }\n const prevNote = i > 0 ? voice[i - 1] : undefined;\n const nextNote = i + 1 < voice.length ? voice[i + 1] : undefined;\n // A neighboring note sharing this note's onset is a simultaneous cluster\n // member, not a melodic predecessor/successor; treating it as one would\n // fabricate passing/suspension figures, so it is dropped here.\n const prevEnd =\n prevNote === undefined\n ? Number.NEGATIVE_INFINITY\n : prevNote.startBeat + prevNote.durationBeat;\n const noteEnd = note.startBeat + note.durationBeat;\n const prev =\n prevNote !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS ? prevNote : undefined;\n const next =\n nextNote !== undefined && Math.abs(noteEnd - nextNote.startBeat) <= EPS\n ? nextNote\n : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n if (role) {\n labels.push({ kind: 'chordTone', role });\n } else {\n const ic = intervalAboveRoot(note.pitch, chord);\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n }\n handled = true;\n }\n\n if (!handled && chord && prev && next) {\n const prevChord = chordAtBeat(prev.startBeat);\n const prepared = prev.pitch === note.pitch && isChordMember(prev.pitch, prevChord);\n const resolves = isStep(next.pitch, note.pitch);\n const others = otherVoicesAtBeat(note.startBeat);\n const verticallyDissonant = others.some((ov) =>\n createsVerticalDissonance(note.pitch, ov.pitch, true),\n );\n if (prepared && verticallyDissonant && resolves) {\n const ic = intervalAboveBass(note.pitch, others, chord);\n const type = suspensionType(ic, next.pitch - note.pitch);\n if (type !== null) {\n labels.push({ kind: 'suspension', type, resolveTo: next.pitch });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const up = note.pitch - prev.pitch > 0;\n const contInto = next.pitch - note.pitch > 0;\n const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;\n if (\n prevMember &&\n nextMember &&\n !member &&\n isStep(note.pitch, prev.pitch) &&\n isStep(next.pitch, note.pitch) &&\n sameDir\n ) {\n labels.push({ kind: 'passing' });\n handled = true;\n }\n }\n\n if (\n !handled &&\n chord &&\n prev &&\n next &&\n !member &&\n prev.pitch === next.pitch &&\n isChordMember(prev.pitch, chordAtBeat(prev.startBeat)) &&\n isStep(note.pitch, prev.pitch)\n ) {\n labels.push({ kind: 'neighbor' });\n handled = true;\n }\n\n if (!handled && chord && next && !member && note.pitch === next.pitch) {\n const nextChord = chordAtBeat(next.startBeat);\n if (isChordMember(next.pitch, nextChord)) {\n labels.push({ kind: 'anticipation' });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const stepFromPrev = isStep(note.pitch, prev.pitch);\n const leapToNext = Math.abs(next.pitch - note.pitch) >= 3;\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {\n labels.push({ kind: 'escape' });\n handled = true;\n }\n }\n\n if (!handled && chord && !member) {\n const ic = intervalAboveRoot(note.pitch, chord);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid =\n (ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5)) ||\n (ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10));\n if (avoid) {\n labels.push({ kind: 'avoid' });\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n } else if (isTension) {\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n } else {\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n }\n }\n\n if (next && isLeadingToneResolution(note.pitch, next.pitch, key)) {\n labels.push({ kind: 'leadingTone', resolveTo: next.pitch });\n }\n\n const analyzed: AnalyzedNote = {\n noteId: note.id ?? i,\n pitch: note.pitch,\n startBeat: note.startBeat,\n durationBeat: note.durationBeat,\n labels,\n rationale: describe(labels),\n };\n if (note.originalIndex !== undefined) {\n analyzed.originalIndex = note.originalIndex;\n }\n result.push(analyzed);\n }\n\n return result;\n}\n\n/** Build a short rationale from a note's primary label. */\nfunction describe(labels: TheoryLabel[]): string {\n const primary = labels[0];\n if (!primary) {\n return 'Unclassified note';\n }\n switch (primary.kind) {\n case 'chordTone':\n return `Chord ${primary.role}`;\n case 'tension':\n return `${primary.degree}th tension`;\n case 'avoid':\n return 'Avoid note';\n case 'passing':\n return 'Passing tone between chord tones';\n case 'neighbor':\n return 'Neighbor tone';\n case 'suspension':\n return `Suspension (${primary.type})`;\n case 'anticipation':\n return 'Anticipation of the next chord';\n case 'escape':\n return 'Escape tone';\n case 'needsResolution':\n return 'Unresolved dissonance';\n case 'leadingTone':\n return 'Leading tone resolving to the tonic';\n }\n}\n","/**\n * Arrangement track-role analysis: a single harmony is inferred from every\n * track's notes pooled together, each track is then analysed against that shared\n * harmony, and notes that clash with the sounding chord are collected as\n * conflicts.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport {\n evaluateSafety,\n NoteSafety,\n type SafetyProfile,\n type VoiceSnapshot,\n} from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport {\n type CadenceHit,\n type ChordTimeline,\n chordTimelineFromNotes,\n detectCadences,\n} from '../timeline/index.js';\nimport {\n type AnalyzedNote,\n analyzeVoice,\n type TheoryLabel,\n type VoiceNote,\n} from '../voice/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\n\n/**\n * The musical role a track plays in the arrangement.\n *\n * Only `drums` changes the analysis: its pitches select instruments rather than\n * naming harmony, so such a track is excluded from chord and key inference and\n * from every voice-leading comparison, and is reported back with no\n * annotations. `harmony` is meaningful through\n * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords\n * are inferred from. The rest are labels carried through to\n * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so\n * a track left unlabelled reports `'other'` rather than a guess.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';\n\n/**\n * One input track: its notes plus optional name and role.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementTrack = {\n name?: string;\n role?: TrackRole;\n notes: readonly NoteEvent[];\n};\n\n/**\n * A track after analysis: its resolved name, role, and per-note annotations.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackAnalysis = {\n name: string;\n role: TrackRole;\n /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */\n notes: AnalyzedNote[];\n};\n\n/**\n * A note that clashes with the harmony sounding beneath it.\n *\n * @category Arrangement & Analysis\n */\nexport type Conflict = {\n beat: number;\n trackName: string;\n /** The clashing track's index in the caller's input array. */\n trackIndex: number;\n /** The note's index in that track's own note array. */\n originalIndex?: number;\n /** The id the note carries in {@link TrackAnalysis.notes}. */\n noteId: number;\n pitch: number;\n /**\n * The note's theory labels at its onset, so a caller can tell a genuine clash\n * from a passing tone, a neighbour, or a prepared suspension — all of which\n * are ordinary non-chord tones that still evaluate as unsafe.\n */\n labels: TheoryLabel[];\n /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */\n safety: NoteSafety;\n /** {@link SafetyResult.reasons} bitmask. */\n reasons: number;\n /** Preferred stepwise resolution, when the safety evaluator provides one. */\n resolveTo?: number;\n /** Nearby fully safe replacement pitches, nearest first. */\n suggestions?: number[];\n rationale?: string;\n};\n\n/**\n * The full result of {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementAnalysis = {\n key: KeyScale;\n timeline: ChordTimeline;\n segmentConfidence: number[];\n cadences: CadenceHit[];\n tracks: TrackAnalysis[];\n /** Notes clashing with the sounding harmony, worst severity first. */\n conflicts: Conflict[];\n};\n\n/**\n * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementOptions = {\n /** Key context; inferred from the pooled notes when omitted. */\n key?: KeyScale;\n /**\n * A chord timeline to analyse against, instead of inferring one from the\n * notes. Supply it to analyse a hand-written progression, or to reuse the\n * timeline of an earlier call rather than re-running chord inference.\n */\n timeline?: ChordTimeline;\n /**\n * Indices of the tracks the harmony is inferred from. Defaults to every\n * pitched track. Percussion tracks are excluded either way.\n */\n harmonyTracks?: number[];\n /**\n * Lowest severity reported in `conflicts`.\n *\n * @defaultValue {@link NoteSafety.Warning}\n */\n minSeverity?: NoteSafety;\n /**\n * Time signature; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Chord-slot length in beats; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * Safety profile used for conflict detection; defaults to `pop`.\n *\n * @defaultValue `'pop'`\n */\n profile?: SafetyProfile;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Sounding pitches of every sub-voice at a beat, excluding one sub-voice.\n *\n * Only the sub-voice under analysis is excluded — sibling sub-voices of the\n * same track are included, so dissonant clusters inside a single polyphonic\n * track are still detected.\n */\nfunction otherVoicesSounding(\n prepared: PreparedTrack[],\n excludeTrack: number,\n excludeVoice: number,\n beat: number,\n): VoiceSnapshot[] {\n const out: VoiceSnapshot[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (let v = 0; v < track.voices.length; v += 1) {\n if (t === excludeTrack && v === excludeVoice) {\n continue;\n }\n // Each sub-voice is monophonic, so this is one binary search per voice\n // rather than a scan of every note in the arrangement.\n const note = track.voices[v]?.at(beat);\n if (note === undefined) {\n continue;\n }\n const snap: VoiceSnapshot = { pitch: note.pitch };\n // Motion reasons compare one real transition shared by both voices.\n // A voice attacking exactly here contributes its adjacent predecessor;\n // a sustained voice contributes the same pitch (oblique motion).\n const previous = Math.abs(note.startBeat - beat) <= EPS ? note.prevPitch : note.pitch;\n if (previous !== undefined) {\n snap.prevPitch = previous;\n }\n out.push(snap);\n }\n }\n return out;\n}\n\n/** Index of the first segment starting strictly after a beat. */\nfunction firstSegmentAfter(timeline: ChordTimeline, beat: number): number {\n let low = 0;\n let high = timeline.segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((timeline.segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * The beats at which a note must be checked against the harmony: its onset,\n * plus the onset of every later chord segment the note sustains across. Each\n * crossed segment is visited exactly once (at its own start), so a boundary\n * beat is never evaluated twice for the same note.\n *\n * Segments are in beat order, so the crossed ones are a contiguous run found by\n * binary search rather than by scanning the whole timeline for every note.\n */\nfunction evaluationBeats(note: VoiceNote, timeline: ChordTimeline): number[] {\n const noteEnd = note.startBeat + note.durationBeat;\n const beats = [note.startBeat];\n for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {\n const start = timeline.segments[i]?.startBeat;\n if (start === undefined || start >= noteEnd - EPS) {\n break;\n }\n beats.push(start);\n }\n return beats;\n}\n\n/**\n * Analyse a whole arrangement against a single inferred harmony.\n *\n * The chord timeline and key are inferred from the pooled notes of every\n * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is\n * robust even when roles are absent or a track doubles the harmony, so it is\n * preferred over deriving the harmony from a subset. A track marked\n * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and\n * pooling them would corrupt the key, the timeline and every reading built on\n * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to\n * analyse against a progression you already have. Zero- and negative-length\n * notes never sound, so they are dropped at ingest and appear in neither the\n * annotations nor the conflicts.\n *\n * Voice analysis assumes one monophonic voice at a time, so each track is\n * first partitioned into monophonic sub-voices (a polyphonic block-chord track\n * fans out into one sub-voice per simultaneous note; a monophonic track is\n * unaffected). Each sub-voice is labelled note-by-note against the harmony\n * with {@link analyzeVoice}, seeing every other sub-voice — including siblings\n * within the same track — as its accompaniment. Every note is then re-checked\n * with {@link evaluateSafety} at its onset and again at each chord change it\n * sustains across, so a held note that clashes with a later chord is caught:\n * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per\n * clashing beat, sorted worst severity first and then by beat.\n *\n * Every ordinary non-chord tone — a passing note, a neighbour, a prepared\n * suspension — is by definition unsafe against the chord under it, so the\n * conflict list is a report, not a fault list. Each conflict carries the note's\n * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those\n * apart and map a conflict back to the note it passed in; `minSeverity` narrows\n * the list to outright dissonance.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options; see {@link ArrangementOptions}.\n * @returns The inferred harmony, per-track annotations, cadences, and conflicts.\n * @example\n * ```ts\n * import { analyzeArrangement } from '@libraz/libcantus';\n * const melody = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 },\n * { pitch: 67, startBeat: 2, durationBeat: 2 },\n * ];\n * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);\n * conflicts; // notes clashing with the inferred harmony, worst severity first\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeArrangement(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n): ArrangementAnalysis {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n let noteCount = 0;\n for (let index = 0; index < tracks.length; index += 1) {\n const notes = tracks[index]?.notes;\n // A missing array is rejected by name here rather than becoming a raw\n // TypeError further down, where the message no longer says which track.\n if (!Array.isArray(notes)) {\n throw new InvalidInputError(\n `tracks[${index}].notes must be an array; received ${typeof notes}`,\n );\n }\n assertNoteEvents(notes, `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n noteCount += notes.length;\n }\n // The pooled total is what the downstream timeline analysis actually sizes\n // its work by, so it is checked here, under this layer's own name.\n assertGenerationBudget(noteCount, 'arrangement notes', budget);\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const minSeverity = opts.minSeverity ?? NoteSafety.Warning;\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n\n const inferred =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled)[0]?.key ?? majorKey(0),\n segmentConfidence: opts.timeline.segments.map(() => 1),\n };\n const { timeline, key, segmentConfidence } = inferred;\n const cadences = detectCadences(timeline, key);\n const prepared = prepareTracks(tracks);\n\n const trackAnalyses: TrackAnalysis[] = [];\n const conflicts: Conflict[] = [];\n\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track) {\n continue;\n }\n if (isPercussion(track.role)) {\n // A drum hit has no harmonic reading, so labelling it against the chord\n // would produce noise rather than analysis.\n trackAnalyses.push({ name: track.name, role: track.role, notes: [] });\n continue;\n }\n const notes: AnalyzedNote[] = [];\n for (let v = 0; v < track.voices.length; v += 1) {\n const subVoice = track.voices[v];\n if (!subVoice) {\n continue;\n }\n const analyzed = analyzeVoice(subVoice.voice, timeline.at, key, (beat) =>\n otherVoicesSounding(prepared, t, v, beat),\n );\n notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));\n const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));\n\n for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {\n const note = subVoice.voice[noteIndex];\n const preparedNote = subVoice.sounding[noteIndex];\n if (note === undefined || preparedNote === undefined) {\n continue;\n }\n for (const beat of evaluationBeats(note, timeline)) {\n const atOnset = Math.abs(beat - note.startBeat) <= EPS;\n const result = evaluateSafety({\n profile,\n candidatePitch: note.pitch,\n prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,\n chord: timeline.at(beat),\n key,\n otherVoices: otherVoicesSounding(prepared, t, v, beat),\n strongBeat: isStrongBeat(beat, ts),\n });\n if (result.safety !== NoteSafety.Safe && result.safety >= minSeverity) {\n const conflict: Conflict = {\n beat,\n trackName: track.name,\n trackIndex: track.trackIndex,\n noteId: note.id,\n pitch: note.pitch,\n labels: labelsById.get(note.id) ?? [],\n safety: result.safety,\n reasons: result.reasons,\n };\n if (note.originalIndex !== undefined) {\n conflict.originalIndex = note.originalIndex;\n }\n if (result.rationale !== undefined) {\n conflict.rationale = result.rationale;\n }\n if (result.resolveTo !== undefined) {\n conflict.resolveTo = result.resolveTo;\n }\n if (result.suggestions !== undefined) {\n conflict.suggestions = [...result.suggestions];\n }\n conflicts.push(conflict);\n }\n }\n }\n }\n // Sub-voice results interleave; restore the track's onset-then-pitch order.\n notes.sort((a, b) => a.noteId - b.noteId);\n trackAnalyses.push({ name: track.name, role: track.role, notes });\n }\n\n conflicts.sort((a, b) => b.safety - a.safety || a.beat - b.beat);\n\n return { key, timeline, segmentConfidence, cadences, tracks: trackAnalyses, conflicts };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../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"],"sourcesContent":["import { pitchClassOf as pitchClass } from '../../core/pitch/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/** 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/**\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, then by\n * scale size (heptatonic before larger scales), then by 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)) {\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 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 if (a.size !== b.size) {\n return a.size - b.size;\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 if (!scaleMatchesChord(chordPcs, mask, rootPc)) {\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 if (chordSet.has(pitchClass(pc - 1))) {\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 if (!scaleMatchesChord(chordPcs, mask, rootPc)) {\n return [];\n }\n const chordSet = new Set(chordPcs);\n const avoidSet = 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 const matches = chordScales(chord);\n const chosen = limit === undefined ? matches : matches.slice(0, Math.max(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 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 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/** 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 if (degreeOffset !== undefined) {\n const letter = mod7(tonic.letter + degreeOffset);\n return { letter, alter: alterFor(letter, pc) };\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 exported for the model layer's inversion support; the package\n * barrels intentionally expose the key-aware {@link spellChord} entry point.\n */\nexport function spellChordFromRoot(chord: Chord, root: Note): Note[] {\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} from '../../core/pitch/index.js';\nimport { assertFiniteNumber } 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})?/;\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})?$/;\n\n/** Recognized quality suffixes, keyed by their exact lead-sheet spelling. */\nconst QUALITY_MAP: Record<string, ChordQuality> = {\n '': '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 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 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.trim();\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 const chord = parseChordSymbol(text);\n const transposed: Chord = { ...chord, rootPc: pitchClass(chord.rootPc + semitones) };\n if (chord.bassPc !== undefined) {\n transposed.bassPc = pitchClass(chord.bassPc + semitones);\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/**\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 = 40;\n/** Hard cap on candidate voicings evaluated per chord, keeping the search bounded. */\nconst 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 MAX_SEARCH_NODES = MAX_CANDIDATES * 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 {@link MAX_CANDIDATES} completed\n * voicings and {@link MAX_SEARCH_NODES} 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): 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 build = (voice: number): void => {\n nodes += 1;\n if (results.length >= MAX_CANDIDATES || nodes > MAX_SEARCH_NODES) {\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 (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 structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport type { VoicingOptions } from './satb.js';\nimport { 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 const maxSpacing = resolveMaxSpacing(opts);\n const candidates = enumerateVoicings(chord, ranges, maxSpacing);\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(current, candidate) +\n VIOLATION_PENALTY * violationCount(current, candidate);\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 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\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/**\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);\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\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 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, () => enumerateVoicings(chord, ranges, maxSpacing));\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"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../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"],"sourcesContent":["/**\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 return (\n value instanceof InvalidInputError ||\n value instanceof NoSolutionError ||\n value instanceof BudgetExceededError\n );\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/**\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/**\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 assertInteger(event.pitch, `${name}.pitch`, 0, 127);\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 TypeError(`${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};\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 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 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 octaves = Math.floor(steps / 12);\n const letterSteps = (LETTER_STEPS_BY_SEMITONE[steps - 12 * octaves] ?? 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 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, with an ascending (positive) span.\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 assertInteger(interval.number, 'interval.number', 1, 64);\n assertFiniteNumber(interval.semitones, 'interval.semitones');\n const 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 return { number, quality, semitones };\n}\n","import { assertFiniteNumber } from '../validation/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 assertFiniteNumber(semitones, 'semitones');\n return Math.abs(Math.round(semitones)) % 12;\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"],"mappings":"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","names":["ConsonanceClass"]}
|