@libraz/libcantus 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -1,11 +1,20 @@
1
- import { K as KeyScale } from './types-MGP29r3t.js';
1
+ import { K as KeyScale } from './types-BN6_Q9Kl.js';
2
2
 
3
3
  /**
4
4
  * Chord quality identifiers understood by the chord builder.
5
5
  *
6
6
  * @category Chords
7
7
  */
8
- type ChordQuality = 'maj' | 'min' | 'dim' | 'aug' | 'maj7' | 'min7' | 'dom7' | 'dim7' | 'm7b5' | 'minMaj7' | 'aug7' | 'augMaj7' | 'majb5' | '6' | 'min6' | '6/9' | 'sus2' | 'sus4' | 'add9' | 'add11' | 'maj9' | 'min9' | 'dom9' | '7b9' | '7#9' | '7#11' | '7b13' | '11' | '13' | '5';
8
+ type ChordQuality = 'maj' | 'min' | 'dim' | 'aug' | 'maj7' | 'min7' | 'dom7' | 'dim7' | 'm7b5' | 'minMaj7' | 'aug7' | 'augMaj7' | 'majb5' | '6' | 'min6' | '6/9' | 'sus2' | 'sus4' | 'add9' | 'add11' | 'maj9' | 'min9' | 'dom9' | '7b9' | '7#9' | '7#11' | '7b13' | '11' | '13' | '5' | '7sus4' | '7b5' | '7alt' | '13b9' | 'maj13' | 'maj7#11' | 'min11' | 'min13' | 'minAdd9' | 'min6/9';
9
+ /**
10
+ * The basic role a chord tone plays: the degrees a chord's own template names.
11
+ *
12
+ * The shared vocabulary behind {@link chordToneRole}, the harmony layer's
13
+ * `HarmonyRole`, and the voice analyser's labels, so the three cannot drift.
14
+ *
15
+ * @category Chords
16
+ */
17
+ type ChordToneRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh';
9
18
  /**
10
19
  * A bare spelled pitch used as an enharmonic hint: a diatonic letter
11
20
  * (0..6 = C..B) plus a chromatic alteration (-2 double-flat .. +2 double-sharp).
@@ -54,7 +63,26 @@ type ChordSpan = {
54
63
  secondaryDominant?: boolean;
55
64
  };
56
65
  /**
57
- * All chord qualities the builder understands, longest templates last.
66
+ * A chord occupying a half-open beat span `[startBeat, endBeat)`.
67
+ *
68
+ * The exchange record between anything that lays chords out in time — a
69
+ * timeline analysis, a bass-line plan, an accompaniment generator — so those
70
+ * surfaces share one definition instead of agreeing by coincidence.
71
+ *
72
+ * @category Composition
73
+ */
74
+ type ChordSegment = {
75
+ startBeat: number;
76
+ endBeat: number;
77
+ chord: Chord;
78
+ };
79
+ /**
80
+ * All chord qualities the builder understands, in declaration order.
81
+ *
82
+ * The order is stable but carries no meaning: it is neither alphabetical nor
83
+ * sorted by template size. {@link detectChord} uses it as its final tie-break,
84
+ * so it is part of the observable behaviour and does not get reshuffled
85
+ * casually — but do not read it as a ranking.
58
86
  *
59
87
  * @category Chords
60
88
  */
