@libraz/libcantus 0.9.3 → 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 +2 -2
- package/dist/analyze/index.cjs +39 -1947
- package/dist/analyze/index.cjs.map +1 -1
- package/dist/analyze/index.d.cts +197 -37
- package/dist/analyze/index.d.ts +197 -37
- 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-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
- 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-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
- package/dist/chunk-D2FAJBYL.js.map +1 -0
- package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
- 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-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
- package/dist/chunk-GQSGS2N4.js.map +1 -0
- package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
- package/dist/chunk-HKGYCQ34.js.map +1 -0
- package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
- package/dist/chunk-I24MV65U.js.map +1 -0
- package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
- 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-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
- 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-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
- package/dist/chunk-NYYWMV2C.js.map +1 -0
- package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
- 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-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
- 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-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
- 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 -647
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +301 -39
- package/dist/core/index.d.ts +301 -39
- package/dist/core/index.js +54 -16
- package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
- package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
- package/dist/generate/index.cjs +28 -4085
- package/dist/generate/index.cjs.map +1 -1
- package/dist/generate/index.d.cts +370 -62
- package/dist/generate/index.d.ts +370 -62
- package/dist/generate/index.js +23 -9
- package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
- package/dist/index-70p168VM.d.ts +192 -0
- package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
- package/dist/index-BtXT6UQ0.d.cts +192 -0
- package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
- package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
- package/dist/{index-Bg5dYMmZ.d.ts → 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-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
- package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
- package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
- package/dist/index.cjs +419 -7383
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +111 -27
- package/dist/model/index.cjs +8 -2562
- package/dist/model/index.cjs.map +1 -1
- package/dist/model/index.d.cts +433 -75
- package/dist/model/index.d.ts +433 -75
- 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 -1822
- 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 +30 -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 +24 -6
- package/dist/chunk-2ZWMSJH6.js.map +0 -1
- package/dist/chunk-4MHA432G.js.map +0 -1
- package/dist/chunk-62QZKCJQ.js.map +0 -1
- package/dist/chunk-AKLYPWIN.js.map +0 -1
- package/dist/chunk-C5XYKUVG.js.map +0 -1
- package/dist/chunk-CHMXELDS.js.map +0 -1
- package/dist/chunk-DYWFNC4A.js +0 -152
- package/dist/chunk-DYWFNC4A.js.map +0 -1
- package/dist/chunk-EH6EM37U.js.map +0 -1
- package/dist/chunk-FBQK3ZQN.js +0 -131
- package/dist/chunk-FBQK3ZQN.js.map +0 -1
- package/dist/chunk-GOAW4VYL.js.map +0 -1
- package/dist/chunk-I5R7ZBJS.js.map +0 -1
- package/dist/chunk-IT2O4XFE.js.map +0 -1
- package/dist/chunk-MIWO425M.js.map +0 -1
- package/dist/chunk-SGU7SPLK.js.map +0 -1
- package/dist/index-CGMZNCx6.d.cts +0 -140
- package/dist/index-CGMZNCx6.d.ts +0 -140
- package/dist/index-CnZCo29u.d.cts +0 -108
- package/dist/index-JnF4v9bx.d.ts +0 -108
- package/dist/styled-DZ7GjShb.d.ts +0 -282
- package/dist/styled-ZTbdaVFM.d.cts +0 -282
- package/src/analyze/arrange/index.ts +0 -21
- package/src/analyze/arrange/internal.ts +0 -127
- package/src/analyze/arrange/tension.ts +0 -199
- package/src/analyze/arrange/tracks.ts +0 -312
- package/src/analyze/detect/index.ts +0 -272
- 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 -402
- package/src/analyze/index.ts +0 -49
- package/src/analyze/timeline/index.ts +0 -451
- package/src/analyze/voice/index.ts +0 -339
- package/src/core/event-index/index.ts +0 -104
- package/src/core/index.ts +0 -61
- package/src/core/interval/index.ts +0 -81
- package/src/core/meter/index.ts +0 -302
- package/src/core/pitch/index.ts +0 -311
- package/src/core/random/index.ts +0 -76
- package/src/core/tuning/index.ts +0 -186
- package/src/core/types.ts +0 -29
- package/src/core/validation/index.ts +0 -119
- package/src/generate/bass/index.ts +0 -373
- package/src/generate/countermelody/index.ts +0 -470
- package/src/generate/drums/beat.ts +0 -266
- package/src/generate/drums/euclid.ts +0 -65
- 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 -361
- 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 -351
- package/src/generate/harmonize/index.ts +0 -451
- package/src/generate/index.ts +0 -58
- package/src/generate/motif/index.ts +0 -422
- package/src/generate/progression/index.ts +0 -362
- package/src/generate/reharmony/index.ts +0 -323
- package/src/generate/rhythm/index.ts +0 -175
- package/src/index.ts +0 -13
- package/src/model/chord.ts +0 -442
- 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 -375
- package/src/theory/chordscale/index.ts +0 -374
- package/src/theory/counterpoint/index.ts +0 -230
- package/src/theory/harmony/index.ts +0 -77
- package/src/theory/index.ts +0 -112
- package/src/theory/safety/index.ts +0 -488
- 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 -249
- 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 -216
- package/src/theory/voicing/styled.ts +0 -199
- package/tsconfig.json +0 -23
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertGenerationBudget,
|
|
3
|
-
assertInteger,
|
|
4
|
-
assertPositiveInt,
|
|
5
|
-
assertRange,
|
|
6
|
-
} from '../../core/validation/index.js';
|
|
7
|
-
import {
|
|
8
|
-
type BeatCtx,
|
|
9
|
-
generateGhostNotesForBeat,
|
|
10
|
-
generateHiHatForBeat,
|
|
11
|
-
generateKickForBeat,
|
|
12
|
-
generatePreChorusBuildup,
|
|
13
|
-
generateSnareForBeat,
|
|
14
|
-
type SectionCtx,
|
|
15
|
-
} from './beat.js';
|
|
16
|
-
import { euclideanRhythm } from './euclid.js';
|
|
17
|
-
import { type FillType, generateFill, getFillStartBeat, selectFillType } from './fills.js';
|
|
18
|
-
import {
|
|
19
|
-
footHiHatVelocity,
|
|
20
|
-
getHiHatLevel,
|
|
21
|
-
openHiHatBarInterval,
|
|
22
|
-
openHiHatBeat,
|
|
23
|
-
shouldPlayHiHat,
|
|
24
|
-
shouldUseFootHiHat,
|
|
25
|
-
shouldUseRideForSection,
|
|
26
|
-
} from './hihat.js';
|
|
27
|
-
import { type DrumHit, HitList } from './hit.js';
|
|
28
|
-
import {
|
|
29
|
-
calculateVelocity,
|
|
30
|
-
type DrumRole,
|
|
31
|
-
type Feel,
|
|
32
|
-
feelSwingAmount,
|
|
33
|
-
GM,
|
|
34
|
-
type GrooveStyle,
|
|
35
|
-
ghostMoodCategory,
|
|
36
|
-
mapDensity,
|
|
37
|
-
mapSection,
|
|
38
|
-
mapStyle,
|
|
39
|
-
type PublicSection,
|
|
40
|
-
percMoodCategory,
|
|
41
|
-
sectionDensityMultiplier,
|
|
42
|
-
sectionEnergy,
|
|
43
|
-
} from './internal.js';
|
|
44
|
-
import { getKickPattern, isInPreChorusLift, type KickPattern } from './kick.js';
|
|
45
|
-
import { generateAuxPercussionForBar, getPercussionConfig } from './percussion.js';
|
|
46
|
-
import { createRng } from './rng.js';
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* A single drum onset emitted by {@link generateDrums}.
|
|
50
|
-
*
|
|
51
|
-
* @category Composition
|
|
52
|
-
*/
|
|
53
|
-
export type { DrumHit } from './hit.js';
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Groove feel (the swing/straight rhythmic character) for {@link generateDrums}.
|
|
57
|
-
*
|
|
58
|
-
* @category Composition
|
|
59
|
-
*/
|
|
60
|
-
export type GrooveFeel = Feel;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Drum voicing role and groove style identifiers for {@link generateDrums}.
|
|
64
|
-
*
|
|
65
|
-
* @category Composition
|
|
66
|
-
*/
|
|
67
|
-
export type { DrumRole, GrooveStyle } from './internal.js';
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Public section identifiers for {@link generateDrums}.
|
|
71
|
-
*
|
|
72
|
-
* @category Composition
|
|
73
|
-
*/
|
|
74
|
-
export type Section = PublicSection;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* A Euclidean (Bjorklund) kick pattern for {@link generateDrums}.
|
|
78
|
-
*
|
|
79
|
-
* @category Composition
|
|
80
|
-
*/
|
|
81
|
-
export type EuclideanKick = {
|
|
82
|
-
/** Number of kick onsets, clamped to `[0, steps]`. */
|
|
83
|
-
pulses: number;
|
|
84
|
-
/**
|
|
85
|
-
* Total steps in the bar (1..16).
|
|
86
|
-
*
|
|
87
|
-
* @defaultValue 16
|
|
88
|
-
*/
|
|
89
|
-
steps?: number;
|
|
90
|
-
/**
|
|
91
|
-
* Steps to rotate the onsets toward later positions.
|
|
92
|
-
*
|
|
93
|
-
* @defaultValue 0
|
|
94
|
-
*/
|
|
95
|
-
rotation?: number;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Options controlling {@link generateDrums}.
|
|
100
|
-
*
|
|
101
|
-
* @category Composition
|
|
102
|
-
*/
|
|
103
|
-
export type DrumsOptions = {
|
|
104
|
-
bars: number;
|
|
105
|
-
bpm: number;
|
|
106
|
-
style: GrooveStyle;
|
|
107
|
-
section: Section;
|
|
108
|
-
density: number;
|
|
109
|
-
/**
|
|
110
|
-
* Replace the final bar with a fill.
|
|
111
|
-
*
|
|
112
|
-
* @defaultValue false
|
|
113
|
-
*/
|
|
114
|
-
fills?: boolean;
|
|
115
|
-
feel?: GrooveFeel;
|
|
116
|
-
/**
|
|
117
|
-
* Voicing role; `'fxOnly'` suppresses the main kick/snare/ghost/fill voices.
|
|
118
|
-
*
|
|
119
|
-
* @defaultValue `'full'`
|
|
120
|
-
*/
|
|
121
|
-
role?: DrumRole;
|
|
122
|
-
/**
|
|
123
|
-
* Seed for the deterministic PRNG.
|
|
124
|
-
*
|
|
125
|
-
* @defaultValue 0
|
|
126
|
-
*/
|
|
127
|
-
seed?: number;
|
|
128
|
-
/**
|
|
129
|
-
* Section the final-bar fill leads into. Shapes which fill archetype is
|
|
130
|
-
* chosen (into-chorus and out-of-intro fills differ from generic ones).
|
|
131
|
-
* Defaults to `section`, i.e. a within-section fill.
|
|
132
|
-
*/
|
|
133
|
-
nextSection?: Section;
|
|
134
|
-
/**
|
|
135
|
-
* When set, the kick follows this Euclidean rhythm instead of the
|
|
136
|
-
* style/section pattern, giving direct access to evenly spread onsets.
|
|
137
|
-
*/
|
|
138
|
-
euclideanKick?: EuclideanKick;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Generate a drum performance as a flat list of onsets.
|
|
143
|
-
*
|
|
144
|
-
* Every voice (kick, snare, ghost snares, closed/open/foot hi-hats, ride, toms,
|
|
145
|
-
* crash, and auxiliary percussion) is emitted as a {@link DrumHit} distinguished
|
|
146
|
-
* by its General MIDI pitch. Groove style selects an internal style and feel;
|
|
147
|
-
* `density` sets the backing-density level; `section` shapes kick, hi-hat, ghost,
|
|
148
|
-
* and percussion density. 16th-note hi-hats drop to 8ths at or above 150 BPM.
|
|
149
|
-
* When `fills` is true the final bar is replaced with a fill whose archetype is
|
|
150
|
-
* shaped by `nextSection`; a fill that would emit nothing on its beat falls back
|
|
151
|
-
* to the normal groove so the phrase end is never silent. `euclideanKick`
|
|
152
|
-
* overrides the kick with an evenly-spread Euclidean pattern. Output is fully
|
|
153
|
-
* determined by the options plus `seed`.
|
|
154
|
-
*
|
|
155
|
-
* @param opts Generation options.
|
|
156
|
-
* @returns Percussion onsets in bar order.
|
|
157
|
-
*
|
|
158
|
-
* @example
|
|
159
|
-
* ```ts
|
|
160
|
-
* import { generateDrums } from '@libraz/libcantus';
|
|
161
|
-
* const hits = generateDrums({ bars: 4, bpm: 120, style: 'standard', section: 'chorus', density: 0.6, fills: true });
|
|
162
|
-
* // Fully determined by the options plus seed (defaults to 0).
|
|
163
|
-
* ```
|
|
164
|
-
*
|
|
165
|
-
* @category Composition
|
|
166
|
-
*/
|
|
167
|
-
export function generateDrums(opts: DrumsOptions): DrumHit[] {
|
|
168
|
-
assertPositiveInt(opts.bars, 'drum bars');
|
|
169
|
-
assertRange(opts.bpm, Number.MIN_VALUE, 1000, 'drum bpm');
|
|
170
|
-
assertRange(opts.density, 0, 1, 'drum density');
|
|
171
|
-
assertGenerationBudget(opts.bars * 128, 'drum hits');
|
|
172
|
-
if (opts.euclideanKick) {
|
|
173
|
-
const steps = opts.euclideanKick.steps ?? 16;
|
|
174
|
-
assertInteger(steps, 'euclidean steps', 1, 16);
|
|
175
|
-
assertInteger(opts.euclideanKick.pulses, 'euclidean pulses');
|
|
176
|
-
assertInteger(opts.euclideanKick.rotation ?? 0, 'euclidean rotation');
|
|
177
|
-
}
|
|
178
|
-
const track = new HitList();
|
|
179
|
-
const rng = createRng(opts.seed ?? 0);
|
|
180
|
-
const mapping = mapStyle(opts.style);
|
|
181
|
-
const style = mapping.style;
|
|
182
|
-
const feel: Feel = opts.feel ?? mapping.feel;
|
|
183
|
-
const role: DrumRole = opts.role ?? 'full';
|
|
184
|
-
// fxOnly leaves only fx/aux voices: the main kick, snare, ghost, and fill
|
|
185
|
-
// voices are suppressed just as timekeeping hi-hats already are.
|
|
186
|
-
const playMainVoices = role !== 'fxOnly';
|
|
187
|
-
const section = mapSection(opts.section);
|
|
188
|
-
const backingDensity = mapDensity(opts.density);
|
|
189
|
-
const swingAmount = feelSwingAmount(feel);
|
|
190
|
-
|
|
191
|
-
let densityMult = sectionDensityMultiplier(section);
|
|
192
|
-
if (backingDensity === 'thin') {
|
|
193
|
-
densityMult *= 0.75;
|
|
194
|
-
} else if (backingDensity === 'thick') {
|
|
195
|
-
densityMult *= 1.15;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const sec: SectionCtx = {
|
|
199
|
-
style,
|
|
200
|
-
feel,
|
|
201
|
-
densityMult,
|
|
202
|
-
backingDensity,
|
|
203
|
-
hhLevel: getHiHatLevel(section, style, backingDensity, opts.bpm, rng),
|
|
204
|
-
useGhostNotes:
|
|
205
|
-
(section === 'b' || section === 'chorus' || section === 'bridge') && style !== 'sparse',
|
|
206
|
-
ghostBoost: mapping.ghostBoost,
|
|
207
|
-
useRide: shouldUseRideForSection(section, style),
|
|
208
|
-
useFootHh: shouldUseFootHiHat(section, role),
|
|
209
|
-
role,
|
|
210
|
-
ghostMood: ghostMoodCategory(style),
|
|
211
|
-
snareBeat3: mapping.snareBeat3,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const ohhBarInterval = openHiHatBarInterval(section, style);
|
|
215
|
-
const energy = sectionEnergy(section);
|
|
216
|
-
const fillStartBeat = getFillStartBeat(energy);
|
|
217
|
-
const percMood = percMoodCategory(style);
|
|
218
|
-
|
|
219
|
-
// The final-bar fill is shaped by the section it leads into. When no next
|
|
220
|
-
// section is given the fill is treated as within-section (from === to).
|
|
221
|
-
const nextSection = opts.nextSection ? mapSection(opts.nextSection) : section;
|
|
222
|
-
const nextEnergy = sectionEnergy(nextSection);
|
|
223
|
-
|
|
224
|
-
const euclidSteps = opts.euclideanKick?.steps ?? 16;
|
|
225
|
-
const euclidKick = opts.euclideanKick
|
|
226
|
-
? euclideanRhythm(opts.euclideanKick.pulses, euclidSteps, opts.euclideanKick.rotation ?? 0)
|
|
227
|
-
: undefined;
|
|
228
|
-
|
|
229
|
-
const reuseSectionKick = (section === 'b' || section === 'chorus') && style !== 'sparse';
|
|
230
|
-
let sectionKick: KickPattern | undefined;
|
|
231
|
-
|
|
232
|
-
for (let bar = 0; bar < opts.bars; bar += 1) {
|
|
233
|
-
const barStart = bar * 4;
|
|
234
|
-
const isLastBar = bar === opts.bars - 1;
|
|
235
|
-
|
|
236
|
-
if (bar === 0 && section === 'chorus') {
|
|
237
|
-
track.add(GM.CRASH, barStart, 0.5, 100 * densityMult);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
let barHasOpenHh = false;
|
|
241
|
-
let openHhBeatIndex = 3;
|
|
242
|
-
if (ohhBarInterval > 0 && bar % ohhBarInterval === ohhBarInterval - 1) {
|
|
243
|
-
openHhBeatIndex = openHiHatBeat(section, rng);
|
|
244
|
-
barHasOpenHh = !track.hasCrashNear(barStart + openHhBeatIndex);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
let kick: KickPattern | undefined;
|
|
248
|
-
if (!euclidKick && reuseSectionKick) {
|
|
249
|
-
sectionKick ??= getKickPattern(section, style, 0, rng);
|
|
250
|
-
kick = sectionKick;
|
|
251
|
-
} else if (!euclidKick) {
|
|
252
|
-
kick = getKickPattern(section, style, bar, rng);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const inLift = isInPreChorusLift(section, bar, opts.bars, section === 'b');
|
|
256
|
-
let currentFill: FillType = 'snareRoll';
|
|
257
|
-
|
|
258
|
-
for (let beat = 0; beat < 4; beat += 1) {
|
|
259
|
-
const beatTick = barStart + beat;
|
|
260
|
-
const velocity = calculateVelocity(section, beat);
|
|
261
|
-
|
|
262
|
-
if (opts.fills && isLastBar && !inLift && beat >= fillStartBeat) {
|
|
263
|
-
if (beat === fillStartBeat) {
|
|
264
|
-
currentFill = selectFillType(section, nextSection, style, nextEnergy, rng);
|
|
265
|
-
}
|
|
266
|
-
if (playMainVoices) {
|
|
267
|
-
const before = track.hits.length;
|
|
268
|
-
generateFill(track, beatTick, beat, currentFill, velocity);
|
|
269
|
-
if (track.hits.length > before) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
// Safety net: an archetype that contributes nothing on this beat would
|
|
273
|
-
// leave a silent phrase end, so fall through to the normal groove.
|
|
274
|
-
} else {
|
|
275
|
-
// fxOnly deliberately emits no main voices on the fill beats.
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const ctx: BeatCtx = {
|
|
281
|
-
track,
|
|
282
|
-
beatTick,
|
|
283
|
-
beat,
|
|
284
|
-
velocity,
|
|
285
|
-
section,
|
|
286
|
-
bpm: opts.bpm,
|
|
287
|
-
bar,
|
|
288
|
-
sectionBars: opts.bars,
|
|
289
|
-
inPrechorusLift: inLift,
|
|
290
|
-
swingAmount,
|
|
291
|
-
barHasOpenHh,
|
|
292
|
-
openHhBeat: openHhBeatIndex,
|
|
293
|
-
rng,
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
if (playMainVoices) {
|
|
297
|
-
if (inLift) {
|
|
298
|
-
generatePreChorusBuildup(ctx, sec, isLastBar);
|
|
299
|
-
}
|
|
300
|
-
if (euclidKick && !inLift) {
|
|
301
|
-
const stepLength = 4 / euclidSteps;
|
|
302
|
-
for (let step = 0; step < euclidKick.length; step += 1) {
|
|
303
|
-
const onset = step * stepLength;
|
|
304
|
-
if (euclidKick[step] && Math.floor(onset) === beat) {
|
|
305
|
-
const wholeBeat = Math.abs(onset - Math.round(onset)) < 1e-9;
|
|
306
|
-
track.add(
|
|
307
|
-
GM.BD,
|
|
308
|
-
barStart + onset,
|
|
309
|
-
Math.min(0.5, stepLength),
|
|
310
|
-
velocity * (wholeBeat ? 1 : 0.85),
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
} else if (kick) {
|
|
315
|
-
generateKickForBeat(ctx, sec, kick);
|
|
316
|
-
}
|
|
317
|
-
generateSnareForBeat(ctx, sec, section === 'intro' && bar === 0);
|
|
318
|
-
if (sec.useGhostNotes && !inLift) {
|
|
319
|
-
generateGhostNotesForBeat(ctx, sec);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
generateHiHatForBeat(ctx, sec);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (sec.useFootHh && shouldPlayHiHat(role)) {
|
|
326
|
-
for (let fhhBeat = 0; fhhBeat < 4; fhhBeat += 2) {
|
|
327
|
-
const fhhTick = barStart + fhhBeat;
|
|
328
|
-
const occupied = track.hits.some((h) => h.pitch === GM.FHH && h.startBeat === fhhTick);
|
|
329
|
-
if (!occupied) {
|
|
330
|
-
track.add(GM.FHH, fhhTick, 0.5, footHiHatVelocity(rng));
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
let percussionConfig = getPercussionConfig(percMood, section);
|
|
336
|
-
if (
|
|
337
|
-
role === 'fxOnly' &&
|
|
338
|
-
!percussionConfig.tambourine &&
|
|
339
|
-
!percussionConfig.shaker &&
|
|
340
|
-
!percussionConfig.handclap
|
|
341
|
-
) {
|
|
342
|
-
percussionConfig = {
|
|
343
|
-
tambourine: false,
|
|
344
|
-
shaker: true,
|
|
345
|
-
handclap: false,
|
|
346
|
-
shaker16th: false,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
generateAuxPercussionForBar(
|
|
350
|
-
track,
|
|
351
|
-
barStart,
|
|
352
|
-
percussionConfig,
|
|
353
|
-
role,
|
|
354
|
-
densityMult,
|
|
355
|
-
rng,
|
|
356
|
-
opts.bpm,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return track.hits;
|
|
361
|
-
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
/** General MIDI percussion note numbers used across the drum engine. */
|
|
2
|
-
export const GM = {
|
|
3
|
-
BD: 36,
|
|
4
|
-
SD: 38,
|
|
5
|
-
SIDESTICK: 37,
|
|
6
|
-
HANDCLAP: 39,
|
|
7
|
-
CHH: 42,
|
|
8
|
-
FHH: 44,
|
|
9
|
-
OHH: 46,
|
|
10
|
-
CRASH: 49,
|
|
11
|
-
RIDE: 51,
|
|
12
|
-
TAMBOURINE: 54,
|
|
13
|
-
TOM_H: 50,
|
|
14
|
-
TOM_M: 47,
|
|
15
|
-
TOM_L: 45,
|
|
16
|
-
SHAKER: 82,
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
|
-
/** One beat and one sixteenth expressed as fractions of a beat. */
|
|
20
|
-
export const EIGHTH = 0.5;
|
|
21
|
-
export const SIXTEENTH = 0.25;
|
|
22
|
-
|
|
23
|
-
/** Internal drum style selected by the public groove style. */
|
|
24
|
-
export type DrumStyle =
|
|
25
|
-
| 'sparse'
|
|
26
|
-
| 'standard'
|
|
27
|
-
| 'fourOnFloor'
|
|
28
|
-
| 'upbeat'
|
|
29
|
-
| 'rock'
|
|
30
|
-
| 'synth'
|
|
31
|
-
| 'trap'
|
|
32
|
-
| 'latin';
|
|
33
|
-
|
|
34
|
-
/** Groove feel controlling off-beat swing. */
|
|
35
|
-
export type Feel = 'straight' | 'swing' | 'shuffle';
|
|
36
|
-
|
|
37
|
-
/** Reduced section set the engine reasons about. */
|
|
38
|
-
export type SectionType = 'intro' | 'a' | 'b' | 'chorus' | 'bridge' | 'outro';
|
|
39
|
-
|
|
40
|
-
/** Backing density bucket derived from the public density knob. */
|
|
41
|
-
export type BackingDensity = 'thin' | 'normal' | 'thick';
|
|
42
|
-
|
|
43
|
-
/** Section energy level, used to shape fills. */
|
|
44
|
-
export type SectionEnergy = 'low' | 'medium' | 'high' | 'peak';
|
|
45
|
-
|
|
46
|
-
/** Ghost-note mood category for the density table. */
|
|
47
|
-
export enum MoodCategory {
|
|
48
|
-
Calm = 0,
|
|
49
|
-
Standard = 1,
|
|
50
|
-
Energetic = 2,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Percussion mood category for the activation table. */
|
|
54
|
-
export enum PercMoodCategory {
|
|
55
|
-
Calm = 0,
|
|
56
|
-
Standard = 1,
|
|
57
|
-
Energetic = 2,
|
|
58
|
-
Idol = 3,
|
|
59
|
-
RockDark = 4,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Public groove style identifiers. */
|
|
63
|
-
export type GrooveStyle =
|
|
64
|
-
| 'standard'
|
|
65
|
-
| 'funk'
|
|
66
|
-
| 'shuffle'
|
|
67
|
-
| 'bossa'
|
|
68
|
-
| 'trap'
|
|
69
|
-
| 'halftime'
|
|
70
|
-
| 'breakbeat'
|
|
71
|
-
| 'house'
|
|
72
|
-
| 'synthpop';
|
|
73
|
-
|
|
74
|
-
/** Public role identifiers gating which voices are present. */
|
|
75
|
-
export type DrumRole = 'full' | 'ambient' | 'minimal' | 'fxOnly';
|
|
76
|
-
|
|
77
|
-
/** Public section identifiers. */
|
|
78
|
-
export type PublicSection = 'intro' | 'verse' | 'prechorus' | 'chorus' | 'bridge' | 'outro';
|
|
79
|
-
|
|
80
|
-
/** Resolved internal parameters for a public groove style. */
|
|
81
|
-
export type StyleMapping = {
|
|
82
|
-
style: DrumStyle;
|
|
83
|
-
feel: Feel;
|
|
84
|
-
ghostBoost: boolean;
|
|
85
|
-
snareBeat3: boolean;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const STYLE_MAP: Record<GrooveStyle, StyleMapping> = {
|
|
89
|
-
// Standard pop pulse: kick on beats 1 and 3, backbeat snare on 2 and 4.
|
|
90
|
-
standard: { style: 'standard', feel: 'straight', ghostBoost: false, snareBeat3: false },
|
|
91
|
-
funk: { style: 'upbeat', feel: 'straight', ghostBoost: true, snareBeat3: false },
|
|
92
|
-
shuffle: { style: 'standard', feel: 'shuffle', ghostBoost: false, snareBeat3: false },
|
|
93
|
-
bossa: { style: 'latin', feel: 'straight', ghostBoost: false, snareBeat3: false },
|
|
94
|
-
trap: { style: 'trap', feel: 'straight', ghostBoost: false, snareBeat3: true },
|
|
95
|
-
halftime: { style: 'sparse', feel: 'straight', ghostBoost: false, snareBeat3: true },
|
|
96
|
-
breakbeat: { style: 'rock', feel: 'straight', ghostBoost: false, snareBeat3: false },
|
|
97
|
-
// Four-on-the-floor dance kick with idol-style auxiliary percussion.
|
|
98
|
-
house: { style: 'fourOnFloor', feel: 'straight', ghostBoost: false, snareBeat3: false },
|
|
99
|
-
// Synth-pop pulse with denser 16th hi-hats and busy kick syncopation.
|
|
100
|
-
synthpop: { style: 'synth', feel: 'straight', ghostBoost: true, snareBeat3: false },
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/** Resolve the internal style/feel parameters for a public groove style. */
|
|
104
|
-
export function mapStyle(style: GrooveStyle): StyleMapping {
|
|
105
|
-
return STYLE_MAP[style];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Map a public section to the internal section type. */
|
|
109
|
-
export function mapSection(section: PublicSection): SectionType {
|
|
110
|
-
switch (section) {
|
|
111
|
-
case 'intro':
|
|
112
|
-
return 'intro';
|
|
113
|
-
case 'verse':
|
|
114
|
-
return 'a';
|
|
115
|
-
case 'prechorus':
|
|
116
|
-
return 'b';
|
|
117
|
-
case 'chorus':
|
|
118
|
-
return 'chorus';
|
|
119
|
-
case 'bridge':
|
|
120
|
-
return 'bridge';
|
|
121
|
-
case 'outro':
|
|
122
|
-
return 'outro';
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Bucket the 0..1 density knob into a backing-density level. */
|
|
127
|
-
export function mapDensity(density: number): BackingDensity {
|
|
128
|
-
if (density < 0.34) {
|
|
129
|
-
return 'thin';
|
|
130
|
-
}
|
|
131
|
-
if (density < 0.67) {
|
|
132
|
-
return 'normal';
|
|
133
|
-
}
|
|
134
|
-
return 'thick';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Swing amount (0..1) implied by a groove feel. */
|
|
138
|
-
export function feelSwingAmount(feel: Feel): number {
|
|
139
|
-
if (feel === 'swing') {
|
|
140
|
-
return 0.33;
|
|
141
|
-
}
|
|
142
|
-
if (feel === 'shuffle') {
|
|
143
|
-
return 0.5;
|
|
144
|
-
}
|
|
145
|
-
return 0;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Ghost-note mood category derived from the internal style. */
|
|
149
|
-
export function ghostMoodCategory(style: DrumStyle): MoodCategory {
|
|
150
|
-
if (style === 'sparse') {
|
|
151
|
-
return MoodCategory.Calm;
|
|
152
|
-
}
|
|
153
|
-
if (style === 'trap' || style === 'upbeat') {
|
|
154
|
-
return MoodCategory.Energetic;
|
|
155
|
-
}
|
|
156
|
-
return MoodCategory.Standard;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Percussion mood category derived from the internal style. */
|
|
160
|
-
export function percMoodCategory(style: DrumStyle): PercMoodCategory {
|
|
161
|
-
switch (style) {
|
|
162
|
-
case 'sparse':
|
|
163
|
-
return PercMoodCategory.Calm;
|
|
164
|
-
case 'upbeat':
|
|
165
|
-
case 'latin':
|
|
166
|
-
return PercMoodCategory.Energetic;
|
|
167
|
-
case 'fourOnFloor':
|
|
168
|
-
return PercMoodCategory.Idol;
|
|
169
|
-
case 'trap':
|
|
170
|
-
case 'rock':
|
|
171
|
-
return PercMoodCategory.RockDark;
|
|
172
|
-
default:
|
|
173
|
-
return PercMoodCategory.Standard;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Zero-based section index shared by the ghost and percussion tables. */
|
|
178
|
-
export function sectionIndex(section: SectionType): number {
|
|
179
|
-
switch (section) {
|
|
180
|
-
case 'intro':
|
|
181
|
-
return 0;
|
|
182
|
-
case 'a':
|
|
183
|
-
return 1;
|
|
184
|
-
case 'b':
|
|
185
|
-
return 2;
|
|
186
|
-
case 'chorus':
|
|
187
|
-
return 3;
|
|
188
|
-
case 'bridge':
|
|
189
|
-
return 4;
|
|
190
|
-
case 'outro':
|
|
191
|
-
return 6;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Section energy used to size fills. */
|
|
196
|
-
export function sectionEnergy(section: SectionType): SectionEnergy {
|
|
197
|
-
switch (section) {
|
|
198
|
-
case 'intro':
|
|
199
|
-
case 'outro':
|
|
200
|
-
return 'low';
|
|
201
|
-
case 'a':
|
|
202
|
-
case 'bridge':
|
|
203
|
-
return 'medium';
|
|
204
|
-
case 'b':
|
|
205
|
-
return 'high';
|
|
206
|
-
case 'chorus':
|
|
207
|
-
return 'peak';
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Section velocity multiplier applied to the base drum velocity. */
|
|
212
|
-
export function sectionVelocityMultiplier(section: SectionType): number {
|
|
213
|
-
switch (section) {
|
|
214
|
-
case 'intro':
|
|
215
|
-
case 'outro':
|
|
216
|
-
return 0.8;
|
|
217
|
-
case 'a':
|
|
218
|
-
return 0.9;
|
|
219
|
-
case 'b':
|
|
220
|
-
return 1.0;
|
|
221
|
-
case 'chorus':
|
|
222
|
-
return 1.1;
|
|
223
|
-
case 'bridge':
|
|
224
|
-
return 0.85;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Section density multiplier before backing-density scaling. */
|
|
229
|
-
export function sectionDensityMultiplier(section: SectionType): number {
|
|
230
|
-
switch (section) {
|
|
231
|
-
case 'intro':
|
|
232
|
-
return 0.5;
|
|
233
|
-
case 'a':
|
|
234
|
-
return 0.7;
|
|
235
|
-
case 'b':
|
|
236
|
-
return 0.85;
|
|
237
|
-
case 'chorus':
|
|
238
|
-
return 1.0;
|
|
239
|
-
case 'bridge':
|
|
240
|
-
return 0.6;
|
|
241
|
-
case 'outro':
|
|
242
|
-
return 0.6;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Base drum velocity for a section beat (downbeats accented). */
|
|
247
|
-
export function calculateVelocity(section: SectionType, beat: number): number {
|
|
248
|
-
const base = 80;
|
|
249
|
-
const beatAdj = beat === 0 ? 10 : beat === 2 ? 5 : 0;
|
|
250
|
-
const velocity = (base + beatAdj) * sectionVelocityMultiplier(section);
|
|
251
|
-
return clampVel(velocity);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/** Round and clamp a velocity into the valid MIDI range. */
|
|
255
|
-
export function clampVel(velocity: number): number {
|
|
256
|
-
return Math.max(1, Math.min(127, Math.round(velocity)));
|
|
257
|
-
}
|