@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
@@ -1,3077 +0,0 @@
1
- import {
2
- createRng
3
- } from "./chunk-HKGYCQ34.js";
4
- import {
5
- beatsPerBar,
6
- createNoteEventIndex,
7
- formatTimeSignature,
8
- isStrongBeat,
9
- metricWeight
10
- } from "./chunk-YLEMGEFH.js";
11
- import {
12
- isDiatonic,
13
- parallelKey
14
- } from "./chunk-KMHOICIY.js";
15
- import {
16
- roleOf
17
- } from "./chunk-NKIBQ6PT.js";
18
- import {
19
- enumerateSafePitches,
20
- evaluateSafety
21
- } from "./chunk-NYYWMV2C.js";
22
- import {
23
- NATURAL_MINOR_MASK,
24
- chordPitchClasses,
25
- chordToneRole,
26
- createsParallelPerfect,
27
- diatonicTriad,
28
- isForbiddenMelodicLeap,
29
- isScaleTone,
30
- majorKey,
31
- makeChord,
32
- minorKey,
33
- nearestScaleTone,
34
- scaleTonesInDegreeOrder
35
- } from "./chunk-D2FAJBYL.js";
36
- import {
37
- InvalidInputError,
38
- assertFiniteNumber,
39
- assertGenerationBudget,
40
- assertInteger,
41
- assertNoteEvents,
42
- assertOneOf,
43
- assertPositiveInt,
44
- assertRange,
45
- assertTimeSignature,
46
- dropSilentNotes,
47
- pitchClassOf
48
- } from "./chunk-XEBG3G53.js";
49
-
50
- // src/generate/bass/index.ts
51
- var BASS_STYLES = ["root", "rootFifth", "pop", "walking", "arpeggio"];
52
- var DEFAULT_TS = { numerator: 4, denominator: 4 };
53
- var DEFAULT_STYLE = "root";
54
- var DEFAULT_OCTAVE = 2;
55
- var STRONG_VELOCITY = 100;
56
- var WEAK_VELOCITY = 80;
57
- var PICKUP_PROB = 0.35;
58
- var EPS = 1e-9;
59
- function placePc(pc, anchor, low) {
60
- const high = low + 12;
61
- let midi = pc + 12 * Math.round((anchor - pc) / 12);
62
- while (midi < low) {
63
- midi += 12;
64
- }
65
- while (midi > high) {
66
- midi -= 12;
67
- }
68
- return midi;
69
- }
70
- function chordTonePcs(chord) {
71
- const seen = /* @__PURE__ */ new Set();
72
- const pcs = [];
73
- for (const interval of chord.intervals) {
74
- const pc = pitchClassOf(chord.rootPc + interval);
75
- if (!seen.has(pc)) {
76
- seen.add(pc);
77
- pcs.push(pc);
78
- }
79
- }
80
- if (pcs.length === 0) {
81
- pcs.push(pitchClassOf(chord.rootPc));
82
- }
83
- return pcs;
84
- }
85
- function fifthPcOf(chord) {
86
- const fifths = new Set(chord.intervals.map((i) => pitchClassOf(i)));
87
- const chosen = fifths.has(7) ? 7 : fifths.has(6) ? 6 : fifths.has(8) ? 8 : void 0;
88
- return chosen === void 0 ? pitchClassOf(chord.rootPc) : pitchClassOf(chord.rootPc + chosen);
89
- }
90
- function bassPcOf(chord) {
91
- return pitchClassOf(chord.bassPc ?? chord.rootPc);
92
- }
93
- function beatPositions(start, end) {
94
- const positions = [];
95
- for (let p = start; p < end - EPS; p += 1) {
96
- positions.push(p);
97
- }
98
- if (positions.length === 0) {
99
- positions.push(start);
100
- }
101
- return positions;
102
- }
103
- function emit(ctx, pos, pc, midiOverride) {
104
- const midi = midiOverride ?? placePc(pc, ctx.prevMidi, ctx.low);
105
- const velocity = isStrongBeat(pos, ctx.ts) ? STRONG_VELOCITY : WEAK_VELOCITY;
106
- ctx.notes.push({ startBeat: pos, pitch: midi, velocity });
107
- return midi;
108
- }
109
- function buildRoot(ctx, seg) {
110
- ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));
111
- }
112
- function buildRootFifth(ctx, seg) {
113
- ctx.prevMidi = emit(ctx, seg.startBeat, bassPcOf(seg.chord));
114
- const mid = (seg.startBeat + seg.endBeat) / 2;
115
- if (mid > seg.startBeat + EPS && mid < seg.endBeat - EPS) {
116
- ctx.prevMidi = emit(ctx, mid, fifthPcOf(seg.chord));
117
- }
118
- }
119
- function buildPop(ctx, seg) {
120
- const positions = beatPositions(seg.startBeat, seg.endBeat);
121
- const rootPc = bassPcOf(seg.chord);
122
- let emitted = false;
123
- for (const pos of positions) {
124
- if (isStrongBeat(pos, ctx.ts)) {
125
- ctx.prevMidi = emit(ctx, pos, rootPc);
126
- emitted = true;
127
- } else if (ctx.rng.prob(PICKUP_PROB)) {
128
- if (ctx.rng.prob(0.5)) {
129
- const base = placePc(rootPc, ctx.prevMidi, ctx.low);
130
- const dropped = base - 12;
131
- emit(ctx, pos, rootPc, dropped >= 0 ? dropped : base);
132
- } else {
133
- ctx.prevMidi = emit(ctx, pos, fifthPcOf(seg.chord));
134
- }
135
- }
136
- }
137
- if (!emitted) {
138
- ctx.prevMidi = emit(ctx, seg.startBeat, rootPc);
139
- }
140
- }
141
- function buildArpeggio(ctx, seg) {
142
- const tones = chordTonePcs(seg.chord);
143
- const positions = beatPositions(seg.startBeat, seg.endBeat);
144
- positions.forEach((pos, i) => {
145
- const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
146
- ctx.prevMidi = emit(ctx, pos, pc);
147
- });
148
- }
149
- function approachNote(ctx, target, from) {
150
- const dir = from <= target ? -1 : 1;
151
- const chromatic = target + dir;
152
- if (ctx.rng.prob(0.5)) {
153
- return foldIntoBand(chromatic, ctx.low);
154
- }
155
- const cand = nearestScaleTone(target + dir * 2, ctx.key);
156
- const step = Math.abs(cand - target);
157
- return foldIntoBand(step >= 1 && step <= 2 ? cand : chromatic, ctx.low);
158
- }
159
- function foldIntoBand(midi, low) {
160
- let result = midi;
161
- while (result < low) {
162
- result += 12;
163
- }
164
- while (result > low + 12) {
165
- result -= 12;
166
- }
167
- return result;
168
- }
169
- function buildWalking(ctx, seg, next) {
170
- const tones = chordTonePcs(seg.chord);
171
- const positions = beatPositions(seg.startBeat, seg.endBeat);
172
- const count = positions.length;
173
- positions.forEach((pos, i) => {
174
- if (i === 0) {
175
- ctx.prevMidi = emit(ctx, pos, bassPcOf(seg.chord));
176
- return;
177
- }
178
- if (next && count > 1 && i === count - 1) {
179
- const nextBass = placePc(bassPcOf(next.chord), ctx.prevMidi, ctx.low);
180
- const midi = approachNote(ctx, nextBass, ctx.prevMidi);
181
- ctx.prevMidi = emit(ctx, pos, pitchClassOf(midi), midi);
182
- return;
183
- }
184
- const pc = tones[i % tones.length] ?? bassPcOf(seg.chord);
185
- ctx.prevMidi = emit(ctx, pos, pc);
186
- });
187
- }
188
- function generateBassLine(opts) {
189
- assertGenerationBudget(opts.segments.length, "bass segments");
190
- for (let index = 0; index < opts.segments.length; index += 1) {
191
- const segment = opts.segments[index];
192
- if (!segment) continue;
193
- assertRange(segment.startBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].startBeat`);
194
- assertRange(segment.endBeat, 0, Number.MAX_SAFE_INTEGER, `segments[${index}].endBeat`);
195
- if (segment.endBeat <= segment.startBeat) {
196
- throw new InvalidInputError(`segments[${index}] must have a positive duration`);
197
- }
198
- }
199
- const segments = [...opts.segments].sort((a, b) => a.startBeat - b.startBeat);
200
- if (segments.length === 0) {
201
- return [];
202
- }
203
- const ts = opts.ts ?? DEFAULT_TS;
204
- assertTimeSignature(ts);
205
- const style = assertOneOf(opts.style ?? DEFAULT_STYLE, BASS_STYLES, "bass style");
206
- const octave = opts.octave ?? DEFAULT_OCTAVE;
207
- assertInteger(octave, "bass octave", -1, 9);
208
- const estimatedNotes = segments.reduce(
209
- (count, segment) => count + Math.max(1, Math.ceil(segment.endBeat - segment.startBeat)),
210
- 0
211
- );
212
- assertGenerationBudget(estimatedNotes, "bass notes");
213
- const low = octave * 12 + 12;
214
- const ctx = {
215
- ts,
216
- low,
217
- key: opts.key,
218
- rng: createRng(opts.seed ?? 0),
219
- notes: [],
220
- prevMidi: low
221
- };
222
- for (let s = 0; s < segments.length; s += 1) {
223
- const seg = segments[s];
224
- if (!seg) {
225
- continue;
226
- }
227
- switch (style) {
228
- case "rootFifth":
229
- buildRootFifth(ctx, seg);
230
- break;
231
- case "pop":
232
- buildPop(ctx, seg);
233
- break;
234
- case "walking":
235
- buildWalking(ctx, seg, segments[s + 1]);
236
- break;
237
- case "arpeggio":
238
- buildArpeggio(ctx, seg);
239
- break;
240
- default:
241
- buildRoot(ctx, seg);
242
- break;
243
- }
244
- }
245
- ctx.notes.sort((a, b) => a.startBeat - b.startBeat);
246
- const lastEnd = segments.reduce((m, seg) => Math.max(m, seg.endBeat), Number.NEGATIVE_INFINITY);
247
- const out = [];
248
- for (let i = 0; i < ctx.notes.length; i += 1) {
249
- const note = ctx.notes[i];
250
- if (!note) {
251
- continue;
252
- }
253
- const nextStart = ctx.notes[i + 1]?.startBeat ?? lastEnd;
254
- const durationBeat = nextStart - note.startBeat;
255
- if (durationBeat <= EPS) {
256
- continue;
257
- }
258
- out.push({
259
- pitch: note.pitch,
260
- startBeat: note.startBeat,
261
- durationBeat,
262
- velocity: note.velocity
263
- });
264
- }
265
- return out;
266
- }
267
-
268
- // src/generate/countermelody/index.ts
269
- var DEFAULT_TS2 = { numerator: 4, denominator: 4 };
270
- var EPS2 = 1e-9;
271
- var GRID_STEP = 0.5;
272
- var MIN_ONSET_GAP = 1;
273
- var REGISTER_OFFSET = 12;
274
- var DEFAULT_HALF_RANGE = 10;
275
- var HOLD_ON_BEAT_PROB = 0.55;
276
- var HOLD_OFF_BEAT_PROB = 0.2;
277
- var REINFORCE_PROB = 0.35;
278
- var DEFAULT_MELODY_VELOCITY = 96;
279
- var VELOCITY_DROP = 16;
280
- var TIE_BREAK_JITTER = 1e-6;
281
- function isWholeBeat(beat) {
282
- return Math.abs(beat - Math.round(beat)) < EPS2;
283
- }
284
- function melodyNoteAt(melody, beat) {
285
- return melody.at(beat)?.note;
286
- }
287
- function melodyAttacksAt(melody, beat) {
288
- return melody.attacksAt(beat);
289
- }
290
- function melodySnapshotAt(melody, beat) {
291
- const current = melodyNoteAt(melody, beat);
292
- if (current === void 0) {
293
- return void 0;
294
- }
295
- const attacked = Math.abs(current.startBeat - beat) < EPS2;
296
- const previous = attacked ? melodyNoteAt(melody, beat - EPS2 * 2) : current;
297
- const snapshot = { pitch: current.pitch };
298
- if (previous !== void 0) {
299
- snapshot.prevPitch = previous.pitch;
300
- }
301
- return snapshot;
302
- }
303
- function followOnsets(melody) {
304
- return [...new Set(melody.notes.map(({ note }) => note.startBeat))];
305
- }
306
- function complementOnsets(melody, ts, spanStart, spanEnd, rng) {
307
- const onsets = [];
308
- const gridStart = Math.floor(spanStart / GRID_STEP) * GRID_STEP;
309
- let last = Number.NEGATIVE_INFINITY;
310
- for (let beat = gridStart; beat < spanEnd - EPS2; beat += GRID_STEP) {
311
- const attacked = melodyAttacksAt(melody, beat);
312
- const sounding = melodyNoteAt(melody, beat) !== void 0;
313
- let place = false;
314
- if (!attacked && !sounding) {
315
- place = isWholeBeat(beat);
316
- } else if (!attacked) {
317
- place = rng.prob(isWholeBeat(beat) ? HOLD_ON_BEAT_PROB : HOLD_OFF_BEAT_PROB);
318
- } else if (isStrongBeat(beat, ts)) {
319
- place = rng.prob(REINFORCE_PROB);
320
- }
321
- if (place && beat - last >= MIN_ONSET_GAP - EPS2) {
322
- onsets.push(beat);
323
- last = beat;
324
- }
325
- }
326
- return onsets;
327
- }
328
- function scoreCandidate(pitch, melPitch, melPrev, prevPitch, chord, center) {
329
- let score = 0;
330
- if (melPitch !== void 0) {
331
- const ic = Math.abs(pitch - melPitch) % 12;
332
- if (ic === 3 || ic === 4 || ic === 8 || ic === 9) {
333
- score += 2;
334
- } else if (ic === 0 || ic === 7) {
335
- score += 0.5;
336
- } else if (ic === 1 || ic === 2 || ic === 6 || ic === 10 || ic === 11) {
337
- score -= 2;
338
- }
339
- }
340
- if (chord && chordToneRole(pitch, chord) !== null) {
341
- score += 1;
342
- }
343
- if (prevPitch !== void 0) {
344
- const move = pitch - prevPitch;
345
- const dist = Math.abs(move);
346
- score -= dist * 0.3;
347
- if (dist > 0 && dist <= 2) {
348
- score += 1;
349
- }
350
- if (isForbiddenMelodicLeap(prevPitch, pitch)) {
351
- score -= 6;
352
- }
353
- if (melPitch !== void 0 && melPrev !== void 0) {
354
- const melMove = melPitch - melPrev;
355
- if (move > 0 && melMove < 0 || move < 0 && melMove > 0) {
356
- score += 2;
357
- } else if (move === 0 || melMove === 0) {
358
- score += 1;
359
- } else {
360
- score -= 1;
361
- }
362
- }
363
- }
364
- score -= Math.abs(pitch - center) * 0.02;
365
- return score;
366
- }
367
- function heldNoteBoundaries(melody, startBeat, endBeat, chordChangeBeats) {
368
- const boundaries = /* @__PURE__ */ new Set([startBeat]);
369
- for (const onset of melody.onsetsBetween(startBeat, endBeat)) {
370
- boundaries.add(onset);
371
- }
372
- for (const beat of chordChangeBeats) {
373
- if (beat > startBeat + EPS2 && beat < endBeat - EPS2) {
374
- boundaries.add(beat);
375
- }
376
- }
377
- const firstGrid = Math.floor(startBeat / GRID_STEP + 1) * GRID_STEP;
378
- for (let beat = firstGrid; beat < endBeat - EPS2; beat += GRID_STEP) {
379
- boundaries.add(beat);
380
- }
381
- return [...boundaries].sort((a, b) => a - b);
382
- }
383
- function boundaryContexts(melody, harmony, startBeat, endBeat, ts) {
384
- const boundaries = heldNoteBoundaries(melody, startBeat, endBeat, harmony.chordChangeBeats);
385
- return boundaries.map((boundary) => {
386
- const melodyVoice = melodySnapshotAt(melody, boundary);
387
- return {
388
- melodyVoice,
389
- otherVoices: melodyVoice === void 0 ? [] : [melodyVoice],
390
- chord: harmony.chordAt(boundary),
391
- strongBeat: isStrongBeat(boundary, ts),
392
- atCounterOnset: Math.abs(boundary - startBeat) < EPS2
393
- };
394
- });
395
- }
396
- function heldPitchSafety(pitch, contexts, prevPitch, key, profile, register, low, high) {
397
- let worst = 0 /* Safe */;
398
- for (const context of contexts) {
399
- const melodyVoice = context.melodyVoice;
400
- if (melodyVoice !== void 0 && (register === "below" && pitch >= melodyVoice.pitch || register === "above" && pitch <= melodyVoice.pitch)) {
401
- return null;
402
- }
403
- const result = evaluateSafety(
404
- {
405
- profile,
406
- candidatePitch: pitch,
407
- prevPitch: context.atCounterOnset ? prevPitch : pitch,
408
- chord: context.chord,
409
- key,
410
- otherVoices: context.otherVoices,
411
- strongBeat: context.strongBeat,
412
- vocalLow: low,
413
- vocalHigh: high
414
- },
415
- { suggestions: false }
416
- );
417
- worst = Math.max(worst, result.safety);
418
- if (worst === 2 /* Dissonant */) {
419
- return worst;
420
- }
421
- }
422
- return worst;
423
- }
424
- function generateCounterMelody(opts) {
425
- const timeline = opts.timeline;
426
- const chordAt = timeline === void 0 ? opts.chordAt : (beat) => timeline.at(beat);
427
- if (chordAt === void 0) {
428
- throw new TypeError("countermelody needs a timeline or a chordAt callback");
429
- }
430
- const chordChangeBeats = timeline === void 0 ? opts.chordChangeBeats ?? [] : timeline.segments.flatMap((segment) => [segment.startBeat, segment.endBeat]);
431
- const register = opts.register ?? "below";
432
- const melody = createNoteEventIndex(opts.melody, {
433
- allowNonPositiveDuration: true,
434
- tieBreak: register === "below" ? "highest" : "lowest"
435
- });
436
- const ts = opts.ts ?? DEFAULT_TS2;
437
- assertTimeSignature(ts);
438
- const rhythm = assertOneOf(opts.rhythm ?? "complement", ["complement", "follow"], "rhythm");
439
- const profile = assertOneOf(opts.profile ?? "pop", ["strict", "pop"], "safety profile");
440
- const rng = createRng(opts.seed ?? 0);
441
- if (opts.pitchLow !== void 0) {
442
- assertInteger(opts.pitchLow, "countermelody pitchLow");
443
- }
444
- if (opts.pitchHigh !== void 0) {
445
- assertInteger(opts.pitchHigh, "countermelody pitchHigh");
446
- }
447
- if (melody.notes.every((indexed) => indexed.note.durationBeat <= 0)) {
448
- return [];
449
- }
450
- const spanStart = melody.notes[0]?.note.startBeat ?? 0;
451
- const spanEnd = melody.notes.reduce((end, n) => Math.max(end, n.endBeat), spanStart);
452
- assertGenerationBudget(Math.ceil((spanEnd - spanStart) / GRID_STEP), "countermelody grid");
453
- const meanPitch = melody.notes.reduce((sum, n) => sum + n.note.pitch, 0) / melody.notes.length;
454
- const defaultCenter = register === "below" ? meanPitch - REGISTER_OFFSET : meanPitch + REGISTER_OFFSET;
455
- const low = opts.pitchLow ?? Math.round(defaultCenter) - DEFAULT_HALF_RANGE;
456
- const high = opts.pitchHigh ?? Math.round(defaultCenter) + DEFAULT_HALF_RANGE;
457
- assertInteger(low, "countermelody pitchLow");
458
- assertInteger(high, "countermelody pitchHigh");
459
- if (low > high) {
460
- throw new InvalidInputError(
461
- `countermelody pitchLow must not exceed pitchHigh; received ${low} > ${high}`
462
- );
463
- }
464
- assertGenerationBudget(high - low + 1, "countermelody pitch candidates");
465
- assertGenerationBudget(chordChangeBeats.length, "chord change beats");
466
- for (let index = 0; index < chordChangeBeats.length; index += 1) {
467
- assertRange(
468
- chordChangeBeats[index] ?? Number.NaN,
469
- 0,
470
- Number.MAX_SAFE_INTEGER,
471
- `chordChangeBeats[${index}]`
472
- );
473
- }
474
- const center = (low + high) / 2;
475
- const onsets = rhythm === "follow" ? followOnsets(melody) : complementOnsets(melody, ts, spanStart, spanEnd, rng);
476
- const boundaryEstimate = onsets.length + Math.ceil((spanEnd - spanStart) / GRID_STEP) + melody.notes.length + chordChangeBeats.length;
477
- assertGenerationBudget(boundaryEstimate * (high - low + 1), "countermelody search");
478
- const jitter = Array.from({ length: high - low + 1 }, () => rng.next() * TIE_BREAK_JITTER);
479
- const out = [];
480
- let prevPitch;
481
- for (let onsetIndex = 0; onsetIndex < onsets.length; onsetIndex += 1) {
482
- const beat = onsets[onsetIndex];
483
- if (beat === void 0) {
484
- continue;
485
- }
486
- const nextOnset = onsets[onsetIndex + 1];
487
- const endBeat = nextOnset ?? Math.max(spanEnd, beat + GRID_STEP);
488
- const melNote = melodyNoteAt(melody, beat);
489
- const melPitch = melNote?.pitch;
490
- const melodyVoice = melodySnapshotAt(melody, beat);
491
- const melPrev = melodyVoice?.prevPitch;
492
- const contexts = boundaryContexts(melody, { chordAt, chordChangeBeats }, beat, endBeat, ts);
493
- const chord = contexts[0]?.chord ?? chordAt(beat);
494
- const otherVoices = melodyVoice !== void 0 ? [melodyVoice] : [];
495
- const candidates = enumerateSafePitches(
496
- {
497
- profile,
498
- prevPitch,
499
- chord,
500
- key: opts.key,
501
- otherVoices,
502
- strongBeat: isStrongBeat(beat, ts),
503
- vocalLow: low,
504
- vocalHigh: high
505
- },
506
- low,
507
- high
508
- );
509
- let bestPitch;
510
- let bestWorstSafety = 2 /* Dissonant */;
511
- let bestScore = Number.NEGATIVE_INFINITY;
512
- for (const candidate of candidates) {
513
- const worstSafety = heldPitchSafety(
514
- candidate,
515
- contexts,
516
- prevPitch,
517
- opts.key,
518
- profile,
519
- register,
520
- low,
521
- high
522
- );
523
- if (worstSafety === null || worstSafety === 2 /* Dissonant */) {
524
- continue;
525
- }
526
- if (melPitch !== void 0) {
527
- if (prevPitch !== void 0 && melPrev !== void 0 && // Parallel octaves are the perfect-class-zero case of this predicate,
528
- // so a single check covers both parallel fifths and octaves.
529
- createsParallelPerfect(prevPitch, candidate, melPrev, melPitch)) {
530
- continue;
531
- }
532
- }
533
- const score = scoreCandidate(candidate, melPitch, melPrev, prevPitch, chord, center) + (jitter[candidate - low] ?? 0);
534
- const tie = Math.abs(score - bestScore) <= EPS2 && candidate < (bestPitch ?? Number.POSITIVE_INFINITY);
535
- if (worstSafety < bestWorstSafety || worstSafety === bestWorstSafety && (score > bestScore + EPS2 || tie)) {
536
- bestWorstSafety = worstSafety;
537
- bestScore = score;
538
- bestPitch = candidate;
539
- }
540
- }
541
- if (bestPitch === void 0) {
542
- prevPitch = void 0;
543
- continue;
544
- }
545
- const pitch = bestPitch;
546
- const velocity = Math.max(
547
- 1,
548
- Math.min(127, Math.round((melNote?.velocity ?? DEFAULT_MELODY_VELOCITY) - VELOCITY_DROP))
549
- );
550
- out.push({ pitch, startBeat: beat, durationBeat: endBeat - beat, velocity });
551
- prevPitch = pitch;
552
- }
553
- return out;
554
- }
555
-
556
- // src/generate/drums/internal.ts
557
- var GM = {
558
- BD: 36,
559
- SD: 38,
560
- SIDESTICK: 37,
561
- HANDCLAP: 39,
562
- CHH: 42,
563
- FHH: 44,
564
- OHH: 46,
565
- CRASH: 49,
566
- RIDE: 51,
567
- TAMBOURINE: 54,
568
- TOM_H: 50,
569
- TOM_M: 47,
570
- TOM_L: 45,
571
- SHAKER: 82
572
- };
573
- var EIGHTH = 0.5;
574
- var SIXTEENTH = 0.25;
575
- var DRUM_FEELS = ["straight", "swing", "shuffle"];
576
- var GROOVE_STYLES = [
577
- "standard",
578
- "funk",
579
- "shuffle",
580
- "bossa",
581
- "trap",
582
- "halftime",
583
- "breakbeat",
584
- "house",
585
- "synthpop"
586
- ];
587
- var DRUM_ROLES = ["full", "ambient", "minimal", "fxOnly"];
588
- var PUBLIC_SECTIONS = [
589
- "intro",
590
- "verse",
591
- "prechorus",
592
- "chorus",
593
- "bridge",
594
- "outro"
595
- ];
596
- var STYLE_MAP = {
597
- // Standard pop pulse: kick on beats 1 and 3, backbeat snare on 2 and 4.
598
- standard: { style: "standard", feel: "straight", ghostBoost: false, snareBeat3: false },
599
- funk: { style: "upbeat", feel: "straight", ghostBoost: true, snareBeat3: false },
600
- shuffle: { style: "standard", feel: "shuffle", ghostBoost: false, snareBeat3: false },
601
- bossa: { style: "latin", feel: "straight", ghostBoost: false, snareBeat3: false },
602
- trap: { style: "trap", feel: "straight", ghostBoost: false, snareBeat3: true },
603
- halftime: { style: "sparse", feel: "straight", ghostBoost: false, snareBeat3: true },
604
- breakbeat: { style: "rock", feel: "straight", ghostBoost: false, snareBeat3: false },
605
- // Four-on-the-floor dance kick with idol-style auxiliary percussion.
606
- house: { style: "fourOnFloor", feel: "straight", ghostBoost: false, snareBeat3: false },
607
- // Synth-pop pulse with denser 16th hi-hats and busy kick syncopation.
608
- synthpop: { style: "synth", feel: "straight", ghostBoost: true, snareBeat3: false }
609
- };
610
- function mapStyle(style) {
611
- return STYLE_MAP[style];
612
- }
613
- function mapSection(section) {
614
- switch (section) {
615
- case "intro":
616
- return "intro";
617
- case "verse":
618
- return "a";
619
- case "prechorus":
620
- return "b";
621
- case "chorus":
622
- return "chorus";
623
- case "bridge":
624
- return "bridge";
625
- case "outro":
626
- return "outro";
627
- }
628
- }
629
- function mapDensity(density) {
630
- if (density < 0.34) {
631
- return "thin";
632
- }
633
- if (density < 0.67) {
634
- return "normal";
635
- }
636
- return "thick";
637
- }
638
- function feelSwingAmount(feel) {
639
- if (feel === "swing") {
640
- return 0.33;
641
- }
642
- if (feel === "shuffle") {
643
- return 0.5;
644
- }
645
- return 0;
646
- }
647
- function ghostMoodCategory(style) {
648
- if (style === "sparse") {
649
- return 0 /* Calm */;
650
- }
651
- if (style === "trap" || style === "upbeat") {
652
- return 2 /* Energetic */;
653
- }
654
- return 1 /* Standard */;
655
- }
656
- function percMoodCategory(style) {
657
- switch (style) {
658
- case "sparse":
659
- return 0 /* Calm */;
660
- case "upbeat":
661
- case "latin":
662
- return 2 /* Energetic */;
663
- case "fourOnFloor":
664
- return 3 /* Idol */;
665
- case "trap":
666
- case "rock":
667
- return 4 /* RockDark */;
668
- default:
669
- return 1 /* Standard */;
670
- }
671
- }
672
- function sectionIndex(section) {
673
- switch (section) {
674
- case "intro":
675
- return 0;
676
- case "a":
677
- return 1;
678
- case "b":
679
- return 2;
680
- case "chorus":
681
- return 3;
682
- case "bridge":
683
- return 4;
684
- case "outro":
685
- return 6;
686
- }
687
- }
688
- function sectionEnergy(section) {
689
- switch (section) {
690
- case "intro":
691
- case "outro":
692
- return "low";
693
- case "a":
694
- case "bridge":
695
- return "medium";
696
- case "b":
697
- return "high";
698
- case "chorus":
699
- return "peak";
700
- }
701
- }
702
- function sectionVelocityMultiplier(section) {
703
- switch (section) {
704
- case "intro":
705
- case "outro":
706
- return 0.8;
707
- case "a":
708
- return 0.9;
709
- case "b":
710
- return 1;
711
- case "chorus":
712
- return 1.1;
713
- case "bridge":
714
- return 0.85;
715
- }
716
- }
717
- function sectionDensityMultiplier(section) {
718
- switch (section) {
719
- case "intro":
720
- return 0.5;
721
- case "a":
722
- return 0.7;
723
- case "b":
724
- return 0.85;
725
- case "chorus":
726
- return 1;
727
- case "bridge":
728
- return 0.6;
729
- case "outro":
730
- return 0.6;
731
- }
732
- }
733
- function calculateVelocity(section, beat) {
734
- const base = 80;
735
- const beatAdj = beat === 0 ? 10 : beat === 2 ? 5 : 0;
736
- const velocity = (base + beatAdj) * sectionVelocityMultiplier(section);
737
- return clampVel(velocity);
738
- }
739
- function clampVel(velocity) {
740
- return Math.max(1, Math.min(127, Math.round(velocity)));
741
- }
742
-
743
- // src/generate/drums/ghost.ts
744
- var GHOST_DENSITY_TABLE = [
745
- /* intro */
746
- [0, 1, 1],
747
- /* a */
748
- [0, 1, 2],
749
- /* b */
750
- [1, 2, 2],
751
- /* chorus */
752
- [1, 2, 3],
753
- /* bridge */
754
- [1, 1, 2],
755
- /* interlude */
756
- [0, 1, 1],
757
- /* outro */
758
- [0, 1, 1]
759
- ];
760
- function levelToProbability(level) {
761
- switch (level) {
762
- case 0:
763
- return 0;
764
- case 1:
765
- return 0.15;
766
- case 2:
767
- return 0.3;
768
- case 3:
769
- return 0.45;
770
- }
771
- }
772
- function clampLevel(level) {
773
- return Math.max(0, Math.min(3, level));
774
- }
775
- function adjustForBpm(level, bpm) {
776
- if (bpm >= 160 && level !== 0) {
777
- return clampLevel(level - 1);
778
- }
779
- if (bpm <= 90 && level !== 3) {
780
- return clampLevel(level + 1);
781
- }
782
- return level;
783
- }
784
- function getGhostDensity(mood, section, backingDensity, bpm) {
785
- const row = GHOST_DENSITY_TABLE[sectionIndex(section)] ?? GHOST_DENSITY_TABLE[1];
786
- const level = adjustForBpm(row?.[mood] ?? 0, bpm);
787
- if (backingDensity === "thin" && level !== 0) {
788
- return levelToProbability(clampLevel(level - 1));
789
- }
790
- if (backingDensity === "thick" && level !== 3) {
791
- return levelToProbability(clampLevel(level + 1));
792
- }
793
- return levelToProbability(level);
794
- }
795
- function getGhostVelocity(section, beatPosition) {
796
- let base = 0.4;
797
- const even = beatPosition % 2 === 0 ? 0.05 : 0;
798
- switch (section) {
799
- case "a":
800
- base = 0.35 + even;
801
- break;
802
- case "chorus":
803
- base = 0.5 + even;
804
- break;
805
- case "bridge":
806
- base = 0.25 + even;
807
- break;
808
- case "b":
809
- base = 0.4 + even;
810
- break;
811
- case "intro":
812
- case "outro":
813
- base = 0.38;
814
- break;
815
- }
816
- return Math.max(0.25, Math.min(0.65, base));
817
- }
818
- function getGhostProbabilityAtPosition(beat, sixteenthInBeat) {
819
- const leadsIntoBackbeat = (beat === 0 || beat === 2) && sixteenthInBeat === 3;
820
- return leadsIntoBackbeat ? 0.6 : 0.25;
821
- }
822
- function selectGhostPositions(mood, rng) {
823
- if (mood === 2 /* Energetic */) {
824
- return ["e", "a"];
825
- }
826
- if (mood === 0 /* Calm */) {
827
- return rng.prob(0.5) ? ["e"] : [];
828
- }
829
- return ["e"];
830
- }
831
-
832
- // src/generate/drums/hihat.ts
833
- var HH_16TH_BPM_THRESHOLD = 150;
834
- var FHH_VEL_MIN = 45;
835
- var FHH_VEL_MAX = 60;
836
- var OHH_VEL_BOOST = 7;
837
- function shouldPlayHiHat(role) {
838
- return role !== "fxOnly";
839
- }
840
- function roleHiHatInstrument(role, useRide) {
841
- if (role === "ambient") {
842
- return GM.RIDE;
843
- }
844
- return useRide ? GM.RIDE : GM.CHH;
845
- }
846
- function sparser(level) {
847
- if (level === "sixteenth") {
848
- return "eighth";
849
- }
850
- if (level === "eighth") {
851
- return "quarter";
852
- }
853
- return "quarter";
854
- }
855
- function denser(level) {
856
- if (level === "quarter") {
857
- return "eighth";
858
- }
859
- if (level === "eighth") {
860
- return "sixteenth";
861
- }
862
- return "sixteenth";
863
- }
864
- function getHiHatLevel(section, style, backingDensity, bpm, rng) {
865
- const allow16th = bpm < HH_16TH_BPM_THRESHOLD;
866
- let base = "eighth";
867
- if (style === "sparse") {
868
- base = section === "chorus" ? "eighth" : "quarter";
869
- } else if (style === "fourOnFloor") {
870
- if (allow16th && section === "chorus" && rng.prob(0.25)) {
871
- return "sixteenth";
872
- }
873
- return "eighth";
874
- } else if (style === "synth") {
875
- if (!allow16th) {
876
- return "eighth";
877
- }
878
- if (section === "a" && rng.prob(0.2)) {
879
- return "eighth";
880
- }
881
- return "sixteenth";
882
- } else if (style === "trap") {
883
- return allow16th ? "sixteenth" : "eighth";
884
- } else if (style === "latin") {
885
- if (allow16th && section === "chorus" && rng.prob(0.3)) {
886
- return "sixteenth";
887
- }
888
- return "eighth";
889
- } else {
890
- switch (section) {
891
- case "intro":
892
- base = "quarter";
893
- break;
894
- case "outro":
895
- base = "eighth";
896
- break;
897
- case "a":
898
- base = rng.prob(0.3) ? "quarter" : "eighth";
899
- break;
900
- case "b":
901
- base = allow16th && rng.prob(0.25) ? "sixteenth" : "eighth";
902
- break;
903
- case "chorus":
904
- base = allow16th && rng.prob(0.35) ? "sixteenth" : "eighth";
905
- break;
906
- case "bridge":
907
- base = "eighth";
908
- break;
909
- }
910
- }
911
- if (backingDensity === "thin") {
912
- base = sparser(base);
913
- } else if (backingDensity === "thick") {
914
- base = denser(base);
915
- }
916
- if (!allow16th && base === "sixteenth") {
917
- base = "eighth";
918
- }
919
- return base;
920
- }
921
- function hiHatVelocityMultiplier(sixteenth, rng) {
922
- let baseValue;
923
- switch (sixteenth) {
924
- case 0:
925
- baseValue = 0.95;
926
- break;
927
- case 2:
928
- baseValue = 0.75;
929
- break;
930
- case 1:
931
- baseValue = 0.55;
932
- break;
933
- default:
934
- baseValue = 0.5;
935
- break;
936
- }
937
- return baseValue * rng.float(0.95, 1.05);
938
- }
939
- function openHiHatBarInterval(section, style) {
940
- if (style === "sparse") {
941
- return section === "chorus" ? 4 : 0;
942
- }
943
- switch (section) {
944
- case "intro":
945
- return style === "fourOnFloor" ? 4 : 0;
946
- case "a":
947
- return style === "fourOnFloor" || style === "upbeat" ? 2 : 4;
948
- case "b":
949
- return 2;
950
- case "chorus":
951
- return style === "rock" || style === "fourOnFloor" ? 1 : 2;
952
- case "bridge":
953
- return 0;
954
- case "outro":
955
- return 4;
956
- }
957
- }
958
- function openHiHatBeat(section, rng) {
959
- if (section === "chorus") {
960
- const choice = rng.range(0, 3);
961
- if (choice < 2) {
962
- return 3;
963
- }
964
- if (choice < 3) {
965
- return 1;
966
- }
967
- return 2;
968
- }
969
- return 3;
970
- }
971
- function shouldUseFootHiHat(section, role) {
972
- if (role === "fxOnly") {
973
- return false;
974
- }
975
- switch (section) {
976
- case "intro":
977
- case "bridge":
978
- case "outro":
979
- return true;
980
- default:
981
- return role === "ambient" || role === "minimal";
982
- }
983
- }
984
- function sectionHiHatType(section, role) {
985
- if (role === "ambient") {
986
- return "ride";
987
- }
988
- if (role === "minimal") {
989
- return "pedal";
990
- }
991
- switch (section) {
992
- case "intro":
993
- case "a":
994
- return "pedal";
995
- case "b":
996
- return "closed";
997
- case "chorus":
998
- return "open";
999
- case "bridge":
1000
- return "ride";
1001
- case "outro":
1002
- return "halfOpen";
1003
- }
1004
- }
1005
- function hiHatNote(type) {
1006
- switch (type) {
1007
- case "pedal":
1008
- return GM.FHH;
1009
- case "open":
1010
- return GM.OHH;
1011
- case "ride":
1012
- return GM.RIDE;
1013
- default:
1014
- return GM.CHH;
1015
- }
1016
- }
1017
- function hiHatTypeVelocityMultiplier(type) {
1018
- switch (type) {
1019
- case "halfOpen":
1020
- return 0.75;
1021
- case "pedal":
1022
- return 0.65;
1023
- case "open":
1024
- return 1;
1025
- case "ride":
1026
- return 0.9;
1027
- default:
1028
- return 0.85;
1029
- }
1030
- }
1031
- function shouldAddOpenHHAccent(section, beat, bar, rng) {
1032
- if (section !== "chorus" && section !== "b") {
1033
- return false;
1034
- }
1035
- if (section === "chorus") {
1036
- if (beat === 1 || beat === 3) {
1037
- return rng.prob(0.6);
1038
- }
1039
- return false;
1040
- }
1041
- if (beat === 3 && bar % 2 === 1) {
1042
- return rng.prob(0.4);
1043
- }
1044
- return false;
1045
- }
1046
- function footHiHatVelocity(rng) {
1047
- return rng.range(FHH_VEL_MIN, FHH_VEL_MAX);
1048
- }
1049
- function shouldUseRideForSection(section, style) {
1050
- if (style === "rock" && section === "chorus") {
1051
- return true;
1052
- }
1053
- if (style === "sparse") {
1054
- return false;
1055
- }
1056
- return section === "bridge";
1057
- }
1058
- function shouldUseBridgeCrossStick(section, beat) {
1059
- return section === "bridge" && (beat === 1 || beat === 3);
1060
- }
1061
-
1062
- // src/generate/drums/swing.ts
1063
- var SIXTEENTH2 = 0.25;
1064
- var EIGHTH2 = 0.5;
1065
- var HALF_16TH = 0.125;
1066
- var DELTA_16 = 1 / 3 - 1 / 4;
1067
- var DELTA_8 = (2 / 3 - 1 / 2) / 2 + (2 / 3 - 1 / 2) / 2;
1068
- function effectiveSwing(feel, swingAmount) {
1069
- if (feel === "straight") {
1070
- return 0;
1071
- }
1072
- if (feel === "shuffle") {
1073
- return Math.min(1, swingAmount * 1.5);
1074
- }
1075
- return swingAmount;
1076
- }
1077
- function quantizeSwing(startBeat, swing, resolution) {
1078
- const clamped = Math.max(0, Math.min(1, swing));
1079
- if (clamped <= 0) {
1080
- return startBeat;
1081
- }
1082
- const base = Math.floor(startBeat);
1083
- const offset = startBeat - base;
1084
- if (resolution === "sixteenth") {
1085
- if (offset >= HALF_16TH && offset < SIXTEENTH2 + HALF_16TH) {
1086
- return base + SIXTEENTH2 + DELTA_16 * clamped;
1087
- }
1088
- if (offset >= SIXTEENTH2 + HALF_16TH && offset < EIGHTH2 + HALF_16TH) {
1089
- return base + EIGHTH2 + DELTA_8 * clamped;
1090
- }
1091
- if (offset >= EIGHTH2 + HALF_16TH && offset < 3 * SIXTEENTH2 + HALF_16TH) {
1092
- return base + 3 * SIXTEENTH2 + DELTA_16 * clamped;
1093
- }
1094
- return startBeat;
1095
- }
1096
- if (offset >= SIXTEENTH2 && offset < 3 * SIXTEENTH2) {
1097
- return base + EIGHTH2 + DELTA_8 * clamped;
1098
- }
1099
- return startBeat;
1100
- }
1101
-
1102
- // src/generate/drums/beat.ts
1103
- function styleSwingFactor(style) {
1104
- if (style === "trap") {
1105
- return 0;
1106
- }
1107
- if (style === "latin") {
1108
- return 0.35;
1109
- }
1110
- return 0.5;
1111
- }
1112
- function sectionSwing(sec, swingAmount) {
1113
- return effectiveSwing(sec.feel, swingAmount) * styleSwingFactor(sec.style);
1114
- }
1115
- function swing16(tick, sec, swingAmount) {
1116
- return quantizeSwing(tick, sectionSwing(sec, swingAmount), "sixteenth");
1117
- }
1118
- function generateKickForBeat(ctx, sec, kick) {
1119
- if (ctx.inPrechorusLift) {
1120
- return;
1121
- }
1122
- const on = [kick.beat1, kick.beat2, kick.beat3, kick.beat4][ctx.beat] ?? false;
1123
- const and = [kick.beat1and, kick.beat2and, kick.beat3and, kick.beat4and][ctx.beat] ?? false;
1124
- if (on) {
1125
- ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);
1126
- }
1127
- if (and) {
1128
- const andTick = swing16(ctx.beatTick + EIGHTH, sec, ctx.swingAmount);
1129
- ctx.track.add(GM.BD, andTick, EIGHTH, ctx.velocity * 0.85);
1130
- }
1131
- }
1132
- function generateSnareForBeat(ctx, sec, isIntroFirst) {
1133
- if (ctx.inPrechorusLift) {
1134
- return;
1135
- }
1136
- const snareOn = sec.snareBeat3 ? ctx.beat === 2 : ctx.beat === 1 || ctx.beat === 3;
1137
- if (!snareOn || isIntroFirst) {
1138
- return;
1139
- }
1140
- if (sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat)) {
1141
- return;
1142
- }
1143
- const backbeatVel = Math.min(127, ctx.velocity + 16);
1144
- const promoteSparseChorus = sec.style === "sparse" && ctx.section === "chorus" && sec.role === "full";
1145
- if (sec.role === "fxOnly") {
1146
- return;
1147
- }
1148
- if (sec.role === "minimal") {
1149
- ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.65);
1150
- return;
1151
- }
1152
- if (promoteSparseChorus) {
1153
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
1154
- } else if (sec.style === "sparse" || sec.role === "ambient") {
1155
- ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);
1156
- } else {
1157
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
1158
- }
1159
- }
1160
- function generateGhostNotesForBeat(ctx, sec) {
1161
- if (ctx.beat !== 0 && ctx.beat !== 2) {
1162
- return;
1163
- }
1164
- const positions = selectGhostPositions(sec.ghostMood, ctx.rng);
1165
- let ghostProb = getGhostDensity(sec.ghostMood, ctx.section, sec.backingDensity, ctx.bpm);
1166
- if (sec.ghostBoost) {
1167
- ghostProb = Math.min(1, ghostProb * 1.4);
1168
- }
1169
- for (const pos of positions) {
1170
- const sixteenthInBeat = pos === "e" ? 1 : 3;
1171
- const posProb = getGhostProbabilityAtPosition(ctx.beat, sixteenthInBeat);
1172
- if (!ctx.rng.prob(ghostProb * posProb)) {
1173
- continue;
1174
- }
1175
- const variation = ctx.rng.float(0.85, 1.15);
1176
- const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2);
1177
- let ghostVel = ctx.velocity * ghostBase * variation;
1178
- if (pos === "a") {
1179
- ghostVel *= 0.9;
1180
- }
1181
- const offset = pos === "e" ? SIXTEENTH : 3 * SIXTEENTH;
1182
- const tick = swing16(ctx.beatTick + offset, sec, ctx.swingAmount);
1183
- ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);
1184
- }
1185
- }
1186
- function generatePreChorusBuildup(ctx, sec, isSectionLastBar) {
1187
- if (sec.style === "sparse") {
1188
- if (isSectionLastBar && ctx.beat === 3) {
1189
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, Math.max(45, ctx.velocity * 0.75));
1190
- ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 0.9);
1191
- }
1192
- return;
1193
- }
1194
- const barsInLift = 2;
1195
- const barInLift = ctx.bar - (ctx.sectionBars - barsInLift);
1196
- const progress = (barInLift * 4 + ctx.beat) / (barsInLift * 4);
1197
- const buildupVel = ctx.velocity * (0.5 + 0.5 * progress);
1198
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);
1199
- ctx.track.add(GM.SD, ctx.beatTick + EIGHTH, EIGHTH, buildupVel * 0.85);
1200
- if (isSectionLastBar && ctx.beat === 3) {
1201
- ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 1.1);
1202
- }
1203
- }
1204
- function generateHiHatForBeat(ctx, sec) {
1205
- if (!shouldPlayHiHat(sec.role)) {
1206
- if (sec.useFootHh && (ctx.beat === 0 || ctx.beat === 2)) {
1207
- ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
1208
- }
1209
- return;
1210
- }
1211
- const crossStick = sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat);
1212
- const hhInstrument = crossStick ? GM.SIDESTICK : roleHiHatInstrument(sec.role, sec.useRide);
1213
- const hhType = sectionHiHatType(ctx.section, sec.role);
1214
- const typeMult = hiHatTypeVelocityMultiplier(hhType);
1215
- const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat;
1216
- const dm = sec.densityMult;
1217
- if (sec.hhLevel === "quarter") {
1218
- const introRest = ctx.section === "intro" && ctx.beat !== 0;
1219
- if (!introRest) {
1220
- if (isDynamicOpen) {
1221
- ctx.track.add(
1222
- GM.OHH,
1223
- ctx.beatTick,
1224
- EIGHTH,
1225
- ctx.velocity * dm * 0.75 * typeMult + OHH_VEL_BOOST
1226
- );
1227
- } else {
1228
- ctx.track.add(
1229
- hhInstrument,
1230
- ctx.beatTick,
1231
- EIGHTH,
1232
- Math.max(20, ctx.velocity * dm * 0.75 * typeMult)
1233
- );
1234
- }
1235
- } else if (sec.useFootHh) {
1236
- ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
1237
- }
1238
- return;
1239
- }
1240
- if (sec.hhLevel === "eighth") {
1241
- for (let eighth = 0; eighth < 2; eighth += 1) {
1242
- let hhTick = ctx.beatTick + eighth * EIGHTH;
1243
- if (eighth === 1) {
1244
- hhTick = swing16(hhTick, sec, ctx.swingAmount);
1245
- }
1246
- if (ctx.section === "intro" && eighth === 1) {
1247
- if (sec.useFootHh && ctx.beat % 2 === 0) {
1248
- ctx.track.add(GM.FHH, hhTick, EIGHTH, footHiHatVelocity(ctx.rng));
1249
- }
1250
- continue;
1251
- }
1252
- const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));
1253
- if (isDynamicOpen && eighth === 0) {
1254
- ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);
1255
- continue;
1256
- }
1257
- let useOpen = false;
1258
- if (sec.style === "fourOnFloor" && eighth === 1) {
1259
- const openProb = Math.max(0.15, Math.min(0.8, 45 / ctx.bpm));
1260
- useOpen = (ctx.beat === 1 || ctx.beat === 3) && ctx.rng.prob(openProb);
1261
- } else if (eighth === 0) {
1262
- useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.rng);
1263
- }
1264
- if (useOpen) {
1265
- ctx.track.add(hiHatNote("open"), hhTick, EIGHTH, Math.max(20, hhVel * 1.1));
1266
- } else {
1267
- ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);
1268
- }
1269
- }
1270
- return;
1271
- }
1272
- for (let sixteenth = 0; sixteenth < 4; sixteenth += 1) {
1273
- let hhTick = ctx.beatTick + sixteenth * SIXTEENTH;
1274
- if (sixteenth !== 0) {
1275
- hhTick = swing16(hhTick, sec, ctx.swingAmount);
1276
- }
1277
- const metricVel = hiHatVelocityMultiplier(sixteenth, ctx.rng);
1278
- const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
1279
- if (isDynamicOpen && sixteenth === 0) {
1280
- ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);
1281
- continue;
1282
- }
1283
- if (ctx.beat === 3 && sixteenth === 3) {
1284
- const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));
1285
- if (ctx.rng.prob(openProb)) {
1286
- ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));
1287
- continue;
1288
- }
1289
- }
1290
- ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel);
1291
- }
1292
- }
1293
-
1294
- // src/generate/drums/euclid.ts
1295
- function euclideanRhythm(pulses, steps, rotation = 0) {
1296
- assertPositiveInt(steps, "euclidean steps");
1297
- assertInteger(pulses, "euclidean pulses");
1298
- assertInteger(rotation, "euclidean rotation");
1299
- assertGenerationBudget(steps, "euclidean pattern steps");
1300
- const p = Math.max(0, Math.min(pulses, steps));
1301
- const base = bjorklund(p, steps);
1302
- const shift = (rotation % steps + steps) % steps;
1303
- if (shift === 0) {
1304
- return base;
1305
- }
1306
- return base.map((_, i) => base[(i - shift + steps) % steps] ?? false);
1307
- }
1308
- function bjorklund(pulses, steps) {
1309
- if (pulses === 0) {
1310
- return Array.from({ length: steps }, () => false);
1311
- }
1312
- if (pulses >= steps) {
1313
- return Array.from({ length: steps }, () => true);
1314
- }
1315
- let head = Array.from({ length: pulses }, () => [true]);
1316
- let remainder = Array.from({ length: steps - pulses }, () => [false]);
1317
- while (remainder.length > 1) {
1318
- const count = Math.min(head.length, remainder.length);
1319
- const next = [];
1320
- for (let i = 0; i < count; i += 1) {
1321
- next.push([...head[i] ?? [], ...remainder[i] ?? []]);
1322
- }
1323
- remainder = head.length > count ? head.slice(count) : remainder.slice(count);
1324
- head = next;
1325
- }
1326
- return [...head.flat(), ...remainder.flat()];
1327
- }
1328
-
1329
- // src/generate/drums/fills.ts
1330
- function getFillStartBeat(energy) {
1331
- switch (energy) {
1332
- case "low":
1333
- return 3;
1334
- case "medium":
1335
- return 2;
1336
- case "high":
1337
- case "peak":
1338
- return 0;
1339
- }
1340
- }
1341
- function selectFillType(from, to, style, nextEnergy, rng) {
1342
- if (style === "sparse") {
1343
- return rng.range(0, 1) === 0 ? "simpleCrash" : "breakdownFill";
1344
- }
1345
- const toChorus = to === "chorus";
1346
- const fromIntro = from === "intro";
1347
- const highEnergy = style === "rock" || style === "fourOnFloor";
1348
- if (nextEnergy === "low") {
1349
- switch (rng.range(0, 2)) {
1350
- case 0:
1351
- return "simpleCrash";
1352
- case 1:
1353
- return "breakdownFill";
1354
- default:
1355
- return "halfTimeFill";
1356
- }
1357
- }
1358
- if (toChorus) {
1359
- const choice2 = rng.range(0, 7);
1360
- if (highEnergy) {
1361
- switch (choice2) {
1362
- case 0:
1363
- case 1:
1364
- return "tomDescend";
1365
- case 2:
1366
- return "snareRoll";
1367
- case 3:
1368
- return "linearFill";
1369
- case 4:
1370
- return "bdSnareAlternate";
1371
- case 5:
1372
- return "flamsAndDrags";
1373
- case 6:
1374
- return "tomShuffle";
1375
- default:
1376
- return "ghostToAccent";
1377
- }
1378
- }
1379
- switch (choice2) {
1380
- case 0:
1381
- case 1:
1382
- return "snareTomCombo";
1383
- case 2:
1384
- return "tomDescend";
1385
- case 3:
1386
- return "ghostToAccent";
1387
- case 4:
1388
- return "hiHatChoke";
1389
- case 5:
1390
- return "linearFill";
1391
- default:
1392
- return "snareRoll";
1393
- }
1394
- }
1395
- if (fromIntro) {
1396
- switch (rng.range(0, 5)) {
1397
- case 0:
1398
- case 1:
1399
- return "snareRoll";
1400
- case 2:
1401
- return "simpleCrash";
1402
- case 3:
1403
- return "ghostToAccent";
1404
- case 4:
1405
- return "breakdownFill";
1406
- default:
1407
- return "halfTimeFill";
1408
- }
1409
- }
1410
- const choice = rng.range(0, 7);
1411
- if (highEnergy) {
1412
- switch (choice) {
1413
- case 0:
1414
- return "tomDescend";
1415
- case 1:
1416
- return "snareRoll";
1417
- case 2:
1418
- return "tomAscend";
1419
- case 3:
1420
- return "snareTomCombo";
1421
- case 4:
1422
- return "linearFill";
1423
- case 5:
1424
- return "bdSnareAlternate";
1425
- case 6:
1426
- return "flamsAndDrags";
1427
- default:
1428
- return "tomShuffle";
1429
- }
1430
- }
1431
- switch (choice) {
1432
- case 0:
1433
- case 1:
1434
- return "snareRoll";
1435
- case 2:
1436
- return "snareTomCombo";
1437
- case 3:
1438
- return "ghostToAccent";
1439
- case 4:
1440
- return "hiHatChoke";
1441
- case 5:
1442
- return "halfTimeFill";
1443
- default:
1444
- return "breakdownFill";
1445
- }
1446
- }
1447
- function generateFill(track, beatTick, beat, fillType, velocity) {
1448
- const fillVel = velocity * 0.9;
1449
- const accentVel = velocity * 0.95;
1450
- const S = SIXTEENTH;
1451
- const E = EIGHTH;
1452
- if (beat === 0) {
1453
- track.add(GM.BD, beatTick, E, fillVel);
1454
- track.add(GM.SD, beatTick + E, E, fillVel - 5);
1455
- return;
1456
- }
1457
- if (beat === 1) {
1458
- track.add(GM.SD, beatTick, E, fillVel);
1459
- track.add(GM.TOM_H, beatTick + E, E, fillVel - 3);
1460
- return;
1461
- }
1462
- switch (fillType) {
1463
- case "snareRoll":
1464
- if (beat === 2) {
1465
- for (let i = 0; i < 4; i += 1) {
1466
- track.add(GM.SD, beatTick + i * S, S, fillVel * (0.6 + 0.1 * i));
1467
- }
1468
- } else {
1469
- for (let i = 0; i < 3; i += 1) {
1470
- track.add(GM.SD, beatTick + i * S, S, fillVel * (0.7 + 0.1 * i));
1471
- }
1472
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
1473
- }
1474
- break;
1475
- case "tomDescend":
1476
- if (beat === 2) {
1477
- track.add(GM.SD, beatTick, E, fillVel);
1478
- track.add(GM.TOM_H, beatTick + E, E, fillVel - 5);
1479
- } else {
1480
- track.add(GM.TOM_H, beatTick, S, fillVel);
1481
- track.add(GM.TOM_M, beatTick + S, S, fillVel - 3);
1482
- track.add(GM.TOM_M, beatTick + E, S, fillVel - 5);
1483
- track.add(GM.TOM_L, beatTick + E + S, S, accentVel);
1484
- }
1485
- break;
1486
- case "tomAscend":
1487
- if (beat === 2) {
1488
- track.add(GM.SD, beatTick, E, fillVel);
1489
- track.add(GM.TOM_L, beatTick + E, E, fillVel - 5);
1490
- } else {
1491
- track.add(GM.TOM_L, beatTick, S, fillVel);
1492
- track.add(GM.TOM_M, beatTick + S, S, fillVel + 3);
1493
- track.add(GM.TOM_M, beatTick + E, S, fillVel + 5);
1494
- track.add(GM.TOM_H, beatTick + E + S, S, accentVel);
1495
- }
1496
- break;
1497
- case "snareTomCombo":
1498
- if (beat === 2) {
1499
- track.add(GM.SD, beatTick, E, fillVel);
1500
- track.add(GM.SD, beatTick + E, S, fillVel - 5);
1501
- track.add(GM.TOM_H, beatTick + E + S, S, fillVel);
1502
- } else {
1503
- track.add(GM.TOM_M, beatTick, S, fillVel);
1504
- track.add(GM.SD, beatTick + S, S, fillVel - 3);
1505
- track.add(GM.TOM_L, beatTick + E, S, fillVel + 2);
1506
- track.add(GM.BD, beatTick + E + S, S, accentVel);
1507
- }
1508
- break;
1509
- case "simpleCrash":
1510
- if (beat === 3) {
1511
- track.add(GM.BD, beatTick + E + S, S, accentVel);
1512
- track.add(GM.CRASH, beatTick + E + S, E, accentVel);
1513
- }
1514
- break;
1515
- case "linearFill":
1516
- if (beat === 2) {
1517
- track.add(GM.BD, beatTick, S, fillVel);
1518
- track.add(GM.SD, beatTick + S, S, fillVel);
1519
- track.add(GM.TOM_H, beatTick + 2 * S, S, fillVel);
1520
- track.add(GM.TOM_M, beatTick + 3 * S, S, fillVel);
1521
- } else {
1522
- track.add(GM.TOM_L, beatTick, S, fillVel + 3);
1523
- track.add(GM.SD, beatTick + S, S, fillVel + 5);
1524
- track.add(GM.BD, beatTick + 2 * S, S, fillVel + 7);
1525
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
1526
- }
1527
- break;
1528
- case "ghostToAccent":
1529
- if (beat === 2) {
1530
- const ghost = fillVel * 0.4;
1531
- track.add(GM.SD, beatTick, S, ghost);
1532
- track.add(GM.SD, beatTick + S, S, ghost + 10);
1533
- track.add(GM.SD, beatTick + 2 * S, S, ghost + 20);
1534
- track.add(GM.SD, beatTick + 3 * S, S, ghost + 30);
1535
- } else {
1536
- track.add(GM.SD, beatTick, E, fillVel);
1537
- track.add(GM.SD, beatTick + E, E, accentVel);
1538
- }
1539
- break;
1540
- case "bdSnareAlternate":
1541
- if (beat === 2) {
1542
- track.add(GM.BD, beatTick, S, fillVel);
1543
- track.add(GM.SD, beatTick + S, S, fillVel);
1544
- track.add(GM.BD, beatTick + 2 * S, S, fillVel + 3);
1545
- track.add(GM.SD, beatTick + 3 * S, S, fillVel + 3);
1546
- } else {
1547
- track.add(GM.BD, beatTick, S, fillVel + 5);
1548
- track.add(GM.SD, beatTick + S, S, fillVel + 5);
1549
- track.add(GM.BD, beatTick + 2 * S, S, accentVel);
1550
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
1551
- }
1552
- break;
1553
- case "hiHatChoke":
1554
- if (beat === 2) {
1555
- track.add(GM.OHH, beatTick, E, fillVel);
1556
- track.add(GM.OHH, beatTick + E, E, fillVel + 5);
1557
- } else {
1558
- track.add(GM.OHH, beatTick, S, fillVel + 8);
1559
- track.add(GM.CHH, beatTick + S, S, accentVel);
1560
- track.add(GM.SD, beatTick + E, E, accentVel);
1561
- }
1562
- break;
1563
- case "tomShuffle":
1564
- if (beat === 2) {
1565
- track.add(GM.TOM_H, beatTick, E, fillVel);
1566
- track.add(GM.TOM_M, beatTick + E + S / 2, S, fillVel - 5);
1567
- } else {
1568
- track.add(GM.TOM_M, beatTick, E, fillVel);
1569
- track.add(GM.TOM_L, beatTick + E + S / 2, S, fillVel + 5);
1570
- }
1571
- break;
1572
- case "breakdownFill":
1573
- if (beat === 3) {
1574
- track.add(GM.SD, beatTick + E, S, accentVel);
1575
- }
1576
- break;
1577
- case "flamsAndDrags":
1578
- if (beat === 2) {
1579
- track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
1580
- track.add(GM.SD, beatTick, E, fillVel);
1581
- track.add(GM.SD, beatTick + E, S / 2, fillVel * 0.6);
1582
- track.add(GM.SD, beatTick + E + S / 2, S / 2, fillVel * 0.6);
1583
- track.add(GM.SD, beatTick + E + S, E, fillVel);
1584
- } else {
1585
- track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
1586
- track.add(GM.SD, beatTick, 1, accentVel);
1587
- }
1588
- break;
1589
- case "halfTimeFill":
1590
- if (beat === 2) {
1591
- track.add(GM.SD, beatTick, 1, accentVel);
1592
- track.add(GM.BD, beatTick, 1, fillVel);
1593
- } else if (beat === 3) {
1594
- track.add(GM.SD, beatTick, EIGHTH, accentVel);
1595
- track.add(GM.SD, beatTick + E, S, fillVel * 0.6);
1596
- track.add(GM.SD, beatTick + E + S, S, accentVel);
1597
- }
1598
- break;
1599
- }
1600
- }
1601
-
1602
- // src/generate/drums/hit.ts
1603
- var DRUM_NOTES = {
1604
- kick: GM.BD,
1605
- snare: GM.SD,
1606
- sideStick: GM.SIDESTICK,
1607
- handClap: GM.HANDCLAP,
1608
- closedHiHat: GM.CHH,
1609
- pedalHiHat: GM.FHH,
1610
- openHiHat: GM.OHH,
1611
- crash: GM.CRASH,
1612
- ride: GM.RIDE,
1613
- tambourine: GM.TAMBOURINE,
1614
- highTom: GM.TOM_H,
1615
- midTom: GM.TOM_M,
1616
- lowTom: GM.TOM_L,
1617
- shaker: GM.SHAKER
1618
- };
1619
- var VOICE_BY_NOTE = new Map(
1620
- Object.entries(DRUM_NOTES).map(([voice, note]) => [note, voice])
1621
- );
1622
- function drumVoiceOf(pitch) {
1623
- return VOICE_BY_NOTE.get(pitch);
1624
- }
1625
- var ONSET_KEY_SCALE = 128;
1626
- var HitList = class {
1627
- hits = [];
1628
- /** Onset keys already occupied, as `pitch:quantizedBeat`. */
1629
- #onsets = /* @__PURE__ */ new Set();
1630
- /** Append a hit, clamping velocity and dropping non-positive positions. */
1631
- add(pitch, startBeat, durationBeat, velocity) {
1632
- const beat = Math.max(0, startBeat);
1633
- this.hits.push({
1634
- pitch,
1635
- startBeat: beat,
1636
- durationBeat,
1637
- velocity: clampVel(velocity)
1638
- });
1639
- this.#onsets.add(onsetKey(pitch, beat));
1640
- }
1641
- /** Whether a hit of this pitch already starts exactly at `startBeat`. */
1642
- hasOnset(pitch, startBeat) {
1643
- return this.#onsets.has(onsetKey(pitch, startBeat));
1644
- }
1645
- /** True when a crash already sits within a 16th of `startBeat`. */
1646
- hasCrashNear(startBeat) {
1647
- const steps = Math.round(0.25 * ONSET_KEY_SCALE);
1648
- for (let step = 0; step < steps; step += 1) {
1649
- if (this.#onsets.has(onsetKey(GM.CRASH, startBeat + step / ONSET_KEY_SCALE))) {
1650
- return true;
1651
- }
1652
- }
1653
- return false;
1654
- }
1655
- };
1656
- function onsetKey(pitch, startBeat) {
1657
- return `${pitch}:${Math.round(startBeat * ONSET_KEY_SCALE)}`;
1658
- }
1659
-
1660
- // src/generate/drums/kick.ts
1661
- var EMPTY = {
1662
- beat1: false,
1663
- beat1and: false,
1664
- beat2: false,
1665
- beat2and: false,
1666
- beat3: false,
1667
- beat3and: false,
1668
- beat4: false,
1669
- beat4and: false
1670
- };
1671
- function isInPreChorusLift(section, bar, sectionBars, nextIsChorus) {
1672
- if (section !== "b" || !nextIsChorus || sectionBars < 3) {
1673
- return false;
1674
- }
1675
- return bar >= sectionBars - 2;
1676
- }
1677
- function getKickPattern(section, style, bar, rng) {
1678
- const p = { ...EMPTY };
1679
- if (section === "outro") {
1680
- p.beat1 = true;
1681
- p.beat3 = true;
1682
- return p;
1683
- }
1684
- switch (style) {
1685
- case "sparse":
1686
- p.beat1 = true;
1687
- if (section === "chorus" && bar % 2 === 1) {
1688
- p.beat3 = true;
1689
- }
1690
- break;
1691
- case "fourOnFloor":
1692
- p.beat1 = p.beat2 = p.beat3 = p.beat4 = true;
1693
- if (section === "chorus" && rng.prob(0.2)) {
1694
- p.beat2and = true;
1695
- }
1696
- break;
1697
- case "upbeat":
1698
- p.beat1 = true;
1699
- p.beat3 = true;
1700
- if (section === "b" || section === "chorus") {
1701
- p.beat2and = rng.prob(0.7);
1702
- }
1703
- if (section === "chorus") {
1704
- p.beat4and = rng.prob(0.6);
1705
- }
1706
- break;
1707
- case "rock":
1708
- p.beat1 = true;
1709
- p.beat3 = true;
1710
- if (section === "chorus") {
1711
- p.beat2and = rng.prob(0.65);
1712
- p.beat4and = rng.prob(0.4);
1713
- } else if (section === "b") {
1714
- p.beat2and = rng.prob(0.3);
1715
- }
1716
- break;
1717
- case "synth":
1718
- p.beat1 = true;
1719
- p.beat3 = true;
1720
- if (section === "b" || section === "chorus") {
1721
- p.beat2and = rng.prob(0.75);
1722
- }
1723
- if (section === "chorus") {
1724
- p.beat4and = rng.prob(0.65);
1725
- }
1726
- break;
1727
- case "trap":
1728
- p.beat1 = true;
1729
- p.beat2and = rng.prob(0.8);
1730
- p.beat3 = rng.prob(0.3);
1731
- p.beat4and = rng.prob(0.7);
1732
- break;
1733
- case "latin":
1734
- p.beat1 = true;
1735
- p.beat2and = true;
1736
- p.beat3 = true;
1737
- p.beat4and = rng.prob(0.5);
1738
- break;
1739
- default:
1740
- p.beat1 = true;
1741
- p.beat3 = true;
1742
- if (section === "b") {
1743
- p.beat2and = rng.prob(0.5);
1744
- } else if (section === "chorus") {
1745
- p.beat2and = rng.prob(0.55);
1746
- p.beat4and = rng.prob(0.35);
1747
- }
1748
- break;
1749
- }
1750
- return p;
1751
- }
1752
-
1753
- // src/generate/drums/percussion.ts
1754
- var F = [false, false, false];
1755
- var PERC_TABLE = [
1756
- /* calm */
1757
- [F, F, F, F, F, F, F, F, F],
1758
- /* std */
1759
- [F, F, [false, true, false], [true, false, true], F, F, F, F, [true, false, true]],
1760
- /* ener */
1761
- [
1762
- F,
1763
- [false, true, false],
1764
- [false, true, false],
1765
- [true, true, true],
1766
- F,
1767
- F,
1768
- F,
1769
- F,
1770
- [true, true, true]
1771
- ],
1772
- /* idol */
1773
- [
1774
- F,
1775
- [false, true, false],
1776
- [false, true, false],
1777
- [true, true, true],
1778
- F,
1779
- F,
1780
- F,
1781
- F,
1782
- [true, true, true]
1783
- ],
1784
- /* rock */
1785
- [F, F, F, [false, false, true], F, F, F, F, [false, false, true]]
1786
- ];
1787
- function getPercussionConfig(mood, section) {
1788
- const row = PERC_TABLE[mood] ?? PERC_TABLE[1 /* Standard */];
1789
- const act = row?.[sectionIndex(section)] ?? F;
1790
- return {
1791
- tambourine: act[0],
1792
- shaker: act[1],
1793
- handclap: act[2],
1794
- shaker16th: act[1]
1795
- };
1796
- }
1797
- function generateAuxPercussionForBar(track, barStart, config, role, densityMult, rng, bpm) {
1798
- if (role === "minimal") {
1799
- return;
1800
- }
1801
- if (config.tambourine) {
1802
- for (let beat = 1; beat <= 3; beat += 2) {
1803
- const raw = 70 * densityMult * rng.float(0.9, 1.1);
1804
- track.add(GM.TAMBOURINE, barStart + beat, EIGHTH, Math.max(40, Math.min(90, raw)));
1805
- }
1806
- }
1807
- if (config.shaker) {
1808
- const use16th = config.shaker16th && bpm < HH_16TH_BPM_THRESHOLD;
1809
- if (use16th) {
1810
- const velCurve = [0.75, 0.45, 0.6, 0.45];
1811
- for (let beat = 0; beat < 4; beat += 1) {
1812
- for (let sub = 0; sub < 4; sub += 1) {
1813
- const raw = 80 * (velCurve[sub] ?? 0.5) * densityMult * rng.float(0.9, 1.1);
1814
- track.add(
1815
- GM.SHAKER,
1816
- barStart + beat + sub * SIXTEENTH,
1817
- SIXTEENTH,
1818
- Math.max(25, Math.min(85, raw))
1819
- );
1820
- }
1821
- }
1822
- } else {
1823
- const velCurve = [0.75, 0.55];
1824
- for (let beat = 0; beat < 4; beat += 1) {
1825
- for (let sub = 0; sub < 2; sub += 1) {
1826
- const raw = 80 * (velCurve[sub] ?? 0.6) * densityMult * rng.float(0.9, 1.1);
1827
- track.add(
1828
- GM.SHAKER,
1829
- barStart + beat + sub * EIGHTH,
1830
- EIGHTH,
1831
- Math.max(25, Math.min(85, raw))
1832
- );
1833
- }
1834
- }
1835
- }
1836
- }
1837
- if (config.handclap) {
1838
- for (let beat = 1; beat <= 3; beat += 2) {
1839
- const raw = 85 * densityMult * rng.float(0.9, 1.1);
1840
- track.add(GM.HANDCLAP, barStart + beat, EIGHTH, Math.max(50, Math.min(100, raw)));
1841
- }
1842
- }
1843
- }
1844
-
1845
- // src/generate/drums/index.ts
1846
- var MAX_HITS_PER_BAR = 128;
1847
- function generateDrums(opts) {
1848
- assertPositiveInt(opts.bars, "drum bars");
1849
- assertRange(opts.bpm, Number.MIN_VALUE, 1e3, "drum bpm");
1850
- assertRange(opts.density, 0, 1, "drum density");
1851
- assertGenerationBudget(opts.bars * MAX_HITS_PER_BAR, "drum hits");
1852
- if (opts.euclideanKick) {
1853
- const steps = opts.euclideanKick.steps ?? 16;
1854
- assertInteger(steps, "euclidean steps", 1, 16);
1855
- assertInteger(opts.euclideanKick.pulses, "euclidean pulses");
1856
- assertInteger(opts.euclideanKick.rotation ?? 0, "euclidean rotation");
1857
- }
1858
- const publicStyle = assertOneOf(opts.style, GROOVE_STYLES, "drum style");
1859
- const publicSection = assertOneOf(opts.section, PUBLIC_SECTIONS, "drum section");
1860
- if (opts.nextSection !== void 0) {
1861
- assertOneOf(opts.nextSection, PUBLIC_SECTIONS, "drum nextSection");
1862
- }
1863
- const track = new HitList();
1864
- const rng = createRng(opts.seed ?? 0);
1865
- const mapping = mapStyle(publicStyle);
1866
- const style = mapping.style;
1867
- const feel = opts.feel === void 0 ? mapping.feel : assertOneOf(opts.feel, DRUM_FEELS, "drum feel");
1868
- const role = opts.role === void 0 ? "full" : assertOneOf(opts.role, DRUM_ROLES, "drum role");
1869
- const playMainVoices = role !== "fxOnly";
1870
- const section = mapSection(publicSection);
1871
- const backingDensity = mapDensity(opts.density);
1872
- const swingAmount = feelSwingAmount(feel);
1873
- let densityMult = sectionDensityMultiplier(section);
1874
- if (backingDensity === "thin") {
1875
- densityMult *= 0.75;
1876
- } else if (backingDensity === "thick") {
1877
- densityMult *= 1.15;
1878
- }
1879
- const sec = {
1880
- style,
1881
- feel,
1882
- densityMult,
1883
- backingDensity,
1884
- hhLevel: getHiHatLevel(section, style, backingDensity, opts.bpm, rng),
1885
- useGhostNotes: (section === "b" || section === "chorus" || section === "bridge") && style !== "sparse",
1886
- ghostBoost: mapping.ghostBoost,
1887
- useRide: shouldUseRideForSection(section, style),
1888
- useFootHh: shouldUseFootHiHat(section, role),
1889
- role,
1890
- ghostMood: ghostMoodCategory(style),
1891
- snareBeat3: mapping.snareBeat3
1892
- };
1893
- const ohhBarInterval = openHiHatBarInterval(section, style);
1894
- const energy = sectionEnergy(section);
1895
- const fillStartBeat = getFillStartBeat(energy);
1896
- const percMood = percMoodCategory(style);
1897
- const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;
1898
- const nextEnergy = sectionEnergy(nextSection);
1899
- const euclidSteps = opts.euclideanKick?.steps ?? 16;
1900
- const euclidKick = opts.euclideanKick ? euclideanRhythm(opts.euclideanKick.pulses, euclidSteps, opts.euclideanKick.rotation ?? 0) : void 0;
1901
- const reuseSectionKick = (section === "b" || section === "chorus") && style !== "sparse";
1902
- let sectionKick;
1903
- for (let bar = 0; bar < opts.bars; bar += 1) {
1904
- const barStart = bar * 4;
1905
- const isLastBar = bar === opts.bars - 1;
1906
- if (bar === 0 && section === "chorus") {
1907
- track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
1908
- }
1909
- let barHasOpenHh = false;
1910
- let openHhBeatIndex = 3;
1911
- if (ohhBarInterval > 0 && bar % ohhBarInterval === ohhBarInterval - 1) {
1912
- openHhBeatIndex = openHiHatBeat(section, rng);
1913
- barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);
1914
- }
1915
- let kick;
1916
- if (!euclidKick && reuseSectionKick) {
1917
- sectionKick ??= getKickPattern(section, style, 0, rng);
1918
- kick = sectionKick;
1919
- } else if (!euclidKick) {
1920
- kick = getKickPattern(section, style, bar, rng);
1921
- }
1922
- const inLift = isInPreChorusLift(section, bar, opts.bars, nextSection === "chorus");
1923
- let currentFill = "snareRoll";
1924
- for (let beat = 0; beat < 4; beat += 1) {
1925
- const beatTick = barStart + beat;
1926
- const velocity = calculateVelocity(section, beat);
1927
- if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {
1928
- if (beat === fillStartBeat) {
1929
- currentFill = selectFillType(section, nextSection, style, nextEnergy, rng);
1930
- }
1931
- if (playMainVoices) {
1932
- const before = track.hits.length;
1933
- generateFill(track, beatTick, beat, currentFill, velocity);
1934
- if (track.hits.length > before) {
1935
- continue;
1936
- }
1937
- } else {
1938
- continue;
1939
- }
1940
- }
1941
- const ctx = {
1942
- track,
1943
- beatTick,
1944
- beat,
1945
- velocity,
1946
- section,
1947
- bpm: opts.bpm,
1948
- bar,
1949
- sectionBars: opts.bars,
1950
- inPrechorusLift: inLift,
1951
- swingAmount,
1952
- barHasOpenHh,
1953
- openHhBeat: openHhBeatIndex,
1954
- rng
1955
- };
1956
- if (playMainVoices) {
1957
- if (inLift) {
1958
- generatePreChorusBuildup(ctx, sec, isLastBar);
1959
- }
1960
- if (euclidKick && !inLift) {
1961
- const stepLength = 4 / euclidSteps;
1962
- for (let step = 0; step < euclidKick.length; step += 1) {
1963
- const onset = step * stepLength;
1964
- if (euclidKick[step] && Math.floor(onset) === beat) {
1965
- const wholeBeat = Math.abs(onset - Math.round(onset)) < 1e-9;
1966
- track.add(
1967
- GM.BD,
1968
- barStart + onset,
1969
- Math.min(0.5, stepLength),
1970
- velocity * (wholeBeat ? 1 : 0.85)
1971
- );
1972
- }
1973
- }
1974
- } else if (kick) {
1975
- generateKickForBeat(ctx, sec, kick);
1976
- }
1977
- generateSnareForBeat(ctx, sec, section === "intro" && bar === 0);
1978
- if (sec.useGhostNotes && !inLift) {
1979
- generateGhostNotesForBeat(ctx, sec);
1980
- }
1981
- }
1982
- generateHiHatForBeat(ctx, sec);
1983
- }
1984
- if (sec.useFootHh && shouldPlayHiHat(role)) {
1985
- for (let fhhBeat = 0; fhhBeat < 4; fhhBeat += 2) {
1986
- const fhhTick = barStart + fhhBeat;
1987
- if (!track.hasOnset(GM.FHH, fhhTick)) {
1988
- track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
1989
- }
1990
- }
1991
- }
1992
- let percussionConfig = getPercussionConfig(percMood, section);
1993
- if (role === "fxOnly" && !percussionConfig.tambourine && !percussionConfig.shaker && !percussionConfig.handclap) {
1994
- percussionConfig = {
1995
- tambourine: false,
1996
- shaker: true,
1997
- handclap: false,
1998
- shaker16th: false
1999
- };
2000
- }
2001
- generateAuxPercussionForBar(
2002
- track,
2003
- barStart,
2004
- percussionConfig,
2005
- role,
2006
- densityMult,
2007
- rng,
2008
- opts.bpm
2009
- );
2010
- }
2011
- return [...track.hits].sort((a, b) => a.startBeat - b.startBeat || a.pitch - b.pitch);
2012
- }
2013
-
2014
- // src/generate/groove/index.ts
2015
- var DEFAULT_TS3 = { numerator: 4, denominator: 4 };
2016
- var DEFAULT_GROOVE_SUBDIVISION = 4;
2017
- var DEFAULT_TIMING = 0.02;
2018
- var DEFAULT_VELOCITY_JITTER = 8;
2019
- var DEFAULT_ACCENT = 12;
2020
- var DEFAULT_BASE_VELOCITY = 80;
2021
- var MAX_METRIC_WEIGHT = 3;
2022
- var MIN_VELOCITY = 1;
2023
- var MAX_VELOCITY = 127;
2024
- function humanize(events, opts = {}) {
2025
- const ts = opts.ts ?? DEFAULT_TS3;
2026
- assertTimeSignature(ts);
2027
- assertNoteEvents(events, "humanize events", { allowNonPositiveDuration: true });
2028
- const sounding = dropSilentNotes(events);
2029
- const timing = opts.timing ?? DEFAULT_TIMING;
2030
- const velocityJitter = opts.velocity ?? DEFAULT_VELOCITY_JITTER;
2031
- const accent = opts.accent ?? DEFAULT_ACCENT;
2032
- const baseVelocity = opts.baseVelocity ?? DEFAULT_BASE_VELOCITY;
2033
- assertRange(timing, 0, Number.MAX_SAFE_INTEGER, "humanize timing");
2034
- assertRange(velocityJitter, 0, 127, "humanize velocity jitter");
2035
- assertRange(accent, 0, 127, "humanize accent");
2036
- assertRange(baseVelocity, 0, 127, "humanize base velocity");
2037
- const rng = createRng(opts.seed ?? 0);
2038
- return sounding.map((event) => {
2039
- const timingOffset = rng.float(-timing, timing);
2040
- const startBeat = Math.max(0, event.startBeat + timingOffset);
2041
- const weight = metricWeight(event.startBeat, ts);
2042
- const accentBoost = weight / MAX_METRIC_WEIGHT * accent;
2043
- const velocityOffset = rng.float(-velocityJitter, velocityJitter);
2044
- const rawVelocity = (event.velocity ?? baseVelocity) + accentBoost + velocityOffset;
2045
- const velocity = Math.min(MAX_VELOCITY, Math.max(MIN_VELOCITY, Math.round(rawVelocity)));
2046
- return {
2047
- pitch: event.pitch,
2048
- startBeat,
2049
- durationBeat: event.durationBeat,
2050
- velocity
2051
- };
2052
- });
2053
- }
2054
- function quantizeToGrid(beat, barBeats, subdivision, slotsPerBar) {
2055
- const barIndex = Math.floor(beat / barBeats);
2056
- const modBeat = beat - barIndex * barBeats;
2057
- let slotIndex = Math.round(modBeat * subdivision);
2058
- let effectiveBarIndex = barIndex;
2059
- if (slotIndex >= slotsPerBar) {
2060
- slotIndex = 0;
2061
- effectiveBarIndex += 1;
2062
- }
2063
- return {
2064
- quantizedBeat: effectiveBarIndex * barBeats + slotIndex / subdivision,
2065
- slotIndex
2066
- };
2067
- }
2068
- function extractGrooveTemplate(events, ts, subdivision = DEFAULT_GROOVE_SUBDIVISION) {
2069
- assertTimeSignature(ts);
2070
- assertNoteEvents(events, "groove source events", { allowNonPositiveDuration: true });
2071
- assertPositiveInt(subdivision, "groove subdivision");
2072
- const barBeats = beatsPerBar(ts);
2073
- const slotsPerBar = Math.round(barBeats * subdivision);
2074
- assertGenerationBudget(slotsPerBar, "groove template slots");
2075
- const offsetSums = new Array(slotsPerBar).fill(0);
2076
- const velocitySums = new Array(slotsPerBar).fill(0);
2077
- const counts = new Array(slotsPerBar).fill(0);
2078
- const velocityCounts = new Array(slotsPerBar).fill(0);
2079
- for (const event of dropSilentNotes(events)) {
2080
- const { quantizedBeat, slotIndex } = quantizeToGrid(
2081
- event.startBeat,
2082
- barBeats,
2083
- subdivision,
2084
- slotsPerBar
2085
- );
2086
- offsetSums[slotIndex] = (offsetSums[slotIndex] ?? 0) + (event.startBeat - quantizedBeat);
2087
- counts[slotIndex] = (counts[slotIndex] ?? 0) + 1;
2088
- if (event.velocity !== void 0) {
2089
- velocitySums[slotIndex] = (velocitySums[slotIndex] ?? 0) + event.velocity;
2090
- velocityCounts[slotIndex] = (velocityCounts[slotIndex] ?? 0) + 1;
2091
- }
2092
- }
2093
- const slots = [];
2094
- for (let i = 0; i < slotsPerBar; i += 1) {
2095
- const count = counts[i] ?? 0;
2096
- if (count === 0) {
2097
- slots.push({ timingOffset: 0, velocity: null });
2098
- continue;
2099
- }
2100
- const velocityCount = velocityCounts[i] ?? 0;
2101
- slots.push({
2102
- timingOffset: (offsetSums[i] ?? 0) / count,
2103
- velocity: velocityCount > 0 ? (velocitySums[i] ?? 0) / velocityCount : null
2104
- });
2105
- }
2106
- return { subdivision, slotsPerBar, slots, ts };
2107
- }
2108
- function applyGrooveTemplate(events, template, ts) {
2109
- assertTimeSignature(ts);
2110
- assertNoteEvents(events, "groove target events", { allowNonPositiveDuration: true });
2111
- assertPositiveInt(template.subdivision, "template subdivision");
2112
- assertPositiveInt(template.slotsPerBar, "template slotsPerBar");
2113
- assertGenerationBudget(template.slots.length, "template slots");
2114
- if (template.slots.length !== template.slotsPerBar) {
2115
- throw new InvalidInputError("template slots length must equal slotsPerBar");
2116
- }
2117
- if (template.ts && (template.ts.numerator !== ts.numerator || template.ts.denominator !== ts.denominator)) {
2118
- throw new InvalidInputError(
2119
- `Groove template meter ${formatTimeSignature(template.ts)} does not match apply-time meter ${formatTimeSignature(ts)}`
2120
- );
2121
- }
2122
- const barBeats = beatsPerBar(ts);
2123
- return dropSilentNotes(events).map((event) => {
2124
- const { quantizedBeat, slotIndex } = quantizeToGrid(
2125
- event.startBeat,
2126
- barBeats,
2127
- template.subdivision,
2128
- template.slotsPerBar
2129
- );
2130
- const slot = template.slots[slotIndex];
2131
- const startBeat = quantizedBeat + (slot?.timingOffset ?? 0);
2132
- const velocity = slot && slot.velocity !== null && slot.velocity !== void 0 ? Math.round(slot.velocity) : event.velocity;
2133
- return {
2134
- pitch: event.pitch,
2135
- startBeat,
2136
- durationBeat: event.durationBeat,
2137
- velocity
2138
- };
2139
- });
2140
- }
2141
-
2142
- // src/generate/harmonize/index.ts
2143
- var FALLBACK = {
2144
- rootPc: 0,
2145
- quality: "maj",
2146
- secondaryDominant: false,
2147
- base: 0,
2148
- pcs: chordPitchClasses(makeChord(0, "maj"))
2149
- };
2150
- var DEFAULT_HARMONIC_RHYTHM = 2;
2151
- var DEFAULT_PLACEMENT = { transposeSearch: false, octaveSearch: false };
2152
- var DEFAULT_METER = { numerator: 4, denominator: 4 };
2153
- var COMFORT_LOW = 55;
2154
- var COMFORT_HIGH = 79;
2155
- var TESSITURA_WEIGHT = 1e-3;
2156
- var TIE_BREAK_JITTER2 = 1e-6;
2157
- function inferKey(melody) {
2158
- let best = majorKey(0);
2159
- let bestScore = Number.NEGATIVE_INFINITY;
2160
- const candidates = [];
2161
- for (let tonic = 0; tonic < 12; tonic += 1) {
2162
- candidates.push(majorKey(tonic), minorKey(tonic));
2163
- }
2164
- for (const key of candidates) {
2165
- const tonic = pitchClassOf(key.rootPc);
2166
- const isMinor = key.modeMask12 === NATURAL_MINOR_MASK;
2167
- const leadingTone = (tonic + 11) % 12;
2168
- let score = 0;
2169
- for (const n of melody) {
2170
- const w = Math.max(0.25, n.durationBeat);
2171
- const pc = pitchClassOf(n.pitch);
2172
- if (isScaleTone(n.pitch, key) || isMinor && pc === leadingTone) {
2173
- score += w;
2174
- }
2175
- if (pc === tonic) {
2176
- score += 0.5 * w;
2177
- }
2178
- }
2179
- if (score > bestScore) {
2180
- bestScore = score;
2181
- best = key;
2182
- }
2183
- }
2184
- return best;
2185
- }
2186
- function buildCandidates(key, reharmonize) {
2187
- const tones = scaleTonesInDegreeOrder(key);
2188
- const candidates = tones.map((rootPc, degree) => {
2189
- const quality = diatonicTriad(degree, key).quality;
2190
- return {
2191
- rootPc,
2192
- quality,
2193
- degree,
2194
- secondaryDominant: false,
2195
- base: 0,
2196
- pcs: chordPitchClasses(makeChord(rootPc, quality))
2197
- };
2198
- });
2199
- if (reharmonize !== "diatonic") {
2200
- for (const target of [1, 3, 4, 5]) {
2201
- const targetRoot = tones[target] ?? 0;
2202
- const rootPc = (targetRoot + 7) % 12;
2203
- candidates.push({
2204
- rootPc,
2205
- quality: "dom7",
2206
- secondaryDominant: true,
2207
- targetDegree: target,
2208
- base: 1.5,
2209
- pcs: chordPitchClasses(makeChord(rootPc, "dom7"))
2210
- });
2211
- }
2212
- }
2213
- if (reharmonize === "borrowed") {
2214
- const parallel = parallelKey(key);
2215
- const parallelTones = scaleTonesInDegreeOrder(parallel);
2216
- for (let degree = 0; degree < parallelTones.length; degree += 1) {
2217
- const chord = diatonicTriad(degree, parallel);
2218
- if (!isDiatonic(chord, key)) {
2219
- candidates.push({
2220
- rootPc: chord.rootPc,
2221
- quality: chord.quality,
2222
- secondaryDominant: false,
2223
- base: 1.25,
2224
- pcs: chordPitchClasses(chord)
2225
- });
2226
- }
2227
- }
2228
- }
2229
- const seen = /* @__PURE__ */ new Set();
2230
- return candidates.filter((candidate) => {
2231
- const id = `${candidate.rootPc}:${candidate.quality}`;
2232
- if (seen.has(id)) {
2233
- return false;
2234
- }
2235
- seen.add(id);
2236
- return true;
2237
- });
2238
- }
2239
- function emissionCost(seg, cand, melody, key, ts) {
2240
- const pcs = cand.pcs;
2241
- let cost = cand.base;
2242
- for (const idx of seg.noteIndices) {
2243
- const note = melody[idx];
2244
- if (!note) {
2245
- continue;
2246
- }
2247
- const overlapStart = Math.max(note.startBeat, seg.startBeat);
2248
- const overlap = Math.min(note.startBeat + note.durationBeat, seg.endBeat) - overlapStart;
2249
- if (overlap <= 0) {
2250
- continue;
2251
- }
2252
- const w = Math.max(0.25, overlap);
2253
- if (pcs.includes(pitchClassOf(note.pitch))) {
2254
- continue;
2255
- }
2256
- const strong = isStrongBeat(overlapStart, ts);
2257
- cost += (strong ? 10 : 2) * w;
2258
- if (!isScaleTone(note.pitch, key)) {
2259
- cost += w;
2260
- }
2261
- }
2262
- return cost;
2263
- }
2264
- function tessituraCost(melody) {
2265
- let cost = 0;
2266
- for (const n of melody) {
2267
- if (n.pitch < COMFORT_LOW) {
2268
- cost += (COMFORT_LOW - n.pitch) * TESSITURA_WEIGHT;
2269
- } else if (n.pitch > COMFORT_HIGH) {
2270
- cost += (n.pitch - COMFORT_HIGH) * TESSITURA_WEIGHT;
2271
- }
2272
- }
2273
- return cost;
2274
- }
2275
- function transitionCost(prev, cur, tonicPc) {
2276
- let cost = 0;
2277
- if ((prev.rootPc - cur.rootPc + 12) % 12 === 7) {
2278
- cost -= 2;
2279
- }
2280
- if (prev.rootPc === (tonicPc + 7) % 12 && (prev.quality === "maj" || prev.quality === "dom7") && cur.rootPc === tonicPc) {
2281
- cost -= 3;
2282
- }
2283
- if (prev.secondaryDominant) {
2284
- cost += cur.degree === prev.targetDegree ? -3 : 2;
2285
- }
2286
- const dist = Math.min((prev.rootPc - cur.rootPc + 12) % 12, (cur.rootPc - prev.rootPc + 12) % 12);
2287
- cost += dist * 0.3;
2288
- return cost;
2289
- }
2290
- function harmonizeOnce(melody, key, candidates, segments, jitter, ts) {
2291
- const tonicPc = pitchClassOf(key.rootPc);
2292
- const n = candidates.length;
2293
- const candAt = (i) => candidates[i] ?? FALLBACK;
2294
- const seg0 = segments[0];
2295
- let dp = candidates.map(
2296
- (c, ci) => (seg0 ? emissionCost(seg0, c, melody, key, ts) : 0) + (jitter[ci] ?? 0)
2297
- );
2298
- const back = [];
2299
- for (let s = 1; s < segments.length; s += 1) {
2300
- const seg = segments[s];
2301
- if (!seg) {
2302
- continue;
2303
- }
2304
- const next = [];
2305
- const ptr = [];
2306
- for (let c = 0; c < n; c += 1) {
2307
- let best = Number.POSITIVE_INFINITY;
2308
- let bestPrev = 0;
2309
- for (let p = 0; p < n; p += 1) {
2310
- const cost = (dp[p] ?? Number.POSITIVE_INFINITY) + transitionCost(candAt(p), candAt(c), tonicPc);
2311
- if (cost < best) {
2312
- best = cost;
2313
- bestPrev = p;
2314
- }
2315
- }
2316
- next[c] = best + emissionCost(seg, candAt(c), melody, key, ts) + (jitter[c] ?? 0);
2317
- ptr[c] = bestPrev;
2318
- }
2319
- dp = next;
2320
- back.push(ptr);
2321
- }
2322
- let bestCost = Number.POSITIVE_INFINITY;
2323
- let bestEnd = 0;
2324
- for (let c = 0; c < n; c += 1) {
2325
- const cadence = candAt(c).rootPc === tonicPc ? -2 : 0;
2326
- const total = (dp[c] ?? Number.POSITIVE_INFINITY) + cadence;
2327
- if (total < bestCost) {
2328
- bestCost = total;
2329
- bestEnd = c;
2330
- }
2331
- }
2332
- const path = new Array(segments.length).fill(bestEnd);
2333
- for (let s = segments.length - 2; s >= 0; s -= 1) {
2334
- const ptr = back[s];
2335
- const nextIdx = path[s + 1] ?? bestEnd;
2336
- path[s] = ptr ? ptr[nextIdx] ?? bestEnd : bestEnd;
2337
- }
2338
- return { cost: bestCost, path };
2339
- }
2340
- function harmonizeMelody(opts) {
2341
- const noteIndex = createNoteEventIndex(opts.melody, { allowNonPositiveDuration: true });
2342
- const ts = opts.ts ?? DEFAULT_METER;
2343
- assertTimeSignature(ts);
2344
- const requestedKey = opts.key ?? "infer";
2345
- const key = requestedKey === "infer" ? inferKey(opts.melody) : requestedKey;
2346
- const placement = opts.placement ?? DEFAULT_PLACEMENT;
2347
- if (noteIndex.notes.every((indexed) => indexed.note.durationBeat <= 0)) {
2348
- return { transposeSemitones: 0, key, chords: [], melodyRoles: [] };
2349
- }
2350
- const candidates = buildCandidates(key, opts.reharmonize ?? "diatonic");
2351
- const candAt = (i) => candidates[i] ?? FALLBACK;
2352
- const rng = createRng(opts.seed ?? 0);
2353
- const jitter = candidates.map(() => rng.next() * TIE_BREAK_JITTER2);
2354
- const melodyEnd = noteIndex.notes.reduce((m, n) => Math.max(m, n.endBeat), 0);
2355
- const hr = assertRange(
2356
- opts.harmonicRhythm ?? DEFAULT_HARMONIC_RHYTHM,
2357
- Number.MIN_VALUE,
2358
- Number.MAX_SAFE_INTEGER,
2359
- "harmonic rhythm"
2360
- );
2361
- const segCount = Math.max(1, Math.ceil(melodyEnd / hr));
2362
- assertGenerationBudget(segCount, "harmonic segments");
2363
- assertGenerationBudget(segCount * candidates.length * candidates.length, "harmonization search");
2364
- const segments = Array.from({ length: segCount }, (_, s) => ({
2365
- startBeat: s * hr,
2366
- endBeat: (s + 1) * hr,
2367
- noteIndices: []
2368
- }));
2369
- let memberships = 0;
2370
- for (const indexed of noteIndex.notes) {
2371
- if (indexed.note.durationBeat <= 0) {
2372
- continue;
2373
- }
2374
- const first = Math.max(0, Math.floor(indexed.note.startBeat / hr));
2375
- const lastExclusive = Math.min(segCount, Math.ceil(indexed.endBeat / hr - Number.EPSILON));
2376
- memberships += Math.max(0, lastExclusive - first);
2377
- assertGenerationBudget(memberships, "note-to-segment memberships");
2378
- for (let segment = first; segment < lastExclusive; segment += 1) {
2379
- segments[segment]?.noteIndices.push(indexed.originalIndex);
2380
- }
2381
- }
2382
- const transposes = [0];
2383
- if (placement.transposeSearch) {
2384
- for (let s = -6; s <= 6; s += 1) {
2385
- if (s !== 0) {
2386
- transposes.push(s);
2387
- }
2388
- }
2389
- }
2390
- if (placement.octaveSearch) {
2391
- for (const o of [-12, 12]) {
2392
- if (!transposes.includes(o)) {
2393
- transposes.push(o);
2394
- }
2395
- }
2396
- }
2397
- let bestCost = Number.POSITIVE_INFINITY;
2398
- let bestTs = 0;
2399
- let bestPath = [];
2400
- let bestMelody = opts.melody;
2401
- for (const shift of transposes) {
2402
- const shifted = opts.melody.map((n) => ({ ...n, pitch: n.pitch + shift }));
2403
- const { cost, path } = harmonizeOnce(shifted, key, candidates, segments, jitter, ts);
2404
- const total = cost + tessituraCost(shifted);
2405
- if (total < bestCost) {
2406
- bestCost = total;
2407
- bestTs = shift;
2408
- bestPath = path;
2409
- bestMelody = shifted;
2410
- }
2411
- }
2412
- const chords = bestPath.map((ci, s) => {
2413
- const cand = candAt(ci);
2414
- const chord = {
2415
- rootPc: cand.rootPc,
2416
- quality: cand.quality,
2417
- startBeat: segments[s]?.startBeat ?? s * hr
2418
- };
2419
- if (cand.degree !== void 0) {
2420
- chord.degree = cand.degree;
2421
- }
2422
- if (cand.secondaryDominant) {
2423
- chord.secondaryDominant = true;
2424
- }
2425
- return chord;
2426
- });
2427
- const melodyRoles = bestMelody.map((note, noteIndex2) => {
2428
- const segIdx = Math.min(segments.length - 1, Math.max(0, Math.floor(note.startBeat / hr)));
2429
- const cand = candAt(bestPath[segIdx] ?? 0);
2430
- const chord = makeChord(cand.rootPc, cand.quality);
2431
- return { noteIndex: noteIndex2, role: roleOf(note.pitch, chord).role };
2432
- });
2433
- return { transposeSemitones: bestTs, key, chords, melodyRoles };
2434
- }
2435
-
2436
- // src/generate/motif/index.ts
2437
- var MIN_TILE_SPAN = 1 / 256;
2438
- var DEFAULT_TS4 = { numerator: 4, denominator: 4 };
2439
- var EPS3 = 1e-9;
2440
- function isDownbeat(startBeat, barBeats) {
2441
- const bar = startBeat / barBeats;
2442
- return Math.abs(bar - Math.round(bar)) < EPS3;
2443
- }
2444
- function upScaleTone(pitch, key) {
2445
- for (let d = 1; d <= 12; d += 1) {
2446
- if (isScaleTone(pitch + d, key)) {
2447
- return pitch + d;
2448
- }
2449
- }
2450
- return pitch + 12;
2451
- }
2452
- function downScaleTone(pitch, key) {
2453
- for (let d = 1; d <= 12; d += 1) {
2454
- if (isScaleTone(pitch - d, key)) {
2455
- return pitch - d;
2456
- }
2457
- }
2458
- return pitch - 12;
2459
- }
2460
- function stepDiatonic(pitch, degrees, key) {
2461
- let p = nearestScaleTone(pitch, key);
2462
- const steps = Math.abs(Math.trunc(degrees));
2463
- for (let i = 0; i < steps; i += 1) {
2464
- p = degrees >= 0 ? upScaleTone(p, key) : downScaleTone(p, key);
2465
- }
2466
- return p;
2467
- }
2468
- function nearestChordTone(pitch, chord) {
2469
- const pcs = chordPitchClasses(chord);
2470
- for (let d = 0; d <= 12; d += 1) {
2471
- if (pcs.includes(pitchClassOf(pitch - d))) {
2472
- return pitch - d;
2473
- }
2474
- if (pcs.includes(pitchClassOf(pitch + d))) {
2475
- return pitch + d;
2476
- }
2477
- }
2478
- return pitch;
2479
- }
2480
- function cellOrigin(notes) {
2481
- let origin = Number.POSITIVE_INFINITY;
2482
- for (const note of notes) {
2483
- origin = Math.min(origin, note.startBeat);
2484
- }
2485
- return Number.isFinite(origin) ? origin : 0;
2486
- }
2487
- function cellEnd(notes) {
2488
- let end = Number.NEGATIVE_INFINITY;
2489
- for (const note of notes) {
2490
- end = Math.max(end, note.startBeat + note.durationBeat);
2491
- }
2492
- return Number.isFinite(end) ? end : 0;
2493
- }
2494
- function cellSpan(cell) {
2495
- if (cell.notes.length === 0) {
2496
- return 0;
2497
- }
2498
- const start = cellOrigin(cell.notes);
2499
- const end = cellEnd(cell.notes);
2500
- return end - start;
2501
- }
2502
- function clone(cell) {
2503
- return { notes: cell.notes.map((n) => ({ ...n })) };
2504
- }
2505
- function contourOffsets(contour, count) {
2506
- const offsets = [];
2507
- for (let i = 0; i < count; i += 1) {
2508
- switch (contour) {
2509
- case "ascending":
2510
- offsets.push(i);
2511
- break;
2512
- case "descending":
2513
- offsets.push(-i);
2514
- break;
2515
- case "arch":
2516
- offsets.push(Math.min(i, count - 1 - i));
2517
- break;
2518
- case "wave":
2519
- offsets.push([0, 1, 0, -1][i % 4] ?? 0);
2520
- break;
2521
- }
2522
- }
2523
- return offsets;
2524
- }
2525
- function generateMotif(opts) {
2526
- const contour = opts.contour ?? "arch";
2527
- const bars = assertPositiveInt(opts.bars, "motif bars");
2528
- const barBeats = beatsPerBar(opts.ts ?? DEFAULT_TS4);
2529
- const totalBeats = bars * barBeats;
2530
- const noteCount = Math.max(3, bars * 2);
2531
- assertGenerationBudget(noteCount, "motif notes");
2532
- const beatsPerNote = totalBeats / noteCount;
2533
- const rng = createRng(opts.seed ?? 0);
2534
- const requestedJitter = assertFiniteNumber(opts.jitter ?? 0, "motif jitter");
2535
- const jitterProb = Math.min(1, Math.max(0, requestedJitter));
2536
- const tonic = pitchClassOf(opts.key.rootPc) + 60;
2537
- const offsets = contourOffsets(contour, noteCount);
2538
- const notes = [];
2539
- for (let i = 0; i < noteCount; i += 1) {
2540
- let jitter = 0;
2541
- if (jitterProb > 0 && rng.next() < jitterProb) {
2542
- jitter = rng.next() < 0.5 ? 1 : -1;
2543
- }
2544
- let pitch = stepDiatonic(tonic, (offsets[i] ?? 0) + jitter, opts.key);
2545
- const startBeat = i * beatsPerNote;
2546
- if (opts.chord && isDownbeat(startBeat, barBeats)) {
2547
- pitch = nearestChordTone(pitch, opts.chord);
2548
- }
2549
- notes.push({ pitch, startBeat, durationBeat: beatsPerNote });
2550
- }
2551
- return { notes };
2552
- }
2553
- function motifToNoteEvents(cell) {
2554
- return cell.notes.map((note) => ({ ...note }));
2555
- }
2556
- function transformMotif(cell, t, amount, key) {
2557
- return assertInRange(transformUnchecked(cell, t, amount, key));
2558
- }
2559
- function assertInRange(cell) {
2560
- for (const note of cell.notes) {
2561
- if (note.pitch < 0 || note.pitch > 127) {
2562
- throw new InvalidInputError(
2563
- `transformed motif pitch ${note.pitch} is outside the MIDI range 0..127`
2564
- );
2565
- }
2566
- }
2567
- return cell;
2568
- }
2569
- function transformUnchecked(cell, t, amount, key) {
2570
- assertNoteEvents(cell.notes, "motif notes");
2571
- if (amount !== void 0) {
2572
- assertFiniteNumber(amount, "motif transform amount");
2573
- }
2574
- if ((t === "transposeDiatonic" || t === "sequence") && amount !== void 0) {
2575
- assertInteger(amount, "diatonic transform amount");
2576
- }
2577
- if ((t === "augment" || t === "diminish") && amount !== void 0 && amount <= 0) {
2578
- throw new InvalidInputError("time transform amount must be positive");
2579
- }
2580
- const notes = cell.notes;
2581
- switch (t) {
2582
- case "transposeChromatic": {
2583
- const semis = amount ?? 0;
2584
- return { notes: notes.map((n) => ({ ...n, pitch: n.pitch + semis })) };
2585
- }
2586
- case "transposeDiatonic": {
2587
- const degrees = amount ?? 1;
2588
- return {
2589
- notes: notes.map((n) => ({
2590
- ...n,
2591
- // With a key, shift by scale degrees; without one, fall back to a
2592
- // chromatic shift of `degrees` semitones.
2593
- pitch: key ? stepDiatonic(n.pitch, degrees, key) : n.pitch + degrees
2594
- }))
2595
- };
2596
- }
2597
- case "invert": {
2598
- const pivot = notes[0]?.pitch ?? 0;
2599
- return { notes: notes.map((n) => ({ ...n, pitch: 2 * pivot - n.pitch })) };
2600
- }
2601
- case "retrograde": {
2602
- if (notes.length === 0) {
2603
- return { notes: [] };
2604
- }
2605
- const start = cellOrigin(notes);
2606
- const end = cellEnd(notes);
2607
- return {
2608
- notes: notes.map((n) => ({
2609
- pitch: n.pitch,
2610
- startBeat: start + (end - (n.startBeat + n.durationBeat)),
2611
- durationBeat: n.durationBeat
2612
- }))
2613
- };
2614
- }
2615
- case "augment":
2616
- return scaleTime(cell, amount ?? 2);
2617
- case "diminish":
2618
- return scaleTime(cell, 1 / (amount ?? 2));
2619
- case "sequence": {
2620
- const degrees = amount ?? 2;
2621
- const span = cellSpan(cell);
2622
- const copy = transformMotif(cell, "transposeDiatonic", degrees, key);
2623
- const shifted = copy.notes.map((n) => ({ ...n, startBeat: n.startBeat + span }));
2624
- return { notes: [...clone(cell).notes, ...shifted] };
2625
- }
2626
- }
2627
- }
2628
- function scaleTime(cell, factor) {
2629
- const origin = cellOrigin(cell.notes);
2630
- return {
2631
- notes: cell.notes.map((n) => ({
2632
- pitch: n.pitch,
2633
- startBeat: origin + (n.startBeat - origin) * factor,
2634
- durationBeat: n.durationBeat * factor
2635
- }))
2636
- };
2637
- }
2638
- function developMotif(cell, timeline, key, bars, ts = DEFAULT_TS4) {
2639
- assertPositiveInt(bars, "development bars");
2640
- assertNoteEvents(cell.notes, "motif notes");
2641
- const span = cellSpan(cell);
2642
- const totalBeats = bars * beatsPerBar(ts);
2643
- const origin = cellOrigin(cell.notes);
2644
- const out = [];
2645
- if (span <= 0) {
2646
- return clone(cell);
2647
- }
2648
- const tileSpan = Math.max(span, MIN_TILE_SPAN);
2649
- const tileCount = Math.ceil(totalBeats / tileSpan);
2650
- assertGenerationBudget(tileCount * cell.notes.length, "developed motif notes");
2651
- for (let k = 0; k < tileCount; k += 1) {
2652
- const offset = k * tileSpan;
2653
- for (const n of cell.notes) {
2654
- const startBeat = offset + (n.startBeat - origin);
2655
- if (startBeat >= totalBeats) {
2656
- continue;
2657
- }
2658
- const chord = timeline.at(startBeat);
2659
- let pitch = n.pitch;
2660
- if (chord) {
2661
- const isMember = chordPitchClasses(chord).includes(pitchClassOf(pitch));
2662
- pitch = isMember ? pitch : nearestChordTone(pitch, chord);
2663
- } else {
2664
- pitch = nearestScaleTone(pitch, key);
2665
- }
2666
- out.push({ pitch, startBeat, durationBeat: n.durationBeat });
2667
- }
2668
- }
2669
- return { notes: out };
2670
- }
2671
-
2672
- // src/generate/progression/index.ts
2673
- var BORROWED_DEGREES = {
2674
- /** Flat submediant: bVI. */
2675
- bVI: 8,
2676
- /** Flat subtonic: bVII. */
2677
- bVII: 10,
2678
- /** Flat mediant: bIII. */
2679
- bIII: 11,
2680
- /** Minor subdominant borrowed from the parallel minor: iv. */
2681
- iv: 12,
2682
- /** Neapolitan: bII. */
2683
- bII: 13,
2684
- /** Sharp subdominant, the diminished #IV. */
2685
- sharpIV: 14
2686
- };
2687
- var PRESETS = [
2688
- {
2689
- id: "fourChordPop",
2690
- name: "Four Chord Pop",
2691
- degrees: [0, 4, 5, 3],
2692
- functional: "loop",
2693
- styles: ["minimal", "dance", "idol"]
2694
- },
2695
- {
2696
- id: "pop1",
2697
- name: "Pop 1",
2698
- degrees: [0, 5, 3, 4],
2699
- functional: "loop",
2700
- styles: ["minimal", "dance", "idol"]
2701
- },
2702
- {
2703
- id: "axis",
2704
- name: "Axis",
2705
- degrees: [5, 3, 0, 4],
2706
- functional: "loop",
2707
- styles: ["minimal", "dance", "idol", "rock"]
2708
- },
2709
- {
2710
- id: "pop2",
2711
- name: "Pop 2",
2712
- degrees: [3, 0, 4, 5],
2713
- functional: "loop",
2714
- styles: ["minimal", "dance", "idol"]
2715
- },
2716
- {
2717
- id: "classic",
2718
- name: "Classic",
2719
- degrees: [0, 3, 4, 0],
2720
- functional: "cadenceStrong",
2721
- styles: ["dance", "idol", "rock"]
2722
- },
2723
- {
2724
- id: "pop3",
2725
- name: "Pop 3",
2726
- degrees: [0, 3, 5, 4],
2727
- functional: "loop",
2728
- styles: ["minimal", "dance", "idol"]
2729
- },
2730
- {
2731
- id: "royalRoad",
2732
- name: "Royal Road",
2733
- degrees: [3, 4, 2, 5],
2734
- functional: "tensionBuild",
2735
- styles: ["dance", "idol"]
2736
- },
2737
- {
2738
- id: "minor1",
2739
- name: "Minor 1",
2740
- degrees: [5, 4, 3, 4],
2741
- functional: "tensionBuild",
2742
- styles: ["idol", "rock"]
2743
- },
2744
- {
2745
- id: "minor2",
2746
- name: "Minor 2",
2747
- degrees: [5, 3, 4, 0],
2748
- functional: "tensionBuild",
2749
- styles: ["idol", "rock"]
2750
- },
2751
- {
2752
- id: "pop4",
2753
- name: "Pop 4",
2754
- degrees: [0, 4, 2, 3],
2755
- functional: "loop",
2756
- styles: ["minimal", "dance", "idol"]
2757
- },
2758
- {
2759
- id: "pop5",
2760
- name: "Pop 5",
2761
- degrees: [0, 2, 3, 4],
2762
- functional: "stable",
2763
- styles: ["minimal", "dance", "idol"]
2764
- },
2765
- {
2766
- id: "rock1",
2767
- name: "Rock 1",
2768
- degrees: [0, 10, 3, 0],
2769
- functional: "tensionBuild",
2770
- styles: ["rock"]
2771
- },
2772
- {
2773
- id: "rock2",
2774
- name: "Rock 2",
2775
- degrees: [0, 3, 10, 0],
2776
- functional: "tensionBuild",
2777
- styles: ["rock"]
2778
- },
2779
- {
2780
- id: "extended4",
2781
- name: "Extended 4",
2782
- degrees: [0, 4, 5, 2],
2783
- functional: "stable",
2784
- styles: ["minimal", "dance"]
2785
- },
2786
- {
2787
- id: "minor3",
2788
- name: "Minor 3",
2789
- degrees: [5, 0, 4, 3],
2790
- functional: "loop",
2791
- styles: ["dance", "idol"]
2792
- },
2793
- {
2794
- id: "aeolianPop",
2795
- name: "Aeolian Pop",
2796
- degrees: [5, 8, 10, 0],
2797
- functional: "tensionBuild",
2798
- styles: ["minimal", "dance", "idol", "rock"]
2799
- },
2800
- {
2801
- id: "animeHighEnergy1",
2802
- name: "Anime High Energy 1",
2803
- degrees: [5, 2, 3, 0],
2804
- functional: "loop",
2805
- styles: ["dance", "idol"]
2806
- },
2807
- {
2808
- id: "jazzPop",
2809
- name: "Jazz Pop",
2810
- degrees: [1, 4, 0, 5],
2811
- functional: "cadenceStrong",
2812
- styles: ["minimal", "dance"]
2813
- },
2814
- {
2815
- id: "animeHighEnergy2",
2816
- name: "Anime High Energy 2",
2817
- degrees: [5, 1, 4, 0],
2818
- functional: "cadenceStrong",
2819
- styles: ["dance", "idol"]
2820
- },
2821
- {
2822
- id: "cityPop",
2823
- name: "City Pop",
2824
- degrees: [0, 5, 1, 4],
2825
- functional: "stable",
2826
- styles: ["minimal", "dance"]
2827
- },
2828
- {
2829
- id: "extended5",
2830
- name: "Extended 5",
2831
- degrees: [0, 4, 5, 2, 3],
2832
- functional: "loop",
2833
- styles: ["minimal", "dance", "idol"]
2834
- },
2835
- {
2836
- id: "neapolitanPop",
2837
- name: "Neapolitan Pop",
2838
- degrees: [5, 12, 13, 4, 0],
2839
- functional: "cadenceStrong",
2840
- styles: ["minimal", "dance", "idol"]
2841
- }
2842
- ];
2843
- var BORROWED_OFFSET = {
2844
- [BORROWED_DEGREES.bVI]: 8,
2845
- [BORROWED_DEGREES.bVII]: 10,
2846
- [BORROWED_DEGREES.bIII]: 3,
2847
- [BORROWED_DEGREES.iv]: 5,
2848
- [BORROWED_DEGREES.bII]: 1,
2849
- [BORROWED_DEGREES.sharpIV]: 6
2850
- };
2851
- function progressions() {
2852
- return PRESETS.map((p) => ({ ...p, degrees: [...p.degrees], styles: [...p.styles] }));
2853
- }
2854
- function progressionsByStyle(style) {
2855
- return progressions().filter((p) => p.styles.includes(style));
2856
- }
2857
- function degreeToRootPc(degree, key) {
2858
- if (degree >= 0 && degree <= 6) {
2859
- const tones = scaleTonesInDegreeOrder(key);
2860
- return tones.length > 0 ? tones[degree % tones.length] ?? key.rootPc % 12 : key.rootPc % 12;
2861
- }
2862
- const offset = BORROWED_OFFSET[degree] ?? 0;
2863
- return ((key.rootPc % 12 + offset) % 12 + 12) % 12;
2864
- }
2865
- function autoQuality(degree, key) {
2866
- if (degree >= 0 && degree <= 6) {
2867
- return diatonicTriad(degree, key).quality;
2868
- }
2869
- if (degree === 14) {
2870
- return "dim";
2871
- }
2872
- if (degree === 12) {
2873
- return "min";
2874
- }
2875
- return "maj";
2876
- }
2877
- function resolveCycle(degrees, key, ext) {
2878
- const steps = [];
2879
- for (const degree of degrees) {
2880
- const step = {
2881
- source: degree,
2882
- rootPc: degreeToRootPc(degree, key),
2883
- quality: ext !== void 0 && ext !== "auto" ? ext : autoQuality(degree, key)
2884
- };
2885
- if (degree >= 0 && degree <= 6) {
2886
- step.degree = degree;
2887
- }
2888
- const previous = steps[steps.length - 1];
2889
- if (previous !== void 0 && previous.source !== step.source && sameStep(previous, step)) {
2890
- continue;
2891
- }
2892
- steps.push(step);
2893
- }
2894
- const first = steps[0];
2895
- const last = steps[steps.length - 1];
2896
- if (steps.length > 1 && first !== void 0 && last !== void 0 && first.source !== last.source && sameStep(first, last)) {
2897
- steps.pop();
2898
- }
2899
- return steps.length > 0 ? steps : [{ source: 0, degree: 0, rootPc: degreeToRootPc(0, key), quality: "maj" }];
2900
- }
2901
- function sameStep(a, b) {
2902
- return a.rootPc === b.rootPc && a.quality === b.quality;
2903
- }
2904
- function pickProgressionPreset(style, seed = 0) {
2905
- const pool = PRESETS.filter((preset) => preset.styles.includes(style));
2906
- if (pool.length === 0) {
2907
- throw new InvalidInputError(`Unknown progression style: ${style}`);
2908
- }
2909
- const rng = createRng(seed);
2910
- const index = Math.floor(rng.next() * pool.length) % pool.length;
2911
- return pool[index] ?? PRESETS[0];
2912
- }
2913
- function generateProgression(opts) {
2914
- assertPositiveInt(opts.bars, "progression bars");
2915
- assertGenerationBudget(opts.bars, "progression chords");
2916
- const seed = opts.seed ?? 0;
2917
- let preset;
2918
- if (opts.preset !== void 0) {
2919
- if (opts.preset.degrees.length === 0) {
2920
- throw new InvalidInputError("progression preset must name at least one degree");
2921
- }
2922
- preset = {
2923
- id: opts.preset.id ?? "custom",
2924
- name: opts.preset.name ?? "Custom",
2925
- degrees: opts.preset.degrees,
2926
- functional: opts.preset.functional ?? "loop",
2927
- styles: opts.preset.styles ?? [opts.style]
2928
- };
2929
- }
2930
- if (preset === void 0 && opts.presetId !== void 0) {
2931
- preset = PRESETS.find((p) => p.id === opts.presetId);
2932
- if (preset === void 0) {
2933
- throw new InvalidInputError(`Unknown progression preset: ${opts.presetId}`);
2934
- }
2935
- }
2936
- preset ??= pickProgressionPreset(opts.style, seed);
2937
- const cycle = resolveCycle(preset?.degrees ?? [0], opts.key, opts.ext);
2938
- const chords = [];
2939
- for (let bar = 0; bar < opts.bars; bar += 1) {
2940
- const step = cycle[bar % cycle.length];
2941
- const chord = {
2942
- rootPc: step?.rootPc ?? 0,
2943
- quality: step?.quality ?? "maj",
2944
- startBeat: bar * 4
2945
- };
2946
- if (step?.degree !== void 0) {
2947
- chord.degree = step.degree;
2948
- }
2949
- chords.push(chord);
2950
- }
2951
- if (opts.reharmonize) {
2952
- const tonicPc = (opts.key.rootPc % 12 + 12) % 12;
2953
- const rrng = createRng((seed ^ 2654435769) >>> 0);
2954
- for (let i = 0; i < chords.length - 1; i += 1) {
2955
- const cur = chords[i];
2956
- const next = chords[i + 1];
2957
- if (!cur || !next) {
2958
- continue;
2959
- }
2960
- const domRoot = (next.rootPc + 7) % 12;
2961
- const targetsTonic = (next.rootPc - tonicPc + 12) % 12 === 0;
2962
- const alreadySecondary = cur.rootPc === domRoot && cur.quality === "dom7";
2963
- const isResolutionTarget = chords[i - 1]?.secondaryDominant === true;
2964
- if (targetsTonic || alreadySecondary || isResolutionTarget) {
2965
- continue;
2966
- }
2967
- if (rrng.next() < 0.5) {
2968
- chords[i] = {
2969
- rootPc: domRoot,
2970
- quality: "dom7",
2971
- startBeat: cur.startBeat,
2972
- secondaryDominant: true
2973
- };
2974
- }
2975
- }
2976
- }
2977
- return chords;
2978
- }
2979
-
2980
- // src/generate/rhythm/index.ts
2981
- var DEFAULT_RHYTHM_VELOCITY = 96;
2982
- function rhythmToNoteEvents(events, pitch, velocity = DEFAULT_RHYTHM_VELOCITY) {
2983
- assertFiniteNumber(pitch, "pitch");
2984
- assertRange(velocity, 0, 127, "velocity");
2985
- return events.map((event) => ({
2986
- pitch,
2987
- startBeat: event.position,
2988
- durationBeat: event.duration,
2989
- velocity
2990
- }));
2991
- }
2992
- var DEFAULT_SUBDIVISION = 2;
2993
- var DEFAULT_DENSITY = 0.5;
2994
- var DEFAULT_BARS = 1;
2995
- function onsetWeightCurve(weight) {
2996
- switch (weight) {
2997
- case 3:
2998
- return 1;
2999
- case 2:
3000
- return 0.8;
3001
- case 1:
3002
- return 0.55;
3003
- default:
3004
- return 0.25;
3005
- }
3006
- }
3007
- function generateRhythm(ts, opts = {}) {
3008
- const seed = opts.seed ?? 0;
3009
- const bars = opts.bars ?? DEFAULT_BARS;
3010
- const subdivision = opts.subdivision ?? DEFAULT_SUBDIVISION;
3011
- const density = assertRange(opts.density ?? DEFAULT_DENSITY, 0, 1, "rhythm density");
3012
- assertPositiveInt(bars, "rhythm bars");
3013
- assertPositiveInt(subdivision, "rhythm subdivision");
3014
- const barBeats = beatsPerBar(ts);
3015
- const spanBeats = barBeats * bars;
3016
- const step = 1 / subdivision;
3017
- const slotCount = Math.round(spanBeats * subdivision);
3018
- assertGenerationBudget(slotCount, "rhythm grid slots");
3019
- const slotsPerBar = Math.round(barBeats * subdivision);
3020
- const rng = createRng(seed);
3021
- const positions = [];
3022
- for (let i = 0; i < slotCount; i += 1) {
3023
- const position = i * step;
3024
- if (i % slotsPerBar === 0) {
3025
- positions.push(position);
3026
- continue;
3027
- }
3028
- const weight = metricWeight(position, ts);
3029
- const probability = onsetWeightCurve(weight) * density;
3030
- if (rng.prob(probability)) {
3031
- positions.push(position);
3032
- }
3033
- }
3034
- const events = [];
3035
- for (let i = 0; i < positions.length; i += 1) {
3036
- const position = positions[i] ?? 0;
3037
- const nextPosition = positions[i + 1] ?? spanBeats;
3038
- events.push({ position, duration: nextPosition - position });
3039
- }
3040
- return events;
3041
- }
3042
- function rhythmDensity(events, ts) {
3043
- if (events.length === 0) {
3044
- return 0;
3045
- }
3046
- const last = events[events.length - 1];
3047
- const spanBeats = (last?.position ?? 0) + (last?.duration ?? 0);
3048
- const bars = spanBeats / beatsPerBar(ts);
3049
- return bars > 0 ? events.length / bars : 0;
3050
- }
3051
-
3052
- export {
3053
- BASS_STYLES,
3054
- generateBassLine,
3055
- generateCounterMelody,
3056
- DRUM_NOTES,
3057
- drumVoiceOf,
3058
- generateDrums,
3059
- humanize,
3060
- extractGrooveTemplate,
3061
- applyGrooveTemplate,
3062
- harmonizeMelody,
3063
- generateMotif,
3064
- motifToNoteEvents,
3065
- transformMotif,
3066
- developMotif,
3067
- BORROWED_DEGREES,
3068
- progressions,
3069
- progressionsByStyle,
3070
- pickProgressionPreset,
3071
- generateProgression,
3072
- rhythmToNoteEvents,
3073
- onsetWeightCurve,
3074
- generateRhythm,
3075
- rhythmDensity
3076
- };
3077
- //# sourceMappingURL=chunk-7NKDPF74.js.map