@libraz/libcantus 0.9.2 → 0.9.3

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.
Files changed (126) hide show
  1. package/README.md +4 -3
  2. package/dist/analyze/index.cjs +315 -42
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +11 -99
  5. package/dist/analyze/index.d.ts +11 -99
  6. package/dist/analyze/index.js +6 -6
  7. package/dist/chunk-2ZWMSJH6.js +30 -0
  8. package/dist/chunk-2ZWMSJH6.js.map +1 -0
  9. package/dist/{chunk-R3S2LUBH.js → chunk-4MHA432G.js} +316 -184
  10. package/dist/chunk-4MHA432G.js.map +1 -0
  11. package/dist/chunk-62QZKCJQ.js +49 -0
  12. package/dist/chunk-62QZKCJQ.js.map +1 -0
  13. package/dist/chunk-AKLYPWIN.js +83 -0
  14. package/dist/chunk-AKLYPWIN.js.map +1 -0
  15. package/dist/{chunk-XTC3X6MS.js → chunk-C5XYKUVG.js} +100 -12
  16. package/dist/chunk-C5XYKUVG.js.map +1 -0
  17. package/dist/{chunk-LN7ZZMD2.js → chunk-CHMXELDS.js} +33 -14
  18. package/dist/chunk-CHMXELDS.js.map +1 -0
  19. package/dist/{chunk-A4532ZFR.js → chunk-EH6EM37U.js} +60 -4
  20. package/dist/chunk-EH6EM37U.js.map +1 -0
  21. package/dist/chunk-FBQK3ZQN.js +131 -0
  22. package/dist/chunk-FBQK3ZQN.js.map +1 -0
  23. package/dist/{chunk-5HTNMQW4.js → chunk-GOAW4VYL.js} +87 -21
  24. package/dist/chunk-GOAW4VYL.js.map +1 -0
  25. package/dist/{chunk-MSWFZF7J.js → chunk-I5R7ZBJS.js} +90 -6
  26. package/dist/chunk-I5R7ZBJS.js.map +1 -0
  27. package/dist/{chunk-GQKN5KDT.js → chunk-IT2O4XFE.js} +4 -9
  28. package/dist/chunk-IT2O4XFE.js.map +1 -0
  29. package/dist/{chunk-7Y7WF64D.js → chunk-MIWO425M.js} +6 -4
  30. package/dist/chunk-MIWO425M.js.map +1 -0
  31. package/dist/chunk-N6IHQUOX.js +1 -0
  32. package/dist/chunk-N6IHQUOX.js.map +1 -0
  33. package/dist/{chunk-COKTX2G5.js → chunk-SGU7SPLK.js} +29 -12
  34. package/dist/chunk-SGU7SPLK.js.map +1 -0
  35. package/dist/core/index.cjs +230 -14
  36. package/dist/core/index.cjs.map +1 -1
  37. package/dist/core/index.d.cts +56 -4
  38. package/dist/core/index.d.ts +56 -4
  39. package/dist/core/index.js +24 -4
  40. package/dist/{function-2bJW1Zp_.d.ts → function-DVH4Eusr.d.ts} +1 -1
  41. package/dist/{function-r2sh-jOO.d.cts → function-DYt4iKsn.d.cts} +1 -1
  42. package/dist/generate/index.cjs +830 -494
  43. package/dist/generate/index.cjs.map +1 -1
  44. package/dist/generate/index.d.cts +11 -5
  45. package/dist/generate/index.d.ts +11 -5
  46. package/dist/generate/index.js +9 -9
  47. package/dist/{index-BOBXBNwP.d.cts → index-BNlGP86A.d.cts} +1 -1
  48. package/dist/{index-CP3wAfEH.d.ts → index-Bg5dYMmZ.d.ts} +1 -1
  49. package/dist/{index-CM5W4rcm.d.ts → index-Bt4tOUD9.d.ts} +1 -1
  50. package/dist/index-CnZCo29u.d.cts +108 -0
  51. package/dist/{index-D_ZTUNKp.d.cts → index-CpOsP1y9.d.cts} +2 -2
  52. package/dist/{index-DZ1M21do.d.cts → index-CxqBQtH1.d.cts} +2 -1
  53. package/dist/{index-BdeQ1r_p.d.cts → index-Dikst44v.d.cts} +1 -1
  54. package/dist/{index-BMPxwjwV.d.ts → index-DxvARIP2.d.ts} +2 -2
  55. package/dist/{index-BreoJRw0.d.ts → index-Jc0Ryi79.d.ts} +2 -1
  56. package/dist/index-JnF4v9bx.d.ts +108 -0
  57. package/dist/index.cjs +818 -283
  58. package/dist/index.cjs.map +1 -1
  59. package/dist/index.d.cts +9 -8
  60. package/dist/index.d.ts +9 -8
  61. package/dist/index.js +34 -13
  62. package/dist/model/index.cjs +170 -31
  63. package/dist/model/index.cjs.map +1 -1
  64. package/dist/model/index.d.cts +6 -5
  65. package/dist/model/index.d.ts +6 -5
  66. package/dist/model/index.js +6 -6
  67. package/dist/{styled-D7BAx3S3.d.ts → styled-DZ7GjShb.d.ts} +1 -1
  68. package/dist/{styled-BjXtOFNk.d.cts → styled-ZTbdaVFM.d.cts} +1 -1
  69. package/dist/theory/index.cjs +180 -34
  70. package/dist/theory/index.cjs.map +1 -1
  71. package/dist/theory/index.d.cts +6 -6
  72. package/dist/theory/index.d.ts +6 -6
  73. package/dist/theory/index.js +6 -5
  74. package/package.json +50 -19
  75. package/src/analyze/arrange/internal.ts +3 -1
  76. package/src/analyze/arrange/tension.ts +15 -3
  77. package/src/analyze/arrange/tracks.ts +36 -3
  78. package/src/analyze/detect/index.ts +46 -9
  79. package/src/analyze/functional/roman.ts +14 -5
  80. package/src/analyze/index.ts +1 -1
  81. package/src/analyze/timeline/index.ts +43 -7
  82. package/src/analyze/voice/index.ts +11 -2
  83. package/src/core/event-index/index.ts +104 -0
  84. package/src/core/index.ts +14 -0
  85. package/src/core/meter/index.ts +24 -4
  86. package/src/core/random/index.ts +26 -3
  87. package/src/core/tuning/index.ts +36 -6
  88. package/src/core/validation/index.ts +119 -0
  89. package/src/generate/bass/index.ts +23 -0
  90. package/src/generate/countermelody/index.ts +167 -76
  91. package/src/generate/drums/euclid.ts +10 -3
  92. package/src/generate/drums/index.ts +56 -24
  93. package/src/generate/drums/percussion.ts +1 -1
  94. package/src/generate/groove/index.ts +25 -0
  95. package/src/generate/harmonize/index.ts +55 -44
  96. package/src/generate/index.ts +0 -1
  97. package/src/generate/motif/index.ts +45 -21
  98. package/src/generate/progression/index.ts +8 -16
  99. package/src/generate/reharmony/index.ts +1 -8
  100. package/src/generate/rhythm/index.ts +11 -1
  101. package/src/index.ts +13 -247
  102. package/src/model/chord.ts +29 -10
  103. package/src/theory/chord/index.ts +4 -2
  104. package/src/theory/harmony/index.ts +6 -21
  105. package/src/theory/index.ts +6 -1
  106. package/src/theory/safety/index.ts +71 -2
  107. package/src/theory/spelling/index.ts +70 -3
  108. package/src/theory/voicing/satb.ts +25 -4
  109. package/src/theory/voicing/styled.ts +26 -3
  110. package/dist/chunk-5HTNMQW4.js.map +0 -1
  111. package/dist/chunk-7Y7WF64D.js.map +0 -1
  112. package/dist/chunk-A4532ZFR.js.map +0 -1
  113. package/dist/chunk-BHUT2S5G.js +0 -22
  114. package/dist/chunk-BHUT2S5G.js.map +0 -1
  115. package/dist/chunk-CCBLVAPK.js +0 -42
  116. package/dist/chunk-CCBLVAPK.js.map +0 -1
  117. package/dist/chunk-COKTX2G5.js.map +0 -1
  118. package/dist/chunk-GQKN5KDT.js.map +0 -1
  119. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  120. package/dist/chunk-MSWFZF7J.js.map +0 -1
  121. package/dist/chunk-R3S2LUBH.js.map +0 -1
  122. package/dist/chunk-SDM3VJJ5.js +0 -41
  123. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  124. package/dist/chunk-XTC3X6MS.js.map +0 -1
  125. package/dist/chunk-ZXW5YIWK.js +0 -55
  126. package/dist/chunk-ZXW5YIWK.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generate/reharmony/index.ts"],"sourcesContent":["/**\n * Reharmonization: chord substitution, modal-interchange palettes, and negative\n * harmony. These transforms take an existing chord (or a key) and propose\n * alternative harmonies that preserve a chosen relationship — a shared function,\n * a common tone, a tonicizing dominant, or a reflection across the key axis.\n *\n * Roots are pitch classes (0..11); no spelled key signature is required.\n */\n\nimport { detectChordBest } from '../../analyze/detect/index.js';\nimport {\n type BorrowedSource,\n borrowedSource,\n chordToRoman,\n functionOf,\n type HarmonicFunction,\n isDiatonic,\n parallelKey,\n} from '../../analyze/functional/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n type Chord,\n type ChordQuality,\n chordPitchClasses,\n diatonicTriad,\n makeChord,\n} from '../../theory/chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../../theory/scale/index.js';\n\n/** Reduce a value to a pitch class in [0, 11]. */\nfunction mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/**\n * The kind of substitution relationship a candidate realizes.\n *\n * @category Reharmonization\n */\nexport type SubstitutionType =\n | 'tritone'\n | 'relative'\n | 'borrowed'\n | 'chromatic-mediant'\n | 'secondary-dominant';\n\n/**\n * A proposed chord substitution with its relationship, numeral, and function.\n *\n * @category Reharmonization\n */\nexport type Substitution = {\n chord: Chord;\n type: SubstitutionType;\n roman: string;\n function: HarmonicFunction;\n};\n\n/**\n * Options controlling which substitutions {@link substituteChord} returns.\n *\n * @category Reharmonization\n */\nexport type SubstituteOptions = {\n /**\n * Melody pitch classes that must remain chord tones. When given, only\n * substitutions whose pitch classes contain every listed pitch class are\n * kept, so the melody stays consonant against the new harmony.\n */\n melodyPcs?: number[];\n};\n\n/** Whether the chord's template carries a given interval (mod 12). */\nfunction hasInterval(chord: Chord, semitone: number): boolean {\n return chord.intervals.some((iv) => mod12(iv) === semitone);\n}\n\n/** Whether a chord is a dominant type: a major third with a minor seventh. */\nfunction isDominantType(chord: Chord): boolean {\n return hasInterval(chord, 4) && hasInterval(chord, 10);\n}\n\n/**\n * A chord that can be tonicized: it has a perfect fifth and a third but is not\n * itself a dominant seventh (which is an unstable, tonicizing sonority rather\n * than a stable target).\n */\nfunction isStableTarget(chord: Chord): boolean {\n const hasFifth = hasInterval(chord, 7);\n const hasThird = hasInterval(chord, 3) || hasInterval(chord, 4);\n return hasFifth && hasThird && !isDominantType(chord);\n}\n\n/** Count the pitch classes shared between two pitch-class sets. */\nfunction commonToneCount(a: number[], b: number[]): number {\n const set = new Set(b);\n return a.filter((pc) => set.has(pc)).length;\n}\n\n/** The diatonic triads of a key, one per scale degree. */\nfunction diatonicTriadsOf(key: KeyScale): Chord[] {\n const degrees = scaleTonesInDegreeOrder(key).length;\n const triads: Chord[] = [];\n for (let degree = 0; degree < degrees; degree += 1) {\n triads.push(diatonicTriad(degree, key));\n }\n return triads;\n}\n\n/** Whether two chords share the same root pitch class and quality. */\nfunction sameChord(a: Chord, b: Chord): boolean {\n return mod12(a.rootPc) === mod12(b.rootPc) && a.quality === b.quality;\n}\n\n/** Root offsets (semitones) that form a third above or below a root. */\nconst THIRD_OFFSETS = [3, 4, 8, 9] as const;\n\n/**\n * Propose substitutions for a chord within a key.\n *\n * Candidates are generated by several relationships and deduplicated against the\n * original chord and each other:\n *\n * - `tritone`: for a dominant-type chord, the dominant seventh a tritone away.\n * - `secondary-dominant`: for a stable target, the dominant seventh a fifth\n * above its root (tonicizing it).\n * - `relative`: a diatonic triad a third away that shares two common tones.\n * - `borrowed`: a triad from the parallel mode with the same harmonic function.\n * - `chromatic-mediant`: a major/minor triad a third away sharing one common\n * tone.\n *\n * Each result carries its Roman numeral and harmonic function in `key`. When\n * `opts.melodyPcs` is given, only substitutions whose pitch classes contain all\n * of those pitch classes are returned.\n *\n * @param chord The chord to reharmonize.\n * @param key The prevailing key.\n * @param opts Optional melody-preservation constraint.\n * @returns The deduplicated substitution candidates.\n * @example\n * ```ts\n * import { substituteChord, makeChord, majorKey } from '@libraz/libcantus';\n * const subs = substituteChord(makeChord(7, 'dom7'), majorKey(0));\n * // includes the tritone sub Db7 (rootPc 1, quality 'dom7')\n * ```\n * @category Reharmonization\n */\nexport function substituteChord(\n chord: Chord,\n key: KeyScale,\n opts?: SubstituteOptions,\n): Substitution[] {\n const original = chordPitchClasses(chord);\n const candidates: { chord: Chord; type: SubstitutionType }[] = [];\n\n // Tritone substitution: only for dominant-type chords.\n if (isDominantType(chord)) {\n candidates.push({ chord: makeChord(mod12(chord.rootPc + 6), 'dom7'), type: 'tritone' });\n }\n\n // Secondary dominant: the V7 tonicizing a stable target.\n if (isStableTarget(chord)) {\n candidates.push({\n chord: makeChord(mod12(chord.rootPc + 7), 'dom7'),\n type: 'secondary-dominant',\n });\n }\n\n // Relative: a diatonic triad a third away sharing two common tones.\n for (const triad of diatonicTriadsOf(key)) {\n const offset = mod12(triad.rootPc - chord.rootPc);\n if (\n (THIRD_OFFSETS as readonly number[]).includes(offset) &&\n commonToneCount(original, chordPitchClasses(triad)) === 2\n ) {\n candidates.push({ chord: triad, type: 'relative' });\n }\n }\n\n // Borrowed: a parallel-mode triad sharing the original's harmonic function.\n const targetFunction = functionOf(chord, key);\n for (const triad of diatonicTriadsOf(parallelKey(key))) {\n if (functionOf(triad, key) === targetFunction && !isDiatonic(triad, key)) {\n candidates.push({ chord: triad, type: 'borrowed' });\n }\n }\n\n // Chromatic mediants: major/minor triads a third away sharing one common tone.\n for (const offset of THIRD_OFFSETS) {\n for (const quality of ['maj', 'min'] as const) {\n const mediant = makeChord(mod12(chord.rootPc + offset), quality);\n if (\n commonToneCount(original, chordPitchClasses(mediant)) === 1 &&\n !isDiatonic(mediant, key)\n ) {\n candidates.push({ chord: mediant, type: 'chromatic-mediant' });\n }\n }\n }\n\n const melodyPcs = opts?.melodyPcs?.map(mod12);\n const seen = new Set<string>();\n const results: Substitution[] = [];\n for (const candidate of candidates) {\n if (sameChord(candidate.chord, chord)) {\n continue;\n }\n const dedupeKey = `${mod12(candidate.chord.rootPc)}:${candidate.chord.quality}`;\n if (seen.has(dedupeKey)) {\n continue;\n }\n seen.add(dedupeKey);\n if (melodyPcs !== undefined) {\n const pcs = new Set(chordPitchClasses(candidate.chord));\n if (!melodyPcs.every((pc) => pcs.has(pc))) {\n continue;\n }\n }\n results.push({\n chord: candidate.chord,\n type: candidate.type,\n roman: chordToRoman(candidate.chord, key),\n function: functionOf(candidate.chord, key),\n });\n }\n return results;\n}\n\n/**\n * A borrowed chord in the modal-interchange palette of a key.\n *\n * @category Reharmonization\n */\nexport type BorrowedChord = {\n chord: Chord;\n roman: string;\n source: BorrowedSource;\n};\n\n/**\n * The modal-interchange palette of a key.\n *\n * Lists the diatonic triads of the {@link parallelKey} that are not diatonic to\n * `key` (the classic borrowed chords such as `iv`, `bVI`, and `bVII` in a major\n * key), plus the Neapolitan (a major triad on the flat second degree). Each\n * carries its Roman numeral and {@link borrowedSource} relative to `key`.\n *\n * @param key The prevailing key.\n * @returns The borrowed-chord palette.\n * @category Reharmonization\n */\nexport function modalInterchangePalette(key: KeyScale): BorrowedChord[] {\n const palette: BorrowedChord[] = [];\n const seen = new Set<string>();\n const add = (chord: Chord) => {\n const dedupeKey = `${mod12(chord.rootPc)}:${chord.quality}`;\n if (seen.has(dedupeKey)) {\n return;\n }\n seen.add(dedupeKey);\n palette.push({\n chord,\n roman: chordToRoman(chord, key),\n source: borrowedSource(chord, key),\n });\n };\n\n for (const triad of diatonicTriadsOf(parallelKey(key))) {\n if (!isDiatonic(triad, key)) {\n add(triad);\n }\n }\n // The Neapolitan (major triad on b2) sits outside both parallel modes, so it\n // is added explicitly — unless b2 is already diatonic (e.g. in a Phrygian key,\n // where it is a native chord rather than a borrowing).\n const neapolitan = makeChord(mod12(key.rootPc + 1), 'maj');\n if (!isDiatonic(neapolitan, key)) {\n add(neapolitan);\n }\n return palette;\n}\n\n/**\n * Reflect a chord across the tonic–dominant axis of a key (negative harmony).\n *\n * Every pitch class `p` maps to `(2*T + 7 - p) mod 12`, where `T` is the key\n * tonic — the reflection that swaps the tonic with the dominant. The mirrored\n * pitch classes (including the bass, if set) are reassembled into a chord by\n * {@link detectChordBest}; when no chord is recognized, the mirrored root and\n * the original quality are used as a fallback.\n *\n * @param chord The chord to reflect.\n * @param key The prevailing key.\n * @returns The negative-harmony counterpart of the chord.\n * @example\n * ```ts\n * import { negativeHarmonyMirror, makeChord, majorKey } from '@libraz/libcantus';\n * negativeHarmonyMirror(makeChord(7, 'maj'), majorKey(0));\n * // G major reflected across C's axis => F minor (rootPc 5, quality 'min')\n * ```\n * @category Reharmonization\n */\nexport function negativeHarmonyMirror(chord: Chord, key: KeyScale): Chord {\n const tonic = mod12(key.rootPc);\n const mirror = (p: number) => mod12(2 * tonic + 7 - p);\n const pcs = chordPitchClasses(chord).map(mirror);\n const bassPc = chord.bassPc !== undefined ? mirror(chord.bassPc) : undefined;\n // Place the mirrored bass an octave below so it is recognized as the lowest\n // note and preserved through detection.\n const pitches = bassPc !== undefined ? [bassPc - 12, ...pcs] : pcs;\n const detected = detectChordBest(pitches);\n if (detected) {\n // Without an input bass the mirrored tones are octave-less, so detection may\n // read the lowest pitch class as a slash bass and report a spurious\n // inversion. Restore the recognized chord to root position in that case.\n if (bassPc === undefined && detected.bassPc !== undefined) {\n return makeChord(detected.rootPc, detected.quality);\n }\n return detected;\n }\n const quality: ChordQuality = chord.quality;\n return makeChord(mirror(chord.rootPc), quality, bassPc);\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isConsonantInterval
3
- } from "./chunk-CCBLVAPK.js";
3
+ } from "./chunk-FBQK3ZQN.js";
4
4
 
