@libraz/libsonare 1.5.1 → 1.5.3

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 (48) hide show
  1. package/README.md +18 -3
  2. package/dist/index.d.ts +1165 -178
  3. package/dist/index.js +4830 -3871
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare.js +1 -1
  6. package/dist/sonare.wasm +0 -0
  7. package/dist/worklet.d.ts +29 -1
  8. package/dist/worklet.js +1068 -974
  9. package/dist/worklet.js.map +1 -1
  10. package/package.json +2 -1
  11. package/src/_chain_config.ts +46 -0
  12. package/src/audio.ts +24 -4
  13. package/src/effects_mastering.ts +25 -1
  14. package/src/effects_transform.ts +283 -43
  15. package/src/effects_voice_change.ts +46 -53
  16. package/src/feature_core.ts +316 -12
  17. package/src/feature_music.ts +462 -0
  18. package/src/feature_pitch.ts +61 -0
  19. package/src/feature_resample.ts +17 -2
  20. package/src/feature_spectral.ts +349 -2
  21. package/src/feature_spectrogram.ts +336 -0
  22. package/src/features.ts +2 -0
  23. package/src/index.ts +133 -1
  24. package/src/mastering_chain.ts +305 -50
  25. package/src/mastering_core.ts +242 -16
  26. package/src/mastering_dynamics.ts +66 -10
  27. package/src/mastering_repair.ts +119 -7
  28. package/src/metering.ts +366 -81
  29. package/src/mixer.ts +10 -3
  30. package/src/mixing_oneshot.ts +25 -5
  31. package/src/module_state.ts +1 -1
  32. package/src/project_class.ts +17 -6
  33. package/src/project_internal.ts +10 -2
  34. package/src/project_types.ts +25 -2
  35. package/src/public_types_mastering.ts +114 -4
  36. package/src/public_types_music.ts +1 -0
  37. package/src/public_types_spectral.ts +7 -0
  38. package/src/quick_analysis.ts +325 -117
  39. package/src/realtime_engine.ts +8 -0
  40. package/src/sonare.js.d.ts +56 -0
  41. package/src/stream_analyzer.ts +25 -2
  42. package/src/stream_types.ts +16 -1
  43. package/src/validation.ts +17 -2
  44. package/src/worklet/engine-clips.ts +67 -2
  45. package/src/worklet/engine-processor.ts +33 -5
  46. package/src/worklet/engine.ts +7 -3
  47. package/src/worklet/guards.ts +3 -0
  48. package/src/worklet/messages.ts +27 -0
package/src/metering.ts CHANGED
@@ -2,6 +2,25 @@ import { getSonareModule } from './module_state';
2
2
  import type { ValidateOptions } from './validation';
3
3
  import { assertSamples } from './validation';
4
4
 
5
+ /**
6
+ * Validates a true-peak oversample factor: `0` (meaning "use the default 4") or
7
+ * a power of two in `[1, 16]`. The native layer applies the same `0 -> 4`
8
+ * normalization (see `metering::true_peak_db`), so the raw factor can be passed
9
+ * through unchanged after this check. Kept module-local (not exported) so it does
10
+ * not surface as a WASM-only symbol with no C-API counterpart.
11
+ */
12
+ function assertOversampleFactor(fnName: string, factor: number): void {
13
+ const normalized = factor === 0 ? 4 : factor;
14
+ if (
15
+ !Number.isInteger(normalized) ||
16
+ normalized < 1 ||
17
+ normalized > 16 ||
18
+ (normalized & (normalized - 1)) !== 0
19
+ ) {
20
+ throw new RangeError(`${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`);
21
+ }
22
+ }
23
+
5
24
  // ============================================================================
6
25
  // Metering — basic / true-peak / clipping / dynamic range
7
26
  // ============================================================================
@@ -50,64 +69,122 @@ export interface MeteringDynamicRangeOptions extends ValidateOptions {
50
69
  highPercentile?: number;
51
70
  }
52
71
 
