@libraz/libcantus 0.9.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +61 -261
  2. package/README_ja.md +115 -0
  3. package/dist/analyze/index.cjs +54 -7
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +213 -456
  6. package/dist/analyze/index.d.ts +213 -456
  7. package/dist/analyze/index.js +56 -9
  8. package/dist/chunk-3JXCW6DX.cjs +3220 -0
  9. package/dist/chunk-3JXCW6DX.cjs.map +1 -0
  10. package/dist/chunk-4A2RMUCC.js +7647 -0
  11. package/dist/chunk-4A2RMUCC.js.map +1 -0
  12. package/dist/chunk-6XPTBE7Y.cjs +49 -0
  13. package/dist/chunk-6XPTBE7Y.cjs.map +1 -0
  14. package/dist/chunk-AFM2ULJH.cjs +5121 -0
  15. package/dist/chunk-AFM2ULJH.cjs.map +1 -0
  16. package/dist/chunk-E3F4AHAH.cjs +690 -0
  17. package/dist/chunk-E3F4AHAH.cjs.map +1 -0
  18. package/dist/chunk-ELCTZUJJ.js +379 -0
  19. package/dist/chunk-ELCTZUJJ.js.map +1 -0
  20. package/dist/chunk-FVSI36UV.js +690 -0
  21. package/dist/chunk-FVSI36UV.js.map +1 -0
  22. package/dist/chunk-GEDTJHAC.cjs +1 -0
  23. package/dist/chunk-GEDTJHAC.cjs.map +1 -0
  24. package/dist/chunk-GOZG57ER.js +3220 -0
  25. package/dist/chunk-GOZG57ER.js.map +1 -0
  26. package/dist/chunk-H3VORETI.js +2243 -0
  27. package/dist/chunk-H3VORETI.js.map +1 -0
  28. package/dist/chunk-H42UHPYW.js +1577 -0
  29. package/dist/chunk-H42UHPYW.js.map +1 -0
  30. package/dist/chunk-HFPS7JFD.js +1 -0
  31. package/dist/chunk-INHVXP6Z.js +3053 -0
  32. package/dist/chunk-INHVXP6Z.js.map +1 -0
  33. package/dist/chunk-K4ZJUGF4.cjs +379 -0
  34. package/dist/chunk-K4ZJUGF4.cjs.map +1 -0
  35. package/dist/chunk-KNJEKLKV.js +49 -0
  36. package/dist/chunk-KNJEKLKV.js.map +1 -0
  37. package/dist/chunk-M4CWTJWJ.cjs +7647 -0
  38. package/dist/chunk-M4CWTJWJ.cjs.map +1 -0
  39. package/dist/chunk-MXO72PLY.cjs +211 -0
  40. package/dist/chunk-MXO72PLY.cjs.map +1 -0
  41. package/dist/chunk-NLEU4QSE.cjs +360 -0
  42. package/dist/chunk-NLEU4QSE.cjs.map +1 -0
  43. package/dist/chunk-OE6WK2Y2.cjs +1577 -0
  44. package/dist/chunk-OE6WK2Y2.cjs.map +1 -0
  45. package/dist/chunk-Q2WGKJNN.cjs +1064 -0
  46. package/dist/chunk-Q2WGKJNN.cjs.map +1 -0
  47. package/dist/chunk-QYQQ5J6E.js +1 -0
  48. package/dist/chunk-QYQQ5J6E.js.map +1 -0
  49. package/dist/chunk-RHIJ3S4I.cjs +1 -0
  50. package/dist/chunk-RHIJ3S4I.cjs.map +1 -0
  51. package/dist/chunk-TTCLJX6D.js +211 -0
  52. package/dist/chunk-TTCLJX6D.js.map +1 -0
  53. package/dist/chunk-UADYAYAT.cjs +3053 -0
  54. package/dist/chunk-UADYAYAT.cjs.map +1 -0
  55. package/dist/chunk-VOHZB45S.js +5121 -0
  56. package/dist/chunk-VOHZB45S.js.map +1 -0
  57. package/dist/chunk-VTI6FOLB.js +1064 -0
  58. package/dist/chunk-VTI6FOLB.js.map +1 -0
  59. package/dist/chunk-WWURE4XT.cjs +2243 -0
  60. package/dist/chunk-WWURE4XT.cjs.map +1 -0
  61. package/dist/chunk-YH3BIKHH.js +360 -0
  62. package/dist/chunk-YH3BIKHH.js.map +1 -0
  63. package/dist/core/index.cjs +103 -6
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +96 -345
  66. package/dist/core/index.d.ts +96 -345
  67. package/dist/core/index.js +104 -7
  68. package/dist/generate/index.cjs +89 -10
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +437 -1095
  71. package/dist/generate/index.d.ts +437 -1095
  72. package/dist/generate/index.js +94 -15
  73. package/dist/index-9wcMhG73.d.ts +1095 -0
  74. package/dist/{index-D6b6na69.d.cts → index-B4o55NDr.d.cts} +13 -9
  75. package/dist/index-B9MNZboQ.d.cts +770 -0
  76. package/dist/index-B9MNZboQ.d.ts +770 -0
  77. package/dist/index-BGFW-8P2.d.cts +2307 -0
  78. package/dist/index-BO6xE9YU.d.cts +1804 -0
  79. package/dist/index-BkGmlyI3.d.cts +470 -0
  80. package/dist/index-CCNJRG-P.d.cts +1095 -0
  81. package/dist/index-CGpRv5sl.d.cts +643 -0
  82. package/dist/index-C_8o--VE.d.cts +1242 -0
  83. package/dist/index-CgHqxdps.d.ts +2307 -0
  84. package/dist/index-Chbst3Jx.d.ts +1343 -0
  85. package/dist/index-Cj49F892.d.ts +470 -0
  86. package/dist/{index-tSQh-xwY.d.ts → index-CunYKLiu.d.ts} +13 -9
  87. package/dist/index-D57QRbVn.d.ts +1804 -0
  88. package/dist/index-DInQqT8Q.d.cts +66 -0
  89. package/dist/index-DInQqT8Q.d.ts +66 -0
  90. package/dist/index-Dzr13q3d.d.cts +1343 -0
  91. package/dist/index-G1uwGGWQ.d.ts +643 -0
  92. package/dist/index-szBv3496.d.ts +1242 -0
  93. package/dist/index.cjs +358 -15
  94. package/dist/index.cjs.map +1 -1
  95. package/dist/index.d.cts +17 -15
  96. package/dist/index.d.ts +17 -15
  97. package/dist/index.js +407 -64
  98. package/dist/masks-DYbE485J.d.cts +445 -0
  99. package/dist/masks-DYbE485J.d.ts +445 -0
  100. package/dist/model/index.cjs +39 -7
  101. package/dist/model/index.cjs.map +1 -1
  102. package/dist/model/index.d.cts +4685 -243
  103. package/dist/model/index.d.ts +4685 -243
  104. package/dist/model/index.js +40 -8
  105. package/dist/positional-Cptlj-xs.d.ts +287 -0
  106. package/dist/positional-DvH_sd8u.d.cts +287 -0
  107. package/dist/theory/index.cjs +101 -7
  108. package/dist/theory/index.cjs.map +1 -1
  109. package/dist/theory/index.d.cts +513 -173
  110. package/dist/theory/index.d.ts +513 -173
  111. package/dist/theory/index.js +118 -24
  112. package/package.json +16 -11
  113. package/dist/chunk-36N73KZ3.cjs +0 -246
  114. package/dist/chunk-36N73KZ3.cjs.map +0 -1
  115. package/dist/chunk-4HLLZPXT.cjs +0 -3077
  116. package/dist/chunk-4HLLZPXT.cjs.map +0 -1
  117. package/dist/chunk-7MGZ7D62.cjs +0 -498
  118. package/dist/chunk-7MGZ7D62.cjs.map +0 -1
  119. package/dist/chunk-7NKDPF74.js +0 -3077
  120. package/dist/chunk-7NKDPF74.js.map +0 -1
  121. package/dist/chunk-CAN76NCU.cjs +0 -679
  122. package/dist/chunk-CAN76NCU.cjs.map +0 -1
  123. package/dist/chunk-D2FAJBYL.js +0 -479
  124. package/dist/chunk-D2FAJBYL.js.map +0 -1
  125. package/dist/chunk-EHVEBWM5.js +0 -1449
  126. package/dist/chunk-EHVEBWM5.js.map +0 -1
  127. package/dist/chunk-FCANXBAT.cjs +0 -30
  128. package/dist/chunk-FCANXBAT.cjs.map +0 -1
  129. package/dist/chunk-FNDWXRRI.cjs +0 -479
  130. package/dist/chunk-FNDWXRRI.cjs.map +0 -1
  131. package/dist/chunk-GQSGS2N4.js +0 -173
  132. package/dist/chunk-GQSGS2N4.js.map +0 -1
  133. package/dist/chunk-HKGYCQ34.js +0 -53
  134. package/dist/chunk-HKGYCQ34.js.map +0 -1
  135. package/dist/chunk-I24MV65U.js +0 -106
  136. package/dist/chunk-I24MV65U.js.map +0 -1
  137. package/dist/chunk-KMHOICIY.js +0 -679
  138. package/dist/chunk-KMHOICIY.js.map +0 -1
  139. package/dist/chunk-LW53AHQU.cjs +0 -1117
  140. package/dist/chunk-LW53AHQU.cjs.map +0 -1
  141. package/dist/chunk-N6IHQUOX.js +0 -1
  142. package/dist/chunk-NKIBQ6PT.js +0 -30
  143. package/dist/chunk-NKIBQ6PT.js.map +0 -1
  144. package/dist/chunk-NP4NA2FB.cjs +0 -106
  145. package/dist/chunk-NP4NA2FB.cjs.map +0 -1
  146. package/dist/chunk-NVE34YGZ.cjs +0 -1449
  147. package/dist/chunk-NVE34YGZ.cjs.map +0 -1
  148. package/dist/chunk-NYYWMV2C.js +0 -327
  149. package/dist/chunk-NYYWMV2C.js.map +0 -1
  150. package/dist/chunk-Q2XUP227.js +0 -845
  151. package/dist/chunk-Q2XUP227.js.map +0 -1
  152. package/dist/chunk-Q6OP4U43.cjs +0 -327
  153. package/dist/chunk-Q6OP4U43.cjs.map +0 -1
  154. package/dist/chunk-RC6GBMCD.cjs +0 -845
  155. package/dist/chunk-RC6GBMCD.cjs.map +0 -1
  156. package/dist/chunk-RK35RIN7.js +0 -1117
  157. package/dist/chunk-RK35RIN7.js.map +0 -1
  158. package/dist/chunk-RUMU3UA6.cjs +0 -53
  159. package/dist/chunk-RUMU3UA6.cjs.map +0 -1
  160. package/dist/chunk-T64C7ZD7.cjs +0 -1
  161. package/dist/chunk-T64C7ZD7.cjs.map +0 -1
  162. package/dist/chunk-XEBG3G53.js +0 -498
  163. package/dist/chunk-XEBG3G53.js.map +0 -1
  164. package/dist/chunk-YLEMGEFH.js +0 -246
  165. package/dist/chunk-YLEMGEFH.js.map +0 -1
  166. package/dist/chunk-ZEJR4TKL.cjs +0 -173
  167. package/dist/chunk-ZEJR4TKL.cjs.map +0 -1
  168. package/dist/function-BtiZb9i9.d.ts +0 -208
  169. package/dist/function-DNugAOrt.d.cts +0 -208
  170. package/dist/index-6VR8-kit.d.ts +0 -143
  171. package/dist/index-70p168VM.d.ts +0 -192
  172. package/dist/index-Be74mcon.d.cts +0 -152
  173. package/dist/index-BtXT6UQ0.d.cts +0 -192
  174. package/dist/index-C5UBbQ4G.d.ts +0 -152
  175. package/dist/index-C83mZI1P.d.ts +0 -242
  176. package/dist/index-DXd9JikN.d.cts +0 -246
  177. package/dist/index-DXd9JikN.d.ts +0 -246
  178. package/dist/index-OG65ocOr.d.cts +0 -272
  179. package/dist/index-OG65ocOr.d.ts +0 -272
  180. package/dist/index-tJhkmQgE.d.cts +0 -143
  181. package/dist/index-y_ak9_Hi.d.cts +0 -242
  182. package/dist/styled-DIRkk9Uz.d.ts +0 -506
  183. package/dist/styled-DJbkJQfj.d.cts +0 -506
  184. package/dist/types-BN6_Q9Kl.d.cts +0 -39
  185. package/dist/types-BN6_Q9Kl.d.ts +0 -39
  186. /package/dist/{chunk-N6IHQUOX.js.map → chunk-HFPS7JFD.js.map} +0 -0