5
5
  // src/theory/scale/degrees.ts
6
6
  function pitchClass(pitch) {
@@ -263,8 +263,10 @@ function classifySeventh(thirdIc, fifthIc, seventhIc) {
263
263
  function stackThirds(degree, key, size) {
264
264
  const tones = scaleTonesInDegreeOrder(key);
265
265
  const length = tones.length;
266
- if (length === 0) {
267
- return { rootPc: pitchClass2(key.rootPc), quality: "maj", intervals: [0, 4, 7] };
266
+ if (length !== 7) {
267
+ throw new Error(
268
+ `diatonic chord stacking requires a heptatonic scale (received ${length} tones)`
269
+ );
268
270
  }
269
271
  const idx = (step) => ((degree + step) % length + length) % length;
270
272
  const rootPc = tones[idx(0)] ?? pitchClass2(key.rootPc);
@@ -407,4 +409,4 @@ export {
407
409
  exceedsSpacing,
408
410
  isLeadingToneResolution
409
411
  };
410
- //# sourceMappingURL=chunk-7Y7WF64D.js.map
412
+ //# sourceMappingURL=chunk-MIWO425M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theory/scale/degrees.ts","../src/theory/scale/masks.ts","../src/theory/scale/key.ts","../src/theory/chord/index.ts","../src/theory/counterpoint/index.ts"],"sourcesContent":["import type { KeyScale } from '../../core/types.js';\n\n/** Reduce a pitch (or pitch class) to a pitch class in [0, 11]. */\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\n/** Scale offset of a pitch relative to the key root, in [0, 11]. */\nfunction scaleOffset(pitch: number, key: KeyScale): number {\n return (pitchClass(pitch) - pitchClass(key.rootPc) + 12) % 12;\n}\n\n/**\n * Test whether a pitch belongs to the scale.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to test against.\n * @returns True if the pitch class is a member of the scale.\n *\n * @example\n * ```ts\n * import { majorKey, isScaleTone } from '@libraz/libcantus';\n * const key = majorKey(0); // C major\n * isScaleTone(64, key); // true — E is in C major\n * isScaleTone(61, key); // false — C# is not\n * ```\n *\n * @category Scales\n */\nexport function isScaleTone(pitch: number, key: KeyScale): boolean {\n return ((key.modeMask12 >> scaleOffset(pitch, key)) & 1) === 1;\n}\n\n/**\n * Find the nearest MIDI pitch whose pitch class is in the scale.\n *\n * The search expands symmetrically outward from `pitch`. On a tie (equal\n * distance above and below) the lower pitch is returned.\n *\n * @param pitch MIDI pitch to snap.\n * @param key The key/scale to snap to.\n * @returns The nearest in-scale MIDI pitch.\n *\n * @category Scales\n */\nexport function nearestScaleTone(pitch: number, key: KeyScale): number {\n const base = Math.round(pitch);\n for (let distance = 0; distance < 12; distance += 1) {\n const lower = base - distance;\n if (isScaleTone(lower, key)) {\n return lower;\n }\n const higher = base + distance;\n if (isScaleTone(higher, key)) {\n return higher;\n }\n }\n return base;\n}\n\n/**\n * Get the 0-based scale degree of a pitch.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param key The key/scale to measure against.\n * @returns The 0-based degree, or -1 if the pitch is not in the scale.\n *\n * @category Scales\n */\nexport function pitchToScaleDegree(pitch: number, key: KeyScale): number {\n const offset = scaleOffset(pitch, key);\n if (((key.modeMask12 >> offset) & 1) === 0) {\n return -1;\n }\n let degree = 0;\n for (let n = 0; n < offset; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n degree += 1;\n }\n }\n return degree;\n}\n\n/**\n * List the pitch classes of a key's scale.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes, sorted ascending in [0, 11].\n *\n * @category Scales\n */\nexport function diatonicPitchClasses(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs.sort((a, b) => a - b);\n}\n\n/**\n * List the pitch classes of a key's scale in ascending scale-degree order.\n *\n * Degree 0 is the root; degrees follow the mask bits in offset order rather\n * than sorted pitch-class order.\n *\n * @param key The key/scale to enumerate.\n * @returns The member pitch classes ordered by scale degree.\n *\n * @category Scales\n */\nexport function scaleTonesInDegreeOrder(key: KeyScale): number[] {\n const root = pitchClass(key.rootPc);\n const pcs: number[] = [];\n for (let n = 0; n < 12; n += 1) {\n if (((key.modeMask12 >> n) & 1) === 1) {\n pcs.push((root + n) % 12);\n }\n }\n return pcs;\n}\n","/**\n * Build a 12-bit mode mask from a list of semitone offsets above the root.\n *\n * Bit 0 (the root) is always set, enforcing the `KeyScale` invariant that the\n * root is a scale tone even when the offset list omits 0.\n *\n * @category Scales\n */\nexport function maskFromOffsets(offsets: readonly number[]): number {\n let mask = 1;\n for (const offset of offsets) {\n mask |= 1 << (((offset % 12) + 12) % 12);\n }\n return mask;\n}\n\n/**\n * Mode mask for the major (Ionian) scale: offsets {0, 2, 4, 5, 7, 9, 11}.\n *\n * @category Scales\n */\nexport const MAJOR_MASK = 0b101010110101;\n\n/**\n * Mode mask for the natural minor (Aeolian) scale: offsets {0, 2, 3, 5, 7, 8, 10}.\n *\n * @category Scales\n */\nexport const NATURAL_MINOR_MASK = 0b010110101101;\n\n/**\n * Harmonic minor: natural minor with a raised seventh — offsets {0,2,3,5,7,8,11}.\n *\n * @category Scales\n */\nexport const HARMONIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 8, 11]);\n\n/**\n * Ascending melodic minor: offsets {0,2,3,5,7,9,11}.\n *\n * @category Scales\n */\nexport const MELODIC_MINOR_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 11]);\n\n/**\n * Dorian mode: offsets {0,2,3,5,7,9,10}.\n *\n * @category Scales\n */\nexport const DORIAN_MASK = maskFromOffsets([0, 2, 3, 5, 7, 9, 10]);\n\n/**\n * Phrygian mode: offsets {0,1,3,5,7,8,10}.\n *\n * @category Scales\n */\nexport const PHRYGIAN_MASK = maskFromOffsets([0, 1, 3, 5, 7, 8, 10]);\n\n/**\n * Lydian mode: offsets {0,2,4,6,7,9,11}.\n *\n * @category Scales\n */\nexport const LYDIAN_MASK = maskFromOffsets([0, 2, 4, 6, 7, 9, 11]);\n\n/**\n * Mixolydian mode: offsets {0,2,4,5,7,9,10}.\n *\n * @category Scales\n */\nexport const MIXOLYDIAN_MASK = maskFromOffsets([0, 2, 4, 5, 7, 9, 10]);\n\n/**\n * Locrian mode: offsets {0,1,3,5,6,8,10}.\n *\n * @category Scales\n */\nexport const LOCRIAN_MASK = maskFromOffsets([0, 1, 3, 5, 6, 8, 10]);\n\n/**\n * Major pentatonic: offsets {0,2,4,7,9}.\n *\n * @category Scales\n */\nexport const MAJOR_PENTATONIC_MASK = maskFromOffsets([0, 2, 4, 7, 9]);\n\n/**\n * Minor pentatonic: offsets {0,3,5,7,10}.\n *\n * @category Scales\n */\nexport const MINOR_PENTATONIC_MASK = maskFromOffsets([0, 3, 5, 7, 10]);\n\n/**\n * Hexatonic blues scale: minor pentatonic plus the flat-fifth — {0,3,5,6,7,10}.\n *\n * @category Scales\n */\nexport const BLUES_MASK = maskFromOffsets([0, 3, 5, 6, 7, 10]);\n\n/**\n * Whole-tone scale: offsets {0,2,4,6,8,10}.\n *\n * @category Scales\n */\nexport const WHOLE_TONE_MASK = maskFromOffsets([0, 2, 4, 6, 8, 10]);\n\n/**\n * Octatonic (half-step first): offsets {0,1,3,4,6,7,9,10}.\n *\n * @category Scales\n */\nexport const OCTATONIC_HALF_WHOLE_MASK = maskFromOffsets([0, 1, 3, 4, 6, 7, 9, 10]);\n\n/**\n * Octatonic (whole-step first): offsets {0,2,3,5,6,8,9,11}.\n *\n * @category Scales\n */\nexport const OCTATONIC_WHOLE_HALF_MASK = maskFromOffsets([0, 2, 3, 5, 6, 8, 9, 11]);\n\n/**\n * Chromatic scale: all twelve pitch classes.\n *\n * @category Scales\n */\nexport const CHROMATIC_MASK = 0b111111111111;\n\n/**\n * Named scale masks addressable by {@link scaleByName}.\n *\n * @category Scales\n */\nexport const NAMED_SCALES: Record<string, number> = {\n major: MAJOR_MASK,\n ionian: MAJOR_MASK,\n naturalMinor: NATURAL_MINOR_MASK,\n aeolian: NATURAL_MINOR_MASK,\n harmonicMinor: HARMONIC_MINOR_MASK,\n melodicMinor: MELODIC_MINOR_MASK,\n dorian: DORIAN_MASK,\n phrygian: PHRYGIAN_MASK,\n lydian: LYDIAN_MASK,\n mixolydian: MIXOLYDIAN_MASK,\n locrian: LOCRIAN_MASK,\n majorPentatonic: MAJOR_PENTATONIC_MASK,\n minorPentatonic: MINOR_PENTATONIC_MASK,\n blues: BLUES_MASK,\n wholeTone: WHOLE_TONE_MASK,\n octatonicHalfWhole: OCTATONIC_HALF_WHOLE_MASK,\n octatonicWholeHalf: OCTATONIC_WHOLE_HALF_MASK,\n chromatic: CHROMATIC_MASK,\n};\n","import type { KeyScale } from '../../core/types.js';\nimport { MAJOR_MASK, NAMED_SCALES, NATURAL_MINOR_MASK } from './masks.js';\n\n/**\n * Build a `KeyScale` for a major key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { majorKey } from '@libraz/libcantus';\n * const cMajor = majorKey(0); // C major: { rootPc: 0, modeMask12: MAJOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function majorKey(rootPc: number): KeyScale {\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: MAJOR_MASK };\n}\n\n/**\n * Build a `KeyScale` for a natural-minor key on the given root pitch class.\n *\n * @example\n * ```ts\n * import { minorKey } from '@libraz/libcantus';\n * const aMinor = minorKey(9); // A natural minor: { rootPc: 9, modeMask12: NATURAL_MINOR_MASK }\n * ```\n *\n * @category Scales\n */\nexport function minorKey(rootPc: number): KeyScale {\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: NATURAL_MINOR_MASK };\n}\n\n/**\n * Build a `KeyScale` from a named scale (see {@link NAMED_SCALES}).\n *\n * @param name The scale name, e.g. `'dorian'` or `'harmonicMinor'`.\n * @param rootPc The root pitch class.\n * @returns The key/scale.\n * @throws If the name is not a known scale.\n *\n * @example\n * ```ts\n * import { scaleByName } from '@libraz/libcantus';\n * const dDorian = scaleByName('dorian', 2); // D Dorian, rootPc 2\n * ```\n *\n * @category Scales\n */\nexport function scaleByName(name: string, rootPc: number): KeyScale {\n const mask = NAMED_SCALES[name];\n if (mask === undefined) {\n throw new Error(`Unknown scale: ${name}`);\n }\n return { rootPc: ((rootPc % 12) + 12) % 12, modeMask12: mask };\n}\n","import type { KeyScale } from '../../core/types.js';\nimport { scaleTonesInDegreeOrder } from '../scale/index.js';\n\n/**\n * Chord quality identifiers understood by the chord builder.\n *\n * @category Chords\n */\nexport type ChordQuality =\n | 'maj'\n | 'min'\n | 'dim'\n | 'aug'\n | 'maj7'\n | 'min7'\n | 'dom7'\n | 'dim7'\n | 'm7b5'\n | 'minMaj7'\n | 'aug7'\n | 'augMaj7'\n | 'majb5'\n | '6'\n | 'min6'\n | '6/9'\n | 'sus2'\n | 'sus4'\n | 'add9'\n | 'add11'\n | 'maj9'\n | 'min9'\n | 'dom9'\n | '7b9'\n | '7#9'\n | '7#11'\n | '7b13'\n | '11'\n | '13'\n | '5';\n\n/**\n * A bare spelled pitch used as an enharmonic hint: a diatonic letter\n * (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).\n * Mirrors the letter/alter half of the pitch module's `Note` without an octave.\n *\n * @category Chords\n */\nexport type PitchSpelling = {\n letter: number;\n alter: number;\n};\n\n/**\n * A chord expressed as a root pitch class plus semitone offsets.\n *\n * `rootSpelling`/`bassSpelling` are optional enharmonic hints recorded by\n * parsers (e.g. `parseChordSymbol('Bb7')`) so formatters can reproduce the\n * original spelling instead of defaulting to sharps. Consumers may ignore\n * them; a hint is only trusted when its pitch class still matches the\n * corresponding `rootPc`/`bassPc`.\n *\n * @category Chords\n */\nexport type Chord = {\n rootPc: number;\n quality: ChordQuality;\n intervals: number[];\n bassPc?: number;\n rootSpelling?: PitchSpelling;\n bassSpelling?: PitchSpelling;\n};\n\n/**\n * A chord positioned in time: a root pitch class and quality placed at a beat,\n * with optional harmonic-analysis hints. Serves as the neutral exchange record\n * between progression generators and timeline analysers.\n *\n * @category Composition\n */\nexport type ChordSpan = {\n rootPc: number;\n quality: ChordQuality;\n startBeat: number;\n bassPc?: number;\n /** 0-based scale degree of the chord root, when known. */\n degree?: number;\n /** True when the chord is a secondary dominant tonicizing another degree. */\n secondaryDominant?: boolean;\n};\n\n/** Semitone offsets from the root for each supported chord quality. */\nconst QUALITY_INTERVALS: Record<ChordQuality, number[]> = {\n maj: [0, 4, 7],\n min: [0, 3, 7],\n dim: [0, 3, 6],\n aug: [0, 4, 8],\n majb5: [0, 4, 6],\n maj7: [0, 4, 7, 11],\n min7: [0, 3, 7, 10],\n dom7: [0, 4, 7, 10],\n dim7: [0, 3, 6, 9],\n m7b5: [0, 3, 6, 10],\n minMaj7: [0, 3, 7, 11],\n aug7: [0, 4, 8, 10],\n augMaj7: [0, 4, 8, 11],\n '6': [0, 4, 7, 9],\n min6: [0, 3, 7, 9],\n '6/9': [0, 4, 7, 9, 14],\n sus2: [0, 2, 7],\n sus4: [0, 5, 7],\n add9: [0, 4, 7, 14],\n add11: [0, 4, 7, 17],\n maj9: [0, 4, 7, 11, 14],\n min9: [0, 3, 7, 10, 14],\n dom9: [0, 4, 7, 10, 14],\n '7b9': [0, 4, 7, 10, 13],\n '7#9': [0, 4, 7, 10, 15],\n '7#11': [0, 4, 7, 10, 18],\n '7b13': [0, 4, 7, 10, 20],\n '11': [0, 7, 10, 14, 17],\n '13': [0, 4, 7, 10, 14, 21],\n '5': [0, 7],\n};\n\n/** Reduce a value to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/**\n * All chord qualities the builder understands, longest templates last.\n *\n * @category Chords\n */\nexport function chordQualities(): ChordQuality[] {\n return Object.keys(QUALITY_INTERVALS) as ChordQuality[];\n}\n\n/**\n * Build a chord rooted on a diatonic scale degree.\n *\n * The root pitch class is the degree's diatonic pitch class in `key`; the\n * quality's interval template is attached unchanged. Degrees beyond the scale\n * length wrap around.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param ext Chord quality to apply.\n * @param key Key context supplying the diatonic root.\n * @returns The constructed chord.\n *\n * @category Chords\n */\nexport function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScale): Chord {\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n const index = length > 0 ? ((degree % length) + length) % length : 0;\n const rootPc = tones[index] ?? pitchClass(key.rootPc);\n return { rootPc, quality: ext, intervals: [...QUALITY_INTERVALS[ext]] };\n}\n\n/**\n * Build a chord from an explicit root pitch class and quality.\n *\n * @param rootPc Root pitch class (0..11).\n * @param quality Chord quality supplying the interval template.\n * @param bassPc Optional slash-chord bass pitch class.\n * @returns The constructed chord.\n *\n * @example\n * ```ts\n * import { makeChord } from '@libraz/libcantus';\n * const cmaj7 = makeChord(0, 'maj7');\n * // { rootPc: 0, quality: 'maj7', intervals: [0, 4, 7, 11] }\n * ```\n *\n * @category Chords\n */\nexport function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord {\n const chord: Chord = {\n rootPc: pitchClass(rootPc),\n quality,\n intervals: [...QUALITY_INTERVALS[quality]],\n };\n if (bassPc !== undefined) {\n chord.bassPc = pitchClass(bassPc);\n }\n return chord;\n}\n\n/**\n * Get the sorted, deduplicated pitch classes of a chord.\n *\n * @param chord The chord to enumerate.\n * @returns The chord's pitch classes, sorted ascending in [0, 11].\n *\n * @example\n * ```ts\n * import { makeChord, chordPitchClasses } from '@libraz/libcantus';\n * chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]\n * ```\n *\n * @category Chords\n */\nexport function chordPitchClasses(chord: Chord): number[] {\n const set = new Set<number>();\n for (const interval of chord.intervals) {\n set.add(pitchClass(chord.rootPc + interval));\n }\n return [...set].sort((a, b) => a - b);\n}\n\n/** The chord-tone offsets present in a chord, reduced to pitch-class space. */\nfunction chordToneOffsets(chord: Chord): Set<number> {\n return new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));\n}\n\n/**\n * Determine a pitch's harmonic role within a chord.\n *\n * The role is derived from the pitch's interval above the chord root, reduced\n * modulo 12. A major sixth (9) reads as a `sixth` for sixth chords but as a\n * `seventh` for a diminished-seventh chord; ninths and other tensions have no\n * basic role and return null.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @returns The chord-tone role, or null if the pitch has no basic role.\n *\n * @category Chords\n */\nexport function chordToneRole(\n pitch: number,\n chord: Chord,\n): 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | null {\n const interval = (pitchClass(pitch) - pitchClass(chord.rootPc) + 12) % 12;\n const tones = chordToneOffsets(chord);\n if (interval === 0) {\n return 'root';\n }\n if (interval === 3 || interval === 4) {\n return tones.has(interval) ? 'third' : null;\n }\n if (interval === 7) {\n return tones.has(7) ? 'fifth' : null;\n }\n if (interval === 6) {\n // A diminished fifth is the chord's fifth only when no perfect fifth is\n // present; alongside a perfect fifth it is a #11 tension, not a fifth.\n return tones.has(6) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 8) {\n // An augmented fifth is the chord's fifth only without a perfect fifth;\n // alongside a perfect fifth it is a b13 tension, not a fifth.\n return tones.has(8) && !tones.has(7) ? 'fifth' : null;\n }\n if (interval === 9) {\n const hasHigherSeventh = tones.has(10) || tones.has(11);\n const isDiminishedSeventh = tones.has(3) && tones.has(6) && !hasHigherSeventh;\n if (isDiminishedSeventh) {\n return 'seventh';\n }\n if (tones.has(9) && !hasHigherSeventh) {\n return 'sixth';\n }\n return null;\n }\n if (interval === 10 || interval === 11) {\n return tones.has(interval) ? 'seventh' : null;\n }\n return null;\n}\n\n/** Classify a stacked-thirds triad into a chord quality from its interval set. */\nfunction classifyTriad(thirdIc: number, fifthIc: number): ChordQuality {\n if (thirdIc === 4 && fifthIc === 8) {\n return 'aug';\n }\n if (thirdIc === 4 && fifthIc === 6) {\n return 'majb5';\n }\n if (thirdIc === 3 && fifthIc === 6) {\n return 'dim';\n }\n if (thirdIc === 3) {\n return 'min';\n }\n return 'maj';\n}\n\n/** Classify a stacked-thirds seventh chord into a chord quality. */\nfunction classifySeventh(thirdIc: number, fifthIc: number, seventhIc: number): ChordQuality {\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 9) {\n return 'dim7';\n }\n if (thirdIc === 3 && fifthIc === 6 && seventhIc === 10) {\n return 'm7b5';\n }\n if (thirdIc === 3 && seventhIc === 11) {\n return 'minMaj7';\n }\n if (thirdIc === 3) {\n return 'min7';\n }\n if (fifthIc === 8 && seventhIc === 10) {\n return 'aug7';\n }\n if (fifthIc === 8 && seventhIc === 11) {\n return 'augMaj7';\n }\n if (seventhIc === 11) {\n return 'maj7';\n }\n return 'dom7';\n}\n\n/** Stack scale thirds from a scale degree into a chord of `size` notes. */\nfunction stackThirds(degree: number, key: KeyScale, size: 3 | 4): Chord {\n const tones = scaleTonesInDegreeOrder(key);\n const length = tones.length;\n if (length !== 7) {\n throw new Error(\n `diatonic chord stacking requires a heptatonic scale (received ${length} tones)`,\n );\n }\n const idx = (step: number) => (((degree + step) % length) + length) % length;\n const rootPc = tones[idx(0)] ?? pitchClass(key.rootPc);\n const offsets = [0, 2, 4, 6].slice(0, size).map((step) => {\n const pc = tones[idx(step)] ?? rootPc;\n return (((pc - rootPc) % 12) + 12) % 12;\n });\n const thirdIc = offsets[1] ?? 4;\n const fifthIc = offsets[2] ?? 7;\n const quality =\n size === 3\n ? classifyTriad(thirdIc, fifthIc)\n : classifySeventh(thirdIc, fifthIc, offsets[3] ?? 11);\n return { rootPc, quality, intervals: offsets };\n}\n\n/**\n * Build the diatonic triad rooted on a scale degree by stacking scale thirds.\n *\n * Unlike {@link chordFromDegree}, the chord quality is derived from the scale\n * rather than supplied, so degrees yield their scale-correct triads (e.g. a\n * diminished triad on the leading tone of a major key). Intended for heptatonic\n * scales; other scales stack by scale step regardless.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic triad.\n *\n * @example\n * ```ts\n * import { majorKey, diatonicTriad } from '@libraz/libcantus';\n * const tonic = diatonicTriad(0, majorKey(0)); // C major triad (degree 0 of C major)\n * ```\n *\n * @category Chords\n */\nexport function diatonicTriad(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 3);\n}\n\n/**\n * Build the diatonic seventh chord rooted on a scale degree.\n *\n * @param degree 0-based scale degree of the chord root.\n * @param key Key/scale context.\n * @returns The diatonic seventh chord.\n *\n * @category Chords\n */\nexport function diatonicSeventh(degree: number, key: KeyScale): Chord {\n return stackThirds(degree, key, 4);\n}\n","import { isConsonantInterval } from '../../core/interval/index.js';\nimport type { KeyScale } from '../../core/types.js';\n\n/** Reduce a MIDI pitch (or pitch class) to a pitch class in [0, 11]. */\nfunction pitchClass(pitch: number): number {\n return ((Math.trunc(pitch) % 12) + 12) % 12;\n}\n\n/**\n * Whether an upper voice has crossed below a lower voice.\n *\n * @param upperPitch Pitch of the nominally higher voice.\n * @param lowerPitch Pitch of the nominally lower voice.\n * @returns True when the upper voice sits below the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceCrossing(upperPitch: number, lowerPitch: number): boolean {\n return upperPitch < lowerPitch;\n}\n\n/**\n * Whether two simultaneous pitches form a dissonance.\n *\n * @param a First pitch.\n * @param b Second pitch.\n * @param twoVoice When true, the perfect fourth counts as dissonant.\n * @returns True when the vertical interval is dissonant.\n * @category Voicing & Counterpoint\n */\nexport function createsVerticalDissonance(a: number, b: number, twoVoice: boolean): boolean {\n return !isConsonantInterval(a - b, twoVoice);\n}\n\n/**\n * Whether a melodic move is a forbidden leap: a tritone, either seventh (minor\n * or major), or any leap wider than an octave.\n *\n * Operates on the raw absolute distance, so compound leaps are not reduced to\n * their simple class before the octave check.\n *\n * @param prev Starting pitch.\n * @param cur Ending pitch.\n * @returns True when the leap is forbidden in strict counterpoint.\n * @category Voicing & Counterpoint\n */\nexport function isForbiddenMelodicLeap(prev: number, cur: number): boolean {\n const semis = Math.abs(cur - prev);\n return semis === 6 || semis === 10 || semis === 11 || semis > 12;\n}\n\n/** Reduce an interval to its simple class in [0, 11]. */\nfunction simpleClass(semitones: number): number {\n return Math.abs(semitones) % 12;\n}\n\n/** Whether a simple interval class is a perfect kind (unison/octave or fifth). */\nfunction isPerfectClass(cls: number): boolean {\n return cls === 0 || cls === 7;\n}\n\n/** Whether two voices move in the same direction (similar or parallel motion). */\nfunction similarMotion(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0 && aMove > 0 === bMove > 0;\n}\n\n/** Whether both voices actually move (neither is stationary — excludes oblique motion). */\nfunction bothVoicesMove(aMove: number, bMove: number): boolean {\n return aMove !== 0 && bMove !== 0;\n}\n\n/**\n * Whether two voices move into consecutive perfect intervals of the same kind\n * (fifth-to-fifth, octave-to-octave, unison-to-unison).\n *\n * Both true parallels (similar motion) and anti-parallels — the same perfect\n * class reached by contrary motion, e.g. octave to octave with the voices moving\n * in opposite directions — are flagged, as both are forbidden in strict two-voice\n * counterpoint. The rule requires that both voices actually move: oblique motion\n * (either voice stationary) and the no-change case (identical pitches) are excluded.\n *\n * A fifth expanding to a twelfth counts (same perfect class); a fifth moving to\n * an octave does not (different perfect kinds — the direct/hidden case owned by\n * {@link createsHiddenParallelPerfect}).\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!bothVoicesMove(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n return isPerfectClass(nowClass) && nowClass === prevClass;\n}\n\n/**\n * Whether two voices move in consecutive parallel octaves (or unisons) by\n * similar motion.\n *\n * This is a strict subset of {@link createsParallelPerfect}: a similar-motion\n * octave-to-octave is the perfect-class-zero case that predicate already flags\n * (and it additionally catches the contrary-motion anti-parallel). Callers that\n * tally parallel violations should therefore use {@link createsParallelPerfect}\n * alone to avoid double counting; this predicate remains for callers wanting a\n * dedicated similar-motion octave test.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelOctave(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (!similarMotion(aCur - aPrev, bCur - bPrev)) {\n return false;\n }\n return simpleClass(aCur - bCur) === 0 && simpleClass(aPrev - bPrev) === 0;\n}\n\n/**\n * Whether two voices move in consecutive parallel unisons — both landing on the\n * same pitch, having shared a pitch on the previous move.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsParallelUnison(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n if (aCur === aPrev || bCur === bPrev) {\n return false;\n }\n return aCur === bCur && aPrev === bPrev;\n}\n\n/**\n * Whether two voices reach a perfect interval by similar motion from an\n * imperfect one (a hidden/direct fifth or octave).\n *\n * The traditional step exception is applied: the approach is allowed when the\n * upper of the two voices moves by step, so only leaps into the perfect\n * interval are flagged.\n *\n * @category Voicing & Counterpoint\n */\nexport function createsHiddenParallelPerfect(\n aPrev: number,\n aCur: number,\n bPrev: number,\n bCur: number,\n): boolean {\n const aMove = aCur - aPrev;\n const bMove = bCur - bPrev;\n if (!similarMotion(aMove, bMove)) {\n return false;\n }\n const nowClass = simpleClass(aCur - bCur);\n const prevClass = simpleClass(aPrev - bPrev);\n // Approaching the same perfect class (e.g. fifth to fifth) is a true parallel\n // owned by createsParallelPerfect; approaching a different perfect interval\n // (fifth to octave, or vice versa) is the hidden/direct case flagged here.\n if (!isPerfectClass(nowClass) || prevClass === nowClass) {\n return false;\n }\n const upperMove = aCur >= bCur ? aMove : bMove;\n if (Math.abs(upperMove) <= 2) {\n return false; // upper voice moves by step — direct interval is acceptable\n }\n return true;\n}\n\n/**\n * Whether two voices overlap: the upper voice descends below where the lower\n * voice just was, or the lower voice rises above where the upper voice just was.\n * Distinct from a simultaneous voice crossing.\n *\n * @param upperPrev Previous pitch of the upper voice.\n * @param upperCur Current pitch of the upper voice.\n * @param lowerPrev Previous pitch of the lower voice.\n * @param lowerCur Current pitch of the lower voice.\n * @category Voicing & Counterpoint\n */\nexport function createsVoiceOverlap(\n upperPrev: number,\n upperCur: number,\n lowerPrev: number,\n lowerCur: number,\n): boolean {\n return upperCur < lowerPrev || lowerCur > upperPrev;\n}\n\n/**\n * Whether two adjacent upper voices are spaced more than a maximum apart\n * (commonly an octave). Bass-to-tenor spacing is conventionally exempt, so this\n * is meant for the upper voice pairs.\n *\n * @param upperPitch Pitch of the higher voice.\n * @param lowerPitch Pitch of the lower voice.\n * @param maxSemitones Maximum allowed spacing in semitones (default an octave).\n * @category Voicing & Counterpoint\n */\nexport function exceedsSpacing(upperPitch: number, lowerPitch: number, maxSemitones = 12): boolean {\n return Math.abs(upperPitch - lowerPitch) > maxSemitones;\n}\n\n/**\n * Whether a leading tone resolves correctly upward to the tonic.\n *\n * @param prev The leading-tone pitch.\n * @param cur The following pitch.\n * @param key Key context supplying the tonic.\n * @returns True when `prev` is the leading tone and `cur` is the tonic a step above.\n * @category Voicing & Counterpoint\n */\nexport function isLeadingToneResolution(prev: number, cur: number, key: KeyScale): boolean {\n const tonic = pitchClass(key.rootPc);\n const leading = (tonic + 11) % 12;\n if (pitchClass(prev) !== leading || pitchClass(cur) !== tonic) {\n return false;\n }\n return cur > prev && cur - prev <= 2;\n}\n"],"mappings":"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","names":["pitchClass","pitchClass"]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-N6IHQUOX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  negativeHarmonyMirror
3
- } from "./chunk-GQKN5KDT.js";
3
+ } from "./chunk-IT2O4XFE.js";
4
4
  import {
5
5
  analyzeChord,
6
6
  borrowedSource,
@@ -12,7 +12,7 @@ import {
12
12
  isBorrowedChord,
13
13
  isMinorKey,
14
14
  romanToChord
15
- } from "./chunk-LN7ZZMD2.js";
15
+ } from "./chunk-CHMXELDS.js";
16
16
  import {
17
17
  availableTensions,
18
18
  avoidNotes,
@@ -20,11 +20,13 @@ import {
20
20
  formatChordSymbol,
21
21
  parseChordSymbol,
22
22
  spellChord,
23
+ spellChordFromRoot,
24
+ spellPitchClass,
23
25
  spellScale,
24
26
  voiceChord,
25
27
  voiceChordStyled,
26
28
  voiceProgression
27
- } from "./chunk-XTC3X6MS.js";
29
+ } from "./chunk-C5XYKUVG.js";
28
30
  import {
29
31
  formatNote,
30
32
  midiToNote,
@@ -44,7 +46,7 @@ import {
44
46
  minorKey,
45
47
  scaleByName,
46
48
  scaleTonesInDegreeOrder
47
- } from "./chunk-7Y7WF64D.js";
49
+ } from "./chunk-MIWO425M.js";
48
50
 
49
51
  // src/model/shared.ts
50
52
  function mod12(n) {
@@ -338,7 +340,11 @@ var Chord = class _Chord {
338
340
  * @param key Optional key context for analysis methods.
339
341
  */
340
342
  constructor(data, key) {
341
- this.#data = copyChord(data);
343
+ const copy = copyChord(data);
344
+ if (copy.rootSpelling === void 0 && key !== void 0) {
345
+ copy.rootSpelling = spellPitchClass(copy.rootPc, key.tonic.data, key.scale);
346
+ }
347
+ this.#data = copy;
342
348
  this.#key = key;
343
349
  }
344
350
  /**
@@ -350,8 +356,13 @@ var Chord = class _Chord {
350
356
  * @returns The chord (without key context).
351
357
  */
352
358
  static of(root, quality, bass) {
353
- const rootPc = typeof root === "string" ? Note.of(root).pitchClass : root;
354
- return new _Chord(makeChord(rootPc, quality, bass));
359
+ if (typeof root === "string") {
360
+ const rootNote = Note.of(root);
361
+ const data = makeChord(rootNote.pitchClass, quality, bass);
362
+ data.rootSpelling = { letter: rootNote.letter, alter: rootNote.alter };
363
+ return new _Chord(data);
364
+ }
365
+ return new _Chord(makeChord(root, quality, bass));
355
366
  }
356
367
  /**
357
368
  * Wrap an existing plain chord object.
@@ -378,8 +389,8 @@ var Chord = class _Chord {
378
389
  * @param pitches MIDI pitches or bare pitch classes.
379
390
  * @returns Ranked chord interpretations (may be empty).
380
391
  */
381
- static detect(pitches) {
382
- return detectChord(pitches).map(
392
+ static detect(pitches, opts) {
393
+ return detectChord(pitches, opts).map(
383
394
  (match) => new _Chord(makeChord(match.rootPc, match.quality, match.bassPc))
384
395
  );
385
396
  }
@@ -389,8 +400,8 @@ var Chord = class _Chord {
389
400
  * @param pitches MIDI pitches or bare pitch classes.
390
401
  * @returns The top-ranked chord, or null when nothing matches.
391
402
  */
392
- static detectBest(pitches) {
393
- const best = detectChordBest(pitches);
403
+ static detectBest(pitches, opts) {
404
+ const best = detectChordBest(pitches, opts);
394
405
  return best === null ? null : new _Chord(best);
395
406
  }
396
407
  /** The root pitch class (0..11). */
@@ -548,8 +559,14 @@ var Chord = class _Chord {
548
559
  const data = copyChord(this.#data);
549
560
  if (index === 0) {
550
561
  delete data.bassPc;
562
+ delete data.bassSpelling;
551
563
  } else {
552
564
  data.bassPc = mod12(this.#data.rootPc + (intervals[index] ?? 0));
565
+ const rootSpelling = data.rootSpelling ?? (this.#key ? spellPitchClass(data.rootPc, this.#key.tonic.data, this.#key.scale) : spellPitchClassBare(data.rootPc, "sharp"));
566
+ const bassSpelling = spellChordFromRoot(data, rootSpelling)[index];
567
+ if (bassSpelling !== void 0) {
568
+ data.bassSpelling = bassSpelling;
569
+ }
553
570
  }
554
571
  return new _Chord(data, this.#key);
555
572
  }
@@ -904,4 +921,4 @@ export {
904
921
  Interval,
905
922
  Key
906
923
  };
907
- //# sourceMappingURL=chunk-COKTX2G5.js.map
924
+ //# sourceMappingURL=chunk-SGU7SPLK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/model/shared.ts","../src/model/note.ts","../src/model/progression.ts","../src/model/chord.ts","../src/model/interval.ts","../src/model/key.ts"],"sourcesContent":["import { midiToNote, type Note as NoteData } from '../core/pitch/index.js';\n\n/** Reduce any integer to a pitch class in [0, 11]. */\nexport function mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\n/** Spell a bare pitch class as an octave-less note with a sharp/flat preference. */\nexport function spellPitchClassBare(pc: number, spelling: 'sharp' | 'flat'): NoteData {\n const spelled = midiToNote(60 + mod12(pc), spelling);\n return { letter: spelled.letter, alter: spelled.alter };\n}\n","import {\n formatNote,\n midiToNote,\n type Note as NoteData,\n noteToMidi,\n noteToPitchClass,\n parseNote,\n type SpelledInterval,\n spelledInterval,\n} from '../core/pitch/index.js';\nimport { spellPitchClassBare } from './shared.js';\n\n/** Defensive copy of a plain note. */\nfunction copyNote(data: NoteData): NoteData {\n const copy: NoteData = { letter: data.letter, alter: data.alter };\n if (data.octave !== undefined) {\n copy.octave = data.octave;\n }\n return copy;\n}\n\n/**\n * An immutable spelled note: a diatonic letter plus a chromatic alteration and\n * an optional octave. Wraps the plain note object and delegates to the pitch\n * module; every transformation returns a new instance.\n *\n * @category Class API\n * @example\n * ```ts\n * import { Note } from '@libraz/libcantus';\n * Note.of('C4').transpose(7).name; // 'G4'\n * ```\n */\nexport class Note {\n readonly #data: NoteData;\n\n /**\n * Wrap a plain note object.\n *\n * @param data The spelled note; it is copied, never retained or mutated.\n */\n constructor(data: NoteData) {\n this.#data = copyNote(data);\n }\n\n /**\n * Parse scientific pitch notation (e.g. `'C#4'`, `'Bb'`, `'F##3'`).\n *\n * @param name The note text.\n * @returns The parsed note.\n * @throws If the text is not a valid note.\n */\n static of(name: string): Note {\n return new Note(parseNote(name));\n }\n\n /**\n * Name a MIDI number (middle C = C4 = 60) as a note.\n *\n * @param midi The MIDI number.\n * @param spelling Whether to prefer sharps or flats for black keys.\n * @returns The spelled note, with octave.\n */\n static fromMidi(midi: number, spelling: 'sharp' | 'flat' = 'sharp'): Note {\n return new Note(midiToNote(midi, spelling));\n }\n\n /**\n * Wrap an existing plain note object.\n *\n * @param data The plain note.\n * @returns The wrapped note.\n */\n static fromData(data: NoteData): Note {\n return new Note(data);\n }\n\n /** The note rendered as scientific pitch notation, e.g. `'G4'` or `'Bb'`. */\n get name(): string {\n return formatNote(this.#data);\n }\n\n /** The pitch class (0..11), ignoring octave. */\n get pitchClass(): number {\n return noteToPitchClass(this.#data);\n }\n\n /**\n * The MIDI number (middle C = C4 = 60).\n *\n * @throws If the note has no octave and therefore no fixed pitch.\n */\n get midi(): number {\n if (this.#data.octave === undefined) {\n throw new Error(`note ${this.name} has no octave, so it has no MIDI number`);\n }\n return noteToMidi(this.#data);\n }\n\n /** The diatonic letter number: 0..6 for C..B. */\n get letter(): number {\n return this.#data.letter;\n }\n\n /** The chromatic alteration in semitones: -1 flat, 0 natural, +1 sharp, ... */\n get alter(): number {\n return this.#data.alter;\n }\n\n /** The octave (scientific pitch notation), or undefined for a bare pitch class. */\n get octave(): number | undefined {\n return this.#data.octave;\n }\n\n /** A copy of the underlying plain note object. */\n get data(): NoteData {\n return copyNote(this.#data);\n }\n\n /**\n * Transpose by a signed number of semitones.\n *\n * When the note carries an octave the transposition happens in MIDI space and\n * the result is spelled with a sharp preference. An octave-less note stays\n * octave-less: only its pitch class is moved. Transposing by zero is the\n * identity: the original spelling is preserved (no enharmonic respelling).\n *\n * @param semitones The signed semitone offset.\n * @returns The transposed note.\n */\n transpose(semitones: number): Note {\n if (semitones === 0) {\n return new Note(this.#data);\n }\n if (this.#data.octave !== undefined) {\n return Note.fromMidi(this.midi + semitones);\n }\n return new Note(spellPitchClassBare(this.pitchClass + semitones, 'sharp'));\n }\n\n /**\n * The spelled interval from this note to another.\n *\n * @param other The second note.\n * @returns The diatonic number, quality, and semitone span.\n */\n intervalTo(other: Note): SpelledInterval {\n return spelledInterval(this.#data, other.#data);\n }\n\n /**\n * Whether another note has the same letter, alteration, and octave.\n *\n * @param other The note to compare.\n * @returns True if the spellings are identical.\n */\n equals(other: Note): boolean {\n return (\n this.#data.letter === other.#data.letter &&\n this.#data.alter === other.#data.alter &&\n this.#data.octave === other.#data.octave\n );\n }\n\n /**\n * The plain note data, for JSON serialization.\n *\n * Private class fields do not serialize, so an explicit `toJSON` keeps\n * `JSON.stringify(note)` from collapsing to `{}`.\n *\n * @returns A copy of the underlying plain note object.\n */\n toJSON(): NoteData {\n return this.data;\n }\n}\n","import {\n type Cadence,\n type ChordAnalysis,\n detectCadence,\n type HarmonicFunction,\n} from '../analyze/functional/index.js';\nimport type { Note as NoteData } from '../core/pitch/index.js';\nimport type { KeyScale } from '../core/types.js';\nimport type { Chord as ChordData } from '../theory/chord/index.js';\nimport { type VoicingOptions, voiceProgression } from '../theory/voicing/index.js';\nimport type { Chord } from './chord.js';\nimport type { Key } from './key.js';\n\n/**\n * An immutable ordered sequence of chords, optionally carrying a {@link Key}\n * context shared by its analysis methods.\n *\n * @category Class API\n * @example\n * ```ts\n * import { Key } from '@libraz/libcantus';\n * const key = Key.major('C');\n * key.chord(1).progressionTo(key.chord(4), key.chord(0)).roman();\n * // ['ii', 'V', 'I']\n * ```\n */\nexport class Progression {\n readonly #chords: readonly Chord[];\n readonly #key: Key | undefined;\n\n /**\n * Wrap a chord sequence.\n *\n * @param chords The chords in order; the array is copied.\n * @param key Optional key context for analysis methods.\n */\n constructor(chords: readonly Chord[], key?: Key) {\n this.#chords = [...chords];\n this.#key = key;\n }\n\n /** A copy of the chord sequence. */\n get chords(): readonly Chord[] {\n return [...this.#chords];\n }\n\n /** The carried key context, if any. */\n get key(): Key | undefined {\n return this.#key;\n }\n\n /** The number of chords. */\n get length(): number {\n return this.#chords.length;\n }\n\n /**\n * A copy of this progression with a chord appended.\n *\n * @param chord The chord to append.\n * @returns The new progression.\n */\n add(chord: Chord): Progression {\n return new Progression([...this.#chords, chord], this.#key);\n }\n\n /**\n * A copy of this progression carrying the given key context.\n *\n * @param key The key context to attach.\n * @returns The new progression.\n */\n withKey(key: Key): Progression {\n return new Progression(this.#chords, key);\n }\n\n /**\n * Voice the progression with smooth voice leading.\n *\n * @param opts Voicing options; defaults to four SATB voices.\n * @returns One ascending voicing (MIDI pitches) per chord.\n * @throws If any chord admits no voicing within the given ranges.\n */\n voice(opts?: VoicingOptions): number[][] {\n return voiceProgression(\n this.#chords.map((chord) => chord.data),\n opts,\n );\n }\n\n /**\n * The Roman numeral of each chord in a key.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns One numeral per chord.\n * @throws If no key is given and none is carried.\n */\n roman(key?: Key): string[] {\n const resolved = this.#resolveKey(key);\n return this.#chords.map((chord) => chord.roman(resolved));\n }\n\n /**\n * The harmonic function of each chord in a key.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns One function per chord.\n * @throws If no key is given and none is carried.\n */\n functions(key?: Key): HarmonicFunction[] {\n const resolved = this.#resolveKey(key);\n return this.#chords.map((chord) => chord.function(resolved));\n }\n\n /**\n * Analyze every chord and classify the closing cadence.\n *\n * The cadence is detected on the final chord pair and is null when the\n * progression has fewer than two chords.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns Per-chord analyses and the closing cadence.\n * @throws If no key is given and none is carried.\n */\n analyze(key?: Key): { chords: ChordAnalysis[]; cadence: Cadence } {\n const resolved = this.#resolveKey(key);\n const chords = this.#chords.map((chord) => chord.analyze(resolved));\n const from = this.#chords[this.#chords.length - 2];\n const to = this.#chords[this.#chords.length - 1];\n const cadence =\n from !== undefined && to !== undefined\n ? detectCadence(from.data, to.data, resolved.scale)\n : null;\n return { chords, cadence };\n }\n\n /**\n * The plain progression data, for JSON serialization.\n *\n * Private class fields do not serialize, so an explicit `toJSON` keeps\n * `JSON.stringify(progression)` from collapsing to `{}`. The chords are\n * emitted as plain data and the carried key, when present, as its own data.\n *\n * @returns The chord data sequence and the carried key, if any.\n */\n toJSON(): { chords: ChordData[]; key: { scale: KeyScale; tonic: NoteData } | undefined } {\n return {\n chords: this.#chords.map((chord) => chord.data),\n key: this.#key?.toJSON(),\n };\n }\n\n /** Resolve the key for an analysis method: explicit first, then carried. */\n #resolveKey(key?: Key): Key {\n const resolved = key ?? this.#key;\n if (resolved === undefined) {\n throw new Error('progression has no key context; pass a Key or attach one with withKey()');\n }\n return resolved;\n }\n}\n","import { type DetectChordOptions, detectChord, detectChordBest } from '../analyze/detect/index.js';\nimport {\n analyzeChord,\n type BorrowedSource,\n borrowedSource,\n type ChordAnalysis,\n chordToRoman,\n functionOf,\n type HarmonicFunction,\n isBorrowedChord,\n} from '../analyze/functional/index.js';\nimport { negativeHarmonyMirror } from '../generate/reharmony/index.js';\nimport {\n type Chord as ChordData,\n type ChordQuality,\n chordPitchClasses,\n makeChord,\n} from '../theory/chord/index.js';\nimport {\n availableTensions,\n avoidNotes,\n type ChordScaleMatch,\n chordScales,\n} from '../theory/chordscale/index.js';\nimport { spellChord, spellChordFromRoot, spellPitchClass } from '../theory/spelling/index.js';\nimport { formatChordSymbol, parseChordSymbol } from '../theory/symbol/index.js';\nimport {\n type StyledVoicingOptions,\n type VoicingOptions,\n voiceChord,\n voiceChordStyled,\n} from '../theory/voicing/index.js';\nimport type { Key } from './key.js';\nimport { Note } from './note.js';\nimport { Progression } from './progression.js';\nimport { mod12, spellPitchClassBare } from './shared.js';\n\n/**\n * Defensive copy of a plain chord.\n *\n * Any enharmonic spelling hints (`rootSpelling`/`bassSpelling`, populated by\n * `parseChordSymbol`) are carried through so a flat-named chord round-trips\n * through the class API; a hint that no longer matches its pitch class is\n * simply ignored by the formatter.\n */\nfunction copyChord(data: ChordData): ChordData {\n const copy: ChordData = {\n rootPc: data.rootPc,\n quality: data.quality,\n intervals: [...data.intervals],\n };\n if (data.bassPc !== undefined) {\n copy.bassPc = data.bassPc;\n }\n if (data.rootSpelling !== undefined) {\n copy.rootSpelling = { letter: data.rootSpelling.letter, alter: data.rootSpelling.alter };\n }\n if (data.bassSpelling !== undefined) {\n copy.bassSpelling = { letter: data.bassSpelling.letter, alter: data.bassSpelling.alter };\n }\n return copy;\n}\n\n/**\n * An immutable chord: a root pitch class, quality, interval template, and\n * optional slash bass, optionally carrying a {@link Key} context. Analysis\n * methods (`roman`, `function`, `analyze`, ...) use an explicitly passed key\n * first and fall back to the carried context.\n *\n * @category Class API\n * @example\n * ```ts\n * import { Chord } from '@libraz/libcantus';\n * Chord.parse('Cmaj7').invert(1).symbol(); // 'Cmaj7/E' (third in the bass)\n * ```\n */\nexport class Chord {\n readonly #data: ChordData;\n readonly #key: Key | undefined;\n\n /**\n * Wrap a plain chord object.\n *\n * @param data The chord; it is copied, never retained or mutated.\n * @param key Optional key context for analysis methods.\n */\n constructor(data: ChordData, key?: Key) {\n const copy = copyChord(data);\n if (copy.rootSpelling === undefined && key !== undefined) {\n copy.rootSpelling = spellPitchClass(copy.rootPc, key.tonic.data, key.scale);\n }\n this.#data = copy;\n this.#key = key;\n }\n\n /**\n * Build a chord from a root and quality.\n *\n * @param root Root as a note name (e.g. `'Eb'`) or a pitch class.\n * @param quality The chord quality.\n * @param bass Optional slash-chord bass pitch class.\n * @returns The chord (without key context).\n */\n static of(root: string | number, quality: ChordQuality, bass?: number): Chord {\n if (typeof root === 'string') {\n const rootNote = Note.of(root);\n const data = makeChord(rootNote.pitchClass, quality, bass);\n data.rootSpelling = { letter: rootNote.letter, alter: rootNote.alter };\n return new Chord(data);\n }\n return new Chord(makeChord(root, quality, bass));\n }\n\n /**\n * Wrap an existing plain chord object.\n *\n * @param data The plain chord.\n * @returns The wrapped chord (without key context).\n */\n static from(data: ChordData): Chord {\n return new Chord(data);\n }\n\n /**\n * Parse a lead-sheet chord symbol (e.g. `'Cmaj7'`, `'F#m7b5'`, `'C/G'`).\n *\n * @param symbol The chord symbol.\n * @returns The chord (without key context).\n * @throws If the root or quality is not recognized.\n */\n static parse(symbol: string): Chord {\n return new Chord(parseChordSymbol(symbol));\n }\n\n /**\n * Identify the chords matching a set of pitches, best interpretation first.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns Ranked chord interpretations (may be empty).\n */\n static detect(pitches: number[], opts?: DetectChordOptions): Chord[] {\n return detectChord(pitches, opts).map(\n (match) => new Chord(makeChord(match.rootPc, match.quality, match.bassPc)),\n );\n }\n\n /**\n * The single best chord interpretation of a pitch set.\n *\n * @param pitches MIDI pitches or bare pitch classes.\n * @returns The top-ranked chord, or null when nothing matches.\n */\n static detectBest(pitches: number[], opts?: DetectChordOptions): Chord | null {\n const best = detectChordBest(pitches, opts);\n return best === null ? null : new Chord(best);\n }\n\n /** The root pitch class (0..11). */\n get rootPc(): number {\n return this.#data.rootPc;\n }\n\n /** The chord quality. */\n get quality(): ChordQuality {\n return this.#data.quality;\n }\n\n /** A copy of the semitone offsets above the root. */\n get intervals(): number[] {\n return [...this.#data.intervals];\n }\n\n /** The slash-chord bass pitch class, or undefined in root position. */\n get bassPc(): number | undefined {\n return this.#data.bassPc;\n }\n\n /** A copy of the underlying plain chord object. */\n get data(): ChordData {\n return copyChord(this.#data);\n }\n\n /** The carried key context, if any. */\n get key(): Key | undefined {\n return this.#key;\n }\n\n /**\n * A copy of this chord carrying the given key context.\n *\n * @param key The key context to attach.\n * @returns The new chord.\n */\n withKey(key: Key): Chord {\n return new Chord(this.#data, key);\n }\n\n /**\n * The chord's sorted, deduplicated pitch classes.\n *\n * @returns Pitch classes ascending in [0, 11].\n */\n pitchClasses(): number[] {\n return chordPitchClasses(this.#data);\n }\n\n /**\n * The chord's Roman numeral in a key.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns The Roman numeral string.\n * @throws If no key is given and none is carried.\n */\n roman(key?: Key): string {\n return chordToRoman(this.#data, this.#resolveKey(key).scale);\n }\n\n /**\n * The chord's harmonic function (tonic / subdominant / dominant) in a key.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns The harmonic function.\n * @throws If no key is given and none is carried.\n */\n function(key?: Key): HarmonicFunction {\n return functionOf(this.#data, this.#resolveKey(key).scale);\n }\n\n /**\n * Full functional analysis: function, borrowing, and Roman numeral.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns The chord analysis.\n * @throws If no key is given and none is carried.\n */\n analyze(key?: Key): ChordAnalysis {\n return analyzeChord(this.#data, this.#resolveKey(key).scale);\n }\n\n /**\n * Whether the chord is borrowed from the parallel mode (modal interchange).\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns True if the chord is borrowed.\n * @throws If no key is given and none is carried.\n */\n isBorrowed(key?: Key): boolean {\n return isBorrowedChord(this.#data, this.#resolveKey(key).scale);\n }\n\n /**\n * The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.\n *\n * @param key Key to analyze in; falls back to the carried context.\n * @returns The borrowing source, or null.\n * @throws If no key is given and none is carried.\n */\n borrowedSource(key?: Key): BorrowedSource {\n return borrowedSource(this.#data, this.#resolveKey(key).scale);\n }\n\n /**\n * The chord rendered as a lead-sheet symbol (e.g. `'Cmaj7'`, `'F#m7'`, `'C/G'`).\n *\n * @param opts Set `flats: true` to spell the root/bass with flats.\n * @returns The chord symbol.\n */\n symbol(opts?: { flats?: boolean }): string {\n return formatChordSymbol(this.#data, opts);\n }\n\n /**\n * Realize the chord as one MIDI pitch per voice, ascending.\n *\n * @param opts Voicing options; defaults to four SATB voices.\n * @returns MIDI pitches, ascending, one per voice.\n * @throws If no voicing fits the given ranges.\n */\n voice(opts?: VoicingOptions): number[] {\n return voiceChord(this.#data, opts);\n }\n\n /**\n * Realize the chord as a single styled voicing (`close`, `drop2`, `drop3`,\n * `shell`, or `rootless`), optionally constraining the top voice.\n *\n * @param opts Styled-voicing options; defaults to a close-position voicing.\n * @returns MIDI pitches, ascending.\n */\n styledVoicing(opts?: StyledVoicingOptions): number[] {\n return voiceChordStyled(this.#data, opts);\n }\n\n /**\n * The negative-harmony mirror of the chord about the key's tonic–dominant\n * axis (major becomes minor and vice versa).\n *\n * @param key Key providing the reflection axis; falls back to the carried\n * context.\n * @returns The mirrored chord, keeping any key context.\n * @throws If no key is given and none is carried.\n */\n negativeHarmony(key?: Key): Chord {\n const resolved = this.#resolveKey(key);\n // Retain the key that anchored the reflection (explicit first, then carried)\n // so a later no-arg analysis method still has a key context.\n return new Chord(negativeHarmonyMirror(this.#data, resolved.scale), key ?? this.#key);\n }\n\n /**\n * The n-th inversion: a copy whose bass is the chord tone `n` steps above the\n * root in the interval template (`invert(1)` puts the third in the bass).\n * `n` wraps around the template length; negative values count backwards.\n *\n * `invert(0)` (and any `n` that wraps to it) is root position, so it carries\n * no slash bass and equals the original chord.\n *\n * @param n The inversion number.\n * @returns The inverted chord, keeping any key context.\n * @throws If the chord has no intervals.\n */\n invert(n: number): Chord {\n const intervals = this.#data.intervals;\n const length = intervals.length;\n if (length === 0) {\n throw new Error('cannot invert a chord with no intervals');\n }\n const index = ((n % length) + length) % length;\n const data = copyChord(this.#data);\n if (index === 0) {\n delete data.bassPc;\n delete data.bassSpelling;\n } else {\n data.bassPc = mod12(this.#data.rootPc + (intervals[index] ?? 0));\n const rootSpelling =\n data.rootSpelling ??\n (this.#key\n ? spellPitchClass(data.rootPc, this.#key.tonic.data, this.#key.scale)\n : spellPitchClassBare(data.rootPc, 'sharp'));\n const bassSpelling = spellChordFromRoot(data, rootSpelling)[index];\n if (bassSpelling !== undefined) {\n data.bassSpelling = bassSpelling;\n }\n }\n return new Chord(data, this.#key);\n }\n\n /**\n * The named scales that fit over this chord, best fit first, rooted on the\n * chord root.\n *\n * @returns The matching scales.\n */\n scales(): ChordScaleMatch[] {\n return chordScales(this.#data);\n }\n\n /**\n * The available tensions (usable non-chord, non-avoid scale tones) of a\n * scale over this chord.\n *\n * @param scaleName A named scale, rooted on the chord root.\n * @returns Tension pitch classes, ascending in [0, 11].\n */\n tensions(scaleName: string): number[] {\n return availableTensions(this.#data, scaleName);\n }\n\n /**\n * The avoid notes (scale tones a semitone above a chord tone) of a scale\n * over this chord.\n *\n * @param scaleName A named scale, rooted on the chord root.\n * @returns Avoid-note pitch classes, ascending in [0, 11].\n */\n avoidNotes(scaleName: string): number[] {\n return avoidNotes(this.#data, scaleName);\n }\n\n /**\n * Spell the chord tones with letter names, root first, in the key's spelling.\n *\n * @param key Key providing the spelled tonic; falls back to the carried\n * context.\n * @returns Spelled octave-less notes in the chord's own (tertian) order.\n * @throws If no key is given and none is carried.\n */\n spell(key?: Key): Note[] {\n const resolved = this.#resolveKey(key);\n return spellChord(this.#data, resolved.tonic.data, resolved.scale).map(\n (note) => new Note(note),\n );\n }\n\n /**\n * Start a progression with this chord followed by others, carrying this\n * chord's key context (if any).\n *\n * @param others The chords following this one.\n * @returns The progression.\n */\n progressionTo(...others: Chord[]): Progression {\n return new Progression([this, ...others], this.#key);\n }\n\n /**\n * Whether another chord has the same root, quality, intervals, and bass.\n * Key context is not compared.\n *\n * @param other The chord to compare.\n * @returns True if the chord data is identical.\n */\n equals(other: Chord): boolean {\n const a = this.#data;\n const b = other.#data;\n return (\n a.rootPc === b.rootPc &&\n a.quality === b.quality &&\n a.bassPc === b.bassPc &&\n a.intervals.length === b.intervals.length &&\n a.intervals.every((interval, i) => interval === b.intervals[i])\n );\n }\n\n /**\n * The plain chord data, for JSON serialization.\n *\n * @returns A copy of the underlying plain chord object.\n */\n toJSON(): ChordData {\n return this.data;\n }\n\n /** Resolve the key for an analysis method: explicit first, then carried. */\n #resolveKey(key?: Key): Key {\n const resolved = key ?? this.#key;\n if (resolved === undefined) {\n throw new Error('chord has no key context; pass a Key or attach one with withKey()');\n }\n return resolved;\n }\n}\n","import type { SpelledInterval } from '../core/pitch/index.js';\nimport type { Note } from './note.js';\n\n/**\n * An immutable spelled interval value: a diatonic number, a quality label, and\n * a signed semitone span. A thin convenience wrapper over the pitch module's\n * plain interval result.\n *\n * @category Class API\n * @example\n * ```ts\n * import { Interval, Note } from '@libraz/libcantus';\n * Interval.between(Note.of('C4'), Note.of('G4')).name; // 'P5'\n * ```\n */\nexport class Interval {\n readonly #number: number;\n readonly #quality: string;\n readonly #semitones: number;\n\n private constructor(numberValue: number, quality: string, semitones: number) {\n this.#number = numberValue;\n this.#quality = quality;\n this.#semitones = semitones;\n }\n\n /**\n * The spelled interval between two notes.\n *\n * @param a The first note.\n * @param b The second note.\n * @returns The interval from `a` to `b`.\n */\n static between(a: Note, b: Note): Interval {\n const spelled = a.intervalTo(b);\n return new Interval(spelled.number, spelled.quality, spelled.semitones);\n }\n\n /**\n * Build an interval from explicit components.\n *\n * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.\n * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.\n * @param semitones Signed semitone span.\n * @returns The interval.\n */\n static of(numberValue: number, quality: string, semitones: number): Interval {\n return new Interval(numberValue, quality, semitones);\n }\n\n /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */\n get number(): number {\n return this.#number;\n }\n\n /** Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`. */\n get quality(): string {\n return this.#quality;\n }\n\n /** Signed semitone span. */\n get semitones(): number {\n return this.#semitones;\n }\n\n /** A readable label composed of quality and number, e.g. `'P5'` or `'M3'`. */\n get name(): string {\n return `${this.#quality}${this.#number}`;\n }\n\n /**\n * The plain interval data, for JSON serialization.\n *\n * Private class fields do not serialize, so an explicit `toJSON` keeps\n * `JSON.stringify(interval)` from collapsing to `{}`.\n *\n * @returns The diatonic number, quality, and semitone span.\n */\n toJSON(): SpelledInterval {\n return { number: this.#number, quality: this.#quality, semitones: this.#semitones };\n }\n}\n","import { isMinorKey, romanToChord } from '../analyze/functional/index.js';\nimport type { Note as NoteData } from '../core/pitch/index.js';\nimport type { KeyScale } from '../core/types.js';\nimport {\n type ChordQuality,\n chordFromDegree,\n diatonicSeventh,\n diatonicTriad,\n} from '../theory/chord/index.js';\nimport {\n isScaleTone,\n majorKey,\n minorKey,\n scaleByName,\n scaleTonesInDegreeOrder,\n} from '../theory/scale/index.js';\nimport { spellScale } from '../theory/spelling/index.js';\nimport { Chord } from './chord.js';\nimport { Note } from './note.js';\nimport { mod12, spellPitchClassBare } from './shared.js';\n\n/** Resolve a string or numeric key root into a spelled tonic note. */\nfunction resolveTonic(root: string | number, spelling: 'sharp' | 'flat'): Note {\n return typeof root === 'string' ? Note.of(root) : new Note(spellPitchClassBare(root, spelling));\n}\n\n/** Total accidentals a spelled tonic produces across a key's whole scale. */\nfunction accidentalLoad(tonic: NoteData, scale: KeyScale): number {\n return spellScale(tonic, scale).reduce((sum, note) => sum + Math.abs(note.alter), 0);\n}\n\n/**\n * Choose the tonic spelling (sharp- or flat-side) that spells `scale` with the\n * fewest accidentals, so a numeric root never yields a double-flat/double-sharp\n * scale (e.g. pitch class 6 minor spells as F# minor, not Gb minor with Bbb).\n */\nfunction bestTonicForScale(rootPc: number, scale: KeyScale): Note {\n const sharp = spellPitchClassBare(rootPc, 'sharp');\n const flat = spellPitchClassBare(rootPc, 'flat');\n if (sharp.letter === flat.letter && sharp.alter === flat.alter) {\n return new Note(sharp);\n }\n return accidentalLoad(flat, scale) <= accidentalLoad(sharp, scale)\n ? new Note(flat)\n : new Note(sharp);\n}\n\n/**\n * An immutable key/scale: a `KeyScale` (root pitch class plus mode mask) paired\n * with a spelled tonic that anchors letter-name spelling. Acts as the factory\n * for key-aware chords.\n *\n * @category Class API\n * @example\n * ```ts\n * import { Key } from '@libraz/libcantus';\n * Key.major('C').chord(4).symbol(); // 'G' (the diatonic triad on scale degree 4)\n * ```\n */\nexport class Key {\n readonly #scale: KeyScale;\n readonly #tonic: Note;\n\n /**\n * Wrap a key/scale and its spelled tonic.\n *\n * @param scale The key/scale; its root is normalized to a pitch class.\n * @param tonic The spelled tonic anchoring letter-name spelling.\n */\n constructor(scale: KeyScale, tonic: Note) {\n this.#scale = { rootPc: mod12(scale.rootPc), modeMask12: scale.modeMask12 };\n this.#tonic = tonic;\n }\n\n /**\n * A major key.\n *\n * @param root Tonic as a note name (e.g. `'Eb'`) or a pitch class; a numeric\n * root is spelled with whichever accidental side yields the fewest\n * accidentals across the scale.\n * @returns The major key.\n */\n static major(root: string | number): Key {\n if (typeof root === 'string') {\n const tonic = Note.of(root);\n return new Key(majorKey(tonic.pitchClass), tonic);\n }\n const scale = majorKey(root);\n return new Key(scale, bestTonicForScale(root, scale));\n }\n\n /**\n * A natural-minor key.\n *\n * @param root Tonic as a note name or a pitch class; a numeric root is\n * spelled with whichever accidental side yields the fewest accidentals\n * across the scale.\n * @returns The minor key.\n */\n static minor(root: string | number): Key {\n if (typeof root === 'string') {\n const tonic = Note.of(root);\n return new Key(minorKey(tonic.pitchClass), tonic);\n }\n const scale = minorKey(root);\n return new Key(scale, bestTonicForScale(root, scale));\n }\n\n /**\n * A key on a named scale (e.g. `'dorian'`, `'harmonicMinor'`).\n *\n * @param name The scale name, a key of the scale module's named-scale table.\n * @param root Tonic as a note name or a pitch class.\n * @returns The key.\n * @throws If the name is not a known scale.\n */\n static named(name: string, root: string | number): Key {\n const tonic = resolveTonic(root, 'sharp');\n return new Key(scaleByName(name, tonic.pitchClass), tonic);\n }\n\n /**\n * Wrap an existing `KeyScale`, synthesizing a spelled tonic when none is\n * given (flat-preferred for minor-third scales, sharp-preferred otherwise).\n *\n * @param scale The key/scale to wrap.\n * @param tonic Optional spelled tonic.\n * @returns The key.\n */\n static of(scale: KeyScale, tonic?: Note): Key {\n const spelled =\n tonic ?? new Note(spellPitchClassBare(scale.rootPc, isMinorKey(scale) ? 'flat' : 'sharp'));\n return new Key(scale, spelled);\n }\n\n /** A copy of the underlying plain `KeyScale`. */\n get scale(): KeyScale {\n return { rootPc: this.#scale.rootPc, modeMask12: this.#scale.modeMask12 };\n }\n\n /** The spelled tonic. */\n get tonic(): Note {\n return this.#tonic;\n }\n\n /** The tonic pitch class (0..11). */\n get rootPc(): number {\n return this.#scale.rootPc;\n }\n\n /** Whether the scale has a minor third and no major third. */\n get isMinor(): boolean {\n return isMinorKey(this.#scale);\n }\n\n /**\n * The scale's pitch classes in ascending scale-degree order (degree 0 first).\n *\n * @returns One pitch class per scale degree.\n */\n pitchClasses(): number[] {\n return scaleTonesInDegreeOrder(this.#scale);\n }\n\n /**\n * The spelled scale, one note per degree (e.g. C D E F G A B for C major).\n *\n * @returns Spelled octave-less notes in scale-degree order.\n */\n notes(): Note[] {\n return spellScale(this.#tonic.data, this.#scale).map((note) => new Note(note));\n }\n\n /**\n * Alias of {@link Key.notes}.\n *\n * @returns Spelled octave-less notes in scale-degree order.\n */\n spell(): Note[] {\n return this.notes();\n }\n\n /**\n * The spelled scale as letter-name strings.\n *\n * @returns One name per scale degree.\n */\n noteNames(): string[] {\n return this.notes().map((note) => note.name);\n }\n\n /**\n * Build a chord on a scale degree, carrying this key as context.\n *\n * With an explicit quality the quality's interval template is attached to the\n * degree's diatonic root; without one the scale-correct diatonic triad is\n * stacked (e.g. a diminished triad on the leading tone of a major key).\n *\n * @param degree 0-based scale degree of the chord root.\n * @param quality Optional chord quality.\n * @returns The chord, with this key attached.\n */\n chord(degree: number, quality?: ChordQuality): Chord {\n const data =\n quality === undefined\n ? diatonicTriad(degree, this.#scale)\n : chordFromDegree(degree, quality, this.#scale);\n return new Chord(data, this);\n }\n\n /**\n * The diatonic triad on a scale degree, carrying this key as context.\n *\n * @param degree 0-based scale degree of the chord root.\n * @returns The triad, with this key attached.\n */\n diatonicTriad(degree: number): Chord {\n return new Chord(diatonicTriad(degree, this.#scale), this);\n }\n\n /**\n * The diatonic seventh chord on a scale degree, carrying this key as context.\n *\n * @param degree 0-based scale degree of the chord root.\n * @returns The seventh chord, with this key attached.\n */\n diatonicSeventh(degree: number): Chord {\n return new Chord(diatonicSeventh(degree, this.#scale), this);\n }\n\n /**\n * Build the chord denoted by a Roman numeral in this key (including applied\n * chords such as `'V7/V'`), carrying this key as context.\n *\n * @param text The Roman numeral.\n * @returns The chord, with this key attached.\n * @throws If the numeral is not valid.\n */\n roman(text: string): Chord {\n return new Chord(romanToChord(text, this.#scale), this);\n }\n\n /**\n * Whether a pitch belongs to the scale.\n *\n * @param x A MIDI pitch, bare pitch class, or note.\n * @returns True if the pitch class is a scale tone.\n */\n contains(x: number | Note): boolean {\n return isScaleTone(typeof x === 'number' ? x : x.pitchClass, this.#scale);\n }\n\n /**\n * The plain key data, for JSON serialization.\n *\n * Private class fields do not serialize, so an explicit `toJSON` keeps\n * `JSON.stringify(key)` from collapsing to `{}`. The result pairs the\n * `KeyScale` with the spelled tonic, enough to reconstruct the key via\n * {@link Key.of}.\n *\n * @returns The key/scale and its spelled tonic.\n */\n toJSON(): { scale: KeyScale; tonic: NoteData } {\n return { scale: this.scale, tonic: this.#tonic.data };\n }\n}\n"],"mappings":"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","names":[]}