@libraz/libsonare 1.5.2 → 1.5.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.
- package/README.md +69 -725
- package/dist/index.d.ts +1155 -183
- package/dist/index.js +4924 -3935
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +29 -1
- package/dist/worklet.js +1141 -970
- package/dist/worklet.js.map +1 -1
- package/package.json +15 -3
- package/src/_chain_config.ts +46 -0
- package/src/audio.ts +24 -4
- package/src/codes.ts +27 -4
- package/src/effects_mastering.ts +25 -1
- package/src/effects_transform.ts +292 -46
- package/src/effects_voice_change.ts +46 -53
- package/src/feature_core.ts +316 -12
- package/src/feature_music.ts +371 -4
- package/src/feature_pitch.ts +61 -0
- package/src/feature_resample.ts +17 -2
- package/src/feature_spectral.ts +349 -2
- package/src/feature_spectrogram.ts +336 -0
- package/src/index.ts +130 -1
- package/src/mastering_chain.ts +305 -50
- package/src/mastering_core.ts +232 -16
- package/src/mastering_dynamics.ts +66 -10
- package/src/mastering_repair.ts +119 -7
- package/src/metering.ts +370 -83
- package/src/mixer.ts +9 -2
- package/src/mixing_oneshot.ts +25 -5
- package/src/module_state.ts +1 -1
- package/src/project_class.ts +30 -6
- package/src/project_internal.ts +12 -2
- package/src/project_types.ts +25 -2
- package/src/public_types_mastering.ts +114 -4
- package/src/public_types_spectral.ts +7 -0
- package/src/quick_analysis.ts +325 -117
- package/src/realtime_engine.ts +8 -0
- package/src/realtime_voice_changer.ts +79 -3
- package/src/sonare.js.d.ts +35 -0
- package/src/stream_analyzer.ts +20 -1
- package/src/stream_types.ts +12 -1
- package/src/validation.ts +17 -2
- package/src/worklet/engine-clips.ts +67 -2
- package/src/worklet/engine-processor.ts +33 -5
- package/src/worklet/engine.ts +7 -3
- package/src/worklet/guards.ts +3 -0
- 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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return requireModule().meteringTruePeakDb(samples, sampleRate,
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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,209 @@ 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
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
* Side / mid energy ratio
|
|
236
|
-
*
|
|
386
|
+
* Side / mid energy ratio in `[0, +Infinity)`: 0 = pure mono, ~1 = wide stereo,
|
|
387
|
+
* larger = increasingly decorrelated / out-of-phase. The value is unbounded and
|
|
388
|
+
* returns `Infinity` when the mid channel is silent (a mono-collapsed / fully
|
|
389
|
+
* out-of-phase signal).
|
|
237
390
|
*/
|
|
391
|
+
export function meteringStereoWidth(request: MeteringStereoRequest): number;
|
|
238
392
|
export function meteringStereoWidth(
|
|
239
393
|
left: Float32Array,
|
|
240
394
|
right: Float32Array,
|
|
395
|
+
sampleRate?: number,
|
|
396
|
+
options?: ValidateOptions,
|
|
397
|
+
): number;
|
|
398
|
+
export function meteringStereoWidth(
|
|
399
|
+
left: Float32Array | MeteringStereoRequest,
|
|
400
|
+
right?: Float32Array,
|
|
241
401
|
sampleRate = 22050,
|
|
242
402
|
options: ValidateOptions = {},
|
|
243
403
|
): number {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
404
|
+
const request =
|
|
405
|
+
left instanceof Float32Array
|
|
406
|
+
? { left, right: right as Float32Array, sampleRate, ...options }
|
|
407
|
+
: left;
|
|
408
|
+
const validate = request.validate !== false;
|
|
409
|
+
assertSamples('meteringStereoWidth', request.left, validate, 'left');
|
|
410
|
+
assertSamples('meteringStereoWidth', request.right, validate, 'right');
|
|
411
|
+
return requireModule().meteringStereoWidth(
|
|
412
|
+
request.left,
|
|
413
|
+
request.right,
|
|
414
|
+
request.sampleRate ?? 22050,
|
|
415
|
+
);
|
|
248
416
|
}
|
|
249
417
|
|
|
250
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* Mid/side vectorscope point series. By default emits one point per input
|
|
420
|
+
* sample; pass `maxPoints` to get a display-sized decimated point set (matching
|
|
421
|
+
* the Node `meteringVectorscope` shape).
|
|
422
|
+
*/
|
|
423
|
+
export function meteringVectorscope(request: MeteringStereoDecimatedRequest): VectorscopeReport;
|
|
251
424
|
export function meteringVectorscope(
|
|
252
425
|
left: Float32Array,
|
|
253
426
|
right: Float32Array,
|
|
427
|
+
sampleRate?: number,
|
|
428
|
+
options?: ScopeOptions,
|
|
429
|
+
): VectorscopeReport;
|
|
430
|
+
export function meteringVectorscope(
|
|
431
|
+
left: Float32Array | MeteringStereoDecimatedRequest,
|
|
432
|
+
right?: Float32Array,
|
|
254
433
|
sampleRate = 22050,
|
|
255
|
-
options:
|
|
434
|
+
options: ScopeOptions = {},
|
|
256
435
|
): VectorscopeReport {
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
436
|
+
const request =
|
|
437
|
+
left instanceof Float32Array
|
|
438
|
+
? { left, right: right as Float32Array, sampleRate, ...options }
|
|
439
|
+
: left;
|
|
440
|
+
const validate = request.validate !== false;
|
|
441
|
+
assertSamples('meteringVectorscope', request.left, validate, 'left');
|
|
442
|
+
assertSamples('meteringVectorscope', request.right, validate, 'right');
|
|
443
|
+
return requireModule().meteringVectorscopeDecimated(
|
|
444
|
+
request.left,
|
|
445
|
+
request.right,
|
|
446
|
+
request.sampleRate ?? 22050,
|
|
447
|
+
request.maxPoints ?? 0,
|
|
448
|
+
);
|
|
261
449
|
}
|
|
262
450
|
|
|
263
451
|
/**
|
|
264
|
-
* Display-sized mid/side vectorscope.
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
* Node
|
|
452
|
+
* Display-sized mid/side vectorscope.
|
|
453
|
+
*
|
|
454
|
+
* @deprecated Pass `maxPoints` to {@link meteringVectorscope} instead; it now
|
|
455
|
+
* folds `maxPoints` into the request, matching the Node surface. This alias is
|
|
456
|
+
* kept for backward compatibility and simply delegates.
|
|
268
457
|
*/
|
|
458
|
+
export function meteringVectorscopeDecimated(
|
|
459
|
+
request: MeteringStereoDecimatedRequest,
|
|
460
|
+
): VectorscopeReport;
|
|
269
461
|
export function meteringVectorscopeDecimated(
|
|
270
462
|
left: Float32Array,
|
|
271
463
|
right: Float32Array,
|
|
464
|
+
sampleRate?: number,
|
|
465
|
+
maxPoints?: number,
|
|
466
|
+
options?: ValidateOptions,
|
|
467
|
+
): VectorscopeReport;
|
|
468
|
+
export function meteringVectorscopeDecimated(
|
|
469
|
+
left: Float32Array | MeteringStereoDecimatedRequest,
|
|
470
|
+
right?: Float32Array,
|
|
272
471
|
sampleRate = 22050,
|
|
273
472
|
maxPoints = 0,
|
|
274
473
|
options: ValidateOptions = {},
|
|
275
474
|
): VectorscopeReport {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
475
|
+
const request =
|
|
476
|
+
left instanceof Float32Array
|
|
477
|
+
? { left, right: right as Float32Array, sampleRate, maxPoints, ...options }
|
|
478
|
+
: left;
|
|
479
|
+
const validate = request.validate !== false;
|
|
480
|
+
assertSamples('meteringVectorscopeDecimated', request.left, validate, 'left');
|
|
481
|
+
assertSamples('meteringVectorscopeDecimated', request.right, validate, 'right');
|
|
482
|
+
return requireModule().meteringVectorscopeDecimated(
|
|
483
|
+
request.left,
|
|
484
|
+
request.right,
|
|
485
|
+
request.sampleRate ?? 22050,
|
|
486
|
+
request.maxPoints ?? 0,
|
|
487
|
+
);
|
|
280
488
|
}
|
|
281
489
|
|
|
282
|
-
/**
|
|
490
|
+
/**
|
|
491
|
+
* Phase-scope point series plus summary stats. By default emits one point per
|
|
492
|
+
* input sample; pass `maxPoints` to decimate the point cloud for display
|
|
493
|
+
* (matching the Node `meteringPhaseScope` shape). The summary stats are always
|
|
494
|
+
* computed over the full-resolution signal.
|
|
495
|
+
*/
|
|
496
|
+
export function meteringPhaseScope(request: MeteringStereoDecimatedRequest): PhaseScopeReport;
|
|
283
497
|
export function meteringPhaseScope(
|
|
284
498
|
left: Float32Array,
|
|
285
499
|
right: Float32Array,
|
|
500
|
+
sampleRate?: number,
|
|
501
|
+
options?: ScopeOptions,
|
|
502
|
+
): PhaseScopeReport;
|
|
503
|
+
export function meteringPhaseScope(
|
|
504
|
+
left: Float32Array | MeteringStereoDecimatedRequest,
|
|
505
|
+
right?: Float32Array,
|
|
286
506
|
sampleRate = 22050,
|
|
287
|
-
options:
|
|
507
|
+
options: ScopeOptions = {},
|
|
288
508
|
): PhaseScopeReport {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
509
|
+
const request =
|
|
510
|
+
left instanceof Float32Array
|
|
511
|
+
? { left, right: right as Float32Array, sampleRate, ...options }
|
|
512
|
+
: left;
|
|
513
|
+
const validate = request.validate !== false;
|
|
514
|
+
assertSamples('meteringPhaseScope', request.left, validate, 'left');
|
|
515
|
+
assertSamples('meteringPhaseScope', request.right, validate, 'right');
|
|
516
|
+
return requireModule().meteringPhaseScopeDecimated(
|
|
517
|
+
request.left,
|
|
518
|
+
request.right,
|
|
519
|
+
request.sampleRate ?? 22050,
|
|
520
|
+
request.maxPoints ?? 0,
|
|
521
|
+
);
|
|
293
522
|
}
|
|
294
523
|
|
|
295
524
|
/**
|
|
296
|
-
* Display-sized phase scope.
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
525
|
+
* Display-sized phase scope.
|
|
526
|
+
*
|
|
527
|
+
* @deprecated Pass `maxPoints` to {@link meteringPhaseScope} instead; it now
|
|
528
|
+
* folds `maxPoints` into the request, matching the Node surface. This alias is
|
|
529
|
+
* kept for backward compatibility and simply delegates.
|
|
301
530
|
*/
|
|
531
|
+
export function meteringPhaseScopeDecimated(
|
|
532
|
+
request: MeteringStereoDecimatedRequest,
|
|
533
|
+
): PhaseScopeReport;
|
|
302
534
|
export function meteringPhaseScopeDecimated(
|
|
303
535
|
left: Float32Array,
|
|
304
536
|
right: Float32Array,
|
|
537
|
+
sampleRate?: number,
|
|
538
|
+
maxPoints?: number,
|
|
539
|
+
options?: ValidateOptions,
|
|
540
|
+
): PhaseScopeReport;
|
|
541
|
+
export function meteringPhaseScopeDecimated(
|
|
542
|
+
left: Float32Array | MeteringStereoDecimatedRequest,
|
|
543
|
+
right?: Float32Array,
|
|
305
544
|
sampleRate = 22050,
|
|
306
545
|
maxPoints = 0,
|
|
307
546
|
options: ValidateOptions = {},
|
|
308
547
|
): PhaseScopeReport {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
548
|
+
const request =
|
|
549
|
+
left instanceof Float32Array
|
|
550
|
+
? { left, right: right as Float32Array, sampleRate, maxPoints, ...options }
|
|
551
|
+
: left;
|
|
552
|
+
const validate = request.validate !== false;
|
|
553
|
+
assertSamples('meteringPhaseScopeDecimated', request.left, validate, 'left');
|
|
554
|
+
assertSamples('meteringPhaseScopeDecimated', request.right, validate, 'right');
|
|
555
|
+
return requireModule().meteringPhaseScopeDecimated(
|
|
556
|
+
request.left,
|
|
557
|
+
request.right,
|
|
558
|
+
request.sampleRate ?? 22050,
|
|
559
|
+
request.maxPoints ?? 0,
|
|
560
|
+
);
|
|
313
561
|
}
|
|
314
562
|
|
|
315
563
|
/**
|
|
@@ -318,14 +566,20 @@ export function meteringPhaseScopeDecimated(
|
|
|
318
566
|
* are averaged). For a true single-frame snapshot, use
|
|
319
567
|
* {@link meteringSpectrumFrame}.
|
|
320
568
|
*/
|
|
569
|
+
export function meteringSpectrum(request: MeteringSpectrumRequest): SpectrumReport;
|
|
321
570
|
export function meteringSpectrum(
|
|
322
571
|
samples: Float32Array,
|
|
323
|
-
sampleRate
|
|
572
|
+
sampleRate?: number,
|
|
324
573
|
options?: SpectrumOptions & ValidateOptions,
|
|
574
|
+
): SpectrumReport;
|
|
575
|
+
export function meteringSpectrum(
|
|
576
|
+
samples: Float32Array | MeteringSpectrumRequest,
|
|
577
|
+
sampleRate = 22050,
|
|
578
|
+
options: SpectrumOptions & ValidateOptions = {},
|
|
325
579
|
): SpectrumReport {
|
|
326
|
-
const
|
|
327
|
-
assertSamples('meteringSpectrum', samples, validate);
|
|
328
|
-
return requireModule().meteringSpectrum(samples, sampleRate
|
|
580
|
+
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
581
|
+
assertSamples('meteringSpectrum', request.samples, request.validate !== false);
|
|
582
|
+
return requireModule().meteringSpectrum(request.samples, request.sampleRate ?? 22050, request);
|
|
329
583
|
}
|
|
330
584
|
|
|
331
585
|
/**
|
|
@@ -334,47 +588,80 @@ export function meteringSpectrum(
|
|
|
334
588
|
* time-averaged like {@link meteringSpectrum}. The analysis frame spans
|
|
335
589
|
* `[frameOffset, frameOffset + nFft)`; samples past the end are zero-padded.
|
|
336
590
|
*/
|
|
591
|
+
export function meteringSpectrumFrame(request: MeteringSpectrumFrameRequest): SpectrumReport;
|
|
337
592
|
export function meteringSpectrumFrame(
|
|
338
593
|
samples: Float32Array,
|
|
594
|
+
sampleRate?: number,
|
|
595
|
+
frameOffset?: number,
|
|
596
|
+
options?: SpectrumOptions & ValidateOptions,
|
|
597
|
+
): SpectrumReport;
|
|
598
|
+
export function meteringSpectrumFrame(
|
|
599
|
+
samples: Float32Array | MeteringSpectrumFrameRequest,
|
|
339
600
|
sampleRate = 22050,
|
|
340
601
|
frameOffset = 0,
|
|
341
|
-
options
|
|
602
|
+
options: SpectrumOptions & ValidateOptions = {},
|
|
342
603
|
): SpectrumReport {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
604
|
+
const request =
|
|
605
|
+
samples instanceof Float32Array ? { samples, sampleRate, frameOffset, ...options } : samples;
|
|
606
|
+
assertSamples('meteringSpectrumFrame', request.samples, request.validate !== false);
|
|
607
|
+
return requireModule().meteringSpectrumFrame(
|
|
608
|
+
request.samples,
|
|
609
|
+
request.sampleRate ?? 22050,
|
|
610
|
+
request.frameOffset ?? 0,
|
|
611
|
+
request,
|
|
612
|
+
);
|
|
346
613
|
}
|
|
347
614
|
|
|
348
615
|
/** Compute per-channel min/max waveform buckets from interleaved audio. */
|
|
616
|
+
export function waveformPeaks(request: WaveformPeaksRequest): WaveformPeaksReport;
|
|
349
617
|
export function waveformPeaks(
|
|
350
618
|
samples: Float32Array,
|
|
351
619
|
channels: number,
|
|
620
|
+
options?: WaveformPeaksOptions,
|
|
621
|
+
): WaveformPeaksReport;
|
|
622
|
+
export function waveformPeaks(
|
|
623
|
+
samples: Float32Array | WaveformPeaksRequest,
|
|
624
|
+
channels?: number,
|
|
352
625
|
options: WaveformPeaksOptions = {},
|
|
353
626
|
): WaveformPeaksReport {
|
|
354
|
-
|
|
355
|
-
|
|
627
|
+
const request =
|
|
628
|
+
samples instanceof Float32Array
|
|
629
|
+
? { samples, channels: channels as number, ...options }
|
|
630
|
+
: samples;
|
|
631
|
+
assertSamples('waveformPeaks', request.samples, request.validate !== false);
|
|
632
|
+
if (request.channels <= 0 || request.samples.length % request.channels !== 0) {
|
|
356
633
|
throw new RangeError('waveformPeaks: samples length must be a multiple of channels');
|
|
357
634
|
}
|
|
358
|
-
const samplesPerBucket =
|
|
635
|
+
const samplesPerBucket = request.samplesPerBucket ?? 512;
|
|
359
636
|
if (samplesPerBucket <= 0) {
|
|
360
637
|
throw new RangeError('waveformPeaks: samplesPerBucket must be > 0');
|
|
361
638
|
}
|
|
362
|
-
return requireModule().waveformPeaks(samples, channels, samplesPerBucket);
|
|
639
|
+
return requireModule().waveformPeaks(request.samples, request.channels, samplesPerBucket);
|
|
363
640
|
}
|
|
364
641
|
|
|
365
642
|
/** Compute waveform peak buckets for several zoom levels. */
|
|
643
|
+
export function waveformPeakPyramid(request: WaveformPeakPyramidRequest): WaveformPeaksReport[];
|
|
366
644
|
export function waveformPeakPyramid(
|
|
367
645
|
samples: Float32Array,
|
|
368
646
|
channels: number,
|
|
647
|
+
options?: WaveformPeakPyramidOptions,
|
|
648
|
+
): WaveformPeaksReport[];
|
|
649
|
+
export function waveformPeakPyramid(
|
|
650
|
+
samples: Float32Array | WaveformPeakPyramidRequest,
|
|
651
|
+
channels?: number,
|
|
369
652
|
options: WaveformPeakPyramidOptions = {},
|
|
370
653
|
): WaveformPeaksReport[] {
|
|
371
|
-
|
|
372
|
-
|
|
654
|
+
const request =
|
|
655
|
+
samples instanceof Float32Array
|
|
656
|
+
? { samples, channels: channels as number, ...options }
|
|
657
|
+
: samples;
|
|
658
|
+
assertSamples('waveformPeakPyramid', request.samples, request.validate !== false);
|
|
659
|
+
if (request.channels <= 0 || request.samples.length % request.channels !== 0) {
|
|
373
660
|
throw new RangeError('waveformPeakPyramid: samples length must be a multiple of channels');
|
|
374
661
|
}
|
|
375
|
-
const levels =
|
|
662
|
+
const levels = request.samplesPerBucketLevels ?? [512, 1024, 2048, 4096];
|
|
376
663
|
if (levels.length === 0 || levels.some((level) => level <= 0)) {
|
|
377
664
|
throw new RangeError('waveformPeakPyramid: samplesPerBucketLevels must be non-empty and > 0');
|
|
378
665
|
}
|
|
379
|
-
return requireModule().waveformPeakPyramid(samples, channels, levels);
|
|
666
|
+
return requireModule().waveformPeakPyramid(request.samples, request.channels, levels);
|
|
380
667
|
}
|
package/src/mixer.ts
CHANGED
|
@@ -55,9 +55,11 @@ export interface MixerRealtimeBuffer {
|
|
|
55
55
|
*/
|
|
56
56
|
export class Mixer {
|
|
57
57
|
private mixer: import('./sonare.js').WasmMixer;
|
|
58
|
+
private readonly blockSize: number;
|
|
58
59
|
|
|
59
|
-
private constructor(mixer: import('./sonare.js').WasmMixer) {
|
|
60
|
+
private constructor(mixer: import('./sonare.js').WasmMixer, blockSize: number) {
|
|
60
61
|
this.mixer = mixer;
|
|
62
|
+
this.blockSize = blockSize;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/**
|
|
@@ -69,7 +71,7 @@ export class Mixer {
|
|
|
69
71
|
*/
|
|
70
72
|
static fromSceneJson(json: string, sampleRate = 48000, blockSize = 512): Mixer {
|
|
71
73
|
const module = getSonareModule();
|
|
72
|
-
return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize));
|
|
74
|
+
return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/** Rebuild and compile the routing graph from the current scene topology. */
|
|
@@ -536,6 +538,11 @@ export class Mixer {
|
|
|
536
538
|
* master (`left`, `right`, `sampleRate`).
|
|
537
539
|
*/
|
|
538
540
|
drainTailStereo(numSamples: number): MixerProcessResult {
|
|
541
|
+
if (!Number.isSafeInteger(numSamples) || numSamples <= 0 || numSamples > this.blockSize) {
|
|
542
|
+
throw new RangeError(
|
|
543
|
+
`Mixer.drainTailStereo: numSamples must be an integer in [1, ${this.blockSize}]`,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
539
546
|
return this.mixer.drainTailStereo(numSamples);
|
|
540
547
|
}
|
|
541
548
|
|