@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,770 @@
1
+ /**
2
+ * Every playing technique a note can carry, in declaration order.
3
+ *
4
+ * These name intent, not a rendering: how `flam` becomes a grace stroke or
5
+ * `slide` becomes pitch bend is the caller's decision, because it depends on the
6
+ * sampler, key switches, and controller layout in use. Carrying the intent
7
+ * instead of a baked note pair is what lets any phrase be flammed, ghosted, or
8
+ * muted without the generator having to know how it will be voiced.
9
+ *
10
+ * @category Core
11
+ */
12
+ declare const ARTICULATIONS: readonly ["accent", "ghost", "staccato", "legato", "slide", "hammer", "mute", "flam", "drag", "roll", "choke", "open"];
13
+ /**
14
+ * How a note is played, independent of its pitch and length.
15
+ *
16
+ * @category Core
17
+ */
18
+ type Articulation = (typeof ARTICULATIONS)[number];
19
+
20
+ /**
21
+ * A key/scale definition anchored on a root pitch class.
22
+ *
23
+ * `modeMask12` is a 12-bit mask where bit `n` set means the pitch class
24
+ * `(rootPc + n) % 12` belongs to the scale. Bit 0 (the root) is always set;
25
+ * {@link maskFromOffsets} enforces this so the root is always a scale tone.
26
+ *
27
+ * @category Core
28
+ */
29
+ type KeyScale = {
30
+ rootPc: number;
31
+ modeMask12: number;
32
+ };
33
+ /**
34
+ * A single sounding note on a beat-indexed timeline: an absolute MIDI pitch, an
35
+ * onset and duration measured in quarter-note beats, and an optional MIDI
36
+ * velocity. This is the library-wide interchange shape for note events read
37
+ * from or written to a DAW/MIDI track; the harmonizer's `MelodyNote` and the
38
+ * analysis `VoiceNote` are specializations of it.
39
+ *
40
+ * @category Core
41
+ */
42
+ type NoteEvent = {
43
+ /** MIDI pitch in [0, 127] (middle C = 60). */
44
+ pitch: number;
45
+ /**
46
+ * Onset in quarter-note beats, absolute from the start of the timeline, and
47
+ * unbounded below: beat 0 is the first downbeat, so a pickup sounds before it
48
+ * and is written at a negative onset, in the bar numbered -1. Shifting the
49
+ * whole timeline a bar later instead would move every strong beat with it,
50
+ * and the upbeat would then be analyzed as a downbeat. A caller that knows
51
+ * how long its pickup is narrows the bound with
52
+ * {@link NoteEventAssertOptions.minStartBeat}.
53
+ */
54
+ startBeat: number;
55
+ /** Duration in quarter-note beats. */
56
+ durationBeat: number;
57
+ /** MIDI velocity in [0, 127], when known. */
58
+ velocity?: number;
59
+ /**
60
+ * How the note is played, when the writer had an intent to record. The
61
+ * library carries the intent only: turning `slide` into pitch bend or `ghost`
62
+ * into a velocity floor depends on the instrument and controller layout at
63
+ * the far end, so it is the reader's decision. Optional, so a reader that
64
+ * knows nothing of articulation reads the note exactly as before.
65
+ */
66
+ articulation?: Articulation;
67
+ };
68
+
69
+ /**
70
+ * The library's error taxonomy.
71
+ *
72
+ * Every failure this library raises falls into one of three kinds, and the kind
73
+ * decides what a caller can do about it. Batch-voicing a lead sheet, for
74
+ * instance, has to tell "fix the input" apart from "loosen the constraints",
75
+ * and a message string is not a contract.
76
+ *
77
+ * The classes extend the built-in error types a caller would otherwise catch —
78
+ * a rejected argument is still a `RangeError` — so the `code` narrows an
79
+ * existing category rather than replacing it.
80
+ */
81
+ /**
82
+ * What kind of failure an error reports.
83
+ *
84
+ * - `INVALID_INPUT`: an argument is malformed or out of range. The caller's
85
+ * input must change.
86
+ * - `NO_SOLUTION`: the input is valid but nothing satisfies the constraints.
87
+ * The constraints must change, or the failure must be accepted.
88
+ * - `BUDGET_EXCEEDED`: the requested work is larger than the synchronous
89
+ * generation budget. The request must be smaller, or the budget larger.
90
+ *
91
+ * @category Core
92
+ */
93
+ type LibcantusErrorCode = 'INVALID_INPUT' | 'NO_SOLUTION' | 'BUDGET_EXCEEDED';
94
+ /**
95
+ * Any error this library raises, as a union of its three classes.
96
+ *
97
+ * The type a non-throwing entry point reports its failure as: a caller that
98
+ * holds one of these already knows it carries a {@link LibcantusErrorCode} and
99
+ * a message worth showing.
100
+ *
101
+ * @category Core
102
+ */
103
+ type LibcantusError = InvalidInputError | NoSolutionError | BudgetExceededError;
104
+ /**
105
+ * The outcome of a parse that reports failure instead of throwing it.
106
+ *
107
+ * A text field is parsed on every keystroke, and most of those keystrokes are
108
+ * halfway through a valid symbol, so failure is the normal case rather than an
109
+ * exceptional one. The error travels in the result — not as `null` — because an
110
+ * input field has to say what is wrong with what was typed.
111
+ *
112
+ * @typeParam T The value a successful parse produces.
113
+ * @category Core
114
+ * @example
115
+ * ```ts
116
+ * import { tryParseChordSymbol } from '@libraz/libcantus';
117
+ * const result = tryParseChordSymbol('Cmaj');
118
+ * const label = result.ok ? result.value.quality : result.error.message;
119
+ * ```
120
+ */
121
+ type ParseResult<T> = {
122
+ ok: true;
123
+ value: T;
124
+ } | {
125
+ ok: false;
126
+ error: LibcantusError;
127
+ };
128
+ /**
129
+ * A rejected argument: malformed, out of range, or naming something unknown.
130
+ *
131
+ * @category Core
132
+ */
133
+ declare class InvalidInputError extends RangeError {
134
+ readonly code: "INVALID_INPUT";
135
+ readonly name = "InvalidInputError";
136
+ }
137
+ /**
138
+ * A valid request with no answer: no voicing fits the given ranges, no chord
139
+ * matches the pitches, no preset claims the style.
140
+ *
141
+ * @category Core
142
+ */
143
+ declare class NoSolutionError extends Error {
144
+ readonly code: "NO_SOLUTION";
145
+ readonly name = "NoSolutionError";
146
+ /**
147
+ * Where in a sequence the failure occurred, when the operation was working
148
+ * through one — the chord index of a progression, say.
149
+ */
150
+ readonly at: number | undefined;
151
+ constructor(message: string, options?: {
152
+ at?: number;
153
+ });
154
+ }
155
+ /**
156
+ * Work that would exceed the synchronous generation budget.
157
+ *
158
+ * @category Core
159
+ */
160
+ declare class BudgetExceededError extends RangeError {
161
+ readonly code: "BUDGET_EXCEEDED";
162
+ readonly name = "BudgetExceededError";
163
+ }
164
+ /**
165
+ * Whether a caught value is one of this library's coded errors.
166
+ *
167
+ * @param value The caught value.
168
+ * @returns True when it carries a {@link LibcantusErrorCode}.
169
+ * @example
170
+ * ```ts
171
+ * import { isLibcantusError, voiceChord, parseChordSymbol } from '@libraz/libcantus';
172
+ * try {
173
+ * voiceChord(parseChordSymbol('C'), { ranges: [] });
174
+ * } catch (error) {
175
+ * if (isLibcantusError(error) && error.code === 'NO_SOLUTION') {
176
+ * // loosen the constraints rather than blaming the input
177
+ * }
178
+ * }
179
+ * ```
180
+ * @category Core
181
+ */
182
+ declare function isLibcantusError(value: unknown): value is InvalidInputError | NoSolutionError | BudgetExceededError;
183
+
184
+ /**
185
+ * Note-name notation systems.
186
+ *
187
+ * A pitch has one identity but many written names: the note an English score
188
+ * calls `G#` is `gis` in German, `嬰ト` in Japanese and `sol diesis` in Italian.
189
+ * This module holds each system's table — letters, accidental affixes and mode
190
+ * words — so a name can be read and written in the system a source actually
191
+ * uses, and so a name of unknown origin can be attributed to one.
192
+ *
193
+ * The tables live together because attribution needs them together: `B` is a B
194
+ * natural in English but a B flat in German, and only a table that knows both
195
+ * can say so.
196
+ */
197
+
198
+ /**
199
+ * A system of written note names.
200
+ *
201
+ * - `english`: C D E F G A B, `#` sharp and `b` flat (`C#`, `Bb`, `F##`).
202
+ * - `german`: C D E F G A H, `-is` sharp and `-es` flat (`cis`, `des`), with B
203
+ * for B flat and H for B natural.
204
+ * - `japanese`: the katakana 音名 ハ ニ ホ ヘ ト イ ロ, prefixed 嬰 sharp and
205
+ * 変 flat.
206
+ * - `italian`: the solfège syllables do re mi fa sol la si, followed by
207
+ * `diesis` or `bemolle`.
208
+ * - `fixedDo`: the same syllables read as fixed-do note names; identical to
209
+ * `italian`, and named separately because callers ask for it by that name.
210
+ *
211
+ * @category Pitch & Intervals
212
+ */
213
+ type NoteNameSystem = 'english' | 'german' | 'japanese' | 'italian' | 'fixedDo';
214
+ /**
215
+ * Which notation system to read or write a name in.
216
+ *
217
+ * @category Pitch & Intervals
218
+ */
219
+ type NoteNameOptions = {
220
+ /**
221
+ * The system. Omitted on parsing, the system is detected from the name
222
+ * itself; omitted on formatting, the name is written in English.
223
+ */
224
+ system?: NoteNameSystem;
225
+ };
226
+ /**
227
+ * A key named the way a score names it: a spelled tonic and a mode.
228
+ *
229
+ * This is what a key *name* carries. It is deliberately not a key/scale: a name
230
+ * fixes the tonic's spelling and says major or minor, and nothing more.
231
+ *
232
+ * @category Scales
233
+ */
234
+ type KeyName = {
235
+ /** The spelled tonic, without an octave. */
236
+ tonic: Note;
237
+ /** Which of the two modes the name gives. */
238
+ mode: 'major' | 'minor';
239
+ };
240
+ /**
241
+ * The notation system a note name or key name is written in.
242
+ *
243
+ * A name several systems could read is attributed to the first of English,
244
+ * German, Japanese, Italian that reads it — so `B` is the English B natural,
245
+ * and German is chosen only for a name carrying a German-only cue (`H`, an
246
+ * `-is`/`-es` ending, or `dur`/`moll`). Because the fixed-do syllables are the
247
+ * Italian ones, detection reports `'italian'` for them; `'fixedDo'` is only ever
248
+ * a system a caller asks for by name.
249
+ *
250
+ * @param text The note name or key name.
251
+ * @returns The system that reads it.
252
+ * @throws If no system reads the name, or the name mixes two systems — a German
253
+ * tonic under an English mode word, say.
254
+ * @example
255
+ * ```ts
256
+ * import { detectNoteNameSystem } from '@libraz/libcantus';
257
+ * detectNoteNameSystem('B'); // 'english' — never the German B flat
258
+ * detectNoteNameSystem('gis moll'); // 'german'
259
+ * detectNoteNameSystem('嬰ト短調'); // 'japanese'
260
+ * ```
261
+ * @category Pitch & Intervals
262
+ */
263
+ declare function detectNoteNameSystem(text: string): NoteNameSystem;
264
+
265
+ /**
266
+ * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
267
+ * intervals.
268
+ *
269
+ * The rest of the library works in pitch classes (0..11), which cannot tell a
270
+ * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
271
+ * an explicit spelling layer — a note is a diatonic letter plus a chromatic
272
+ * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
273
+ * preserved.
274
+ */
275
+
276
+ /**
277
+ * A spelled note: a diatonic letter, a chromatic alteration, and an optional
278
+ * octave.
279
+ *
280
+ * - `letter` is 0..6 for C..B.
281
+ * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
282
+ * 0 natural, +1 sharp, +2 double-sharp.
283
+ * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
284
+ * absent, the note denotes a bare pitch class.
285
+ *
286
+ * @category Pitch & Intervals
287
+ */
288
+ type Note = {
289
+ letter: number;
290
+ alter: number;
291
+ octave?: number;
292
+ };
293
+ /**
294
+ * The quality label of a spelled interval: perfect, major, minor, or one or
295
+ * more augmentations or diminutions.
296
+ *
297
+ * This names how an interval is *spelled*. It is a different notion from
298
+ * {@link ConsonanceClass}, which classifies how an interval *sounds* against
299
+ * another voice.
300
+ *
301
+ * @see {@link ConsonanceClass}
302
+ * @category Pitch & Intervals
303
+ */
304
+ type IntervalQualityLabel = 'P' | 'M' | 'm' | `A${string}` | `d${string}`;
305
+ /**
306
+ * A spelled interval: a diatonic number, a quality label, and its semitone span.
307
+ *
308
+ * @category Pitch & Intervals
309
+ */
310
+ type SpelledInterval = {
311
+ /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
312
+ number: number;
313
+ /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
314
+ quality: IntervalQualityLabel;
315
+ /** Signed semitone distance from the first note to the second. */
316
+ semitones: number;
317
+ /**
318
+ * True when the diatonic letters move down, and absent otherwise — an
319
+ * ascending interval leaves the key out rather than carrying `false`, so
320
+ * every producer of this type returns the same object for the same interval.
321
+ * The flag is not redundant with the sign of the span: a descending unison
322
+ * spans zero semitones, and a letter can rise while the pitch falls.
323
+ */
324
+ descending?: boolean;
325
+ };
326
+ /**
327
+ * Reduce a MIDI pitch, a bare pitch class, or any signed offset to a pitch
328
+ * class in [0, 11].
329
+ *
330
+ * This is the library's single definition of pitch-class arithmetic: every
331
+ * layer routes through it so a fractional pitch reduces the same way
332
+ * everywhere. A non-integral input is rounded to the nearest semitone first, so
333
+ * a microtonal 60.6 reads as C# rather than C.
334
+ *
335
+ * @param value A MIDI pitch, pitch class, or signed semitone offset.
336
+ * @returns The pitch class in [0, 11].
337
+ * @example
338
+ * ```ts
339
+ * import { pitchClassOf } from '@libraz/libcantus';
340
+ * pitchClassOf(61); // 1
341
+ * pitchClassOf(-1); // 11
342
+ * pitchClassOf(60.6); // 1
343
+ * ```
344
+ * @category Pitch & Intervals
345
+ */
346
+ declare function pitchClassOf(value: number): number;
347
+ /**
348
+ * Reduce a letter number or letter offset to a diatonic letter in [0, 6]
349
+ * (0 = C .. 6 = B).
350
+ *
351
+ * @param value A letter number or signed letter offset.
352
+ * @returns The diatonic letter in [0, 6].
353
+ * @category Pitch & Intervals
354
+ */
355
+ declare function diatonicLetterOf(value: number): number;
356
+ /**
357
+ * The pitch class of a diatonic letter with no accidental (C = 0, D = 2, ...).
358
+ *
359
+ * @param letter A letter number or signed letter offset; reduced to [0, 6].
360
+ * @returns The natural pitch class of that letter.
361
+ * @category Pitch & Intervals
362
+ */
363
+ declare function naturalPitchClassOf(letter: number): number;
364
+ /**
365
+ * Parse a note name into a {@link Note}, in any supported notation system.
366
+ *
367
+ * English (the default when a name could be read in more than one system)
368
+ * accepts a letter (case-insensitive) followed by any number of same-direction
369
+ * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
370
+ * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
371
+ * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
372
+ *
373
+ * Without a `system` the name is attributed by {@link detectNoteNameSystem},
374
+ * which reads a name any two systems could claim as English: `"B"` is the B
375
+ * natural, never the German B flat. German is chosen only for a German-only
376
+ * name such as `"H"`, `"fis"` or `"es"`.
377
+ *
378
+ * A scientific octave may follow the name in every system (`"gis4"`,
379
+ * `"嬰ト4"`), which is what makes formatting and parsing exact inverses for an
380
+ * octave-bearing note.
381
+ *
382
+ * @param text The note name.
383
+ * @param opts `system` reads the name in that notation system instead of
384
+ * detecting it.
385
+ * @returns The parsed note.
386
+ * @throws If the text is not a valid note in the given (or detected) system.
387
+ * Use {@link tryParseNote} where failure is ordinary, such as a note field
388
+ * read on every keystroke.
389
+ * @example
390
+ * ```ts
391
+ * import { parseNote, noteToMidi, noteToPitchClass } from '@libraz/libcantus';
392
+ * noteToMidi(parseNote('C#4')); // 61
393
+ * noteToPitchClass(parseNote('gis')); // 8 — the German G sharp
394
+ * noteToPitchClass(parseNote('B', { system: 'german' })); // 10 — the German B flat
395
+ * ```
396
+ * @category Pitch & Intervals
397
+ */
398
+ declare function parseNote(text: string, opts?: NoteNameOptions): Note;
399
+ /**
400
+ * Parse a note name, reporting failure instead of throwing it.
401
+ *
402
+ * The same reading as {@link parseNote} — that function is this one with its
403
+ * error thrown — for the callers where a name that does not parse yet is the
404
+ * normal state of the input rather than a fault.
405
+ *
406
+ * @param text The note name.
407
+ * @param opts `system` reads the name in that notation system instead of
408
+ * detecting it.
409
+ * @returns The note, or the error explaining why the text is not one.
410
+ * @example
411
+ * ```ts
412
+ * import { tryParseNote } from '@libraz/libcantus';
413
+ * const result = tryParseNote('C#4');
414
+ * result.ok ? result.value.letter : result.error.message;
415
+ * ```
416
+ * @category Pitch & Intervals
417
+ */
418
+ declare function tryParseNote(text: string, opts?: NoteNameOptions): ParseResult<Note>;
419
+ /**
420
+ * Render a {@link Note} as a note name, in any supported notation system.
421
+ *
422
+ * The inverse of {@link parseNote} when both are given the same `system`,
423
+ * including double accidentals and octaves. Without one, {@link parseNote}
424
+ * detects the system and reads a bare `B` as the English B natural, so a German
425
+ * B flat written as `'b'` does not survive a round trip that names no system.
426
+ *
427
+ * @param note The note to format.
428
+ * @param opts `system` writes the name in that notation system instead of
429
+ * English.
430
+ * @returns The note name, including the octave when present.
431
+ * @example
432
+ * ```ts
433
+ * import { formatNote, parseNote } from '@libraz/libcantus';
434
+ * formatNote(parseNote('C#4')); // 'C#4'
435
+ * formatNote(parseNote('G#'), { system: 'german' }); // 'gis'
436
+ * formatNote(parseNote('G#'), { system: 'japanese' }); // '嬰ト'
437
+ * ```
438
+ * @category Pitch & Intervals
439
+ */
440
+ declare function formatNote(note: Note, opts?: NoteNameOptions): string;
441
+ /**
442
+ * Parse a key name such as `'C major'`, `'gis moll'` or `'嬰ト短調'`.
443
+ *
444
+ * The name is a tonic followed by a mode word, in any of the supported
445
+ * notation systems; German also accepts its hyphenated form (`'gis-Moll'`).
446
+ * Without a `system` the name is attributed by {@link detectNoteNameSystem},
447
+ * so a mode word is itself enough to pick one: `'B dur'` is B flat major while
448
+ * `'B major'` is B major.
449
+ *
450
+ * German carries the mode twice, in the word and in the case of the tonic
451
+ * (`C dur`, `c moll`). When the two disagree the word wins — `'C moll'` is C
452
+ * minor — because the word is what the writer said and the case only what they
453
+ * typed. A German name with no mode word is decided by its case alone
454
+ * (`'gis'` is G sharp minor, `'Gis'` G sharp major); a bare tonic in any other
455
+ * system names a major key.
456
+ *
457
+ * @param text The key name.
458
+ * @param opts `system` reads the name in that notation system instead of
459
+ * detecting it.
460
+ * @returns The spelled tonic and the mode.
461
+ * @throws If the text is not a key name in the given (or detected) system, if
462
+ * it mixes two systems, or if the tonic carries an octave. Use
463
+ * {@link tryParseKeyName} where failure is ordinary, such as a key field read
464
+ * on every keystroke.
465
+ * @example
466
+ * ```ts
467
+ * import { formatNote, parseKeyName } from '@libraz/libcantus';
468
+ * const key = parseKeyName('gis moll');
469
+ * formatNote(key.tonic); // 'G#'
470
+ * key.mode; // 'minor'
471
+ * ```
472
+ * @category Scales
473
+ */
474
+ declare function parseKeyName(text: string, opts?: NoteNameOptions): KeyName;
475
+ /**
476
+ * Parse a key name, reporting failure instead of throwing it.
477
+ *
478
+ * The same reading as {@link parseKeyName} — that function is this one with its
479
+ * error thrown — for the callers where a name that does not parse yet is the
480
+ * normal state of the input rather than a fault.
481
+ *
482
+ * @param text The key name.
483
+ * @param opts `system` reads the name in that notation system instead of
484
+ * detecting it.
485
+ * @returns The key, or the error explaining why the text is not one.
486
+ * @example
487
+ * ```ts
488
+ * import { tryParseKeyName } from '@libraz/libcantus';
489
+ * const result = tryParseKeyName('gis moll');
490
+ * result.ok ? result.value.mode : result.error.message; // 'minor'
491
+ * ```
492
+ * @category Scales
493
+ */
494
+ declare function tryParseKeyName(text: string, opts?: NoteNameOptions): ParseResult<KeyName>;
495
+ /**
496
+ * Render a spelled tonic and a mode as a key name.
497
+ *
498
+ * The inverse of {@link parseKeyName}. German is written the way it is read,
499
+ * with the mode in the case of the tonic as well as in the word.
500
+ *
501
+ * @param key The spelled tonic and mode; any octave on the tonic is dropped,
502
+ * since a key has no register.
503
+ * @param opts `system` writes the name in that notation system instead of
504
+ * English.
505
+ * @returns The key name.
506
+ * @throws If the tonic or the mode is not valid.
507
+ * @example
508
+ * ```ts
509
+ * import { formatKeyName, parseNote } from '@libraz/libcantus';
510
+ * formatKeyName({ tonic: parseNote('G#'), mode: 'minor' }); // 'G# minor'
511
+ * formatKeyName({ tonic: parseNote('G#'), mode: 'minor' }, { system: 'german' }); // 'gis moll'
512
+ * ```
513
+ * @category Scales
514
+ */
515
+ declare function formatKeyName(key: KeyName, opts?: NoteNameOptions): string;
516
+ /**
517
+ * Pitch class (0..11) of a note, ignoring octave.
518
+ *
519
+ * @param note The note.
520
+ * @returns The pitch class.
521
+ * @example
522
+ * ```ts
523
+ * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
524
+ * noteToPitchClass(parseNote('Db')); // 1
525
+ * ```
526
+ * @category Pitch & Intervals
527
+ */
528
+ declare function noteToPitchClass(note: Note): number;
529
+ /**
530
+ * MIDI number of a note (middle C = C4 = 60).
531
+ *
532
+ * @param note The note; must carry an octave.
533
+ * @returns The MIDI number.
534
+ * @throws If the note has no octave.
535
+ * @example
536
+ * ```ts
537
+ * import { noteToMidi, parseNote } from '@libraz/libcantus';
538
+ * noteToMidi(parseNote('A4')); // 69
539
+ * ```
540
+ * @category Pitch & Intervals
541
+ */
542
+ declare function noteToMidi(note: Note): number;
543
+ /**
544
+ * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
545
+ *
546
+ * The input is rounded to the nearest integer but is not clamped to the MIDI
547
+ * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
548
+ * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
549
+ *
550
+ * @param midi The MIDI number.
551
+ * @param spelling Whether to prefer sharps or flats for black keys.
552
+ * @returns The spelled note, with octave.
553
+ * @throws If `spelling` is neither `'sharp'` nor `'flat'`; a name the table does
554
+ * not carry would otherwise be spelled as one of them without saying which.
555
+ * @example
556
+ * ```ts
557
+ * import { midiToNote, formatNote } from '@libraz/libcantus';
558
+ * formatNote(midiToNote(61)); // 'C#4'
559
+ * formatNote(midiToNote(61, 'flat')); // 'Db4'
560
+ * ```
561
+ * @category Pitch & Intervals
562
+ */
563
+ declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
564
+ /**
565
+ * Transpose a note by a signed number of semitones, keeping its spelling.
566
+ *
567
+ * The letter moves by the diatonic distance of the conventional interval for
568
+ * that many semitones and the accidental is recomputed for the new letter, so
569
+ * enharmonic identity survives: `Ab4` up a major second is `Bb4`, not `A#4`, and
570
+ * the operation is an exact inverse of itself. An octave-less note stays
571
+ * octave-less.
572
+ *
573
+ * A bare semitone count cannot distinguish an augmented fourth from a diminished
574
+ * fifth; pass `spelling` to override the letter distance when the enharmonic
575
+ * choice matters.
576
+ *
577
+ * @param note The note to transpose.
578
+ * @param semitones The signed semitone offset.
579
+ * @param opts `spelling` forces the result onto the sharp or flat side instead
580
+ * of following the source note's letter.
581
+ * @returns The transposed note.
582
+ * @example
583
+ * ```ts
584
+ * import { transposeNote, formatNote, parseNote } from '@libraz/libcantus';
585
+ * formatNote(transposeNote(parseNote('Ab4'), 2)); // 'Bb4'
586
+ * formatNote(transposeNote(parseNote('Ab4'), 2, { spelling: 'sharp' })); // 'A#4'
587
+ * ```
588
+ * @category Pitch & Intervals
589
+ */
590
+ declare function transposeNote(note: Note, semitones: number, opts?: {
591
+ spelling?: 'sharp' | 'flat';
592
+ }): Note;
593
+ /**
594
+ * The semitone span of the interval named by a diatonic number and a quality.
595
+ *
596
+ * The inverse of the quality derivation in {@link spelledInterval}: it turns a
597
+ * name back into a distance, which is what transposing by a named interval or
598
+ * parsing one from text needs.
599
+ *
600
+ * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.
601
+ * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.
602
+ * @returns The unsigned semitone span.
603
+ * @throws If the quality cannot apply to the number, such as a major fifth or a
604
+ * diminished unison.
605
+ * @example
606
+ * ```ts
607
+ * import { intervalSemitones } from '@libraz/libcantus';
608
+ * intervalSemitones(5, 'P'); // 7
609
+ * ```
610
+ * @category Pitch & Intervals
611
+ */
612
+ declare function intervalSemitones(numberValue: number, quality: IntervalQualityLabel): number;
613
+ /**
614
+ * Parse an interval name such as `'P5'`, `'m3'`, `'AA4'`, or `'-m3'`.
615
+ *
616
+ * A leading `'-'` names the same interval taken downward: the span is negated
617
+ * and `descending` is set. The flag is not redundant with the sign — a
618
+ * descending unison spans zero semitones, so only the flag records that the
619
+ * letters move down.
620
+ *
621
+ * @param name The interval name: an optional `'-'`, a quality label, and a
622
+ * diatonic number. Surrounding whitespace is ignored.
623
+ * @returns The spelled interval. An ascending name carries a non-negative span
624
+ * and no `descending` flag.
625
+ * @throws If the name is not a quality label followed by a number, or the two
626
+ * cannot describe the same interval. Use {@link tryParseInterval} where
627
+ * failure is ordinary, such as an interval field read on every keystroke.
628
+ * @example
629
+ * ```ts
630
+ * import { parseInterval } from '@libraz/libcantus';
631
+ * parseInterval('m3'); // { number: 3, quality: 'm', semitones: 3 }
632
+ * parseInterval('-m3'); // { number: 3, quality: 'm', semitones: -3, descending: true }
633
+ * ```
634
+ * @category Pitch & Intervals
635
+ */
636
+ declare function parseInterval(name: string): SpelledInterval;
637
+ /**
638
+ * Parse an interval name, reporting failure instead of throwing it.
639
+ *
640
+ * The same reading as {@link parseInterval} — that function is this one with
641
+ * its error thrown — for the callers where a name that does not parse yet is
642
+ * the normal state of the input rather than a fault.
643
+ *
644
+ * @param name The interval name.
645
+ * @returns The interval, or the error explaining why the text is not one.
646
+ * @example
647
+ * ```ts
648
+ * import { tryParseInterval } from '@libraz/libcantus';
649
+ * const result = tryParseInterval('M5');
650
+ * result.ok ? result.value.semitones : result.error.message; // a fifth cannot be major
651
+ * ```
652
+ * @category Pitch & Intervals
653
+ */
654
+ declare function tryParseInterval(name: string): ParseResult<SpelledInterval>;
655
+ /**
656
+ * Anything that names a spelled interval: an interval name, plain interval
657
+ * data, or a value that serializes to interval data such as the `Interval`
658
+ * class.
659
+ *
660
+ * @category Pitch & Intervals
661
+ */
662
+ type IntervalLike = string | SpelledInterval | {
663
+ /** The interval data this value stands for. */
664
+ toJSON(): SpelledInterval;
665
+ };
666
+ /**
667
+ * Resolve any interval-shaped value to plain {@link SpelledInterval} data.
668
+ *
669
+ * This is what lets an entry point accept an interval name, the plain data the
670
+ * pitch module returns, or an `Interval` instance without every caller
671
+ * branching on the form. An instance is accepted through its `toJSON` method
672
+ * rather than by its type, because the core layer cannot import the model layer
673
+ * that defines the class.
674
+ *
675
+ * @param value An interval name, plain interval data, or a value whose `toJSON`
676
+ * returns interval data.
677
+ * @returns The validated interval data, carrying `descending` only when the
678
+ * interval descends.
679
+ * @throws If the value is not interval-shaped, or its number, quality, and span
680
+ * do not describe the same interval.
681
+ * @example
682
+ * ```ts
683
+ * import { toSpelledInterval } from '@libraz/libcantus';
684
+ * toSpelledInterval('P5'); // { number: 5, quality: 'P', semitones: 7 }
685
+ * toSpelledInterval({ number: 5, quality: 'P', semitones: -7 });
686
+ * // { number: 5, quality: 'P', semitones: -7, descending: true }
687
+ * ```
688
+ * @category Pitch & Intervals
689
+ */
690
+ declare function toSpelledInterval(value: IntervalLike): SpelledInterval;
691
+ /**
692
+ * Anything that names a spelled note: a note name, plain note data, a MIDI
693
+ * number, or a value that serializes to note data such as the `Note` class.
694
+ *
695
+ * @category Pitch & Intervals
696
+ */
697
+ type NoteLike = string | number | Note | {
698
+ /** The note data this value stands for. */
699
+ toJSON(): Note;
700
+ };
701
+ /**
702
+ * Resolve any note-shaped value to plain {@link Note} data.
703
+ *
704
+ * The counterpart of {@link toSpelledInterval} for notes: an entry point takes
705
+ * whatever form the caller has — the name a text field holds, the MIDI number a
706
+ * device sends, the data the pitch module returns, or a `Note` instance — and
707
+ * gets one shape back. An instance is accepted through its `toJSON` method
708
+ * rather than by its type, because the core layer cannot import the model layer
709
+ * that defines the class.
710
+ *
711
+ * A number is read as MIDI and spelled with sharps, the same reading
712
+ * {@link midiToNote} takes by default. Where a key is known the caller spells
713
+ * it there instead — `spellPitch` reads the key — because no single default
714
+ * suits both a G# in E major and an Ab in Eb major.
715
+ *
716
+ * @param value A note name, a MIDI number, plain note data, or a value whose
717
+ * `toJSON` returns note data.
718
+ * @returns The validated note data, carrying `octave` only when the value has
719
+ * one.
720
+ * @throws If the value is not note-shaped, or its letter, alteration, or octave
721
+ * fall outside the range a spelled note holds.
722
+ * @example
723
+ * ```ts
724
+ * import { toNoteData } from '@libraz/libcantus';
725
+ * toNoteData('Eb4'); // { letter: 2, alter: -1, octave: 4 }
726
+ * toNoteData(60); // { letter: 0, alter: 0, octave: 4 }
727
+ * ```
728
+ * @category Pitch & Intervals
729
+ */
730
+ declare function toNoteData(value: NoteLike): Note;
731
+ /**
732
+ * Transpose a note by a spelled interval, keeping the spelling the interval
733
+ * names.
734
+ *
735
+ * Unlike {@link transposeNote}, which picks a letter from the semitone count,
736
+ * the diatonic number decides the letter here: transposing C by an augmented
737
+ * second gives D#, not Eb.
738
+ *
739
+ * @param note The note to transpose.
740
+ * @param interval The interval to apply; a negative span transposes downward.
741
+ * @returns The transposed note, octave-less if the source was.
742
+ * @example
743
+ * ```ts
744
+ * import { formatNote, parseInterval, parseNote, transposeByInterval } from '@libraz/libcantus';
745
+ * formatNote(transposeByInterval(parseNote('C4'), parseInterval('A2'))); // 'D#4'
746
+ * ```
747
+ * @category Pitch & Intervals
748
+ */
749
+ declare function transposeByInterval(note: Note, interval: SpelledInterval): Note;
750
+ /**
751
+ * The spelled interval from one note to another.
752
+ *
753
+ * When both notes carry octaves, the interval reflects their full signed
754
+ * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
755
+ * interval is measured within a single ascending octave from `a` to `b`.
756
+ *
757
+ * @param a The lower/first note.
758
+ * @param b The upper/second note.
759
+ * @returns The diatonic number, quality, and semitone span.
760
+ * @example
761
+ * ```ts
762
+ * import { spelledInterval, parseNote } from '@libraz/libcantus';
763
+ * spelledInterval(parseNote('C4'), parseNote('G4'));
764
+ * // { number: 5, quality: 'P', semitones: 7 }
765
+ * ```
766
+ * @category Pitch & Intervals
767
+ */
768
+ declare function spelledInterval(a: Note, b: Note): SpelledInterval;
769
+
770
+ export { ARTICULATIONS as A, BudgetExceededError as B, toSpelledInterval as C, transposeByInterval as D, transposeNote as E, tryParseInterval as F, tryParseKeyName as G, tryParseNote as H, type IntervalLike as I, type KeyName as K, type LibcantusError as L, NoSolutionError as N, type ParseResult as P, type SpelledInterval as S, type Articulation as a, type IntervalQualityLabel as b, InvalidInputError as c, type KeyScale as d, type LibcantusErrorCode as e, type Note as f, type NoteEvent as g, type NoteLike as h, type NoteNameOptions as i, type NoteNameSystem as j, detectNoteNameSystem as k, diatonicLetterOf as l, formatKeyName as m, formatNote as n, intervalSemitones as o, isLibcantusError as p, midiToNote as q, naturalPitchClassOf as r, noteToMidi as s, noteToPitchClass as t, parseInterval as u, parseKeyName as v, parseNote as w, pitchClassOf as x, spelledInterval as y, toNoteData as z };