@libraz/libcantus 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +6 -5
  2. package/dist/analyze/index.cjs +41 -1676
  3. package/dist/analyze/index.cjs.map +1 -1
  4. package/dist/analyze/index.d.cts +194 -122
  5. package/dist/analyze/index.d.ts +194 -122
  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-R3S2LUBH.js → chunk-7NKDPF74.js} +660 -305
  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-7Y7WF64D.js → chunk-D2FAJBYL.js} +104 -35
  18. package/dist/chunk-D2FAJBYL.js.map +1 -0
  19. package/dist/{chunk-COKTX2G5.js → chunk-EHVEBWM5.js} +1052 -510
  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-GQKN5KDT.js → chunk-GQSGS2N4.js} +43 -29
  26. package/dist/chunk-GQSGS2N4.js.map +1 -0
  27. package/dist/chunk-HKGYCQ34.js +53 -0
  28. package/dist/chunk-HKGYCQ34.js.map +1 -0
  29. package/dist/chunk-I24MV65U.js +106 -0
  30. package/dist/chunk-I24MV65U.js.map +1 -0
  31. package/dist/{chunk-LN7ZZMD2.js → chunk-KMHOICIY.js} +322 -173
  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-N6IHQUOX.js +1 -0
  36. package/dist/chunk-N6IHQUOX.js.map +1 -0
  37. package/dist/chunk-NKIBQ6PT.js +30 -0
  38. package/dist/chunk-NKIBQ6PT.js.map +1 -0
  39. package/dist/chunk-NP4NA2FB.cjs +106 -0
  40. package/dist/chunk-NP4NA2FB.cjs.map +1 -0
  41. package/dist/chunk-NVE34YGZ.cjs +1449 -0
  42. package/dist/chunk-NVE34YGZ.cjs.map +1 -0
  43. package/dist/{chunk-A4532ZFR.js → chunk-NYYWMV2C.js} +115 -47
  44. package/dist/chunk-NYYWMV2C.js.map +1 -0
  45. package/dist/{chunk-5HTNMQW4.js → chunk-Q2XUP227.js} +310 -118
  46. package/dist/chunk-Q2XUP227.js.map +1 -0
  47. package/dist/chunk-Q6OP4U43.cjs +327 -0
  48. package/dist/chunk-Q6OP4U43.cjs.map +1 -0
  49. package/dist/chunk-RC6GBMCD.cjs +845 -0
  50. package/dist/chunk-RC6GBMCD.cjs.map +1 -0
  51. package/dist/{chunk-XTC3X6MS.js → chunk-RK35RIN7.js} +453 -152
  52. package/dist/chunk-RK35RIN7.js.map +1 -0
  53. package/dist/chunk-RUMU3UA6.cjs +53 -0
  54. package/dist/chunk-RUMU3UA6.cjs.map +1 -0
  55. package/dist/chunk-T64C7ZD7.cjs +1 -0
  56. package/dist/chunk-T64C7ZD7.cjs.map +1 -0
  57. package/dist/chunk-XEBG3G53.js +498 -0
  58. package/dist/chunk-XEBG3G53.js.map +1 -0
  59. package/dist/chunk-YLEMGEFH.js +246 -0
  60. package/dist/chunk-YLEMGEFH.js.map +1 -0
  61. package/dist/chunk-ZEJR4TKL.cjs +173 -0
  62. package/dist/chunk-ZEJR4TKL.cjs.map +1 -0
  63. package/dist/core/index.cjs +130 -431
  64. package/dist/core/index.cjs.map +1 -1
  65. package/dist/core/index.d.cts +331 -17
  66. package/dist/core/index.d.ts +331 -17
  67. package/dist/core/index.js +71 -13
  68. package/dist/{function-2bJW1Zp_.d.ts → function-BtiZb9i9.d.ts} +28 -7
  69. package/dist/{function-r2sh-jOO.d.cts → function-DNugAOrt.d.cts} +28 -7
  70. package/dist/generate/index.cjs +30 -3751
  71. package/dist/generate/index.cjs.map +1 -1
  72. package/dist/generate/index.d.cts +373 -59
  73. package/dist/generate/index.d.ts +373 -59
  74. package/dist/generate/index.js +23 -9
  75. package/dist/{index-DZ1M21do.d.cts → index-6VR8-kit.d.ts} +20 -4
  76. package/dist/index-70p168VM.d.ts +192 -0
  77. package/dist/{index-D_ZTUNKp.d.cts → index-Be74mcon.d.cts} +18 -17
  78. package/dist/index-BtXT6UQ0.d.cts +192 -0
  79. package/dist/{index-BMPxwjwV.d.ts → index-C5UBbQ4G.d.ts} +18 -17
  80. package/dist/{index-BOBXBNwP.d.cts → index-C83mZI1P.d.ts} +93 -8
  81. package/dist/{index-BdeQ1r_p.d.cts → index-D6b6na69.d.cts} +14 -11
  82. package/dist/{index-Cl7mhOFM.d.cts → index-DXd9JikN.d.cts} +92 -10
  83. package/dist/{index-Cl7mhOFM.d.ts → index-DXd9JikN.d.ts} +92 -10
  84. package/dist/index-OG65ocOr.d.cts +272 -0
  85. package/dist/index-OG65ocOr.d.ts +272 -0
  86. package/dist/{index-BreoJRw0.d.ts → index-tJhkmQgE.d.cts} +20 -4
  87. package/dist/{index-CP3wAfEH.d.ts → index-tSQh-xwY.d.ts} +14 -11
  88. package/dist/{index-CM5W4rcm.d.ts → index-y_ak9_Hi.d.cts} +93 -8
  89. package/dist/index.cjs +419 -6848
  90. package/dist/index.cjs.map +1 -1
  91. package/dist/index.d.cts +14 -13
  92. package/dist/index.d.ts +14 -13
  93. package/dist/index.js +132 -27
  94. package/dist/model/index.cjs +8 -2423
  95. package/dist/model/index.cjs.map +1 -1
  96. package/dist/model/index.d.cts +433 -74
  97. package/dist/model/index.d.ts +433 -74
  98. package/dist/model/index.js +6 -7
  99. package/dist/styled-DIRkk9Uz.d.ts +506 -0
  100. package/dist/styled-DJbkJQfj.d.cts +506 -0
  101. package/dist/theory/index.cjs +172 -1676
  102. package/dist/theory/index.cjs.map +1 -1
  103. package/dist/theory/index.d.cts +151 -233
  104. package/dist/theory/index.d.ts +151 -233
  105. package/dist/theory/index.js +31 -7
  106. package/dist/{types-MGP29r3t.d.cts → types-BN6_Q9Kl.d.cts} +11 -2
  107. package/dist/{types-MGP29r3t.d.ts → types-BN6_Q9Kl.d.ts} +11 -2
  108. package/package.json +73 -24
  109. package/dist/chunk-5HTNMQW4.js.map +0 -1
  110. package/dist/chunk-7Y7WF64D.js.map +0 -1
  111. package/dist/chunk-A4532ZFR.js.map +0 -1
  112. package/dist/chunk-BHUT2S5G.js +0 -22
  113. package/dist/chunk-BHUT2S5G.js.map +0 -1
  114. package/dist/chunk-CCBLVAPK.js +0 -42
  115. package/dist/chunk-CCBLVAPK.js.map +0 -1
  116. package/dist/chunk-COKTX2G5.js.map +0 -1
  117. package/dist/chunk-DYWFNC4A.js +0 -152
  118. package/dist/chunk-DYWFNC4A.js.map +0 -1
  119. package/dist/chunk-GQKN5KDT.js.map +0 -1
  120. package/dist/chunk-LN7ZZMD2.js.map +0 -1
  121. package/dist/chunk-MSWFZF7J.js +0 -118
  122. package/dist/chunk-MSWFZF7J.js.map +0 -1
  123. package/dist/chunk-R3S2LUBH.js.map +0 -1
  124. package/dist/chunk-SDM3VJJ5.js +0 -41
  125. package/dist/chunk-SDM3VJJ5.js.map +0 -1
  126. package/dist/chunk-XTC3X6MS.js.map +0 -1
  127. package/dist/chunk-ZXW5YIWK.js +0 -55
  128. package/dist/chunk-ZXW5YIWK.js.map +0 -1
  129. package/dist/index-CGMZNCx6.d.cts +0 -140
  130. package/dist/index-CGMZNCx6.d.ts +0 -140
  131. package/dist/styled-BjXtOFNk.d.cts +0 -282
  132. package/dist/styled-D7BAx3S3.d.ts +0 -282
  133. package/src/analyze/arrange/index.ts +0 -21
  134. package/src/analyze/arrange/internal.ts +0 -125
  135. package/src/analyze/arrange/tension.ts +0 -187
  136. package/src/analyze/arrange/tracks.ts +0 -279
  137. package/src/analyze/detect/index.ts +0 -235
  138. package/src/analyze/functional/borrowed.ts +0 -84
  139. package/src/analyze/functional/cadence.ts +0 -66
  140. package/src/analyze/functional/function.ts +0 -184
  141. package/src/analyze/functional/index.ts +0 -23
  142. package/src/analyze/functional/internal.ts +0 -29
  143. package/src/analyze/functional/roman.ts +0 -393
  144. package/src/analyze/index.ts +0 -49
  145. package/src/analyze/timeline/index.ts +0 -415
  146. package/src/analyze/voice/index.ts +0 -330
  147. package/src/core/index.ts +0 -47
  148. package/src/core/interval/index.ts +0 -81
  149. package/src/core/meter/index.ts +0 -282
  150. package/src/core/pitch/index.ts +0 -311
  151. package/src/core/random/index.ts +0 -53
  152. package/src/core/tuning/index.ts +0 -156
  153. package/src/core/types.ts +0 -29
  154. package/src/generate/bass/index.ts +0 -350
  155. package/src/generate/countermelody/index.ts +0 -379
  156. package/src/generate/drums/beat.ts +0 -266
  157. package/src/generate/drums/euclid.ts +0 -58
  158. package/src/generate/drums/fills.ts +0 -356
  159. package/src/generate/drums/ghost.ts +0 -119
  160. package/src/generate/drums/hihat.ts +0 -285
  161. package/src/generate/drums/hit.ts +0 -31
  162. package/src/generate/drums/index.ts +0 -329
  163. package/src/generate/drums/internal.ts +0 -257
  164. package/src/generate/drums/kick.ts +0 -160
  165. package/src/generate/drums/percussion.ts +0 -121
  166. package/src/generate/drums/rng.ts +0 -7
  167. package/src/generate/drums/swing.ts +0 -71
  168. package/src/generate/groove/index.ts +0 -326
  169. package/src/generate/harmonize/index.ts +0 -440
  170. package/src/generate/index.ts +0 -59
  171. package/src/generate/motif/index.ts +0 -398
  172. package/src/generate/progression/index.ts +0 -370
  173. package/src/generate/reharmony/index.ts +0 -330
  174. package/src/generate/rhythm/index.ts +0 -165
  175. package/src/index.ts +0 -247
  176. package/src/model/chord.ts +0 -423
  177. package/src/model/index.ts +0 -15
  178. package/src/model/interval.ts +0 -82
  179. package/src/model/key.ts +0 -266
  180. package/src/model/note.ts +0 -176
  181. package/src/model/progression.ts +0 -161
  182. package/src/model/shared.ts +0 -12
  183. package/src/theory/chord/index.ts +0 -373
  184. package/src/theory/chordscale/index.ts +0 -374
  185. package/src/theory/counterpoint/index.ts +0 -230
  186. package/src/theory/harmony/index.ts +0 -92
  187. package/src/theory/index.ts +0 -107
  188. package/src/theory/safety/index.ts +0 -419
  189. package/src/theory/scale/degrees.ts +0 -123
  190. package/src/theory/scale/index.ts +0 -28
  191. package/src/theory/scale/key.ts +0 -56
  192. package/src/theory/scale/masks.ts +0 -153
  193. package/src/theory/spelling/index.ts +0 -182
  194. package/src/theory/symbol/index.ts +0 -268
  195. package/src/theory/voicing/index.ts +0 -5
  196. package/src/theory/voicing/internal.ts +0 -168
  197. package/src/theory/voicing/leading.ts +0 -112
  198. package/src/theory/voicing/satb.ts +0 -195
  199. package/src/theory/voicing/styled.ts +0 -176
  200. package/tsconfig.json +0 -23