72
+ /** Canonical request form for single-channel meter readings. */
73
+ export interface MeteringSamplesRequest extends ValidateOptions {
74
+ samples: Float32Array;
75
+ sampleRate?: number;
76
+ }
77
+
78
+ /** Canonical request form for true-peak analysis. */
79
+ export interface MeteringTruePeakRequest extends MeteringSamplesRequest {
80
+ oversampleFactor?: number;
81
+ }
82
+
83
+ /** Canonical request form for clipping analysis. */
84
+ export interface MeteringDetectClippingRequest extends MeteringDetectClippingOptions {
85
+ samples: Float32Array;
86
+ sampleRate?: number;
87
+ }
88
+
89
+ /** Canonical request form for dynamic-range analysis. */
90
+ export interface MeteringDynamicRangeRequest extends MeteringDynamicRangeOptions {
91
+ samples: Float32Array;
92
+ sampleRate?: number;
93
+ }
94
+
53
95
  function requireModule() {
54
96
  return getSonareModule();
55
97
  }
56
98
 
99
+ export function meteringPeakDb(request: MeteringSamplesRequest): number;
57
100
  export function meteringPeakDb(
58
101
  samples: Float32Array,
102
+ sampleRate?: number,
103
+ options?: ValidateOptions,
104
+ ): number;
105
+ export function meteringPeakDb(
106
+ samples: Float32Array | MeteringSamplesRequest,
59
107
  sampleRate = 22050,
60
108
  options: ValidateOptions = {},
61
109
  ): number {
62
- assertSamples('meteringPeakDb', samples, options.validate !== false);
63
- return requireModule().meteringPeakDb(samples, sampleRate);
110
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
111
+ assertSamples('meteringPeakDb', request.samples, request.validate !== false);
112
+ return requireModule().meteringPeakDb(request.samples, request.sampleRate ?? 22050);
64
113
  }
65
114
 
115
+ export function meteringRmsDb(request: MeteringSamplesRequest): number;
66
116
  export function meteringRmsDb(
67
117
  samples: Float32Array,
118
+ sampleRate?: number,
119
+ options?: ValidateOptions,
120
+ ): number;
121
+ export function meteringRmsDb(
122
+ samples: Float32Array | MeteringSamplesRequest,
68
123
  sampleRate = 22050,
69
124
  options: ValidateOptions = {},
70
125
  ): number {
71
- assertSamples('meteringRmsDb', samples, options.validate !== false);
72
- return requireModule().meteringRmsDb(samples, sampleRate);
126
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
127
+ assertSamples('meteringRmsDb', request.samples, request.validate !== false);
128
+ return requireModule().meteringRmsDb(request.samples, request.sampleRate ?? 22050);
73
129
  }
74
130
 
131
+ export function meteringCrestFactorDb(request: MeteringSamplesRequest): number;
75
132
  export function meteringCrestFactorDb(
76
133
  samples: Float32Array,
134
+ sampleRate?: number,
135
+ options?: ValidateOptions,
136
+ ): number;
137
+ export function meteringCrestFactorDb(
138
+ samples: Float32Array | MeteringSamplesRequest,
77
139
  sampleRate = 22050,
78
140
  options: ValidateOptions = {},
79
141
  ): number {
80
- assertSamples('meteringCrestFactorDb', samples, options.validate !== false);
81
- return requireModule().meteringCrestFactorDb(samples, sampleRate);
142
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
143
+ assertSamples('meteringCrestFactorDb', request.samples, request.validate !== false);
144
+ return requireModule().meteringCrestFactorDb(request.samples, request.sampleRate ?? 22050);
82
145
  }
83
146
 
147
+ export function meteringDcOffset(request: MeteringSamplesRequest): number;
84
148
  export function meteringDcOffset(
85
149
  samples: Float32Array,
150
+ sampleRate?: number,
151
+ options?: ValidateOptions,
152
+ ): number;
153
+ export function meteringDcOffset(
154
+ samples: Float32Array | MeteringSamplesRequest,
86
155
  sampleRate = 22050,
87
156
  options: ValidateOptions = {},
88
157
  ): number {
89
- assertSamples('meteringDcOffset', samples, options.validate !== false);
90
- return requireModule().meteringDcOffset(samples, sampleRate);
158
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
159
+ assertSamples('meteringDcOffset', request.samples, request.validate !== false);
160
+ return requireModule().meteringDcOffset(request.samples, request.sampleRate ?? 22050);
91
161
  }
