@libraz/libcantus 0.9.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -270
- package/README_ja.md +61 -214
- package/dist/analyze/index.cjs +54 -7
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +200 -353
- package/dist/analyze/index.d.ts +200 -353
- package/dist/analyze/index.js +56 -9
- package/dist/chunk-3JXCW6DX.cjs +3220 -0
- package/dist/chunk-3JXCW6DX.cjs.map +1 -0
- package/dist/chunk-4A2RMUCC.js +7647 -0
- package/dist/chunk-4A2RMUCC.js.map +1 -0
- package/dist/chunk-6XPTBE7Y.cjs +49 -0
- package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
- package/dist/chunk-AFM2ULJH.cjs +5121 -0
- package/dist/chunk-AFM2ULJH.cjs.map +1 -0
- package/dist/chunk-E3F4AHAH.cjs +690 -0
- package/dist/chunk-E3F4AHAH.cjs.map +1 -0
- package/dist/{chunk-2PTFGVD7.js → chunk-ELCTZUJJ.js} +250 -132
- package/dist/chunk-ELCTZUJJ.js.map +1 -0
- package/dist/chunk-FVSI36UV.js +690 -0
- package/dist/chunk-FVSI36UV.js.map +1 -0
- package/dist/chunk-GEDTJHAC.cjs +1 -0
- package/dist/chunk-GEDTJHAC.cjs.map +1 -0
- package/dist/chunk-GOZG57ER.js +3220 -0
- package/dist/chunk-GOZG57ER.js.map +1 -0
- package/dist/chunk-H3VORETI.js +2243 -0
- package/dist/chunk-H3VORETI.js.map +1 -0
- package/dist/chunk-H42UHPYW.js +1577 -0
- package/dist/chunk-H42UHPYW.js.map +1 -0
- package/dist/chunk-HFPS7JFD.js +1 -0
- package/dist/chunk-INHVXP6Z.js +3053 -0
- package/dist/chunk-INHVXP6Z.js.map +1 -0
- package/dist/chunk-K4ZJUGF4.cjs +379 -0
- package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
- package/dist/chunk-KNJEKLKV.js +49 -0
- package/dist/chunk-KNJEKLKV.js.map +1 -0
- package/dist/chunk-M4CWTJWJ.cjs +7647 -0
- package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
- package/dist/chunk-MXO72PLY.cjs +211 -0
- package/dist/chunk-MXO72PLY.cjs.map +1 -0
- package/dist/chunk-NLEU4QSE.cjs +360 -0
- package/dist/chunk-NLEU4QSE.cjs.map +1 -0
- package/dist/chunk-OE6WK2Y2.cjs +1577 -0
- package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
- package/dist/chunk-Q2WGKJNN.cjs +1064 -0
- package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
- package/dist/chunk-QYQQ5J6E.js +1 -0
- package/dist/chunk-QYQQ5J6E.js.map +1 -0
- package/dist/chunk-RHIJ3S4I.cjs +1 -0
- package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
- package/dist/chunk-TTCLJX6D.js +211 -0
- package/dist/chunk-TTCLJX6D.js.map +1 -0
- package/dist/chunk-UADYAYAT.cjs +3053 -0
- package/dist/chunk-UADYAYAT.cjs.map +1 -0
- package/dist/chunk-VOHZB45S.js +5121 -0
- package/dist/chunk-VOHZB45S.js.map +1 -0
- package/dist/chunk-VTI6FOLB.js +1064 -0
- package/dist/chunk-VTI6FOLB.js.map +1 -0
- package/dist/chunk-WWURE4XT.cjs +2243 -0
- package/dist/chunk-WWURE4XT.cjs.map +1 -0
- package/dist/chunk-YH3BIKHH.js +360 -0
- package/dist/chunk-YH3BIKHH.js.map +1 -0
- package/dist/core/index.cjs +93 -6
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +87 -346
- package/dist/core/index.d.ts +87 -346
- package/dist/core/index.js +94 -7
- package/dist/generate/index.cjs +89 -10
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +436 -1094
- package/dist/generate/index.d.ts +436 -1094
- package/dist/generate/index.js +94 -15
- package/dist/index-9wcMhG73.d.ts +1095 -0
- package/dist/{index-Cfx8xgH0.d.ts → index-B4o55NDr.d.cts} +13 -9
- package/dist/index-B9MNZboQ.d.cts +770 -0
- package/dist/index-B9MNZboQ.d.ts +770 -0
- package/dist/index-BGFW-8P2.d.cts +2307 -0
- package/dist/index-BO6xE9YU.d.cts +1804 -0
- package/dist/index-BkGmlyI3.d.cts +470 -0
- package/dist/index-CCNJRG-P.d.cts +1095 -0
- package/dist/index-CGpRv5sl.d.cts +643 -0
- package/dist/index-C_8o--VE.d.cts +1242 -0
- package/dist/index-CgHqxdps.d.ts +2307 -0
- package/dist/index-Chbst3Jx.d.ts +1343 -0
- package/dist/index-Cj49F892.d.ts +470 -0
- package/dist/{index-x2vcsduE.d.cts → index-CunYKLiu.d.ts} +13 -9
- package/dist/index-D57QRbVn.d.ts +1804 -0
- package/dist/index-DInQqT8Q.d.cts +66 -0
- package/dist/index-DInQqT8Q.d.ts +66 -0
- package/dist/index-Dzr13q3d.d.cts +1343 -0
- package/dist/index-G1uwGGWQ.d.ts +643 -0
- package/dist/index-szBv3496.d.ts +1242 -0
- package/dist/index.cjs +348 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -15
- package/dist/index.d.ts +17 -15
- package/dist/index.js +397 -64
- package/dist/masks-DYbE485J.d.cts +445 -0
- package/dist/masks-DYbE485J.d.ts +445 -0
- package/dist/model/index.cjs +39 -7
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +4659 -249
- package/dist/model/index.d.ts +4659 -249
- package/dist/model/index.js +40 -8
- package/dist/positional-Cptlj-xs.d.ts +287 -0
- package/dist/positional-DvH_sd8u.d.cts +287 -0
- package/dist/theory/index.cjs +101 -7
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +511 -171
- package/dist/theory/index.d.ts +511 -171
- package/dist/theory/index.js +118 -24
- package/package.json +6 -4
- package/dist/chunk-2A2QMA74.cjs +0 -261
- package/dist/chunk-2A2QMA74.cjs.map +0 -1
- package/dist/chunk-2PTFGVD7.js.map +0 -1
- package/dist/chunk-357ZDPVU.cjs +0 -162
- package/dist/chunk-357ZDPVU.cjs.map +0 -1
- package/dist/chunk-3KX76RLG.js +0 -1548
- package/dist/chunk-3KX76RLG.js.map +0 -1
- package/dist/chunk-4VZ5MZAM.cjs +0 -889
- package/dist/chunk-4VZ5MZAM.cjs.map +0 -1
- package/dist/chunk-7EONYVI5.js +0 -1204
- package/dist/chunk-7EONYVI5.js.map +0 -1
- package/dist/chunk-7JEP3MFY.js +0 -162
- package/dist/chunk-7JEP3MFY.js.map +0 -1
- package/dist/chunk-AID75RN3.js +0 -3176
- package/dist/chunk-AID75RN3.js.map +0 -1
- package/dist/chunk-BSLTLGM3.cjs +0 -497
- package/dist/chunk-BSLTLGM3.cjs.map +0 -1
- package/dist/chunk-CU6VG2YM.cjs +0 -109
- package/dist/chunk-CU6VG2YM.cjs.map +0 -1
- package/dist/chunk-DZMHIGI4.js +0 -337
- package/dist/chunk-DZMHIGI4.js.map +0 -1
- package/dist/chunk-EDOTEABK.js +0 -109
- package/dist/chunk-EDOTEABK.js.map +0 -1
- package/dist/chunk-HN2OEUN2.js +0 -33
- package/dist/chunk-HN2OEUN2.js.map +0 -1
- package/dist/chunk-KBTTEX7Q.js +0 -497
- package/dist/chunk-KBTTEX7Q.js.map +0 -1
- package/dist/chunk-KQN2ZYND.cjs +0 -724
- package/dist/chunk-KQN2ZYND.cjs.map +0 -1
- package/dist/chunk-MPTVJIVG.cjs +0 -3176
- package/dist/chunk-MPTVJIVG.cjs.map +0 -1
- package/dist/chunk-MTMZTMWM.cjs +0 -53
- package/dist/chunk-MTMZTMWM.cjs.map +0 -1
- package/dist/chunk-N6IHQUOX.js +0 -1
- package/dist/chunk-NNB67XA2.js +0 -724
- package/dist/chunk-NNB67XA2.js.map +0 -1
- package/dist/chunk-OL3IHHSJ.cjs +0 -1204
- package/dist/chunk-OL3IHHSJ.cjs.map +0 -1
- package/dist/chunk-PI3UXRWK.cjs +0 -528
- package/dist/chunk-PI3UXRWK.cjs.map +0 -1
- package/dist/chunk-R2VVFAIN.js +0 -528
- package/dist/chunk-R2VVFAIN.js.map +0 -1
- package/dist/chunk-T64C7ZD7.cjs +0 -1
- package/dist/chunk-T64C7ZD7.cjs.map +0 -1
- package/dist/chunk-WFDXOLUB.cjs +0 -337
- package/dist/chunk-WFDXOLUB.cjs.map +0 -1
- package/dist/chunk-WTAN4ACG.cjs +0 -1548
- package/dist/chunk-WTAN4ACG.cjs.map +0 -1
- package/dist/chunk-YFJXS3AU.cjs +0 -33
- package/dist/chunk-YFJXS3AU.cjs.map +0 -1
- package/dist/chunk-YUE6TJ4M.js +0 -889
- package/dist/chunk-YUE6TJ4M.js.map +0 -1
- package/dist/chunk-ZGLJM4A4.js +0 -53
- package/dist/chunk-ZGLJM4A4.js.map +0 -1
- package/dist/function-CxixTB02.d.cts +0 -299
- package/dist/function-DTCt675z.d.ts +0 -299
- package/dist/index-B2Ulxxpo.d.cts +0 -144
- package/dist/index-BkYJ0xMq.d.cts +0 -152
- package/dist/index-BuaB-ypt.d.ts +0 -144
- package/dist/index-CAN7v-kw.d.ts +0 -199
- package/dist/index-COw1Ky9g.d.ts +0 -242
- package/dist/index-DRtIhlJj.d.ts +0 -152
- package/dist/index-DXd9JikN.d.cts +0 -246
- package/dist/index-DXd9JikN.d.ts +0 -246
- package/dist/index-alrRninU.d.cts +0 -199
- package/dist/index-oTQbYYIs.d.cts +0 -242
- package/dist/index-tR9jTn26.d.cts +0 -275
- package/dist/index-tR9jTn26.d.ts +0 -275
- package/dist/styled-Bl0ZK28h.d.ts +0 -543
- package/dist/styled-Crv6SA4S.d.cts +0 -543
- package/dist/types-BN6_Q9Kl.d.cts +0 -39
- package/dist/types-BN6_Q9Kl.d.ts +0 -39
- /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../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":["import { createNoteEventIndex } from '../../core/event-index/index.js';\nimport type { TimeSignature } from '../../core/meter/index.js';\nimport { beatsPerBar, metricWeight, parseTimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport type { Chord, ChordSpan } from '../../theory/chord/index.js';\nimport { chordPitchClasses, makeChord } from '../../theory/chord/index.js';\nimport { isScaleTone, majorKey } from '../../theory/scale/index.js';\nimport type { ChordMatch } from '../detect/index.js';\nimport { detectChord, detectKeyFromNotes } from '../detect/index.js';\nimport type { Cadence } from '../functional/index.js';\nimport { detectCadence } from '../functional/index.js';\n\nexport type { ChordSegment } from '../../theory/chord/index.js';\n\nimport type { ChordSegment } from '../../theory/chord/index.js';\n\n/**\n * A beat-indexed sequence of chord segments.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimeline = {\n /** The chord sounding at a beat, or null when no segment covers it. */\n at: (beat: number) => Chord | null;\n segments: ChordSegment[];\n};\n\n/**\n * Build a chord timeline from placed chords.\n *\n * Each chord spans from its `startBeat` to the next chord's `startBeat`; the last\n * chord runs to `totalBeats`. Segments with no positive length (from duplicate\n * onsets or a last chord at or past `totalBeats`) are dropped. `at(beat)` returns\n * the covering segment's chord, or null when the beat lies outside every segment.\n *\n * @param chords Placed chords in time order.\n * @param totalBeats End of the timeline in beats.\n * @returns A queryable chord timeline.\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromChords(\n chords: readonly ChordSpan[],\n totalBeats: number,\n): ChordTimeline {\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n assertGenerationBudget(chords.length, 'timeline chords');\n for (let index = 0; index < chords.length; index += 1) {\n assertRange(\n chords[index]?.startBeat ?? Number.NaN,\n 0,\n Number.MAX_SAFE_INTEGER,\n `chords[${index}].startBeat`,\n );\n }\n const sorted = [...chords]\n .filter((chord) => chord.startBeat < totalBeats)\n .sort((a, b) => a.startBeat - b.startBeat);\n const segments: ChordSegment[] = sorted\n .map((gc, i) => {\n const next = sorted[i + 1];\n const nextBeat = next ? next.startBeat : totalBeats;\n const endBeat = Math.min(totalBeats, Math.max(gc.startBeat, nextBeat));\n return {\n startBeat: gc.startBeat,\n endBeat,\n chord: makeChord(gc.rootPc, gc.quality, gc.bassPc),\n };\n })\n .filter((seg) => seg.endBeat > seg.startBeat);\n\n return { at: segmentLookup(segments), segments };\n}\n\n/**\n * Build the `at(beat)` lookup over a segment list.\n *\n * Segments are disjoint and in beat order, so the covering one is found by\n * binary search. A linear scan here is the inner loop of arrangement analysis,\n * which queries it once per note per chord change.\n */\nfunction segmentLookup(segments: ChordSegment[]): (beat: number) => Chord | null {\n return (beat) => {\n assertFiniteNumber(beat, 'timeline query beat');\n let low = 0;\n let high = segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n const candidate = segments[low - 1];\n return candidate !== undefined && beat >= candidate.startBeat && beat < candidate.endBeat\n ? candidate.chord\n : null;\n };\n}\n\nconst EPS = 1e-9;\n\n/** Fraction of the strongest pitch-class weight below which a pc is noise. */\nconst NOISE_THRESHOLD_RATIO = 0.2;\n\n/** Maximum number of pitch classes fed to chord detection per window. */\nconst MAX_DETECTION_PCS = 6;\n\n/** Confidence multiplier applied when the chosen chord match is inexact. */\nconst INEXACT_CONFIDENCE_FACTOR = 0.85;\n\n/** Score bonus for a match whose tones are all in the key. */\nconst DIATONIC_BONUS = 0.5;\n\n/** Score bonus for an exact match (no extra and no missing tones). */\nconst EXACT_BONUS = 0.5;\n\n/** Score penalty per extra or missing tone in a match. */\nconst MISMATCH_PENALTY = 0.3;\n\n/**\n * Options controlling {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineOptions = {\n /** Key context; inferred from the notes with {@link detectKeyFromNotes} when omitted. */\n key?: KeyScale;\n /**\n * Time signature used for metric accents; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Window length in beats per chord slot; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * End of the analyzed span in beats; defaults to the end of the last note.\n *\n * @defaultValue the end of the last note\n */\n totalBeats?: number;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * The result of {@link chordTimelineFromNotes}.\n *\n * @category Arrangement & Analysis\n */\nexport type ChordTimelineResult = {\n /** The inferred timeline, with adjacent identical chords merged. */\n timeline: ChordTimeline;\n /** The key used for the analysis (given or inferred). */\n key: KeyScale;\n /** One confidence value in [0, 1] per segment, in segment order. */\n segmentConfidence: number[];\n};\n\n/** A window's inferred chord and its confidence, or null for an empty window. */\ntype WindowChord = {\n chord: Chord;\n confidence: number;\n};\n\n/**\n * Score a chord match against a window's pitch-class weights: a heavily\n * weighted root, in-key tones, and exactness all raise the score; extra or\n * missing tones lower it.\n */\nfunction scoreMatch(\n match: ChordMatch,\n weights: number[],\n maxWeight: number,\n key: KeyScale,\n): number {\n const rootWeight = maxWeight > 0 ? (weights[match.rootPc] ?? 0) / maxWeight : 0;\n const tones = chordPitchClasses(makeChord(match.rootPc, match.quality));\n let score = rootWeight;\n if (tones.every((pc) => isScaleTone(pc, key))) {\n score += DIATONIC_BONUS;\n }\n if (match.exact) {\n score += EXACT_BONUS;\n }\n score -= MISMATCH_PENALTY * (match.extraPcs.length + match.missingPcs.length);\n return score;\n}\n\n/** Confidence of a chord for a window: chord-tone weight over total weight. */\nfunction chordConfidence(\n chord: Chord,\n weights: number[],\n totalWeight: number,\n exact: boolean,\n): number {\n const tones = new Set(chordPitchClasses(chord));\n let chordWeight = 0;\n for (let pc = 0; pc < 12; pc += 1) {\n if (tones.has(pc)) {\n chordWeight += weights[pc] ?? 0;\n }\n }\n const raw = totalWeight > 0 ? chordWeight / totalWeight : 0;\n const confidence = Math.min(1, Math.max(0, raw));\n return exact ? confidence : confidence * INEXACT_CONFIDENCE_FACTOR;\n}\n\n/**\n * Infer the chord sounding in one window from the notes overlapping it.\n *\n * Builds a pitch-class weight histogram (overlap duration x velocity x\n * metric-accent bonus for onsets inside the window), keeps the significantly\n * weighted pitch classes, and picks the best {@link detectChord} match by\n * root weight, key membership, and exactness.\n */\nfunction analyzeWindow(\n notes: readonly NoteEvent[],\n windowStart: number,\n windowEnd: number,\n ts: TimeSignature,\n key: KeyScale,\n): WindowChord | null {\n const weights = new Array<number>(12).fill(0);\n let lowestPitch = Number.POSITIVE_INFINITY;\n for (const note of notes) {\n const noteEnd = note.startBeat + note.durationBeat;\n const overlap = Math.min(noteEnd, windowEnd) - Math.max(note.startBeat, windowStart);\n if (overlap <= EPS) {\n continue;\n }\n const velocityFactor = note.velocity !== undefined ? note.velocity / 127 : 1;\n const onsetInWindow = note.startBeat >= windowStart - EPS && note.startBeat < windowEnd - EPS;\n const accent = onsetInWindow ? 1 + metricWeight(note.startBeat, ts) / 3 : 1;\n const pc = pitchClass(note.pitch);\n weights[pc] = (weights[pc] ?? 0) + overlap * velocityFactor * accent;\n if (note.pitch < lowestPitch) {\n lowestPitch = note.pitch;\n }\n }\n let totalWeight = 0;\n let maxWeight = 0;\n for (const w of weights) {\n totalWeight += w;\n maxWeight = Math.max(maxWeight, w);\n }\n if (totalWeight <= EPS) {\n return null;\n }\n\n // Keep pitch classes with meaningful weight, strongest first.\n const ranked = weights\n .map((weight, pc) => ({ pc, weight }))\n .filter(({ weight }) => weight > 0)\n .sort((a, b) => b.weight - a.weight || a.pc - b.pc);\n const selected = ranked\n .filter(({ weight }) => weight >= maxWeight * NOISE_THRESHOLD_RATIO)\n .slice(0, MAX_DETECTION_PCS)\n .map(({ pc }) => pc);\n\n // Feed detection with the window's true bass as the lowest pitch so inversion\n // detection works. If that pitch class was removed as noise, detection must\n // become unordered instead: promoting the strongest remaining tone to bass\n // manufactures a slash chord that the input never established.\n const bassPc = pitchClass(lowestPitch);\n const hasBass = selected.includes(bassPc);\n const detectionPitches = hasBass\n ? selected.map((pc) => (pc === bassPc ? pc : pc + 12))\n : selected;\n\n const matches = detectChord(detectionPitches, { input: hasBass ? 'midi' : 'pitchClass' });\n let bestMatch: ChordMatch | null = null;\n let bestScore = Number.NEGATIVE_INFINITY;\n for (const match of matches) {\n const score = scoreMatch(match, weights, maxWeight, 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 return null;\n}\n\n/** Whether two chords are the same root, quality, and bass. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc;\n}\n\n/**\n * Infer a chord timeline from raw multi-track notes.\n *\n * The span `[0, totalBeats)` is sliced into `harmonicRhythm`-beat windows; each\n * window's chord is inferred from a pitch-class weight histogram of the notes\n * overlapping it (weight = overlap duration x velocity x metric-accent bonus\n * for onsets in the window). Adjacent windows carrying the identical chord are\n * merged into one segment; windows with no notes produce no segment, so\n * `at(beat)` returns null there. Each segment carries a confidence in [0, 1]:\n * the fraction of the window weight explained by chord tones, reduced when the\n * match is inexact, and duration-weighted across merged windows.\n *\n * Notes with a zero or negative duration never sound, so they are dropped at\n * ingest: they contribute to neither the key inference, the span, nor any\n * window's histogram.\n *\n * @param notes The notes to analyze (any number of tracks, flattened).\n * @param opts Analysis options; see {@link ChordTimelineOptions}.\n * @returns The inferred timeline, the key used, and per-segment confidences.\n * @throws If `harmonicRhythm` is not positive.\n * @example\n * ```ts\n * import { chordTimelineFromNotes } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 }, // C\n * { pitch: 64, startBeat: 0, durationBeat: 2 }, // E\n * { pitch: 67, startBeat: 0, durationBeat: 2 }, // G\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * timeline.at(0); // the chord inferred over beat 0, or null\n * ```\n * @category Arrangement & Analysis\n */\nexport function chordTimelineFromNotes(\n notes: NoteEvent[],\n opts: ChordTimelineOptions = {},\n): ChordTimelineResult {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const harmonicRhythm = opts.harmonicRhythm ?? beatsPerBar(ts);\n assertRange(harmonicRhythm, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'harmonic rhythm');\n const budget = opts.budget;\n assertNoteEvents(notes, 'timeline notes', { allowNonPositiveDuration: true, budget });\n // Zero/negative-length notes never sound; drop them before any inference.\n const soundingIndex = createNoteEventIndex(\n notes.filter((note) => note.durationBeat > 0),\n { name: 'timeline notes', budget },\n );\n const sounding = soundingIndex.notes.map(({ note }) => note);\n const lastNoteEnd = sounding.reduce((end, n) => Math.max(end, n.startBeat + n.durationBeat), 0);\n const totalBeats = opts.totalBeats ?? lastNoteEnd;\n assertRange(totalBeats, 0, Number.MAX_SAFE_INTEGER, 'timeline totalBeats');\n // Weighted by duration and velocity, exactly as the per-window chord\n // histogram below is: a busy ornamental figure must not outvote the sustained\n // harmony that establishes the key.\n const key = opts.key ?? detectKeyFromNotes(sounding, { budget })[0]?.key ?? majorKey(0);\n\n const segments: ChordSegment[] = [];\n const segmentConfidence: number[] = [];\n const windowCount = Math.max(0, Math.ceil(totalBeats / harmonicRhythm - EPS));\n assertGenerationBudget(windowCount, 'timeline windows', budget);\n const windowNotes: NoteEvent[][] = Array.from({ length: windowCount }, () => []);\n let memberships = 0;\n for (const indexed of soundingIndex.notes) {\n const first = Math.max(0, Math.floor(indexed.note.startBeat / harmonicRhythm));\n const lastExclusive = Math.min(windowCount, Math.ceil(indexed.endBeat / harmonicRhythm));\n memberships += Math.max(0, lastExclusive - first);\n assertGenerationBudget(memberships, 'timeline note-to-window memberships', budget);\n for (let window = first; window < lastExclusive; window += 1) {\n windowNotes[window]?.push(indexed.note);\n }\n }\n for (let i = 0; i < windowCount; i += 1) {\n const start = i * harmonicRhythm;\n const end = Math.min(start + harmonicRhythm, totalBeats);\n const inferred = analyzeWindow(windowNotes[i] ?? [], start, end, ts, key);\n if (!inferred) {\n continue;\n }\n const last = segments[segments.length - 1];\n if (last && sameChord(last.chord, inferred.chord) && Math.abs(last.endBeat - start) < EPS) {\n // Merge into the previous segment, blending confidence by duration.\n const lastLength = last.endBeat - last.startBeat;\n const length = end - start;\n const lastConfidence = segmentConfidence[segmentConfidence.length - 1] ?? 0;\n segmentConfidence[segmentConfidence.length - 1] =\n (lastConfidence * lastLength + inferred.confidence * length) / (lastLength + length);\n last.endBeat = end;\n } else {\n segments.push({ startBeat: start, endBeat: end, chord: inferred.chord });\n segmentConfidence.push(inferred.confidence);\n }\n }\n\n return {\n timeline: { at: segmentLookup(segments), segments },\n key,\n segmentConfidence,\n };\n}\n\n/**\n * A cadence found between two consecutive timeline segments.\n *\n * @category Arrangement & Analysis\n */\nexport type CadenceHit = {\n /** The beat where the cadence arrives (the second chord's onset). */\n atBeat: number;\n type: Exclude<Cadence, null>;\n from: Chord;\n to: Chord;\n};\n\n/**\n * Find cadences between consecutive segments of a chord timeline.\n *\n * Each temporally adjacent segment pair is classified with\n * {@link detectCadence}; pairs forming no cadence are skipped. Segments\n * separated by a gap (a rest in the timeline) are not a chord-to-chord\n * progression, so they are never paired.\n *\n * @param timeline The chord timeline to scan.\n * @param key The prevailing key.\n * @returns The cadences found, in time order.\n * @example\n * ```ts\n * import { chordTimelineFromNotes, detectCadences } from '@libraz/libcantus';\n * const notes = [\n * { pitch: 67, startBeat: 0, durationBeat: 4 }, // G, a dominant\n * { pitch: 60, startBeat: 4, durationBeat: 4 }, // C, the tonic\n * ];\n * const { timeline, key } = chordTimelineFromNotes(notes);\n * const cadences = detectCadences(timeline, key);\n * ```\n * @category Arrangement & Analysis\n */\nexport function detectCadences(timeline: ChordTimeline, key: KeyScale): CadenceHit[] {\n const hits: CadenceHit[] = [];\n for (let i = 1; i < timeline.segments.length; i += 1) {\n const prev = timeline.segments[i - 1];\n const cur = timeline.segments[i];\n if (!prev || !cur) {\n continue;\n }\n if (Math.abs(cur.startBeat - prev.endBeat) > EPS) {\n continue; // A rest separates the chords; no cadential motion across it.\n }\n const type = detectCadence(prev.chord, cur.chord, key);\n if (type !== null) {\n hits.push({ atBeat: cur.startBeat, type, from: prev.chord, to: cur.chord });\n }\n }\n return hits;\n}\n","/**\n * Shared helpers for the arrangement-analysis modules: pooling every track's\n * sounding notes and partitioning a track into monophonic sub-voices, plus the\n * small geometric primitives both the track-role and tension passes rely on.\n */\n\nimport type { NoteEvent } from '../../core/types.js';\nimport type { IdentifiedVoiceNote } from '../voice/index.js';\nimport type { ArrangementTrack, TrackRole } from './tracks.js';\n\n/** Float tolerance for beat boundary comparisons. */\nexport const EPS = 1e-9;\n\n/**\n * Largest interval, in semitones, a sub-voice will span between consecutive\n * notes. Beyond two octaves the connection is not a melodic step or leap but an\n * artefact of lane packing, so a new sub-voice is opened instead.\n */\nconst MAX_LANE_LEAP = 24;\n\n/** A note with the pitch that immediately preceded it in the same sub-voice. */\nexport type PreparedNote = {\n pitch: number;\n prevPitch?: number;\n startBeat: number;\n endBeat: number;\n};\n\n/** One monophonic sub-voice of a track: ordered notes plus sounding spans. */\nexport type PreparedVoice = {\n /** The sub-voice's notes in time order, each with a stable id. */\n voice: IdentifiedVoiceNote[];\n /** The same notes as sounding spans, each carrying its predecessor's pitch. */\n sounding: PreparedNote[];\n /**\n * The span sounding at a beat, or undefined. A sub-voice is monophonic by\n * construction, so at most one span covers any beat and the lookup is a\n * single binary search rather than a scan.\n */\n at: (beat: number) => PreparedNote | undefined;\n};\n\n/** A track prepared for analysis: resolved metadata plus monophonic sub-voices. */\nexport type PreparedTrack = {\n name: string;\n role: TrackRole;\n /** The track's index in the caller's input array. */\n trackIndex: number;\n /**\n * The track's notes partitioned into monophonic sub-voices. Voice analysis\n * assumes one note at a time, so a polyphonic track (e.g. a 'harmony' track\n * playing block chords) is split here; a monophonic track yields exactly one\n * sub-voice.\n */\n voices: PreparedVoice[];\n};\n\n/** Whether a sounding span `[start, end)` covers a beat. */\nexport function covers(note: PreparedNote, beat: number): boolean {\n return beat >= note.startBeat - EPS && beat < note.endBeat - EPS;\n}\n\n/** Whether a track's role means it carries no harmonic content. */\nexport function isPercussion(role: TrackRole | undefined): boolean {\n return role === 'drums';\n}\n\n/**\n * Every sounding note of every pitched track pooled into one flat list. Zero-\n * and negative-length notes never sound, so they are dropped here (matching the\n * chord-inference ingest in the timeline module), and percussion tracks are\n * skipped entirely: their pitches are instrument selections, not harmony.\n */\nexport function poolNotes(tracks: ArrangementTrack[], only?: ReadonlySet<number>): NoteEvent[] {\n const notes: NoteEvent[] = [];\n for (let index = 0; index < tracks.length; index += 1) {\n const track = tracks[index];\n if (track === undefined || isPercussion(track.role)) {\n continue;\n }\n if (only !== undefined && !only.has(index)) {\n continue;\n }\n for (const note of track.notes) {\n if (note.durationBeat > 0) {\n notes.push(note);\n }\n }\n }\n return notes;\n}\n\n/**\n * Split a track's ordered notes into monophonic sub-voices.\n *\n * Each note joins the free sub-voice — one whose last note has ended — whose\n * last pitch is nearest its own, so a track whose voices cross keeps each voice\n * on its own lane instead of handing a low note to whichever lane happened to\n * be created first. Ties go to the lane that ended latest, which is the one the\n * note actually follows. A connection wider than {@link MAX_LANE_LEAP} is not a\n * melodic one, so it opens a new sub-voice instead.\n *\n * The predecessor pitch this produces feeds leap and parallel detection, so a\n * mis-assigned lane invents voice-leading faults that are not in the music.\n */\nfunction splitIntoSubVoices(ordered: IdentifiedVoiceNote[]): IdentifiedVoiceNote[][] {\n const lanes: IdentifiedVoiceNote[][] = [];\n const attachNearest = (note: IdentifiedVoiceNote, excluded = new Set<number>()): boolean => {\n let best = -1;\n let bestDistance = Number.POSITIVE_INFINITY;\n let bestEnd = Number.NEGATIVE_INFINITY;\n for (let lane = 0; lane < lanes.length; lane += 1) {\n if (excluded.has(lane)) {\n continue;\n }\n const last = lanes[lane]?.[(lanes[lane]?.length ?? 0) - 1];\n if (last === undefined) {\n continue;\n }\n const end = last.startBeat + last.durationBeat;\n if (end > note.startBeat + EPS) {\n continue; // still sounding, so the lane is not free\n }\n const distance = Math.abs(last.pitch - note.pitch);\n if (distance > MAX_LANE_LEAP) {\n continue;\n }\n if (distance < bestDistance || (distance === bestDistance && end > bestEnd)) {\n best = lane;\n bestDistance = distance;\n bestEnd = end;\n }\n }\n if (best >= 0) {\n lanes[best]?.push(note);\n return true;\n }\n return false;\n };\n\n for (let start = 0; start < ordered.length; ) {\n const onset = ordered[start]?.startBeat ?? 0;\n let end = start + 1;\n while (end < ordered.length && Math.abs((ordered[end]?.startBeat ?? 0) - onset) <= EPS) {\n end += 1;\n }\n const block = ordered.slice(start, end);\n // Notes struck together form a vertical slice, not a sequence of greedy\n // nearest-neighbour decisions. Pair their low-to-high order with the\n // already-free lanes' low-to-high order so parallel block chords cannot\n // swap lanes and manufacture a crossing on the following beat.\n if (block.length > 1) {\n const freeLanes = lanes\n .map((lane, index) => ({ index, last: lane[lane.length - 1] }))\n .filter(\n (entry): entry is { index: number; last: IdentifiedVoiceNote } =>\n entry.last !== undefined &&\n entry.last.startBeat + entry.last.durationBeat <= onset + EPS,\n )\n .sort((a, b) => a.last.pitch - b.last.pitch);\n const paired = new Set<number>();\n for (let index = 0; index < block.length; index += 1) {\n const note = block[index];\n const lane = freeLanes[index];\n if (\n note !== undefined &&\n lane !== undefined &&\n Math.abs(lane.last.pitch - note.pitch) <= MAX_LANE_LEAP\n ) {\n lanes[lane.index]?.push(note);\n paired.add(lane.index);\n } else if (note !== undefined && !attachNearest(note, paired)) {\n lanes.push([note]);\n }\n }\n } else {\n const note = block[0];\n if (note !== undefined && !attachNearest(note)) {\n lanes.push([note]);\n }\n }\n start = end;\n }\n return lanes;\n}\n\n/** Index of the last span starting at or before a beat, or -1. */\nfunction lastStartingAtOrBefore(sounding: PreparedNote[], beat: number): number {\n let low = 0;\n let high = sounding.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((sounding[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low - 1;\n}\n\n/**\n * Resolve each track's metadata and derive its monophonic sub-voices.\n *\n * Notes are sorted by onset, then pitch, so ordering is deterministic, and ids\n * are assigned in that order; each note keeps the index it had in the caller's\n * array so annotations can be mapped back. Zero- and negative-length notes never\n * sound and are dropped at ingest. Each track is then partitioned into\n * monophonic sub-voices (see {@link splitIntoSubVoices}); the predecessor pitch\n * used for suspension and parallel detection is the previous note of the same\n * sub-voice, never a simultaneous chord member.\n */\nexport function prepareTracks(tracks: ArrangementTrack[]): PreparedTrack[] {\n let nextId = 0;\n return tracks.map((track, index) => {\n const ordered: IdentifiedVoiceNote[] = track.notes\n .map((note, originalIndex) => ({ note, originalIndex }))\n .filter(({ note }) => note.durationBeat > 0)\n .sort((a, b) => a.note.startBeat - b.note.startBeat || a.note.pitch - b.note.pitch)\n .map(({ note, originalIndex }) => ({ ...note, id: nextId++, originalIndex }));\n const voices: PreparedVoice[] = splitIntoSubVoices(ordered).map((voice) => {\n const sounding: PreparedNote[] = voice.map((note, i) => {\n const prev = i > 0 ? voice[i - 1] : undefined;\n const prevEnd = prev === undefined ? undefined : prev.startBeat + prev.durationBeat;\n const contiguous = prevEnd !== undefined && Math.abs(prevEnd - note.startBeat) <= EPS;\n return {\n pitch: note.pitch,\n prevPitch: contiguous ? prev?.pitch : undefined,\n startBeat: note.startBeat,\n endBeat: note.startBeat + note.durationBeat,\n };\n });\n return {\n voice,\n sounding,\n at: (beat: number) => {\n const candidate = sounding[lastStartingAtOrBefore(sounding, beat)];\n return candidate !== undefined && covers(candidate, beat) ? candidate : undefined;\n },\n };\n });\n return {\n name: track.name ?? `track ${index + 1}`,\n role: track.role ?? 'other',\n trackIndex: index,\n voices,\n };\n });\n}\n","/**\n * Arrangement tension analysis: a coarse harmonic-tension curve sampled at\n * regular beats across a whole multi-track piece, combining the sounding\n * chord's harmonic function, the dissonance of the sounding notes, and their\n * registral span into a single normalized reading.\n */\n\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertRange,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport { chordPitchClasses } from '../../theory/chord/index.js';\nimport { evaluateSafety, NoteSafety, type SafetyProfile } from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport { functionOf } from '../functional/index.js';\nimport { type ChordTimeline, chordTimelineFromNotes } from '../timeline/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\nimport type { ArrangementAnalysis, ArrangementOptions, ArrangementTrack } from './tracks.js';\n\n/**\n * A tension reading sampled at a beat.\n *\n * @category Arrangement & Analysis\n */\nexport type TensionPoint = {\n beat: number;\n /** Combined tension in [0, 1]. */\n tension: number;\n};\n\n/** Tension contributed by the sounding chord's harmonic function. */\nconst FUNCTION_TENSION: Record<'tonic' | 'subdominant' | 'dominant', number> = {\n tonic: 0,\n subdominant: 0.5,\n dominant: 1,\n};\n\n/** Weight of the harmonic-function term in the combined tension score. */\nconst FUNCTION_WEIGHT = 0.5;\n/** Weight of the non-chord-tone / dissonance term. */\nconst DISSONANCE_WEIGHT = 0.35;\n/** Weight of the registral-span term. */\nconst SPAN_WEIGHT = 0.15;\n/** Pitch span, in semitones, that saturates the span term. */\nconst SPAN_SATURATION = 24;\n\n/** Clamp a value into [0, 1]. */\nfunction clamp01(value: number): number {\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Sample the harmonic tension of an arrangement at regular beats.\n *\n * At each sample beat the tension combines three normalized terms:\n * - the sounding chord's harmonic function (dominant `1`, subdominant `0.5`,\n * tonic or no chord `0`), weighted `FUNCTION_WEIGHT`;\n * - the dissonance of the sounding notes — the larger of the share of sounding\n * pitches that are not chord tones and the share that {@link evaluateSafety}\n * rates {@link NoteSafety.Dissonant} — weighted `DISSONANCE_WEIGHT`;\n * - the registral span of the sounding notes, saturating at\n * `SPAN_SATURATION` semitones, weighted `SPAN_WEIGHT`.\n *\n * The weighted sum is clamped to [0, 1]. The harmony is inferred from all tracks\n * pooled together, so the result is deterministic and self-contained.\n *\n * @param tracks The tracks to sample.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @throws If `step` is not positive.\n * @example\n * ```ts\n * import { tensionCurve } from '@libraz/libcantus';\n * const notes = [{ pitch: 60, startBeat: 0, durationBeat: 4 }];\n * const curve = tensionCurve([{ notes }], { step: 1 });\n * curve; // one { beat, tension } sample per beat, in beat order\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurve(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n for (let index = 0; index < tracks.length; index += 1) {\n assertNoteEvents(tracks[index]?.notes ?? [], `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n }\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const step = opts.step ?? 1;\n assertRange(step, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, 'tension sampling step');\n\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n const totalBeats = poolNotes(tracks).reduce(\n (end, n) => Math.max(end, n.startBeat + n.durationBeat),\n 0,\n );\n const { timeline, key } =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled, { budget })[0]?.key ?? majorKey(0),\n };\n\n const prepared = prepareTracks(tracks);\n const points: TensionPoint[] = [];\n const sampleCount = Math.max(0, Math.ceil(totalBeats / step - EPS));\n assertGenerationBudget(sampleCount, 'tension samples', budget);\n // The per-sample cost is one lookup per sub-voice, so it is their product —\n // not either dimension alone — that has to stay inside the budget.\n const voiceCount = prepared.reduce((sum, track) => sum + track.voices.length, 0);\n assertGenerationBudget(\n sampleCount * Math.min(128, voiceCount) * voiceCount,\n 'tension sample-voice comparisons',\n budget,\n );\n for (let i = 0; i < sampleCount; i += 1) {\n const beat = i * step;\n points.push({ beat, tension: sampleTension(prepared, timeline, key, ts, profile, beat) });\n }\n return points;\n}\n\n/**\n * Sample the tension of an arrangement already analysed by\n * {@link analyzeArrangement}, reusing its harmony instead of inferring it again.\n *\n * `analyzeArrangement` followed by `tensionCurve` runs chord and key inference\n * twice over the same notes. This takes the analysis it already produced, so\n * the curve and the annotations describe the same harmony by construction.\n *\n * @param tracks The same tracks that were analysed.\n * @param analysis The result of {@link analyzeArrangement} for those tracks.\n * @param opts Analysis options plus an optional `step` (default one beat).\n * @returns One {@link TensionPoint} per sampled beat, in beat order.\n * @example\n * ```ts\n * import { analyzeArrangement, tensionCurveFrom } from '@libraz/libcantus';\n * const tracks = [{ notes: [{ pitch: 60, startBeat: 0, durationBeat: 4 }] }];\n * const analysis = analyzeArrangement(tracks);\n * tensionCurveFrom(tracks, analysis, { step: 1 });\n * ```\n * @category Arrangement & Analysis\n */\nexport function tensionCurveFrom(\n tracks: ArrangementTrack[],\n analysis: ArrangementAnalysis,\n opts: ArrangementOptions & { step?: number } = {},\n): TensionPoint[] {\n return tensionCurve(tracks, { ...opts, timeline: analysis.timeline, key: analysis.key });\n}\n\n/**\n * All sounding pitched notes across every track at a beat, tagged with their\n * track. Percussion carries no harmony, so it is skipped; each sub-voice is\n * monophonic, so its contribution is one binary search rather than a scan.\n */\nfunction allSounding(prepared: PreparedTrack[], beat: number): { pitch: number; track: number }[] {\n const out: { pitch: number; track: number }[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (const subVoice of track.voices) {\n const note = subVoice.at(beat);\n if (note !== undefined) {\n out.push({ pitch: note.pitch, track: t });\n }\n }\n }\n return out;\n}\n\n/** Combine the harmonic, dissonance, and span terms at one sample beat. */\nfunction sampleTension(\n prepared: PreparedTrack[],\n timeline: ChordTimeline,\n key: KeyScale,\n ts: TimeSignature,\n profile: SafetyProfile,\n beat: number,\n): number {\n const sounding = allSounding(prepared, beat);\n if (sounding.length === 0) {\n return 0;\n }\n const chord = timeline.at(beat);\n const functionScore = chord ? FUNCTION_TENSION[functionOf(chord, key)] : 0;\n\n const chordPcs = chord ? new Set(chordPitchClasses(chord)) : null;\n const strongBeat = isStrongBeat(beat, ts);\n let nonChord = 0;\n let dissonant = 0;\n // A coarse tension sample supplies no individual voice history. Every voice\n // at the same MIDI pitch consequently receives the same safety verdict, so\n // a dense pad needs at most 128 evaluations instead of one per voice.\n const pitches = new Map<number, number>();\n for (const voice of sounding) {\n pitches.set(voice.pitch, (pitches.get(voice.pitch) ?? 0) + 1);\n }\n for (const [pitch, count] of pitches) {\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(pitch))) {\n nonChord += count;\n }\n const others = [...pitches].flatMap(([otherPitch, otherCount]) =>\n Array.from({ length: otherCount - (otherPitch === pitch ? 1 : 0) }, () => ({\n pitch: otherPitch,\n })),\n );\n // Only the verdict is read, so the replacement-pitch search is skipped.\n const result = evaluateSafety(\n {\n profile,\n candidatePitch: pitch,\n chord,\n key,\n otherVoices: others,\n strongBeat,\n },\n { suggestions: false },\n );\n if (result.safety === NoteSafety.Dissonant) {\n dissonant += count;\n }\n }\n const dissonanceScore = Math.max(nonChord, dissonant) / sounding.length;\n\n let low = Number.POSITIVE_INFINITY;\n let high = Number.NEGATIVE_INFINITY;\n for (const voice of sounding) {\n low = Math.min(low, voice.pitch);\n high = Math.max(high, voice.pitch);\n }\n const spanScore = Math.min(1, (high - low) / SPAN_SATURATION);\n\n return clamp01(\n FUNCTION_WEIGHT * functionScore + DISSONANCE_WEIGHT * dissonanceScore + SPAN_WEIGHT * spanScore,\n );\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport { assertNoteEvents } from '../../core/validation/index.js';\nimport type { Chord, ChordToneRole } from '../../theory/chord/index.js';\nimport { chordToneRole, intervalAboveRoot, isChordMember } from '../../theory/chord/index.js';\nimport {\n createsVerticalDissonance,\n isLeadingToneResolution,\n} from '../../theory/counterpoint/index.js';\nimport type { VoiceSnapshot } from '../../theory/safety/index.js';\n\n/**\n * A suspension figure, named by the interval above the bass and the interval it\n * resolves to.\n *\n * @category Arrangement & Analysis\n */\nexport type SuspensionFigure = 'sus4-3' | 'sus6-5' | 'sus7-6' | 'sus9-8' | 'sus2-3';\n\n/**\n * A theory annotation attached to a note.\n *\n * @category Arrangement & Analysis\n */\nexport type TheoryLabel =\n | { kind: 'chordTone'; role: ChordToneRole }\n | { kind: 'tension'; degree: 9 | 11 | 13 }\n | { kind: 'avoid' }\n | { kind: 'passing' }\n | { kind: 'neighbor' }\n | { kind: 'suspension'; type: SuspensionFigure; resolveTo: number }\n | { kind: 'anticipation' }\n | { kind: 'escape' }\n | { kind: 'needsResolution'; resolveTo: number }\n | { kind: 'leadingTone'; resolveTo: number };\n\n/**\n * A note with its theory labels and a short rationale.\n *\n * The labels describe the note against the chord sounding at its **onset**;\n * they are not re-derived for the later chords a long note sustains across.\n * `analyzeArrangement` does re-check every crossed chord change, and reports\n * what it finds as conflicts, so a note labelled a chord tone here can still\n * appear in `conflicts` at a later beat.\n *\n * The identity fields make an annotation mappable back to the caller's input:\n * `originalIndex` is the note's position in the array that was passed in, and\n * `trackIndex` is set by {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type AnalyzedNote = {\n noteId: number;\n /** The note's index in the caller's own array, when the caller supplied one. */\n originalIndex?: number;\n /** The note's track, set by `analyzeArrangement`. */\n trackIndex?: number;\n pitch: number;\n startBeat: number;\n durationBeat: number;\n labels: TheoryLabel[];\n rationale?: string;\n};\n\n/**\n * A single note in a monophonic voice: a {@link NoteEvent} with a stable id and,\n * optionally, its index in the caller's original array.\n *\n * @category Arrangement & Analysis\n */\nexport type VoiceNote = NoteEvent & { id?: number; originalIndex?: number };\n\n/**\n * A voice note whose id has been assigned, as {@link toVoiceNotes} returns and\n * {@link analyzeArrangement} works in.\n *\n * @category Arrangement & Analysis\n */\nexport type IdentifiedVoiceNote = VoiceNote & { id: number };\n\n/**\n * Give plain note events the ids {@link analyzeVoice} reports back.\n *\n * The id is only an identity handle, so the array position serves; this exists\n * so the mapping is a named call rather than a `map` every caller writes.\n *\n * @param events The note events, in time order.\n * @returns The same notes as voice notes, each carrying its index as its id.\n * @category Arrangement & Analysis\n */\nexport function toVoiceNotes(events: readonly NoteEvent[]): IdentifiedVoiceNote[] {\n return events.map((event, index) => ({ ...event, id: index, originalIndex: index }));\n}\n\n/** Maximum gap/overlap treated as adjacent after MIDI humanization. */\nconst HUMANIZE_ADJACENCY = 0.05;\n\n/**\n * Interval class of a pitch above the actual sounding bass.\n *\n * Suspension figures (4-3, 7-6, 9-8) are named for the interval above the bass,\n * not the chord root, so this prefers the lowest pitch among the other sounding\n * voices when one lies below the note. Without a lower sounding voice it falls\n * back to the chord's bass pitch class (or root in root position).\n */\nfunction intervalAboveBass(pitch: number, others: VoiceSnapshot[], chord: Chord): number {\n let bass = Number.POSITIVE_INFINITY;\n for (const ov of others) {\n bass = Math.min(bass, ov.pitch);\n }\n if (Number.isFinite(bass) && bass < pitch) {\n return (((pitch - bass) % 12) + 12) % 12;\n }\n const ref = chord.bassPc ?? chord.rootPc;\n return (((pitchClass(pitch) - pitchClass(ref)) % 12) + 12) % 12;\n}\n\n/**\n * Map an interval class above the root to its extended-tension scale degree:\n * a flat/natural ninth (1 or 2) to 9, an eleventh (5 or 6) to 11, and a\n * thirteenth (8 or 9) to 13.\n */\nfunction tensionDegree(ic: number): 9 | 11 | 13 {\n if (ic === 1 || ic === 2) {\n return 9;\n }\n if (ic === 5 || ic === 6) {\n return 11;\n }\n return 13;\n}\n\nfunction isStep(a: number, b: number): boolean {\n const d = Math.abs(a - b);\n return d === 1 || d === 2;\n}\n\n/**\n * Classify a suspension figure from the interval class above the sounding bass\n * (`ic`) and the resolution direction (`delta`, positive when resolving upward).\n */\nfunction suspensionType(ic: number, delta: number): SuspensionFigure | null {\n if (delta > 0) {\n return 'sus2-3';\n }\n if (ic === 2) {\n return 'sus9-8';\n }\n if (ic === 10 || ic === 11) {\n return 'sus7-6';\n }\n if (ic === 5 || ic === 6) {\n return 'sus4-3';\n }\n if (ic === 8 || ic === 9) {\n return 'sus6-5';\n }\n // Any other interval above the bass is a downward-resolving dissonance with\n // no standard figure; a catch-all `sus4-3` would name it after an interval it\n // does not form.\n return null;\n}\n\nfunction stepResolution(pitch: number, chord: Chord): number | undefined {\n for (let delta = 1; delta <= 2; delta += 1) {\n if (isChordMember(pitch - delta, chord)) {\n return pitch - delta;\n }\n }\n return undefined;\n}\n\n/**\n * Label every note of a voice with its theory roles.\n *\n * Each note is classified against the chord sounding at its beat. Chord tones\n * get a role label; non-chord tones are matched, in order, as suspensions\n * (prepared by an identical consonant pitch and resolving by step), passing\n * tones, neighbors, anticipations, and escape tones, then fall back to tension,\n * avoid, or an unresolved-dissonance label. Leading-tone resolutions are noted\n * additionally.\n *\n * The voice is expected to be monophonic (one note at a time). Notes sharing an\n * onset are treated as simultaneous cluster members, not melodic neighbors, so\n * they receive no melodic labels (suspension, passing, neighbor, anticipation,\n * escape) — only harmonic ones. Callers with truly polyphonic material should\n * split it into monophonic sub-voices first, as `analyzeArrangement` does.\n *\n * @param voice The monophonic voice, in time order. A note without an `id` is\n * identified by its position, so plain note events can be passed straight in.\n * @param chordAtBeat Chord sounding at a given beat, or null.\n * @param key Key context for leading-tone detection.\n * @param otherVoicesAtBeat Other sounding voices at a given beat; defaults to\n * none, which is the whole story for a solo line.\n * @returns One annotation per input note.\n * @example\n * ```ts\n * import { analyzeVoice, makeChord, majorKey } from '@libraz/libcantus';\n * const voice = [\n * { pitch: 60, startBeat: 0, durationBeat: 1 },\n * { pitch: 62, startBeat: 1, durationBeat: 1 },\n * { pitch: 64, startBeat: 2, durationBeat: 1 },\n * ];\n * const cMajor = makeChord(0, 'maj');\n * const labels = analyzeVoice(voice, () => cMajor, majorKey(0));\n * labels; // one AnalyzedNote per input note, in the same order\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeVoice(\n voice: readonly VoiceNote[],\n chordAtBeat: (beat: number) => Chord | null,\n key: KeyScale,\n otherVoicesAtBeat: (beat: number) => VoiceSnapshot[] = () => [],\n): AnalyzedNote[] {\n assertNoteEvents(voice, 'voice notes', { allowNonPositiveDuration: true });\n const result: AnalyzedNote[] = [];\n\n for (let i = 0; i < voice.length; i += 1) {\n const note = voice[i];\n if (!note) {\n continue;\n }\n const prevNote = i > 0 ? voice[i - 1] : undefined;\n const nextNote = i + 1 < voice.length ? voice[i + 1] : undefined;\n // A neighboring note sharing this note's onset is a simultaneous cluster\n // member, not a melodic predecessor/successor; treating it as one would\n // fabricate passing/suspension figures, so it is dropped here.\n const prevEnd =\n prevNote === undefined\n ? Number.NEGATIVE_INFINITY\n : prevNote.startBeat + prevNote.durationBeat;\n const noteEnd = note.startBeat + note.durationBeat;\n const prev =\n prevNote !== undefined && Math.abs(prevEnd - note.startBeat) <= HUMANIZE_ADJACENCY\n ? prevNote\n : undefined;\n const next =\n nextNote !== undefined && Math.abs(noteEnd - nextNote.startBeat) <= HUMANIZE_ADJACENCY\n ? nextNote\n : undefined;\n const chord = chordAtBeat(note.startBeat);\n const labels: TheoryLabel[] = [];\n const member = isChordMember(note.pitch, chord);\n let handled = false;\n\n if (chord && member) {\n const role = chordToneRole(note.pitch, chord);\n if (role) {\n labels.push({ kind: 'chordTone', role });\n } else {\n const ic = intervalAboveRoot(note.pitch, chord);\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n }\n handled = true;\n }\n\n if (!handled && chord && prev && next) {\n const prevChord = chordAtBeat(prev.startBeat);\n const prepared = prev.pitch === note.pitch && isChordMember(prev.pitch, prevChord);\n const resolves = isStep(next.pitch, note.pitch);\n const others = otherVoicesAtBeat(note.startBeat);\n const verticallyDissonant = others.some((ov) =>\n createsVerticalDissonance(note.pitch, ov.pitch, true),\n );\n if (prepared && verticallyDissonant && resolves) {\n const ic = intervalAboveBass(note.pitch, others, chord);\n const type = suspensionType(ic, next.pitch - note.pitch);\n if (type !== null) {\n labels.push({ kind: 'suspension', type, resolveTo: next.pitch });\n handled = true;\n }\n }\n }\n\n if (!handled && chord && prev && next) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const up = note.pitch - prev.pitch > 0;\n const contInto = next.pitch - note.pitch > 0;\n const sameDir = note.pitch !== prev.pitch && next.pitch !== note.pitch && up === contInto;\n if (\n prevMember &&\n nextMember &&\n !member &&\n isStep(note.pitch, prev.pitch) &&\n isStep(next.pitch, note.pitch) &&\n sameDir\n ) {\n labels.push({ kind: 'passing' });\n handled = true;\n }\n }\n\n if (\n !handled &&\n chord &&\n prev &&\n next &&\n !member &&\n prev.pitch === next.pitch &&\n isChordMember(prev.pitch, chordAtBeat(prev.startBeat)) &&\n isStep(note.pitch, prev.pitch)\n ) {\n labels.push({ kind: 'neighbor' });\n handled = true;\n }\n\n if (!handled && chord && next && !member && note.pitch === next.pitch) {\n const nextChord = chordAtBeat(next.startBeat);\n if (isChordMember(next.pitch, nextChord)) {\n labels.push({ kind: 'anticipation' });\n handled = true;\n }\n }\n\n if (!handled && chord && prev && next && !member) {\n const prevMember = isChordMember(prev.pitch, chordAtBeat(prev.startBeat));\n const nextMember = isChordMember(next.pitch, chordAtBeat(next.startBeat));\n const stepFromPrev = isStep(note.pitch, prev.pitch);\n const leapToNext = Math.abs(next.pitch - note.pitch) >= 3;\n const opposite = note.pitch - prev.pitch > 0 !== next.pitch - note.pitch > 0;\n if (prevMember && nextMember && stepFromPrev && leapToNext && opposite) {\n labels.push({ kind: 'escape' });\n handled = true;\n }\n }\n\n if (!handled && chord && !member) {\n const ic = intervalAboveRoot(note.pitch, chord);\n const isTension = ic === 2 || ic === 5 || ic === 9;\n const avoid =\n (ic === 5 && chord.intervals.includes(4) && !chord.intervals.includes(5)) ||\n (ic === 11 && chord.intervals.includes(4) && chord.intervals.includes(10));\n if (avoid) {\n labels.push({ kind: 'avoid' });\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n } else if (isTension) {\n labels.push({ kind: 'tension', degree: tensionDegree(ic) });\n } else {\n const resolveTo = stepResolution(note.pitch, chord);\n if (resolveTo !== undefined) {\n labels.push({ kind: 'needsResolution', resolveTo });\n }\n }\n }\n\n if (next && isLeadingToneResolution(note.pitch, next.pitch, key)) {\n labels.push({ kind: 'leadingTone', resolveTo: next.pitch });\n }\n\n const analyzed: AnalyzedNote = {\n noteId: note.id ?? i,\n pitch: note.pitch,\n startBeat: note.startBeat,\n durationBeat: note.durationBeat,\n labels,\n rationale: describe(labels),\n };\n if (note.originalIndex !== undefined) {\n analyzed.originalIndex = note.originalIndex;\n }\n result.push(analyzed);\n }\n\n return result;\n}\n\n/** Build a short rationale from a note's primary label. */\nfunction describe(labels: TheoryLabel[]): string {\n const primary = labels[0];\n if (!primary) {\n return 'Unclassified note';\n }\n switch (primary.kind) {\n case 'chordTone':\n return `Chord ${primary.role}`;\n case 'tension':\n return `${primary.degree}th tension`;\n case 'avoid':\n return 'Avoid note';\n case 'passing':\n return 'Passing tone between chord tones';\n case 'neighbor':\n return 'Neighbor tone';\n case 'suspension':\n return `Suspension (${primary.type})`;\n case 'anticipation':\n return 'Anticipation of the next chord';\n case 'escape':\n return 'Escape tone';\n case 'needsResolution':\n return 'Unresolved dissonance';\n case 'leadingTone':\n return 'Leading tone resolving to the tonic';\n }\n}\n","/**\n * Arrangement track-role analysis: a single harmony is inferred from every\n * track's notes pooled together, each track is then analysed against that shared\n * harmony, and notes that clash with the sounding chord are collected as\n * conflicts.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport { isStrongBeat, parseTimeSignature, type TimeSignature } from '../../core/meter/index.js';\nimport type { KeyScale, NoteEvent } from '../../core/types.js';\nimport {\n assertGenerationBudget,\n assertNoteEvents,\n assertTimeSignature,\n} from '../../core/validation/index.js';\nimport {\n evaluateSafety,\n NoteSafety,\n type SafetyProfile,\n type VoiceSnapshot,\n} from '../../theory/safety/index.js';\nimport { majorKey } from '../../theory/scale/index.js';\nimport { detectKeyFromNotes } from '../detect/index.js';\nimport {\n type CadenceHit,\n type ChordTimeline,\n chordTimelineFromNotes,\n detectCadences,\n} from '../timeline/index.js';\nimport {\n type AnalyzedNote,\n analyzeVoice,\n type TheoryLabel,\n type VoiceNote,\n} from '../voice/index.js';\nimport { EPS, isPercussion, type PreparedTrack, poolNotes, prepareTracks } from './internal.js';\n\n/**\n * The musical role a track plays in the arrangement.\n *\n * Only `drums` changes the analysis: its pitches select instruments rather than\n * naming harmony, so such a track is excluded from chord and key inference and\n * from every voice-leading comparison, and is reported back with no\n * annotations. `harmony` is meaningful through\n * {@link ArrangementOptions.harmonyTracks}, which names the tracks the chords\n * are inferred from. The rest are labels carried through to\n * {@link TrackAnalysis.role} for the caller's own use; nothing is inferred, so\n * a track left unlabelled reports `'other'` rather than a guess.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackRole = 'melody' | 'harmony' | 'bass' | 'drums' | 'other';\n\n/**\n * One input track: its notes plus optional name and role.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementTrack = {\n name?: string;\n role?: TrackRole;\n notes: readonly NoteEvent[];\n};\n\n/**\n * A track after analysis: its resolved name, role, and per-note annotations.\n *\n * @category Arrangement & Analysis\n */\nexport type TrackAnalysis = {\n name: string;\n role: TrackRole;\n /** One {@link AnalyzedNote} per note, labelled against the inferred chords. */\n notes: AnalyzedNote[];\n};\n\n/**\n * A note that clashes with the harmony sounding beneath it.\n *\n * @category Arrangement & Analysis\n */\nexport type Conflict = {\n beat: number;\n trackName: string;\n /** The clashing track's index in the caller's input array. */\n trackIndex: number;\n /** The note's index in that track's own note array. */\n originalIndex?: number;\n /** The id the note carries in {@link TrackAnalysis.notes}. */\n noteId: number;\n pitch: number;\n /**\n * The note's theory labels at its onset, so a caller can tell a genuine clash\n * from a passing tone, a neighbour, or a prepared suspension — all of which\n * are ordinary non-chord tones that still evaluate as unsafe.\n */\n labels: TheoryLabel[];\n /** The clash severity: {@link NoteSafety.Warning} or {@link NoteSafety.Dissonant}. */\n safety: NoteSafety;\n /** {@link SafetyResult.reasons} bitmask. */\n reasons: number;\n /** Preferred stepwise resolution, when the safety evaluator provides one. */\n resolveTo?: number;\n /** Nearby fully safe replacement pitches, nearest first. */\n suggestions?: number[];\n rationale?: string;\n};\n\n/**\n * The full result of {@link analyzeArrangement}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementAnalysis = {\n key: KeyScale;\n timeline: ChordTimeline;\n segmentConfidence: number[];\n cadences: CadenceHit[];\n tracks: TrackAnalysis[];\n /** Notes clashing with the sounding harmony, worst severity first. */\n conflicts: Conflict[];\n};\n\n/**\n * Options controlling {@link analyzeArrangement} and {@link tensionCurve}.\n *\n * @category Arrangement & Analysis\n */\nexport type ArrangementOptions = {\n /** Key context; inferred from the pooled notes when omitted. */\n key?: KeyScale;\n /**\n * A chord timeline to analyse against, instead of inferring one from the\n * notes. Supply it to analyse a hand-written progression, or to reuse the\n * timeline of an earlier call rather than re-running chord inference.\n */\n timeline?: ChordTimeline;\n /**\n * Indices of the tracks the harmony is inferred from. Defaults to every\n * pitched track. Percussion tracks are excluded either way.\n */\n harmonyTracks?: number[];\n /**\n * Lowest severity reported in `conflicts`.\n *\n * @defaultValue {@link NoteSafety.Warning}\n */\n minSeverity?: NoteSafety;\n /**\n * Time signature; defaults to 4/4.\n *\n * @defaultValue `4/4`\n */\n ts?: TimeSignature;\n /**\n * Chord-slot length in beats; defaults to one bar of `ts`.\n *\n * @defaultValue one bar of `ts`\n */\n harmonicRhythm?: number;\n /**\n * Safety profile used for conflict detection; defaults to `pop`.\n *\n * @defaultValue `'pop'`\n */\n profile?: SafetyProfile;\n /**\n * Upper bound on the work this call may do — note counts, windows, and\n * candidate counts are each checked against it before anything is allocated.\n *\n * Raise it to analyse a piece larger than the default allows; the default is\n * {@link DEFAULT_GENERATION_BUDGET}, chosen so a runaway input fails fast\n * rather than blocking the thread.\n *\n * @defaultValue {@link DEFAULT_GENERATION_BUDGET}\n */\n budget?: number;\n};\n\n/**\n * Sounding pitches of every sub-voice at a beat, excluding one sub-voice.\n *\n * Only the sub-voice under analysis is excluded — sibling sub-voices of the\n * same track are included, so dissonant clusters inside a single polyphonic\n * track are still detected.\n */\ntype SoundingVoice = {\n track: number;\n voice: number;\n snapshot: VoiceSnapshot;\n};\n\n/** Build one complete arrangement snapshot for an evaluation beat. */\nfunction soundingVoicesAt(prepared: PreparedTrack[], beat: number): SoundingVoice[] {\n const out: SoundingVoice[] = [];\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track || isPercussion(track.role)) {\n continue;\n }\n for (let v = 0; v < track.voices.length; v += 1) {\n // Each sub-voice is monophonic, so this is one binary search per voice\n // rather than a scan of every note in the arrangement.\n const note = track.voices[v]?.at(beat);\n if (note === undefined) {\n continue;\n }\n const snap: VoiceSnapshot = { pitch: note.pitch };\n // Motion reasons compare one real transition shared by both voices.\n // A voice attacking exactly here contributes its adjacent predecessor;\n // a sustained voice contributes the same pitch (oblique motion).\n const previous = Math.abs(note.startBeat - beat) <= EPS ? note.prevPitch : note.pitch;\n if (previous !== undefined) {\n snap.prevPitch = previous;\n }\n out.push({ track: t, voice: v, snapshot: snap });\n }\n }\n return out;\n}\n\n/** Get every simultaneous voice except the one being evaluated. */\nfunction otherVoicesSounding(\n prepared: PreparedTrack[],\n excludeTrack: number,\n excludeVoice: number,\n beat: number,\n cache: Map<number, SoundingVoice[]>,\n): VoiceSnapshot[] {\n const sounding = cache.get(beat) ?? soundingVoicesAt(prepared, beat);\n cache.set(beat, sounding);\n return sounding\n .filter(({ track, voice }) => track !== excludeTrack || voice !== excludeVoice)\n .map(({ snapshot }) => snapshot);\n}\n\n/** Index of the first segment starting strictly after a beat. */\nfunction firstSegmentAfter(timeline: ChordTimeline, beat: number): number {\n let low = 0;\n let high = timeline.segments.length;\n while (low < high) {\n const middle = Math.floor((low + high) / 2);\n if ((timeline.segments[middle]?.startBeat ?? Number.POSITIVE_INFINITY) <= beat + EPS) {\n low = middle + 1;\n } else {\n high = middle;\n }\n }\n return low;\n}\n\n/**\n * The beats at which a note must be checked against the harmony: its onset,\n * plus the onset of every later chord segment the note sustains across. Each\n * crossed segment is visited exactly once (at its own start), so a boundary\n * beat is never evaluated twice for the same note.\n *\n * Segments are in beat order, so the crossed ones are a contiguous run found by\n * binary search rather than by scanning the whole timeline for every note.\n */\nfunction evaluationBeats(note: VoiceNote, timeline: ChordTimeline): number[] {\n const noteEnd = note.startBeat + note.durationBeat;\n const beats = [note.startBeat];\n for (let i = firstSegmentAfter(timeline, note.startBeat); i < timeline.segments.length; i += 1) {\n const start = timeline.segments[i]?.startBeat;\n if (start === undefined || start >= noteEnd - EPS) {\n break;\n }\n beats.push(start);\n }\n return beats;\n}\n\n/**\n * Analyse a whole arrangement against a single inferred harmony.\n *\n * The chord timeline and key are inferred from the pooled notes of every\n * pitched track (see {@link chordTimelineFromNotes}); pooling all voices is\n * robust even when roles are absent or a track doubles the harmony, so it is\n * preferred over deriving the harmony from a subset. A track marked\n * `role: 'drums'` is excluded — its pitches name instruments, not harmony, and\n * pooling them would corrupt the key, the timeline and every reading built on\n * them. Pass `harmonyTracks` to restrict inference further, or `timeline` to\n * analyse against a progression you already have. Zero- and negative-length\n * notes never sound, so they are dropped at ingest and appear in neither the\n * annotations nor the conflicts.\n *\n * Voice analysis assumes one monophonic voice at a time, so each track is\n * first partitioned into monophonic sub-voices (a polyphonic block-chord track\n * fans out into one sub-voice per simultaneous note; a monophonic track is\n * unaffected). Each sub-voice is labelled note-by-note against the harmony\n * with {@link analyzeVoice}, seeing every other sub-voice — including siblings\n * within the same track — as its accompaniment. Every note is then re-checked\n * with {@link evaluateSafety} at its onset and again at each chord change it\n * sustains across, so a held note that clashes with a later chord is caught:\n * evaluations that are not {@link NoteSafety.Safe} become conflicts, one per\n * clashing beat, sorted worst severity first and then by beat.\n *\n * Every ordinary non-chord tone — a passing note, a neighbour, a prepared\n * suspension — is by definition unsafe against the chord under it, so the\n * conflict list is a report, not a fault list. Each conflict carries the note's\n * `labels` and its `trackIndex`/`originalIndex`, so a caller can tell those\n * apart and map a conflict back to the note it passed in; `minSeverity` narrows\n * the list to outright dissonance.\n *\n * @param tracks The tracks to analyse.\n * @param opts Analysis options; see {@link ArrangementOptions}.\n * @returns The inferred harmony, per-track annotations, cadences, and conflicts.\n * @example\n * ```ts\n * import { analyzeArrangement } from '@libraz/libcantus';\n * const melody = [\n * { pitch: 60, startBeat: 0, durationBeat: 2 },\n * { pitch: 67, startBeat: 2, durationBeat: 2 },\n * ];\n * const { key, conflicts } = analyzeArrangement([{ role: 'melody', notes: melody }]);\n * conflicts; // notes clashing with the inferred harmony, worst severity first\n * ```\n * @category Arrangement & Analysis\n */\nexport function analyzeArrangement(\n tracks: ArrangementTrack[],\n opts: ArrangementOptions = {},\n): ArrangementAnalysis {\n const ts = opts.ts ?? parseTimeSignature('4/4');\n assertTimeSignature(ts);\n const budget = opts.budget;\n assertGenerationBudget(tracks.length, 'arrangement tracks', budget);\n let noteCount = 0;\n for (let index = 0; index < tracks.length; index += 1) {\n const notes = tracks[index]?.notes;\n // A missing array is rejected by name here rather than becoming a raw\n // TypeError further down, where the message no longer says which track.\n if (!Array.isArray(notes)) {\n throw new InvalidInputError(\n `tracks[${index}].notes must be an array; received ${typeof notes}`,\n );\n }\n assertNoteEvents(notes, `tracks[${index}].notes`, {\n allowNonPositiveDuration: true,\n budget,\n });\n noteCount += notes.length;\n }\n // The pooled total is what the downstream timeline analysis actually sizes\n // its work by, so it is checked here, under this layer's own name.\n assertGenerationBudget(noteCount, 'arrangement notes', budget);\n const profile: SafetyProfile = opts.profile ?? 'pop';\n const minSeverity = opts.minSeverity ?? NoteSafety.Warning;\n const harmonyTracks =\n opts.harmonyTracks === undefined ? undefined : new Set(opts.harmonyTracks.map(Math.trunc));\n const pooled = poolNotes(tracks, harmonyTracks);\n\n const inferred =\n opts.timeline === undefined\n ? chordTimelineFromNotes(pooled, {\n key: opts.key,\n ts,\n harmonicRhythm: opts.harmonicRhythm,\n budget,\n })\n : {\n timeline: opts.timeline,\n key: opts.key ?? detectKeyFromNotes(pooled, { budget })[0]?.key ?? majorKey(0),\n // A caller-provided timeline was not inferred from these notes, so\n // it has no measured confidence. Reporting 1 would turn confidence\n // gates into unconditional passes.\n segmentConfidence: opts.timeline.segments.map(() => 0),\n };\n const { timeline, key, segmentConfidence } = inferred;\n const cadences = detectCadences(timeline, key);\n const prepared = prepareTracks(tracks);\n const soundingCache = new Map<number, SoundingVoice[]>();\n\n const trackAnalyses: TrackAnalysis[] = [];\n const conflicts: Conflict[] = [];\n\n for (let t = 0; t < prepared.length; t += 1) {\n const track = prepared[t];\n if (!track) {\n continue;\n }\n if (isPercussion(track.role)) {\n // A drum hit has no harmonic reading, so labelling it against the chord\n // would produce noise rather than analysis.\n trackAnalyses.push({ name: track.name, role: track.role, notes: [] });\n continue;\n }\n const notes: AnalyzedNote[] = [];\n for (let v = 0; v < track.voices.length; v += 1) {\n const subVoice = track.voices[v];\n if (!subVoice) {\n continue;\n }\n const analyzed = analyzeVoice(subVoice.voice, timeline.at, key, (beat) =>\n otherVoicesSounding(prepared, t, v, beat, soundingCache),\n );\n notes.push(...analyzed.map((note) => ({ ...note, trackIndex: track.trackIndex })));\n const labelsById = new Map(analyzed.map((note) => [note.noteId, note.labels]));\n\n for (let noteIndex = 0; noteIndex < subVoice.voice.length; noteIndex += 1) {\n const note = subVoice.voice[noteIndex];\n const preparedNote = subVoice.sounding[noteIndex];\n if (note === undefined || preparedNote === undefined) {\n continue;\n }\n for (const beat of evaluationBeats(note, timeline)) {\n const atOnset = Math.abs(beat - note.startBeat) <= EPS;\n const safetyQuery = {\n profile,\n candidatePitch: note.pitch,\n prevPitch: atOnset ? preparedNote.prevPitch : note.pitch,\n chord: timeline.at(beat),\n key,\n otherVoices: otherVoicesSounding(prepared, t, v, beat, soundingCache),\n strongBeat: isStrongBeat(beat, ts),\n };\n // Most evaluations become no reportable conflict. Avoid their\n // replacement-pitch scan (up to 24 recursive safety checks), then\n // collect suggestions only for the conflicts we will retain.\n let result = evaluateSafety(safetyQuery, { suggestions: false });\n if (result.safety !== NoteSafety.Safe && result.safety >= minSeverity) {\n result = evaluateSafety(safetyQuery);\n const conflict: Conflict = {\n beat,\n trackName: track.name,\n trackIndex: track.trackIndex,\n noteId: note.id,\n pitch: note.pitch,\n labels: labelsById.get(note.id) ?? [],\n safety: result.safety,\n reasons: result.reasons,\n };\n if (note.originalIndex !== undefined) {\n conflict.originalIndex = note.originalIndex;\n }\n if (result.rationale !== undefined) {\n conflict.rationale = result.rationale;\n }\n if (result.resolveTo !== undefined) {\n conflict.resolveTo = result.resolveTo;\n }\n if (result.suggestions !== undefined) {\n conflict.suggestions = [...result.suggestions];\n }\n conflicts.push(conflict);\n }\n }\n }\n }\n // Sub-voice results interleave; restore the track's onset-then-pitch order.\n notes.sort((a, b) => a.noteId - b.noteId);\n trackAnalyses.push({ name: track.name, role: track.role, notes });\n }\n\n conflicts.sort((a, b) => b.safety - a.safety || a.beat - b.beat);\n\n return { key, timeline, segmentConfidence, cadences, tracks: trackAnalyses, conflicts };\n}\n"],"mappings":"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","names":["EPS","EPS","EPS"]}
|
package/dist/chunk-ZGLJM4A4.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InvalidInputError,
|
|
3
|
-
assertFiniteNumber,
|
|
4
|
-
assertInteger,
|
|
5
|
-
assertRange
|
|
6
|
-
} from "./chunk-R2VVFAIN.js";
|
|
7
|
-
|
|
8
|
-
// src/core/random/index.ts
|
|
9
|
-
function createRng(seed) {
|
|
10
|
-
assertInteger(seed, "seed", 0, 4294967295);
|
|
11
|
-
let state = seed >>> 0;
|
|
12
|
-
const next = () => {
|
|
13
|
-
state = state + 1831565813 >>> 0;
|
|
14
|
-
let t = state;
|
|
15
|
-
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
16
|
-
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
17
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
18
|
-
};
|
|
19
|
-
return {
|
|
20
|
-
next,
|
|
21
|
-
prob: (p) => {
|
|
22
|
-
assertRange(p, 0, 1, "probability");
|
|
23
|
-
return next() < p;
|
|
24
|
-
},
|
|
25
|
-
range: (lo, hi) => {
|
|
26
|
-
assertInteger(lo, "range lower bound");
|
|
27
|
-
assertInteger(hi, "range upper bound");
|
|
28
|
-
if (lo > hi) {
|
|
29
|
-
throw new InvalidInputError(
|
|
30
|
-
`range lower bound must not exceed upper bound; received ${lo} > ${hi}`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
assertInteger(hi - lo, "range span", 0);
|
|
34
|
-
return lo + Math.floor(next() * (hi - lo + 1));
|
|
35
|
-
},
|
|
36
|
-
float: (lo, hi) => {
|
|
37
|
-
assertFiniteNumber(lo, "float lower bound");
|
|
38
|
-
assertFiniteNumber(hi, "float upper bound");
|
|
39
|
-
if (lo > hi) {
|
|
40
|
-
throw new InvalidInputError(
|
|
41
|
-
`float lower bound must not exceed upper bound; received ${lo} > ${hi}`
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
assertFiniteNumber(hi - lo, "float span");
|
|
45
|
-
return lo + next() * (hi - lo);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
createRng
|
|
52
|
-
};
|
|
53
|
-
//# sourceMappingURL=chunk-ZGLJM4A4.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/random/index.ts"],"sourcesContent":["/**\n * Deterministic seeded pseudo-random number generation shared across the\n * generative modules (rhythm, drums, bass, groove, counter-melody). A single\n * mulberry32 core exposes the small set of samplers those modules need; given a\n * seed the stream is fully reproducible.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport { assertFiniteNumber, assertInteger, assertRange } from '../validation/index.js';\n\n/**\n * A deterministic PRNG with the sampling helpers the generators need.\n *\n * @category Utilities\n */\nexport type Rng = {\n /** Next float in [0, 1). */\n next: () => number;\n /** True with probability `p`. */\n prob: (p: number) => boolean;\n /** Integer in the inclusive range [lo, hi]. */\n range: (lo: number, hi: number) => number;\n /** Float in [lo, hi). */\n float: (lo: number, hi: number) => number;\n};\n\n/**\n * Create a seeded PRNG (mulberry32) exposing the shared samplers.\n *\n * @param seed The 32-bit seed; the same seed always yields the same stream.\n * @returns The seeded generator.\n * @example\n * ```ts\n * import { createRng } from '@libraz/libcantus';\n * const rng = createRng(42);\n * rng.next(); // deterministic float in [0, 1)\n * rng.range(1, 6); // deterministic integer in [1, 6]\n * ```\n * @category Utilities\n */\nexport function createRng(seed: number): Rng {\n // The state is 32 bits, so a wider or fractional seed would be truncated —\n // silently mapping distinct seeds onto one sequence.\n assertInteger(seed, 'seed', 0, 0xffffffff);\n let state = seed >>> 0;\n const next = () => {\n state = (state + 0x6d2b79f5) >>> 0;\n let t = state;\n t = Math.imul(t ^ (t >>> 15), t | 1);\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n return {\n next,\n prob: (p) => {\n // Validate first: a draw consumed by a rejected call shifts the stream,\n // so a caller who catches the error gets a different sequence than one\n // who never made the call.\n assertRange(p, 0, 1, 'probability');\n return next() < p;\n },\n range: (lo, hi) => {\n assertInteger(lo, 'range lower bound');\n assertInteger(hi, 'range upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `range lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertInteger(hi - lo, 'range span', 0);\n return lo + Math.floor(next() * (hi - lo + 1));\n },\n float: (lo, hi) => {\n assertFiniteNumber(lo, 'float lower bound');\n assertFiniteNumber(hi, 'float upper bound');\n if (lo > hi) {\n throw new InvalidInputError(\n `float lower bound must not exceed upper bound; received ${lo} > ${hi}`,\n );\n }\n assertFiniteNumber(hi - lo, 'float span');\n return lo + next() * (hi - lo);\n },\n };\n}\n"],"mappings":";;;;;;;;AAwCO,SAAS,UAAU,MAAmB;AAG3C,gBAAc,MAAM,QAAQ,GAAG,UAAU;AACzC,MAAI,QAAQ,SAAS;AACrB,QAAM,OAAO,MAAM;AACjB,YAAS,QAAQ,eAAgB;AACjC,QAAI,IAAI;AACR,QAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,SAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,CAAC,MAAM;AAIX,kBAAY,GAAG,GAAG,GAAG,aAAa;AAClC,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,OAAO,CAAC,IAAI,OAAO;AACjB,oBAAc,IAAI,mBAAmB;AACrC,oBAAc,IAAI,mBAAmB;AACrC,UAAI,KAAK,IAAI;AACX,cAAM,IAAI;AAAA,UACR,2DAA2D,EAAE,MAAM,EAAE;AAAA,QACvE;AAAA,MACF;AACA,oBAAc,KAAK,IAAI,cAAc,CAAC;AACtC,aAAO,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,EAAE;AAAA,IAC/C;AAAA,IACA,OAAO,CAAC,IAAI,OAAO;AACjB,yBAAmB,IAAI,mBAAmB;AAC1C,yBAAmB,IAAI,mBAAmB;AAC1C,UAAI,KAAK,IAAI;AACX,cAAM,IAAI;AAAA,UACR,2DAA2D,EAAE,MAAM,EAAE;AAAA,QACvE;AAAA,MACF;AACA,yBAAmB,KAAK,IAAI,YAAY;AACxC,aAAO,KAAK,KAAK,KAAK,KAAK;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import { K as KeyScale } from './types-BN6_Q9Kl.cjs';
|
|
2
|
-
import { C as Chord } from './index-oTQbYYIs.cjs';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Borrowed chords (modal interchange) and the Neapolitan.
|
|
6
|
-
*
|
|
7
|
-
* Roots are pitch classes measured against the key tonic, so borrowed and
|
|
8
|
-
* chromatic chords are handled by their semitone offset rather than requiring a
|
|
9
|
-
* spelled key signature.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The origin of a recognized non-diatonic chord, or null when none applies.
|
|
14
|
-
*
|
|
15
|
-
* @category Functional Harmony
|
|
16
|
-
*/
|
|
17
|
-
type BorrowedSource = 'parallelMinor' | 'parallelMajor' | 'neapolitan' | null;
|
|
18
|
-
/**
|
|
19
|
-
* Whether a chord is borrowed from the parallel mode (modal interchange).
|
|
20
|
-
*
|
|
21
|
-
* True when the chord is not diatonic to `key` but is diatonic to its
|
|
22
|
-
* {@link parallelKey} — e.g. iv, bVI, or bVII in a major key, or the Picardy
|
|
23
|
-
* tonic and major IV in a minor key. Two non-diatonic families are excluded:
|
|
24
|
-
* chords diatonic to neither mode (they are chromatic, not borrowed), and, in
|
|
25
|
-
* a minor key, chords explained by the altered minor forms (the major
|
|
26
|
-
* dominant and raised-leading-tone chords), which are in-key alterations
|
|
27
|
-
* rather than interchange even though they happen to fit the parallel major.
|
|
28
|
-
*
|
|
29
|
-
* @param chord The chord to test.
|
|
30
|
-
* @param key The prevailing key.
|
|
31
|
-
* @returns True if the chord is borrowed from the parallel mode.
|
|
32
|
-
* @category Functional Harmony
|
|
33
|
-
*/
|
|
34
|
-
declare function isBorrowedChord(chord: Chord, key: KeyScale): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Identify where a non-diatonic chord comes from.
|
|
37
|
-
*
|
|
38
|
-
* The Neapolitan (major triad on b2) is recognized first, since it sits
|
|
39
|
-
* outside both parallel modes; parallel-mode borrowing follows the
|
|
40
|
-
* {@link isBorrowedChord} rules. Diatonic chords and altered-minor chords,
|
|
41
|
-
* and unrecognized chromatic chords all yield null.
|
|
42
|
-
*
|
|
43
|
-
* @param chord The chord to classify.
|
|
44
|
-
* @param key The prevailing key.
|
|
45
|
-
* @returns The borrowing source, or null.
|
|
46
|
-
* @category Functional Harmony
|
|
47
|
-
*/
|
|
48
|
-
declare function borrowedSource(chord: Chord, key: KeyScale): BorrowedSource;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Cadence detection between two successive chords.
|
|
52
|
-
*
|
|
53
|
-
* Roots are pitch classes measured against the key tonic, so borrowed and
|
|
54
|
-
* chromatic chords are handled by their semitone offset rather than requiring a
|
|
55
|
-
* spelled key signature.
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* A recognized cadence type, or null when a chord pair forms none.
|
|
60
|
-
*
|
|
61
|
-
* @category Functional Harmony
|
|
62
|
-
*/
|
|
63
|
-
type Cadence = 'authentic' | 'plagal' | 'half' | 'deceptive' | null;
|
|
64
|
-
/**
|
|
65
|
-
* Classify the cadence formed by moving from one chord to the next.
|
|
66
|
-
*
|
|
67
|
-
* - authentic: V (dominant a fifth above the tonic) to I
|
|
68
|
-
* - plagal: IV (a fourth above the tonic) to I
|
|
69
|
-
* - deceptive: V to the submediant (diatonic vi or borrowed bVI in major, VI in
|
|
70
|
-
* minor)
|
|
71
|
-
* - half: any chord other than the dominant itself to V
|
|
72
|
-
*
|
|
73
|
-
* A static V-to-V repeat with no root motion is not a cadence and yields null.
|
|
74
|
-
*
|
|
75
|
-
* @param from The penultimate chord.
|
|
76
|
-
* @param to The final chord.
|
|
77
|
-
* @param key The prevailing key.
|
|
78
|
-
* @returns The cadence type, or null.
|
|
79
|
-
* @example
|
|
80
|
-
* ```ts
|
|
81
|
-
* import { detectCadence, makeChord, majorKey } from '@libraz/libcantus';
|
|
82
|
-
* detectCadence(makeChord(7, 'maj'), makeChord(0, 'maj'), majorKey(0)); // => 'authentic'
|
|
83
|
-
* ```
|
|
84
|
-
* @category Functional Harmony
|
|
85
|
-
*/
|
|
86
|
-
declare function detectCadence(from: Chord, to: Chord, key: KeyScale): Cadence;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Roman-numeral parsing and formatting.
|
|
90
|
-
*
|
|
91
|
-
* The {@link ROMAN_STYLE} table is the single source of truth for both
|
|
92
|
-
* directions, so {@link chordToRoman} and {@link romanToChord} stay mutual
|
|
93
|
-
* inverses by construction.
|
|
94
|
-
*/
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Build the chord denoted by a Roman numeral in a key.
|
|
98
|
-
*
|
|
99
|
-
* Supports accidentals (`bVII`, `#iv`), case-based triad quality, the `o`/`ø`/`+`
|
|
100
|
-
* suffixes, sevenths (`V7`, `viio7`, `Imaj7`), figured-bass inversions
|
|
101
|
-
* (`V6`, `V64`, `V65`, `V43`, `V42`), and applied/secondary chords via a slash
|
|
102
|
-
* (`V7/V`, `viio/ii`). The target after the slash is read as a scale degree
|
|
103
|
-
* whose root becomes a local major tonic for the applied chord. Inverted chords
|
|
104
|
-
* carry a `bassPc`.
|
|
105
|
-
*
|
|
106
|
-
* The `ø` glyph always denotes the half-diminished seventh (`iiø` == `iiø7` ==
|
|
107
|
-
* `m7b5`), since no half-diminished triad exists. Extension and added-tone
|
|
108
|
-
* suffixes are the canonical case-sensitive forms emitted by
|
|
109
|
-
* {@link chordToRoman} and honor the numeral case: `V9` -> `dom9`, `ii9` ->
|
|
110
|
-
* `min9`, `Imaj9` -> `maj9`, `V11` -> `11`, `V13` -> `13`, `Iadd6`/`iadd6` ->
|
|
111
|
-
* `6`/`min6`, `I69` -> `6/9`, `Isus2`/`Isus4`, `Iadd9`/`Iadd11`, `Ib5` ->
|
|
112
|
-
* `majb5`, `I5` -> `5`, and the altered dominants `V7b9`/`V7#9`/`V7#11`/`V7b13`.
|
|
113
|
-
* A figure string that is neither a recognized inversion nor a supported
|
|
114
|
-
* quality suffix throws rather than silently degrading to a triad.
|
|
115
|
-
*
|
|
116
|
-
* @param text The Roman numeral.
|
|
117
|
-
* @param key The prevailing key.
|
|
118
|
-
* @returns The chord.
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* import { romanToChord, majorKey } from '@libraz/libcantus';
|
|
122
|
-
* romanToChord('V7', majorKey(0)); // G7 in C major: { rootPc: 7, quality: 'dom7', ... }
|
|
123
|
-
* ```
|
|
124
|
-
* @category Functional Harmony
|
|
125
|
-
*/
|
|
126
|
-
declare function romanToChord(text: string, key: KeyScale): Chord;
|
|
127
|
-
/**
|
|
128
|
-
* Options for {@link chordToRoman}.
|
|
129
|
-
*
|
|
130
|
-
* @category Functional Harmony
|
|
131
|
-
*/
|
|
132
|
-
type ChordToRomanOptions = {
|
|
133
|
-
/**
|
|
134
|
-
* Render a tonicizing chord as an applied numeral (`V7/V`, `viio7/ii`)
|
|
135
|
-
* instead of naming its root against the home key (`II7`, `#ivo7`).
|
|
136
|
-
*
|
|
137
|
-
* Off by default: naming the root is always a correct spelling, whereas
|
|
138
|
-
* whether a chromatic dominant is genuinely applied is a reading only the
|
|
139
|
-
* caller can make. Turning it on makes {@link chordToRoman} the exact inverse
|
|
140
|
-
* of {@link romanToChord} for the chords {@link secondaryDominant} builds.
|
|
141
|
-
*/
|
|
142
|
-
applied?: boolean;
|
|
143
|
-
};
|
|
144
|
-
/**
|
|
145
|
-
* Render a chord as a Roman numeral relative to a key.
|
|
146
|
-
*
|
|
147
|
-
* Diatonic roots take their scale-degree numeral directly, so numerals are
|
|
148
|
-
* correct in both major and minor keys (and any custom scale). The quality
|
|
149
|
-
* selects the case and suffix; chromatic roots receive a flat/sharp spelling by
|
|
150
|
-
* convention. A key that is not heptatonic has no numeral of its own for every
|
|
151
|
-
* degree, so its roots are named against its parallel major.
|
|
152
|
-
*
|
|
153
|
-
* When the chord carries a `bassPc` on a chord tone, a figured-bass inversion
|
|
154
|
-
* (`6`, `64`, `65`, `43`, `42`) is emitted for plain triads and true seventh
|
|
155
|
-
* chords; added-tone and extended qualities have no lossless figure and render
|
|
156
|
-
* in root position instead (the bass is dropped, pitch classes are preserved).
|
|
157
|
-
*
|
|
158
|
-
* A `bassPc` that is *not* a chord tone — a pedal or passing bass such as
|
|
159
|
-
* `C/D` — is likewise dropped, and the numeral names the upper chord alone.
|
|
160
|
-
* Roman-numeral notation spends its slash on applied chords (`V7/V`), so there
|
|
161
|
-
* is no unambiguous way to write such a bass; a caller that must keep it should
|
|
162
|
-
* read `chord.bassPc` alongside the numeral.
|
|
163
|
-
*
|
|
164
|
-
* @param chord The chord to name.
|
|
165
|
-
* @param key The prevailing key.
|
|
166
|
-
* @param opts `applied` renders tonicizing chords as `V7/V`-style numerals.
|
|
167
|
-
* @returns The Roman numeral string.
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* import { chordToRoman, makeChord, majorKey } from '@libraz/libcantus';
|
|
171
|
-
* chordToRoman(makeChord(7, 'dom7'), majorKey(0)); // => 'V7' (G7 in C major)
|
|
172
|
-
* chordToRoman(makeChord(2, 'dom7'), majorKey(0), { applied: true }); // => 'V7/V'
|
|
173
|
-
* ```
|
|
174
|
-
* @category Functional Harmony
|
|
175
|
-
*/
|
|
176
|
-
declare function chordToRoman(chord: Chord, key: KeyScale, opts?: ChordToRomanOptions): string;
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Harmonic function (tonic / subdominant / dominant), diatonicity, parallel-key
|
|
180
|
-
* mirroring, and full chord analysis.
|
|
181
|
-
*
|
|
182
|
-
* Roots are pitch classes measured against the key tonic, so borrowed and
|
|
183
|
-
* chromatic chords are handled by their semitone offset rather than requiring a
|
|
184
|
-
* spelled key signature.
|
|
185
|
-
*/
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* The three broad harmonic functions of tonal music.
|
|
189
|
-
*
|
|
190
|
-
* @category Functional Harmony
|
|
191
|
-
*/
|
|
192
|
-
type HarmonicFunction = 'tonic' | 'subdominant' | 'dominant';
|
|
193
|
-
/**
|
|
194
|
-
* Whether a key's scale has a minor third and no major third (a minor key).
|
|
195
|
-
*
|
|
196
|
-
* @category Functional Harmony
|
|
197
|
-
*/
|
|
198
|
-
declare function isMinorKey(key: KeyScale): boolean;
|
|
199
|
-
/**
|
|
200
|
-
* The harmonic function of a chord in a key.
|
|
201
|
-
*
|
|
202
|
-
* The root's offset above the tonic gives the baseline mapping, which follows
|
|
203
|
-
* common-practice major-key function and is a useful approximation in minor and
|
|
204
|
-
* for borrowed chords. Chord quality then refines it where the sonority settles
|
|
205
|
-
* an ambiguity the root offset cannot:
|
|
206
|
-
*
|
|
207
|
-
* - A chord that *sounds* like a dominant — a major third with a minor seventh,
|
|
208
|
-
* or a diminished-family chord — and is not diatonic to the key has dominant
|
|
209
|
-
* function when its root resolves down a fifth or by a semitone onto a
|
|
210
|
-
* diatonic degree. This is what makes an applied dominant (`A7` in C, which
|
|
211
|
-
* tonicizes ii) and its tritone substitute (`Db7`) read as dominant rather
|
|
212
|
-
* than inheriting the function of the degree they happen to sit on.
|
|
213
|
-
* - The Neapolitan is subdominant.
|
|
214
|
-
* - A major triad on bVI or bVII of a major key is subdominant — the borrowed
|
|
215
|
-
* pop cadence chord, distinct from the bVII7 above, which has a seventh and
|
|
216
|
-
* is a dominant sonority.
|
|
217
|
-
*
|
|
218
|
-
* @param chord The chord.
|
|
219
|
-
* @param key The prevailing key.
|
|
220
|
-
* @returns The harmonic function.
|
|
221
|
-
* @example
|
|
222
|
-
* ```ts
|
|
223
|
-
* import { functionOf, makeChord, majorKey } from '@libraz/libcantus';
|
|
224
|
-
* functionOf(makeChord(9, 'dom7'), majorKey(0)); // 'dominant' — A7 tonicizes ii
|
|
225
|
-
* functionOf(makeChord(10, 'maj'), majorKey(0)); // 'subdominant' — borrowed bVII
|
|
226
|
-
* ```
|
|
227
|
-
* @category Functional Harmony
|
|
228
|
-
*/
|
|
229
|
-
declare function functionOf(chord: Chord, key: KeyScale): HarmonicFunction;
|
|
230
|
-
/**
|
|
231
|
-
* The result of {@link analyzeChord}: function, borrowing, and Roman numeral.
|
|
232
|
-
*
|
|
233
|
-
* @category Functional Harmony
|
|
234
|
-
*/
|
|
235
|
-
type ChordAnalysis = {
|
|
236
|
-
function: HarmonicFunction;
|
|
237
|
-
borrowed: boolean;
|
|
238
|
-
source: BorrowedSource;
|
|
239
|
-
roman: string;
|
|
240
|
-
};
|
|
241
|
-
/**
|
|
242
|
-
* Whether every pitch class of a chord belongs to the key's scale.
|
|
243
|
-
*
|
|
244
|
-
* The test is strict against the key's own mode mask: in a natural-minor key
|
|
245
|
-
* the harmonic-minor dominant (major V) is *not* diatonic, since the raised
|
|
246
|
-
* leading tone lies outside the mask. Borrowing predicates treat that case as
|
|
247
|
-
* an in-key alteration separately (see {@link isBorrowedChord}).
|
|
248
|
-
*
|
|
249
|
-
* @param chord The chord to test.
|
|
250
|
-
* @param key The prevailing key.
|
|
251
|
-
* @returns True if all chord pitch classes are scale tones.
|
|
252
|
-
* @category Functional Harmony
|
|
253
|
-
*/
|
|
254
|
-
declare function isDiatonic(chord: Chord, key: KeyScale): boolean;
|
|
255
|
-
/**
|
|
256
|
-
* The parallel key: same tonic, opposite mode.
|
|
257
|
-
*
|
|
258
|
-
* A key with a minor third (natural/harmonic/melodic minor, dorian, phrygian)
|
|
259
|
-
* maps to the parallel major; any other key maps to the parallel natural minor.
|
|
260
|
-
*
|
|
261
|
-
* @param key The key to mirror.
|
|
262
|
-
* @returns The parallel major or natural-minor key on the same tonic.
|
|
263
|
-
* @category Functional Harmony
|
|
264
|
-
*/
|
|
265
|
-
declare function parallelKey(key: KeyScale): KeyScale;
|
|
266
|
-
/**
|
|
267
|
-
* Analyze a chord in a key: harmonic function, borrowing, and Roman numeral.
|
|
268
|
-
*
|
|
269
|
-
* The function is quality-aware (see the predicates behind it), the source
|
|
270
|
-
* follows {@link borrowedSource}, and the numeral comes from
|
|
271
|
-
* {@link chordToRoman}. `borrowed` is true whenever a source is identified —
|
|
272
|
-
* including the Neapolitan, which the stricter parallel-mode predicate
|
|
273
|
-
* {@link isBorrowedChord} does not count.
|
|
274
|
-
*
|
|
275
|
-
* @param chord The chord to analyze.
|
|
276
|
-
* @param key The prevailing key.
|
|
277
|
-
* @param opts Options used when rendering the Roman numeral.
|
|
278
|
-
* @returns The chord analysis.
|
|
279
|
-
* @example
|
|
280
|
-
* ```ts
|
|
281
|
-
* import { analyzeChord, makeChord, majorKey } from '@libraz/libcantus';
|
|
282
|
-
* analyzeChord(makeChord(7, 'dom7'), majorKey(0));
|
|
283
|
-
* // { function: 'dominant', borrowed: false, source: null, roman: 'V7' }
|
|
284
|
-
* ```
|
|
285
|
-
* @category Functional Harmony
|
|
286
|
-
*/
|
|
287
|
-
declare function analyzeChord(chord: Chord, key: KeyScale, opts?: ChordToRomanOptions): ChordAnalysis;
|
|
288
|
-
/**
|
|
289
|
-
* The secondary dominant (V7) that tonicizes a scale degree.
|
|
290
|
-
*
|
|
291
|
-
* @param targetDegree 0-based scale degree to tonicize.
|
|
292
|
-
* @param key The prevailing key.
|
|
293
|
-
* @returns A dominant-seventh chord a fifth above the target's root.
|
|
294
|
-
* @throws If `targetDegree` is not an integer naming a degree in `key`.
|
|
295
|
-
* @category Functional Harmony
|
|
296
|
-
*/
|
|
297
|
-
declare function secondaryDominant(targetDegree: number, key: KeyScale): Chord;
|
|
298
|
-
|
|
299
|
-
export { type BorrowedSource as B, type Cadence as C, type HarmonicFunction as H, type ChordAnalysis as a, type ChordToRomanOptions as b, analyzeChord as c, borrowedSource as d, chordToRoman as e, detectCadence as f, functionOf as g, isDiatonic as h, isBorrowedChord as i, isMinorKey as j, parallelKey as p, romanToChord as r, secondaryDominant as s };
|