@libraz/libcantus 0.9.3 → 0.9.5

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 (201) hide show
  1. package/README.md +29 -20
  2. package/README_ja.md +268 -0
  3. package/dist/analyze/index.cjs +39 -1947
  4. package/dist/analyze/index.cjs.map +1 -1
  5. package/dist/analyze/index.d.cts +161 -91
  6. package/dist/analyze/index.d.ts +161 -91
  7. package/dist/analyze/index.js +19 -8
  8. package/dist/chunk-2A2QMA74.cjs +261 -0
  9. package/dist/chunk-2A2QMA74.cjs.map +1 -0
  10. package/dist/chunk-2PTFGVD7.js +261 -0
  11. package/dist/chunk-2PTFGVD7.js.map +1 -0
  12. package/dist/chunk-357ZDPVU.cjs +162 -0
  13. package/dist/chunk-357ZDPVU.cjs.map +1 -0
  14. package/dist/chunk-3KX76RLG.js +1548 -0
  15. package/dist/chunk-3KX76RLG.js.map +1 -0
  16. package/dist/chunk-4VZ5MZAM.cjs +889 -0
  17. package/dist/chunk-4VZ5MZAM.cjs.map +1 -0
  18. package/dist/{chunk-C5XYKUVG.js → chunk-7EONYVI5.js} +471 -171
  19. package/dist/chunk-7EONYVI5.js.map +1 -0
  20. package/dist/{chunk-IT2O4XFE.js → chunk-7JEP3MFY.js} +42 -34
  21. package/dist/chunk-7JEP3MFY.js.map +1 -0
  22. package/dist/{chunk-4MHA432G.js → chunk-AID75RN3.js} +572 -250
  23. package/dist/chunk-AID75RN3.js.map +1 -0
  24. package/dist/chunk-BSLTLGM3.cjs +497 -0
  25. package/dist/chunk-BSLTLGM3.cjs.map +1 -0
  26. package/dist/chunk-CU6VG2YM.cjs +109 -0
  27. package/dist/chunk-CU6VG2YM.cjs.map +1 -0
  28. package/dist/{chunk-EH6EM37U.js → chunk-DZMHIGI4.js} +81 -59
  29. package/dist/chunk-DZMHIGI4.js.map +1 -0
  30. package/dist/{chunk-AKLYPWIN.js → chunk-EDOTEABK.js} +45 -19
  31. package/dist/chunk-EDOTEABK.js.map +1 -0
  32. package/dist/{chunk-2ZWMSJH6.js → chunk-HN2OEUN2.js} +7 -4
  33. package/dist/chunk-HN2OEUN2.js.map +1 -0
  34. package/dist/{chunk-MIWO425M.js → chunk-KBTTEX7Q.js} +128 -43
  35. package/dist/chunk-KBTTEX7Q.js.map +1 -0
  36. package/dist/chunk-KQN2ZYND.cjs +724 -0
  37. package/dist/chunk-KQN2ZYND.cjs.map +1 -0
  38. package/dist/chunk-MPTVJIVG.cjs +3176 -0
  39. package/dist/chunk-MPTVJIVG.cjs.map +1 -0
  40. package/dist/chunk-MTMZTMWM.cjs +53 -0
  41. package/dist/chunk-MTMZTMWM.cjs.map +1 -0
  42. package/dist/{chunk-CHMXELDS.js → chunk-NNB67XA2.js} +372 -197
  43. package/dist/chunk-NNB67XA2.js.map +1 -0
  44. package/dist/chunk-OL3IHHSJ.cjs +1204 -0
  45. package/dist/chunk-OL3IHHSJ.cjs.map +1 -0
  46. package/dist/chunk-PI3UXRWK.cjs +528 -0
  47. package/dist/chunk-PI3UXRWK.cjs.map +1 -0
  48. package/dist/chunk-R2VVFAIN.js +528 -0
  49. package/dist/chunk-R2VVFAIN.js.map +1 -0
  50. package/dist/chunk-T64C7ZD7.cjs +1 -0
  51. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  52. package/dist/chunk-WFDXOLUB.cjs +337 -0
  53. package/dist/chunk-WFDXOLUB.cjs.map +1 -0
  54. package/dist/chunk-WTAN4ACG.cjs +1548 -0
  55. package/dist/chunk-WTAN4ACG.cjs.map +1 -0
  56. package/dist/chunk-YFJXS3AU.cjs +33 -0
  57. package/dist/chunk-YFJXS3AU.cjs.map +1 -0
  58. package/dist/{chunk-GOAW4VYL.js → chunk-YUE6TJ4M.js} +315 -145
  59. package/dist/chunk-YUE6TJ4M.js.map +1 -0
  60. package/dist/{chunk-62QZKCJQ.js → chunk-ZGLJM4A4.js} +10 -6
  61. package/dist/chunk-ZGLJM4A4.js.map +1 -0
  62. package/dist/core/index.cjs +140 -647
  63. package/dist/core/index.cjs.map +1 -1
  64. package/dist/core/index.d.cts +313 -41
  65. package/dist/core/index.d.ts +313 -41
  66. package/dist/core/index.js +64 -16
  67. package/dist/{function-DVH4Eusr.d.ts → function-CxixTB02.d.cts} +122 -10
  68. package/dist/{function-DYt4iKsn.d.cts → function-DTCt675z.d.ts} +122 -10
  69. package/dist/generate/index.cjs +28 -4085
  70. package/dist/generate/index.cjs.map +1 -1
  71. package/dist/generate/index.d.cts +374 -66
  72. package/dist/generate/index.d.ts +374 -66
  73. package/dist/generate/index.js +23 -9
  74. package/dist/{index-Jc0Ryi79.d.ts → index-B2Ulxxpo.d.cts} +21 -5
  75. package/dist/{index-CpOsP1y9.d.cts → index-BkYJ0xMq.d.cts} +18 -17
  76. package/dist/{index-CxqBQtH1.d.cts → index-BuaB-ypt.d.ts} +21 -5
  77. package/dist/index-CAN7v-kw.d.ts +199 -0
  78. package/dist/{index-Bt4tOUD9.d.ts → index-COw1Ky9g.d.ts} +93 -8
  79. package/dist/{index-Dikst44v.d.cts → index-Cfx8xgH0.d.ts} +14 -11
  80. package/dist/{index-DxvARIP2.d.ts → index-DRtIhlJj.d.ts} +18 -17
  81. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  82. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  83. package/dist/index-alrRninU.d.cts +199 -0
  84. package/dist/{index-BNlGP86A.d.cts → index-oTQbYYIs.d.cts} +93 -8
  85. package/dist/index-tR9jTn26.d.cts +275 -0
  86. package/dist/index-tR9jTn26.d.ts +275 -0
  87. package/dist/{index-Bg5dYMmZ.d.ts → index-x2vcsduE.d.cts} +14 -11
  88. package/dist/index.cjs +429 -7383
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +15 -15
  91. package/dist/index.d.ts +15 -15
  92. package/dist/index.js +121 -27
  93. package/dist/model/index.cjs +8 -2562
  94. package/dist/model/index.cjs.map +1 -1
  95. package/dist/model/index.d.cts +471 -81
  96. package/dist/model/index.d.ts +471 -81
  97. package/dist/model/index.js +6 -7
  98. package/dist/styled-Bl0ZK28h.d.ts +543 -0
  99. package/dist/styled-Crv6SA4S.d.cts +543 -0
  100. package/dist/theory/index.cjs +172 -1822
  101. package/dist/theory/index.cjs.map +1 -1
  102. package/dist/theory/index.d.cts +151 -233
  103. package/dist/theory/index.d.ts +151 -233
  104. package/dist/theory/index.js +30 -7
  105. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  106. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  107. package/package.json +34 -13
  108. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  109. package/dist/chunk-4MHA432G.js.map +0 -1
  110. package/dist/chunk-62QZKCJQ.js.map +0 -1
  111. package/dist/chunk-AKLYPWIN.js.map +0 -1
  112. package/dist/chunk-C5XYKUVG.js.map +0 -1
  113. package/dist/chunk-CHMXELDS.js.map +0 -1
  114. package/dist/chunk-DYWFNC4A.js +0 -152
  115. package/dist/chunk-DYWFNC4A.js.map +0 -1
  116. package/dist/chunk-EH6EM37U.js.map +0 -1
  117. package/dist/chunk-FBQK3ZQN.js +0 -131
  118. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  119. package/dist/chunk-GOAW4VYL.js.map +0 -1
  120. package/dist/chunk-I5R7ZBJS.js +0 -202
  121. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  122. package/dist/chunk-IT2O4XFE.js.map +0 -1
  123. package/dist/chunk-MIWO425M.js.map +0 -1
  124. package/dist/chunk-SGU7SPLK.js +0 -924
  125. package/dist/chunk-SGU7SPLK.js.map +0 -1
  126. package/dist/index-CGMZNCx6.d.cts +0 -140
  127. package/dist/index-CGMZNCx6.d.ts +0 -140
  128. package/dist/index-CnZCo29u.d.cts +0 -108
  129. package/dist/index-JnF4v9bx.d.ts +0 -108
  130. package/dist/styled-DZ7GjShb.d.ts +0 -282
  131. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  132. package/src/analyze/arrange/index.ts +0 -21
  133. package/src/analyze/arrange/internal.ts +0 -127
  134. package/src/analyze/arrange/tension.ts +0 -199
  135. package/src/analyze/arrange/tracks.ts +0 -312
  136. package/src/analyze/detect/index.ts +0 -272
  137. package/src/analyze/functional/borrowed.ts +0 -84
  138. package/src/analyze/functional/cadence.ts +0 -66
  139. package/src/analyze/functional/function.ts +0 -184
  140. package/src/analyze/functional/index.ts +0 -23
  141. package/src/analyze/functional/internal.ts +0 -29
  142. package/src/analyze/functional/roman.ts +0 -402
  143. package/src/analyze/index.ts +0 -49
  144. package/src/analyze/timeline/index.ts +0 -451
  145. package/src/analyze/voice/index.ts +0 -339
  146. package/src/core/event-index/index.ts +0 -104
  147. package/src/core/index.ts +0 -61
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -302
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -76
  152. package/src/core/tuning/index.ts +0 -186
  153. package/src/core/types.ts +0 -29
  154. package/src/core/validation/index.ts +0 -119
  155. package/src/generate/bass/index.ts +0 -373
  156. package/src/generate/countermelody/index.ts +0 -470
  157. package/src/generate/drums/beat.ts +0 -266
  158. package/src/generate/drums/euclid.ts +0 -65
  159. package/src/generate/drums/fills.ts +0 -356
  160. package/src/generate/drums/ghost.ts +0 -119
  161. package/src/generate/drums/hihat.ts +0 -285
  162. package/src/generate/drums/hit.ts +0 -31
  163. package/src/generate/drums/index.ts +0 -361
  164. package/src/generate/drums/internal.ts +0 -257
  165. package/src/generate/drums/kick.ts +0 -160
  166. package/src/generate/drums/percussion.ts +0 -121
  167. package/src/generate/drums/rng.ts +0 -7
  168. package/src/generate/drums/swing.ts +0 -71
  169. package/src/generate/groove/index.ts +0 -351
  170. package/src/generate/harmonize/index.ts +0 -451
  171. package/src/generate/index.ts +0 -58
  172. package/src/generate/motif/index.ts +0 -422
  173. package/src/generate/progression/index.ts +0 -362
  174. package/src/generate/reharmony/index.ts +0 -323
  175. package/src/generate/rhythm/index.ts +0 -175
  176. package/src/index.ts +0 -13
  177. package/src/model/chord.ts +0 -442
  178. package/src/model/index.ts +0 -15
  179. package/src/model/interval.ts +0 -82
  180. package/src/model/key.ts +0 -266
  181. package/src/model/note.ts +0 -176
  182. package/src/model/progression.ts +0 -161
  183. package/src/model/shared.ts +0 -12
  184. package/src/theory/chord/index.ts +0 -375
  185. package/src/theory/chordscale/index.ts +0 -374
  186. package/src/theory/counterpoint/index.ts +0 -230
  187. package/src/theory/harmony/index.ts +0 -77
  188. package/src/theory/index.ts +0 -112
  189. package/src/theory/safety/index.ts +0 -488
  190. package/src/theory/scale/degrees.ts +0 -123
  191. package/src/theory/scale/index.ts +0 -28
  192. package/src/theory/scale/key.ts +0 -56
  193. package/src/theory/scale/masks.ts +0 -153
  194. package/src/theory/spelling/index.ts +0 -249
  195. package/src/theory/symbol/index.ts +0 -268
  196. package/src/theory/voicing/index.ts +0 -5
  197. package/src/theory/voicing/internal.ts +0 -168
  198. package/src/theory/voicing/leading.ts +0 -112
  199. package/src/theory/voicing/satb.ts +0 -216
  200. package/src/theory/voicing/styled.ts +0 -199
  201. 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
- }