@@ -92,21 +120,73 @@ declare function chordFromDegree(degree: number, ext: ChordQuality, key: KeyScal
92
120
  * @category Chords
93
121
  */
94
122
  declare function makeChord(rootPc: number, quality: ChordQuality, bassPc?: number): Chord;
123
+ /**
124
+ * Transpose a chord by a number of semitones.
125
+ *
126
+ * The interval template is carried over untouched, so a chord whose intervals
127
+ * were customised keeps them, and a slash bass moves with the root. Enharmonic
128
+ * spelling hints are dropped rather than shifted: the letter a transposed root
129
+ * should take depends on the destination key, which a semitone count does not
130
+ * carry. Pass the result through {@link spellChord} to respell it.
131
+ *
132
+ * @param chord The chord to transpose.
133
+ * @param semitones The signed semitone offset.
134
+ * @returns The transposed chord.
135
+ * @throws If `semitones` is not finite.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * import { formatChordSymbol, parseChordSymbol, transposeChord } from '@libraz/libcantus';
140
+ * formatChordSymbol(transposeChord(parseChordSymbol('C/G'), 2)); // 'D/A'
141
+ * ```
142
+ *
143
+ * @category Chords
144
+ */
145
+ declare function transposeChord(chord: Chord, semitones: number): Chord;
95
146
  /**
96
147
  * Get the sorted, deduplicated pitch classes of a chord.
97
148
  *
149
+ * A slash bass is one of them: `F/G` sounds a G, the voicers put it in the
150
+ * bass, and a set that omitted it would not re-detect as the chord it came
151
+ * from. Pass `includeBass: false` to enumerate the interval template alone.
152
+ *
98
153
  * @param chord The chord to enumerate.
154
+ * @param opts Set `includeBass: false` to omit a slash bass.
99
155
  * @returns The chord's pitch classes, sorted ascending in [0, 11].
100
156
  *
101
157
  * @example
102
158
  * ```ts
103
159
  * import { makeChord, chordPitchClasses } from '@libraz/libcantus';
104
160
  * chordPitchClasses(makeChord(0, 'maj7')); // [0, 4, 7, 11]
161
+ * chordPitchClasses(makeChord(5, 'maj', 7)); // [0, 5, 7, 9] — F/G, G included
105
162
  * ```
106
163
  *
107
164
  * @category Chords
108
165
  */
109
- declare function chordPitchClasses(chord: Chord): number[];
166
+ declare function chordPitchClasses(chord: Chord, opts?: {
167
+ includeBass?: boolean;
168
+ }): number[];
169
+ /**
170
+ * A pitch's interval class above the chord root, in [0, 11].
171
+ *
172
+ * The shared reading of "where does this pitch sit in the chord", used by the
173
+ * safety evaluator and the voice analyser alike so they cannot disagree.
174
+ *
175
+ * @param pitch MIDI pitch or bare pitch class.
176
+ * @param chord The chord providing the root reference.
177
+ * @returns The interval class above the root.
178
+ * @category Chords
179
+ */
180
+ declare function intervalAboveRoot(pitch: number, chord: Chord): number;
181
+ /**
182
+ * Whether a pitch is one of a chord's tones, ignoring octave.
183
+ *
184
+ * @param pitch MIDI pitch or bare pitch class.
185
+ * @param chord The chord, or null for no sounding harmony.
186
+ * @returns True when the pitch class belongs to the chord.
187
+ * @category Chords
188
+ */
189
+ declare function isChordMember(pitch: number, chord: Chord | null): boolean;
110
190
  /**
111
191
  * Determine a pitch's harmonic role within a chord.
112
192
  *
@@ -121,18 +201,21 @@ declare function chordPitchClasses(chord: Chord): number[];
121
201
  *
122
202
  * @category Chords
123
203
  */
124
- declare function chordToneRole(pitch: number, chord: Chord): 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | null;
204
+ declare function chordToneRole(pitch: number, chord: Chord): ChordToneRole | null;
125
205
  /**
126
206
  * Build the diatonic triad rooted on a scale degree by stacking scale thirds.
127
207
  *
128
208
  * Unlike {@link chordFromDegree}, the chord quality is derived from the scale
129
209
  * rather than supplied, so degrees yield their scale-correct triads (e.g. a
130
- * diminished triad on the leading tone of a major key). Intended for heptatonic
131
- * scales; other scales stack by scale step regardless.
210
+ * diminished triad on the leading tone of a major key).
132
211
  *
133
212
  * @param degree 0-based scale degree of the chord root.
134
213
  * @param key Key/scale context.
135
214
  * @returns The diatonic triad.
215
+ * @throws If the scale is not heptatonic. Stacking thirds means skipping every
216
+ * other degree, which only lands on a triad when there are seven of them, so
217
+ * the pentatonic, blues, whole-tone, octatonic and chromatic scales have no
218
+ * diatonic triad; use {@link chordFromDegree} with an explicit quality there.
136
219
  *
137
220
  * @example
138
221
  * ```ts
@@ -149,9 +232,11 @@ declare function diatonicTriad(degree: number, key: KeyScale): Chord;
149
232
  * @param degree 0-based scale degree of the chord root.
150
233
  * @param key Key/scale context.
151
234
  * @returns The diatonic seventh chord.
235
+ * @throws If the scale is not heptatonic, for the reason given on
236
+ * {@link diatonicTriad}.
152
237
  *
153
238
  * @category Chords
154
239
  */
155
240
  declare function diatonicSeventh(degree: number, key: KeyScale): Chord;
156
241
 
157
- export { type Chord as C, type PitchSpelling as P, type ChordQuality as a, type ChordSpan as b, chordFromDegree as c, chordPitchClasses as d, chordQualities as e, chordToneRole as f, diatonicSeventh as g, diatonicTriad as h, makeChord as m };
242
+ export { type Chord as C, type PitchSpelling as P, type ChordQuality as a, type ChordSegment as b, type ChordSpan as c, type ChordToneRole as d, chordFromDegree as e, chordPitchClasses as f, chordQualities as g, chordToneRole as h, diatonicSeventh as i, diatonicTriad as j, intervalAboveRoot as k, isChordMember as l, makeChord as m, transposeChord as t };
@@ -1,11 +1,11 @@
1
- import { C as Chord } from './index-Bt4tOUD9.js';
1
+ import { d as ChordToneRole, C as Chord } from './index-y_ak9_Hi.cjs';
2
2
 
3
3
  /**
4
4
  * Harmonic role a pitch plays within a chord.
5
5
  *
6
6
  * @category Functional Harmony
7
7
  */
8
- type HarmonyRole = 'root' | 'third' | 'fifth' | 'sixth' | 'seventh' | 'tension' | 'doubling';
8
+ type HarmonyRole = ChordToneRole | 'tension' | 'doubling';
9
9
  /**
10
10
  * How firmly a pitch is locked to the chord's identity.
11
11
  *
@@ -29,15 +29,18 @@ type VoicedRole = {
29
29
  /**
30
30
  * Classify a pitch's harmonic role and lock level within a chord.
31
31
  *
32
- * The role comes from the pitch's interval class above the chord root: root (0),
33
- * third (3/4), fifth (6/7/8), seventh (10/11), and 9/11/13 tensions (2/5/9, and
34
- * the flat ninth 1). The root locks the chord identity, the third locks its
35
- * quality, and everything else is free voicing. In a suspended chord (a chord
36
- * whose intervals include a 4th (5) or 2nd (2) but no third), the suspended tone
37
- * takes the third's place as the quality-defining tone and is locked to
38
- * `quality`, since moving it changes the chord. Detecting an octave doubling
39
- * requires the surrounding voicing, which this single-pitch query does not carry,
40
- * so `doubling` is part of the type but not returned here.
32
+ * The role comes from the pitch's interval class above the chord root, read
33
+ * against the chord's own template: root (0), third (3/4), fifth (6/7/8),
34
+ * sixth or seventh (9/10/11). Every pitch the template does not claim is
35
+ * `'tension'` — including one that is simply foreign to the chord, which this
36
+ * single-pitch query cannot tell from a colour tone. The root locks the chord
37
+ * identity, the third locks its quality, and everything else is free voicing.
38
+ * In a suspended chord (a chord whose intervals include a 4th (5) or 2nd (2)
39
+ * but no third), the suspended tone takes the third's place as the
40
+ * quality-defining tone and is locked to `quality`, since moving it changes the
41
+ * chord. Detecting an octave doubling requires the surrounding voicing, which
42
+ * this query does not carry, so `'doubling'` is part of the type — the shared
43
+ * vocabulary of harmonic roles — but is never returned here.
41
44
  *
42
45
  * @param pitch MIDI pitch or bare pitch class.
43
46
  * @param chord The chord providing the root reference.
@@ -16,9 +16,17 @@ type TimeSignature = {
16
16
  /**
17
17
  * Optional additive grouping of the bar's main pulses, as the felt-beat
18
18
  * lengths in pulses — e.g. `[2, 2, 3]` for a 2+2+3 reading of 7/8, or
19
- * `[3, 2]` for 5/8. The entries must be positive integers summing to
20
- * {@link pulsesPerBar}. When present, the head pulse of each group (other
21
- * than the downbeat) is treated as a secondary strong pulse by
19
+ * `[3, 2]` for 5/8. The entries must be positive integers summing to the
20
+ * numerator.
21
+ *
22
+ * A compound numerator accepts both readings. 9/8 groups its three dotted
23
+ * pulses as `[1, 1, 1]`, or reads additively as `[2, 2, 2, 3]` quavers; a
24
+ * grouping that sums to the numerator selects the additive reading, so
25
+ * aksak and other additive metres are expressible. {@link pulsesPerBar}
26
+ * and {@link isCompound} follow whichever reading the grouping selects.
27
+ *
28
+ * When present, the head pulse of each group (other than the downbeat) is
29
+ * treated as a secondary strong pulse by
22
30
  * {@link metricWeight}/{@link isStrongBeat}; when absent, all main pulses
23
31
  * are weighted equally as flat, evenly divided pulses.
24
32
  */
@@ -30,8 +38,13 @@ type TimeSignature = {
30
38
  * @category Rhythm & Meter
31
39
  */
32
40
  type BarPosition = {
41
+ /** 0-based bar index. {@link formatBarPosition} renders it 1-based. */
33
42
  bar: number;
34
- /** Quarter-note offset from the start of the bar. */
43
+ /**
44
+ * Quarter-note offset from the start of the bar — not the felt beat. In 6/8
45
+ * the second felt beat is at `beat: 1.5`; {@link barPositionToPulse} converts
46
+ * to felt-beat numbering.
47
+ */
35
48
  beat: number;
36
49
  };
37
50
  /**
@@ -49,16 +62,34 @@ type BarPosition = {
49
62
  */
50
63
  declare function parseTimeSignature(text: string): TimeSignature;
51
64
  /**
52
- * Render a time signature as `"n/d"`.
65
+ * Render a time signature as `"n/d"`, or as `"a+b+c/d"` when it carries an
66
+ * additive grouping and `grouping` is requested.
67
+ *
68
+ * The plain form drops the grouping, so a round trip through it reads 7/8 as
69
+ * flat rather than as 2+2+3; ask for the additive form to keep it.
53
70
  *
71
+ * @param ts The time signature.
72
+ * @param opts Set `grouping: true` to render an additive grouping.
73
+ * @returns The formatted signature.
74
+ * @example
75
+ * ```ts
76
+ * import { formatTimeSignature } from '@libraz/libcantus';
77
+ * const aksak = { numerator: 7, denominator: 8, grouping: [2, 2, 3] };
78
+ * formatTimeSignature(aksak); // '7/8'
79
+ * formatTimeSignature(aksak, { grouping: true }); // '2+2+3/8'
80
+ * ```
54
81
  * @category Rhythm & Meter
55
82
  */
56
- declare function formatTimeSignature(ts: TimeSignature): string;
83
+ declare function formatTimeSignature(ts: TimeSignature, opts?: {
84
+ grouping?: boolean;
85
+ }): string;
57
86
  /**
58
87
  * Whether a meter is compound: its main pulses each divide into three, as in
59
88
  * 6/8, 9/8, 12/8, or 6/4. Compound meters are those whose numerator is a
60
89
  * multiple of three greater than three, independent of the denominator. Meters
61
- * like 3/8 and 3/4 (simple triples, numerator 3) are not compound.
90
+ * like 3/8 and 3/4 (simple triples, numerator 3) are not compound, and neither
91
+ * is a signature whose {@link TimeSignature.grouping} selects the additive
92
+ * reading (9/8 as 2+2+2+3).
62
93
  *
63
94
  * @param ts The time signature.
64
95
  * @returns True for compound meters.
@@ -103,6 +134,57 @@ declare function pulsesPerBar(ts: TimeSignature): number;
103
134
  * @category Rhythm & Meter
104
135
  */
105
136
  declare function beatToBarPosition(beatInQuarters: number, ts: TimeSignature): BarPosition;
137
+ /**
138
+ * Length of one felt beat (main pulse) in quarter-note beats.
139
+ *
140
+ * A quarter note in simple meters, a dotted quarter in compound ones, and one
141
+ * denominator unit in an additive metre. Position displays, click tracks, and
142
+ * score export are all written against this length.
143
+ *
144
+ * @param ts The time signature.
145
+ * @returns The pulse length in quarter-note beats.
146
+ * @example
147
+ * ```ts
148
+ * import { parseTimeSignature, pulseBeats } from '@libraz/libcantus';
149
+ * pulseBeats(parseTimeSignature('6/8')); // 1.5
150
+ * ```
151
+ * @category Rhythm & Meter
152
+ */
153
+ declare function pulseBeats(ts: TimeSignature): number;
154
+ /**
155
+ * The felt-beat number of a bar position, 1-based.
156
+ *
157
+ * {@link BarPosition.beat} is a quarter-note offset, so in 6/8 the second felt
158
+ * beat reads as 1.5. This is the number a musician says and a position display
159
+ * shows.
160
+ *
161
+ * @param pos The bar position.
162
+ * @param ts The time signature.
163
+ * @returns The 1-based felt-beat number, fractional between pulses.
164
+ * @example
165
+ * ```ts
166
+ * import { barPositionToPulse, parseTimeSignature } from '@libraz/libcantus';
167
+ * barPositionToPulse({ bar: 0, beat: 1.5 }, parseTimeSignature('6/8')); // 2
168
+ * ```
169
+ * @category Rhythm & Meter
170
+ */
171
+ declare function barPositionToPulse(pos: BarPosition, ts: TimeSignature): number;
172
+ /**
173
+ * Render an absolute position as the `bar.beat` a DAW or a score shows:
174
+ * 1-based bar, 1-based felt beat.
175
+ *
176
+ * @param beatInQuarters Absolute position in quarter-note beats.
177
+ * @param ts The time signature.
178
+ * @param decimals Digits of the fractional beat to keep.
179
+ * @returns The formatted position, e.g. `'3.2'` for bar 3, felt beat 2.
180
+ * @example
181
+ * ```ts
182
+ * import { formatBarPosition, parseTimeSignature } from '@libraz/libcantus';
183
+ * formatBarPosition(7.5, parseTimeSignature('6/8')); // '3.2'
184
+ * ```
185
+ * @category Rhythm & Meter
186
+ */
187
+ declare function formatBarPosition(beatInQuarters: number, ts: TimeSignature, decimals?: number): string;
106
188
  /**
107
189
  * Convert a bar index and in-bar offset back to an absolute quarter-note
108
190
  * position.
@@ -128,8 +210,8 @@ declare function barPositionToBeat(pos: BarPosition, ts: TimeSignature): number;
128
210
  * @param beatInQuarters Absolute or in-bar quarter-note position.
129
211
  * @param ts The time signature.
130
212
  * @returns The metric weight (0–3).
131
- * @throws If `ts.grouping` is present but is not a positive-integer list
132
- * summing to {@link pulsesPerBar}.
213
+ * @throws If the time signature is malformed, including a grouping that is not
214
+ * a positive-integer list summing to the pulse count or the numerator.
133
215
  * @example
134
216
  * ```ts
135
217
  * import { parseTimeSignature, metricWeight } from '@libraz/libcantus';
@@ -161,4 +243,4 @@ declare function isStrongBeat(beatInQuarters: number, ts: TimeSignature): boolea
161
243
  */
162
244
  declare function tuplet(totalBeats: number, count: number): number[];
163
245
 
164
- export { type BarPosition as B, type TimeSignature as T, beatToBarPosition as a, barPositionToBeat as b, beatsPerBar as c, isStrongBeat as d, pulsesPerBar as e, formatTimeSignature as f, isCompound as i, metricWeight as m, parseTimeSignature as p, tuplet as t };
246
+ export { type BarPosition as B, type TimeSignature as T, barPositionToPulse as a, barPositionToBeat as b, beatToBarPosition as c, beatsPerBar as d, formatTimeSignature as e, formatBarPosition as f, isStrongBeat as g, pulseBeats as h, isCompound as i, pulsesPerBar as j, metricWeight as m, parseTimeSignature as p, tuplet as t };
@@ -16,9 +16,17 @@ type TimeSignature = {
16
16
  /**
17
17
  * Optional additive grouping of the bar's main pulses, as the felt-beat
18
18
  * lengths in pulses — e.g. `[2, 2, 3]` for a 2+2+3 reading of 7/8, or
19
- * `[3, 2]` for 5/8. The entries must be positive integers summing to
20
- * {@link pulsesPerBar}. When present, the head pulse of each group (other
21
- * than the downbeat) is treated as a secondary strong pulse by
19
+ * `[3, 2]` for 5/8. The entries must be positive integers summing to the
20
+ * numerator.
21
+ *
22
+ * A compound numerator accepts both readings. 9/8 groups its three dotted
23
+ * pulses as `[1, 1, 1]`, or reads additively as `[2, 2, 2, 3]` quavers; a
24
+ * grouping that sums to the numerator selects the additive reading, so
25
+ * aksak and other additive metres are expressible. {@link pulsesPerBar}
26
+ * and {@link isCompound} follow whichever reading the grouping selects.
27
+ *
28
+ * When present, the head pulse of each group (other than the downbeat) is
29
+ * treated as a secondary strong pulse by
22
30
  * {@link metricWeight}/{@link isStrongBeat}; when absent, all main pulses
23
31
  * are weighted equally as flat, evenly divided pulses.
24
32
  */
@@ -30,8 +38,13 @@ type TimeSignature = {
30
38
  * @category Rhythm & Meter
31
39
  */
32
40
  type BarPosition = {
41
+ /** 0-based bar index. {@link formatBarPosition} renders it 1-based. */
33
42
  bar: number;
34
- /** Quarter-note offset from the start of the bar. */
43
+ /**
44
+ * Quarter-note offset from the start of the bar — not the felt beat. In 6/8
45
+ * the second felt beat is at `beat: 1.5`; {@link barPositionToPulse} converts
46
+ * to felt-beat numbering.
47
+ */
35
48
  beat: number;
36
49
  };
37
50
  /**
@@ -49,16 +62,34 @@ type BarPosition = {
49
62
  */
50
63
  declare function parseTimeSignature(text: string): TimeSignature;
51
64
  /**
52
- * Render a time signature as `"n/d"`.
65
+ * Render a time signature as `"n/d"`, or as `"a+b+c/d"` when it carries an
66
+ * additive grouping and `grouping` is requested.
67
+ *
68
+ * The plain form drops the grouping, so a round trip through it reads 7/8 as
69
+ * flat rather than as 2+2+3; ask for the additive form to keep it.
53
70
  *
71
+ * @param ts The time signature.
72
+ * @param opts Set `grouping: true` to render an additive grouping.
73
+ * @returns The formatted signature.
74
+ * @example
75
+ * ```ts
76
+ * import { formatTimeSignature } from '@libraz/libcantus';
77
+ * const aksak = { numerator: 7, denominator: 8, grouping: [2, 2, 3] };
78
+ * formatTimeSignature(aksak); // '7/8'
79
+ * formatTimeSignature(aksak, { grouping: true }); // '2+2+3/8'
80
+ * ```
54
81
  * @category Rhythm & Meter
55
82
  */
56
- declare function formatTimeSignature(ts: TimeSignature): string;
83
+ declare function formatTimeSignature(ts: TimeSignature, opts?: {
84
+ grouping?: boolean;
85
+ }): string;
57
86
  /**
58
87
  * Whether a meter is compound: its main pulses each divide into three, as in
59
88
  * 6/8, 9/8, 12/8, or 6/4. Compound meters are those whose numerator is a
60
89
  * multiple of three greater than three, independent of the denominator. Meters
61
- * like 3/8 and 3/4 (simple triples, numerator 3) are not compound.
90
+ * like 3/8 and 3/4 (simple triples, numerator 3) are not compound, and neither
91
+ * is a signature whose {@link TimeSignature.grouping} selects the additive
92
+ * reading (9/8 as 2+2+2+3).
62
93
  *
63
94
  * @param ts The time signature.
64
95
  * @returns True for compound meters.
@@ -103,6 +134,57 @@ declare function pulsesPerBar(ts: TimeSignature): number;
103
134
  * @category Rhythm & Meter
104
135
  */
105
136
  declare function beatToBarPosition(beatInQuarters: number, ts: TimeSignature): BarPosition;
137
+ /**
138
+ * Length of one felt beat (main pulse) in quarter-note beats.
139
+ *
140
+ * A quarter note in simple meters, a dotted quarter in compound ones, and one
141
+ * denominator unit in an additive metre. Position displays, click tracks, and
142
+ * score export are all written against this length.
143
+ *
144
+ * @param ts The time signature.
145
+ * @returns The pulse length in quarter-note beats.
146
+ * @example
147
+ * ```ts
148
+ * import { parseTimeSignature, pulseBeats } from '@libraz/libcantus';
149
+ * pulseBeats(parseTimeSignature('6/8')); // 1.5
150
+ * ```
151
+ * @category Rhythm & Meter
152
+ */
153
+ declare function pulseBeats(ts: TimeSignature): number;
154
+ /**
155
+ * The felt-beat number of a bar position, 1-based.
156
+ *
157
+ * {@link BarPosition.beat} is a quarter-note offset, so in 6/8 the second felt
158
+ * beat reads as 1.5. This is the number a musician says and a position display
159
+ * shows.
160
+ *
161
+ * @param pos The bar position.
162
+ * @param ts The time signature.
163
+ * @returns The 1-based felt-beat number, fractional between pulses.
164
+ * @example
165
+ * ```ts
166
+ * import { barPositionToPulse, parseTimeSignature } from '@libraz/libcantus';
167
+ * barPositionToPulse({ bar: 0, beat: 1.5 }, parseTimeSignature('6/8')); // 2
168
+ * ```
169
+ * @category Rhythm & Meter
170
+ */
171
+ declare function barPositionToPulse(pos: BarPosition, ts: TimeSignature): number;
172
+ /**
173
+ * Render an absolute position as the `bar.beat` a DAW or a score shows:
174
+ * 1-based bar, 1-based felt beat.
175
+ *
176
+ * @param beatInQuarters Absolute position in quarter-note beats.
177
+ * @param ts The time signature.
178
+ * @param decimals Digits of the fractional beat to keep.
179
+ * @returns The formatted position, e.g. `'3.2'` for bar 3, felt beat 2.
180
+ * @example
181
+ * ```ts
182
+ * import { formatBarPosition, parseTimeSignature } from '@libraz/libcantus';
183
+ * formatBarPosition(7.5, parseTimeSignature('6/8')); // '3.2'
184
+ * ```
185
+ * @category Rhythm & Meter
186
+ */
187
+ declare function formatBarPosition(beatInQuarters: number, ts: TimeSignature, decimals?: number): string;
106
188
  /**
107
189
  * Convert a bar index and in-bar offset back to an absolute quarter-note
108
190
  * position.
@@ -128,8 +210,8 @@ declare function barPositionToBeat(pos: BarPosition, ts: TimeSignature): number;
128
210
  * @param beatInQuarters Absolute or in-bar quarter-note position.
129
211
  * @param ts The time signature.
130
212
  * @returns The metric weight (0–3).
131
- * @throws If `ts.grouping` is present but is not a positive-integer list
132
- * summing to {@link pulsesPerBar}.
213
+ * @throws If the time signature is malformed, including a grouping that is not
214
+ * a positive-integer list summing to the pulse count or the numerator.
133
215
  * @example
134
216
  * ```ts
135
217
  * import { parseTimeSignature, metricWeight } from '@libraz/libcantus';
@@ -161,4 +243,4 @@ declare function isStrongBeat(beatInQuarters: number, ts: TimeSignature): boolea
161
243
  */
162
244
  declare function tuplet(totalBeats: number, count: number): number[];
163
245
 
164
- export { type BarPosition as B, type TimeSignature as T, beatToBarPosition as a, barPositionToBeat as b, beatsPerBar as c, isStrongBeat as d, pulsesPerBar as e, formatTimeSignature as f, isCompound as i, metricWeight as m, parseTimeSignature as p, tuplet as t };
246
+ export { type BarPosition as B, type TimeSignature as T, barPositionToPulse as a, barPositionToBeat as b, beatToBarPosition as c, beatsPerBar as d, formatTimeSignature as e, formatBarPosition as f, isStrongBeat as g, pulseBeats as h, isCompound as i, pulsesPerBar as j, metricWeight as m, parseTimeSignature as p, tuplet as t };