@libraz/libsonare 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -725
- package/dist/index.d.ts +1155 -183
- package/dist/index.js +4924 -3935
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +29 -1
- package/dist/worklet.js +1141 -970
- package/dist/worklet.js.map +1 -1
- package/package.json +15 -3
- package/src/_chain_config.ts +46 -0
- package/src/audio.ts +24 -4
- package/src/codes.ts +27 -4
- package/src/effects_mastering.ts +25 -1
- package/src/effects_transform.ts +292 -46
- package/src/effects_voice_change.ts +46 -53
- package/src/feature_core.ts +316 -12
- package/src/feature_music.ts +371 -4
- package/src/feature_pitch.ts +61 -0
- package/src/feature_resample.ts +17 -2
- package/src/feature_spectral.ts +349 -2
- package/src/feature_spectrogram.ts +336 -0
- package/src/index.ts +130 -1
- package/src/mastering_chain.ts +305 -50
- package/src/mastering_core.ts +232 -16
- package/src/mastering_dynamics.ts +66 -10
- package/src/mastering_repair.ts +119 -7
- package/src/metering.ts +370 -83
- package/src/mixer.ts +9 -2
- package/src/mixing_oneshot.ts +25 -5
- package/src/module_state.ts +1 -1
- package/src/project_class.ts +30 -6
- package/src/project_internal.ts +12 -2
- package/src/project_types.ts +25 -2
- package/src/public_types_mastering.ts +114 -4
- package/src/public_types_spectral.ts +7 -0
- package/src/quick_analysis.ts +325 -117
- package/src/realtime_engine.ts +8 -0
- package/src/realtime_voice_changer.ts +79 -3
- package/src/sonare.js.d.ts +35 -0
- package/src/stream_analyzer.ts +20 -1
- package/src/stream_types.ts +12 -1
- package/src/validation.ts +17 -2
- package/src/worklet/engine-clips.ts +67 -2
- package/src/worklet/engine-processor.ts +33 -5
- package/src/worklet/engine.ts +7 -3
- package/src/worklet/guards.ts +3 -0
- package/src/worklet/messages.ts +27 -0
package/src/effects_transform.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
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,49 +157,82 @@ 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
|
|
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
|
-
|
|
67
|
-
|
|
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
|
/**
|
|
71
177
|
* Time-stretch audio without changing pitch.
|
|
72
178
|
*
|
|
73
179
|
* @param samples - Audio samples (mono, float32)
|
|
74
|
-
* @param sampleRate - Sample rate in Hz
|
|
180
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
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
|
-
|
|
85
|
-
|
|
197
|
+
const request: TimeStretchRequest =
|
|
198
|
+
samples instanceof Float32Array
|
|
199
|
+
? { samples, sampleRate, rate: rate as number, ...options }
|
|
200
|
+
: samples;
|
|
201
|
+
assertSamples('timeStretch', request.samples, request.validate !== false);
|
|
202
|
+
return requireModule().timeStretch(request.samples, request.sampleRate ?? 22050, request.rate);
|
|
86
203
|
}
|
|
87
204
|
|
|
88
205
|
/**
|
|
89
206
|
* Pitch-shift audio without changing duration.
|
|
90
207
|
*
|
|
91
208
|
* @param samples - Audio samples (mono, float32)
|
|
92
|
-
* @param sampleRate - Sample rate in Hz
|
|
209
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
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
|
-
|
|
103
|
-
|
|
226
|
+
const request: PitchShiftRequest =
|
|
227
|
+
samples instanceof Float32Array
|
|
228
|
+
? { samples, sampleRate, semitones: semitones as number, ...options }
|
|
229
|
+
: samples;
|
|
230
|
+
assertSamples('pitchShift', request.samples, request.validate !== false);
|
|
231
|
+
return requireModule().pitchShift(
|
|
232
|
+
request.samples,
|
|
233
|
+
request.sampleRate ?? 22050,
|
|
234
|
+
request.semitones,
|
|
235
|
+
);
|
|
104
236
|
}
|
|
105
237
|
|
|
106
238
|
/**
|
|
@@ -112,15 +244,32 @@ export function pitchShift(
|
|
|
112
244
|
* @param targetMidi - Desired MIDI note number
|
|
113
245
|
* @returns Pitch-corrected audio
|
|
114
246
|
*/
|
|
247
|
+
export function pitchCorrectToMidi(request: PitchCorrectToMidiRequest): Float32Array;
|
|
115
248
|
export function pitchCorrectToMidi(
|
|
116
249
|
samples: Float32Array,
|
|
250
|
+
sampleRate?: number,
|
|
251
|
+
currentMidi?: number,
|
|
252
|
+
targetMidi?: number,
|
|
253
|
+
options?: ValidateOptions,
|
|
254
|
+
): Float32Array;
|
|
255
|
+
export function pitchCorrectToMidi(
|
|
256
|
+
samples: Float32Array | PitchCorrectToMidiRequest,
|
|
117
257
|
sampleRate = 22050,
|
|
118
258
|
currentMidi = 69.0,
|
|
119
259
|
targetMidi = 69.0,
|
|
120
260
|
options: ValidateOptions = {},
|
|
121
261
|
): Float32Array {
|
|
122
|
-
|
|
123
|
-
|
|
262
|
+
const request =
|
|
263
|
+
samples instanceof Float32Array
|
|
264
|
+
? { samples, sampleRate, currentMidi, targetMidi, ...options }
|
|
265
|
+
: samples;
|
|
266
|
+
assertSamples('pitchCorrectToMidi', request.samples, request.validate !== false);
|
|
267
|
+
return requireModule().pitchCorrectToMidi(
|
|
268
|
+
request.samples,
|
|
269
|
+
request.sampleRate ?? 22050,
|
|
270
|
+
request.currentMidi ?? 69.0,
|
|
271
|
+
request.targetMidi ?? 69.0,
|
|
272
|
+
);
|
|
124
273
|
}
|
|
125
274
|
|
|
126
275
|
/**
|
|
@@ -141,34 +290,60 @@ export function pitchCorrectToMidi(
|
|
|
141
290
|
* @param voicedProb - Optional per-frame voicing probability in [0, 1]
|
|
142
291
|
* @returns Pitch-corrected audio
|
|
143
292
|
*/
|
|
293
|
+
export function pitchCorrectToMidiTimevarying(
|
|
294
|
+
request: PitchCorrectToMidiTimevaryingRequest,
|
|
295
|
+
): Float32Array;
|
|
144
296
|
export function pitchCorrectToMidiTimevarying(
|
|
145
297
|
samples: Float32Array,
|
|
146
298
|
f0Hz: Float32Array,
|
|
147
299
|
targetMidi: number,
|
|
300
|
+
sampleRate?: number,
|
|
301
|
+
hopLength?: number,
|
|
302
|
+
voiced?: Int32Array,
|
|
303
|
+
voicedProb?: Float32Array,
|
|
304
|
+
options?: ValidateOptions,
|
|
305
|
+
): Float32Array;
|
|
306
|
+
export function pitchCorrectToMidiTimevarying(
|
|
307
|
+
samples: Float32Array | PitchCorrectToMidiTimevaryingRequest,
|
|
308
|
+
f0Hz?: Float32Array,
|
|
309
|
+
targetMidi?: number,
|
|
148
310
|
sampleRate = 22050,
|
|
149
311
|
hopLength = 512,
|
|
150
312
|
voiced?: Int32Array,
|
|
151
313
|
voicedProb?: Float32Array,
|
|
152
314
|
options: ValidateOptions = {},
|
|
153
315
|
): Float32Array {
|
|
154
|
-
|
|
155
|
-
|
|
316
|
+
const request: PitchCorrectToMidiTimevaryingRequest =
|
|
317
|
+
samples instanceof Float32Array
|
|
318
|
+
? {
|
|
319
|
+
samples,
|
|
320
|
+
f0Hz: f0Hz as Float32Array,
|
|
321
|
+
targetMidi: targetMidi as number,
|
|
322
|
+
sampleRate,
|
|
323
|
+
hopLength,
|
|
324
|
+
voiced,
|
|
325
|
+
voicedProb,
|
|
326
|
+
...options,
|
|
327
|
+
}
|
|
328
|
+
: samples;
|
|
329
|
+
assertSamples('pitchCorrectToMidiTimevarying', request.samples, request.validate !== false);
|
|
330
|
+
if (request.voiced && request.voiced.length !== request.f0Hz.length) {
|
|
156
331
|
throw new RangeError('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length');
|
|
157
332
|
}
|
|
158
|
-
if (voicedProb && voicedProb.length !== f0Hz.length) {
|
|
333
|
+
if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
|
|
159
334
|
throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
|
|
160
335
|
}
|
|
161
336
|
// The embind layer reads the companion arrays as Float32Array (voiced uses
|
|
162
337
|
// 0.0/1.0); convert here so a single native conversion path suffices.
|
|
163
|
-
const voicedF32 = voiced ? Float32Array.from(voiced) : undefined;
|
|
338
|
+
const voicedF32 = request.voiced ? Float32Array.from(request.voiced) : undefined;
|
|
164
339
|
return requireModule().pitchCorrectToMidiTimevarying(
|
|
165
|
-
samples,
|
|
166
|
-
sampleRate,
|
|
167
|
-
f0Hz,
|
|
168
|
-
targetMidi,
|
|
169
|
-
hopLength,
|
|
340
|
+
request.samples,
|
|
341
|
+
request.sampleRate ?? 22050,
|
|
342
|
+
request.f0Hz,
|
|
343
|
+
request.targetMidi,
|
|
344
|
+
request.hopLength ?? 512,
|
|
170
345
|
voicedF32,
|
|
171
|
-
voicedProb,
|
|
346
|
+
request.voicedProb,
|
|
172
347
|
);
|
|
173
348
|
}
|
|
174
349
|
|
|
@@ -188,31 +363,43 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
188
363
|
* @param options - Target mode + retune knobs + optional voiced/voicedProb arrays
|
|
189
364
|
* @returns Pitch-corrected audio
|
|
190
365
|
*/
|
|
366
|
+
export function pitchCorrectTimevarying(request: PitchCorrectTimevaryingRequest): Float32Array;
|
|
191
367
|
export function pitchCorrectTimevarying(
|
|
192
368
|
samples: Float32Array,
|
|
193
369
|
f0Hz: Float32Array,
|
|
370
|
+
sampleRate?: number,
|
|
371
|
+
hopLength?: number,
|
|
372
|
+
options?: PitchCorrectOptions,
|
|
373
|
+
): Float32Array;
|
|
374
|
+
export function pitchCorrectTimevarying(
|
|
375
|
+
samples: Float32Array | PitchCorrectTimevaryingRequest,
|
|
376
|
+
f0Hz?: Float32Array,
|
|
194
377
|
sampleRate = 22050,
|
|
195
378
|
hopLength = 512,
|
|
196
379
|
options: PitchCorrectOptions = {},
|
|
197
380
|
): Float32Array {
|
|
198
|
-
|
|
199
|
-
|
|
381
|
+
const request: PitchCorrectTimevaryingRequest =
|
|
382
|
+
samples instanceof Float32Array
|
|
383
|
+
? { samples, f0Hz: f0Hz as Float32Array, sampleRate, hopLength, ...options }
|
|
384
|
+
: samples;
|
|
385
|
+
assertSamples('pitchCorrectTimevarying', request.samples, request.validate !== false);
|
|
386
|
+
if (request.voiced && request.voiced.length !== request.f0Hz.length) {
|
|
200
387
|
throw new RangeError('pitchCorrectTimevarying: voiced length must match f0Hz length');
|
|
201
388
|
}
|
|
202
|
-
if (
|
|
389
|
+
if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
|
|
203
390
|
throw new RangeError('pitchCorrectTimevarying: voicedProb length must match f0Hz length');
|
|
204
391
|
}
|
|
205
392
|
// The embind layer reads the companion arrays as Float32Array (voiced uses
|
|
206
393
|
// 0.0/1.0); convert here so a single native conversion path suffices.
|
|
207
394
|
const nativeOptions = {
|
|
208
|
-
...
|
|
209
|
-
voiced:
|
|
395
|
+
...request,
|
|
396
|
+
voiced: request.voiced ? Float32Array.from(request.voiced) : undefined,
|
|
210
397
|
};
|
|
211
398
|
return requireModule().pitchCorrectTimevarying(
|
|
212
|
-
samples,
|
|
213
|
-
sampleRate,
|
|
214
|
-
f0Hz,
|
|
215
|
-
hopLength,
|
|
399
|
+
request.samples,
|
|
400
|
+
request.sampleRate ?? 22050,
|
|
401
|
+
request.f0Hz,
|
|
402
|
+
request.hopLength ?? 512,
|
|
216
403
|
nativeOptions,
|
|
217
404
|
);
|
|
218
405
|
}
|
|
@@ -227,18 +414,48 @@ export function pitchCorrectTimevarying(
|
|
|
227
414
|
* @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
|
|
228
415
|
* @returns Audio with the note region stretched
|
|
229
416
|
*/
|
|
417
|
+
export function noteStretch(request: NoteStretchRequest): Float32Array;
|
|
230
418
|
export function noteStretch(
|
|
231
419
|
samples: Float32Array,
|
|
420
|
+
sampleRate?: number,
|
|
421
|
+
options?: NoteStretchOptions & ValidateOptions,
|
|
422
|
+
): Float32Array;
|
|
423
|
+
export function noteStretch(
|
|
424
|
+
samples: Float32Array | NoteStretchRequest,
|
|
232
425
|
sampleRate = 22050,
|
|
233
426
|
options: NoteStretchOptions & ValidateOptions = {},
|
|
234
427
|
): Float32Array {
|
|
235
|
-
|
|
428
|
+
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
429
|
+
assertSamples('noteStretch', request.samples, request.validate !== false);
|
|
236
430
|
return requireModule().noteStretch(
|
|
237
|
-
samples,
|
|
238
|
-
sampleRate,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
431
|
+
request.samples,
|
|
432
|
+
request.sampleRate ?? 22050,
|
|
433
|
+
request.onsetSample ?? 0,
|
|
434
|
+
request.offsetSample ?? 0,
|
|
435
|
+
request.stretchRatio ?? 1.0,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Move a note region to a new onset without changing its duration. */
|
|
440
|
+
export function noteMove(request: NoteMoveRequest): Float32Array;
|
|
441
|
+
export function noteMove(
|
|
442
|
+
samples: Float32Array,
|
|
443
|
+
sampleRate?: number,
|
|
444
|
+
options?: NoteMoveOptions & ValidateOptions,
|
|
445
|
+
): Float32Array;
|
|
446
|
+
export function noteMove(
|
|
447
|
+
samples: Float32Array | NoteMoveRequest,
|
|
448
|
+
sampleRate = 22050,
|
|
449
|
+
options: NoteMoveOptions & ValidateOptions = {},
|
|
450
|
+
): Float32Array {
|
|
451
|
+
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
452
|
+
assertSamples('noteMove', request.samples, request.validate !== false);
|
|
453
|
+
return requireModule().noteMove(
|
|
454
|
+
request.samples,
|
|
455
|
+
request.sampleRate ?? 22050,
|
|
456
|
+
request.onsetSample ?? 0,
|
|
457
|
+
request.offsetSample ?? 0,
|
|
458
|
+
request.targetOnsetSample ?? 0,
|
|
242
459
|
);
|
|
243
460
|
}
|
|
244
461
|
|
|
@@ -246,18 +463,31 @@ export function noteStretch(
|
|
|
246
463
|
* Normalize audio to target peak level.
|
|
247
464
|
*
|
|
248
465
|
* @param samples - Audio samples (mono, float32)
|
|
249
|
-
* @param sampleRate - Sample rate in Hz
|
|
466
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
250
467
|
* @param targetDb - Target peak level in dB (default: 0 dB = full scale)
|
|
251
468
|
* @returns Normalized audio
|
|
252
469
|
*/
|
|
470
|
+
export function normalize(request: NormalizeRequest): Float32Array;
|
|
253
471
|
export function normalize(
|
|
254
472
|
samples: Float32Array,
|
|
255
473
|
sampleRate: number,
|
|
474
|
+
targetDb?: number,
|
|
475
|
+
options?: ValidateOptions,
|
|
476
|
+
): Float32Array;
|
|
477
|
+
export function normalize(
|
|
478
|
+
samples: Float32Array | NormalizeRequest,
|
|
479
|
+
sampleRate?: number,
|
|
256
480
|
targetDb = 0.0,
|
|
257
481
|
options: ValidateOptions = {},
|
|
258
482
|
): Float32Array {
|
|
259
|
-
|
|
260
|
-
|
|
483
|
+
const request: NormalizeRequest =
|
|
484
|
+
samples instanceof Float32Array ? { samples, sampleRate, targetDb, ...options } : samples;
|
|
485
|
+
assertSamples('normalize', request.samples, request.validate !== false);
|
|
486
|
+
return requireModule().normalize(
|
|
487
|
+
request.samples,
|
|
488
|
+
request.sampleRate ?? 22050,
|
|
489
|
+
request.targetDb ?? 0.0,
|
|
490
|
+
);
|
|
261
491
|
}
|
|
262
492
|
|
|
263
493
|
/**
|
|
@@ -274,13 +504,29 @@ export function normalize(
|
|
|
274
504
|
* @param options - STFT + heal configuration ({@link SpectralEditOptions})
|
|
275
505
|
* @returns Edited audio
|
|
276
506
|
*/
|
|
507
|
+
export function spectralEdit(request: SpectralEditRequest): Float32Array;
|
|
277
508
|
export function spectralEdit(
|
|
278
509
|
samples: Float32Array,
|
|
279
510
|
sampleRate: number,
|
|
511
|
+
ops?: SpectralRegionOp[],
|
|
512
|
+
options?: SpectralEditOptions & ValidateOptions,
|
|
513
|
+
): Float32Array;
|
|
514
|
+
export function spectralEdit(
|
|
515
|
+
samples: Float32Array | SpectralEditRequest,
|
|
516
|
+
sampleRate?: number,
|
|
280
517
|
ops: SpectralRegionOp[] = [],
|
|
281
518
|
options: SpectralEditOptions & ValidateOptions = {},
|
|
282
519
|
): Float32Array {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
520
|
+
const request: SpectralEditRequest =
|
|
521
|
+
samples instanceof Float32Array
|
|
522
|
+
? { samples, sampleRate: sampleRate as number, ops, ...options }
|
|
523
|
+
: samples;
|
|
524
|
+
assertSamples('spectralEdit', request.samples, request.validate !== false);
|
|
525
|
+
assertSampleRate('spectralEdit', request.sampleRate);
|
|
526
|
+
return requireModule().spectralEdit(
|
|
527
|
+
request.samples,
|
|
528
|
+
request.sampleRate,
|
|
529
|
+
request.ops ?? [],
|
|
530
|
+
request as unknown as Record<string, unknown>,
|
|
531
|
+
);
|
|
286
532
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getSonareModule } from './module_state';
|
|
2
2
|
import type { RealtimeVoiceChangerConfigInput } from './public_types';
|
|
3
|
-
import { RealtimeVoiceChanger } from './streaming_mixing';
|
|
4
3
|
import type { ValidateOptions } from './validation';
|
|
5
4
|
import { assertSamples } from './validation';
|
|
6
5
|
|
|
@@ -16,6 +15,12 @@ export interface VoiceChangeOptions extends ValidateOptions {
|
|
|
16
15
|
formantFactor?: number;
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
/** Canonical request form for one-shot voice changing. */
|
|
19
|
+
export interface VoiceChangeRequest extends VoiceChangeOptions {
|
|
20
|
+
samples: Float32Array;
|
|
21
|
+
sampleRate?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
25
|
* Apply a voice change by shifting pitch and formants independently.
|
|
21
26
|
*
|
|
@@ -24,17 +29,24 @@ export interface VoiceChangeOptions extends ValidateOptions {
|
|
|
24
29
|
* @param options - Pitch/formant settings ({@link VoiceChangeOptions})
|
|
25
30
|
* @returns Voice-changed audio
|
|
26
31
|
*/
|
|
32
|
+
export function voiceChange(request: VoiceChangeRequest): Float32Array;
|
|
27
33
|
export function voiceChange(
|
|
28
34
|
samples: Float32Array,
|
|
35
|
+
sampleRate?: number,
|
|
36
|
+
options?: VoiceChangeOptions,
|
|
37
|
+
): Float32Array;
|
|
38
|
+
export function voiceChange(
|
|
39
|
+
samples: Float32Array | VoiceChangeRequest,
|
|
29
40
|
sampleRate = 22050,
|
|
30
41
|
options: VoiceChangeOptions = {},
|
|
31
42
|
): Float32Array {
|
|
32
|
-
|
|
43
|
+
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
44
|
+
assertSamples('voiceChange', request.samples, request.validate !== false);
|
|
33
45
|
return requireModule().voiceChange(
|
|
34
|
-
samples,
|
|
35
|
-
sampleRate,
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
request.samples,
|
|
47
|
+
request.sampleRate ?? 22050,
|
|
48
|
+
request.pitchSemitones ?? 0.0,
|
|
49
|
+
request.formantFactor ?? 1.0,
|
|
38
50
|
);
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -42,50 +54,23 @@ export function voiceChange(
|
|
|
42
54
|
export interface VoiceChangeRealtimeOptions extends ValidateOptions {
|
|
43
55
|
/** Channel count (1 = mono, 2 = interleaved stereo). */
|
|
44
56
|
channels?: 1 | 2;
|
|
45
|
-
/**
|
|
57
|
+
/** @deprecated The shared C-ABI renderer uses a fixed cross-surface block size. */
|
|
46
58
|
blockSize?: number;
|
|
47
59
|
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
samples: Float32Array
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
): Float32Array {
|
|
55
|
-
const latencyFrames = Math.max(0, changer.latencySamples());
|
|
56
|
-
if (channels === 1) {
|
|
57
|
-
const total = samples.length + latencyFrames;
|
|
58
|
-
const input = new Float32Array(total);
|
|
59
|
-
input.set(samples);
|
|
60
|
-
const processed = new Float32Array(total);
|
|
61
|
-
for (let offset = 0; offset < total; offset += blockFrames) {
|
|
62
|
-
const block = input.subarray(offset, Math.min(offset + blockFrames, total));
|
|
63
|
-
processed.set(changer.processMono(block), offset);
|
|
64
|
-
}
|
|
65
|
-
return processed.slice(latencyFrames, latencyFrames + samples.length);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const frames = samples.length / 2;
|
|
69
|
-
const totalFrames = frames + latencyFrames;
|
|
70
|
-
const input = new Float32Array(totalFrames * 2);
|
|
71
|
-
input.set(samples);
|
|
72
|
-
const processed = new Float32Array(totalFrames * 2);
|
|
73
|
-
const frameStride = blockFrames * 2;
|
|
74
|
-
for (let offset = 0; offset < input.length; offset += frameStride) {
|
|
75
|
-
const block = input.subarray(offset, Math.min(offset + frameStride, input.length));
|
|
76
|
-
processed.set(changer.processInterleaved(block, 2), offset);
|
|
77
|
-
}
|
|
78
|
-
const start = latencyFrames * 2;
|
|
79
|
-
return processed.slice(start, start + samples.length);
|
|
61
|
+
/** Canonical request form for offline realtime voice changing. */
|
|
62
|
+
export interface VoiceChangeRealtimeRequest extends VoiceChangeRealtimeOptions {
|
|
63
|
+
samples: Float32Array;
|
|
64
|
+
sampleRate?: number;
|
|
65
|
+
preset?: RealtimeVoiceChangerConfigInput;
|
|
80
66
|
}
|
|
81
67
|
|
|
82
68
|
/**
|
|
83
69
|
* Applies the realtime voice-changer chain to a whole buffer in one call.
|
|
84
70
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
|
|
71
|
+
* Uses the shared C-ABI renderer, so Python, Node, and WASM use the same
|
|
72
|
+
* fixed block size and latency compensation. For mono, `samples` is a plain
|
|
73
|
+
* buffer; for stereo, it is interleaved (L0,R0,L1,R1,...).
|
|
89
74
|
*
|
|
90
75
|
* @param samples - Audio samples (mono, or interleaved stereo when channels=2)
|
|
91
76
|
* @param sampleRate - Sample rate in Hz (default 48000, matching Python/Node)
|
|
@@ -93,26 +78,34 @@ function latencyCompensatedVoiceChange(
|
|
|
93
78
|
* @param options - Channel count and block size ({@link VoiceChangeRealtimeOptions})
|
|
94
79
|
* @returns The processed buffer (same layout/length as the input).
|
|
95
80
|
*/
|
|
81
|
+
export function voiceChangeRealtime(request: VoiceChangeRealtimeRequest): Float32Array;
|
|
96
82
|
export function voiceChangeRealtime(
|
|
97
83
|
samples: Float32Array,
|
|
84
|
+
sampleRate?: number,
|
|
85
|
+
preset?: RealtimeVoiceChangerConfigInput,
|
|
86
|
+
options?: VoiceChangeRealtimeOptions,
|
|
87
|
+
): Float32Array;
|
|
88
|
+
export function voiceChangeRealtime(
|
|
89
|
+
samples: Float32Array | VoiceChangeRealtimeRequest,
|
|
98
90
|
sampleRate = 48000,
|
|
99
91
|
preset: RealtimeVoiceChangerConfigInput = 'neutral-monitor',
|
|
100
92
|
options: VoiceChangeRealtimeOptions = {},
|
|
101
93
|
): Float32Array {
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
const request =
|
|
95
|
+
samples instanceof Float32Array ? { samples, sampleRate, preset, ...options } : samples;
|
|
96
|
+
assertSamples('voiceChangeRealtime', request.samples, request.validate !== false);
|
|
97
|
+
const channels = request.channels ?? 1;
|
|
104
98
|
if (channels !== 1 && channels !== 2) {
|
|
105
99
|
throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
|
|
106
100
|
}
|
|
107
|
-
if (channels === 2 && samples.length % 2 !== 0) {
|
|
101
|
+
if (channels === 2 && request.samples.length % 2 !== 0) {
|
|
108
102
|
throw new Error('voiceChangeRealtime: stereo input length must be a multiple of 2.');
|
|
109
103
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
104
|
+
const presetConfig = request.preset ?? 'neutral-monitor';
|
|
105
|
+
return requireModule().voiceChangeRealtime(
|
|
106
|
+
request.samples,
|
|
107
|
+
request.sampleRate ?? 48000,
|
|
108
|
+
typeof presetConfig === 'string' ? presetConfig : JSON.stringify(presetConfig),
|
|
109
|
+
channels,
|
|
110
|
+
);
|
|
118
111
|
}
|