@libraz/libsonare 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +18 -3
  2. package/dist/index.d.ts +1165 -178
  3. package/dist/index.js +4830 -3871
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare.js +1 -1
  6. package/dist/sonare.wasm +0 -0
  7. package/dist/worklet.d.ts +29 -1
  8. package/dist/worklet.js +1068 -974
  9. package/dist/worklet.js.map +1 -1
  10. package/package.json +2 -1
  11. package/src/_chain_config.ts +46 -0
  12. package/src/audio.ts +24 -4
  13. package/src/effects_mastering.ts +25 -1
  14. package/src/effects_transform.ts +283 -43
  15. package/src/effects_voice_change.ts +46 -53
  16. package/src/feature_core.ts +316 -12
  17. package/src/feature_music.ts +462 -0
  18. package/src/feature_pitch.ts +61 -0
  19. package/src/feature_resample.ts +17 -2
  20. package/src/feature_spectral.ts +349 -2
  21. package/src/feature_spectrogram.ts +336 -0
  22. package/src/features.ts +2 -0
  23. package/src/index.ts +133 -1
  24. package/src/mastering_chain.ts +305 -50
  25. package/src/mastering_core.ts +242 -16
  26. package/src/mastering_dynamics.ts +66 -10
  27. package/src/mastering_repair.ts +119 -7
  28. package/src/metering.ts +366 -81
  29. package/src/mixer.ts +10 -3
  30. package/src/mixing_oneshot.ts +25 -5
  31. package/src/module_state.ts +1 -1
  32. package/src/project_class.ts +17 -6
  33. package/src/project_internal.ts +10 -2
  34. package/src/project_types.ts +25 -2
  35. package/src/public_types_mastering.ts +114 -4
  36. package/src/public_types_music.ts +1 -0
  37. package/src/public_types_spectral.ts +7 -0
  38. package/src/quick_analysis.ts +325 -117
  39. package/src/realtime_engine.ts +8 -0
  40. package/src/sonare.js.d.ts +56 -0
  41. package/src/stream_analyzer.ts +25 -2
  42. package/src/stream_types.ts +16 -1
  43. package/src/validation.ts +17 -2
  44. package/src/worklet/engine-clips.ts +67 -2
  45. package/src/worklet/engine-processor.ts +33 -5
  46. package/src/worklet/engine.ts +7 -3
  47. package/src/worklet/guards.ts +3 -0
  48. package/src/worklet/messages.ts +27 -0
package/src/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. */
@@ -515,7 +517,7 @@ export class Mixer {
515
517
  }
516
518
 
517
519
  /**
518
- * Maximum processor tail length (samples) in the compiled mixer graph. Lazily
520
+ * Longest audible serial processor-tail path to the master, in samples. Lazily
519
521
  * compiles the routing graph if the topology is dirty.
520
522
  */
