@libraz/libcantus 0.9.3 → 0.9.5

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 (201) hide show
  1. package/README.md +29 -20
  2. package/README_ja.md +268 -0
  3. package/dist/analyze/index.cjs +39 -1947
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +161 -91
  6. package/dist/analyze/index.d.ts +161 -91
  7. package/dist/analyze/index.js +19 -8
  8. package/dist/chunk-2A2QMA74.cjs +261 -0
  9. package/dist/chunk-2A2QMA74.cjs.map +1 -0
  10. package/dist/chunk-2PTFGVD7.js +261 -0
  11. package/dist/chunk-2PTFGVD7.js.map +1 -0
  12. package/dist/chunk-357ZDPVU.cjs +162 -0
  13. package/dist/chunk-357ZDPVU.cjs.map +1 -0
  14. package/dist/chunk-3KX76RLG.js +1548 -0
  15. package/dist/chunk-3KX76RLG.js.map +1 -0
  16. package/dist/chunk-4VZ5MZAM.cjs +889 -0
  17. package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
  18. package/dist/{chunk-C5XYKUVG.js → chunk-7EONYVI5.js} +471 -171
  19. package/dist/chunk-7EONYVI5.js.map +1 -0
  20. package/dist/{chunk-IT2O4XFE.js → chunk-7JEP3MFY.js} +42 -34
  21. package/dist/chunk-7JEP3MFY.js.map +1 -0
  22. package/dist/{chunk-4MHA432G.js → chunk-AID75RN3.js} +572 -250
  23. package/dist/chunk-AID75RN3.js.map +1 -0
  24. package/dist/chunk-BSLTLGM3.cjs +497 -0
  25. package/dist/chunk-BSLTLGM3.cjs.map +1 -0
  26. package/dist/chunk-CU6VG2YM.cjs +109 -0
  27. package/dist/chunk-CU6VG2YM.cjs.map +1 -0
  28. package/dist/{chunk-EH6EM37U.js → chunk-DZMHIGI4.js} +81 -59
  29. package/dist/chunk-DZMHIGI4.js.map +1 -0
  30. package/dist/{chunk-AKLYPWIN.js → chunk-EDOTEABK.js} +45 -19
  31. package/dist/chunk-EDOTEABK.js.map +1 -0
  32. package/dist/{chunk-2ZWMSJH6.js → chunk-HN2OEUN2.js} +7 -4
  33. package/dist/chunk-HN2OEUN2.js.map +1 -0
  34. package/dist/{chunk-MIWO425M.js → chunk-KBTTEX7Q.js} +128 -43
  35. package/dist/chunk-KBTTEX7Q.js.map +1 -0
  36. package/dist/chunk-KQN2ZYND.cjs +724 -0
  37. package/dist/chunk-KQN2ZYND.cjs.map +1 -0
  38. package/dist/chunk-MPTVJIVG.cjs +3176 -0
  39. package/dist/chunk-MPTVJIVG.cjs.map +1 -0
  40. package/dist/chunk-MTMZTMWM.cjs +53 -0
  41. package/dist/chunk-MTMZTMWM.cjs.map +1 -0
  42. package/dist/{chunk-CHMXELDS.js → chunk-NNB67XA2.js} +372 -197
  43. package/dist/chunk-NNB67XA2.js.map +1 -0
  44. package/dist/chunk-OL3IHHSJ.cjs +1204 -0
  45. package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
  46. package/dist/chunk-PI3UXRWK.cjs +528 -0
  47. package/dist/chunk-PI3UXRWK.cjs.map +1 -0
  48. package/dist/chunk-R2VVFAIN.js +528 -0
  49. package/dist/chunk-R2VVFAIN.js.map +1 -0
  50. package/dist/chunk-T64C7ZD7.cjs +1 -0
  51. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  52. package/dist/chunk-WFDXOLUB.cjs +337 -0
  53. package/dist/chunk-WFDXOLUB.cjs.map +1 -0
  54. package/dist/chunk-WTAN4ACG.cjs +1548 -0
  55. package/dist/chunk-WTAN4ACG.cjs.map +1 -0
  56. package/dist/chunk-YFJXS3AU.cjs +33 -0
  57. package/dist/chunk-YFJXS3AU.cjs.map +1 -0
  58. package/dist/{chunk-GOAW4VYL.js → chunk-YUE6TJ4M.js} +315 -145
  59. package/dist/chunk-YUE6TJ4M.js.map +1 -0
  60. package/dist/{chunk-62QZKCJQ.js → chunk-ZGLJM4A4.js} +10 -6
  61. package/dist/chunk-ZGLJM4A4.js.map +1 -0
  62. package/dist/core/index.cjs +140 -647
  63. package/dist/core/index.cjs.map +1 -1
  64. package/dist/core/index.d.cts +313 -41
  65. package/dist/core/index.d.ts +313 -41
  66. package/dist/core/index.js +64 -16
  67. package/dist/{function-DVH4Eusr.d.ts → function-CxixTB02.d.cts} +122 -10
  68. package/dist/{function-DYt4iKsn.d.cts → function-DTCt675z.d.ts} +122 -10
  69. package/dist/generate/index.cjs +28 -4085
  70. package/dist/generate/index.cjs.map +1 -1
  71. package/dist/generate/index.d.cts +374 -66
  72. package/dist/generate/index.d.ts +374 -66
  73. package/dist/generate/index.js +23 -9
  74. package/dist/{index-Jc0Ryi79.d.ts → index-B2Ulxxpo.d.cts} +21 -5
  75. package/dist/{index-CpOsP1y9.d.cts → index-BkYJ0xMq.d.cts} +18 -17
  76. package/dist/{index-CxqBQtH1.d.cts → index-BuaB-ypt.d.ts} +21 -5
  77. package/dist/index-CAN7v-kw.d.ts +199 -0
  78. package/dist/{index-Bt4tOUD9.d.ts → index-COw1Ky9g.d.ts} +93 -8
  79. package/dist/{index-Dikst44v.d.cts → index-Cfx8xgH0.d.ts} +14 -11
  80. package/dist/{index-DxvARIP2.d.ts → index-DRtIhlJj.d.ts} +18 -17
  81. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  82. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  83. package/dist/index-alrRninU.d.cts +199 -0
  84. package/dist/{index-BNlGP86A.d.cts → index-oTQbYYIs.d.cts} +93 -8
  85. package/dist/index-tR9jTn26.d.cts +275 -0
  86. package/dist/index-tR9jTn26.d.ts +275 -0
  87. package/dist/{index-Bg5dYMmZ.d.ts → index-x2vcsduE.d.cts} +14 -11
  88. package/dist/index.cjs +429 -7383
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +15 -15
  91. package/dist/index.d.ts +15 -15
  92. package/dist/index.js +121 -27
  93. package/dist/model/index.cjs +8 -2562
  94. package/dist/model/index.cjs.map +1 -1
  95. package/dist/model/index.d.cts +471 -81
  96. package/dist/model/index.d.ts +471 -81
  97. package/dist/model/index.js +6 -7
  98. package/dist/styled-Bl0ZK28h.d.ts +543 -0
  99. package/dist/styled-Crv6SA4S.d.cts +543 -0
  100. package/dist/theory/index.cjs +172 -1822
  101. package/dist/theory/index.cjs.map +1 -1
  102. package/dist/theory/index.d.cts +151 -233
  103. package/dist/theory/index.d.ts +151 -233
  104. package/dist/theory/index.js +30 -7
  105. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  106. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  107. package/package.json +34 -13
  108. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  109. package/dist/chunk-4MHA432G.js.map +0 -1
  110. package/dist/chunk-62QZKCJQ.js.map +0 -1
  111. package/dist/chunk-AKLYPWIN.js.map +0 -1
  112. package/dist/chunk-C5XYKUVG.js.map +0 -1
  113. package/dist/chunk-CHMXELDS.js.map +0 -1
  114. package/dist/chunk-DYWFNC4A.js +0 -152
  115. package/dist/chunk-DYWFNC4A.js.map +0 -1
  116. package/dist/chunk-EH6EM37U.js.map +0 -1
  117. package/dist/chunk-FBQK3ZQN.js +0 -131
  118. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  119. package/dist/chunk-GOAW4VYL.js.map +0 -1
  120. package/dist/chunk-I5R7ZBJS.js +0 -202
  121. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  122. package/dist/chunk-IT2O4XFE.js.map +0 -1
  123. package/dist/chunk-MIWO425M.js.map +0 -1
  124. package/dist/chunk-SGU7SPLK.js +0 -924
  125. package/dist/chunk-SGU7SPLK.js.map +0 -1
  126. package/dist/index-CGMZNCx6.d.cts +0 -140
  127. package/dist/index-CGMZNCx6.d.ts +0 -140
  128. package/dist/index-CnZCo29u.d.cts +0 -108
  129. package/dist/index-JnF4v9bx.d.ts +0 -108
  130. package/dist/styled-DZ7GjShb.d.ts +0 -282
  131. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  132. package/src/analyze/arrange/index.ts +0 -21
  133. package/src/analyze/arrange/internal.ts +0 -127
  134. package/src/analyze/arrange/tension.ts +0 -199
  135. package/src/analyze/arrange/tracks.ts +0 -312
  136. package/src/analyze/detect/index.ts +0 -272
  137. package/src/analyze/functional/borrowed.ts +0 -84
  138. package/src/analyze/functional/cadence.ts +0 -66
  139. package/src/analyze/functional/function.ts +0 -184
  140. package/src/analyze/functional/index.ts +0 -23
  141. package/src/analyze/functional/internal.ts +0 -29
  142. package/src/analyze/functional/roman.ts +0 -402
  143. package/src/analyze/index.ts +0 -49
  144. package/src/analyze/timeline/index.ts +0 -451
  145. package/src/analyze/voice/index.ts +0 -339
  146. package/src/core/event-index/index.ts +0 -104
  147. package/src/core/index.ts +0 -61
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -302
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -76
  152. package/src/core/tuning/index.ts +0 -186
  153. package/src/core/types.ts +0 -29
  154. package/src/core/validation/index.ts +0 -119
  155. package/src/generate/bass/index.ts +0 -373
  156. package/src/generate/countermelody/index.ts +0 -470
  157. package/src/generate/drums/beat.ts +0 -266
  158. package/src/generate/drums/euclid.ts +0 -65
  159. package/src/generate/drums/fills.ts +0 -356
  160. package/src/generate/drums/ghost.ts +0 -119
  161. package/src/generate/drums/hihat.ts +0 -285
  162. package/src/generate/drums/hit.ts +0 -31
  163. package/src/generate/drums/index.ts +0 -361
  164. package/src/generate/drums/internal.ts +0 -257
  165. package/src/generate/drums/kick.ts +0 -160
  166. package/src/generate/drums/percussion.ts +0 -121
  167. package/src/generate/drums/rng.ts +0 -7
  168. package/src/generate/drums/swing.ts +0 -71
  169. package/src/generate/groove/index.ts +0 -351
  170. package/src/generate/harmonize/index.ts +0 -451
  171. package/src/generate/index.ts +0 -58
  172. package/src/generate/motif/index.ts +0 -422
  173. package/src/generate/progression/index.ts +0 -362
  174. package/src/generate/reharmony/index.ts +0 -323
  175. package/src/generate/rhythm/index.ts +0 -175
  176. package/src/index.ts +0 -13
  177. package/src/model/chord.ts +0 -442
  178. package/src/model/index.ts +0 -15
  179. package/src/model/interval.ts +0 -82
  180. package/src/model/key.ts +0 -266
  181. package/src/model/note.ts +0 -176
  182. package/src/model/progression.ts +0 -161
  183. package/src/model/shared.ts +0 -12
  184. package/src/theory/chord/index.ts +0 -375
  185. package/src/theory/chordscale/index.ts +0 -374
  186. package/src/theory/counterpoint/index.ts +0 -230
  187. package/src/theory/harmony/index.ts +0 -77
  188. package/src/theory/index.ts +0 -112
  189. package/src/theory/safety/index.ts +0 -488
  190. package/src/theory/scale/degrees.ts +0 -123
  191. package/src/theory/scale/index.ts +0 -28
  192. package/src/theory/scale/key.ts +0 -56
  193. package/src/theory/scale/masks.ts +0 -153
  194. package/src/theory/spelling/index.ts +0 -249
  195. package/src/theory/symbol/index.ts +0 -268
  196. package/src/theory/voicing/index.ts +0 -5
  197. package/src/theory/voicing/internal.ts +0 -168
  198. package/src/theory/voicing/leading.ts +0 -112
  199. package/src/theory/voicing/satb.ts +0 -216
  200. package/src/theory/voicing/styled.ts +0 -199
  201. package/tsconfig.json +0 -23