@@ -1,326 +0,0 @@
1
- /**
2
- * Groove and humanization: nudging quantized note events toward a more human
3
- * feel, and capturing/replaying the timing-and-velocity "feel" of a
4
- * performance as a reusable groove template.
5
- *
6
- * Positions and durations are measured in quarter-note beats, matching the
7
- * library-wide convention. Randomness, where used, always goes through the
8
- * seeded PRNG from the `random` module so results are fully reproducible.
9
- */
10
-
11
- import {
12
- beatsPerBar,
13
- formatTimeSignature,
14
- metricWeight,
15
- type TimeSignature,
16
- } from '../../core/meter/index.js';
17
- import { createRng } from '../../core/random/index.js';
18
- import type { NoteEvent } from '../../core/types.js';
19
-
20
- /**
21
- * Options controlling {@link humanize}.
22
- *
23
- * @category Rhythm & Meter
24
- */
25
- export type HumanizeOptions = {
26
- /**
27
- * Time signature used to derive metric accents.
28
- *
29
- * @defaultValue 4/4
30
- */
31
- ts?: TimeSignature;
32
- /**
33
- * Maximum timing jitter in quarter-note beats, applied as ±this value.
34
- *
35
- * @defaultValue 0.02
36
- */
37
- timing?: number;
38
- /**
39
- * Maximum velocity jitter (MIDI units), applied as ±this value.
40
- *
41
- * @defaultValue 8
42
- */
43
- velocity?: number;
44
- /**
45
- * How much louder strong beats get relative to weak ones, in MIDI velocity units.
46
- *
47
- * @defaultValue 12
48
- */
49
- accent?: number;
50
- /**
51
- * Velocity assumed for events with no `velocity` of their own.
52
- *
53
- * @defaultValue 80
54
- */
55
- baseVelocity?: number;
56
- /**
57
- * Seed for the deterministic PRNG.
58
- *
59
- * @defaultValue 0
60
- */
61
- seed?: number;
62
- };
63
-
64
- /** Default time signature for {@link humanize} when none is given. */
65
- const DEFAULT_TS: TimeSignature = { numerator: 4, denominator: 4 };
66
- const DEFAULT_TIMING = 0.02;
67
- const DEFAULT_VELOCITY_JITTER = 8;
68
- const DEFAULT_ACCENT = 12;
69
- const DEFAULT_BASE_VELOCITY = 80;
70
- const MAX_METRIC_WEIGHT = 3;
71
- const MIN_VELOCITY = 1;
72
- const MAX_VELOCITY = 127;
73
-
74
- /**
75
- * Humanize a sequence of note events by nudging their timing and velocity
76
- * with small, deterministic (seeded) randomness plus a metric-accent shape:
77
- * events on strong beats (see {@link metricWeight}) come out louder on
78
- * average than events on weak ones.
79
- *
80
- * Each returned event is a copy; `startBeat` is jittered within
81
- * `[-timing, +timing]` beats and clamped to be non-negative, and `velocity`
82
- * is the event's own velocity (or `baseVelocity` if it has none) plus an
83
- * accent term scaled by the event's metric weight plus a jitter within
84
- * `[-velocity, +velocity]`, clamped to `[1, 127]` and rounded. The metric
85
- * weight is computed from each event's original (pre-jitter) `startBeat`.
86
- * `pitch` and `durationBeat` pass through unchanged, and the output keeps the
87
- * input order.
88
- *
89
- * @param events The events to humanize.
90
- * @param opts Humanization options.
91
- * @returns Humanized copies of `events`, in input order.
92
- * @example
93
- * ```ts
94
- * import { humanize } from '@libraz/libcantus';
95
- * const events = [{ pitch: 60, startBeat: 0, durationBeat: 1, velocity: 80 }];
96
- * humanize(events, { seed: 1, timing: 0.03 }); // copies with jittered timing and accented velocity
97
- * ```
98
- * @category Rhythm & Meter
99
- */
100
- export function humanize(events: NoteEvent[], opts: HumanizeOptions = {}): NoteEvent[] {
101
- const ts = opts.ts ?? DEFAULT_TS;
102
- const timing = opts.timing ?? DEFAULT_TIMING;
103
- const velocityJitter = opts.velocity ?? DEFAULT_VELOCITY_JITTER;
104
- const accent = opts.accent ?? DEFAULT_ACCENT;
105
- const baseVelocity = opts.baseVelocity ?? DEFAULT_BASE_VELOCITY;
106
- const rng = createRng(opts.seed ?? 0);
107
-
108
- return events.map((event) => {
109
- const timingOffset = rng.float(-timing, timing);
110
- const startBeat = Math.max(0, event.startBeat + timingOffset);
111
-
112
- const weight = metricWeight(event.startBeat, ts);
113
- const accentBoost = (weight / MAX_METRIC_WEIGHT) * accent;
114
- const velocityOffset = rng.float(-velocityJitter, velocityJitter);
115
- const rawVelocity = (event.velocity ?? baseVelocity) + accentBoost + velocityOffset;
116
- const velocity = Math.min(MAX_VELOCITY, Math.max(MIN_VELOCITY, Math.round(rawVelocity)));
117
-
118
- return {
119
- pitch: event.pitch,
120
- startBeat,
121
- durationBeat: event.durationBeat,
122
- velocity,
123
- };
124
- });
125
- }
126
-
127
- /**
128
- * The recorded feel at a single grid slot: an average timing deviation and velocity.
129
- *
130
- * @category Rhythm & Meter
131
- */
132
- export type GrooveSlot = {
133
- /** Average `actualStartBeat - quantizedBeat` (in quarter-note beats) for events landing on this slot. */
134
- timingOffset: number;
135
- /**
136
- * Average velocity of events landing on this slot, or `null` if no event
137
- * with a velocity did. `null` (not 0) is the "unrecorded" sentinel so that a
138
- * genuine velocity of 0 is preserved and applied like any other value.
139
- */
140
- velocity: number | null;
141
- };
142
-
143
- /**
144
- * A per-bar grid of timing and velocity deviations captured from a performance.
145
- *
146
- * @category Rhythm & Meter
147
- */
148
- export type GrooveTemplate = {
149
- /** Grid resolution: equal grid steps per quarter-note beat. */
150
- subdivision: number;
151
- /** Number of grid slots per bar (`beatsPerBar(ts) * subdivision`). */
152
- slotsPerBar: number;
153
- /** One entry per grid slot, in slot-index order (slot 0 is the downbeat). */
154
- slots: GrooveSlot[];
155
- /**
156
- * The time signature the template was extracted under. {@link applyGrooveTemplate}
157
- * requires the apply-time meter to match this so the per-bar grid lines up
158
- * instead of silently drifting. Optional for backward compatibility with
159
- * templates constructed without it (in which case no check is performed).
160
- */
161
- ts?: TimeSignature;
162
- };
163
-
164
- /**
165
- * Quantize a beat position to the nearest grid slot at the given subdivision,
166
- * within the bar that contains it.
167
- *
168
- * @param beat Absolute position in quarter-note beats.
169
- * @param barBeats Bar length in quarter-note beats.
170
- * @param subdivision Grid steps per quarter-note beat.
171
- * @param slotsPerBar Number of grid slots per bar.
172
- * @returns The quantized absolute beat and its 0-based slot index within the bar.
173
- */
174
- function quantizeToGrid(
175
- beat: number,
176
- barBeats: number,
177
- subdivision: number,
178
- slotsPerBar: number,
179
- ): { quantizedBeat: number; slotIndex: number } {
180
- const barIndex = Math.floor(beat / barBeats);
181
- const modBeat = beat - barIndex * barBeats;
182
- let slotIndex = Math.round(modBeat * subdivision);
183
- let effectiveBarIndex = barIndex;
184
- // Rounding the last slot in a bar up to the grid step count means it
185
- // actually lands on the next bar's downbeat.
186
- if (slotIndex >= slotsPerBar) {
187
- slotIndex = 0;
188
- effectiveBarIndex += 1;
189
- }
190
- return {
191
- quantizedBeat: effectiveBarIndex * barBeats + slotIndex / subdivision,
192
- slotIndex,
193
- };
194
- }
195
-
196
- /**
197
- * Extract a groove template from a set of note events: for each grid slot,
198
- * the average timing deviation from the quantized grid and the average
199
- * velocity of events landing on it. Slots with no velocity-carrying event
200
- * record `velocity: null` (the "unrecorded" sentinel); empty slots default to
201
- * `{ timingOffset: 0, velocity: null }`. The time signature is stored on the
202
- * template so {@link applyGrooveTemplate} can verify the meter matches.
203
- *
204
- * @param events The (typically "groovy", human-played) events to analyze.
205
- * @param ts The time signature, used to compute the bar length.
206
- * @param subdivision Grid resolution: equal grid steps per quarter-note beat.
207
- * @returns The extracted groove template.
208
- * @category Rhythm & Meter
209
- */
210
- export function extractGrooveTemplate(
211
- events: NoteEvent[],
212
- ts: TimeSignature,
213
- subdivision: number,
214
- ): GrooveTemplate {
215
- const barBeats = beatsPerBar(ts);
216
- const slotsPerBar = Math.round(barBeats * subdivision);
217
- const offsetSums = new Array<number>(slotsPerBar).fill(0);
218
- const velocitySums = new Array<number>(slotsPerBar).fill(0);
219
- const counts = new Array<number>(slotsPerBar).fill(0);
220
- const velocityCounts = new Array<number>(slotsPerBar).fill(0);
221
-
222
- for (const event of events) {
223
- const { quantizedBeat, slotIndex } = quantizeToGrid(
224
- event.startBeat,
225
- barBeats,
226
- subdivision,
227
- slotsPerBar,
228
- );
229
- offsetSums[slotIndex] = (offsetSums[slotIndex] ?? 0) + (event.startBeat - quantizedBeat);
230
- counts[slotIndex] = (counts[slotIndex] ?? 0) + 1;
231
- // Only events that carry a velocity contribute to the velocity average, so a
232
- // velocity-less event does not pull the slot's recorded velocity toward zero.
233
- if (event.velocity !== undefined) {
234
- velocitySums[slotIndex] = (velocitySums[slotIndex] ?? 0) + event.velocity;
235
- velocityCounts[slotIndex] = (velocityCounts[slotIndex] ?? 0) + 1;
236
- }
237
- }
238
-
239
- const slots: GrooveSlot[] = [];
240
- for (let i = 0; i < slotsPerBar; i += 1) {
241
- const count = counts[i] ?? 0;
242
- if (count === 0) {
243
- slots.push({ timingOffset: 0, velocity: null });
244
- continue;
245
- }
246
- const velocityCount = velocityCounts[i] ?? 0;
247
- slots.push({
248
- timingOffset: (offsetSums[i] ?? 0) / count,
249
- velocity: velocityCount > 0 ? (velocitySums[i] ?? 0) / velocityCount : null,
250
- });
251
- }
252
-
253
- return { subdivision, slotsPerBar, slots, ts };
254
- }
255
-
256
- /**
257
- * Apply a groove template to a set of (typically quantized) note events: each
258
- * event is snapped to its grid slot and then offset by that slot's recorded
259
- * timing deviation, pushing stiff timing toward the template's feel.
260
- * Velocity is set to the slot's recorded average when it recorded one (a
261
- * non-`null` velocity, including a genuine 0); otherwise the event's own
262
- * velocity is left untouched.
263
- *
264
- * If the template carries a time signature (see {@link GrooveTemplate.ts}) it
265
- * must match `ts`, since a differing bar length would misalign the per-bar
266
- * grid and drift the feel; a mismatch throws rather than corrupting timing
267
- * silently.
268
- *
269
- * @param events The events to reshape.
270
- * @param template The groove template, from {@link extractGrooveTemplate}.
271
- * @param ts The time signature, used to compute the bar length.
272
- * @returns Reshaped copies of `events`, in input order.
273
- * @throws If `template.ts` is set and does not match `ts`.
274
- * @example
275
- * ```ts
276
- * import { extractGrooveTemplate, applyGrooveTemplate, parseTimeSignature } from '@libraz/libcantus';
277
- * const ts = parseTimeSignature('4/4');
278
- * // Learn the feel from a loosely played performance...
279
- * const groovy = [
280
- * { pitch: 36, startBeat: 0.02, durationBeat: 1, velocity: 100 },
281
- * { pitch: 38, startBeat: 1.06, durationBeat: 1, velocity: 70 },
282
- * ];
283
- * const template = extractGrooveTemplate(groovy, ts, 4);
284
- * // ...then impose it on stiff, quantized events.
285
- * const quantized = [{ pitch: 36, startBeat: 0, durationBeat: 1, velocity: 80 }];
286
- * applyGrooveTemplate(quantized, template, ts); // events nudged toward the captured feel
287
- * ```
288
- * @category Rhythm & Meter
289
- */
290
- export function applyGrooveTemplate(
291
- events: NoteEvent[],
292
- template: GrooveTemplate,
293
- ts: TimeSignature,
294
- ): NoteEvent[] {
295
- if (
296
- template.ts &&
297
- (template.ts.numerator !== ts.numerator || template.ts.denominator !== ts.denominator)
298
- ) {
299
- throw new Error(
300
- `Groove template meter ${formatTimeSignature(template.ts)} does not match ` +
301
- `apply-time meter ${formatTimeSignature(ts)}`,
302
- );
303
- }
304
- const barBeats = beatsPerBar(ts);
305
- return events.map((event) => {
306
- const { quantizedBeat, slotIndex } = quantizeToGrid(
307
- event.startBeat,
308
- barBeats,
309
- template.subdivision,
310
- template.slotsPerBar,
311
- );
312
- const slot = template.slots[slotIndex];
313
- const startBeat = quantizedBeat + (slot?.timingOffset ?? 0);
314
- const velocity =
315
- slot && slot.velocity !== null && slot.velocity !== undefined
316
- ? Math.round(slot.velocity)
317
- : event.velocity;
318
-
319
- return {
320
- pitch: event.pitch,
321
- startBeat,
322
- durationBeat: event.durationBeat,
323
- velocity,
324
- };
325
- });
326
- }