@libraz/libcantus 0.9.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +61 -261
  2. package/README_ja.md +115 -0
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +213 -456
  6. package/dist/analyze/index.d.ts +213 -456
  7. package/dist/analyze/index.js +56 -9
  8. package/dist/chunk-3JXCW6DX.cjs +3220 -0
  9. package/dist/chunk-3JXCW6DX.cjs.map +1 -0
  10. package/dist/chunk-4A2RMUCC.js +7647 -0
  11. package/dist/chunk-4A2RMUCC.js.map +1 -0
  12. package/dist/chunk-6XPTBE7Y.cjs +49 -0
  13. package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
  14. package/dist/chunk-AFM2ULJH.cjs +5121 -0
  15. package/dist/chunk-AFM2ULJH.cjs.map +1 -0
  16. package/dist/chunk-E3F4AHAH.cjs +690 -0
  17. package/dist/chunk-E3F4AHAH.cjs.map +1 -0
  18. package/dist/chunk-ELCTZUJJ.js +379 -0
  19. package/dist/chunk-ELCTZUJJ.js.map +1 -0
  20. package/dist/chunk-FVSI36UV.js +690 -0
  21. package/dist/chunk-FVSI36UV.js.map +1 -0
  22. package/dist/chunk-GEDTJHAC.cjs +1 -0
  23. package/dist/chunk-GEDTJHAC.cjs.map +1 -0
  24. package/dist/chunk-GOZG57ER.js +3220 -0
  25. package/dist/chunk-GOZG57ER.js.map +1 -0
  26. package/dist/chunk-H3VORETI.js +2243 -0
  27. package/dist/chunk-H3VORETI.js.map +1 -0
  28. package/dist/chunk-H42UHPYW.js +1577 -0
  29. package/dist/chunk-H42UHPYW.js.map +1 -0
  30. package/dist/chunk-HFPS7JFD.js +1 -0
  31. package/dist/chunk-INHVXP6Z.js +3053 -0
  32. package/dist/chunk-INHVXP6Z.js.map +1 -0
  33. package/dist/chunk-K4ZJUGF4.cjs +379 -0
  34. package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
  35. package/dist/chunk-KNJEKLKV.js +49 -0
  36. package/dist/chunk-KNJEKLKV.js.map +1 -0
  37. package/dist/chunk-M4CWTJWJ.cjs +7647 -0
  38. package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
  39. package/dist/chunk-MXO72PLY.cjs +211 -0
  40. package/dist/chunk-MXO72PLY.cjs.map +1 -0
  41. package/dist/chunk-NLEU4QSE.cjs +360 -0
  42. package/dist/chunk-NLEU4QSE.cjs.map +1 -0
  43. package/dist/chunk-OE6WK2Y2.cjs +1577 -0
  44. package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
  45. package/dist/chunk-Q2WGKJNN.cjs +1064 -0
  46. package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
  47. package/dist/chunk-QYQQ5J6E.js +1 -0
  48. package/dist/chunk-QYQQ5J6E.js.map +1 -0
  49. package/dist/chunk-RHIJ3S4I.cjs +1 -0
  50. package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
  51. package/dist/chunk-TTCLJX6D.js +211 -0
  52. package/dist/chunk-TTCLJX6D.js.map +1 -0
  53. package/dist/chunk-UADYAYAT.cjs +3053 -0
  54. package/dist/chunk-UADYAYAT.cjs.map +1 -0
  55. package/dist/chunk-VOHZB45S.js +5121 -0
  56. package/dist/chunk-VOHZB45S.js.map +1 -0
  57. package/dist/chunk-VTI6FOLB.js +1064 -0
  58. package/dist/chunk-VTI6FOLB.js.map +1 -0
  59. package/dist/chunk-WWURE4XT.cjs +2243 -0
  60. package/dist/chunk-WWURE4XT.cjs.map +1 -0
  61. package/dist/chunk-YH3BIKHH.js +360 -0
  62. package/dist/chunk-YH3BIKHH.js.map +1 -0
  63. package/dist/core/index.cjs +103 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +96 -345
  66. package/dist/core/index.d.ts +96 -345
  67. package/dist/core/index.js +104 -7
  68. package/dist/generate/index.cjs +89 -10
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +437 -1095
  71. package/dist/generate/index.d.ts +437 -1095
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
  75. package/dist/index-B9MNZboQ.d.cts +770 -0
  76. package/dist/index-B9MNZboQ.d.ts +770 -0
  77. package/dist/index-BGFW-8P2.d.cts +2307 -0
  78. package/dist/index-BO6xE9YU.d.cts +1804 -0
  79. package/dist/index-BkGmlyI3.d.cts +470 -0
  80. package/dist/index-CCNJRG-P.d.cts +1095 -0
  81. package/dist/index-CGpRv5sl.d.cts +643 -0
  82. package/dist/index-C_8o--VE.d.cts +1242 -0
  83. package/dist/index-CgHqxdps.d.ts +2307 -0
  84. package/dist/index-Chbst3Jx.d.ts +1343 -0
  85. package/dist/index-Cj49F892.d.ts +470 -0
  86. package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
  87. package/dist/index-D57QRbVn.d.ts +1804 -0
  88. package/dist/index-DInQqT8Q.d.cts +66 -0
  89. package/dist/index-DInQqT8Q.d.ts +66 -0
  90. package/dist/index-Dzr13q3d.d.cts +1343 -0
  91. package/dist/index-G1uwGGWQ.d.ts +643 -0
  92. package/dist/index-szBv3496.d.ts +1242 -0
  93. package/dist/index.cjs +358 -15
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.d.cts +17 -15
  96. package/dist/index.d.ts +17 -15
  97. package/dist/index.js +407 -64
  98. package/dist/masks-DYbE485J.d.cts +445 -0
  99. package/dist/masks-DYbE485J.d.ts +445 -0
  100. package/dist/model/index.cjs +39 -7
  101. package/dist/model/index.cjs.map +1 -1
  102. package/dist/model/index.d.cts +4685 -243
  103. package/dist/model/index.d.ts +4685 -243
  104. package/dist/model/index.js +40 -8
  105. package/dist/positional-Cptlj-xs.d.ts +287 -0
  106. package/dist/positional-DvH_sd8u.d.cts +287 -0
  107. package/dist/theory/index.cjs +101 -7
  108. package/dist/theory/index.cjs.map +1 -1
  109. package/dist/theory/index.d.cts +513 -173
  110. package/dist/theory/index.d.ts +513 -173
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +16 -11
  113. package/dist/chunk-36N73KZ3.cjs +0 -246
  114. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  115. package/dist/chunk-4HLLZPXT.cjs +0 -3077
  116. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  117. package/dist/chunk-7MGZ7D62.cjs +0 -498
  118. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  119. package/dist/chunk-7NKDPF74.js +0 -3077
  120. package/dist/chunk-7NKDPF74.js.map +0 -1
  121. package/dist/chunk-CAN76NCU.cjs +0 -679
  122. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  123. package/dist/chunk-D2FAJBYL.js +0 -479
  124. package/dist/chunk-D2FAJBYL.js.map +0 -1
  125. package/dist/chunk-EHVEBWM5.js +0 -1449
  126. package/dist/chunk-EHVEBWM5.js.map +0 -1
  127. package/dist/chunk-FCANXBAT.cjs +0 -30
  128. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  129. package/dist/chunk-FNDWXRRI.cjs +0 -479
  130. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  131. package/dist/chunk-GQSGS2N4.js +0 -173
  132. package/dist/chunk-GQSGS2N4.js.map +0 -1
  133. package/dist/chunk-HKGYCQ34.js +0 -53
  134. package/dist/chunk-HKGYCQ34.js.map +0 -1
  135. package/dist/chunk-I24MV65U.js +0 -106
  136. package/dist/chunk-I24MV65U.js.map +0 -1
  137. package/dist/chunk-KMHOICIY.js +0 -679
  138. package/dist/chunk-KMHOICIY.js.map +0 -1
  139. package/dist/chunk-LW53AHQU.cjs +0 -1117
  140. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  141. package/dist/chunk-N6IHQUOX.js +0 -1
  142. package/dist/chunk-NKIBQ6PT.js +0 -30
  143. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  144. package/dist/chunk-NP4NA2FB.cjs +0 -106
  145. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  146. package/dist/chunk-NVE34YGZ.cjs +0 -1449
  147. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  148. package/dist/chunk-NYYWMV2C.js +0 -327
  149. package/dist/chunk-NYYWMV2C.js.map +0 -1
  150. package/dist/chunk-Q2XUP227.js +0 -845
  151. package/dist/chunk-Q2XUP227.js.map +0 -1
  152. package/dist/chunk-Q6OP4U43.cjs +0 -327
  153. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  154. package/dist/chunk-RC6GBMCD.cjs +0 -845
  155. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  156. package/dist/chunk-RK35RIN7.js +0 -1117
  157. package/dist/chunk-RK35RIN7.js.map +0 -1
  158. package/dist/chunk-RUMU3UA6.cjs +0 -53
  159. package/dist/chunk-RUMU3UA6.cjs.map +0 -1
  160. package/dist/chunk-T64C7ZD7.cjs +0 -1
  161. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  162. package/dist/chunk-XEBG3G53.js +0 -498
  163. package/dist/chunk-XEBG3G53.js.map +0 -1
  164. package/dist/chunk-YLEMGEFH.js +0 -246
  165. package/dist/chunk-YLEMGEFH.js.map +0 -1
  166. package/dist/chunk-ZEJR4TKL.cjs +0 -173
  167. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  168. package/dist/function-BtiZb9i9.d.ts +0 -208
  169. package/dist/function-DNugAOrt.d.cts +0 -208
  170. package/dist/index-6VR8-kit.d.ts +0 -143
  171. package/dist/index-70p168VM.d.ts +0 -192
  172. package/dist/index-Be74mcon.d.cts +0 -152
  173. package/dist/index-BtXT6UQ0.d.cts +0 -192
  174. package/dist/index-C5UBbQ4G.d.ts +0 -152
  175. package/dist/index-C83mZI1P.d.ts +0 -242
  176. package/dist/index-DXd9JikN.d.cts +0 -246
  177. package/dist/index-DXd9JikN.d.ts +0 -246
  178. package/dist/index-OG65ocOr.d.cts +0 -272
  179. package/dist/index-OG65ocOr.d.ts +0 -272
  180. package/dist/index-tJhkmQgE.d.cts +0 -143
  181. package/dist/index-y_ak9_Hi.d.cts +0 -242
  182. package/dist/styled-DIRkk9Uz.d.ts +0 -506
  183. package/dist/styled-DJbkJQfj.d.cts +0 -506
  184. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  185. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  186. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -1,17 +1,21 @@
