@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,289 @@
1
+ /**
2
+ * YIN-based monophonic pitch detection.
3
+ *
4
+ * Implements the YIN algorithm for fundamental frequency (f0) estimation.
5
+ * Designed for monophonic sources: vocals, bass, lead synths.
6
+ *
7
+ * Reference: de Cheveigné, A., & Kawahara, H. (2002).
8
+ * "YIN, a fundamental frequency estimator for speech and music."
9
+ * Journal of the Acoustical Society of America, 111(4), 1917-1930.
10
+ */
11
+
12
+ import type { ActivitySignal } from "./activity";
13
+ import { interpolateActivity } from "./activity";
14
+
15
+ /** Options for activity gating in pitch detection */
16
+ export type PitchActivityOptions = {
17
+ /** Pre-computed activity signal to use for gating */
18
+ activity?: ActivitySignal;
19
+ /** How to handle inactive frames. Default: "zero" */
20
+ inactiveBehavior?: "zero" | "hold";
21
+ };
22
+
23
+ export type PitchConfig = {
24
+ /** Minimum detectable frequency in Hz. Default: 50 */
25
+ fMinHz?: number;
26
+ /** Maximum detectable frequency in Hz. Default: 1000 */
27
+ fMaxHz?: number;
28
+ /** Hop size in samples (determines time resolution). Default: 512 */
29
+ hopSize?: number;
30
+ /** Window size for analysis. Default: 2048 */
31
+ windowSize?: number;
32
+ /** YIN threshold for voiced detection (0-1). Default: 0.15 */
33
+ threshold?: number;
34
+ /** Activity gating options */
35
+ activityOptions?: PitchActivityOptions;
36
+ };
37
+
38
+ export type PitchResult = {
39
+ times: Float32Array;
40
+ values: Float32Array;
41
+ };
42
+
43
+ /**
44
+ * Extract fundamental frequency (f0) from mono audio.
45
+ *
46
+ * Uses the YIN algorithm for robust pitch detection.
47
+ * Returns 0 Hz for unvoiced/silent frames.
48
+ *
49
+ * @param samples - Mono audio samples
50
+ * @param sampleRate - Sample rate of the audio
51
+ * @param config - Configuration options
52
+ * @returns Times (seconds) and f0 values (Hz, 0 for unvoiced)
53
+ */
54
+ export function pitchF0(
55
+ samples: Float32Array,
56
+ sampleRate: number,
57
+ config?: PitchConfig
58
+ ): PitchResult {
59
+ const result = yinPitchDetection(samples, sampleRate, config);
60
+ const f0 = result.f0;
61
+
62
+ // Apply activity gating if provided
63
+ const activityOpts = config?.activityOptions;
64
+ if (activityOpts?.activity) {
65
+ const activity = interpolateActivity(activityOpts.activity, result.times);
66
+ const behavior = activityOpts.inactiveBehavior ?? "zero";
67
+
68
+ let lastActiveValue = 0;
69
+ for (let i = 0; i < f0.length; i++) {
70
+ if (!activity.isActive[i]) {
71
+ if (behavior === "zero") {
72
+ f0[i] = 0;
73
+ } else {
74
+ // hold: keep last active value
75
+ f0[i] = lastActiveValue;
76
+ }
77
+ } else {
78
+ lastActiveValue = f0[i] ?? 0;
79
+ }
80
+ }
81
+ }
82
+
83
+ return {
84
+ times: result.times,
85
+ values: f0,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Extract pitch detection confidence from mono audio.
91
+ *
92
+ * Uses the YIN algorithm's aperiodicity measure.
93
+ * Returns values in [0, 1] where 1 = highly periodic (confident pitch).
94
+ *
95
+ * @param samples - Mono audio samples
96
+ * @param sampleRate - Sample rate of the audio
97
+ * @param config - Configuration options
98
+ * @returns Times (seconds) and confidence values (0-1)
99
+ */
100
+ export function pitchConfidence(
101
+ samples: Float32Array,
102
+ sampleRate: number,
103
+ config?: PitchConfig
104
+ ): PitchResult {
105
+ const result = yinPitchDetection(samples, sampleRate, config);
106
+ const confidence = result.confidence;
107
+
108
+ // Apply activity gating if provided
109
+ const activityOpts = config?.activityOptions;
110
+ if (activityOpts?.activity) {
111
+ const activity = interpolateActivity(activityOpts.activity, result.times);
112
+ const behavior = activityOpts.inactiveBehavior ?? "zero";
113
+
114
+ let lastActiveValue = 0;
115
+ for (let i = 0; i < confidence.length; i++) {
116
+ if (!activity.isActive[i]) {
117
+ if (behavior === "zero") {
118
+ confidence[i] = 0;
119
+ } else {
120
+ // hold: keep last active value
121
+ confidence[i] = lastActiveValue;
122
+ }
123
+ } else {
124
+ lastActiveValue = confidence[i] ?? 0;
125
+ }
126
+ }
127
+ }
128
+
129
+ return {
130
+ times: result.times,
131
+ values: confidence,
132
+ };
133
+ }
134
+
135
+ // Internal result type with both f0 and confidence
136
+ type YinResult = {
137
+ times: Float32Array;
138
+ f0: Float32Array;
139
+ confidence: Float32Array;
140
+ };
141
+
142
+ /**
143
+ * Core YIN pitch detection algorithm.
144
+ *
145
+ * Computes both f0 and confidence in a single pass.
146
+ */
147
+ function yinPitchDetection(
148
+ samples: Float32Array,
149
+ sampleRate: number,
150
+ config?: PitchConfig
151
+ ): YinResult {
152
+ const fMinHz = config?.fMinHz ?? 50;
153
+ const fMaxHz = config?.fMaxHz ?? 1000;
154
+ const hopSize = config?.hopSize ?? 512;
155
+ const windowSize = config?.windowSize ?? 2048;
156
+ const threshold = config?.threshold ?? 0.15;
157
+
158
+ // Convert frequency limits to lag limits
159
+ // tau_max corresponds to fMin, tau_min corresponds to fMax
160
+ const tauMin = Math.max(2, Math.floor(sampleRate / fMaxHz));
161
+ const tauMax = Math.min(windowSize / 2, Math.ceil(sampleRate / fMinHz));
162
+
163
+ // Number of frames
164
+ const nFrames = Math.max(0, Math.floor((samples.length - windowSize) / hopSize) + 1);
165
+
166
+ const times = new Float32Array(nFrames);
167
+ const f0 = new Float32Array(nFrames);
168
+ const confidence = new Float32Array(nFrames);
169
+
170
+ // Working buffers (reused across frames)
171
+ const d = new Float32Array(tauMax + 1); // Difference function
172
+ const dPrime = new Float32Array(tauMax + 1); // Cumulative mean normalized difference
173
+
174
+ for (let frame = 0; frame < nFrames; frame++) {
175
+ const start = frame * hopSize;
176
+ const frameCenter = start + windowSize / 2;
177
+ times[frame] = frameCenter / sampleRate;
178
+
179
+ // Extract frame
180
+ const frameEnd = Math.min(start + windowSize, samples.length);
181
+ const frameLen = frameEnd - start;
182
+
183
+ // Check for silence (avoid division by zero and false detections)
184
+ let energy = 0;
185
+ for (let i = 0; i < frameLen; i++) {
186
+ const s = samples[start + i] ?? 0;
187
+ energy += s * s;
188
+ }
189
+ const rms = Math.sqrt(energy / frameLen);
190
+
191
+ if (rms < 1e-6) {
192
+ // Silence: unvoiced with zero confidence
193
+ f0[frame] = 0;
194
+ confidence[frame] = 0;
195
+ continue;
196
+ }
197
+
198
+ // Step 1: Compute difference function d(tau)
199
+ // d(tau) = sum_{j=0}^{W-1-tau} (x[j] - x[j+tau])^2
200
+ d[0] = 0;
201
+ for (let tau = 1; tau <= tauMax && tau < frameLen; tau++) {
202
+ let sum = 0;
203
+ const limit = Math.min(frameLen - tau, windowSize - tau);
204
+ for (let j = 0; j < limit; j++) {
205
+ const diff = (samples[start + j] ?? 0) - (samples[start + j + tau] ?? 0);
206
+ sum += diff * diff;
207
+ }
208
+ d[tau] = sum;
209
+ }
210
+
211
+ // Step 2: Compute cumulative mean normalized difference d'(tau)
212
+ // d'(tau) = d(tau) / ((1/tau) * sum_{j=1}^{tau} d(j))
213
+ dPrime[0] = 1;
214
+ let runningSum = 0;
215
+ for (let tau = 1; tau <= tauMax && tau < frameLen; tau++) {
216
+ const dVal = d[tau] ?? 0;
217
+ runningSum += dVal;
218
+ if (runningSum > 0) {
219
+ dPrime[tau] = dVal * tau / runningSum;
220
+ } else {
221
+ dPrime[tau] = 1;
222
+ }
223
+ }
224
+
225
+ // Step 3: Absolute threshold search
226
+ // Find first tau where d'(tau) < threshold, starting from tauMin
227
+ let bestTau = -1;
228
+ let bestDPrime = 1;
229
+
230
+ for (let tau = tauMin; tau <= tauMax && tau < frameLen - 1; tau++) {
231
+ const dPrimeVal = dPrime[tau] ?? 1;
232
+ if (dPrimeVal < threshold) {
233
+ // Found a candidate - look for local minimum
234
+ while (tau + 1 <= tauMax && tau + 1 < frameLen - 1 && (dPrime[tau + 1] ?? 1) < (dPrime[tau] ?? 1)) {
235
+ tau++;
236
+ }
237
+ bestTau = tau;
238
+ bestDPrime = dPrime[tau] ?? 1;
239
+ break;
240
+ }
241
+ }
242
+
243
+ // If no tau found below threshold, find global minimum in range
244
+ if (bestTau < 0) {
245
+ for (let tau = tauMin; tau <= tauMax && tau < frameLen; tau++) {
246
+ const dPrimeVal = dPrime[tau] ?? 1;
247
+ if (dPrimeVal < bestDPrime) {
248
+ bestDPrime = dPrimeVal;
249
+ bestTau = tau;
250
+ }
251
+ }
252
+ }
253
+
254
+ // Step 4: Parabolic interpolation for sub-sample precision
255
+ if (bestTau > tauMin && bestTau < tauMax - 1 && bestTau < frameLen - 1) {
256
+ const y0 = dPrime[bestTau - 1] ?? 1;
257
+ const y1 = dPrime[bestTau] ?? 1;
258
+ const y2 = dPrime[bestTau + 1] ?? 1;
259
+
260
+ // Parabolic interpolation: find vertex of parabola through 3 points
261
+ const denom = 2 * (2 * y1 - y0 - y2);
262
+ if (Math.abs(denom) > 1e-10) {
263
+ const delta = (y0 - y2) / denom;
264
+ const interpolatedTau = bestTau + delta;
265
+
266
+ // Only use if interpolation stays within bounds
267
+ if (interpolatedTau >= tauMin && interpolatedTau <= tauMax) {
268
+ // Convert to frequency
269
+ f0[frame] = sampleRate / interpolatedTau;
270
+ // Confidence is 1 - d'(tau) at the minimum
271
+ confidence[frame] = Math.max(0, Math.min(1, 1 - bestDPrime));
272
+ continue;
273
+ }
274
+ }
275
+ }
276
+
277
+ // Fallback: use integer tau
278
+ if (bestTau >= tauMin && bestTau <= tauMax) {
279
+ f0[frame] = sampleRate / bestTau;
280
+ confidence[frame] = Math.max(0, Math.min(1, 1 - bestDPrime));
281
+ } else {
282
+ // No valid pitch found
283
+ f0[frame] = 0;
284
+ confidence[frame] = 0;
285
+ }
286
+ }
287
+
288
+ return { times, f0, confidence };
289
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Resample audio using linear interpolation.
3
+ *
4
+ * This is a simple, fast resampling algorithm suitable for MIR analysis
5
+ * where perfect reconstruction is not critical.
6
+ *
7
+ * @param samples - Input audio samples
8
+ * @param fromRate - Original sample rate (Hz)
9
+ * @param toRate - Target sample rate (Hz)
10
+ * @returns Resampled audio samples
11
+ */
12
+ export function resample(
13
+ samples: Float32Array,
14
+ fromRate: number,
15
+ toRate: number
16
+ ): Float32Array {
17
+ // No-op if rates match
18
+ if (fromRate === toRate) {
19
+ return samples;
20
+ }
21
+
22
+ const ratio = fromRate / toRate;
23
+ const newLength = Math.floor(samples.length / ratio);
24
+
25
+ // Handle edge case of empty input
26
+ if (newLength <= 0) {
27
+ return new Float32Array(0);
28
+ }
29
+
30
+ const result = new Float32Array(newLength);
31
+
32
+ for (let i = 0; i < newLength; i++) {
33
+ const srcIndex = i * ratio;
34
+ const srcIndexFloor = Math.floor(srcIndex);
35
+ const frac = srcIndex - srcIndexFloor;
36
+
37
+ // Linear interpolation between adjacent samples
38
+ const a = samples[srcIndexFloor] ?? 0;
39
+ const b = samples[srcIndexFloor + 1] ?? a;
40
+ result[i] = a + frac * (b - a);
41
+ }
42
+
43
+ return result;
44
+ }