521
523
  tailSamples(): number {
@@ -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
 
@@ -21,6 +21,13 @@ export function mixingScenePresetJson(presetName: string): string {
21
21
  return requireModule().mixingScenePresetJson(presetName);
22
22
  }
23
23
 
24
+ /** Inputs for the one-shot {@link mixStereo} facade. */
25
+ export interface MixStereoRequest extends MixOptions {
26
+ leftChannels: Float32Array[];
27
+ rightChannels: Float32Array[];
28
+ sampleRate?: number;
29
+ }
30
+
24
31
  /**
25
32
  * One-shot stereo mix of multiple strips through the routing graph + master bus.
26
33
  *
@@ -36,19 +43,32 @@ export function mixingScenePresetJson(presetName: string): string {
36
43
  * @param sampleRate - Sample rate in Hz
37
44
  * @param options - Per-strip mix options (trim, fader, pan, width, mute)
38
45
  */
46
+ export function mixStereo(request: MixStereoRequest): MixResult;
39
47
  export function mixStereo(
40
48
  leftChannels: Float32Array[],
41
49
  rightChannels: Float32Array[],
50
+ sampleRate?: number,
51
+ options?: MixOptions,
52
+ ): MixResult;
53
+ export function mixStereo(
54
+ leftChannels: Float32Array[] | MixStereoRequest,
55
+ rightChannels?: Float32Array[],
42
56
  sampleRate = 48000,
43
57
  options: MixOptions = {},
44
58
  ): MixResult {
45
- if (leftChannels.length === 0 || leftChannels.length !== rightChannels.length) {
59
+ const request = Array.isArray(leftChannels)
60
+ ? { leftChannels, rightChannels: rightChannels ?? [], sampleRate, ...options }
61
+ : leftChannels;
62
+ if (
63
+ request.leftChannels.length === 0 ||
64
+ request.leftChannels.length !== request.rightChannels.length
65
+ ) {
46
66
  throw new Error('leftChannels and rightChannels must have the same non-zero length.');
47
67
  }
48
68
  return requireModule().mixStereo(
49
- leftChannels,
50
- rightChannels,
51
- sampleRate,
52
- options as Record<string, unknown>,
69
+ request.leftChannels,
70
+ request.rightChannels,
71
+ request.sampleRate ?? 48000,
72
+ request as unknown as Record<string, unknown>,
53
73
  );
54
74
  }
@@ -149,7 +149,7 @@ function wrapModuleErrors(raw: SonareModule): SonareModule {
149
149
  },
150
150
  construct(t, args, newTarget) {
151
151
  try {
152
- return wrapNativeObject(Reflect.construct(t, args as unknown[], newTarget));
152
+ return wrapNativeObject(Reflect.construct(t, args as unknown[], newTarget)) as object;
153
153
  } catch (error) {
154
154
  return convert(error) as object;
155
155
  }
@@ -32,6 +32,7 @@ import type {
32
32
  ProjectMidiRouteConfig,
33
33
  ProjectMidiRouteResult,
34
34
  ProjectNotePairValidation,
35
+ ProjectTempoCandidate,
35
36
  ProjectTempoSegment,
36
37
  ProjectTimeSignatureSegment,
37
38
  ProjectTrackDesc,
@@ -504,14 +505,24 @@ export class Project {
504
505
  return this.native.validateMidiNotes(clipId);
505
506
  }
506
507
 
507
- /** Detect tempo from a mono buffer and install it; returns the primary BPM. */
508
- autoTempo(audio: Float32Array, sampleRate: number): number {
509
- return this.native.autoTempo(audio, sampleRate);
508
+ /** Return ranked tempo-octave and detected-meter candidates without editing. */
509
+ analyzeTempo(audio: Float32Array, sampleRate: number): ProjectTempoCandidate[] {
510
+ return this.native.analyzeTempo(audio, sampleRate);
510
511
  }
511
512
 
512
- /** Snap a PPQ coordinate to the nearest beat of the project grid. */
513
- snapToGrid(ppq: number, strength = 1.0): number {
514
- return this.native.snapToGrid(ppq, strength);
513
+ /** Detect and install a ranked tempo candidate; optionally apply detected meter. */
514
+ autoTempo(
515
+ audio: Float32Array,
516
+ sampleRate: number,
517
+ candidateIndex = 0,
518
+ applyTimeSignatures = false,
519
+ ): number {
520
+ return this.native.autoTempo(audio, sampleRate, candidateIndex, applyTimeSignatures);
521
+ }
522
+
523
+ /** Snap to a bar (`division=0`), beat (`1`), or beat subdivision (`2+`). */
524
+ snapToGrid(ppq: number, strength = 1.0, division = 1): number {
525
+ return this.native.snapToGrid(ppq, strength, division);
515
526
  }
516
527
 
517
528
  /** Compile the project into a renderable timeline, surfacing diagnostics. */
@@ -1,6 +1,7 @@
1
1
  import { getSonareModule } from './module_state';
2
2
  import type {
3
3
  BuiltinSynthBinding,
4
+ ProjectAssistSidecar,
4
5
  ProjectAutomationPoint,
5
6
  ProjectBounceOptions,
6
7
  ProjectChordSymbol,
@@ -20,6 +21,7 @@ import type {
20
21
  ProjectMidiRouteConfig,
21
22
  ProjectMidiRouteResult,
22
23
  ProjectNotePairValidation,
24
+ ProjectTempoCandidate,
23
25
  ProjectTempoSegment,
24
26
  ProjectTimeSignatureSegment,
25
27
  ProjectTrackKind,
@@ -75,8 +77,14 @@ export interface WasmProject {
75
77
  bakeMidiFx: (clipId: number, configJson: string) => void;
76
78
  setMidiFx: (clipId: number, configJson: string) => void;
77
79
  validateMidiNotes: (clipId: number) => ProjectNotePairValidation;
78
- autoTempo: (audio: Float32Array, sampleRate: number) => number;
79
- snapToGrid: (ppq: number, strength: number) => number;
80
+ analyzeTempo: (audio: Float32Array, sampleRate: number) => ProjectTempoCandidate[];
81
+ autoTempo: (
82
+ audio: Float32Array,
83
+ sampleRate: number,
84
+ candidateIndex: number,
85
+ applyTimeSignatures: boolean,
86
+ ) => number;
87
+ snapToGrid: (ppq: number, strength: number, division: number) => number;
80
88
  compile: () => ProjectCompileResult;
81
89
  bounce: (options: ProjectBounceOptions) => Float32Array;
82
90
  bounceWithBuiltinInstrument: (
@@ -362,8 +362,22 @@ export interface ProjectLoopRecordingResult {
362
362
  export type ProjectLoopMode = 'off' | 'loop' | 0 | 1;
363
363
  export type ProjectWarpMode = 'off' | 'repitch' | 'tempo-sync' | 0 | 1 | 2;
364
364
 
365
- /** Automation breakpoint interpolation for {@link ProjectAutomationPoint}. */
366
- export type ProjectAutomationCurve = 'linear' | 'exponential' | 'hold' | 'scurve' | 0 | 1 | 2 | 3;
365
+ /**
366
+ * Automation breakpoint interpolation for {@link ProjectAutomationPoint}.
367
+ *
368
+ * `'s-curve'` is the canonical spelling, matching the Node engine and the mixer
369
+ * automation types. The legacy `'scurve'` remains accepted for compatibility.
370
+ */
371
+ export type ProjectAutomationCurve =
372
+ | 'linear'
373
+ | 'exponential'
374
+ | 'hold'
375
+ | 's-curve'
376
+ | 'scurve'
377
+ | 0
378
+ | 1
379
+ | 2
380
+ | 3;
367
381
 
368
382
  /** One automation breakpoint accepted by the automation-lane edit ops. */
369
383
  export interface ProjectAutomationPoint {
@@ -405,6 +419,15 @@ export interface ProjectTimeSignatureSegment {
405
419
  denominator: number;
406
420
  }
407
421
 
422
+ /** A ranked primary/half/double tempo hypothesis returned by {@link Project.analyzeTempo}. */
423
+ export interface ProjectTempoCandidate {
424
+ bpm: number;
425
+ confidence: number;
426
+ label: 'primary' | 'half' | 'double';
427
+ timeSignatureCount: number;
428
+ timeSignature: ProjectTimeSignatureSegment;
429
+ }
430
+
408
431
  /** Key segment for {@link Project.annotateKeys}. */
409
432
  export interface ProjectKeySegment {
410
433
  startPpq: number;
@@ -143,14 +143,65 @@ export interface MasteringResult {
143
143
 
144
144
  export type MasteringProcessorParams = Record<string, number | boolean>;
145
145
 
146
+ /**
147
+ * Nested mastering-chain configuration. A boolean toggles a module/processor's
148
+ * `enabled` flag; setting any field implicitly enables its module unless
149
+ * `enabled: false` is also given.
150
+ *
151
+ * Exception — color stages as `masterAudio` overrides: the `saturation.tape`
152
+ * and `saturation.exciter` stages are engaged from an override only when you
153
+ * pass `enabled: true` explicitly. On a preset where they are off, adjusting a
154
+ * parameter alone (e.g. `saturation: { tape: { driveDb: 6 } }`) has no audible
155
+ * effect; use `saturation: { tape: { enabled: true, driveDb: 6 } }`.
156
+ */
146
157
  export interface MasteringChainConfig {
147
158
  repair?: {
148
- denoise?: boolean;
159
+ /** `boolean` is retained as a deprecated shorthand for `{ enabled }`. */
160
+ denoise?:
161
+ | boolean
162
+ | {
163
+ enabled?: boolean;
164
+ nFft?: number;
165
+ hopLength?: number;
166
+ ddAlpha?: number;
167
+ gainFloor?: number;
168
+ overSubtraction?: number;
169
+ spectralFloor?: number;
170
+ noiseEstimationQuantile?: number;
171
+ speechPresenceGain?: boolean;
172
+ gainSmoothing?: boolean;
173
+ };
149
174
  nFft?: number;
150
175
  hopLength?: number;
151
176
  ddAlpha?: number;
152
177
  gainFloor?: number;
178
+ declip?: {
179
+ enabled?: boolean;
180
+ clipThreshold?: number;
181
+ lpcOrder?: number;
182
+ iterations?: number;
183
+ lpcBlend?: number;
184
+ };
185
+ decrackle?: {
186
+ enabled?: boolean;
187
+ threshold?: number;
188
+ /** 0 = median, 1 = wavelet shrinkage. */
189
+ mode?: number;
190
+ levels?: number;
191
+ };
192
+ dehum?: {
193
+ enabled?: boolean;
194
+ fundamentalHz?: number;
195
+ harmonics?: number;
196
+ q?: number;
197
+ adaptive?: boolean;
198
+ searchRangeHz?: number;
199
+ adaptation?: number;
200
+ frameSize?: number;
201
+ pllBandwidth?: number;
202
+ };
153
203
  declick?: {
204
+ enabled?: boolean;
154
205
  threshold?: number;
155
206
  neighborRatio?: number;
156
207
  maxClickSamples?: number;
@@ -158,6 +209,7 @@ export interface MasteringChainConfig {
158
209
  residualRatio?: number;
159
210
  };
160
211
  dereverb?: {
212
+ enabled?: boolean;
161
213
  threshold?: number;
162
214
  attenuation?: number;
163
215
  nFft?: number;
@@ -173,11 +225,20 @@ export interface MasteringChainConfig {
173
225
  };
174
226
  };
175
227
  eq?: {
228
+ /** Canonical nested tilt stage. */
229
+ tilt?: {
230
+ enabled?: boolean;
231
+ tiltDb?: number;
232
+ pivotHz?: number;
233
+ };
234
+ /** @deprecated Use `eq.tilt.tiltDb`. */
176
235
  tiltDb?: number;
236
+ /** @deprecated Use `eq.tilt.pivotHz`. */
177
237
  pivotHz?: number;
178
238
  };
179
239
  dynamics?: {
180
240
  compressor?: {
241
+ enabled?: boolean;
181
242
  thresholdDb?: number;
182
243
  ratio?: number;
183
244
  attackMs?: number;
@@ -187,6 +248,7 @@ export interface MasteringChainConfig {
187
248
  autoMakeup?: boolean;
188
249
  };
189
250
  deesser?: {
251
+ enabled?: boolean;
190
252
  frequencyHz?: number;
191
253
  thresholdDb?: number;
192
254
  ratio?: number;
@@ -196,6 +258,7 @@ export interface MasteringChainConfig {
196
258
  bandpassQ?: number;
197
259
  };
198
260
  transientShaper?: {
261
+ enabled?: boolean;
199
262
  attackGainDb?: number;
200
263
  sustainGainDb?: number;
201
264
  fastAttackMs?: number;
@@ -208,6 +271,7 @@ export interface MasteringChainConfig {
208
271
  lookaheadMs?: number;
209
272
  };
210
273
  multibandComp?: {
274
+ enabled?: boolean;
211
275
  lowCutoffHz?: number;
212
276
  highCutoffHz?: number;
213
277
  lowThresholdDb?: number;
@@ -226,6 +290,7 @@ export interface MasteringChainConfig {
226
290
  };
227
291
  saturation?: {
228
292
  tape?: {
293
+ enabled?: boolean;
229
294
  driveDb?: number;
230
295
  saturation?: number;
231
296
  hysteresis?: number;
@@ -236,6 +301,7 @@ export interface MasteringChainConfig {
236
301
  gapLoss?: number;
237
302
  };
238
303
  exciter?: {
304
+ enabled?: boolean;
239
305
  frequencyHz?: number;
240
306
  driveDb?: number;
241
307
  amount?: number;
@@ -245,6 +311,7 @@ export interface MasteringChainConfig {
245
311
  };
246
312
  spectral?: {
247
313
  airBand?: {
314
+ enabled?: boolean;
248
315
  amount?: number;
249
316
  shelfFrequencyHz?: number;
250
317
  dynamicThresholdDb?: number;
@@ -253,17 +320,20 @@ export interface MasteringChainConfig {
253
320
  };
254
321
  stereo?: {
255
322
  imager?: {
323
+ enabled?: boolean;
256
324
  width?: number;
257
325
  outputGainDb?: number;
258
326
  decorrelationAmount?: number;
259
327
  preserveEnergy?: boolean;
260
328
  };
261
329
  monoMaker?: {
330
+ enabled?: boolean;
262
331
  amount?: number;
263
332
  };
264
333
  };
265
334
  maximizer?: {
266
335
  truePeakLimiter?: {
336
+ enabled?: boolean;
267
337
  ceilingDb?: number;
268
338
  lookaheadMs?: number;
269
339
  releaseMs?: number;
@@ -272,9 +342,12 @@ export interface MasteringChainConfig {
272
342
  };
273
343
  };
274
344
  loudness?: {
345
+ enabled?: boolean;
275
346
  targetLufs?: number;
276
347
  ceilingDb?: number;
277
348
  truePeakOversample?: number;
349
+ releaseMs?: number;
350
+ applyGainAtInputRate?: boolean;
278
351
  };
279
352
  }
280
353
 
@@ -307,11 +380,38 @@ export interface StreamingMasteringChainConfig extends MasteringChainConfig {
307
380
  loudnessStaticGainPeakDb?: number;
308
381
  }
309
382
 
310
- export interface MasteringChainResult extends MasteringResult {
383
+ /** Gain reduction reported by a single dynamics/maximizer chain stage. */
384
+ export interface StageGainReduction {
385
+ /** Stage identifier, e.g. `"dynamics.compressor"`. */
386
+ stage: string;
387
+ /**
388
+ * Most recent (typically last-block) gain reduction in dB (negative or
389
+ * zero); for multiband stages it is the most-reduced band.
390
+ */
391
+ gainReductionDb: number;
392
+ }
393
+
394
+ export interface MasteringChainResult {
395
+ /** Latency-compensated offline output; no separate latency field is reported. */
396
+ samples: Float32Array;
397
+ sampleRate: number;
398
+ inputLufs: number;
399
+ outputLufs: number;
400
+ appliedGainDb: number;
311
401
  stages: string[];
402
+ /**
403
+ * ITU-R BS.1770-4 true peak of the output (dBTP), measured with the chain's
404
+ * configured loudness true-peak oversample factor (default 4x). Lets callers
405
+ * verify a preset ceiling was met without a second oversampled scan.
406
+ */
407
+ outputTruePeakDbtp: number;
408
+ /** EBU Tech 3342 Loudness Range of the output (LU). */
409
+ outputLra: number;
410
+ /** Per-stage gain reductions for the dynamics/maximizer stages (a subset of `stages`). */
411
+ stageGainReductions: StageGainReduction[];
312
412
  }
313
413
 
314
- export interface MasteringStereoChainResult {
414
+ export interface MasteringChainStereoResult {
315
415
  left: Float32Array;
316
416
  right: Float32Array;
317
417
  sampleRate: number;
@@ -319,9 +419,19 @@ export interface MasteringStereoChainResult {
319
419
  outputLufs: number;
320
420
  appliedGainDb: number;
321
421
  stages: string[];
322
- latencySamples?: number;
422
+ /** See {@link MasteringChainResult} for field semantics. */
423
+ outputTruePeakDbtp: number;
424
+ outputLra: number;
425
+ stageGainReductions: StageGainReduction[];
323
426
  }
324
427
 
428
+ /**
429
+ * @deprecated Use {@link MasteringChainStereoResult}. Retained as an alias for
430
+ * source compatibility; the canonical name matches the Node and Python
431
+ * bindings (`MasteringChainStereoResult`).
432
+ */
433
+ export type MasteringStereoChainResult = MasteringChainStereoResult;
434
+
325
435
  export interface MasteringStereoResult {
326
436
  left: Float32Array;
327
437
  right: Float32Array;
@@ -140,6 +140,7 @@ export interface KeyCandidate {
140
140
  export interface ChordDetectionOptions extends ValidateOptions {
141
141
  minDuration?: number;
142
142
  smoothingWindow?: number;
143
+ /** Final-template correlation threshold in [0, 1]; below it emits Unknown / N.C. */
143
144
  threshold?: number;
144
145
  useTriadsOnly?: boolean;
145
146
  nFft?: number;
@@ -36,6 +36,13 @@ export interface NoteStretchOptions {
36
36
  stretchRatio?: number;
37
37
  }
38
38
 
39
+ /** Options for `noteMove`. */
40
+ export interface NoteMoveOptions {
41
+ onsetSample?: number;
42
+ offsetSample?: number;
43
+ targetOnsetSample?: number;
44
+ }
45
+
39
46
  /** How a `spectralEdit` region op modifies the masked bins. */
40
47
  export type SpectralEditMode = 'gain' | 'attenuate' | 'mute' | 'heal';
41
48