@@ -8,27 +8,215 @@ import {
8
8
  isScaleTone,
9
9
  majorKey,
10
10
  makeChord,
11
- minorKey,
12
11
  scaleTonesInDegreeOrder
13
- } from "./chunk-MIWO425M.js";
12
+ } from "./chunk-KBTTEX7Q.js";
14
13
  import {
14
+ InvalidInputError,
15
15
  assertFiniteNumber,
16
- assertGenerationBudget
17
- } from "./chunk-FBQK3ZQN.js";
16
+ assertGenerationBudget,
17
+ assertInteger,
18
+ assertMidiPitch,
19
+ assertNoteEvents,
20
+ pitchClassOf
21
+ } from "./chunk-R2VVFAIN.js";
18
22
 
19
- // src/analyze/functional/internal.ts
20
- function mod12(n) {
21
- return (n % 12 + 12) % 12;
23
+ // src/analyze/detect/index.ts
24
+ function uniquePitchClasses(pitches) {
25
+ return [...new Set(pitches.map(pitchClassOf))].sort((a, b) => a - b);
26
+ }
27
+ function assertPitches(pitches, name, budget = void 0) {
28
+ assertGenerationBudget(pitches.length, name, budget);
29
+ for (let index = 0; index < pitches.length; index += 1) {
30
+ assertMidiPitch(pitches[index] ?? Number.NaN, `pitches[${index}]`);
31
+ }
32
+ }
33
+ function detectChord(pitches, opts = {}) {
34
+ assertPitches(pitches, "chord detection pitches");
35
+ if (opts.bassPc !== void 0) assertFiniteNumber(opts.bassPc, "bassPc");
36
+ const input = uniquePitchClasses(pitches);
37
+ if (input.length === 0) {
38
+ return [];
39
+ }
40
+ const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
41
+ const bassPc = opts.bassPc !== void 0 ? pitchClassOf(opts.bassPc) : inputKind === "midi" ? pitchClassOf(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
42
+ const inputSet = new Set(input);
43
+ const matches = [];
44
+ const qualities = chordQualities();
45
+ const toneCounts = new Map(
46
+ qualities.map((quality) => [quality, chordPitchClasses(makeChord(0, quality)).length])
47
+ );
48
+ for (const rootPc of input) {
49
+ for (const quality of qualities) {
50
+ const chord = makeChord(rootPc, quality);
51
+ const tones = chordPitchClasses(chord);
52
+ const toneSet = new Set(tones);
53
+ const missingPcs = tones.filter((pc) => !inputSet.has(pc));
54
+ const fifthPc = pitchClassOf(rootPc + 7);
55
+ if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
56
+ continue;
57
+ }
58
+ const extraPcs = input.filter((pc) => !toneSet.has(pc));
59
+ const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => pitchClassOf(rootPc + iv) === bassPc);
60
+ const inversion = bassPc === void 0 || bassIndex < 0 ? null : bassIndex;
61
+ const match = {
62
+ rootPc,
63
+ quality,
64
+ missingPcs,
65
+ extraPcs,
66
+ exact: extraPcs.length === 0 && missingPcs.length === 0,
67
+ inversion
68
+ };
69
+ if (bassPc !== void 0 && bassPc !== rootPc) {
70
+ match.bassPc = bassPc;
71
+ }
72
+ matches.push(match);
73
+ }
74
+ }
75
+ matches.sort((a, b) => {
76
+ if (a.extraPcs.length !== b.extraPcs.length) {
77
+ return a.extraPcs.length - b.extraPcs.length;
78
+ }
79
+ if (a.missingPcs.length !== b.missingPcs.length) {
80
+ return a.missingPcs.length - b.missingPcs.length;
81
+ }
82
+ if (a.inversion === 0 !== (b.inversion === 0)) {
83
+ return a.inversion === 0 ? -1 : 1;
84
+ }
85
+ const aSize = toneCounts.get(a.quality) ?? 0;
86
+ const bSize = toneCounts.get(b.quality) ?? 0;
87
+ return bSize - aSize;
88
+ });
89
+ return matches;
90
+ }
91
+ function detectChordBest(pitches, opts = {}) {
92
+ const best = detectChord(pitches, opts)[0];
93
+ if (!best) {
94
+ return null;
95
+ }
96
+ return makeChord(best.rootPc, best.quality, best.bassPc);
97
+ }
98
+ var MINOR_VARIANTS = [
99
+ { variant: "natural", mask: NATURAL_MINOR_MASK },
100
+ { variant: "harmonic", mask: HARMONIC_MINOR_MASK },
101
+ { variant: "melodic", mask: MELODIC_MINOR_MASK }
102
+ ];
103
+ var MAJOR_VARIANTS = [{ variant: "major", mask: MAJOR_MASK }];
104
+ function detectKey(pitches, opts = {}) {
105
+ assertPitches(pitches, "key detection pitches", opts.budget);
106
+ const weights = opts.weights;
107
+ if (weights !== void 0 && weights.length !== pitches.length) {
108
+ throw new InvalidInputError("weights must have one entry per pitch");
109
+ }
110
+ const counts = /* @__PURE__ */ new Map();
111
+ let total = 0;
112
+ for (let index = 0; index < pitches.length; index += 1) {
113
+ const weight = weights === void 0 ? 1 : weights[index] ?? 0;
114
+ assertFiniteNumber(weight, `weights[${index}]`);
115
+ if (weight <= 0) {
116
+ continue;
117
+ }
118
+ const pc = pitchClassOf(pitches[index] ?? 0);
119
+ counts.set(pc, (counts.get(pc) ?? 0) + weight);
120
+ total += weight;
121
+ }
122
+ if (total === 0) {
123
+ return [];
124
+ }
125
+ const input = [...counts.keys()];
126
+ const results = [];
127
+ for (let tonic = 0; tonic < 12; tonic += 1) {
128
+ for (const mode of ["major", "minor"]) {
129
+ const variants = mode === "major" ? MAJOR_VARIANTS : MINOR_VARIANTS;
130
+ let inScale = 0;
131
+ let weighted = Number.NEGATIVE_INFINITY;
132
+ let best = variants[0];
133
+ for (const candidate of variants) {
134
+ let variantInScale = 0;
135
+ let variantWeighted = 0;
136
+ for (const pc of input) {
137
+ const offset = (pc - tonic + 12) % 12;
138
+ if (candidate.mask >> offset & 1) {
139
+ variantInScale += 1;
140
+ }
141
+ }
142
+ for (const [pc, count] of counts) {
143
+ const offset = (pc - tonic + 12) % 12;
144
+ if (candidate.mask >> offset & 1) {
145
+ variantWeighted += count;
146
+ }
147
+ }
148
+ if (variantWeighted > weighted) {
149
+ weighted = variantWeighted;
150
+ inScale = variantInScale;
151
+ best = candidate;
152
+ }
153
+ }
154
+ weighted += (counts.get(tonic) ?? 0) * 0.5;
155
+ results.push({
156
+ key: { rootPc: tonic, modeMask12: best.mask },
157
+ mode,
158
+ variant: best.variant,
159
+ fit: inScale / input.length,
160
+ score: weighted / total
161
+ });
162
+ }
163
+ }
164
+ results.sort((a, b) => b.score - a.score);
165
+ return results;
22
166
  }
