@libraz/libcantus 0.9.2 → 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 (200) hide show
  1. package/README.md +6 -5
  2. package/dist/analyze/index.cjs +41 -1676
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +194 -122
  5. package/dist/analyze/index.d.ts +194 -122
  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-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
  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-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
  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-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/chunk-HKGYCQ34.js +53 -0
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/chunk-I24MV65U.js +106 -0
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
  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-N6IHQUOX.js +1 -0
  36. package/dist/chunk-N6IHQUOX.js.map +1 -0
  37. package/dist/chunk-NKIBQ6PT.js +30 -0
  38. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  39. package/dist/chunk-NP4NA2FB.cjs +106 -0
  40. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  41. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  42. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  43. package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
  44. package/dist/chunk-NYYWMV2C.js.map +1 -0
  45. package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
  46. package/dist/chunk-Q2XUP227.js.map +1 -0
  47. package/dist/chunk-Q6OP4U43.cjs +327 -0
  48. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  49. package/dist/chunk-RC6GBMCD.cjs +845 -0
  50. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  51. package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
  52. package/dist/chunk-RK35RIN7.js.map +1 -0
  53. package/dist/chunk-RUMU3UA6.cjs +53 -0
  54. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  55. package/dist/chunk-T64C7ZD7.cjs +1 -0
  56. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  57. package/dist/chunk-XEBG3G53.js +498 -0
  58. package/dist/chunk-XEBG3G53.js.map +1 -0
  59. package/dist/chunk-YLEMGEFH.js +246 -0
  60. package/dist/chunk-YLEMGEFH.js.map +1 -0
  61. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  62. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  63. package/dist/core/index.cjs +130 -431
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +331 -17
  66. package/dist/core/index.d.ts +331 -17
  67. package/dist/core/index.js +71 -13
  68. package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
  69. package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
  70. package/dist/generate/index.cjs +30 -3751
  71. package/dist/generate/index.cjs.map +1 -1
  72. package/dist/generate/index.d.cts +373 -59
  73. package/dist/generate/index.d.ts +373 -59
  74. package/dist/generate/index.js +23 -9
  75. package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
  76. package/dist/index-70p168VM.d.ts +192 -0
  77. package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
  78. package/dist/index-BtXT6UQ0.d.cts +192 -0
  79. package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  80. package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
  81. package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
  82. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  83. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  84. package/dist/index-OG65ocOr.d.cts +272 -0
  85. package/dist/index-OG65ocOr.d.ts +272 -0
  86. package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
  87. package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
  88. package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
  89. package/dist/index.cjs +419 -6848
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.cts +14 -13
  92. package/dist/index.d.ts +14 -13
  93. package/dist/index.js +132 -27
  94. package/dist/model/index.cjs +8 -2423
  95. package/dist/model/index.cjs.map +1 -1
  96. package/dist/model/index.d.cts +433 -74
  97. package/dist/model/index.d.ts +433 -74
  98. package/dist/model/index.js +6 -7
  99. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  100. package/dist/styled-DJbkJQfj.d.cts +506 -0
  101. package/dist/theory/index.cjs +172 -1676
  102. package/dist/theory/index.cjs.map +1 -1
  103. package/dist/theory/index.d.cts +151 -233
  104. package/dist/theory/index.d.ts +151 -233
  105. package/dist/theory/index.js +31 -7
  106. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  107. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  108. package/package.json +73 -24
  109. package/dist/chunk-5HTNMQW4.js.map +0 -1
  110. package/dist/chunk-7Y7WF64D.js.map +0 -1
  111. package/dist/chunk-A4532ZFR.js.map +0 -1
  112. package/dist/chunk-BHUT2S5G.js +0 -22
  113. package/dist/chunk-BHUT2S5G.js.map +0 -1
  114. package/dist/chunk-CCBLVAPK.js +0 -42
  115. package/dist/chunk-CCBLVAPK.js.map +0 -1
  116. package/dist/chunk-COKTX2G5.js.map +0 -1
  117. package/dist/chunk-DYWFNC4A.js +0 -152
  118. package/dist/chunk-DYWFNC4A.js.map +0 -1
  119. package/dist/chunk-GQKN5KDT.js.map +0 -1
  120. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  121. package/dist/chunk-MSWFZF7J.js +0 -118
  122. package/dist/chunk-MSWFZF7J.js.map +0 -1
  123. package/dist/chunk-R3S2LUBH.js.map +0 -1
  124. package/dist/chunk-SDM3VJJ5.js +0 -41
  125. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  126. package/dist/chunk-XTC3X6MS.js.map +0 -1
  127. package/dist/chunk-ZXW5YIWK.js +0 -55
  128. package/dist/chunk-ZXW5YIWK.js.map +0 -1
  129. package/dist/index-CGMZNCx6.d.cts +0 -140
  130. package/dist/index-CGMZNCx6.d.ts +0 -140
  131. package/dist/styled-BjXtOFNk.d.cts +0 -282
  132. package/dist/styled-D7BAx3S3.d.ts +0 -282
  133. package/src/analyze/arrange/index.ts +0 -21
  134. package/src/analyze/arrange/internal.ts +0 -125
  135. package/src/analyze/arrange/tension.ts +0 -187
  136. package/src/analyze/arrange/tracks.ts +0 -279
  137. package/src/analyze/detect/index.ts +0 -235
  138. package/src/analyze/functional/borrowed.ts +0 -84
  139. package/src/analyze/functional/cadence.ts +0 -66
  140. package/src/analyze/functional/function.ts +0 -184
  141. package/src/analyze/functional/index.ts +0 -23
  142. package/src/analyze/functional/internal.ts +0 -29
  143. package/src/analyze/functional/roman.ts +0 -393
  144. package/src/analyze/index.ts +0 -49
  145. package/src/analyze/timeline/index.ts +0 -415
  146. package/src/analyze/voice/index.ts +0 -330
  147. package/src/core/index.ts +0 -47
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -282
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -53
  152. package/src/core/tuning/index.ts +0 -156
  153. package/src/core/types.ts +0 -29
  154. package/src/generate/bass/index.ts +0 -350
  155. package/src/generate/countermelody/index.ts +0 -379
  156. package/src/generate/drums/beat.ts +0 -266
  157. package/src/generate/drums/euclid.ts +0 -58
  158. package/src/generate/drums/fills.ts +0 -356
  159. package/src/generate/drums/ghost.ts +0 -119
  160. package/src/generate/drums/hihat.ts +0 -285
  161. package/src/generate/drums/hit.ts +0 -31
  162. package/src/generate/drums/index.ts +0 -329
  163. package/src/generate/drums/internal.ts +0 -257
  164. package/src/generate/drums/kick.ts +0 -160
  165. package/src/generate/drums/percussion.ts +0 -121
  166. package/src/generate/drums/rng.ts +0 -7
  167. package/src/generate/drums/swing.ts +0 -71
  168. package/src/generate/groove/index.ts +0 -326
  169. package/src/generate/harmonize/index.ts +0 -440
  170. package/src/generate/index.ts +0 -59
  171. package/src/generate/motif/index.ts +0 -398
  172. package/src/generate/progression/index.ts +0 -370
  173. package/src/generate/reharmony/index.ts +0 -330
  174. package/src/generate/rhythm/index.ts +0 -165
  175. package/src/index.ts +0 -247
  176. package/src/model/chord.ts +0 -423
  177. package/src/model/index.ts +0 -15
  178. package/src/model/interval.ts +0 -82
  179. package/src/model/key.ts +0 -266
  180. package/src/model/note.ts +0 -176
  181. package/src/model/progression.ts +0 -161
  182. package/src/model/shared.ts +0 -12
  183. package/src/theory/chord/index.ts +0 -373
  184. package/src/theory/chordscale/index.ts +0 -374
  185. package/src/theory/counterpoint/index.ts +0 -230
  186. package/src/theory/harmony/index.ts +0 -92
  187. package/src/theory/index.ts +0 -107
  188. package/src/theory/safety/index.ts +0 -419
  189. package/src/theory/scale/degrees.ts +0 -123
  190. package/src/theory/scale/index.ts +0 -28
  191. package/src/theory/scale/key.ts +0 -56
  192. package/src/theory/scale/masks.ts +0 -153
  193. package/src/theory/spelling/index.ts +0 -182
  194. package/src/theory/symbol/index.ts +0 -268
  195. package/src/theory/voicing/index.ts +0 -5
  196. package/src/theory/voicing/internal.ts +0 -168
  197. package/src/theory/voicing/leading.ts +0 -112
  198. package/src/theory/voicing/satb.ts +0 -195
  199. package/src/theory/voicing/styled.ts +0 -176
  200. package/tsconfig.json +0 -23
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/theory/chordscale/index.ts","../src/theory/spelling/index.ts","../src/theory/symbol/index.ts","../src/theory/voicing/internal.ts","../src/theory/voicing/leading.ts","../src/theory/voicing/satb.ts","../src/theory/voicing/styled.ts"],"sourcesContent":["import type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport { NAMED_SCALES } from '../scale/index.js';\n\n/** Reduce a value to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/** 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 * 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 scale name. For bare\n * triads and smaller chords, heptatonic scales rank before pentatonics and\n * other sizes: a pentatonic adds no modal color over a triad, so the seven-note\n * modes are the more useful answer. The chromatic scale is only returned as a\n * fallback when no other scale contains the chord.\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 = NAMED_SCALES[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 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 *\n * @category Scales\n */\nexport function avoidNotes(chord: Chord, scaleName: string): number[] {\n const mask = NAMED_SCALES[scaleName];\n if (mask === undefined) {\n return [];\n }\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 *\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: string): number[] {\n const mask = NAMED_SCALES[scaleName];\n if (mask === undefined) {\n return [];\n }\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 = NAMED_SCALES[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 { formatNote } 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/** Semitone offset of each natural letter above C: C D E F G A B. */\nconst LETTER_SEMITONES = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/**\n * 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. This covers the flat-side alterations (b2, b3, b6, b7), the\n * raised fourth (#4), and the raised degrees of a minor key: the raised sixth\n * (#6) and the raised leading tone (#7). The latter two keep sharp-side minor\n * keys spelling their raised degrees with the key-correct letter (e.g. F# minor\n * spells its leading tone E#, not F).\n *\n * The `alter` field is illustrative only; `spellPitchClass` recomputes the\n * accidental from the chosen letter.\n */\nconst CHROMATIC_SPELLING: Record<number, { degreeOffset: number; alter: number }> = {\n 1: { degreeOffset: 1, alter: -1 }, // b2\n 3: { degreeOffset: 2, alter: -1 }, // b3\n 6: { degreeOffset: 3, alter: 1 }, // #4\n 8: { degreeOffset: 5, alter: -1 }, // b6\n 9: { degreeOffset: 5, alter: 1 }, // #6 (raised sixth of a minor key)\n 10: { degreeOffset: 6, alter: -1 }, // b7\n 11: { degreeOffset: 6, alter: 1 }, // #7 (raised leading tone of a minor key)\n};\n\nfunction mod12(n: number): number {\n return ((n % 12) + 12) % 12;\n}\n\nfunction mod7(n: number): number {\n return ((n % 7) + 7) % 7;\n}\n\n/** Natural pitch class of a letter (0 = C .. 6 = B). */\nfunction naturalPc(letter: number): number {\n return LETTER_SEMITONES[mod7(letter)] ?? 0;\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/** 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/**\n * Spell a single pitch class relative to a spelled tonic and key.\n *\n * Diatonic pitch classes take the scale's letter for their degree; the common\n * chromatic tones (b2, b3, #4, b6, b7) and a minor key's raised degrees (#6, #7)\n * take their conventional, key-correct accidental spelling; anything else falls\n * back to a sharp spelling of the nearest natural.\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 const tones = scaleTonesInDegreeOrder(key);\n\n if (isHeptatonic(key)) {\n const degree = tones.indexOf(mod12(pc));\n if (degree >= 0) {\n const letter = mod7(tonic.letter + degree);\n return { letter, alter: alterFor(letter, pc) };\n }\n const chromatic = CHROMATIC_SPELLING[offset];\n if (chromatic) {\n const letter = mod7(tonic.letter + chromatic.degreeOffset);\n return { letter, alter: alterFor(letter, pc) };\n }\n }\n\n // Fallback: name the pitch class from the nearest natural, preferring a sharp.\n for (let letter = 0; letter < 7; letter += 1) {\n if (naturalPc(letter) === mod12(pc)) {\n return { letter, alter: 0 };\n }\n }\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). Non-standard\n * scales are spelled tone-by-tone with a sharp preference.\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 * 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 chord.intervals.map((interval) =>\n spellPitchClass(mod12(chord.rootPc + interval), tonic, key),\n );\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 {\n formatNote,\n midiToNote,\n type Note,\n noteToPitchClass,\n parseNote,\n} from '../../core/pitch/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};\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};\n\n/** Reduce a value to a pitch class in [0, 11]. */\nfunction pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\n\n/** 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 Error(`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 = QUALITY_MAP[before];\n if (beforeQuality !== undefined && BASS_RE.test(after)) {\n return makeSpelledChord(rootNote, beforeQuality, parseNote(after));\n }\n }\n\n const quality = QUALITY_MAP[rest];\n if (quality === undefined) {\n throw new Error(`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 */\nfunction pitchClassName(pc: number, hint: PitchSpelling | undefined, flats?: boolean): string {\n if (flats === undefined && hint !== undefined && noteToPitchClass(hint) === pc) {\n return formatNote(hint);\n }\n const note = midiToNote(60 + pc, flats ? '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 const rootPc = pitchClass(chord.rootPc);\n const rootName = pitchClassName(rootPc, chord.rootSpelling, opts?.flats);\n const suffix = CANONICAL_SUFFIX[chord.quality];\n let symbol = `${rootName}${suffix}`;\n if (chord.bassPc !== undefined && pitchClass(chord.bassPc) !== rootPc) {\n symbol += `/${pitchClassName(pitchClass(chord.bassPc), chord.bassSpelling, opts?.flats)}`;\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 type { Chord } from '../chord/index.js';\nimport { chordPitchClasses } from '../chord/index.js';\nimport {\n createsParallelPerfect,\n createsVoiceCrossing,\n createsVoiceOverlap,\n exceedsSpacing,\n} 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/** Score penalty per doubled tone that is neither the root nor the fifth. */\nconst POOR_DOUBLING_PENALTY = 4;\n/** Hard cap on candidate voicings evaluated per chord, keeping the search bounded. */\nconst MAX_CANDIDATES = 4000;\n\n/** Reduce a value to a pitch class in [0, 11]. */\nexport function pitchClass(value: number): number {\n return ((Math.trunc(value) % 12) + 12) % 12;\n}\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}.\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 const results: number[][] = [];\n const current: number[] = [];\n const build = (voice: number): void => {\n if (results.length >= MAX_CANDIDATES) {\n return;\n }\n if (voice === ranges.length) {\n results.push([...current]);\n return;\n }\n const range = ranges[voice];\n if (range === undefined) {\n return;\n }\n const pcs = voice === 0 ? [bassPc] : chordPcs;\n const prev = current[voice - 1];\n for (const pc of pcs) {\n for (const pitch of pitchesForPc(pc, range)) {\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 };\n build(0);\n return results;\n}\n\n/**\n * Structural quality penalty of a single voicing: heavily penalize missing\n * chord tones, mildly penalize doubling anything other than the root or fifth.\n */\nexport function structuralPenalty(pitches: number[], chord: Chord): 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 += MISSING_TONE_PENALTY;\n }\n }\n const rootPc = pitchClass(chord.rootPc);\n const fifthPc = pitchClass(chord.rootPc + 7);\n for (const [pc, count] of counts) {\n if (count > 1 && pc !== rootPc && pc !== fifthPc) {\n penalty += (count - 1) * POOR_DOUBLING_PENALTY;\n }\n }\n return penalty;\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, voice crossings in the new voicing, and — on adjacent pairs — voice\n * overlaps and over-wide spacing between upper voices.\n */\nexport function violationCount(prev: number[], cur: number[], maxSpacing: 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 (createsVoiceCrossing(curUpper, curLower)) {\n count += 1;\n }\n if (upper === lower + 1) {\n if (createsVoiceOverlap(prevUpper, curUpper, prevLower, curLower)) {\n count += 1;\n }\n if (lower > 0 && exceedsSpacing(curUpper, curLower, maxSpacing)) {\n count += 1;\n }\n }\n }\n }\n return count;\n}\n","import type { Chord } from '../chord/index.js';\nimport { createsHiddenParallelPerfect } from '../counterpoint/index.js';\nimport {\n DEFAULT_MAX_SPACING,\n enumerateVoicings,\n structuralPenalty,\n VIOLATION_PENALTY,\n violationCount,\n} from './internal.js';\nimport type { VoicingOptions } from './satb.js';\nimport { 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 {@link 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 no voicing fits the derived ranges.\n * @category Voicing & Counterpoint\n */\nexport function nextVoicing(current: number[], chord: Chord, opts?: VoicingOptions): number[] {\n const ranges =\n opts?.ranges !== undefined || opts?.voices !== undefined\n ? resolveRanges(opts)\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 const maxSpacing = opts?.maxSpacing ?? DEFAULT_MAX_SPACING;\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) +\n voiceLeadingCost(current, candidate) +\n VIOLATION_PENALTY * violationCount(current, candidate, maxSpacing);\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new Error('no voicing satisfies the given ranges');\n }\n return best;\n}\n","import type { Chord } from '../chord/index.js';\nimport {\n DEFAULT_MAX_SPACING,\n enumerateVoicings,\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 min: number;\n 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 VoiceRange[] = [\n { min: 40, max: 60 },\n { min: 48, max: 67 },\n { min: 55, max: 74 },\n { 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\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 Error('ranges must contain at least one voice range');\n }\n return opts.ranges.map((range) => ({ ...range }));\n }\n const voices = opts?.voices ?? 4;\n if (voices < 1) {\n throw new Error('voices must be at least 1');\n }\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 * 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 = opts?.maxSpacing ?? DEFAULT_MAX_SPACING;\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);\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 Error('no voicing satisfies the given ranges');\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 * @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.\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 */\nexport function voiceProgression(chords: Chord[], opts?: VoicingOptions): number[][] {\n const ranges = resolveRanges(opts);\n const maxSpacing = opts?.maxSpacing ?? DEFAULT_MAX_SPACING;\n const result: number[][] = [];\n let prev: number[] | undefined;\n for (const chord of chords) {\n if (prev === undefined) {\n prev = voiceChord(chord, opts);\n result.push(prev);\n continue;\n }\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) +\n voiceLeadingCost(prev, candidate) +\n VIOLATION_PENALTY * violationCount(prev, candidate, maxSpacing);\n if (score < bestScore) {\n bestScore = score;\n best = candidate;\n }\n }\n if (best === undefined) {\n throw new Error('no voicing satisfies the given ranges');\n }\n result.push(best);\n prev = best;\n }\n return result;\n}\n","import 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 for the close stack; the stack begins near `12 * octave`.\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 * @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 * @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 base = 12 * ((opts?.octave ?? DEFAULT_STYLE_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 role === 'root' || role === 'third' || role === guide;\n });\n }\n\n if (omitRoot) {\n order = order.filter((pc) => pc !== rootPc);\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 return stack.sort((a, b) => a - b);\n}\n"],"mappings":"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","names":["pitchClass","pitchClass","pitchClass"]}
@@ -1,55 +0,0 @@
1
- // src/core/tuning/index.ts
2
- var TWELVE_TET = { refStep: 69, refFreq: 440, divisions: 12 };
3
- function edo(n, refFreq = 440, refStep = 69) {
4
- return { refStep, refFreq, divisions: n };
5
- }
6
- function frequencyOf(step, tuning = TWELVE_TET) {
7
- return tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);
8
- }
9
- function nearestStep(freq, tuning = TWELVE_TET) {
10
- return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));
11
- }
12
- function centsBetweenFreq(a, b) {
13
- return 1200 * Math.log2(b / a);
14
- }
15
- function centsOfSteps(steps, tuning = TWELVE_TET) {
16
- return steps * 1200 / tuning.divisions;
17
- }
18
- function ratioToCents(numerator, denominator) {
19
- return 1200 * Math.log2(numerator / denominator);
20
- }
21
- var JUST_RATIOS = {
22
- 0: [1, 1],
23
- 1: [16, 15],
24
- 2: [9, 8],
25
- 3: [6, 5],
26
- 4: [5, 4],
27
- 5: [4, 3],
28
- 6: [45, 32],
29
- 7: [3, 2],
30
- 8: [8, 5],
31
- 9: [5, 3],
32
- 10: [9, 5],
33
- 11: [15, 8],
34
- 12: [2, 1]
35
- };
36
- function justDeviationCents(semitoneClass) {
37
- const ratio = JUST_RATIOS[semitoneClass];
38
- if (!ratio) {
39
- return Number.NaN;
40
- }
41
- return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;
42
- }
43
-
44
- export {
45
- TWELVE_TET,
46
- edo,
47
- frequencyOf,
48
- nearestStep,
49
- centsBetweenFreq,
50
- centsOfSteps,
51
- ratioToCents,
52
- JUST_RATIOS,
53
- justDeviationCents
54
- };
55
- //# sourceMappingURL=chunk-ZXW5YIWK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/tuning/index.ts"],"sourcesContent":["/**\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\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 refStep: number;\n /** Frequency in Hz of `refStep`. */\n refFreq: number;\n /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */\n 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 = { refStep: 69, refFreq: 440, divisions: 12 };\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 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 return tuning.refFreq * 2 ** ((step - tuning.refStep) / tuning.divisions);\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 return Math.round(tuning.refStep + tuning.divisions * Math.log2(freq / tuning.refFreq));\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 return 1200 * Math.log2(b / 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 return (steps * 1200) / tuning.divisions;\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 return 1200 * Math.log2(numerator / 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: Record<number, [number, number]> = {\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, or NaN if the class has no listed ratio.\n * @category Pitch & Intervals\n */\nexport function justDeviationCents(semitoneClass: number): number {\n const ratio = JUST_RATIOS[semitoneClass];\n if (!ratio) {\n return Number.NaN;\n }\n return ratioToCents(ratio[0], ratio[1]) - semitoneClass * 100;\n}\n"],"mappings":";AA8BO,IAAM,aAAqB,EAAE,SAAS,IAAI,SAAS,KAAK,WAAW,GAAG;AAiBtE,SAAS,IAAI,GAAW,UAAU,KAAK,UAAU,IAAY;AAClE,SAAO,EAAE,SAAS,SAAS,WAAW,EAAE;AAC1C;AAgBO,SAAS,YAAY,MAAc,SAAiB,YAAoB;AAC7E,SAAO,OAAO,UAAU,OAAO,OAAO,OAAO,WAAW,OAAO;AACjE;AAWO,SAAS,YAAY,MAAc,SAAiB,YAAoB;AAC7E,SAAO,KAAK,MAAM,OAAO,UAAU,OAAO,YAAY,KAAK,KAAK,OAAO,OAAO,OAAO,CAAC;AACxF;AAUO,SAAS,iBAAiB,GAAW,GAAmB;AAC7D,SAAO,OAAO,KAAK,KAAK,IAAI,CAAC;AAC/B;AAUO,SAAS,aAAa,OAAe,SAAiB,YAAoB;AAC/E,SAAQ,QAAQ,OAAQ,OAAO;AACjC;AAWO,SAAS,aAAa,WAAmB,aAA6B;AAC3E,SAAO,OAAO,KAAK,KAAK,YAAY,WAAW;AACjD;AAQO,IAAM,cAAgD;AAAA,EAC3D,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,IAAI,EAAE;AAAA,EACV,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,IAAI,EAAE;AAAA,EACV,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,GAAG,CAAC,GAAG,CAAC;AAAA,EACR,IAAI,CAAC,GAAG,CAAC;AAAA,EACT,IAAI,CAAC,IAAI,CAAC;AAAA,EACV,IAAI,CAAC,GAAG,CAAC;AACX;AAUO,SAAS,mBAAmB,eAA+B;AAChE,QAAM,QAAQ,YAAY,aAAa;AACvC,MAAI,CAAC,OAAO;AACV,WAAO,OAAO;AAAA,EAChB;AACA,SAAO,aAAa,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,gBAAgB;AAC5D;","names":[]}
@@ -1,140 +0,0 @@
1
- /**
2
- * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
3
- * intervals.
4
- *
5
- * The rest of the library works in pitch classes (0..11), which cannot tell a
6
- * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
7
- * an explicit spelling layer — a note is a diatonic letter plus a chromatic
8
- * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
9
- * preserved.
10
- */
11
- /**
12
- * A spelled note: a diatonic letter, a chromatic alteration, and an optional
13
- * octave.
14
- *
15
- * - `letter` is 0..6 for C..B.
16
- * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
17
- * 0 natural, +1 sharp, +2 double-sharp.
18
- * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
19
- * absent, the note denotes a bare pitch class.
20
- *
21
- * @category Pitch & Intervals
22
- */
23
- type Note = {
24
- letter: number;
25
- alter: number;
26
- octave?: number;
27
- };
28
- /**
29
- * A spelled interval: a diatonic number, a quality label, and its semitone span.
30
- *
31
- * @category Pitch & Intervals
32
- */
33
- type SpelledInterval = {
34
- /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
35
- number: number;
36
- /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
37
- quality: string;
38
- /** Signed semitone distance from the first note to the second. */
39
- semitones: number;
40
- };
41
- /**
42
- * Parse scientific pitch notation into a {@link Note}.
43
- *
44
- * Accepts a letter (case-insensitive) followed by any number of same-direction
45
- * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
46
- * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
47
- * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
48
- *
49
- * @param text The note text.
50
- * @returns The parsed note.
51
- * @throws If the text is not a valid note.
52
- * @example
53
- * ```ts
54
- * import { parseNote, noteToMidi } from '@libraz/libcantus';
55
- * const n = parseNote('C#4');
56
- * noteToMidi(n); // 61
57
- * ```
58
- * @category Pitch & Intervals
59
- */
60
- declare function parseNote(text: string): Note;
61
- /**
62
- * Render a {@link Note} as scientific pitch notation.
63
- *
64
- * @param note The note to format.
65
- * @returns The note text, including the octave when present.
66
- * @example
67
- * ```ts
68
- * import { formatNote, parseNote } from '@libraz/libcantus';
69
- * formatNote(parseNote('C#4')); // 'C#4'
70
- * ```
71
- * @category Pitch & Intervals
72
- */
73
- declare function formatNote(note: Note): string;
74
- /**
75
- * Pitch class (0..11) of a note, ignoring octave.
76
- *
77
- * @param note The note.
78
- * @returns The pitch class.
79
- * @example
80
- * ```ts
81
- * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
82
- * noteToPitchClass(parseNote('Db')); // 1
83
- * ```
84
- * @category Pitch & Intervals
85
- */
86
- declare function noteToPitchClass(note: Note): number;
87
- /**
88
- * MIDI number of a note (middle C = C4 = 60).
89
- *
90
- * @param note The note; must carry an octave.
91
- * @returns The MIDI number.
92
- * @throws If the note has no octave.
93
- * @example
94
- * ```ts
95
- * import { noteToMidi, parseNote } from '@libraz/libcantus';
96
- * noteToMidi(parseNote('A4')); // 69
97
- * ```
98
- * @category Pitch & Intervals
99
- */
100
- declare function noteToMidi(note: Note): number;
101
- /**
102
- * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
103
- *
104
- * The input is rounded to the nearest integer but is not clamped to the MIDI
105
- * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
106
- * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
107
- *
108
- * @param midi The MIDI number.
109
- * @param spelling Whether to prefer sharps or flats for black keys.
110
- * @returns The spelled note, with octave.
111
- * @example
112
- * ```ts
113
- * import { midiToNote, formatNote } from '@libraz/libcantus';
114
- * formatNote(midiToNote(61)); // 'C#4'
115
- * formatNote(midiToNote(61, 'flat')); // 'Db4'
116
- * ```
117
- * @category Pitch & Intervals
118
- */
119
- declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
120
- /**
121
- * The spelled interval from one note to another.
122
- *
123
- * When both notes carry octaves, the interval reflects their full signed
124
- * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
125
- * interval is measured within a single ascending octave from `a` to `b`.
126
- *
127
- * @param a The lower/first note.
128
- * @param b The upper/second note.
129
- * @returns The diatonic number, quality, and semitone span.
130
- * @example
131
- * ```ts
132
- * import { spelledInterval, parseNote } from '@libraz/libcantus';
133
- * spelledInterval(parseNote('C4'), parseNote('G4'));
134
- * // { number: 5, quality: 'P', semitones: 7 }
135
- * ```
136
- * @category Pitch & Intervals
137
- */
138
- declare function spelledInterval(a: Note, b: Note): SpelledInterval;
139
-
140
- export { type Note as N, type SpelledInterval as S, noteToPitchClass as a, formatNote as f, midiToNote as m, noteToMidi as n, parseNote as p, spelledInterval as s };
@@ -1,140 +0,0 @@
1
- /**
2
- * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
3
- * intervals.
4
- *
5
- * The rest of the library works in pitch classes (0..11), which cannot tell a
6
- * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
7
- * an explicit spelling layer — a note is a diatonic letter plus a chromatic
8
- * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
9
- * preserved.
10
- */
11
- /**
12
- * A spelled note: a diatonic letter, a chromatic alteration, and an optional
13
- * octave.
14
- *
15
- * - `letter` is 0..6 for C..B.
16
- * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
17
- * 0 natural, +1 sharp, +2 double-sharp.
18
- * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
19
- * absent, the note denotes a bare pitch class.
20
- *
21
- * @category Pitch & Intervals
22
- */
23
- type Note = {
24
- letter: number;
25
- alter: number;
26
- octave?: number;
27
- };
28
- /**
29
- * A spelled interval: a diatonic number, a quality label, and its semitone span.
30
- *
31
- * @category Pitch & Intervals
32
- */
33
- type SpelledInterval = {
34
- /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
35
- number: number;
36
- /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
37
- quality: string;
38
- /** Signed semitone distance from the first note to the second. */
39
- semitones: number;
40
- };
41
- /**
42
- * Parse scientific pitch notation into a {@link Note}.
43
- *
44
- * Accepts a letter (case-insensitive) followed by any number of same-direction
45
- * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
46
- * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
47
- * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
48
- *
49
- * @param text The note text.
50
- * @returns The parsed note.
51
- * @throws If the text is not a valid note.
52
- * @example
53
- * ```ts
54
- * import { parseNote, noteToMidi } from '@libraz/libcantus';
55
- * const n = parseNote('C#4');
56
- * noteToMidi(n); // 61
57
- * ```
58
- * @category Pitch & Intervals
59
- */
60
- declare function parseNote(text: string): Note;
61
- /**
62
- * Render a {@link Note} as scientific pitch notation.
63
- *
64
- * @param note The note to format.
65
- * @returns The note text, including the octave when present.
66
- * @example
67
- * ```ts
68
- * import { formatNote, parseNote } from '@libraz/libcantus';
69
- * formatNote(parseNote('C#4')); // 'C#4'
70
- * ```
71
- * @category Pitch & Intervals
72
- */
73
- declare function formatNote(note: Note): string;
74
- /**
75
- * Pitch class (0..11) of a note, ignoring octave.
76
- *
77
- * @param note The note.
78
- * @returns The pitch class.
79
- * @example
80
- * ```ts
81
- * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
82
- * noteToPitchClass(parseNote('Db')); // 1
83
- * ```
84
- * @category Pitch & Intervals
85
- */
86
- declare function noteToPitchClass(note: Note): number;
87
- /**
88
- * MIDI number of a note (middle C = C4 = 60).
89
- *
90
- * @param note The note; must carry an octave.
91
- * @returns The MIDI number.
92
- * @throws If the note has no octave.
93
- * @example
94
- * ```ts
95
- * import { noteToMidi, parseNote } from '@libraz/libcantus';
96
- * noteToMidi(parseNote('A4')); // 69
97
- * ```
98
- * @category Pitch & Intervals
99
- */
100
- declare function noteToMidi(note: Note): number;
101
- /**
102
- * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
103
- *
104
- * The input is rounded to the nearest integer but is not clamped to the MIDI
105
- * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
106
- * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
107
- *
108
- * @param midi The MIDI number.
109
- * @param spelling Whether to prefer sharps or flats for black keys.
110
- * @returns The spelled note, with octave.
111
- * @example
112
- * ```ts
113
- * import { midiToNote, formatNote } from '@libraz/libcantus';
114
- * formatNote(midiToNote(61)); // 'C#4'
115
- * formatNote(midiToNote(61, 'flat')); // 'Db4'
116
- * ```
117
- * @category Pitch & Intervals
118
- */
119
- declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
120
- /**
121
- * The spelled interval from one note to another.
122
- *
123
- * When both notes carry octaves, the interval reflects their full signed
124
- * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
125
- * interval is measured within a single ascending octave from `a` to `b`.
126
- *
127
- * @param a The lower/first note.
128
- * @param b The upper/second note.
129
- * @returns The diatonic number, quality, and semitone span.
130
- * @example
131
- * ```ts
132
- * import { spelledInterval, parseNote } from '@libraz/libcantus';
133
- * spelledInterval(parseNote('C4'), parseNote('G4'));
134
- * // { number: 5, quality: 'P', semitones: 7 }
135
- * ```
136
- * @category Pitch & Intervals
137
- */
138
- declare function spelledInterval(a: Note, b: Note): SpelledInterval;
139
-
140
- export { type Note as N, type SpelledInterval as S, noteToPitchClass as a, formatNote as f, midiToNote as m, noteToMidi as n, parseNote as p, spelledInterval as s };