92
162
 
93
163
  /**
94
164
  * Inter-sample (true) peak in dBFS. `oversampleFactor` must be a power of two
95
165
  * in [1, 16]; pass 0 to use the library default (4).
96
166
  */
167
+ export function meteringTruePeakDb(request: MeteringTruePeakRequest): number;
97
168
  export function meteringTruePeakDb(
98
169
  samples: Float32Array,
170
+ sampleRate?: number,
171
+ oversampleFactor?: number,
172
+ options?: ValidateOptions,
173
+ ): number;
174
+ export function meteringTruePeakDb(
175
+ samples: Float32Array | MeteringTruePeakRequest,
99
176
  sampleRate = 22050,
100
177
  oversampleFactor = 4,
101
178
  options: ValidateOptions = {},
102
179
  ): number {
103
- assertSamples('meteringTruePeakDb', samples, options.validate !== false);
104
- const factor = oversampleFactor === 0 ? 4 : oversampleFactor;
105
- if (factor < 1 || factor > 16 || (factor & (factor - 1)) !== 0) {
106
- throw new RangeError(
107
- 'meteringTruePeakDb: oversampleFactor must be 0 or a power of two from 1 to 16',
108
- );
109
- }
110
- return requireModule().meteringTruePeakDb(samples, sampleRate, oversampleFactor);
180
+ const request =
181
+ samples instanceof Float32Array
182
+ ? { samples, sampleRate, oversampleFactor, ...options }
183
+ : samples;
184
+ assertSamples('meteringTruePeakDb', request.samples, request.validate !== false);
185
+ const factor = request.oversampleFactor ?? 4;
186
+ assertOversampleFactor('meteringTruePeakDb', factor);
187
+ return requireModule().meteringTruePeakDb(request.samples, request.sampleRate ?? 22050, factor);
111
188
  }
112
189
 
113
190
  /**
@@ -116,17 +193,24 @@ export function meteringTruePeakDb(
116
193
  * @param threshold Linear absolute threshold (default 0.999).
117
194
  * @param minRegionSamples Minimum run length to report (default 1).
118
195
  */
196
+ export function meteringDetectClipping(request: MeteringDetectClippingRequest): ClippingReport;
119
197
  export function meteringDetectClipping(
120
198
  samples: Float32Array,
199
+ sampleRate?: number,
200
+ options?: MeteringDetectClippingOptions,
201
+ ): ClippingReport;
202
+ export function meteringDetectClipping(
203
+ samples: Float32Array | MeteringDetectClippingRequest,
121
204
  sampleRate = 22050,
122
205
  options: MeteringDetectClippingOptions = {},
123
206
  ): ClippingReport {
124
- assertSamples('meteringDetectClipping', samples, options.validate !== false);
207
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
208
+ assertSamples('meteringDetectClipping', request.samples, request.validate !== false);
125
209
  return requireModule().meteringDetectClipping(
126
- samples,
127
- sampleRate,
128
- options.threshold ?? 0.999,
129
- options.minRegionSamples ?? 1,
210
+ request.samples,
211
+ request.sampleRate ?? 22050,
212
+ request.threshold ?? 0.999,
213
+ request.minRegionSamples ?? 1,
130
214
  );
131
215
  }
132
216
 
