@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
@@ -0,0 +1,643 @@
1
+ import { g as NoteEvent } from './index-B9MNZboQ.js';
2
+ import { e as StringFingering, c as Limb, I as InstrumentProfile } from './positional-Cptlj-xs.js';
3
+
4
+ /**
5
+ * What makes a passage hard or impossible on an instrument.
6
+ *
7
+ * `noteOutOfRange` and `articulationUnavailable` belong to the first layer: the
8
+ * instrument has no position for what was written. `stringConflict`,
9
+ * `stretchTooWide`, `limbConflict` and `polyphonyExceeded` belong to the second:
10
+ * the notes exist but the arrangement cannot be held or struck. `tooFast`
11
+ * belongs to the third, the only one that depends on tempo.
12
+ *
13
+ * @category Core
14
+ */
15
+ type PlayabilityIssueType = 'noteOutOfRange' | 'articulationUnavailable' | 'stringConflict' | 'stretchTooWide' | 'limbConflict' | 'polyphonyExceeded' | 'tooFast';
16
+ /**
17
+ * Which playability layer an issue belongs to: 1 the note does not exist, 2 the
18
+ * arrangement does not hold together, 3 there is not enough time.
19
+ *
20
+ * @category Core
21
+ */
22
+ type PlayabilityLayer = 1 | 2 | 3;
23
+ /**
24
+ * One thing standing between a passage and a performance of it.
25
+ *
26
+ * @category Core
27
+ */
28
+ type PlayabilityIssue = {
29
+ type: PlayabilityIssueType;
30
+ layer: PlayabilityLayer;
31
+ /** Indices into the analysed note array, ascending. */
32
+ notes: readonly number[];
33
+ /** Where the problem occurs, in quarter-note beats. */
34
+ startBeat: number;
35
+ /**
36
+ * True when practice cannot fix it. Layers 1 and 2 depend on the instrument
37
+ * alone, so they are impossible however skilled the player; only a layer-3
38
+ * issue is a matter of degree.
39
+ */
40
+ impossible: boolean;
41
+ message: string;
42
+ };
43
+ /**
44
+ * How one note is actually produced: the string and fret it is stopped at, or
45
+ * the limb that strikes it.
46
+ *
47
+ * This is reported for every note that has a position at all, whether or not
48
+ * the passage raises issues, so a caller can apply its own standard rather than
49
+ * take this module's.
50
+ *
51
+ * @category Core
52
+ */
53
+ type NotePlacement = {
54
+ /** Index into the analysed note array. */
55
+ note: number;
56
+ /** String instruments: where the note is stopped. */
57
+ fingering?: StringFingering;
58
+ /** Percussion: which limb plays the stroke. */
59
+ limb?: Limb;
60
+ };
61
+ /**
62
+ * The verdict on a passage: how hard it is, what stands in the way, and how
63
+ * each note would be produced.
64
+ *
65
+ * @category Core
66
+ */
67
+ type PlayabilityReport = {
68
+ /**
69
+ * Movement per unit time, mapped onto 1 (trivial) to 5 (at the limit).
70
+ * Without a tempo the passage is measured at 120 BPM, so the number stays
71
+ * comparable between passages; with one it rises as the tempo does.
72
+ */
73
+ difficulty: number;
74
+ issues: PlayabilityIssue[];
75
+ placements: NotePlacement[];
76
+ };
77
+ /**
78
+ * Judge whether a passage can be played on an instrument, and how hard it is.
79
+ *
80
+ * Inspection is deliberately separate from generation: this answers "can this
81
+ * be played" for a chart handed to a player or a practice tool, and never
82
+ * rewrites or rejects anything. The three layers are reported together but stay
83
+ * distinct — an out-of-range note or an impossible stretch is not the bottom of
84
+ * the difficulty scale but a different axis, since no amount of skill puts a
85
+ * note on an instrument that does not have it. Only the third layer consults
86
+ * `bpm`, so omitting the tempo reports what the instrument alone decides.
87
+ *
88
+ * @param notes The passage, in any order. Drum hits qualify as note events.
89
+ * @param profile The instrument to play it on.
90
+ * @param bpm Tempo in quarter-note beats per minute; enables the third layer.
91
+ * @returns Difficulty, the issues found, and each note's string/fret or limb.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { BASS_4_STRING, playability } from '@libraz/libcantus';
96
+ * const report = playability([{ pitch: 27, startBeat: 0, durationBeat: 1 }], BASS_4_STRING);
97
+ * report.issues[0]?.type; // 'noteOutOfRange'
98
+ * ```
99
+ * @category Core
100
+ */
101
+ declare function playability(notes: readonly NoteEvent[], profile: InstrumentProfile, bpm?: number): PlayabilityReport;
102
+
103
+ /**
104
+ * Note values: the written durations a score shows — a base value, its
105
+ * augmentation dots, and an optional tuplet ratio — and the conversion between
106
+ * them and the quarter-note beats the rest of the library measures in.
107
+ *
108
+ * The base values are named the American way (whole, half, quarter, eighth),
109
+ * matching the library's "quarter-note beat" convention and the vocabulary of
110
+ * the MIDI and DAW world this engine reads events from.
111
+ */
112
+ /**
113
+ * A written base note value, from the whole note down to the sixty-fourth.
114
+ *
115
+ * Longer values (the breve and beyond) are written as tied notes; see
116
+ * {@link beatsToTiedDurations}.
117
+ *
118
+ * @category Rhythm & Meter
119
+ */
120
+ type NoteValue = 'whole' | 'half' | 'quarter' | 'eighth' | 'sixteenth' | 'thirtySecond' | 'sixtyFourth';
121
+ /**
122
+ * Every base note value, longest first.
123
+ *
124
+ * @category Rhythm & Meter
125
+ */
126
+ declare const NOTE_VALUES: readonly NoteValue[];
127
+ /**
128
+ * A tuplet ratio: `actual` written notes played in the time of `normal` ones —
129
+ * `{ actual: 3, normal: 2 }` for a triplet, `{ actual: 5, normal: 4 }` for a
130
+ * quintuplet.
131
+ *
132
+ * @category Rhythm & Meter
133
+ */
134
+ type Tuplet = {
135
+ /** How many notes are written. */
136
+ actual: number;
137
+ /** How many of the same base value they occupy. */
138
+ normal: number;
139
+ };
140
+ /**
141
+ * A written duration: a base value, optional augmentation dots, and an optional
142
+ * tuplet ratio. Its length is `base * (2 - 2^-dots) * normal / actual`.
143
+ *
144
+ * @category Rhythm & Meter
145
+ */
146
+ type DurationData = {
147
+ base: NoteValue;
148
+ /** Augmentation dots, 0 when absent; up to four are accepted. */
149
+ dots?: number;
150
+ tuplet?: Tuplet;
151
+ };
152
+ /**
153
+ * A duration as {@link beatsToDuration} spells it, with the dot count always
154
+ * stated.
155
+ *
156
+ * @category Rhythm & Meter
157
+ */
158
+ type SpelledDuration = {
159
+ base: NoteValue;
160
+ dots: number;
161
+ tuplet?: Tuplet;
162
+ };
163
+ /**
164
+ * Length of a written duration in beats.
165
+ *
166
+ * @param duration The duration, or a bare base value as shorthand for an
167
+ * undotted one.
168
+ * @param options `beatUnit` sets what one beat is; a quarter note by default,
169
+ * so a dotted quarter reads as 1.5. Pass `{ base: 'quarter', dots: 1 }` to
170
+ * count in the felt beats of a compound meter.
171
+ * @returns The length in beats.
172
+ * @throws If the base value is unknown, the dot count is not an integer in
173
+ * 0..4, or a tuplet side is not a positive integer.
174
+ * @example
175
+ * ```ts
176
+ * import { durationToBeats } from '@libraz/libcantus';
177
+ * durationToBeats('quarter'); // 1
178
+ * durationToBeats({ base: 'quarter', dots: 1 }); // 1.5
179
+ * durationToBeats({ base: 'eighth', tuplet: { actual: 3, normal: 2 } }); // 1/3
180
+ * ```
181
+ * @category Rhythm & Meter
182
+ */
183
+ declare function durationToBeats(duration: NoteValue | DurationData, options?: {
184
+ beatUnit?: NoteValue | DurationData;
185
+ }): number;
186
+ /**
187
+ * Spell a length in beats as the note value a score would show.
188
+ *
189
+ * The spelling is the conventional one, not merely an arithmetically valid one:
190
+ * 1.5 beats is a dotted quarter rather than a quarter tied to an eighth, and a
191
+ * third of a beat is an eighth triplet rather than a twenty-fourth note. Plain
192
+ * values are preferred to dotted ones and both to tuplets, and within the
193
+ * tuplets triplets come before quintuplets and septuplets.
194
+ *
195
+ * Lengths are matched within 1e-6 beats, so a rounded decimal such as 0.3333333
196
+ * still reads as a triplet.
197
+ *
198
+ * @param beats The length in beats.
199
+ * @param options `beatUnit` sets what one beat is; a quarter note by default.
200
+ * @returns The base value, its dot count, and its tuplet when it has one.
201
+ * @throws If `beats` is not positive, or — as a `NoSolutionError` — if no single
202
+ * note value spells it, as for 5 beats in 4/4. Use
203
+ * {@link beatsToTiedDurations} for those.
204
+ * @example
205
+ * ```ts
206
+ * import { beatsToDuration } from '@libraz/libcantus';
207
+ * beatsToDuration(1.5); // { base: 'quarter', dots: 1 }
208
+ * beatsToDuration(1 / 3); // { base: 'eighth', dots: 0, tuplet: { actual: 3, normal: 2 } }
209
+ * ```
210
+ * @category Rhythm & Meter
211
+ */
212
+ declare function beatsToDuration(beats: number, options?: {
213
+ beatUnit?: NoteValue | DurationData;
214
+ }): SpelledDuration;
215
+ /**
216
+ * Spell a length in beats as a chain of tied note values.
217
+ *
218
+ * A length with a single spelling comes back as a one-element chain. Anything
219
+ * else is broken into undotted values, longest first, and a value immediately
220
+ * followed by its own half is then written as a dot instead: 5 beats reads as a
221
+ * whole note tied to a quarter, and 1.9375 beats as a double-dotted quarter tied
222
+ * to a dotted thirty-second. The chain knows nothing of barlines or beat
223
+ * grouping — an engraver splits further at those — and it uses no tuplets, so a
224
+ * length off the plain grid and with no single spelling has no chain either.
225
+ *
226
+ * @param beats The length in beats.
227
+ * @param options `beatUnit` sets what one beat is; a quarter note by default.
228
+ * @returns The tied durations, longest first, summing to `beats`.
229
+ * @throws If `beats` is not positive, if the chain would exceed the generation
230
+ * budget, or — as a `NoSolutionError` — if no chain of plain and dotted values
231
+ * sums to it.
232
+ * @example
233
+ * ```ts
234
+ * import { beatsToTiedDurations } from '@libraz/libcantus';
235
+ * beatsToTiedDurations(5); // [{ base: 'whole', dots: 0 }, { base: 'quarter', dots: 0 }]
236
+ * ```
237
+ * @category Rhythm & Meter
238
+ */
239
+ declare function beatsToTiedDurations(beats: number, options?: {
240
+ beatUnit?: NoteValue | DurationData;
241
+ }): SpelledDuration[];
242
+
243
+ /**
244
+ * A validated note retaining its position in the caller's original array.
245
+ *
246
+ * @category Core
247
+ */
248
+ type IndexedNoteEvent = {
249
+ note: Readonly<NoteEvent>;
250
+ originalIndex: number;
251
+ endBeat: number;
252
+ };
253
+ /**
254
+ * Which note wins when several sound from the same onset.
255
+ *
256
+ * `'highest'` and `'lowest'` name a voice, so the answer does not depend on the
257
+ * order the caller happened to store the chord in; `'last'` takes the one
258
+ * latest in the input array.
259
+ *
260
+ * @category Core
261
+ */
262
+ type OnsetTieBreak = 'highest' | 'lowest' | 'last';
263
+ /**
264
+ * Binary-searchable, stable-sorted index over note onsets and active spans.
265
+ *
266
+ * @category Core
267
+ */
268
+ type NoteEventIndex = {
269
+ notes: readonly IndexedNoteEvent[];
270
+ /**
271
+ * Latest-onset note sounding at `beat`. Simultaneous onsets are resolved by
272
+ * the index's {@link OnsetTieBreak}.
273
+ *
274
+ * @throws If `beat` is not finite.
275
+ */
276
+ at: (beat: number) => IndexedNoteEvent | undefined;
277
+ /**
278
+ * Whether one or more sounding notes attack at `beat`.
279
+ *
280
+ * @throws If `beat` is not finite.
281
+ */
282
+ attacksAt: (beat: number) => boolean;
283
+ /**
284
+ * Unique sounding-note attack beats strictly inside `(startBeat, endBeat)`.
285
+ *
286
+ * @throws If either bound is not finite.
287
+ */
288
+ onsetsBetween: (startBeat: number, endBeat: number) => number[];
289
+ };
290
+ /**
291
+ * How an index reads the events it is built from.
292
+ *
293
+ * @category Core
294
+ */
295
+ type NoteEventIndexOptions = {
296
+ allowNonPositiveDuration?: boolean;
297
+ tieBreak?: OnsetTieBreak;
298
+ budget?: number;
299
+ /** What the events are, for the validation error message. */
300
+ name?: string;
301
+ };
302
+ /**
303
+ * Validate and stable-sort note events once, then expose logarithmic onset and
304
+ * active-note lookups. Non-positive-duration notes may be retained for callers
305
+ * that intentionally filter them later, but never count as sounding.
306
+ *
307
+ * @category Core
308
+ */
309
+ declare function createNoteEventIndex(events: readonly NoteEvent[], options?: NoteEventIndexOptions): NoteEventIndex;
310
+
311
+ /**
312
+ * Tempo and time conversion: beats to wall-clock seconds across tempo changes,
313
+ * and beats to MIDI ticks.
314
+ *
315
+ * Positions are measured in quarter-note beats, matching the rest of the
316
+ * library's beat convention. A {@link TempoMap} is piecewise constant: each
317
+ * entry sets the tempo from its beat onwards, so the elapsed time over a span is
318
+ * the sum of the segments it crosses rather than the tempo at either end.
319
+ */
320
+ /**
321
+ * A tempo change: the beat it takes effect on, and the tempo it sets.
322
+ *
323
+ * The tempo holds until the next entry, so a map of one entry is a constant
324
+ * tempo for the whole timeline.
325
+ *
326
+ * @category Rhythm & Meter
327
+ */
328
+ type TempoEvent = {
329
+ /** Onset in quarter-note beats, absolute from the start of the timeline. */
330
+ readonly startBeat: number;
331
+ /** Quarter-note beats per minute from `startBeat` onwards. */
332
+ readonly bpm: number;
333
+ };
334
+ /**
335
+ * A tempo track: {@link TempoEvent}s sorted by `startBeat`, strictly ascending.
336
+ *
337
+ * The first entry is the time origin — {@link beatsToSeconds} returns 0 for its
338
+ * `startBeat` — and its tempo also governs the beats before it, which is where
339
+ * a pickup sounds. A map cannot say anything else about that stretch, and a
340
+ * piece is played into its first downbeat at the tempo it starts in, so elapsed
341
+ * time there is negative rather than an error.
342
+ *
343
+ * @category Rhythm & Meter
344
+ */
345
+ type TempoMap = readonly TempoEvent[];
346
+ /**
347
+ * Wall-clock seconds from the start of a tempo map to a beat.
348
+ *
349
+ * The result integrates every tempo segment the span crosses; it is not the
350
+ * tempo in effect at `beat` applied to the whole span.
351
+ *
352
+ * A beat before the map's first event reports negative elapsed time, at the
353
+ * opening tempo: a pickup sounds before the downbeat, and asking when it sounds
354
+ * is a fair question.
355
+ *
356
+ * @param beat Position in quarter-note beats.
357
+ * @param map The tempo map; its first event is the time origin.
358
+ * @returns Elapsed seconds, 0 at the map's first event and negative before it.
359
+ * @throws If the map is empty, unsorted, or carries a non-positive tempo.
360
+ * @example
361
+ * ```ts
362
+ * import { beatsToSeconds } from '@libraz/libcantus';
363
+ * const map = [{ startBeat: 0, bpm: 120 }, { startBeat: 4, bpm: 60 }];
364
+ * beatsToSeconds(8, map); // 6 = 4 beats at 120 + 4 beats at 60
365
+ * ```
366
+ * @category Rhythm & Meter
367
+ */
368
+ declare function beatsToSeconds(beat: number, map: TempoMap): number;
369
+ /**
370
+ * The beat reached after a number of seconds — the inverse of
371
+ * {@link beatsToSeconds}.
372
+ *
373
+ * @param seconds Elapsed seconds from the map's first event; negative for a
374
+ * position before it, such as a pickup.
375
+ * @param map The tempo map.
376
+ * @returns The position in quarter-note beats.
377
+ * @throws If the map is malformed or `seconds` is not finite.
378
+ * @example
379
+ * ```ts
380
+ * import { secondsToBeats } from '@libraz/libcantus';
381
+ * const map = [{ startBeat: 0, bpm: 120 }, { startBeat: 4, bpm: 60 }];
382
+ * secondsToBeats(6, map); // 8
383
+ * ```
384
+ * @category Rhythm & Meter
385
+ */
386
+ declare function secondsToBeats(seconds: number, map: TempoMap): number;
387
+ /**
388
+ * Duration in seconds of a span of beats.
389
+ *
390
+ * Equal to the difference of two {@link beatsToSeconds} calls — the integral is
391
+ * additive, so that reading is exact — but integrated over the span directly, so
392
+ * a short note late in a long piece does not lose precision to the subtraction
393
+ * of two large elapsed times.
394
+ *
395
+ * @param startBeat Where the span begins, in quarter-note beats; a pickup
396
+ * starts before the map's first event and is measured like any other span.
397
+ * @param lengthBeats How long the span lasts, in quarter-note beats.
398
+ * @param map The tempo map.
399
+ * @returns The span's duration in seconds, never negative.
400
+ * @throws If the map is malformed or the length is negative.
401
+ * @example
402
+ * ```ts
403
+ * import { durationToSeconds } from '@libraz/libcantus';
404
+ * const map = [{ startBeat: 0, bpm: 120 }, { startBeat: 4, bpm: 60 }];
405
+ * durationToSeconds(2, 4, map); // 3 = 2 beats at 120 + 2 beats at 60
406
+ * ```
407
+ * @category Rhythm & Meter
408
+ */
409
+ declare function durationToSeconds(startBeat: number, lengthBeats: number, map: TempoMap): number;
410
+ /**
411
+ * The tempo in effect at a beat.
412
+ *
413
+ * A beat before the first event reads the opening tempo, which is the tempo a
414
+ * pickup is played at.
415
+ *
416
+ * @param beat Position in quarter-note beats.
417
+ * @param map The tempo map.
418
+ * @returns Quarter-note beats per minute.
419
+ * @throws If the map is malformed.
420
+ * @example
421
+ * ```ts
422
+ * import { tempoAt } from '@libraz/libcantus';
423
+ * tempoAt(5, [{ startBeat: 0, bpm: 120 }, { startBeat: 4, bpm: 60 }]); // 60
424
+ * ```
425
+ * @category Rhythm & Meter
426
+ */
427
+ declare function tempoAt(beat: number, map: TempoMap): number;
428
+ /**
429
+ * Convert quarter-note beats to MIDI ticks.
430
+ *
431
+ * Ticks are whole units of a sequencer's grid, so the result is rounded to the
432
+ * nearest tick; a beat that is not on the grid is not representable and comes
433
+ * back quantized.
434
+ *
435
+ * A position before the first downbeat is a negative tick count, which is how a
436
+ * pickup crosses the MIDI boundary and comes back.
437
+ *
438
+ * @param beat Position or length in quarter-note beats.
439
+ * @param ppq Pulses (ticks) per quarter note, e.g. 96, 480, or 960.
440
+ * @returns The whole tick count.
441
+ * @throws If `beat` is not finite or `ppq` is not a positive integer.
442
+ * @example
443
+ * ```ts
444
+ * import { beatsToTicks } from '@libraz/libcantus';
445
+ * beatsToTicks(1.5, 480); // 720
446
+ * ```
447
+ * @category Rhythm & Meter
448
+ */
449
+ declare function beatsToTicks(beat: number, ppq: number): number;
450
+ /**
451
+ * Convert MIDI ticks to quarter-note beats.
452
+ *
453
+ * @param ticks Whole tick count, negative before the first downbeat.
454
+ * @param ppq Pulses (ticks) per quarter note, e.g. 96, 480, or 960.
455
+ * @returns The position or length in quarter-note beats.
456
+ * @throws If `ticks` is not an integer or `ppq` is not a positive integer.
457
+ * @example
458
+ * ```ts
459
+ * import { ticksToBeats } from '@libraz/libcantus';
460
+ * ticksToBeats(720, 480); // 1.5
461
+ * ```
462
+ * @category Rhythm & Meter
463
+ */
464
+ declare function ticksToBeats(ticks: number, ppq: number): number;
465
+
466
+ /**
467
+ * Tuning and microtonality: frequency conversion, cents, arbitrary equal
468
+ * temperaments (EDO), and just-intonation ratios.
469
+ *
470
+ * The rest of the library reasons in twelve-tone pitch classes, which is a
471
+ * deliberate scope choice for tonal theory. This module is the escape hatch for
472
+ * anyone who needs actual frequencies, cents, non-12 equal temperaments, or the
473
+ * acoustic (just) tuning behind the tempered intervals.
474
+ */
475
+ /**
476
+ * An equal-tempered tuning: a reference pitch and a number of equal divisions of
477
+ * the octave. With `divisions === 12` a step index is an ordinary MIDI number.
478
+ *
479
+ * @category Pitch & Intervals
480
+ */
481
+ type TuningTable = {
482
+ /** Step index (MIDI number when `divisions` is 12) whose frequency is `refFreq`. */
483
+ readonly refStep: number;
484
+ /** Frequency in Hz of `refStep`. */
485
+ readonly refFreq: number;
486
+ /** Equal divisions of the octave (12 standard; e.g. 19 or 31 for microtonal). */
487
+ readonly divisions: number;
488
+ };
489
+ /**
490
+ * Standard twelve-tone equal temperament, A4 (MIDI 69) = 440 Hz.
491
+ *
492
+ * @category Pitch & Intervals
493
+ */
494
+ declare const TWELVE_TET: TuningTable;
495
+ /**
496
+ * Build an equal temperament with `n` divisions of the octave.
497
+ *
498
+ * @param n Divisions of the octave (e.g. 19, 24, 31).
499
+ * @param refFreq Reference frequency in Hz (default 440).
500
+ * @param refStep Step index of the reference (default 69).
501
+ * @returns The tuning.
502
+ * @example
503
+ * ```ts
504
+ * import { edo, frequencyOf } from '@libraz/libcantus';
505
+ * const et19 = edo(19); // { refStep: 69, refFreq: 440, divisions: 19 }
506
+ * frequencyOf(70, et19); // one 19-EDO step above A4
507
+ * ```
508
+ * @category Pitch & Intervals
509
+ */
510
+ declare function edo(n: number, refFreq?: number, refStep?: number): TuningTable;
511
+ /**
512
+ * Frequency in Hz of a step index under a tuning.
513
+ *
514
+ * @param step Step index (a MIDI number under 12-EDO).
515
+ * @param tuning The tuning (default 12-TET).
516
+ * @returns The frequency in Hz.
517
+ * @example
518
+ * ```ts
519
+ * import { frequencyOf } from '@libraz/libcantus';
520
+ * frequencyOf(69); // 440 (A4 in 12-TET)
521
+ * frequencyOf(60); // middle C in Hz
522
+ * ```
523
+ * @category Pitch & Intervals
524
+ */
525
+ declare function frequencyOf(step: number, tuning?: TuningTable): number;
526
+ /**
527
+ * Nearest step index to a frequency under a tuning (the inverse of
528
+ * {@link frequencyOf}, rounded).
529
+ *
530
+ * @param freq Frequency in Hz.
531
+ * @param tuning The tuning (default 12-TET).
532
+ * @returns The nearest step index.
533
+ * @category Pitch & Intervals
534
+ */
535
+ declare function nearestStep(freq: number, tuning?: TuningTable): number;
536
+ /**
537
+ * Step index of a frequency under a tuning, unrounded.
538
+ *
539
+ * The exact inverse of {@link frequencyOf}. {@link nearestStep} rounds it,
540
+ * which discards how far off the pitch actually is — the figure a tuner
541
+ * display, a pitch-bend amount, or an intonation check is built on.
542
+ *
543
+ * @param freq Frequency in Hz.
544
+ * @param tuning The tuning (default 12-TET).
545
+ * @returns The fractional step index.
546
+ * @example
547
+ * ```ts
548
+ * import { stepOf } from '@libraz/libcantus';
549
+ * stepOf(442); // slightly above 69, the step for A440
550
+ * ```
551
+ * @category Pitch & Intervals
552
+ */
553
+ declare function stepOf(freq: number, tuning?: TuningTable): number;
554
+ /**
555
+ * How far a frequency sits from its nearest step, in cents.
556
+ *
557
+ * Positive means sharp of the step, negative flat.
558
+ *
559
+ * @param freq Frequency in Hz.
560
+ * @param tuning The tuning (default 12-TET).
561
+ * @returns The signed deviation in cents.
562
+ * @example
563
+ * ```ts
564
+ * import { centsFromNearestStep } from '@libraz/libcantus';
565
+ * Math.round(centsFromNearestStep(442)); // 8
566
+ * ```
567
+ * @category Pitch & Intervals
568
+ */
569
+ declare function centsFromNearestStep(freq: number, tuning?: TuningTable): number;
570
+ /**
571
+ * Steps spanned by an interval in cents, unrounded — the inverse of
572
+ * {@link centsOfSteps}.
573
+ *
574
+ * @param cents The interval in cents.
575
+ * @param tuning The tuning (default 12-TET).
576
+ * @returns The fractional step count.
577
+ * @category Pitch & Intervals
578
+ */
579
+ declare function stepsOfCents(cents: number, tuning?: TuningTable): number;
580
+ /**
581
+ * The frequency ratio an interval in cents spans — the inverse of
582
+ * {@link ratioToCents}.
583
+ *
584
+ * Multiply a frequency by this to move it by that many cents, which is how a
585
+ * cents offset (a pitch-bend spec, a tuning table entry) is applied.
586
+ *
587
+ * @param cents The interval in cents.
588
+ * @returns The frequency ratio.
589
+ * @example
590
+ * ```ts
591
+ * import { centsToRatio } from '@libraz/libcantus';
592
+ * 440 * centsToRatio(14); // A440 raised by 14 cents
593
+ * ```
594
+ * @category Pitch & Intervals
595
+ */
596
+ declare function centsToRatio(cents: number): number;
597
+ /**
598
+ * Interval in cents between two frequencies.
599
+ *
600
+ * @param a Lower/first frequency in Hz.
601
+ * @param b Upper/second frequency in Hz.
602
+ * @returns Cents from `a` to `b` (negative if `b` is lower).
603
+ * @category Pitch & Intervals
604
+ */
605
+ declare function centsBetweenFreq(a: number, b: number): number;
606
+ /**
607
+ * Cents spanned by a number of equal-temperament steps.
608
+ *
609
+ * @param steps Number of steps.
610
+ * @param tuning The tuning (default 12-TET).
611
+ * @returns The cents.
612
+ * @category Pitch & Intervals
613
+ */
614
+ declare function centsOfSteps(steps: number, tuning?: TuningTable): number;
615
+ /**
616
+ * Cents of a frequency ratio, e.g. `ratioToCents(3, 2)` ≈ 701.955 for the just
617
+ * perfect fifth.
618
+ *
619
+ * @param numerator Ratio numerator.
620
+ * @param denominator Ratio denominator.
621
+ * @returns The interval in cents.
622
+ * @category Pitch & Intervals
623
+ */
624
+ declare function ratioToCents(numerator: number, denominator: number): number;
625
+ /**
626
+ * Five-limit just-intonation ratios for the twelve interval classes above a
627
+ * unison, indexed by semitone class (0..12).
628
+ *
629
+ * @category Pitch & Intervals
630
+ */
631
+ declare const JUST_RATIOS: Readonly<Record<number, readonly [number, number]>>;
632
+ /**
633
+ * Cents by which a five-limit just interval departs from its 12-TET tempering
634
+ * (positive means the just interval is wider).
635
+ *
636
+ * @param semitoneClass Semitone class in [0, 12].
637
+ * @returns The deviation in cents.
638
+ * @throws If `semitoneClass` is not an integer in [0, 12].
639
+ * @category Pitch & Intervals
640
+ */
641
+ declare function justDeviationCents(semitoneClass: number): number;
642
+
643
+ export { playability as A, ratioToCents as B, secondsToBeats as C, type DurationData as D, stepOf as E, stepsOfCents as F, tempoAt as G, ticksToBeats as H, type IndexedNoteEvent as I, JUST_RATIOS as J, NOTE_VALUES as N, type OnsetTieBreak as O, type PlayabilityIssue as P, type SpelledDuration as S, TWELVE_TET as T, type NoteEventIndex as a, type NoteEventIndexOptions as b, type NotePlacement as c, type NoteValue as d, type PlayabilityIssueType as e, type PlayabilityLayer as f, type PlayabilityReport as g, type TempoEvent as h, type TempoMap as i, type TuningTable as j, type Tuplet as k, beatsToDuration as l, beatsToSeconds as m, beatsToTicks as n, beatsToTiedDurations as o, centsBetweenFreq as p, centsFromNearestStep as q, centsOfSteps as r, centsToRatio as s, createNoteEventIndex as t, durationToBeats as u, durationToSeconds as v, edo as w, frequencyOf as x, justDeviationCents as y, nearestStep as z };