@libraz/libsonare 1.5.2 → 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 (46) hide show
  1. package/README.md +18 -3
  2. package/dist/index.d.ts +1147 -181
  3. package/dist/index.js +4826 -3941
  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 +1067 -973
  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 +357 -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/index.ts +130 -1
  23. package/src/mastering_chain.ts +305 -50
  24. package/src/mastering_core.ts +232 -16
  25. package/src/mastering_dynamics.ts +66 -10
  26. package/src/mastering_repair.ts +119 -7
  27. package/src/metering.ts +366 -81
  28. package/src/mixer.ts +9 -2
  29. package/src/mixing_oneshot.ts +25 -5
  30. package/src/module_state.ts +1 -1
  31. package/src/project_class.ts +17 -6
  32. package/src/project_internal.ts +10 -2
  33. package/src/project_types.ts +25 -2
  34. package/src/public_types_mastering.ts +114 -4
  35. package/src/public_types_spectral.ts +7 -0
  36. package/src/quick_analysis.ts +325 -117
  37. package/src/realtime_engine.ts +8 -0
  38. package/src/sonare.js.d.ts +31 -0
  39. package/src/stream_analyzer.ts +20 -1
  40. package/src/stream_types.ts +12 -1
  41. package/src/validation.ts +17 -2
  42. package/src/worklet/engine-clips.ts +67 -2
  43. package/src/worklet/engine-processor.ts +33 -5
  44. package/src/worklet/engine.ts +7 -3
  45. package/src/worklet/guards.ts +3 -0
  46. package/src/worklet/messages.ts +27 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.5.2",
3
+ "version": "1.5.3",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Audio analysis library for music information retrieval",
@@ -35,6 +35,7 @@
35
35
  "format": "biome format --write src/ tests/",
36
36
  "test": "vitest run",
37
37
  "test:browser": "node scripts/audio-worklet-smoke.mjs",
38
+ "test:browser:audit": "node scripts/audit-boundary-smoke.mjs",
38
39
  "test:watch": "vitest",
39
40
  "prepublishOnly": "yarn clean && cp ../../LICENSE LICENSE && yarn build && yarn lint && yarn test"
40
41
  },
@@ -0,0 +1,46 @@
1
+ import type { MasteringChainConfig } from './public_types';
2
+
3
+ type ChainSection = { [key: string]: number | boolean | ChainSection | undefined };
4
+
5
+ /**
6
+ * Flattens a nested {@link MasteringChainConfig} into the dot-notation
7
+ * `{ "module.processor.param": value }` map the WASM core consumes. Internal
8
+ * helper shared by the mastering-chain / master-audio entry points; mirrors the
9
+ * Node facade so a nested config produces identical overrides on both surfaces.
10
+ */
11
+ export function flattenChainConfig(config: MasteringChainConfig): Record<string, number | boolean> {
12
+ const out: Record<string, number | boolean> = {};
13
+ const walk = (node: ChainSection, prefix: string): void => {
14
+ for (const [key, value] of Object.entries(node)) {
15
+ const path = prefix ? `${prefix}.${key}` : key;
16
+ if (typeof value === 'number' || typeof value === 'boolean') {
17
+ out[path] = value;
18
+ } else if (value !== null && typeof value === 'object') {
19
+ walk(value, path);
20
+ }
21
+ }
22
+ };
23
+ walk(config as ChainSection, '');
24
+
25
+ // Compatibility aliases for the original WASM-only shorthand. Normalize at
26
+ // this boundary so every native entry point receives the core parser's one
27
+ // canonical vocabulary; public types can migrate to the nested spelling
28
+ // without preserving a second C++ parser indefinitely.
29
+ const aliases: Record<string, string> = {
30
+ 'repair.denoise': 'repair.denoise.enabled',
31
+ 'repair.nFft': 'repair.denoise.nFft',
32
+ 'repair.hopLength': 'repair.denoise.hopLength',
33
+ 'repair.ddAlpha': 'repair.denoise.ddAlpha',
34
+ 'repair.gainFloor': 'repair.denoise.gainFloor',
35
+ 'eq.tiltDb': 'eq.tilt.tiltDb',
36
+ 'eq.pivotHz': 'eq.tilt.pivotHz',
37
+ };
38
+ for (const [legacy, canonical] of Object.entries(aliases)) {
39
+ const value = out[legacy];
40
+ if (value !== undefined) {
41
+ out[canonical] = value;
42
+ delete out[legacy];
43
+ }
44
+ }
45
+ return out;
46
+ }
package/src/audio.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  masteringChain,
8
8
  masteringProcess,