1
- import { N as Note } from '../index-OG65ocOr.js';
2
- import { K as KeyScale } from '../types-BN6_Q9Kl.js';
3
- import { C as Chord } from '../index-C83mZI1P.js';
4
- export { a as ChordQuality, b as ChordSegment, c as ChordSpan, d as ChordToneRole, P as PitchSpelling, e as chordFromDegree, f as chordPitchClasses, g as chordQualities, h as chordToneRole, i as diatonicSeventh, j as diatonicTriad, k as intervalAboveRoot, l as isChordMember, m as makeChord, t as transposeChord } from '../index-C83mZI1P.js';
5
- import { p as ScaleNameInput, r as VoicingOptions } from '../styled-DIRkk9Uz.js';
6
- export { A as ALTERED_MASK, B as BLUES_MASK, C as CHROMATIC_MASK, a as ChordScaleMatch, b as ChordScaleReportEntry, D as DORIAN_MASK, H as HARMONIC_MINOR_MASK, L as LOCRIAN_MASK, c as LOCRIAN_NATURAL2_MASK, d as LYDIAN_DOMINANT_MASK, e as LYDIAN_MASK, M as MAJOR_MASK, f as MAJOR_PENTATONIC_MASK, g as MELODIC_MINOR_MASK, h as MINOR_PENTATONIC_MASK, i as MIXOLYDIAN_B13_MASK, j as MIXOLYDIAN_MASK, N as NAMED_SCALES, k as NATURAL_MINOR_MASK, O as OCTATONIC_HALF_WHOLE_MASK, l as OCTATONIC_WHOLE_HALF_MASK, P as PHRYGIAN_DOMINANT_MASK, m as PHRYGIAN_MASK, S as SATB_RANGES, n as ScaleChoice, o as ScaleName, q as StyledVoicingOptions, V as VoiceRange, s as VoicingStyle, W as WHOLE_TONE_MASK, t as availableTensions, u as avoidNotes, v as chordScaleReport, w as chordScales, x as maskFromOffsets, y as namedScaleMask, z as requireScaleMask, E as scaleMatchesChord, F as scalesForChanges, G as voiceChord, I as voiceChordStyled, J as voiceProgression } from '../styled-DIRkk9Uz.js';
7
- export { H as HarmonyRole, L as LockLevel, V as VoicedRole, r as roleOf } from '../index-tSQh-xwY.js';
8
- export { E as EvaluateSafetyOptions, N as NoteSafety, R as ReasonFlag, S as SafetyProfile, a as SafetyQuery, b as SafetyResult, V as VoiceSnapshot, e as enumerateSafePitches, c as evaluateSafety } from '../index-6VR8-kit.js';
1
+ import { d as KeyScale, S as SpelledInterval, f as Note, h as NoteLike } from '../index-B9MNZboQ.js';
2
+ export { B as BudgetExceededError, I as IntervalLike, b as IntervalQualityLabel, c as InvalidInputError, L as LibcantusError, N as NoSolutionError, i as NoteNameOptions, j as NoteNameSystem, P as ParseResult } from '../index-B9MNZboQ.js';
3
+ import { C as ConsonanceClass } from '../index-DInQqT8Q.js';
4
+ import { K as KeyLike, c as ChordLike, b as Chord } from '../index-szBv3496.js';
5
+ export { A as Alteration, a as AlteredDegree, C as ChordBase, d as ChordQuality, e as ChordSegment, f as ChordSeventh, g as ChordSpan, h as ChordSpec, i as ChordSymbolOptions, j as ChordToneRole, E as EvaluateSafetyOptions, k as KeyRelation, N as NoteSafety, P as PROFILE_WEIGHTS, l as PitchSpelling, m as ProfileWeights, R as ReasonFlag, S as SafetyProfile, n as SafetyQuery, o as SafetyResult, p as SpelledKey, V as VoiceSnapshot, q as chordFromDegree, r as chordFromSpan, s as chordFromSpec, t as chordPitchClasses, u as chordQualities, v as chordSpecIntervals, w as chordSpecOf, x as chordSpecQuality, y as chordToneRole, z as diatonicSeventh, B as diatonicTriad, D as dominantKeyOf, F as enharmonicKeyOf, G as enumerateSafePitches, H as evaluateSafety, I as formatChordSymbol, J as intervalAboveRoot, L as isChordMember, M as keyRelationBetween, O as makeChord, Q as parallelKeyOf, T as parseChordSymbol, U as profileWeights, W as relatedKeysOf, X as relativeKeyOf, Y as spanFromChord, Z as spelledKeyOf, _ as subdominantKeyOf, $ as toChordData, a0 as toKeyScale, a1 as transposeChord, a2 as transposeChordSymbol, a3 as tryParseChordSymbol } from '../index-szBv3496.js';
6
+ import { q as VoicingOptions } from '../index-9wcMhG73.js';
7
+ export { A as AvailableTensionsOptions, a as AvoidNoteUse, b as AvoidNotesOptions, C as ChordScaleMatch, c as ChordScaleReportEntry, K as KeyMode, P as PartWritingOptions, d as PartWritingViolation, e as PartWritingViolationKind, S as SATB_RANGES, f as SCALE_SYSTEMS, g as ScaleChoice, h as ScaleSystem, i as Species, j as SpeciesOptions, k as SpelledVoicing, l as StyledVoicingOptions, T as TRANSPOSING_INSTRUMENTS, m as TransposingInstrument, n as TransposingInstrumentName, V as VoiceIndependenceOptions, o as VoiceIndependenceReport, p as VoiceRange, r as VoicingStyle, s as availableTensions, t as avoidNotes, u as checkPartWriting, v as checkSpecies, w as chordScaleReport, x as chordScales, y as instrumentTransposition, z as keyFromFifths, B as keySignatureFifths, D as scaleMatchesChord, E as scaleSystemOf, F as scalesForChanges, G as spellVoicing, H as supportsFunctionalHarmony, I as toSoundingPitch, J as toWrittenPitch, L as voiceChord, M as voiceChordStyled, N as voiceIndependence, O as voiceProgression } from '../index-9wcMhG73.js';
8
+ export { H as HarmonyRole, L as LockLevel, V as VoicedRole, r as roleOf } from '../index-CunYKLiu.js';
9
+ import { s as ScaleNameInput } from '../masks-DYbE485J.js';
10
+ export { A as ALTERED_MASK, B as BLUES_MASK, C as CHROMATIC_MASK, D as DORIAN_MASK, a as DOUBLE_HARMONIC_MASK, H as HARMONIC_MINOR_MASK, L as LOCRIAN_MASK, b as LOCRIAN_NATURAL2_MASK, c as LYDIAN_DOMINANT_MASK, d as LYDIAN_MASK, M as MAJOR_MASK, e as MAJOR_PENTATONIC_MASK, f as MARWA_MASK, g as MELODIC_MINOR_MASK, h as MINOR_PENTATONIC_MASK, i as MIXOLYDIAN_B13_MASK, j as MIXOLYDIAN_MASK, k as MIYAKO_BUSHI_MASK, N as NAMED_SCALES, l as NATURAL_MINOR_MASK, O as OCTATONIC_HALF_WHOLE_MASK, m as OCTATONIC_WHOLE_HALF_MASK, P as PHRYGIAN_DOMINANT_MASK, n as PHRYGIAN_MASK, o as PURVI_MASK, R as RITSU_MASK, p as RYUKYU_MASK, S as SCALE_ALIASES, q as ScaleAliasName, r as ScaleName, T as TODI_MASK, W as WHOLE_TONE_MASK, t as WORLD_SCALES, u as WorldScaleName, v as maskFromOffsets, w as namedScaleMask, x as requireScaleMask, y as resolveScaleName } from '../masks-DYbE485J.js';
9
11
 
