@libraz/libcantus 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +6 -5
  2. package/dist/analyze/index.cjs +41 -1676
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +194 -122
  5. package/dist/analyze/index.d.ts +194 -122
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/chunk-HKGYCQ34.js +53 -0
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/chunk-I24MV65U.js +106 -0
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/chunk-N6IHQUOX.js +1 -0
  36. package/dist/chunk-N6IHQUOX.js.map +1 -0
  37. package/dist/chunk-NKIBQ6PT.js +30 -0
  38. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  39. package/dist/chunk-NP4NA2FB.cjs +106 -0
  40. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  41. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  42. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  43. package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
  44. package/dist/chunk-NYYWMV2C.js.map +1 -0
  45. package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
  46. package/dist/chunk-Q2XUP227.js.map +1 -0
  47. package/dist/chunk-Q6OP4U43.cjs +327 -0
  48. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  49. package/dist/chunk-RC6GBMCD.cjs +845 -0
  50. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  51. package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
  52. package/dist/chunk-RK35RIN7.js.map +1 -0
  53. package/dist/chunk-RUMU3UA6.cjs +53 -0
  54. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  55. package/dist/chunk-T64C7ZD7.cjs +1 -0
  56. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  57. package/dist/chunk-XEBG3G53.js +498 -0
  58. package/dist/chunk-XEBG3G53.js.map +1 -0
  59. package/dist/chunk-YLEMGEFH.js +246 -0
  60. package/dist/chunk-YLEMGEFH.js.map +1 -0
  61. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  62. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  63. package/dist/core/index.cjs +130 -431
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +331 -17
  66. package/dist/core/index.d.ts +331 -17
  67. package/dist/core/index.js +71 -13
  68. package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
  69. package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
  70. package/dist/generate/index.cjs +30 -3751
  71. package/dist/generate/index.cjs.map +1 -1
  72. package/dist/generate/index.d.cts +373 -59
  73. package/dist/generate/index.d.ts +373 -59
  74. package/dist/generate/index.js +23 -9
  75. package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
  76. package/dist/index-70p168VM.d.ts +192 -0
  77. package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
  78. package/dist/index-BtXT6UQ0.d.cts +192 -0
  79. package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  80. package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
  81. package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
  82. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  83. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  84. package/dist/index-OG65ocOr.d.cts +272 -0
  85. package/dist/index-OG65ocOr.d.ts +272 -0
  86. package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
  87. package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
  88. package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
  89. package/dist/index.cjs +419 -6848
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.cts +14 -13
  92. package/dist/index.d.ts +14 -13
  93. package/dist/index.js +132 -27
  94. package/dist/model/index.cjs +8 -2423
  95. package/dist/model/index.cjs.map +1 -1
  96. package/dist/model/index.d.cts +433 -74
  97. package/dist/model/index.d.ts +433 -74
  98. package/dist/model/index.js +6 -7
  99. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  100. package/dist/styled-DJbkJQfj.d.cts +506 -0
  101. package/dist/theory/index.cjs +172 -1676
  102. package/dist/theory/index.cjs.map +1 -1
  103. package/dist/theory/index.d.cts +151 -233
  104. package/dist/theory/index.d.ts +151 -233
  105. package/dist/theory/index.js +31 -7
  106. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  107. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  108. package/package.json +73 -24
  109. package/dist/chunk-5HTNMQW4.js.map +0 -1
  110. package/dist/chunk-7Y7WF64D.js.map +0 -1
  111. package/dist/chunk-A4532ZFR.js.map +0 -1
  112. package/dist/chunk-BHUT2S5G.js +0 -22
  113. package/dist/chunk-BHUT2S5G.js.map +0 -1
  114. package/dist/chunk-CCBLVAPK.js +0 -42
  115. package/dist/chunk-CCBLVAPK.js.map +0 -1
  116. package/dist/chunk-COKTX2G5.js.map +0 -1
  117. package/dist/chunk-DYWFNC4A.js +0 -152
  118. package/dist/chunk-DYWFNC4A.js.map +0 -1
  119. package/dist/chunk-GQKN5KDT.js.map +0 -1
  120. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  121. package/dist/chunk-MSWFZF7J.js +0 -118
  122. package/dist/chunk-MSWFZF7J.js.map +0 -1
  123. package/dist/chunk-R3S2LUBH.js.map +0 -1
  124. package/dist/chunk-SDM3VJJ5.js +0 -41
  125. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  126. package/dist/chunk-XTC3X6MS.js.map +0 -1
  127. package/dist/chunk-ZXW5YIWK.js +0 -55
  128. package/dist/chunk-ZXW5YIWK.js.map +0 -1
  129. package/dist/index-CGMZNCx6.d.cts +0 -140
  130. package/dist/index-CGMZNCx6.d.ts +0 -140
  131. package/dist/styled-BjXtOFNk.d.cts +0 -282
  132. package/dist/styled-D7BAx3S3.d.ts +0 -282
  133. package/src/analyze/arrange/index.ts +0 -21
  134. package/src/analyze/arrange/internal.ts +0 -125
  135. package/src/analyze/arrange/tension.ts +0 -187
  136. package/src/analyze/arrange/tracks.ts +0 -279
  137. package/src/analyze/detect/index.ts +0 -235
  138. package/src/analyze/functional/borrowed.ts +0 -84
  139. package/src/analyze/functional/cadence.ts +0 -66
  140. package/src/analyze/functional/function.ts +0 -184
  141. package/src/analyze/functional/index.ts +0 -23
  142. package/src/analyze/functional/internal.ts +0 -29
  143. package/src/analyze/functional/roman.ts +0 -393
  144. package/src/analyze/index.ts +0 -49
  145. package/src/analyze/timeline/index.ts +0 -415
  146. package/src/analyze/voice/index.ts +0 -330
  147. package/src/core/index.ts +0 -47
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -282
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -53
  152. package/src/core/tuning/index.ts +0 -156
  153. package/src/core/types.ts +0 -29
  154. package/src/generate/bass/index.ts +0 -350
  155. package/src/generate/countermelody/index.ts +0 -379
  156. package/src/generate/drums/beat.ts +0 -266
  157. package/src/generate/drums/euclid.ts +0 -58
  158. package/src/generate/drums/fills.ts +0 -356
  159. package/src/generate/drums/ghost.ts +0 -119
  160. package/src/generate/drums/hihat.ts +0 -285
  161. package/src/generate/drums/hit.ts +0 -31
  162. package/src/generate/drums/index.ts +0 -329
  163. package/src/generate/drums/internal.ts +0 -257
  164. package/src/generate/drums/kick.ts +0 -160
  165. package/src/generate/drums/percussion.ts +0 -121
  166. package/src/generate/drums/rng.ts +0 -7
  167. package/src/generate/drums/swing.ts +0 -71
  168. package/src/generate/groove/index.ts +0 -326
  169. package/src/generate/harmonize/index.ts +0 -440
  170. package/src/generate/index.ts +0 -59
  171. package/src/generate/motif/index.ts +0 -398
  172. package/src/generate/progression/index.ts +0 -370
  173. package/src/generate/reharmony/index.ts +0 -330
  174. package/src/generate/rhythm/index.ts +0 -165
  175. package/src/index.ts +0 -247
  176. package/src/model/chord.ts +0 -423
  177. package/src/model/index.ts +0 -15
  178. package/src/model/interval.ts +0 -82
  179. package/src/model/key.ts +0 -266
  180. package/src/model/note.ts +0 -176
  181. package/src/model/progression.ts +0 -161
  182. package/src/model/shared.ts +0 -12
  183. package/src/theory/chord/index.ts +0 -373
  184. package/src/theory/chordscale/index.ts +0 -374
  185. package/src/theory/counterpoint/index.ts +0 -230
  186. package/src/theory/harmony/index.ts +0 -92
  187. package/src/theory/index.ts +0 -107
  188. package/src/theory/safety/index.ts +0 -419
  189. package/src/theory/scale/degrees.ts +0 -123
  190. package/src/theory/scale/index.ts +0 -28
  191. package/src/theory/scale/key.ts +0 -56
  192. package/src/theory/scale/masks.ts +0 -153
  193. package/src/theory/spelling/index.ts +0 -182
  194. package/src/theory/symbol/index.ts +0 -268
  195. package/src/theory/voicing/index.ts +0 -5
  196. package/src/theory/voicing/internal.ts +0 -168
  197. package/src/theory/voicing/leading.ts +0 -112
  198. package/src/theory/voicing/satb.ts +0 -195
  199. package/src/theory/voicing/styled.ts +0 -176
  200. package/tsconfig.json +0 -23
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
3
+ * intervals.
4
+ *
5
+ * The rest of the library works in pitch classes (0..11), which cannot tell a
6
+ * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
7
+ * an explicit spelling layer — a note is a diatonic letter plus a chromatic
8
+ * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
9
+ * preserved.
10
+ */
11
+ /**
12
+ * A spelled note: a diatonic letter, a chromatic alteration, and an optional
13
+ * octave.
14
+ *
15
+ * - `letter` is 0..6 for C..B.
16
+ * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
17
+ * 0 natural, +1 sharp, +2 double-sharp.
18
+ * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
19
+ * absent, the note denotes a bare pitch class.
20
+ *
21
+ * @category Pitch & Intervals
22
+ */
23
+ type Note = {
24
+ letter: number;
25
+ alter: number;
26
+ octave?: number;
27
+ };
28
+ /**
29
+ * The quality label of a spelled interval: perfect, major, minor, or one or
30
+ * more augmentations or diminutions.
31
+ *
32
+ * This names how an interval is *spelled*. It is a different notion from
33
+ * {@link ConsonanceClass}, which classifies how an interval *sounds* against
34
+ * another voice.
35
+ *
36
+ * @see {@link ConsonanceClass}
37
+ * @category Pitch & Intervals
38
+ */
39
+ type IntervalQualityLabel = 'P' | 'M' | 'm' | `A${string}` | `d${string}`;
40
+ /**
41
+ * A spelled interval: a diatonic number, a quality label, and its semitone span.
42
+ *
43
+ * @category Pitch & Intervals
44
+ */
45
+ type SpelledInterval = {
46
+ /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
47
+ number: number;
48
+ /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
49
+ quality: IntervalQualityLabel;
50
+ /** Signed semitone distance from the first note to the second. */
51
+ semitones: number;
52
+ };
53
+ /**
54
+ * Reduce a MIDI pitch, a bare pitch class, or any signed offset to a pitch
55
+ * class in [0, 11].
56
+ *
57
+ * This is the library's single definition of pitch-class arithmetic: every
58
+ * layer routes through it so a fractional pitch reduces the same way
59
+ * everywhere. A non-integral input is rounded to the nearest semitone first, so
60
+ * a microtonal 60.6 reads as C# rather than C.
61
+ *
62
+ * @param value A MIDI pitch, pitch class, or signed semitone offset.
63
+ * @returns The pitch class in [0, 11].
64
+ * @example
65
+ * ```ts
66
+ * import { pitchClassOf } from '@libraz/libcantus';
67
+ * pitchClassOf(61); // 1
68
+ * pitchClassOf(-1); // 11
69
+ * pitchClassOf(60.6); // 1
70
+ * ```
71
+ * @category Pitch & Intervals
72
+ */
73
+ declare function pitchClassOf(value: number): number;
74
+ /**
75
+ * Reduce a letter number or letter offset to a diatonic letter in [0, 6]
76
+ * (0 = C .. 6 = B).
77
+ *
78
+ * @param value A letter number or signed letter offset.
79
+ * @returns The diatonic letter in [0, 6].
80
+ * @category Pitch & Intervals
81
+ */
82
+ declare function diatonicLetterOf(value: number): number;
83
+ /**
84
+ * The pitch class of a diatonic letter with no accidental (C = 0, D = 2, ...).
85
+ *
86
+ * @param letter A letter number or signed letter offset; reduced to [0, 6].
87
+ * @returns The natural pitch class of that letter.
88
+ * @category Pitch & Intervals
89
+ */
90
+ declare function naturalPitchClassOf(letter: number): number;
91
+ /**
92
+ * Parse scientific pitch notation into a {@link Note}.
93
+ *
94
+ * Accepts a letter (case-insensitive) followed by any number of same-direction
95
+ * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
96
+ * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
97
+ * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
98
+ *
99
+ * @param text The note text.
100
+ * @returns The parsed note.
101
+ * @throws If the text is not a valid note.
102
+ * @example
103
+ * ```ts
104
+ * import { parseNote, noteToMidi } from '@libraz/libcantus';
105
+ * const n = parseNote('C#4');
106
+ * noteToMidi(n); // 61
107
+ * ```
108
+ * @category Pitch & Intervals
109
+ */
110
+ declare function parseNote(text: string): Note;
111
+ /**
112
+ * Render a {@link Note} as scientific pitch notation.
113
+ *
114
+ * @param note The note to format.
115
+ * @returns The note text, including the octave when present.
116
+ * @example
117
+ * ```ts
118
+ * import { formatNote, parseNote } from '@libraz/libcantus';
119
+ * formatNote(parseNote('C#4')); // 'C#4'
120
+ * ```
121
+ * @category Pitch & Intervals
122
+ */
123
+ declare function formatNote(note: Note): string;
124
+ /**
125
+ * Pitch class (0..11) of a note, ignoring octave.
126
+ *
127
+ * @param note The note.
128
+ * @returns The pitch class.
129
+ * @example
130
+ * ```ts
131
+ * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
132
+ * noteToPitchClass(parseNote('Db')); // 1
133
+ * ```
134
+ * @category Pitch & Intervals
135
+ */
136
+ declare function noteToPitchClass(note: Note): number;
137
+ /**
138
+ * MIDI number of a note (middle C = C4 = 60).
139
+ *
140
+ * @param note The note; must carry an octave.
141
+ * @returns The MIDI number.
142
+ * @throws If the note has no octave.
143
+ * @example
144
+ * ```ts
145
+ * import { noteToMidi, parseNote } from '@libraz/libcantus';
146
+ * noteToMidi(parseNote('A4')); // 69
147
+ * ```
148
+ * @category Pitch & Intervals
149
+ */
150
+ declare function noteToMidi(note: Note): number;
151
+ /**
152
+ * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
153
+ *
154
+ * The input is rounded to the nearest integer but is not clamped to the MIDI
155
+ * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
156
+ * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
157
+ *
158
+ * @param midi The MIDI number.
159
+ * @param spelling Whether to prefer sharps or flats for black keys.
160
+ * @returns The spelled note, with octave.
161
+ * @example
162
+ * ```ts
163
+ * import { midiToNote, formatNote } from '@libraz/libcantus';
164
+ * formatNote(midiToNote(61)); // 'C#4'
165
+ * formatNote(midiToNote(61, 'flat')); // 'Db4'
166
+ * ```
167
+ * @category Pitch & Intervals
168
+ */
169
+ declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
170
+ /**
171
+ * Transpose a note by a signed number of semitones, keeping its spelling.
172
+ *
173
+ * The letter moves by the diatonic distance of the conventional interval for
174
+ * that many semitones and the accidental is recomputed for the new letter, so
175
+ * enharmonic identity survives: `Ab4` up a major second is `Bb4`, not `A#4`, and
176
+ * the operation is an exact inverse of itself. An octave-less note stays
177
+ * octave-less.
178
+ *
179
+ * A bare semitone count cannot distinguish an augmented fourth from a diminished
180
+ * fifth; pass `spelling` to override the letter distance when the enharmonic
181
+ * choice matters.
182
+ *
183
+ * @param note The note to transpose.
184
+ * @param semitones The signed semitone offset.
185
+ * @param opts `spelling` forces the result onto the sharp or flat side instead
186
+ * of following the source note's letter.
187
+ * @returns The transposed note.
188
+ * @example
189
+ * ```ts
190
+ * import { transposeNote, formatNote, parseNote } from '@libraz/libcantus';
191
+ * formatNote(transposeNote(parseNote('Ab4'), 2)); // 'Bb4'
192
+ * formatNote(transposeNote(parseNote('Ab4'), 2, { spelling: 'sharp' })); // 'A#4'
193
+ * ```
194
+ * @category Pitch & Intervals
195
+ */
196
+ declare function transposeNote(note: Note, semitones: number, opts?: {
197
+ spelling?: 'sharp' | 'flat';
198
+ }): Note;
199
+ /**
200
+ * The semitone span of the interval named by a diatonic number and a quality.
201
+ *
202
+ * The inverse of the quality derivation in {@link spelledInterval}: it turns a
203
+ * name back into a distance, which is what transposing by a named interval or
204
+ * parsing one from text needs.
205
+ *
206
+ * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.
207
+ * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.
208
+ * @returns The unsigned semitone span.
209
+ * @throws If the quality cannot apply to the number, such as a major fifth.
210
+ * @example
211
+ * ```ts
212
+ * import { intervalSemitones } from '@libraz/libcantus';
213
+ * intervalSemitones(5, 'P'); // 7
214
+ * ```
215
+ * @category Pitch & Intervals
216
+ */
217
+ declare function intervalSemitones(numberValue: number, quality: IntervalQualityLabel): number;
218
+ /**
219
+ * Parse an interval name such as `'P5'`, `'m3'`, or `'AA4'`.
220
+ *
221
+ * @param name The interval name: a quality label followed by a diatonic number.
222
+ * @returns The spelled interval, with an ascending (positive) span.
223
+ * @throws If the name is not a quality label followed by a number, or the two
224
+ * cannot describe the same interval.
225
+ * @example
226
+ * ```ts
227
+ * import { parseInterval } from '@libraz/libcantus';
228
+ * parseInterval('m3'); // { number: 3, quality: 'm', semitones: 3 }
229
+ * ```
230
+ * @category Pitch & Intervals
231
+ */
232
+ declare function parseInterval(name: string): SpelledInterval;
233
+ /**
234
+ * Transpose a note by a spelled interval, keeping the spelling the interval
235
+ * names.
236
+ *
237
+ * Unlike {@link transposeNote}, which picks a letter from the semitone count,
238
+ * the diatonic number decides the letter here: transposing C by an augmented
239
+ * second gives D#, not Eb.
240
+ *
241
+ * @param note The note to transpose.
242
+ * @param interval The interval to apply; a negative span transposes downward.
243
+ * @returns The transposed note, octave-less if the source was.
244
+ * @example
245
+ * ```ts
246
+ * import { formatNote, parseInterval, parseNote, transposeByInterval } from '@libraz/libcantus';
247
+ * formatNote(transposeByInterval(parseNote('C4'), parseInterval('A2'))); // 'D#4'
248
+ * ```
249
+ * @category Pitch & Intervals
250
+ */
251
+ declare function transposeByInterval(note: Note, interval: SpelledInterval): Note;
252
+ /**
253
+ * The spelled interval from one note to another.
254
+ *
255
+ * When both notes carry octaves, the interval reflects their full signed
256
+ * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
257
+ * interval is measured within a single ascending octave from `a` to `b`.
258
+ *
259
+ * @param a The lower/first note.
260
+ * @param b The upper/second note.
261
+ * @returns The diatonic number, quality, and semitone span.
262
+ * @example
263
+ * ```ts
264
+ * import { spelledInterval, parseNote } from '@libraz/libcantus';
265
+ * spelledInterval(parseNote('C4'), parseNote('G4'));
266
+ * // { number: 5, quality: 'P', semitones: 7 }
267
+ * ```
268
+ * @category Pitch & Intervals
269
+ */
270
+ declare function spelledInterval(a: Note, b: Note): SpelledInterval;
271
+
272
+ export { type IntervalQualityLabel as I, type Note as N, type SpelledInterval as S, noteToMidi as a, noteToPitchClass as b, parseNote as c, diatonicLetterOf as d, pitchClassOf as e, formatNote as f, transposeNote as g, intervalSemitones as i, midiToNote as m, naturalPitchClassOf as n, parseInterval as p, spelledInterval as s, transposeByInterval as t };
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Pitch spelling: letter-name notes, enharmonic-aware conversion, and spelled
3
+ * intervals.
4
+ *
5
+ * The rest of the library works in pitch classes (0..11), which cannot tell a
6
+ * G# from an Ab or an augmented fourth from a diminished fifth. This module adds
7
+ * an explicit spelling layer — a note is a diatonic letter plus a chromatic
8
+ * alteration — so interval quality (P/M/m/A/d) and enharmonic identity are
9
+ * preserved.
10
+ */
11
+ /**
12
+ * A spelled note: a diatonic letter, a chromatic alteration, and an optional
13
+ * octave.
14
+ *
15
+ * - `letter` is 0..6 for C..B.
16
+ * - `alter` is the number of semitones of alteration: -2 double-flat, -1 flat,
17
+ * 0 natural, +1 sharp, +2 double-sharp.
18
+ * - `octave` follows scientific pitch notation (middle C = C4 = MIDI 60). When
19
+ * absent, the note denotes a bare pitch class.
20
+ *
21
+ * @category Pitch & Intervals
22
+ */
23
+ type Note = {
24
+ letter: number;
25
+ alter: number;
26
+ octave?: number;
27
+ };
28
+ /**
29
+ * The quality label of a spelled interval: perfect, major, minor, or one or
30
+ * more augmentations or diminutions.
31
+ *
32
+ * This names how an interval is *spelled*. It is a different notion from
33
+ * {@link ConsonanceClass}, which classifies how an interval *sounds* against
34
+ * another voice.
35
+ *
36
+ * @see {@link ConsonanceClass}
37
+ * @category Pitch & Intervals
38
+ */
39
+ type IntervalQualityLabel = 'P' | 'M' | 'm' | `A${string}` | `d${string}`;
40
+ /**
41
+ * A spelled interval: a diatonic number, a quality label, and its semitone span.
42
+ *
43
+ * @category Pitch & Intervals
44
+ */
45
+ type SpelledInterval = {
46
+ /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
47
+ number: number;
48
+ /** Quality label: 'P', 'M', 'm', or repeated 'A'/'d' for (multiply) aug/dim. */
49
+ quality: IntervalQualityLabel;
50
+ /** Signed semitone distance from the first note to the second. */
51
+ semitones: number;
52
+ };
53
+ /**
54
+ * Reduce a MIDI pitch, a bare pitch class, or any signed offset to a pitch
55
+ * class in [0, 11].
56
+ *
57
+ * This is the library's single definition of pitch-class arithmetic: every
58
+ * layer routes through it so a fractional pitch reduces the same way
59
+ * everywhere. A non-integral input is rounded to the nearest semitone first, so
60
+ * a microtonal 60.6 reads as C# rather than C.
61
+ *
62
+ * @param value A MIDI pitch, pitch class, or signed semitone offset.
63
+ * @returns The pitch class in [0, 11].
64
+ * @example
65
+ * ```ts
66
+ * import { pitchClassOf } from '@libraz/libcantus';
67
+ * pitchClassOf(61); // 1
68
+ * pitchClassOf(-1); // 11
69
+ * pitchClassOf(60.6); // 1
70
+ * ```
71
+ * @category Pitch & Intervals
72
+ */
73
+ declare function pitchClassOf(value: number): number;
74
+ /**
75
+ * Reduce a letter number or letter offset to a diatonic letter in [0, 6]
76
+ * (0 = C .. 6 = B).
77
+ *
78
+ * @param value A letter number or signed letter offset.
79
+ * @returns The diatonic letter in [0, 6].
80
+ * @category Pitch & Intervals
81
+ */
82
+ declare function diatonicLetterOf(value: number): number;
83
+ /**
84
+ * The pitch class of a diatonic letter with no accidental (C = 0, D = 2, ...).
85
+ *
86
+ * @param letter A letter number or signed letter offset; reduced to [0, 6].
87
+ * @returns The natural pitch class of that letter.
88
+ * @category Pitch & Intervals
89
+ */
90
+ declare function naturalPitchClassOf(letter: number): number;
91
+ /**
92
+ * Parse scientific pitch notation into a {@link Note}.
93
+ *
94
+ * Accepts a letter (case-insensitive) followed by any number of same-direction
95
+ * accidentals and an optional octave, e.g. `"C"`, `"C#4"`, `"Bb"`, `"F##3"`,
96
+ * `"Ebb2"`. Both `#` and `x` (double-sharp) are accepted for sharps; `b` for
97
+ * flats. Mixing sharps and flats (e.g. `"C#b"`) is rejected.
98
+ *
99
+ * @param text The note text.
100
+ * @returns The parsed note.
101
+ * @throws If the text is not a valid note.
102
+ * @example
103
+ * ```ts
104
+ * import { parseNote, noteToMidi } from '@libraz/libcantus';
105
+ * const n = parseNote('C#4');
106
+ * noteToMidi(n); // 61
107
+ * ```
108
+ * @category Pitch & Intervals
109
+ */
110
+ declare function parseNote(text: string): Note;
111
+ /**
112
+ * Render a {@link Note} as scientific pitch notation.
113
+ *
114
+ * @param note The note to format.
115
+ * @returns The note text, including the octave when present.
116
+ * @example
117
+ * ```ts
118
+ * import { formatNote, parseNote } from '@libraz/libcantus';
119
+ * formatNote(parseNote('C#4')); // 'C#4'
120
+ * ```
121
+ * @category Pitch & Intervals
122
+ */
123
+ declare function formatNote(note: Note): string;
124
+ /**
125
+ * Pitch class (0..11) of a note, ignoring octave.
126
+ *
127
+ * @param note The note.
128
+ * @returns The pitch class.
129
+ * @example
130
+ * ```ts
131
+ * import { noteToPitchClass, parseNote } from '@libraz/libcantus';
132
+ * noteToPitchClass(parseNote('Db')); // 1
133
+ * ```
134
+ * @category Pitch & Intervals
135
+ */
136
+ declare function noteToPitchClass(note: Note): number;
137
+ /**
138
+ * MIDI number of a note (middle C = C4 = 60).
139
+ *
140
+ * @param note The note; must carry an octave.
141
+ * @returns The MIDI number.
142
+ * @throws If the note has no octave.
143
+ * @example
144
+ * ```ts
145
+ * import { noteToMidi, parseNote } from '@libraz/libcantus';
146
+ * noteToMidi(parseNote('A4')); // 69
147
+ * ```
148
+ * @category Pitch & Intervals
149
+ */
150
+ declare function noteToMidi(note: Note): number;
151
+ /**
152
+ * Name a MIDI number as a {@link Note}, choosing sharp or flat spelling.
153
+ *
154
+ * The input is rounded to the nearest integer but is not clamped to the MIDI
155
+ * range 0..127: out-of-range values extrapolate linearly (e.g. `-1` -> `B-2`,
156
+ * `128` -> `G#9`) and remain an exact inverse of {@link noteToMidi}.
157
+ *
158
+ * @param midi The MIDI number.
159
+ * @param spelling Whether to prefer sharps or flats for black keys.
160
+ * @returns The spelled note, with octave.
161
+ * @example
162
+ * ```ts
163
+ * import { midiToNote, formatNote } from '@libraz/libcantus';
164
+ * formatNote(midiToNote(61)); // 'C#4'
165
+ * formatNote(midiToNote(61, 'flat')); // 'Db4'
166
+ * ```
167
+ * @category Pitch & Intervals
168
+ */
169
+ declare function midiToNote(midi: number, spelling?: 'sharp' | 'flat'): Note;
170
+ /**
171
+ * Transpose a note by a signed number of semitones, keeping its spelling.
172
+ *
173
+ * The letter moves by the diatonic distance of the conventional interval for
174
+ * that many semitones and the accidental is recomputed for the new letter, so
175
+ * enharmonic identity survives: `Ab4` up a major second is `Bb4`, not `A#4`, and
176
+ * the operation is an exact inverse of itself. An octave-less note stays
177
+ * octave-less.
178
+ *
179
+ * A bare semitone count cannot distinguish an augmented fourth from a diminished
180
+ * fifth; pass `spelling` to override the letter distance when the enharmonic
181
+ * choice matters.
182
+ *
183
+ * @param note The note to transpose.
184
+ * @param semitones The signed semitone offset.
185
+ * @param opts `spelling` forces the result onto the sharp or flat side instead
186
+ * of following the source note's letter.
187
+ * @returns The transposed note.
188
+ * @example
189
+ * ```ts
190
+ * import { transposeNote, formatNote, parseNote } from '@libraz/libcantus';
191
+ * formatNote(transposeNote(parseNote('Ab4'), 2)); // 'Bb4'
192
+ * formatNote(transposeNote(parseNote('Ab4'), 2, { spelling: 'sharp' })); // 'A#4'
193
+ * ```
194
+ * @category Pitch & Intervals
195
+ */
196
+ declare function transposeNote(note: Note, semitones: number, opts?: {
197
+ spelling?: 'sharp' | 'flat';
198
+ }): Note;
199
+ /**
200
+ * The semitone span of the interval named by a diatonic number and a quality.
201
+ *
202
+ * The inverse of the quality derivation in {@link spelledInterval}: it turns a
203
+ * name back into a distance, which is what transposing by a named interval or
204
+ * parsing one from text needs.
205
+ *
206
+ * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.
207
+ * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.
208
+ * @returns The unsigned semitone span.
209
+ * @throws If the quality cannot apply to the number, such as a major fifth.
210
+ * @example
211
+ * ```ts
212
+ * import { intervalSemitones } from '@libraz/libcantus';
213
+ * intervalSemitones(5, 'P'); // 7
214
+ * ```
215
+ * @category Pitch & Intervals
216
+ */
217
+ declare function intervalSemitones(numberValue: number, quality: IntervalQualityLabel): number;
218
+ /**
219
+ * Parse an interval name such as `'P5'`, `'m3'`, or `'AA4'`.
220
+ *
221
+ * @param name The interval name: a quality label followed by a diatonic number.
222
+ * @returns The spelled interval, with an ascending (positive) span.
223
+ * @throws If the name is not a quality label followed by a number, or the two
224
+ * cannot describe the same interval.
225
+ * @example
226
+ * ```ts
227
+ * import { parseInterval } from '@libraz/libcantus';
228
+ * parseInterval('m3'); // { number: 3, quality: 'm', semitones: 3 }
229
+ * ```
230
+ * @category Pitch & Intervals
231
+ */
232
+ declare function parseInterval(name: string): SpelledInterval;
233
+ /**
234
+ * Transpose a note by a spelled interval, keeping the spelling the interval
235
+ * names.
236
+ *
237
+ * Unlike {@link transposeNote}, which picks a letter from the semitone count,
238
+ * the diatonic number decides the letter here: transposing C by an augmented
239
+ * second gives D#, not Eb.
240
+ *
241
+ * @param note The note to transpose.
242
+ * @param interval The interval to apply; a negative span transposes downward.
243
+ * @returns The transposed note, octave-less if the source was.
244
+ * @example
245
+ * ```ts
246
+ * import { formatNote, parseInterval, parseNote, transposeByInterval } from '@libraz/libcantus';
247
+ * formatNote(transposeByInterval(parseNote('C4'), parseInterval('A2'))); // 'D#4'
248
+ * ```
249
+ * @category Pitch & Intervals
250
+ */
251
+ declare function transposeByInterval(note: Note, interval: SpelledInterval): Note;
252
+ /**
253
+ * The spelled interval from one note to another.
254
+ *
255
+ * When both notes carry octaves, the interval reflects their full signed
256
+ * distance (so `C4 -> C5` is a perfect octave). When either lacks an octave the
257
+ * interval is measured within a single ascending octave from `a` to `b`.
258
+ *
259
+ * @param a The lower/first note.
260
+ * @param b The upper/second note.
261
+ * @returns The diatonic number, quality, and semitone span.
262
+ * @example
263
+ * ```ts
264
+ * import { spelledInterval, parseNote } from '@libraz/libcantus';
265
+ * spelledInterval(parseNote('C4'), parseNote('G4'));
266
+ * // { number: 5, quality: 'P', semitones: 7 }
267
+ * ```
268
+ * @category Pitch & Intervals
269
+ */
270
+ declare function spelledInterval(a: Note, b: Note): SpelledInterval;
271
+
272
+ export { type IntervalQualityLabel as I, type Note as N, type SpelledInterval as S, noteToMidi as a, noteToPitchClass as b, parseNote as c, diatonicLetterOf as d, pitchClassOf as e, formatNote as f, transposeNote as g, intervalSemitones as i, midiToNote as m, naturalPitchClassOf as n, parseInterval as p, spelledInterval as s, transposeByInterval as t };
@@ -1,5 +1,5 @@
1
- import { K as KeyScale } from './types-MGP29r3t.js';
2
- import { C as Chord } from './index-CM5W4rcm.js';
1
+ import { K as KeyScale } from './types-BN6_Q9Kl.cjs';
2
+ import { C as Chord } from './index-y_ak9_Hi.cjs';
3
3
 