9
9
  normalize,
10
+ noteMove,
10
11
  noteStretch,
11
12
  percussive,
12
13
  pitchCorrectToMidi,
@@ -332,6 +333,10 @@ export class Audio {
332
333
  return noteStretch(this._samples, this._sampleRate, options);
333
334
  }
334
335
 
336
+ noteMove(options: import('./public_types.js').NoteMoveOptions = {}): Float32Array {
337
+ return noteMove(this._samples, this._sampleRate, options);
338
+ }
339
+
335
340
  voiceChange(options: VoiceChangeOptions = {}): Float32Array {
336
341
  return voiceChange(this._samples, this._sampleRate, options);
337
342
  }
@@ -344,15 +349,30 @@ export class Audio {
344
349
  return mastering(this._samples, this._sampleRate, options);
345
350
  }
346
351
 
347
- masteringChain(config: MasteringChainConfig): MasteringChainResult {
348
- return masteringChain(this._samples, this._sampleRate, config);
352
+ masteringChain(
353
+ config: MasteringChainConfig = {},
354
+ onProgress?: ProgressCallback,
355
+ ): MasteringChainResult {
356
+ return masteringChain({
357
+ samples: this._samples,
358
+ sampleRate: this._sampleRate,
359
+ config,
360
+ onProgress,
361
+ });
349
362
  }
350
363
 
351
364
  masterAudio(
352
365
  presetName: MasteringPreset = 'pop',
353
- overrides: Record<string, number | boolean> | null = null,
366
+ overrides: MasteringChainConfig | null = null,
367
+ onProgress?: ProgressCallback,
354
368
  ): MasteringChainResult {
355
- return masterAudio(this._samples, this._sampleRate, presetName, overrides ?? {});
369
+ return masterAudio({
370
+ samples: this._samples,
371
+ sampleRate: this._sampleRate,
372
+ preset: presetName,
373
+ overrides: overrides ?? {},
374
+ onProgress,
375
+ });
356
376
  }
357
377
 
358
378
  masteringProcess(
@@ -2,6 +2,7 @@ export {
2
2
  harmonic,
3
3
  hpss,
4
4
  normalize,
5
+ noteMove,
5
6
  noteStretch,
6
7
  percussive,
7
8
  pitchCorrectTimevarying,
@@ -11,7 +12,12 @@ export {
11
12
  spectralEdit,
12
13
  timeStretch,
13
14
  } from './effects_transform';
14
- export type { VoiceChangeOptions, VoiceChangeRealtimeOptions } from './effects_voice_change';
15
+ export type {
16
+ VoiceChangeOptions,
17
+ VoiceChangeRealtimeOptions,
18
+ VoiceChangeRealtimeRequest,
19
+ VoiceChangeRequest,
20
+ } from './effects_voice_change';
15
21
  export { voiceChange, voiceChangeRealtime } from './effects_voice_change';
16
22
  export {
17
23
  masterAudio,
@@ -27,7 +33,14 @@ export {
27
33
  export type {
28
34
  MasteringChannelPolicy,
29
35
  MasteringInsertParamInfo,
36
+ MasteringPairAnalyzeRequest,
37
+ MasteringPairProcessRequest,
30
38
  MasteringProcessorCatalogEntry,
39
+ MasteringProcessRequest,
40
+ MasteringProcessStereoRequest,
41
+ MasteringSamplesParamsRequest,
42
+ MasteringStereoAnalyzeRequest,
43
+ MasteringStreamingPreviewRequest,
31
44
  } from './mastering_core';
32
45
  export {
33
46
  mastering,
@@ -53,6 +66,9 @@ export type {
53
66
  CompressorOptions,
54
67
  DynamicsResult,
55
68
  GateOptions,
69
+ MasteringDynamicsCompressorRequest,
70
+ MasteringDynamicsGateRequest,
71
+ MasteringDynamicsTransientShaperRequest,
56
72
  TransientShaperOptions,
57
73
  } from './mastering_dynamics';
58
74
  export {
@@ -70,6 +86,13 @@ export type {
70
86
  DenoiseClassicalNoiseEstimator,
71
87
  DenoiseClassicalOptions,
72
88
  DereverbClassicalOptions,
89
+ MasteringRepairDeclickRequest,
90
+ MasteringRepairDeclipRequest,
91
+ MasteringRepairDecrackleRequest,
92
+ MasteringRepairDehumRequest,
93
+ MasteringRepairDenoiseClassicalRequest,
94
+ MasteringRepairDereverbClassicalRequest,
95
+ MasteringRepairTrimSilenceRequest,
73
96
  TrimSilenceMode,
74
97
  TrimSilenceOptions,
75
98
  } from './mastering_repair';
@@ -82,4 +105,5 @@ export {
82
105
  masteringRepairDereverbClassical,
83
106
  masteringRepairTrimSilence,
84
107
  } from './mastering_repair';
108
+ export type { MixStereoRequest } from './mixing_oneshot';
85
109
  export { mixingScenePresetJson, mixingScenePresetNames, mixStereo } from './mixing_oneshot';
@@ -1,6 +1,7 @@
1
1
  import { getSonareModule } from './module_state';
2
2
  import type {
3
3
  HpssResult,
4
+ NoteMoveOptions,
4
5
  NoteStretchOptions,
5
6
  PitchCorrectOptions,
6
7
  SpectralEditOptions,
@@ -13,6 +14,81 @@ function requireModule() {
13
14
  return getSonareModule();
14
15
  }
15
16
 
17
+ /** Canonical request form for HPSS. */
18
+ export interface HpssRequest {
19
+ samples: Float32Array;
20
+ sampleRate?: number;
21
+ kernelHarmonic?: number;
22
+ kernelPercussive?: number;
23
+ }
24
+
25
+ export interface HarmonicRequest extends ValidateOptions {
26
+ samples: Float32Array;
27
+ sampleRate?: number;
28
+ }
29
+
30
+ export interface PercussiveRequest extends ValidateOptions {
31
+ samples: Float32Array;
32
+ sampleRate?: number;
33
+ }
34
+
35
+ export interface TimeStretchRequest extends ValidateOptions {
36
+ samples: Float32Array;
37
+ sampleRate: number;
38
+ rate: number;
39
+ }
40
+
41
+ export interface PitchShiftRequest extends ValidateOptions {
42
+ samples: Float32Array;
43
+ sampleRate: number;
44
+ semitones: number;
45
+ }
46
+
47
+ export interface PitchCorrectToMidiRequest extends ValidateOptions {
48
+ samples: Float32Array;
49
+ sampleRate?: number;
50
+ currentMidi?: number;
51
+ targetMidi?: number;
52
+ }
53
+
54
+ export interface PitchCorrectToMidiTimevaryingRequest extends ValidateOptions {
55
+ samples: Float32Array;
56
+ f0Hz: Float32Array;
57
+ targetMidi: number;
58
+ sampleRate?: number;
59
+ hopLength?: number;
60
+ voiced?: Int32Array;
61
+ voicedProb?: Float32Array;
62
+ }
63
+
64
+ export interface PitchCorrectTimevaryingRequest extends PitchCorrectOptions {
65
+ samples: Float32Array;
66
+ f0Hz: Float32Array;
67
+ sampleRate?: number;
68
+ hopLength?: number;
69
+ }
70
+
71
+ export interface NoteStretchRequest extends NoteStretchOptions, ValidateOptions {
72
+ samples: Float32Array;
73
+ sampleRate?: number;
74
+ }
75
+ export interface NoteMoveRequest extends NoteMoveOptions, ValidateOptions {
76
+ samples: Float32Array;
77
+ sampleRate?: number;
78
+ }
79
+
80
+ export interface NormalizeRequest extends ValidateOptions {
81
+ samples: Float32Array;
82
+ sampleRate: number;
83
+ targetDb?: number;
84
+ }
85
+
86
+ export interface SpectralEditRequest extends SpectralEditOptions, ValidateOptions {
87
+ samples: Float32Array;
88
+ sampleRate: number;
89
+ ops?: SpectralRegionOp[];
90
+ }
91
+
16
92
  // ============================================================================
17
93
  // Effects
18
94
  // ============================================================================
@@ -26,13 +102,29 @@ function requireModule() {
26
102
  * @param kernelPercussive - Vertical median filter size for percussive (default: 31)
27
103
  * @returns Separated harmonic and percussive components
28
104
  */
105
+ export function hpss(request: HpssRequest): HpssResult;
29
106
  export function hpss(
30
107
  samples: Float32Array,
108
+ sampleRate?: number,
109
+ kernelHarmonic?: number,
110
+ kernelPercussive?: number,
111
+ ): HpssResult;
112
+ export function hpss(
113
+ samples: Float32Array | HpssRequest,
31
114
  sampleRate = 22050,
32
115
  kernelHarmonic = 31,
33
116
  kernelPercussive = 31,
34
117
  ): HpssResult {
35
- return requireModule().hpss(samples, sampleRate, kernelHarmonic, kernelPercussive);
118
+ const request =
119
+ samples instanceof Float32Array
120
+ ? { samples, sampleRate, kernelHarmonic, kernelPercussive }
121
+ : samples;
122
+ return requireModule().hpss(
123
+ request.samples,
124
+ request.sampleRate ?? 22050,
125
+ request.kernelHarmonic ?? 31,
126
+ request.kernelPercussive ?? 31,
127
+ );
36
128
  }
37
129
 
38
130
  /**
@@ -42,13 +134,20 @@ export function hpss(
42
134
  * @param sampleRate - Sample rate in Hz
43
135
  * @returns Harmonic component
44
136
  */
137
+ export function harmonic(request: HarmonicRequest): Float32Array;
45
138
  export function harmonic(
46
139
  samples: Float32Array,
47
- sampleRate: number,
140
+ sampleRate?: number,
141
+ options?: ValidateOptions,
142
+ ): Float32Array;
143
+ export function harmonic(
144
+ samples: Float32Array | HarmonicRequest,
145
+ sampleRate = 22050,
48
146
  options: ValidateOptions = {},
49
147
  ): Float32Array {
50
- assertSamples('harmonic', samples, options.validate !== false);
51
- return requireModule().harmonic(samples, sampleRate);
148
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
149
+ assertSamples('harmonic', request.samples, request.validate !== false);
150
+ return requireModule().harmonic(request.samples, request.sampleRate ?? 22050);
52
151
  }
53
152
 
54
153
  /**
@@ -58,13 +157,20 @@ export function harmonic(
58
157
  * @param sampleRate - Sample rate in Hz
59
158
  * @returns Percussive component
60
159
  */
160
+ export function percussive(request: PercussiveRequest): Float32Array;
61
161
  export function percussive(
62
162
  samples: Float32Array,
63
- sampleRate: number,
163
+ sampleRate?: number,
164
+ options?: ValidateOptions,
165
+ ): Float32Array;
166
+ export function percussive(
167
+ samples: Float32Array | PercussiveRequest,
168
+ sampleRate = 22050,
64
169
  options: ValidateOptions = {},
65
170
  ): Float32Array {
66
- assertSamples('percussive', samples, options.validate !== false);
67
- return requireModule().percussive(samples, sampleRate);
171
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
172
+ assertSamples('percussive', request.samples, request.validate !== false);
173
+ return requireModule().percussive(request.samples, request.sampleRate ?? 22050);
68
174
  }
69
175
 
70
176
  /**
@@ -75,14 +181,25 @@ export function percussive(
75
181
  * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
76
182
  * @returns Time-stretched audio
77
183
  */
184
+ export function timeStretch(request: TimeStretchRequest): Float32Array;
78
185
  export function timeStretch(
79
186
  samples: Float32Array,
80
187
  sampleRate: number,
81
188
  rate: number,
189
+ options?: ValidateOptions,
190
+ ): Float32Array;
191
+ export function timeStretch(
192
+ samples: Float32Array | TimeStretchRequest,
193
+ sampleRate?: number,
194
+ rate?: number,
82
195
  options: ValidateOptions = {},
83
196
  ): Float32Array {
84
- assertSamples('timeStretch', samples, options.validate !== false);
85
- return requireModule().timeStretch(samples, sampleRate, rate);
197
+ const request: TimeStretchRequest =
198
+ samples instanceof Float32Array
199
+ ? { samples, sampleRate: sampleRate as number, rate: rate as number, ...options }
200
+ : samples;
201
+ assertSamples('timeStretch', request.samples, request.validate !== false);
202
+ return requireModule().timeStretch(request.samples, request.sampleRate, request.rate);
86
203
  }
87
204
 
88
205
  /**
@@ -93,14 +210,25 @@ export function timeStretch(
93
210
  * @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
94
211
  * @returns Pitch-shifted audio
95
212
  */
213
+ export function pitchShift(request: PitchShiftRequest): Float32Array;
96
214
  export function pitchShift(
97
215
  samples: Float32Array,
98
216
  sampleRate: number,
99
217
  semitones: number,
218
+ options?: ValidateOptions,
219
+ ): Float32Array;
220
+ export function pitchShift(
221
+ samples: Float32Array | PitchShiftRequest,
222
+ sampleRate?: number,
223
+ semitones?: number,
100
224
  options: ValidateOptions = {},
101
225
  ): Float32Array {
102
- assertSamples('pitchShift', samples, options.validate !== false);
103
- return requireModule().pitchShift(samples, sampleRate, semitones);
226
+ const request: PitchShiftRequest =
227
+ samples instanceof Float32Array
228
+ ? { samples, sampleRate: sampleRate as number, semitones: semitones as number, ...options }
229
+ : samples;
230
+ assertSamples('pitchShift', request.samples, request.validate !== false);
231
+ return requireModule().pitchShift(request.samples, request.sampleRate, request.semitones);
104
232
  }
105
233
 
106
234
  /**
@@ -112,15 +240,32 @@ export function pitchShift(
112
240
  * @param targetMidi - Desired MIDI note number
113
241
  * @returns Pitch-corrected audio
114
242
  */
243
+ export function pitchCorrectToMidi(request: PitchCorrectToMidiRequest): Float32Array;
115
244
  export function pitchCorrectToMidi(
116
245
  samples: Float32Array,
246
+ sampleRate?: number,
247
+ currentMidi?: number,
248
+ targetMidi?: number,
249
+ options?: ValidateOptions,
250
+ ): Float32Array;
251
+ export function pitchCorrectToMidi(
252
+ samples: Float32Array | PitchCorrectToMidiRequest,
117
253
  sampleRate = 22050,
118
254
  currentMidi = 69.0,
119
255
  targetMidi = 69.0,
120
256
  options: ValidateOptions = {},
121
257
  ): Float32Array {
122
- assertSamples('pitchCorrectToMidi', samples, options.validate !== false);
123
- return requireModule().pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
258
+ const request =
259
+ samples instanceof Float32Array
260
+ ? { samples, sampleRate, currentMidi, targetMidi, ...options }
261
+ : samples;
262
+ assertSamples('pitchCorrectToMidi', request.samples, request.validate !== false);
263
+ return requireModule().pitchCorrectToMidi(
264
+ request.samples,
265
+ request.sampleRate ?? 22050,
266
+ request.currentMidi ?? 69.0,
267
+ request.targetMidi ?? 69.0,
268
+ );
124
269
  }
125
270
 
126
271
  /**
@@ -141,34 +286,60 @@ export function pitchCorrectToMidi(
141
286
  * @param voicedProb - Optional per-frame voicing probability in [0, 1]
142
287
  * @returns Pitch-corrected audio
143
288
  */
289
+ export function pitchCorrectToMidiTimevarying(
290
+ request: PitchCorrectToMidiTimevaryingRequest,
291
+ ): Float32Array;
144
292
  export function pitchCorrectToMidiTimevarying(
145
293
  samples: Float32Array,
146
294
  f0Hz: Float32Array,
147
295
  targetMidi: number,
296
+ sampleRate?: number,
297
+ hopLength?: number,
298
+ voiced?: Int32Array,
299
+ voicedProb?: Float32Array,
300
+ options?: ValidateOptions,
301
+ ): Float32Array;
302
+ export function pitchCorrectToMidiTimevarying(
303
+ samples: Float32Array | PitchCorrectToMidiTimevaryingRequest,
304
+ f0Hz?: Float32Array,
305
+ targetMidi?: number,
148
306
  sampleRate = 22050,
149
307
  hopLength = 512,
150
308
  voiced?: Int32Array,
151
309
  voicedProb?: Float32Array,
152
310
  options: ValidateOptions = {},
153
311
  ): Float32Array {
154
- assertSamples('pitchCorrectToMidiTimevarying', samples, options.validate !== false);
155
- if (voiced && voiced.length !== f0Hz.length) {
312
+ const request: PitchCorrectToMidiTimevaryingRequest =
313
+ samples instanceof Float32Array
314
+ ? {
315
+ samples,
316
+ f0Hz: f0Hz as Float32Array,
317
+ targetMidi: targetMidi as number,
318
+ sampleRate,
319
+ hopLength,
320
+ voiced,
321
+ voicedProb,
322
+ ...options,
323
+ }
324
+ : samples;
325
+ assertSamples('pitchCorrectToMidiTimevarying', request.samples, request.validate !== false);
326
+ if (request.voiced && request.voiced.length !== request.f0Hz.length) {
156
327
  throw new RangeError('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length');
157
328
  }
158
- if (voicedProb && voicedProb.length !== f0Hz.length) {
329
+ if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
159
330
  throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
160
331
  }
161
332
  // The embind layer reads the companion arrays as Float32Array (voiced uses
162
333
  // 0.0/1.0); convert here so a single native conversion path suffices.
163
- const voicedF32 = voiced ? Float32Array.from(voiced) : undefined;
334
+ const voicedF32 = request.voiced ? Float32Array.from(request.voiced) : undefined;
164
335
  return requireModule().pitchCorrectToMidiTimevarying(
165
- samples,
166
- sampleRate,
167
- f0Hz,
168
- targetMidi,
169
- hopLength,
336
+ request.samples,
337
+ request.sampleRate ?? 22050,
338
+ request.f0Hz,
339
+ request.targetMidi,
340
+ request.hopLength ?? 512,
170
341
  voicedF32,
171
- voicedProb,
342
+ request.voicedProb,
172
343
  );
173
344
  }
174
345
 
@@ -188,31 +359,43 @@ export function pitchCorrectToMidiTimevarying(
188
359
  * @param options - Target mode + retune knobs + optional voiced/voicedProb arrays
189
360
  * @returns Pitch-corrected audio
190
361
  */
362
+ export function pitchCorrectTimevarying(request: PitchCorrectTimevaryingRequest): Float32Array;
191
363
  export function pitchCorrectTimevarying(
192
364
  samples: Float32Array,
193
365
  f0Hz: Float32Array,
366
+ sampleRate?: number,
367
+ hopLength?: number,
368
+ options?: PitchCorrectOptions,
369
+ ): Float32Array;
370
+ export function pitchCorrectTimevarying(
371
+ samples: Float32Array | PitchCorrectTimevaryingRequest,
372
+ f0Hz?: Float32Array,
194
373
  sampleRate = 22050,
195
374
  hopLength = 512,
196
375
  options: PitchCorrectOptions = {},
197
376
  ): Float32Array {
198
- assertSamples('pitchCorrectTimevarying', samples, options.validate !== false);
199
- if (options.voiced && options.voiced.length !== f0Hz.length) {
377
+ const request: PitchCorrectTimevaryingRequest =
378
+ samples instanceof Float32Array
379
+ ? { samples, f0Hz: f0Hz as Float32Array, sampleRate, hopLength, ...options }
380
+ : samples;
381
+ assertSamples('pitchCorrectTimevarying', request.samples, request.validate !== false);
382
+ if (request.voiced && request.voiced.length !== request.f0Hz.length) {
200
383
  throw new RangeError('pitchCorrectTimevarying: voiced length must match f0Hz length');
201
384
  }
202
- if (options.voicedProb && options.voicedProb.length !== f0Hz.length) {
385
+ if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
203
386
  throw new RangeError('pitchCorrectTimevarying: voicedProb length must match f0Hz length');
204
387
  }
205
388
  // The embind layer reads the companion arrays as Float32Array (voiced uses
206
389
  // 0.0/1.0); convert here so a single native conversion path suffices.
207
390
  const nativeOptions = {
208
- ...options,
209
- voiced: options.voiced ? Float32Array.from(options.voiced) : undefined,
391
+ ...request,
392
+ voiced: request.voiced ? Float32Array.from(request.voiced) : undefined,
210
393
  };
211
394
  return requireModule().pitchCorrectTimevarying(
212
- samples,
213
- sampleRate,
214
- f0Hz,
215
- hopLength,
395
+ request.samples,
396
+ request.sampleRate ?? 22050,
397
+ request.f0Hz,
398
+ request.hopLength ?? 512,
216
399
  nativeOptions,
217
400
  );
218
401
  }
@@ -227,18 +410,48 @@ export function pitchCorrectTimevarying(
227
410
  * @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
228
411
  * @returns Audio with the note region stretched
229
412
  */
413
+ export function noteStretch(request: NoteStretchRequest): Float32Array;
230
414
  export function noteStretch(
231
415
  samples: Float32Array,
416
+ sampleRate?: number,
417
+ options?: NoteStretchOptions & ValidateOptions,
418
+ ): Float32Array;
419
+ export function noteStretch(
420
+ samples: Float32Array | NoteStretchRequest,
232
421
  sampleRate = 22050,
233
422
  options: NoteStretchOptions & ValidateOptions = {},
234
423
  ): Float32Array {
235
- assertSamples('noteStretch', samples, options.validate !== false);
424
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
425
+ assertSamples('noteStretch', request.samples, request.validate !== false);
236
426
  return requireModule().noteStretch(
237
- samples,
238
- sampleRate,
239
- options.onsetSample ?? 0,
240
- options.offsetSample ?? 0,
241
- options.stretchRatio ?? 1.0,
427
+ request.samples,
428
+ request.sampleRate ?? 22050,
429
+ request.onsetSample ?? 0,
430
+ request.offsetSample ?? 0,
431
+ request.stretchRatio ?? 1.0,
432
+ );
433
+ }
434
+
435
+ /** Move a note region to a new onset without changing its duration. */
436
+ export function noteMove(request: NoteMoveRequest): Float32Array;
437
+ export function noteMove(
438
+ samples: Float32Array,
439
+ sampleRate?: number,
440
+ options?: NoteMoveOptions & ValidateOptions,
441
+ ): Float32Array;
442
+ export function noteMove(
443
+ samples: Float32Array | NoteMoveRequest,
444
+ sampleRate = 22050,
445
+ options: NoteMoveOptions & ValidateOptions = {},
446
+ ): Float32Array {
447
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
448
+ assertSamples('noteMove', request.samples, request.validate !== false);
449
+ return requireModule().noteMove(
450
+ request.samples,
451
+ request.sampleRate ?? 22050,
452
+ request.onsetSample ?? 0,
453
+ request.offsetSample ?? 0,
454
+ request.targetOnsetSample ?? 0,
242
455
  );
243
456
  }
244
457
 
@@ -250,14 +463,25 @@ export function noteStretch(
250
463
  * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
251
464
  * @returns Normalized audio
252
465
  */
466
+ export function normalize(request: NormalizeRequest): Float32Array;
253
467
  export function normalize(
254
468
  samples: Float32Array,
255
469
  sampleRate: number,
470
+ targetDb?: number,
471
+ options?: ValidateOptions,
472
+ ): Float32Array;
473
+ export function normalize(
474
+ samples: Float32Array | NormalizeRequest,
475
+ sampleRate?: number,
256
476
  targetDb = 0.0,
257
477
  options: ValidateOptions = {},
258
478
  ): Float32Array {
259
- assertSamples('normalize', samples, options.validate !== false);
260
- return requireModule().normalize(samples, sampleRate, targetDb);
479
+ const request: NormalizeRequest =
480
+ samples instanceof Float32Array
481
+ ? { samples, sampleRate: sampleRate as number, targetDb, ...options }
482
+ : samples;
483
+ assertSamples('normalize', request.samples, request.validate !== false);
484
+ return requireModule().normalize(request.samples, request.sampleRate, request.targetDb ?? 0.0);
261
485
  }
262
486
 
263
487
  /**
@@ -274,13 +498,29 @@ export function normalize(
274
498
  * @param options - STFT + heal configuration ({@link SpectralEditOptions})
275
499
  * @returns Edited audio
276
500
  */
501
+ export function spectralEdit(request: SpectralEditRequest): Float32Array;
277
502
  export function spectralEdit(
278
503
  samples: Float32Array,
279
504
  sampleRate: number,
505
+ ops?: SpectralRegionOp[],
506
+ options?: SpectralEditOptions & ValidateOptions,
507
+ ): Float32Array;
508
+ export function spectralEdit(
509
+ samples: Float32Array | SpectralEditRequest,
510
+ sampleRate?: number,
280
511
  ops: SpectralRegionOp[] = [],
281
512
  options: SpectralEditOptions & ValidateOptions = {},
282
513
  ): Float32Array {
283
- assertSamples('spectralEdit', samples, options.validate !== false);
284
- assertSampleRate('spectralEdit', sampleRate);
285
- return requireModule().spectralEdit(samples, sampleRate, ops, options as Record<string, unknown>);
514
+ const request: SpectralEditRequest =
515
+ samples instanceof Float32Array
516
+ ? { samples, sampleRate: sampleRate as number, ops, ...options }
517
+ : samples;
518
+ assertSamples('spectralEdit', request.samples, request.validate !== false);
519
+ assertSampleRate('spectralEdit', request.sampleRate);
520
+ return requireModule().spectralEdit(
521
+ request.samples,
522
+ request.sampleRate,
523
+ request.ops ?? [],
524
+ request as unknown as Record<string, unknown>,
525
+ );
286
526
  }