10
12
  /**
11
13
  * Test whether a pitch belongs to the scale.
12
14
  *
13
15
  * @param pitch MIDI pitch or bare pitch class.
14
- * @param key The key/scale to test against.
16
+ * @param key The key/scale to test against. This predicate reads the mask as
17
+ * given rather than taking a key name: the spelling search runs it over
18
+ * masks that name no key, which resolving a name would refuse.
15
19
  * @returns True if the pitch class is a member of the scale.
16
20
  *
17
21
  * @example
@@ -32,26 +36,34 @@ declare function isScaleTone(pitch: number, key: KeyScale): boolean;
32
36
  * distance above and below) the lower pitch is returned.
33
37
  *
34
38
  * @param pitch MIDI pitch to snap.
35
- * @param key The key/scale to snap to.
39
+ * @param key The key/scale to snap to, as a key name, a key/scale, or a `Key`.
36
40
  * @returns The nearest in-scale MIDI pitch.
37
41
  *
38
42
  * @category Scales
39
43
  */
40
- declare function nearestScaleTone(pitch: number, key: KeyScale): number;
44
+ declare function nearestScaleTone(pitch: number, key: KeyLike): number;
41
45
  /**
42
- * Get the 0-based scale degree of a pitch.
46
+ * Get the scale degree of a pitch, counted from 1: the tonic is degree 1.
47
+ *
48
+ * A pitch outside the scale answers -1 rather than 0, so a caller testing the
49
+ * result for a degree cannot mistake the "not in the scale" answer for the
50
+ * tonic.
43
51
  *
44
52
  * @param pitch MIDI pitch or bare pitch class.
45
- * @param key The key/scale to measure against.
46
- * @returns The 0-based degree, or -1 if the pitch is not in the scale.
53
+ * @param key The key/scale to measure against, as a key name, a key/scale, or a
54
+ * `Key`.
55
+ * @returns The 1-based degree, or -1 if the pitch is not in the scale.
47
56
  *
48
57
  * @category Scales
49
58
  */
50
- declare function pitchToScaleDegree(pitch: number, key: KeyScale): number;
59
+ declare function pitchToScaleDegree(pitch: number, key: KeyLike): number;
51
60
  /**
52
61
  * List the pitch classes of a key's scale.
53
62
  *
54
- * @param key The key/scale to enumerate.
63
+ * @param key The key/scale to enumerate. The two enumerators here take a plain
64
+ * key/scale rather than a key name: they read the mask as given, including
65
+ * the tonic-less masks the spelling search works through, which no key name
66
+ * describes.
55
67
  * @returns The member pitch classes, sorted ascending in [0, 11].
56
68
  *
57
69
  * @category Scales
@@ -60,10 +72,11 @@ declare function diatonicPitchClasses(key: KeyScale): number[];
60
72
  /**
61
73
  * List the pitch classes of a key's scale in ascending scale-degree order.
62
74
  *
63
- * Degree 0 is the root; degrees follow the mask bits in offset order rather
64
- * than sorted pitch-class order.
75
+ * The first entry is the root (degree 1); degrees follow the mask bits in
76
+ * offset order rather than sorted pitch-class order.
65
77
  *
66
- * @param key The key/scale to enumerate.
78
+ * @param key The key/scale to enumerate, read as given for the reason
79
+ * {@link diatonicPitchClasses} gives.
67
80
  * @returns The member pitch classes ordered by scale degree.
68
81
  *
69
82
  * @category Scales
@@ -113,71 +126,141 @@ declare function minorKey(rootPc: number): KeyScale;
113
126
  declare function scaleByName(name: ScaleNameInput, rootPc: number): KeyScale;
114
127
 
115
128
  /**
116
- * Total voice-leading cost between two voicings: the sum of absolute semitone
117
- * motion across voices, plus a moderate `HIDDEN_PERFECT_PENALTY` when the
118
- * outer-voice (bass–soprano) pair reaches a hidden/direct perfect fifth or
119
- * octave by similar motion. The arrays must be the same length; when they
120
- * differ the voicings are not comparable and the cost is `Infinity`.
129
+ * Shared interval reading for the counterpoint rules.
121
130
  *
122
- * @param from The previous voicing, one MIDI pitch per voice.
123
- * @param to The next voicing, one MIDI pitch per voice.
124
- * @returns The summed absolute motion (plus any hidden-perfect penalty), or
125
- * `Infinity` when lengths differ.
126
- * @category Voicing & Counterpoint
131
+ * The predicates, the species checker and the independence metric all need the
132
+ * same two conversions — a pitch argument to a sounding pitch, and a spelled
133
+ * interval to its consonance class — and importing them from one another would
134
+ * make the module graph circular.
127
135
  */
128
- declare function voiceLeadingCost(from: number[], to: number[]): number;
136
+
129
137
  /**
130
- * Voice a single chord to follow smoothly from an arbitrary current voicing.
138
+ * Classify a spelled interval by consonance, the way a counterpoint exercise
139
+ * reads it.
131
140
  *
132
- * Candidate voicings of `chord` are enumerated within ranges taken from `opts`,
133
- * or — when neither `voices` nor `ranges` is given — from a one-octave window
134
- * around each pitch of `current` (so the result matches `current`'s voice
135
- * count). Each candidate is scored by structural quality, voice-leading motion
136
- * from `current`, and a large penalty per counterpoint violation; the lowest
137
- * scoring candidate is returned, ascending.
141
+ * The spelling decides, not the semitone count: a diminished fourth is a
142
+ * dissonance though it sounds like a major third, and an augmented fifth is a
143
+ * dissonance though it sounds like a minor sixth. Only the perfect unison, fifth
144
+ * and octave and the major and minor thirds and sixths are consonant. The
145
+ * perfect fourth is context-dependent exactly as in {@link classifyInterval}.
138
146
  *
139
- * @param current The current voicing to lead from, ascending (index 0 = lowest).
140
- * @param chord The next chord to voice.
141
- * @param opts Voicing options; when omitted, ranges follow `current`'s span.
142
- * @returns The chosen voicing, ascending with one MIDI pitch per voice.
143
- * @throws If `current` is empty or holds a non-finite pitch while the ranges
144
- * are derived from it, or if no voicing fits the ranges.
147
+ * @param interval The spelled interval between the two voices.
148
+ * @param twoVoice When true, the perfect fourth counts as a dissonance.
149
+ * @returns The counterpoint quality of the interval.
150
+ * @example
151
+ * ```ts
152
+ * import { classifySpelledInterval, ConsonanceClass, parseInterval } from '@libraz/libcantus';
153
+ * classifySpelledInterval(parseInterval('d4')); // ConsonanceClass.Dissonance
154
+ * classifySpelledInterval(parseInterval('M3')); // ConsonanceClass.ImperfectConsonance
155
+ * ```
145
156
  * @category Voicing & Counterpoint
146
157
  */
