@octoseq/mir 0.1.0-main.4ecb074 → 0.1.0-main.5fdb072

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 (47) hide show
  1. package/dist/chunk-HF3QHCRK.js +3234 -0
  2. package/dist/chunk-HF3QHCRK.js.map +1 -0
  3. package/dist/index.d.ts +2122 -47
  4. package/dist/index.js +3524 -39
  5. package/dist/index.js.map +1 -1
  6. package/dist/runMir-CnJQbBr8.d.ts +187 -0
  7. package/dist/runner/runMir.d.ts +2 -2
  8. package/dist/runner/runMir.js +1 -1
  9. package/dist/runner/workerProtocol.d.ts +8 -1
  10. package/dist/runner/workerProtocol.js.map +1 -1
  11. package/dist/types-DqH4umN8.d.ts +761 -0
  12. package/package.json +2 -2
  13. package/src/dsp/activity.ts +544 -0
  14. package/src/dsp/bandCqt.ts +662 -0
  15. package/src/dsp/bandEvents.ts +351 -0
  16. package/src/dsp/bandMask.ts +225 -0
  17. package/src/dsp/bandMir.ts +524 -0
  18. package/src/dsp/bandProposal.ts +552 -0
  19. package/src/dsp/beatCandidates.ts +299 -0
  20. package/src/dsp/cqt.ts +386 -0
  21. package/src/dsp/cqtSignals.ts +462 -0
  22. package/src/dsp/customSignalReduction.ts +841 -0
  23. package/src/dsp/eventToSignal.ts +531 -0
  24. package/src/dsp/frequencyBand.ts +956 -0
  25. package/src/dsp/mel.ts +56 -3
  26. package/src/dsp/musicalTime.ts +240 -0
  27. package/src/dsp/onset.ts +296 -30
  28. package/src/dsp/peakPicking.ts +519 -0
  29. package/src/dsp/phaseAlignment.ts +153 -0
  30. package/src/dsp/pitch.ts +289 -0
  31. package/src/dsp/resample.ts +44 -0
  32. package/src/dsp/signalTransforms.ts +660 -0
  33. package/src/dsp/silenceGating.ts +511 -0
  34. package/src/dsp/spectral.ts +124 -4
  35. package/src/dsp/tempoHypotheses.ts +395 -0
  36. package/src/gpu/bufferPool.ts +266 -0
  37. package/src/gpu/context.ts +30 -6
  38. package/src/gpu/helpers.ts +85 -0
  39. package/src/gpu/melProject.ts +83 -0
  40. package/src/index.ts +366 -5
  41. package/src/runner/runMir.ts +234 -2
  42. package/src/runner/workerProtocol.ts +9 -1
  43. package/src/types.ts +768 -3
  44. package/dist/chunk-DUWYCAVG.js +0 -1525
  45. package/dist/chunk-DUWYCAVG.js.map +0 -1
  46. package/dist/runMir-CSIBwNZ3.d.ts +0 -84
  47. package/dist/types-BE3py4fZ.d.ts +0 -83
