@libraz/libcantus 0.9.1 → 0.9.2
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 +15 -0
- package/dist/analyze/index.cjs +1697 -0
- package/dist/analyze/index.cjs.map +1 -0
- package/dist/analyze/index.d.cts +429 -0
- package/dist/analyze/index.d.ts +429 -0
- package/dist/analyze/index.js +51 -0
- package/dist/analyze/index.js.map +1 -0
- package/dist/chunk-5HTNMQW4.js +653 -0
- package/dist/chunk-5HTNMQW4.js.map +1 -0
- package/dist/chunk-7Y7WF64D.js +410 -0
- package/dist/chunk-7Y7WF64D.js.map +1 -0
- package/dist/chunk-A4532ZFR.js +259 -0
- package/dist/chunk-A4532ZFR.js.map +1 -0
- package/dist/chunk-BHUT2S5G.js +22 -0
- package/dist/chunk-BHUT2S5G.js.map +1 -0
- package/dist/chunk-CCBLVAPK.js +42 -0
- package/dist/chunk-CCBLVAPK.js.map +1 -0
- package/dist/chunk-COKTX2G5.js +907 -0
- package/dist/chunk-COKTX2G5.js.map +1 -0
- package/dist/chunk-DYWFNC4A.js +152 -0
- package/dist/chunk-DYWFNC4A.js.map +1 -0
- package/dist/chunk-GQKN5KDT.js +159 -0
- package/dist/chunk-GQKN5KDT.js.map +1 -0
- package/dist/chunk-LN7ZZMD2.js +530 -0
- package/dist/chunk-LN7ZZMD2.js.map +1 -0
- package/dist/chunk-MSWFZF7J.js +118 -0
- package/dist/chunk-MSWFZF7J.js.map +1 -0
- package/dist/chunk-R3S2LUBH.js +2722 -0
- package/dist/chunk-R3S2LUBH.js.map +1 -0
- package/dist/chunk-SDM3VJJ5.js +41 -0
- package/dist/chunk-SDM3VJJ5.js.map +1 -0
- package/dist/chunk-XTC3X6MS.js +816 -0
- package/dist/chunk-XTC3X6MS.js.map +1 -0
- package/dist/chunk-ZXW5YIWK.js +55 -0
- package/dist/chunk-ZXW5YIWK.js.map +1 -0
- package/dist/core/index.cjs +432 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +210 -0
- package/dist/core/index.d.ts +210 -0
- package/dist/core/index.js +73 -0
- package/dist/core/index.js.map +1 -0
- package/dist/function-2bJW1Zp_.d.ts +187 -0
- package/dist/function-r2sh-jOO.d.cts +187 -0
- package/dist/generate/index.cjs +3787 -0
- package/dist/generate/index.cjs.map +1 -0
- package/dist/generate/index.d.cts +980 -0
- package/dist/generate/index.d.ts +980 -0
- package/dist/generate/index.js +52 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index-BMPxwjwV.d.ts +151 -0
- package/dist/index-BOBXBNwP.d.cts +157 -0
- package/dist/index-BdeQ1r_p.d.cts +56 -0
- package/dist/index-BreoJRw0.d.ts +127 -0
- package/dist/index-CGMZNCx6.d.cts +140 -0
- package/dist/index-CGMZNCx6.d.ts +140 -0
- package/dist/index-CM5W4rcm.d.ts +157 -0
- package/dist/index-CP3wAfEH.d.ts +56 -0
- package/dist/index-Cl7mhOFM.d.cts +164 -0
- package/dist/index-Cl7mhOFM.d.ts +164 -0
- package/dist/index-DZ1M21do.d.cts +127 -0
- package/dist/index-D_ZTUNKp.d.cts +151 -0
- package/dist/index.cjs +3016 -2984
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -3982
- package/dist/index.d.ts +14 -3982
- package/dist/index.js +170 -6504
- package/dist/index.js.map +1 -1
- package/dist/model/index.cjs +2435 -0
- package/dist/model/index.cjs.map +1 -0
- package/dist/model/index.d.cts +630 -0
- package/dist/model/index.d.ts +630 -0
- package/dist/model/index.js +21 -0
- package/dist/model/index.js.map +1 -0
- package/dist/styled-BjXtOFNk.d.cts +282 -0
- package/dist/styled-D7BAx3S3.d.ts +282 -0
- package/dist/theory/index.cjs +1677 -0
- package/dist/theory/index.cjs.map +1 -0
- package/dist/theory/index.d.cts +535 -0
- package/dist/theory/index.d.ts +535 -0
- package/dist/theory/index.js +149 -0
- package/dist/theory/index.js.map +1 -0
- package/dist/types-MGP29r3t.d.cts +30 -0
- package/dist/types-MGP29r3t.d.ts +30 -0
- package/package.json +27 -2
- package/src/analyze/arrange/index.ts +21 -0
- package/src/analyze/arrange/internal.ts +125 -0
- package/src/analyze/arrange/tension.ts +187 -0
- package/src/analyze/arrange/tracks.ts +279 -0
- package/src/{detect → analyze/detect}/index.ts +4 -4
- package/src/analyze/functional/borrowed.ts +84 -0
- package/src/analyze/functional/cadence.ts +66 -0
- package/src/analyze/functional/function.ts +184 -0
- package/src/analyze/functional/index.ts +23 -0
- package/src/analyze/functional/internal.ts +29 -0
- package/src/{functional/index.ts → analyze/functional/roman.ts} +9 -327
- package/src/analyze/index.ts +49 -0
- package/src/{timeline → analyze/timeline}/index.ts +7 -11
- package/src/{analysis → analyze/voice}/index.ts +8 -5
- package/src/core/index.ts +47 -0
- package/src/{bass → generate/bass}/index.ts +5 -5
- package/src/{countermelody → generate/countermelody}/index.ts +11 -11
- package/src/{drums → generate/drums}/index.ts +2 -2
- package/src/{drums → generate/drums}/rng.ts +2 -2
- package/src/{groove → generate/groove}/index.ts +3 -3
- package/src/{harmonize → generate/harmonize}/index.ts +13 -13
- package/src/generate/index.ts +59 -0
- package/src/{motif → generate/motif}/index.ts +7 -7
- package/src/{progression → generate/progression}/index.ts +11 -25
- package/src/{reharmony → generate/reharmony}/index.ts +11 -11
- package/src/{rhythm → generate/rhythm}/index.ts +2 -2
- package/src/index.ts +130 -117
- package/src/model/chord.ts +423 -0
- package/src/model/index.ts +5 -1096
- package/src/model/interval.ts +82 -0
- package/src/model/key.ts +266 -0
- package/src/model/note.ts +176 -0
- package/src/model/progression.ts +161 -0
- package/src/model/shared.ts +12 -0
- package/src/{chord → theory/chord}/index.ts +19 -1
- package/src/{counterpoint → theory/counterpoint}/index.ts +2 -2
- package/src/theory/index.ts +107 -0
- package/src/{safety → theory/safety}/index.ts +1 -1
- package/src/theory/scale/degrees.ts +123 -0
- package/src/theory/scale/index.ts +28 -0
- package/src/theory/scale/key.ts +56 -0
- package/src/theory/scale/masks.ts +153 -0
- package/src/{spelling → theory/spelling}/index.ts +3 -3
- package/src/{symbol → theory/symbol}/index.ts +8 -1
- package/src/theory/voicing/index.ts +5 -0
- package/src/theory/voicing/internal.ts +168 -0
- package/src/theory/voicing/leading.ts +112 -0
- package/src/theory/voicing/satb.ts +195 -0
- package/src/theory/voicing/styled.ts +176 -0
- package/src/arrange/index.ts +0 -571
- package/src/scale/index.ts +0 -331
- package/src/voicing/index.ts +0 -623
- /package/src/{interval → core/interval}/index.ts +0 -0
- /package/src/{meter → core/meter}/index.ts +0 -0
- /package/src/{pitch → core/pitch}/index.ts +0 -0
- /package/src/{random → core/random}/index.ts +0 -0
- /package/src/{tuning → core/tuning}/index.ts +0 -0
- /package/src/{types.ts → core/types.ts} +0 -0
- /package/src/{drums → generate/drums}/beat.ts +0 -0
- /package/src/{drums → generate/drums}/euclid.ts +0 -0
- /package/src/{drums → generate/drums}/fills.ts +0 -0
- /package/src/{drums → generate/drums}/ghost.ts +0 -0
- /package/src/{drums → generate/drums}/hihat.ts +0 -0
- /package/src/{drums → generate/drums}/hit.ts +0 -0
- /package/src/{drums → generate/drums}/internal.ts +0 -0
- /package/src/{drums → generate/drums}/kick.ts +0 -0
- /package/src/{drums → generate/drums}/percussion.ts +0 -0
- /package/src/{drums → generate/drums}/swing.ts +0 -0
- /package/src/{chordscale → theory/chordscale}/index.ts +0 -0
- /package/src/{harmony → theory/harmony}/index.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/analyze/index.ts","../../src/core/meter/index.ts","../../src/theory/scale/degrees.ts","../../src/theory/scale/masks.ts","../../src/theory/scale/key.ts","../../src/theory/chord/index.ts","../../src/core/interval/index.ts","../../src/theory/counterpoint/index.ts","../../src/theory/safety/index.ts","../../src/analyze/functional/internal.ts","../../src/analyze/functional/roman.ts","../../src/analyze/functional/function.ts","../../src/analyze/functional/borrowed.ts","../../src/analyze/functional/cadence.ts","../../src/analyze/detect/index.ts","../../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"],"sourcesContent":["/**\n * Public API of the analyze layer. Re-exports every analyze-layer module;\n * also available from the package root.\n */\nexport type {\n ArrangementAnalysis,\n ArrangementOptions,\n ArrangementTrack,\n Conflict,\n TensionPoint,\n TrackAnalysis,\n TrackRole,\n} from './arrange/index.js';\nexport { analyzeArrangement, tensionCurve } from './arrange/index.js';\nexport type { ChordMatch, KeyMatch } from './detect/index.js';\nexport { detectChord, detectChordBest, detectKey } from './detect/index.js';\nexport type {\n BorrowedSource,\n Cadence,\n ChordAnalysis,\n HarmonicFunction,\n} from './functional/index.js';\nexport {\n analyzeChord,\n borrowedSource,\n chordToRoman,\n detectCadence,\n functionOf,\n isBorrowedChord,\n isDiatonic,\n isMinorKey,\n parallelKey,\n romanToChord,\n secondaryDominant,\n} from './functional/index.js';\nexport type {\n CadenceHit,\n ChordSegment,\n ChordTimeline,\n ChordTimelineOptions,\n ChordTimelineResult,\n} from './timeline/index.js';\nexport {\n chordTimelineFromChords,\n chordTimelineFromNotes,\n detectCadences,\n} from './timeline/index.js';\nexport type { AnalyzedNote, TheoryLabel, VoiceNote } from './voice/index.js';\nexport { analyzeVoice } from './voice/index.js';\n","/**\n * Meter and time signatures: bar/beat arithmetic, simple vs. compound meters,\n * metric-accent hierarchy, and tuplet subdivision.\n *\n * Positions and durations are measured in quarter-note beats, matching the rest\n * of the library's beat convention (four quarter-note beats per bar in 4/4).\n */\n\n/**\n * A time signature as a numerator over a note-value denominator.\n *\n * @category Rhythm & Meter\n */\nexport type TimeSignature = {\n numerator: number;\n denominator: number;\n /**\n * Optional additive grouping of the bar's main pulses, as the felt-beat\n * lengths in pulses — e.g. `[2, 2, 3]` for a 2+2+3 reading of 7/8, or\n * `[3, 2]` for 5/8. The entries must be positive integers summing to\n * {@link pulsesPerBar}. When present, the head pulse of each group (other\n * than the downbeat) is treated as a secondary strong pulse by\n * {@link metricWeight}/{@link isStrongBeat}; when absent, all main pulses\n * are weighted equally as flat, evenly divided pulses.\n */\n grouping?: number[];\n};\n\n/**\n * A position expressed as a bar index and a quarter-note offset within the bar.\n *\n * @category Rhythm & Meter\n */\nexport type BarPosition = {\n bar: number;\n /** Quarter-note offset from the start of the bar. */\n beat: number;\n};\n\nconst EPS = 1e-9;\n\n/** Whether `value` is an integer multiple of `unit` (within a float tolerance). */\nfunction isMultiple(value: number, unit: number): boolean {\n if (unit === 0) {\n return false;\n }\n const ratio = value / unit;\n return Math.abs(ratio - Math.round(ratio)) < EPS;\n}\n\n/**\n * Parse a time signature such as `\"4/4\"` or `\"6/8\"`.\n *\n * @param text The signature text.\n * @returns The parsed time signature.\n * @throws If the text is not `n/d` with positive integers.\n * @example\n * ```ts\n * import { parseTimeSignature } from '@libraz/libcantus';\n * parseTimeSignature('6/8'); // { numerator: 6, denominator: 8 }\n * ```\n * @category Rhythm & Meter\n */\nexport function parseTimeSignature(text: string): TimeSignature {\n const match = /^\\s*(\\d+)\\s*\\/\\s*(\\d+)\\s*$/.exec(text);\n if (!match) {\n throw new Error(`Invalid time signature: ${text}`);\n }\n const numerator = Number.parseInt(match[1] ?? '', 10);\n const denominator = Number.parseInt(match[2] ?? '', 10);\n if (numerator <= 0 || denominator <= 0) {\n throw new Error(`Invalid time signature: ${text}`);\n }\n return { numerator, denominator };\n}\n\n/**\n * Render a time signature as `\"n/d\"`.\n *\n * @category Rhythm & Meter\n */\nexport function formatTimeSignature(ts: TimeSignature): string {\n return `${ts.numerator}/${ts.denominator}`;\n}\n\n/**\n * Whether a meter is compound: its main pulses each divide into three, as in\n * 6/8, 9/8, 12/8, or 6/4. Compound meters are those whose numerator is a\n * multiple of three greater than three, independent of the denominator. Meters\n * like 3/8 and 3/4 (simple triples, numerator 3) are not compound.\n *\n * @param ts The time signature.\n * @returns True for compound meters.\n * @category Rhythm & Meter\n */\nexport function isCompound(ts: TimeSignature): boolean {\n return ts.numerator % 3 === 0 && ts.numerator > 3;\n}\n\n/** Length of one denominator unit in quarter-note beats. */\nfunction unitBeats(ts: TimeSignature): number {\n return 4 / ts.denominator;\n}\n\n/**\n * Length of a bar in quarter-note beats.\n *\n * @param ts The time signature.\n * @returns The bar length in quarter notes.\n * @category Rhythm & Meter\n */\nexport function beatsPerBar(ts: TimeSignature): number {\n return ts.numerator * unitBeats(ts);\n}\n\n/** Length of one main pulse (a dotted value in compound meters) in quarter notes. */\nfunction pulseBeats(ts: TimeSignature): number {\n return isCompound(ts) ? 3 * unitBeats(ts) : unitBeats(ts);\n}\n\n/**\n * Number of main pulses (felt beats) per bar: the numerator for simple meters,\n * a third of it for compound meters.\n *\n * For additive/irregular meters such as 7/8 or 5/8 this counts the raw pulses\n * (7 and 5 respectively), which are otherwise felt as flat, evenly divided and\n * equally accented pulses. To recover a 2+2+3 or 3+2 felt-beat grouping, set\n * {@link TimeSignature.grouping}; {@link metricWeight} then accents each\n * group's head pulse.\n *\n * @param ts The time signature.\n * @returns The pulse count per bar.\n * @category Rhythm & Meter\n */\nexport function pulsesPerBar(ts: TimeSignature): number {\n return beatsPerBar(ts) / pulseBeats(ts);\n}\n\n/**\n * Whether `grouping` is a valid additive grouping for a bar of `pulses` main\n * pulses: a non-empty list of positive integers summing to `pulses`.\n */\nfunction isValidGrouping(grouping: number[], pulses: number): boolean {\n if (grouping.length === 0) {\n return false;\n }\n let sum = 0;\n for (const g of grouping) {\n if (!Number.isInteger(g) || g <= 0) {\n return false;\n }\n sum += g;\n }\n return sum === pulses;\n}\n\n/** Whether `pulseIndex` is the head pulse of one of the additive groups. */\nfunction isGroupHead(grouping: number[], pulseIndex: number): boolean {\n let acc = 0;\n for (const g of grouping) {\n if (acc === pulseIndex) {\n return true;\n }\n acc += g;\n }\n return false;\n}\n\n/**\n * Convert an absolute quarter-note position to a bar index and in-bar offset.\n *\n * @param beatInQuarters Absolute position in quarter-note beats.\n * @param ts The time signature.\n * @returns The bar and in-bar quarter-note offset.\n * @example\n * ```ts\n * import { parseTimeSignature, beatToBarPosition } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * beatToBarPosition(5, ts); // { bar: 1, beat: 1 }\n * ```\n * @category Rhythm & Meter\n */\nexport function beatToBarPosition(beatInQuarters: number, ts: TimeSignature): BarPosition {\n const barLen = beatsPerBar(ts);\n const bar = Math.floor(beatInQuarters / barLen);\n return { bar, beat: beatInQuarters - bar * barLen };\n}\n\n/**\n * Convert a bar index and in-bar offset back to an absolute quarter-note\n * position.\n *\n * @param pos The bar position.\n * @param ts The time signature.\n * @returns The absolute position in quarter-note beats.\n * @category Rhythm & Meter\n */\nexport function barPositionToBeat(pos: BarPosition, ts: TimeSignature): number {\n return pos.bar * beatsPerBar(ts) + pos.beat;\n}\n\n/**\n * Metric weight of a position within its bar, on a 0–3 scale:\n * 3 the downbeat, 2 a secondary strong pulse, 1 any other main pulse, and 0 an\n * off-pulse subdivision.\n *\n * The secondary strong pulse is the bar's midpoint in even simple/compound\n * meters. For an additive/irregular meter with {@link TimeSignature.grouping}\n * set (e.g. `[2, 2, 3]` for 7/8), each group's head pulse other than the\n * downbeat is the secondary strong pulse instead; without a grouping every\n * non-downbeat main pulse of such a meter weighs 1 (flat, evenly divided\n * pulses).\n *\n * @param beatInQuarters Absolute or in-bar quarter-note position.\n * @param ts The time signature.\n * @returns The metric weight (0–3).\n * @throws If `ts.grouping` is present but is not a positive-integer list\n * summing to {@link pulsesPerBar}.\n * @example\n * ```ts\n * import { parseTimeSignature, metricWeight } from '@libraz/libcantus';\n * const ts = parseTimeSignature('4/4');\n * metricWeight(0, ts); // 3 (the downbeat)\n * ```\n * @category Rhythm & Meter\n */\nexport function metricWeight(beatInQuarters: number, ts: TimeSignature): number {\n const { beat } = beatToBarPosition(beatInQuarters, ts);\n const pulse = pulseBeats(ts);\n if (!isMultiple(beat, pulse)) {\n return 0;\n }\n const pulses = pulsesPerBar(ts);\n const pulseIndex = Math.round(beat / pulse) % pulses;\n if (pulseIndex === 0) {\n return 3;\n }\n const grouping = ts.grouping;\n if (grouping !== undefined) {\n if (!isValidGrouping(grouping, pulses)) {\n throw new Error(\n `Invalid grouping [${grouping.join(', ')}] for ${formatTimeSignature(ts)}: ` +\n `entries must be positive integers summing to ${pulses}`,\n );\n }\n return isGroupHead(grouping, pulseIndex) ? 2 : 1;\n }\n if (pulses % 2 === 0 && pulseIndex === pulses / 2) {\n return 2;\n }\n return 1;\n}\n\n/**\n * Whether a position is metrically accented (weight 2 or more — a downbeat or a\n * secondary strong pulse).\n *\n * @param beatInQuarters Absolute or in-bar quarter-note position.\n * @param ts The time signature.\n * @returns True on strong beats.\n * @category Rhythm & Meter\n */\nexport function isStrongBeat(beatInQuarters: number, ts: TimeSignature): boolean {\n return metricWeight(beatInQuarters, ts) >= 2;\n}\n\n/**\n * Subdivide a span into `count` equal tuplet durations (e.g. an eighth-note\n * triplet is `tuplet(1, 3)`).\n *\n * @param totalBeats Total span in quarter-note beats.\n * @param count Number of equal parts.\n * @returns `count` equal durations summing to `totalBeats`.\n * @throws If `count` is not a positive integer.\n * @category Rhythm & Meter\n */\nexport function tuplet(totalBeats: number, count: number): number[] {\n if (!Number.isInteger(count) || count <= 0) {\n throw new Error(`Invalid tuplet count: ${count}`);\n }\n return new Array<number>(count).fill(totalBeats / count);\n}\n","import type { KeyScale } from '../../core/types.js';\n\n/** Reduce a pitch (or pitch class) to a pitch class in [0, 11]. */\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\n/** Scale offset of a pitch relative to the key root, in [0, 11]. */\nfunction scaleOffset(pitch: number, key: KeyScale): number {\n return (pitchClass(pitch) - pitchClass(key.rootPc) + 12) % 12;\n}\n\n/**\n * Test whether a pitch belongs to the scale.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to test against.\n * @returns True if the pitch class is a member of the scale.\n *\n * @example\n * ```ts\n * import { majorKey, isScaleTone } from '@libraz/libcantus';\n * const key = majorKey(0); // C major\n * isScaleTone(64, key); // true — E is in C major\n * isScaleTone(61, key); // false — C# is not\n * ```\n *\n * @category Scales\n */\nexport function isScaleTone(pitch: number, key: KeyScale): boolean {\n return ((key.modeMask12 >> scaleOffset(pitch, key)) & 1) === 1;\n}\n\n/**\n * Find the nearest MIDI pitch whose pitch class is in the scale.\n *\n * The search expands symmetrically outward from `pitch`. On a tie (equal\n * distance above and below) the lower pitch is returned.\n *\n * @param pitch MIDI pitch to snap.\n * @param key The key/scale to snap to.\n * @returns The nearest in-scale MIDI pitch.\n *\n * @category Scales\n */\nexport function nearestScaleTone(pitch: number, key: KeyScale): number {\n const base = Math.round(pitch);\n for (let distance = 0; distance < 12; distance += 1) {\n const lower = base - distance;\n if (isScaleTone(lower, key)) {\n return lower;\n }\n const higher = base + distance;\n if (isScaleTone(higher, key)) {\n return higher;\n }\n }\n return base;\n}\n\n/**\n * Get the 0-based scale degree of a pitch.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to measure against.\n * @returns The 0-based degree, or -1 if the pitch is not in the scale.\n *\n * @category Scales\n */\nexport function pitchToScaleDegree(pitch: number, key: KeyScale): number {\n const offset = scaleOffset(pitch, key);\n if (((key.modeMask12 >> offset) & 1) === 0) {\n return -1;\n }\n let degree = 0;\n for (let n = 0; n < offset; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n degree += 1;\n }\n }\n return degree;\n}\n\n/**\n * List the pitch classes of a key's scale.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes, sorted ascending in [0, 11].\n *\n * @category Scales\n */\nexport function diatonicPitchClasses(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs.sort((a, b) => a - b);\n}\n\n/**\n * List the pitch classes of a key's scale in ascending scale-degree order.\n *\n * Degree 0 is the root; degrees follow the mask bits in offset order rather\n * than sorted pitch-class order.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes ordered by scale degree.\n *\n * @category Scales\n */\nexport function scaleTonesInDegreeOrder(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs;\n}\n","/**\n * Build a 12-bit mode mask from a list of semitone offsets above the root.\n *\n * Bit 0 (the root) is always set, enforcing the `KeyScale` invariant that the\n * root is a scale tone even when the offset list omits 0.\n *\n * @category Scales\n */\nexport function maskFromOffsets(offsets: readonly number[]): number {\n let mask = 1;\n for (const offset of offsets) {\n mask |= 1 << (((offset % 12) + 12) % 12);\n }\n return mask;\n}\n\n/**\n * Mode mask for the major (Ionian) scale: offsets {0, 2, 4, 5, 7, 9, 11}.\n *\n * @category Scales\n */\nexport const MAJOR_MASK = 0b101010110101;\n\n/**\n * Mode mask for the natural minor (Aeolian) scale: offsets {0, 2, 3, 5, 7, 8, 10}.\n *\n * @category Scales\n */\nexport const NATURAL_MINOR_MASK = 0b010110101101;\n\n/**\n * Harmonic minor: natural minor with a raised seventh — offsets {0,2,3,5,7,8,11}.\n *\n * @category Scales\n */\nexport const HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);\n\n/**\n * Ascending melodic minor: offsets {0,2,3,5,7,9,11}.\n *\n * @category Scales\n */\nexport const MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);\n\n/**\n * Dorian mode: offsets {0,2,3,5,7,9,10}.\n *\n * @category Scales\n */\nexport const DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);\n\n/**\n * Phrygian mode: offsets {0,1,3,5,7,8,10}.\n *\n * @category Scales\n */\nexport const PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);\n\n/**\n * Lydian mode: offsets {0,2,4,6,7,9,11}.\n *\n * @category Scales\n */\nexport const LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);\n\n/**\n * Mixolydian mode: offsets {0,2,4,5,7,9,10}.\n *\n * @category Scales\n */\nexport const MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);\n\n/**\n * Locrian mode: offsets {0,1,3,5,6,8,10}.\n *\n * @category Scales\n */\nexport const LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);\n\n/**\n * Major pentatonic: offsets {0,2,4,7,9}.\n *\n * @category Scales\n */\nexport const MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);\n\n/**\n * Minor pentatonic: offsets {0,3,5,7,10}.\n *\n * @category Scales\n */\nexport const MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);\n\n/**\n * Hexatonic blues scale: minor pentatonic plus the flat-fifth — {0,3,5,6,7,10}.\n *\n * @category Scales\n */\nexport const BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);\n\n/**\n * Whole-tone scale: offsets {0,2,4,6,8,10}.\n *\n * @category Scales\n */\nexport const WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);\n\n/**\n * Octatonic (half-step first): offsets {0,1,3,4,6,7,9,10}.\n *\n * @category Scales\n */\nexport const OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);\n\n/**\n * Octatonic (whole-step first): offsets {0,2,3,5,6,8,9,11}.\n *\n * @category Scales\n */\nexport const OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);\n\n/**\n * Chromatic scale: all twelve pitch classes.\n *\n * @category Scales\n */\nexport const CHROMATIC_MASK = 0b111111111111;\n\n/**\n * Named scale masks addressable by {@link scaleByName}.\n *\n * @category Scales\n */\nexport const NAMED_SCALES: Record<string, number> = {\n major: MAJOR_MASK,\n ionian: MAJOR_MASK,\n naturalMinor: NATURAL_MINOR_MASK,\n aeolian: NATURAL_MINOR_MASK,\n harmonicMinor: HARMONIC_MINOR_MASK,\n melodicMinor: MELODIC_MINOR_MASK,\n dorian: DORIAN_MASK,\n phrygian: PHRYGIAN_MASK,\n lydian: LYDIAN_MASK,\n mixolydian: MIXOLYDIAN_MASK,\n locrian: LOCRIAN_MASK,\n majorPentatonic: MAJOR_PENTATONIC_MASK,\n minorPentatonic: MINOR_PENTATONIC_MASK,\n blues: BLUES_MASK,\n wholeTone: WHOLE_TONE_MASK,\n octatonicHalfWhole: OCTATONIC_HALF_WHOLE_MASK,\n octatonicWholeHalf: OCTATONIC_WHOLE_HALF_MASK,\n chromatic: CHROMATIC_MASK,\n};\n","import type { KeyScale } from '../../core/types.js';\nimport { MAJOR_MASK, NAMED_SCALES, NATURAL_MINOR_MASK } from './masks.js';\n\n/**\n * Build a `KeyScale` for a major key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { majorKey } from '@libraz/libcantus';\n * const cMajor = majorKey(0); // C major: { rootPc: 0, modeMask12: MAJOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function majorKey(rootPc: number): KeyScale {\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: MAJOR_MASK };\n}\n\n/**\n * Build a `KeyScale` for a natural-minor key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { minorKey } from '@libraz/libcantus';\n * const aMinor = minorKey(9); // A natural minor: { rootPc: 9, modeMask12: NATURAL_MINOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function minorKey(rootPc: number): KeyScale {\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: NATURAL_MINOR_MASK };\n}\n\n/**\n * Build a `KeyScale` from a named scale (see {@link NAMED_SCALES}).\n *\n * @param name The scale name, e.g. `'dorian'` or `'harmonicMinor'`.\n * @param rootPc The root pitch class.\n * @returns The key/scale.\n * @throws If the name is not a known scale.\n *\n * @example\n * ```ts\n * import { scaleByName } from '@libraz/libcantus';\n * const dDorian = scaleByName('dorian', 2); // D Dorian, rootPc 2\n * ```\n *\n * @category Scales\n */\nexport function scaleByName(name: string, rootPc: number): KeyScale {\n const mask = NAMED_SCALES[name];\n if (mask === undefined) {\n throw new Error(`Unknown scale: ${name}`);\n }\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: mask };\n}\n","import type { KeyScale } from '../../core/types.js';\nimport { scaleTonesInDegreeOrder } from '../scale/index.js';\n\n/**\n * Chord quality identifiers understood by the chord builder.\n *\n * @category Chords\n */\nexport type ChordQuality =\n | 'maj'\n | 'min'\n | 'dim'\n | 'aug'\n | 'maj7'\n | 'min7'\n | 'dom7'\n | 'dim7'\n | 'm7b5'\n | 'minMaj7'\n | 'aug7'\n | 'augMaj7'\n | 'majb5'\n | '6'\n | 'min6'\n | '6/9'\n | 'sus2'\n | 'sus4'\n | 'add9'\n | 'add11'\n | 'maj9'\n | 'min9'\n | 'dom9'\n | '7b9'\n | '7#9'\n | '7#11'\n | '7b13'\n | '11'\n | '13'\n | '5';\n\n/**\n * A bare spelled pitch used as an enharmonic hint: a diatonic letter\n * (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).\n * Mirrors the letter/alter half of the pitch module's `Note` without an octave.\n *\n * @category Chords\n */\nexport type PitchSpelling = {\n letter: number;\n alter: number;\n};\n\n/**\n * A chord expressed as a root pitch class plus semitone offsets.\n *\n * `rootSpelling`/`bassSpelling` are optional enharmonic hints recorded by\n * parsers (e.g. `parseChordSymbol('Bb7')`) so formatters can reproduce the\n * original spelling instead of defaulting to sharps. Consumers may ignore\n * them; a hint is only trusted when its pitch class still matches the\n * corresponding `rootPc`/`bassPc`.\n *\n * @category Chords\n */\nexport type Chord = {\n rootPc: number;\n quality: ChordQuality;\n intervals: number[];\n bassPc?: number;\n rootSpelling?: PitchSpelling;\n bassSpelling?: PitchSpelling;\n};\n\n/**\n * A chord positioned in time: a root pitch class and quality placed at a beat,\n * with optional harmonic-analysis hints. Serves as the neutral exchange record\n * between progression generators and timeline analysers.\n *\n * @category Composition\n */\nexport type ChordSpan = {\n rootPc: number;\n quality: ChordQuality;\n startBeat: number;\n bassPc?: number;\n /** 0-based scale degree of the chord root, when known. */\n degree?: number;\n /** True when the chord is a secondary dominant tonicizing another degree. */\n secondaryDominant?: boolean;\n};\n\n/** Semitone offsets from the root for each supported chord quality. */\nconst QUALITY_INTERVALS: Record<ChordQuality, number[]> = {\n maj: [0, 4, 7],\n min: [0, 3, 7],\n dim: [0, 3, 6],\n aug: [0, 4, 8],\n majb5: [0, 4, 6],\n maj7: [0, 4, 7, 11],\n min7: [0, 3, 7, 10],\n dom7: [0, 4, 7, 10],\n dim7: [0, 3, 6, 9],\n m7b5: [0, 3, 6, 10],\n minMaj7: [0, 3, 7, 11],\n aug7: [0, 4, 8, 10],\n augMaj7: [0, 4, 8, 11],\n '6': [0, 4, 7, 9],\n min6: [0, 3, 7, 9],\n '6/9': [0, 4, 7, 9, 14],\n sus2: [0, 2, 7],\n sus4: [0, 5, 7],\n add9: [0, 4, 7, 14],\n add11: [0, 4, 7, 17],\n maj9: [0, 4, 7, 11, 14],\n min9: [0, 3, 7, 10, 14],\n dom9: [0, 4, 7, 10, 14],\n '7b9': [0, 4, 7, 10, 13],\n '7#9': [0, 4, 7, 10, 15],\n '7#11': [0, 4, 7, 10, 18],\n '7b13': [0, 4, 7, 10, 20],\n '11': [0, 7, 10, 14, 17],\n '13': [0, 4, 7, 10, 14, 21],\n '5': [0, 7],\n};\n\n/** Reduce a value to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/**\n * All chord qualities the builder understands, longest templates last.\n *\n * @category Chords\n */\nexport function chordQualities(): ChordQuality[] {\n return Object.keys(QUALITY_INTERVALS) as ChordQuality[];\n}\n\n/**\n * Build a chord rooted on a diatonic scale degree.\n *\n * The root pitch class is the degree's diatonic pitch class in `key`; the\n * quality's interval template is attached unchanged. Degrees beyond the scale\n * length wrap around.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param ext Chord quality to apply.\n * @param key Key context supplying the diatonic root.\n * @returns The constructed chord.\n *\n * @category Chords\n */\nexport function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScale): Chord {\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n const index = length > 0 ? ((degree % length) + length) % length : 0;\n const rootPc = tones[index] ?? pitchClass(key.rootPc);\n return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };\n}\n\n/**\n * Build a chord from an explicit root pitch class and quality.\n *\n * @param rootPc Root pitch class (0..11).\n * @param quality Chord quality supplying the interval template.\n * @param bassPc Optional slash-chord bass pitch class.\n * @returns The constructed chord.\n *\n * @example\n * ```ts\n * import { makeChord } from '@libraz/libcantus';\n * const cmaj7 = makeChord(0, 'maj7');\n * // { rootPc: 0, quality: 'maj7', intervals: [0, 4, 7, 11] }\n * ```\n *\n * @category Chords\n */\nexport function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord {\n const chord: Chord = {\n rootPc: pitchClass(rootPc),\n quality,\n intervals: [...QUALITY_INTERVALS[quality]],\n };\n if (bassPc !== undefined) {\n chord.bassPc = pitchClass(bassPc);\n }\n return chord;\n}\n\n/**\n * Get the sorted, deduplicated pitch classes of a chord.\n *\n * @param chord The chord to enumerate.\n * @returns The chord's pitch classes, sorted ascending in [0, 11].\n *\n * @example\n * ```ts\n * import { makeChord, chordPitchClasses } from '@libraz/libcantus';\n * chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]\n * ```\n *\n * @category Chords\n */\nexport function chordPitchClasses(chord: Chord): number[] {\n const set = new Set<number>();\n for (const interval of chord.intervals) {\n set.add(pitchClass(chord.rootPc + interval));\n }\n return [...set].sort((a, b) => a - b);\n}\n\n/** The chord-tone offsets present in a chord, reduced to pitch-class space. */\nfunction chordToneOffsets(chord: Chord): Set<number> {\n return new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));\n}\n\n/**\n * Determine a pitch's harmonic role within a chord.\n *\n * The role is derived from the pitch's interval above the chord root, reduced\n * modulo 12. A major sixth (9) reads as a `sixth` for sixth chords but as a\n * `seventh` for a diminished-seventh chord; ninths and other tensions have no\n * basic role and return null.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @returns The chord-tone role, or null if the pitch has no basic role.\n *\n * @category Chords\n */\nexport function chordToneRole(\n pitch: number,\n chord: Chord,\n): 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | null {\n const interval = (pitchClass(pitch) - pitchClass(chord.rootPc) + 12) % 12;\n const tones = chordToneOffsets(chord);\n if (interval === 0) {\n return 'root';\n }\n if (interval === 3 || interval === 4) {\n return tones.has(interval) ? 'third' : null;\n }\n if (interval === 7) {\n return tones.has(7) ? 'fifth' : null;\n }\n if (interval === 6) {\n // A diminished fifth is the chord's fifth only when no perfect fifth is\n // present; alongside a perfect fifth it is a #11 tension, not a fifth.\n return tones.has(6) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 8) {\n // An augmented fifth is the chord's fifth only without a perfect fifth;\n // alongside a perfect fifth it is a b13 tension, not a fifth.\n return tones.has(8) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 9) {\n const hasHigherSeventh = tones.has(10) || tones.has(11);\n const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;\n if (isDiminishedSeventh) {\n return 'seventh';\n }\n if (tones.has(9) && !hasHigherSeventh) {\n return 'sixth';\n }\n return null;\n }\n if (interval === 10 || interval === 11) {\n return tones.has(interval) ? 'seventh' : null;\n }\n return null;\n}\n\n/** Classify a stacked-thirds triad into a chord quality from its interval set. */\nfunction classifyTriad(thirdIc: number, fifthIc: number): ChordQuality {\n if (thirdIc === 4 && fifthIc === 8) {\n return 'aug';\n }\n if (thirdIc === 4 && fifthIc === 6) {\n return 'majb5';\n }\n if (thirdIc === 3 && fifthIc === 6) {\n return 'dim';\n }\n if (thirdIc === 3) {\n return 'min';\n }\n return 'maj';\n}\n\n/** Classify a stacked-thirds seventh chord into a chord quality. */\nfunction classifySeventh(thirdIc: number, fifthIc: number, seventhIc: number): ChordQuality {\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {\n return 'dim7';\n }\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {\n return 'm7b5';\n }\n if (thirdIc === 3 && seventhIc === 11) {\n return 'minMaj7';\n }\n if (thirdIc === 3) {\n return 'min7';\n }\n if (fifthIc === 8 && seventhIc === 10) {\n return 'aug7';\n }\n if (fifthIc === 8 && seventhIc === 11) {\n return 'augMaj7';\n }\n if (seventhIc === 11) {\n return 'maj7';\n }\n return 'dom7';\n}\n\n/** Stack scale thirds from a scale degree into a chord of `size` notes. */\nfunction stackThirds(degree: number, key: KeyScale, size: 3 | 4): Chord {\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n if (length === 0) {\n return { rootPc: pitchClass(key.rootPc), quality: 'maj', intervals: [0, 4, 7] };\n }\n const idx = (step: number) => (((degree + step) % length) + length) % length;\n const rootPc = tones[idx(0)] ?? pitchClass(key.rootPc);\n const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {\n const pc = tones[idx(step)] ?? rootPc;\n return (((pc - rootPc) % 12) + 12) % 12;\n });\n const thirdIc = offsets[1] ?? 4;\n const fifthIc = offsets[2] ?? 7;\n const quality =\n size === 3\n ? classifyTriad(thirdIc, fifthIc)\n : classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);\n return { rootPc, quality, intervals: offsets };\n}\n\n/**\n * Build the diatonic triad rooted on a scale degree by stacking scale thirds.\n *\n * Unlike {@link chordFromDegree}, the chord quality is derived from the scale\n * rather than supplied, so degrees yield their scale-correct triads (e.g. a\n * diminished triad on the leading tone of a major key). Intended for heptatonic\n * scales; other scales stack by scale step regardless.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic triad.\n *\n * @example\n * ```ts\n * import { majorKey, diatonicTriad } from '@libraz/libcantus';\n * const tonic = diatonicTriad(0, majorKey(0)); // C major triad (degree 0 of C major)\n * ```\n *\n * @category Chords\n */\nexport function diatonicTriad(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 3);\n}\n\n/**\n * Build the diatonic seventh chord rooted on a scale degree.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic seventh chord.\n *\n * @category Chords\n */\nexport function diatonicSeventh(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 4);\n}\n","/**\n * Counterpoint classification of a harmonic interval.\n *\n * @category Pitch & Intervals\n */\nexport enum IntervalQuality {\n PerfectConsonance = 0,\n ImperfectConsonance = 1,\n Dissonance = 2,\n}\n\n/** Reduce an interval to a simple interval class in the range [0, 11]. */\nfunction simpleInterval(semitones: number): number {\n return Math.abs(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, IntervalQuality } from '@libraz/libcantus';\n * classifyInterval(7); // IntervalQuality.PerfectConsonance\n * classifyInterval(5); // IntervalQuality.Dissonance (two-voice)\n * classifyInterval(5, false); // IntervalQuality.ImperfectConsonance\n * ```\n * @category Pitch & Intervals\n */\nexport function classifyInterval(semitones: number, twoVoice = true): IntervalQuality {\n const pc = simpleInterval(semitones);\n if (pc === 0 || pc === 7) {\n return IntervalQuality.PerfectConsonance;\n }\n if (pc === 3 || pc === 4 || pc === 8 || pc === 9) {\n return IntervalQuality.ImperfectConsonance;\n }\n if (pc === 5) {\n return twoVoice ? IntervalQuality.Dissonance : IntervalQuality.ImperfectConsonance;\n }\n return IntervalQuality.Dissonance;\n}\n\n/**\n * Test whether an interval is a perfect interval (unison/octave or fifth).\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","import { isConsonantInterval } from '../../core/interval/index.js';\nimport type { KeyScale } from '../../core/types.js';\n\n/** Reduce a MIDI pitch (or pitch class) to a pitch class in [0, 11]. */\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\n/**\n * Whether an upper voice has crossed below a lower voice.\n *\n * @param upperPitch Pitch of the nominally higher voice.\n * @param lowerPitch Pitch of the nominally lower voice.\n * @returns True when the upper voice sits below the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceCrossing(upperPitch: number, lowerPitch: number): boolean {\n return upperPitch < lowerPitch;\n}\n\n/**\n * Whether two simultaneous pitches form a dissonance.\n *\n * @param a First pitch.\n * @param b Second pitch.\n * @param twoVoice When true, the perfect fourth counts as dissonant.\n * @returns True when the vertical interval is dissonant.\n * @category Voicing & Counterpoint\n */\nexport function createsVerticalDissonance(a: number, b: number, twoVoice: boolean): boolean {\n return !isConsonantInterval(a - b, twoVoice);\n}\n\n/**\n * Whether a melodic move is a forbidden leap: a tritone, either seventh (minor\n * or major), or any leap wider than an octave.\n *\n * Operates on the raw absolute distance, so compound leaps are not reduced to\n * their simple class before the octave check.\n *\n * @param prev Starting pitch.\n * @param cur Ending pitch.\n * @returns True when the leap is forbidden in strict counterpoint.\n * @category Voicing & Counterpoint\n */\nexport function isForbiddenMelodicLeap(prev: number, cur: number): boolean {\n const semis = Math.abs(cur - prev);\n return semis === 6 || semis === 10 || semis === 11 || semis > 12;\n}\n\n/** Reduce an interval to its simple class in [0, 11]. */\nfunction simpleClass(semitones: number): number {\n return Math.abs(semitones) % 12;\n}\n\n/** Whether a simple interval class is a perfect kind (unison/octave or fifth). */\nfunction isPerfectClass(cls: number): boolean {\n return cls === 0 || cls === 7;\n}\n\n/** Whether two voices move in the same direction (similar or parallel motion). */\nfunction similarMotion(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0 && aMove > 0 === bMove > 0;\n}\n\n/** Whether both voices actually move (neither is stationary — excludes oblique motion). */\nfunction bothVoicesMove(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0;\n}\n\n/**\n * Whether two voices move into consecutive perfect intervals of the same kind\n * (fifth-to-fifth, octave-to-octave, unison-to-unison).\n *\n * Both true parallels (similar motion) and anti-parallels — the same perfect\n * class reached by contrary motion, e.g. octave to octave with the voices moving\n * in opposite directions — are flagged, as both are forbidden in strict two-voice\n * counterpoint. The rule requires that both voices actually move: oblique motion\n * (either voice stationary) and the no-change case (identical pitches) are excluded.\n *\n * A fifth expanding to a twelfth counts (same perfect class); a fifth moving to\n * an octave does not (different perfect kinds — the direct/hidden case owned by\n * {@link createsHiddenParallelPerfect}).\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!bothVoicesMove(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n return isPerfectClass(nowClass) && nowClass === prevClass;\n}\n\n/**\n * Whether two voices move in consecutive parallel octaves (or unisons) by\n * similar motion.\n *\n * This is a strict subset of {@link createsParallelPerfect}: a similar-motion\n * octave-to-octave is the perfect-class-zero case that predicate already flags\n * (and it additionally catches the contrary-motion anti-parallel). Callers that\n * tally parallel violations should therefore use {@link createsParallelPerfect}\n * alone to avoid double counting; this predicate remains for callers wanting a\n * dedicated similar-motion octave test.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelOctave(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!similarMotion(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n return simpleClass(aCur - bCur) === 0 && simpleClass(aPrev - bPrev) === 0;\n}\n\n/**\n * Whether two voices move in consecutive parallel unisons — both landing on the\n * same pitch, having shared a pitch on the previous move.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelUnison(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (aCur === aPrev || bCur === bPrev) {\n return false;\n }\n return aCur === bCur && aPrev === bPrev;\n}\n\n/**\n * Whether two voices reach a perfect interval by similar motion from an\n * imperfect one (a hidden/direct fifth or octave).\n *\n * The traditional step exception is applied: the approach is allowed when the\n * upper of the two voices moves by step, so only leaps into the perfect\n * interval are flagged.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsHiddenParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n const aMove = aCur - aPrev;\n const bMove = bCur - bPrev;\n if (!similarMotion(aMove, bMove)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n // Approaching the same perfect class (e.g. fifth to fifth) is a true parallel\n // owned by createsParallelPerfect; approaching a different perfect interval\n // (fifth to octave, or vice versa) is the hidden/direct case flagged here.\n if (!isPerfectClass(nowClass) || prevClass === nowClass) {\n return false;\n }\n const upperMove = aCur >= bCur ? aMove : bMove;\n if (Math.abs(upperMove) <= 2) {\n return false; // upper voice moves by step — direct interval is acceptable\n }\n return true;\n}\n\n/**\n * Whether two voices overlap: the upper voice descends below where the lower\n * voice just was, or the lower voice rises above where the upper voice just was.\n * Distinct from a simultaneous voice crossing.\n *\n * @param upperPrev Previous pitch of the upper voice.\n * @param upperCur Current pitch of the upper voice.\n * @param lowerPrev Previous pitch of the lower voice.\n * @param lowerCur Current pitch of the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceOverlap(\n upperPrev: number,\n upperCur: number,\n lowerPrev: number,\n lowerCur: number,\n): boolean {\n return upperCur < lowerPrev || lowerCur > upperPrev;\n}\n\n/**\n * Whether two adjacent upper voices are spaced more than a maximum apart\n * (commonly an octave). Bass-to-tenor spacing is conventionally exempt, so this\n * is meant for the upper voice pairs.\n *\n * @param upperPitch Pitch of the higher voice.\n * @param lowerPitch Pitch of the lower voice.\n * @param maxSemitones Maximum allowed spacing in semitones (default an octave).\n * @category Voicing & Counterpoint\n */\nexport function exceedsSpacing(upperPitch: number, lowerPitch: number, maxSemitones = 12): boolean {\n return Math.abs(upperPitch - lowerPitch) > maxSemitones;\n}\n\n/**\n * Whether a leading tone resolves correctly upward to the tonic.\n *\n * @param prev The leading-tone pitch.\n * @param cur The following pitch.\n * @param key Key context supplying the tonic.\n * @returns True when `prev` is the leading tone and `cur` is the tonic a step above.\n * @category Voicing & Counterpoint\n */\nexport function isLeadingToneResolution(prev: number, cur: number, key: KeyScale): boolean {\n const tonic = pitchClass(key.rootPc);\n const leading = (tonic + 11) % 12;\n if (pitchClass(prev) !== leading || pitchClass(cur) !== tonic) {\n return false;\n }\n return cur > prev && cur - prev <= 2;\n}\n","import type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport {\n createsHiddenParallelPerfect,\n createsParallelPerfect,\n createsVerticalDissonance,\n isForbiddenMelodicLeap,\n} from '../counterpoint/index.js';\nimport { isScaleTone } from '../scale/index.js';\n\n/**\n * Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyProfile = 'strict' | 'pop';\n\n/**\n * Overall placeability verdict for a candidate pitch.\n *\n * @category Arrangement & Analysis\n */\nexport enum NoteSafety {\n Safe = 0,\n Warning = 1,\n Dissonant = 2,\n}\n\n/**\n * Bit flags describing why a pitch received its verdict.\n *\n * @category Arrangement & Analysis\n */\nexport enum ReasonFlag {\n ChordTone = 1 << 0,\n Tension = 1 << 1,\n AvoidNote = 1 << 2,\n ScaleTone = 1 << 3,\n NonScale = 1 << 4,\n OutOfRange = 1 << 5,\n Tritone = 1 << 6,\n LargeLeap = 1 << 7,\n MinorSecond = 1 << 8,\n MajorSeventh = 1 << 9,\n VerticalDissonance = 1 << 10,\n ParallelPerfect = 1 << 11,\n HiddenParallel = 1 << 12,\n VoiceCrossing = 1 << 13,\n Suspension = 1 << 14,\n NeedsResolution = 1 << 15,\n}\n\n/**\n * A neighbouring voice's current and previous pitch.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceSnapshot = {\n pitch: number;\n prevPitch?: number;\n};\n\n/**\n * Everything needed to evaluate one candidate pitch.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyQuery = {\n profile: SafetyProfile;\n candidatePitch: number;\n prevPitch?: number;\n chord: Chord | null;\n key: KeyScale;\n otherVoices: VoiceSnapshot[];\n strongBeat: boolean;\n vocalLow?: number;\n vocalHigh?: number;\n};\n\n/**\n * The verdict, reason flags, and optional resolution guidance.\n *\n * @category Arrangement & Analysis\n */\nexport type SafetyResult = {\n safety: NoteSafety;\n reasons: number;\n resolveTo?: number;\n suggestions?: number[];\n rationale?: string;\n};\n\n/** Semitone radius searched around a rejected candidate for safe alternatives. */\nconst SUGGESTION_WINDOW = 12;\n/** Maximum number of alternative pitches returned in `SafetyResult.suggestions`. */\nconst MAX_SUGGESTIONS = 3;\n\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\nfunction intervalAboveRoot(pitch: number, chord: Chord): number {\n return (((pitchClass(pitch) - pitchClass(chord.rootPc)) % 12) + 12) % 12;\n}\n\nfunction isChordMember(pitch: number, chord: Chord): boolean {\n return chordPitchClasses(chord).includes(pitchClass(pitch));\n}\n\n/** Whether the perfect fourth is an avoid note over this chord. */\nfunction avoidsFourth(chord: Chord): boolean {\n return chord.intervals.includes(4) && !chord.intervals.includes(5);\n}\n\n/** Whether the major seventh is an avoid note over this chord (dominant). */\nfunction avoidsMajorSeventh(chord: Chord): boolean {\n return chord.intervals.includes(4) && chord.intervals.includes(10);\n}\n\n/** Nearest chord tone at or below the candidate, within two semitones. */\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 * Evaluate the safety of placing a candidate pitch in a voice.\n *\n * Reasons are merged from harmonic context (chord tone / tension / avoid / scale)\n * and counterpoint (vertical dissonance, parallels, voice crossing). The `strict`\n * profile treats avoid notes and parallels as dissonant; `pop` treats them as\n * warnings. The final verdict is the worst severity among the reasons.\n *\n * @param q The candidate and its harmonic/voice-leading context.\n * @returns The verdict, reason bitmask, and optional resolution guidance.\n * @example\n * ```ts\n * import { evaluateSafety, makeChord, majorKey, NoteSafety } from '@libraz/libcantus';\n * const result = evaluateSafety({\n * profile: 'pop',\n * candidatePitch: 60, // C over a C major chord\n * chord: makeChord(0, 'maj'),\n * key: majorKey(0),\n * otherVoices: [],\n * strongBeat: true,\n * });\n * result.safety === NoteSafety.Safe; // true — C is a chord tone\n * ```\n * @category Arrangement & Analysis\n */\nexport function evaluateSafety(q: SafetyQuery): SafetyResult {\n return evaluateInternal(q, true);\n}\n\n/**\n * Core safety evaluation.\n *\n * @param q The candidate and its harmonic/voice-leading context.\n * @param collectSuggestions Whether to search for safe alternatives on a\n * non-Safe verdict. Set false during that search itself to bound recursion.\n * @returns The verdict, reason bitmask, and optional guidance.\n */\nfunction evaluateInternal(q: SafetyQuery, collectSuggestions: boolean): SafetyResult {\n let reasons = 0;\n let level = NoteSafety.Safe;\n let resolveTo: number | undefined;\n const raise = (l: NoteSafety) => {\n if (l > level) {\n level = l;\n }\n };\n const avoidSeverity = q.profile === 'strict' ? NoteSafety.Dissonant : NoteSafety.Warning;\n const parallelSeverity = avoidSeverity;\n const pitch = q.candidatePitch;\n\n if (\n (q.vocalLow !== undefined && pitch < q.vocalLow) ||\n (q.vocalHigh !== undefined && pitch > q.vocalHigh)\n ) {\n reasons |= ReasonFlag.OutOfRange;\n raise(NoteSafety.Warning);\n }\n\n const inScale = isScaleTone(pitch, q.key);\n const chord = q.chord;\n\n if (chord && isChordMember(pitch, chord)) {\n reasons |= ReasonFlag.ChordTone;\n } else if (chord) {\n const ic = intervalAboveRoot(pitch, chord);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid = (ic === 5 && avoidsFourth(chord)) || (ic === 11 && avoidsMajorSeventh(chord));\n if (avoid) {\n reasons |= ReasonFlag.AvoidNote;\n if (isTension) {\n reasons |= ReasonFlag.Tension;\n }\n reasons |= ReasonFlag.NeedsResolution;\n resolveTo = stepResolution(pitch, chord);\n raise(avoidSeverity);\n } else if (isTension && inScale) {\n reasons |= ReasonFlag.Tension;\n raise(NoteSafety.Warning);\n } else if (inScale) {\n reasons |= ReasonFlag.ScaleTone;\n raise(NoteSafety.Warning);\n } else {\n reasons |= ReasonFlag.NonScale;\n raise(NoteSafety.Dissonant);\n }\n } else if (inScale) {\n reasons |= ReasonFlag.ScaleTone;\n raise(NoteSafety.Warning);\n } else {\n reasons |= ReasonFlag.NonScale;\n raise(NoteSafety.Dissonant);\n }\n\n // Harmonic tritone against a chord tone (except in dominant chords, where the\n // tritone is a defining, stable colour).\n if (chord && !(chord.intervals.includes(4) && chord.intervals.includes(10))) {\n const ic = intervalAboveRoot(pitch, chord);\n const toneOffsets = chord.intervals.map((i) => ((i % 12) + 12) % 12);\n if (toneOffsets.some((t) => Math.abs(ic - t) === 6)) {\n reasons |= ReasonFlag.Tritone;\n }\n }\n\n if (q.prevPitch !== undefined) {\n // Quality flags reduce the melodic interval to its pitch-class interval so\n // compound intervals are treated uniformly with their simple form. The\n // forbidden-leap flag defers to the counterpoint rule, which preserves the\n // octave boundary (an octave leap is allowed, wider leaps are not).\n const pc = Math.abs(pitch - q.prevPitch) % 12;\n if (pc === 6) {\n reasons |= ReasonFlag.Tritone;\n }\n if (isForbiddenMelodicLeap(q.prevPitch, pitch)) {\n reasons |= ReasonFlag.LargeLeap;\n }\n if (pc === 1) {\n reasons |= ReasonFlag.MinorSecond;\n }\n if (pc === 11) {\n reasons |= ReasonFlag.MajorSeventh;\n }\n }\n\n if (q.strongBeat) {\n const twoVoice = q.otherVoices.length === 1;\n const prev = q.prevPitch;\n for (const ov of q.otherVoices) {\n if (!createsVerticalDissonance(pitch, ov.pitch, twoVoice)) {\n continue;\n }\n reasons |= ReasonFlag.VerticalDissonance;\n raise(NoteSafety.Dissonant);\n // A held pitch that was consonant on the previous step and is now\n // dissonant against the same voice is a prepared suspension.\n if (\n prev !== undefined &&\n pitch === prev &&\n ov.prevPitch !== undefined &&\n !createsVerticalDissonance(prev, ov.prevPitch, twoVoice)\n ) {\n reasons |= ReasonFlag.Suspension;\n }\n }\n }\n\n if (q.prevPitch !== undefined) {\n const prev = q.prevPitch;\n for (const ov of q.otherVoices) {\n if (ov.prevPitch === undefined) {\n continue;\n }\n // createsParallelPerfect covers every perfect class (unison/octave and\n // fifth), so no separate octave check is needed.\n if (createsParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {\n reasons |= ReasonFlag.ParallelPerfect;\n raise(parallelSeverity);\n }\n if (createsHiddenParallelPerfect(prev, pitch, ov.prevPitch, ov.pitch)) {\n reasons |= ReasonFlag.HiddenParallel;\n raise(parallelSeverity);\n }\n const crossedNow = pitch - ov.pitch;\n const crossedPrev = prev - ov.prevPitch;\n if (crossedNow !== 0 && crossedPrev !== 0 && crossedNow > 0 !== crossedPrev > 0) {\n reasons |= ReasonFlag.VoiceCrossing;\n raise(parallelSeverity);\n }\n }\n }\n\n const result: SafetyResult = { safety: level, reasons, rationale: describe(reasons, q) };\n if (resolveTo !== undefined) {\n result.resolveTo = resolveTo;\n }\n if (collectSuggestions && level !== NoteSafety.Safe) {\n const suggestions = findSafeNearby(q, pitch);\n if (suggestions.length > 0) {\n result.suggestions = suggestions;\n }\n }\n return result;\n}\n\n/**\n * Find safe alternative pitches near a rejected candidate.\n *\n * Searches outward from the candidate within `SUGGESTION_WINDOW` semitones,\n * probing the pitch below before the one above at each distance so the result\n * is deterministic and ordered by nearness.\n *\n * @param q The safety context of the rejected candidate.\n * @param candidate The candidate pitch that was not Safe.\n * @returns Up to `MAX_SUGGESTIONS` safe pitches, nearest first.\n */\nfunction findSafeNearby(q: SafetyQuery, candidate: number): number[] {\n const out: number[] = [];\n for (let d = 1; d <= SUGGESTION_WINDOW; d += 1) {\n for (const p of [candidate - d, candidate + d]) {\n if (evaluateInternal({ ...q, candidatePitch: p }, false).safety === NoteSafety.Safe) {\n out.push(p);\n if (out.length >= MAX_SUGGESTIONS) {\n return out;\n }\n }\n }\n }\n return out;\n}\n\n/** Build a short human explanation from the reason flags. */\nfunction describe(reasons: number, q: SafetyQuery): string {\n if (reasons & ReasonFlag.OutOfRange) {\n return 'Outside the target vocal range';\n }\n if (reasons & ReasonFlag.Suspension) {\n return 'Prepared suspension — a held consonance now dissonant, awaiting resolution';\n }\n if (reasons & ReasonFlag.VerticalDissonance) {\n return 'Dissonant against a sounding voice on a strong beat';\n }\n if (reasons & ReasonFlag.AvoidNote) {\n return 'Avoid note — clashes with a chord tone a semitone away';\n }\n if (reasons & ReasonFlag.ParallelPerfect) {\n return 'Parallel perfect interval with another voice';\n }\n if (reasons & ReasonFlag.HiddenParallel) {\n return 'Hidden perfect interval by similar motion';\n }\n if (reasons & ReasonFlag.VoiceCrossing) {\n return 'Crosses another voice';\n }\n if (reasons & ReasonFlag.ChordTone) {\n return 'Chord tone';\n }\n if (reasons & ReasonFlag.Tension) {\n return 'Chord tension';\n }\n if (reasons & ReasonFlag.NonScale) {\n return 'Outside the key';\n }\n if (reasons & ReasonFlag.ScaleTone) {\n return 'Scale tone, not in the chord';\n }\n return q.chord ? 'Placeable' : 'No chord context';\n}\n\n/**\n * Enumerate placeable pitches in a range, chord tones first, descending.\n *\n * @param q The safety context minus the candidate pitch.\n * @param pitchLow Lowest MIDI pitch to consider (inclusive).\n * @param pitchHigh Highest MIDI pitch to consider (inclusive).\n * @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.\n * @example\n * ```ts\n * import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';\n * const pitches = enumerateSafePitches(\n * { profile: 'pop', chord: makeChord(0, 'maj'), key: majorKey(0), otherVoices: [], strongBeat: true },\n * 60,\n * 72,\n * );\n * pitches; // placeable pitches in [60, 72], chord tones first, each group descending\n * ```\n * @category Arrangement & Analysis\n */\nexport function enumerateSafePitches(\n q: Omit<SafetyQuery, 'candidatePitch'>,\n pitchLow: number,\n pitchHigh: number,\n): number[] {\n const chordTones: number[] = [];\n const others: number[] = [];\n if (!Number.isFinite(pitchLow) || !Number.isFinite(pitchHigh)) {\n return [];\n }\n for (let pitch = pitchHigh; pitch >= pitchLow; pitch -= 1) {\n const result = evaluateInternal({ ...q, candidatePitch: pitch }, false);\n if (result.safety === NoteSafety.Dissonant) {\n continue;\n }\n if (result.reasons & ReasonFlag.ChordTone) {\n chordTones.push(pitch);\n } else {\n others.push(pitch);\n }\n }\n return [...chordTones, ...others];\n}\n","/**\n * Shared helpers for the functional-harmony modules.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\n\nexport function mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/** Diatonic pitch class of a 1-based scale degree in a key. */\nexport function degreeRootPc(degreeNumber: number, key: KeyScale): number {\n const tones = scaleTonesInDegreeOrder(key);\n if (tones.length === 0) {\n return mod12(key.rootPc);\n }\n return tones[(degreeNumber - 1) % tones.length] ?? mod12(key.rootPc);\n}\n\n/** Whether a chord is the Neapolitan: a major triad on the flat second degree. */\nexport function isNeapolitan(chord: Chord, key: KeyScale): boolean {\n return mod12(chord.rootPc - key.rootPc) === 1 && chord.quality === 'maj';\n}\n","/**\n * Roman-numeral parsing and formatting.\n *\n * The {@link ROMAN_STYLE} table is the single source of truth for both\n * directions, so {@link chordToRoman} and {@link romanToChord} stay mutual\n * inverses by construction.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { makeChord } from '../../theory/chord/index.js';\nimport { majorKey, scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\nimport { degreeRootPc, mod12 } from './internal.js';\n\n/** Roman numeral glyphs indexed by degree number - 1. */\nconst ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] as const;\n\n/** Roman glyph -> degree number (1..7). */\nconst ROMAN_TO_DEGREE: Record<string, number> = {\n I: 1,\n II: 2,\n III: 3,\n IV: 4,\n V: 5,\n VI: 6,\n VII: 7,\n};\n\n/** Major-key Roman spelling for each semitone offset above the tonic. */\nconst OFFSET_SPELLING: readonly [number, string][] = [\n [1, ''],\n [2, 'b'],\n [2, ''],\n [3, 'b'],\n [3, ''],\n [4, ''],\n [4, '#'],\n [5, ''],\n [6, 'b'],\n [6, ''],\n [7, 'b'],\n [7, ''],\n];\n\n/** Choose a seventh-chord quality from a triad label and the numeral case. */\nfunction seventhQuality(base: 'maj' | 'min' | 'dim' | 'aug', halfDim: boolean): ChordQuality {\n if (halfDim) {\n return 'm7b5';\n }\n if (base === 'dim') {\n return 'dim7';\n }\n if (base === 'aug') {\n return 'aug7';\n }\n return base === 'maj' ? 'dom7' : 'min7';\n}\n\n/** Figure strings that denote a recognized figured-bass inversion. */\nconst INVERSION_FIGURES = new Set(['6', '64', '65', '43', '42', '2', '7']);\n\n/**\n * Canonical Roman-numeral rendering (numeral case and quality suffix) for every\n * supported chord quality. The table is the single source of truth for both\n * directions: {@link chordToRoman} renders from it and {@link romanToChord}\n * recognizes its exact case-sensitive suffixes, so the two stay mutual\n * inverses by construction. Suffixes are chosen to never collide with\n * figured-bass inversion digits (`6`, `64`, `65`, `43`, `42`, `2`) — the added\n * sixth is `add6`, the six-nine chord is `69` — and each (case, suffix) pair is\n * unique.\n */\nconst ROMAN_STYLE: Record<ChordQuality, { lower: boolean; suffix: string }> = {\n maj: { lower: false, suffix: '' },\n min: { lower: true, suffix: '' },\n dim: { lower: true, suffix: 'o' },\n aug: { lower: false, suffix: '+' },\n maj7: { lower: false, suffix: 'maj7' },\n min7: { lower: true, suffix: '7' },\n dom7: { lower: false, suffix: '7' },\n dim7: { lower: true, suffix: 'o7' },\n m7b5: { lower: true, suffix: 'ø7' },\n minMaj7: { lower: true, suffix: 'maj7' },\n aug7: { lower: false, suffix: '+7' },\n augMaj7: { lower: false, suffix: '+maj7' },\n majb5: { lower: false, suffix: 'b5' },\n '6': { lower: false, suffix: 'add6' },\n min6: { lower: true, suffix: 'add6' },\n '6/9': { lower: false, suffix: '69' },\n sus2: { lower: false, suffix: 'sus2' },\n sus4: { lower: false, suffix: 'sus4' },\n add9: { lower: false, suffix: 'add9' },\n add11: { lower: false, suffix: 'add11' },\n maj9: { lower: false, suffix: 'maj9' },\n min9: { lower: true, suffix: '9' },\n dom9: { lower: false, suffix: '9' },\n '7b9': { lower: false, suffix: '7b9' },\n '7#9': { lower: false, suffix: '7#9' },\n '7#11': { lower: false, suffix: '7#11' },\n '7b13': { lower: false, suffix: '7b13' },\n '11': { lower: false, suffix: '11' },\n '13': { lower: false, suffix: '13' },\n '5': { lower: false, suffix: '5' },\n};\n\n/** Reverse lookup of {@link ROMAN_STYLE}: `(case, exact suffix) -> quality`. */\nconst SUFFIX_QUALITY: ReadonlyMap<string, ChordQuality> = new Map(\n (Object.entries(ROMAN_STYLE) as [ChordQuality, { lower: boolean; suffix: string }][]).map(\n ([quality, style]) => [`${style.lower ? 'l' : 'u'}:${style.suffix}`, quality],\n ),\n);\n\n/** Read figured-bass digits into a chord inversion and whether a seventh is implied. */\nfunction parseInversion(figures: string): { inversion: number; seventh: boolean } {\n if (figures === '65') {\n return { inversion: 1, seventh: true };\n }\n if (figures === '43') {\n return { inversion: 2, seventh: true };\n }\n if (figures === '42' || figures === '2') {\n return { inversion: 3, seventh: true };\n }\n if (figures === '7') {\n return { inversion: 0, seventh: true };\n }\n if (figures === '64') {\n return { inversion: 2, seventh: false };\n }\n if (figures === '6') {\n return { inversion: 1, seventh: false };\n }\n return { inversion: 0, seventh: false };\n}\n\n/** Parse a Roman numeral (no secondary '/') into a root, quality, and inversion. */\nfunction parseSimpleRoman(\n text: string,\n key: KeyScale,\n): { rootPc: number; quality: ChordQuality; inversion: number } {\n const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());\n if (!match) {\n throw new Error(`Invalid Roman numeral: ${text}`);\n }\n const accidental = match[1] === 'b' ? -1 : match[1] === '#' ? 1 : 0;\n const glyph = (match[2] ?? '').toUpperCase();\n const degreeNumber = ROMAN_TO_DEGREE[glyph];\n if (degreeNumber === undefined) {\n throw new Error(`Invalid Roman numeral: ${text}`);\n }\n const isUpper = (match[2] ?? '')[0] === (match[2] ?? '')[0]?.toUpperCase();\n const suffix = match[3] ?? '';\n const rootPc = mod12(degreeRootPc(degreeNumber, key) + accidental);\n\n // Canonical quality suffixes (exact match, case-sensitive on the numeral)\n // come first so every chordToRoman rendering re-parses to the same quality:\n // `V9` -> dom9 but `ii9` -> min9 and `Imaj9` -> maj9, and added-tone suffixes\n // whose digits would otherwise be misread as figured bass (`Iadd6`, `I69`,\n // `Isus2`, `Iadd11`) resolve to their qualities in root position.\n const canonical = SUFFIX_QUALITY.get(`${isUpper ? 'u' : 'l'}:${suffix}`);\n if (canonical !== undefined) {\n return { rootPc, quality: canonical, inversion: 0 };\n }\n\n const isDim = /o|°|dim/.test(suffix);\n const isAug = /\\+|aug/.test(suffix);\n const isHalfDim = /ø/.test(suffix);\n const explicitMaj7 = /maj7|M7/.test(suffix);\n const figures = suffix.replace(/maj7|M7/g, '').replace(/[^0-9]/g, '');\n\n // Reject figure strings that are neither a known figured-bass inversion nor a\n // canonical quality suffix rather than silently degrading to a root triad.\n if (figures !== '' && !INVERSION_FIGURES.has(figures)) {\n throw new Error(`Unsupported figured-bass or extension \"${figures}\" in Roman numeral: ${text}`);\n }\n\n const { inversion, seventh } = parseInversion(figures);\n // The ø glyph is the half-diminished seventh by definition (there is no\n // half-diminished triad), so it always implies a seventh.\n const hasSeventh = explicitMaj7 || seventh || isHalfDim;\n\n let base: 'maj' | 'min' | 'dim' | 'aug';\n if (isDim || isHalfDim) {\n base = 'dim';\n } else if (isAug) {\n base = 'aug';\n } else {\n base = isUpper ? 'maj' : 'min';\n }\n\n let quality: ChordQuality;\n if (explicitMaj7) {\n quality = base === 'min' ? 'minMaj7' : base === 'aug' ? 'augMaj7' : 'maj7';\n } else if (hasSeventh) {\n quality = seventhQuality(base, isHalfDim);\n } else if (base === 'dim') {\n quality = 'dim';\n } else if (base === 'aug') {\n quality = 'aug';\n } else {\n quality = base;\n }\n return { rootPc, quality, inversion };\n}\n\n/** Build a chord from a parsed Roman numeral, attaching a bass for inversions. */\nfunction chordFromParsed(parsed: {\n rootPc: number;\n quality: ChordQuality;\n inversion: number;\n}): Chord {\n const chord = makeChord(parsed.rootPc, parsed.quality);\n if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {\n chord.bassPc = mod12(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));\n }\n return chord;\n}\n\n/**\n * Build the chord denoted by a Roman numeral in a key.\n *\n * Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`\n * suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions\n * (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash\n * (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree\n * whose root becomes a local major tonic for the applied chord. Inverted chords\n * carry a `bassPc`.\n *\n * The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==\n * `m7b5`), since no half-diminished triad exists. Extension and added-tone\n * suffixes are the canonical case-sensitive forms emitted by\n * {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->\n * `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->\n * `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->\n * `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.\n * A figure string that is neither a recognized inversion nor a supported\n * quality suffix throws rather than silently degrading to a triad.\n *\n * @param text The Roman numeral.\n * @param key The prevailing key.\n * @returns The chord.\n * @example\n * ```ts\n * import { romanToChord, majorKey } from '@libraz/libcantus';\n * romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }\n * ```\n * @category Functional Harmony\n */\nexport function romanToChord(text: string, key: KeyScale): Chord {\n const trimmed = text.trim();\n const slash = trimmed.indexOf('/');\n if (slash >= 0) {\n const applied = trimmed.slice(0, slash);\n const target = trimmed.slice(slash + 1);\n const targetRoot = parseSimpleRoman(target, key).rootPc;\n const localKey = majorKey(targetRoot);\n return chordFromParsed(parseSimpleRoman(applied, localKey));\n }\n return chordFromParsed(parseSimpleRoman(trimmed, key));\n}\n\n/** Case and suffix for rendering a chord quality as a Roman numeral. */\nfunction romanStyle(quality: ChordQuality): { lower: boolean; suffix: string } {\n return ROMAN_STYLE[quality];\n}\n\n/** Choose the degree number and accidental to spell a root as a Roman numeral. */\nfunction romanSpelling(\n rootPc: number,\n key: KeyScale,\n): { degreeNumber: number; accidental: string } {\n const tones = scaleTonesInDegreeOrder(key);\n const diatonic = tones.indexOf(mod12(rootPc));\n if (diatonic >= 0) {\n return { degreeNumber: diatonic + 1, accidental: '' };\n }\n // Chromatic root: prefer a flat of the diatonic degree a semitone above it,\n // then a sharp of the degree a semitone below. Degree 1 (the tonic) and\n // degree 5 (the dominant) are skipped in the flat pass, so a raised leading\n // tone spells as `#vii` rather than a flat tonic `bI`, and the tritone above\n // the tonic spells as `#iv` rather than a flat fifth `bV` (matching the\n // conventional `OFFSET_SPELLING` for offset 6).\n for (let i = 0; i < tones.length; i += 1) {\n const degreeNumber = i + 1;\n if (degreeNumber === 1 || degreeNumber === 5) {\n continue;\n }\n if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {\n return { degreeNumber, accidental: 'b' };\n }\n }\n for (let i = 0; i < tones.length; i += 1) {\n if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {\n return { degreeNumber: i + 1, accidental: '#' };\n }\n }\n const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ''];\n return { degreeNumber, accidental };\n}\n\n/** Quality marker (without the seventh digit) used when a figure carries the 7. */\nfunction baseMarker(quality: ChordQuality): string {\n if (quality === 'dim' || quality === 'dim7') {\n return 'o';\n }\n if (quality === 'm7b5') {\n return 'ø';\n }\n if (quality === 'aug' || quality === 'aug7') {\n return '+';\n }\n if (quality === 'augMaj7') {\n return '+maj7';\n }\n if (quality === 'maj7' || quality === 'minMaj7') {\n return 'maj7';\n }\n return '';\n}\n\nconst TRIAD_FIGURES: Record<number, string> = { 1: '6', 2: '64' };\nconst SEVENTH_FIGURES: Record<number, string> = { 1: '65', 2: '43', 3: '42' };\n\n/** Qualities whose inversions render as lossless triad figures (6, 64). */\nconst TRIAD_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set(['maj', 'min', 'dim', 'aug']);\n\n/**\n * Qualities carrying a chordal seventh, whose inversions render as seventh\n * figures (65, 43, 42). Membership is decided by the quality itself — a 7th\n * above the root (10 or 11 semitones, or the diminished 7th of `dim7`) — never\n * by interval count, which would turn added-tone chords into false sevenths.\n */\nconst SEVENTH_FIGURE_QUALITIES: ReadonlySet<ChordQuality> = new Set([\n 'maj7',\n 'min7',\n 'dom7',\n 'dim7',\n 'm7b5',\n 'minMaj7',\n 'aug7',\n 'augMaj7',\n]);\n\n/**\n * Render a chord as a Roman numeral relative to a key.\n *\n * Diatonic roots take their scale-degree numeral directly, so numerals are\n * correct in both major and minor keys (and any custom scale). The quality\n * selects the case and suffix; chromatic roots receive a flat/sharp spelling by\n * convention. When the chord carries a `bassPc` on a chord tone, a figured-bass\n * inversion (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true\n * seventh chords; added-tone and extended qualities have no lossless figure and\n * render in root position instead (the bass is dropped, pitch classes are\n * preserved).\n *\n * @param chord The chord to name.\n * @param key The prevailing key.\n * @returns The Roman numeral string.\n * @example\n * ```ts\n * import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';\n * chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)\n * ```\n * @category Functional Harmony\n */\nexport function chordToRoman(chord: Chord, key: KeyScale): string {\n const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);\n const { lower, suffix } = romanStyle(chord.quality);\n const numeral: string = ROMAN[degreeNumber - 1] ?? 'I';\n const cased = lower ? numeral.toLowerCase() : numeral;\n\n let inversion = 0;\n if (chord.bassPc !== undefined) {\n const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);\n if (idx > 0) {\n inversion = idx;\n }\n }\n if (inversion > 0) {\n const figure = SEVENTH_FIGURE_QUALITIES.has(chord.quality)\n ? SEVENTH_FIGURES[inversion]\n : TRIAD_FIGURE_QUALITIES.has(chord.quality)\n ? TRIAD_FIGURES[inversion]\n : undefined;\n if (figure !== undefined) {\n return `${accidental}${cased}${baseMarker(chord.quality)}${figure}`;\n }\n // No lossless figured-bass symbol exists — the quality is an added-tone or\n // extended chord, or the bass falls on a tension beyond the seventh — so\n // fall back to root-position rendering with the quality suffix rather than\n // emitting a figure that would re-parse as a different chord.\n }\n return `${accidental}${cased}${suffix}`;\n}\n","/**\n * Harmonic function (tonic / subdominant / dominant), diatonicity, parallel-key\n * mirroring, and full chord analysis.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, MAJOR_MASK, NATURAL_MINOR_MASK } from '../../theory/scale/index.js';\nimport { type BorrowedSource, borrowedSource } from './borrowed.js';\nimport { degreeRootPc, isNeapolitan, mod12 } from './internal.js';\nimport { chordToRoman } from './roman.js';\n\n/**\n * The three broad harmonic functions of tonal music.\n *\n * @category Functional Harmony\n */\nexport type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';\n\n/** Harmonic function of each semitone offset above the tonic (major context). */\nconst FUNCTION_BY_OFFSET: readonly HarmonicFunction[] = [\n 'tonic', // 0 I\n 'subdominant', // 1 bII (Neapolitan)\n 'subdominant', // 2 ii\n 'tonic', // 3 bIII\n 'tonic', // 4 iii\n 'subdominant', // 5 IV\n 'dominant', // 6 #IV / bV\n 'dominant', // 7 V\n 'subdominant', // 8 bVI\n 'tonic', // 9 vi\n 'subdominant', // 10 bVII\n 'dominant', // 11 vii\n];\n\n/**\n * Whether a key's scale has a minor third and no major third (a minor key).\n *\n * @category Functional Harmony\n */\nexport function isMinorKey(key: KeyScale): boolean {\n const hasMinorThird = (key.modeMask12 >> 3) & 1;\n const hasMajorThird = (key.modeMask12 >> 4) & 1;\n return Boolean(hasMinorThird) && !hasMajorThird;\n}\n\n/**\n * The harmonic function of a chord in a key, from its root's offset above the\n * tonic. The mapping follows common-practice major-key function and is a useful\n * approximation in minor and for borrowed chords.\n *\n * @param chord The chord.\n * @param key The prevailing key.\n * @returns The harmonic function.\n * @category Functional Harmony\n */\nexport function functionOf(chord: Chord, key: KeyScale): HarmonicFunction {\n return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? 'tonic';\n}\n\n/**\n * The result of {@link analyzeChord}: function, borrowing, and Roman numeral.\n *\n * @category Functional Harmony\n */\nexport type ChordAnalysis = {\n function: HarmonicFunction;\n borrowed: boolean;\n source: BorrowedSource;\n roman: string;\n};\n\n/**\n * Whether every pitch class of a chord belongs to the key's scale.\n *\n * The test is strict against the key's own mode mask: in a natural-minor key\n * the harmonic-minor dominant (major V) is *not* diatonic, since the raised\n * leading tone lies outside the mask. Borrowing predicates treat that case as\n * an in-key alteration separately (see {@link isBorrowedChord}).\n *\n * @param chord The chord to test.\n * @param key The prevailing key.\n * @returns True if all chord pitch classes are scale tones.\n * @category Functional Harmony\n */\nexport function isDiatonic(chord: Chord, key: KeyScale): boolean {\n return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));\n}\n\n/**\n * The parallel key: same tonic, opposite mode.\n *\n * A key with a minor third (natural/harmonic/melodic minor, dorian, phrygian)\n * maps to the parallel major; any other key maps to the parallel natural minor.\n *\n * @param key The key to mirror.\n * @returns The parallel major or natural-minor key on the same tonic.\n * @category Functional Harmony\n */\nexport function parallelKey(key: KeyScale): KeyScale {\n return {\n rootPc: mod12(key.rootPc),\n modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK,\n };\n}\n\n/** Diminished-family qualities: diminished triad, dim7, half-diminished. */\nfunction isDiminishedQuality(quality: ChordQuality): boolean {\n return quality === 'dim' || quality === 'dim7' || quality === 'm7b5';\n}\n\n/** Whether the chord's interval template carries a major third above the root. */\nfunction hasMajorThird(chord: Chord): boolean {\n return chord.intervals.some((interval) => mod12(interval) === 4);\n}\n\n/**\n * Quality-aware harmonic function, refining the offset table where chord\n * quality disambiguates: diminished chords on the leading tone or the raised\n * subdominant resolve by semitone and act as dominants; the Neapolitan and the\n * major-third chords on bVI/bVII borrowed from the parallel minor act as\n * subdominant (predominant) harmony. All other chords keep {@link functionOf}.\n */\nfunction qualityAwareFunction(chord: Chord, key: KeyScale): HarmonicFunction {\n const offset = mod12(chord.rootPc - key.rootPc);\n if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {\n return 'dominant';\n }\n if (isNeapolitan(chord, key)) {\n return 'subdominant';\n }\n if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {\n return 'subdominant';\n }\n return functionOf(chord, key);\n}\n\n/**\n * Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.\n *\n * The function is quality-aware (see the predicates behind it), the source\n * follows {@link borrowedSource}, and the numeral comes from\n * {@link chordToRoman}. `borrowed` is true whenever a source is identified —\n * including the Neapolitan, which the stricter parallel-mode predicate\n * {@link isBorrowedChord} does not count.\n *\n * @param chord The chord to analyze.\n * @param key The prevailing key.\n * @returns The chord analysis.\n * @example\n * ```ts\n * import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';\n * analyzeChord(makeChord(7, 'dom7'), majorKey(0));\n * // { function: 'dominant', borrowed: false, source: null, roman: 'V7' }\n * ```\n * @category Functional Harmony\n */\nexport function analyzeChord(chord: Chord, key: KeyScale): ChordAnalysis {\n const source = borrowedSource(chord, key);\n return {\n function: qualityAwareFunction(chord, key),\n borrowed: source !== null,\n source,\n roman: chordToRoman(chord, key),\n };\n}\n\n/**\n * The secondary dominant (V7) that tonicizes a scale degree.\n *\n * @param targetDegree 0-based scale degree to tonicize.\n * @param key The prevailing key.\n * @returns A dominant-seventh chord a fifth above the target's root.\n * @category Functional Harmony\n */\nexport function secondaryDominant(targetDegree: number, key: KeyScale): Chord {\n const targetRoot = degreeRootPc(targetDegree + 1, key);\n return makeChord(mod12(targetRoot + 7), 'dom7');\n}\n","/**\n * Borrowed chords (modal interchange) and the Neapolitan.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { HARMONIC_MINOR_MASK } from '../../theory/scale/index.js';\nimport { isDiatonic, isMinorKey, parallelKey } from './function.js';\nimport { isNeapolitan, mod12 } from './internal.js';\n\n/**\n * The origin of a recognized non-diatonic chord, or null when none applies.\n *\n * @category Functional Harmony\n */\nexport type BorrowedSource = 'parallel-minor' | 'parallel-major' | 'neapolitan' | null;\n\n/**\n * In a minor key, whether a non-diatonic chord is explained by the harmonic\n * minor scale on the same tonic (major V, V7, the raised-leading-tone viio).\n * Such chords are in-key chromatic alterations, not modal interchange.\n */\nfunction isHarmonicMinorAlteration(chord: Chord, key: KeyScale): boolean {\n return (\n isMinorKey(key) &&\n isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK })\n );\n}\n\n/**\n * Whether a chord is borrowed from the parallel mode (modal interchange).\n *\n * True when the chord is not diatonic to `key` but is diatonic to its\n * {@link parallelKey} — e.g. iv, bVI, or bVII in a major key, or the Picardy\n * tonic and major IV in a minor key. Two non-diatonic families are excluded:\n * chords diatonic to neither mode (they are chromatic, not borrowed), and, in\n * a minor key, chords explained by the harmonic minor scale (the major\n * dominant and raised-leading-tone chords), which are in-key alterations\n * rather than interchange even though they happen to fit the parallel major.\n *\n * @param chord The chord to test.\n * @param key The prevailing key.\n * @returns True if the chord is borrowed from the parallel mode.\n * @category Functional Harmony\n */\nexport function isBorrowedChord(chord: Chord, key: KeyScale): boolean {\n if (isDiatonic(chord, key)) {\n return false;\n }\n if (isHarmonicMinorAlteration(chord, key)) {\n return false;\n }\n return isDiatonic(chord, parallelKey(key));\n}\n\n/**\n * Identify where a non-diatonic chord comes from.\n *\n * The Neapolitan (major triad on b2) is recognized first, since it sits\n * outside both parallel modes; parallel-mode borrowing follows the\n * {@link isBorrowedChord} rules. Diatonic chords, harmonic-minor alterations,\n * and unrecognized chromatic chords all yield null.\n *\n * @param chord The chord to classify.\n * @param key The prevailing key.\n * @returns The borrowing source, or null.\n * @category Functional Harmony\n */\nexport function borrowedSource(chord: Chord, key: KeyScale): BorrowedSource {\n if (isDiatonic(chord, key)) {\n return null;\n }\n if (isNeapolitan(chord, key)) {\n return 'neapolitan';\n }\n if (isBorrowedChord(chord, key)) {\n return isMinorKey(key) ? 'parallel-major' : 'parallel-minor';\n }\n return null;\n}\n","/**\n * Cadence detection between two successive chords.\n *\n * Roots are pitch classes measured against the key tonic, so borrowed and\n * chromatic chords are handled by their semitone offset rather than requiring a\n * spelled key signature.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { isMinorKey } from './function.js';\nimport { mod12 } from './internal.js';\n\n/**\n * A recognized cadence type, or null when a chord pair forms none.\n *\n * @category Functional Harmony\n */\nexport type Cadence = 'authentic' | 'plagal' | 'half' | 'deceptive' | null;\n\n/**\n * Classify the cadence formed by moving from one chord to the next.\n *\n * - authentic: V (dominant a fifth above the tonic) to I\n * - plagal: IV (a fourth above the tonic) to I\n * - deceptive: V to the submediant (diatonic vi or borrowed bVI in major, VI in\n * minor)\n * - half: any chord other than the dominant itself to V\n *\n * A static V-to-V repeat with no root motion is not a cadence and yields null.\n *\n * @param from The penultimate chord.\n * @param to The final chord.\n * @param key The prevailing key.\n * @returns The cadence type, or null.\n * @example\n * ```ts\n * import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';\n * detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0)); // => 'authentic'\n * ```\n * @category Functional Harmony\n */\nexport function detectCadence(from: Chord, to: Chord, key: KeyScale): Cadence {\n const tonic = mod12(key.rootPc);\n const fromOffset = mod12(from.rootPc - tonic);\n const toOffset = mod12(to.rootPc - tonic);\n // Deceptive targets: the diatonic submediant (vi at offset 9 in major, VI at\n // offset 8 in minor) plus, in a major key, the borrowed flat-submediant bVI\n // at offset 8.\n const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];\n if (fromOffset === 7 && toOffset === 0) {\n return 'authentic';\n }\n if (fromOffset === 5 && toOffset === 0) {\n return 'plagal';\n }\n if (fromOffset === 7 && deceptiveTargets.includes(toOffset)) {\n return 'deceptive';\n }\n // A move to the dominant is a half cadence, but a no-root-motion V-to-V repeat\n // is not a cadence at all.\n if (toOffset === 7 && fromOffset !== 7) {\n return 'half';\n }\n return null;\n}\n","/**\n * Recognition: infer a chord name from a set of pitches, or a key from a set of\n * pitch classes. This is the inverse direction of the chord/scale builders,\n * which only go name -> notes.\n */\n\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord, ChordQuality } from '../../theory/chord/index.js';\nimport { chordPitchClasses, chordQualities, makeChord } from '../../theory/chord/index.js';\nimport {\n HARMONIC_MINOR_MASK,\n MAJOR_MASK,\n MELODIC_MINOR_MASK,\n majorKey,\n minorKey,\n NATURAL_MINOR_MASK,\n} from '../../theory/scale/index.js';\n\n/**\n * A candidate chord interpretation of a pitch set.\n *\n * @category Recognition\n */\nexport type ChordMatch = {\n rootPc: number;\n quality: ChordQuality;\n /** Chord tones absent from the input (an incomplete voicing). */\n missingPcs: number[];\n /** Input pitch classes not belonging to the chord. */\n extraPcs: number[];\n /** True when the input pitch-class set equals the chord exactly. */\n exact: boolean;\n /** Inversion implied by the lowest note: 0 root position, 1 first, ... */\n inversion: number;\n /** Bass pitch class when inverted (the lowest note is not the root). */\n bassPc?: number;\n};\n\n/**\n * A candidate key interpretation of a pitch-class set.\n *\n * @category Recognition\n */\nexport type KeyMatch = {\n key: KeyScale;\n mode: 'major' | 'minor';\n /** Fraction of input pitch classes that are in the scale, in [0, 1]. */\n fit: number;\n};\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/** Unique pitch classes of the input, sorted ascending. */\nfunction uniquePitchClasses(pitches: number[]): number[] {\n return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);\n}\n\n/**\n * Identify chords matching a set of pitches.\n *\n * Every input pitch class is tried as a root against every known chord quality.\n * A match is reported when all of the chord's tones are present in the input, or\n * when the only absent tone is the perfect fifth (a common omission in shell\n * voicings); matches are ranked best-first by fewest extra notes, then fewest\n * missing notes, then most specific (largest) chord. An exact match (no extras,\n * no missing) is flagged and ranked first.\n *\n * @param pitches MIDI pitches or bare pitch classes (octave-agnostic).\n * @returns Ranked chord interpretations (may be empty).\n * @example\n * ```ts\n * import { detectChord } from '@libraz/libcantus';\n * const matches = detectChord([60, 64, 67]); // C E G\n * matches[0]; // { rootPc: 0, quality: 'maj', exact: true, ... }\n * ```\n * @category Recognition\n */\nexport function detectChord(pitches: number[]): ChordMatch[] {\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const bassPc = pitchClass(Math.min(...pitches));\n const inputSet = new Set(input);\n const matches: ChordMatch[] = [];\n const qualities = chordQualities();\n for (const rootPc of input) {\n for (const quality of qualities) {\n const chord = makeChord(rootPc, quality);\n const tones = chordPitchClasses(chord);\n const toneSet = new Set(tones);\n const missingPcs = tones.filter((pc) => !inputSet.has(pc));\n // Accept an exact-tone match, or one whose only absent tone is the perfect\n // fifth; any other missing tone (third, sus tone, seventh, ...) rejects.\n const fifthPc = pitchClass(rootPc + 7);\n if (missingPcs.length > 1 || (missingPcs.length === 1 && missingPcs[0] !== fifthPc)) {\n continue;\n }\n const extraPcs = input.filter((pc) => !toneSet.has(pc));\n const bassIndex = chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);\n const inversion = bassIndex > 0 ? bassIndex : 0;\n const match: ChordMatch = {\n rootPc,\n quality,\n missingPcs,\n extraPcs,\n exact: extraPcs.length === 0 && missingPcs.length === 0,\n inversion,\n };\n if (inversion > 0) {\n match.bassPc = bassPc;\n }\n matches.push(match);\n }\n }\n matches.sort((a, b) => {\n if (a.extraPcs.length !== b.extraPcs.length) {\n return a.extraPcs.length - b.extraPcs.length;\n }\n if (a.missingPcs.length !== b.missingPcs.length) {\n return a.missingPcs.length - b.missingPcs.length;\n }\n // Prefer root position (the bass is the chord root) on a tie.\n if ((a.inversion === 0) !== (b.inversion === 0)) {\n return a.inversion === 0 ? -1 : 1;\n }\n const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;\n const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;\n return bSize - aSize;\n });\n return matches;\n}\n\n/**\n * The single best chord interpretation of a pitch set, or null if none.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns The top-ranked chord, or null when nothing matches.\n * @example\n * ```ts\n * import { detectChordBest } from '@libraz/libcantus';\n * detectChordBest([60, 64, 67]); // C major triad: { rootPc: 0, quality: 'maj', ... }\n * ```\n * @category Recognition\n */\nexport function detectChordBest(pitches: number[]): Chord | null {\n const best = detectChord(pitches)[0];\n if (!best) {\n return null;\n }\n return makeChord(best.rootPc, best.quality, best.bassPc);\n}\n\n/**\n * Minor-scale variants scored for each minor-key candidate. Scoring against all\n * three lets the raised sixth and seventh (e.g. the leading tone G# in A minor)\n * count toward their own tonic instead of only penalizing it.\n */\nconst MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];\n\n/**\n * Rank major and minor keys by how well they contain a set of pitch classes.\n *\n * The tonic is weighted so that, among equally-fitting keys, the one whose root\n * appears in the input is preferred. Minor candidates are scored against the\n * natural, harmonic, and melodic minor variants and keep the best of the three,\n * so a minor cadence containing the leading tone still resolves to its own\n * tonic; the returned key is always the natural-minor scale. Returns all 24\n * keys ranked best-first, or an empty array for an empty input (mirroring\n * {@link detectChord}).\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns Ranked key interpretations (empty for an empty input).\n * @example\n * ```ts\n * import { detectKey } from '@libraz/libcantus';\n * const keys = detectKey([60, 62, 64, 65, 67, 69, 71]); // C major scale\n * keys[0].mode; // 'major', with keys[0].key.rootPc === 0\n * ```\n * @category Recognition\n */\nexport function detectKey(pitches: number[]): KeyMatch[] {\n const input = uniquePitchClasses(pitches);\n if (input.length === 0) {\n return [];\n }\n const counts = new Map<number, number>();\n for (const pc of pitches.map(pitchClass)) {\n counts.set(pc, (counts.get(pc) ?? 0) + 1);\n }\n const total = pitches.length;\n const results: (KeyMatch & { score: number })[] = [];\n for (let tonic = 0; tonic < 12; tonic += 1) {\n for (const mode of ['major', 'minor'] as const) {\n const key = mode === 'major' ? majorKey(tonic) : minorKey(tonic);\n const masks = mode === 'major' ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;\n // Score each scale variant and keep the best; ties keep the earlier\n // (more diatonic) variant.\n let inScale = 0;\n let weighted = Number.NEGATIVE_INFINITY;\n for (const mask of masks) {\n let variantInScale = 0;\n let variantWeighted = 0;\n for (const pc of input) {\n const offset = (pc - tonic + 12) % 12;\n if ((mask >> offset) & 1) {\n variantInScale += 1;\n }\n }\n for (const [pc, count] of counts) {\n const offset = (pc - tonic + 12) % 12;\n if ((mask >> offset) & 1) {\n variantWeighted += count;\n }\n }\n if (variantWeighted > weighted) {\n weighted = variantWeighted;\n inScale = variantInScale;\n }\n }\n weighted += (counts.get(tonic) ?? 0) * 0.5;\n results.push({\n key,\n mode,\n fit: inScale / input.length,\n score: weighted / total,\n });\n }\n }\n results.sort((a, b) => b.score - a.score);\n return results.map(({ key, mode, fit }) => ({ key, mode, fit }));\n}\n","import type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar, metricWeight, parseTimeSignature } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.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, detectKey } from '../detect/index.js';\nimport type { Cadence } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\n\n/**\n * A chord occupying a half-open beat span.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordSegment = {\n startBeat: number;\n endBeat: number;\n chord: Chord;\n};\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(chords: ChordSpan[], totalBeats: number): ChordTimeline {\n const sorted = [...chords].sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const endBeat = Math.max(gc.startBeat, next ? next.startBeat : totalBeats);\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/** Build the `at(beat)` lookup over a segment list. */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => {\n for (const seg of segments) {\n if (beat >= seg.startBeat && beat < seg.endBeat) {\n return seg.chord;\n }\n }\n return 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/** Reduce a value to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\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 detectKey} 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\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: 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 const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBar(ts);\n if (!(harmonicRhythm > 0)) {\n throw new Error(`Invalid harmonic rhythm: ${harmonicRhythm}`);\n }\n // Zero/negative-length notes never sound; drop them before any inference.\n const sounding = notes.filter((note) => note.durationBeat > 0);\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n const key = opts.key ?? detectKey(sounding.map((n) => n.pitch))[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 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(sounding, 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 { VoiceNote } 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/** 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: VoiceNote[];\n /** The same notes as sounding spans, each carrying its predecessor's pitch. */\n sounding: PreparedNote[];\n};\n\n/** A track prepared for analysis: resolved metadata plus monophonic sub-voices. */\nexport type PreparedTrack = {\n name: string;\n role: TrackRole;\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/**\n * Every sounding note of every track pooled into one flat list. Zero- and\n * negative-length notes never sound, so they are dropped here (matching the\n * chord-inference ingest in the timeline module).\n */\nexport function poolNotes(tracks: ArrangementTrack[]): NoteEvent[] {\n const notes: NoteEvent[] = [];\n for (const track of tracks) {\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 * Greedy lane assignment: each note joins the first sub-voice that is free at\n * its onset (its last note has ended), otherwise it opens a new sub-voice. A\n * monophonic track therefore stays a single sub-voice, while block chords fan\n * out into one sub-voice per simultaneous note, keeping every sub-voice's\n * note-to-note adjacency genuinely melodic.\n */\nfunction splitIntoSubVoices(ordered: VoiceNote[]): VoiceNote[][] {\n const lanes: VoiceNote[][] = [];\n for (const note of ordered) {\n const lane = lanes.find((candidate) => {\n const last = candidate[candidate.length - 1];\n return last !== undefined && last.startBeat + last.durationBeat <= note.startBeat + EPS;\n });\n if (lane) {\n lane.push(note);\n } else {\n lanes.push([note]);\n }\n }\n return lanes;\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. Zero- and negative-length notes never sound and\n * are dropped at ingest. Each track is then partitioned into monophonic\n * sub-voices (see {@link splitIntoSubVoices}); the predecessor pitch used for\n * suspension and parallel detection is the previous note of the same sub-voice,\n * 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: VoiceNote[] = track.notes\n .filter((note) => note.durationBeat > 0)\n .sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch)\n .map((note) => ({ ...note, id: nextId++ }));\n const voices: PreparedVoice[] = splitIntoSubVoices(ordered).map((voice) => ({\n voice,\n sounding: voice.map((note, i) => {\n const prev = i > 0 ? voice[i - 1] : undefined;\n return {\n pitch: note.pitch,\n prevPitch: prev?.pitch,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n };\n }),\n }));\n return {\n name: track.name ?? `track ${index + 1}`,\n role: track.role ?? 'other',\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 type { KeyScale } from '../../core/types.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport { evaluateSafety, NoteSafety, type SafetyProfile } from '../../theory/safety/index.js';\nimport { functionOf } from '../functional/index.js';\nimport { type ChordTimeline, chordTimelineFromNotes } from '../timeline/index.js';\nimport { covers, EPS, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\nimport type { 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/** Reduce a pitch to a pitch class in [0, 11]. */\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\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 {@link 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 {@link DISSONANCE_WEIGHT};\n * - the registral span of the sounding notes, saturating at\n * {@link SPAN_SATURATION} semitones, weighted {@link 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 const profile: SafetyProfile = opts.profile ?? 'pop';\n const step = opts.step ?? 1;\n if (!(step > 0)) {\n throw new Error(`Invalid tension sampling step: ${step}`);\n }\n\n const pooled = poolNotes(tracks);\n const totalBeats = pooled.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const { timeline, key } = chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n });\n\n const prepared = prepareTracks(tracks);\n const points: TensionPoint[] = [];\n const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS));\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/** All sounding notes across every track at a beat, tagged with their track. */\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) {\n continue;\n }\n for (const subVoice of track.voices) {\n for (const note of subVoice.sounding) {\n if (covers(note, beat)) {\n out.push({ pitch: note.pitch, track: t });\n }\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 const result = evaluateSafety({\n profile,\n candidatePitch: voice.pitch,\n chord,\n key,\n otherVoices: others,\n strongBeat,\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 type { KeyScale, NoteEvent } from '../../core/types.js';\nimport type { Chord } from '../../theory/chord/index.js';\nimport { chordPitchClasses, chordToneRole } 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 theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3'; 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 * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: 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.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id: number };\n\n/** Float tolerance for beat comparisons. */\nconst EPS = 1e-9;\n\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\nfunction intervalAboveRoot(pitch: number, chord: Chord): number {\n return (((pitchClass(pitch) - pitchClass(chord.rootPc)) % 12) + 12) % 12;\n}\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * 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 isChordMember(pitch: number, chord: Chord | null): boolean {\n return chord ? chordPitchClasses(chord).includes(pitchClass(pitch)) : false;\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): TheoryLabel & { kind: 'suspension' } {\n let type: 'sus4-3' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n if (delta > 0) {\n type = 'sus2-3';\n } else if (ic === 2) {\n type = 'sus9-8';\n } else if (ic === 10 || ic === 11) {\n type = 'sus7-6';\n } else {\n type = 'sus4-3';\n }\n return { kind: 'suspension', type, resolveTo: 0 };\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.\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.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { id: 0, pitch: 60, startBeat: 0, durationBeat: 1 },\n * { id: 1, pitch: 62, startBeat: 1, durationBeat: 1 },\n * { id: 2, 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: VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyScale,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[],\n): AnalyzedNote[] {\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 prev =\n prevNote !== undefined && prevNote.startBeat < note.startBeat - EPS ? prevNote : undefined;\n const next =\n nextNote !== undefined && nextNote.startBeat > note.startBeat + EPS ? nextNote : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n 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 sus = suspensionType(ic, next.pitch - note.pitch);\n sus.resolveTo = next.pitch;\n labels.push(sus);\n handled = true;\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 result.push({ noteId: note.id, labels, rationale: describe(labels) });\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 { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n evaluateSafety,\n NoteSafety,\n type SafetyProfile,\n type VoiceSnapshot,\n} from '../../theory/safety/index.js';\nimport {\n type CadenceHit,\n type ChordTimeline,\n chordTimelineFromNotes,\n detectCadences,\n} from '../timeline/index.js';\nimport { type AnalyzedNote, analyzeVoice, type VoiceNote } from '../voice/index.js';\nimport { covers, EPS, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\n\n/**\n * The musical role a track plays in the arrangement.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackRole = 'melody' | 'harmony' | 'bass' | '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: 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 pitch: number;\n /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */\n safety: NoteSafety;\n /** {@link import('../safety/index.js').SafetyResult.reasons} bitmask. */\n reasons: 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 * 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\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) {\n continue;\n }\n for (let v = 0; v < track.voices.length; v += 1) {\n if (t === excludeTrack && v === excludeVoice) {\n continue;\n }\n const subVoice = track.voices[v];\n if (!subVoice) {\n continue;\n }\n for (const note of subVoice.sounding) {\n if (!covers(note, beat)) {\n continue;\n }\n const snap: VoiceSnapshot = { pitch: note.pitch };\n if (note.prevPitch !== undefined) {\n snap.prevPitch = note.prevPitch;\n }\n out.push(snap);\n }\n }\n }\n return out;\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 */\nfunction evaluationBeats(note: VoiceNote, timeline: ChordTimeline): number[] {\n const noteEnd = note.startBeat + note.durationBeat;\n const beats = [note.startBeat];\n for (const segment of timeline.segments) {\n if (segment.startBeat > note.startBeat + EPS && segment.startBeat < noteEnd - EPS) {\n beats.push(segment.startBeat);\n }\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 track\n * (see {@link chordTimelineFromNotes}); pooling all voices is robust even when\n * roles are absent or a track doubles the harmony, so it is preferred over\n * deriving the harmony from a subset. Zero- and negative-length notes never\n * sound, so they are dropped at ingest and appear in neither the annotations\n * 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 * @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 const profile: SafetyProfile = opts.profile ?? 'pop';\n const pooled = poolNotes(tracks);\n\n const { timeline, key, segmentConfidence } = chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n });\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 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 notes.push(\n ...analyzeVoice(subVoice.voice, timeline.at, key, (beat) =>\n otherVoicesSounding(prepared, t, v, beat),\n ),\n );\n\n for (const note of subVoice.voice) {\n for (const beat of evaluationBeats(note, timeline)) {\n const result = evaluateSafety({\n profile,\n candidatePitch: 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) {\n const conflict: Conflict = {\n beat,\n trackName: track.name,\n pitch: note.pitch,\n safety: result.safety,\n reasons: result.reasons,\n };\n if (result.rationale !== undefined) {\n conflict.rationale = result.rationale;\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"],"mappings":"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","names":["pitchClass","pitchClass","pitchClass","pitchClass","pitchClass","degreeNumber","hasMajorThird","pitchClass","EPS","pitchClass","EPS","pitchClass","EPS","EPS","pitchClass","intervalAboveRoot","isChordMember","stepResolution","describe","EPS"]}
|