147
- declare function nextVoicing(current: number[], chord: Chord, opts?: VoicingOptions): number[];
158
+ declare function classifySpelledInterval(interval: SpelledInterval, twoVoice?: boolean): ConsonanceClass;
159
+
160
+ /**
161
+ * Counterpoint predicates: each judges one pair of voices at one moment.
162
+ *
163
+ * Every predicate reads spelled notes, because spelling is what decides two of
164
+ * these rules. An augmented second and a minor third span the same three
165
+ * semitones, and a diminished fourth sounds like a major third; the first is a
166
+ * forbidden melodic leap and the second a vertical dissonance, and neither can
167
+ * be seen in a MIDI integer. {@link createsVerticalDissonance} and
168
+ * {@link isForbiddenMelodicLeap} therefore answer the spelled question when
169
+ * given spelled notes, and the sounding question — all a bare pitch can support
170
+ * — when given integers. {@link isAugmentedMelodicInterval} asks a question that
171
+ * has no sounding half at all, so its integer form answers false rather than
172
+ * refusing the call: every predicate here takes either form, and the ones that
173
+ * need spelling say so by degrading.
174
+ *
175
+ * The remaining rules compare registers or perfect classes, which spelling
176
+ * cannot change: a parallel fifth is a parallel fifth however the two voices are
177
+ * written. Those keep their integer form as an equal partner.
178
+ */
148
179
 
149
180
  /**
150
181
  * Whether an upper voice has crossed below a lower voice.
151
182
  *
152
- * @param upperPitch Pitch of the nominally higher voice.
153
- * @param lowerPitch Pitch of the nominally lower voice.
183
+ * @param upper The nominally higher voice.
184
+ * @param lower The nominally lower voice.
154
185
  * @returns True when the upper voice sits below the lower voice.
155
186
  * @category Voicing & Counterpoint
156
187
  */
157
- declare function createsVoiceCrossing(upperPitch: number, lowerPitch: number): boolean;
188
+ declare function createsVoiceCrossing(upper: Note, lower: Note): boolean;
189
+ declare function createsVoiceCrossing(upper: number, lower: number): boolean;
158
190
  /**
159
- * Whether two simultaneous pitches form a dissonance.
191
+ * Whether two simultaneous voices form a dissonance.
160
192
  *
161
- * @param a First pitch.
162
- * @param b Second pitch.
193
+ * Given spelled notes the spelling decides, so a diminished fourth reads as the
194
+ * dissonance it is. Given MIDI integers only the sounding interval is available,
195
+ * which cannot tell that fourth from a major third; use the spelled form
196
+ * wherever the exercise is written in notes.
197
+ *
198
+ * @param a First voice.
199
+ * @param b Second voice.
163
200
  * @param twoVoice When true, the perfect fourth counts as dissonant.
164
201
  * @returns True when the vertical interval is dissonant.
202
+ * @example
203
+ * ```ts
204
+ * import { createsVerticalDissonance, parseNote } from '@libraz/libcantus';
205
+ * createsVerticalDissonance(parseNote('Fb4'), parseNote('C4'), true); // true — d4
206
+ * createsVerticalDissonance(64, 60, true); // false — the same pitches read as a major third
207
+ * ```
165
208
  * @category Voicing & Counterpoint
166
209
  */
210
+ declare function createsVerticalDissonance(a: Note, b: Note, twoVoice: boolean): boolean;
167
211
  declare function createsVerticalDissonance(a: number, b: number, twoVoice: boolean): boolean;
168
212
  /**
169
- * Whether a melodic move is a forbidden leap: a tritone, either seventh (minor
170
- * or major), or any leap wider than an octave.
213
+ * Whether a melodic move is a forbidden leap.
171
214
  *
172
- * Operates on the raw absolute distance, so compound leaps are not reduced to
173
- * their simple class before the octave check.
215
+ * Forbidden are the seventh in either quality, any leap wider than an octave,
216
+ * and — where the spelling is known — every augmented and diminished interval,
217
+ * the augmented second of the harmonic minor among them. Given MIDI integers the
218
+ * tritone is still caught, since it is forbidden under either spelling, but an
219
+ * augmented second is indistinguishable from a minor third and passes.
174
220
  *
175
- * @param prev Starting pitch.
176
- * @param cur Ending pitch.
221
+ * The raw distance is used for the octave check, so a compound leap is not
222
+ * reduced to its simple class first.
223
+ *
224
+ * @param prev Starting note.
225
+ * @param cur Ending note.
177
226
  * @returns True when the leap is forbidden in strict counterpoint.
227
+ * @example
228
+ * ```ts
229
+ * import { isForbiddenMelodicLeap, parseNote } from '@libraz/libcantus';
230
+ * isForbiddenMelodicLeap(parseNote('Ab4'), parseNote('B4')); // true — augmented second
231
+ * isForbiddenMelodicLeap(68, 71); // false — the same pitches read as a minor third
232
+ * ```
178
233
  * @category Voicing & Counterpoint
179
234
  */
235
+ declare function isForbiddenMelodicLeap(prev: Note, cur: Note): boolean;
180
236
  declare function isForbiddenMelodicLeap(prev: number, cur: number): boolean;
237
+ /**
238
+ * Whether a voice moves by an augmented interval — the melodic step no
239
+ * sixteenth-century line takes, and the one a MIDI integer cannot show.
240
+ *
241
+ * The augmented unison is excluded: raising or lowering the note a voice already
242
+ * holds is a chromatic inflection, not a leap.
243
+ *
244
+ * Every augmented interval sounds like a plain one — the augmented second like a
245
+ * minor third, the augmented fourth like a diminished fifth — so MIDI integers
246
+ * carry nothing this rule can read, and the numeric form always answers false.
247
+ * It exists so a host can run the whole predicate set over a bare pitch pair;
248
+ * take the spelled form wherever the exercise is written in notes.
249
+ *
250
+ * @param prev Starting note.
251
+ * @param cur Ending note.
252
+ * @returns True when the move spans an augmented second or wider.
253
+ * @example
254
+ * ```ts
255
+ * import { isAugmentedMelodicInterval, parseNote } from '@libraz/libcantus';
256
+ * isAugmentedMelodicInterval(parseNote('Ab4'), parseNote('B4')); // true — A2 in C minor
257
+ * isAugmentedMelodicInterval(parseNote('A4'), parseNote('C5')); // false — m3
258
+ * isAugmentedMelodicInterval(68, 71); // false — the same pitches carry no spelling
259
+ * ```
260
+ * @category Voicing & Counterpoint
261
+ */
262
+ declare function isAugmentedMelodicInterval(prev: Note, cur: Note): boolean;
263
+ declare function isAugmentedMelodicInterval(prev: number, cur: number): boolean;
181
264
  /**
182
265
  * Whether two voices move into consecutive perfect intervals of the same kind
183
266
  * (fifth-to-fifth, octave-to-octave, unison-to-unison).
@@ -192,8 +275,12 @@ declare function isForbiddenMelodicLeap(prev: number, cur: number): boolean;
192
275
  * an octave does not (different perfect kinds — the direct/hidden case owned by
193
276
  * {@link createsHiddenParallelPerfect}).
194
277
  *
278
+ * The perfect classes are what they sound like under any spelling, so spelled
279
+ * notes and MIDI integers give the same answer here.
280
+ *
195
281
  * @category Voicing & Counterpoint
196
282
  */
283
+ declare function createsParallelPerfect(aPrev: Note, aCur: Note, bPrev: Note, bCur: Note): boolean;
197
284
  declare function createsParallelPerfect(aPrev: number, aCur: number, bPrev: number, bCur: number): boolean;
198
285
  /**
199
286
  * Whether two voices move in consecutive parallel octaves (or unisons) by
@@ -208,6 +295,7 @@ declare function createsParallelPerfect(aPrev: number, aCur: number, bPrev: numb
208
295
  *
209
296
  * @category Voicing & Counterpoint
210
297
  */
298
+ declare function createsParallelOctave(aPrev: Note, aCur: Note, bPrev: Note, bCur: Note): boolean;
211
299
  declare function createsParallelOctave(aPrev: number, aCur: number, bPrev: number, bCur: number): boolean;
212
300
  /**
213
301
  * Whether two voices move in consecutive parallel unisons — both landing on the
@@ -215,6 +303,7 @@ declare function createsParallelOctave(aPrev: number, aCur: number, bPrev: numbe
215
303
  *
216
304
  * @category Voicing & Counterpoint
217
305
  */
306
+ declare function createsParallelUnison(aPrev: Note, aCur: Note, bPrev: Note, bCur: Note): boolean;
218
307
  declare function createsParallelUnison(aPrev: number, aCur: number, bPrev: number, bCur: number): boolean;
219
308
  /**
220
309
  * Whether two voices reach a perfect interval by similar motion from an
@@ -226,41 +315,276 @@ declare function createsParallelUnison(aPrev: number, aCur: number, bPrev: numbe
226
315
  *
227
316
  * @category Voicing & Counterpoint
228
317
  */