@@ -0,0 +1,531 @@
1
+ /**
2
+ * Event to Signal Conversion
3
+ *
4
+ * Converts discrete event streams (onsets, beats, authored events)
5
+ * into continuous 1D signals for use in derived signal pipelines.
6
+ *
7
+ * Reducer algorithms:
8
+ * - eventCount: Count events per window
9
+ * - eventDensity: Normalized count (events per second)
10
+ * - weightedSum: Sum of event weights per window
11
+ * - weightedMean: Mean of event weights per window
12
+ * - envelope: Generate continuous envelope from events
13
+ */
14
+
15
+ // ============================================================================
16
+ // TYPES
17
+ // ============================================================================
18
+
19
+ /**
20
+ * A discrete event with time and optional weight.
21
+ */
22
+ export interface DiscreteEvent {
23
+ /** Event time in seconds. */
24
+ time: number;
25
+ /** Event weight (default 1.0). */
26
+ weight?: number;
27
+ /** Optional duration in seconds. */
28
+ duration?: number;
29
+ }
30
+
31
+ /**
32
+ * Window specification for event aggregation.
33
+ */
34
+ export type EventWindowSpec =
35
+ | { kind: "seconds"; windowSize: number }
36
+ | { kind: "samples"; windowSize: number };
37
+
38
+ /**
39
+ * Envelope shape for event-to-signal conversion.
40
+ */
41
+ export type EnvelopeShape =
42
+ | { kind: "impulse" }
43
+ | { kind: "gaussian"; widthMs: number }
44
+ | { kind: "attackDecay"; attackMs: number; decayMs: number }
45
+ | { kind: "gate" }; // Uses event duration
46
+
47
+ /**
48
+ * Options for event-to-signal conversion.
49
+ */
50
+ export interface EventToSignalOptions {
51
+ /** Sample rate for output signal (samples per second). */
52
+ sampleRate: number;
53
+ /** Total duration in seconds. */
54
+ duration: number;
55
+ /** Whether to normalize output to 0-1 range. */
56
+ normalize?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Result of event-to-signal conversion.
61
+ */
62
+ export interface EventSignalResult {
63
+ /** Signal values. */
64
+ values: Float32Array;
65
+ /** Time points in seconds. */
66
+ times: Float32Array;
67
+ /** Value range before normalization. */
68
+ rawRange: { min: number; max: number };
69
+ }
70
+
71
+ // ============================================================================
72
+ // ENVELOPE GENERATORS
73
+ // ============================================================================
74
+
75
+ /**
76
+ * Generate a Gaussian envelope centered at a given sample.
77
+ */
78
+ function generateGaussianEnvelope(
79
+ centerSample: number,
80
+ widthSamples: number,
81
+ numSamples: number
82
+ ): Float32Array {
83
+ const envelope = new Float32Array(numSamples);
84
+ const sigma = widthSamples / 4; // 4 sigma covers ~95% of the bell
85
+ const twoSigmaSq = 2 * sigma * sigma;
86
+
87
+ const startSample = Math.max(0, Math.floor(centerSample - widthSamples));
88
+ const endSample = Math.min(numSamples, Math.ceil(centerSample + widthSamples));
89
+
90
+ for (let i = startSample; i < endSample; i++) {
91
+ const diff = i - centerSample;
92
+ envelope[i] = Math.exp(-(diff * diff) / twoSigmaSq);
93
+ }
94
+
95
+ return envelope;
96
+ }
97
+
98
+ /**
99
+ * Generate an attack-decay envelope starting at a given sample.
100
+ */
101
+ function generateAttackDecayEnvelope(
102
+ startSample: number,
103
+ attackSamples: number,
104
+ decaySamples: number,
105
+ numSamples: number
106
+ ): Float32Array {
107
+ const envelope = new Float32Array(numSamples);
108
+
109
+ // Attack phase
110
+ const attackEnd = Math.min(numSamples, startSample + attackSamples);
111
+ for (let i = startSample; i < attackEnd; i++) {
112
+ if (i >= 0) {
113
+ const t = (i - startSample) / attackSamples;
114
+ envelope[i] = t;
115
+ }
116
+ }
117
+
118
+ // Decay phase
119
+ const decayStart = startSample + attackSamples;
120
+ const decayEnd = Math.min(numSamples, decayStart + decaySamples);
121
+ for (let i = decayStart; i < decayEnd; i++) {
122
+ if (i >= 0) {
123
+ const t = (i - decayStart) / decaySamples;
124
+ envelope[i] = 1 - t;
125
+ }
126
+ }
127
+
128
+ return envelope;
129
+ }
130
+
131
+ /**
132
+ * Generate a gate envelope (rectangular) for an event with duration.
133
+ */
134
+ function generateGateEnvelope(
135
+ startSample: number,
136
+ durationSamples: number,
137
+ numSamples: number
138
+ ): Float32Array {
139
+ const envelope = new Float32Array(numSamples);
140
+
141
+ const endSample = Math.min(numSamples, startSample + durationSamples);
142
+ for (let i = Math.max(0, startSample); i < endSample; i++) {
143
+ envelope[i] = 1;
144
+ }
145
+
146
+ return envelope;
147
+ }
148
+
149
+ // ============================================================================
150
+ // REDUCER IMPLEMENTATIONS
151
+ // ============================================================================
152
+
153
+ /**
154
+ * Count events per window (sliding window approach).
155
+ */
156
+ export function eventCount(
157
+ events: DiscreteEvent[],
158
+ windowSpec: EventWindowSpec,
159
+ options: EventToSignalOptions
160
+ ): EventSignalResult {
161
+ const { sampleRate, duration, normalize = false } = options;
162
+ const numSamples = Math.ceil(duration * sampleRate);
163
+ const values = new Float32Array(numSamples);
164
+ const times = new Float32Array(numSamples);
165
+
166
+ // Generate time array
167
+ for (let i = 0; i < numSamples; i++) {
168
+ times[i] = i / sampleRate;
169
+ }
170
+
171
+ const windowSamples =
172
+ windowSpec.kind === "seconds"
173
+ ? Math.ceil(windowSpec.windowSize * sampleRate)
174
+ : windowSpec.windowSize;
175
+
176
+ const halfWindow = Math.floor(windowSamples / 2);
177
+
178
+ // Sort events by time
179
+ const sortedEvents = [...events].sort((a, b) => a.time - b.time);
180
+
181
+ // Count events in each window
182
+ for (let i = 0; i < numSamples; i++) {
183
+ const windowStart = (i - halfWindow) / sampleRate;
184
+ const windowEnd = (i + halfWindow) / sampleRate;
185
+
186
+ let count = 0;
187
+ for (const event of sortedEvents) {
188
+ if (event.time >= windowStart && event.time < windowEnd) {
189
+ count++;
190
+ }
191
+ // Early exit if past window
192
+ if (event.time >= windowEnd) break;
193
+ }
194
+ values[i] = count;
195
+ }
196
+
197
+ // Compute range
198
+ let min = 0;
199
+ let max = 0;
200
+ if (values.length > 0) {
201
+ min = values[0]!;
202
+ max = values[0]!;
203
+ for (let i = 1; i < values.length; i++) {
204
+ const v = values[i]!;
205
+ if (v < min) min = v;
206
+ if (v > max) max = v;
207
+ }
208
+ }
209
+
210
+ // Normalize if requested
211
+ if (normalize && max > min) {
212
+ const range = max - min;
213
+ for (let i = 0; i < values.length; i++) {
214
+ values[i] = (values[i]! - min) / range;
215
+ }
216
+ }
217
+
218
+ return { values, times, rawRange: { min, max } };
219
+ }
220
+
221
+ /**
222
+ * Event density: events per second in each window.
223
+ */
224
+ export function eventDensity(
225
+ events: DiscreteEvent[],
226
+ windowSpec: EventWindowSpec,
227
+ options: EventToSignalOptions
228
+ ): EventSignalResult {
229
+ const result = eventCount(events, windowSpec, { ...options, normalize: false });
230
+
231
+ const windowSeconds =
232
+ windowSpec.kind === "seconds"
233
+ ? windowSpec.windowSize
234
+ : windowSpec.windowSize / options.sampleRate;
235
+
236
+ // Convert count to density
237
+ for (let i = 0; i < result.values.length; i++) {
238
+ result.values[i] = result.values[i]! / windowSeconds;
239
+ }
240
+
241
+ // Recompute range
242
+ let min = 0;
243
+ let max = 0;
244
+ if (result.values.length > 0) {
245
+ min = result.values[0]!;
246
+ max = result.values[0]!;
247
+ for (let i = 1; i < result.values.length; i++) {
248
+ const v = result.values[i]!;
249
+ if (v < min) min = v;
250
+ if (v > max) max = v;
251
+ }
252
+ }
253
+
254
+ // Normalize if requested
255
+ if (options.normalize && max > min) {
256
+ const range = max - min;
257
+ for (let i = 0; i < result.values.length; i++) {
258
+ result.values[i] = (result.values[i]! - min) / range;
259
+ }
260
+ }
261
+
262
+ return { ...result, rawRange: { min, max } };
263
+ }
264
+
265
+ /**
266
+ * Weighted sum of events per window.
267
+ */
268
+ export function weightedSum(
269
+ events: DiscreteEvent[],
270
+ windowSpec: EventWindowSpec,
271
+ options: EventToSignalOptions
272
+ ): EventSignalResult {
273
+ const { sampleRate, duration, normalize = false } = options;
274
+ const numSamples = Math.ceil(duration * sampleRate);
275
+ const values = new Float32Array(numSamples);
276
+ const times = new Float32Array(numSamples);
277
+
278
+ // Generate time array
279
+ for (let i = 0; i < numSamples; i++) {
280
+ times[i] = i / sampleRate;
281
+ }
282
+
283
+ const windowSamples =
284
+ windowSpec.kind === "seconds"
285
+ ? Math.ceil(windowSpec.windowSize * sampleRate)
286
+ : windowSpec.windowSize;
287
+
288
+ const halfWindow = Math.floor(windowSamples / 2);
289
+
290
+ // Sort events by time
291
+ const sortedEvents = [...events].sort((a, b) => a.time - b.time);
292
+
293
+ // Sum weights in each window
294
+ for (let i = 0; i < numSamples; i++) {
295
+ const windowStart = (i - halfWindow) / sampleRate;
296
+ const windowEnd = (i + halfWindow) / sampleRate;
297
+
298
+ let sum = 0;
299
+ for (const event of sortedEvents) {
300
+ if (event.time >= windowStart && event.time < windowEnd) {
301
+ sum += event.weight ?? 1;
302
+ }
303
+ if (event.time >= windowEnd) break;
304
+ }
305
+ values[i] = sum;
306
+ }
307
+
308
+ // Compute range
309
+ let min = 0;
310
+ let max = 0;
311
+ if (values.length > 0) {
312
+ min = values[0]!;
313
+ max = values[0]!;
314
+ for (let i = 1; i < values.length; i++) {
315
+ const v = values[i]!;
316
+ if (v < min) min = v;
317
+ if (v > max) max = v;
318
+ }
319
+ }
320
+
321
+ // Normalize if requested
322
+ if (normalize && max > min) {
323
+ const range = max - min;
324
+ for (let i = 0; i < values.length; i++) {
325
+ values[i] = (values[i]! - min) / range;
326
+ }
327
+ }
328
+
329
+ return { values, times, rawRange: { min, max } };
330
+ }
331
+
332
+ /**
333
+ * Weighted mean of events per window.
334
+ */
335
+ export function weightedMean(
336
+ events: DiscreteEvent[],
337
+ windowSpec: EventWindowSpec,
338
+ options: EventToSignalOptions
339
+ ): EventSignalResult {
340
+ const { sampleRate, duration, normalize = false } = options;
341
+ const numSamples = Math.ceil(duration * sampleRate);
342
+ const values = new Float32Array(numSamples);
343
+ const times = new Float32Array(numSamples);
344
+
345
+ // Generate time array
346
+ for (let i = 0; i < numSamples; i++) {
347
+ times[i] = i / sampleRate;
348
+ }
349
+
350
+ const windowSamples =
351
+ windowSpec.kind === "seconds"
352
+ ? Math.ceil(windowSpec.windowSize * sampleRate)
353
+ : windowSpec.windowSize;
354
+
355
+ const halfWindow = Math.floor(windowSamples / 2);
356
+
357
+ // Sort events by time
358
+ const sortedEvents = [...events].sort((a, b) => a.time - b.time);
359
+
360
+ // Compute weighted mean in each window
361
+ for (let i = 0; i < numSamples; i++) {
362
+ const windowStart = (i - halfWindow) / sampleRate;
363
+ const windowEnd = (i + halfWindow) / sampleRate;
364
+
365
+ let sum = 0;
366
+ let count = 0;
367
+ for (const event of sortedEvents) {
368
+ if (event.time >= windowStart && event.time < windowEnd) {
369
+ sum += event.weight ?? 1;
370
+ count++;
371
+ }
372
+ if (event.time >= windowEnd) break;
373
+ }
374
+ values[i] = count > 0 ? sum / count : 0;
375
+ }
376
+
377
+ // Compute range
378
+ let min = 0;
379
+ let max = 0;
380
+ if (values.length > 0) {
381
+ min = values[0]!;
382
+ max = values[0]!;
383
+ for (let i = 1; i < values.length; i++) {
384
+ const v = values[i]!;
385
+ if (v < min) min = v;
386
+ if (v > max) max = v;
387
+ }
388
+ }
389
+
390
+ // Normalize if requested
391
+ if (normalize && max > min) {
392
+ const range = max - min;
393
+ for (let i = 0; i < values.length; i++) {
394
+ values[i] = (values[i]! - min) / range;
395
+ }
396
+ }
397
+
398
+ return { values, times, rawRange: { min, max } };
399
+ }
400
+
401
+ /**
402
+ * Generate continuous envelope from events.
403
+ * Each event contributes an envelope shape to the output.
404
+ */
405
+ export function eventEnvelope(
406
+ events: DiscreteEvent[],
407
+ shape: EnvelopeShape,
408
+ options: EventToSignalOptions
409
+ ): EventSignalResult {
410
+ const { sampleRate, duration, normalize = false } = options;
411
+ const numSamples = Math.ceil(duration * sampleRate);
412
+ const values = new Float32Array(numSamples);
413
+ const times = new Float32Array(numSamples);
414
+
415
+ // Generate time array
416
+ for (let i = 0; i < numSamples; i++) {
417
+ times[i] = i / sampleRate;
418
+ }
419
+
420
+ // Generate envelope for each event and sum
421
+ for (const event of events) {
422
+ const eventSample = Math.floor(event.time * sampleRate);
423
+ const weight = event.weight ?? 1;
424
+ let envelope: Float32Array;
425
+
426
+ switch (shape.kind) {
427
+ case "impulse":
428
+ // Single sample impulse
429
+ if (eventSample >= 0 && eventSample < numSamples) {
430
+ values[eventSample] = values[eventSample]! + weight;
431
+ }
432
+ continue;
433
+
434
+ case "gaussian":
435
+ const widthSamples = (shape.widthMs / 1000) * sampleRate;
436
+ envelope = generateGaussianEnvelope(eventSample, widthSamples, numSamples);
437
+ break;
438
+
439
+ case "attackDecay":
440
+ const attackSamples = (shape.attackMs / 1000) * sampleRate;
441
+ const decaySamples = (shape.decayMs / 1000) * sampleRate;
442
+ envelope = generateAttackDecayEnvelope(eventSample, attackSamples, decaySamples, numSamples);
443
+ break;
444
+
445
+ case "gate":
446
+ const durationSamples = event.duration
447
+ ? event.duration * sampleRate
448
+ : sampleRate * 0.1; // Default 100ms
449
+ envelope = generateGateEnvelope(eventSample, durationSamples, numSamples);
450
+ break;
451
+
452
+ default:
453
+ continue;
454
+ }
455
+
456
+ // Add weighted envelope to output
457
+ for (let i = 0; i < numSamples; i++) {
458
+ values[i] = values[i]! + envelope[i]! * weight;
459
+ }
460
+ }
461
+
462
+ // Compute range
463
+ let min = 0;
464
+ let max = 0;
465
+ if (values.length > 0) {
466
+ min = values[0]!;
467
+ max = values[0]!;
468
+ for (let i = 1; i < values.length; i++) {
469
+ const v = values[i]!;
470
+ if (v < min) min = v;
471
+ if (v > max) max = v;
472
+ }
473
+ }
474
+
475
+ // Normalize if requested
476
+ if (normalize && max > min) {
477
+ const range = max - min;
478
+ for (let i = 0; i < values.length; i++) {
479
+ values[i] = (values[i]! - min) / range;
480
+ }
481
+ }
482
+
483
+ return { values, times, rawRange: { min, max } };
484
+ }
485
+
486
+ // ============================================================================
487
+ // MAIN CONVERSION FUNCTION
488
+ // ============================================================================
489
+
490
+ /**
491
+ * Convert events to signal using specified reducer.
492
+ */
493
+ export type EventReducer = "eventCount" | "eventDensity" | "weightedSum" | "weightedMean" | "envelope";
494
+
495
+ export interface EventToSignalParams {
496
+ /** Reducer algorithm. */
497
+ reducer: EventReducer;
498
+ /** Window spec for count/density/sum/mean reducers. */
499
+ window?: EventWindowSpec;
500
+ /** Envelope shape for envelope reducer. */
501
+ envelopeShape?: EnvelopeShape;
502
+ }
503
+
504
+ export function eventsToSignal(
505
+ events: DiscreteEvent[],
506
+ params: EventToSignalParams,
507
+ options: EventToSignalOptions
508
+ ): EventSignalResult {
509
+ const defaultWindow: EventWindowSpec = { kind: "seconds", windowSize: 0.5 };
510
+ const defaultShape: EnvelopeShape = { kind: "attackDecay", attackMs: 5, decayMs: 100 };
511
+
512
+ switch (params.reducer) {
513
+ case "eventCount":
514
+ return eventCount(events, params.window ?? defaultWindow, options);
515
+
516
+ case "eventDensity":
517
+ return eventDensity(events, params.window ?? defaultWindow, options);
518
+
519
+ case "weightedSum":
520
+ return weightedSum(events, params.window ?? defaultWindow, options);
521
+
522
+ case "weightedMean":
523
+ return weightedMean(events, params.window ?? defaultWindow, options);
524
+
525
+ case "envelope":
526
+ return eventEnvelope(events, params.envelopeShape ?? defaultShape, options);
527
+
528
+ default:
529
+ throw new Error(`Unknown event reducer: ${params.reducer}`);
530
+ }
531
+ }