@libraz/libsonare 1.5.4 → 1.6.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 (79) hide show
  1. package/README.md +109 -20
  2. package/dist/analysis.d.ts +5730 -0
  3. package/dist/analysis.js +2464 -0
  4. package/dist/analysis.js.map +1 -0
  5. package/dist/index.d.ts +873 -63
  6. package/dist/index.js +1037 -265
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
  9. package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
  10. package/dist/sonare-analysis.js +2 -0
  11. package/dist/sonare-analysis.wasm +0 -0
  12. package/dist/sonare.js +1 -1
  13. package/dist/sonare.wasm +0 -0
  14. package/dist/worker.d.ts +65 -0
  15. package/dist/worker.js +707 -0
  16. package/dist/worker.js.map +1 -0
  17. package/dist/worklet.d.ts +596 -86
  18. package/dist/worklet.js +1658 -235
  19. package/dist/worklet.js.map +1 -1
  20. package/package.json +23 -11
  21. package/src/_chain_config.ts +5 -23
  22. package/src/analysis.ts +137 -0
  23. package/src/analysis_helpers.ts +29 -0
  24. package/src/audio.ts +5 -3
  25. package/src/clip_page_streamer.ts +62 -8
  26. package/src/codes.ts +51 -64
  27. package/src/effects_mastering.ts +1 -0
  28. package/src/effects_transform.ts +12 -6
  29. package/src/errors.ts +1 -0
  30. package/src/feature_core.ts +159 -3
  31. package/src/feature_music.ts +35 -16
  32. package/src/feature_pitch.ts +75 -6
  33. package/src/feature_spectral.ts +234 -8
  34. package/src/feature_spectrogram.ts +186 -5
  35. package/src/features.ts +16 -1
  36. package/src/index.ts +104 -7
  37. package/src/mastering_chain.ts +20 -8
  38. package/src/mastering_core.ts +7 -0
  39. package/src/mastering_dynamics.ts +7 -1
  40. package/src/metering.ts +51 -5
  41. package/src/mixer.ts +14 -1
  42. package/src/opfs_clip_pages.ts +26 -1
  43. package/src/project.ts +7 -0
  44. package/src/project_class.ts +74 -13
  45. package/src/project_internal.ts +24 -29
  46. package/src/project_types.ts +69 -10
  47. package/src/public_types.ts +68 -0
  48. package/src/public_types_mastering.ts +28 -0
  49. package/src/public_types_mixing.ts +11 -2
  50. package/src/public_types_music.ts +13 -0
  51. package/src/public_types_spectral.ts +43 -3
  52. package/src/quick_analysis.ts +48 -12
  53. package/src/realtime_engine.ts +162 -4
  54. package/src/realtime_voice_changer.ts +51 -77
  55. package/src/sonare-analysis.js.d.ts +8 -0
  56. package/src/sonare.js.d.ts +305 -9
  57. package/src/streaming_processors.ts +22 -1
  58. package/src/validation.ts +6 -0
  59. package/src/web_midi.ts +36 -4
  60. package/src/worker.ts +196 -0
  61. package/src/worker_client.ts +352 -0
  62. package/src/worker_protocol.ts +59 -0
  63. package/src/worklet/engine-automation.ts +2 -1
  64. package/src/worklet/engine-capture-facade.ts +13 -4
  65. package/src/worklet/engine-clips.ts +12 -3
  66. package/src/worklet/engine-node.ts +212 -4
  67. package/src/worklet/engine-offline.ts +7 -2
  68. package/src/worklet/engine-parameter-facade.ts +26 -5
  69. package/src/worklet/engine-processor.ts +383 -42
  70. package/src/worklet/engine-register.ts +8 -0
  71. package/src/worklet/engine-strips.ts +17 -1
  72. package/src/worklet/engine-sync.ts +2 -6
  73. package/src/worklet/engine-tempo-facade.ts +8 -6
  74. package/src/worklet/engine.ts +380 -13
  75. package/src/worklet/guards.ts +22 -1
  76. package/src/worklet/messages.ts +141 -9
  77. package/src/worklet/protocol.ts +303 -17
  78. package/src/worklet/voice-changer-processor.ts +12 -15
  79. package/src/worklet.ts +20 -1
package/src/index.ts CHANGED
@@ -19,7 +19,12 @@
19
19
  */
20
20
 
21
21
  import { setSonareModule } from './module_state';