318
+ declare function createsHiddenParallelPerfect(aPrev: Note, aCur: Note, bPrev: Note, bCur: Note): boolean;
229
319
  declare function createsHiddenParallelPerfect(aPrev: number, aCur: number, bPrev: number, bCur: number): boolean;
320
+ /**
321
+ * Whether two voices arrive at a perfect octave or unison by contrary motion
322
+ * with the upper voice leaping down — the *ottava battuta* the sixteenth-century
323
+ * theorists forbid.
324
+ *
325
+ * The stepwise arrival is allowed, as is the same octave reached with the upper
326
+ * voice rising, so only the downward leap into the perfect class is flagged.
327
+ *
328
+ * @category Voicing & Counterpoint
329
+ */
330
+ declare function createsBattuta(aPrev: Note, aCur: Note, bPrev: Note, bCur: Note): boolean;
331
+ declare function createsBattuta(aPrev: number, aCur: number, bPrev: number, bCur: number): boolean;
230
332
  /**
231
333
  * Whether two voices overlap: the upper voice descends below where the lower
232
334
  * voice just was, or the lower voice rises above where the upper voice just was.
233
335
  * Distinct from a simultaneous voice crossing.
234
336
  *
235
- * @param upperPrev Previous pitch of the upper voice.
236
- * @param upperCur Current pitch of the upper voice.
237
- * @param lowerPrev Previous pitch of the lower voice.
238
- * @param lowerCur Current pitch of the lower voice.
337
+ * @param upperPrev Previous note of the upper voice.
338
+ * @param upperCur Current note of the upper voice.
339
+ * @param lowerPrev Previous note of the lower voice.
340
+ * @param lowerCur Current note of the lower voice.
239
341
  * @category Voicing & Counterpoint
240
342
  */
343
+ declare function createsVoiceOverlap(upperPrev: Note, upperCur: Note, lowerPrev: Note, lowerCur: Note): boolean;
241
344
  declare function createsVoiceOverlap(upperPrev: number, upperCur: number, lowerPrev: number, lowerCur: number): boolean;
242
345
  /**
243
346
  * Whether two adjacent upper voices are spaced more than a maximum apart
244
347
  * (commonly an octave). Bass-to-tenor spacing is conventionally exempt, so this
245
348
  * is meant for the upper voice pairs.
246
349
  *
247
- * @param upperPitch Pitch of the higher voice.
248
- * @param lowerPitch Pitch of the lower voice.
350
+ * @param upper The higher voice.
351
+ * @param lower The lower voice.
249
352
  * @param maxSemitones Maximum allowed spacing in semitones (default an octave).
250
353
  * @category Voicing & Counterpoint
251
354
  */
252
- declare function exceedsSpacing(upperPitch: number, lowerPitch: number, maxSemitones?: number): boolean;
355
+ declare function exceedsSpacing(upper: Note, lower: Note, maxSemitones?: number): boolean;
356
+ declare function exceedsSpacing(upper: number, lower: number, maxSemitones?: number): boolean;
253
357
  /**
254
358
  * Whether a leading tone resolves correctly upward to the tonic.
255
359
  *
256
- * @param prev The leading-tone pitch.
257
- * @param cur The following pitch.
360
+ * @param prev The leading-tone note.
361
+ * @param cur The following note.
258
362
  * @param key Key context supplying the tonic.
259
363
  * @returns True when `prev` is the leading tone and `cur` is the tonic a step above.
260
364
  * @category Voicing & Counterpoint
261
365
  */
366
+ declare function isLeadingToneResolution(prev: Note, cur: Note, key: KeyScale): boolean;
262
367
  declare function isLeadingToneResolution(prev: number, cur: number, key: KeyScale): boolean;
263
368
 