@@ -0,0 +1,690 @@
1
+ import {
2
+ InvalidInputError,
3
+ assertFiniteNumber,
4
+ assertInteger,
5
+ assertNoteEvents,
6
+ assertRange
7
+ } from "./chunk-VTI6FOLB.js";
8
+
9
+ // src/core/instrument/articulation.ts
10
+ var ARTICULATIONS = Object.freeze([
11
+ "accent",
12
+ "ghost",
13
+ "staccato",
14
+ "legato",
15
+ "slide",
16
+ "hammer",
17
+ "mute",
18
+ "flam",
19
+ "drag",
20
+ "roll",
21
+ "choke",
22
+ "open"
23
+ ]);
24
+
25
+ // src/core/instrument/profile.ts
26
+ var LIMBS = Object.freeze(["rightHand", "leftHand", "rightFoot", "leftFoot"]);
27
+ function toInstrumentProfile(value, name = "instrument") {
28
+ if (typeof value !== "object" || value === null) {
29
+ throw new InvalidInputError(`${name} must be an instrument profile; received ${typeof value}`);
30
+ }
31
+ const data = "toJSON" in value && typeof value.toJSON === "function" ? value.toJSON() : value;
32
+ const kind = data?.kind;
33
+ if (kind !== "stringed" && kind !== "percussion") {
34
+ throw new InvalidInputError(
35
+ `${name} must be a stringed or percussion profile; received kind ${String(kind)}`
36
+ );
37
+ }
38
+ assertProfile(data);
39
+ return data;
40
+ }
41
+ function toStringedProfile(value, name = "instrument") {
42
+ const profile = toInstrumentProfile(value, name);
43
+ if (profile.kind !== "stringed") {
44
+ throw new InvalidInputError(
45
+ `${name} must be a stringed instrument; ${profile.name} has no strings`
46
+ );
47
+ }
48
+ return profile;
49
+ }
50
+ function assertProfile(profile) {
51
+ if (profile.kind === "stringed") {
52
+ if (profile.tuning.length === 0) {
53
+ throw new InvalidInputError(`${profile.name} must have at least one string`);
54
+ }
55
+ if (!Number.isInteger(profile.frets) || profile.frets < 0) {
56
+ throw new InvalidInputError(`${profile.name} frets must be a non-negative integer`);
57
+ }
58
+ return;
59
+ }
60
+ if (Object.keys(profile.reach).length === 0) {
61
+ throw new InvalidInputError(`${profile.name} must have at least one voice within reach`);
62
+ }
63
+ }
64
+ function reachOf(profile, pitch) {
65
+ if (pitch === void 0) {
66
+ return [];
67
+ }
68
+ return (profile.reach[pitch] ?? []).filter((limb) => profile.limbs.includes(limb));
69
+ }
70
+ function instrumentRange(profile) {
71
+ assertProfile(profile);
72
+ if (profile.kind === "stringed") {
73
+ let low2 = Number.POSITIVE_INFINITY;
74
+ let high2 = Number.NEGATIVE_INFINITY;
75
+ for (const open of profile.tuning) {
76
+ low2 = Math.min(low2, open);
77
+ high2 = Math.max(high2, open + profile.frets);
78
+ }
79
+ return { low: low2, high: high2 };
80
+ }
81
+ let low = Number.POSITIVE_INFINITY;
82
+ let high = Number.NEGATIVE_INFINITY;
83
+ for (const key of Object.keys(profile.reach)) {
84
+ const pitch = Number(key);
85
+ if (reachOf(profile, pitch).length === 0) {
86
+ continue;
87
+ }
88
+ low = Math.min(low, pitch);
89
+ high = Math.max(high, pitch);
90
+ }
91
+ return { low, high };
92
+ }
93
+ function canSound(profile, pitch) {
94
+ assertProfile(profile);
95
+ if (profile.kind === "stringed") {
96
+ return profile.tuning.some((open) => pitch >= open && pitch - open <= profile.frets);
97
+ }
98
+ return reachOf(profile, pitch).length > 0;
99
+ }
100
+ function fingeringsFor(profile, pitch) {
101
+ assertProfile(profile);
102
+ const out = [];
103
+ for (let string = 0; string < profile.tuning.length; string += 1) {
104
+ const open = profile.tuning[string];
105
+ if (open === void 0) {
106
+ continue;
107
+ }
108
+ const fret = pitch - open;
109
+ if (fret >= 0 && fret <= profile.frets) {
110
+ out.push({ string, fret });
111
+ }
112
+ }
113
+ return out;
114
+ }
115
+ function foldIntoRange(pitch, profile) {
116
+ if (canSound(profile, pitch)) {
117
+ return pitch;
118
+ }
119
+ const { low, high } = instrumentRange(profile);
120
+ for (let candidate = pitch + 12; candidate <= high; candidate += 12) {
121
+ if (canSound(profile, candidate)) {
122
+ return candidate;
123
+ }
124
+ }
125
+ for (let candidate = pitch - 12; candidate >= low; candidate -= 12) {
126
+ if (canSound(profile, candidate)) {
127
+ return candidate;
128
+ }
129
+ }
130
+ return pitch;
131
+ }
132
+
133
+ // src/core/instrument/playability.ts
134
+ var REFERENCE_BPM = 120;
135
+ var MAX_FRET_SHIFT_PER_SECOND = 100;
136
+ var MIN_LIMB_STROKE_SECONDS = 0.06;
137
+ var DIFFICULTY_SCALE = 12;
138
+ var MIN_DIFFICULTY = 1;
139
+ var MAX_DIFFICULTY = 5;
140
+ var STRING_CROSSING_COST = 2;
141
+ var ONSET_GRID = 128;
142
+ var EPS = 1e-9;
143
+ function playability(notes, profile, bpm) {
144
+ assertNoteEvents(notes, "playability notes", { allowNonPositiveDuration: true });
145
+ if (bpm !== void 0) {
146
+ assertRange(bpm, Number.MIN_VALUE, 1e3, "playability bpm");
147
+ }
148
+ const order = notes.map((_, index) => index).sort((a, b) => {
149
+ const left = notes[a];
150
+ const right = notes[b];
151
+ if (!left || !right) {
152
+ return a - b;
153
+ }
154
+ return left.startBeat - right.startBeat || left.pitch - right.pitch || a - b;
155
+ });
156
+ const state = {
157
+ notes,
158
+ order,
159
+ placements: notes.map((_, index) => ({ note: index })),
160
+ issues: [],
161
+ movement: 0,
162
+ secondsPerBeat: bpm === void 0 ? void 0 : 60 / bpm
163
+ };
164
+ checkExistence(state, profile);
165
+ if (profile.kind === "stringed") {
166
+ placeOnNeck(state, profile);
167
+ } else {
168
+ placeOnKit(state, profile);
169
+ }
170
+ return {
171
+ difficulty: difficultyOf(state, bpm),
172
+ issues: state.issues,
173
+ placements: state.placements
174
+ };
175
+ }
176
+ function checkExistence(state, profile) {
177
+ for (const index of state.order) {
178
+ const note = state.notes[index];
179
+ if (!note) {
180
+ continue;
181
+ }
182
+ if (!canSound(profile, note.pitch)) {
183
+ state.issues.push({
184
+ type: "noteOutOfRange",
185
+ layer: 1,
186
+ notes: [index],
187
+ startBeat: note.startBeat,
188
+ impossible: true,
189
+ message: `${profile.name} cannot sound MIDI ${note.pitch}`
190
+ });
191
+ }
192
+ const articulation = note.articulation;
193
+ if (articulation !== void 0 && !profile.articulations.includes(articulation)) {
194
+ state.issues.push({
195
+ type: "articulationUnavailable",
196
+ layer: 1,
197
+ notes: [index],
198
+ startBeat: note.startBeat,
199
+ impossible: true,
200
+ message: `${profile.name} cannot play ${articulation}`
201
+ });
202
+ }
203
+ }
204
+ }
205
+ function soundingGroups(state) {
206
+ const groups = [];
207
+ let active = [];
208
+ let cursor = 0;
209
+ let previousKey = Number.NaN;
210
+ for (const index of state.order) {
211
+ const note = state.notes[index];
212
+ if (!note) {
213
+ continue;
214
+ }
215
+ const key = Math.round(note.startBeat * ONSET_GRID);
216
+ if (key === previousKey) {
217
+ continue;
218
+ }
219
+ previousKey = key;
220
+ const at = note.startBeat;
221
+ active = active.filter((member) => {
222
+ const held = state.notes[member];
223
+ return held !== void 0 && held.startBeat + held.durationBeat > at + EPS;
224
+ });
225
+ while (cursor < state.order.length) {
226
+ const candidateIndex = state.order[cursor];
227
+ const candidate = candidateIndex === void 0 ? void 0 : state.notes[candidateIndex];
228
+ if (!candidate || candidate.startBeat > at + EPS) {
229
+ break;
230
+ }
231
+ if (candidateIndex !== void 0 && candidate.startBeat + candidate.durationBeat > at + EPS) {
232
+ active.push(candidateIndex);
233
+ }
234
+ cursor += 1;
235
+ }
236
+ groups.push({ startBeat: at, members: [...active] });
237
+ }
238
+ return groups;
239
+ }
240
+ function placeOnNeck(state, profile) {
241
+ const stringFreeAt = profile.tuning.map(() => Number.NEGATIVE_INFINITY);
242
+ const stringHeldBy = profile.tuning.map(() => void 0);
243
+ let previous;
244
+ let previousIndex;
245
+ for (const index of state.order) {
246
+ const note = state.notes[index];
247
+ const placement = state.placements[index];
248
+ if (!note || !placement) {
249
+ continue;
250
+ }
251
+ const candidates = fingeringsFor(profile, note.pitch);
252
+ if (candidates.length === 0) {
253
+ continue;
254
+ }
255
+ const free = candidates.filter(
256
+ (candidate) => (stringFreeAt[candidate.string] ?? 0) <= note.startBeat + EPS
257
+ );
258
+ if (free.length === 0) {
259
+ const blocked = candidates.map((candidate) => stringHeldBy[candidate.string]).filter((held) => held !== void 0);
260
+ state.issues.push({
261
+ type: "stringConflict",
262
+ layer: 2,
263
+ notes: [.../* @__PURE__ */ new Set([...blocked, index])].sort((a, b) => a - b),
264
+ startBeat: note.startBeat,
265
+ impossible: true,
266
+ message: `MIDI ${note.pitch} needs a string already sounding on ${profile.name}`
267
+ });
268
+ }
269
+ const pool = free.length > 0 ? free : candidates;
270
+ const chosen = pool.reduce(
271
+ (best, candidate) => fingeringCost(candidate, previous) < fingeringCost(best, previous) ? candidate : best
272
+ );
273
+ placement.fingering = chosen;
274
+ stringFreeAt[chosen.string] = note.startBeat + note.durationBeat;
275
+ stringHeldBy[chosen.string] = index;
276
+ state.movement += 1;
277
+ if (previous) {
278
+ state.movement += Math.abs(chosen.fret - previous.fret) + Math.abs(chosen.string - previous.string);
279
+ checkShiftSpeed(state, profile, previousIndex, index, previous, chosen);
280
+ }
281
+ previous = chosen;
282
+ previousIndex = index;
283
+ }
284
+ for (const group of soundingGroups(state)) {
285
+ checkStretch(state, profile, group);
286
+ checkPolyphony(state, profile, group);
287
+ }
288
+ }
289
+ function fingeringCost(candidate, previous) {
290
+ if (!previous) {
291
+ return candidate.fret + candidate.string / 100;
292
+ }
293
+ return Math.abs(candidate.fret - previous.fret) + STRING_CROSSING_COST * Math.abs(candidate.string - previous.string) + candidate.string / 100;
294
+ }
295
+ function checkShiftSpeed(state, profile, fromIndex, toIndex, from, to) {
296
+ const secondsPerBeat = state.secondsPerBeat;
297
+ if (secondsPerBeat === void 0 || fromIndex === void 0) {
298
+ return;
299
+ }
300
+ const previousNote = state.notes[fromIndex];
301
+ const note = state.notes[toIndex];
302
+ if (!previousNote || !note) {
303
+ return;
304
+ }
305
+ const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;
306
+ const shift = Math.abs(to.fret - from.fret);
307
+ if (seconds <= EPS || shift === 0) {
308
+ return;
309
+ }
310
+ if (shift / seconds > MAX_FRET_SHIFT_PER_SECOND) {
311
+ state.issues.push({
312
+ type: "tooFast",
313
+ layer: 3,
314
+ notes: [fromIndex, toIndex].sort((a, b) => a - b),
315
+ startBeat: note.startBeat,
316
+ impossible: false,
317
+ message: `a ${shift}-fret shift in ${seconds.toFixed(3)}s is beyond ${profile.name}`
318
+ });
319
+ }
320
+ }
321
+ function checkStretch(state, profile, group) {
322
+ const frets = group.members.map((member) => state.placements[member]?.fingering).filter(
323
+ (fingering) => fingering !== void 0 && fingering.fret > 0
324
+ ).map((fingering) => fingering.fret);
325
+ if (frets.length < 2) {
326
+ return;
327
+ }
328
+ const span = Math.max(...frets) - Math.min(...frets);
329
+ if (span > profile.maxStretch) {
330
+ state.issues.push({
331
+ type: "stretchTooWide",
332
+ layer: 2,
333
+ notes: [...group.members].sort((a, b) => a - b),
334
+ startBeat: group.startBeat,
335
+ impossible: true,
336
+ message: `a ${span}-fret stretch exceeds the ${profile.maxStretch} frets one hand spans on ${profile.name}`
337
+ });
338
+ }
339
+ }
340
+ function checkPolyphony(state, profile, group) {
341
+ if (group.members.length <= profile.polyphony) {
342
+ return;
343
+ }
344
+ state.issues.push({
345
+ type: "polyphonyExceeded",
346
+ layer: 2,
347
+ notes: [...group.members].sort((a, b) => a - b),
348
+ startBeat: group.startBeat,
349
+ impossible: true,
350
+ message: `${group.members.length} notes sound at once; ${profile.name} has ${profile.polyphony}`
351
+ });
352
+ }
353
+ function placeOnKit(state, profile) {
354
+ const limbForVoice = /* @__PURE__ */ new Map();
355
+ const lastStroke = /* @__PURE__ */ new Map();
356
+ for (const group of onsetGroups(state)) {
357
+ const taken = /* @__PURE__ */ new Set();
358
+ let conflicted = false;
359
+ const members = [...group.members].sort((a, b) => {
360
+ const left = reachOf(profile, state.notes[a]?.pitch);
361
+ const right = reachOf(profile, state.notes[b]?.pitch);
362
+ return left.length - right.length || a - b;
363
+ });
364
+ for (const index of members) {
365
+ const note = state.notes[index];
366
+ const placement = state.placements[index];
367
+ if (!note || !placement) {
368
+ continue;
369
+ }
370
+ const candidates = reachOf(profile, note.pitch);
371
+ if (candidates.length === 0) {
372
+ continue;
373
+ }
374
+ const preferred = limbForVoice.get(note.pitch);
375
+ const free = candidates.filter((limb) => !taken.has(limb));
376
+ const chosen = free.length === 0 ? candidates[0] : preferred !== void 0 && free.includes(preferred) ? preferred : free[0];
377
+ if (free.length === 0) {
378
+ conflicted = true;
379
+ }
380
+ if (chosen === void 0) {
381
+ continue;
382
+ }
383
+ taken.add(chosen);
384
+ placement.limb = chosen;
385
+ limbForVoice.set(note.pitch, chosen);
386
+ state.movement += 1;
387
+ const previousIndex = lastStroke.get(chosen);
388
+ if (previousIndex !== void 0) {
389
+ const previousNote = state.notes[previousIndex];
390
+ if (previousNote && previousNote.pitch !== note.pitch) {
391
+ state.movement += 1;
392
+ }
393
+ checkStrokeSpeed(state, profile, previousIndex, index, chosen);
394
+ }
395
+ lastStroke.set(chosen, index);
396
+ }
397
+ if (conflicted) {
398
+ state.issues.push({
399
+ type: "limbConflict",
400
+ layer: 2,
401
+ notes: [...group.members].sort((a, b) => a - b),
402
+ startBeat: group.startBeat,
403
+ impossible: true,
404
+ message: `${group.members.length} simultaneous strokes cannot be shared among the limbs of ${profile.name}`
405
+ });
406
+ } else {
407
+ checkPolyphony(state, profile, group);
408
+ }
409
+ }
410
+ }
411
+ function checkStrokeSpeed(state, profile, fromIndex, toIndex, limb) {
412
+ const secondsPerBeat = state.secondsPerBeat;
413
+ if (secondsPerBeat === void 0) {
414
+ return;
415
+ }
416
+ const previousNote = state.notes[fromIndex];
417
+ const note = state.notes[toIndex];
418
+ if (!previousNote || !note) {
419
+ return;
420
+ }
421
+ const seconds = (note.startBeat - previousNote.startBeat) * secondsPerBeat;
422
+ if (seconds <= EPS || seconds >= MIN_LIMB_STROKE_SECONDS) {
423
+ return;
424
+ }
425
+ state.issues.push({
426
+ type: "tooFast",
427
+ layer: 3,
428
+ notes: [fromIndex, toIndex].sort((a, b) => a - b),
429
+ startBeat: note.startBeat,
430
+ impossible: false,
431
+ message: `${limb} strikes twice ${seconds.toFixed(3)}s apart on ${profile.name}`
432
+ });
433
+ }
434
+ function onsetGroups(state) {
435
+ const groups = [];
436
+ let key = Number.NaN;
437
+ let current;
438
+ for (const index of state.order) {
439
+ const note = state.notes[index];
440
+ if (!note) {
441
+ continue;
442
+ }
443
+ const onsetKey = Math.round(note.startBeat * ONSET_GRID);
444
+ if (!current || onsetKey !== key) {
445
+ current = { startBeat: note.startBeat, members: [] };
446
+ groups.push(current);
447
+ key = onsetKey;
448
+ }
449
+ current.members.push(index);
450
+ }
451
+ return groups;
452
+ }
453
+ function difficultyOf(state, bpm) {
454
+ let first = Number.POSITIVE_INFINITY;
455
+ let last = Number.NEGATIVE_INFINITY;
456
+ for (const note of state.notes) {
457
+ if (!note) {
458
+ continue;
459
+ }
460
+ first = Math.min(first, note.startBeat);
461
+ last = Math.max(last, note.startBeat + Math.max(0, note.durationBeat));
462
+ }
463
+ const spanBeats = last - first;
464
+ if (!Number.isFinite(spanBeats) || spanBeats <= EPS) {
465
+ return MIN_DIFFICULTY;
466
+ }
467
+ const seconds = spanBeats * 60 / (bpm ?? REFERENCE_BPM);
468
+ const rate = state.movement / seconds;
469
+ const scaled = MIN_DIFFICULTY + (MAX_DIFFICULTY - MIN_DIFFICULTY) * (1 - Math.exp(-rate / DIFFICULTY_SCALE));
470
+ return Math.round(scaled * 1e4) / 1e4;
471
+ }
472
+
473
+ // src/core/instrument/profiles.ts
474
+ var STRINGED_ARTICULATIONS = Object.freeze([
475
+ "accent",
476
+ "ghost",
477
+ "staccato",
478
+ "legato",
479
+ "slide",
480
+ "hammer",
481
+ "mute",
482
+ "open"
483
+ ]);
484
+ var BASS_4_STRING = Object.freeze({
485
+ kind: "stringed",
486
+ name: "4-string bass",
487
+ tuning: Object.freeze([28, 33, 38, 43]),
488
+ frets: 24,
489
+ maxStretch: 4,
490
+ articulations: STRINGED_ARTICULATIONS,
491
+ polyphony: 4
492
+ });
493
+ var BASS_5_STRING = Object.freeze({
494
+ kind: "stringed",
495
+ name: "5-string bass",
496
+ tuning: Object.freeze([23, 28, 33, 38, 43]),
497
+ frets: 24,
498
+ maxStretch: 4,
499
+ articulations: STRINGED_ARTICULATIONS,
500
+ polyphony: 5
501
+ });
502
+ var GUITAR_STANDARD = Object.freeze({
503
+ kind: "stringed",
504
+ name: "guitar",
505
+ tuning: Object.freeze([40, 45, 50, 55, 59, 64]),
506
+ frets: 24,
507
+ maxStretch: 5,
508
+ articulations: STRINGED_ARTICULATIONS,
509
+ polyphony: 6
510
+ });
511
+ var GUITAR_DROP_D = Object.freeze({
512
+ kind: "stringed",
513
+ name: "guitar (drop D)",
514
+ tuning: Object.freeze([38, 45, 50, 55, 59, 64]),
515
+ frets: 24,
516
+ maxStretch: 5,
517
+ articulations: STRINGED_ARTICULATIONS,
518
+ polyphony: 6
519
+ });
520
+
521
+ // src/core/random/internal.ts
522
+ var UINT32_RANGE = 4294967296;
523
+ var FNV_OFFSET_BASIS = 2166136261;
524
+ var FNV_PRIME = 16777619;
525
+ var DOMAIN_SEED = 1;
526
+ var DOMAIN_POSITION = 2;
527
+ var TAG_STRING = 1;
528
+ var TAG_NUMBER = 2;
529
+ var numberBytes = new DataView(new ArrayBuffer(8));
530
+ function absorb(hash, byte) {
531
+ return Math.imul(hash ^ byte & 255, FNV_PRIME) >>> 0;
532
+ }
533
+ function absorbUint32(hash, value) {
534
+ let next = hash;
535
+ for (let shift = 0; shift < 32; shift += 8) {
536
+ next = absorb(next, value >>> shift & 255);
537
+ }
538
+ return next;
539
+ }
540
+ function fmix32(value) {
541
+ let mixed = value >>> 0;
542
+ mixed ^= mixed >>> 16;
543
+ mixed = Math.imul(mixed, 2246822507);
544
+ mixed ^= mixed >>> 13;
545
+ mixed = Math.imul(mixed, 3266489909);
546
+ mixed ^= mixed >>> 16;
547
+ return mixed >>> 0;
548
+ }
549
+ function absorbSegment(hash, segment, index) {
550
+ if (typeof segment === "string") {
551
+ let next = absorb(hash, TAG_STRING);
552
+ next = absorbUint32(next, segment.length);
553
+ for (let i = 0; i < segment.length; i += 1) {
554
+ const unit = segment.charCodeAt(i);
555
+ next = absorb(next, unit & 255);
556
+ next = absorb(next, unit >>> 8 & 255);
557
+ }
558
+ return next;
559
+ }
560
+ if (typeof segment === "number") {
561
+ assertFiniteNumber(segment, `seed path segment ${index}`);
562
+ numberBytes.setFloat64(0, segment === 0 ? 0 : segment, true);
563
+ let next = absorb(hash, TAG_NUMBER);
564
+ for (let i = 0; i < 8; i += 1) {
565
+ next = absorb(next, numberBytes.getUint8(i));
566
+ }
567
+ return next;
568
+ }
569
+ throw new InvalidInputError(
570
+ `seed path segment ${index} must be a string or a number; received ${typeof segment}`
571
+ );
572
+ }
573
+ function hashPath(domain, root, path) {
574
+ let hash = absorb(FNV_OFFSET_BASIS, domain);
575
+ hash = absorbUint32(hash, root);
576
+ for (let index = 0; index < path.length; index += 1) {
577
+ const segment = path[index];
578
+ if (segment === void 0) {
579
+ throw new InvalidInputError(
580
+ `seed path segment ${index} must be a string or a number; received undefined`
581
+ );
582
+ }
583
+ hash = absorbSegment(hash, segment, index);
584
+ }
585
+ return fmix32(hash);
586
+ }
587
+
588
+ // src/core/random/positional.ts
589
+ function createPositionalRng(seed) {
590
+ assertInteger(seed, "seed", 0, 4294967295);
591
+ return {
592
+ at: (...path) => hashPath(DOMAIN_POSITION, seed, path) / UINT32_RANGE
593
+ };
594
+ }
595
+ function includeAt(rng, complexity, ...path) {
596
+ assertRange(complexity, 0, 1, "complexity");
597
+ const draw = rng.at(...path);
598
+ if (!(draw >= 0 && draw < 1)) {
599
+ throw new InvalidInputError(`positional draw must be in [0, 1); received ${draw}`);
600
+ }
601
+ return draw < complexity;
602
+ }
603
+
604
+ // src/core/random/seed.ts
605
+ function deriveSeed(root, ...path) {
606
+ assertInteger(root, "root seed", 0, 4294967295);
607
+ return hashPath(DOMAIN_SEED, root, path);
608
+ }
609
+
610
+ // src/core/random/version.ts
611
+ var ALGORITHM_VERSION = 1;
612
+ var MIN_ALGORITHM_VERSION = 1;
613
+ function resolveAlgorithmVersion(requested) {
614
+ if (requested === void 0) {
615
+ return ALGORITHM_VERSION;
616
+ }
617
+ assertInteger(requested, "algorithm version");
618
+ if (requested < MIN_ALGORITHM_VERSION || requested > ALGORITHM_VERSION) {
619
+ throw new InvalidInputError(
620
+ `algorithm version ${requested} is not produced by this build; supported versions are ${MIN_ALGORITHM_VERSION}..${ALGORITHM_VERSION}`
621
+ );
622
+ }
623
+ return requested;
624
+ }
625
+
626
+ // src/core/random/index.ts
627
+ function createRng(seed) {
628
+ assertInteger(seed, "seed", 0, 4294967295);
629
+ let state = seed >>> 0;
630
+ const next = () => {
631
+ state = state + 1831565813 >>> 0;
632
+ let t = state;
633
+ t = Math.imul(t ^ t >>> 15, t | 1);
634
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
635
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
636
+ };
637
+ return {
638
+ next,
639
+ prob: (p) => {
640
+ assertRange(p, 0, 1, "probability");
641
+ return next() < p;
642
+ },
643
+ range: (lo, hi) => {
644
+ assertInteger(lo, "range lower bound");
645
+ assertInteger(hi, "range upper bound");
646
+ if (lo > hi) {
647
+ throw new InvalidInputError(
648
+ `range lower bound must not exceed upper bound; received ${lo} > ${hi}`
649
+ );
650
+ }
651
+ assertInteger(hi - lo, "range span", 0);
652
+ return lo + Math.floor(next() * (hi - lo + 1));
653
+ },
654
+ float: (lo, hi) => {
655
+ assertFiniteNumber(lo, "float lower bound");
656
+ assertFiniteNumber(hi, "float upper bound");
657
+ if (lo > hi) {
658
+ throw new InvalidInputError(
659
+ `float lower bound must not exceed upper bound; received ${lo} > ${hi}`
660
+ );
661
+ }
662
+ assertFiniteNumber(hi - lo, "float span");
663
+ return lo + next() * (hi - lo);
664
+ }
665
+ };
666
+ }
667
+
668
+ export {
669
+ ARTICULATIONS,
670
+ LIMBS,
671
+ toInstrumentProfile,
672
+ toStringedProfile,
673
+ instrumentRange,
674
+ canSound,
675
+ fingeringsFor,
676
+ foldIntoRange,
677
+ playability,
678
+ BASS_4_STRING,
679
+ BASS_5_STRING,
680
+ GUITAR_STANDARD,
681
+ GUITAR_DROP_D,
682
+ createPositionalRng,
683
+ includeAt,
684
+ deriveSeed,
685
+ ALGORITHM_VERSION,
686
+ MIN_ALGORITHM_VERSION,
687
+ resolveAlgorithmVersion,
688
+ createRng
689
+ };
690
+ //# sourceMappingURL=chunk-FVSI36UV.js.map