@libraz/libcantus 0.9.3 → 0.9.4

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 (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-LW53AHQU.cjs","../src/theory/chordscale/index.ts","../src/theory/spelling/index.ts","../src/theory/symbol/index.ts","../src/theory/voicing/internal.ts","../src/theory/voicing/leading.ts","../src/theory/voicing/satb.ts","../src/theory/voicing/styled.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-LW53AHQU.cjs","sourcesContent":[null,"import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport type { ScaleNameInput } from '../scale/index.js';\nimport { NAMED_SCALES, namedScaleMask, requireScaleMask } from '../scale/index.js';\n\n/** Count the set bits (scale tones) in a 12-bit mode mask. */\nfunction popcount12(mask: number): number {\n let count = 0;\n for (let n = 0; n < 12; n += 1) {\n count += (mask >> n) & 1;\n }\n return count;\n}\n\n/** Test whether a pitch class belongs to a mode mask rooted on `scaleRootPc`. */\nfunction maskHasPitchClass(mask: number, scaleRootPc: number, pc: number): boolean {\n const offset = pitchClass(pc - scaleRootPc);\n return ((mask >> offset) & 1) === 1;\n}\n\n/**\n * Test whether every chord pitch class is contained in a scale.\n *\n * @param chordPcs The chord's pitch classes (0..11).\n * @param scaleMask The scale's 12-bit mode mask.\n * @param scaleRootPc The pitch class the mask is rooted on.\n * @returns True if the scale is a superset of the chord.\n *\n * @category Scales\n */\nexport function scaleMatchesChord(\n chordPcs: number[],\n scaleMask: number,\n scaleRootPc: number,\n): boolean {\n for (const pc of chordPcs) {\n if (!maskHasPitchClass(scaleMask, scaleRootPc, pc)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A named scale rooted on a pitch class that fits over a chord.\n *\n * @category Scales\n */\nexport type ChordScaleMatch = {\n name: string;\n rootPc: number;\n};\n\n/**\n * Scale names whose mask duplicates another {@link NAMED_SCALES} entry\n * (major/ionian, naturalMinor/aeolian). Chord-scale analysis conventionally\n * speaks in mode names, so these aliases are skipped and only the modal name\n * is reported.\n */\nconst ALIASED_SCALE_NAMES = new Set(['major', 'naturalMinor']);\n\n/** Number of tones in a heptatonic (seven-note) scale. */\nconst HEPTATONIC_SIZE = 7;\n\n/**\n * Conventional preference among equally-fitting scales, brightest first.\n *\n * Several modes fit a chord that states no third or sixth — a power chord, a\n * suspension — equally well. Alphabetical order made `aeolian` the answer for\n * those, naming a minor mode as the best fit for a mode-neutral chord.\n */\nconst SCALE_PREFERENCE = [\n 'ionian',\n 'major',\n 'mixolydian',\n 'dorian',\n 'lydian',\n 'aeolian',\n 'naturalMinor',\n 'phrygian',\n 'locrian',\n] as const;\n\n/** Position of a scale in {@link SCALE_PREFERENCE}, or last when unlisted. */\nfunction conventionalRank(name: string): number {\n const index = (SCALE_PREFERENCE as readonly string[]).indexOf(name);\n return index < 0 ? SCALE_PREFERENCE.length : index;\n}\n\n/**\n * List the scales that fit over a chord, best fit first.\n *\n * Only the chord root is considered as the scale root, matching the\n * conventional chord-scale relationship. Every entry of {@link NAMED_SCALES}\n * whose pitch-class set is a superset of the chord's is returned once per\n * distinct pitch-class set (aliased masks such as major/ionian report only the\n * modal name), ranked by fewest extra scale tones beyond the chord, then by\n * scale size (heptatonic before larger scales), then by conventional\n * preference. For bare triads and smaller chords, heptatonic scales rank before\n * pentatonics and other sizes: a pentatonic adds no modal color over a triad,\n * so the seven-note modes are the more useful answer. The chromatic scale is\n * only returned as a fallback when no other scale contains the chord.\n *\n * The ranking is by fit, so the first entry is the tightest-fitting scale\n * rather than the idiomatic choice: a four-note chord that a pentatonic covers\n * exactly puts that pentatonic ahead of the mode a player would name. Read the\n * whole list, or pick by name, when the idiomatic scale is what is wanted;\n * {@link scalesForChanges} makes that choice for a progression.\n *\n * @param chord The chord to fit scales over.\n * @returns The matching scales rooted on the chord root, best fit first.\n *\n * @example\n * ```ts\n * import { makeChord, chordScales } from '@libraz/libcantus';\n * const scales = chordScales(makeChord(0, 'maj7'));\n * // scales for Cmaj7, best fit first; each is { name, rootPc: 0 }\n * ```\n *\n * @category Scales\n */\nexport function chordScales(chord: Chord): ChordScaleMatch[] {\n const chordPcs = chordPitchClasses(chord);\n const rootPc = pitchClass(chord.rootPc);\n const ranked: { name: string; extra: number; size: number }[] = [];\n const seenMasks = new Set<number>();\n for (const name of Object.keys(NAMED_SCALES)) {\n if (name === 'chromatic' || ALIASED_SCALE_NAMES.has(name)) {\n continue;\n }\n const mask = namedScaleMask(name);\n if (mask === undefined || seenMasks.has(mask)) {\n continue;\n }\n seenMasks.add(mask);\n if (scaleMatchesChord(chordPcs, mask, rootPc)) {\n const size = popcount12(mask);\n ranked.push({ name, extra: size - chordPcs.length, size });\n }\n }\n if (ranked.length === 0) {\n // No diatonic-style scale contains the chord; fall back to the chromatic\n // scale, which trivially contains every pitch class.\n if (NAMED_SCALES.chromatic !== undefined) {\n return [{ name: 'chromatic', rootPc }];\n }\n return [];\n }\n const preferHeptatonic = chordPcs.length <= 3;\n ranked.sort((a, b) => {\n if (preferHeptatonic && (a.size === HEPTATONIC_SIZE) !== (b.size === HEPTATONIC_SIZE)) {\n return a.size === HEPTATONIC_SIZE ? -1 : 1;\n }\n if (a.extra !== b.extra) {\n return a.extra - b.extra;\n }\n if (a.size !== b.size) {\n return a.size - b.size;\n }\n const rankA = conventionalRank(a.name);\n const rankB = conventionalRank(b.name);\n if (rankA !== rankB) {\n return rankA - rankB;\n }\n return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n return ranked.map((entry) => ({ name: entry.name, rootPc }));\n}\n\n/**\n * List a scale's avoid notes over a chord.\n *\n * An avoid note is a non-chord scale tone that lies a semitone directly above a\n * chord tone; sounding it against the chord clashes. The scale is rooted on the\n * chord root. If the scale does not contain the chord, no avoid notes exist.\n *\n * @param chord The chord providing the chord tones.\n * @param scaleName A key of {@link NAMED_SCALES}.\n * @returns The avoid-note pitch classes, sorted ascending in [0, 11].\n * @throws If `scaleName` is not a built-in scale. An empty result already\n * means \"this scale has no avoid notes over this chord\"; answering a typo\n * the same way would make the two indistinguishable.\n *\n * @category Scales\n */\nexport function avoidNotes(chord: Chord, scaleName: ScaleNameInput): number[] {\n const mask = requireScaleMask(scaleName);\n const rootPc = pitchClass(chord.rootPc);\n const chordPcs = chordPitchClasses(chord);\n if (!scaleMatchesChord(chordPcs, mask, rootPc)) {\n return [];\n }\n const chordSet = new Set(chordPcs);\n const avoid: number[] = [];\n for (let pc = 0; pc < 12; pc += 1) {\n if (!maskHasPitchClass(mask, rootPc, pc) || chordSet.has(pc)) {\n continue;\n }\n if (chordSet.has(pitchClass(pc - 1))) {\n avoid.push(pc);\n }\n }\n return avoid.sort((a, b) => a - b);\n}\n\n/**\n * List a scale's available tensions over a chord.\n *\n * Available tensions are scale tones that are neither chord tones nor avoid\n * notes, i.e. the usable color tones (typically the 9/11/13 region). The scale\n * is rooted on the chord root. If the scale does not contain the chord, there\n * are no available tensions.\n *\n * @param chord The chord providing the chord tones.\n * @param scaleName A key of {@link NAMED_SCALES}.\n * @returns The available-tension pitch classes, sorted ascending in [0, 11].\n * @throws If `scaleName` is not a built-in scale, as {@link avoidNotes} does.\n *\n * @example\n * ```ts\n * import { makeChord, availableTensions } from '@libraz/libcantus';\n * availableTensions(makeChord(0, 'maj7'), 'ionian'); // [2, 9] — color tones over Cmaj7\n * ```\n *\n * @category Scales\n */\nexport function availableTensions(chord: Chord, scaleName: ScaleNameInput): number[] {\n const mask = requireScaleMask(scaleName);\n const rootPc = pitchClass(chord.rootPc);\n const chordPcs = chordPitchClasses(chord);\n if (!scaleMatchesChord(chordPcs, mask, rootPc)) {\n return [];\n }\n const chordSet = new Set(chordPcs);\n const avoidSet = new Set(avoidNotes(chord, scaleName));\n const tensions: number[] = [];\n for (let pc = 0; pc < 12; pc += 1) {\n if (!maskHasPitchClass(mask, rootPc, pc) || chordSet.has(pc) || avoidSet.has(pc)) {\n continue;\n }\n tensions.push(pc);\n }\n return tensions.sort((a, b) => a - b);\n}\n\n/**\n * A scale fit over a chord together with its avoid notes and tensions.\n *\n * @category Scales\n */\nexport type ChordScaleReportEntry = {\n name: string;\n rootPc: number;\n avoid: number[];\n tensions: number[];\n};\n\n/**\n * Report the best-fitting scales for a chord with their avoid notes and tensions.\n *\n * Combines {@link chordScales}, {@link avoidNotes}, and {@link availableTensions}\n * into a single ergonomic result, ordered best fit first.\n *\n * @param chord The chord to analyze.\n * @param limit Optional maximum number of scales to report; all by default.\n * @returns One entry per reported scale, best fit first.\n *\n * @category Scales\n */\nexport function chordScaleReport(chord: Chord, limit?: number): ChordScaleReportEntry[] {\n const matches = chordScales(chord);\n const chosen = limit === undefined ? matches : matches.slice(0, Math.max(0, limit));\n return chosen.map((match) => ({\n name: match.name,\n rootPc: match.rootPc,\n avoid: avoidNotes(chord, match.name),\n tensions: availableTensions(chord, match.name),\n }));\n}\n\n/**\n * A chord paired with the scale chosen for it by {@link scalesForChanges}.\n *\n * @category Scales\n */\nexport type ScaleChoice = {\n chord: Chord;\n scale: ChordScaleMatch;\n};\n\n/**\n * Small per-candidate penalty added in {@link scalesForChanges} so that, among\n * choices of otherwise-equal transition cost, the tighter best-fit scale wins.\n */\nconst RANK_PENALTY = 0.01;\n\n/** Build the pitch-class set of a named scale rooted on `rootPc`. */\nfunction scalePitchClassSet(name: string, rootPc: number): Set<number> {\n const mask = namedScaleMask(name);\n const pcs = new Set<number>();\n if (mask === undefined) {\n return pcs;\n }\n for (let n = 0; n < 12; n += 1) {\n if (((mask >> n) & 1) === 1) {\n pcs.add(pitchClass(rootPc + n));\n }\n }\n return pcs;\n}\n\n/** Count pitch classes that belong to exactly one of two sets. */\nfunction symmetricDifferenceSize(a: Set<number>, b: Set<number>): number {\n let count = 0;\n for (const pc of a) {\n if (!b.has(pc)) {\n count += 1;\n }\n }\n for (const pc of b) {\n if (!a.has(pc)) {\n count += 1;\n }\n }\n return count;\n}\n\n/**\n * Choose one scale per chord across a progression, favoring continuity.\n *\n * Each chord's {@link chordScales} candidates form a stage in a Viterbi-style\n * dynamic program. The transition cost between adjacent choices is the number\n * of pitch classes that differ between their pitch-class sets (the symmetric\n * difference), plus a small penalty for straying from a chord's best-fit scale\n * so that ties break toward the tighter fit. The minimum-total-cost path is\n * returned, one {@link ScaleChoice} per input chord in the original order.\n *\n * @param chords The chord sequence to choose scales for.\n * @returns One scale choice per chord, in input order.\n *\n * @category Scales\n */\nexport function scalesForChanges(chords: Chord[]): ScaleChoice[] {\n if (chords.length === 0) {\n return [];\n }\n const candidateLists = chords.map((chord) => chordScales(chord));\n const pcSets = candidateLists.map((candidates) =>\n candidates.map((match) => scalePitchClassSet(match.name, match.rootPc)),\n );\n\n const first = candidateLists[0] ?? [];\n const dp: number[][] = [first.map((_, j) => j * RANK_PENALTY)];\n const back: number[][] = [first.map(() => -1)];\n\n for (let i = 1; i < candidateLists.length; i += 1) {\n const candidates = candidateLists[i] ?? [];\n const prevCosts = dp[i - 1] ?? [];\n const prevSets = pcSets[i - 1] ?? [];\n const curSets = pcSets[i] ?? [];\n const stageCosts: number[] = [];\n const stageBack: number[] = [];\n for (let j = 0; j < candidates.length; j += 1) {\n let bestCost = Number.POSITIVE_INFINITY;\n let bestPrev = -1;\n for (let k = 0; k < prevCosts.length; k += 1) {\n const transition = symmetricDifferenceSize(\n prevSets[k] ?? new Set(),\n curSets[j] ?? new Set(),\n );\n const cost = (prevCosts[k] ?? 0) + transition;\n if (cost < bestCost) {\n bestCost = cost;\n bestPrev = k;\n }\n }\n stageCosts.push(bestCost + j * RANK_PENALTY);\n stageBack.push(bestPrev);\n }\n dp.push(stageCosts);\n back.push(stageBack);\n }\n\n const lastCosts = dp[dp.length - 1] ?? [];\n let bestFinal = 0;\n for (let j = 1; j < lastCosts.length; j += 1) {\n if ((lastCosts[j] ?? Number.POSITIVE_INFINITY) < (lastCosts[bestFinal] ?? 0)) {\n bestFinal = j;\n }\n }\n\n const chosenIndices: number[] = new Array(candidateLists.length);\n let current = bestFinal;\n for (let i = candidateLists.length - 1; i >= 0; i -= 1) {\n chosenIndices[i] = current;\n current = back[i]?.[current] ?? -1;\n }\n\n return chords.map((chord, i) => {\n const candidates = candidateLists[i] ?? [];\n const scale = candidates[chosenIndices[i] ?? 0] ?? { name: 'chromatic', rootPc: chord.rootPc };\n return { chord, scale };\n });\n}\n","/**\n * Spelling: derive letter-name notes for scales and chords from a spelled tonic.\n *\n * The theory core works in pitch classes, which cannot choose between (say) a\n * G# and an Ab. Given a spelled tonic, this module assigns diatonic letters to\n * scale degrees and chord tones so a C major scale spells as C D E F G A B and\n * A harmonic minor spells its seventh as G#.\n */\n\nimport type { Note } from '../../core/pitch/index.js';\nimport {\n formatNote,\n diatonicLetterOf as mod7,\n pitchClassOf as mod12,\n naturalPitchClassOf as naturalPc,\n} from '../../core/pitch/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../chord/index.js';\nimport { scaleTonesInDegreeOrder } from '../scale/index.js';\n\n/**\n * Conventional degree assignment for each chromatic offset above the tonic.\n *\n * The letter is derived from the tonic's letter plus `degreeOffset` (so the\n * spelling follows the key's diatonic letters), and the accidental is recomputed\n * for that letter. The table covers the whole octave, so a pitch class that is\n * not a degree of the scale — a chromatic tone in a heptatonic key, or any tone\n * outside a pentatonic or blues scale — still spells from the tonic's letter\n * rather than falling back to a fixed sharp table.\n *\n * Offsets follow the conventional ascending chromatic spelling: b2, M2, b3, M3,\n * P4, #4, P5, b6, #6, b7, #7. The raised sixth and leading tone keep sharp-side\n * minor keys spelling their raised degrees with the key-correct letter (e.g. F#\n * minor spells its leading tone E#, not F).\n */\nconst CHROMATIC_SPELLING: Record<number, number> = {\n 1: 1, // b2\n 2: 1, // M2\n 3: 2, // b3\n 4: 2, // M3\n 5: 3, // P4\n 6: 3, // #4\n 7: 4, // P5\n 8: 5, // b6\n 9: 5, // #6 (raised sixth of a minor key)\n 10: 6, // b7\n 11: 6, // #7 (raised leading tone of a minor key)\n};\n\n/**\n * Flat-side table for scales that are not heptatonic.\n *\n * Identical to {@link CHROMATIC_SPELLING} except at the tritone, which a\n * flat-side scale spells as a diminished fifth rather than the augmented fourth\n * a lydian mode wants: C blues spells C Eb F Gb G Bb.\n */\nconst CHROMATIC_SPELLING_FLAT: Record<number, number> = {\n ...CHROMATIC_SPELLING,\n 6: 4, // b5\n};\n\n/**\n * Sharp-side counterpart, used for scales that are not heptatonic and lean\n * sharp: C whole-tone spells C D E F# G# A#.\n */\nconst CHROMATIC_SPELLING_SHARP: Record<number, number> = {\n 1: 0, // #1\n 2: 1, // M2\n 3: 1, // #2\n 4: 2, // M3\n 5: 3, // P4\n 6: 3, // #4\n 7: 4, // P5\n 8: 4, // #5\n 9: 5, // M6\n 10: 5, // #6\n 11: 6, // M7\n};\n\n/** Shortest signed alteration (in [-6, 6]) taking a letter's natural pc to `pc`. */\nfunction alterFor(letter: number, pc: number): number {\n let d = mod12(pc - naturalPc(letter));\n if (d > 6) {\n d -= 12;\n }\n return d;\n}\n\n/** Spell `pc` on the letter `degreeOffset` steps above the tonic's letter. */\nfunction letterFor(tonic: Note, degreeOffset: number | undefined, pc: number): Note | undefined {\n if (degreeOffset === undefined) {\n return undefined;\n }\n const letter = mod7(tonic.letter + degreeOffset);\n return { letter, alter: alterFor(letter, pc) };\n}\n\n/** The lighter-accidental of two candidate spellings; ties go to `preferFlat`. */\nfunction pickSpelling(\n flat: Note | undefined,\n sharp: Note | undefined,\n preferFlat: boolean,\n): Note | undefined {\n if (flat === undefined || sharp === undefined) {\n return flat ?? sharp;\n }\n if (Math.abs(flat.alter) !== Math.abs(sharp.alter)) {\n return Math.abs(flat.alter) < Math.abs(sharp.alter) ? flat : sharp;\n }\n return preferFlat ? flat : sharp;\n}\n\n/** Whether the key's scale is a seven-note (heptatonic) scale. */\nfunction isHeptatonic(key: KeyScale): boolean {\n return scaleTonesInDegreeOrder(key).length === 7;\n}\n\n/** Whether the scale has a minor third and no major third — it leans flat. */\nfunction hasMinorThird(key: KeyScale): boolean {\n const tones = scaleTonesInDegreeOrder(key);\n const root = mod12(key.rootPc);\n return tones.includes(mod12(root + 3)) && !tones.includes(mod12(root + 4));\n}\n\n/**\n * Spell a single pitch class relative to a spelled tonic and key.\n *\n * The tonic always spells as itself. In a heptatonic key each scale degree takes\n * the next letter above the tonic, so the scale spells with one letter per\n * degree. Every other pitch class — a chromatic tone in a heptatonic key, or any\n * tone of a scale that is not heptatonic (pentatonic, blues, octatonic) — takes\n * the conventional interval spelling above the tonic's letter, so a flat-side\n * key keeps flat-side names.\n *\n * @param pc The pitch class to spell.\n * @param tonic The spelled tonic (its letter anchors the spelling).\n * @param key The key/scale.\n * @returns The spelled note (without octave).\n * @category Pitch & Intervals\n */\nexport function spellPitchClass(pc: number, tonic: Note, key: KeyScale): Note {\n const tonicPc = mod12(naturalPc(tonic.letter) + tonic.alter);\n const offset = mod12(pc - tonicPc);\n\n // The tonic spells as itself, whatever the scale: a caller that asked for Eb\n // major pentatonic must not be handed back a D#.\n if (offset === 0) {\n return { letter: mod7(tonic.letter), alter: tonic.alter };\n }\n\n const heptatonic = isHeptatonic(key);\n if (heptatonic) {\n const degree = scaleTonesInDegreeOrder(key).indexOf(mod12(pc));\n if (degree >= 0) {\n const letter = mod7(tonic.letter + degree);\n return { letter, alter: alterFor(letter, pc) };\n }\n const degreeOffset = CHROMATIC_SPELLING[offset];\n if (degreeOffset !== undefined) {\n const letter = mod7(tonic.letter + degreeOffset);\n return { letter, alter: alterFor(letter, pc) };\n }\n } else {\n // A scale that is not heptatonic has no letter-per-degree spelling to\n // follow, so take whichever conventional letter needs the smaller\n // accidental. Ties go to the side the scale itself leans: a flat tonic or a\n // minor third both mean flats (C blues spells Gb and Bb, not F# and A#).\n const flat = letterFor(tonic, CHROMATIC_SPELLING_FLAT[offset], pc);\n const sharp = letterFor(tonic, CHROMATIC_SPELLING_SHARP[offset], pc);\n const best = pickSpelling(flat, sharp, tonic.alter < 0 || hasMinorThird(key));\n if (best !== undefined) {\n return best;\n }\n }\n\n // Unreachable for a pitch class, since both tables cover offsets 1..11 and\n // offset 0 returns above; kept so a non-integral input still names something.\n const belowLetter = mod7([0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][mod12(pc)] ?? 0);\n return { letter: belowLetter, alter: alterFor(belowLetter, pc) };\n}\n\n/**\n * Spell every pitch class of a scale, in ascending scale-degree order.\n *\n * Correct for heptatonic scales (each degree gets the next letter). A scale that\n * is not heptatonic is spelled tone by tone, on the accidental side the scale\n * leans towards.\n *\n * @param tonic The spelled tonic.\n * @param key The key/scale.\n * @returns Spelled notes, one per scale degree.\n * @example\n * ```ts\n * import { spellScale, noteNames, parseNote, majorKey } from '@libraz/libcantus';\n * noteNames(spellScale(parseNote('C'), majorKey(0)));\n * // ['C', 'D', 'E', 'F', 'G', 'A', 'B']\n * ```\n * @category Pitch & Intervals\n */\nexport function spellScale(tonic: Note, key: KeyScale): Note[] {\n return scaleTonesInDegreeOrder(key).map((pc) => spellPitchClass(pc, tonic, key));\n}\n\n/**\n * Spell an arbitrary list of pitch classes relative to a key.\n *\n * @param pcs The pitch classes.\n * @param tonic The spelled tonic.\n * @param key The key/scale.\n * @returns Spelled notes, in input order.\n * @category Pitch & Intervals\n */\nexport function spellPitchClasses(pcs: number[], tonic: Note, key: KeyScale): Note[] {\n return pcs.map((pc) => spellPitchClass(pc, tonic, key));\n}\n\n/**\n * Diatonic letter distance implied by a chord interval.\n *\n * Chord intervals are not merely pitch-class distances: 6 is a diminished\n * fifth in the basic chord templates while 18 is an augmented eleventh, and 9\n * is a diminished seventh in `dim7` but a sixth elsewhere. Keeping that\n * distinction is what prevents functional chord tones from being respelled by\n * their nearest key pitch (for example G# becoming Ab in E7).\n */\nfunction chordLetterOffset(interval: number, chord: Chord): number {\n switch (interval) {\n case 0:\n case 12:\n return 0;\n case 1:\n case 2:\n case 13:\n case 14:\n return 1;\n case 3:\n case 4:\n case 16:\n return 2;\n case 15:\n // A raised ninth over a chord that already has a third is a ninth, not a\n // second third: C7#9 spells D#, not a duplicate E.\n return chord.intervals.some((i) => i === 3 || i === 4) ? 1 : 2;\n case 5:\n case 17:\n case 18:\n return 3;\n case 6:\n case 7:\n case 8:\n case 19:\n return 4;\n case 9:\n return chord.quality === 'dim7' ? 6 : 5;\n case 20:\n case 21:\n return 5;\n case 10:\n case 11:\n case 22:\n case 23:\n return 6;\n default: {\n // Unknown/custom interval data gets the nearest conventional diatonic\n // distance. Public builders use the explicit cases above.\n const octaves = Math.floor(Math.max(0, interval) / 12);\n const simple = mod12(interval);\n const simpleOffset = [0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 6, 6][simple] ?? 0;\n return octaves * 7 + simpleOffset;\n }\n }\n}\n\n/**\n * Spell chord tones from an already chosen root spelling.\n *\n * This is exported for the model layer's inversion support; the package\n * barrels intentionally expose the key-aware {@link spellChord} entry point.\n */\nexport function spellChordFromRoot(chord: Chord, root: Note): Note[] {\n return chord.intervals.map((interval) => {\n const letter = mod7(root.letter + chordLetterOffset(interval, chord));\n const pc = mod12(chord.rootPc + interval);\n return { letter, alter: alterFor(letter, pc) };\n });\n}\n\n/**\n * Spell a chord's tones, in the chord's own (tertian) order, relative to a key.\n *\n * Diatonic chords spell exactly (e.g. G7 in C major -> G B D F). Chromatic chord\n * tones take their conventional spelling; enharmonically ambiguous altered\n * tensions may be spelled by the general convention rather than by chord\n * function.\n *\n * @param chord The chord.\n * @param tonic The spelled tonic of the key.\n * @param key The key/scale.\n * @returns Spelled chord tones, root first.\n * @example\n * ```ts\n * import { spellChord, noteNames, parseNote, majorKey, makeChord } from '@libraz/libcantus';\n * // G7 in C major\n * noteNames(spellChord(makeChord(7, 'dom7'), parseNote('C'), majorKey(0)));\n * // ['G', 'B', 'D', 'F']\n * ```\n * @category Pitch & Intervals\n */\nexport function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[] {\n return spellChordFromRoot(chord, chordRootSpelling(chord, tonic, key));\n}\n\n/**\n * The root spelling to use for a chord: the chord's own hint when it still\n * matches the root pitch class, otherwise the key's spelling of that pitch\n * class.\n *\n * Honouring the hint is what keeps a chord's spelled tones agreeing with the\n * symbol it renders as — `parseChordSymbol('C#7')` must spell C# E# G# B even\n * when analysed in a flat-side key.\n */\nfunction chordRootSpelling(chord: Chord, tonic: Note, key: KeyScale): Note {\n const hint = chord.rootSpelling;\n if (hint !== undefined && mod12(naturalPc(hint.letter) + hint.alter) === mod12(chord.rootPc)) {\n return { letter: mod7(hint.letter), alter: hint.alter };\n }\n return spellPitchClass(chord.rootPc, tonic, key);\n}\n\n/**\n * Spell a MIDI pitch relative to a key, keeping its octave.\n *\n * The octave-less sibling of this function, {@link spellPitchClass}, is enough\n * for chord and scale spelling; this one is for naming actual sounding pitches\n * (a generated line, an imported track) without losing the register.\n *\n * @param pitch The MIDI pitch; rounded to the nearest integer.\n * @param tonic The spelled tonic of the key.\n * @param key The key/scale.\n * @returns The spelled note, carrying the octave that reproduces `pitch`.\n * @example\n * ```ts\n * import { spellPitch, formatNote, parseNote, majorKey } from '@libraz/libcantus';\n * formatNote(spellPitch(70, parseNote('Eb'), majorKey(3))); // 'Bb4'\n * ```\n * @category Pitch & Intervals\n */\nexport function spellPitch(pitch: number, tonic: Note, key: KeyScale): Note {\n const rounded = Math.round(pitch);\n const spelled = spellPitchClass(mod12(rounded), tonic, key);\n const octave = (rounded - naturalPc(spelled.letter) - spelled.alter) / 12 - 1;\n return { letter: spelled.letter, alter: spelled.alter, octave };\n}\n\n/**\n * Convenience: render spelled notes as letter-name strings.\n *\n * @param notes The notes.\n * @returns Their formatted names.\n * @category Pitch & Intervals\n */\nexport function noteNames(notes: Note[]): string[] {\n return notes.map(formatNote);\n}\n","/**\n * Lead-sheet chord symbol parsing and formatting.\n *\n * Bridges the informal `<root><quality>[/<bass>]` notation musicians write\n * (`Cmaj7`, `F#m7b5`, `Bb7`, `C/G`) and the library's structured {@link Chord}\n * type, so callers can accept/emit plain text at the edges of the API.\n */\n\nimport { InvalidInputError } from '../../core/errors/index.js';\nimport {\n formatNote,\n midiToNote,\n type Note,\n noteToPitchClass,\n parseNote,\n pitchClassOf as pitchClass,\n} from '../../core/pitch/index.js';\nimport { assertFiniteNumber } from '../../core/validation/index.js';\nimport { type Chord, type ChordQuality, makeChord, type PitchSpelling } from '../chord/index.js';\n\n/**\n * Matches a chord root: a letter A-G (case-insensitive, like {@link parseNote})\n * with an optional single-direction accidental.\n */\nconst ROOT_RE = /^[A-Ga-g](?:#{1,2}|b{1,2})?/;\n\n/** Matches a bare note usable as a slash-chord bass (no octave). */\nconst BASS_RE = /^[A-Ga-g](?:#{1,2}|b{1,2})?$/;\n\n/** Recognized quality suffixes, keyed by their exact lead-sheet spelling. */\nconst QUALITY_MAP: Record<string, ChordQuality> = {\n '': 'maj',\n m: 'min',\n min: 'min',\n '-': 'min',\n dim: 'dim',\n '°': 'dim',\n aug: 'aug',\n '+': 'aug',\n maj7: 'maj7',\n M7: 'maj7',\n Δ: 'maj7',\n Δ7: 'maj7',\n m7: 'min7',\n min7: 'min7',\n '-7': 'min7',\n '7': 'dom7',\n dim7: 'dim7',\n '°7': 'dim7',\n m7b5: 'm7b5',\n ø: 'm7b5',\n ø7: 'm7b5',\n min7b5: 'm7b5',\n mMaj7: 'minMaj7',\n mM7: 'minMaj7',\n minMaj7: 'minMaj7',\n aug7: 'aug7',\n '+7': 'aug7',\n '7#5': 'aug7',\n augMaj7: 'augMaj7',\n augM7: 'augMaj7',\n '+maj7': 'augMaj7',\n '+M7': 'augMaj7',\n majb5: 'majb5',\n 'maj(b5)': 'majb5',\n '(b5)': 'majb5',\n '6': '6',\n m6: 'min6',\n min6: 'min6',\n '6/9': '6/9',\n '69': '6/9',\n sus2: 'sus2',\n sus: 'sus4',\n sus4: 'sus4',\n add9: 'add9',\n add11: 'add11',\n maj9: 'maj9',\n M9: 'maj9',\n m9: 'min9',\n min9: 'min9',\n '9': 'dom9',\n '7b9': '7b9',\n '7#9': '7#9',\n '7#11': '7#11',\n '7b13': '7b13',\n '11': '11',\n '13': '13',\n '5': '5',\n '7sus4': '7sus4',\n '7sus': '7sus4',\n '9sus4': '11',\n '9sus': '11',\n '7b5': '7b5',\n '7alt': '7alt',\n alt: '7alt',\n '13b9': '13b9',\n maj13: 'maj13',\n M13: 'maj13',\n 'maj7#11': 'maj7#11',\n 'M7#11': 'maj7#11',\n m11: 'min11',\n min11: 'min11',\n m13: 'min13',\n min13: 'min13',\n madd9: 'minAdd9',\n 'min(add9)': 'minAdd9',\n 'm(add9)': 'minAdd9',\n '6add9': '6/9',\n 'm6/9': 'min6/9',\n m69: 'min6/9',\n 'min6/9': 'min6/9',\n // ASCII stand-ins for the degree and half-diminished glyphs, which most\n // lead sheets and chord-chart text files use instead of the typographic ones.\n o: 'dim',\n o7: 'dim7',\n h: 'm7b5',\n h7: 'm7b5',\n};\n\n/** Canonical lead-sheet suffix emitted for each chord quality when formatting. */\nconst CANONICAL_SUFFIX: Record<ChordQuality, string> = {\n maj: '',\n min: 'm',\n dim: 'dim',\n aug: 'aug',\n maj7: 'maj7',\n min7: 'm7',\n dom7: '7',\n dim7: 'dim7',\n m7b5: 'm7b5',\n minMaj7: 'mMaj7',\n aug7: 'aug7',\n augMaj7: 'augMaj7',\n majb5: 'maj(b5)',\n '6': '6',\n min6: 'm6',\n '6/9': '6/9',\n sus2: 'sus2',\n sus4: 'sus4',\n add9: 'add9',\n add11: 'add11',\n maj9: 'maj9',\n min9: 'm9',\n dom9: '9',\n '7b9': '7b9',\n '7#9': '7#9',\n '7#11': '7#11',\n '7b13': '7b13',\n '11': '11',\n '13': '13',\n '5': '5',\n '7sus4': '7sus4',\n '7b5': '7b5',\n '7alt': '7alt',\n '13b9': '13b9',\n maj13: 'maj13',\n 'maj7#11': 'maj7#11',\n min11: 'm11',\n min13: 'm13',\n minAdd9: 'madd9',\n 'min6/9': 'm6/9',\n};\n\n/**\n * The quality a suffix names, or undefined when it names none.\n *\n * The own-property check is what stops `'constructor'` and `'toString'` from\n * resolving through `Object.prototype` and reaching the chord builder with a\n * quality it has no interval template for.\n */\nfunction knownQuality(suffix: string): ChordQuality | undefined {\n return Object.hasOwn(QUALITY_MAP, suffix) ? QUALITY_MAP[suffix] : undefined;\n}\n\n/** Build a chord and record the parsed spellings as enharmonic hints. */\nfunction makeSpelledChord(root: Note, quality: ChordQuality, bass?: Note): Chord {\n const chord = makeChord(\n noteToPitchClass(root),\n quality,\n bass === undefined ? undefined : noteToPitchClass(bass),\n );\n chord.rootSpelling = { letter: root.letter, alter: root.alter };\n if (bass !== undefined) {\n chord.bassSpelling = { letter: bass.letter, alter: bass.alter };\n }\n return chord;\n}\n\n/**\n * Parse a lead-sheet chord symbol into a {@link Chord}.\n *\n * Accepts `<root><quality>[/<bass>]`, e.g. `Cmaj7`, `F#m7b5`, `Bb7`, `C/G`,\n * `C6/9`, `C6/9/E`. Root and bass letters are case-insensitive, matching\n * {@link parseNote}. A `/` is ambiguous between a slash bass and the `6/9`\n * quality; the text splits at the last `/` whose right-hand side is a valid\n * bare note preceded by a recognized quality, so `6/9` (with or without a\n * further slash bass) parses as a quality.\n *\n * The parsed chord carries the root/bass spellings as enharmonic hints\n * (`rootSpelling`/`bassSpelling`) so {@link formatChordSymbol} can reproduce\n * flat spellings such as `Bbmaj7` instead of respelling them with sharps.\n *\n * @param text The chord symbol text.\n * @returns The parsed chord.\n * @throws If the root or quality is not recognized.\n * @example\n * ```ts\n * import { parseChordSymbol, formatChordSymbol } from '@libraz/libcantus';\n * const c = parseChordSymbol('Bbmaj7');\n * formatChordSymbol(c); // 'Bbmaj7' — flat spelling is preserved on round-trip\n * ```\n * @category Chords\n */\nexport function parseChordSymbol(text: string): Chord {\n const trimmed = text.trim();\n const rootMatch = ROOT_RE.exec(trimmed);\n if (!rootMatch) {\n throw new InvalidInputError(`Invalid chord symbol: ${text}`);\n }\n const rootToken = rootMatch[0];\n const rootNote = parseNote(rootToken);\n const rest = trimmed.slice(rootToken.length);\n\n // Split at the last '/' so the '6/9' quality's own slash never masks a\n // trailing slash bass (e.g. 'C6/9/E').\n const slashIndex = rest.lastIndexOf('/');\n if (slashIndex !== -1) {\n const before = rest.slice(0, slashIndex);\n const after = rest.slice(slashIndex + 1);\n const beforeQuality = knownQuality(before);\n if (beforeQuality !== undefined && BASS_RE.test(after)) {\n return makeSpelledChord(rootNote, beforeQuality, parseNote(after));\n }\n }\n\n const quality = knownQuality(rest);\n if (quality === undefined) {\n throw new InvalidInputError(`Unrecognized chord quality: ${text}`);\n }\n return makeSpelledChord(rootNote, quality);\n}\n\n/**\n * Name a pitch class, preferring a spelling hint when it is still valid.\n *\n * A hint is used only when no explicit sharp/flat preference was given and the\n * hint still resolves to `pc` (a stale hint left over after transposition is\n * ignored). Otherwise the pitch class is respelled from the requested table,\n * falling back to `inheritFlats` so an unhinted slash bass follows the side its\n * root was spelled on rather than flipping to sharps inside one symbol.\n */\nfunction pitchClassName(\n pc: number,\n hint: PitchSpelling | undefined,\n flats?: boolean,\n inheritFlats?: boolean,\n): string {\n if (flats === undefined && hint !== undefined && noteToPitchClass(hint) === pc) {\n return formatNote(hint);\n }\n const note = midiToNote(60 + pc, (flats ?? inheritFlats) ? 'flat' : 'sharp');\n return formatNote({ letter: note.letter, alter: note.alter });\n}\n\n/**\n * Format a {@link Chord} as a lead-sheet chord symbol.\n *\n * The inverse of {@link parseChordSymbol}: each quality maps to one canonical\n * suffix spelling, and a slash bass is appended only when it differs from the\n * root. When the chord carries `rootSpelling`/`bassSpelling` hints (as chords\n * from {@link parseChordSymbol} do) and no explicit `flats` preference is\n * given, the hints are reused so flat symbols round-trip unchanged.\n *\n * @param chord The chord to format.\n * @param opts Formatting options.\n * @param opts.flats Prefer flat spellings over sharps for altered roots/basses;\n * passing either `true` or `false` overrides any spelling hints.\n * @returns The chord symbol text.\n * @example\n * ```ts\n * import { makeChord, formatChordSymbol } from '@libraz/libcantus';\n * formatChordSymbol(makeChord(0, 'min7')); // 'Cm7'\n * ```\n * @category Chords\n */\nexport function formatChordSymbol(chord: Chord, opts?: { flats?: boolean }): string {\n // A chord rebuilt from JSON or arrived at through arithmetic can carry a\n // non-finite root or a quality this module has no suffix for; formatting\n // those produces 'C' and 'Cundefined', which read as real symbols.\n assertFiniteNumber(chord.rootPc, 'chord rootPc');\n if (chord.bassPc !== undefined) {\n assertFiniteNumber(chord.bassPc, 'chord bassPc');\n }\n if (!Object.hasOwn(CANONICAL_SUFFIX, chord.quality)) {\n throw new InvalidInputError(`Unknown chord quality: ${String(chord.quality)}`);\n }\n const rootPc = pitchClass(chord.rootPc);\n const rootHint = chord.rootSpelling;\n const rootName = pitchClassName(rootPc, rootHint, opts?.flats);\n const suffix = CANONICAL_SUFFIX[chord.quality];\n let symbol = `${rootName}${suffix}`;\n if (chord.bassPc !== undefined && pitchClass(chord.bassPc) !== rootPc) {\n const inheritFlats =\n rootHint !== undefined && noteToPitchClass(rootHint) === rootPc\n ? rootHint.alter < 0\n : undefined;\n symbol += `/${pitchClassName(pitchClass(chord.bassPc), chord.bassSpelling, opts?.flats, inheritFlats)}`;\n }\n return symbol;\n}\n\n/**\n * Transpose a chord symbol by a number of semitones.\n *\n * @param text The chord symbol text.\n * @param semitones Signed semitone offset to apply to the root and bass.\n * @param opts Formatting options for the result; see {@link formatChordSymbol}.\n * @returns The transposed chord symbol text.\n * @throws If `text` does not parse as a chord symbol.\n * @example\n * ```ts\n * import { transposeChordSymbol } from '@libraz/libcantus';\n * transposeChordSymbol('C/G', 2); // 'D/A'\n * ```\n * @category Chords\n */\nexport function transposeChordSymbol(\n text: string,\n semitones: number,\n opts?: { flats?: boolean },\n): string {\n const chord = parseChordSymbol(text);\n const transposed: Chord = { ...chord, rootPc: pitchClass(chord.rootPc + semitones) };\n if (chord.bassPc !== undefined) {\n transposed.bassPc = pitchClass(chord.bassPc + semitones);\n }\n return formatChordSymbol(transposed, opts);\n}\n","import { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\n\nexport { pitchClassOf as pitchClass } from '../../core/pitch/index.js';\n\nimport { NoSolutionError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordPitchClasses, chordToneRole } from '../chord/index.js';\nimport { createsParallelPerfect, createsVoiceOverlap } from '../counterpoint/index.js';\nimport type { VoiceRange } from './satb.js';\n\n/** Default maximum spacing between adjacent upper voices (one octave). */\nexport const DEFAULT_MAX_SPACING = 12;\n\n/** Score penalty per counterpoint violation between consecutive voicings. */\nexport const VIOLATION_PENALTY = 1000;\n/** Score penalty per chord tone absent from a voicing. */\nconst MISSING_TONE_PENALTY = 500;\n/**\n * Score penalty for omitting the chord's fifth. Far milder than any other\n * missing tone: the fifth carries no identity, and the textbook resolution of a\n * complete dominant seventh onto a tonic triad — every tendency tone resolving\n * inward — leaves the tonic without its fifth. Weighted below\n * {@link RESOLUTION_PENALTY} so a resolution can buy the omission, and above\n * {@link POOR_DOUBLING_PENALTY} so nothing else can.\n */\nconst MISSING_FIFTH_PENALTY = 20;\n/** Score penalty per doubled tone that is neither the root nor the fifth. */\nconst POOR_DOUBLING_PENALTY = 4;\n/**\n * Score penalty for doubling the key's leading tone. Doubling it guarantees\n * parallel octaves on its resolution, so it is a rule violation rather than a\n * matter of taste — weighted far above {@link POOR_DOUBLING_PENALTY}, but below\n * a missing chord tone, which damages the chord itself.\n */\nconst LEADING_TONE_DOUBLING_PENALTY = 200;\n/**\n * Score penalty per tendency tone that fails to resolve. Well above the few\n * semitones of extra motion a correct resolution usually costs, so smoothness\n * can never buy an unresolved seventh, and well below\n * {@link VIOLATION_PENALTY}, so avoiding parallels still comes first.\n */\nexport const RESOLUTION_PENALTY = 40;\n/** Hard cap on candidate voicings evaluated per chord, keeping the search bounded. */\nconst MAX_CANDIDATES = 4000;\n/**\n * Hard cap on search-tree nodes visited per chord. The candidate cap alone only\n * counts completed voicings, so a search whose upper voices admit no chord tone\n * would expand the whole cartesian product without ever reaching a leaf.\n */\nconst MAX_SEARCH_NODES = MAX_CANDIDATES * 16;\n\n/**\n * All MIDI pitches of a pitch class inside an inclusive range, ordered from the\n * centre of the range outward (ties break toward the lower pitch). Enumerating\n * centre-outward keeps the candidate set balanced around the register when it\n * is truncated at {@link MAX_CANDIDATES}, instead of skewing to the low octaves\n * that a plain ascending scan would visit first.\n */\nfunction pitchesForPc(pc: number, range: VoiceRange): number[] {\n const result: number[] = [];\n for (let midi = Math.ceil(range.min); midi <= range.max; midi += 1) {\n if (pitchClass(midi) === pc) {\n result.push(midi);\n }\n }\n const center = (range.min + range.max) / 2;\n return result.sort((a, b) => {\n const da = Math.abs(a - center);\n const db = Math.abs(b - center);\n return da === db ? a - b : da - db;\n });\n}\n\n/**\n * Enumerate candidate voicings for a chord: the bass takes the chord's bass\n * (or root) pitch class at each available octave, and each upper voice takes\n * any chord pitch class within its range. Candidates are built in ascending\n * voice order and pruned to exclude voice crossings and over-wide adjacent\n * spacing (the bass–tenor pair is allowed an extra octave, per convention).\n * Enumeration is deterministic and capped at {@link MAX_CANDIDATES} completed\n * voicings and {@link MAX_SEARCH_NODES} visited nodes.\n *\n * Each voice's admissible pitches are resolved once up front rather than\n * recomputed at every node, and a voice that admits no chord tone at all fails\n * immediately: continuing would explore every combination of the voices below\n * it only to reach no leaf.\n *\n * @throws If any voice's range contains no pitch of any chord tone.\n */\nexport function enumerateVoicings(\n chord: Chord,\n ranges: VoiceRange[],\n maxSpacing: number,\n): number[][] {\n const chordPcs = chordPitchClasses(chord);\n const bassPc = pitchClass(chord.bassPc ?? chord.rootPc);\n // Pitch-class-major order, matching the enumeration order of the search.\n const byVoice = ranges.map((range, voice) =>\n (voice === 0 ? [bassPc] : chordPcs).flatMap((pc) => pitchesForPc(pc, range)),\n );\n for (let voice = 0; voice < byVoice.length; voice += 1) {\n if ((byVoice[voice] ?? []).length === 0) {\n throw new NoSolutionError('no voicing satisfies the given ranges');\n }\n }\n const results: number[][] = [];\n const current: number[] = [];\n let nodes = 0;\n const build = (voice: number): void => {\n nodes += 1;\n if (results.length >= MAX_CANDIDATES || nodes > MAX_SEARCH_NODES) {\n return;\n }\n if (voice === ranges.length) {\n results.push([...current]);\n return;\n }\n const prev = current[voice - 1];\n for (const pitch of byVoice[voice] ?? []) {\n if (prev !== undefined) {\n if (pitch < prev) {\n continue; // would cross below the next lower voice\n }\n const spacingLimit = voice === 1 ? maxSpacing + 12 : maxSpacing;\n if (pitch - prev > spacingLimit) {\n continue;\n }\n }\n current.push(pitch);\n build(voice + 1);\n current.pop();\n }\n };\n build(0);\n return results;\n}\n\n/**\n * Structural quality penalty of a single voicing: heavily penalize missing\n * chord tones (mildly for the fifth, which carries no identity), mildly\n * penalize doubling anything other than the root or fifth, and — when a key is\n * known — heavily penalize doubling its leading tone.\n *\n * The exempt fifth is the chord's own fifth, whatever its size: a diminished,\n * augmented, or absent fifth would leave the root as the only freely doubled\n * tone, which is what forces a doubled leading tone in a `viio` chord.\n */\nexport function structuralPenalty(pitches: number[], chord: Chord, key?: KeyScale): number {\n const counts = new Map<number, number>();\n for (const pitch of pitches) {\n const pc = pitchClass(pitch);\n counts.set(pc, (counts.get(pc) ?? 0) + 1);\n }\n let penalty = 0;\n for (const pc of chordPitchClasses(chord)) {\n if (!counts.has(pc)) {\n penalty +=\n chordToneRole(pc, chord) === 'fifth' ? MISSING_FIFTH_PENALTY : MISSING_TONE_PENALTY;\n }\n }\n const rootPc = pitchClass(chord.rootPc);\n const leadingTonePc = key === undefined ? undefined : pitchClass(key.rootPc - 1);\n for (const [pc, count] of counts) {\n if (count <= 1) {\n continue;\n }\n if (pc === leadingTonePc) {\n penalty += (count - 1) * LEADING_TONE_DOUBLING_PENALTY;\n continue;\n }\n if (pc !== rootPc && chordToneRole(pc, chord) !== 'fifth') {\n penalty += (count - 1) * POOR_DOUBLING_PENALTY;\n }\n }\n return penalty;\n}\n\n/** The chord's own seventh as a pitch class, or undefined when it has none. */\nfunction seventhPcOf(chord: Chord): number | undefined {\n for (const interval of chord.intervals) {\n const pc = pitchClass(chord.rootPc + interval);\n if (chordToneRole(pc, chord) === 'seventh') {\n return pc;\n }\n }\n return undefined;\n}\n\n/**\n * Count the tendency tones that fail to resolve between two consecutive\n * voicings of the same progression.\n *\n * A chordal seventh is a dissonance: unless the next chord holds it as a common\n * tone, the voice carrying it must fall by step. A leading tone must rise to\n * the tonic whenever the next chord contains one; without a key there is no\n * leading tone to speak of, so that half of the rule is skipped.\n */\nexport function resolutionViolations(\n prev: number[],\n cur: number[],\n prevChord: Chord,\n nextChord: Chord,\n key?: KeyScale,\n): number {\n const seventhPc = seventhPcOf(prevChord);\n const leadingTonePc = key === undefined ? undefined : pitchClass(key.rootPc - 1);\n const tonicPc = key === undefined ? undefined : pitchClass(key.rootPc);\n const nextPcs = new Set(chordPitchClasses(nextChord));\n let count = 0;\n for (let voice = 0; voice < prev.length && voice < cur.length; voice += 1) {\n const from = prev[voice];\n const to = cur[voice];\n if (from === undefined || to === undefined) {\n continue;\n }\n const fromPc = pitchClass(from);\n const motion = to - from;\n if (fromPc === seventhPc && !nextPcs.has(fromPc) && motion !== -1 && motion !== -2) {\n count += 1;\n }\n if (\n fromPc === leadingTonePc &&\n tonicPc !== undefined &&\n nextPcs.has(tonicPc) &&\n !nextPcs.has(fromPc) &&\n motion !== 1\n ) {\n count += 1;\n }\n }\n return count;\n}\n\n/**\n * Count counterpoint violations between two consecutive voicings of equal\n * length: parallel perfects (fifths and octaves alike, since octaves are the\n * perfect-class-zero case of {@link createsParallelPerfect}) on every voice\n * pair, and voice overlaps on adjacent pairs.\n *\n * Voice crossing and over-wide spacing are not counted: {@link\n * enumerateVoicings} rejects both while building a candidate, so a candidate\n * that reaches here cannot exhibit either. Overlap involves the previous\n * voicing and so is still live.\n */\nexport function violationCount(prev: number[], cur: number[]): number {\n let count = 0;\n for (let lower = 0; lower < cur.length; lower += 1) {\n for (let upper = lower + 1; upper < cur.length; upper += 1) {\n const prevLower = prev[lower];\n const prevUpper = prev[upper];\n const curLower = cur[lower];\n const curUpper = cur[upper];\n if (\n prevLower === undefined ||\n prevUpper === undefined ||\n curLower === undefined ||\n curUpper === undefined\n ) {\n continue;\n }\n if (createsParallelPerfect(prevUpper, curUpper, prevLower, curLower)) {\n count += 1;\n }\n if (upper === lower + 1) {\n if (createsVoiceOverlap(prevUpper, curUpper, prevLower, curLower)) {\n count += 1;\n }\n }\n }\n }\n return count;\n}\n","import { InvalidInputError, NoSolutionError } from '../../core/errors/index.js';\nimport { assertFiniteNumber } from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { createsHiddenParallelPerfect } from '../counterpoint/index.js';\nimport {\n enumerateVoicings,\n structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport type { VoicingOptions } from './satb.js';\nimport { resolveMaxSpacing, resolveRanges } from './satb.js';\n\n/**\n * Moderate penalty for a hidden/direct perfect fifth or octave reached on the\n * outer-voice (bass–soprano) pair. Unlike a true parallel perfect it is\n * discouraged rather than forbidden, so the weight sits alongside voice-leading\n * motion rather than the hard {@link VIOLATION_PENALTY}.\n */\nconst HIDDEN_PERFECT_PENALTY = 6;\n\n/**\n * Total voice-leading cost between two voicings: the sum of absolute semitone\n * motion across voices, plus a moderate `HIDDEN_PERFECT_PENALTY` when the\n * outer-voice (bass–soprano) pair reaches a hidden/direct perfect fifth or\n * octave by similar motion. The arrays must be the same length; when they\n * differ the voicings are not comparable and the cost is `Infinity`.\n *\n * @param from The previous voicing, one MIDI pitch per voice.\n * @param to The next voicing, one MIDI pitch per voice.\n * @returns The summed absolute motion (plus any hidden-perfect penalty), or\n * `Infinity` when lengths differ.\n * @category Voicing & Counterpoint\n */\nexport function voiceLeadingCost(from: number[], to: number[]): number {\n if (from.length !== to.length) {\n return Number.POSITIVE_INFINITY;\n }\n let total = 0;\n for (let i = 0; i < from.length; i += 1) {\n const a = from[i];\n const b = to[i];\n if (a === undefined || b === undefined) {\n continue;\n }\n total += Math.abs(b - a);\n }\n // Discourage hidden/direct perfects between the outermost voices, where they\n // are most audible. True parallels are handled (and forbidden) elsewhere.\n if (from.length >= 2) {\n const bassPrev = from[0];\n const bassCur = to[0];\n const sopPrev = from[from.length - 1];\n const sopCur = to[to.length - 1];\n if (\n bassPrev !== undefined &&\n bassCur !== undefined &&\n sopPrev !== undefined &&\n sopCur !== undefined &&\n createsHiddenParallelPerfect(bassPrev, bassCur, sopPrev, sopCur)\n ) {\n total += HIDDEN_PERFECT_PENALTY;\n }\n }\n return total;\n}\n\n/**\n * Voice a single chord to follow smoothly from an arbitrary current voicing.\n *\n * Candidate voicings of `chord` are enumerated within ranges taken from `opts`,\n * or — when neither `voices` nor `ranges` is given — from a one-octave window\n * around each pitch of `current` (so the result matches `current`'s voice\n * count). Each candidate is scored by structural quality, voice-leading motion\n * from `current`, and a large penalty per counterpoint violation; the lowest\n * scoring candidate is returned, ascending.\n *\n * @param current The current voicing to lead from, ascending (index 0 = lowest).\n * @param chord The next chord to voice.\n * @param opts Voicing options; when omitted, ranges follow `current`'s span.\n * @returns The chosen voicing, ascending with one MIDI pitch per voice.\n * @throws If `current` is empty or holds a non-finite pitch while the ranges\n * are derived from it, or if no voicing fits the ranges.\n * @category Voicing & Counterpoint\n */\nexport function nextVoicing(current: number[], chord: Chord, opts?: VoicingOptions): number[] {\n const derived = opts?.ranges === undefined && opts?.voices === undefined;\n if (derived) {\n // The ranges are read off `current`, so an empty voicing would describe a\n // zero-voice chord and return an empty result — the same input that throws\n // when written as an explicit `{ ranges: [] }`.\n if (current.length === 0) {\n throw new InvalidInputError('current must contain at least one pitch');\n }\n for (let index = 0; index < current.length; index += 1) {\n assertFiniteNumber(current[index] ?? Number.NaN, `current[${index}]`);\n }\n }\n const ranges = derived\n ? current.map((pitch) => {\n // Window one octave around each current pitch, clamped so extreme-low\n // or extreme-high input can never yield MIDI outside [0, 127].\n const centre = Math.min(127, Math.max(0, pitch));\n return { min: Math.max(0, centre - 12), max: Math.min(127, centre + 12) };\n })\n : resolveRanges(opts);\n const maxSpacing = resolveMaxSpacing(opts);\n const candidates = enumerateVoicings(chord, ranges, maxSpacing);\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n const score =\n structuralPenalty(candidate, chord, opts?.key) +\n voiceLeadingCost(current, candidate) +\n VIOLATION_PENALTY * violationCount(current, candidate);\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new NoSolutionError('no voicing satisfies the given ranges');\n }\n return best;\n}\n","import { InvalidInputError, NoSolutionError } from '../../core/errors/index.js';\nimport type { KeyScale } from '../../core/types.js';\nimport {\n assertFiniteNumber,\n assertGenerationBudget,\n assertPositiveInt,\n} from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { formatChordSymbol } from '../symbol/index.js';\nimport {\n DEFAULT_MAX_SPACING,\n enumerateVoicings,\n RESOLUTION_PENALTY,\n resolutionViolations,\n structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport { voiceLeadingCost } from './leading.js';\n\n/**\n * An inclusive MIDI pitch range for a single voice.\n *\n * @category Voicing & Counterpoint\n */\nexport type VoiceRange = {\n readonly min: number;\n readonly max: number;\n};\n\n/**\n * Default four-voice SATB ranges, ascending (index 0 = lowest):\n * bass E2–C4 (40–60), tenor C3–G4 (48–67), alto G3–D5 (55–74),\n * soprano C4–G5 (60–79).\n *\n * @category Voicing & Counterpoint\n */\nexport const SATB_RANGES: readonly Readonly<VoiceRange>[] = Object.freeze([\n Object.freeze({ min: 40, max: 60 }),\n Object.freeze({ min: 48, max: 67 }),\n Object.freeze({ min: 55, max: 74 }),\n Object.freeze({ min: 60, max: 79 }),\n]);\n\n/**\n * Options controlling {@link voiceChord} and {@link voiceProgression}.\n *\n * @category Voicing & Counterpoint\n */\nexport type VoicingOptions = {\n /**\n * Number of voices to realize. Ignored when `ranges` is given.\n *\n * @defaultValue 4\n */\n voices?: number;\n /** Explicit per-voice ranges, ascending (index 0 = lowest). Takes precedence over `voices`. */\n ranges?: VoiceRange[];\n /**\n * Maximum spacing in semitones between adjacent upper voices.\n *\n * @defaultValue 12\n */\n maxSpacing?: number;\n /**\n * Maximum number of chords {@link voiceProgression} will voice. The search\n * per chord is bounded internally, so the cost of a progression is linear in\n * its length; this is the guard against an unbounded caller, not a limit on\n * the search.\n *\n * @defaultValue 1000000\n */\n budget?: number;\n /**\n * The prevailing key. Supplying it enables the rules that only make sense\n * relative to a tonic: the leading tone is neither doubled nor left\n * unresolved. Without it, voicings are chosen from chord structure and\n * voice-leading distance alone.\n */\n key?: KeyScale;\n};\n\n/** Overall pitch floor/ceiling used when deriving ranges for arbitrary voice counts. */\nconst DERIVED_LOW = 40;\nconst DERIVED_HIGH = 79;\n/** Span of each derived per-voice range in semitones. */\nconst DERIVED_SPAN = 19;\n\n/**\n * Resolve the per-voice ranges implied by the options: explicit `ranges` win,\n * four voices use {@link SATB_RANGES}, and other counts get evenly spaced\n * ranges spanning roughly the bass-to-soprano compass.\n */\nexport function resolveRanges(opts?: VoicingOptions): VoiceRange[] {\n if (opts?.ranges !== undefined) {\n if (opts.ranges.length === 0) {\n throw new InvalidInputError('ranges must contain at least one voice range');\n }\n assertGenerationBudget(opts.ranges.length, 'voice ranges', 128);\n return opts.ranges.map((range, index) => {\n assertFiniteNumber(range.min, `ranges[${index}].min`);\n assertFiniteNumber(range.max, `ranges[${index}].max`);\n if (range.min > range.max) {\n throw new InvalidInputError(`ranges[${index}].min must not exceed max`);\n }\n assertGenerationBudget(\n Math.floor(range.max) - Math.ceil(range.min) + 1,\n `ranges[${index}] span`,\n 4096,\n );\n return { ...range };\n });\n }\n const voices = opts?.voices ?? 4;\n assertPositiveInt(voices, 'voices', 128);\n if (voices === 4) {\n return SATB_RANGES.map((range) => ({ ...range }));\n }\n if (voices === 1) {\n return [{ min: DERIVED_LOW, max: DERIVED_HIGH }];\n }\n const ranges: VoiceRange[] = [];\n for (let i = 0; i < voices; i += 1) {\n const min = Math.round(\n DERIVED_LOW + (i * (DERIVED_HIGH - DERIVED_SPAN - DERIVED_LOW)) / (voices - 1),\n );\n ranges.push({ min, max: min + DERIVED_SPAN });\n }\n return ranges;\n}\n\n/**\n * Resolve and validate the adjacent-voice spacing limit. Shared by every entry\n * point so `nextVoicing` cannot accept a NaN that silently disables the spacing\n * constraint, nor a negative value that reports itself as an impossible range.\n */\nexport function resolveMaxSpacing(opts?: VoicingOptions): number {\n const maxSpacing = opts?.maxSpacing ?? DEFAULT_MAX_SPACING;\n assertFiniteNumber(maxSpacing, 'maxSpacing');\n if (maxSpacing < 0) {\n throw new InvalidInputError('maxSpacing must be non-negative');\n }\n return maxSpacing;\n}\n\n/**\n * Realize a single chord as one MIDI pitch per voice, ascending (index 0 =\n * lowest). The bass voice takes the chord's `bassPc` when set, otherwise the\n * root; upper voices take chord pitch classes, doubling the root or fifth as\n * needed to fill all voices. The result stays inside each voice's range, keeps\n * adjacent upper voices within `maxSpacing`, avoids voice crossing, and is\n * deterministic: a compact close-position voicing centered in the ranges.\n *\n * @param chord The chord to voice.\n * @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.\n * @returns MIDI pitches, ascending, one per voice.\n * @throws If no voicing fits the given ranges.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceChord } from '@libraz/libcantus';\n * const chord = parseChordSymbol('Cmaj7');\n * voiceChord(chord); // four ascending MIDI pitches within the SATB ranges\n * ```\n * @category Voicing & Counterpoint\n */\nexport function voiceChord(chord: Chord, opts?: VoicingOptions): number[] {\n const ranges = resolveRanges(opts);\n const maxSpacing = resolveMaxSpacing(opts);\n const candidates = enumerateVoicings(chord, ranges, maxSpacing);\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n let score = structuralPenalty(candidate, chord, opts?.key);\n for (let i = 0; i < candidate.length; i += 1) {\n const pitch = candidate[i];\n const range = ranges[i];\n if (pitch === undefined || range === undefined) {\n continue;\n }\n // Prefer pitches near the middle of each voice's range for a centered,\n // compact default voicing.\n score += Math.abs(pitch - (range.min + range.max) / 2);\n }\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new NoSolutionError(\n `no voicing satisfies the given ranges for ${formatChordSymbol(chord)}`,\n );\n }\n return best;\n}\n\n/**\n * Voice a chord progression with smooth voice leading. The first chord is\n * voiced with {@link voiceChord}; each subsequent chord picks, from a bounded\n * deterministic candidate set, the voicing minimizing the voice-leading cost\n * from the previous voicing plus a large penalty per counterpoint violation\n * (parallel perfects/octaves, voice crossing, voice overlap, and over-wide\n * upper-voice spacing).\n *\n * Tendency tones are resolved rather than merely moved economically: the voice\n * holding a chordal seventh falls by step unless the next chord keeps that tone,\n * and — when `opts.key` is given — the leading tone rises to the tonic and is\n * never doubled. Without a key the leading-tone rules cannot apply, since\n * nothing identifies which pitch class is the leading tone.\n *\n * @param chords The chords to voice in order.\n * @param opts Voicing options; defaults to four voices in {@link SATB_RANGES}.\n * @returns One voicing per chord, each ascending with one MIDI pitch per voice.\n * @throws If any chord admits no voicing within the given ranges, or if the\n * progression is longer than `opts.budget` chords.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceProgression } from '@libraz/libcantus';\n * const chords = ['C', 'Am', 'F', 'G'].map((s) => parseChordSymbol(s));\n * voiceProgression(chords); // one four-voice voicing per chord, smoothly led\n * ```\n * @category Voicing & Counterpoint\n */\n/** The unsatisfiable-constraints error for one chord of a progression. */\nfunction noSolutionAt(index: number, chord: Chord): NoSolutionError {\n return new NoSolutionError(\n `no voicing satisfies the given ranges for ${formatChordSymbol(chord)} at index ${index}`,\n { at: index },\n );\n}\n\n/**\n * Run one chord's work, re-raising an unsatisfiable-constraints failure with\n * the chord it happened on. Voicing a whole lead sheet is otherwise told only\n * that some chord did not fit.\n */\nfunction locate<T>(index: number, chord: Chord, work: () => T): T {\n try {\n return work();\n } catch (error) {\n if (error instanceof NoSolutionError) {\n throw noSolutionAt(index, chord);\n }\n throw error;\n }\n}\n\nexport function voiceProgression(chords: readonly Chord[], opts?: VoicingOptions): number[][] {\n assertGenerationBudget(chords.length, 'voiced progression chords', opts?.budget);\n const ranges = resolveRanges(opts);\n const maxSpacing = resolveMaxSpacing(opts);\n const result: number[][] = [];\n let prev: number[] | undefined;\n let prevChord: Chord | undefined;\n for (let index = 0; index < chords.length; index += 1) {\n const chord = chords[index];\n if (chord === undefined) {\n continue;\n }\n if (prev === undefined) {\n prev = locate(index, chord, () => voiceChord(chord, opts));\n prevChord = chord;\n result.push(prev);\n continue;\n }\n const candidates = locate(index, chord, () => enumerateVoicings(chord, ranges, maxSpacing));\n let best: number[] | undefined;\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of candidates) {\n const unresolved =\n prevChord === undefined\n ? 0\n : resolutionViolations(prev, candidate, prevChord, chord, opts?.key);\n const score =\n structuralPenalty(candidate, chord, opts?.key) +\n voiceLeadingCost(prev, candidate) +\n VIOLATION_PENALTY * violationCount(prev, candidate) +\n RESOLUTION_PENALTY * unresolved;\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw noSolutionAt(index, chord);\n }\n result.push(best);\n prev = best;\n prevChord = chord;\n }\n return result;\n}\n","import { InvalidInputError } from '../../core/errors/index.js';\nimport { assertFiniteNumber, assertInteger } from '../../core/validation/index.js';\nimport type { Chord } from '../chord/index.js';\nimport { chordToneRole } from '../chord/index.js';\nimport { pitchClass } from './internal.js';\n\n/**\n * A tertian voicing style for {@link voiceChordStyled}.\n *\n * - `close`: the plain close-position tertian stack.\n * - `drop2`: the second voice from the top dropped an octave (drop-2 voicing).\n * - `drop3`: the third voice from the top dropped an octave (drop-3 voicing).\n * - `shell`: root plus guide tones (third and seventh) for seventh chords, or\n * root/third/fifth for triads; the fifth and tensions are omitted.\n * - `rootless`: the root omitted, keeping third/fifth/seventh and tensions\n * (a typical left-hand jazz voicing).\n *\n * @category Voicing & Counterpoint\n */\nexport type VoicingStyle = 'close' | 'drop2' | 'drop3' | 'shell' | 'rootless';\n\n/**\n * Options controlling {@link voiceChordStyled}.\n *\n * @category Voicing & Counterpoint\n */\nexport type StyledVoicingOptions = {\n /**\n * Voicing style to build.\n *\n * @defaultValue 'close'\n */\n style?: VoicingStyle;\n /** Constrain the highest voice to this pitch class (0..11) when given. */\n topNote?: number;\n /**\n * Base octave in scientific pitch notation: octave 4 starts the stack near\n * middle C (MIDI 60), i.e. near `12 * (octave + 1)`.\n *\n * @defaultValue 4\n */\n octave?: number;\n /**\n * Omit the root regardless of style (relevant for jazz voicings).\n *\n * @defaultValue false\n */\n rootless?: boolean;\n};\n\n/** Default base octave for a styled close-position stack. */\nconst DEFAULT_STYLE_OCTAVE = 4;\n\n/** Return the pitch classes of a list in order, without duplicates. */\nfunction dedupePcs(pcs: number[]): number[] {\n const seen = new Set<number>();\n const result: number[] = [];\n for (const pc of pcs) {\n if (!seen.has(pc)) {\n seen.add(pc);\n result.push(pc);\n }\n }\n return result;\n}\n\n/** The lowest MIDI pitch of a pitch class at or above a floor. */\nfunction lowestPitchAtOrAbove(pc: number, floor: number): number {\n return floor + ((((pc - pitchClass(floor)) % 12) + 12) % 12);\n}\n\n/** The pitch class in `pcs` whose circular distance to `target` is smallest. */\nfunction nearestPc(target: number, pcs: number[]): number {\n let best = pcs[0] ?? target;\n let bestDist = 12;\n for (const pc of pcs) {\n const dist = Math.min((((pc - target) % 12) + 12) % 12, (((target - pc) % 12) + 12) % 12);\n if (dist < bestDist) {\n bestDist = dist;\n best = pc;\n }\n }\n return best;\n}\n\n/**\n * Realize a single chord as an explicit tertian voicing in a chosen style,\n * independent of the SATB range search used by {@link voiceChord}.\n *\n * The chord tones are stacked in close position from the bass (`bassPc`, else\n * `rootPc`) in the given scientific-pitch `octave` upward (octave 4 starts the\n * bass near C4 = MIDI 60, matching {@link midiToNote} and the bass module).\n * When `topNote` is given the stack is rotated so the highest voice's pitch\n * class equals that note (or the nearest chord tone). The chosen\n * {@link VoicingStyle} then transforms the stack, and the result is returned as\n * ascending MIDI pitches (index 0 = lowest).\n *\n * Every returned pitch is a valid MIDI number: an octave whose stack would run\n * off either end of the 0..127 range is rejected rather than voiced out of\n * range, matching {@link nextVoicing}, which clamps its derived ranges.\n *\n * @param chord The chord to voice.\n * @param opts Styled voicing options; defaults to a close voicing at octave 4.\n * @returns MIDI pitches, ascending, one per retained voice.\n * @throws If the voicing would not fit inside MIDI 0..127 at the given octave.\n * @example\n * ```ts\n * import { parseChordSymbol, voiceChordStyled } from '@libraz/libcantus';\n * const chord = parseChordSymbol('Dm7');\n * voiceChordStyled(chord, { style: 'drop2' }); // ascending MIDI pitches, drop-2 voicing\n * ```\n * @category Voicing & Counterpoint\n */\nexport function voiceChordStyled(chord: Chord, opts?: StyledVoicingOptions): number[] {\n const style = opts?.style ?? 'close';\n const octave = opts?.octave ?? DEFAULT_STYLE_OCTAVE;\n assertInteger(octave, 'styled voicing octave', -10, 10);\n if (opts?.topNote !== undefined) {\n assertFiniteNumber(opts.topNote, 'styled voicing topNote');\n }\n const base = 12 * (octave + 1);\n const rootPc = pitchClass(chord.rootPc);\n const bassPc = pitchClass(chord.bassPc ?? chord.rootPc);\n const omitRoot = style === 'rootless' || opts?.rootless === true;\n\n // Tertian chord tones in order, with the bass tone rotated to the bottom.\n let order = dedupePcs(chord.intervals.map((interval) => pitchClass(chord.rootPc + interval)));\n const bassIndex = order.indexOf(bassPc);\n if (bassIndex > 0) {\n order = [...order.slice(bassIndex), ...order.slice(0, bassIndex)];\n } else if (bassIndex < 0) {\n order = [bassPc, ...order];\n }\n\n if (style === 'shell') {\n const roles = order.map((pc) => chordToneRole(pc, chord));\n // The guide tone is the seventh when present; on a sixth chord the sixth\n // stands in for it, and otherwise the voicing falls back to the fifth.\n const guide = roles.includes('seventh')\n ? 'seventh'\n : roles.includes('sixth')\n ? 'sixth'\n : 'fifth';\n order = order.filter((_pc, i) => {\n const role = roles[i];\n return _pc === bassPc || role === 'root' || role === 'third' || role === guide;\n });\n }\n\n if (omitRoot) {\n order = order.filter((pc) => pc !== rootPc || (chord.bassPc !== undefined && pc === bassPc));\n }\n\n if (order.length === 0) {\n return [];\n }\n\n if (opts?.topNote !== undefined) {\n const target = order.includes(pitchClass(opts.topNote))\n ? pitchClass(opts.topNote)\n : nearestPc(pitchClass(opts.topNote), order);\n const targetIndex = order.indexOf(target);\n order = [...order.slice(targetIndex + 1), ...order.slice(0, targetIndex + 1)];\n }\n\n // Stack the ordered pitch classes upward in close position.\n const stack: number[] = [];\n let prev: number | undefined;\n for (const pc of order) {\n if (prev === undefined) {\n prev = lowestPitchAtOrAbove(pc, base);\n } else {\n let delta = (((pc - pitchClass(prev)) % 12) + 12) % 12;\n if (delta === 0) {\n delta = 12;\n }\n prev += delta;\n }\n stack.push(prev);\n }\n\n if (style === 'drop2' && stack.length >= 2) {\n stack[stack.length - 2] = (stack[stack.length - 2] ?? 0) - 12;\n } else if (style === 'drop3' && stack.length >= 3) {\n stack[stack.length - 3] = (stack[stack.length - 3] ?? 0) - 12;\n }\n\n // An explicit slash bass is a structural requirement, including when it is\n // not a chord member. Drop voicings and top-note rotation may have moved a\n // different tone below it, so lower the retained bass by octaves before the\n // final sort. This gives the bass priority over a conflicting top-note hint.\n if (chord.bassPc !== undefined) {\n const bassIndexInStack = stack.findIndex((pitch) => pitchClass(pitch) === bassPc);\n if (bassIndexInStack >= 0) {\n const otherLowest = Math.min(\n ...stack.filter((_pitch, index) => index !== bassIndexInStack),\n Number.POSITIVE_INFINITY,\n );\n while ((stack[bassIndexInStack] ?? 0) >= otherLowest) {\n stack[bassIndexInStack] = (stack[bassIndexInStack] ?? 0) - 12;\n }\n }\n }\n\n const voicing = stack.sort((a, b) => a - b);\n const lowest = voicing[0] ?? 0;\n const highest = voicing[voicing.length - 1] ?? 0;\n if (lowest < 0 || highest > 127) {\n throw new InvalidInputError(\n `styled voicing at octave ${octave} spans MIDI ${lowest}..${highest}, outside 0..127`,\n );\n }\n return voicing;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  chordToneRole
3
- } from "./chunk-MIWO425M.js";
3
+ } from "./chunk-D2FAJBYL.js";
4
4
 
5
5
  // src/theory/harmony/index.ts
6
6
  function roleOf(pitch, chord, chordId = 0) {
@@ -27,4 +27,4 @@ function roleOf(pitch, chord, chordId = 0) {
27
27
  export {
28
28
  roleOf
29
29
  };
30
- //# sourceMappingURL=chunk-2ZWMSJH6.js.map
30
+ //# sourceMappingURL=chunk-NKIBQ6PT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theory/harmony/index.ts"],"sourcesContent":["import { type Chord, type ChordToneRole, chordToneRole } from '../chord/index.js';\n\n/**\n * Harmonic role a pitch plays within a chord.\n *\n * @category Functional Harmony\n */\nexport type HarmonyRole = ChordToneRole | 'tension' | 'doubling';\n\n/**\n * How firmly a pitch is locked to the chord's identity.\n *\n * - `identity`: moving it produces a different chord (the root).\n * - `quality`: moving it flips the chord quality (the third).\n * - `voicing`: it can move freely without changing chord identity or quality.\n *\n * @category Functional Harmony\n */\nexport type LockLevel = 'identity' | 'quality' | 'voicing';\n\n/**\n * A pitch's role, lock level, and owning chord.\n *\n * @category Functional Harmony\n */\nexport type VoicedRole = {\n role: HarmonyRole;\n lock: LockLevel;\n belongsToChordId: number;\n};\n\n/**\n * Classify a pitch's harmonic role and lock level within a chord.\n *\n * The role comes from the pitch's interval class above the chord root, read\n * against the chord's own template: root (0), third (3/4), fifth (6/7/8),\n * sixth or seventh (9/10/11). Every pitch the template does not claim is\n * `'tension'` — including one that is simply foreign to the chord, which this\n * single-pitch query cannot tell from a colour tone. The root locks the chord\n * identity, the third locks its quality, and everything else is free voicing.\n * In a suspended chord (a chord whose intervals include a 4th (5) or 2nd (2)\n * but no third), the suspended tone takes the third's place as the\n * quality-defining tone and is locked to `quality`, since moving it changes the\n * chord. Detecting an octave doubling requires the surrounding voicing, which\n * this query does not carry, so `'doubling'` is part of the type — the shared\n * vocabulary of harmonic roles — but is never returned here.\n *\n * @param pitch MIDI pitch or bare pitch class.\n * @param chord The chord providing the root reference.\n * @param chordId Identifier stored on the result (defaults to 0).\n * @returns The pitch's role, lock level, and owning chord id.\n * @example\n * ```ts\n * import { roleOf, makeChord } from '@libraz/libcantus';\n * roleOf(64, makeChord(0, 'maj')); // E over C major\n * // { role: 'third', lock: 'quality', belongsToChordId: 0 }\n * ```\n * @category Functional Harmony\n */\nexport function roleOf(pitch: number, chord: Chord, chordId = 0): VoicedRole {\n const interval = (((Math.trunc(pitch) - chord.rootPc) % 12) + 12) % 12;\n const tones = new Set(chord.intervals.map((i) => ((i % 12) + 12) % 12));\n const hasThird = tones.has(3) || tones.has(4);\n const isSuspendedTone =\n !hasThird && ((interval === 5 && tones.has(5)) || (interval === 2 && tones.has(2)));\n const chordRole = chordToneRole(pitch, chord);\n let role: HarmonyRole;\n let lock: LockLevel;\n if (isSuspendedTone) {\n role = 'third'; // suspended tone occupies the third's quality-defining slot\n lock = 'quality';\n } else if (chordRole !== null) {\n role = chordRole;\n lock = chordRole === 'root' ? 'identity' : chordRole === 'third' ? 'quality' : 'voicing';\n } else {\n role = 'tension';\n lock = 'voicing';\n }\n return { role, lock, belongsToChordId: chordId };\n}\n"],"mappings":";;;;;AA2DO,SAAS,OAAO,OAAe,OAAc,UAAU,GAAe;AAC3E,QAAM,aAAc,KAAK,MAAM,KAAK,IAAI,MAAM,UAAU,KAAM,MAAM;AACpE,QAAM,QAAQ,IAAI,IAAI,MAAM,UAAU,IAAI,CAAC,OAAQ,IAAI,KAAM,MAAM,EAAE,CAAC;AACtE,QAAM,WAAW,MAAM,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC;AAC5C,QAAM,kBACJ,CAAC,aAAc,aAAa,KAAK,MAAM,IAAI,CAAC,KAAO,aAAa,KAAK,MAAM,IAAI,CAAC;AAClF,QAAM,YAAY,cAAc,OAAO,KAAK;AAC5C,MAAI;AACJ,MAAI;AACJ,MAAI,iBAAiB;AACnB,WAAO;AACP,WAAO;AAAA,EACT,WAAW,cAAc,MAAM;AAC7B,WAAO;AACP,WAAO,cAAc,SAAS,aAAa,cAAc,UAAU,YAAY;AAAA,EACjF,OAAO;AACL,WAAO;AACP,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,MAAM,kBAAkB,QAAQ;AACjD;","names":[]}
@@ -0,0 +1,106 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunk7MGZ7D62cjs = require('./chunk-7MGZ7D62.cjs');
8
+
9
+ // src/core/tuning/index.ts
10
+ var TWELVE_TET = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });
11
+ function assertTuning(tuning) {
12
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, tuning.refStep, "tuning.refStep");
13
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "tuning.refFreq");
14
+ _chunk7MGZ7D62cjs.assertPositiveInt.call(void 0, tuning.divisions, "tuning.divisions");
15
+ return tuning;
16
+ }
17
+ function edo(n, refFreq = 440, refStep = 69) {
18
+ _chunk7MGZ7D62cjs.assertPositiveInt.call(void 0, n, "EDO divisions");
19
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, refFreq, Number.MIN_VALUE, Number.MAX_VALUE, "reference frequency");
20
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, refStep, "reference step");
21
+ return { refStep, refFreq, divisions: n };
22
+ }
23
+ function frequencyOf(step, tuning = TWELVE_TET) {
24
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, step, "step");
25
+ assertTuning(tuning);
26
+ const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);
27
+ return _chunk7MGZ7D62cjs.assertRange.call(void 0, result, Number.MIN_VALUE, Number.MAX_VALUE, "frequency result");
28
+ }
29
+ function nearestStep(freq, tuning = TWELVE_TET) {
30
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
31
+ assertTuning(tuning);
32
+ return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));
33
+ }
34
+ function stepOf(freq, tuning = TWELVE_TET) {
35
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, freq, Number.MIN_VALUE, Number.MAX_VALUE, "frequency");
36
+ assertTuning(tuning);
37
+ return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);
38
+ }
39
+ function centsFromNearestStep(freq, tuning = TWELVE_TET) {
40
+ const exact = stepOf(freq, tuning);
41
+ return centsOfSteps(exact - Math.round(exact), tuning);
42
+ }
43
+ function stepsOfCents(cents, tuning = TWELVE_TET) {
44
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, cents, "cents");
45
+ assertTuning(tuning);
46
+ return _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, cents * tuning.divisions / 1200, "steps result");
47
+ }
48
+ function centsToRatio(cents) {
49
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, cents, "cents");
50
+ return 2 ** (cents / 1200);
51
+ }
52
+ function centsBetweenFreq(a, b) {
53
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, a, Number.MIN_VALUE, Number.MAX_VALUE, "first frequency");
54
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, b, Number.MIN_VALUE, Number.MAX_VALUE, "second frequency");
55
+ return 1200 * (Math.log2(b) - Math.log2(a));
56
+ }
57
+ function centsOfSteps(steps, tuning = TWELVE_TET) {
58
+ _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, steps, "steps");
59
+ assertTuning(tuning);
60
+ return _chunk7MGZ7D62cjs.assertFiniteNumber.call(void 0, steps * 1200 / tuning.divisions, "cents result");
61
+ }
62
+ function ratioToCents(numerator, denominator) {
63
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, numerator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio numerator");
64
+ _chunk7MGZ7D62cjs.assertRange.call(void 0, denominator, Number.MIN_VALUE, Number.MAX_VALUE, "ratio denominator");
65
+ return 1200 * (Math.log2(numerator) - Math.log2(denominator));
66
+ }
67
+ var JUST_RATIOS = Object.freeze({
68
+ 0: [1, 1],
69
+ 1: [16, 15],
70
+ 2: [9, 8],
71
+ 3: [6, 5],
72
+ 4: [5, 4],
73
+ 5: [4, 3],
74
+ 6: [45, 32],
75
+ 7: [3, 2],
76
+ 8: [8, 5],
77
+ 9: [5, 3],
78
+ 10: [9, 5],
79
+ 11: [15, 8],
80
+ 12: [2, 1]
81
+ });
82
+ function justDeviationCents(semitoneClass) {
83
+ _chunk7MGZ7D62cjs.assertInteger.call(void 0, semitoneClass, "semitone class", 0, 12);
84
+ const ratio = JUST_RATIOS[semitoneClass];
85
+ if (!ratio) {
86
+ throw new (0, _chunk7MGZ7D62cjs.InvalidInputError)(`semitone class has no just ratio: ${semitoneClass}`);
87
+ }
88
+ return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;
89
+ }
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+ exports.TWELVE_TET = TWELVE_TET; exports.edo = edo; exports.frequencyOf = frequencyOf; exports.nearestStep = nearestStep; exports.stepOf = stepOf; exports.centsFromNearestStep = centsFromNearestStep; exports.stepsOfCents = stepsOfCents; exports.centsToRatio = centsToRatio; exports.centsBetweenFreq = centsBetweenFreq; exports.centsOfSteps = centsOfSteps; exports.ratioToCents = ratioToCents; exports.JUST_RATIOS = JUST_RATIOS; exports.justDeviationCents = justDeviationCents;
106
+ //# sourceMappingURL=chunk-NP4NA2FB.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/libcantus/libcantus/dist/chunk-NP4NA2FB.cjs","../src/core/tuning/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/libcantus/libcantus/dist/chunk-NP4NA2FB.cjs","sourcesContent":[null,"/**\n * Tuning and microtonality: frequency conversion, cents, arbitrary equal\n * temperaments (EDO), and just-intonation ratios.\n *\n * The rest of the library reasons in twelve-tone pitch classes, which is a\n * deliberate scope choice for tonal theory. This module is the escape hatch for\n * anyone who needs actual frequencies, cents, non-12 equal temperaments, or the\n * acoustic (just) tuning behind the tempered intervals.\n */\n\nimport { InvalidInputError } from '../errors/index.js';\nimport {\n assertFiniteNumber,\n assertInteger,\n assertPositiveInt,\n assertRange,\n} from '../validation/index.js';\n\n/**\n * An equal-tempered tuning: a reference pitch and a number of equal divisions of\n * the octave. With `divisions === 12` a step index is an ordinary MIDI number.\n *\n * @category Pitch & Intervals\n */\nexport type Tuning = {\n /** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */\n readonly refStep: number;\n /** Frequency in Hz of `refStep`. */\n readonly refFreq: number;\n /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */\n readonly divisions: number;\n};\n\n/**\n * Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.\n *\n * @category Pitch & Intervals\n */\nexport const TWELVE_TET: Tuning = Object.freeze({ refStep: 69, refFreq: 440, divisions: 12 });\n\nfunction assertTuning(tuning: Tuning): Tuning {\n assertFiniteNumber(tuning.refStep, 'tuning.refStep');\n assertRange(tuning.refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'tuning.refFreq');\n assertPositiveInt(tuning.divisions, 'tuning.divisions');\n return tuning;\n}\n\n/**\n * Build an equal temperament with `n` divisions of the octave.\n *\n * @param n Divisions of the octave (e.g. 19, 24, 31).\n * @param refFreq Reference frequency in Hz (default 440).\n * @param refStep Step index of the reference (default 69).\n * @returns The tuning.\n * @example\n * ```ts\n * import { edo, frequencyOf } from '@libraz/libcantus';\n * const et19 = edo(19); // { refStep: 69, refFreq: 440, divisions: 19 }\n * frequencyOf(70, et19); // one 19-EDO step above A4\n * ```\n * @category Pitch & Intervals\n */\nexport function edo(n: number, refFreq = 440, refStep = 69): Tuning {\n assertPositiveInt(n, 'EDO divisions');\n assertRange(refFreq, Number.MIN_VALUE, Number.MAX_VALUE, 'reference frequency');\n assertFiniteNumber(refStep, 'reference step');\n return { refStep, refFreq, divisions: n };\n}\n\n/**\n * Frequency in Hz of a step index under a tuning.\n *\n * @param step Step index (a MIDI number under 12-EDO).\n * @param tuning The tuning (default 12-TET).\n * @returns The frequency in Hz.\n * @example\n * ```ts\n * import { frequencyOf } from '@libraz/libcantus';\n * frequencyOf(69); // 440 (A4 in 12-TET)\n * frequencyOf(60); // middle C in Hz\n * ```\n * @category Pitch & Intervals\n */\nexport function frequencyOf(step: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(step, 'step');\n assertTuning(tuning);\n const result = tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);\n return assertRange(result, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency result');\n}\n\n/**\n * Nearest step index to a frequency under a tuning (the inverse of\n * {@link frequencyOf}, rounded).\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The nearest step index.\n * @category Pitch & Intervals\n */\nexport function nearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));\n}\n\n/**\n * Step index of a frequency under a tuning, unrounded.\n *\n * The exact inverse of {@link frequencyOf}. {@link nearestStep} rounds it,\n * which discards how far off the pitch actually is — the figure a tuner\n * display, a pitch-bend amount, or an intonation check is built on.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step index.\n * @example\n * ```ts\n * import { stepOf } from '@libraz/libcantus';\n * stepOf(442); // slightly above 69, the step for A440\n * ```\n * @category Pitch & Intervals\n */\nexport function stepOf(freq: number, tuning: Tuning = TWELVE_TET): number {\n assertRange(freq, Number.MIN_VALUE, Number.MAX_VALUE, 'frequency');\n assertTuning(tuning);\n return tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq);\n}\n\n/**\n * How far a frequency sits from its nearest step, in cents.\n *\n * Positive means sharp of the step, negative flat.\n *\n * @param freq Frequency in Hz.\n * @param tuning The tuning (default 12-TET).\n * @returns The signed deviation in cents.\n * @example\n * ```ts\n * import { centsFromNearestStep } from '@libraz/libcantus';\n * Math.round(centsFromNearestStep(442)); // 8\n * ```\n * @category Pitch & Intervals\n */\nexport function centsFromNearestStep(freq: number, tuning: Tuning = TWELVE_TET): number {\n const exact = stepOf(freq, tuning);\n return centsOfSteps(exact - Math.round(exact), tuning);\n}\n\n/**\n * Steps spanned by an interval in cents, unrounded — the inverse of\n * {@link centsOfSteps}.\n *\n * @param cents The interval in cents.\n * @param tuning The tuning (default 12-TET).\n * @returns The fractional step count.\n * @category Pitch & Intervals\n */\nexport function stepsOfCents(cents: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(cents, 'cents');\n assertTuning(tuning);\n return assertFiniteNumber((cents * tuning.divisions) / 1200, 'steps result');\n}\n\n/**\n * The frequency ratio an interval in cents spans — the inverse of\n * {@link ratioToCents}.\n *\n * Multiply a frequency by this to move it by that many cents, which is how a\n * cents offset (a pitch-bend spec, a tuning table entry) is applied.\n *\n * @param cents The interval in cents.\n * @returns The frequency ratio.\n * @example\n * ```ts\n * import { centsToRatio } from '@libraz/libcantus';\n * 440 * centsToRatio(14); // A440 raised by 14 cents\n * ```\n * @category Pitch & Intervals\n */\nexport function centsToRatio(cents: number): number {\n assertFiniteNumber(cents, 'cents');\n return 2 ** (cents / 1200);\n}\n\n/**\n * Interval in cents between two frequencies.\n *\n * @param a Lower/first frequency in Hz.\n * @param b Upper/second frequency in Hz.\n * @returns Cents from `a` to `b` (negative if `b` is lower).\n * @category Pitch & Intervals\n */\nexport function centsBetweenFreq(a: number, b: number): number {\n assertRange(a, Number.MIN_VALUE, Number.MAX_VALUE, 'first frequency');\n assertRange(b, Number.MIN_VALUE, Number.MAX_VALUE, 'second frequency');\n return 1200 * (Math.log2(b) - Math.log2(a));\n}\n\n/**\n * Cents spanned by a number of equal-temperament steps.\n *\n * @param steps Number of steps.\n * @param tuning The tuning (default 12-TET).\n * @returns The cents.\n * @category Pitch & Intervals\n */\nexport function centsOfSteps(steps: number, tuning: Tuning = TWELVE_TET): number {\n assertFiniteNumber(steps, 'steps');\n assertTuning(tuning);\n return assertFiniteNumber((steps * 1200) / tuning.divisions, 'cents result');\n}\n\n/**\n * Cents of a frequency ratio, e.g. `ratioToCents(3, 2)` ≈ 701.955 for the just\n * perfect fifth.\n *\n * @param numerator Ratio numerator.\n * @param denominator Ratio denominator.\n * @returns The interval in cents.\n * @category Pitch & Intervals\n */\nexport function ratioToCents(numerator: number, denominator: number): number {\n assertRange(numerator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio numerator');\n assertRange(denominator, Number.MIN_VALUE, Number.MAX_VALUE, 'ratio denominator');\n return 1200 * (Math.log2(numerator) - Math.log2(denominator));\n}\n\n/**\n * Five-limit just-intonation ratios for the twelve interval classes above a\n * unison, indexed by semitone class (0..12).\n *\n * @category Pitch & Intervals\n */\nexport const JUST_RATIOS: Readonly<Record<number, readonly [number, number]>> = Object.freeze({\n 0: [1, 1],\n 1: [16, 15],\n 2: [9, 8],\n 3: [6, 5],\n 4: [5, 4],\n 5: [4, 3],\n 6: [45, 32],\n 7: [3, 2],\n 8: [8, 5],\n 9: [5, 3],\n 10: [9, 5],\n 11: [15, 8],\n 12: [2, 1],\n});\n\n/**\n * Cents by which a five-limit just interval departs from its 12-TET tempering\n * (positive means the just interval is wider).\n *\n * @param semitoneClass Semitone class in [0, 12].\n * @returns The deviation in cents.\n * @throws If `semitoneClass` is not an integer in [0, 12].\n * @category Pitch & Intervals\n */\nexport function justDeviationCents(semitoneClass: number): number {\n assertInteger(semitoneClass, 'semitone class', 0, 12);\n const ratio = JUST_RATIOS[semitoneClass];\n if (!ratio) {\n throw new InvalidInputError(`semitone class has no just ratio: ${semitoneClass}`);\n }\n return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;\n}\n"]}