@libraz/libcantus 0.9.5 → 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 (185) hide show
  1. package/README.md +61 -270
  2. package/README_ja.md +61 -214
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +200 -353
  6. package/dist/analyze/index.d.ts +200 -353
  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-2PTFGVD7.js → chunk-ELCTZUJJ.js} +250 -132
  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 +93 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +87 -346
  66. package/dist/core/index.d.ts +87 -346
  67. package/dist/core/index.js +94 -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 +436 -1094
  71. package/dist/generate/index.d.ts +436 -1094
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-Cfx8xgH0.d.ts → 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-x2vcsduE.d.cts → 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 +348 -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 +397 -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 +4659 -249
  103. package/dist/model/index.d.ts +4659 -249
  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 +511 -171
  110. package/dist/theory/index.d.ts +511 -171
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +6 -4
  113. package/dist/chunk-2A2QMA74.cjs +0 -261
  114. package/dist/chunk-2A2QMA74.cjs.map +0 -1
  115. package/dist/chunk-2PTFGVD7.js.map +0 -1
  116. package/dist/chunk-357ZDPVU.cjs +0 -162
  117. package/dist/chunk-357ZDPVU.cjs.map +0 -1
  118. package/dist/chunk-3KX76RLG.js +0 -1548
  119. package/dist/chunk-3KX76RLG.js.map +0 -1
  120. package/dist/chunk-4VZ5MZAM.cjs +0 -889
  121. package/dist/chunk-4VZ5MZAM.cjs.map +0 -1
  122. package/dist/chunk-7EONYVI5.js +0 -1204
  123. package/dist/chunk-7EONYVI5.js.map +0 -1
  124. package/dist/chunk-7JEP3MFY.js +0 -162
  125. package/dist/chunk-7JEP3MFY.js.map +0 -1
  126. package/dist/chunk-AID75RN3.js +0 -3176
  127. package/dist/chunk-AID75RN3.js.map +0 -1
  128. package/dist/chunk-BSLTLGM3.cjs +0 -497
  129. package/dist/chunk-BSLTLGM3.cjs.map +0 -1
  130. package/dist/chunk-CU6VG2YM.cjs +0 -109
  131. package/dist/chunk-CU6VG2YM.cjs.map +0 -1
  132. package/dist/chunk-DZMHIGI4.js +0 -337
  133. package/dist/chunk-DZMHIGI4.js.map +0 -1
  134. package/dist/chunk-EDOTEABK.js +0 -109
  135. package/dist/chunk-EDOTEABK.js.map +0 -1
  136. package/dist/chunk-HN2OEUN2.js +0 -33
  137. package/dist/chunk-HN2OEUN2.js.map +0 -1
  138. package/dist/chunk-KBTTEX7Q.js +0 -497
  139. package/dist/chunk-KBTTEX7Q.js.map +0 -1
  140. package/dist/chunk-KQN2ZYND.cjs +0 -724
  141. package/dist/chunk-KQN2ZYND.cjs.map +0 -1
  142. package/dist/chunk-MPTVJIVG.cjs +0 -3176
  143. package/dist/chunk-MPTVJIVG.cjs.map +0 -1
  144. package/dist/chunk-MTMZTMWM.cjs +0 -53
  145. package/dist/chunk-MTMZTMWM.cjs.map +0 -1
  146. package/dist/chunk-N6IHQUOX.js +0 -1
  147. package/dist/chunk-NNB67XA2.js +0 -724
  148. package/dist/chunk-NNB67XA2.js.map +0 -1
  149. package/dist/chunk-OL3IHHSJ.cjs +0 -1204
  150. package/dist/chunk-OL3IHHSJ.cjs.map +0 -1
  151. package/dist/chunk-PI3UXRWK.cjs +0 -528
  152. package/dist/chunk-PI3UXRWK.cjs.map +0 -1
  153. package/dist/chunk-R2VVFAIN.js +0 -528
  154. package/dist/chunk-R2VVFAIN.js.map +0 -1
  155. package/dist/chunk-T64C7ZD7.cjs +0 -1
  156. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  157. package/dist/chunk-WFDXOLUB.cjs +0 -337
  158. package/dist/chunk-WFDXOLUB.cjs.map +0 -1
  159. package/dist/chunk-WTAN4ACG.cjs +0 -1548
  160. package/dist/chunk-WTAN4ACG.cjs.map +0 -1
  161. package/dist/chunk-YFJXS3AU.cjs +0 -33
  162. package/dist/chunk-YFJXS3AU.cjs.map +0 -1
  163. package/dist/chunk-YUE6TJ4M.js +0 -889
  164. package/dist/chunk-YUE6TJ4M.js.map +0 -1
  165. package/dist/chunk-ZGLJM4A4.js +0 -53
  166. package/dist/chunk-ZGLJM4A4.js.map +0 -1
  167. package/dist/function-CxixTB02.d.cts +0 -299
  168. package/dist/function-DTCt675z.d.ts +0 -299
  169. package/dist/index-B2Ulxxpo.d.cts +0 -144
  170. package/dist/index-BkYJ0xMq.d.cts +0 -152
  171. package/dist/index-BuaB-ypt.d.ts +0 -144
  172. package/dist/index-CAN7v-kw.d.ts +0 -199
  173. package/dist/index-COw1Ky9g.d.ts +0 -242
  174. package/dist/index-DRtIhlJj.d.ts +0 -152
  175. package/dist/index-DXd9JikN.d.cts +0 -246
  176. package/dist/index-DXd9JikN.d.ts +0 -246
  177. package/dist/index-alrRninU.d.cts +0 -199
  178. package/dist/index-oTQbYYIs.d.cts +0 -242
  179. package/dist/index-tR9jTn26.d.cts +0 -275
  180. package/dist/index-tR9jTn26.d.ts +0 -275
  181. package/dist/styled-Bl0ZK28h.d.ts +0 -543
  182. package/dist/styled-Crv6SA4S.d.cts +0 -543
  183. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  184. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  185. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -1,1548 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
