@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.
Files changed (198) hide show
  1. package/README.md +2 -2
  2. package/dist/analyze/index.cjs +39 -1947
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +197 -37
  5. package/dist/analyze/index.d.ts +197 -37
  6. package/dist/analyze/index.js +19 -8
  7. package/dist/chunk-36N73KZ3.cjs +246 -0
  8. package/dist/chunk-36N73KZ3.cjs.map +1 -0
  9. package/dist/chunk-4HLLZPXT.cjs +3077 -0
  10. package/dist/chunk-4HLLZPXT.cjs.map +1 -0
  11. package/dist/chunk-7MGZ7D62.cjs +498 -0
  12. package/dist/chunk-7MGZ7D62.cjs.map +1 -0
  13. package/dist/{chunk-4MHA432G.js → chunk-7NKDPF74.js} +403 -180
  14. package/dist/chunk-7NKDPF74.js.map +1 -0
  15. package/dist/chunk-CAN76NCU.cjs +679 -0
  16. package/dist/chunk-CAN76NCU.cjs.map +1 -0
  17. package/dist/{chunk-MIWO425M.js → chunk-D2FAJBYL.js} +101 -34
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-SGU7SPLK.js → chunk-EHVEBWM5.js} +1056 -531
  20. package/dist/chunk-EHVEBWM5.js.map +1 -0
  21. package/dist/chunk-FCANXBAT.cjs +30 -0
  22. package/dist/chunk-FCANXBAT.cjs.map +1 -0
  23. package/dist/chunk-FNDWXRRI.cjs +479 -0
  24. package/dist/chunk-FNDWXRRI.cjs.map +1 -0
  25. package/dist/{chunk-IT2O4XFE.js → chunk-GQSGS2N4.js} +43 -24
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/{chunk-62QZKCJQ.js → chunk-HKGYCQ34.js} +10 -6
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/{chunk-AKLYPWIN.js → chunk-I24MV65U.js} +29 -6
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-CHMXELDS.js → chunk-KMHOICIY.js} +311 -181
  32. package/dist/chunk-KMHOICIY.js.map +1 -0
  33. package/dist/chunk-LW53AHQU.cjs +1117 -0
  34. package/dist/chunk-LW53AHQU.cjs.map +1 -0
  35. package/dist/{chunk-2ZWMSJH6.js → chunk-NKIBQ6PT.js} +2 -2
  36. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  37. package/dist/chunk-NP4NA2FB.cjs +106 -0
  38. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  39. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  40. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  41. package/dist/{chunk-EH6EM37U.js → chunk-NYYWMV2C.js} +61 -49
  42. package/dist/chunk-NYYWMV2C.js.map +1 -0
  43. package/dist/{chunk-GOAW4VYL.js → chunk-Q2XUP227.js} +243 -117
  44. package/dist/chunk-Q2XUP227.js.map +1 -0
  45. package/dist/chunk-Q6OP4U43.cjs +327 -0
  46. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  47. package/dist/chunk-RC6GBMCD.cjs +845 -0
  48. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  49. package/dist/{chunk-C5XYKUVG.js → chunk-RK35RIN7.js} +370 -157
  50. package/dist/chunk-RK35RIN7.js.map +1 -0
  51. package/dist/chunk-RUMU3UA6.cjs +53 -0
  52. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  53. package/dist/chunk-T64C7ZD7.cjs +1 -0
  54. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  55. package/dist/chunk-XEBG3G53.js +498 -0
  56. package/dist/chunk-XEBG3G53.js.map +1 -0
  57. package/dist/{chunk-I5R7ZBJS.js → chunk-YLEMGEFH.js} +103 -59
  58. package/dist/chunk-YLEMGEFH.js.map +1 -0
  59. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  60. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  61. package/dist/core/index.cjs +130 -647
  62. package/dist/core/index.cjs.map +1 -1
  63. package/dist/core/index.d.cts +301 -39
  64. package/dist/core/index.d.ts +301 -39
  65. package/dist/core/index.js +54 -16
  66. package/dist/{function-DYt4iKsn.d.cts → function-BtiZb9i9.d.ts} +28 -7
  67. package/dist/{function-DVH4Eusr.d.ts → function-DNugAOrt.d.cts} +28 -7
  68. package/dist/generate/index.cjs +28 -4085
  69. package/dist/generate/index.cjs.map +1 -1
  70. package/dist/generate/index.d.cts +370 -62
  71. package/dist/generate/index.d.ts +370 -62
  72. package/dist/generate/index.js +23 -9
  73. package/dist/{index-CxqBQtH1.d.cts → index-6VR8-kit.d.ts} +19 -4
  74. package/dist/index-70p168VM.d.ts +192 -0
  75. package/dist/{index-CpOsP1y9.d.cts → index-Be74mcon.d.cts} +18 -17
  76. package/dist/index-BtXT6UQ0.d.cts +192 -0
  77. package/dist/{index-DxvARIP2.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  78. package/dist/{index-Bt4tOUD9.d.ts → index-C83mZI1P.d.ts} +93 -8
  79. package/dist/{index-Bg5dYMmZ.d.ts → index-D6b6na69.d.cts} +14 -11
  80. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  81. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  82. package/dist/index-OG65ocOr.d.cts +272 -0
  83. package/dist/index-OG65ocOr.d.ts +272 -0
  84. package/dist/{index-Jc0Ryi79.d.ts → index-tJhkmQgE.d.cts} +19 -4
  85. package/dist/{index-Dikst44v.d.cts → index-tSQh-xwY.d.ts} +14 -11
  86. package/dist/{index-BNlGP86A.d.cts → index-y_ak9_Hi.d.cts} +93 -8
  87. package/dist/index.cjs +419 -7383
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +14 -14
  90. package/dist/index.d.ts +14 -14
  91. package/dist/index.js +111 -27
  92. package/dist/model/index.cjs +8 -2562
  93. package/dist/model/index.cjs.map +1 -1
  94. package/dist/model/index.d.cts +433 -75
  95. package/dist/model/index.d.ts +433 -75
  96. package/dist/model/index.js +6 -7
  97. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  98. package/dist/styled-DJbkJQfj.d.cts +506 -0
  99. package/dist/theory/index.cjs +172 -1822
  100. package/dist/theory/index.cjs.map +1 -1
  101. package/dist/theory/index.d.cts +151 -233
  102. package/dist/theory/index.d.ts +151 -233
  103. package/dist/theory/index.js +30 -7
  104. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  105. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  106. package/package.json +24 -6
  107. package/dist/chunk-2ZWMSJH6.js.map +0 -1
  108. package/dist/chunk-4MHA432G.js.map +0 -1
  109. package/dist/chunk-62QZKCJQ.js.map +0 -1
  110. package/dist/chunk-AKLYPWIN.js.map +0 -1
  111. package/dist/chunk-C5XYKUVG.js.map +0 -1
  112. package/dist/chunk-CHMXELDS.js.map +0 -1
  113. package/dist/chunk-DYWFNC4A.js +0 -152
  114. package/dist/chunk-DYWFNC4A.js.map +0 -1
  115. package/dist/chunk-EH6EM37U.js.map +0 -1
  116. package/dist/chunk-FBQK3ZQN.js +0 -131
  117. package/dist/chunk-FBQK3ZQN.js.map +0 -1
  118. package/dist/chunk-GOAW4VYL.js.map +0 -1
  119. package/dist/chunk-I5R7ZBJS.js.map +0 -1
  120. package/dist/chunk-IT2O4XFE.js.map +0 -1
  121. package/dist/chunk-MIWO425M.js.map +0 -1
  122. package/dist/chunk-SGU7SPLK.js.map +0 -1
  123. package/dist/index-CGMZNCx6.d.cts +0 -140
  124. package/dist/index-CGMZNCx6.d.ts +0 -140
  125. package/dist/index-CnZCo29u.d.cts +0 -108
  126. package/dist/index-JnF4v9bx.d.ts +0 -108
  127. package/dist/styled-DZ7GjShb.d.ts +0 -282
  128. package/dist/styled-ZTbdaVFM.d.cts +0 -282
  129. package/src/analyze/arrange/index.ts +0 -21
  130. package/src/analyze/arrange/internal.ts +0 -127
  131. package/src/analyze/arrange/tension.ts +0 -199
  132. package/src/analyze/arrange/tracks.ts +0 -312
  133. package/src/analyze/detect/index.ts +0 -272
  134. package/src/analyze/functional/borrowed.ts +0 -84
  135. package/src/analyze/functional/cadence.ts +0 -66
  136. package/src/analyze/functional/function.ts +0 -184
  137. package/src/analyze/functional/index.ts +0 -23
  138. package/src/analyze/functional/internal.ts +0 -29
  139. package/src/analyze/functional/roman.ts +0 -402
  140. package/src/analyze/index.ts +0 -49
  141. package/src/analyze/timeline/index.ts +0 -451
  142. package/src/analyze/voice/index.ts +0 -339
  143. package/src/core/event-index/index.ts +0 -104
  144. package/src/core/index.ts +0 -61
  145. package/src/core/interval/index.ts +0 -81
  146. package/src/core/meter/index.ts +0 -302
  147. package/src/core/pitch/index.ts +0 -311
  148. package/src/core/random/index.ts +0 -76
  149. package/src/core/tuning/index.ts +0 -186
  150. package/src/core/types.ts +0 -29
  151. package/src/core/validation/index.ts +0 -119
  152. package/src/generate/bass/index.ts +0 -373
  153. package/src/generate/countermelody/index.ts +0 -470
  154. package/src/generate/drums/beat.ts +0 -266
  155. package/src/generate/drums/euclid.ts +0 -65
  156. package/src/generate/drums/fills.ts +0 -356
  157. package/src/generate/drums/ghost.ts +0 -119
  158. package/src/generate/drums/hihat.ts +0 -285
  159. package/src/generate/drums/hit.ts +0 -31
  160. package/src/generate/drums/index.ts +0 -361
  161. package/src/generate/drums/internal.ts +0 -257
  162. package/src/generate/drums/kick.ts +0 -160
  163. package/src/generate/drums/percussion.ts +0 -121
  164. package/src/generate/drums/rng.ts +0 -7
  165. package/src/generate/drums/swing.ts +0 -71
  166. package/src/generate/groove/index.ts +0 -351
  167. package/src/generate/harmonize/index.ts +0 -451
  168. package/src/generate/index.ts +0 -58
  169. package/src/generate/motif/index.ts +0 -422
  170. package/src/generate/progression/index.ts +0 -362
  171. package/src/generate/reharmony/index.ts +0 -323
  172. package/src/generate/rhythm/index.ts +0 -175
  173. package/src/index.ts +0 -13
  174. package/src/model/chord.ts +0 -442
  175. package/src/model/index.ts +0 -15
  176. package/src/model/interval.ts +0 -82
  177. package/src/model/key.ts +0 -266
  178. package/src/model/note.ts +0 -176
  179. package/src/model/progression.ts +0 -161
  180. package/src/model/shared.ts +0 -12
  181. package/src/theory/chord/index.ts +0 -375
  182. package/src/theory/chordscale/index.ts +0 -374
  183. package/src/theory/counterpoint/index.ts +0 -230
  184. package/src/theory/harmony/index.ts +0 -77
  185. package/src/theory/index.ts +0 -112
  186. package/src/theory/safety/index.ts +0 -488
  187. package/src/theory/scale/degrees.ts +0 -123
  188. package/src/theory/scale/index.ts +0 -28
  189. package/src/theory/scale/key.ts +0 -56
  190. package/src/theory/scale/masks.ts +0 -153
  191. package/src/theory/spelling/index.ts +0 -249
  192. package/src/theory/symbol/index.ts +0 -268
  193. package/src/theory/voicing/index.ts +0 -5
  194. package/src/theory/voicing/internal.ts +0 -168
  195. package/src/theory/voicing/leading.ts +0 -112
  196. package/src/theory/voicing/satb.ts +0 -216
  197. package/src/theory/voicing/styled.ts +0 -199
  198. package/tsconfig.json +0 -23
@@ -1,266 +0,0 @@
1
- import {
2
- getGhostDensity,
3
- getGhostProbabilityAtPosition,
4
- getGhostVelocity,
5
- selectGhostPositions,
6
- } from './ghost.js';
7
- import {
8
- footHiHatVelocity,
9
- type HiHatLevel,
10
- hiHatNote,
11
- hiHatTypeVelocityMultiplier,
12
- hiHatVelocityMultiplier,
13
- OHH_VEL_BOOST,
14
- roleHiHatInstrument,
15
- sectionHiHatType,
16
- shouldAddOpenHHAccent,
17
- shouldPlayHiHat,
18
- shouldUseBridgeCrossStick,
19
- } from './hihat.js';
20
- import type { HitList } from './hit.js';
21
- import type { BackingDensity, DrumRole, DrumStyle, Feel, SectionType } from './internal.js';
22
- import { EIGHTH, GM, type MoodCategory, SIXTEENTH } from './internal.js';
23
- import type { KickPattern } from './kick.js';
24
- import type { DrumRng } from './rng.js';
25
- import { effectiveSwing, quantizeSwing } from './swing.js';
26
-
27
- /** Per-section drum state shared across the bars of a section. */
28
- export type SectionCtx = {
29
- style: DrumStyle;
30
- feel: Feel;
31
- densityMult: number;
32
- backingDensity: BackingDensity;
33
- hhLevel: HiHatLevel;
34
- useGhostNotes: boolean;
35
- ghostBoost: boolean;
36
- useRide: boolean;
37
- useFootHh: boolean;
38
- role: DrumRole;
39
- ghostMood: MoodCategory;
40
- snareBeat3: boolean;
41
- };
42
-
43
- /** Per-beat context passed to every beat processor. */
44
- export type BeatCtx = {
45
- track: HitList;
46
- beatTick: number;
47
- beat: number;
48
- velocity: number;
49
- section: SectionType;
50
- bpm: number;
51
- bar: number;
52
- sectionBars: number;
53
- inPrechorusLift: boolean;
54
- swingAmount: number;
55
- barHasOpenHh: boolean;
56
- openHhBeat: number;
57
- rng: DrumRng;
58
- };
59
-
60
- function swing16(tick: number, feel: Feel, swingAmount: number): number {
61
- return quantizeSwing(tick, effectiveSwing(feel, swingAmount), 'sixteenth');
62
- }
63
-
64
- function hatSwingFactor(style: DrumStyle): number {
65
- if (style === 'trap') {
66
- return 0;
67
- }
68
- if (style === 'latin') {
69
- return 0.35;
70
- }
71
- return 0.5;
72
- }
73
-
74
- /** Emit the kick for one beat. */
75
- export function generateKickForBeat(ctx: BeatCtx, sec: SectionCtx, kick: KickPattern): void {
76
- if (ctx.inPrechorusLift) {
77
- return;
78
- }
79
- const on = [kick.beat1, kick.beat2, kick.beat3, kick.beat4][ctx.beat] ?? false;
80
- const and = [kick.beat1and, kick.beat2and, kick.beat3and, kick.beat4and][ctx.beat] ?? false;
81
-
82
- if (on) {
83
- ctx.track.add(GM.BD, ctx.beatTick, EIGHTH, ctx.velocity);
84
- }
85
- if (and) {
86
- const andTick = swing16(ctx.beatTick + EIGHTH, sec.feel, ctx.swingAmount);
87
- ctx.track.add(GM.BD, andTick, EIGHTH, ctx.velocity * 0.85);
88
- }
89
- }
90
-
91
- /** Emit the backbeat snare (or side-stick) for one beat. */
92
- export function generateSnareForBeat(ctx: BeatCtx, sec: SectionCtx, isIntroFirst: boolean): void {
93
- if (ctx.inPrechorusLift) {
94
- return;
95
- }
96
- const snareOn = sec.snareBeat3 ? ctx.beat === 2 : ctx.beat === 1 || ctx.beat === 3;
97
- if (!snareOn || isIntroFirst) {
98
- return;
99
- }
100
- if (sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat)) {
101
- return;
102
- }
103
-
104
- const backbeatVel = Math.min(127, ctx.velocity + 16);
105
- const promoteSparseChorus =
106
- sec.style === 'sparse' && ctx.section === 'chorus' && sec.role === 'full';
107
-
108
- if (promoteSparseChorus) {
109
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
110
- } else if (sec.style === 'sparse' || sec.role === 'ambient') {
111
- if (sec.role !== 'fxOnly' && sec.role !== 'minimal') {
112
- ctx.track.add(GM.SIDESTICK, ctx.beatTick, EIGHTH, ctx.velocity * 0.8);
113
- }
114
- } else {
115
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, backbeatVel);
116
- }
117
- }
118
-
119
- /** Emit ghost snares at the "e"/"a" 16ths of beats 1 and 3. */
120
- export function generateGhostNotesForBeat(ctx: BeatCtx, sec: SectionCtx): void {
121
- if (ctx.beat !== 0 && ctx.beat !== 2) {
122
- return;
123
- }
124
- const positions = selectGhostPositions(sec.ghostMood, ctx.rng);
125
- let ghostProb = getGhostDensity(sec.ghostMood, ctx.section, sec.backingDensity, ctx.bpm);
126
- if (sec.ghostBoost) {
127
- ghostProb = Math.min(1, ghostProb * 1.4);
128
- }
129
-
130
- for (const pos of positions) {
131
- const sixteenthInBeat = pos === 'e' ? 1 : 3;
132
- const posProb = getGhostProbabilityAtPosition(ctx.beat, sixteenthInBeat);
133
- if (!ctx.rng.prob(ghostProb * posProb)) {
134
- continue;
135
- }
136
- const variation = ctx.rng.float(0.85, 1.15);
137
- const ghostBase = getGhostVelocity(ctx.section, ctx.beat / 2, false);
138
- let ghostVel = ctx.velocity * ghostBase * variation;
139
- if (pos === 'a') {
140
- ghostVel *= 0.9;
141
- }
142
- const offset = pos === 'e' ? SIXTEENTH : 3 * SIXTEENTH;
143
- const tick = swing16(ctx.beatTick + offset, sec.feel, ctx.swingAmount);
144
- ctx.track.add(GM.SD, tick, SIXTEENTH, ghostVel);
145
- }
146
- }
147
-
148
- /** Emit the pre-chorus lift buildup for one beat. */
149
- export function generatePreChorusBuildup(
150
- ctx: BeatCtx,
151
- sec: SectionCtx,
152
- isSectionLastBar: boolean,
153
- ): void {
154
- if (sec.style === 'sparse') {
155
- if (isSectionLastBar && ctx.beat === 3) {
156
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, Math.max(45, ctx.velocity * 0.75));
157
- ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 0.9);
158
- }
159
- return;
160
- }
161
- const barsInLift = 2;
162
- const barInLift = ctx.bar - (ctx.sectionBars - barsInLift);
163
- const progress = (barInLift * 4 + ctx.beat) / (barsInLift * 4);
164
- const buildupVel = ctx.velocity * (0.5 + 0.5 * progress);
165
- ctx.track.add(GM.SD, ctx.beatTick, EIGHTH, buildupVel);
166
- ctx.track.add(GM.SD, ctx.beatTick + EIGHTH, EIGHTH, buildupVel * 0.85);
167
- if (isSectionLastBar && ctx.beat === 3) {
168
- ctx.track.add(GM.CRASH, ctx.beatTick + EIGHTH + SIXTEENTH, SIXTEENTH, ctx.velocity * 1.1);
169
- }
170
- }
171
-
172
- /** Emit the timekeeping hi-hat (or ride/foot) for one beat. */
173
- export function generateHiHatForBeat(ctx: BeatCtx, sec: SectionCtx): void {
174
- if (!shouldPlayHiHat(sec.role)) {
175
- if (sec.useFootHh && (ctx.beat === 0 || ctx.beat === 2)) {
176
- ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
177
- }
178
- return;
179
- }
180
-
181
- const crossStick = sec.useRide && shouldUseBridgeCrossStick(ctx.section, ctx.beat);
182
- const hhInstrument = crossStick ? GM.SIDESTICK : roleHiHatInstrument(sec.role, sec.useRide);
183
- const hhType = sectionHiHatType(ctx.section, sec.role);
184
- const typeMult = hiHatTypeVelocityMultiplier(hhType);
185
- const isDynamicOpen = ctx.barHasOpenHh && ctx.beat === ctx.openHhBeat;
186
- const dm = sec.densityMult;
187
-
188
- if (sec.hhLevel === 'quarter') {
189
- const introRest = ctx.section === 'intro' && ctx.beat !== 0;
190
- if (!introRest) {
191
- if (isDynamicOpen) {
192
- ctx.track.add(
193
- GM.OHH,
194
- ctx.beatTick,
195
- EIGHTH,
196
- ctx.velocity * dm * 0.75 * typeMult + OHH_VEL_BOOST,
197
- );
198
- } else {
199
- ctx.track.add(
200
- hhInstrument,
201
- ctx.beatTick,
202
- EIGHTH,
203
- Math.max(20, ctx.velocity * dm * 0.75 * typeMult),
204
- );
205
- }
206
- } else if (sec.useFootHh) {
207
- ctx.track.add(GM.FHH, ctx.beatTick, EIGHTH, footHiHatVelocity(ctx.rng));
208
- }
209
- return;
210
- }
211
-
212
- if (sec.hhLevel === 'eighth') {
213
- for (let eighth = 0; eighth < 2; eighth += 1) {
214
- let hhTick = ctx.beatTick + eighth * EIGHTH;
215
- if (eighth === 1) {
216
- hhTick = swing16(hhTick, sec.feel, ctx.swingAmount);
217
- }
218
- if (ctx.section === 'intro' && eighth === 1) {
219
- if (sec.useFootHh && ctx.beat % 2 === 0) {
220
- ctx.track.add(GM.FHH, hhTick, EIGHTH, footHiHatVelocity(ctx.rng));
221
- }
222
- continue;
223
- }
224
- const hhVel = Math.max(20, ctx.velocity * dm * typeMult * (eighth === 0 ? 0.9 : 0.65));
225
- if (isDynamicOpen && eighth === 0) {
226
- ctx.track.add(GM.OHH, hhTick, EIGHTH, hhVel + OHH_VEL_BOOST);
227
- continue;
228
- }
229
- let useOpen = false;
230
- if (sec.style === 'fourOnFloor' && eighth === 1) {
231
- const openProb = Math.max(0.15, Math.min(0.8, 45 / ctx.bpm));
232
- useOpen = (ctx.beat === 1 || ctx.beat === 3) && ctx.rng.prob(openProb);
233
- } else if (eighth === 0) {
234
- useOpen = shouldAddOpenHHAccent(ctx.section, ctx.beat, ctx.bar, ctx.rng);
235
- }
236
- if (useOpen) {
237
- ctx.track.add(hiHatNote('open'), hhTick, EIGHTH, Math.max(20, hhVel * 1.1));
238
- } else {
239
- ctx.track.add(hhInstrument, hhTick, EIGHTH / 2, hhVel);
240
- }
241
- }
242
- return;
243
- }
244
-
245
- for (let sixteenth = 0; sixteenth < 4; sixteenth += 1) {
246
- let hhTick = ctx.beatTick + sixteenth * SIXTEENTH;
247
- if (sixteenth === 1 || sixteenth === 3) {
248
- const swung = effectiveSwing(sec.feel, ctx.swingAmount) * hatSwingFactor(sec.style);
249
- hhTick = quantizeSwing(hhTick, swung, 'sixteenth');
250
- }
251
- const metricVel = hiHatVelocityMultiplier(sixteenth, ctx.rng);
252
- const hhVel = Math.max(20, ctx.velocity * dm * typeMult * metricVel);
253
- if (isDynamicOpen && sixteenth === 0) {
254
- ctx.track.add(GM.OHH, hhTick, SIXTEENTH, hhVel + OHH_VEL_BOOST);
255
- continue;
256
- }
257
- if (ctx.beat === 3 && sixteenth === 3) {
258
- const openProb = Math.max(0.1, Math.min(0.4, 30 / ctx.bpm));
259
- if (ctx.rng.prob(openProb)) {
260
- ctx.track.add(GM.OHH, hhTick, SIXTEENTH, Math.max(20, hhVel * 1.2));
261
- continue;
262
- }
263
- }
264
- ctx.track.add(hhInstrument, hhTick, SIXTEENTH / 2, hhVel);
265
- }
266
- }
@@ -1,65 +0,0 @@
1
- /**
2
- * Generate a euclidean onset pattern (Bjorklund's algorithm), distributing
3
- * `pulses` as evenly as possible across `steps`, then rotating.
4
- *
5
- * @param pulses Number of onsets, clamped to [0, steps].
6
- * @param steps Total steps (>= 1).
7
- * @param rotation Steps to rotate onsets toward later positions.
8
- * @returns One boolean per step; true marks an onset.
9
- */
10
- import {
11
- assertGenerationBudget,
12
- assertInteger,
13
- assertPositiveInt,
14
- } from '../../core/validation/index.js';
15
-
16
- export function euclideanRhythm(pulses: number, steps: number, rotation = 0): boolean[] {
17
- assertPositiveInt(steps, 'euclidean steps');
18
- assertInteger(pulses, 'euclidean pulses');
19
- assertInteger(rotation, 'euclidean rotation');
20
- assertGenerationBudget(steps, 'euclidean pattern steps');
21
- const p = Math.max(0, Math.min(pulses, steps));
22
- const base = bjorklund(p, steps);
23
- const shift = ((rotation % steps) + steps) % steps;
24
- if (shift === 0) {
25
- return base;
26
- }
27
- return base.map((_, i) => base[(i - shift + steps) % steps] ?? false);
28
- }
29
-
30
- /** True when the 16-step bitmask has an onset at `step`. */
31
- export function hasHit(mask: number, step: number): boolean {
32
- return ((mask >> step) & 1) === 1;
33
- }
34
-
35
- /** Convert a euclidean boolean pattern to a 16-step bitmask (LSB = step 0). */
36
- export function patternToMask(pattern: boolean[]): number {
37
- let mask = 0;
38
- for (let i = 0; i < pattern.length; i += 1) {
39
- if (pattern[i]) {
40
- mask |= 1 << i;
41
- }
42
- }
43
- return mask;
44
- }
45
-
46
- function bjorklund(pulses: number, steps: number): boolean[] {
47
- if (pulses === 0) {
48
- return Array.from({ length: steps }, () => false);
49
- }
50
- if (pulses >= steps) {
51
- return Array.from({ length: steps }, () => true);
52
- }
53
- let head: boolean[][] = Array.from({ length: pulses }, () => [true]);
54
- let remainder: boolean[][] = Array.from({ length: steps - pulses }, () => [false]);
55
- while (remainder.length > 1) {
56
- const count = Math.min(head.length, remainder.length);
57
- const next: boolean[][] = [];
58
- for (let i = 0; i < count; i += 1) {
59
- next.push([...(head[i] ?? []), ...(remainder[i] ?? [])]);
60
- }
61
- remainder = head.length > count ? head.slice(count) : remainder.slice(count);
62
- head = next;
63
- }
64
- return [...head.flat(), ...remainder.flat()];
65
- }
@@ -1,356 +0,0 @@
1
- import type { HitList } from './hit.js';
2
- import type { DrumStyle, SectionEnergy, SectionType } from './internal.js';
3
- import { EIGHTH, GM, SIXTEENTH } from './internal.js';
4
- import type { DrumRng } from './rng.js';
5
-
6
- /** The thirteen fill archetypes for section transitions. */
7
- export type FillType =
8
- | 'snareRoll'
9
- | 'tomDescend'
10
- | 'tomAscend'
11
- | 'snareTomCombo'
12
- | 'simpleCrash'
13
- | 'linearFill'
14
- | 'ghostToAccent'
15
- | 'bdSnareAlternate'
16
- | 'hiHatChoke'
17
- | 'tomShuffle'
18
- | 'breakdownFill'
19
- | 'flamsAndDrags'
20
- | 'halfTimeFill';
21
-
22
- /** Beat at which a fill begins, widening with section energy. */
23
- export function getFillStartBeat(energy: SectionEnergy): number {
24
- switch (energy) {
25
- case 'low':
26
- return 3;
27
- case 'medium':
28
- return 2;
29
- case 'high':
30
- case 'peak':
31
- return 0;
32
- }
33
- }
34
-
35
- /**
36
- * Pick a fill archetype for a section transition.
37
- *
38
- * The transition-specific archetypes (into a chorus, out of an intro) are
39
- * checked before the generic energy fills so every variation stays reachable;
40
- * with `from === to` the caller effectively asks for a within-section fill.
41
- *
42
- * @param from Section the fill leaves.
43
- * @param to Section the fill leads into.
44
- * @param style Internal drum style.
45
- * @param nextEnergy Energy of the section the fill leads into.
46
- * @param rng Deterministic PRNG.
47
- */
48
- export function selectFillType(
49
- from: SectionType,
50
- to: SectionType,
51
- style: DrumStyle,
52
- nextEnergy: SectionEnergy,
53
- rng: DrumRng,
54
- ): FillType {
55
- if (style === 'sparse') {
56
- return rng.range(0, 1) === 0 ? 'simpleCrash' : 'breakdownFill';
57
- }
58
-
59
- const toChorus = to === 'chorus';
60
- const fromIntro = from === 'intro';
61
- const highEnergy = style === 'rock' || style === 'fourOnFloor';
62
-
63
- // Dropping into a low-energy section: keep the phrase end gentle.
64
- if (nextEnergy === 'low') {
65
- switch (rng.range(0, 2)) {
66
- case 0:
67
- return 'simpleCrash';
68
- case 1:
69
- return 'breakdownFill';
70
- default:
71
- return 'halfTimeFill';
72
- }
73
- }
74
-
75
- // Big lead-ins to a chorus take precedence over the generic energy fills.
76
- if (toChorus) {
77
- const choice = rng.range(0, 7);
78
- if (highEnergy) {
79
- switch (choice) {
80
- case 0:
81
- case 1:
82
- return 'tomDescend';
83
- case 2:
84
- return 'snareRoll';
85
- case 3:
86
- return 'linearFill';
87
- case 4:
88
- return 'bdSnareAlternate';
89
- case 5:
90
- return 'flamsAndDrags';
91
- case 6:
92
- return 'tomShuffle';
93
- default:
94
- return 'ghostToAccent';
95
- }
96
- }
97
- switch (choice) {
98
- case 0:
99
- case 1:
100
- return 'snareTomCombo';
101
- case 2:
102
- return 'tomDescend';
103
- case 3:
104
- return 'ghostToAccent';
105
- case 4:
106
- return 'hiHatChoke';
107
- case 5:
108
- return 'linearFill';
109
- default:
110
- return 'snareRoll';
111
- }
112
- }
113
-
114
- // Leaving the intro: lighter, building character.
115
- if (fromIntro) {
116
- switch (rng.range(0, 5)) {
117
- case 0:
118
- case 1:
119
- return 'snareRoll';
120
- case 2:
121
- return 'simpleCrash';
122
- case 3:
123
- return 'ghostToAccent';
124
- case 4:
125
- return 'breakdownFill';
126
- default:
127
- return 'halfTimeFill';
128
- }
129
- }
130
-
131
- // Generic medium/high/peak fills, split by style energy.
132
- const choice = rng.range(0, 7);
133
- if (highEnergy) {
134
- switch (choice) {
135
- case 0:
136
- return 'tomDescend';
137
- case 1:
138
- return 'snareRoll';
139
- case 2:
140
- return 'tomAscend';
141
- case 3:
142
- return 'snareTomCombo';
143
- case 4:
144
- return 'linearFill';
145
- case 5:
146
- return 'bdSnareAlternate';
147
- case 6:
148
- return 'flamsAndDrags';
149
- default:
150
- return 'tomShuffle';
151
- }
152
- }
153
-
154
- switch (choice) {
155
- case 0:
156
- case 1:
157
- return 'snareRoll';
158
- case 2:
159
- return 'snareTomCombo';
160
- case 3:
161
- return 'ghostToAccent';
162
- case 4:
163
- return 'hiHatChoke';
164
- case 5:
165
- return 'halfTimeFill';
166
- default:
167
- return 'breakdownFill';
168
- }
169
- }
170
-
171
- /**
172
- * Emit a fill for a single beat of the fill bar.
173
- *
174
- * @param track Hit accumulator.
175
- * @param beatTick Absolute beat position of this beat.
176
- * @param beat Beat index within the bar (0-3).
177
- * @param fillType Selected fill archetype.
178
- * @param velocity Base velocity for the beat.
179
- */
180
- export function generateFill(
181
- track: HitList,
182
- beatTick: number,
183
- beat: number,
184
- fillType: FillType,
185
- velocity: number,
186
- ): void {
187
- const fillVel = velocity * 0.9;
188
- const accentVel = velocity * 0.95;
189
- const S = SIXTEENTH;
190
- const E = EIGHTH;
191
-
192
- if (beat === 0) {
193
- track.add(GM.BD, beatTick, E, fillVel);
194
- track.add(GM.SD, beatTick + E, E, fillVel - 5);
195
- return;
196
- }
197
- if (beat === 1) {
198
- track.add(GM.SD, beatTick, E, fillVel);
199
- track.add(GM.TOM_H, beatTick + E, E, fillVel - 3);
200
- return;
201
- }
202
-
203
- switch (fillType) {
204
- case 'snareRoll':
205
- if (beat === 2) {
206
- for (let i = 0; i < 4; i += 1) {
207
- track.add(GM.SD, beatTick + i * S, S, fillVel * (0.6 + 0.1 * i));
208
- }
209
- } else {
210
- for (let i = 0; i < 3; i += 1) {
211
- track.add(GM.SD, beatTick + i * S, S, fillVel * (0.7 + 0.1 * i));
212
- }
213
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
214
- }
215
- break;
216
-
217
- case 'tomDescend':
218
- if (beat === 2) {
219
- track.add(GM.SD, beatTick, E, fillVel);
220
- track.add(GM.TOM_H, beatTick + E, E, fillVel - 5);
221
- } else {
222
- track.add(GM.TOM_H, beatTick, S, fillVel);
223
- track.add(GM.TOM_M, beatTick + S, S, fillVel - 3);
224
- track.add(GM.TOM_M, beatTick + E, S, fillVel - 5);
225
- track.add(GM.TOM_L, beatTick + E + S, S, accentVel);
226
- }
227
- break;
228
-
229
- case 'tomAscend':
230
- if (beat === 2) {
231
- track.add(GM.SD, beatTick, E, fillVel);
232
- track.add(GM.TOM_L, beatTick + E, E, fillVel - 5);
233
- } else {
234
- track.add(GM.TOM_L, beatTick, S, fillVel);
235
- track.add(GM.TOM_M, beatTick + S, S, fillVel + 3);
236
- track.add(GM.TOM_M, beatTick + E, S, fillVel + 5);
237
- track.add(GM.TOM_H, beatTick + E + S, S, accentVel);
238
- }
239
- break;
240
-
241
- case 'snareTomCombo':
242
- if (beat === 2) {
243
- track.add(GM.SD, beatTick, E, fillVel);
244
- track.add(GM.SD, beatTick + E, S, fillVel - 5);
245
- track.add(GM.TOM_H, beatTick + E + S, S, fillVel);
246
- } else {
247
- track.add(GM.TOM_M, beatTick, S, fillVel);
248
- track.add(GM.SD, beatTick + S, S, fillVel - 3);
249
- track.add(GM.TOM_L, beatTick + E, S, fillVel + 2);
250
- track.add(GM.BD, beatTick + E + S, S, accentVel);
251
- }
252
- break;
253
-
254
- case 'simpleCrash':
255
- if (beat === 3) {
256
- track.add(GM.BD, beatTick + E + S, S, accentVel);
257
- track.add(GM.CRASH, beatTick + E + S, E, accentVel);
258
- }
259
- break;
260
-
261
- case 'linearFill':
262
- if (beat === 2) {
263
- track.add(GM.BD, beatTick, S, fillVel);
264
- track.add(GM.SD, beatTick + S, S, fillVel);
265
- track.add(GM.TOM_H, beatTick + 2 * S, S, fillVel);
266
- track.add(GM.TOM_M, beatTick + 3 * S, S, fillVel);
267
- } else {
268
- track.add(GM.TOM_L, beatTick, S, fillVel + 3);
269
- track.add(GM.SD, beatTick + S, S, fillVel + 5);
270
- track.add(GM.BD, beatTick + 2 * S, S, fillVel + 7);
271
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
272
- }
273
- break;
274
-
275
- case 'ghostToAccent':
276
- if (beat === 2) {
277
- const ghost = fillVel * 0.4;
278
- track.add(GM.SD, beatTick, S, ghost);
279
- track.add(GM.SD, beatTick + S, S, ghost + 10);
280
- track.add(GM.SD, beatTick + 2 * S, S, ghost + 20);
281
- track.add(GM.SD, beatTick + 3 * S, S, ghost + 30);
282
- } else {
283
- track.add(GM.SD, beatTick, E, fillVel);
284
- track.add(GM.SD, beatTick + E, E, accentVel);
285
- }
286
- break;
287
-
288
- case 'bdSnareAlternate':
289
- if (beat === 2) {
290
- track.add(GM.BD, beatTick, S, fillVel);
291
- track.add(GM.SD, beatTick + S, S, fillVel);
292
- track.add(GM.BD, beatTick + 2 * S, S, fillVel + 3);
293
- track.add(GM.SD, beatTick + 3 * S, S, fillVel + 3);
294
- } else {
295
- track.add(GM.BD, beatTick, S, fillVel + 5);
296
- track.add(GM.SD, beatTick + S, S, fillVel + 5);
297
- track.add(GM.BD, beatTick + 2 * S, S, accentVel);
298
- track.add(GM.SD, beatTick + 3 * S, S, accentVel);
299
- }
300
- break;
301
-
302
- case 'hiHatChoke':
303
- if (beat === 2) {
304
- track.add(GM.OHH, beatTick, E, fillVel);
305
- track.add(GM.OHH, beatTick + E, E, fillVel + 5);
306
- } else {
307
- track.add(GM.OHH, beatTick, S, fillVel + 8);
308
- track.add(GM.CHH, beatTick + S, S, accentVel);
309
- track.add(GM.SD, beatTick + E, E, accentVel);
310
- }
311
- break;
312
-
313
- case 'tomShuffle':
314
- if (beat === 2) {
315
- track.add(GM.TOM_H, beatTick, E, fillVel);
316
- track.add(GM.TOM_M, beatTick + E + S / 2, S, fillVel - 5);
317
- } else {
318
- track.add(GM.TOM_M, beatTick, E, fillVel);
319
- track.add(GM.TOM_L, beatTick + E + S / 2, S, fillVel + 5);
320
- }
321
- break;
322
-
323
- case 'breakdownFill':
324
- if (beat === 3) {
325
- track.add(GM.SD, beatTick + E, S, accentVel);
326
- }
327
- break;
328
-
329
- case 'flamsAndDrags':
330
- if (beat === 2) {
331
- track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
332
- track.add(GM.SD, beatTick, E, fillVel);
333
- track.add(GM.SD, beatTick + E, S / 2, fillVel * 0.6);
334
- track.add(GM.SD, beatTick + E + S / 2, S / 2, fillVel * 0.6);
335
- track.add(GM.SD, beatTick + E + S, E, fillVel);
336
- } else {
337
- track.add(GM.SD, beatTick - S / 4, S / 4, fillVel * 0.5);
338
- track.add(GM.SD, beatTick, 1, accentVel);
339
- }
340
- break;
341
-
342
- case 'halfTimeFill':
343
- if (beat === 2) {
344
- track.add(GM.SD, beatTick, 1, accentVel);
345
- track.add(GM.BD, beatTick, 1, fillVel);
346
- } else if (beat === 3) {
347
- // At low energy the fill spans only beat 3, so this branch keeps the
348
- // phrase end from going silent: a broad half-time backbeat snare with a
349
- // light pickup into the next section.
350
- track.add(GM.SD, beatTick, EIGHTH, accentVel);
351
- track.add(GM.SD, beatTick + E, S, fillVel * 0.6);
352
- track.add(GM.SD, beatTick + E + S, S, accentVel);
353
- }
354
- break;
355
- }
356
- }