167
+ var DEFAULT_VELOCITY = 100;
168
+ function detectKeyFromNotes(notes, opts = {}) {
169
+ assertNoteEvents(notes, "key detection notes", {
170
+ allowNonPositiveDuration: true,
171
+ budget: opts.budget
172
+ });
173
+ const sounding = notes.filter((note) => note.durationBeat > 0);
174
+ return detectKey(
175
+ sounding.map((note) => note.pitch),
176
+ {
177
+ weights: sounding.map((note) => note.durationBeat * (note.velocity ?? DEFAULT_VELOCITY)),
178
+ budget: opts.budget
179
+ }
180
+ );
181
+ }
182
+ function detectKeyBest(pitches, opts = {}) {
183
+ return detectKey(pitches, opts)[0] ?? null;
184
+ }
185
+
186
+ // src/analyze/functional/internal.ts
23
187
  function degreeRootPc(degreeNumber, key) {
24
188
  const tones = scaleTonesInDegreeOrder(key);
25
189
  if (tones.length === 0) {
26
- return mod12(key.rootPc);
190
+ return pitchClassOf(key.rootPc);
191
+ }
192
+ const index = ((degreeNumber - 1) % tones.length + tones.length) % tones.length;
193
+ return tones[index] ?? pitchClassOf(key.rootPc);
194
+ }
195
+ function loweredDegrees(key) {
196
+ const tones = scaleTonesInDegreeOrder(key);
197
+ const out = /* @__PURE__ */ new Set();
198
+ if (tones.length !== 7) {
199
+ return out;
200
+ }
201
+ const major = scaleTonesInDegreeOrder({ rootPc: pitchClassOf(key.rootPc), modeMask12: MAJOR_MASK });
202
+ for (let index = 0; index < 7; index += 1) {
203
+ const tone = tones[index];
204
+ const majorTone = major[index];
205
+ if (tone !== void 0 && majorTone !== void 0 && pitchClassOf(majorTone - tone) === 1) {
206
+ out.add(index + 1);
207
+ }
27
208
  }
28
- return tones[(degreeNumber - 1) % tones.length] ?? mod12(key.rootPc);
209
+ return out;
210
+ }
211
+ function romanReference(key) {
212
+ return scaleTonesInDegreeOrder(key).length === 7 ? key : majorKey(pitchClassOf(key.rootPc));
29
213
  }