@@ -136,19 +220,26 @@ export function meteringDetectClipping(
136
220
  * "use the library default" (low=0.10, high=0.95) because 0 is a literal 0th
137
221
  * percentile; omitted percentiles therefore default to -1.
138
222
  */
223
+ export function meteringDynamicRange(request: MeteringDynamicRangeRequest): DynamicRangeReport;
139
224
  export function meteringDynamicRange(
140
225
  samples: Float32Array,
226
+ sampleRate?: number,
227
+ options?: MeteringDynamicRangeOptions,
228
+ ): DynamicRangeReport;
229
+ export function meteringDynamicRange(
230
+ samples: Float32Array | MeteringDynamicRangeRequest,
141
231
  sampleRate = 22050,
142
232
  options: MeteringDynamicRangeOptions = {},
143
233
  ): DynamicRangeReport {
144
- assertSamples('meteringDynamicRange', samples, options.validate !== false);
234
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
235
+ assertSamples('meteringDynamicRange', request.samples, request.validate !== false);
145
236
  return requireModule().meteringDynamicRange(
146
- samples,
147
- sampleRate,
148
- options.windowSec ?? 0,
149
- options.hopSec ?? 0,
150
- options.lowPercentile ?? -1,
151
- options.highPercentile ?? -1,
237
+ request.samples,
238
+ request.sampleRate ?? 22050,
239
+ request.windowSec ?? 0,
240
+ request.hopSec ?? 0,
241
+ request.lowPercentile ?? -1,
242
+ request.highPercentile ?? -1,
152
243
  );
153
244
  }
154
245
 
@@ -209,6 +300,51 @@ export interface WaveformPeakPyramidOptions extends ValidateOptions {
209
300
  samplesPerBucketLevels?: number[];
210
301
  }
211
302
 
303
+ /** Canonical request form for stereo meter readings. */
304
+ export interface MeteringStereoRequest extends ValidateOptions {
305
+ left: Float32Array;
306
+ right: Float32Array;
307
+ sampleRate?: number;
308
+ }
309
+
310
+ /** Canonical request form for display-decimated stereo scopes. */
311
+ export interface MeteringStereoDecimatedRequest extends MeteringStereoRequest {
312
+ maxPoints?: number;
313
+ }
314
+
315
+ /** Options for the scope functions (mirrors the Node `ScopeOptions`). */
316
+ export interface ScopeOptions extends ValidateOptions {
317
+ /**
318
+ * Upper bound on the returned point count. Omit / `0` (or a value `>= length`)
319
+ * yields one point per input sample; otherwise the point cloud is
320
+ * deterministically decimated to at most `maxPoints` points for display.
321
+ */
322
+ maxPoints?: number;
323
+ }
324
+
325
+ /** Canonical request form for whole-signal spectrum analysis. */
326
+ export interface MeteringSpectrumRequest extends SpectrumOptions, ValidateOptions {
327
+ samples: Float32Array;
328
+ sampleRate?: number;
329
+ }
330
+
331
+ /** Canonical request form for a single spectrum frame. */
332
+ export interface MeteringSpectrumFrameRequest extends MeteringSpectrumRequest {
333
+ frameOffset?: number;
334
+ }
335
+
336
+ /** Canonical request form for waveform bucket generation. */
337
+ export interface WaveformPeaksRequest extends WaveformPeaksOptions {
338
+ samples: Float32Array;
339
+ channels: number;
340
+ }
341
+
342
+ /** Canonical request form for multi-resolution waveform bucket generation. */
343
+ export interface WaveformPeakPyramidRequest extends WaveformPeakPyramidOptions {
344
+ samples: Float32Array;
345
+ channels: number;
346
+ }
347
+
212
348
  /** Per-channel min/max waveform buckets. Arrays are channel-major. */
213
349
  export interface WaveformPeaksReport {
214
350
  min: Float32Array;
@@ -219,97 +355,207 @@ export interface WaveformPeaksReport {
219
355
  }
220
356
 
221
357
  /** Pearson correlation in [-1, 1] between two equal-length channels. */
358
+ export function meteringStereoCorrelation(request: MeteringStereoRequest): number;
222
359
  export function meteringStereoCorrelation(
223
360
  left: Float32Array,
224
361
  right: Float32Array,
362
+ sampleRate?: number,
363
+ options?: ValidateOptions,
364
+ ): number;
365
+ export function meteringStereoCorrelation(
366
+ left: Float32Array | MeteringStereoRequest,
367
+ right?: Float32Array,
225
368
  sampleRate = 22050,
226
369
  options: ValidateOptions = {},
227
370
  ): number {
228
- const validate = options.validate !== false;
229
- assertSamples('meteringStereoCorrelation', left, validate, 'left');
230
- assertSamples('meteringStereoCorrelation', right, validate, 'right');
231
- return requireModule().meteringStereoCorrelation(left, right, sampleRate);
371
+ const request =
372
+ left instanceof Float32Array
373
+ ? { left, right: right as Float32Array, sampleRate, ...options }
374
+ : left;
375
+ const validate = request.validate !== false;
376
+ assertSamples('meteringStereoCorrelation', request.left, validate, 'left');
377
+ assertSamples('meteringStereoCorrelation', request.right, validate, 'right');
378
+ return requireModule().meteringStereoCorrelation(
379
+ request.left,
380
+ request.right,
381
+ request.sampleRate ?? 22050,
382
+ );
232
383
  }
233
384
 
234
385
  /**
235
386
  * Side / mid energy ratio, clamped to `[0, 2]`: 0 = pure mono, ~1 = wide
236
387
  * stereo, 2 = fully decorrelated / out-of-phase.
237
388
  */
389
+ export function meteringStereoWidth(request: MeteringStereoRequest): number;
238
390
  export function meteringStereoWidth(
239
391
  left: Float32Array,
240
392
  right: Float32Array,
393
+ sampleRate?: number,
394
+ options?: ValidateOptions,
395
+ ): number;
396
+ export function meteringStereoWidth(
397
+ left: Float32Array | MeteringStereoRequest,
398
+ right?: Float32Array,
241
399
  sampleRate = 22050,
242
400
  options: ValidateOptions = {},
243
401
  ): number {
244
- const validate = options.validate !== false;
245
- assertSamples('meteringStereoWidth', left, validate, 'left');
246
- assertSamples('meteringStereoWidth', right, validate, 'right');
247
- return requireModule().meteringStereoWidth(left, right, sampleRate);
402
+ const request =
403
+ left instanceof Float32Array
404
+ ? { left, right: right as Float32Array, sampleRate, ...options }
405
+ : left;
406
+ const validate = request.validate !== false;
407
+ assertSamples('meteringStereoWidth', request.left, validate, 'left');
408
+ assertSamples('meteringStereoWidth', request.right, validate, 'right');
409
+ return requireModule().meteringStereoWidth(
410
+ request.left,
411
+ request.right,
412
+ request.sampleRate ?? 22050,
413
+ );
248
414
  }
249
415
 
250
- /** Per-sample mid/side point series (one entry per input frame). */
416
+ /**
417
+ * Mid/side vectorscope point series. By default emits one point per input
418
+ * sample; pass `maxPoints` to get a display-sized decimated point set (matching
419
+ * the Node `meteringVectorscope` shape).
420
+ */
421
+ export function meteringVectorscope(request: MeteringStereoDecimatedRequest): VectorscopeReport;
251
422
  export function meteringVectorscope(
252
423
  left: Float32Array,
253
424
  right: Float32Array,
425
+ sampleRate?: number,
426
+ options?: ScopeOptions,
427
+ ): VectorscopeReport;
428
+ export function meteringVectorscope(
429
+ left: Float32Array | MeteringStereoDecimatedRequest,
430
+ right?: Float32Array,
254
431
  sampleRate = 22050,
255
- options: ValidateOptions = {},
432
+ options: ScopeOptions = {},
256
433
  ): VectorscopeReport {
257
- const validate = options.validate !== false;
258
- assertSamples('meteringVectorscope', left, validate, 'left');
259
- assertSamples('meteringVectorscope', right, validate, 'right');
260
- return requireModule().meteringVectorscope(left, right, sampleRate);
434
+ const request =
435
+ left instanceof Float32Array
436
+ ? { left, right: right as Float32Array, sampleRate, ...options }
437
+ : left;
438
+ const validate = request.validate !== false;
439
+ assertSamples('meteringVectorscope', request.left, validate, 'left');
440
+ assertSamples('meteringVectorscope', request.right, validate, 'right');
441
+ return requireModule().meteringVectorscopeDecimated(
442
+ request.left,
443
+ request.right,
444
+ request.sampleRate ?? 22050,
445
+ request.maxPoints ?? 0,
446
+ );
261
447
  }
262
448
 
263
449
  /**
264
- * Display-sized mid/side vectorscope. Like {@link meteringVectorscope} but the
265
- * point series is deterministically decimated to at most `maxPoints` points
266
- * (`0`, or a value `>= length`, yields one point per input sample). Mirrors the
267
- * Node/Python decimated vectorscope.
450
+ * Display-sized mid/side vectorscope.
451
+ *
452
+ * @deprecated Pass `maxPoints` to {@link meteringVectorscope} instead; it now
453
+ * folds `maxPoints` into the request, matching the Node surface. This alias is
454
+ * kept for backward compatibility and simply delegates.
268
455
  */
456
+ export function meteringVectorscopeDecimated(
457
+ request: MeteringStereoDecimatedRequest,
458
+ ): VectorscopeReport;
269
459
  export function meteringVectorscopeDecimated(
270
460
  left: Float32Array,
271
461
  right: Float32Array,
462
+ sampleRate?: number,
463
+ maxPoints?: number,
464
+ options?: ValidateOptions,
465
+ ): VectorscopeReport;
466
+ export function meteringVectorscopeDecimated(
467
+ left: Float32Array | MeteringStereoDecimatedRequest,
468
+ right?: Float32Array,
272
469
  sampleRate = 22050,
273
470
  maxPoints = 0,
274
471
  options: ValidateOptions = {},
275
472
  ): VectorscopeReport {
276
- const validate = options.validate !== false;
277
- assertSamples('meteringVectorscopeDecimated', left, validate, 'left');
278
- assertSamples('meteringVectorscopeDecimated', right, validate, 'right');
279
- return requireModule().meteringVectorscopeDecimated(left, right, sampleRate, maxPoints);
473
+ const request =
474
+ left instanceof Float32Array
475
+ ? { left, right: right as Float32Array, sampleRate, maxPoints, ...options }
476
+ : left;
477
+ const validate = request.validate !== false;
478
+ assertSamples('meteringVectorscopeDecimated', request.left, validate, 'left');
479
+ assertSamples('meteringVectorscopeDecimated', request.right, validate, 'right');
480
+ return requireModule().meteringVectorscopeDecimated(
481
+ request.left,
482
+ request.right,
483
+ request.sampleRate ?? 22050,
484
+ request.maxPoints ?? 0,
485
+ );
280
486
  }
281
487
 
282
- /** Phase-scope point series plus summary stats. */
488
+ /**
489
+ * Phase-scope point series plus summary stats. By default emits one point per
490
+ * input sample; pass `maxPoints` to decimate the point cloud for display
491
+ * (matching the Node `meteringPhaseScope` shape). The summary stats are always
492
+ * computed over the full-resolution signal.
493
+ */
494
+ export function meteringPhaseScope(request: MeteringStereoDecimatedRequest): PhaseScopeReport;
283
495
  export function meteringPhaseScope(
284
496
  left: Float32Array,
285
497
  right: Float32Array,
498
+ sampleRate?: number,
499
+ options?: ScopeOptions,
500
+ ): PhaseScopeReport;
501
+ export function meteringPhaseScope(
502
+ left: Float32Array | MeteringStereoDecimatedRequest,
503
+ right?: Float32Array,
286
504
  sampleRate = 22050,
287
- options: ValidateOptions = {},
505
+ options: ScopeOptions = {},
288
506
  ): PhaseScopeReport {
289
- const validate = options.validate !== false;
290
- assertSamples('meteringPhaseScope', left, validate, 'left');
291
- assertSamples('meteringPhaseScope', right, validate, 'right');
292
- return requireModule().meteringPhaseScope(left, right, sampleRate);
507
+ const request =
508
+ left instanceof Float32Array
509
+ ? { left, right: right as Float32Array, sampleRate, ...options }
510
+ : left;
511
+ const validate = request.validate !== false;
512
+ assertSamples('meteringPhaseScope', request.left, validate, 'left');
513
+ assertSamples('meteringPhaseScope', request.right, validate, 'right');
514
+ return requireModule().meteringPhaseScopeDecimated(
515
+ request.left,
516
+ request.right,
517
+ request.sampleRate ?? 22050,
518
+ request.maxPoints ?? 0,
519
+ );
293
520
  }
294
521
 
295
522
  /**
296
- * Display-sized phase scope. Like {@link meteringPhaseScope} but the point
297
- * series is deterministically decimated to at most `maxPoints` points (`0`, or
298
- * a value `>= length`, yields one point per input sample). The summary stats are
299
- * always computed over the full-resolution signal. Mirrors the Node/Python
300
- * decimated phase scope.
523
+ * Display-sized phase scope.
524
+ *
525
+ * @deprecated Pass `maxPoints` to {@link meteringPhaseScope} instead; it now
526
+ * folds `maxPoints` into the request, matching the Node surface. This alias is
527
+ * kept for backward compatibility and simply delegates.
301
528
  */
529
+ export function meteringPhaseScopeDecimated(
530
+ request: MeteringStereoDecimatedRequest,
531
+ ): PhaseScopeReport;
302
532
  export function meteringPhaseScopeDecimated(
303
533
  left: Float32Array,
304
534
  right: Float32Array,
535
+ sampleRate?: number,
536
+ maxPoints?: number,
537
+ options?: ValidateOptions,
538
+ ): PhaseScopeReport;
539
+ export function meteringPhaseScopeDecimated(
540
+ left: Float32Array | MeteringStereoDecimatedRequest,
541
+ right?: Float32Array,
305
542
  sampleRate = 22050,
306
543
  maxPoints = 0,
307
544
  options: ValidateOptions = {},
308
545
  ): PhaseScopeReport {
309
- const validate = options.validate !== false;
310
- assertSamples('meteringPhaseScopeDecimated', left, validate, 'left');
311
- assertSamples('meteringPhaseScopeDecimated', right, validate, 'right');
312
- return requireModule().meteringPhaseScopeDecimated(left, right, sampleRate, maxPoints);
546
+ const request =
547
+ left instanceof Float32Array
548
+ ? { left, right: right as Float32Array, sampleRate, maxPoints, ...options }
549
+ : left;
550
+ const validate = request.validate !== false;
551
+ assertSamples('meteringPhaseScopeDecimated', request.left, validate, 'left');
552
+ assertSamples('meteringPhaseScopeDecimated', request.right, validate, 'right');
553
+ return requireModule().meteringPhaseScopeDecimated(
554
+ request.left,
555
+ request.right,
556
+ request.sampleRate ?? 22050,
557
+ request.maxPoints ?? 0,
558
+ );
313
559
  }
314
560
 
315
561
  /**
@@ -318,14 +564,20 @@ export function meteringPhaseScopeDecimated(
318
564
  * are averaged). For a true single-frame snapshot, use
319
565
  * {@link meteringSpectrumFrame}.
320
566
  */
567
+ export function meteringSpectrum(request: MeteringSpectrumRequest): SpectrumReport;
321
568
  export function meteringSpectrum(
322
569
  samples: Float32Array,
323
- sampleRate = 22050,
570
+ sampleRate?: number,
324
571
  options?: SpectrumOptions & ValidateOptions,
572
+ ): SpectrumReport;
573
+ export function meteringSpectrum(
574
+ samples: Float32Array | MeteringSpectrumRequest,
575
+ sampleRate = 22050,
576
+ options: SpectrumOptions & ValidateOptions = {},
325
577
  ): SpectrumReport {
326
- const validate = options?.validate !== false;
327
- assertSamples('meteringSpectrum', samples, validate);
328
- return requireModule().meteringSpectrum(samples, sampleRate, options ?? {});
578
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
579
+ assertSamples('meteringSpectrum', request.samples, request.validate !== false);
580
+ return requireModule().meteringSpectrum(request.samples, request.sampleRate ?? 22050, request);
329
581
  }
330
582
 
331
583
  /**
@@ -334,47 +586,80 @@ export function meteringSpectrum(
334
586
  * time-averaged like {@link meteringSpectrum}. The analysis frame spans
335
587
  * `[frameOffset, frameOffset + nFft)`; samples past the end are zero-padded.
336
588
  */
589
+ export function meteringSpectrumFrame(request: MeteringSpectrumFrameRequest): SpectrumReport;
337
590
  export function meteringSpectrumFrame(
338
591
  samples: Float32Array,
592
+ sampleRate?: number,
593
+ frameOffset?: number,
594
+ options?: SpectrumOptions & ValidateOptions,
595
+ ): SpectrumReport;
596
+ export function meteringSpectrumFrame(
597
+ samples: Float32Array | MeteringSpectrumFrameRequest,
339
598
  sampleRate = 22050,
340
599
  frameOffset = 0,
341
- options?: SpectrumOptions & ValidateOptions,
600
+ options: SpectrumOptions & ValidateOptions = {},
342
601
  ): SpectrumReport {
343
- const validate = options?.validate !== false;
344
- assertSamples('meteringSpectrumFrame', samples, validate);
345
- return requireModule().meteringSpectrumFrame(samples, sampleRate, frameOffset, options ?? {});
602
+ const request =
603
+ samples instanceof Float32Array ? { samples, sampleRate, frameOffset, ...options } : samples;
604
+ assertSamples('meteringSpectrumFrame', request.samples, request.validate !== false);
605
+ return requireModule().meteringSpectrumFrame(
606
+ request.samples,
607
+ request.sampleRate ?? 22050,
608
+ request.frameOffset ?? 0,
609
+ request,
610
+ );
346
611
  }
347
612
 
348
613
  /** Compute per-channel min/max waveform buckets from interleaved audio. */
614
+ export function waveformPeaks(request: WaveformPeaksRequest): WaveformPeaksReport;
349
615
  export function waveformPeaks(
350
616
  samples: Float32Array,
351
617
  channels: number,
618
+ options?: WaveformPeaksOptions,
619
+ ): WaveformPeaksReport;
620
+ export function waveformPeaks(
621
+ samples: Float32Array | WaveformPeaksRequest,
622
+ channels?: number,
352
623
  options: WaveformPeaksOptions = {},
353
624
  ): WaveformPeaksReport {
354
- assertSamples('waveformPeaks', samples, options.validate !== false);
355
- if (channels <= 0 || samples.length % channels !== 0) {
625
+ const request =
626
+ samples instanceof Float32Array
627
+ ? { samples, channels: channels as number, ...options }
628
+ : samples;
629
+ assertSamples('waveformPeaks', request.samples, request.validate !== false);
630
+ if (request.channels <= 0 || request.samples.length % request.channels !== 0) {
356
631
  throw new RangeError('waveformPeaks: samples length must be a multiple of channels');
357
632
  }
358
- const samplesPerBucket = options.samplesPerBucket ?? 512;
633
+ const samplesPerBucket = request.samplesPerBucket ?? 512;
359
634
  if (samplesPerBucket <= 0) {
360
635
  throw new RangeError('waveformPeaks: samplesPerBucket must be > 0');
361
636
  }
362
- return requireModule().waveformPeaks(samples, channels, samplesPerBucket);
637
+ return requireModule().waveformPeaks(request.samples, request.channels, samplesPerBucket);
363
638
  }
364
639
 
365
640
  /** Compute waveform peak buckets for several zoom levels. */
641
+ export function waveformPeakPyramid(request: WaveformPeakPyramidRequest): WaveformPeaksReport[];
366
642
  export function waveformPeakPyramid(
367
643
  samples: Float32Array,
368
644
  channels: number,
645
+ options?: WaveformPeakPyramidOptions,
646
+ ): WaveformPeaksReport[];
647
+ export function waveformPeakPyramid(
648
+ samples: Float32Array | WaveformPeakPyramidRequest,
649
+ channels?: number,
369
650
  options: WaveformPeakPyramidOptions = {},
370
651
  ): WaveformPeaksReport[] {
371
- assertSamples('waveformPeakPyramid', samples, options.validate !== false);
372
- if (channels <= 0 || samples.length % channels !== 0) {
652
+ const request =
653
+ samples instanceof Float32Array
654
+ ? { samples, channels: channels as number, ...options }
655
+ : samples;
656
+ assertSamples('waveformPeakPyramid', request.samples, request.validate !== false);
657
+ if (request.channels <= 0 || request.samples.length % request.channels !== 0) {
373
658
  throw new RangeError('waveformPeakPyramid: samples length must be a multiple of channels');
374
659
  }
375
- const levels = options.samplesPerBucketLevels ?? [512, 1024, 2048, 4096];
660
+ const levels = request.samplesPerBucketLevels ?? [512, 1024, 2048, 4096];
376
661
  if (levels.length === 0 || levels.some((level) => level <= 0)) {
377
662
  throw new RangeError('waveformPeakPyramid: samplesPerBucketLevels must be non-empty and > 0');
378
663
  }
379
- return requireModule().waveformPeakPyramid(samples, channels, levels);
664
+ return requireModule().waveformPeakPyramid(request.samples, request.channels, levels);
380
665
  }