-
3
- var _chunk357ZDPVUcjs = require('./chunk-357ZDPVU.cjs');
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
- var _chunkKQN2ZYNDcjs = require('./chunk-KQN2ZYND.cjs');
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
- var _chunkOL3IHHSJcjs = require('./chunk-OL3IHHSJ.cjs');
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
- var _chunkBSLTLGM3cjs = require('./chunk-BSLTLGM3.cjs');
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
- var _chunkPI3UXRWKcjs = require('./chunk-PI3UXRWK.cjs');
63
-
64
- // src/model/interval.ts
65
- var INVERSION_SUM = 9;
66
- function invertQuality(quality) {
67
- if (quality === "P") {
68
- return "P";
69
- }
70
- if (quality === "M") {
71
- return "m";
72
- }
73
- if (quality === "m") {
74
- return "M";
75
- }
76
- return quality.startsWith("A") ? `d${"d".repeat(quality.length - 1)}` : `A${"A".repeat(quality.length - 1)}`;
77
- }
78
- var Interval = class _Interval {
79
- #number;
80
- #quality;
81
- #semitones;
82
- #descending;
83
- constructor(numberValue, quality, semitones, descending = semitones < 0) {
84
- this.#number = numberValue;
85
- this.#quality = quality;
86
- this.#semitones = semitones;
87
- this.#descending = descending;
88
- }
89
- /**
90
- * The spelled interval between two notes.
91
- *
92
- * @param a The first note.
93
- * @param b The second note.
94
- * @returns The interval from `a` to `b`.
95
- */
96
- static between(a, b) {
97
- return a.intervalTo(b);
98
- }
99
- /**
100
- * Build an interval from explicit components.
101
- *
102
- * @param numberValue Diatonic size: 1 = unison, 2 = second, ... 8 = octave.
103
- * @param quality Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`.
104
- * @param semitones Signed semitone span; its magnitude must be the span the
105
- * number and quality describe.
106
- * @returns The interval.
107
- * @throws If the three components do not describe the same interval — a
108
- * `P5` spanning 8 semitones is not a value any other method can produce.
109
- */
110
- static of(numberValue, quality, semitones) {
111
- const expected = _chunkPI3UXRWKcjs.intervalSemitones.call(void 0, numberValue, quality);
112
- if (Math.abs(semitones) !== expected) {
113
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
114
- `${quality}${numberValue} spans ${expected} semitones; received ${semitones}`
115
- );
116
- }
117
- return new _Interval(numberValue, quality, semitones);
118
- }
119
- /**
120
- * Wrap a plain spelled interval, as returned by the pitch module.
121
- *
122
- * @param data The plain interval.
123
- * @returns The wrapped interval.
124
- */
125
- static fromData(data) {
126
- return new _Interval(
127
- data.number,
128
- data.quality,
129
- data.semitones,
130
- _nullishCoalesce(data.descending, () => ( data.semitones < 0))
131
- );
132
- }
133
- /**
134
- * Rebuild an interval from its {@link Interval.toJSON} output.
135
- *
136
- * @param data The serialized interval.
137
- * @returns The wrapped interval.
138
- */
139
- static fromJSON(data) {
140
- return _Interval.fromData(data);
141
- }
142
- /**
143
- * Parse an interval name such as `'P5'`, `'m3'`, or `'AA4'`.
144
- *
145
- * @param name The interval name.
146
- * @returns The ascending interval of that name.
147
- * @throws If the name is not a quality label followed by a diatonic number.
148
- */
149
- static parse(name) {
150
- return _Interval.fromData(_chunkPI3UXRWKcjs.parseInterval.call(void 0, name));
151
- }
152
- /** Diatonic size: 1 = unison, 2 = second, ... 8 = octave, and beyond. */
153
- get number() {
154
- return this.#number;
155
- }
156
- /** Quality label: `'P'`, `'M'`, `'m'`, or repeated `'A'`/`'d'`. */
157
- get quality() {
158
- return this.#quality;
159
- }
160
- /** Signed semitone span. */
161
- get semitones() {
162
- return this.#semitones;
163
- }
164
- /** A readable label composed of quality and number, e.g. `'P5'` or `'M3'`. */
165
- get name() {
166
- return `${this.#quality}${this.#number}`;
167
- }
168
- /**
169
- * The interval's inversion: the complement that completes the octave.
170
- *
171
- * A compound interval is reduced to its simple form first, and the result is
172
- * always ascending — an inversion answers "what is left of the octave",
173
- * which has no direction of its own.
174
- *
175
- * @returns The inverted interval, e.g. `M3` becomes `m6`.
176
- */
177
- invert() {
178
- const simple = this.#number === 8 ? 8 : (this.#number - 1) % 7 + 1;
179
- const numberValue = INVERSION_SUM - simple;
180
- const quality = invertQuality(this.#quality);
181
- return _Interval.of(numberValue, quality, _chunkPI3UXRWKcjs.intervalSemitones.call(void 0, numberValue, quality));
182
- }
183
- /**
184
- * Whether the interval is consonant.
185
- *
186
- * @param twoVoice When true, the perfect fourth counts as a dissonance,
187
- * matching two-voice counterpoint.
188
- * @returns True if the interval is consonant in that context.
189
- */
190
- isConsonant(twoVoice = true) {
191
- return _chunkPI3UXRWKcjs.isConsonantInterval.call(void 0, this.#semitones, twoVoice);
192
- }
193
- /**
194
- * Whether another interval is spelled identically.
195
- *
196
- * Enharmonic equivalents are not equal: an augmented second and a minor
197
- * third span the same distance but are different intervals.
198
- *
199
- * @param other The interval to compare with.
200
- * @returns True when number, quality, and span all match.
201
- */
202
- equals(other) {
203
- return this.#number === other.number && this.#quality === other.quality && this.#semitones === other.semitones && this.#descending === other.#descending;
204
- }
205
- /**
206
- * The plain interval data, for JSON serialization.
207
- *
208
- * Private class fields do not serialize, so an explicit `toJSON` keeps
209
- * `JSON.stringify(interval)` from collapsing to `{}`.
210
- *
211
- * @returns The diatonic number, quality, and semitone span.
212
- */
213
- toJSON() {
214
- return this.#descending ? {
215
- number: this.#number,
216
- quality: this.#quality,
217
- semitones: this.#semitones,
218
- descending: true
219
- } : this.#semitones < 0 ? {
220
- number: this.#number,
221
- quality: this.#quality,
222
- semitones: this.#semitones,
223
- descending: false
224
- } : { number: this.#number, quality: this.#quality, semitones: this.#semitones };
225
- }
226
- /**
227
- * The interval's name, so a template literal or a log line reads as the
228
- * interval.
229
- *
230
- * @returns The name, e.g. `'M3'`.
231
- */
232
- toString() {
233
- return this.name;
234
- }
235
- };
236
-
237
- // src/model/note.ts
238
- function copyNote(data) {
239
- if (!Number.isInteger(data.letter) || data.letter < 0 || data.letter > 6) {
240
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
241
- `note letter must be an integer in [0, 6]; received ${data.letter}`
242
- );
243
- }
244
- const copy = { letter: data.letter, alter: data.alter };
245
- if (data.octave !== void 0) {
246
- copy.octave = data.octave;
247
- }
248
- _chunkPI3UXRWKcjs.noteToPitchClass.call(void 0, copy);
249
- return copy;
250
- }
251
- var Note = class _Note {
252
- #data;
253
- /**
254
- * Wrap a plain note object.
255
- *
256
- * @param data The spelled note; it is copied, never retained or mutated.
257
- * @throws If `letter` is not an integer in 0..6.
258
- */
259
- constructor(data) {
260
- this.#data = copyNote(data);
261
- }
262
- /**
263
- * Parse scientific pitch notation (e.g. `'C#4'`, `'Bb'`, `'F##3'`).
264
- *
265
- * @param name The note text.
266
- * @returns The parsed note.
267
- * @throws If the text is not a valid note.
268
- */
269
- static of(name) {
270
- return new _Note(_chunkPI3UXRWKcjs.parseNote.call(void 0, name));
271
- }
272
- /**
273
- * Name a MIDI number (middle C = C4 = 60) as a note.
274
- *
275
- * @param midi The MIDI number.
276
- * @param spelling Whether to prefer sharps or flats for black keys.
277
- * @returns The spelled note, with octave.
278
- */
279
- static fromMidi(midi, spelling = "sharp") {
280
- return new _Note(_chunkPI3UXRWKcjs.midiToNote.call(void 0, midi, spelling));
281
- }
282
- /**
283
- * Wrap an existing plain note object.
284
- *
285
- * @param data The plain note.
286
- * @returns The wrapped note.
287
- * @throws If `letter` is not an integer in 0..6.
288
- */
289
- static fromData(data) {
290
- return new _Note(data);
291
- }
292
- /** The note rendered as scientific pitch notation, e.g. `'G4'` or `'Bb'`. */
293
- get name() {
294
- return _chunkPI3UXRWKcjs.formatNote.call(void 0, this.#data);
295
- }
296
- /** The pitch class (0..11), ignoring octave. */
297
- get pitchClass() {
298
- return _chunkPI3UXRWKcjs.noteToPitchClass.call(void 0, this.#data);
299
- }
300
- /**
301
- * The MIDI number (middle C = C4 = 60).
302
- *
303
- * @throws If the note has no octave and therefore no fixed pitch.
304
- */
305
- get midi() {
306
- if (this.#data.octave === void 0) {
307
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(`note ${this.name} has no octave, so it has no MIDI number`);
308
- }
309
- return _chunkPI3UXRWKcjs.noteToMidi.call(void 0, this.#data);
310
- }
311
- /** The diatonic letter number: 0..6 for C..B. */
312
- get letter() {
313
- return this.#data.letter;
314
- }
315
- /** The chromatic alteration in semitones: -1 flat, 0 natural, +1 sharp, ... */
316
- get alter() {
317
- return this.#data.alter;
318
- }
319
- /** The octave (scientific pitch notation), or undefined for a bare pitch class. */
320
- get octave() {
321
- return this.#data.octave;
322
- }
323
- /** A copy of the underlying plain note object. */
324
- get data() {
325
- return copyNote(this.#data);
326
- }
327
- /**
328
- * Transpose by a signed number of semitones, keeping the spelling.
329
- *
330
- * The letter moves by the diatonic distance of the conventional interval for
331
- * that many semitones, so `Ab4` up a major second is `Bb4` rather than `A#4`
332
- * and a flat key stays on the flat side. An octave-less note stays
333
- * octave-less: only its pitch class is moved. Transposing by zero is the
334
- * identity: the original spelling is preserved (no enharmonic respelling).
335
- *
336
- * @param semitones The signed semitone offset.
337
- * @param opts `spelling` forces the result onto the sharp or flat side
338
- * instead of following this note's letter.
339
- * @returns The transposed note.
340
- * @example
341
- * ```ts
342
- * import { Note } from '@libraz/libcantus';
343
- * Note.of('Ab4').transpose(2).name; // 'Bb4'
344
- * Note.of('Ab4').transpose(2, { spelling: 'sharp' }).name; // 'A#4'
345
- * ```
346
- */
347
- transpose(semitones, opts) {
348
- if (semitones === 0 && _optionalChain([opts, 'optionalAccess', _ => _.spelling]) === void 0) {
349
- return new _Note(this.#data);
350
- }
351
- return new _Note(_chunkPI3UXRWKcjs.transposeNote.call(void 0, this.#data, semitones, opts));
352
- }
353
- /**
354
- * The spelled interval from this note to another.
355
- *
356
- * @param other The second note.
357
- * @returns The interval, which {@link Interval.toJSON} unwraps to plain data.
358
- */
359
- intervalTo(other) {
360
- return Interval.fromData(_chunkPI3UXRWKcjs.spelledInterval.call(void 0, this.#data, other.data));
361
- }
362
- /**
363
- * Transpose by a spelled interval, keeping the spelling the interval names.
364
- *
365
- * Unlike {@link Note.transpose}, which picks a letter from the semitone
366
- * count, the interval's diatonic number decides the letter: C up an
367
- * augmented second is D#, not Eb.
368
- *
369
- * @param interval The interval to apply; a descending interval moves down.
370
- * @returns The transposed note.
371
- * @example
372
- * ```ts
373
- * import { Interval, Note } from '@libraz/libcantus';
374
- * Note.of('C4').transposeBy(Interval.parse('A2')).name; // 'D#4'
375
- * ```
376
- */
377
- transposeBy(interval) {
378
- return new _Note(_chunkPI3UXRWKcjs.transposeByInterval.call(void 0, this.#data, interval.toJSON()));
379
- }
380
- /**
381
- * Rebuild a note from its {@link Note.toJSON} output.
382
- *
383
- * @param data The serialized note.
384
- * @returns The wrapped note.
385
- * @throws If `letter` is not an integer in 0..6.
386
- */
387
- static fromJSON(data) {
388
- return new _Note(data);
389
- }
390
- /**
391
- * Whether another note has the same letter, alteration, and octave.
392
- *
393
- * @param other The note to compare.
394
- * @returns True if the spellings are identical.
395
- */
396
- equals(other) {
397
- const b = other.data;
398
- return this.#data.letter === b.letter && this.#data.alter === b.alter && this.#data.octave === b.octave;
399
- }
400
- /**
401
- * The plain note data, for JSON serialization.
402
- *
403
- * Private class fields do not serialize, so an explicit `toJSON` keeps
404
- * `JSON.stringify(note)` from collapsing to `{}`.
405
- *
406
- * @returns A copy of the underlying plain note object.
407
- */
408
- toJSON() {
409
- return this.data;
410
- }
411
- /**
412
- * The note's name, so a template literal or a log line reads as the note.
413
- *
414
- * @returns The spelled name, e.g. `'Bb3'`.
415
- */
416
- toString() {
417
- return this.name;
418
- }
419
- };
420
-
421
- // src/model/shared.ts
422
- function spellPitchClassBare(pc, spelling) {
423
- const spelled = _chunkPI3UXRWKcjs.midiToNote.call(void 0, 60 + _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pc), spelling);
424
- return { letter: spelled.letter, alter: spelled.alter };
425
- }
426
-
427
- // src/model/key.ts
428
- function accidentalLoad(tonic, scale) {
429
- return _chunkOL3IHHSJcjs.spellScale.call(void 0, tonic, scale).reduce((sum, note) => sum + Math.abs(note.alter), 0);
430
- }
431
- function bestTonicForScale(rootPc, scale) {
432
- const sharp = spellPitchClassBare(rootPc, "sharp");
433
- const flat = spellPitchClassBare(rootPc, "flat");
434
- if (sharp.letter === flat.letter && sharp.alter === flat.alter) {
435
- return new Note(sharp);
436
- }
437
- return accidentalLoad(flat, scale) <= accidentalLoad(sharp, scale) ? new Note(flat) : new Note(sharp);
438
- }
439
- var Key = class _Key {
440
- #scale;
441
- #tonic;
442
- #variant;
443
- /**
444
- * Wrap a key/scale and its spelled tonic.
445
- *
446
- * @param scale The key/scale; its root is normalized to a pitch class.
447
- * @param tonic The spelled tonic anchoring letter-name spelling.
448
- * @param variant Optional detected scale form retained for display.
449
- */
450
- constructor(scale, tonic, variant) {
451
- _chunkPI3UXRWKcjs.assertInteger.call(void 0, scale.rootPc, "scale.rootPc");
452
- _chunkPI3UXRWKcjs.assertInteger.call(void 0, scale.modeMask12, "scale.modeMask12", 1, 4095);
453
- if ((scale.modeMask12 & 1) === 0) {
454
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)("scale.modeMask12 must include the tonic (bit 0)");
455
- }
456
- const rootPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, scale.rootPc);
457
- if (tonic.pitchClass !== rootPc) {
458
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
459
- `tonic ${tonic.name} does not match the scale root pitch class ${rootPc}; pass a tonic that spells the scale root, or omit it to have one chosen`
460
- );
461
- }
462
- this.#scale = { rootPc, modeMask12: scale.modeMask12 };
463
- this.#tonic = tonic;
464
- this.#variant = variant;
465
- }
466
- /** Preserve detection metadata while replacing its plain key with this API's Key. */
467
- static #fromMatch(match) {
468
- const key = new _Key(
469
- match.key,
470
- bestTonicForScale(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, match.key.rootPc), match.key),
471
- match.variant
472
- );
473
- return { ...match, key };
474
- }
475
- /**
476
- * A major key.
477
- *
478
- * @param root Tonic as a note name (e.g. `'Eb'`) or a pitch class; a numeric
479
- * root is spelled with whichever accidental side yields the fewest
480
- * accidentals across the scale.
481
- * @returns The major key.
482
- */
483
- static major(root) {
484
- if (typeof root === "string") {
485
- const tonic = Note.of(root);
486
- return new _Key(_chunkBSLTLGM3cjs.majorKey.call(void 0, tonic.pitchClass), tonic);
487
- }
488
- const scale = _chunkBSLTLGM3cjs.majorKey.call(void 0, root);
489
- return new _Key(scale, bestTonicForScale(root, scale));
490
- }
491
- /**
492
- * A natural-minor key.
493
- *
494
- * @param root Tonic as a note name or a pitch class; a numeric root is
495
- * spelled with whichever accidental side yields the fewest accidentals
496
- * across the scale.
497
- * @returns The minor key.
498
- */
499
- static minor(root) {
500
- if (typeof root === "string") {
501
- const tonic = Note.of(root);
502
- return new _Key(_chunkBSLTLGM3cjs.minorKey.call(void 0, tonic.pitchClass), tonic);
503
- }
504
- const scale = _chunkBSLTLGM3cjs.minorKey.call(void 0, root);
505
- return new _Key(scale, bestTonicForScale(root, scale));
506
- }
507
- /**
508
- * A key on a named scale (e.g. `'dorian'`, `'harmonicMinor'`).
509
- *
510
- * @param name The scale name, a key of the scale module's named-scale table.
511
- * @param root Tonic as a note name or a pitch class; a numeric root is
512
- * spelled with whichever accidental side yields the fewest accidentals
513
- * across the scale, exactly as {@link Key.major} does.
514
- * @returns The key.
515
- * @throws If the name is not a known scale.
516
- */
517
- static named(name, root) {
518
- if (typeof root === "string") {
519
- const tonic = Note.of(root);
520
- return new _Key(_chunkBSLTLGM3cjs.scaleByName.call(void 0, name, tonic.pitchClass), tonic);
521
- }
522
- const scale = _chunkBSLTLGM3cjs.scaleByName.call(void 0, name, _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, root));
523
- return new _Key(scale, bestTonicForScale(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, root), scale));
524
- }
525
- /**
526
- * Wrap an existing `KeyScale`, synthesizing a spelled tonic when none is
527
- * given.
528
- *
529
- * The synthesized tonic is chosen the same way as for a numeric root
530
- * elsewhere: whichever accidental side spells the scale with the fewest
531
- * accidentals. This is what keeps `Key.of(detectKey(...).scale)` from handing
532
- * every downstream chord a double-sharp spelling.
533
- *
534
- * @param scale The key/scale to wrap.
535
- * @param tonic Optional spelled tonic; must spell the scale's root pitch class.
536
- * @returns The key.
537
- * @throws If the given tonic is not the scale's root pitch class.
538
- */
539
- static of(scale, tonic) {
540
- return new _Key(scale, _nullishCoalesce(tonic, () => ( bestTonicForScale(_chunkPI3UXRWKcjs.pitchClassOf.call(void 0, scale.rootPc), scale))));
541
- }
542
- /**
543
- * Rebuild a key from its {@link Key.toJSON} output.
544
- *
545
- * @param data The serialized key and tonic.
546
- * @returns The key.
547
- */
548
- static fromJSON(data) {
549
- return new _Key(data.scale, new Note(data.tonic));
550
- }
551
- /**
552
- * Identify the keys a set of pitches fits, best interpretation first.
553
- *
554
- * The counterpart of {@link Chord.detect}.
555
- *
556
- * @param pitches MIDI pitches or bare pitch classes.
557
- * @param opts How to weigh the input; see {@link DetectKeyOptions}.
558
- * @returns Ranked keys (may be empty).
559
- */
560
- static detect(pitches, opts) {
561
- return _Key.detectMatches(pitches, opts).map((match) => match.key);
562
- }
563
- /**
564
- * Identify keys while retaining each candidate's score, fit, and scale form.
565
- *
566
- * @param pitches MIDI pitches or bare pitch classes.
567
- * @param opts How to weigh the input; see {@link DetectKeyOptions}.
568
- * @returns Ranked key matches (may be empty).
569
- */
570
- static detectMatches(pitches, opts) {
571
- return _chunkKQN2ZYNDcjs.detectKey.call(void 0, pitches, opts).map((match) => _Key.#fromMatch(match));
572
- }
573
- /**
574
- * The single best key interpretation of a pitch set.
575
- *
576
- * @param pitches MIDI pitches or bare pitch classes.
577
- * @param opts How to weigh the input; see {@link DetectKeyOptions}.
578
- * @returns The top-ranked key, or null when nothing matches.
579
- * @example
580
- * ```ts
581
- * import { Key } from '@libraz/libcantus';
582
- * Key.detectBest([0, 2, 4, 5, 7, 9, 11])?.toString(); // 'C major'
583
- * ```
584
- */
585
- static detectBest(pitches, opts) {
586
- const best = _chunkKQN2ZYNDcjs.detectKeyBest.call(void 0, pitches, opts);
587
- return best === null ? null : _Key.#fromMatch(best).key;
588
- }
589
- /**
590
- * Whether another key has the same tonic pitch class and mode.
591
- *
592
- * The spelled tonic is not compared: C# major and Db major are the same key
593
- * written two ways.
594
- *
595
- * @param other The key to compare.
596
- * @returns True when tonic and mode mask both match.
597
- */
598
- equals(other) {
599
- const scale = other.scale;
600
- return this.#scale.rootPc === scale.rootPc && this.#scale.modeMask12 === scale.modeMask12;
601
- }
602
- /** A copy of the underlying plain `KeyScale`. */
603
- get scale() {
604
- return { rootPc: this.#scale.rootPc, modeMask12: this.#scale.modeMask12 };
605
- }
606
- /** The spelled tonic. */
607
- get tonic() {
608
- return this.#tonic;
609
- }
610
- /** The tonic pitch class (0..11). */
611
- get rootPc() {
612
- return this.#scale.rootPc;
613
- }
614
- /** Whether the scale has a minor third and no major third. */
615
- get isMinor() {
616
- return _chunkKQN2ZYNDcjs.isMinorKey.call(void 0, this.#scale);
617
- }
618
- /** The detected scale form, when this key came from key detection. */
619
- get variant() {
620
- return this.#variant;
621
- }
622
- /**
623
- * The scale's pitch classes in ascending scale-degree order (degree 0 first).
624
- *
625
- * @returns One pitch class per scale degree.
626
- */
627
- pitchClasses() {
628
- return _chunkBSLTLGM3cjs.scaleTonesInDegreeOrder.call(void 0, this.#scale);
629
- }
630
- /**
631
- * Transpose the key by a number of semitones.
632
- *
633
- * The mode mask is unchanged, so the scale keeps its shape; only the tonic
634
- * moves. The new tonic is spelled the way that key is conventionally written
635
- * — D major, not C## major.
636
- *
637
- * @param semitones The signed semitone offset.
638
- * @returns The transposed key.
639
- * @example
640
- * ```ts
641
- * import { Key } from '@libraz/libcantus';
642
- * Key.major('C').transpose(2).toString(); // 'D major'
643
- * ```
644
- */
645
- transpose(semitones) {
646
- _chunkPI3UXRWKcjs.assertFiniteNumber.call(void 0, semitones, "semitones");
647
- const rootPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, this.#scale.rootPc + Math.round(semitones));
648
- return new _Key(
649
- { rootPc, modeMask12: this.#scale.modeMask12 },
650
- this.#tonic.transpose(semitones),
651
- this.#variant
652
- );
653
- }
654
- /**
655
- * The spelled scale, one note per degree (e.g. C D E F G A B for C major).
656
- *
657
- * @returns Spelled octave-less notes in scale-degree order.
658
- */
659
- notes() {
660
- return _chunkOL3IHHSJcjs.spellScale.call(void 0, this.#tonic.data, this.#scale).map((note) => new Note(note));
661
- }
662
- /**
663
- * Alias of {@link Key.notes}.
664
- *
665
- * @returns Spelled octave-less notes in scale-degree order.
666
- */
667
- spell() {
668
- return this.notes();
669
- }
670
- /**
671
- * The spelled scale as letter-name strings.
672
- *
673
- * @returns One name per scale degree.
674
- */
675
- noteNames() {
676
- return this.notes().map((note) => note.name);
677
- }
678
- /**
679
- * Build a chord on a scale degree, carrying this key as context.
680
- *
681
- * With an explicit quality the quality's interval template is attached to the
682
- * degree's diatonic root; without one the scale-correct diatonic triad is
683
- * stacked (e.g. a diminished triad on the leading tone of a major key).
684
- *
685
- * @param degree 0-based scale degree of the chord root.
686
- * @param quality Optional chord quality.
687
- * @returns The chord, with this key attached.
688
- * @throws Without a `quality`, if this key's scale is not heptatonic —
689
- * stacking thirds needs seven degrees, so the pentatonic, blues,
690
- * whole-tone, octatonic and chromatic scales have no diatonic triad. Pass
691
- * an explicit `quality` for those.
692
- */
693
- chord(degree, quality) {
694
- const data = quality === void 0 ? _chunkBSLTLGM3cjs.diatonicTriad.call(void 0, degree, this.#scale) : _chunkBSLTLGM3cjs.chordFromDegree.call(void 0, degree, quality, this.#scale);
695
- return new Chord(data, this);
696
- }
697
- /**
698
- * The diatonic triad on a scale degree, carrying this key as context.
699
- *
700
- * @param degree 0-based scale degree of the chord root.
701
- * @returns The triad, with this key attached.
702
- * @throws If this key's scale is not heptatonic; stacking thirds needs seven
703
- * degrees. Use {@link Key.chord} with an explicit quality instead.
704
- */
705
- diatonicTriad(degree) {
706
- return new Chord(_chunkBSLTLGM3cjs.diatonicTriad.call(void 0, degree, this.#scale), this);
707
- }
708
- /**
709
- * The diatonic seventh chord on a scale degree, carrying this key as context.
710
- *
711
- * @param degree 0-based scale degree of the chord root.
712
- * @returns The seventh chord, with this key attached.
713
- * @throws If this key's scale is not heptatonic; stacking thirds needs seven
714
- * degrees. Use {@link Key.chord} with an explicit quality instead.
715
- */
716
- diatonicSeventh(degree) {
717
- return new Chord(_chunkBSLTLGM3cjs.diatonicSeventh.call(void 0, degree, this.#scale), this);
718
- }
719
- /**
720
- * Build the chord denoted by a Roman numeral in this key (including applied
721
- * chords such as `'V7/V'`), carrying this key as context.
722
- *
723
- * @param text The Roman numeral.
724
- * @returns The chord, with this key attached.
725
- * @throws If the numeral is not valid.
726
- */
727
- roman(text) {
728
- return new Chord(_chunkKQN2ZYNDcjs.romanToChord.call(void 0, text, this.#scale), this);
729
- }
730
- /**
731
- * Whether a pitch belongs to the scale.
732
- *
733
- * @param x A MIDI pitch, bare pitch class, or note.
734
- * @returns True if the pitch class is a scale tone.
735
- */
736
- contains(x) {
737
- return _chunkBSLTLGM3cjs.isScaleTone.call(void 0, typeof x === "number" ? x : x.pitchClass, this.#scale);
738
- }
739
- /**
740
- * The plain key data, for JSON serialization.
741
- *
742
- * Private class fields do not serialize, so an explicit `toJSON` keeps
743
- * `JSON.stringify(key)` from collapsing to `{}`. The result pairs the
744
- * `KeyScale` with the spelled tonic, enough to reconstruct the key via
745
- * {@link Key.of}.
746
- *
747
- * @returns The key/scale and its spelled tonic.
748
- */
749
- toJSON() {
750
- return { scale: this.scale, tonic: this.#tonic.data };
751
- }
752
- /**
753
- * The key's tonic and mode, so a template literal or a log line reads as the
754
- * key. Detected harmonic and melodic minor keys retain their scale form.
755
- *
756
- * @returns The name, e.g. `'C major'` or `'A minor'`.
757
- */
758
- toString() {
759
- if (this.#variant === "harmonic" || this.#variant === "melodic") {
760
- return `${this.#tonic.name} ${this.#variant} minor`;
761
- }
762
- return `${this.#tonic.name} ${this.isMinor ? "minor" : "major"}`;
763
- }
764
- };
765
-
766
- // src/model/progression.ts
767
- var Progression = class _Progression {
768
- #chords;
769
- #key;
770
- /**
771
- * Wrap a chord sequence.
772
- *
773
- * @param chords The chords in order; the array is copied.
774
- * @param key Optional key context for analysis methods.
775
- */
776
- constructor(chords, key) {
777
- this.#chords = Object.freeze(_Progression.#attachKey(chords, key));
778
- this.#key = key;
779
- }
780
- /** Attach a progression key to chord members that do not already carry one. */
781
- static #attachKey(chords, key) {
782
- return key === void 0 ? [...chords] : chords.map((chord) => chord.key === void 0 ? chord.withKey(key) : chord);
783
- }
784
- /**
785
- * Build a progression from the {@link ChordSpan} records the generators
786
- * return, keeping their order.
787
- *
788
- * The spans' `startBeat`, `degree`, and `secondaryDominant` are analysis
789
- * annotations that a chord sequence does not carry; only the harmony crosses
790
- * over. Keep the spans themselves if the timing matters.
791
- *
792
- * @param spans The chord spans, in order.
793
- * @param key Optional key context for the analysis methods.
794
- * @returns The progression.
795
- * @example
796
- * ```ts
797
- * import { generateProgression, Key, Progression } from '@libraz/libcantus';
798
- * const key = Key.major('C');
799
- * const spans = generateProgression({ key: key.scale, style: 'dance', bars: 4 });
800
- * Progression.fromSpans(spans, key).roman();
801
- * ```
802
- */
803
- static fromSpans(spans, key) {
804
- const chords = spans.map(
805
- (span) => Chord.from(_chunkBSLTLGM3cjs.makeChord.call(void 0, span.rootPc, span.quality, span.bassPc))
806
- );
807
- return new _Progression(chords, key);
808
- }
809
- /**
810
- * Rebuild a progression from its {@link Progression.toJSON} output.
811
- *
812
- * @param data The serialized chords and key.
813
- * @returns The progression.
814
- */
815
- static fromJSON(data) {
816
- const key = data.key === void 0 ? void 0 : Key.fromJSON(data.key);
817
- const chords = data.chords.map((chord) => Chord.fromJSON(chord));
818
- return key === void 0 ? new _Progression(chords) : new _Progression(chords, key);
819
- }
820
- /**
821
- * The chord sequence.
822
- *
823
- * The array is the progression's own and is frozen rather than copied, so
824
- * reading it in a loop stays linear.
825
- */
826
- get chords() {
827
- return this.#chords;
828
- }
829
- /** The carried key context, if any. */
830
- get key() {
831
- return this.#key;
832
- }
833
- /** The number of chords. */
834
- get length() {
835
- return this.#chords.length;
836
- }
837
- /**
838
- * The chord at an index.
839
- *
840
- * @param index 0-based position; a negative index counts from the end.
841
- * @returns The chord, or undefined when the index is out of range.
842
- */
843
- at(index) {
844
- return this.#chords.at(index);
845
- }
846
- /** Iterate the chords in order, so a progression works with `for...of`. */
847
- [Symbol.iterator]() {
848
- return this.#chords[Symbol.iterator]();
849
- }
850
- /**
851
- * Whether another progression holds the same chords in the same order.
852
- *
853
- * The key context is not compared: it is an analysis lens, not part of the
854
- * harmony. Chord equality follows {@link Chord.equals}.
855
- *
856
- * @param other The progression to compare.
857
- * @returns True when the chord sequences match.
858
- */
859
- equals(other) {
860
- if (this.#chords.length !== other.length) {
861
- return false;
862
- }
863
- return this.#chords.every((chord, index) => {
864
- const theirs = other.at(index);
865
- return theirs !== void 0 && chord.equals(theirs);
866
- });
867
- }
868
- /**
869
- * A copy of this progression with a chord appended.
870
- *
871
- * @param chord The chord to append.
872
- * @returns The new progression.
873
- */
874
- add(chord) {
875
- return new _Progression([...this.#chords, chord], this.#key);
876
- }
877
- /**
878
- * A copy of this progression carrying the given key context.
879
- *
880
- * @param key The key context to attach.
881
- * @returns The new progression.
882
- */
883
- withKey(key) {
884
- return new _Progression(this.#chords, key);
885
- }
886
- /**
887
- * Voice the progression with smooth voice leading.
888
- *
889
- * @param opts Voicing options; defaults to four SATB voices.
890
- * @returns One ascending voicing (MIDI pitches) per chord.
891
- * @throws If any chord admits no voicing within the given ranges.
892
- */
893
- voice(opts) {
894
- const key = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _2 => _2.key]), () => ( _optionalChain([this, 'access', _3 => _3.#key, 'optionalAccess', _4 => _4.scale])));
895
- return _chunkOL3IHHSJcjs.voiceProgression.call(void 0,
896
- this.#chords.map((chord) => chord.data),
897
- key === void 0 ? opts : { ...opts, key }
898
- );
899
- }
900
- /**
901
- * The Roman numeral of each chord in a key.
902
- *
903
- * @param key Key to analyze in; falls back to the carried context.
904
- * @param opts Applied-numeral rendering options.
905
- * @returns One numeral per chord.
906
- * @throws If no key is given and none is carried.
907
- */
908
- roman(key, opts) {
909
- const resolved = this.#resolveKey(key);
910
- return this.#chords.map((chord) => chord.roman(resolved, opts));
911
- }
912
- /**
913
- * The harmonic function of each chord in a key.
914
- *
915
- * @param key Key to analyze in; falls back to the carried context.
916
- * @returns One function per chord.
917
- * @throws If no key is given and none is carried.
918
- */
919
- functions(key) {
920
- const resolved = this.#resolveKey(key);
921
- return this.#chords.map((chord) => chord.function(resolved));
922
- }
923
- /**
924
- * Analyze every chord and classify the closing cadence.
925
- *
926
- * The cadence is detected on the final chord pair and is null when the
927
- * progression has fewer than two chords.
928
- *
929
- * @param key Key to analyze in; falls back to the carried context.
930
- * @param opts Applied-numeral rendering options.
931
- * @returns Per-chord analyses and the closing cadence.
932
- * @throws If no key is given and none is carried.
933
- */
934
- analyze(key, opts) {
935
- const resolved = this.#resolveKey(key);
936
- const chords = this.#chords.map((chord) => chord.analyze(resolved, opts));
937
- const from = this.#chords[this.#chords.length - 2];
938
- const to = this.#chords[this.#chords.length - 1];
939
- const cadence = from !== void 0 && to !== void 0 ? _chunkKQN2ZYNDcjs.detectCadence.call(void 0, from.data, to.data, resolved.scale) : null;
940
- return { chords, cadence };
941
- }
942
- /**
943
- * Choose one compatible scale for every chord, favoring smooth changes.
944
- *
945
- * @returns One scale choice per chord in this progression.
946
- */
947
- scales() {
948
- return _chunkOL3IHHSJcjs.scalesForChanges.call(void 0, this.#chords.map((chord) => chord.data));
949
- }
950
- /**
951
- * The plain progression data, for JSON serialization.
952
- *
953
- * Private class fields do not serialize, so an explicit `toJSON` keeps
954
- * `JSON.stringify(progression)` from collapsing to `{}`. The chords are
955
- * emitted as plain data and the carried key, when present, as its own data.
956
- *
957
- * @returns The chord data sequence and the carried key, if any.
958
- */
959
- /**
960
- * Transpose every chord by a number of semitones.
961
- *
962
- * A carried key moves with the chords, so the progression keeps its degrees
963
- * and functions in the new key.
964
- *
965
- * @param semitones The signed semitone offset.
966
- * @returns The transposed progression.
967
- * @example
968
- * ```ts
969
- * import { Chord, Progression } from '@libraz/libcantus';
970
- * new Progression([Chord.parse('C'), Chord.parse('G')]).transpose(2).toString(); // 'D A'
971
- * ```
972
- */
973
- transpose(semitones) {
974
- const key = _optionalChain([this, 'access', _5 => _5.#key, 'optionalAccess', _6 => _6.transpose, 'call', _7 => _7(semitones)]);
975
- const chords = this.#chords.map((chord) => {
976
- const moved = chord.transpose(semitones);
977
- return key !== void 0 && moved.key === void 0 ? moved.withKey(key) : moved;
978
- });
979
- return new _Progression(chords, key);
980
- }
981
- /**
982
- * The chord symbols separated by spaces, so a template literal or a log line
983
- * reads as the progression.
984
- *
985
- * @returns The symbols in order, e.g. `'C Am F G'`.
986
- */
987
- toString() {
988
- return this.#chords.map((chord) => chord.symbol()).join(" ");
989
- }
990
- /**
991
- * The plain progression data, for JSON serialization.
992
- *
993
- * Private class fields do not serialize, so this preserves both the chord
994
- * data and any carried key in `JSON.stringify(progression)`.
995
- *
996
- * @returns The chord data sequence and the carried key, if any.
997
- */
998
- toJSON() {
999
- return {
1000
- chords: this.#chords.map((chord) => chord.toJSON()),
1001
- key: _optionalChain([this, 'access', _8 => _8.#key, 'optionalAccess', _9 => _9.toJSON, 'call', _10 => _10()])
1002
- };
1003
- }
1004
- /** Resolve the key for an analysis method: explicit first, then carried. */
1005
- #resolveKey(key) {
1006
- const resolved = _nullishCoalesce(key, () => ( this.#key));
1007
- if (resolved === void 0) {
1008
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
1009
- "progression has no key context; pass a Key or attach one with withKey()"
1010
- );
1011
- }
1012
- return resolved;
1013
- }
1014
- };
1015
-
1016
- // src/model/chord.ts
1017
- function hintMatches(hint, pc) {
1018
- return hint !== void 0 && pc !== void 0 && _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, _chunkPI3UXRWKcjs.noteToPitchClass.call(void 0, hint)) === _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, pc);
1019
- }
1020
- function transposeHint(hint, semitones) {
1021
- if (hint === void 0) {
1022
- return void 0;
1023
- }
1024
- const moved = _chunkPI3UXRWKcjs.transposeNote.call(void 0, hint, semitones);
1025
- return { letter: moved.letter, alter: moved.alter };
1026
- }
1027
- function copyChord(data) {
1028
- const copy = {
1029
- rootPc: data.rootPc,
1030
- quality: data.quality,
1031
- intervals: [...data.intervals]
1032
- };
1033
- if (data.bassPc !== void 0) {
1034
- copy.bassPc = data.bassPc;
1035
- }
1036
- if (hintMatches(data.rootSpelling, data.rootPc) && data.rootSpelling !== void 0) {
1037
- copy.rootSpelling = { letter: data.rootSpelling.letter, alter: data.rootSpelling.alter };
1038
- }
1039
- if (hintMatches(data.bassSpelling, data.bassPc) && data.bassSpelling !== void 0) {
1040
- copy.bassSpelling = { letter: data.bassSpelling.letter, alter: data.bassSpelling.alter };
1041
- }
1042
- return copy;
1043
- }
1044
- function deriveBassSpelling(chord, key) {
1045
- const bassPc = chord.bassPc;
1046
- if (bassPc === void 0) {
1047
- return void 0;
1048
- }
1049
- const root = chord.rootSpelling;
1050
- if (root !== void 0) {
1051
- const index = chord.intervals.findIndex((i) => _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, chord.rootPc + i) === bassPc);
1052
- const tone = index >= 0 ? _chunkOL3IHHSJcjs.spellChordFromRoot.call(void 0, chord, root)[index] : void 0;
1053
- if (tone !== void 0) {
1054
- return { letter: tone.letter, alter: tone.alter };
1055
- }
1056
- }
1057
- if (key === void 0) {
1058
- return void 0;
1059
- }
1060
- const spelled = _chunkOL3IHHSJcjs.spellPitchClass.call(void 0, bassPc, key.tonic.data, key.scale);
1061
- return { letter: spelled.letter, alter: spelled.alter };
1062
- }
1063
- var Chord = class _Chord {
1064
- /** Exactly what the caller supplied: any spelling here is the caller's own. */
1065
- #given;
1066
- #key;
1067
- /**
1068
- * Wrap a plain chord object.
1069
- *
1070
- * @param data The chord; it is copied, never retained or mutated.
1071
- * @param key Optional key context for analysis methods.
1072
- */
1073
- constructor(data, key) {
1074
- this.#given = copyChord(data);
1075
- this.#key = key;
1076
- }
1077
- /**
1078
- * The chord data with any missing spelling hint filled in from the key.
1079
- *
1080
- * Derived spellings are computed on read rather than baked in at construction,
1081
- * so re-attaching a different key re-spells the chord instead of carrying the
1082
- * first key's letters forever. A spelling the caller supplied always wins.
1083
- */
1084
- get #data() {
1085
- return this.#dataWithKey(this.#key);
1086
- }
1087
- /** Build a read view whose missing spellings are derived from `key`. */
1088
- #dataWithKey(key) {
1089
- const out = copyChord(this.#given);
1090
- if (out.rootSpelling === void 0 && key !== void 0) {
1091
- out.rootSpelling = _chunkOL3IHHSJcjs.spellPitchClass.call(void 0, out.rootPc, key.tonic.data, key.scale);
1092
- }
1093
- if (out.bassSpelling === void 0) {
1094
- const bass = deriveBassSpelling(out, key);
1095
- if (bass !== void 0) {
1096
- out.bassSpelling = bass;
1097
- }
1098
- }
1099
- return out;
1100
- }
1101
- /**
1102
- * Build a chord from a root and quality.
1103
- *
1104
- * @param root Root as a note name (e.g. `'Eb'`) or a pitch class.
1105
- * @param quality The chord quality.
1106
- * @param bass Optional slash-chord bass, as a note name (e.g. `'Bb'`) or a
1107
- * pitch class. A named bass keeps its own spelling.
1108
- * @returns The chord (without key context).
1109
- */
1110
- static of(root, quality, bass) {
1111
- const bassNote = typeof bass === "string" ? Note.of(bass) : void 0;
1112
- const bassPc = bassNote !== void 0 ? bassNote.pitchClass : bass;
1113
- const rootNote = typeof root === "string" ? Note.of(root) : void 0;
1114
- const data = _chunkBSLTLGM3cjs.makeChord.call(void 0,
1115
- rootNote !== void 0 ? rootNote.pitchClass : root,
1116
- quality,
1117
- bassPc
1118
- );
1119
- if (rootNote !== void 0) {
1120
- data.rootSpelling = { letter: rootNote.letter, alter: rootNote.alter };
1121
- }
1122
- if (bassNote !== void 0) {
1123
- data.bassSpelling = { letter: bassNote.letter, alter: bassNote.alter };
1124
- }
1125
- return new _Chord(data);
1126
- }
1127
- /**
1128
- * Wrap an existing plain chord object.
1129
- *
1130
- * @param data The plain chord.
1131
- * @returns The wrapped chord (without key context).
1132
- */
1133
- static from(data) {
1134
- return new _Chord(data);
1135
- }
1136
- /**
1137
- * Alias of {@link Chord.from}, matching the `fromData` factory on the other
1138
- * classes.
1139
- *
1140
- * @param data The plain chord.
1141
- * @returns The wrapped chord (without key context).
1142
- */
1143
- static fromData(data) {
1144
- return new _Chord(data);
1145
- }
1146
- /**
1147
- * Rebuild a chord from its {@link Chord.toJSON} output.
1148
- *
1149
- * The key context is not serialized, so the result carries none; re-attach
1150
- * one with {@link Chord.withKey}.
1151
- *
1152
- * @param data The serialized chord.
1153
- * @returns The wrapped chord.
1154
- */
1155
- static fromJSON(data) {
1156
- return new _Chord(data);
1157
- }
1158
- /**
1159
- * Parse a lead-sheet chord symbol (e.g. `'Cmaj7'`, `'F#m7b5'`, `'C/G'`).
1160
- *
1161
- * @param symbol The chord symbol.
1162
- * @returns The chord (without key context).
1163
- * @throws If the root or quality is not recognized.
1164
- */
1165
- static parse(symbol) {
1166
- return new _Chord(_chunkOL3IHHSJcjs.parseChordSymbol.call(void 0, symbol));
1167
- }
1168
- /**
1169
- * Identify the chords matching a set of pitches, best interpretation first.
1170
- *
1171
- * @param pitches MIDI pitches or bare pitch classes.
1172
- * @param opts How to interpret the input; see {@link DetectChordOptions}.
1173
- * @returns Ranked chord interpretations (may be empty).
1174
- */
1175
- static detect(pitches, opts) {
1176
- return _chunkKQN2ZYNDcjs.detectChord.call(void 0, pitches, opts).map(
1177
- (match) => new _Chord(_chunkBSLTLGM3cjs.makeChord.call(void 0, match.rootPc, match.quality, match.bassPc))
1178
- );
1179
- }
1180
- /**
1181
- * Identify the chords matching a set of pitches, keeping each match's
1182
- * recognition metadata beside the chord.
1183
- *
1184
- * {@link Chord.detect} discards the confidence signals a recognition UI needs
1185
- * — whether the set matched exactly, which chord tones were missing, which
1186
- * input notes were foreign, and which inversion the bass implies.
1187
- *
1188
- * @param pitches MIDI pitches or bare pitch classes.
1189
- * @param opts How to interpret the input; see {@link DetectChordOptions}.
1190
- * @returns Ranked interpretations, each with its chord and its match record.
1191
- * @example
1192
- * ```ts
1193
- * import { Chord } from '@libraz/libcantus';
1194
- * const [best] = Chord.detectMatches([60, 64, 67]);
1195
- * best?.match.exact; // true
1196
- * ```
1197
- */
1198
- static detectMatches(pitches, opts) {
1199
- return _chunkKQN2ZYNDcjs.detectChord.call(void 0, pitches, opts).map((match) => ({
1200
- chord: new _Chord(_chunkBSLTLGM3cjs.makeChord.call(void 0, match.rootPc, match.quality, match.bassPc)),
1201
- match
1202
- }));
1203
- }
1204
- /**
1205
- * The single best chord interpretation of a pitch set.
1206
- *
1207
- * @param pitches MIDI pitches or bare pitch classes.
1208
- * @param opts How to interpret the input; see {@link DetectChordOptions}.
1209
- * @returns The top-ranked chord, or null when nothing matches.
1210
- */
1211
- static detectBest(pitches, opts) {
1212
- const best = _chunkKQN2ZYNDcjs.detectChordBest.call(void 0, pitches, opts);
1213
- return best === null ? null : new _Chord(best);
1214
- }
1215
- /** The root pitch class (0..11). */
1216
- get rootPc() {
1217
- return this.#data.rootPc;
1218
- }
1219
- /** The chord quality. */
1220
- get quality() {
1221
- return this.#data.quality;
1222
- }
1223
- /** A copy of the semitone offsets above the root. */
1224
- get intervals() {
1225
- return [...this.#data.intervals];
1226
- }
1227
- /** The slash-chord bass pitch class, or undefined in root position. */
1228
- get bassPc() {
1229
- return this.#data.bassPc;
1230
- }
1231
- /**
1232
- * A copy of the underlying plain chord object, including the spellings
1233
- * derived from any attached key.
1234
- */
1235
- get data() {
1236
- return this.#data;
1237
- }
1238
- /** The carried key context, if any. */
1239
- get key() {
1240
- return this.#key;
1241
- }
1242
- /**
1243
- * A copy of this chord carrying the given key context.
1244
- *
1245
- * Only a spelling the caller supplied (via `Chord.parse`, `Chord.of` with a
1246
- * named root, or plain data carrying a hint) survives; a spelling that came
1247
- * from a previously attached key is re-derived, so re-keying a progression
1248
- * during a modulation does not keep the old key's letters and the order of
1249
- * `withKey` calls does not affect the result.
1250
- *
1251
- * @param key The key context to attach.
1252
- * @returns The new chord.
1253
- */
1254
- withKey(key) {
1255
- return new _Chord(this.#given, key);
1256
- }
1257
- /**
1258
- * The chord's sorted, deduplicated pitch classes.
1259
- *
1260
- * @returns Pitch classes ascending in [0, 11].
1261
- */
1262
- pitchClasses() {
1263
- return _chunkBSLTLGM3cjs.chordPitchClasses.call(void 0, this.#data);
1264
- }
1265
- /**
1266
- * The chord's Roman numeral in a key.
1267
- *
1268
- * @param key Key to analyze in; falls back to the carried context.
1269
- * @param opts Applied-numeral rendering options.
1270
- * @returns The Roman numeral string.
1271
- * @throws If no key is given and none is carried.
1272
- */
1273
- roman(key, opts) {
1274
- return _chunkKQN2ZYNDcjs.chordToRoman.call(void 0, this.#data, this.#resolveKey(key).scale, opts);
1275
- }
1276
- /**
1277
- * The chord's harmonic function (tonic / subdominant / dominant) in a key.
1278
- *
1279
- * @param key Key to analyze in; falls back to the carried context.
1280
- * @returns The harmonic function.
1281
- * @throws If no key is given and none is carried.
1282
- */
1283
- function(key) {
1284
- return _chunkKQN2ZYNDcjs.functionOf.call(void 0, this.#data, this.#resolveKey(key).scale);
1285
- }
1286
- /**
1287
- * Full functional analysis: function, borrowing, and Roman numeral.
1288
- *
1289
- * @param key Key to analyze in; falls back to the carried context.
1290
- * @param opts Applied-numeral rendering options.
1291
- * @returns The chord analysis.
1292
- * @throws If no key is given and none is carried.
1293
- */
1294
- analyze(key, opts) {
1295
- return _chunkKQN2ZYNDcjs.analyzeChord.call(void 0, this.#data, this.#resolveKey(key).scale, opts);
1296
- }
1297
- /**
1298
- * Whether the chord is borrowed from the parallel mode (modal interchange).
1299
- *
1300
- * @param key Key to analyze in; falls back to the carried context.
1301
- * @returns True if the chord is borrowed.
1302
- * @throws If no key is given and none is carried.
1303
- */
1304
- isBorrowed(key) {
1305
- return _chunkKQN2ZYNDcjs.isBorrowedChord.call(void 0, this.#data, this.#resolveKey(key).scale);
1306
- }
1307
- /**
1308
- * The origin of a non-diatonic chord (parallel mode or Neapolitan), or null.
1309
- *
1310
- * @param key Key to analyze in; falls back to the carried context.
1311
- * @returns The borrowing source, or null.
1312
- * @throws If no key is given and none is carried.
1313
- */
1314
- borrowedSource(key) {
1315
- return _chunkKQN2ZYNDcjs.borrowedSource.call(void 0, this.#data, this.#resolveKey(key).scale);
1316
- }
1317
- /**
1318
- * The chord rendered as a lead-sheet symbol (e.g. `'Cmaj7'`, `'F#m7'`, `'C/G'`).
1319
- *
1320
- * @param opts Set `flats: true` to spell the root/bass with flats.
1321
- * @returns The chord symbol.
1322
- */
1323
- symbol(opts) {
1324
- return _chunkOL3IHHSJcjs.formatChordSymbol.call(void 0, this.#data, opts);
1325
- }
1326
- /**
1327
- * Realize the chord as one MIDI pitch per voice, ascending.
1328
- *
1329
- * A chord that carries a key passes it to the voicer, so the leading tone is
1330
- * not doubled; an explicit `opts.key` overrides it.
1331
- *
1332
- * @param opts Voicing options; defaults to four SATB voices.
1333
- * @returns MIDI pitches, ascending, one per voice.
1334
- * @throws If no voicing fits the given ranges.
1335
- */
1336
- voice(opts) {
1337
- const key = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _11 => _11.key]), () => ( _optionalChain([this, 'access', _12 => _12.#key, 'optionalAccess', _13 => _13.scale])));
1338
- return _chunkOL3IHHSJcjs.voiceChord.call(void 0, this.#data, key === void 0 ? opts : { ...opts, key });
1339
- }
1340
- /**
1341
- * Realize the chord as a single styled voicing (`close`, `drop2`, `drop3`,
1342
- * `shell`, or `rootless`), optionally constraining the top voice.
1343
- *
1344
- * @param opts Styled-voicing options; defaults to a close-position voicing.
1345
- * @returns MIDI pitches, ascending.
1346
- */
1347
- styledVoicing(opts) {
1348
- return _chunkOL3IHHSJcjs.voiceChordStyled.call(void 0, this.#data, opts);
1349
- }
1350
- /**
1351
- * The negative-harmony mirror of the chord about the key's tonic–dominant
1352
- * axis (major becomes minor and vice versa).
1353
- *
1354
- * @param key Key providing the reflection axis; falls back to the carried
1355
- * context.
1356
- * @returns The mirrored chord, keeping any key context.
1357
- * @throws If no key is given and none is carried.
1358
- */
1359
- negativeHarmony(key) {
1360
- const resolved = this.#resolveKey(key);
1361
- return new _Chord(_chunk357ZDPVUcjs.negativeHarmonyMirror.call(void 0, this.#data, resolved.scale), _nullishCoalesce(key, () => ( this.#key)));
1362
- }
1363
- /**
1364
- * The n-th inversion: a copy whose bass is the chord tone `n` steps above the
1365
- * root in the interval template (`invert(1)` puts the third in the bass).
1366
- * `n` wraps around the template length; negative values count backwards.
1367
- *
1368
- * `invert(0)` (and any `n` that wraps to it) is root position, so it carries
1369
- * no slash bass and equals the original chord.
1370
- *
1371
- * @param n The inversion number.
1372
- * @returns The inverted chord, keeping any key context.
1373
- * @throws If the chord has no intervals.
1374
- */
1375
- invert(n) {
1376
- const data = copyChord(this.#given);
1377
- const intervals = data.intervals;
1378
- const length = intervals.length;
1379
- if (length === 0) {
1380
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)("cannot invert a chord with no intervals");
1381
- }
1382
- const index = (n % length + length) % length;
1383
- delete data.bassSpelling;
1384
- if (index === 0) {
1385
- delete data.bassPc;
1386
- } else {
1387
- data.bassPc = _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, data.rootPc + (_nullishCoalesce(intervals[index], () => ( 0))));
1388
- }
1389
- return new _Chord(data, this.#key);
1390
- }
1391
- /**
1392
- * Transpose the chord by a number of semitones.
1393
- *
1394
- * The quality and interval template are carried over, so a chord that a
1395
- * symbol round-trip could not express — a custom interval set, an inversion —
1396
- * survives. A carried key moves with the chord, so the transposed chord keeps
1397
- * the same degree and function inside the transposed key.
1398
- *
1399
- * @param semitones The signed semitone offset.
1400
- * @returns The transposed chord, in the transposed key when one is carried.
1401
- * @example
1402
- * ```ts
1403
- * import { Chord } from '@libraz/libcantus';
1404
- * Chord.parse('C/G').transpose(2).symbol(); // 'D/A'
1405
- * ```
1406
- */
1407
- transpose(semitones) {
1408
- const moved = _chunkBSLTLGM3cjs.transposeChord.call(void 0, this.#given, semitones);
1409
- const rootSpelling = transposeHint(this.#given.rootSpelling, semitones);
1410
- const bassSpelling = transposeHint(this.#given.bassSpelling, semitones);
1411
- if (rootSpelling !== void 0) {
1412
- moved.rootSpelling = rootSpelling;
1413
- }
1414
- if (bassSpelling !== void 0) {
1415
- moved.bassSpelling = bassSpelling;
1416
- }
1417
- return new _Chord(moved, _optionalChain([this, 'access', _14 => _14.#key, 'optionalAccess', _15 => _15.transpose, 'call', _16 => _16(semitones)]));
1418
- }
1419
- /**
1420
- * The named scales that fit over this chord, best fit first, rooted on the
1421
- * chord root.
1422
- *
1423
- * @returns The matching scales.
1424
- */
1425
- scales() {
1426
- return _chunkOL3IHHSJcjs.chordScales.call(void 0, this.#data);
1427
- }
1428
- /**
1429
- * The available tensions (usable non-chord, non-avoid scale tones) of a
1430
- * scale over this chord.
1431
- *
1432
- * @param scaleName A named scale, rooted on the chord root.
1433
- * @returns Tension pitch classes, ascending in [0, 11].
1434
- * @throws If `scaleName` is not a built-in scale.
1435
- */
1436
- tensions(scaleName) {
1437
- return _chunkOL3IHHSJcjs.availableTensions.call(void 0, this.#data, scaleName);
1438
- }
1439
- /**
1440
- * The avoid notes (scale tones a semitone above a chord tone) of a scale
1441
- * over this chord.
1442
- *
1443
- * @param scaleName A named scale, rooted on the chord root.
1444
- * @returns Avoid-note pitch classes, ascending in [0, 11].
1445
- * @throws If `scaleName` is not a built-in scale.
1446
- */
1447
- avoidNotes(scaleName) {
1448
- return _chunkOL3IHHSJcjs.avoidNotes.call(void 0, this.#data, scaleName);
1449
- }
1450
- /**
1451
- * Spell the chord tones with letter names, root first, in the key's spelling.
1452
- *
1453
- * A chord that already knows how its root is spelled — one from
1454
- * `Chord.parse`, or `Chord.of` with a named root — can spell itself with no
1455
- * key at all; a key is only needed to choose a spelling for a bare pitch
1456
- * class.
1457
- *
1458
- * A slash bass outside the chord tones is appended, so the spelled notes
1459
- * are the notes the chord sounds — the same set {@link Chord.pitchClasses}
1460
- * reports.
1461
- *
1462
- * @param key Key providing the spelled tonic; falls back to the carried
1463
- * context, then to the chord's own root spelling.
1464
- * @returns Spelled octave-less notes in the chord's own (tertian) order,
1465
- * with any foreign slash bass last.
1466
- * @throws If no key is given, none is carried, and the chord has no root
1467
- * spelling of its own.
1468
- */
1469
- spell(key) {
1470
- const resolved = _nullishCoalesce(key, () => ( this.#key));
1471
- const data = this.#dataWithKey(resolved);
1472
- let tones;
1473
- if (resolved === void 0) {
1474
- const root = data.rootSpelling;
1475
- if (root === void 0) {
1476
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
1477
- "chord has no key context and no root spelling; pass a Key, attach one with withKey(), or build the chord from a symbol"
1478
- );
1479
- }
1480
- tones = _chunkOL3IHHSJcjs.spellChordFromRoot.call(void 0, data, root);
1481
- } else {
1482
- tones = _chunkOL3IHHSJcjs.spellChord.call(void 0, data, resolved.tonic.data, resolved.scale);
1483
- }
1484
- const bassPc = data.bassPc;
1485
- const bass = data.bassSpelling;
1486
- if (bassPc !== void 0 && bass !== void 0 && !tones.some((tone) => _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, _chunkPI3UXRWKcjs.noteToPitchClass.call(void 0, tone)) === _chunkPI3UXRWKcjs.pitchClassOf.call(void 0, bassPc))) {
1487
- tones = [...tones, { letter: bass.letter, alter: bass.alter }];
1488
- }
1489
- return tones.map((note) => new Note(note));
1490
- }
1491
- /**
1492
- * Start a progression with this chord followed by others, carrying this
1493
- * chord's key context (if any).
1494
- *
1495
- * @param others The chords following this one.
1496
- * @returns The progression.
1497
- */
1498
- progressionTo(...others) {
1499
- return new Progression([this, ...others], this.#key);
1500
- }
1501
- /**
1502
- * Whether another chord has the same root, quality, intervals, and bass.
1503
- * Key context is not compared.
1504
- *
1505
- * @param other The chord to compare.
1506
- * @returns True if the chord data is identical.
1507
- */
1508
- equals(other) {
1509
- const a = this.#data;
1510
- const b = other.data;
1511
- return a.rootPc === b.rootPc && a.quality === b.quality && a.bassPc === b.bassPc && a.intervals.length === b.intervals.length && a.intervals.every((interval, i) => interval === b.intervals[i]);
1512
- }
1513
- /**
1514
- * The plain chord data, for JSON serialization.
1515
- *
1516
- * @returns A copy of the underlying plain chord object.
1517
- */
1518
- toJSON() {
1519
- return copyChord(this.#given);
1520
- }
1521
- /**
1522
- * The chord symbol, so a template literal or a log line reads as the chord.
1523
- *
1524
- * @returns The symbol, e.g. `'Cmaj7'`.
1525
- */
1526
- toString() {
1527
- return this.symbol();
1528
- }
1529
- /** Resolve the key for an analysis method: explicit first, then carried. */
1530
- #resolveKey(key) {
1531
- const resolved = _nullishCoalesce(key, () => ( this.#key));
1532
- if (resolved === void 0) {
1533
- throw new (0, _chunkPI3UXRWKcjs.InvalidInputError)(
1534
- "chord has no key context; pass a Key or attach one with withKey()"
1535
- );
1536
- }
1537
- return resolved;
1538
- }
1539
- };
1540
-
1541
-
1542
-
1543
-
1544
-
1545
-
1546
-
1547
- exports.Interval = Interval; exports.Note = Note; exports.Key = Key; exports.Progression = Progression; exports.Chord = Chord;
1548
- //# sourceMappingURL=chunk-WTAN4ACG.cjs.map