@libraz/libsonare 1.5.5 → 1.7.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.
- package/README.md +109 -20
- package/dist/analysis.d.ts +5809 -0
- package/dist/analysis.js +2574 -0
- package/dist/analysis.js.map +1 -0
- package/dist/index.d.ts +884 -55
- package/dist/index.js +1302 -292
- package/dist/index.js.map +1 -1
- package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
- package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
- package/dist/sonare-analysis.js +2 -0
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worker.d.ts +65 -0
- package/dist/worker.js +734 -0
- package/dist/worker.js.map +1 -0
- package/dist/worklet.d.ts +628 -88
- package/dist/worklet.js +1816 -266
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -11
- package/src/_chain_config.ts +5 -3
- package/src/analysis.ts +137 -0
- package/src/analysis_helpers.ts +40 -29
- package/src/audio.ts +7 -2
- package/src/clip_page_streamer.ts +62 -8
- package/src/codes.ts +87 -64
- package/src/effects_mastering.ts +1 -0
- package/src/effects_transform.ts +179 -10
- package/src/errors.ts +1 -0
- package/src/feature_core.ts +140 -2
- package/src/feature_music.ts +25 -3
- package/src/feature_pitch.ts +70 -1
- package/src/feature_spectral.ts +295 -7
- package/src/feature_spectrogram.ts +203 -3
- package/src/features.ts +16 -1
- package/src/index.ts +120 -8
- package/src/mastering_chain.ts +20 -8
- package/src/mastering_core.ts +5 -0
- package/src/mastering_dynamics.ts +7 -1
- package/src/metering.ts +6 -1
- package/src/mixer.ts +3 -3
- package/src/opfs_clip_pages.ts +26 -1
- package/src/project.ts +13 -0
- package/src/project_class.ts +204 -23
- package/src/project_internal.ts +38 -34
- package/src/project_types.ts +114 -11
- package/src/public_types.ts +68 -0
- package/src/public_types_mastering.ts +22 -0
- package/src/public_types_mixing.ts +27 -2
- package/src/public_types_music.ts +9 -0
- package/src/public_types_realtime.ts +23 -5
- package/src/public_types_spectral.ts +33 -0
- package/src/quick_analysis.ts +46 -10
- package/src/realtime_engine.ts +167 -6
- package/src/realtime_voice_changer.ts +51 -77
- package/src/sonare-analysis.js.d.ts +8 -0
- package/src/sonare.js.d.ts +340 -6
- package/src/stream_analyzer.ts +6 -1
- package/src/streaming_processors.ts +15 -0
- package/src/web_midi.ts +46 -11
- package/src/worker.ts +196 -0
- package/src/worker_client.ts +352 -0
- package/src/worker_protocol.ts +59 -0
- package/src/worklet/engine-automation.ts +2 -1
- package/src/worklet/engine-capture-facade.ts +13 -4
- package/src/worklet/engine-clips.ts +12 -3
- package/src/worklet/engine-node.ts +264 -21
- package/src/worklet/engine-offline.ts +7 -2
- package/src/worklet/engine-parameter-facade.ts +55 -5
- package/src/worklet/engine-processor.ts +435 -62
- package/src/worklet/engine-register.ts +9 -1
- package/src/worklet/engine-strips.ts +19 -3
- package/src/worklet/engine-sync.ts +2 -6
- package/src/worklet/engine-tempo-facade.ts +8 -6
- package/src/worklet/engine.ts +390 -13
- package/src/worklet/guards.ts +121 -8
- package/src/worklet/messages.ts +166 -8
- package/src/worklet/protocol.ts +305 -17
- package/src/worklet/voice-changer-processor.ts +12 -15
- package/src/worklet.ts +20 -1
package/src/effects_transform.ts
CHANGED
|
@@ -14,12 +14,65 @@ function requireModule() {
|
|
|
14
14
|
return getSonareModule();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function resolveEffectFftOptions(
|
|
18
|
+
fnName: string,
|
|
19
|
+
nFft: unknown,
|
|
20
|
+
hopLength: unknown,
|
|
21
|
+
): { nFft: number; hopLength: number } {
|
|
22
|
+
const resolvedNFft = nFft === undefined ? 2048 : nFft;
|
|
23
|
+
const resolvedHopLength = hopLength === undefined ? 512 : hopLength;
|
|
24
|
+
if (typeof resolvedNFft !== 'number' || !Number.isInteger(resolvedNFft)) {
|
|
25
|
+
throw new TypeError(`${fnName}: nFft must be an integer`);
|
|
26
|
+
}
|
|
27
|
+
if (resolvedNFft < 2 || resolvedNFft > 2 ** 30) {
|
|
28
|
+
throw new RangeError(`${fnName}: nFft must be an even power of two >= 2`);
|
|
29
|
+
}
|
|
30
|
+
if ((resolvedNFft & (resolvedNFft - 1)) !== 0) {
|
|
31
|
+
throw new RangeError(`${fnName}: nFft must be an even power of two >= 2`);
|
|
32
|
+
}
|
|
33
|
+
if (typeof resolvedHopLength !== 'number' || !Number.isInteger(resolvedHopLength)) {
|
|
34
|
+
throw new TypeError(`${fnName}: hopLength must be an integer`);
|
|
35
|
+
}
|
|
36
|
+
if (resolvedHopLength <= 0 || resolvedHopLength > 2 ** 31 - 1) {
|
|
37
|
+
throw new RangeError(`${fnName}: hopLength must be a positive integer`);
|
|
38
|
+
}
|
|
39
|
+
return { nFft: resolvedNFft, hopLength: resolvedHopLength };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type NormalizeMode = 'peak' | 'rms';
|
|
43
|
+
|
|
44
|
+
function resolveNormalizeMode(value: unknown): NormalizeMode {
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return 'peak';
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== 'string') {
|
|
49
|
+
throw new TypeError("normalize: mode must be the string 'peak' or 'rms'");
|
|
50
|
+
}
|
|
51
|
+
if (value !== 'peak' && value !== 'rms') {
|
|
52
|
+
throw new RangeError("normalize: mode must be the string 'peak' or 'rms'");
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveHardMask(value: unknown, fnName: string): boolean {
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value !== 'boolean') {
|
|
62
|
+
throw new TypeError(`${fnName}: hardMask must be a boolean`);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
17
67
|
/** Canonical request form for HPSS. */
|
|
18
68
|
export interface HpssRequest {
|
|
19
69
|
samples: Float32Array;
|
|
20
70
|
sampleRate?: number;
|
|
21
71
|
kernelHarmonic?: number;
|
|
22
72
|
kernelPercussive?: number;
|
|
73
|
+
nFft?: number;
|
|
74
|
+
hopLength?: number;
|
|
75
|
+
hardMask?: boolean;
|
|
23
76
|
}
|
|
24
77
|
|
|
25
78
|
export interface HarmonicRequest extends ValidateOptions {
|
|
@@ -36,12 +89,16 @@ export interface TimeStretchRequest extends ValidateOptions {
|
|
|
36
89
|
samples: Float32Array;
|
|
37
90
|
sampleRate?: number;
|
|
38
91
|
rate: number;
|
|
92
|
+
nFft?: number;
|
|
93
|
+
hopLength?: number;
|
|
39
94
|
}
|
|
40
95
|
|
|
41
96
|
export interface PitchShiftRequest extends ValidateOptions {
|
|
42
97
|
samples: Float32Array;
|
|
43
98
|
sampleRate?: number;
|
|
44
99
|
semitones: number;
|
|
100
|
+
nFft?: number;
|
|
101
|
+
hopLength?: number;
|
|
45
102
|
}
|
|
46
103
|
|
|
47
104
|
export interface PitchCorrectToMidiRequest extends ValidateOptions {
|
|
@@ -81,6 +138,7 @@ export interface NormalizeRequest extends ValidateOptions {
|
|
|
81
138
|
samples: Float32Array;
|
|
82
139
|
sampleRate?: number;
|
|
83
140
|
targetDb?: number;
|
|
141
|
+
mode?: NormalizeMode;
|
|
84
142
|
}
|
|
85
143
|
|
|
86
144
|
export interface SpectralEditRequest extends SpectralEditOptions, ValidateOptions {
|
|
@@ -108,22 +166,33 @@ export function hpss(
|
|
|
108
166
|
sampleRate?: number,
|
|
109
167
|
kernelHarmonic?: number,
|
|
110
168
|
kernelPercussive?: number,
|
|
169
|
+
nFft?: number,
|
|
170
|
+
hopLength?: number,
|
|
171
|
+
hardMask?: boolean,
|
|
111
172
|
): HpssResult;
|
|
112
173
|
export function hpss(
|
|
113
174
|
samples: Float32Array | HpssRequest,
|
|
114
175
|
sampleRate = 22050,
|
|
115
176
|
kernelHarmonic = 31,
|
|
116
177
|
kernelPercussive = 31,
|
|
178
|
+
nFft?: number,
|
|
179
|
+
hopLength?: number,
|
|
180
|
+
hardMask?: boolean,
|
|
117
181
|
): HpssResult {
|
|
118
182
|
const request =
|
|
119
183
|
samples instanceof Float32Array
|
|
120
|
-
? { samples, sampleRate, kernelHarmonic, kernelPercussive }
|
|
184
|
+
? { samples, sampleRate, kernelHarmonic, kernelPercussive, nFft, hopLength, hardMask }
|
|
121
185
|
: samples;
|
|
122
|
-
|
|
186
|
+
const fftOptions = resolveEffectFftOptions('hpss', request.nFft, request.hopLength);
|
|
187
|
+
const resolvedHardMask = resolveHardMask(request.hardMask, 'hpss');
|
|
188
|
+
return requireModule().hpssEx(
|
|
123
189
|
request.samples,
|
|
124
190
|
request.sampleRate ?? 22050,
|
|
125
191
|
request.kernelHarmonic ?? 31,
|
|
126
192
|
request.kernelPercussive ?? 31,
|
|
193
|
+
fftOptions.nFft,
|
|
194
|
+
fftOptions.hopLength,
|
|
195
|
+
resolvedHardMask,
|
|
127
196
|
);
|
|
128
197
|
}
|
|
129
198
|
|
|
@@ -188,18 +257,56 @@ export function timeStretch(
|
|
|
188
257
|
rate: number,
|
|
189
258
|
options?: ValidateOptions,
|
|
190
259
|
): Float32Array;
|
|
260
|
+
export function timeStretch(
|
|
261
|
+
samples: Float32Array,
|
|
262
|
+
sampleRate: number,
|
|
263
|
+
rate: number,
|
|
264
|
+
nFft?: number,
|
|
265
|
+
hopLength?: number,
|
|
266
|
+
options?: ValidateOptions,
|
|
267
|
+
): Float32Array;
|
|
191
268
|
export function timeStretch(
|
|
192
269
|
samples: Float32Array | TimeStretchRequest,
|
|
193
270
|
sampleRate?: number,
|
|
194
271
|
rate?: number,
|
|
272
|
+
nFftOrOptions?: number | ValidateOptions,
|
|
273
|
+
hopLength?: number,
|
|
195
274
|
options: ValidateOptions = {},
|
|
196
275
|
): Float32Array {
|
|
276
|
+
if (
|
|
277
|
+
nFftOrOptions !== undefined &&
|
|
278
|
+
nFftOrOptions !== null &&
|
|
279
|
+
typeof nFftOrOptions !== 'number' &&
|
|
280
|
+
typeof nFftOrOptions !== 'object'
|
|
281
|
+
) {
|
|
282
|
+
throw new TypeError('timeStretch: nFft must be an integer or options object');
|
|
283
|
+
}
|
|
284
|
+
if (nFftOrOptions === null) {
|
|
285
|
+
throw new TypeError('timeStretch: nFft must be an integer or options object');
|
|
286
|
+
}
|
|
287
|
+
const positionalOptions =
|
|
288
|
+
typeof nFftOrOptions === 'object' && nFftOrOptions !== null ? nFftOrOptions : options;
|
|
289
|
+
const positionalNFft = typeof nFftOrOptions === 'number' ? nFftOrOptions : undefined;
|
|
197
290
|
const request: TimeStretchRequest =
|
|
198
291
|
samples instanceof Float32Array
|
|
199
|
-
? {
|
|
292
|
+
? {
|
|
293
|
+
samples,
|
|
294
|
+
sampleRate,
|
|
295
|
+
rate: rate as number,
|
|
296
|
+
nFft: positionalNFft,
|
|
297
|
+
hopLength,
|
|
298
|
+
...positionalOptions,
|
|
299
|
+
}
|
|
200
300
|
: samples;
|
|
201
301
|
assertSamples('timeStretch', request.samples, request.validate !== false);
|
|
202
|
-
|
|
302
|
+
const fftOptions = resolveEffectFftOptions('timeStretch', request.nFft, request.hopLength);
|
|
303
|
+
return requireModule().timeStretchEx(
|
|
304
|
+
request.samples,
|
|
305
|
+
request.sampleRate ?? 22050,
|
|
306
|
+
request.rate,
|
|
307
|
+
fftOptions.nFft,
|
|
308
|
+
fftOptions.hopLength,
|
|
309
|
+
);
|
|
203
310
|
}
|
|
204
311
|
|
|
205
312
|
/**
|
|
@@ -217,21 +324,55 @@ export function pitchShift(
|
|
|
217
324
|
semitones: number,
|
|
218
325
|
options?: ValidateOptions,
|
|
219
326
|
): Float32Array;
|
|
327
|
+
export function pitchShift(
|
|
328
|
+
samples: Float32Array,
|
|
329
|
+
sampleRate: number,
|
|
330
|
+
semitones: number,
|
|
331
|
+
nFft?: number,
|
|
332
|
+
hopLength?: number,
|
|
333
|
+
options?: ValidateOptions,
|
|
334
|
+
): Float32Array;
|
|
220
335
|
export function pitchShift(
|
|
221
336
|
samples: Float32Array | PitchShiftRequest,
|
|
222
337
|
sampleRate?: number,
|
|
223
338
|
semitones?: number,
|
|
339
|
+
nFftOrOptions?: number | ValidateOptions,
|
|
340
|
+
hopLength?: number,
|
|
224
341
|
options: ValidateOptions = {},
|
|
225
342
|
): Float32Array {
|
|
343
|
+
if (
|
|
344
|
+
nFftOrOptions !== undefined &&
|
|
345
|
+
nFftOrOptions !== null &&
|
|
346
|
+
typeof nFftOrOptions !== 'number' &&
|
|
347
|
+
typeof nFftOrOptions !== 'object'
|
|
348
|
+
) {
|
|
349
|
+
throw new TypeError('pitchShift: nFft must be an integer or options object');
|
|
350
|
+
}
|
|
351
|
+
if (nFftOrOptions === null) {
|
|
352
|
+
throw new TypeError('pitchShift: nFft must be an integer or options object');
|
|
353
|
+
}
|
|
354
|
+
const positionalOptions =
|
|
355
|
+
typeof nFftOrOptions === 'object' && nFftOrOptions !== null ? nFftOrOptions : options;
|
|
356
|
+
const positionalNFft = typeof nFftOrOptions === 'number' ? nFftOrOptions : undefined;
|
|
226
357
|
const request: PitchShiftRequest =
|
|
227
358
|
samples instanceof Float32Array
|
|
228
|
-
? {
|
|
359
|
+
? {
|
|
360
|
+
samples,
|
|
361
|
+
sampleRate,
|
|
362
|
+
semitones: semitones as number,
|
|
363
|
+
nFft: positionalNFft,
|
|
364
|
+
hopLength,
|
|
365
|
+
...positionalOptions,
|
|
366
|
+
}
|
|
229
367
|
: samples;
|
|
230
368
|
assertSamples('pitchShift', request.samples, request.validate !== false);
|
|
231
|
-
|
|
369
|
+
const fftOptions = resolveEffectFftOptions('pitchShift', request.nFft, request.hopLength);
|
|
370
|
+
return requireModule().pitchShiftEx(
|
|
232
371
|
request.samples,
|
|
233
372
|
request.sampleRate ?? 22050,
|
|
234
373
|
request.semitones,
|
|
374
|
+
fftOptions.nFft,
|
|
375
|
+
fftOptions.hopLength,
|
|
235
376
|
);
|
|
236
377
|
}
|
|
237
378
|
|
|
@@ -466,11 +607,13 @@ export function noteMove(
|
|
|
466
607
|
}
|
|
467
608
|
|
|
468
609
|
/**
|
|
469
|
-
* Normalize audio to target peak level.
|
|
610
|
+
* Normalize audio to a target peak or RMS level.
|
|
470
611
|
*
|
|
471
612
|
* @param samples - Audio samples (mono, float32)
|
|
472
613
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
473
|
-
* @param targetDb - Finite target at or below 0 dBFS (default: 0 dB = full scale)
|
|
614
|
+
* @param targetDb - Finite target at or below 0 dBFS (default: 0 dB = full scale).
|
|
615
|
+
* For `mode: 'peak'`, this is the peak target; for `mode: 'rms'`, this is the RMS target.
|
|
616
|
+
* @param mode - Normalization mode: `'peak'` (default) or `'rms'`.
|
|
474
617
|
* @returns Normalized audio
|
|
475
618
|
*/
|
|
476
619
|
export function normalize(request: NormalizeRequest): Float32Array;
|
|
@@ -480,19 +623,45 @@ export function normalize(
|
|
|
480
623
|
targetDb?: number,
|
|
481
624
|
options?: ValidateOptions,
|
|
482
625
|
): Float32Array;
|
|
626
|
+
export function normalize(
|
|
627
|
+
samples: Float32Array,
|
|
628
|
+
sampleRate: number,
|
|
629
|
+
targetDb?: number,
|
|
630
|
+
mode?: NormalizeMode,
|
|
631
|
+
options?: ValidateOptions,
|
|
632
|
+
): Float32Array;
|
|
483
633
|
export function normalize(
|
|
484
634
|
samples: Float32Array | NormalizeRequest,
|
|
485
635
|
sampleRate?: number,
|
|
486
636
|
targetDb = 0.0,
|
|
637
|
+
modeOrOptions: NormalizeMode | ValidateOptions = 'peak',
|
|
487
638
|
options: ValidateOptions = {},
|
|
488
639
|
): Float32Array {
|
|
640
|
+
if (
|
|
641
|
+
modeOrOptions !== undefined &&
|
|
642
|
+
modeOrOptions !== null &&
|
|
643
|
+
typeof modeOrOptions !== 'string' &&
|
|
644
|
+
typeof modeOrOptions !== 'object'
|
|
645
|
+
) {
|
|
646
|
+
throw new TypeError("normalize: mode must be the string 'peak' or 'rms'");
|
|
647
|
+
}
|
|
648
|
+
if (modeOrOptions === null) {
|
|
649
|
+
throw new TypeError("normalize: mode must be the string 'peak' or 'rms'");
|
|
650
|
+
}
|
|
651
|
+
const positionalOptions =
|
|
652
|
+
typeof modeOrOptions === 'object' && modeOrOptions !== null ? modeOrOptions : options;
|
|
653
|
+
const positionalMode = typeof modeOrOptions === 'string' ? modeOrOptions : undefined;
|
|
489
654
|
const request: NormalizeRequest =
|
|
490
|
-
samples instanceof Float32Array
|
|
655
|
+
samples instanceof Float32Array
|
|
656
|
+
? { samples, sampleRate, targetDb, mode: positionalMode, ...positionalOptions }
|
|
657
|
+
: samples;
|
|
491
658
|
assertSamples('normalize', request.samples, request.validate !== false);
|
|
492
|
-
|
|
659
|
+
const mode = resolveNormalizeMode(request.mode);
|
|
660
|
+
return requireModule().normalizeEx(
|
|
493
661
|
request.samples,
|
|
494
662
|
request.sampleRate ?? 22050,
|
|
495
663
|
request.targetDb ?? 0.0,
|
|
664
|
+
mode,
|
|
496
665
|
);
|
|
497
666
|
}
|
|
498
667
|
|
package/src/errors.ts
CHANGED
package/src/feature_core.ts
CHANGED
|
@@ -11,6 +11,85 @@ function requireModule() {
|
|
|
11
11
|
return getSonareModule();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export function tone(request?: ToneRequest): Float32Array;
|
|
15
|
+
export function tone(
|
|
16
|
+
frequency?: number,
|
|
17
|
+
sampleRate?: number,
|
|
18
|
+
duration?: number,
|
|
19
|
+
phase?: number,
|
|
20
|
+
amplitude?: number,
|
|
21
|
+
): Float32Array;
|
|
22
|
+
export function tone(
|
|
23
|
+
frequency: number | ToneRequest = 440,
|
|
24
|
+
sampleRate = 22050,
|
|
25
|
+
duration = 1,
|
|
26
|
+
phase = 0,
|
|
27
|
+
amplitude = 1,
|
|
28
|
+
): Float32Array {
|
|
29
|
+
const request =
|
|
30
|
+
typeof frequency === 'number'
|
|
31
|
+
? { frequency, sampleRate, duration, phase, amplitude }
|
|
32
|
+
: frequency;
|
|
33
|
+
return requireModule().tone(
|
|
34
|
+
request.frequency ?? 440,
|
|
35
|
+
request.sampleRate ?? 22050,
|
|
36
|
+
request.duration ?? 1,
|
|
37
|
+
request.phase ?? 0,
|
|
38
|
+
request.amplitude ?? 1,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function chirp(request?: ChirpRequest): Float32Array;
|
|
43
|
+
export function chirp(
|
|
44
|
+
fmin?: number,
|
|
45
|
+
fmax?: number,
|
|
46
|
+
sampleRate?: number,
|
|
47
|
+
duration?: number,
|
|
48
|
+
linear?: boolean,
|
|
49
|
+
): Float32Array;
|
|
50
|
+
export function chirp(
|
|
51
|
+
fmin: number | ChirpRequest = 440,
|
|
52
|
+
fmax = 880,
|
|
53
|
+
sampleRate = 22050,
|
|
54
|
+
duration = 1,
|
|
55
|
+
linear = true,
|
|
56
|
+
): Float32Array {
|
|
57
|
+
const request = typeof fmin === 'number' ? { fmin, fmax, sampleRate, duration, linear } : fmin;
|
|
58
|
+
return requireModule().chirp(
|
|
59
|
+
request.fmin ?? 440,
|
|
60
|
+
request.fmax ?? 880,
|
|
61
|
+
request.sampleRate ?? 22050,
|
|
62
|
+
request.duration ?? 1,
|
|
63
|
+
request.linear ?? true,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function clicks(request: ClicksRequest): Float32Array;
|
|
68
|
+
export function clicks(
|
|
69
|
+
times: Float32Array,
|
|
70
|
+
sampleRate?: number,
|
|
71
|
+
length?: number,
|
|
72
|
+
frequency?: number,
|
|
73
|
+
clickDuration?: number,
|
|
74
|
+
): Float32Array;
|
|
75
|
+
export function clicks(
|
|
76
|
+
times: Float32Array | ClicksRequest,
|
|
77
|
+
sampleRate = 22050,
|
|
78
|
+
length = 0,
|
|
79
|
+
frequency = 1000,
|
|
80
|
+
clickDuration = 0.1,
|
|
81
|
+
): Float32Array {
|
|
82
|
+
const request =
|
|
83
|
+
times instanceof Float32Array ? { times, sampleRate, length, frequency, clickDuration } : times;
|
|
84
|
+
return requireModule().clicks(
|
|
85
|
+
request.times,
|
|
86
|
+
request.sampleRate ?? 22050,
|
|
87
|
+
request.length ?? 0,
|
|
88
|
+
request.frequency ?? 1000,
|
|
89
|
+
request.clickDuration ?? 0.1,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
14
93
|
export interface DbConversionRequest {
|
|
15
94
|
values: Float32Array;
|
|
16
95
|
ref?: number;
|
|
@@ -30,6 +109,27 @@ export interface FrameSignalRequest {
|
|
|
30
109
|
frameLength: number;
|
|
31
110
|
hopLength: number;
|
|
32
111
|
}
|
|
112
|
+
export interface ToneRequest {
|
|
113
|
+
frequency?: number;
|
|
114
|
+
sampleRate?: number;
|
|
115
|
+
duration?: number;
|
|
116
|
+
phase?: number;
|
|
117
|
+
amplitude?: number;
|
|
118
|
+
}
|
|
119
|
+
export interface ChirpRequest {
|
|
120
|
+
fmin?: number;
|
|
121
|
+
fmax?: number;
|
|
122
|
+
sampleRate?: number;
|
|
123
|
+
duration?: number;
|
|
124
|
+
linear?: boolean;
|
|
125
|
+
}
|
|
126
|
+
export interface ClicksRequest {
|
|
127
|
+
times: Float32Array;
|
|
128
|
+
sampleRate?: number;
|
|
129
|
+
length?: number;
|
|
130
|
+
frequency?: number;
|
|
131
|
+
clickDuration?: number;
|
|
132
|
+
}
|
|
33
133
|
/** Canonical request form for pre/de-emphasis filters. */
|
|
34
134
|
export interface EmphasisRequest {
|
|
35
135
|
samples: Float32Array;
|
|
@@ -55,6 +155,10 @@ export interface FixFramesRequest {
|
|
|
55
155
|
xMax?: number;
|
|
56
156
|
pad?: boolean;
|
|
57
157
|
}
|
|
158
|
+
export interface OnsetBacktrackRequest {
|
|
159
|
+
events: Int32Array;
|
|
160
|
+
energy: Float32Array;
|
|
161
|
+
}
|
|
58
162
|
/** Canonical request form for peak selection. */
|
|
59
163
|
export interface PeakPickRequest {
|
|
60
164
|
values: Float32Array;
|
|
@@ -97,6 +201,8 @@ export interface TempogramRequest {
|
|
|
97
201
|
hopLength?: number;
|
|
98
202
|
winLength?: number;
|
|
99
203
|
mode?: TempogramMode;
|
|
204
|
+
center?: boolean;
|
|
205
|
+
norm?: boolean;
|
|
100
206
|
}
|
|
101
207
|
export interface CyclicTempogramRequest {
|
|
102
208
|
onsetEnvelope: Float32Array;
|
|
@@ -391,6 +497,18 @@ export function fixFrames(
|
|
|
391
497
|
return requireModule().fixFrames(frames, xMin, xMax, pad);
|
|
392
498
|
}
|
|
393
499
|
|
|
500
|
+
export function onsetBacktrack(request: OnsetBacktrackRequest): Int32Array;
|
|
501
|
+
export function onsetBacktrack(events: Int32Array, energy: Float32Array): Int32Array;
|
|
502
|
+
export function onsetBacktrack(
|
|
503
|
+
events: Int32Array | OnsetBacktrackRequest,
|
|
504
|
+
energy?: Float32Array,
|
|
505
|
+
): Int32Array {
|
|
506
|
+
if (!(events instanceof Int32Array)) {
|
|
507
|
+
return onsetBacktrack(events.events, events.energy);
|
|
508
|
+
}
|
|
509
|
+
return requireModule().onsetBacktrack(events, energy as Float32Array);
|
|
510
|
+
}
|
|
511
|
+
|
|
394
512
|
export function peakPick(request: PeakPickRequest): Int32Array;
|
|
395
513
|
export function peakPick(
|
|
396
514
|
values: Float32Array,
|
|
@@ -492,6 +610,8 @@ export function tempogram(
|
|
|
492
610
|
hopLength?: number,
|
|
493
611
|
winLength?: number,
|
|
494
612
|
mode?: TempogramMode,
|
|
613
|
+
center?: boolean,
|
|
614
|
+
norm?: boolean,
|
|
495
615
|
): WasmTempogramResult;
|
|
496
616
|
export function tempogram(
|
|
497
617
|
onsetEnvelope: Float32Array | TempogramRequest,
|
|
@@ -499,12 +619,30 @@ export function tempogram(
|
|
|
499
619
|
hopLength = 512,
|
|
500
620
|
winLength = 384,
|
|
501
621
|
mode: TempogramMode = 'autocorrelation',
|
|
622
|
+
center = true,
|
|
623
|
+
norm = true,
|
|
502
624
|
): WasmTempogramResult {
|
|
503
625
|
if (!(onsetEnvelope instanceof Float32Array)) {
|
|
504
626
|
const r = onsetEnvelope;
|
|
505
|
-
return tempogram(
|
|
627
|
+
return tempogram(
|
|
628
|
+
r.onsetEnvelope,
|
|
629
|
+
r.sampleRate,
|
|
630
|
+
r.hopLength,
|
|
631
|
+
r.winLength,
|
|
632
|
+
r.mode,
|
|
633
|
+
r.center,
|
|
634
|
+
r.norm,
|
|
635
|
+
);
|
|
506
636
|
}
|
|
507
|
-
return requireModule().tempogram(
|
|
637
|
+
return requireModule().tempogram(
|
|
638
|
+
onsetEnvelope,
|
|
639
|
+
sampleRate,
|
|
640
|
+
hopLength,
|
|
641
|
+
winLength,
|
|
642
|
+
mode,
|
|
643
|
+
center,
|
|
644
|
+
norm,
|
|
645
|
+
);
|
|
508
646
|
}
|
|
509
647
|
|
|
510
648
|
export function cyclicTempogram(request: CyclicTempogramRequest): WasmCyclicTempogramResult;
|
package/src/feature_music.ts
CHANGED
|
@@ -58,6 +58,8 @@ export interface FourierTempogramRequest extends GuardedOptions {
|
|
|
58
58
|
sampleRate?: number;
|
|
59
59
|
hopLength?: number;
|
|
60
60
|
winLength?: number;
|
|
61
|
+
center?: boolean;
|
|
62
|
+
norm?: boolean;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export interface TempogramRatioRequest extends GuardedOptions {
|
|
@@ -101,6 +103,7 @@ export interface NnlsChromaRequest extends GuardedOptions {
|
|
|
101
103
|
enableStftBlend?: boolean;
|
|
102
104
|
stftBlendWeight?: number;
|
|
103
105
|
stftBlendNFft?: number;
|
|
106
|
+
hopLength?: number;
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
function validateMusicSamples(
|
|
@@ -154,12 +157,18 @@ export function nnlsChroma(
|
|
|
154
157
|
return nnlsChroma(samples.samples, samples.sampleRate, samples);
|
|
155
158
|
}
|
|
156
159
|
validateMusicSamples('nnlsChroma', samples, sampleRate, options);
|
|
157
|
-
|
|
160
|
+
const hopLength = options.hopLength === undefined ? 512 : options.hopLength;
|
|
161
|
+
assertPositiveInteger('nnlsChroma', hopLength, 'hopLength');
|
|
162
|
+
if (hopLength > 2 ** 31 - 1) {
|
|
163
|
+
throw new RangeError('nnlsChroma: hopLength must fit in a signed 32-bit integer');
|
|
164
|
+
}
|
|
165
|
+
return requireModule().nnlsChromaEx(
|
|
158
166
|
samples,
|
|
159
167
|
sampleRate,
|
|
160
168
|
options.enableStftBlend ?? true,
|
|
161
169
|
options.stftBlendWeight ?? 0.55,
|
|
162
170
|
options.stftBlendNFft ?? 4096,
|
|
171
|
+
hopLength,
|
|
163
172
|
);
|
|
164
173
|
}
|
|
165
174
|
|
|
@@ -584,7 +593,7 @@ export interface MelodyOptions {
|
|
|
584
593
|
*/
|
|
585
594
|
usePyin?: boolean;
|
|
586
595
|
/**
|
|
587
|
-
* When {@link usePyin} is `true`,
|
|
596
|
+
* When {@link usePyin} is `true`, zero-pad by `frameLength / 2` so frame
|
|
588
597
|
* `i` is centered at `i * hopLength` (matches `librosa.pyin(center=True)`).
|
|
589
598
|
* Ignored by the plain-YIN path. Defaults to `true`.
|
|
590
599
|
*/
|
|
@@ -760,6 +769,8 @@ export function fourierTempogram(
|
|
|
760
769
|
sampleRate?: number,
|
|
761
770
|
hopLength?: number,
|
|
762
771
|
winLength?: number,
|
|
772
|
+
center?: boolean,
|
|
773
|
+
norm?: boolean,
|
|
763
774
|
options?: GuardedOptions,
|
|
764
775
|
): WasmFourierTempogramResult;
|
|
765
776
|
export function fourierTempogram(
|
|
@@ -767,6 +778,8 @@ export function fourierTempogram(
|
|
|
767
778
|
sampleRate = 22050,
|
|
768
779
|
hopLength = 512,
|
|
769
780
|
winLength = 384,
|
|
781
|
+
center = true,
|
|
782
|
+
norm = true,
|
|
770
783
|
options: GuardedOptions = {},
|
|
771
784
|
): WasmFourierTempogramResult {
|
|
772
785
|
if (!(onsetEnvelope instanceof Float32Array)) {
|
|
@@ -776,13 +789,22 @@ export function fourierTempogram(
|
|
|
776
789
|
request.sampleRate,
|
|
777
790
|
request.hopLength,
|
|
778
791
|
request.winLength,
|
|
792
|
+
request.center,
|
|
793
|
+
request.norm,
|
|
779
794
|
request,
|
|
780
795
|
);
|
|
781
796
|
}
|
|
782
797
|
assertSampleRate('fourierTempogram', sampleRate);
|
|
783
798
|
assertSamples('fourierTempogram', onsetEnvelope, options.validate !== false, 'onsetEnvelope');
|
|
784
799
|
validatePositiveIntegers('fourierTempogram', { hopLength, winLength });
|
|
785
|
-
return requireModule().fourierTempogram(
|
|
800
|
+
return requireModule().fourierTempogram(
|
|
801
|
+
onsetEnvelope,
|
|
802
|
+
sampleRate,
|
|
803
|
+
hopLength,
|
|
804
|
+
winLength,
|
|
805
|
+
center,
|
|
806
|
+
norm,
|
|
807
|
+
);
|
|
786
808
|
}
|
|
787
809
|
|
|
788
810
|
/**
|
package/src/feature_pitch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSonareModule } from './module_state';
|
|
2
|
-
import type { PitchResult } from './public_types';
|
|
2
|
+
import type { NoteSegment, PiptrackResult, PitchResult } from './public_types';
|
|
3
3
|
|
|
4
4
|
function requireModule() {
|
|
5
5
|
return getSonareModule();
|
|
@@ -33,6 +33,51 @@ export interface PitchYinRequest {
|
|
|
33
33
|
fillNa?: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface PiptrackRequest {
|
|
37
|
+
samples: Float32Array;
|
|
38
|
+
sampleRate?: number;
|
|
39
|
+
nFft?: number;
|
|
40
|
+
hopLength?: number;
|
|
41
|
+
fmin?: number;
|
|
42
|
+
fmax?: number;
|
|
43
|
+
threshold?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Per-bin spectral pitch candidates and peak magnitudes (librosa.piptrack). */
|
|
47
|
+
export function piptrack(request: PiptrackRequest): PiptrackResult;
|
|
48
|
+
export function piptrack(
|
|
49
|
+
samples: Float32Array,
|
|
50
|
+
sampleRate?: number,
|
|
51
|
+
nFft?: number,
|
|
52
|
+
hopLength?: number,
|
|
53
|
+
fmin?: number,
|
|
54
|
+
fmax?: number,
|
|
55
|
+
threshold?: number,
|
|
56
|
+
): PiptrackResult;
|
|
57
|
+
export function piptrack(
|
|
58
|
+
samples: Float32Array | PiptrackRequest,
|
|
59
|
+
sampleRate = 22050,
|
|
60
|
+
nFft = 2048,
|
|
61
|
+
hopLength = 512,
|
|
62
|
+
fmin = 150,
|
|
63
|
+
fmax = 4000,
|
|
64
|
+
threshold = 0.1,
|
|
65
|
+
): PiptrackResult {
|
|
66
|
+
if (!(samples instanceof Float32Array)) {
|
|
67
|
+
const request = samples;
|
|
68
|
+
return piptrack(
|
|
69
|
+
request.samples,
|
|
70
|
+
request.sampleRate,
|
|
71
|
+
request.nFft,
|
|
72
|
+
request.hopLength,
|
|
73
|
+
request.fmin,
|
|
74
|
+
request.fmax,
|
|
75
|
+
request.threshold,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return requireModule().piptrack(samples, sampleRate, nFft, hopLength, fmin, fmax, threshold);
|
|
79
|
+
}
|
|
80
|
+
|
|
36
81
|
export function pitchYin(request: PitchYinRequest): PitchResult;
|
|
37
82
|
export function pitchYin(
|
|
38
83
|
samples: Float32Array,
|
|
@@ -139,3 +184,27 @@ export function pitchPyin(
|
|
|
139
184
|
fillNa,
|
|
140
185
|
);
|
|
141
186
|
}
|
|
187
|
+
|
|
188
|
+
/** Parameters for segmenting an F0 track into stable monophonic notes. */
|
|
189
|
+
export interface NoteSegmentsRequest {
|
|
190
|
+
f0Hz: Float32Array;
|
|
191
|
+
voicedProb: Float32Array;
|
|
192
|
+
frameRate: number;
|
|
193
|
+
segmentationThresholdCents?: number;
|
|
194
|
+
minNoteMs?: number;
|
|
195
|
+
referenceHz?: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Segment a caller-supplied monophonic F0 track into stable note regions.
|
|
200
|
+
*
|
|
201
|
+
* `f0Hz` and `voicedProb` must have the same non-zero length. Zero-Hz frames
|
|
202
|
+
* and probabilities below 0.5 are treated as unvoiced.
|
|
203
|
+
*/
|
|
204
|
+
export function noteSegments(request: NoteSegmentsRequest): NoteSegment[] {
|
|
205
|
+
return requireModule().noteSegments(request.f0Hz, request.voicedProb, request.frameRate, {
|
|
206
|
+
segmentationThresholdCents: request.segmentationThresholdCents,
|
|
207
|
+
minNoteMs: request.minNoteMs,
|
|
208
|
+
referenceHz: request.referenceHz,
|
|
209
|
+
});
|
|
210
|
+
}
|