@libraz/libcantus 0.9.2 → 0.9.4
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/README.md +6 -5
- package/dist/analyze/index.cjs +41 -1676
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +194 -122
- package/dist/analyze/index.d.ts +194 -122
- package/dist/analyze/index.js +19 -8
- package/dist/chunk-36N73KZ3.cjs +246 -0
- package/dist/chunk-36N73KZ3.cjs.map +1 -0
- package/dist/chunk-4HLLZPXT.cjs +3077 -0
- package/dist/chunk-4HLLZPXT.cjs.map +1 -0
- package/dist/chunk-7MGZ7D62.cjs +498 -0
- package/dist/chunk-7MGZ7D62.cjs.map +1 -0
- package/dist/{chunk-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
- package/dist/chunk-7NKDPF74.js.map +1 -0
- package/dist/chunk-CAN76NCU.cjs +679 -0
- package/dist/chunk-CAN76NCU.cjs.map +1 -0
- package/dist/{chunk-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
- package/dist/chunk-EHVEBWM5.js.map +1 -0
- package/dist/chunk-FCANXBAT.cjs +30 -0
- package/dist/chunk-FCANXBAT.cjs.map +1 -0
- package/dist/chunk-FNDWXRRI.cjs +479 -0
- package/dist/chunk-FNDWXRRI.cjs.map +1 -0
- package/dist/{chunk-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/chunk-HKGYCQ34.js +53 -0
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/chunk-I24MV65U.js +106 -0
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
- package/dist/chunk-KMHOICIY.js.map +1 -0
- package/dist/chunk-LW53AHQU.cjs +1117 -0
- package/dist/chunk-LW53AHQU.cjs.map +1 -0
- package/dist/chunk-N6IHQUOX.js +1 -0
- package/dist/chunk-N6IHQUOX.js.map +1 -0
- package/dist/chunk-NKIBQ6PT.js +30 -0
- package/dist/chunk-NKIBQ6PT.js.map +1 -0
- package/dist/chunk-NP4NA2FB.cjs +106 -0
- package/dist/chunk-NP4NA2FB.cjs.map +1 -0
- package/dist/chunk-NVE34YGZ.cjs +1449 -0
- package/dist/chunk-NVE34YGZ.cjs.map +1 -0
- package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
- package/dist/chunk-Q2XUP227.js.map +1 -0
- package/dist/chunk-Q6OP4U43.cjs +327 -0
- package/dist/chunk-Q6OP4U43.cjs.map +1 -0
- package/dist/chunk-RC6GBMCD.cjs +845 -0
- package/dist/chunk-RC6GBMCD.cjs.map +1 -0
- package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
- package/dist/chunk-RK35RIN7.js.map +1 -0
- package/dist/chunk-RUMU3UA6.cjs +53 -0
- package/dist/chunk-RUMU3UA6.cjs.map +1 -0
- package/dist/chunk-T64C7ZD7.cjs +1 -0
- package/dist/chunk-T64C7ZD7.cjs.map +1 -0
- package/dist/chunk-XEBG3G53.js +498 -0
- package/dist/chunk-XEBG3G53.js.map +1 -0
- package/dist/chunk-YLEMGEFH.js +246 -0
- package/dist/chunk-YLEMGEFH.js.map +1 -0
- package/dist/chunk-ZEJR4TKL.cjs +173 -0
- package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
- package/dist/core/index.cjs +130 -431
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +331 -17
- package/dist/core/index.d.ts +331 -17
- package/dist/core/index.js +71 -13
- package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +30 -3751
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +373 -59
- package/dist/generate/index.d.ts +373 -59
- package/dist/generate/index.js +23 -9
- package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
- package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
- package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
- package/dist/index-OG65ocOr.d.cts +272 -0
- package/dist/index-OG65ocOr.d.ts +272 -0
- package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
- package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -6848
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -13
- package/dist/index.d.ts +14 -13
- package/dist/index.js +132 -27
- package/dist/model/index.cjs +8 -2423
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -74
- package/dist/model/index.d.ts +433 -74
- package/dist/model/index.js +6 -7
- package/dist/styled-DIRkk9Uz.d.ts +506 -0
- package/dist/styled-DJbkJQfj.d.cts +506 -0
- package/dist/theory/index.cjs +172 -1676
- package/dist/theory/index.cjs.map +1 -1
- package/dist/theory/index.d.cts +151 -233
- package/dist/theory/index.d.ts +151 -233
- package/dist/theory/index.js +31 -7
- package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
- package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
- package/package.json +73 -24
- package/dist/chunk-5HTNMQW4.js.map +0 -1
- package/dist/chunk-7Y7WF64D.js.map +0 -1
- package/dist/chunk-A4532ZFR.js.map +0 -1
- package/dist/chunk-BHUT2S5G.js +0 -22
- package/dist/chunk-BHUT2S5G.js.map +0 -1
- package/dist/chunk-CCBLVAPK.js +0 -42
- package/dist/chunk-CCBLVAPK.js.map +0 -1
- package/dist/chunk-COKTX2G5.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-GQKN5KDT.js.map +0 -1
- package/dist/chunk-LN7ZZMD2.js.map +0 -1
- package/dist/chunk-MSWFZF7J.js +0 -118
- package/dist/chunk-MSWFZF7J.js.map +0 -1
- package/dist/chunk-R3S2LUBH.js.map +0 -1
- package/dist/chunk-SDM3VJJ5.js +0 -41
- package/dist/chunk-SDM3VJJ5.js.map +0 -1
- package/dist/chunk-XTC3X6MS.js.map +0 -1
- package/dist/chunk-ZXW5YIWK.js +0 -55
- package/dist/chunk-ZXW5YIWK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/styled-BjXtOFNk.d.cts +0 -282
- package/dist/styled-D7BAx3S3.d.ts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -125
- package/src/analyze/arrange/tension.ts +0 -187
- package/src/analyze/arrange/tracks.ts +0 -279
- package/src/analyze/detect/index.ts +0 -235
- package/src/analyze/functional/borrowed.ts +0 -84
- package/src/analyze/functional/cadence.ts +0 -66
- package/src/analyze/functional/function.ts +0 -184
- package/src/analyze/functional/index.ts +0 -23
- package/src/analyze/functional/internal.ts +0 -29
- package/src/analyze/functional/roman.ts +0 -393
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -415
- package/src/analyze/voice/index.ts +0 -330
- package/src/core/index.ts +0 -47
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -282
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -53
- package/src/core/tuning/index.ts +0 -156
- package/src/core/types.ts +0 -29
- package/src/generate/bass/index.ts +0 -350
- package/src/generate/countermelody/index.ts +0 -379
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -58
- package/src/generate/drums/fills.ts +0 -356
- package/src/generate/drums/ghost.ts +0 -119
- package/src/generate/drums/hihat.ts +0 -285
- package/src/generate/drums/hit.ts +0 -31
- package/src/generate/drums/index.ts +0 -329
- package/src/generate/drums/internal.ts +0 -257
- package/src/generate/drums/kick.ts +0 -160
- package/src/generate/drums/percussion.ts +0 -121
- package/src/generate/drums/rng.ts +0 -7
- package/src/generate/drums/swing.ts +0 -71
- package/src/generate/groove/index.ts +0 -326
- package/src/generate/harmonize/index.ts +0 -440
- package/src/generate/index.ts +0 -59
- package/src/generate/motif/index.ts +0 -398
- package/src/generate/progression/index.ts +0 -370
- package/src/generate/reharmony/index.ts +0 -330
- package/src/generate/rhythm/index.ts +0 -165
- package/src/index.ts +0 -247
- package/src/model/chord.ts +0 -423
- package/src/model/index.ts +0 -15
- package/src/model/interval.ts +0 -82
- package/src/model/key.ts +0 -266
- package/src/model/note.ts +0 -176
- package/src/model/progression.ts +0 -161
- package/src/model/shared.ts +0 -12
- package/src/theory/chord/index.ts +0 -373
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -92
- package/src/theory/index.ts +0 -107
- package/src/theory/safety/index.ts +0 -419
- package/src/theory/scale/degrees.ts +0 -123
- package/src/theory/scale/index.ts +0 -28
- package/src/theory/scale/key.ts +0 -56
- package/src/theory/scale/masks.ts +0 -153
- package/src/theory/spelling/index.ts +0 -182
- package/src/theory/symbol/index.ts +0 -268
- package/src/theory/voicing/index.ts +0 -5
- package/src/theory/voicing/internal.ts +0 -168
- package/src/theory/voicing/leading.ts +0 -112
- package/src/theory/voicing/satb.ts +0 -195
- package/src/theory/voicing/styled.ts +0 -176
- package/tsconfig.json +0 -23
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import type { DrumRole, DrumStyle, SectionType } from './internal.js';
|
|
2
|
-
import { GM } from './internal.js';
|
|
3
|
-
import type { DrumRng } from './rng.js';
|
|
4
|
-
|
|
5
|
-
/** Hi-hat subdivision level. */
|
|
6
|
-
export type HiHatLevel = 'quarter' | 'eighth' | 'sixteenth';
|
|
7
|
-
|
|
8
|
-
/** Hi-hat articulation type. */
|
|
9
|
-
export type HiHatType = 'closed' | 'pedal' | 'open' | 'halfOpen' | 'ride';
|
|
10
|
-
|
|
11
|
-
/** BPM at or above which 16th-note hi-hats become 8ths for playability. */
|
|
12
|
-
export const HH_16TH_BPM_THRESHOLD = 150;
|
|
13
|
-
|
|
14
|
-
const FHH_VEL_MIN = 45;
|
|
15
|
-
const FHH_VEL_MAX = 60;
|
|
16
|
-
/** Velocity boost applied to dynamic open hi-hat accents. */
|
|
17
|
-
export const OHH_VEL_BOOST = 7;
|
|
18
|
-
|
|
19
|
-
/** True unless the role suppresses timekeeping cymbals entirely. */
|
|
20
|
-
export function shouldPlayHiHat(role: DrumRole): boolean {
|
|
21
|
-
return role !== 'fxOnly';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Preferred timekeeping instrument for a role. */
|
|
25
|
-
export function roleHiHatInstrument(role: DrumRole, useRide: boolean): number {
|
|
26
|
-
if (role === 'ambient') {
|
|
27
|
-
return GM.RIDE;
|
|
28
|
-
}
|
|
29
|
-
return useRide ? GM.RIDE : GM.CHH;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function sparser(level: HiHatLevel): HiHatLevel {
|
|
33
|
-
if (level === 'sixteenth') {
|
|
34
|
-
return 'eighth';
|
|
35
|
-
}
|
|
36
|
-
if (level === 'eighth') {
|
|
37
|
-
return 'quarter';
|
|
38
|
-
}
|
|
39
|
-
return 'quarter';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function denser(level: HiHatLevel): HiHatLevel {
|
|
43
|
-
if (level === 'quarter') {
|
|
44
|
-
return 'eighth';
|
|
45
|
-
}
|
|
46
|
-
if (level === 'eighth') {
|
|
47
|
-
return 'sixteenth';
|
|
48
|
-
}
|
|
49
|
-
return 'sixteenth';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Choose the hi-hat subdivision for a section/style/density/BPM. */
|
|
53
|
-
export function getHiHatLevel(
|
|
54
|
-
section: SectionType,
|
|
55
|
-
style: DrumStyle,
|
|
56
|
-
backingDensity: 'thin' | 'normal' | 'thick',
|
|
57
|
-
bpm: number,
|
|
58
|
-
rng: DrumRng,
|
|
59
|
-
): HiHatLevel {
|
|
60
|
-
const allow16th = bpm < HH_16TH_BPM_THRESHOLD;
|
|
61
|
-
let base: HiHatLevel = 'eighth';
|
|
62
|
-
|
|
63
|
-
if (style === 'sparse') {
|
|
64
|
-
base = section === 'chorus' ? 'eighth' : 'quarter';
|
|
65
|
-
} else if (style === 'fourOnFloor') {
|
|
66
|
-
if (allow16th && section === 'chorus' && rng.prob(0.25)) {
|
|
67
|
-
return 'sixteenth';
|
|
68
|
-
}
|
|
69
|
-
return 'eighth';
|
|
70
|
-
} else if (style === 'synth') {
|
|
71
|
-
if (!allow16th) {
|
|
72
|
-
return 'eighth';
|
|
73
|
-
}
|
|
74
|
-
if (section === 'a' && rng.prob(0.2)) {
|
|
75
|
-
return 'eighth';
|
|
76
|
-
}
|
|
77
|
-
return 'sixteenth';
|
|
78
|
-
} else if (style === 'trap') {
|
|
79
|
-
return allow16th ? 'sixteenth' : 'eighth';
|
|
80
|
-
} else if (style === 'latin') {
|
|
81
|
-
if (allow16th && section === 'chorus' && rng.prob(0.3)) {
|
|
82
|
-
return 'sixteenth';
|
|
83
|
-
}
|
|
84
|
-
return 'eighth';
|
|
85
|
-
} else {
|
|
86
|
-
switch (section) {
|
|
87
|
-
case 'intro':
|
|
88
|
-
base = 'quarter';
|
|
89
|
-
break;
|
|
90
|
-
case 'outro':
|
|
91
|
-
base = 'eighth';
|
|
92
|
-
break;
|
|
93
|
-
case 'a':
|
|
94
|
-
base = rng.prob(0.3) ? 'quarter' : 'eighth';
|
|
95
|
-
break;
|
|
96
|
-
case 'b':
|
|
97
|
-
base = allow16th && rng.prob(0.25) ? 'sixteenth' : 'eighth';
|
|
98
|
-
break;
|
|
99
|
-
case 'chorus':
|
|
100
|
-
base = allow16th && rng.prob(0.35) ? 'sixteenth' : 'eighth';
|
|
101
|
-
break;
|
|
102
|
-
case 'bridge':
|
|
103
|
-
base = 'eighth';
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (backingDensity === 'thin') {
|
|
109
|
-
base = sparser(base);
|
|
110
|
-
} else if (backingDensity === 'thick') {
|
|
111
|
-
base = denser(base);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!allow16th && base === 'sixteenth') {
|
|
115
|
-
base = 'eighth';
|
|
116
|
-
}
|
|
117
|
-
return base;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** Metric velocity multiplier for a 16th position within a beat. */
|
|
121
|
-
export function hiHatVelocityMultiplier(sixteenth: number, rng: DrumRng): number {
|
|
122
|
-
let baseValue: number;
|
|
123
|
-
switch (sixteenth) {
|
|
124
|
-
case 0:
|
|
125
|
-
baseValue = 0.95;
|
|
126
|
-
break;
|
|
127
|
-
case 2:
|
|
128
|
-
baseValue = 0.75;
|
|
129
|
-
break;
|
|
130
|
-
case 1:
|
|
131
|
-
baseValue = 0.55;
|
|
132
|
-
break;
|
|
133
|
-
default:
|
|
134
|
-
baseValue = 0.5;
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
return baseValue * rng.float(0.95, 1.05);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Bars between dynamic open hi-hat accents (0 disables them). */
|
|
141
|
-
export function openHiHatBarInterval(section: SectionType, style: DrumStyle): number {
|
|
142
|
-
if (style === 'sparse') {
|
|
143
|
-
return section === 'chorus' ? 4 : 0;
|
|
144
|
-
}
|
|
145
|
-
switch (section) {
|
|
146
|
-
case 'intro':
|
|
147
|
-
return style === 'fourOnFloor' ? 4 : 0;
|
|
148
|
-
case 'a':
|
|
149
|
-
return style === 'fourOnFloor' || style === 'upbeat' ? 2 : 4;
|
|
150
|
-
case 'b':
|
|
151
|
-
return 2;
|
|
152
|
-
case 'chorus':
|
|
153
|
-
return style === 'rock' || style === 'fourOnFloor' ? 1 : 2;
|
|
154
|
-
case 'bridge':
|
|
155
|
-
return 0;
|
|
156
|
-
case 'outro':
|
|
157
|
-
return 4;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Beat that receives the dynamic open hi-hat within a bar. */
|
|
162
|
-
export function openHiHatBeat(section: SectionType, rng: DrumRng): number {
|
|
163
|
-
if (section === 'chorus') {
|
|
164
|
-
const choice = rng.range(0, 3);
|
|
165
|
-
if (choice < 2) {
|
|
166
|
-
return 3;
|
|
167
|
-
}
|
|
168
|
-
if (choice < 3) {
|
|
169
|
-
return 1;
|
|
170
|
-
}
|
|
171
|
-
return 2;
|
|
172
|
-
}
|
|
173
|
-
return 3;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Whether the section uses an independent foot hi-hat pulse. */
|
|
177
|
-
export function shouldUseFootHiHat(section: SectionType, role: DrumRole): boolean {
|
|
178
|
-
if (role === 'fxOnly') {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
switch (section) {
|
|
182
|
-
case 'intro':
|
|
183
|
-
case 'bridge':
|
|
184
|
-
case 'outro':
|
|
185
|
-
return true;
|
|
186
|
-
default:
|
|
187
|
-
return role === 'ambient' || role === 'minimal';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Primary hi-hat articulation for a section. */
|
|
192
|
-
export function sectionHiHatType(section: SectionType, role: DrumRole): HiHatType {
|
|
193
|
-
if (role === 'ambient') {
|
|
194
|
-
return 'ride';
|
|
195
|
-
}
|
|
196
|
-
if (role === 'minimal') {
|
|
197
|
-
return 'pedal';
|
|
198
|
-
}
|
|
199
|
-
switch (section) {
|
|
200
|
-
case 'intro':
|
|
201
|
-
case 'a':
|
|
202
|
-
return 'pedal';
|
|
203
|
-
case 'b':
|
|
204
|
-
return 'closed';
|
|
205
|
-
case 'chorus':
|
|
206
|
-
return 'open';
|
|
207
|
-
case 'bridge':
|
|
208
|
-
return 'ride';
|
|
209
|
-
case 'outro':
|
|
210
|
-
return 'halfOpen';
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** GM note for a hi-hat articulation. */
|
|
215
|
-
export function hiHatNote(type: HiHatType): number {
|
|
216
|
-
switch (type) {
|
|
217
|
-
case 'pedal':
|
|
218
|
-
return GM.FHH;
|
|
219
|
-
case 'open':
|
|
220
|
-
return GM.OHH;
|
|
221
|
-
case 'ride':
|
|
222
|
-
return GM.RIDE;
|
|
223
|
-
default:
|
|
224
|
-
return GM.CHH;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Velocity multiplier for a hi-hat articulation. */
|
|
229
|
-
export function hiHatTypeVelocityMultiplier(type: HiHatType): number {
|
|
230
|
-
switch (type) {
|
|
231
|
-
case 'halfOpen':
|
|
232
|
-
return 0.75;
|
|
233
|
-
case 'pedal':
|
|
234
|
-
return 0.65;
|
|
235
|
-
case 'open':
|
|
236
|
-
return 1.0;
|
|
237
|
-
case 'ride':
|
|
238
|
-
return 0.9;
|
|
239
|
-
default:
|
|
240
|
-
return 0.85;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Whether to accent with an open hi-hat at a given beat. */
|
|
245
|
-
export function shouldAddOpenHHAccent(
|
|
246
|
-
section: SectionType,
|
|
247
|
-
beat: number,
|
|
248
|
-
bar: number,
|
|
249
|
-
rng: DrumRng,
|
|
250
|
-
): boolean {
|
|
251
|
-
if (section !== 'chorus' && section !== 'b') {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
if (section === 'chorus') {
|
|
255
|
-
if (beat === 1 || beat === 3) {
|
|
256
|
-
return rng.prob(0.6);
|
|
257
|
-
}
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
if (beat === 3 && bar % 2 === 1) {
|
|
261
|
-
return rng.prob(0.4);
|
|
262
|
-
}
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/** Foot hi-hat velocity with slight humanization. */
|
|
267
|
-
export function footHiHatVelocity(rng: DrumRng): number {
|
|
268
|
-
return rng.range(FHH_VEL_MIN, FHH_VEL_MAX);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/** Whether the section uses a ride cymbal instead of hi-hats. */
|
|
272
|
-
export function shouldUseRideForSection(section: SectionType, style: DrumStyle): boolean {
|
|
273
|
-
if (style === 'rock' && section === 'chorus') {
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
if (style === 'sparse') {
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
return section === 'bridge';
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Whether a bridge beat is carried by a cross-stick instead of the hi-hat. */
|
|
283
|
-
export function shouldUseBridgeCrossStick(section: SectionType, beat: number): boolean {
|
|
284
|
-
return section === 'bridge' && (beat === 1 || beat === 3);
|
|
285
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { clampVel, GM } from './internal.js';
|
|
2
|
-
|
|
3
|
-
/** A single percussion onset. */
|
|
4
|
-
export type DrumHit = {
|
|
5
|
-
pitch: number;
|
|
6
|
-
startBeat: number;
|
|
7
|
-
durationBeat: number;
|
|
8
|
-
velocity: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/** Accumulator for drum onsets with velocity clamping and simple lookups. */
|
|
12
|
-
export class HitList {
|
|
13
|
-
readonly hits: DrumHit[] = [];
|
|
14
|
-
|
|
15
|
-
/** Append a hit, clamping velocity and dropping non-positive positions. */
|
|
16
|
-
add(pitch: number, startBeat: number, durationBeat: number, velocity: number): void {
|
|
17
|
-
this.hits.push({
|
|
18
|
-
pitch,
|
|
19
|
-
startBeat: Math.max(0, startBeat),
|
|
20
|
-
durationBeat,
|
|
21
|
-
velocity: clampVel(velocity),
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** True when a crash already sits within a 16th of `startBeat`. */
|
|
26
|
-
hasCrashNear(startBeat: number): boolean {
|
|
27
|
-
return this.hits.some(
|
|
28
|
-
(h) => h.pitch === GM.CRASH && h.startBeat >= startBeat && h.startBeat < startBeat + 0.25,
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type BeatCtx,
|
|
3
|
-
generateGhostNotesForBeat,
|
|
4
|
-
generateHiHatForBeat,
|
|
5
|
-
generateKickForBeat,
|
|
6
|
-
generatePreChorusBuildup,
|
|
7
|
-
generateSnareForBeat,
|
|
8
|
-
type SectionCtx,
|
|
9
|
-
} from './beat.js';
|
|
10
|
-
import { euclideanRhythm, patternToMask } from './euclid.js';
|
|
11
|
-
import { type FillType, generateFill, getFillStartBeat, selectFillType } from './fills.js';
|
|
12
|
-
import {
|
|
13
|
-
footHiHatVelocity,
|
|
14
|
-
getHiHatLevel,
|
|
15
|
-
openHiHatBarInterval,
|
|
16
|
-
openHiHatBeat,
|
|
17
|
-
shouldPlayHiHat,
|
|
18
|
-
shouldUseFootHiHat,
|
|
19
|
-
shouldUseRideForSection,
|
|
20
|
-
} from './hihat.js';
|
|
21
|
-
import { type DrumHit, HitList } from './hit.js';
|
|
22
|
-
import {
|
|
23
|
-
calculateVelocity,
|
|
24
|
-
type DrumRole,
|
|
25
|
-
type Feel,
|
|
26
|
-
feelSwingAmount,
|
|
27
|
-
GM,
|
|
28
|
-
type GrooveStyle,
|
|
29
|
-
ghostMoodCategory,
|
|
30
|
-
mapDensity,
|
|
31
|
-
mapSection,
|
|
32
|
-
mapStyle,
|
|
33
|
-
type PublicSection,
|
|
34
|
-
percMoodCategory,
|
|
35
|
-
sectionDensityMultiplier,
|
|
36
|
-
sectionEnergy,
|
|
37
|
-
} from './internal.js';
|
|
38
|
-
import {
|
|
39
|
-
euclideanToKickPattern,
|
|
40
|
-
getKickPattern,
|
|
41
|
-
isInPreChorusLift,
|
|
42
|
-
type KickPattern,
|
|
43
|
-
} from './kick.js';
|
|
44
|
-
import { generateAuxPercussionForBar, getPercussionConfig } from './percussion.js';
|
|
45
|
-
import { createRng } from './rng.js';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* A single drum onset emitted by {@link generateDrums}.
|
|
49
|
-
*
|
|
50
|
-
* @category Composition
|
|
51
|
-
*/
|
|
52
|
-
export type { DrumHit } from './hit.js';
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Groove feel (the swing/straight rhythmic character) for {@link generateDrums}.
|
|
56
|
-
*
|
|
57
|
-
* @category Composition
|
|
58
|
-
*/
|
|
59
|
-
export type GrooveFeel = Feel;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Drum voicing role and groove style identifiers for {@link generateDrums}.
|
|
63
|
-
*
|
|
64
|
-
* @category Composition
|
|
65
|
-
*/
|
|
66
|
-
export type { DrumRole, GrooveStyle } from './internal.js';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Public section identifiers for {@link generateDrums}.
|
|
70
|
-
*
|
|
71
|
-
* @category Composition
|
|
72
|
-
*/
|
|
73
|
-
export type Section = PublicSection;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* A Euclidean (Bjorklund) kick pattern for {@link generateDrums}.
|
|
77
|
-
*
|
|
78
|
-
* @category Composition
|
|
79
|
-
*/
|
|
80
|
-
export type EuclideanKick = {
|
|
81
|
-
/** Number of kick onsets, clamped to `[0, steps]`. */
|
|
82
|
-
pulses: number;
|
|
83
|
-
/**
|
|
84
|
-
* Total steps in the bar (1..16).
|
|
85
|
-
*
|
|
86
|
-
* @defaultValue 16
|
|
87
|
-
*/
|
|
88
|
-
steps?: number;
|
|
89
|
-
/**
|
|
90
|
-
* Steps to rotate the onsets toward later positions.
|
|
91
|
-
*
|
|
92
|
-
* @defaultValue 0
|
|
93
|
-
*/
|
|
94
|
-
rotation?: number;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Options controlling {@link generateDrums}.
|
|
99
|
-
*
|
|
100
|
-
* @category Composition
|
|
101
|
-
*/
|
|
102
|
-
export type DrumsOptions = {
|
|
103
|
-
bars: number;
|
|
104
|
-
bpm: number;
|
|
105
|
-
style: GrooveStyle;
|
|
106
|
-
section: Section;
|
|
107
|
-
density: number;
|
|
108
|
-
/**
|
|
109
|
-
* Replace the final bar with a fill.
|
|
110
|
-
*
|
|
111
|
-
* @defaultValue false
|
|
112
|
-
*/
|
|
113
|
-
fills?: boolean;
|
|
114
|
-
feel?: GrooveFeel;
|
|
115
|
-
/**
|
|
116
|
-
* Voicing role; `'fxOnly'` suppresses the main kick/snare/ghost/fill voices.
|
|
117
|
-
*
|
|
118
|
-
* @defaultValue `'full'`
|
|
119
|
-
*/
|
|
120
|
-
role?: DrumRole;
|
|
121
|
-
/**
|
|
122
|
-
* Seed for the deterministic PRNG.
|
|
123
|
-
*
|
|
124
|
-
* @defaultValue 0
|
|
125
|
-
*/
|
|
126
|
-
seed?: number;
|
|
127
|
-
/**
|
|
128
|
-
* Section the final-bar fill leads into. Shapes which fill archetype is
|
|
129
|
-
* chosen (into-chorus and out-of-intro fills differ from generic ones).
|
|
130
|
-
* Defaults to `section`, i.e. a within-section fill.
|
|
131
|
-
*/
|
|
132
|
-
nextSection?: Section;
|
|
133
|
-
/**
|
|
134
|
-
* When set, the kick follows this Euclidean rhythm instead of the
|
|
135
|
-
* style/section pattern, giving direct access to evenly spread onsets.
|
|
136
|
-
*/
|
|
137
|
-
euclideanKick?: EuclideanKick;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Generate a drum performance as a flat list of onsets.
|
|
142
|
-
*
|
|
143
|
-
* Every voice (kick, snare, ghost snares, closed/open/foot hi-hats, ride, toms,
|
|
144
|
-
* crash, and auxiliary percussion) is emitted as a {@link DrumHit} distinguished
|
|
145
|
-
* by its General MIDI pitch. Groove style selects an internal style and feel;
|
|
146
|
-
* `density` sets the backing-density level; `section` shapes kick, hi-hat, ghost,
|
|
147
|
-
* and percussion density. 16th-note hi-hats drop to 8ths at or above 150 BPM.
|
|
148
|
-
* When `fills` is true the final bar is replaced with a fill whose archetype is
|
|
149
|
-
* shaped by `nextSection`; a fill that would emit nothing on its beat falls back
|
|
150
|
-
* to the normal groove so the phrase end is never silent. `euclideanKick`
|
|
151
|
-
* overrides the kick with an evenly-spread Euclidean pattern. Output is fully
|
|
152
|
-
* determined by the options plus `seed`.
|
|
153
|
-
*
|
|
154
|
-
* @param opts Generation options.
|
|
155
|
-
* @returns Percussion onsets in bar order.
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```ts
|
|
159
|
-
* import { generateDrums } from '@libraz/libcantus';
|
|
160
|
-
* const hits = generateDrums({ bars: 4, bpm: 120, style: 'standard', section: 'chorus', density: 0.6, fills: true });
|
|
161
|
-
* // Fully determined by the options plus seed (defaults to 0).
|
|
162
|
-
* ```
|
|
163
|
-
*
|
|
164
|
-
* @category Composition
|
|
165
|
-
*/
|
|
166
|
-
export function generateDrums(opts: DrumsOptions): DrumHit[] {
|
|
167
|
-
const track = new HitList();
|
|
168
|
-
const rng = createRng(opts.seed ?? 0);
|
|
169
|
-
const mapping = mapStyle(opts.style);
|
|
170
|
-
const style = mapping.style;
|
|
171
|
-
const feel: Feel = opts.feel ?? mapping.feel;
|
|
172
|
-
const role: DrumRole = opts.role ?? 'full';
|
|
173
|
-
// fxOnly leaves only fx/aux voices: the main kick, snare, ghost, and fill
|
|
174
|
-
// voices are suppressed just as timekeeping hi-hats already are.
|
|
175
|
-
const playMainVoices = role !== 'fxOnly';
|
|
176
|
-
const section = mapSection(opts.section);
|
|
177
|
-
const backingDensity = mapDensity(opts.density);
|
|
178
|
-
const swingAmount = feelSwingAmount(feel);
|
|
179
|
-
|
|
180
|
-
let densityMult = sectionDensityMultiplier(section);
|
|
181
|
-
if (backingDensity === 'thin') {
|
|
182
|
-
densityMult *= 0.75;
|
|
183
|
-
} else if (backingDensity === 'thick') {
|
|
184
|
-
densityMult *= 1.15;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const sec: SectionCtx = {
|
|
188
|
-
style,
|
|
189
|
-
feel,
|
|
190
|
-
densityMult,
|
|
191
|
-
backingDensity,
|
|
192
|
-
hhLevel: getHiHatLevel(section, style, backingDensity, opts.bpm, rng),
|
|
193
|
-
useGhostNotes:
|
|
194
|
-
(section === 'b' || section === 'chorus' || section === 'bridge') && style !== 'sparse',
|
|
195
|
-
ghostBoost: mapping.ghostBoost,
|
|
196
|
-
useRide: shouldUseRideForSection(section, style),
|
|
197
|
-
useFootHh: shouldUseFootHiHat(section, role),
|
|
198
|
-
role,
|
|
199
|
-
ghostMood: ghostMoodCategory(style),
|
|
200
|
-
snareBeat3: mapping.snareBeat3,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const ohhBarInterval = openHiHatBarInterval(section, style);
|
|
204
|
-
const energy = sectionEnergy(section);
|
|
205
|
-
const fillStartBeat = getFillStartBeat(energy);
|
|
206
|
-
const percMood = percMoodCategory(style);
|
|
207
|
-
|
|
208
|
-
// The final-bar fill is shaped by the section it leads into. When no next
|
|
209
|
-
// section is given the fill is treated as within-section (from === to).
|
|
210
|
-
const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;
|
|
211
|
-
const nextEnergy = sectionEnergy(nextSection);
|
|
212
|
-
|
|
213
|
-
const euclidKick: KickPattern | undefined = opts.euclideanKick
|
|
214
|
-
? euclideanToKickPattern(
|
|
215
|
-
patternToMask(
|
|
216
|
-
euclideanRhythm(
|
|
217
|
-
opts.euclideanKick.pulses,
|
|
218
|
-
opts.euclideanKick.steps ?? 16,
|
|
219
|
-
opts.euclideanKick.rotation ?? 0,
|
|
220
|
-
),
|
|
221
|
-
),
|
|
222
|
-
)
|
|
223
|
-
: undefined;
|
|
224
|
-
|
|
225
|
-
const reuseSectionKick = (section === 'b' || section === 'chorus') && style !== 'sparse';
|
|
226
|
-
let sectionKick: KickPattern | undefined;
|
|
227
|
-
|
|
228
|
-
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
229
|
-
const barStart = bar * 4;
|
|
230
|
-
const isLastBar = bar === opts.bars - 1;
|
|
231
|
-
|
|
232
|
-
if (bar === 0 && section === 'chorus') {
|
|
233
|
-
track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
let barHasOpenHh = false;
|
|
237
|
-
let openHhBeatIndex = 3;
|
|
238
|
-
if (ohhBarInterval > 0 && bar % ohhBarInterval === ohhBarInterval - 1) {
|
|
239
|
-
openHhBeatIndex = openHiHatBeat(section, rng);
|
|
240
|
-
barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
let kick: KickPattern;
|
|
244
|
-
if (euclidKick) {
|
|
245
|
-
kick = euclidKick;
|
|
246
|
-
} else if (reuseSectionKick) {
|
|
247
|
-
sectionKick ??= getKickPattern(section, style, 0, rng);
|
|
248
|
-
kick = sectionKick;
|
|
249
|
-
} else {
|
|
250
|
-
kick = getKickPattern(section, style, bar, rng);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const inLift = isInPreChorusLift(section, bar, opts.bars, section === 'b');
|
|
254
|
-
let currentFill: FillType = 'snareRoll';
|
|
255
|
-
|
|
256
|
-
for (let beat = 0; beat < 4; beat += 1) {
|
|
257
|
-
const beatTick = barStart + beat;
|
|
258
|
-
const velocity = calculateVelocity(section, beat);
|
|
259
|
-
|
|
260
|
-
if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {
|
|
261
|
-
if (beat === fillStartBeat) {
|
|
262
|
-
currentFill = selectFillType(section, nextSection, style, nextEnergy, rng);
|
|
263
|
-
}
|
|
264
|
-
if (playMainVoices) {
|
|
265
|
-
const before = track.hits.length;
|
|
266
|
-
generateFill(track, beatTick, beat, currentFill, velocity);
|
|
267
|
-
if (track.hits.length > before) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
// Safety net: an archetype that contributes nothing on this beat would
|
|
271
|
-
// leave a silent phrase end, so fall through to the normal groove.
|
|
272
|
-
} else {
|
|
273
|
-
// fxOnly deliberately emits no main voices on the fill beats.
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const ctx: BeatCtx = {
|
|
279
|
-
track,
|
|
280
|
-
beatTick,
|
|
281
|
-
beat,
|
|
282
|
-
velocity,
|
|
283
|
-
section,
|
|
284
|
-
bpm: opts.bpm,
|
|
285
|
-
bar,
|
|
286
|
-
sectionBars: opts.bars,
|
|
287
|
-
inPrechorusLift: inLift,
|
|
288
|
-
swingAmount,
|
|
289
|
-
barHasOpenHh,
|
|
290
|
-
openHhBeat: openHhBeatIndex,
|
|
291
|
-
rng,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
if (playMainVoices) {
|
|
295
|
-
if (inLift) {
|
|
296
|
-
generatePreChorusBuildup(ctx, sec, isLastBar);
|
|
297
|
-
}
|
|
298
|
-
generateKickForBeat(ctx, sec, kick);
|
|
299
|
-
generateSnareForBeat(ctx, sec, section === 'intro' && bar === 0);
|
|
300
|
-
if (sec.useGhostNotes && !inLift) {
|
|
301
|
-
generateGhostNotesForBeat(ctx, sec);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
generateHiHatForBeat(ctx, sec);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (sec.useFootHh && shouldPlayHiHat(role)) {
|
|
308
|
-
for (let fhhBeat = 0; fhhBeat < 4; fhhBeat += 2) {
|
|
309
|
-
const fhhTick = barStart + fhhBeat;
|
|
310
|
-
const occupied = track.hits.some((h) => h.pitch === GM.FHH && h.startBeat === fhhTick);
|
|
311
|
-
if (!occupied) {
|
|
312
|
-
track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
generateAuxPercussionForBar(
|
|
318
|
-
track,
|
|
319
|
-
barStart,
|
|
320
|
-
getPercussionConfig(percMood, section),
|
|
321
|
-
role,
|
|
322
|
-
densityMult,
|
|
323
|
-
rng,
|
|
324
|
-
opts.bpm,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return track.hits;
|
|
329
|
-
}
|