369
+ /**
370
+ * Total voice-leading cost between two voicings: the sum of absolute semitone
371
+ * motion across voices, plus a moderate hidden-perfect penalty when the
372
+ * outer-voice (bass–soprano) pair reaches a hidden/direct perfect fifth or
373
+ * octave by similar motion. The arrays must be the same length; when they
374
+ * differ the voicings are not comparable and the cost is `Infinity`.
375
+ *
376
+ * @param from The previous voicing, one MIDI pitch per voice.
377
+ * @param to The next voicing, one MIDI pitch per voice.
378
+ * @returns The summed absolute motion (plus any hidden-perfect penalty), or
379
+ * `Infinity` when lengths differ.
380
+ * @category Voicing & Counterpoint
381
+ */
382
+ declare function voiceLeadingCost(from: number[], to: number[]): number;
383
+ /**
384
+ * Voice a single chord to follow smoothly from an arbitrary current voicing.
385
+ *
386
+ * Candidate voicings of `chord` are enumerated within ranges taken from `opts`,
387
+ * or — when neither `voices` nor `ranges` is given — from a one-octave window
388
+ * around each pitch of `current` (so the result matches `current`'s voice
389
+ * count). Each candidate is scored by structural quality, voice-leading motion
390
+ * from `current`, and a large penalty per counterpoint violation; the lowest
391
+ * scoring candidate is returned, ascending.
392
+ *
393
+ * @param current The current voicing to lead from, ascending (index 0 = lowest).
394
+ * @param chord The next chord to voice, as a chord symbol, chord data, or a
395
+ * `Chord`.
396
+ * @param opts Voicing options; when omitted, ranges follow `current`'s span.
397
+ * @returns The chosen voicing, ascending with one MIDI pitch per voice.
398
+ * @throws If `current` is empty or holds a non-finite pitch while the ranges
399
+ * are derived from it, if it asks for more voices than the search will take,
400
+ * or if no voicing fits the ranges.
401
+ * @category Voicing & Counterpoint
402
+ */
403
+ declare function nextVoicing(current: number[], chord: ChordLike, opts?: VoicingOptions): number[];
404
+
405
+ /**
406
+ * Figured bass: read a bass note and its figures as the chord they name.
407
+ *
408
+ * The figures are intervals above the bass, and every interval a figure does
409
+ * not alter is the one the key's own scale gives. That is the whole rule of the
410
+ * notation, and the reason a realization needs the key: the same `6` names a
411
+ * major triad over one degree of C major and a diminished one over another.
412
+ *
413
+ * The bass arrives spelled, so the intervals above it are spelled too. The
414
+ * third above a G in C minor is the Bb the signature gives, and the bare `#`
415
+ * that raises it names B natural rather than an enharmonic Cb — which is what
416
+ * makes the figures of a real exercise readable as written.
417
+ */
418
+
419
+ /**
420
+ * A figure that moves over a held bass, such as the `4-3` suspension.
421
+ *
422
+ * @category Functional Harmony
423
+ */
424
+ type FiguredBassSuspension = {
425
+ /** The interval above the bass that sounds first: the suspended note. */
426
+ from: number;
427
+ /** The interval it moves to, which is the one the chord contains. */
428
+ to: number;
429
+ /** The suspended note, spelled. */
430
+ note: Note;
431
+ /** The note it moves to, spelled. */
432
+ resolution: Note;
433
+ };
434
+ /**
435
+ * Everything a figured bass states: the chord, its spelled notes, and any
436
+ * figure that moves.
437
+ *
438
+ * @category Functional Harmony
439
+ */
440
+ type FiguredBassRealization = {
441
+ /**
442
+ * The chord the figures name, carrying the given bass as its `bassPc` and
443
+ * the letter spellings of its root and bass as enharmonic hints.
444
+ */
445
+ chord: Chord;
446
+ /**
447
+ * The spelled notes, bass first and then each figured interval in ascending
448
+ * order. They carry no octave: figures fix the intervals above the bass, not
449
+ * the register the upper voices take them in.
450
+ */
451
+ notes: Note[];
452
+ /**
453
+ * Every moving figure, in written order, or an empty array for a static one.
454
+ * The chord above is the harmony these resolve into.
455
+ */
456
+ suspensions: FiguredBassSuspension[];
457
+ };
458
+ /**
459
+ * Read a figured bass in full: the chord, its spelled notes, and its
460
+ * suspensions.
461
+ *
462
+ * The figure grammar, in the forms this accepts:
463
+ *
464
+ * - Nothing at all, `5`, `3` or `53` — the root-position triad.
465
+ * - `6` or `63` — the first-inversion triad; `64` — the second-inversion triad.
466
+ * - `7` — the root-position seventh chord; `65`, `43`, and `42` (also written
467
+ * `2`) — its first, second, and third inversions. The full stacks `753`,
468
+ * `653`, `643` and `642` name the same four chords.
469
+ * - Stacked figures may be written run together (`64`) or separated by a slash,
470
+ * a comma or a space (`6/4`), which is the same figure either way.
471
+ * - An accidental in front of a figure alters that interval: `#6`, `b7`, `#43`.
472
+ * A slashed or crossed figure raises its interval, and is typed `+` — `+6` is
473
+ * `#6`. An accidental with no figure alters the third above the bass, so a
474
+ * lone `#` over the dominant of a minor key raises its third to the leading
475
+ * tone. `#` and `b` move the interval a semitone from what the key gives,
476
+ * `##` and `bb` a whole tone, and `n` names the natural note whatever the key
477
+ * gives. The Unicode accidentals are accepted alongside the typed ones.
478
+ * - A figure that moves is written with a hyphen: `4-3`, `9-8`, `7-6`, and any
479
+ * other pair. Each side may carry its own accidental, as `4-#3` does in a
480
+ * minor key. Moving figures may stand beside static ones (`5 4-3`).
481
+ *
482
+ * Every interval no accidental touches is the one the key's scale gives, which
483
+ * is why the key is required and why the same figure names different qualities
484
+ * over different degrees.
485
+ *
486
+ * A moving figure resolves into the chord: `4-3` over a G in C major returns
487
+ * the G major triad, and the suspended C is reported as a suspension rather
488
+ * than as a chord tone. A suspension is a melodic event over a single harmony —
489
+ * the sounding chord does not change when it resolves — so naming the
490
+ * resolution is the reading that answers "which chord is this", and the record
491
+ * of what was suspended is kept beside it rather than thrown away.
492
+ *
493
+ * @param bass The bass note, spelled: a note name, a MIDI number, note data, or
494
+ * a `Note`; its octave, if any, is not used.
495
+ * @param figures The figures written under the bass; empty for an unfigured
496
+ * note.
497
+ * @param key The prevailing key, which supplies every unaltered interval, as a
498
+ * key name, a key/scale, or a `Key`.
499
+ * @returns The chord, its spelled notes, and any suspensions.
500
+ * @throws If the figures are malformed or name no chord
501
+ * ({@link InvalidInputError}), if the key is not heptatonic
502
+ * ({@link InvalidInputError}), or if the figures sound a chord no
503
+ * {@link ChordQuality} names, such as an augmented sixth
504
+ * ({@link NoSolutionError}).
505
+ * @example
506
+ * ```ts
507
+ * import { figuredBassRealization, majorKey, noteNames, parseNote } from '@libraz/libcantus';
508
+ * const realized = figuredBassRealization(parseNote('G'), '4-3', majorKey(0));
509
+ * noteNames(realized.notes); // ['G', 'B', 'D']
510
+ * realized.suspensions[0]?.from; // 4 — the suspended C above the bass
511
+ * ```
512
+ * @category Functional Harmony
513
+ */
514
+ declare function figuredBassRealization(bass: NoteLike, figures: string, key: KeyLike): FiguredBassRealization;
515
+ /**
516
+ * Realize a figured bass: the chord a bass note and its figures name in a key.
517
+ *
518
+ * The chord carries the given bass as its `bassPc`, so an inversion reads as
519
+ * one everywhere the library measures a bass against a root, and carries the
520
+ * letter spellings of its root and bass so the realization can be respelled
521
+ * without losing the accidentals the figures asked for.
522
+ *
523
+ * See {@link figuredBassRealization} for the figure grammar this accepts and
524
+ * for the spelled notes and suspensions it drops. A moving figure such as
525
+ * `4-3` returns the chord it resolves into.
526
+ *
527
+ * @param bass The bass note, spelled; its octave, if any, is not used.
528
+ * @param figures The figures written under the bass; empty for an unfigured
529
+ * note.
530
+ * @param key The prevailing key, which supplies every unaltered interval.
531
+ * @param bass The bass note, spelled: a note name, a MIDI number, note data, or
532
+ * a `Note`.
533
+ * @param figures The figures written under the bass.
534
+ * @param key The prevailing key, as a key name, a key/scale, or a `Key`.
535
+ * @returns The chord the figures name.
536
+ * @throws If the figures are malformed or name no chord
537
+ * ({@link InvalidInputError}), if the key is not heptatonic
538
+ * ({@link InvalidInputError}), or if the figures sound a chord no
539
+ * {@link ChordQuality} names ({@link NoSolutionError}).
540
+ * @example
541
+ * ```ts
542
+ * import { majorKey, minorKey, parseNote, realizeFiguredBass } from '@libraz/libcantus';
543
+ * realizeFiguredBass(parseNote('B'), '6', majorKey(0));
544
+ * // G major with B in the bass: { rootPc: 7, quality: 'maj', bassPc: 11, ... }
545
+ * realizeFiguredBass(parseNote('G'), '#', minorKey(0));
546
+ * // G major in C minor: the raised third is a B natural
547
+ * ```
548
+ * @category Functional Harmony
549
+ */
550
+ declare function realizeFiguredBass(bass: NoteLike, figures: string, key: KeyLike): Chord;
551
+ /**
552
+ * Write a chord as the figures a bass would carry under it, in a key.
553
+ *
554
+ * The inverse of {@link realizeFiguredBass} for every chord the figure grammar
555
+ * reaches: the conventional abbreviation for the inversion (`6`, `64`, `7`,
556
+ * `65`, `43`, `42`, or nothing at all for a root-position triad), with an
557
+ * accidental on any interval the key does not already give. An altered
558
+ * interval the abbreviation would leave implied is written out, so the raised
559
+ * third of a dominant in a minor key emits `#3` rather than the bare `#` a
560
+ * score would print — the figures this returns read back as the same chord.
561
+ *
562
+ * What this returns is read back before it is returned, so the two directions
563
+ * cannot disagree: a chord the digits would name only approximately — an
564
+ * augmented sixth, whose tones do not stack in thirds over its bass — has no
565
+ * figures rather than figures that fail to realize.
566
+ *
567
+ * @param chord The chord to figure, as a chord symbol, chord data, or a
568
+ * `Chord`; its `bassPc`, when present, is the bass the intervals are measured
569
+ * above.
570
+ * @param key The prevailing key, which decides which intervals need no
571
+ * accidental, as a key name, a key/scale, or a `Key`.
572
+ * @returns The figures, as {@link realizeFiguredBass} accepts them.
573
+ * @throws If the key is not heptatonic ({@link InvalidInputError}), or if no
574
+ * figure names the chord — its bass is not one of its tones, its tones do not
575
+ * stack in diatonic thirds above that bass, or it is an added-tone or
576
+ * extended chord the notation has no abbreviation for
577
+ * ({@link NoSolutionError}).
578
+ * @example
579
+ * ```ts
580
+ * import { figuredBassOf, majorKey, makeChord } from '@libraz/libcantus';
581
+ * figuredBassOf(makeChord(7, 'maj', 11), majorKey(0)); // '6' — G major over B
582
+ * figuredBassOf(makeChord(7, 'dom7', 2), majorKey(0)); // '43' — G7 over D
583
+ * ```
584
+ * @category Functional Harmony
585
+ */
586
+ declare function figuredBassOf(chord: ChordLike, key: KeyLike): string;
587
+
264
588
  /**
265
589
  * Spelling: derive letter-name notes for scales and chords from a spelled tonic.
266
590
  *
@@ -268,35 +592,100 @@ declare function isLeadingToneResolution(prev: number, cur: number, key: KeyScal
268
592
  * G# and an Ab. Given a spelled tonic, this module assigns diatonic letters to
269
593
  * scale degrees and chord tones so a C major scale spells as C D E F G A B and
270
594
  * A harmonic minor spells its seventh as G#.
595
+ *
596
+ * Every entry point takes the spelled tonic *of the key it spells*: a tonic
597
+ * sounding a pitch class other than `key.rootPc` is rejected rather than
598
+ * spelled, because the letters counted from it name no reading of the key.
271
599
  */
272
600
 
