@libraz/libcantus 0.9.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.
- package/LICENSE +201 -0
- package/README.md +291 -0
- package/dist/index.cjs +6465 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2621 -0
- package/dist/index.d.ts +2621 -0
- package/dist/index.js +6297 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
- package/src/analysis/index.ts +261 -0
- package/src/arrange/index.ts +409 -0
- package/src/bass/index.ts +301 -0
- package/src/chord/index.ts +292 -0
- package/src/chordscale/index.ts +316 -0
- package/src/countermelody/index.ts +349 -0
- package/src/counterpoint/index.ts +209 -0
- package/src/detect/index.ts +176 -0
- package/src/drums/beat.ts +266 -0
- package/src/drums/euclid.ts +58 -0
- package/src/drums/fills.ts +345 -0
- package/src/drums/ghost.ts +119 -0
- package/src/drums/hihat.ts +285 -0
- package/src/drums/hit.ts +31 -0
- package/src/drums/index.ts +211 -0
- package/src/drums/internal.ts +251 -0
- package/src/drums/kick.ts +160 -0
- package/src/drums/percussion.ts +121 -0
- package/src/drums/rng.ts +7 -0
- package/src/drums/swing.ts +66 -0
- package/src/functional/index.ts +602 -0
- package/src/groove/index.ts +230 -0
- package/src/harmonize/index.ts +367 -0
- package/src/harmony/index.ts +75 -0
- package/src/index.ts +233 -0
- package/src/interval/index.ts +67 -0
- package/src/meter/index.ts +179 -0
- package/src/model/index.ts +957 -0
- package/src/motif/index.ts +321 -0
- package/src/pitch/index.ts +256 -0
- package/src/progression/index.ts +336 -0
- package/src/random/index.ts +41 -0
- package/src/reharmony/index.ts +299 -0
- package/src/rhythm/index.ts +131 -0
- package/src/safety/index.ts +372 -0
- package/src/scale/index.ts +215 -0
- package/src/spelling/index.ts +164 -0
- package/src/symbol/index.ts +213 -0
- package/src/timeline/index.ts +352 -0
- package/src/tuning/index.ts +129 -0
- package/src/types.ts +29 -0
- package/src/voicing/index.ts +523 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import type { HitList } from './hit.js';
|
|
2
|
+
import type { DrumStyle, SectionEnergy, SectionType } from './internal.js';
|
|
3
|
+
import { EIGHTH, GM, SIXTEENTH } from './internal.js';
|
|
4
|
+
import type { DrumRng } from './rng.js';
|
|
5
|
+
|
|
6
|
+
/** The thirteen fill archetypes for section transitions. */
|
|
7
|
+
export type FillType =
|
|
8
|
+
| 'snareRoll'
|
|
9
|
+
| 'tomDescend'
|
|
10
|
+
| 'tomAscend'
|
|
11
|
+
| 'snareTomCombo'
|
|
12
|
+
| 'simpleCrash'
|
|
13
|
+
| 'linearFill'
|
|
14
|
+
| 'ghostToAccent'
|
|
15
|
+
| 'bdSnareAlternate'
|
|
16
|
+
| 'hiHatChoke'
|
|
17
|
+
| 'tomShuffle'
|
|
18
|
+
| 'breakdownFill'
|
|
19
|
+
| 'flamsAndDrags'
|
|
20
|
+
| 'halfTimeFill';
|
|
21
|
+
|
|
22
|
+
/** Beat at which a fill begins, widening with section energy. */
|
|
23
|
+
export function getFillStartBeat(energy: SectionEnergy): number {
|
|
24
|
+
switch (energy) {
|
|
25
|
+
case 'low':
|
|
26
|
+
return 3;
|
|
27
|
+
case 'medium':
|
|
28
|
+
return 2;
|
|
29
|
+
case 'high':
|
|
30
|
+
case 'peak':
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Pick a fill archetype from the transition context. */
|
|
36
|
+
export function selectFillType(
|
|
37
|
+
from: SectionType,
|
|
38
|
+
to: SectionType,
|
|
39
|
+
style: DrumStyle,
|
|
40
|
+
nextEnergy: SectionEnergy,
|
|
41
|
+
rng: DrumRng,
|
|
42
|
+
): FillType {
|
|
43
|
+
if (style === 'sparse') {
|
|
44
|
+
return rng.range(0, 1) === 0 ? 'simpleCrash' : 'breakdownFill';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (nextEnergy === 'low') {
|
|
48
|
+
switch (rng.range(0, 2)) {
|
|
49
|
+
case 0:
|
|
50
|
+
return 'simpleCrash';
|
|
51
|
+
case 1:
|
|
52
|
+
return 'breakdownFill';
|
|
53
|
+
default:
|
|
54
|
+
return 'halfTimeFill';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (nextEnergy === 'peak') {
|
|
59
|
+
switch (rng.range(0, 3)) {
|
|
60
|
+
case 0:
|
|
61
|
+
return 'tomDescend';
|
|
62
|
+
case 1:
|
|
63
|
+
return 'snareRoll';
|
|
64
|
+
case 2:
|
|
65
|
+
return 'linearFill';
|
|
66
|
+
default:
|
|
67
|
+
return 'flamsAndDrags';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const toChorus = to === 'chorus';
|
|
72
|
+
const fromIntro = from === 'intro';
|
|
73
|
+
const highEnergy = style === 'rock' || style === 'fourOnFloor';
|
|
74
|
+
const choice = rng.range(0, 7);
|
|
75
|
+
|
|
76
|
+
if (toChorus) {
|
|
77
|
+
if (highEnergy) {
|
|
78
|
+
switch (choice) {
|
|
79
|
+
case 0:
|
|
80
|
+
case 1:
|
|
81
|
+
return 'tomDescend';
|
|
82
|
+
case 2:
|
|
83
|
+
return 'snareRoll';
|
|
84
|
+
case 3:
|
|
85
|
+
return 'linearFill';
|
|
86
|
+
case 4:
|
|
87
|
+
return 'bdSnareAlternate';
|
|
88
|
+
case 5:
|
|
89
|
+
return 'flamsAndDrags';
|
|
90
|
+
case 6:
|
|
91
|
+
return 'tomShuffle';
|
|
92
|
+
default:
|
|
93
|
+
return 'ghostToAccent';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
switch (choice) {
|
|
97
|
+
case 0:
|
|
98
|
+
case 1:
|
|
99
|
+
return 'snareTomCombo';
|
|
100
|
+
case 2:
|
|
101
|
+
return 'tomDescend';
|
|
102
|
+
case 3:
|
|
103
|
+
return 'ghostToAccent';
|
|
104
|
+
case 4:
|
|
105
|
+
return 'hiHatChoke';
|
|
106
|
+
case 5:
|
|
107
|
+
return 'linearFill';
|
|
108
|
+
default:
|
|
109
|
+
return 'snareRoll';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (fromIntro) {
|
|
114
|
+
switch (choice) {
|
|
115
|
+
case 0:
|
|
116
|
+
case 1:
|
|
117
|
+
return 'snareRoll';
|
|
118
|
+
case 2:
|
|
119
|
+
return 'simpleCrash';
|
|
120
|
+
case 3:
|
|
121
|
+
return 'ghostToAccent';
|
|
122
|
+
case 4:
|
|
123
|
+
return 'breakdownFill';
|
|
124
|
+
default:
|
|
125
|
+
return 'halfTimeFill';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (highEnergy) {
|
|
130
|
+
switch (choice) {
|
|
131
|
+
case 0:
|
|
132
|
+
return 'tomDescend';
|
|
133
|
+
case 1:
|
|
134
|
+
return 'snareRoll';
|
|
135
|
+
case 2:
|
|
136
|
+
return 'tomAscend';
|
|
137
|
+
case 3:
|
|
138
|
+
return 'snareTomCombo';
|
|
139
|
+
case 4:
|
|
140
|
+
return 'linearFill';
|
|
141
|
+
case 5:
|
|
142
|
+
return 'bdSnareAlternate';
|
|
143
|
+
case 6:
|
|
144
|
+
return 'flamsAndDrags';
|
|
145
|
+
default:
|
|
146
|
+
return 'tomShuffle';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
switch (choice) {
|
|
151
|
+
case 0:
|
|
152
|
+
case 1:
|
|
153
|
+
return 'snareRoll';
|
|
154
|
+
case 2:
|
|
155
|
+
return 'snareTomCombo';
|
|
156
|
+
case 3:
|
|
157
|
+
return 'ghostToAccent';
|
|
158
|
+
case 4:
|
|
159
|
+
return 'hiHatChoke';
|
|
160
|
+
case 5:
|
|
161
|
+
return 'halfTimeFill';
|
|
162
|
+
default:
|
|
163
|
+
return 'breakdownFill';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Emit a fill for a single beat of the fill bar.
|
|
169
|
+
*
|
|
170
|
+
* @param track Hit accumulator.
|
|
171
|
+
* @param beatTick Absolute beat position of this beat.
|
|
172
|
+
* @param beat Beat index within the bar (0-3).
|
|
173
|
+
* @param fillType Selected fill archetype.
|
|
174
|
+
* @param velocity Base velocity for the beat.
|
|
175
|
+
*/
|
|
176
|
+
export function generateFill(
|
|
177
|
+
track: HitList,
|
|
178
|
+
beatTick: number,
|
|
179
|
+
beat: number,
|
|
180
|
+
fillType: FillType,
|
|
181
|
+
velocity: number,
|
|
182
|
+
): void {
|
|
183
|
+
const fillVel = velocity * 0.9;
|
|
184
|
+
const accentVel = velocity * 0.95;
|
|
185
|
+
const S = SIXTEENTH;
|
|
186
|
+
const E = EIGHTH;
|
|
187
|
+
|
|
188
|
+
if (beat === 0) {
|
|
189
|
+
track.add(GM.BD, beatTick, E, fillVel);
|
|
190
|
+
track.add(GM.SD, beatTick + E, E, fillVel - 5);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (beat === 1) {
|
|
194
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
195
|
+
track.add(GM.TOM_H, beatTick + E, E, fillVel - 3);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
switch (fillType) {
|
|
200
|
+
case 'snareRoll':
|
|
201
|
+
if (beat === 2) {
|
|
202
|
+
for (let i = 0; i < 4; i += 1) {
|
|
203
|
+
track.add(GM.SD, beatTick + i * S, S, fillVel * (0.6 + 0.1 * i));
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
for (let i = 0; i < 3; i += 1) {
|
|
207
|
+
track.add(GM.SD, beatTick + i * S, S, fillVel * (0.7 + 0.1 * i));
|
|
208
|
+
}
|
|
209
|
+
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case 'tomDescend':
|
|
214
|
+
if (beat === 2) {
|
|
215
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
216
|
+
track.add(GM.TOM_H, beatTick + E, E, fillVel - 5);
|
|
217
|
+
} else {
|
|
218
|
+
track.add(GM.TOM_H, beatTick, S, fillVel);
|
|
219
|
+
track.add(GM.TOM_M, beatTick + S, S, fillVel - 3);
|
|
220
|
+
track.add(GM.TOM_M, beatTick + E, S, fillVel - 5);
|
|
221
|
+
track.add(GM.TOM_L, beatTick + E + S, S, accentVel);
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
|
|
225
|
+
case 'tomAscend':
|
|
226
|
+
if (beat === 2) {
|
|
227
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
228
|
+
track.add(GM.TOM_L, beatTick + E, E, fillVel - 5);
|
|
229
|
+
} else {
|
|
230
|
+
track.add(GM.TOM_L, beatTick, S, fillVel);
|
|
231
|
+
track.add(GM.TOM_M, beatTick + S, S, fillVel + 3);
|
|
232
|
+
track.add(GM.TOM_M, beatTick + E, S, fillVel + 5);
|
|
233
|
+
track.add(GM.TOM_H, beatTick + E + S, S, accentVel);
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case 'snareTomCombo':
|
|
238
|
+
if (beat === 2) {
|
|
239
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
240
|
+
track.add(GM.SD, beatTick + E, S, fillVel - 5);
|
|
241
|
+
track.add(GM.TOM_H, beatTick + E + S, S, fillVel);
|
|
242
|
+
} else {
|
|
243
|
+
track.add(GM.TOM_M, beatTick, S, fillVel);
|
|
244
|
+
track.add(GM.SD, beatTick + S, S, fillVel - 3);
|
|
245
|
+
track.add(GM.TOM_L, beatTick + E, S, fillVel + 2);
|
|
246
|
+
track.add(GM.BD, beatTick + E + S, S, accentVel);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
case 'simpleCrash':
|
|
251
|
+
if (beat === 3) {
|
|
252
|
+
track.add(GM.BD, beatTick + E + S, S, accentVel);
|
|
253
|
+
track.add(GM.CRASH, beatTick + E + S, E, accentVel);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'linearFill':
|
|
258
|
+
if (beat === 2) {
|
|
259
|
+
track.add(GM.BD, beatTick, S, fillVel);
|
|
260
|
+
track.add(GM.SD, beatTick + S, S, fillVel);
|
|
261
|
+
track.add(GM.TOM_H, beatTick + 2 * S, S, fillVel);
|
|
262
|
+
track.add(GM.TOM_M, beatTick + 3 * S, S, fillVel);
|
|
263
|
+
} else {
|
|
264
|
+
track.add(GM.TOM_L, beatTick, S, fillVel + 3);
|
|
265
|
+
track.add(GM.SD, beatTick + S, S, fillVel + 5);
|
|
266
|
+
track.add(GM.BD, beatTick + 2 * S, S, fillVel + 7);
|
|
267
|
+
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
case 'ghostToAccent':
|
|
272
|
+
if (beat === 2) {
|
|
273
|
+
const ghost = fillVel * 0.4;
|
|
274
|
+
track.add(GM.SD, beatTick, S, ghost);
|
|
275
|
+
track.add(GM.SD, beatTick + S, S, ghost + 10);
|
|
276
|
+
track.add(GM.SD, beatTick + 2 * S, S, ghost + 20);
|
|
277
|
+
track.add(GM.SD, beatTick + 3 * S, S, ghost + 30);
|
|
278
|
+
} else {
|
|
279
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
280
|
+
track.add(GM.SD, beatTick + E, E, accentVel);
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'bdSnareAlternate':
|
|
285
|
+
if (beat === 2) {
|
|
286
|
+
track.add(GM.BD, beatTick, S, fillVel);
|
|
287
|
+
track.add(GM.SD, beatTick + S, S, fillVel);
|
|
288
|
+
track.add(GM.BD, beatTick + 2 * S, S, fillVel + 3);
|
|
289
|
+
track.add(GM.SD, beatTick + 3 * S, S, fillVel + 3);
|
|
290
|
+
} else {
|
|
291
|
+
track.add(GM.BD, beatTick, S, fillVel + 5);
|
|
292
|
+
track.add(GM.SD, beatTick + S, S, fillVel + 5);
|
|
293
|
+
track.add(GM.BD, beatTick + 2 * S, S, accentVel);
|
|
294
|
+
track.add(GM.SD, beatTick + 3 * S, S, accentVel);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case 'hiHatChoke':
|
|
299
|
+
if (beat === 2) {
|
|
300
|
+
track.add(GM.OHH, beatTick, E, fillVel);
|
|
301
|
+
track.add(GM.OHH, beatTick + E, E, fillVel + 5);
|
|
302
|
+
} else {
|
|
303
|
+
track.add(GM.OHH, beatTick, S, fillVel + 8);
|
|
304
|
+
track.add(GM.CHH, beatTick + S, S, accentVel);
|
|
305
|
+
track.add(GM.SD, beatTick + E, E, accentVel);
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'tomShuffle':
|
|
310
|
+
if (beat === 2) {
|
|
311
|
+
track.add(GM.TOM_H, beatTick, E, fillVel);
|
|
312
|
+
track.add(GM.TOM_M, beatTick + E + S / 2, S, fillVel - 5);
|
|
313
|
+
} else {
|
|
314
|
+
track.add(GM.TOM_M, beatTick, E, fillVel);
|
|
315
|
+
track.add(GM.TOM_L, beatTick + E + S / 2, S, fillVel + 5);
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
|
|
319
|
+
case 'breakdownFill':
|
|
320
|
+
if (beat === 3) {
|
|
321
|
+
track.add(GM.SD, beatTick + E, S, accentVel);
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'flamsAndDrags':
|
|
326
|
+
if (beat === 2) {
|
|
327
|
+
track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
|
|
328
|
+
track.add(GM.SD, beatTick, E, fillVel);
|
|
329
|
+
track.add(GM.SD, beatTick + E, S / 2, fillVel * 0.6);
|
|
330
|
+
track.add(GM.SD, beatTick + E + S / 2, S / 2, fillVel * 0.6);
|
|
331
|
+
track.add(GM.SD, beatTick + E + S, E, fillVel);
|
|
332
|
+
} else {
|
|
333
|
+
track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
|
|
334
|
+
track.add(GM.SD, beatTick, 1, accentVel);
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
case 'halfTimeFill':
|
|
339
|
+
if (beat === 2) {
|
|
340
|
+
track.add(GM.SD, beatTick, 1, accentVel);
|
|
341
|
+
track.add(GM.BD, beatTick, 1, fillVel);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { BackingDensity, SectionType } from './internal.js';
|
|
2
|
+
import { MoodCategory, sectionIndex } from './internal.js';
|
|
3
|
+
import type { DrumRng } from './rng.js';
|
|
4
|
+
|
|
5
|
+
/** Ghost-note position within a beat: the "e" (1st 16th) or "a" (3rd 16th). */
|
|
6
|
+
export type GhostPosition = 'e' | 'a';
|
|
7
|
+
|
|
8
|
+
/** Ghost density level ordinal (0 = none, 3 = heavy). */
|
|
9
|
+
type GhostDensityLevel = 0 | 1 | 2 | 3;
|
|
10
|
+
|
|
11
|
+
// Ghost density level per [section index][mood category].
|
|
12
|
+
const GHOST_DENSITY_TABLE: GhostDensityLevel[][] = [
|
|
13
|
+
/* intro */ [0, 1, 1],
|
|
14
|
+
/* a */ [0, 1, 2],
|
|
15
|
+
/* b */ [1, 2, 2],
|
|
16
|
+
/* chorus */ [1, 2, 3],
|
|
17
|
+
/* bridge */ [1, 1, 2],
|
|
18
|
+
/* interlude */ [0, 1, 1],
|
|
19
|
+
/* outro */ [0, 1, 1],
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function levelToProbability(level: GhostDensityLevel): number {
|
|
23
|
+
switch (level) {
|
|
24
|
+
case 0:
|
|
25
|
+
return 0;
|
|
26
|
+
case 1:
|
|
27
|
+
return 0.15;
|
|
28
|
+
case 2:
|
|
29
|
+
return 0.3;
|
|
30
|
+
case 3:
|
|
31
|
+
return 0.45;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clampLevel(level: number): GhostDensityLevel {
|
|
36
|
+
return Math.max(0, Math.min(3, level)) as GhostDensityLevel;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function adjustForBpm(level: GhostDensityLevel, bpm: number): GhostDensityLevel {
|
|
40
|
+
if (bpm >= 160 && level !== 0) {
|
|
41
|
+
return clampLevel(level - 1);
|
|
42
|
+
}
|
|
43
|
+
if (bpm <= 90 && level !== 3) {
|
|
44
|
+
return clampLevel(level + 1);
|
|
45
|
+
}
|
|
46
|
+
return level;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Ghost-note trigger probability for a section, mood, density, and tempo. */
|
|
50
|
+
export function getGhostDensity(
|
|
51
|
+
mood: MoodCategory,
|
|
52
|
+
section: SectionType,
|
|
53
|
+
backingDensity: BackingDensity,
|
|
54
|
+
bpm: number,
|
|
55
|
+
): number {
|
|
56
|
+
const row = GHOST_DENSITY_TABLE[sectionIndex(section)] ?? GHOST_DENSITY_TABLE[1];
|
|
57
|
+
const level = adjustForBpm((row?.[mood] ?? 0) as GhostDensityLevel, bpm);
|
|
58
|
+
|
|
59
|
+
if (backingDensity === 'thin' && level !== 0) {
|
|
60
|
+
return levelToProbability(clampLevel(level - 1));
|
|
61
|
+
}
|
|
62
|
+
if (backingDensity === 'thick' && level !== 3) {
|
|
63
|
+
return levelToProbability(clampLevel(level + 1));
|
|
64
|
+
}
|
|
65
|
+
return levelToProbability(level);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Ghost-note velocity multiplier for a section and metric position. */
|
|
69
|
+
export function getGhostVelocity(
|
|
70
|
+
section: SectionType,
|
|
71
|
+
beatPosition: number,
|
|
72
|
+
isAfterSnare: boolean,
|
|
73
|
+
): number {
|
|
74
|
+
let base = 0.4;
|
|
75
|
+
const even = beatPosition % 2 === 0 ? 0.05 : 0;
|
|
76
|
+
switch (section) {
|
|
77
|
+
case 'a':
|
|
78
|
+
base = 0.35 + even;
|
|
79
|
+
break;
|
|
80
|
+
case 'chorus':
|
|
81
|
+
base = 0.5 + even;
|
|
82
|
+
break;
|
|
83
|
+
case 'bridge':
|
|
84
|
+
base = 0.25 + even;
|
|
85
|
+
break;
|
|
86
|
+
case 'b':
|
|
87
|
+
base = 0.4 + even;
|
|
88
|
+
break;
|
|
89
|
+
case 'intro':
|
|
90
|
+
case 'outro':
|
|
91
|
+
base = 0.38;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (isAfterSnare) {
|
|
95
|
+
base += 0.1;
|
|
96
|
+
}
|
|
97
|
+
return Math.max(0.25, Math.min(0.65, base));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Probability of a ghost at a specific 16th position (higher near the snare). */
|
|
101
|
+
export function getGhostProbabilityAtPosition(beat: number, sixteenthInBeat: number): number {
|
|
102
|
+
const nearSnare =
|
|
103
|
+
(beat === 0 && sixteenthInBeat === 3) ||
|
|
104
|
+
(beat === 1 && sixteenthInBeat === 1) ||
|
|
105
|
+
(beat === 2 && sixteenthInBeat === 3) ||
|
|
106
|
+
(beat === 3 && sixteenthInBeat === 1);
|
|
107
|
+
return nearSnare ? 0.6 : 0.25;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Choose which ghost positions a groove favours. */
|
|
111
|
+
export function selectGhostPositions(mood: MoodCategory, rng: DrumRng): GhostPosition[] {
|
|
112
|
+
if (mood === MoodCategory.Energetic) {
|
|
113
|
+
return ['e', 'a'];
|
|
114
|
+
}
|
|
115
|
+
if (mood === MoodCategory.Calm) {
|
|
116
|
+
return rng.prob(0.5) ? ['e'] : [];
|
|
117
|
+
}
|
|
118
|
+
return ['e'];
|
|
119
|
+
}
|