22
- import type { RealtimeVoiceChangerPodConfig, VoicePresetId } from './public_types';
22
+ import type {
23
+ CapabilityCatalog,
24
+ RealtimeVoiceChangerPodConfig,
25
+ SonareCapabilities,
26
+ VoicePresetId,
27
+ } from './public_types';
23
28
  import type {
24
29
  SonareModule,
25
30
  WasmDecomposeResult,
@@ -32,9 +37,11 @@ export { Audio } from './audio';
32
37
  export type {
33
38
  ClipPageStreamerEngine,
34
39
  ClipPageStreamerOptions,
40
+ ClipPageStreamerRequest,
35
41
  ClipPageStreamSource,
36
42
  OpfsClipStream,
37
43
  OpfsClipStreamOptions,
44
+ WorkletOpfsClipStreamHost,
38
45
  } from './clip_page_streamer';
39
46
  export { attachOpfsClipStream, ClipPageStreamer } from './clip_page_streamer';
40
47
  export type {
@@ -61,6 +68,7 @@ export type {
61
68
  MasteringProcessorCatalogEntry,
62
69
  MasteringProcessRequest,
63
70
  MasteringProcessStereoRequest,
71
+ MasteringRealtimeCost,
64
72
  MasteringRepairDeclickRequest,
65
73
  MasteringRepairDeclipRequest,
66
74
  MasteringRepairDecrackleRequest,
@@ -151,18 +159,22 @@ export type {
151
159
  } from './effects_transform';
152
160
  export { ErrorCode, isSonareError, SonareError } from './errors';
153
161
  export type {
162
+ ChirpRequest,
163
+ ClicksRequest,
154
164
  CyclicTempogramRequest,
155
165
  DbConversionRequest,
156
166
  EmphasisRequest,
157
167
  FixFramesRequest,
158
168
  FixLengthRequest,
159
169
  FrameSignalRequest,
170
+ OnsetBacktrackRequest,
160
171
  PadCenterRequest,
161
172
  PcenRequest,
162
173
  PeakPickRequest,
163
174
  PlpRequest,
164
175
  SilenceRequest,
165
176
  TempogramRequest,
177
+ ToneRequest,
166
178
  TonnetzRequest,
167
179
  VectorNormalizeRequest,
168
180
  } from './feature_core';
@@ -181,7 +193,12 @@ export type {
181
193
  VqtRequest,
182
194
  VqtToAudioRequest,
183
195
  } from './feature_music';
184
- export type { PitchPyinRequest, PitchYinRequest } from './feature_pitch';
196
+ export type {
197
+ NoteSegmentsRequest,
198
+ PiptrackRequest,
199
+ PitchPyinRequest,
200
+ PitchYinRequest,
201
+ } from './feature_pitch';
185
202
  export type { ResampleRequest } from './feature_resample';
186
203
  export type {
187
204
  DecomposeRequest,
@@ -195,6 +212,13 @@ export type {
195
212
  PitchTuningRequest,
196
213
  PolyFeaturesRequest,
197
214
  RemixRequest,
215
+ SegmentAgglomerativeRequest,
216
+ SegmentCrossSimilarityRequest,
217
+ SegmentLagToRecurrenceRequest,
218
+ SegmentPathEnhanceRequest,
219
+ SegmentRecurrenceMatrixRequest,
220
+ SegmentRecurrenceToLagRequest,
221
+ SegmentSubsegmentRequest,
198
222
  SpectralContrastRequest,
199
223
  SpectralFrameRequest,
200
224
  SpectralRolloffRequest,
@@ -203,12 +227,15 @@ export type {
203
227
  } from './feature_spectral';
204
228
  export type {
205
229
  ChromaSpectrogramRequest,
230
+ GriffinLimRequest,
231
+ MelDeltaRequest,
206
232
  MelSpectrogramRequest,
207
233
  MelToAudioRequest,
208
234
  MelToStftRequest,
209
235
  MfccRequest,
210
236
  MfccToAudioRequest,
211
237
  MfccToMelRequest,
238
+ ReassignedSpectrogramRequest,
212
239
  SpectrogramRequest,
213
240
  TrimRequest,
214
241
  } from './feature_spectrogram';
@@ -217,9 +244,11 @@ export {
217
244
  analyzeMelody,
218
245
  analyzeSections,
219
246
  bassChroma,
247
+ chirp,
220
248
  chroma,
221
249
  chromaCens,
222
250
  chromaCqt,
251
+ clicks,
223
252
  cqt,
224
253
  cqtToAudio,
225
254
  cyclicTempogram,
@@ -236,6 +265,7 @@ export {
236
265
  frameSignal,
237
266
  framesToSamples,
238
267
  framesToTime,
268
+ griffinLim,
239
269
  hpssWithResidual,
240
270
  hybridCqt,
241
271
  hzToMel,
@@ -243,6 +273,7 @@ export {
243
273
  hzToNote,
244
274
  lufs,
245
275
  lufsInterleaved,
276
+ melDelta,
246
277
  melSpectrogram,
247
278
  melToAudio,
248
279
  melToHz,
@@ -254,13 +285,16 @@ export {
254
285
  momentaryLufs,
255
286
  nnFilter,
256
287
  nnlsChroma,
288
+ noteSegments,
257
289
  noteToHz,
290
+ onsetBacktrack,
258
291
  onsetEnvelope,
259
292
  onsetStrengthMulti,
260
293
  padCenter,
261
294
  pcen,
262
295
  peakPick,
263
296
  phaseVocoder,
297
+ piptrack,
264
298
  pitchPyin,
265
299
  pitchTuning,
266
300
  pitchYin,
@@ -269,15 +303,24 @@ export {
269
303
  powerToDb,
270
304
  preemphasis,
271
305
  pseudoCqt,
306
+ reassignedSpectrogram,
272
307
  remix,
273
308
  resample,
274
309
  rmsEnergy,
275
310
  samplesToFrames,
311
+ segmentAgglomerative,
312
+ segmentCrossSimilarity,
313
+ segmentLagToRecurrence,
314
+ segmentPathEnhance,
315
+ segmentRecurrenceMatrix,
316
+ segmentRecurrenceToLag,
317
+ segmentSubsegment,
276
318
  shortTermLufs,
277
319
  spectralBandwidth,
278
320
  spectralCentroid,
279
321
  spectralContrast,
280
322
  spectralFlatness,
323
+ spectralFlux,
281
324
  spectralRolloff,
282
325
  splitSilence,
283
326
  stft,
@@ -285,6 +328,7 @@ export {
285
328
  tempogram,
286
329
  tempogramRatio,
287
330
  timeToFrames,
331
+ tone,
288
332
  tonnetz,
289
333
  trim,
290
334
  trimSilence,
@@ -312,6 +356,7 @@ export type {
312
356
  MeteringDynamicRangeOptions,
313
357
  MeteringDynamicRangeRequest,
314
358
  MeteringSamplesRequest,
359
+ MeteringSilenceRatioRequest,
315
360
  MeteringSpectrumFrameRequest,
316
361
  MeteringSpectrumRequest,
317
362
  MeteringStereoDecimatedRequest,
@@ -336,6 +381,7 @@ export {
336
381
  meteringPhaseScope,
337
382
  meteringPhaseScopeDecimated,
338
383
  meteringRmsDb,
384
+ meteringSilenceRatio,
339
385
  meteringSpectrum,
340
386
  meteringSpectrumFrame,
341
387
  meteringStereoCorrelation,
@@ -359,6 +405,9 @@ export type {
359
405
  BuiltinSynthBinding,
360
406
  BuiltinSynthConfig,
361
407
  BuiltinSynthWaveform,
408
+ ExternalSeparatedStem,
409
+ ExternalSeparatedStemImportRequest,
410
+ ExternalSeparatedStemImportResult,
362
411
  MidiCcLearnOptions,
363
412
  ProjectAssistSidecar,
364
413
  ProjectAutomationCurve,
@@ -366,6 +415,7 @@ export type {
366
415
  ProjectAutomationPoint,
367
416
  ProjectBounceOptions,
368
417
  ProjectChordSymbol,
418
+ ProjectClip,
369
419
  ProjectClipCompSegment,
370
420
  ProjectClipDesc,
371
421
  ProjectClipFade,
@@ -380,6 +430,8 @@ export type {
380
430
  ProjectMidiClipResult,
381
431
  ProjectMidiEvent,
382
432
  ProjectNotePairValidation,
433
+ ProjectSource,
434
+ ProjectTrack,
383
435
  ProjectTrackDesc,
384
436
  ProjectTrackKind,
385
437
  ProjectWarpAnchor,
@@ -399,6 +451,7 @@ export type {
399
451
  SynthPatch,
400
452
  } from './project';
401
453
  export {
454
+ BUILTIN_SYNTH_WAVEFORMS,
402
455
  EXPECTED_PROJECT_ABI_VERSION,
403
456
  MarkerKind,
404
457
  Project,
@@ -425,6 +478,7 @@ export type {
425
478
  AnalyzeTimbreOptions,
426
479
  AutomationCurve,
427
480
  Beat,
481
+ BpmHypothesis,
428
482
  Chord,
429
483
  ChordAnalysisResult,
430
484
  ChordDetectionOptions,
@@ -448,9 +502,11 @@ export type {
448
502
  MasteringChainConfig,
449
503
  MasteringChainResult,
450
504
  MasteringChainStereoResult,
505
+ MasteringLoudnessSummary,
451
506
  MasteringOptions,
452
507
  MasteringPreset,
453
508
  MasteringProcessorParams,
509
+ MasteringReport,
454
510
  MasteringResult,
455
511
  MasteringStereoChainResult,
456
512
  MasteringStereoResult,
@@ -464,6 +520,7 @@ export type {
464
520
  MixMeterSnapshot,
465
521
  MixOptions,
466
522
  MixResult,
523
+ NoteSegment,
467
524
  NoteStretchOptions,
468
525
  PairAnalysis,
469
526
  PairProcessor,
@@ -481,8 +538,10 @@ export type {
481
538
  RoomGeometryOptions,
482
539
  RoomMorphOptions,
483
540
  Section,
541
+ SegmentMatrix,
484
542
  SendTiming,
485
543
  SoloProcessor,
544
+ SonareCapabilities,
486
545
  SpectralEditMode,
487
546
  SpectralEditOptions,
488
547
  SpectralEditWindow,
@@ -520,8 +579,10 @@ export type {
520
579
  DetectAcousticRequest,
521
580
  DetectChordsRequest,
522
581
  DetectKeyRequest,
582
+ DetectOnsetsRequest,
523
583
  DynamicsAnalysisResult,
524
584
  EstimateRoomRequest,
585
+ MusicAnalyzeRequest,
525
586
  RhythmAnalysisResult,
526
587
  RoomMorphRequest,
527
588
  SamplesRequest,
@@ -557,6 +618,7 @@ export type {
557
618
  EngineBounceResult,
558
619
  EngineBus,
559
620
  EngineCapabilities,
621
+ EngineCaptureSource,
560
622
  EngineCaptureStatus,
561
623
  EngineClip,
562
624
  EngineFreezeOptions,
@@ -579,7 +641,12 @@ export type {
579
641
  ExternalMidiEvent,
580
642
  MidiCcBindOptions,
581
643
  } from './realtime_engine';
582
- export { EXPECTED_ENGINE_ABI_VERSION, engineCapabilities, RealtimeEngine } from './realtime_engine';
644
+ export {
645
+ ClipPageProvider,
646
+ EXPECTED_ENGINE_ABI_VERSION,
647
+ engineCapabilities,
648
+ RealtimeEngine,
649
+ } from './realtime_engine';
583
650
  export { scaleCorrectionSemitones, scalePitchClassEnabled, scaleQuantizeMidi } from './scale';
584
651
  export type { ProgressCallback } from './sonare.js';
585
652
  export { StreamAnalyzer, streamAnalyzerConfigDefaults } from './stream_analyzer';
@@ -620,6 +687,13 @@ export type {
620
687
  WebMidiInputInfo,
621
688
  } from './web_midi';
622
689
  export { bindWebMidi, isWebMidiAvailable } from './web_midi';
690
+ export type {
691
+ OfflineWorker,
692
+ OfflineWorkerCallOptions,
693
+ OfflineWorkerClientOptions,
694
+ OfflineWorkerProgress,
695
+ } from './worker_client';
696
+ export { OfflineWorkerClient, OfflineWorkerTask } from './worker_client';
623
697
 
624
698
  /** Row-major 2-D matrix as a flat buffer plus its dimensions. */
625
699
  export type Matrix2dResult = WasmMatrix2dResult;
@@ -693,6 +767,26 @@ export function version(): string {
693
767
  return module.version();
694
768
  }
695
769
 
770
+ /**
771
+ * Return the capabilities of the loaded WASM build.
772
+ *
773
+ * This is synchronous and only describes the already-initialized module.
774
+ */
775
+ export function capabilities(): SonareCapabilities {
776
+ if (!module) {
777
+ throw new Error('Module not initialized. Call init() first.');
778
+ }
779
+ return module.capabilities();
780
+ }
781
+
782
+ /** Return the initialized module's processors, parameters, and presets. */
783
+ export function capabilityCatalog(): CapabilityCatalog {
784
+ if (!module) {
785
+ throw new Error('Module not initialized. Call init() first.');
786
+ }
787
+ return JSON.parse(module.capabilityCatalog()) as CapabilityCatalog;
788
+ }
789
+
696
790
  /**
697
791
  * Aggregate native ABI version: the per-subsystem ABI macros folded into one
698
792
  * 32-bit value. It bumps whenever any flat C POD layout changes, so callers can
@@ -732,6 +826,9 @@ const VOICE_PRESET_ORDINALS: readonly VoicePresetId[] = [
732
826
 
733
827
  function resolveVoicePresetOrdinal(preset: VoicePresetId | number): number {
734
828
  if (typeof preset === 'number') {
829
+ if (!Number.isSafeInteger(preset) || preset < 0 || preset >= VOICE_PRESET_ORDINALS.length) {
830
+ throw new RangeError(`Unknown voice-character preset ordinal: ${String(preset)}`);
831
+ }
735
832
  return preset;
736
833
  }
737
834
  const ordinal = VOICE_PRESET_ORDINALS.indexOf(preset);
@@ -743,9 +840,9 @@ function resolveVoicePresetOrdinal(preset: VoicePresetId | number): number {
743
840
 
744
841
  /**
745
842
  * Map a voice-character preset ordinal (or canonical id) to its canonical id
746
- * string (e.g. `'bright-idol'`). Returns `null` for an out-of-range ordinal.
843
+ * string (e.g. `'bright-idol'`). Invalid ordinals throw.
747
844
  */
748
- export function voiceCharacterPresetId(preset: VoicePresetId | number): string | null {
845
+ export function voiceCharacterPresetId(preset: VoicePresetId | number): string {
749
846
  if (!module) {
750
847
  throw new Error('Module not initialized. Call init() first.');
751
848
  }
@@ -755,11 +852,11 @@ export function voiceCharacterPresetId(preset: VoicePresetId | number): string |
755
852
  /**
756
853
  * Return the canonical (normalized) flat POD config for a built-in voice
757
854
  * preset, skipping the JSON round-trip. Accepts a canonical preset id or its
758
- * integer ordinal. Returns `null` for an out-of-range ordinal.
855
+ * integer ordinal. Invalid ordinals throw.
759
856
  */
760
857
  export function realtimeVoiceChangerPresetConfig(
761
858
  preset: VoicePresetId | number,
762
- ): RealtimeVoiceChangerPodConfig | null {
859
+ ): RealtimeVoiceChangerPodConfig {
763
860
  if (!module) {
764
861
  throw new Error('Module not initialized. Call init() first.');
765
862
  }
@@ -22,6 +22,7 @@ export interface MasteringChainRequest {
22
22
  sampleRate?: number;
23
23
  config?: MasteringChainConfig;
24
24
  onProgress?: ProgressCallback;
25
+ cancel?: () => boolean;
25
26
  }
26
27
 
27
28
  export interface MasteringChainStereoRequest {
@@ -30,6 +31,7 @@ export interface MasteringChainStereoRequest {
30
31
  sampleRate?: number;
31
32
  config?: MasteringChainConfig;
32
33
  onProgress?: ProgressCallback;
34
+ cancel?: () => boolean;
33
35
  }
34
36
 
35
37
  /** Canonical request form for one-shot preset mastering. */
@@ -39,6 +41,7 @@ export interface MasterAudioRequest {
39
41
  preset?: MasteringPreset;
40
42
  overrides?: MasteringChainConfig;
41
43
  onProgress?: ProgressCallback;
44
+ cancel?: () => boolean;
42
45
  }
43
46
 
44
47
  /** Canonical request form for one-shot stereo preset mastering. */
@@ -49,6 +52,7 @@ export interface MasterAudioStereoRequest {
49
52
  preset?: MasteringPreset;
50
53
  overrides?: MasteringChainConfig;
51
54
  onProgress?: ProgressCallback;
55
+ cancel?: () => boolean;
52
56
  }
53
57
 
54
58
  function masterAudioRequest(
@@ -114,12 +118,13 @@ export function masteringChain(
114
118
  ): MasteringChainResult {
115
119
  const request =
116
120
  samples instanceof Float32Array ? { samples, sampleRate, config, onProgress } : samples;
117
- if (request.onProgress) {
121
+ if (request.onProgress || request.cancel) {
118
122
  return requireModule().masteringChainWithProgress(
119
123
  request.samples,
120
124
  request.sampleRate ?? 22050,
121
125
  canonicalChainConfig(request.config ?? {}),
122
- request.onProgress,
126
+ request.onProgress ?? (() => {}),
127
+ request.cancel ?? (() => false),
123
128
  );
124
129
  }
125
130
  return requireModule().masteringChain(
@@ -162,13 +167,14 @@ export function masteringChainStereo(
162
167
  if (request.left.length !== request.right.length) {
163
168
  throw new Error('Stereo channel lengths must match.');
164
169
  }
165
- if (request.onProgress) {
170
+ if (request.onProgress || request.cancel) {
166
171
  return requireModule().masteringChainStereoWithProgress(
167
172
  request.left,
168
173
  request.right,
169
174
  request.sampleRate ?? 22050,
170
175
  canonicalChainConfig(request.config ?? {}),
171
- request.onProgress,
176
+ request.onProgress ?? (() => {}),
177
+ request.cancel ?? (() => false),
172
178
  );
173
179
  }
174
180
  return requireModule().masteringChainStereo(
@@ -213,6 +219,7 @@ export function masteringChainWithProgress(
213
219
  request.sampleRate ?? 22050,
214
220
  canonicalChainConfig(request.config ?? {}),
215
221
  request.onProgress,
222
+ request.cancel ?? (() => false),
216
223
  );
217
224
  }
218
225
 
@@ -259,6 +266,7 @@ export function masteringChainStereoWithProgress(
259
266
  request.sampleRate ?? 22050,
260
267
  canonicalChainConfig(request.config ?? {}),
261
268
  request.onProgress,
269
+ request.cancel ?? (() => false),
262
270
  );
263
271
  }
264
272
 
@@ -298,13 +306,14 @@ export function masterAudio(
298
306
  ): MasteringChainResult {
299
307
  const request = masterAudioRequest(samples, sampleRate, presetName, overrides, onProgress);
300
308
  const flat = flattenChainConfig(request.overrides ?? {});
301
- if (request.onProgress) {
309
+ if (request.onProgress || request.cancel) {
302
310
  return requireModule().masterAudioWithProgress(
303
311
  request.preset ?? 'pop',
304
312
  request.samples,
305
313
  request.sampleRate ?? 22050,
306
314
  flat,
307
- request.onProgress,
315
+ request.onProgress ?? (() => {}),
316
+ request.cancel ?? (() => false),
308
317
  );
309
318
  }
310
319
  return requireModule().masterAudio(
@@ -355,14 +364,15 @@ export function masterAudioStereo(
355
364
  if (request.left.length !== request.right.length) {
356
365
  throw new Error('Stereo channel lengths must match.');
357
366
  }
358
- if (request.onProgress) {
367
+ if (request.onProgress || request.cancel) {
359
368
  return requireModule().masterAudioStereoWithProgress(
360
369
  request.preset ?? 'pop',
361
370
  request.left,
362
371
  request.right,
363
372
  request.sampleRate ?? 22050,
364
373
  flat,
365
- request.onProgress,
374
+ request.onProgress ?? (() => {}),
375
+ request.cancel ?? (() => false),
366
376
  );
367
377
  }
368
378
  return requireModule().masterAudioStereo(
@@ -405,6 +415,7 @@ export function masterAudioWithProgress(
405
415
  request.sampleRate ?? 22050,
406
416
  flattenChainConfig(request.overrides ?? {}),
407
417
  request.onProgress,
418
+ request.cancel ?? (() => false),
408
419
  );
409
420
  }
410
421
 
@@ -451,5 +462,6 @@ export function masterAudioStereoWithProgress(
451
462
  request.sampleRate ?? 22050,
452
463
  flattenChainConfig(request.overrides ?? {}),
453
464
  request.onProgress,
465
+ request.cancel ?? (() => false),
454
466
  );
455
467
  }
@@ -152,6 +152,8 @@ export interface MasteringInsertParamInfo {
152
152
  id: number;
153
153
  /** Whether the param can be changed live from the audio thread. */
154
154
  rtSafe: boolean;
155
+ /** Physical unit when the parameter is not unitless. */
156
+ unit?: string;
155
157
  }
156
158
 
157
159
  /**
@@ -184,6 +186,9 @@ export type MasteringChannelPolicy =
184
186
  | 'perChannel'
185
187
  | 'passthrough';
186
188
 
189
+ /** Coarse algorithmic work estimate for a realtime insert; not a benchmark. */
190
+ export type MasteringRealtimeCost = 'low' | 'moderate' | 'high';
191
+
187
192
  /** One processor's realtime/offline/pair classification in the catalog. */
188
193
  export interface MasteringProcessorCatalogEntry {
189
194
  /** Processor id (the name used for scene inserts / named processors). */
@@ -208,6 +213,8 @@ export interface MasteringProcessorCatalogEntry {
208
213
  * offline, dry-only, and no-tail processors.
209
214
  */
210
215
  tailSamples: number;
216
+ /** Coarse realtime work estimate, or null when the processor is not an insert. */
217
+ realtimeCost: MasteringRealtimeCost | null;
211
218
  /**
212
219
  * How the mixer wraps the processor on a >2-channel (surround) bus insert:
213
220
  * "multichannel" (one full-buffer call) or "stereoPairOnly" (front L/R pair,
@@ -107,7 +107,13 @@ export function masteringDynamicsCompressor(
107
107
  typeof request.detector === 'string'
108
108
  ? COMPRESSOR_DETECTOR_MAP[request.detector]
109
109
  : request.detector;
110
- const opts: Record<string, unknown> = { ...request };
110
+ // Preserve inherited option values as well as own properties. The embind
111
+ // boundary's hasProperty() intentionally follows the prototype chain, so a
112
+ // spread copy here would otherwise erase values before C++ can read them.
113
+ const opts: Record<string, unknown> = Object.assign(
114
+ Object.create(Object.getPrototypeOf(request)) as Record<string, unknown>,
115
+ request,
116
+ );
111
117
  if (detector !== undefined) {
112
118
  opts.detector = detector;
113
119
  }
package/src/metering.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ErrorCode, SonareError } from './errors';
1
2
  import { getSonareModule } from './module_state';
2
3
  import type { ValidateOptions } from './validation';
3
4
  import { assertSamples } from './validation';
@@ -17,7 +18,11 @@ function assertOversampleFactor(fnName: string, factor: number): void {
17
18
  normalized > 16 ||
18
19
  (normalized & (normalized - 1)) !== 0
19
20
  ) {
20
- throw new RangeError(`${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`);
21
+ throw new SonareError(
22
+ ErrorCode.InvalidParameter,
23
+ 'InvalidParameter',
24
+ `${fnName}: oversampleFactor must be 0 or a power of two from 1 to 16`,
25
+ );
21
26
  }
22
27
  }
23
28
 
@@ -128,6 +133,43 @@ export function meteringRmsDb(
128
133
  return requireModule().meteringRmsDb(request.samples, request.sampleRate ?? 22050);
129
134
  }
130
135
 
136
+ export interface MeteringSilenceRatioRequest extends MeteringSamplesRequest {
137
+ thresholdDb?: number;
138
+ frameLength?: number;
139
+ hopLength?: number;
140
+ }
141
+
142
+ export function meteringSilenceRatio(request: MeteringSilenceRatioRequest): number;
143
+ export function meteringSilenceRatio(
144
+ samples: Float32Array,
145
+ sampleRate?: number,
146
+ thresholdDb?: number,
147
+ frameLength?: number,
148
+ hopLength?: number,
149
+ options?: ValidateOptions,
150
+ ): number;
151
+ export function meteringSilenceRatio(
152
+ samples: Float32Array | MeteringSilenceRatioRequest,
153
+ sampleRate = 22050,
154
+ thresholdDb = -45,
155
+ frameLength = 1024,
156
+ hopLength = 256,
157
+ options: ValidateOptions = {},
158
+ ): number {
159
+ const request =
160
+ samples instanceof Float32Array
161
+ ? { samples, sampleRate, thresholdDb, frameLength, hopLength, ...options }
162
+ : samples;
163
+ assertSamples('meteringSilenceRatio', request.samples, request.validate !== false);
164
+ return requireModule().meteringSilenceRatio(
165
+ request.samples,
166
+ request.sampleRate ?? 22050,
167
+ request.thresholdDb ?? -45,
168
+ request.frameLength ?? 1024,
169
+ request.hopLength ?? 256,
170
+ );
171
+ }
172
+
131
173
  export function meteringCrestFactorDb(request: MeteringSamplesRequest): number;
132
174
  export function meteringCrestFactorDb(
133
175
  samples: Float32Array,
@@ -206,16 +248,20 @@ export function meteringDetectClipping(
206
248
  ): ClippingReport {
207
249
  const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
208
250
  assertSamples('meteringDetectClipping', request.samples, request.validate !== false);
251
+ const minRegionSamples = request.minRegionSamples ?? 1;
252
+ if (!Number.isInteger(minRegionSamples) || minRegionSamples < 0) {
253
+ throw new RangeError('meteringDetectClipping: minRegionSamples must be a non-negative integer');
254
+ }
209
255
  return requireModule().meteringDetectClipping(
210
256
  request.samples,
211
257
  request.sampleRate ?? 22050,
212
258
  request.threshold ?? 0.999,
213
- request.minRegionSamples ?? 1,
259
+ minRegionSamples,
214
260
  );
215
261
  }
216
262
 
217
263
  /**
218
- * Sliding-window dynamic range. Pass 0 for window/hop to use the library
264
+ * Sliding-window dynamic range for mono audio. Pass 0 for window/hop to use the library
219
265
  * default (window=3 s, hop=1 s). The percentiles use a NEGATIVE sentinel for
220
266
  * "use the library default" (low=0.10, high=0.95) because 0 is a literal 0th
221
267
  * percentile; omitted percentiles therefore default to -1.
@@ -354,7 +400,7 @@ export interface WaveformPeaksReport {
354
400
  samplesPerBucket: number;
355
401
  }
356
402
 
357
- /** Pearson correlation in [-1, 1] between two equal-length channels. */
403
+ /** Uncentered correlation (cosine similarity) in [-1, 1] between equal-length channels. */
358
404
  export function meteringStereoCorrelation(request: MeteringStereoRequest): number;
359
405
  export function meteringStereoCorrelation(
360
406
  left: Float32Array,
@@ -561,7 +607,7 @@ export function meteringPhaseScopeDecimated(
561
607
  }
562
608
 
563
609
  /**
564
- * Welch-averaged magnitude / power / dB spectrum over the WHOLE signal (split
610
+ * Welch-averaged magnitude / power / dB spectrum over the WHOLE mono signal (split
565
611
  * into Hann-windowed, 50%-overlapping `nFft`-length frames whose power spectra
566
612
  * are averaged). For a true single-frame snapshot, use
567
613
  * {@link meteringSpectrumFrame}.
package/src/mixer.ts CHANGED
@@ -74,7 +74,10 @@ export class Mixer {
74
74
  return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
75
75
  }
76
76
 
77
- /** Rebuild and compile the routing graph from the current scene topology. */
77
+ /**
78
+ * Rebuild and compile the routing graph without resetting its absolute
79
+ * automation sample position or queued strip automation.
80
+ */
78
81
  compile(): void {
79
82
  this.mixer.compile();
80
83
  }
@@ -261,6 +264,11 @@ export class Mixer {
261
264
  this.mixer.setVcaGroupGainDb(id, gainDb);
262
265
  }
263
266
 
267
+ /** Replace an existing VCA group's strip membership. */
268
+ setVcaGroupMembers(id: string, members: string[]): void {
269
+ this.mixer.setVcaGroupMembers(id, members);
270
+ }
271
+
264
272
  /** Remove a VCA group by id. */
265
273
  removeVcaGroup(id: string): void {
266
274
  this.mixer.removeVcaGroup(id);
@@ -427,6 +435,11 @@ export class Mixer {
427
435
  return this.mixer.meterTap(stripIndex, meterTapCode(tap));
428
436
  }
429
437
 
438
+ /** Read the post-insert meter for a compiled bus, including master. */
439
+ busMeter(busId: string): MixMeterSnapshot {
440
+ return this.mixer.busMeter(busId);
441
+ }
442
+
430
443
  /**
431
444
  * Schedule sample-accurate fader automation on a strip.
432
445
  *