273
601
  /**
274
- * Spell a single pitch class relative to a spelled tonic and key.
602
+ * The musical evidence around a pitch, used to refine an enharmonic choice the
603
+ * key alone cannot make.
275
604
  *
276
- * The tonic always spells as itself. In a heptatonic key each scale degree takes
277
- * the next letter above the tonic, so the scale spells with one letter per
278
- * degree. Every other pitch class — a chromatic tone in a heptatonic key, or any
279
- * tone of a scale that is not heptatonic (pentatonic, blues, octatonic) — takes
280
- * the conventional interval spelling above the tonic's letter, so a flat-side
281
- * key keeps flat-side names.
605
+ * Every field is optional and every field is only ever advisory: a context that
606
+ * says nothing about the pitch leaves its key spelling untouched.
607
+ *
608
+ * @category Pitch & Intervals
609
+ */
610
+ type SpellingContext = {
611
+ /** Root pitch class of the chord sounding under the pitch, when known. */
612
+ chordRoot?: number;
613
+ /** The pitch immediately before this one (MIDI pitch or bare pitch class). */
614
+ previous?: number;
615
+ /** The pitch immediately after this one (MIDI pitch or bare pitch class). */
616
+ next?: number;
617
+ };
618
+ /**
619
+ * Spell a single pitch class relative to a spelled tonic and key, optionally
620
+ * refined by the music around it.
621
+ *
622
+ * Without a context the key decides alone: the tonic spells as itself, each
623
+ * degree of a heptatonic key takes the next letter above the tonic, a gapped
624
+ * scale gives each of its own tones a letter wherever its pitch set allows one,
625
+ * and every other pitch class takes the conventional interval spelling above
626
+ * the tonic's letter, so a flat-side key keeps flat-side names.
627
+ *
628
+ * A context only ever refines a pitch class the key leaves ambiguous — one that
629
+ * is not a tone of `key`. Scale tones, and the tonic, keep their key spelling
630
+ * whatever the context says.
631
+ *
632
+ * The melodic rules read the two neighbours differently, because a chromatic
633
+ * note is an inflection of the note it came from but a leading tone into the
634
+ * note it goes to: `next` moves the letter, `previous` keeps it. For a chromatic
635
+ * pitch class the evidence is weighed in this order, and the first spelling
636
+ * within a double accidental wins:
637
+ *
638
+ * 1. The sounding chord, when `chordRoot` is given and the pitch is an
639
+ * unambiguous chord tone above it (root, third, fifth or seventh) — the third
640
+ * of a secondary dominant D7 in C major spells F#, not Gb.
641
+ * 2. Resolution: `next` a semitone above makes the pitch a leading tone into it,
642
+ * one letter below — the C# rising to D in D minor.
643
+ * 3. Approach: `previous` a semitone below inflects that note upward, keeping
644
+ * its letter, so a chromatic ascent spells with sharps — F F# G in C major.
645
+ * 4. Descent: `next` a semitone below puts the pitch one letter above it, so a
646
+ * falling chromatic line spells with flats — A Ab G in C major. Failing that,
647
+ * `previous` a semitone above inflects that note downward on its own letter,
648
+ * which spells the same line from the note before instead.
649
+ *
650
+ * If no rule applies, or every candidate would need more than a double
651
+ * accidental, the key spelling stands.
282
652
  *
283
653
  * @param pc The pitch class to spell.
284
- * @param tonic The spelled tonic (its letter anchors the spelling).
285
- * @param key The key/scale.
654
+ * @param tonic The spelled tonic (its letter anchors the spelling), as a note
655
+ * name, a MIDI number, note data, or a `Note`.
656
+ * @param key The key/scale, as a key name, a key/scale, or a `Key`.
657
+ * @param context Optional surrounding evidence; an absent or empty context
658
+ * spells exactly as the key alone does.
286
659
  * @returns The spelled note (without octave).
660
+ * @throws If `tonic` does not sound the key's root pitch class.
661
+ * @example
662
+ * ```ts
663
+ * import { spellPitchClass, formatNote, parseNote, majorKey } from '@libraz/libcantus';
664
+ * const c = parseNote('C');
665
+ * formatNote(spellPitchClass(6, c, majorKey(0))); // 'F#'
666
+ * // The third of D7, the secondary dominant of G.
667
+ * formatNote(spellPitchClass(6, 'C', 'C major', { chordRoot: 2 })); // 'F#'
668
+ * // A chromatic passing tone falling from G to F.
669
+ * formatNote(spellPitchClass(6, c, majorKey(0), { previous: 7, next: 5 })); // 'Gb'
670
+ * ```
287
671
  * @category Pitch & Intervals
288
672
  */
289
- declare function spellPitchClass(pc: number, tonic: Note, key: KeyScale): Note;
673
+ declare function spellPitchClass(pc: number, tonic: NoteLike, key: KeyLike, context?: SpellingContext): Note;
290
674
  /**
291
675
  * Spell every pitch class of a scale, in ascending scale-degree order.
292
676
  *
293
- * Correct for heptatonic scales (each degree gets the next letter). A scale that
294
- * is not heptatonic is spelled tone by tone, on the accidental side the scale
295
- * leans towards.
677
+ * Correct for heptatonic scales (each degree gets the next letter). A gapped
678
+ * scale gives each of its tones a letter of its own wherever its pitch set
679
+ * allows one — C miyako-bushi spells C Db F G Ab — and is spelled tone by tone,
680
+ * on the accidental side the scale leans towards, where it does not: the blues
681
+ * scale has to name one letter twice however it is read.
296
682
  *
297
- * @param tonic The spelled tonic.
298
- * @param key The key/scale.
683
+ * @param tonic The spelled tonic, as a note name, a MIDI number, note data, or a
684
+ * `Note`.
685
+ * @param key The key/scale. This one stays a plain key/scale: the key-spelling
686
+ * search reads scales through it that no key name describes.
299
687
  * @returns Spelled notes, one per scale degree.
688
+ * @throws If `tonic` does not sound the key's root pitch class.
300
689
  * @example
301
690
  * ```ts
302
691
  * import { spellScale, noteNames, parseNote, majorKey } from '@libraz/libcantus';
@@ -305,24 +694,35 @@ declare function spellPitchClass(pc: number, tonic: Note, key: KeyScale): Note;
305
694
  * ```
306
695
  * @category Pitch & Intervals
307
696
  */
308
- declare function spellScale(tonic: Note, key: KeyScale): Note[];
697
+ declare function spellScale(tonic: NoteLike, key: KeyScale): Note[];
309
698
  /**
310
699
  * Spell an arbitrary list of pitch classes relative to a key.
311
700
  *
312
701
  * @param pcs The pitch classes.
313
- * @param tonic The spelled tonic.
314
- * @param key The key/scale.
702
+ * @param tonic The spelled tonic, as a note name, a MIDI number, note data, or a
703
+ * `Note`.
704
+ * @param key The key/scale, as a key name, a key/scale, or a `Key`.
315
705
  * @returns Spelled notes, in input order.
706
+ * @throws If `tonic` does not sound the key's root pitch class.
316
707
  * @category Pitch & Intervals
317
708
  */
318
- declare function spellPitchClasses(pcs: number[], tonic: Note, key: KeyScale): Note[];
709
+ declare function spellPitchClasses(pcs: number[], tonic: NoteLike, key: KeyLike): Note[];
319
710
  /**
320
711
  * Spell chord tones from an already chosen root spelling.
321
712
  *
322
- * This is exported for the model layer's inversion support; the package
323
- * barrels intentionally expose the key-aware {@link spellChord} entry point.
713
+ * This is public for callers that already know the desired root spelling;
714
+ * {@link spellChord} instead derives that spelling from the chord and key.
715
+ *
716
+ * Letters follow the chord's own tone spellings when it carries a usable set,
717
+ * and otherwise the diatonic distance each interval implies in a stack of
718
+ * thirds. Accidentals are always recomputed from the sounding pitch class, so
719
+ * the tones name exactly the chord's own pitch classes either way.
720
+ *
721
+ * @param chord The chord whose tones are spelled.
722
+ * @param root The chosen root spelling, as a note name, a MIDI number, note
723
+ * data, or a `Note`.
324
724
  */
325
- declare function spellChordFromRoot(chord: Chord, root: Note): Note[];
725
+ declare function spellChordFromRoot(chord: Chord, root: NoteLike): Note[];
326
726
  /**
327
727
  * Spell a chord's tones, in the chord's own (tertian) order, relative to a key.
328
728
  *
@@ -331,10 +731,18 @@ declare function spellChordFromRoot(chord: Chord, root: Note): Note[];
331
731
  * tensions may be spelled by the general convention rather than by chord
332
732
  * function.
333
733
  *
734
+ * A chord carrying its own tone spellings is spelled by them, since a chord
735
+ * whose letters do not follow from stacking thirds can say so no other way:
736
+ * that is what keeps the German sixth of C major on its F#, the note that
737
+ * resolves outward to the dominant, rather than the Gb a dominant seventh
738
+ * would put there.
739
+ *
334
740
  * @param chord The chord.
335
- * @param tonic The spelled tonic of the key.
336
- * @param key The key/scale.
741
+ * @param tonic The spelled tonic of the key, as a note name, a MIDI number,
742
+ * note data, or a `Note`.
743
+ * @param key The key/scale, as a key name, a key/scale, or a `Key`.
337
744
  * @returns Spelled chord tones, root first.
745
+ * @throws If `tonic` does not sound the key's root pitch class.
338
746
  * @example
339
747
  * ```ts
340
748
  * import { spellChord, noteNames, parseNote, majorKey, makeChord } from '@libraz/libcantus';
@@ -344,7 +752,7 @@ declare function spellChordFromRoot(chord: Chord, root: Note): Note[];
344
752
  * ```
345
753
  * @category Pitch & Intervals
346
754
  */