4
4
  /**
5
5
  * Severity policy: `strict` counterpoint vs. lenient `pop` voice-leading.
@@ -102,7 +102,22 @@ type SafetyResult = {
102
102
  * ```
103
103
  * @category Arrangement & Analysis
104
104
  */
105
- declare function evaluateSafety(q: SafetyQuery): SafetyResult;
105
+ declare function evaluateSafety(q: SafetyQuery, opts?: EvaluateSafetyOptions): SafetyResult;
106
+ /**
107
+ * Options for {@link evaluateSafety}.
108
+ *
109
+ * @category Arrangement & Analysis
110
+ */
111
+ type EvaluateSafetyOptions = {
112
+ /**
113
+ * Whether to search for safe replacement pitches when the verdict is not
114
+ * safe. Turning it off skips up to two dozen extra evaluations per rejected
115
+ * pitch, which is worth doing when only the verdict is wanted.
116
+ *
117
+ * @defaultValue true
118
+ */
119
+ suggestions?: boolean;
120
+ };
106
121
  /**
107
122
  * Enumerate placeable pitches in a range, chord tones first, descending.
108
123
  *
@@ -110,6 +125,7 @@ declare function evaluateSafety(q: SafetyQuery): SafetyResult;
110
125
  * @param pitchLow Lowest MIDI pitch to consider (inclusive).
111
126
  * @param pitchHigh Highest MIDI pitch to consider (inclusive).
112
127
  * @returns Placeable pitches (non-dissonant), chord tones before others, each group descending.
128
+ * @throws If either bound is non-finite/non-integral, reversed, or exceeds the generation budget.
113
129
  * @example
114
130
  * ```ts
115
131
  * import { enumerateSafePitches, makeChord, majorKey } from '@libraz/libcantus';
@@ -124,4 +140,4 @@ declare function evaluateSafety(q: SafetyQuery): SafetyResult;
124
140
  */
125
141
  declare function enumerateSafePitches(q: Omit<SafetyQuery, 'candidatePitch'>, pitchLow: number, pitchHigh: number): number[];
126
142
 
127
- export { NoteSafety as N, ReasonFlag as R, type SafetyProfile as S, type VoiceSnapshot as V, type SafetyQuery as a, type SafetyResult as b, evaluateSafety as c, enumerateSafePitches as e };
143
+ export { type EvaluateSafetyOptions as E, NoteSafety as N, ReasonFlag as R, type SafetyProfile as S, type VoiceSnapshot as V, type SafetyQuery as a, type SafetyResult as b, evaluateSafety as c, enumerateSafePitches as e };
@@ -1,11 +1,11 @@
1
- import { C as Chord } from './index-CM5W4rcm.js';
1
+ import { d as ChordToneRole, C as Chord } from './index-C83mZI1P.js';
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.