@libraz/libsonare 1.2.2 → 1.3.0

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 (44) hide show
  1. package/README.md +38 -1
  2. package/dist/index.d.ts +1 -2722
  3. package/dist/index.js +3659 -1896
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare-rt-module.js +1 -1
  6. package/dist/sonare-rt.js +1 -1
  7. package/dist/sonare-rt.wasm +0 -0
  8. package/dist/sonare.js +1 -1
  9. package/dist/sonare.wasm +0 -0
  10. package/dist/worklet.d.ts +4827 -455
  11. package/dist/worklet.js +1076 -494
  12. package/dist/worklet.js.map +1 -1
  13. package/package.json +2 -1
  14. package/src/analysis_helpers.ts +152 -0
  15. package/src/audio.ts +493 -0
  16. package/src/codes.ts +56 -0
  17. package/src/effects_mastering.ts +964 -0
  18. package/src/feature_core.ts +248 -0
  19. package/src/feature_music.ts +419 -0
  20. package/src/feature_pitch.ts +80 -0
  21. package/src/feature_resample.ts +21 -0
  22. package/src/feature_spectral.ts +330 -0
  23. package/src/feature_spectrogram.ts +454 -0
  24. package/src/features.ts +84 -0
  25. package/src/index.ts +352 -4793
  26. package/src/live_audio.ts +45 -0
  27. package/src/metering.ts +380 -0
  28. package/src/mixer.ts +523 -0
  29. package/src/module_state.ts +14 -0
  30. package/src/opfs_clip_pages.ts +188 -0
  31. package/src/project.ts +1614 -0
  32. package/src/public_types.ts +244 -2
  33. package/src/quick_analysis.ts +508 -0
  34. package/src/realtime_engine.ts +667 -0
  35. package/src/realtime_voice_changer.ts +275 -0
  36. package/src/scale.ts +42 -0
  37. package/src/sonare.js.d.ts +386 -4
  38. package/src/stream_analyzer.ts +275 -0
  39. package/src/stream_types.ts +29 -1
  40. package/src/streaming_mixing.ts +18 -0
  41. package/src/streaming_processors.ts +335 -0
  42. package/src/validation.ts +82 -0
  43. package/src/web_midi.ts +367 -0
  44. package/src/worklet.ts +525 -81