347
- declare function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[];
755
+ declare function spellChord(chord: Chord, tonic: NoteLike, key: KeyLike): Note[];
348
756
  /**
349
757
  * Spell a MIDI pitch relative to a key, keeping its octave.
350
758
  *
@@ -352,10 +760,18 @@ declare function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[];
352
760
  * for chord and scale spelling; this one is for naming actual sounding pitches
353
761
  * (a generated line, an imported track) without losing the register.
354
762
  *
763
+ * The octave is the one that reproduces `pitch` from the chosen spelling, so a
764
+ * context that changes the letter changes the octave with it: MIDI 60 spelled
765
+ * B# is B#3, not B#4.
766
+ *
355
767
  * @param pitch The MIDI pitch; rounded to the nearest integer.
356
- * @param tonic The spelled tonic of the key.
357
- * @param key The key/scale.
768
+ * @param tonic The spelled tonic of the key, as a note name, a MIDI number,
769
+ * note data, or a `Note`.
770
+ * @param key The key/scale, as a key name, a key/scale, or a `Key`.
771
+ * @param context Optional surrounding evidence, weighed exactly as
772
+ * {@link spellPitchClass} weighs it.
358
773
  * @returns The spelled note, carrying the octave that reproduces `pitch`.
774
+ * @throws If `tonic` does not sound the key's root pitch class.
359
775
  * @example
360
776
  * ```ts
361
777
  * import { spellPitch, formatNote, parseNote, majorKey } from '@libraz/libcantus';
@@ -363,91 +779,15 @@ declare function spellChord(chord: Chord, tonic: Note, key: KeyScale): Note[];
363
779
  * ```
364
780
  * @category Pitch & Intervals
365
781
  */
366
- declare function spellPitch(pitch: number, tonic: Note, key: KeyScale): Note;
782
+ declare function spellPitch(pitch: number, tonic: NoteLike, key: KeyLike, context?: SpellingContext): Note;
367
783
  /**
368
784
  * Convenience: render spelled notes as letter-name strings.
369
785
  *
370
- * @param notes The notes.
786
+ * @param notes The notes, each a note name, a MIDI number, note data, or a
787
+ * `Note`.
371
788
  * @returns Their formatted names.
372
789
  * @category Pitch & Intervals
373
790
  */
374
- declare function noteNames(notes: Note[]): string[];
375
-
376
- /**
377
- * Lead-sheet chord symbol parsing and formatting.
378
- *
379
- * Bridges the informal `<root><quality>[/<bass>]` notation musicians write
380
- * (`Cmaj7`, `F#m7b5`, `Bb7`, `C/G`) and the library's structured {@link Chord}
381
- * type, so callers can accept/emit plain text at the edges of the API.
382
- */
383
-
384
- /**
385
- * Parse a lead-sheet chord symbol into a {@link Chord}.
386
- *
387
- * Accepts `<root><quality>[/<bass>]`, e.g. `Cmaj7`, `F#m7b5`, `Bb7`, `C/G`,
388
- * `C6/9`, `C6/9/E`. Root and bass letters are case-insensitive, matching
389
- * {@link parseNote}. A `/` is ambiguous between a slash bass and the `6/9`
390
- * quality; the text splits at the last `/` whose right-hand side is a valid
391
- * bare note preceded by a recognized quality, so `6/9` (with or without a
392
- * further slash bass) parses as a quality.
393
- *
394
- * The parsed chord carries the root/bass spellings as enharmonic hints
395
- * (`rootSpelling`/`bassSpelling`) so {@link formatChordSymbol} can reproduce
396
- * flat spellings such as `Bbmaj7` instead of respelling them with sharps.
397
- *
398
- * @param text The chord symbol text.
399
- * @returns The parsed chord.
400
- * @throws If the root or quality is not recognized.
401
- * @example
402
- * ```ts
403
- * import { parseChordSymbol, formatChordSymbol } from '@libraz/libcantus';
404
- * const c = parseChordSymbol('Bbmaj7');
405
- * formatChordSymbol(c); // 'Bbmaj7' — flat spelling is preserved on round-trip
406
- * ```
407
- * @category Chords
408
- */
409
- declare function parseChordSymbol(text: string): Chord;
410
- /**
411
- * Format a {@link Chord} as a lead-sheet chord symbol.
412
- *
413
- * The inverse of {@link parseChordSymbol}: each quality maps to one canonical
414
- * suffix spelling, and a slash bass is appended only when it differs from the
415
- * root. When the chord carries `rootSpelling`/`bassSpelling` hints (as chords
416
- * from {@link parseChordSymbol} do) and no explicit `flats` preference is
417
- * given, the hints are reused so flat symbols round-trip unchanged.
418
- *
419
- * @param chord The chord to format.
420
- * @param opts Formatting options.
421
- * @param opts.flats Prefer flat spellings over sharps for altered roots/basses;
422
- * passing either `true` or `false` overrides any spelling hints.
423
- * @returns The chord symbol text.
424
- * @example
425
- * ```ts
426
- * import { makeChord, formatChordSymbol } from '@libraz/libcantus';
427
- * formatChordSymbol(makeChord(0, 'min7')); // 'Cm7'
428
- * ```
429
- * @category Chords
430
- */
431
- declare function formatChordSymbol(chord: Chord, opts?: {
432
- flats?: boolean;
433
- }): string;
434
- /**
435
- * Transpose a chord symbol by a number of semitones.
436
- *
437
- * @param text The chord symbol text.
438
- * @param semitones Signed semitone offset to apply to the root and bass.
439
- * @param opts Formatting options for the result; see {@link formatChordSymbol}.
440
- * @returns The transposed chord symbol text.
441
- * @throws If `text` does not parse as a chord symbol.
442
- * @example
443
- * ```ts
444
- * import { transposeChordSymbol } from '@libraz/libcantus';
445
- * transposeChordSymbol('C/G', 2); // 'D/A'
446
- * ```
447
- * @category Chords
448
- */
449
- declare function transposeChordSymbol(text: string, semitones: number, opts?: {
450
- flats?: boolean;
451
- }): string;
791
+ declare function noteNames(notes: readonly NoteLike[]): string[];
452
792
 
453
- export { Chord as ChordData, KeyScale, Note as NoteData, ScaleNameInput, VoicingOptions, createsHiddenParallelPerfect, createsParallelOctave, createsParallelPerfect, createsParallelUnison, createsVerticalDissonance, createsVoiceCrossing, createsVoiceOverlap, diatonicPitchClasses, exceedsSpacing, formatChordSymbol, isForbiddenMelodicLeap, isLeadingToneResolution, isScaleTone, majorKey, minorKey, nearestScaleTone, nextVoicing, noteNames, parseChordSymbol, pitchToScaleDegree, scaleByName, scaleTonesInDegreeOrder, spellChord, spellChordFromRoot, spellPitch, spellPitchClass, spellPitchClasses, spellScale, transposeChordSymbol, voiceLeadingCost };
793
+ export { Chord as ChordData, ChordLike, ConsonanceClass, type FiguredBassRealization, type FiguredBassSuspension, KeyLike, KeyScale, Note as NoteData, NoteLike, ScaleNameInput, SpelledInterval, type SpellingContext, VoicingOptions, classifySpelledInterval, createsBattuta, createsHiddenParallelPerfect, createsParallelOctave, createsParallelPerfect, createsParallelUnison, createsVerticalDissonance, createsVoiceCrossing, createsVoiceOverlap, diatonicPitchClasses, exceedsSpacing, figuredBassOf, figuredBassRealization, isAugmentedMelodicInterval, isForbiddenMelodicLeap, isLeadingToneResolution, isScaleTone, majorKey, minorKey, nearestScaleTone, nextVoicing, noteNames, pitchToScaleDegree, realizeFiguredBass, scaleByName, scaleTonesInDegreeOrder, spellChord, spellChordFromRoot, spellPitch, spellPitchClass, spellPitchClasses, spellScale, voiceLeadingCost };