30
214
  function isNeapolitan(chord, key) {
31
- return mod12(chord.rootPc - key.rootPc) === 1 && chord.quality === "maj";
215
+ return pitchClassOf(chord.rootPc - key.rootPc) === 1 && chord.quality === "maj";
216
+ }
217
+ function isAppliedDominantSonority(chord) {
218
+ const has = (semitones) => chord.intervals.some((i) => pitchClassOf(i) === semitones);
219
+ return chord.quality === "maj" || has(4) && has(10);
32
220
  }
33
221
 
34
222
  // src/analyze/functional/roman.ts
@@ -80,6 +268,9 @@ var ROMAN_STYLE = {
80
268
  dim7: { lower: true, suffix: "o7" },
81
269
  m7b5: { lower: true, suffix: "\xF87" },
82
270
  minMaj7: { lower: true, suffix: "maj7" },
271
+ minMaj9: { lower: true, suffix: "maj9" },
272
+ minMaj11: { lower: true, suffix: "maj11" },
273
+ minMaj13: { lower: true, suffix: "maj13" },
83
274
  aug7: { lower: false, suffix: "+7" },
84
275
  augMaj7: { lower: false, suffix: "+maj7" },
85
276
  majb5: { lower: false, suffix: "b5" },
@@ -99,7 +290,17 @@ var ROMAN_STYLE = {
99
290
  "7b13": { lower: false, suffix: "7b13" },
100
291
  "11": { lower: false, suffix: "11" },
101
292
  "13": { lower: false, suffix: "13" },
102
- "5": { lower: false, suffix: "5" }
293
+ "5": { lower: false, suffix: "5" },
294
+ "7sus4": { lower: false, suffix: "7sus4" },
295
+ "7b5": { lower: false, suffix: "7b5" },
296
+ "7alt": { lower: false, suffix: "7alt" },
297
+ "13b9": { lower: false, suffix: "13b9" },
298
+ maj13: { lower: false, suffix: "maj13" },
299
+ "maj7#11": { lower: false, suffix: "maj7#11" },
300
+ min11: { lower: true, suffix: "11" },
301
+ min13: { lower: true, suffix: "13" },
302
+ minAdd9: { lower: true, suffix: "add9" },
303
+ "min6/9": { lower: true, suffix: "69" }
103
304
  };
104
305
  var SUFFIX_QUALITY = new Map(
105
306
  Object.entries(ROMAN_STYLE).map(
@@ -130,24 +331,33 @@ function parseInversion(figures) {
130
331
  function parseSimpleRoman(text, key) {
131
332
  const match = /^([b#]?)([iIvV]+)(.*)$/.exec(text.trim());
132
333
  if (!match) {
133
- throw new Error(`Invalid Roman numeral: ${text}`);
334
+ throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
335
+ }
336
+ const glyphText = match[2] ?? "";
337
+ if (glyphText !== glyphText.toUpperCase() && glyphText !== glyphText.toLowerCase()) {
338
+ throw new InvalidInputError(`Roman numeral must use one case: ${text}`);
134
339
  }
135
340
  const accidental = match[1] === "b" ? -1 : match[1] === "#" ? 1 : 0;
136
- const glyph = (match[2] ?? "").toUpperCase();
341
+ const glyph = glyphText.toUpperCase();
137
342
  const degreeNumber = ROMAN_TO_DEGREE[glyph];
138
343
  if (degreeNumber === void 0) {
139
- throw new Error(`Invalid Roman numeral: ${text}`);
344
+ throw new InvalidInputError(`Invalid Roman numeral: ${text}`);
140
345
  }
141
- const isUpper = (match[2] ?? "")[0] === (match[2] ?? "")[0]?.toUpperCase();
346
+ const isUpper = glyphText[0] === glyphText[0]?.toUpperCase();
142
347
  const suffix = match[3] ?? "";
143
- const rootPc = mod12(degreeRootPc(degreeNumber, key) + accidental);
348
+ const frame = romanReference(key);
349
+ const redundantFlat = accidental === -1 && loweredDegrees(frame).has(degreeNumber);
350
+ const diatonicRootPc = pitchClassOf(
351
+ degreeRootPc(degreeNumber, frame) + (redundantFlat ? 0 : accidental)
352
+ );
353
+ const rootForQuality = (quality2) => accidental === 0 && degreeNumber === 7 && loweredDegrees(frame).has(7) && (quality2 === "dim" || quality2 === "dim7") ? pitchClassOf(diatonicRootPc + 1) : diatonicRootPc;
144
354
  const canonical = SUFFIX_QUALITY.get(`${isUpper ? "u" : "l"}:${suffix}`);
145
355
  if (canonical !== void 0) {
146
- return { rootPc, quality: canonical, inversion: 0 };
356
+ return { rootPc: rootForQuality(canonical), quality: canonical, inversion: 0 };
147
357
  }
148
358
  const parsedSuffix = /^(o|°|dim|ø|\+|aug)?(maj7|M7)?(65|64|43|42|7|6|2)?$/.exec(suffix);
149
359
  if (!parsedSuffix) {
150
- throw new Error(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
360
+ throw new InvalidInputError(`Unsupported suffix "${suffix}" in Roman numeral: ${text}`);
151
361
  }
152
362
  const qualityMarker = parsedSuffix[1] ?? "";
153
363
  const isDim = qualityMarker === "o" || qualityMarker === "\xB0" || qualityMarker === "dim";
@@ -156,7 +366,9 @@ function parseSimpleRoman(text, key) {
156
366
  const explicitMaj7 = parsedSuffix[2] !== void 0;
157
367
  const figures = parsedSuffix[3] ?? "";
158
368
  if (figures !== "" && !INVERSION_FIGURES.has(figures)) {
159
- throw new Error(`Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`);
369
+ throw new InvalidInputError(
370
+ `Unsupported figured-bass or extension "${figures}" in Roman numeral: ${text}`
371
+ );
160
372
  }
161
373
  const { inversion, seventh } = parseInversion(figures);
162
374
  const hasSeventh = explicitMaj7 || seventh || isHalfDim;
@@ -180,17 +392,17 @@ function parseSimpleRoman(text, key) {
180
392
  } else {
181
393
  quality = base;
182
394
  }
183
- return { rootPc, quality, inversion };
395
+ return { rootPc: rootForQuality(quality), quality, inversion };
184
396
  }
185
397
  function chordFromParsed(parsed) {
186
398
  const chord = makeChord(parsed.rootPc, parsed.quality);
187
399
  if (parsed.inversion > 0 && parsed.inversion < chord.intervals.length) {
188
- chord.bassPc = mod12(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));
400
+ chord.bassPc = pitchClassOf(parsed.rootPc + (chord.intervals[parsed.inversion] ?? 0));
189
401
  }
190
402
  return chord;
191
403
  }
192
404
  function romanToChord(text, key) {
193
- const trimmed = text.trim();
405
+ const trimmed = text.trim().replace(/(\d)\/(?=\d)/g, "$1");
194
406
  const slash = trimmed.indexOf("/");
195
407
  if (slash >= 0) {
196
408
  const applied = trimmed.slice(0, slash);
@@ -205,26 +417,32 @@ function romanStyle(quality) {
205
417
  return ROMAN_STYLE[quality];
206
418
  }
207
419
  function romanSpelling(rootPc, key) {
208
- const tones = scaleTonesInDegreeOrder(key);
209
- const diatonic = tones.indexOf(mod12(rootPc));
420
+ const tones = scaleTonesInDegreeOrder(romanReference(key));
421
+ const diatonic = tones.indexOf(pitchClassOf(rootPc));
210
422
  if (diatonic >= 0) {
211
423
  return { degreeNumber: diatonic + 1, accidental: "" };
212
424
  }
425
+ const lowered = loweredDegrees(romanReference(key));
426
+ for (let i = 0; i < tones.length; i += 1) {
427
+ if (lowered.has(i + 1) && pitchClassOf((tones[i] ?? 0) + 1) === pitchClassOf(rootPc)) {
428
+ return { degreeNumber: i + 1, accidental: "#" };
429
+ }
430
+ }
213
431
  for (let i = 0; i < tones.length; i += 1) {
214
432
  const degreeNumber2 = i + 1;
215
433
  if (degreeNumber2 === 1 || degreeNumber2 === 5) {
216
434
  continue;
217
435
  }
218
- if (mod12((tones[i] ?? 0) - 1) === mod12(rootPc)) {
436
+ if (pitchClassOf((tones[i] ?? 0) - 1) === pitchClassOf(rootPc)) {
219
437
  return { degreeNumber: degreeNumber2, accidental: "b" };
220
438
  }
221
439
  }
222
440
  for (let i = 0; i < tones.length; i += 1) {
223
- if (mod12((tones[i] ?? 0) + 1) === mod12(rootPc)) {
441
+ if (pitchClassOf((tones[i] ?? 0) + 1) === pitchClassOf(rootPc)) {
224
442
  return { degreeNumber: i + 1, accidental: "#" };
225
443
  }
226
444
  }
227
- const [degreeNumber, accidental] = OFFSET_SPELLING[mod12(rootPc - key.rootPc)] ?? [1, ""];
445
+ const [degreeNumber, accidental] = OFFSET_SPELLING[pitchClassOf(rootPc - key.rootPc)] ?? [1, ""];
228
446
  return { degreeNumber, accidental };
229
447
  }
230
448
  function baseMarker(quality) {
@@ -258,14 +476,59 @@ var SEVENTH_FIGURE_QUALITIES = /* @__PURE__ */ new Set([
258
476
  "aug7",
259
477
  "augMaj7"
260
478
  ]);
261
- function chordToRoman(chord, key) {
262
- const { degreeNumber, accidental } = romanSpelling(chord.rootPc, key);
479
+ function numeralFor(degreeNumber, lower) {
480
+ const glyph = ROMAN[degreeNumber - 1];
481
+ if (glyph === void 0) {
482
+ throw new InvalidInputError(`no Roman numeral for scale degree ${degreeNumber}`);
483
+ }
484
+ return lower ? glyph.toLowerCase() : glyph;
485
+ }
486
+ function isDiatonicChord(chord, key) {
487
+ return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
488
+ }
489
+ var LEADING_TONE_QUALITIES = /* @__PURE__ */ new Set(["dim", "dim7", "m7b5"]);
490
+ function degreeTriad(tones, index) {
491
+ const root = tones[index] ?? 0;
492
+ return {
493
+ third: pitchClassOf((tones[(index + 2) % tones.length] ?? 0) - root),
494
+ fifth: pitchClassOf((tones[(index + 4) % tones.length] ?? 0) - root)
495
+ };
496
+ }
497
+ function appliedTarget(chord, key) {
498
+ const dominant = isAppliedDominantSonority(chord);
499
+ const leadingTone = LEADING_TONE_QUALITIES.has(chord.quality);
500
+ if (!dominant && !leadingTone) {
501
+ return null;
502
+ }
503
+ const tones = scaleTonesInDegreeOrder(romanReference(key));
504
+ for (let index = 1; index < tones.length; index += 1) {
505
+ const targetRoot = tones[index];
506
+ if (targetRoot === void 0) {
507
+ continue;
508
+ }
509
+ const triad = degreeTriad(tones, index);
510
+ const expectedRoot = pitchClassOf(targetRoot + (dominant ? 7 : -1));
511
+ if (expectedRoot === pitchClassOf(chord.rootPc)) {
512
+ return { degreeNumber: index + 1, rootPc: targetRoot, lower: triad.third === 3 };
513
+ }
514
+ }
515
+ return null;
516
+ }
517
+ function chordToRoman(chord, key, opts = {}) {
518
+ if (opts.applied === true && !isDiatonicChord(chord, key)) {
519
+ const target = appliedTarget(chord, key);
520
+ if (target !== null) {
521
+ const local = chordToRoman(chord, majorKey(target.rootPc));
522
+ return `${local}/${numeralFor(target.degreeNumber, target.lower)}`;
523
+ }
524
+ }
525
+ const { degreeNumber, accidental: spelledAccidental } = romanSpelling(chord.rootPc, key);
263
526
  const { lower, suffix } = romanStyle(chord.quality);
264
- const numeral = ROMAN[degreeNumber - 1] ?? "I";
265
- const cased = lower ? numeral.toLowerCase() : numeral;
527
+ const cased = numeralFor(degreeNumber, lower);
528
+ const accidental = spelledAccidental === "#" && degreeNumber === 7 && loweredDegrees(romanReference(key)).has(7) && (chord.quality === "dim" || chord.quality === "dim7") ? "" : spelledAccidental;
266
529
  let inversion = 0;
267
530
  if (chord.bassPc !== void 0) {
268
- const idx = chord.intervals.findIndex((iv) => mod12(chord.rootPc + iv) === chord.bassPc);
531
+ const idx = chord.intervals.findIndex((iv) => pitchClassOf(chord.rootPc + iv) === chord.bassPc);
269
532
  if (idx > 0) {
270
533
  inversion = idx;
271
534
  }
@@ -280,14 +543,20 @@ function chordToRoman(chord, key) {
280
543
  }
281
544
 
282
545
  // src/analyze/functional/borrowed.ts
283
- function isHarmonicMinorAlteration(chord, key) {
284
- return isMinorKey(key) && isDiatonic(chord, { rootPc: mod12(key.rootPc), modeMask12: HARMONIC_MINOR_MASK });
546
+ function isMinorScaleAlteration(chord, key) {
547
+ if (!isMinorKey(key)) return false;
548
+ const alteredKey = { rootPc: pitchClassOf(key.rootPc), modeMask12: HARMONIC_MINOR_MASK };
549
+ if (isDiatonic(chord, alteredKey)) return true;
550
+ return pitchClassOf(chord.rootPc - key.rootPc) === 11 && isDiatonic(chord, { rootPc: pitchClassOf(key.rootPc), modeMask12: MELODIC_MINOR_MASK });
285
551
  }
286
552
  function isBorrowedChord(chord, key) {
287
553
  if (isDiatonic(chord, key)) {
288
554
  return false;
289
555
  }
290
- if (isHarmonicMinorAlteration(chord, key)) {
556
+ if (isNeapolitan(chord, key)) {
557
+ return true;
558
+ }
559
+ if (isMinorScaleAlteration(chord, key)) {
291
560
  return false;
292
561
  }
293
562
  return isDiatonic(chord, parallelKey(key));
@@ -296,11 +565,9 @@ function borrowedSource(chord, key) {
296
565
  if (isDiatonic(chord, key)) {
297
566
  return null;
298
567
  }
299
- if (isNeapolitan(chord, key)) {
300
- return "neapolitan";
301
- }
568
+ if (isNeapolitan(chord, key)) return "neapolitan";
302
569
  if (isBorrowedChord(chord, key)) {
303
- return isMinorKey(key) ? "parallel-major" : "parallel-minor";
570
+ return isMinorKey(key) ? "parallelMajor" : "parallelMinor";
304
571
  }
305
572
  return null;
306
573
  }
@@ -332,20 +599,56 @@ var FUNCTION_BY_OFFSET = [
332
599
  "dominant"
333
600
  // 11 vii
334
601
  ];
602
+ var MINOR_FUNCTION_BY_OFFSET = [
603
+ "tonic",
604
+ "subdominant",
605
+ "subdominant",
606
+ "tonic",
607
+ "dominant",
608
+ "subdominant",
609
+ "dominant",
610
+ "dominant",
611
+ "tonic",
612
+ "tonic",
613
+ "subdominant",
614
+ "dominant"
615
+ ];
335
616
  function isMinorKey(key) {
336
617
  const hasMinorThird = key.modeMask12 >> 3 & 1;
337
618
  const hasMajorThird2 = key.modeMask12 >> 4 & 1;
338
619
  return Boolean(hasMinorThird) && !hasMajorThird2;
339
620
  }
340
621
  function functionOf(chord, key) {
341
- return FUNCTION_BY_OFFSET[mod12(chord.rootPc - key.rootPc)] ?? "tonic";
622
+ const offset = pitchClassOf(chord.rootPc - key.rootPc);
623
+ if (isNeapolitan(chord, key)) {
624
+ return "subdominant";
625
+ }
626
+ if (isAppliedDominant(chord, key)) {
627
+ return "dominant";
628
+ }
629
+ if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
630
+ return "subdominant";
631
+ }
632
+ const functions = isMinorKey(key) ? MINOR_FUNCTION_BY_OFFSET : FUNCTION_BY_OFFSET;
633
+ return functions[offset] ?? "tonic";
634
+ }
635
+ function isAppliedDominant(chord, key) {
636
+ if (isDiatonic(chord, key)) {
637
+ return false;
638
+ }
639
+ const root = pitchClassOf(chord.rootPc);
640
+ const resolvesTo = (step) => isScaleTone(pitchClassOf(root + step), key);
641
+ if (isAppliedDominantSonority(chord)) {
642
+ return chord.quality === "maj" ? resolvesTo(5) : resolvesTo(5) || resolvesTo(11);
643
+ }
644
+ return isDiminishedQuality(chord.quality) && resolvesTo(1);
342
645
  }
343
646
  function isDiatonic(chord, key) {
344
647
  return chordPitchClasses(chord).every((pc) => isScaleTone(pc, key));
345
648
  }
346
649
  function parallelKey(key) {
347
650
  return {
348
- rootPc: mod12(key.rootPc),
651
+ rootPc: pitchClassOf(key.rootPc),
349
652
  modeMask12: isMinorKey(key) ? MAJOR_MASK : NATURAL_MINOR_MASK
350
653
  };
351
654
  }
@@ -353,184 +656,59 @@ function isDiminishedQuality(quality) {
353
656
  return quality === "dim" || quality === "dim7" || quality === "m7b5";
354
657
  }
355
658
  function hasMajorThird(chord) {
356
- return chord.intervals.some((interval) => mod12(interval) === 4);
357
- }
358
- function qualityAwareFunction(chord, key) {
359
- const offset = mod12(chord.rootPc - key.rootPc);
360
- if (isDiminishedQuality(chord.quality) && (offset === 11 || offset === 6)) {
361
- return "dominant";
362
- }
363
- if (isNeapolitan(chord, key)) {
364
- return "subdominant";
365
- }
366
- if (!isMinorKey(key) && hasMajorThird(chord) && (offset === 8 || offset === 10)) {
367
- return "subdominant";
368
- }
369
- return functionOf(chord, key);
659
+ return chord.intervals.some((interval) => pitchClassOf(interval) === 4);
370
660
  }
371
- function analyzeChord(chord, key) {
661
+ function analyzeChord(chord, key, opts = {}) {
372
662
  const source = borrowedSource(chord, key);
373
663
  return {
374
- function: qualityAwareFunction(chord, key),
664
+ function: functionOf(chord, key),
375
665
  borrowed: source !== null,
376
666
  source,
377
- roman: chordToRoman(chord, key)
667
+ roman: chordToRoman(chord, key, opts)
378
668
  };
379
669
  }
380
670
  function secondaryDominant(targetDegree, key) {
671
+ const degreeCount = scaleTonesInDegreeOrder(key).length;
672
+ assertInteger(targetDegree, "targetDegree", 0, degreeCount - 1);
381
673
  const targetRoot = degreeRootPc(targetDegree + 1, key);
382
- return makeChord(mod12(targetRoot + 7), "dom7");
674
+ return makeChord(pitchClassOf(targetRoot + 7), "dom7");
383
675
  }
384
676
 
385
677
  // src/analyze/functional/cadence.ts
678
+ function hasDominantThird(chord) {
679
+ return chord.intervals.some((interval) => pitchClassOf(interval) === 4);
680
+ }
681
+ function isLeadingToneDiminished(chord) {
682
+ return chord.quality === "dim" || chord.quality === "dim7" || chord.quality === "m7b5";
683
+ }
386
684
  function detectCadence(from, to, key) {
387
- const tonic = mod12(key.rootPc);
388
- const fromOffset = mod12(from.rootPc - tonic);
389
- const toOffset = mod12(to.rootPc - tonic);
685
+ const tonic = pitchClassOf(key.rootPc);
686
+ const fromOffset = pitchClassOf(from.rootPc - tonic);
687
+ const toOffset = pitchClassOf(to.rootPc - tonic);
390
688
  const deceptiveTargets = isMinorKey(key) ? [8] : [8, 9];
391
- if (fromOffset === 7 && toOffset === 0) {
689
+ const dominant = fromOffset === 7 && hasDominantThird(from);
690
+ const leadingTone = fromOffset === 11 && isLeadingToneDiminished(from);
691
+ if ((dominant || leadingTone) && toOffset === 0) {
392
692
  return "authentic";
393
693
  }
394
694
  if (fromOffset === 5 && toOffset === 0) {
395
695
  return "plagal";
396
696
  }
397
- if (fromOffset === 7 && deceptiveTargets.includes(toOffset)) {
697
+ if (dominant && deceptiveTargets.includes(toOffset)) {
398
698
  return "deceptive";
399
699
  }
400
- if (toOffset === 7 && fromOffset !== 7) {
700
+ if (toOffset === 7 && hasDominantThird(to) && fromOffset !== 7) {
401
701
  return "half";
402
702
  }
403
703
  return null;
404
704
  }
405
705
 
406
- // src/analyze/detect/index.ts
407
- function pitchClass(value) {
408
- return (Math.trunc(value) % 12 + 12) % 12;
409
- }
410
- function uniquePitchClasses(pitches) {
411
- return [...new Set(pitches.map(pitchClass))].sort((a, b) => a - b);
412
- }
413
- function assertPitches(pitches) {
414
- assertGenerationBudget(pitches.length, "detection pitches");
415
- for (let index = 0; index < pitches.length; index += 1) {
416
- assertFiniteNumber(pitches[index] ?? Number.NaN, `pitches[${index}]`);
417
- }
418
- }
419
- function detectChord(pitches, opts = {}) {
420
- assertPitches(pitches);
421
- if (opts.bassPc !== void 0) assertFiniteNumber(opts.bassPc, "bassPc");
422
- const input = uniquePitchClasses(pitches);
423
- if (input.length === 0) {
424
- return [];
425
- }
426
- const inputKind = opts.input === void 0 || opts.input === "auto" ? pitches.every((pitch) => Number.isInteger(pitch) && pitch >= 0 && pitch <= 11) ? "pitchClass" : "midi" : opts.input;
427
- const bassPc = opts.bassPc !== void 0 ? pitchClass(opts.bassPc) : inputKind === "midi" ? pitchClass(pitches.reduce((lowest, pitch) => Math.min(lowest, pitch), Infinity)) : void 0;
428
- const inputSet = new Set(input);
429
- const matches = [];
430
- const qualities = chordQualities();
431
- for (const rootPc of input) {
432
- for (const quality of qualities) {
433
- const chord = makeChord(rootPc, quality);
434
- const tones = chordPitchClasses(chord);
435
- const toneSet = new Set(tones);
436
- const missingPcs = tones.filter((pc) => !inputSet.has(pc));
437
- const fifthPc = pitchClass(rootPc + 7);
438
- if (missingPcs.length > 1 || missingPcs.length === 1 && missingPcs[0] !== fifthPc) {
439
- continue;
440
- }
441
- const extraPcs = input.filter((pc) => !toneSet.has(pc));
442
- const bassIndex = bassPc === void 0 ? -1 : chord.intervals.findIndex((iv) => pitchClass(rootPc + iv) === bassPc);
443
- const inversion = bassPc === void 0 ? null : bassIndex > 0 ? bassIndex : 0;
444
- const match = {
445
- rootPc,
446
- quality,
447
- missingPcs,
448
- extraPcs,
449
- exact: extraPcs.length === 0 && missingPcs.length === 0,
450
- inversion
451
- };
452
- if (bassPc !== void 0 && bassPc !== rootPc) {
453
- match.bassPc = bassPc;
454
- }
455
- matches.push(match);
456
- }
457
- }
458
- matches.sort((a, b) => {
459
- if (a.extraPcs.length !== b.extraPcs.length) {
460
- return a.extraPcs.length - b.extraPcs.length;
461
- }
462
- if (a.missingPcs.length !== b.missingPcs.length) {
463
- return a.missingPcs.length - b.missingPcs.length;
464
- }
465
- if (a.inversion === 0 !== (b.inversion === 0)) {
466
- return a.inversion === 0 ? -1 : 1;
467
- }
468
- const aSize = chordPitchClasses(makeChord(a.rootPc, a.quality)).length;
469
- const bSize = chordPitchClasses(makeChord(b.rootPc, b.quality)).length;
470
- return bSize - aSize;
471
- });
472
- return matches;
473
- }
474
- function detectChordBest(pitches, opts = {}) {
475
- const best = detectChord(pitches, opts)[0];
476
- if (!best) {
477
- return null;
478
- }
479
- return makeChord(best.rootPc, best.quality, best.bassPc);
480
- }
481
- var MINOR_MASK_VARIANTS = [NATURAL_MINOR_MASK, HARMONIC_MINOR_MASK, MELODIC_MINOR_MASK];
482
- function detectKey(pitches) {
483
- assertPitches(pitches);
484
- const input = uniquePitchClasses(pitches);
485
- if (input.length === 0) {
486
- return [];
487
- }
488
- const counts = /* @__PURE__ */ new Map();
489
- for (const pc of pitches.map(pitchClass)) {
490
- counts.set(pc, (counts.get(pc) ?? 0) + 1);
491
- }
492
- const total = pitches.length;
493
- const results = [];
494
- for (let tonic = 0; tonic < 12; tonic += 1) {
495
- for (const mode of ["major", "minor"]) {
496
- const key = mode === "major" ? majorKey(tonic) : minorKey(tonic);
497
- const masks = mode === "major" ? [MAJOR_MASK] : MINOR_MASK_VARIANTS;
498
- let inScale = 0;
499
- let weighted = Number.NEGATIVE_INFINITY;
500
- for (const mask of masks) {
501
- let variantInScale = 0;
502
- let variantWeighted = 0;
503
- for (const pc of input) {
504
- const offset = (pc - tonic + 12) % 12;
505
- if (mask >> offset & 1) {
506
- variantInScale += 1;
507
- }
508
- }
509
- for (const [pc, count] of counts) {
510
- const offset = (pc - tonic + 12) % 12;
511
- if (mask >> offset & 1) {
512
- variantWeighted += count;
513
- }
514
- }
515
- if (variantWeighted > weighted) {
516
- weighted = variantWeighted;
517
- inScale = variantInScale;
518
- }
519
- }
520
- weighted += (counts.get(tonic) ?? 0) * 0.5;
521
- results.push({
522
- key,
523
- mode,
524
- fit: inScale / input.length,
525
- score: weighted / total
526
- });
527
- }
528
- }
529
- results.sort((a, b) => b.score - a.score);
530
- return results.map(({ key, mode, fit }) => ({ key, mode, fit }));
531
- }
532
-
533
706
  export {
707
+ detectChord,
708
+ detectChordBest,
709
+ detectKey,
710
+ detectKeyFromNotes,
711
+ detectKeyBest,
534
712
  romanToChord,
535
713
  chordToRoman,
536
714
  isMinorKey,
@@ -541,9 +719,6 @@ export {
541
719
  secondaryDominant,
542
720
  isBorrowedChord,
543
721
  borrowedSource,
544
- detectCadence,
545
- detectChord,
546
- detectChordBest,
547
- detectKey
722
+ detectCadence
548
723
  };
549
- //# sourceMappingURL=chunk-CHMXELDS.js.map
724
+ //# sourceMappingURL=chunk-NNB67XA2.js.map