@@ -0,0 +1,964 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type {
3
+ HpssResult,
4
+ MasteringChainConfig,
5
+ MasteringChainResult,
6
+ MasteringOptions,
7
+ MasteringPreset,
8
+ MasteringProcessorParams,
9
+ MasteringResult,
10
+ MasteringStereoChainResult,
11
+ MasteringStereoResult,
12
+ MixOptions,
13
+ MixResult,
14
+ NoteStretchOptions,
15
+ PairAnalysis,
16
+ PairProcessor,
17
+ RealtimeVoiceChangerConfigInput,
18
+ SoloProcessor,
19
+ StereoAnalysis,
20
+ StreamingPlatform,
21
+ } from './public_types';
22
+ import type { ProgressCallback } from './sonare.js';
23
+ import { RealtimeVoiceChanger } from './streaming_mixing';
24
+ import type { ValidateOptions } from './validation';
25
+ import { assertSamples } from './validation';
26
+
27
+ function requireModule() {
28
+ return getSonareModule();
29
+ }
30
+
31
+ // ============================================================================
32
+ // Effects
33
+ // ============================================================================
34
+
35
+ /**
36
+ * Perform Harmonic-Percussive Source Separation (HPSS).
37
+ *
38
+ * @param samples - Audio samples (mono, float32)
39
+ * @param sampleRate - Sample rate in Hz (default: 22050)
40
+ * @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
41
+ * @param kernelPercussive - Vertical median filter size for percussive (default: 31)
42
+ * @returns Separated harmonic and percussive components
43
+ */
44
+ export function hpss(
45
+ samples: Float32Array,
46
+ sampleRate = 22050,
47
+ kernelHarmonic = 31,
48
+ kernelPercussive = 31,
49
+ ): HpssResult {
50
+ return requireModule().hpss(samples, sampleRate, kernelHarmonic, kernelPercussive);
51
+ }
52
+
53
+ /**
54
+ * Extract harmonic component from audio.
55
+ *
56
+ * @param samples - Audio samples (mono, float32)
57
+ * @param sampleRate - Sample rate in Hz
58
+ * @returns Harmonic component
59
+ */
60
+ export function harmonic(
61
+ samples: Float32Array,
62
+ sampleRate: number,
63
+ options: ValidateOptions = {},
64
+ ): Float32Array {
65
+ assertSamples('harmonic', samples, options.validate !== false);
66
+ return requireModule().harmonic(samples, sampleRate);
67
+ }
68
+
69
+ /**
70
+ * Extract percussive component from audio.
71
+ *
72
+ * @param samples - Audio samples (mono, float32)
73
+ * @param sampleRate - Sample rate in Hz
74
+ * @returns Percussive component
75
+ */
76
+ export function percussive(
77
+ samples: Float32Array,
78
+ sampleRate: number,
79
+ options: ValidateOptions = {},
80
+ ): Float32Array {
81
+ assertSamples('percussive', samples, options.validate !== false);
82
+ return requireModule().percussive(samples, sampleRate);
83
+ }
84
+
85
+ /**
86
+ * Time-stretch audio without changing pitch.
87
+ *
88
+ * @param samples - Audio samples (mono, float32)
89
+ * @param sampleRate - Sample rate in Hz
90
+ * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
91
+ * @returns Time-stretched audio
92
+ */
93
+ export function timeStretch(
94
+ samples: Float32Array,
95
+ sampleRate: number,
96
+ rate: number,
97
+ options: ValidateOptions = {},
98
+ ): Float32Array {
99
+ assertSamples('timeStretch', samples, options.validate !== false);
100
+ return requireModule().timeStretch(samples, sampleRate, rate);
101
+ }
102
+
103
+ /**
104
+ * Pitch-shift audio without changing duration.
105
+ *
106
+ * @param samples - Audio samples (mono, float32)
107
+ * @param sampleRate - Sample rate in Hz
108
+ * @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
109
+ * @returns Pitch-shifted audio
110
+ */
111
+ export function pitchShift(
112
+ samples: Float32Array,
113
+ sampleRate: number,
114
+ semitones: number,
115
+ options: ValidateOptions = {},
116
+ ): Float32Array {
117
+ assertSamples('pitchShift', samples, options.validate !== false);
118
+ return requireModule().pitchShift(samples, sampleRate, semitones);
119
+ }
120
+
121
+ /**
122
+ * Pitch-correct audio from a current MIDI note to a target MIDI note.
123
+ *
124
+ * @param samples - Audio samples (mono, float32)
125
+ * @param sampleRate - Sample rate in Hz
126
+ * @param currentMidi - Detected/current MIDI note number
127
+ * @param targetMidi - Desired MIDI note number
128
+ * @returns Pitch-corrected audio
129
+ */
130
+ export function pitchCorrectToMidi(
131
+ samples: Float32Array,
132
+ sampleRate = 22050,
133
+ currentMidi = 69.0,
134
+ targetMidi = 69.0,
135
+ options: ValidateOptions = {},
136
+ ): Float32Array {
137
+ assertSamples('pitchCorrectToMidi', samples, options.validate !== false);
138
+ return requireModule().pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
139
+ }
140
+
141
+ /**
142
+ * Contour-following ("time-varying") pitch correction toward a MIDI target.
143
+ *
144
+ * Unlike {@link pitchCorrectToMidi} (a single constant transpose), this follows
145
+ * the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
146
+ * toward `targetMidi`, so vibrato/drift in the source is tracked rather than
147
+ * flattened. `voiced` (non-zero = voiced) and `voicedProb` ([0,1]) are optional;
148
+ * omitting them treats every frame as voiced.
149
+ *
150
+ * @param samples - Audio samples (mono, float32)
151
+ * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
152
+ * @param targetMidi - Desired MIDI note number
153
+ * @param sampleRate - Sample rate in Hz
154
+ * @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
155
+ * @param voiced - Optional per-frame voiced flags (non-zero = voiced)
156
+ * @param voicedProb - Optional per-frame voicing probability in [0, 1]
157
+ * @returns Pitch-corrected audio
158
+ */
159
+ export function pitchCorrectToMidiTimevarying(
160
+ samples: Float32Array,
161
+ f0Hz: Float32Array,
162
+ targetMidi: number,
163
+ sampleRate = 22050,
164
+ hopLength = 512,
165
+ voiced?: Int32Array,
166
+ voicedProb?: Float32Array,
167
+ options: ValidateOptions = {},
168
+ ): Float32Array {
169
+ assertSamples('pitchCorrectToMidiTimevarying', samples, options.validate !== false);
170
+ if (voiced && voiced.length !== f0Hz.length) {
171
+ throw new RangeError('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length');
172
+ }
173
+ if (voicedProb && voicedProb.length !== f0Hz.length) {
174
+ throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
175
+ }
176
+ // The embind layer reads the companion arrays as Float32Array (voiced uses
177
+ // 0.0/1.0); convert here so a single native conversion path suffices.
178
+ const voicedF32 = voiced ? Float32Array.from(voiced) : undefined;
179
+ return requireModule().pitchCorrectToMidiTimevarying(
180
+ samples,
181
+ sampleRate,
182
+ f0Hz,
183
+ targetMidi,
184
+ hopLength,
185
+ voicedF32,
186
+ voicedProb,
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Time-stretch a note region between two sample offsets without changing pitch.
192
+ *
193
+ * @param samples - Audio samples (mono, float32)
194
+ * @param sampleRate - Sample rate in Hz
195
+ * @param onsetSample - Note onset position in samples
196
+ * @param offsetSample - Note offset position in samples
197
+ * @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
198
+ * @returns Audio with the note region stretched
199
+ */
200
+ export function noteStretch(
201
+ samples: Float32Array,
202
+ sampleRate = 22050,
203
+ options: NoteStretchOptions & ValidateOptions = {},
204
+ ): Float32Array {
205
+ assertSamples('noteStretch', samples, options.validate !== false);
206
+ return requireModule().noteStretch(
207
+ samples,
208
+ sampleRate,
209
+ options.onsetSample ?? 0,
210
+ options.offsetSample ?? 0,
211
+ options.stretchRatio ?? 1.0,
212
+ );
213
+ }
214
+
215
+ /** Options for {@link voiceChange}. All fields are optional. */
216
+ export interface VoiceChangeOptions extends ValidateOptions {
217
+ /** Pitch shift in semitones (negative = down). Default 0. */
218
+ pitchSemitones?: number;
219
+ /** Formant scale factor (>1 brightens, <1 darkens). Default 1. */
220
+ formantFactor?: number;
221
+ }
222
+
223
+ /**
224
+ * Apply a voice change by shifting pitch and formants independently.
225
+ *
226
+ * @param samples - Audio samples (mono, float32)
227
+ * @param sampleRate - Sample rate in Hz
228
+ * @param options - Pitch/formant settings ({@link VoiceChangeOptions})
229
+ * @returns Voice-changed audio
230
+ */
231
+ export function voiceChange(
232
+ samples: Float32Array,
233
+ sampleRate = 22050,
234
+ options: VoiceChangeOptions = {},
235
+ ): Float32Array {
236
+ assertSamples('voiceChange', samples, options.validate !== false);
237
+ return requireModule().voiceChange(
238
+ samples,
239
+ sampleRate,
240
+ options.pitchSemitones ?? 0.0,
241
+ options.formantFactor ?? 1.0,
242
+ );
243
+ }
244
+
245
+ /** Options for the offline {@link voiceChangeRealtime} convenience wrapper. */
246
+ export interface VoiceChangeRealtimeOptions extends ValidateOptions {
247
+ sampleRate?: number;
248
+ /** Voice-changer preset id or full config object. */
249
+ preset?: RealtimeVoiceChangerConfigInput;
250
+ /** Channel count (1 = mono, 2 = interleaved stereo). */
251
+ channels?: 1 | 2;
252
+ /** Block size for the internal render loop (default 512). */
253
+ blockSize?: number;
254
+ }
255
+
256
+ function latencyCompensatedVoiceChange(
257
+ changer: RealtimeVoiceChanger,
258
+ samples: Float32Array,
259
+ channels: 1 | 2,
260
+ blockFrames: number,
261
+ ): Float32Array {
262
+ const latencyFrames = Math.max(0, changer.latencySamples());
263
+ if (channels === 1) {
264
+ const total = samples.length + latencyFrames;
265
+ const input = new Float32Array(total);
266
+ input.set(samples);
267
+ const processed = new Float32Array(total);
268
+ for (let offset = 0; offset < total; offset += blockFrames) {
269
+ const block = input.subarray(offset, Math.min(offset + blockFrames, total));
270
+ processed.set(changer.processMono(block), offset);
271
+ }
272
+ return processed.slice(latencyFrames, latencyFrames + samples.length);
273
+ }
274
+
275
+ const frames = samples.length / 2;
276
+ const totalFrames = frames + latencyFrames;
277
+ const input = new Float32Array(totalFrames * 2);
278
+ input.set(samples);
279
+ const processed = new Float32Array(totalFrames * 2);
280
+ const frameStride = blockFrames * 2;
281
+ for (let offset = 0; offset < input.length; offset += frameStride) {
282
+ const block = input.subarray(offset, Math.min(offset + frameStride, input.length));
283
+ processed.set(changer.processInterleaved(block, 2), offset);
284
+ }
285
+ const start = latencyFrames * 2;
286
+ return processed.slice(start, start + samples.length);
287
+ }
288
+
289
+ /**
290
+ * Applies the realtime voice-changer chain to a whole buffer in one call.
291
+ *
292
+ * Constructs and prepares a {@link RealtimeVoiceChanger}, runs the block loop
293
+ * for the caller, then disposes it — matching the Python `voice_change_realtime`
294
+ * and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
295
+ * plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
296
+ *
297
+ * @returns The processed buffer (same layout/length as the input).
298
+ */
299
+ export function voiceChangeRealtime(
300
+ samples: Float32Array,
301
+ options: VoiceChangeRealtimeOptions = {},
302
+ ): Float32Array {
303
+ assertSamples('voiceChangeRealtime', samples, options.validate !== false);
304
+ const channels = options.channels ?? 1;
305
+ if (channels !== 1 && channels !== 2) {
306
+ throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
307
+ }
308
+ if (channels === 2 && samples.length % 2 !== 0) {
309
+ throw new Error('voiceChangeRealtime: stereo input length must be a multiple of 2.');
310
+ }
311
+ // 48000 matches the Python voice_change_realtime and Node voiceChangeRealtime
312
+ // convenience wrappers (and the RealtimeVoiceChanger default).
313
+ const sampleRate = options.sampleRate ?? 48000;
314
+ const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
315
+ const changer = new RealtimeVoiceChanger(options.preset ?? 'neutral-monitor');
316
+ try {
317
+ changer.prepare(sampleRate, blockSize, channels);
318
+ return latencyCompensatedVoiceChange(changer, samples, channels, blockSize);
319
+ } finally {
320
+ changer.delete();
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Normalize audio to target peak level.
326
+ *
327
+ * @param samples - Audio samples (mono, float32)
328
+ * @param sampleRate - Sample rate in Hz
329
+ * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
330
+ * @returns Normalized audio
331
+ */
332
+ export function normalize(
333
+ samples: Float32Array,
334
+ sampleRate: number,
335
+ targetDb = 0.0,
336
+ options: ValidateOptions = {},
337
+ ): Float32Array {
338
+ assertSamples('normalize', samples, options.validate !== false);
339
+ return requireModule().normalize(samples, sampleRate, targetDb);
340
+ }
341
+
342
+ /**
343
+ * Apply mastering loudness normalization with a true-peak ceiling.
344
+ *
345
+ * @param samples - Audio samples (mono, float32)
346
+ * @param sampleRate - Sample rate in Hz (default: 22050)
347
+ * @param options - Loudness/ceiling settings ({@link MasteringOptions})
348
+ * @returns Processed audio and loudness metadata
349
+ */
350
+ export function mastering(
351
+ samples: Float32Array,
352
+ sampleRate = 22050,
353
+ options: MasteringOptions = {},
354
+ ): MasteringResult {
355
+ return requireModule().mastering(
356
+ samples,
357
+ sampleRate,
358
+ options.targetLufs ?? -14.0,
359
+ options.ceilingDb ?? -1.0,
360
+ options.truePeakOversample ?? 4,
361
+ );
362
+ }
363
+
364
+ export function masteringProcessorNames(): SoloProcessor[] {
365
+ return requireModule().masteringProcessorNames() as SoloProcessor[];
366
+ }
367
+
368
+ /**
369
+ * Names of the insert processors the mastering chain can instantiate by name
370
+ * (`mastering::api::insert_factory_names`). Mirrors the C-ABI
371
+ * `sonare_mastering_insert_names` (which joins this list) as a `string[]`.
372
+ */
373
+ export function masteringInsertNames(): string[] {
374
+ return (
375
+ requireModule() as unknown as { masteringInsertNames: () => string[] }
376
+ ).masteringInsertNames();
377
+ }
378
+
379
+ export function masteringPairProcessorNames(): PairProcessor[] {
380
+ return requireModule().masteringPairProcessorNames() as PairProcessor[];
381
+ }
382
+
383
+ export function masteringPairAnalysisNames(): PairAnalysis[] {
384
+ return requireModule().masteringPairAnalysisNames() as PairAnalysis[];
385
+ }
386
+
387
+ export function masteringStereoAnalysisNames(): StereoAnalysis[] {
388
+ return requireModule().masteringStereoAnalysisNames() as StereoAnalysis[];
389
+ }
390
+
391
+ export function masteringProcess(
392
+ processorName: SoloProcessor,
393
+ samples: Float32Array,
394
+ sampleRate = 22050,
395
+ params: MasteringProcessorParams = {},
396
+ ): MasteringResult {
397
+ return requireModule().masteringProcess(processorName, samples, sampleRate, params);
398
+ }
399
+
400
+ export function masteringProcessStereo(
401
+ processorName: SoloProcessor,
402
+ left: Float32Array,
403
+ right: Float32Array,
404
+ sampleRate = 22050,
405
+ params: MasteringProcessorParams = {},
406
+ ): MasteringStereoResult {
407
+ if (left.length !== right.length) {
408
+ throw new Error('Stereo channel lengths must match.');
409
+ }
410
+ return requireModule().masteringProcessStereo(processorName, left, right, sampleRate, params);
411
+ }
412
+
413
+ /**
414
+ * Apply a two-input `match.*` processor. `source` and `reference` may have
415
+ * independent lengths — the match primitives consume each buffer at its own
416
+ * length.
417
+ */
418
+ export function masteringPairProcess(
419
+ processorName: PairProcessor,
420
+ source: Float32Array,
421
+ reference: Float32Array,
422
+ sampleRate = 22050,
423
+ params: MasteringProcessorParams = {},
424
+ ): MasteringResult {
425
+ return requireModule().masteringPairProcess(processorName, source, reference, sampleRate, params);
426
+ }
427
+
428
+ /**
429
+ * Analyze a `source` against a `reference` with a two-input analysis. The two
430
+ * buffers may have independent lengths.
431
+ */
432
+ export function masteringPairAnalyze(
433
+ analysisName: PairAnalysis,
434
+ source: Float32Array,
435
+ reference: Float32Array,
436
+ sampleRate = 22050,
437
+ params: MasteringProcessorParams = {},
438
+ ): string {
439
+ return requireModule().masteringPairAnalyze(analysisName, source, reference, sampleRate, params);
440
+ }
441
+
442
+ export function masteringStereoAnalyze(
443
+ analysisName: StereoAnalysis,
444
+ left: Float32Array,
445
+ right: Float32Array,
446
+ sampleRate = 22050,
447
+ params: MasteringProcessorParams = {},
448
+ ): string {
449
+ return requireModule().masteringStereoAnalyze(analysisName, left, right, sampleRate, params);
450
+ }
451
+
452
+ export function masteringAssistantSuggest(
453
+ samples: Float32Array,
454
+ sampleRate = 22050,
455
+ params: MasteringProcessorParams = {},
456
+ ): string {
457
+ return requireModule().masteringAssistantSuggest(samples, sampleRate, params);
458
+ }
459
+
460
+ export function masteringAudioProfile(
461
+ samples: Float32Array,
462
+ sampleRate = 22050,
463
+ params: MasteringProcessorParams = {},
464
+ ): string {
465
+ return requireModule().masteringAudioProfile(samples, sampleRate, params);
466
+ }
467
+
468
+ export function masteringStreamingPreview(
469
+ samples: Float32Array,
470
+ sampleRate = 22050,
471
+ platforms: StreamingPlatform[] = [],
472
+ ): string {
473
+ return requireModule().masteringStreamingPreview(samples, sampleRate, platforms);
474
+ }
475
+
476
+ // ============================================================================
477
+ // Mastering repair (declick, denoise_classical, declip, decrackle, dehum,
478
+ // dereverb_classical, trim_silence) — hand-written bindings.
479
+ // ============================================================================
480
+
481
+ /** Options for `masteringRepairDeclick`. */
482
+ export interface DeclickOptions {
483
+ threshold?: number;
484
+ neighborRatio?: number;
485
+ maxClickSamples?: number;
486
+ lpcOrder?: number;
487
+ residualRatio?: number;
488
+ }
489
+
490
+ /** Algorithms accepted by `masteringRepairDenoiseClassical`. */
491
+ export type DenoiseClassicalMode = 'logMmse' | 'mmseStsa' | 'spectralSubtraction';
492
+
493
+ /** Noise PSD estimators accepted by `masteringRepairDenoiseClassical`. */
494
+ export type DenoiseClassicalNoiseEstimator = 'quantile' | 'mcra' | 'imcra';
495
+
496
+ /** Options for `masteringRepairDenoiseClassical`. */
497
+ export interface DenoiseClassicalOptions {
498
+ mode?: DenoiseClassicalMode;
499
+ noiseEstimator?: DenoiseClassicalNoiseEstimator;
500
+ nFft?: number;
501
+ hopLength?: number;
502
+ ddAlpha?: number;
503
+ gainFloor?: number;
504
+ overSubtraction?: number;
505
+ spectralFloor?: number;
506
+ noiseEstimationQuantile?: number;
507
+ speechPresenceGain?: boolean;
508
+ gainSmoothing?: boolean;
509
+ }
510
+
511
+ /** Offline LPC-based declicker. */
512
+ export function masteringRepairDeclick(
513
+ samples: Float32Array,
514
+ sampleRate: number,
515
+ options: DeclickOptions = {},
516
+ ): Float32Array {
517
+ return requireModule().masteringRepairDeclick(samples, sampleRate, options);
518
+ }
519
+
520
+ /** Offline STFT-domain classical denoiser (LogMMSE / MMSE-STSA / SpectralSubtraction). */
521
+ export function masteringRepairDenoiseClassical(
522
+ samples: Float32Array,
523
+ sampleRate: number,
524
+ options: DenoiseClassicalOptions = {},
525
+ ): Float32Array {
526
+ return requireModule().masteringRepairDenoiseClassical(samples, sampleRate, options);
527
+ }
528
+
529
+ /** Options for `masteringRepairDeclip`. */
530
+ export interface DeclipOptions {
531
+ clipThreshold?: number;
532
+ lpcOrder?: number;
533
+ iterations?: number;
534
+ lpcBlend?: number;
535
+ }
536
+
537
+ /** Algorithms accepted by `masteringRepairDecrackle`. */
538
+ export type DecrackleMode = 'median' | 'waveletShrinkage';
539
+
540
+ /** Options for `masteringRepairDecrackle`. */
541
+ export interface DecrackleOptions {
542
+ threshold?: number;
543
+ mode?: DecrackleMode;
544
+ levels?: number;
545
+ }
546
+
547
+ /** Options for `masteringRepairDehum`. */
548
+ export interface DehumOptions {
549
+ fundamentalHz?: number;
550
+ harmonics?: number;
551
+ q?: number;
552
+ adaptive?: boolean;
553
+ searchRangeHz?: number;
554
+ adaptation?: number;
555
+ frameSize?: number;
556
+ pllBandwidth?: number;
557
+ }
558
+
559
+ /** Options for `masteringRepairDereverbClassical`. */
560
+ export interface DereverbClassicalOptions {
561
+ threshold?: number;
562
+ attenuation?: number;
563
+ nFft?: number;
564
+ hopLength?: number;
565
+ t60Sec?: number;
566
+ lateDelayMs?: number;
567
+ overSubtraction?: number;
568
+ spectralFloor?: number;
569
+ wpeEnabled?: boolean;
570
+ wpeIterations?: number;
571
+ wpeTaps?: number;
572
+ wpeStrength?: number;
573
+ }
574
+
575
+ /** Trimming modes accepted by `masteringRepairTrimSilence`. */
576
+ export type TrimSilenceMode = 'peak' | 'lufsGated';
577
+
578
+ /** Options for `masteringRepairTrimSilence`. */
579
+ export interface TrimSilenceOptions {
580
+ threshold?: number;
581
+ paddingSamples?: number;
582
+ mode?: TrimSilenceMode;
583
+ gateLufs?: number;
584
+ windowMs?: number;
585
+ }
586
+
587
+ /** Offline LPC-based declipper. */
588
+ export function masteringRepairDeclip(
589
+ samples: Float32Array,
590
+ sampleRate: number,
591
+ options: DeclipOptions = {},
592
+ ): Float32Array {
593
+ return requireModule().masteringRepairDeclip(samples, sampleRate, options);
594
+ }
595
+
596
+ /** Offline crackle suppressor (median or wavelet-shrinkage). */
597
+ export function masteringRepairDecrackle(
598
+ samples: Float32Array,
599
+ sampleRate: number,
600
+ options: DecrackleOptions = {},
601
+ ): Float32Array {
602
+ return requireModule().masteringRepairDecrackle(samples, sampleRate, options);
603
+ }
604
+
605
+ /** Offline mains-hum remover. */
606
+ export function masteringRepairDehum(
607
+ samples: Float32Array,
608
+ sampleRate: number,
609
+ options: DehumOptions = {},
610
+ ): Float32Array {
611
+ return requireModule().masteringRepairDehum(samples, sampleRate, options);
612
+ }
613
+
614
+ /** Offline classical dereverberator (spectral subtraction + optional WPE). */
615
+ export function masteringRepairDereverbClassical(
616
+ samples: Float32Array,
617
+ sampleRate: number,
618
+ options: DereverbClassicalOptions = {},
619
+ ): Float32Array {
620
+ return requireModule().masteringRepairDereverbClassical(samples, sampleRate, options);
621
+ }
622
+
623
+ /** Offline silence trimmer (peak threshold or LUFS-gated). */
624
+ export function masteringRepairTrimSilence(
625
+ samples: Float32Array,
626
+ sampleRate: number,
627
+ options: TrimSilenceOptions = {},
628
+ ): Float32Array {
629
+ return requireModule().masteringRepairTrimSilence(samples, sampleRate, options);
630
+ }
631
+
632
+ // ============================================================================
633
+ // Mastering — offline dynamics processors (compressor / gate / transient_shaper)
634
+ // ============================================================================
635
+
636
+ /** Compressor sidechain detector mode. */
637
+ export type CompressorDetector = 'peak' | 'rms' | 'log_rms';
638
+
639
+ /** Options for `masteringDynamicsCompressor`. */
640
+ export interface CompressorOptions extends ValidateOptions {
641
+ thresholdDb?: number;
642
+ ratio?: number;
643
+ attackMs?: number;
644
+ releaseMs?: number;
645
+ kneeDb?: number;
646
+ makeupGainDb?: number;
647
+ autoMakeup?: boolean;
648
+ detector?: CompressorDetector | number;
649
+ sidechainHpfEnabled?: boolean;
650
+ sidechainHpfHz?: number;
651
+ pdrTimeMs?: number;
652
+ pdrReleaseScale?: number;
653
+ }
654
+
655
+ /** Options for `masteringDynamicsGate`. */
656
+ export interface GateOptions extends ValidateOptions {
657
+ thresholdDb?: number;
658
+ attackMs?: number;
659
+ releaseMs?: number;
660
+ rangeDb?: number;
661
+ holdMs?: number;
662
+ closeThresholdDb?: number;
663
+ keyHpfHz?: number;
664
+ }
665
+
666
+ /** Options for `masteringDynamicsTransientShaper`. */
667
+ export interface TransientShaperOptions extends ValidateOptions {
668
+ attackGainDb?: number;
669
+ sustainGainDb?: number;
670
+ fastAttackMs?: number;
671
+ fastReleaseMs?: number;
672
+ slowAttackMs?: number;
673
+ slowReleaseMs?: number;
674
+ sensitivity?: number;
675
+ maxGainDb?: number;
676
+ gainSmoothingMs?: number;
677
+ lookaheadMs?: number;
678
+ }
679
+
680
+ /** Result envelope returned by offline mastering dynamics processors. */
681
+ export interface DynamicsResult {
682
+ samples: Float32Array;
683
+ latencySamples: number;
684
+ }
685
+
686
+ const COMPRESSOR_DETECTOR_MAP: Record<CompressorDetector, number> = {
687
+ peak: 0,
688
+ rms: 1,
689
+ log_rms: 2,
690
+ };
691
+
692
+ /** Offline feed-forward compressor (soft knee, optional auto-makeup / sidechain HPF). */
693
+ export function masteringDynamicsCompressor(
694
+ samples: Float32Array,
695
+ sampleRate: number,
696
+ options: CompressorOptions = {},
697
+ ): DynamicsResult {
698
+ assertSamples('masteringDynamicsCompressor', samples, options.validate !== false);
699
+ const detector =
700
+ typeof options.detector === 'string'
701
+ ? COMPRESSOR_DETECTOR_MAP[options.detector]
702
+ : options.detector;
703
+ const opts: Record<string, unknown> = { ...options };
704
+ if (detector !== undefined) {
705
+ opts.detector = detector;
706
+ }
707
+ return requireModule().masteringDynamicsCompressor(samples, sampleRate, opts);
708
+ }
709
+
710
+ /** Offline noise gate (hysteresis, hold, optional key HPF). */
711
+ export function masteringDynamicsGate(
712
+ samples: Float32Array,
713
+ sampleRate: number,
714
+ options: GateOptions = {},
715
+ ): DynamicsResult {
716
+ assertSamples('masteringDynamicsGate', samples, options.validate !== false);
717
+ return requireModule().masteringDynamicsGate(samples, sampleRate, options);
718
+ }
719
+
720
+ /** Offline transient shaper (envelope-difference attack/sustain control). */
721
+ export function masteringDynamicsTransientShaper(
722
+ samples: Float32Array,
723
+ sampleRate: number,
724
+ options: TransientShaperOptions = {},
725
+ ): DynamicsResult {
726
+ assertSamples('masteringDynamicsTransientShaper', samples, options.validate !== false);
727
+ return requireModule().masteringDynamicsTransientShaper(samples, sampleRate, options);
728
+ }
729
+
730
+ /**
731
+ * Apply a configurable mastering chain in WASM.
732
+ *
733
+ * @param samples - Audio samples (mono, float32)
734
+ * @param sampleRate - Sample rate in Hz (default: 22050)
735
+ * @param config - Chain stage configuration
736
+ * @returns Processed audio, loudness metadata, and applied stage names
737
+ */
738
+ export function masteringChain(
739
+ samples: Float32Array,
740
+ sampleRate = 22050,
741
+ config: MasteringChainConfig,
742
+ ): MasteringChainResult {
743
+ return requireModule().masteringChain(samples, sampleRate, config as Record<string, unknown>);
744
+ }
745
+
746
+ /**
747
+ * Apply a configurable stereo mastering chain in WASM.
748
+ *
749
+ * @param left - Left channel samples
750
+ * @param right - Right channel samples
751
+ * @param sampleRate - Sample rate in Hz
752
+ * @param config - Chain stage configuration
753
+ * @returns Processed stereo audio, loudness metadata, and applied stage names
754
+ */
755
+ export function masteringChainStereo(
756
+ left: Float32Array,
757
+ right: Float32Array,
758
+ sampleRate = 22050,
759
+ config: MasteringChainConfig,
760
+ ): MasteringStereoChainResult {
761
+ if (left.length !== right.length) {
762
+ throw new Error('Stereo channel lengths must match.');
763
+ }
764
+ return requireModule().masteringChainStereo(
765
+ left,
766
+ right,
767
+ sampleRate,
768
+ config as Record<string, unknown>,
769
+ );
770
+ }
771
+
772
+ /**
773
+ * Apply a configurable mastering chain in WASM with progress reporting.
774
+ *
775
+ * @param samples - Audio samples (mono, float32)
776
+ * @param sampleRate - Sample rate in Hz (default: 22050)
777
+ * @param config - Chain stage configuration
778
+ * @param onProgress - Progress callback (progress: 0-1, stage: string)
779
+ * @returns Processed audio, loudness metadata, and applied stage names
780
+ */
781
+ export function masteringChainWithProgress(
782
+ samples: Float32Array,
783
+ sampleRate = 22050,
784
+ config: MasteringChainConfig,
785
+ onProgress: ProgressCallback,
786
+ ): MasteringChainResult {
787
+ return requireModule().masteringChainWithProgress(
788
+ samples,
789
+ sampleRate,
790
+ config as Record<string, unknown>,
791
+ onProgress,
792
+ );
793
+ }
794
+
795
+ /**
796
+ * Apply a configurable stereo mastering chain in WASM with progress reporting.
797
+ *
798
+ * @param left - Left channel samples
799
+ * @param right - Right channel samples
800
+ * @param sampleRate - Sample rate in Hz
801
+ * @param config - Chain stage configuration
802
+ * @param onProgress - Progress callback (progress: 0-1, stage: string)
803
+ * @returns Processed stereo audio, loudness metadata, and applied stage names
804
+ */
805
+ export function masteringChainStereoWithProgress(
806
+ left: Float32Array,
807
+ right: Float32Array,
808
+ sampleRate = 22050,
809
+ config: MasteringChainConfig,
810
+ onProgress: ProgressCallback,
811
+ ): MasteringStereoChainResult {
812
+ if (left.length !== right.length) {
813
+ throw new Error('Stereo channel lengths must match.');
814
+ }
815
+ return requireModule().masteringChainStereoWithProgress(
816
+ left,
817
+ right,
818
+ sampleRate,
819
+ config as Record<string, unknown>,
820
+ onProgress,
821
+ );
822
+ }
823
+
824
+ /**
825
+ * List built-in mastering preset identifiers.
826
+ *
827
+ * @returns Preset names in display order (e.g. "pop", "edm", "aiMusic")
828
+ */
829
+ export function masteringPresetNames(): MasteringPreset[] {
830
+ return requireModule().masteringPresetNames() as MasteringPreset[];
831
+ }
832
+
833
+ /**
834
+ * Apply a named mastering preset chain to mono audio.
835
+ *
836
+ * @param samples - Audio samples (mono, float32)
837
+ * @param sampleRate - Sample rate in Hz (default: 22050)
838
+ * @param presetName - Preset identifier from {@link masteringPresetNames}
839
+ * @param overrides - Optional flat overrides (dot-notation, e.g. `'loudness.targetLufs'`) applied on top of the preset. Pass `null` for preset defaults.
840
+ * @returns Processed audio, loudness metadata, and applied stage names
841
+ */
842
+ export function masterAudio(
843
+ samples: Float32Array,
844
+ sampleRate = 22050,
845
+ presetName: MasteringPreset = 'pop',
846
+ overrides: Record<string, number | boolean> = {},
847
+ ): MasteringChainResult {
848
+ return requireModule().masterAudio(presetName, samples, sampleRate, overrides);
849
+ }
850
+
851
+ /**
852
+ * Apply a named mastering preset chain to stereo audio.
853
+ *
854
+ * @param left - Left channel samples
855
+ * @param right - Right channel samples
856
+ * @param sampleRate - Sample rate in Hz
857
+ * @param presetName - Preset identifier from {@link masteringPresetNames}
858
+ * @param overrides - Optional flat overrides (dot-notation, e.g. `'loudness.targetLufs'`) applied on top of the preset. Pass `null` for preset defaults.
859
+ * @returns Processed stereo audio, loudness metadata, and applied stage names
860
+ */
861
+ export function masterAudioStereo(
862
+ left: Float32Array,
863
+ right: Float32Array,
864
+ sampleRate = 22050,
865
+ presetName: MasteringPreset = 'pop',
866
+ overrides: Record<string, number | boolean> = {},
867
+ ): MasteringStereoChainResult {
868
+ if (left.length !== right.length) {
869
+ throw new Error('Stereo channel lengths must match.');
870
+ }
871
+ return requireModule().masterAudioStereo(presetName, left, right, sampleRate, overrides);
872
+ }
873
+
874
+ /**
875
+ * Mono `masterAudio` with per-stage progress reporting. `onProgress` is invoked
876
+ * with `(progress, stage)` between each chain stage (progress is in [0,1]).
877
+ */
878
+ export function masterAudioWithProgress(
879
+ samples: Float32Array,
880
+ sampleRate = 22050,
881
+ presetName: MasteringPreset,
882
+ onProgress: ProgressCallback,
883
+ overrides: Record<string, number | boolean> | null = null,
884
+ ): MasteringChainResult {
885
+ return requireModule().masterAudioWithProgress(
886
+ presetName,
887
+ samples,
888
+ sampleRate,
889
+ overrides,
890
+ onProgress,
891
+ );
892
+ }
893
+
894
+ /**
895
+ * Stereo `masterAudio` with per-stage progress reporting.
896
+ */
897
+ export function masterAudioStereoWithProgress(
898
+ left: Float32Array,
899
+ right: Float32Array,
900
+ sampleRate = 22050,
901
+ presetName: MasteringPreset,
902
+ onProgress: ProgressCallback,
903
+ overrides: Record<string, number | boolean> | null = null,
904
+ ): MasteringStereoChainResult {
905
+ if (left.length !== right.length) {
906
+ throw new Error('Stereo channel lengths must match.');
907
+ }
908
+ return requireModule().masterAudioStereoWithProgress(
909
+ presetName,
910
+ left,
911
+ right,
912
+ sampleRate,
913
+ overrides,
914
+ onProgress,
915
+ );
916
+ }
917
+
918
+ export function mixingScenePresetNames(): string[] {
919
+ return requireModule().mixingScenePresetNames();
920
+ }
921
+
922
+ /**
923
+ * Get a built-in mixing scene preset serialized as JSON. This is the canonical
924
+ * name shared with the Node and Python bindings; the returned JSON loads
925
+ * directly into a {@link Mixer} via {@link Mixer.fromSceneJson}.
926
+ *
927
+ * @param presetName - Preset name (see {@link mixingScenePresetNames})
928
+ * @returns Scene JSON string
929
+ */
930
+ export function mixingScenePresetJson(presetName: string): string {
931
+ return requireModule().mixingScenePresetJson(presetName);
932
+ }
933
+
934
+ /**
935
+ * One-shot stereo mix of multiple strips through the routing graph + master bus.
936
+ *
937
+ * Each returned per-strip meter reflects only this single one-shot block. The
938
+ * integrating-meter fields (`momentaryLufs`, `shortTermLufs`, `integratedLufs`
939
+ * and the true-peak fields) require sustained streaming to populate; on a short
940
+ * one-shot mix they read the -120 dB floor sentinel. Drive a streaming
941
+ * {@link Mixer} block-by-block if you need meaningful loudness/true-peak
942
+ * readings.
943
+ *
944
+ * @param leftChannels - Per-strip left input buffers (all the same length)
945
+ * @param rightChannels - Per-strip right input buffers (all the same length)
946
+ * @param sampleRate - Sample rate in Hz
947
+ * @param options - Per-strip mix options (trim, fader, pan, width, mute)
948
+ */
949
+ export function mixStereo(
950
+ leftChannels: Float32Array[],
951
+ rightChannels: Float32Array[],
952
+ sampleRate = 48000,
953
+ options: MixOptions = {},
954
+ ): MixResult {
955
+ if (leftChannels.length === 0 || leftChannels.length !== rightChannels.length) {
956
+ throw new Error('leftChannels and rightChannels must have the same non-zero length.');
957
+ }
958
+ return requireModule().mixStereo(
959
+ leftChannels,
960
+ rightChannels,
961
+ sampleRate,
962
+ options as Record<string, unknown>,
963
+ );
964
+ }