@libraz/libsonare 1.2.0 → 1.2.2
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 +56 -4
- package/dist/index.d.ts +728 -284
- package/dist/index.js +712 -122
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +61 -2
- package/dist/worklet.js +371 -9
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1666 -305
- package/src/public_types.ts +71 -0
- package/src/sonare.js.d.ts +508 -78
- package/src/worklet.ts +295 -9
- package/src/wasm_types.ts +0 -1248
package/src/index.ts
CHANGED
|
@@ -56,7 +56,10 @@ import type {
|
|
|
56
56
|
PairAnalysis,
|
|
57
57
|
PairProcessor,
|
|
58
58
|
PanLaw,
|
|
59
|
+
PanMode,
|
|
59
60
|
PitchResult,
|
|
61
|
+
RealtimeVoiceChangerConfigInput,
|
|
62
|
+
RealtimeVoiceChangerPodConfig,
|
|
60
63
|
Section,
|
|
61
64
|
SectionType,
|
|
62
65
|
SendTiming,
|
|
@@ -66,16 +69,11 @@ import type {
|
|
|
66
69
|
StftResult,
|
|
67
70
|
StreamingEqualizerConfig,
|
|
68
71
|
StreamingPlatform,
|
|
72
|
+
StreamingRetuneConfig,
|
|
69
73
|
TempogramMode,
|
|
74
|
+
VoicePresetId,
|
|
70
75
|
} from './public_types';
|
|
71
76
|
import { KeyProfile as KeyProfileValues, Mode, PitchClass } from './public_types';
|
|
72
|
-
import type {
|
|
73
|
-
AnalyzerStats,
|
|
74
|
-
FrameBuffer,
|
|
75
|
-
StreamConfig,
|
|
76
|
-
StreamFramesI16,
|
|
77
|
-
StreamFramesU8,
|
|
78
|
-
} from './stream_types';
|
|
79
77
|
import type {
|
|
80
78
|
ProgressCallback,
|
|
81
79
|
SonareModule,
|
|
@@ -83,6 +81,7 @@ import type {
|
|
|
83
81
|
WasmAnalysisResult,
|
|
84
82
|
WasmChordAnalysisResult,
|
|
85
83
|
WasmCyclicTempogramResult,
|
|
84
|
+
WasmDecomposeResult,
|
|
86
85
|
WasmEngineAutomationPoint,
|
|
87
86
|
WasmEngineBounceOptions,
|
|
88
87
|
WasmEngineBounceResult,
|
|
@@ -100,13 +99,23 @@ import type {
|
|
|
100
99
|
WasmEngineTransportState,
|
|
101
100
|
WasmFourierTempogramResult,
|
|
102
101
|
WasmFrameResult,
|
|
102
|
+
WasmHpssWithResidualResult,
|
|
103
103
|
WasmKeyCandidateResult,
|
|
104
|
+
WasmLufsResult,
|
|
105
|
+
WasmMatrix2dResult,
|
|
104
106
|
WasmNnlsChromaResult,
|
|
105
107
|
WasmRealtimeEngine,
|
|
106
108
|
WasmStreamAnalyzer,
|
|
107
109
|
WasmTempogramResult,
|
|
108
110
|
WasmTrimResult,
|
|
109
|
-
} from './
|
|
111
|
+
} from './sonare.js';
|
|
112
|
+
import type {
|
|
113
|
+
AnalyzerStats,
|
|
114
|
+
FrameBuffer,
|
|
115
|
+
StreamConfig,
|
|
116
|
+
StreamFramesI16,
|
|
117
|
+
StreamFramesU8,
|
|
118
|
+
} from './stream_types';
|
|
110
119
|
|
|
111
120
|
export type {
|
|
112
121
|
AcousticResult,
|
|
@@ -154,6 +163,7 @@ export type {
|
|
|
154
163
|
PanLaw,
|
|
155
164
|
PanMode,
|
|
156
165
|
PitchResult,
|
|
166
|
+
RealtimeVoiceChangerConfigInput,
|
|
157
167
|
RhythmFeatures,
|
|
158
168
|
Section,
|
|
159
169
|
SendTiming,
|
|
@@ -162,8 +172,10 @@ export type {
|
|
|
162
172
|
StftResult,
|
|
163
173
|
StreamingEqualizerConfig,
|
|
164
174
|
StreamingPlatform,
|
|
175
|
+
StreamingRetuneConfig,
|
|
165
176
|
Timbre,
|
|
166
177
|
TimeSignature,
|
|
178
|
+
VoicePresetId,
|
|
167
179
|
} from './public_types';
|
|
168
180
|
export {
|
|
169
181
|
ChordQuality,
|
|
@@ -172,6 +184,7 @@ export {
|
|
|
172
184
|
PitchClass,
|
|
173
185
|
SectionType,
|
|
174
186
|
} from './public_types';
|
|
187
|
+
export type { ProgressCallback } from './sonare.js';
|
|
175
188
|
export type {
|
|
176
189
|
AnalyzerStats,
|
|
177
190
|
BarChord,
|
|
@@ -183,7 +196,6 @@ export type {
|
|
|
183
196
|
StreamFramesI16,
|
|
184
197
|
StreamFramesU8,
|
|
185
198
|
} from './stream_types';
|
|
186
|
-
export type { ProgressCallback } from './wasm_types';
|
|
187
199
|
|
|
188
200
|
export type EngineClip = WasmEngineClip;
|
|
189
201
|
export type EngineParameterInfo = WasmEngineParameterInfo;
|
|
@@ -200,6 +212,13 @@ export type EngineTelemetry = WasmEngineTelemetry;
|
|
|
200
212
|
export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
|
|
201
213
|
export type EngineTransportState = WasmEngineTransportState;
|
|
202
214
|
|
|
215
|
+
/** Row-major 2-D matrix as a flat buffer plus its dimensions. */
|
|
216
|
+
export type Matrix2dResult = WasmMatrix2dResult;
|
|
217
|
+
/** NMF factor matrices { w, h } from {@link decompose}. */
|
|
218
|
+
export type DecomposeResult = WasmDecomposeResult;
|
|
219
|
+
/** Harmonic / percussive / residual signals from {@link hpssWithResidual}. */
|
|
220
|
+
export type HpssWithResidualResult = WasmHpssWithResidualResult;
|
|
221
|
+
|
|
203
222
|
export const EXPECTED_ENGINE_ABI_VERSION = 2;
|
|
204
223
|
|
|
205
224
|
export interface EngineCapabilities {
|
|
@@ -220,6 +239,43 @@ export interface MixerRealtimeBuffer {
|
|
|
220
239
|
process: (numSamples?: number) => void;
|
|
221
240
|
}
|
|
222
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} mono
|
|
244
|
+
* processing. The `input` / `output` `Float32Array`s are typed-memory views
|
|
245
|
+
* onto the WASM heap — write samples into `input`, call `process()`, then
|
|
246
|
+
* read from `output`. The views are owned by the {@link RealtimeVoiceChanger}
|
|
247
|
+
* and remain valid until `delete()` is called on it.
|
|
248
|
+
*/
|
|
249
|
+
export interface RealtimeVoiceChangerMonoBuffer {
|
|
250
|
+
input: Float32Array;
|
|
251
|
+
output: Float32Array;
|
|
252
|
+
process: () => void;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} interleaved
|
|
257
|
+
* multi-channel processing. Layout is L0,R0,L1,R1,... for stereo. The views
|
|
258
|
+
* are owned by the {@link RealtimeVoiceChanger}.
|
|
259
|
+
*/
|
|
260
|
+
export interface RealtimeVoiceChangerInterleavedBuffer {
|
|
261
|
+
input: Float32Array;
|
|
262
|
+
output: Float32Array;
|
|
263
|
+
channels: number;
|
|
264
|
+
process: () => void;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Zero-copy realtime buffer for {@link RealtimeVoiceChanger} planar stereo
|
|
269
|
+
* processing. Each entry in `channels` is a heap-backed `Float32Array` for one
|
|
270
|
+
* channel (matching AudioWorklet's native layout). Process happens in place:
|
|
271
|
+
* write samples into each channel view, call `process()`, then read back from
|
|
272
|
+
* the same views.
|
|
273
|
+
*/
|
|
274
|
+
export interface RealtimeVoiceChangerPlanarBuffer {
|
|
275
|
+
channels: Float32Array[];
|
|
276
|
+
process: () => void;
|
|
277
|
+
}
|
|
278
|
+
|
|
223
279
|
function automationCurveCode(curve: AutomationCurve): number {
|
|
224
280
|
switch (curve) {
|
|
225
281
|
case 'linear':
|
|
@@ -251,6 +307,22 @@ function panLawCode(panLaw: PanLaw | number): number {
|
|
|
251
307
|
}
|
|
252
308
|
}
|
|
253
309
|
|
|
310
|
+
function panModeCode(panMode: PanMode | number): number {
|
|
311
|
+
if (typeof panMode === 'number') {
|
|
312
|
+
return panMode;
|
|
313
|
+
}
|
|
314
|
+
switch (panMode) {
|
|
315
|
+
case 'stereoPan':
|
|
316
|
+
case 'stereo-pan':
|
|
317
|
+
return 1;
|
|
318
|
+
case 'dualPan':
|
|
319
|
+
case 'dual-pan':
|
|
320
|
+
return 2;
|
|
321
|
+
default:
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
254
326
|
function meterTapCode(tap: MeterTap | number): number {
|
|
255
327
|
return tap === 'preFader' || tap === 0 ? 0 : 1;
|
|
256
328
|
}
|
|
@@ -266,6 +338,62 @@ function sendTimingCode(timing: SendTiming | number): number {
|
|
|
266
338
|
let module: SonareModule | null = null;
|
|
267
339
|
let initPromise: Promise<void> | null = null;
|
|
268
340
|
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Input validation helpers (empty + NaN/Inf guards for sample buffers)
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Per-call validation options accepted by guarded wrappers. Empty-buffer
|
|
347
|
+
* checks are always performed; pass `{ validate: false }` to opt out of the
|
|
348
|
+
* O(n) NaN/Inf scan on hot paths.
|
|
349
|
+
*/
|
|
350
|
+
export interface ValidateOptions {
|
|
351
|
+
validate?: boolean;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function assertNonEmptySamples(
|
|
355
|
+
fnName: string,
|
|
356
|
+
samples: ArrayLike<number>,
|
|
357
|
+
argName = 'samples',
|
|
358
|
+
): void {
|
|
359
|
+
if (samples.length === 0) {
|
|
360
|
+
throw new RangeError(`${fnName}: ${argName} must not be empty`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function assertFiniteSamples(
|
|
365
|
+
fnName: string,
|
|
366
|
+
samples: ArrayLike<number>,
|
|
367
|
+
validate: boolean,
|
|
368
|
+
argName = 'samples',
|
|
369
|
+
): void {
|
|
370
|
+
if (!validate) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
for (let i = 0; i < samples.length; i++) {
|
|
374
|
+
const v = samples[i] as number;
|
|
375
|
+
if (!Number.isFinite(v)) {
|
|
376
|
+
throw new RangeError(`${fnName}: ${argName} contains NaN or Inf at index ${i}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function assertSamples(
|
|
382
|
+
fnName: string,
|
|
383
|
+
samples: ArrayLike<number>,
|
|
384
|
+
validate: boolean,
|
|
385
|
+
argName = 'samples',
|
|
386
|
+
): void {
|
|
387
|
+
assertNonEmptySamples(fnName, samples, argName);
|
|
388
|
+
assertFiniteSamples(fnName, samples, validate, argName);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function assertFiniteScalar(fnName: string, value: number, argName: string): void {
|
|
392
|
+
if (!Number.isFinite(value)) {
|
|
393
|
+
throw new RangeError(`${fnName}: ${argName} must be a finite number`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
269
397
|
// ============================================================================
|
|
270
398
|
// Initialization
|
|
271
399
|
// ============================================================================
|
|
@@ -325,6 +453,60 @@ export function engineAbiVersion(): number {
|
|
|
325
453
|
return module.engineAbiVersion();
|
|
326
454
|
}
|
|
327
455
|
|
|
456
|
+
export function voiceChangerAbiVersion(): number {
|
|
457
|
+
if (!module) {
|
|
458
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
459
|
+
}
|
|
460
|
+
return module.voiceChangerAbiVersion();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Canonical ordinal order of the built-in voice-character presets, matching the
|
|
464
|
+
// C ABI SonareVoiceCharacterPreset enum and SONARE_REALTIME_VOICE_CHANGER_PRESET_IDS.
|
|
465
|
+
const VOICE_PRESET_ORDINALS: readonly VoicePresetId[] = [
|
|
466
|
+
'neutral-monitor',
|
|
467
|
+
'bright-idol',
|
|
468
|
+
'soft-whisper',
|
|
469
|
+
'deep-narrator',
|
|
470
|
+
'robot-mascot',
|
|
471
|
+
'dark-villain',
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
function resolveVoicePresetOrdinal(preset: VoicePresetId | number): number {
|
|
475
|
+
if (typeof preset === 'number') {
|
|
476
|
+
return preset;
|
|
477
|
+
}
|
|
478
|
+
const ordinal = VOICE_PRESET_ORDINALS.indexOf(preset);
|
|
479
|
+
if (ordinal < 0) {
|
|
480
|
+
throw new Error(`Unknown voice character preset: ${preset}`);
|
|
481
|
+
}
|
|
482
|
+
return ordinal;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Map a voice-character preset ordinal (or canonical id) to its canonical id
|
|
487
|
+
* string (e.g. `'bright-idol'`). Returns `null` for an out-of-range ordinal.
|
|
488
|
+
*/
|
|
489
|
+
export function voiceCharacterPresetId(preset: VoicePresetId | number): string | null {
|
|
490
|
+
if (!module) {
|
|
491
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
492
|
+
}
|
|
493
|
+
return module.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Return the canonical (normalized) flat POD config for a built-in voice
|
|
498
|
+
* preset, skipping the JSON round-trip. Accepts a canonical preset id or its
|
|
499
|
+
* integer ordinal. Returns `null` for an out-of-range ordinal.
|
|
500
|
+
*/
|
|
501
|
+
export function realtimeVoiceChangerPresetConfig(
|
|
502
|
+
preset: VoicePresetId | number,
|
|
503
|
+
): RealtimeVoiceChangerPodConfig | null {
|
|
504
|
+
if (!module) {
|
|
505
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
506
|
+
}
|
|
507
|
+
return module.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
|
|
508
|
+
}
|
|
509
|
+
|
|
328
510
|
export function engineCapabilities(): EngineCapabilities {
|
|
329
511
|
const abiVersion = engineAbiVersion();
|
|
330
512
|
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function';
|
|
@@ -567,10 +749,10 @@ export class RealtimeEngine {
|
|
|
567
749
|
* Detect BPM from audio samples.
|
|
568
750
|
*
|
|
569
751
|
* @param samples - Audio samples (mono, float32)
|
|
570
|
-
* @param sampleRate - Sample rate in Hz
|
|
752
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
571
753
|
* @returns Detected BPM
|
|
572
754
|
*/
|
|
573
|
-
export function detectBpm(samples: Float32Array, sampleRate
|
|
755
|
+
export function detectBpm(samples: Float32Array, sampleRate = 22050): number {
|
|
574
756
|
if (!module) {
|
|
575
757
|
throw new Error('Module not initialized. Call init() first.');
|
|
576
758
|
}
|
|
@@ -581,12 +763,12 @@ export function detectBpm(samples: Float32Array, sampleRate: number): number {
|
|
|
581
763
|
* Detect musical key from audio samples.
|
|
582
764
|
*
|
|
583
765
|
* @param samples - Audio samples (mono, float32)
|
|
584
|
-
* @param sampleRate - Sample rate in Hz
|
|
766
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
585
767
|
* @returns Detected key
|
|
586
768
|
*/
|
|
587
769
|
export function detectKey(
|
|
588
770
|
samples: Float32Array,
|
|
589
|
-
sampleRate
|
|
771
|
+
sampleRate = 22050,
|
|
590
772
|
options: KeyDetectionOptions = {},
|
|
591
773
|
): Key {
|
|
592
774
|
if (!module) {
|
|
@@ -683,7 +865,7 @@ function keyProfileValue(profile: KeyDetectionOptions['profile'] | undefined): n
|
|
|
683
865
|
|
|
684
866
|
export function detectKeyCandidates(
|
|
685
867
|
samples: Float32Array,
|
|
686
|
-
sampleRate
|
|
868
|
+
sampleRate = 22050,
|
|
687
869
|
options: KeyDetectionOptions = {},
|
|
688
870
|
): KeyCandidate[] {
|
|
689
871
|
if (!module) {
|
|
@@ -709,10 +891,10 @@ export function detectKeyCandidates(
|
|
|
709
891
|
* Detect onset times from audio samples.
|
|
710
892
|
*
|
|
711
893
|
* @param samples - Audio samples (mono, float32)
|
|
712
|
-
* @param sampleRate - Sample rate in Hz
|
|
894
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
713
895
|
* @returns Array of onset times in seconds
|
|
714
896
|
*/
|
|
715
|
-
export function detectOnsets(samples: Float32Array, sampleRate
|
|
897
|
+
export function detectOnsets(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
716
898
|
if (!module) {
|
|
717
899
|
throw new Error('Module not initialized. Call init() first.');
|
|
718
900
|
}
|
|
@@ -723,10 +905,10 @@ export function detectOnsets(samples: Float32Array, sampleRate: number): Float32
|
|
|
723
905
|
* Detect beat times from audio samples.
|
|
724
906
|
*
|
|
725
907
|
* @param samples - Audio samples (mono, float32)
|
|
726
|
-
* @param sampleRate - Sample rate in Hz
|
|
908
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
727
909
|
* @returns Array of beat times in seconds
|
|
728
910
|
*/
|
|
729
|
-
export function detectBeats(samples: Float32Array, sampleRate
|
|
911
|
+
export function detectBeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
730
912
|
if (!module) {
|
|
731
913
|
throw new Error('Module not initialized. Call init() first.');
|
|
732
914
|
}
|
|
@@ -737,10 +919,10 @@ export function detectBeats(samples: Float32Array, sampleRate: number): Float32A
|
|
|
737
919
|
* Detect downbeat times from audio samples.
|
|
738
920
|
*
|
|
739
921
|
* @param samples - Audio samples (mono, float32)
|
|
740
|
-
* @param sampleRate - Sample rate in Hz
|
|
922
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
741
923
|
* @returns Array of downbeat times in seconds
|
|
742
924
|
*/
|
|
743
|
-
export function detectDownbeats(samples: Float32Array, sampleRate
|
|
925
|
+
export function detectDownbeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
744
926
|
if (!module) {
|
|
745
927
|
throw new Error('Module not initialized. Call init() first.');
|
|
746
928
|
}
|
|
@@ -765,13 +947,13 @@ function convertChordAnalysisResult(wasm: WasmChordAnalysisResult): ChordAnalysi
|
|
|
765
947
|
* Detect chords from audio samples.
|
|
766
948
|
*
|
|
767
949
|
* @param samples - Audio samples (mono, float32)
|
|
768
|
-
* @param sampleRate - Sample rate in Hz
|
|
950
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
769
951
|
* @param options - Optional chord detection settings
|
|
770
952
|
* @returns Detected chord segments
|
|
771
953
|
*/
|
|
772
954
|
export function detectChords(
|
|
773
955
|
samples: Float32Array,
|
|
774
|
-
sampleRate
|
|
956
|
+
sampleRate = 22050,
|
|
775
957
|
options: ChordDetectionOptions = {},
|
|
776
958
|
): ChordAnalysisResult {
|
|
777
959
|
if (!module) {
|
|
@@ -855,10 +1037,10 @@ function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult {
|
|
|
855
1037
|
* Perform complete music analysis.
|
|
856
1038
|
*
|
|
857
1039
|
* @param samples - Audio samples (mono, float32)
|
|
858
|
-
* @param sampleRate - Sample rate in Hz
|
|
1040
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
859
1041
|
* @returns Complete analysis result
|
|
860
1042
|
*/
|
|
861
|
-
export function analyze(samples: Float32Array, sampleRate
|
|
1043
|
+
export function analyze(samples: Float32Array, sampleRate = 22050): AnalysisResult {
|
|
862
1044
|
if (!module) {
|
|
863
1045
|
throw new Error('Module not initialized. Call init() first.');
|
|
864
1046
|
}
|
|
@@ -868,7 +1050,7 @@ export function analyze(samples: Float32Array, sampleRate: number): AnalysisResu
|
|
|
868
1050
|
|
|
869
1051
|
export function analyzeImpulseResponse(
|
|
870
1052
|
samples: Float32Array,
|
|
871
|
-
sampleRate
|
|
1053
|
+
sampleRate = 48000,
|
|
872
1054
|
nOctaveBands = 6,
|
|
873
1055
|
): AcousticResult {
|
|
874
1056
|
if (!module) {
|
|
@@ -884,7 +1066,7 @@ export function analyzeImpulseResponse(
|
|
|
884
1066
|
|
|
885
1067
|
export function detectAcoustic(
|
|
886
1068
|
samples: Float32Array,
|
|
887
|
-
sampleRate
|
|
1069
|
+
sampleRate = 48000,
|
|
888
1070
|
nOctaveBands = 6,
|
|
889
1071
|
nThirdOctaveSubbands = 24,
|
|
890
1072
|
minDecayDb = 30.0,
|
|
@@ -908,13 +1090,13 @@ export function detectAcoustic(
|
|
|
908
1090
|
* Perform complete music analysis with progress reporting.
|
|
909
1091
|
*
|
|
910
1092
|
* @param samples - Audio samples (mono, float32)
|
|
911
|
-
* @param sampleRate - Sample rate in Hz
|
|
1093
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
912
1094
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
913
1095
|
* @returns Complete analysis result
|
|
914
1096
|
*/
|
|
915
1097
|
export function analyzeWithProgress(
|
|
916
1098
|
samples: Float32Array,
|
|
917
|
-
sampleRate
|
|
1099
|
+
sampleRate = 22050,
|
|
918
1100
|
onProgress: ProgressCallback,
|
|
919
1101
|
): AnalysisResult {
|
|
920
1102
|
if (!module) {
|
|
@@ -924,6 +1106,154 @@ export function analyzeWithProgress(
|
|
|
924
1106
|
return convertAnalysisResult(result);
|
|
925
1107
|
}
|
|
926
1108
|
|
|
1109
|
+
export interface BpmCandidate {
|
|
1110
|
+
bpm: number;
|
|
1111
|
+
confidence: number;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export interface BpmAnalysisResult {
|
|
1115
|
+
bpm: number;
|
|
1116
|
+
confidence: number;
|
|
1117
|
+
candidates: BpmCandidate[];
|
|
1118
|
+
autocorrelation: Float32Array;
|
|
1119
|
+
tempogram: Float32Array;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
export interface RhythmAnalysisResult {
|
|
1123
|
+
timeSignature: { numerator: number; denominator: number; confidence: number };
|
|
1124
|
+
syncopation: number;
|
|
1125
|
+
grooveType: string;
|
|
1126
|
+
patternRegularity: number;
|
|
1127
|
+
tempoStability: number;
|
|
1128
|
+
bpm: number;
|
|
1129
|
+
beatIntervals: Float32Array;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
export interface DynamicsAnalysisResult {
|
|
1133
|
+
dynamicRangeDb: number;
|
|
1134
|
+
peakDb: number;
|
|
1135
|
+
rmsDb: number;
|
|
1136
|
+
crestFactor: number;
|
|
1137
|
+
loudnessRangeDb: number;
|
|
1138
|
+
isCompressed: boolean;
|
|
1139
|
+
/** Loudness curve timestamps (seconds), parallel to {@link loudnessRmsDb}. */
|
|
1140
|
+
loudnessTimes: Float32Array;
|
|
1141
|
+
/** Loudness curve RMS values (dB), parallel to {@link loudnessTimes}. */
|
|
1142
|
+
loudnessRmsDb: Float32Array;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/** Timbre metrics for one analysis window. Entries are ordered by time in `timbreOverTime`. */
|
|
1146
|
+
export interface TimbreFrame {
|
|
1147
|
+
brightness: number;
|
|
1148
|
+
warmth: number;
|
|
1149
|
+
density: number;
|
|
1150
|
+
roughness: number;
|
|
1151
|
+
complexity: number;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
export interface TimbreAnalysisResult extends TimbreFrame {
|
|
1155
|
+
spectralCentroid: Float32Array;
|
|
1156
|
+
spectralFlatness: Float32Array;
|
|
1157
|
+
spectralRolloff: Float32Array;
|
|
1158
|
+
/** Time-varying timbre metrics, one entry per analysis window. */
|
|
1159
|
+
timbreOverTime: TimbreFrame[];
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Detailed BPM analysis (BPM, confidence, alternate candidates, autocorrelation,
|
|
1164
|
+
* tempogram). Matches the Node `analyzeBpm` / Python `analyze_bpm` surface.
|
|
1165
|
+
*/
|
|
1166
|
+
export function analyzeBpm(
|
|
1167
|
+
samples: Float32Array,
|
|
1168
|
+
sampleRate = 22050,
|
|
1169
|
+
bpmMin = 30.0,
|
|
1170
|
+
bpmMax = 300.0,
|
|
1171
|
+
startBpm = 120.0,
|
|
1172
|
+
nFft = 2048,
|
|
1173
|
+
hopLength = 512,
|
|
1174
|
+
maxCandidates = 5,
|
|
1175
|
+
): BpmAnalysisResult {
|
|
1176
|
+
if (!module) {
|
|
1177
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1178
|
+
}
|
|
1179
|
+
return module.analyzeBpm(
|
|
1180
|
+
samples,
|
|
1181
|
+
sampleRate,
|
|
1182
|
+
bpmMin,
|
|
1183
|
+
bpmMax,
|
|
1184
|
+
startBpm,
|
|
1185
|
+
nFft,
|
|
1186
|
+
hopLength,
|
|
1187
|
+
maxCandidates,
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Detailed rhythm analysis (time signature, groove, syncopation, beat intervals).
|
|
1193
|
+
*/
|
|
1194
|
+
export function analyzeRhythm(
|
|
1195
|
+
samples: Float32Array,
|
|
1196
|
+
sampleRate = 22050,
|
|
1197
|
+
bpmMin = 60.0,
|
|
1198
|
+
bpmMax = 200.0,
|
|
1199
|
+
startBpm = 120.0,
|
|
1200
|
+
nFft = 2048,
|
|
1201
|
+
hopLength = 512,
|
|
1202
|
+
): RhythmAnalysisResult {
|
|
1203
|
+
if (!module) {
|
|
1204
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1205
|
+
}
|
|
1206
|
+
return module.analyzeRhythm(samples, sampleRate, bpmMin, bpmMax, startBpm, nFft, hopLength);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* Dynamics analysis (RMS, peak, crest factor, LRA, loudness curve).
|
|
1211
|
+
*/
|
|
1212
|
+
export function analyzeDynamics(
|
|
1213
|
+
samples: Float32Array,
|
|
1214
|
+
sampleRate = 22050,
|
|
1215
|
+
windowSec = 0.4,
|
|
1216
|
+
hopLength = 512,
|
|
1217
|
+
compressionThreshold = 6.0,
|
|
1218
|
+
): DynamicsAnalysisResult {
|
|
1219
|
+
if (!module) {
|
|
1220
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1221
|
+
}
|
|
1222
|
+
return module.analyzeDynamics(samples, sampleRate, windowSec, hopLength, compressionThreshold);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Timbre analysis (brightness/warmth/density/roughness/complexity plus spectral
|
|
1227
|
+
* features and per-window timbre frames).
|
|
1228
|
+
*/
|
|
1229
|
+
export function analyzeTimbre(
|
|
1230
|
+
samples: Float32Array,
|
|
1231
|
+
sampleRate = 22050,
|
|
1232
|
+
nFft = 2048,
|
|
1233
|
+
hopLength = 512,
|
|
1234
|
+
nMels = 128,
|
|
1235
|
+
nMfcc = 13,
|
|
1236
|
+
windowSec = 0.5,
|
|
1237
|
+
): TimbreAnalysisResult {
|
|
1238
|
+
if (!module) {
|
|
1239
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1240
|
+
}
|
|
1241
|
+
return module.analyzeTimbre(samples, sampleRate, nFft, hopLength, nMels, nMfcc, windowSec);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Whether this WASM build was compiled with FFmpeg support. Mirrors Node /
|
|
1246
|
+
* Python `hasFfmpegSupport`. In the published WASM binding this currently
|
|
1247
|
+
* always returns `false` (FFmpeg is not bundled into the .wasm), but the API
|
|
1248
|
+
* exists so caller code can branch on capabilities portably.
|
|
1249
|
+
*/
|
|
1250
|
+
export function hasFfmpegSupport(): boolean {
|
|
1251
|
+
if (!module) {
|
|
1252
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1253
|
+
}
|
|
1254
|
+
return module.hasFfmpegSupport();
|
|
1255
|
+
}
|
|
1256
|
+
|
|
927
1257
|
// ============================================================================
|
|
928
1258
|
// Effects
|
|
929
1259
|
// ============================================================================
|
|
@@ -932,14 +1262,14 @@ export function analyzeWithProgress(
|
|
|
932
1262
|
* Perform Harmonic-Percussive Source Separation (HPSS).
|
|
933
1263
|
*
|
|
934
1264
|
* @param samples - Audio samples (mono, float32)
|
|
935
|
-
* @param sampleRate - Sample rate in Hz
|
|
1265
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
936
1266
|
* @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
|
|
937
1267
|
* @param kernelPercussive - Vertical median filter size for percussive (default: 31)
|
|
938
1268
|
* @returns Separated harmonic and percussive components
|
|
939
1269
|
*/
|
|
940
1270
|
export function hpss(
|
|
941
1271
|
samples: Float32Array,
|
|
942
|
-
sampleRate
|
|
1272
|
+
sampleRate = 22050,
|
|
943
1273
|
kernelHarmonic = 31,
|
|
944
1274
|
kernelPercussive = 31,
|
|
945
1275
|
): HpssResult {
|
|
@@ -1022,9 +1352,9 @@ export function pitchShift(
|
|
|
1022
1352
|
*/
|
|
1023
1353
|
export function pitchCorrectToMidi(
|
|
1024
1354
|
samples: Float32Array,
|
|
1025
|
-
sampleRate
|
|
1026
|
-
currentMidi
|
|
1027
|
-
targetMidi
|
|
1355
|
+
sampleRate = 22050,
|
|
1356
|
+
currentMidi = 69.0,
|
|
1357
|
+
targetMidi = 69.0,
|
|
1028
1358
|
): Float32Array {
|
|
1029
1359
|
if (!module) {
|
|
1030
1360
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1044,10 +1374,10 @@ export function pitchCorrectToMidi(
|
|
|
1044
1374
|
*/
|
|
1045
1375
|
export function noteStretch(
|
|
1046
1376
|
samples: Float32Array,
|
|
1047
|
-
sampleRate
|
|
1048
|
-
onsetSample
|
|
1049
|
-
offsetSample
|
|
1050
|
-
stretchRatio
|
|
1377
|
+
sampleRate = 22050,
|
|
1378
|
+
onsetSample = 0,
|
|
1379
|
+
offsetSample = 0,
|
|
1380
|
+
stretchRatio = 1.0,
|
|
1051
1381
|
): Float32Array {
|
|
1052
1382
|
if (!module) {
|
|
1053
1383
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1066,13 +1396,15 @@ export function noteStretch(
|
|
|
1066
1396
|
*/
|
|
1067
1397
|
export function voiceChange(
|
|
1068
1398
|
samples: Float32Array,
|
|
1069
|
-
sampleRate
|
|
1070
|
-
pitchSemitones
|
|
1071
|
-
formantFactor
|
|
1399
|
+
sampleRate = 22050,
|
|
1400
|
+
pitchSemitones = 0.0,
|
|
1401
|
+
formantFactor = 1.0,
|
|
1402
|
+
options: ValidateOptions = {},
|
|
1072
1403
|
): Float32Array {
|
|
1073
1404
|
if (!module) {
|
|
1074
1405
|
throw new Error('Module not initialized. Call init() first.');
|
|
1075
1406
|
}
|
|
1407
|
+
assertSamples('voiceChange', samples, options.validate !== false);
|
|
1076
1408
|
return module.voiceChange(samples, sampleRate, pitchSemitones, formantFactor);
|
|
1077
1409
|
}
|
|
1078
1410
|
|
|
@@ -1095,7 +1427,7 @@ export function normalize(samples: Float32Array, sampleRate: number, targetDb =
|
|
|
1095
1427
|
* Apply mastering loudness normalization with a true-peak ceiling.
|
|
1096
1428
|
*
|
|
1097
1429
|
* @param samples - Audio samples (mono, float32)
|
|
1098
|
-
* @param sampleRate - Sample rate in Hz
|
|
1430
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1099
1431
|
* @param targetLufs - Target integrated LUFS (default: -14)
|
|
1100
1432
|
* @param ceilingDb - True/sample peak ceiling in dBFS (default: -1)
|
|
1101
1433
|
* @param truePeakOversample - Oversampling factor used for peak estimation
|
|
@@ -1103,7 +1435,7 @@ export function normalize(samples: Float32Array, sampleRate: number, targetDb =
|
|
|
1103
1435
|
*/
|
|
1104
1436
|
export function mastering(
|
|
1105
1437
|
samples: Float32Array,
|
|
1106
|
-
sampleRate
|
|
1438
|
+
sampleRate = 22050,
|
|
1107
1439
|
targetLufs = -14.0,
|
|
1108
1440
|
ceilingDb = -1.0,
|
|
1109
1441
|
truePeakOversample = 4,
|
|
@@ -1145,7 +1477,7 @@ export function masteringStereoAnalysisNames(): StereoAnalysis[] {
|
|
|
1145
1477
|
export function masteringProcess(
|
|
1146
1478
|
processorName: SoloProcessor,
|
|
1147
1479
|
samples: Float32Array,
|
|
1148
|
-
sampleRate
|
|
1480
|
+
sampleRate = 22050,
|
|
1149
1481
|
params: MasteringProcessorParams = {},
|
|
1150
1482
|
): MasteringResult {
|
|
1151
1483
|
if (!module) {
|
|
@@ -1158,7 +1490,7 @@ export function masteringProcessStereo(
|
|
|
1158
1490
|
processorName: SoloProcessor,
|
|
1159
1491
|
left: Float32Array,
|
|
1160
1492
|
right: Float32Array,
|
|
1161
|
-
sampleRate
|
|
1493
|
+
sampleRate = 22050,
|
|
1162
1494
|
params: MasteringProcessorParams = {},
|
|
1163
1495
|
): MasteringStereoResult {
|
|
1164
1496
|
if (!module) {
|
|
@@ -1174,7 +1506,7 @@ export function masteringPairProcess(
|
|
|
1174
1506
|
processorName: PairProcessor,
|
|
1175
1507
|
source: Float32Array,
|
|
1176
1508
|
reference: Float32Array,
|
|
1177
|
-
sampleRate
|
|
1509
|
+
sampleRate = 22050,
|
|
1178
1510
|
params: MasteringProcessorParams = {},
|
|
1179
1511
|
): MasteringResult {
|
|
1180
1512
|
if (!module) {
|
|
@@ -1187,7 +1519,7 @@ export function masteringPairAnalyze(
|
|
|
1187
1519
|
analysisName: PairAnalysis,
|
|
1188
1520
|
source: Float32Array,
|
|
1189
1521
|
reference: Float32Array,
|
|
1190
|
-
sampleRate
|
|
1522
|
+
sampleRate = 22050,
|
|
1191
1523
|
params: MasteringProcessorParams = {},
|
|
1192
1524
|
): string {
|
|
1193
1525
|
if (!module) {
|
|
@@ -1200,7 +1532,7 @@ export function masteringStereoAnalyze(
|
|
|
1200
1532
|
analysisName: StereoAnalysis,
|
|
1201
1533
|
left: Float32Array,
|
|
1202
1534
|
right: Float32Array,
|
|
1203
|
-
sampleRate
|
|
1535
|
+
sampleRate = 22050,
|
|
1204
1536
|
params: MasteringProcessorParams = {},
|
|
1205
1537
|
): string {
|
|
1206
1538
|
if (!module) {
|
|
@@ -1211,7 +1543,7 @@ export function masteringStereoAnalyze(
|
|
|
1211
1543
|
|
|
1212
1544
|
export function masteringAssistantSuggest(
|
|
1213
1545
|
samples: Float32Array,
|
|
1214
|
-
sampleRate
|
|
1546
|
+
sampleRate = 22050,
|
|
1215
1547
|
params: MasteringProcessorParams = {},
|
|
1216
1548
|
): string {
|
|
1217
1549
|
if (!module) {
|
|
@@ -1222,7 +1554,7 @@ export function masteringAssistantSuggest(
|
|
|
1222
1554
|
|
|
1223
1555
|
export function masteringAudioProfile(
|
|
1224
1556
|
samples: Float32Array,
|
|
1225
|
-
sampleRate
|
|
1557
|
+
sampleRate = 22050,
|
|
1226
1558
|
params: MasteringProcessorParams = {},
|
|
1227
1559
|
): string {
|
|
1228
1560
|
if (!module) {
|
|
@@ -1233,7 +1565,7 @@ export function masteringAudioProfile(
|
|
|
1233
1565
|
|
|
1234
1566
|
export function masteringStreamingPreview(
|
|
1235
1567
|
samples: Float32Array,
|
|
1236
|
-
sampleRate
|
|
1568
|
+
sampleRate = 22050,
|
|
1237
1569
|
platforms: StreamingPlatform[] = [],
|
|
1238
1570
|
): string {
|
|
1239
1571
|
if (!module) {
|
|
@@ -1242,61 +1574,315 @@ export function masteringStreamingPreview(
|
|
|
1242
1574
|
return module.masteringStreamingPreview(samples, sampleRate, platforms);
|
|
1243
1575
|
}
|
|
1244
1576
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1577
|
+
// ============================================================================
|
|
1578
|
+
// Mastering repair (declick, denoise_classical, declip, decrackle, dehum,
|
|
1579
|
+
// dereverb_classical, trim_silence) — hand-written bindings.
|
|
1580
|
+
// ============================================================================
|
|
1581
|
+
|
|
1582
|
+
/** Options for `masteringRepairDeclick`. */
|
|
1583
|
+
export interface DeclickOptions {
|
|
1584
|
+
threshold?: number;
|
|
1585
|
+
neighborRatio?: number;
|
|
1586
|
+
maxClickSamples?: number;
|
|
1587
|
+
lpcOrder?: number;
|
|
1588
|
+
residualRatio?: number;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/** Algorithms accepted by `masteringRepairDenoiseClassical`. */
|
|
1592
|
+
export type DenoiseClassicalMode = 'logMmse' | 'mmseStsa' | 'spectralSubtraction';
|
|
1593
|
+
|
|
1594
|
+
/** Noise PSD estimators accepted by `masteringRepairDenoiseClassical`. */
|
|
1595
|
+
export type DenoiseClassicalNoiseEstimator = 'quantile' | 'mcra' | 'imcra';
|
|
1596
|
+
|
|
1597
|
+
/** Options for `masteringRepairDenoiseClassical`. */
|
|
1598
|
+
export interface DenoiseClassicalOptions {
|
|
1599
|
+
mode?: DenoiseClassicalMode;
|
|
1600
|
+
noiseEstimator?: DenoiseClassicalNoiseEstimator;
|
|
1601
|
+
nFft?: number;
|
|
1602
|
+
hopLength?: number;
|
|
1603
|
+
ddAlpha?: number;
|
|
1604
|
+
gainFloor?: number;
|
|
1605
|
+
overSubtraction?: number;
|
|
1606
|
+
spectralFloor?: number;
|
|
1607
|
+
noiseEstimationQuantile?: number;
|
|
1608
|
+
speechPresenceGain?: boolean;
|
|
1609
|
+
gainSmoothing?: boolean;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/** Offline LPC-based declicker. */
|
|
1613
|
+
export function masteringRepairDeclick(
|
|
1254
1614
|
samples: Float32Array,
|
|
1255
1615
|
sampleRate: number,
|
|
1256
|
-
|
|
1257
|
-
):
|
|
1258
|
-
|
|
1259
|
-
throw new Error('Module not initialized. Call init() first.');
|
|
1260
|
-
}
|
|
1261
|
-
return module.masteringChain(samples, sampleRate, config as Record<string, unknown>);
|
|
1616
|
+
options: DeclickOptions = {},
|
|
1617
|
+
): Float32Array {
|
|
1618
|
+
return requireModule().masteringRepairDeclick(samples, sampleRate, options);
|
|
1262
1619
|
}
|
|
1263
1620
|
|
|
1264
|
-
/**
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
* @param left - Left channel samples
|
|
1268
|
-
* @param right - Right channel samples
|
|
1269
|
-
* @param sampleRate - Sample rate in Hz
|
|
1270
|
-
* @param config - Chain stage configuration
|
|
1271
|
-
* @returns Processed stereo audio, loudness metadata, and applied stage names
|
|
1272
|
-
*/
|
|
1273
|
-
export function masteringChainStereo(
|
|
1274
|
-
left: Float32Array,
|
|
1275
|
-
right: Float32Array,
|
|
1621
|
+
/** Offline STFT-domain classical denoiser (LogMMSE / MMSE-STSA / SpectralSubtraction). */
|
|
1622
|
+
export function masteringRepairDenoiseClassical(
|
|
1623
|
+
samples: Float32Array,
|
|
1276
1624
|
sampleRate: number,
|
|
1277
|
-
|
|
1278
|
-
):
|
|
1279
|
-
|
|
1280
|
-
throw new Error('Module not initialized. Call init() first.');
|
|
1281
|
-
}
|
|
1282
|
-
if (left.length !== right.length) {
|
|
1283
|
-
throw new Error('Stereo channel lengths must match.');
|
|
1284
|
-
}
|
|
1285
|
-
return module.masteringChainStereo(left, right, sampleRate, config as Record<string, unknown>);
|
|
1625
|
+
options: DenoiseClassicalOptions = {},
|
|
1626
|
+
): Float32Array {
|
|
1627
|
+
return requireModule().masteringRepairDenoiseClassical(samples, sampleRate, options);
|
|
1286
1628
|
}
|
|
1287
1629
|
|
|
1288
|
-
/**
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1630
|
+
/** Options for `masteringRepairDeclip`. */
|
|
1631
|
+
export interface DeclipOptions {
|
|
1632
|
+
clipThreshold?: number;
|
|
1633
|
+
lpcOrder?: number;
|
|
1634
|
+
iterations?: number;
|
|
1635
|
+
lpcBlend?: number;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/** Algorithms accepted by `masteringRepairDecrackle`. */
|
|
1639
|
+
export type DecrackleMode = 'median' | 'waveletShrinkage';
|
|
1640
|
+
|
|
1641
|
+
/** Options for `masteringRepairDecrackle`. */
|
|
1642
|
+
export interface DecrackleOptions {
|
|
1643
|
+
threshold?: number;
|
|
1644
|
+
mode?: DecrackleMode;
|
|
1645
|
+
levels?: number;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/** Options for `masteringRepairDehum`. */
|
|
1649
|
+
export interface DehumOptions {
|
|
1650
|
+
fundamentalHz?: number;
|
|
1651
|
+
harmonics?: number;
|
|
1652
|
+
q?: number;
|
|
1653
|
+
adaptive?: boolean;
|
|
1654
|
+
searchRangeHz?: number;
|
|
1655
|
+
adaptation?: number;
|
|
1656
|
+
frameSize?: number;
|
|
1657
|
+
pllBandwidth?: number;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/** Options for `masteringRepairDereverbClassical`. */
|
|
1661
|
+
export interface DereverbClassicalOptions {
|
|
1662
|
+
threshold?: number;
|
|
1663
|
+
attenuation?: number;
|
|
1664
|
+
nFft?: number;
|
|
1665
|
+
hopLength?: number;
|
|
1666
|
+
t60Sec?: number;
|
|
1667
|
+
lateDelayMs?: number;
|
|
1668
|
+
overSubtraction?: number;
|
|
1669
|
+
spectralFloor?: number;
|
|
1670
|
+
wpeEnabled?: boolean;
|
|
1671
|
+
wpeIterations?: number;
|
|
1672
|
+
wpeTaps?: number;
|
|
1673
|
+
wpeStrength?: number;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/** Trimming modes accepted by `masteringRepairTrimSilence`. */
|
|
1677
|
+
export type TrimSilenceMode = 'peak' | 'lufsGated';
|
|
1678
|
+
|
|
1679
|
+
/** Options for `masteringRepairTrimSilence`. */
|
|
1680
|
+
export interface TrimSilenceOptions {
|
|
1681
|
+
threshold?: number;
|
|
1682
|
+
paddingSamples?: number;
|
|
1683
|
+
mode?: TrimSilenceMode;
|
|
1684
|
+
gateLufs?: number;
|
|
1685
|
+
windowMs?: number;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/** Offline LPC-based declipper. */
|
|
1689
|
+
export function masteringRepairDeclip(
|
|
1690
|
+
samples: Float32Array,
|
|
1691
|
+
sampleRate: number,
|
|
1692
|
+
options: DeclipOptions = {},
|
|
1693
|
+
): Float32Array {
|
|
1694
|
+
return requireModule().masteringRepairDeclip(samples, sampleRate, options);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/** Offline crackle suppressor (median or wavelet-shrinkage). */
|
|
1698
|
+
export function masteringRepairDecrackle(
|
|
1699
|
+
samples: Float32Array,
|
|
1700
|
+
sampleRate: number,
|
|
1701
|
+
options: DecrackleOptions = {},
|
|
1702
|
+
): Float32Array {
|
|
1703
|
+
return requireModule().masteringRepairDecrackle(samples, sampleRate, options);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/** Offline mains-hum remover. */
|
|
1707
|
+
export function masteringRepairDehum(
|
|
1708
|
+
samples: Float32Array,
|
|
1709
|
+
sampleRate: number,
|
|
1710
|
+
options: DehumOptions = {},
|
|
1711
|
+
): Float32Array {
|
|
1712
|
+
return requireModule().masteringRepairDehum(samples, sampleRate, options);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/** Offline classical dereverberator (spectral subtraction + optional WPE). */
|
|
1716
|
+
export function masteringRepairDereverbClassical(
|
|
1717
|
+
samples: Float32Array,
|
|
1718
|
+
sampleRate: number,
|
|
1719
|
+
options: DereverbClassicalOptions = {},
|
|
1720
|
+
): Float32Array {
|
|
1721
|
+
return requireModule().masteringRepairDereverbClassical(samples, sampleRate, options);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/** Offline silence trimmer (peak threshold or LUFS-gated). */
|
|
1725
|
+
export function masteringRepairTrimSilence(
|
|
1726
|
+
samples: Float32Array,
|
|
1727
|
+
sampleRate: number,
|
|
1728
|
+
options: TrimSilenceOptions = {},
|
|
1729
|
+
): Float32Array {
|
|
1730
|
+
return requireModule().masteringRepairTrimSilence(samples, sampleRate, options);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
// ============================================================================
|
|
1734
|
+
// Mastering — offline dynamics processors (compressor / gate / transient_shaper)
|
|
1735
|
+
// ============================================================================
|
|
1736
|
+
|
|
1737
|
+
/** Compressor sidechain detector mode. */
|
|
1738
|
+
export type CompressorDetector = 'peak' | 'rms' | 'log_rms';
|
|
1739
|
+
|
|
1740
|
+
/** Options for `masteringDynamicsCompressor`. */
|
|
1741
|
+
export interface CompressorOptions extends ValidateOptions {
|
|
1742
|
+
thresholdDb?: number;
|
|
1743
|
+
ratio?: number;
|
|
1744
|
+
attackMs?: number;
|
|
1745
|
+
releaseMs?: number;
|
|
1746
|
+
kneeDb?: number;
|
|
1747
|
+
makeupGainDb?: number;
|
|
1748
|
+
autoMakeup?: boolean;
|
|
1749
|
+
detector?: CompressorDetector | number;
|
|
1750
|
+
sidechainHpfEnabled?: boolean;
|
|
1751
|
+
sidechainHpfHz?: number;
|
|
1752
|
+
pdrTimeMs?: number;
|
|
1753
|
+
pdrReleaseScale?: number;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/** Options for `masteringDynamicsGate`. */
|
|
1757
|
+
export interface GateOptions extends ValidateOptions {
|
|
1758
|
+
thresholdDb?: number;
|
|
1759
|
+
attackMs?: number;
|
|
1760
|
+
releaseMs?: number;
|
|
1761
|
+
rangeDb?: number;
|
|
1762
|
+
holdMs?: number;
|
|
1763
|
+
closeThresholdDb?: number;
|
|
1764
|
+
keyHpfHz?: number;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/** Options for `masteringDynamicsTransientShaper`. */
|
|
1768
|
+
export interface TransientShaperOptions extends ValidateOptions {
|
|
1769
|
+
attackGainDb?: number;
|
|
1770
|
+
sustainGainDb?: number;
|
|
1771
|
+
fastAttackMs?: number;
|
|
1772
|
+
fastReleaseMs?: number;
|
|
1773
|
+
slowAttackMs?: number;
|
|
1774
|
+
slowReleaseMs?: number;
|
|
1775
|
+
sensitivity?: number;
|
|
1776
|
+
maxGainDb?: number;
|
|
1777
|
+
gainSmoothingMs?: number;
|
|
1778
|
+
lookaheadMs?: number;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/** Result envelope returned by offline mastering dynamics processors. */
|
|
1782
|
+
export interface DynamicsResult {
|
|
1783
|
+
samples: Float32Array;
|
|
1784
|
+
latencySamples: number;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
const COMPRESSOR_DETECTOR_MAP: Record<CompressorDetector, number> = {
|
|
1788
|
+
peak: 0,
|
|
1789
|
+
rms: 1,
|
|
1790
|
+
log_rms: 2,
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
/** Offline feed-forward compressor (soft knee, optional auto-makeup / sidechain HPF). */
|
|
1794
|
+
export function masteringDynamicsCompressor(
|
|
1795
|
+
samples: Float32Array,
|
|
1796
|
+
sampleRate: number,
|
|
1797
|
+
options: CompressorOptions = {},
|
|
1798
|
+
): DynamicsResult {
|
|
1799
|
+
assertSamples('masteringDynamicsCompressor', samples, options.validate !== false);
|
|
1800
|
+
const detector =
|
|
1801
|
+
typeof options.detector === 'string'
|
|
1802
|
+
? COMPRESSOR_DETECTOR_MAP[options.detector]
|
|
1803
|
+
: options.detector;
|
|
1804
|
+
const opts: Record<string, unknown> = { ...options };
|
|
1805
|
+
if (detector !== undefined) {
|
|
1806
|
+
opts.detector = detector;
|
|
1807
|
+
}
|
|
1808
|
+
return requireModule().masteringDynamicsCompressor(samples, sampleRate, opts);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/** Offline noise gate (hysteresis, hold, optional key HPF). */
|
|
1812
|
+
export function masteringDynamicsGate(
|
|
1813
|
+
samples: Float32Array,
|
|
1814
|
+
sampleRate: number,
|
|
1815
|
+
options: GateOptions = {},
|
|
1816
|
+
): DynamicsResult {
|
|
1817
|
+
assertSamples('masteringDynamicsGate', samples, options.validate !== false);
|
|
1818
|
+
return requireModule().masteringDynamicsGate(samples, sampleRate, options);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
/** Offline transient shaper (envelope-difference attack/sustain control). */
|
|
1822
|
+
export function masteringDynamicsTransientShaper(
|
|
1823
|
+
samples: Float32Array,
|
|
1824
|
+
sampleRate: number,
|
|
1825
|
+
options: TransientShaperOptions = {},
|
|
1826
|
+
): DynamicsResult {
|
|
1827
|
+
assertSamples('masteringDynamicsTransientShaper', samples, options.validate !== false);
|
|
1828
|
+
return requireModule().masteringDynamicsTransientShaper(samples, sampleRate, options);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* Apply a configurable mastering chain in WASM.
|
|
1833
|
+
*
|
|
1834
|
+
* @param samples - Audio samples (mono, float32)
|
|
1835
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1836
|
+
* @param config - Chain stage configuration
|
|
1837
|
+
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1838
|
+
*/
|
|
1839
|
+
export function masteringChain(
|
|
1840
|
+
samples: Float32Array,
|
|
1841
|
+
sampleRate = 22050,
|
|
1842
|
+
config: MasteringChainConfig,
|
|
1843
|
+
): MasteringChainResult {
|
|
1844
|
+
if (!module) {
|
|
1845
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1846
|
+
}
|
|
1847
|
+
return module.masteringChain(samples, sampleRate, config as Record<string, unknown>);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* Apply a configurable stereo mastering chain in WASM.
|
|
1852
|
+
*
|
|
1853
|
+
* @param left - Left channel samples
|
|
1854
|
+
* @param right - Right channel samples
|
|
1292
1855
|
* @param sampleRate - Sample rate in Hz
|
|
1293
1856
|
* @param config - Chain stage configuration
|
|
1857
|
+
* @returns Processed stereo audio, loudness metadata, and applied stage names
|
|
1858
|
+
*/
|
|
1859
|
+
export function masteringChainStereo(
|
|
1860
|
+
left: Float32Array,
|
|
1861
|
+
right: Float32Array,
|
|
1862
|
+
sampleRate = 22050,
|
|
1863
|
+
config: MasteringChainConfig,
|
|
1864
|
+
): MasteringStereoChainResult {
|
|
1865
|
+
if (!module) {
|
|
1866
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1867
|
+
}
|
|
1868
|
+
if (left.length !== right.length) {
|
|
1869
|
+
throw new Error('Stereo channel lengths must match.');
|
|
1870
|
+
}
|
|
1871
|
+
return module.masteringChainStereo(left, right, sampleRate, config as Record<string, unknown>);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* Apply a configurable mastering chain in WASM with progress reporting.
|
|
1876
|
+
*
|
|
1877
|
+
* @param samples - Audio samples (mono, float32)
|
|
1878
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1879
|
+
* @param config - Chain stage configuration
|
|
1294
1880
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
1295
1881
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1296
1882
|
*/
|
|
1297
1883
|
export function masteringChainWithProgress(
|
|
1298
1884
|
samples: Float32Array,
|
|
1299
|
-
sampleRate
|
|
1885
|
+
sampleRate = 22050,
|
|
1300
1886
|
config: MasteringChainConfig,
|
|
1301
1887
|
onProgress: ProgressCallback,
|
|
1302
1888
|
): MasteringChainResult {
|
|
@@ -1324,7 +1910,7 @@ export function masteringChainWithProgress(
|
|
|
1324
1910
|
export function masteringChainStereoWithProgress(
|
|
1325
1911
|
left: Float32Array,
|
|
1326
1912
|
right: Float32Array,
|
|
1327
|
-
sampleRate
|
|
1913
|
+
sampleRate = 22050,
|
|
1328
1914
|
config: MasteringChainConfig,
|
|
1329
1915
|
onProgress: ProgressCallback,
|
|
1330
1916
|
): MasteringStereoChainResult {
|
|
@@ -1359,14 +1945,14 @@ export function masteringPresetNames(): MasteringPreset[] {
|
|
|
1359
1945
|
* Apply a named mastering preset chain to mono audio.
|
|
1360
1946
|
*
|
|
1361
1947
|
* @param samples - Audio samples (mono, float32)
|
|
1362
|
-
* @param sampleRate - Sample rate in Hz
|
|
1948
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1363
1949
|
* @param presetName - Preset identifier from {@link masteringPresetNames}
|
|
1364
1950
|
* @param overrides - Optional flat overrides (dot-notation, e.g. `'loudness.targetLufs'`) applied on top of the preset. Pass `null` for preset defaults.
|
|
1365
1951
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1366
1952
|
*/
|
|
1367
1953
|
export function masterAudio(
|
|
1368
1954
|
samples: Float32Array,
|
|
1369
|
-
sampleRate
|
|
1955
|
+
sampleRate = 22050,
|
|
1370
1956
|
presetName: MasteringPreset,
|
|
1371
1957
|
overrides: Record<string, number | boolean> | null = null,
|
|
1372
1958
|
): MasteringChainResult {
|
|
@@ -1389,7 +1975,7 @@ export function masterAudio(
|
|
|
1389
1975
|
export function masterAudioStereo(
|
|
1390
1976
|
left: Float32Array,
|
|
1391
1977
|
right: Float32Array,
|
|
1392
|
-
sampleRate
|
|
1978
|
+
sampleRate = 22050,
|
|
1393
1979
|
presetName: MasteringPreset,
|
|
1394
1980
|
overrides: Record<string, number | boolean> | null = null,
|
|
1395
1981
|
): MasteringStereoChainResult {
|
|
@@ -1402,6 +1988,50 @@ export function masterAudioStereo(
|
|
|
1402
1988
|
return module.masterAudioStereo(presetName, left, right, sampleRate, overrides);
|
|
1403
1989
|
}
|
|
1404
1990
|
|
|
1991
|
+
/**
|
|
1992
|
+
* Mono `masterAudio` with per-stage progress reporting. `onProgress` is invoked
|
|
1993
|
+
* with `(progress, stage)` between each chain stage (progress is in [0,1]).
|
|
1994
|
+
*/
|
|
1995
|
+
export function masterAudioWithProgress(
|
|
1996
|
+
samples: Float32Array,
|
|
1997
|
+
sampleRate = 22050,
|
|
1998
|
+
presetName: MasteringPreset,
|
|
1999
|
+
onProgress: ProgressCallback,
|
|
2000
|
+
overrides: Record<string, number | boolean> | null = null,
|
|
2001
|
+
): MasteringChainResult {
|
|
2002
|
+
if (!module) {
|
|
2003
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2004
|
+
}
|
|
2005
|
+
return module.masterAudioWithProgress(presetName, samples, sampleRate, overrides, onProgress);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/**
|
|
2009
|
+
* Stereo `masterAudio` with per-stage progress reporting.
|
|
2010
|
+
*/
|
|
2011
|
+
export function masterAudioStereoWithProgress(
|
|
2012
|
+
left: Float32Array,
|
|
2013
|
+
right: Float32Array,
|
|
2014
|
+
sampleRate = 22050,
|
|
2015
|
+
presetName: MasteringPreset,
|
|
2016
|
+
onProgress: ProgressCallback,
|
|
2017
|
+
overrides: Record<string, number | boolean> | null = null,
|
|
2018
|
+
): MasteringStereoChainResult {
|
|
2019
|
+
if (!module) {
|
|
2020
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2021
|
+
}
|
|
2022
|
+
if (left.length !== right.length) {
|
|
2023
|
+
throw new Error('Stereo channel lengths must match.');
|
|
2024
|
+
}
|
|
2025
|
+
return module.masterAudioStereoWithProgress(
|
|
2026
|
+
presetName,
|
|
2027
|
+
left,
|
|
2028
|
+
right,
|
|
2029
|
+
sampleRate,
|
|
2030
|
+
overrides,
|
|
2031
|
+
onProgress,
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1405
2035
|
export function mixingScenePresetNames(): string[] {
|
|
1406
2036
|
if (!module) {
|
|
1407
2037
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1414,14 +2044,14 @@ export function mixingScenePresetNames(): string[] {
|
|
|
1414
2044
|
* name shared with the Node and Python bindings; the returned JSON loads
|
|
1415
2045
|
* directly into a {@link Mixer} via {@link Mixer.fromSceneJson}.
|
|
1416
2046
|
*
|
|
1417
|
-
* @param
|
|
2047
|
+
* @param presetName - Preset name (see {@link mixingScenePresetNames})
|
|
1418
2048
|
* @returns Scene JSON string
|
|
1419
2049
|
*/
|
|
1420
|
-
export function mixingScenePresetJson(
|
|
2050
|
+
export function mixingScenePresetJson(presetName: string): string {
|
|
1421
2051
|
if (!module) {
|
|
1422
2052
|
throw new Error('Module not initialized. Call init() first.');
|
|
1423
2053
|
}
|
|
1424
|
-
return module.mixingScenePresetJson(
|
|
2054
|
+
return module.mixingScenePresetJson(presetName);
|
|
1425
2055
|
}
|
|
1426
2056
|
|
|
1427
2057
|
export function mixStereo(
|
|
@@ -1470,7 +2100,7 @@ export function mixStereo(
|
|
|
1470
2100
|
* ```
|
|
1471
2101
|
*/
|
|
1472
2102
|
export class StreamingMasteringChain {
|
|
1473
|
-
private chain: import('./
|
|
2103
|
+
private chain: import('./sonare.js').WasmStreamingMasteringChain;
|
|
1474
2104
|
|
|
1475
2105
|
constructor(config: MasteringChainConfig) {
|
|
1476
2106
|
if (!module) {
|
|
@@ -1557,7 +2187,7 @@ export class StreamingMasteringChain {
|
|
|
1557
2187
|
* ```
|
|
1558
2188
|
*/
|
|
1559
2189
|
export class StreamingEqualizer {
|
|
1560
|
-
private eq: import('./
|
|
2190
|
+
private eq: import('./sonare.js').WasmStreamingEqualizer;
|
|
1561
2191
|
|
|
1562
2192
|
constructor(config: StreamingEqualizerConfig = {}) {
|
|
1563
2193
|
if (!module) {
|
|
@@ -1684,32 +2314,270 @@ export class StreamingEqualizer {
|
|
|
1684
2314
|
}
|
|
1685
2315
|
|
|
1686
2316
|
// ============================================================================
|
|
1687
|
-
//
|
|
2317
|
+
// StreamingRetune Class
|
|
1688
2318
|
// ============================================================================
|
|
1689
2319
|
|
|
1690
2320
|
/**
|
|
1691
|
-
*
|
|
1692
|
-
* C mixer API (the same path {@link Mixer.fromSceneJson} uses to load it).
|
|
1693
|
-
*
|
|
1694
|
-
* @deprecated Use {@link mixingScenePresetJson}, the canonical name shared with
|
|
1695
|
-
* the Node and Python bindings. This alias is retained for backwards
|
|
1696
|
-
* compatibility and may be removed in a future release. Both functions return a
|
|
1697
|
-
* scene JSON string that loads cleanly into a {@link Mixer}.
|
|
2321
|
+
* Block-by-block mono voice retune / pitch shifter.
|
|
1698
2322
|
*
|
|
1699
|
-
*
|
|
1700
|
-
* @
|
|
2323
|
+
* State is maintained across {@link processMono} calls. Call {@link prepare}
|
|
2324
|
+
* before processing, and call {@link delete} (or use `try/finally`) to release
|
|
2325
|
+
* the underlying WASM object.
|
|
1701
2326
|
*/
|
|
1702
|
-
export
|
|
2327
|
+
export class StreamingRetune {
|
|
2328
|
+
private retune: import('./sonare.js').WasmStreamingRetune;
|
|
2329
|
+
|
|
2330
|
+
constructor(config: StreamingRetuneConfig = {}) {
|
|
2331
|
+
if (!module) {
|
|
2332
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2333
|
+
}
|
|
2334
|
+
this.retune = module.createStreamingRetune(config as Record<string, unknown>);
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
/**
|
|
2338
|
+
* Allocate and initialize native state for the given sample rate and maximum
|
|
2339
|
+
* process block size.
|
|
2340
|
+
*/
|
|
2341
|
+
prepare(sampleRate: number, maxBlockSize: number): void {
|
|
2342
|
+
this.retune.prepare(sampleRate, maxBlockSize);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/** Reset delay, grain, and overlap-add state without changing config. */
|
|
2346
|
+
reset(): void {
|
|
2347
|
+
this.retune.reset();
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/**
|
|
2351
|
+
* Update retune settings. Changing `grainSize` takes effect after the next
|
|
2352
|
+
* {@link prepare} call.
|
|
2353
|
+
*/
|
|
2354
|
+
setConfig(config: StreamingRetuneConfig): void {
|
|
2355
|
+
this.retune.setConfig(config as Record<string, unknown>);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
/** Current native config. */
|
|
2359
|
+
config(): Required<StreamingRetuneConfig> {
|
|
2360
|
+
return this.retune.config();
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/** Resolved grain size in samples after {@link prepare}. */
|
|
2364
|
+
grainSize(): number {
|
|
2365
|
+
return this.retune.grainSize();
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
/** Process one mono block, returning the shifted samples (same length). */
|
|
2369
|
+
processMono(samples: Float32Array): Float32Array {
|
|
2370
|
+
return this.retune.processMono(samples);
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
/** Release the underlying WASM object. Safe to call only once. */
|
|
2374
|
+
delete(): void {
|
|
2375
|
+
this.retune.delete();
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// ============================================================================
|
|
2380
|
+
// RealtimeVoiceChanger Class
|
|
2381
|
+
// ============================================================================
|
|
2382
|
+
|
|
2383
|
+
export class RealtimeVoiceChanger {
|
|
2384
|
+
private changer: import('./sonare.js').WasmRealtimeVoiceChanger;
|
|
2385
|
+
|
|
2386
|
+
constructor(config: RealtimeVoiceChangerConfigInput = 'neutral-monitor') {
|
|
2387
|
+
if (!module) {
|
|
2388
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2389
|
+
}
|
|
2390
|
+
this.changer = module.createRealtimeVoiceChanger(config as Record<string, unknown> | string);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
prepare(sampleRate: number, maxBlockSize = 128, channels = 1): void {
|
|
2394
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
reset(): void {
|
|
2398
|
+
this.changer.reset();
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
setConfig(config: RealtimeVoiceChangerConfigInput): void {
|
|
2402
|
+
this.changer.setConfig(config as Record<string, unknown> | string);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
configJson(): string {
|
|
2406
|
+
return this.changer.configJson();
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
latencySamples(): number {
|
|
2410
|
+
return this.changer.latencySamples();
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
processMono(samples: Float32Array): Float32Array {
|
|
2414
|
+
return this.changer.processMono(samples);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
processMonoInto(samples: Float32Array, output: Float32Array): void {
|
|
2418
|
+
this.changer.processMonoInto(samples, output);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
processInterleaved(samples: Float32Array, channels: number): Float32Array {
|
|
2422
|
+
return this.changer.processInterleaved(samples, channels);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
processInterleavedInto(samples: Float32Array, channels: number, output: Float32Array): void {
|
|
2426
|
+
this.changer.processInterleavedInto(samples, channels, output);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* Acquire a typed-memory view onto the WASM heap for mono input.
|
|
2431
|
+
*
|
|
2432
|
+
* Write your input samples into the returned `Float32Array` directly (e.g.
|
|
2433
|
+
* via `input.set(source)`); no copy crosses the JS↔C++ bridge until
|
|
2434
|
+
* {@link processPreparedMono} is called. The view is owned by this
|
|
2435
|
+
* RealtimeVoiceChanger and becomes invalid after {@link delete}; it may
|
|
2436
|
+
* also be invalidated if you later call this method with a larger
|
|
2437
|
+
* `numSamples` value (the underlying buffer may be reallocated).
|
|
2438
|
+
*/
|
|
2439
|
+
getMonoInputBuffer(numSamples: number): Float32Array {
|
|
2440
|
+
return this.changer.getMonoInputBuffer(numSamples);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
/** Mono output view counterpart to {@link getMonoInputBuffer}. */
|
|
2444
|
+
getMonoOutputBuffer(numSamples: number): Float32Array {
|
|
2445
|
+
return this.changer.getMonoOutputBuffer(numSamples);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/**
|
|
2449
|
+
* Process the previously-acquired mono input buffer in place. The output
|
|
2450
|
+
* appears in the buffer returned by {@link getMonoOutputBuffer}. No JS↔C++
|
|
2451
|
+
* sample-level crossings happen on this call — it just hands control to
|
|
2452
|
+
* the underlying DSP on already-on-heap data.
|
|
2453
|
+
*/
|
|
2454
|
+
processPreparedMono(numSamples: number): void {
|
|
2455
|
+
this.changer.processPreparedMono(numSamples);
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
/** Interleaved input view (layout L0,R0,L1,R1,...). */
|
|
2459
|
+
getInterleavedInputBuffer(numFrames: number, numChannels: number): Float32Array {
|
|
2460
|
+
return this.changer.getInterleavedInputBuffer(numFrames, numChannels);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
/** Interleaved output view counterpart. */
|
|
2464
|
+
getInterleavedOutputBuffer(numFrames: number, numChannels: number): Float32Array {
|
|
2465
|
+
return this.changer.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
/**
|
|
2469
|
+
* Process the previously-acquired interleaved buffer in place. Output
|
|
2470
|
+
* appears in the buffer returned by {@link getInterleavedOutputBuffer}.
|
|
2471
|
+
*/
|
|
2472
|
+
processPreparedInterleaved(numFrames: number, numChannels: number): void {
|
|
2473
|
+
this.changer.processPreparedInterleaved(numFrames, numChannels);
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
/**
|
|
2477
|
+
* Planar-channel input/output view (one Float32Array per channel). Matches
|
|
2478
|
+
* AudioWorklet's native layout; processing happens in place.
|
|
2479
|
+
*/
|
|
2480
|
+
getPlanarChannelBuffer(channel: number, numFrames: number): Float32Array {
|
|
2481
|
+
return this.changer.getPlanarChannelBuffer(channel, numFrames);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
/**
|
|
2485
|
+
* Process the previously-acquired planar channel buffers in place. Each
|
|
2486
|
+
* channel must have been obtained from {@link getPlanarChannelBuffer}
|
|
2487
|
+
* with the same `numFrames`. Output replaces input in the same buffers.
|
|
2488
|
+
*/
|
|
2489
|
+
processPreparedPlanar(numFrames: number): void {
|
|
2490
|
+
this.changer.processPreparedPlanar(numFrames);
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
/**
|
|
2494
|
+
* Convenience factory for the mono zero-copy path: returns the input/output
|
|
2495
|
+
* heap views plus a `process()` thunk wired to the same `numSamples`. The
|
|
2496
|
+
* views are reused across calls and become invalid after {@link delete}.
|
|
2497
|
+
*/
|
|
2498
|
+
createRealtimeMonoBuffer(numSamples: number): RealtimeVoiceChangerMonoBuffer {
|
|
2499
|
+
const input = this.getMonoInputBuffer(numSamples);
|
|
2500
|
+
const output = this.getMonoOutputBuffer(numSamples);
|
|
2501
|
+
return {
|
|
2502
|
+
input,
|
|
2503
|
+
output,
|
|
2504
|
+
process: () => this.processPreparedMono(numSamples),
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
/** Same as {@link createRealtimeMonoBuffer} but for interleaved I/O. */
|
|
2509
|
+
createRealtimeInterleavedBuffer(
|
|
2510
|
+
numFrames: number,
|
|
2511
|
+
numChannels: number,
|
|
2512
|
+
): RealtimeVoiceChangerInterleavedBuffer {
|
|
2513
|
+
const input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
2514
|
+
const output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
2515
|
+
return {
|
|
2516
|
+
input,
|
|
2517
|
+
output,
|
|
2518
|
+
channels: numChannels,
|
|
2519
|
+
process: () => this.processPreparedInterleaved(numFrames, numChannels),
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/**
|
|
2524
|
+
* Convenience factory for the planar zero-copy path. Acquires one
|
|
2525
|
+
* heap-backed Float32Array per channel and returns a `process()` thunk
|
|
2526
|
+
* wired to the same `numFrames`. Buffers are reused across calls and
|
|
2527
|
+
* become invalid after {@link delete}.
|
|
2528
|
+
*/
|
|
2529
|
+
createRealtimePlanarBuffer(
|
|
2530
|
+
numFrames: number,
|
|
2531
|
+
numChannels: number,
|
|
2532
|
+
): RealtimeVoiceChangerPlanarBuffer {
|
|
2533
|
+
const channels: Float32Array[] = [];
|
|
2534
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
2535
|
+
channels.push(this.getPlanarChannelBuffer(ch, numFrames));
|
|
2536
|
+
}
|
|
2537
|
+
return {
|
|
2538
|
+
channels,
|
|
2539
|
+
process: () => this.processPreparedPlanar(numFrames),
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
delete(): void {
|
|
2544
|
+
this.changer.delete();
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
export function realtimeVoiceChangerPresetNames(): VoicePresetId[] {
|
|
1703
2549
|
if (!module) {
|
|
1704
2550
|
throw new Error('Module not initialized. Call init() first.');
|
|
1705
2551
|
}
|
|
1706
|
-
return module.
|
|
2552
|
+
return module.realtimeVoiceChangerPresetNames() as VoicePresetId[];
|
|
1707
2553
|
}
|
|
1708
2554
|
|
|
2555
|
+
export function realtimeVoiceChangerPresetJson(name: VoicePresetId): string {
|
|
2556
|
+
if (!module) {
|
|
2557
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2558
|
+
}
|
|
2559
|
+
return module.realtimeVoiceChangerPresetJson(name);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
export function validateRealtimeVoiceChangerPresetJson(json: string): {
|
|
2563
|
+
ok: boolean;
|
|
2564
|
+
normalizedJson?: string;
|
|
2565
|
+
error?: string;
|
|
2566
|
+
} {
|
|
2567
|
+
if (!module) {
|
|
2568
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2569
|
+
}
|
|
2570
|
+
return module.validateRealtimeVoiceChangerPresetJson(json);
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// ============================================================================
|
|
2574
|
+
// Mixer Class (scene-based persistent mixer)
|
|
2575
|
+
// ============================================================================
|
|
2576
|
+
|
|
1709
2577
|
/**
|
|
1710
2578
|
* Persistent, scene-based stereo mixer.
|
|
1711
2579
|
*
|
|
1712
|
-
* Build one from a scene JSON string (e.g. {@link
|
|
2580
|
+
* Build one from a scene JSON string (e.g. {@link mixingScenePresetJson} or a
|
|
1713
2581
|
* hand-authored scene), then feed per-strip stereo blocks through
|
|
1714
2582
|
* {@link processStereo} to get the routed stereo master. Strips, sends, buses,
|
|
1715
2583
|
* and inserts are described entirely by the scene; the routing graph is
|
|
@@ -1721,7 +2589,7 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1721
2589
|
*
|
|
1722
2590
|
* @example
|
|
1723
2591
|
* ```typescript
|
|
1724
|
-
* const mixer = Mixer.fromSceneJson(
|
|
2592
|
+
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('basicStereo'), 48000, 512);
|
|
1725
2593
|
* try {
|
|
1726
2594
|
* const out = mixer.processStereo([stripL], [stripR]);
|
|
1727
2595
|
* } finally {
|
|
@@ -1730,9 +2598,9 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1730
2598
|
* ```
|
|
1731
2599
|
*/
|
|
1732
2600
|
export class Mixer {
|
|
1733
|
-
private mixer: import('./
|
|
2601
|
+
private mixer: import('./sonare.js').WasmMixer;
|
|
1734
2602
|
|
|
1735
|
-
private constructor(mixer: import('./
|
|
2603
|
+
private constructor(mixer: import('./sonare.js').WasmMixer) {
|
|
1736
2604
|
this.mixer = mixer;
|
|
1737
2605
|
}
|
|
1738
2606
|
|
|
@@ -1901,6 +2769,31 @@ export class Mixer {
|
|
|
1901
2769
|
return this.mixer.vcaGroupCount();
|
|
1902
2770
|
}
|
|
1903
2771
|
|
|
2772
|
+
/** Set the strip's input trim in dB. */
|
|
2773
|
+
setInputTrimDb(stripIndex: number, db: number): void {
|
|
2774
|
+
this.mixer.setInputTrimDb(stripIndex, db);
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
/** Set the strip's fader level in dB. */
|
|
2778
|
+
setFaderDb(stripIndex: number, db: number): void {
|
|
2779
|
+
this.mixer.setFaderDb(stripIndex, db);
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
/** Set the strip's pan position. */
|
|
2783
|
+
setPan(stripIndex: number, pan: number, panMode: PanMode | number = 0): void {
|
|
2784
|
+
this.mixer.setPan(stripIndex, pan, panModeCode(panMode));
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
/** Set the strip's stereo width. */
|
|
2788
|
+
setWidth(stripIndex: number, width: number): void {
|
|
2789
|
+
this.mixer.setWidth(stripIndex, width);
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
/** Set the strip's mute state. */
|
|
2793
|
+
setMuted(stripIndex: number, muted: boolean): void {
|
|
2794
|
+
this.mixer.setMuted(stripIndex, muted);
|
|
2795
|
+
}
|
|
2796
|
+
|
|
1904
2797
|
/**
|
|
1905
2798
|
* Set a strip's solo state. Takes effect on the next process without a
|
|
1906
2799
|
* graph recompile.
|
|
@@ -2114,14 +3007,14 @@ export function trim(samples: Float32Array, sampleRate: number, thresholdDb = -6
|
|
|
2114
3007
|
* Compute Short-Time Fourier Transform (STFT).
|
|
2115
3008
|
*
|
|
2116
3009
|
* @param samples - Audio samples (mono, float32)
|
|
2117
|
-
* @param sampleRate - Sample rate in Hz
|
|
3010
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2118
3011
|
* @param nFft - FFT size (default: 2048)
|
|
2119
3012
|
* @param hopLength - Hop length (default: 512)
|
|
2120
3013
|
* @returns STFT result with magnitude and power spectrograms
|
|
2121
3014
|
*/
|
|
2122
3015
|
export function stft(
|
|
2123
3016
|
samples: Float32Array,
|
|
2124
|
-
sampleRate
|
|
3017
|
+
sampleRate = 22050,
|
|
2125
3018
|
nFft = 2048,
|
|
2126
3019
|
hopLength = 512,
|
|
2127
3020
|
): StftResult {
|
|
@@ -2135,14 +3028,14 @@ export function stft(
|
|
|
2135
3028
|
* Compute STFT and return magnitude in decibels.
|
|
2136
3029
|
*
|
|
2137
3030
|
* @param samples - Audio samples (mono, float32)
|
|
2138
|
-
* @param sampleRate - Sample rate in Hz
|
|
3031
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2139
3032
|
* @param nFft - FFT size (default: 2048)
|
|
2140
3033
|
* @param hopLength - Hop length (default: 512)
|
|
2141
3034
|
* @returns STFT result with dB values
|
|
2142
3035
|
*/
|
|
2143
3036
|
export function stftDb(
|
|
2144
3037
|
samples: Float32Array,
|
|
2145
|
-
sampleRate
|
|
3038
|
+
sampleRate = 22050,
|
|
2146
3039
|
nFft = 2048,
|
|
2147
3040
|
hopLength = 512,
|
|
2148
3041
|
): { nBins: number; nFrames: number; db: Float32Array } {
|
|
@@ -2160,7 +3053,7 @@ export function stftDb(
|
|
|
2160
3053
|
* Compute Mel spectrogram.
|
|
2161
3054
|
*
|
|
2162
3055
|
* @param samples - Audio samples (mono, float32)
|
|
2163
|
-
* @param sampleRate - Sample rate in Hz
|
|
3056
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2164
3057
|
* @param nFft - FFT size (default: 2048)
|
|
2165
3058
|
* @param hopLength - Hop length (default: 512)
|
|
2166
3059
|
* @param nMels - Number of Mel bands (default: 128)
|
|
@@ -2168,7 +3061,7 @@ export function stftDb(
|
|
|
2168
3061
|
*/
|
|
2169
3062
|
export function melSpectrogram(
|
|
2170
3063
|
samples: Float32Array,
|
|
2171
|
-
sampleRate
|
|
3064
|
+
sampleRate = 22050,
|
|
2172
3065
|
nFft = 2048,
|
|
2173
3066
|
hopLength = 512,
|
|
2174
3067
|
nMels = 128,
|
|
@@ -2183,20 +3076,20 @@ export function melSpectrogram(
|
|
|
2183
3076
|
* Compute MFCC (Mel-Frequency Cepstral Coefficients).
|
|
2184
3077
|
*
|
|
2185
3078
|
* @param samples - Audio samples (mono, float32)
|
|
2186
|
-
* @param sampleRate - Sample rate in Hz
|
|
3079
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2187
3080
|
* @param nFft - FFT size (default: 2048)
|
|
2188
3081
|
* @param hopLength - Hop length (default: 512)
|
|
2189
3082
|
* @param nMels - Number of Mel bands (default: 128)
|
|
2190
|
-
* @param nMfcc - Number of MFCC coefficients (default:
|
|
3083
|
+
* @param nMfcc - Number of MFCC coefficients (default: 20)
|
|
2191
3084
|
* @returns MFCC result
|
|
2192
3085
|
*/
|
|
2193
3086
|
export function mfcc(
|
|
2194
3087
|
samples: Float32Array,
|
|
2195
|
-
sampleRate
|
|
3088
|
+
sampleRate = 22050,
|
|
2196
3089
|
nFft = 2048,
|
|
2197
3090
|
hopLength = 512,
|
|
2198
3091
|
nMels = 128,
|
|
2199
|
-
nMfcc =
|
|
3092
|
+
nMfcc = 20,
|
|
2200
3093
|
): MfccResult {
|
|
2201
3094
|
if (!module) {
|
|
2202
3095
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2217,23 +3110,23 @@ export function mfcc(
|
|
|
2217
3110
|
* @param nFrames - Number of time frames
|
|
2218
3111
|
* @param sampleRate - Sample rate in Hz
|
|
2219
3112
|
* @param nFft - FFT size (default: 2048)
|
|
2220
|
-
* @param
|
|
3113
|
+
* @param fmin - Lower Mel band edge in Hz (default: 0)
|
|
3114
|
+
* @param fmax - Upper Mel band edge in Hz (default: sr/2 when 0)
|
|
2221
3115
|
* @returns STFT power spectrogram result
|
|
2222
3116
|
*/
|
|
2223
3117
|
export function melToStft(
|
|
2224
3118
|
melPower: Float32Array,
|
|
2225
3119
|
nMels: number,
|
|
2226
3120
|
nFrames: number,
|
|
2227
|
-
sampleRate
|
|
3121
|
+
sampleRate = 22050,
|
|
2228
3122
|
nFft = 2048,
|
|
2229
|
-
hopLength = 512,
|
|
2230
3123
|
fmin = 0,
|
|
2231
3124
|
fmax = 0,
|
|
2232
3125
|
): StftPowerResult {
|
|
2233
3126
|
if (!module) {
|
|
2234
3127
|
throw new Error('Module not initialized. Call init() first.');
|
|
2235
3128
|
}
|
|
2236
|
-
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft,
|
|
3129
|
+
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft, fmin, fmax);
|
|
2237
3130
|
}
|
|
2238
3131
|
|
|
2239
3132
|
/**
|
|
@@ -2246,6 +3139,8 @@ export function melToStft(
|
|
|
2246
3139
|
* @param sampleRate - Sample rate in Hz
|
|
2247
3140
|
* @param nFft - FFT size (default: 2048)
|
|
2248
3141
|
* @param hopLength - Hop length (default: 512)
|
|
3142
|
+
* @param fmin - Minimum Mel frequency in Hz (default: 0)
|
|
3143
|
+
* @param fmax - Maximum Mel frequency in Hz (default: 0 = sr/2)
|
|
2249
3144
|
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2250
3145
|
* @returns Reconstructed audio samples (mono, float32)
|
|
2251
3146
|
*/
|
|
@@ -2253,12 +3148,12 @@ export function melToAudio(
|
|
|
2253
3148
|
melPower: Float32Array,
|
|
2254
3149
|
nMels: number,
|
|
2255
3150
|
nFrames: number,
|
|
2256
|
-
sampleRate
|
|
3151
|
+
sampleRate = 22050,
|
|
2257
3152
|
nFft = 2048,
|
|
2258
3153
|
hopLength = 512,
|
|
2259
|
-
nIter = 32,
|
|
2260
3154
|
fmin = 0,
|
|
2261
3155
|
fmax = 0,
|
|
3156
|
+
nIter = 32,
|
|
2262
3157
|
): Float32Array {
|
|
2263
3158
|
if (!module) {
|
|
2264
3159
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2270,9 +3165,9 @@ export function melToAudio(
|
|
|
2270
3165
|
sampleRate,
|
|
2271
3166
|
nFft,
|
|
2272
3167
|
hopLength,
|
|
2273
|
-
nIter,
|
|
2274
3168
|
fmin,
|
|
2275
3169
|
fmax,
|
|
3170
|
+
nIter,
|
|
2276
3171
|
);
|
|
2277
3172
|
}
|
|
2278
3173
|
|
|
@@ -2295,114 +3190,318 @@ export function mfccToMel(
|
|
|
2295
3190
|
if (!module) {
|
|
2296
3191
|
throw new Error('Module not initialized. Call init() first.');
|
|
2297
3192
|
}
|
|
2298
|
-
return module.mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
3193
|
+
return module.mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
/**
|
|
3197
|
+
* Reconstruct audio directly from MFCC coefficients via Griffin-Lim. Mirrors
|
|
3198
|
+
* `feature::mfcc_to_audio`.
|
|
3199
|
+
*
|
|
3200
|
+
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
3201
|
+
* @param nMfcc - Number of MFCC coefficients
|
|
3202
|
+
* @param nFrames - Number of time frames
|
|
3203
|
+
* @param nMels - Number of Mel bins (default: 128)
|
|
3204
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3205
|
+
* @param nFft - FFT size (default: 2048)
|
|
3206
|
+
* @param hopLength - Hop length (default: 512)
|
|
3207
|
+
* @param fmin - Minimum Mel frequency in Hz (default: 0)
|
|
3208
|
+
* @param fmax - Maximum Mel frequency in Hz (default: 0 = sr/2)
|
|
3209
|
+
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
3210
|
+
* @returns Reconstructed audio samples (mono, float32)
|
|
3211
|
+
*/
|
|
3212
|
+
export function mfccToAudio(
|
|
3213
|
+
mfccCoefficients: Float32Array,
|
|
3214
|
+
nMfcc: number,
|
|
3215
|
+
nFrames: number,
|
|
3216
|
+
nMels = 128,
|
|
3217
|
+
sampleRate = 22050,
|
|
3218
|
+
nFft = 2048,
|
|
3219
|
+
hopLength = 512,
|
|
3220
|
+
fmin = 0,
|
|
3221
|
+
fmax = 0,
|
|
3222
|
+
nIter = 32,
|
|
3223
|
+
): Float32Array {
|
|
3224
|
+
if (!module) {
|
|
3225
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3226
|
+
}
|
|
3227
|
+
return module.mfccToAudio(
|
|
3228
|
+
mfccCoefficients,
|
|
3229
|
+
nMfcc,
|
|
3230
|
+
nFrames,
|
|
3231
|
+
nMels,
|
|
3232
|
+
sampleRate,
|
|
3233
|
+
nFft,
|
|
3234
|
+
hopLength,
|
|
3235
|
+
fmin,
|
|
3236
|
+
fmax,
|
|
3237
|
+
nIter,
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
// ============================================================================
|
|
3242
|
+
// Features - Chroma
|
|
3243
|
+
// ============================================================================
|
|
3244
|
+
|
|
3245
|
+
/**
|
|
3246
|
+
* Compute chromagram (pitch class distribution).
|
|
3247
|
+
*
|
|
3248
|
+
* @param samples - Audio samples (mono, float32)
|
|
3249
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3250
|
+
* @param nFft - FFT size (default: 2048)
|
|
3251
|
+
* @param hopLength - Hop length (default: 512)
|
|
3252
|
+
* @returns Chroma features result
|
|
3253
|
+
*/
|
|
3254
|
+
export function chroma(
|
|
3255
|
+
samples: Float32Array,
|
|
3256
|
+
sampleRate = 22050,
|
|
3257
|
+
nFft = 2048,
|
|
3258
|
+
hopLength = 512,
|
|
3259
|
+
): ChromaResult {
|
|
3260
|
+
if (!module) {
|
|
3261
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3262
|
+
}
|
|
3263
|
+
return module.chroma(samples, sampleRate, nFft, hopLength);
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
// ============================================================================
|
|
3267
|
+
// Features - Spectral
|
|
3268
|
+
// ============================================================================
|
|
3269
|
+
|
|
3270
|
+
/**
|
|
3271
|
+
* Compute spectral centroid (center of mass of spectrum).
|
|
3272
|
+
*
|
|
3273
|
+
* @param samples - Audio samples (mono, float32)
|
|
3274
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3275
|
+
* @param nFft - FFT size (default: 2048)
|
|
3276
|
+
* @param hopLength - Hop length (default: 512)
|
|
3277
|
+
* @returns Spectral centroid in Hz for each frame
|
|
3278
|
+
*/
|
|
3279
|
+
export function spectralCentroid(
|
|
3280
|
+
samples: Float32Array,
|
|
3281
|
+
sampleRate = 22050,
|
|
3282
|
+
nFft = 2048,
|
|
3283
|
+
hopLength = 512,
|
|
3284
|
+
): Float32Array {
|
|
3285
|
+
if (!module) {
|
|
3286
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3287
|
+
}
|
|
3288
|
+
return module.spectralCentroid(samples, sampleRate, nFft, hopLength);
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* Compute spectral contrast (librosa.feature.spectral_contrast).
|
|
3293
|
+
*
|
|
3294
|
+
* @returns Matrix2d of shape (nBands + 1) x nFrames.
|
|
3295
|
+
*/
|
|
3296
|
+
export function spectralContrast(
|
|
3297
|
+
samples: Float32Array,
|
|
3298
|
+
sampleRate = 22050,
|
|
3299
|
+
nFft = 2048,
|
|
3300
|
+
hopLength = 512,
|
|
3301
|
+
nBands = 6,
|
|
3302
|
+
fmin = 200.0,
|
|
3303
|
+
quantile = 0.02,
|
|
3304
|
+
): WasmMatrix2dResult {
|
|
3305
|
+
if (!module) {
|
|
3306
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3307
|
+
}
|
|
3308
|
+
return module.spectralContrast(samples, sampleRate, nFft, hopLength, nBands, fmin, quantile);
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
/**
|
|
3312
|
+
* Fit per-frame polynomial coefficients (librosa.feature.poly_features).
|
|
3313
|
+
*
|
|
3314
|
+
* @returns Matrix2d of shape (order + 1) x nFrames.
|
|
3315
|
+
*/
|
|
3316
|
+
export function polyFeatures(
|
|
3317
|
+
samples: Float32Array,
|
|
3318
|
+
sampleRate = 22050,
|
|
3319
|
+
nFft = 2048,
|
|
3320
|
+
hopLength = 512,
|
|
3321
|
+
order = 1,
|
|
3322
|
+
): WasmMatrix2dResult {
|
|
3323
|
+
if (!module) {
|
|
3324
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3325
|
+
}
|
|
3326
|
+
return module.polyFeatures(samples, sampleRate, nFft, hopLength, order);
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
/**
|
|
3330
|
+
* Locate zero-crossing indices of a signal (librosa.zero_crossings).
|
|
3331
|
+
*/
|
|
3332
|
+
export function zeroCrossings(
|
|
3333
|
+
samples: Float32Array,
|
|
3334
|
+
threshold = 1e-10,
|
|
3335
|
+
refMagnitude = false,
|
|
3336
|
+
pad = true,
|
|
3337
|
+
zeroPos = true,
|
|
3338
|
+
): Int32Array {
|
|
3339
|
+
if (!module) {
|
|
3340
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3341
|
+
}
|
|
3342
|
+
return module.zeroCrossings(samples, threshold, refMagnitude, pad, zeroPos);
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/**
|
|
3346
|
+
* Estimate the global tuning offset from a set of frequencies
|
|
3347
|
+
* (librosa.pitch_tuning). Returns a deviation in fractions of a bin.
|
|
3348
|
+
*/
|
|
3349
|
+
export function pitchTuning(
|
|
3350
|
+
frequencies: Float32Array,
|
|
3351
|
+
resolution = 0.01,
|
|
3352
|
+
binsPerOctave = 12,
|
|
3353
|
+
): number {
|
|
3354
|
+
if (!module) {
|
|
3355
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3356
|
+
}
|
|
3357
|
+
return module.pitchTuning(frequencies, resolution, binsPerOctave);
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/**
|
|
3361
|
+
* Estimate the tuning offset of an audio signal (librosa.estimate_tuning).
|
|
3362
|
+
*/
|
|
3363
|
+
export function estimateTuning(
|
|
3364
|
+
samples: Float32Array,
|
|
3365
|
+
sampleRate = 22050,
|
|
3366
|
+
nFft = 2048,
|
|
3367
|
+
hopLength = 512,
|
|
3368
|
+
resolution = 0.01,
|
|
3369
|
+
binsPerOctave = 12,
|
|
3370
|
+
): number {
|
|
3371
|
+
if (!module) {
|
|
3372
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3373
|
+
}
|
|
3374
|
+
return module.estimateTuning(samples, sampleRate, nFft, hopLength, resolution, binsPerOctave);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
/**
|
|
3378
|
+
* Non-negative matrix factorisation of a flattened [nFeatures x nFrames]
|
|
3379
|
+
* spectrogram (librosa.decompose.decompose). Returns the W and H factors.
|
|
3380
|
+
*/
|
|
3381
|
+
export function decompose(
|
|
3382
|
+
s: Float32Array,
|
|
3383
|
+
nFeatures: number,
|
|
3384
|
+
nFrames: number,
|
|
3385
|
+
nComponents: number,
|
|
3386
|
+
nIter = 50,
|
|
3387
|
+
beta = 2.0,
|
|
3388
|
+
): WasmDecomposeResult {
|
|
3389
|
+
if (!module) {
|
|
3390
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3391
|
+
}
|
|
3392
|
+
return module.decompose(s, nFeatures, nFrames, nComponents, nIter, beta);
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
/**
|
|
3396
|
+
* Nearest-neighbour filtering of a flattened [nFeatures x nFrames] spectrogram
|
|
3397
|
+
* (librosa.decompose.nn_filter).
|
|
3398
|
+
*/
|
|
3399
|
+
export function nnFilter(
|
|
3400
|
+
s: Float32Array,
|
|
3401
|
+
nFeatures: number,
|
|
3402
|
+
nFrames: number,
|
|
3403
|
+
aggregate = 'mean',
|
|
3404
|
+
k = 7,
|
|
3405
|
+
width = 1,
|
|
3406
|
+
): WasmMatrix2dResult {
|
|
3407
|
+
if (!module) {
|
|
3408
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3409
|
+
}
|
|
3410
|
+
return module.nnFilter(s, nFeatures, nFrames, aggregate, k, width);
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
/**
|
|
3414
|
+
* Reorder/concatenate a signal by interval slices (librosa.effects.remix).
|
|
3415
|
+
*
|
|
3416
|
+
* @param intervals - Flat (start, end) sample pairs (even length).
|
|
3417
|
+
*/
|
|
3418
|
+
export function remix(
|
|
3419
|
+
samples: Float32Array,
|
|
3420
|
+
intervals: Int32Array | ArrayLike<number>,
|
|
3421
|
+
sampleRate = 22050,
|
|
3422
|
+
alignZeros = false,
|
|
3423
|
+
): Float32Array {
|
|
3424
|
+
if (!module) {
|
|
3425
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3426
|
+
}
|
|
3427
|
+
// Sample indices must reach the native side as exact 32-bit integers. Passing
|
|
3428
|
+
// a Float32Array (or a number[] holding fractional/large values) would round
|
|
3429
|
+
// boundaries above 2^24 and misalign the slice. Coerce to an Int32Array,
|
|
3430
|
+
// truncating toward zero, so callers can hand us any numeric array safely.
|
|
3431
|
+
const intervalsI32 =
|
|
3432
|
+
intervals instanceof Int32Array ? intervals : Int32Array.from(intervals, (v) => Math.trunc(v));
|
|
3433
|
+
return module.remix(samples, intervalsI32, sampleRate, alignZeros);
|
|
2299
3434
|
}
|
|
2300
3435
|
|
|
2301
3436
|
/**
|
|
2302
|
-
*
|
|
2303
|
-
* `feature::mfcc_to_audio`.
|
|
2304
|
-
*
|
|
2305
|
-
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
2306
|
-
* @param nMfcc - Number of MFCC coefficients
|
|
2307
|
-
* @param nFrames - Number of time frames
|
|
2308
|
-
* @param nMels - Number of Mel bins (default: 128)
|
|
2309
|
-
* @param sampleRate - Sample rate in Hz
|
|
2310
|
-
* @param nFft - FFT size (default: 2048)
|
|
2311
|
-
* @param hopLength - Hop length (default: 512)
|
|
2312
|
-
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2313
|
-
* @returns Reconstructed audio samples (mono, float32)
|
|
3437
|
+
* Phase-vocoder time-scale modification (rate > 1 faster, < 1 slower).
|
|
2314
3438
|
*/
|
|
2315
|
-
export function
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
nMels: number,
|
|
2320
|
-
sampleRate: number,
|
|
3439
|
+
export function phaseVocoder(
|
|
3440
|
+
samples: Float32Array,
|
|
3441
|
+
rate: number,
|
|
3442
|
+
sampleRate = 22050,
|
|
2321
3443
|
nFft = 2048,
|
|
2322
3444
|
hopLength = 512,
|
|
2323
|
-
nIter = 32,
|
|
2324
|
-
fmin = 0,
|
|
2325
|
-
fmax = 0,
|
|
2326
3445
|
): Float32Array {
|
|
2327
3446
|
if (!module) {
|
|
2328
3447
|
throw new Error('Module not initialized. Call init() first.');
|
|
2329
3448
|
}
|
|
2330
|
-
return module.
|
|
2331
|
-
mfccCoefficients,
|
|
2332
|
-
nMfcc,
|
|
2333
|
-
nFrames,
|
|
2334
|
-
nMels,
|
|
2335
|
-
sampleRate,
|
|
2336
|
-
nFft,
|
|
2337
|
-
hopLength,
|
|
2338
|
-
nIter,
|
|
2339
|
-
fmin,
|
|
2340
|
-
fmax,
|
|
2341
|
-
);
|
|
3449
|
+
return module.phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
|
|
2342
3450
|
}
|
|
2343
3451
|
|
|
2344
|
-
// ============================================================================
|
|
2345
|
-
// Features - Chroma
|
|
2346
|
-
// ============================================================================
|
|
2347
|
-
|
|
2348
3452
|
/**
|
|
2349
|
-
*
|
|
2350
|
-
*
|
|
2351
|
-
* @param samples - Audio samples (mono, float32)
|
|
2352
|
-
* @param sampleRate - Sample rate in Hz
|
|
2353
|
-
* @param nFft - FFT size (default: 2048)
|
|
2354
|
-
* @param hopLength - Hop length (default: 512)
|
|
2355
|
-
* @returns Chroma features result
|
|
3453
|
+
* HPSS into harmonic / percussive / residual signals.
|
|
2356
3454
|
*/
|
|
2357
|
-
export function
|
|
3455
|
+
export function hpssWithResidual(
|
|
2358
3456
|
samples: Float32Array,
|
|
2359
|
-
sampleRate
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
):
|
|
3457
|
+
sampleRate = 22050,
|
|
3458
|
+
kernelHarmonic = 31,
|
|
3459
|
+
kernelPercussive = 31,
|
|
3460
|
+
): WasmHpssWithResidualResult {
|
|
2363
3461
|
if (!module) {
|
|
2364
3462
|
throw new Error('Module not initialized. Call init() first.');
|
|
2365
3463
|
}
|
|
2366
|
-
return module.
|
|
3464
|
+
return module.hpssWithResidual(samples, sampleRate, kernelHarmonic, kernelPercussive);
|
|
2367
3465
|
}
|
|
2368
3466
|
|
|
2369
|
-
// ============================================================================
|
|
2370
|
-
// Features - Spectral
|
|
2371
|
-
// ============================================================================
|
|
2372
|
-
|
|
2373
3467
|
/**
|
|
2374
|
-
*
|
|
2375
|
-
*
|
|
2376
|
-
*
|
|
2377
|
-
* @param sampleRate - Sample rate in Hz
|
|
2378
|
-
* @param nFft - FFT size (default: 2048)
|
|
2379
|
-
* @param hopLength - Hop length (default: 512)
|
|
2380
|
-
* @returns Spectral centroid in Hz for each frame
|
|
3468
|
+
* Channel-weighted multichannel integrated loudness + LRA (ITU-R BS.1770 /
|
|
3469
|
+
* EBU R128) from an interleaved buffer of `frames * channels` samples. The
|
|
3470
|
+
* per-channel frame count is derived from the buffer length and `channels`.
|
|
2381
3471
|
*/
|
|
2382
|
-
export function
|
|
3472
|
+
export function lufsInterleaved(
|
|
2383
3473
|
samples: Float32Array,
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
): Float32Array {
|
|
3474
|
+
channels: number,
|
|
3475
|
+
sampleRate = 22050,
|
|
3476
|
+
): WasmLufsResult {
|
|
2388
3477
|
if (!module) {
|
|
2389
3478
|
throw new Error('Module not initialized. Call init() first.');
|
|
2390
3479
|
}
|
|
2391
|
-
return module.
|
|
3480
|
+
return module.lufsInterleaved(samples, channels, sampleRate);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
/**
|
|
3484
|
+
* Standards-compliant EBU R128 loudness range (LRA) in LU.
|
|
3485
|
+
*/
|
|
3486
|
+
export function ebur128LoudnessRange(samples: Float32Array, sampleRate = 22050): number {
|
|
3487
|
+
if (!module) {
|
|
3488
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3489
|
+
}
|
|
3490
|
+
return module.ebur128LoudnessRange(samples, sampleRate);
|
|
2392
3491
|
}
|
|
2393
3492
|
|
|
2394
3493
|
/**
|
|
2395
3494
|
* Compute spectral bandwidth.
|
|
2396
3495
|
*
|
|
2397
3496
|
* @param samples - Audio samples (mono, float32)
|
|
2398
|
-
* @param sampleRate - Sample rate in Hz
|
|
3497
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2399
3498
|
* @param nFft - FFT size (default: 2048)
|
|
2400
3499
|
* @param hopLength - Hop length (default: 512)
|
|
2401
3500
|
* @returns Spectral bandwidth in Hz for each frame
|
|
2402
3501
|
*/
|
|
2403
3502
|
export function spectralBandwidth(
|
|
2404
3503
|
samples: Float32Array,
|
|
2405
|
-
sampleRate
|
|
3504
|
+
sampleRate = 22050,
|
|
2406
3505
|
nFft = 2048,
|
|
2407
3506
|
hopLength = 512,
|
|
2408
3507
|
): Float32Array {
|
|
@@ -2416,7 +3515,7 @@ export function spectralBandwidth(
|
|
|
2416
3515
|
* Compute spectral rolloff frequency.
|
|
2417
3516
|
*
|
|
2418
3517
|
* @param samples - Audio samples (mono, float32)
|
|
2419
|
-
* @param sampleRate - Sample rate in Hz
|
|
3518
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2420
3519
|
* @param nFft - FFT size (default: 2048)
|
|
2421
3520
|
* @param hopLength - Hop length (default: 512)
|
|
2422
3521
|
* @param rollPercent - Percentage threshold (default: 0.85)
|
|
@@ -2424,7 +3523,7 @@ export function spectralBandwidth(
|
|
|
2424
3523
|
*/
|
|
2425
3524
|
export function spectralRolloff(
|
|
2426
3525
|
samples: Float32Array,
|
|
2427
|
-
sampleRate
|
|
3526
|
+
sampleRate = 22050,
|
|
2428
3527
|
nFft = 2048,
|
|
2429
3528
|
hopLength = 512,
|
|
2430
3529
|
rollPercent = 0.85,
|
|
@@ -2439,14 +3538,14 @@ export function spectralRolloff(
|
|
|
2439
3538
|
* Compute spectral flatness.
|
|
2440
3539
|
*
|
|
2441
3540
|
* @param samples - Audio samples (mono, float32)
|
|
2442
|
-
* @param sampleRate - Sample rate in Hz
|
|
3541
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2443
3542
|
* @param nFft - FFT size (default: 2048)
|
|
2444
3543
|
* @param hopLength - Hop length (default: 512)
|
|
2445
3544
|
* @returns Spectral flatness for each frame (0 = tonal, 1 = noise-like)
|
|
2446
3545
|
*/
|
|
2447
3546
|
export function spectralFlatness(
|
|
2448
3547
|
samples: Float32Array,
|
|
2449
|
-
sampleRate
|
|
3548
|
+
sampleRate = 22050,
|
|
2450
3549
|
nFft = 2048,
|
|
2451
3550
|
hopLength = 512,
|
|
2452
3551
|
): Float32Array {
|
|
@@ -2460,14 +3559,14 @@ export function spectralFlatness(
|
|
|
2460
3559
|
* Compute zero crossing rate.
|
|
2461
3560
|
*
|
|
2462
3561
|
* @param samples - Audio samples (mono, float32)
|
|
2463
|
-
* @param sampleRate - Sample rate in Hz
|
|
3562
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2464
3563
|
* @param frameLength - Frame length (default: 2048)
|
|
2465
3564
|
* @param hopLength - Hop length (default: 512)
|
|
2466
3565
|
* @returns Zero crossing rate for each frame
|
|
2467
3566
|
*/
|
|
2468
3567
|
export function zeroCrossingRate(
|
|
2469
3568
|
samples: Float32Array,
|
|
2470
|
-
sampleRate
|
|
3569
|
+
sampleRate = 22050,
|
|
2471
3570
|
frameLength = 2048,
|
|
2472
3571
|
hopLength = 512,
|
|
2473
3572
|
): Float32Array {
|
|
@@ -2481,14 +3580,14 @@ export function zeroCrossingRate(
|
|
|
2481
3580
|
* Compute RMS energy.
|
|
2482
3581
|
*
|
|
2483
3582
|
* @param samples - Audio samples (mono, float32)
|
|
2484
|
-
* @param sampleRate - Sample rate in Hz
|
|
3583
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2485
3584
|
* @param frameLength - Frame length (default: 2048)
|
|
2486
3585
|
* @param hopLength - Hop length (default: 512)
|
|
2487
3586
|
* @returns RMS energy for each frame
|
|
2488
3587
|
*/
|
|
2489
3588
|
export function rmsEnergy(
|
|
2490
3589
|
samples: Float32Array,
|
|
2491
|
-
sampleRate
|
|
3590
|
+
sampleRate = 22050,
|
|
2492
3591
|
frameLength = 2048,
|
|
2493
3592
|
hopLength = 512,
|
|
2494
3593
|
): Float32Array {
|
|
@@ -2506,54 +3605,76 @@ export function rmsEnergy(
|
|
|
2506
3605
|
* Detect pitch using YIN algorithm.
|
|
2507
3606
|
*
|
|
2508
3607
|
* @param samples - Audio samples (mono, float32)
|
|
2509
|
-
* @param sampleRate - Sample rate in Hz
|
|
3608
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2510
3609
|
* @param frameLength - Frame length (default: 2048)
|
|
2511
3610
|
* @param hopLength - Hop length (default: 512)
|
|
2512
3611
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2513
3612
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2514
3613
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3614
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2515
3615
|
* @returns Pitch detection result
|
|
2516
3616
|
*/
|
|
2517
3617
|
export function pitchYin(
|
|
2518
3618
|
samples: Float32Array,
|
|
2519
|
-
sampleRate
|
|
3619
|
+
sampleRate = 22050,
|
|
2520
3620
|
frameLength = 2048,
|
|
2521
3621
|
hopLength = 512,
|
|
2522
3622
|
fmin = 65.0,
|
|
2523
3623
|
fmax = 2093.0,
|
|
2524
3624
|
threshold = 0.3,
|
|
3625
|
+
fillNa = false,
|
|
2525
3626
|
): PitchResult {
|
|
2526
3627
|
if (!module) {
|
|
2527
3628
|
throw new Error('Module not initialized. Call init() first.');
|
|
2528
3629
|
}
|
|
2529
|
-
return module.pitchYin(
|
|
3630
|
+
return module.pitchYin(
|
|
3631
|
+
samples,
|
|
3632
|
+
sampleRate,
|
|
3633
|
+
frameLength,
|
|
3634
|
+
hopLength,
|
|
3635
|
+
fmin,
|
|
3636
|
+
fmax,
|
|
3637
|
+
threshold,
|
|
3638
|
+
fillNa,
|
|
3639
|
+
);
|
|
2530
3640
|
}
|
|
2531
3641
|
|
|
2532
3642
|
/**
|
|
2533
3643
|
* Detect pitch using pYIN algorithm (probabilistic YIN with HMM smoothing).
|
|
2534
3644
|
*
|
|
2535
3645
|
* @param samples - Audio samples (mono, float32)
|
|
2536
|
-
* @param sampleRate - Sample rate in Hz
|
|
3646
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2537
3647
|
* @param frameLength - Frame length (default: 2048)
|
|
2538
3648
|
* @param hopLength - Hop length (default: 512)
|
|
2539
3649
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2540
3650
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2541
3651
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3652
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2542
3653
|
* @returns Pitch detection result
|
|
2543
3654
|
*/
|
|
2544
3655
|
export function pitchPyin(
|
|
2545
3656
|
samples: Float32Array,
|
|
2546
|
-
sampleRate
|
|
3657
|
+
sampleRate = 22050,
|
|
2547
3658
|
frameLength = 2048,
|
|
2548
3659
|
hopLength = 512,
|
|
2549
3660
|
fmin = 65.0,
|
|
2550
3661
|
fmax = 2093.0,
|
|
2551
3662
|
threshold = 0.3,
|
|
3663
|
+
fillNa = false,
|
|
2552
3664
|
): PitchResult {
|
|
2553
3665
|
if (!module) {
|
|
2554
3666
|
throw new Error('Module not initialized. Call init() first.');
|
|
2555
3667
|
}
|
|
2556
|
-
return module.pitchPyin(
|
|
3668
|
+
return module.pitchPyin(
|
|
3669
|
+
samples,
|
|
3670
|
+
sampleRate,
|
|
3671
|
+
frameLength,
|
|
3672
|
+
hopLength,
|
|
3673
|
+
fmin,
|
|
3674
|
+
fmax,
|
|
3675
|
+
threshold,
|
|
3676
|
+
fillNa,
|
|
3677
|
+
);
|
|
2557
3678
|
}
|
|
2558
3679
|
|
|
2559
3680
|
// ============================================================================
|
|
@@ -2642,11 +3763,11 @@ export function noteToHz(note: string): number {
|
|
|
2642
3763
|
* Convert frame index to time in seconds.
|
|
2643
3764
|
*
|
|
2644
3765
|
* @param frames - Frame index
|
|
2645
|
-
* @param sr - Sample rate in Hz
|
|
2646
|
-
* @param hopLength - Hop length in samples
|
|
3766
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3767
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2647
3768
|
* @returns Time in seconds
|
|
2648
3769
|
*/
|
|
2649
|
-
export function framesToTime(frames: number, sr
|
|
3770
|
+
export function framesToTime(frames: number, sr = 22050, hopLength = 512): number {
|
|
2650
3771
|
if (!module) {
|
|
2651
3772
|
throw new Error('Module not initialized. Call init() first.');
|
|
2652
3773
|
}
|
|
@@ -2657,11 +3778,11 @@ export function framesToTime(frames: number, sr: number, hopLength: number): num
|
|
|
2657
3778
|
* Convert time in seconds to frame index.
|
|
2658
3779
|
*
|
|
2659
3780
|
* @param time - Time in seconds
|
|
2660
|
-
* @param sr - Sample rate in Hz
|
|
2661
|
-
* @param hopLength - Hop length in samples
|
|
3781
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3782
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2662
3783
|
* @returns Frame index
|
|
2663
3784
|
*/
|
|
2664
|
-
export function timeToFrames(time: number, sr
|
|
3785
|
+
export function timeToFrames(time: number, sr = 22050, hopLength = 512): number {
|
|
2665
3786
|
if (!module) {
|
|
2666
3787
|
throw new Error('Module not initialized. Call init() first.');
|
|
2667
3788
|
}
|
|
@@ -2769,18 +3890,18 @@ export function frameSignal(
|
|
|
2769
3890
|
return module.frameSignal(samples, frameLength, hopLength);
|
|
2770
3891
|
}
|
|
2771
3892
|
|
|
2772
|
-
export function padCenter(values: Float32Array,
|
|
3893
|
+
export function padCenter(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2773
3894
|
if (!module) {
|
|
2774
3895
|
throw new Error('Module not initialized. Call init() first.');
|
|
2775
3896
|
}
|
|
2776
|
-
return module.padCenter(values,
|
|
3897
|
+
return module.padCenter(values, targetSize, padValue);
|
|
2777
3898
|
}
|
|
2778
3899
|
|
|
2779
|
-
export function fixLength(values: Float32Array,
|
|
3900
|
+
export function fixLength(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2780
3901
|
if (!module) {
|
|
2781
3902
|
throw new Error('Module not initialized. Call init() first.');
|
|
2782
3903
|
}
|
|
2783
|
-
return module.fixLength(values,
|
|
3904
|
+
return module.fixLength(values, targetSize, padValue);
|
|
2784
3905
|
}
|
|
2785
3906
|
|
|
2786
3907
|
export function fixFrames(frames: Int32Array, xMin = 0, xMax = -1, pad = true): Int32Array {
|
|
@@ -2805,11 +3926,7 @@ export function peakPick(
|
|
|
2805
3926
|
return module.peakPick(values, preMax, postMax, preAvg, postAvg, delta, wait);
|
|
2806
3927
|
}
|
|
2807
3928
|
|
|
2808
|
-
export function vectorNormalize(
|
|
2809
|
-
values: Float32Array,
|
|
2810
|
-
normType = 0,
|
|
2811
|
-
threshold = 1e-12,
|
|
2812
|
-
): Float32Array {
|
|
3929
|
+
export function vectorNormalize(values: Float32Array, normType = 0, threshold = 0.0): Float32Array {
|
|
2813
3930
|
if (!module) {
|
|
2814
3931
|
throw new Error('Module not initialized. Call init() first.');
|
|
2815
3932
|
}
|
|
@@ -2837,7 +3954,7 @@ export function tonnetz(chromagram: Float32Array, nChroma: number, nFrames: numb
|
|
|
2837
3954
|
|
|
2838
3955
|
export function tempogram(
|
|
2839
3956
|
onsetEnvelope: Float32Array,
|
|
2840
|
-
sampleRate
|
|
3957
|
+
sampleRate = 22050,
|
|
2841
3958
|
hopLength = 512,
|
|
2842
3959
|
winLength = 384,
|
|
2843
3960
|
mode: TempogramMode = 'autocorrelation',
|
|
@@ -2850,7 +3967,7 @@ export function tempogram(
|
|
|
2850
3967
|
|
|
2851
3968
|
export function cyclicTempogram(
|
|
2852
3969
|
onsetEnvelope: Float32Array,
|
|
2853
|
-
sampleRate
|
|
3970
|
+
sampleRate = 22050,
|
|
2854
3971
|
hopLength = 512,
|
|
2855
3972
|
winLength = 384,
|
|
2856
3973
|
bpmMin = 60.0,
|
|
@@ -2864,7 +3981,7 @@ export function cyclicTempogram(
|
|
|
2864
3981
|
|
|
2865
3982
|
export function plp(
|
|
2866
3983
|
onsetEnvelope: Float32Array,
|
|
2867
|
-
sampleRate
|
|
3984
|
+
sampleRate = 22050,
|
|
2868
3985
|
hopLength = 512,
|
|
2869
3986
|
tempoMin = 30.0,
|
|
2870
3987
|
tempoMax = 300.0,
|
|
@@ -2949,7 +4066,7 @@ export function vqt(
|
|
|
2949
4066
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2950
4067
|
* @param nFft - FFT size (default: 2048)
|
|
2951
4068
|
* @param hopLength - Hop length (default: 512)
|
|
2952
|
-
* @param minSectionSec - Minimum section duration in seconds (default:
|
|
4069
|
+
* @param minSectionSec - Minimum section duration in seconds (default: 4.0)
|
|
2953
4070
|
* @returns Array of detected sections
|
|
2954
4071
|
*/
|
|
2955
4072
|
export function analyzeSections(
|
|
@@ -2957,7 +4074,7 @@ export function analyzeSections(
|
|
|
2957
4074
|
sampleRate = 22050,
|
|
2958
4075
|
nFft = 2048,
|
|
2959
4076
|
hopLength = 512,
|
|
2960
|
-
minSectionSec =
|
|
4077
|
+
minSectionSec = 4.0,
|
|
2961
4078
|
): Section[] {
|
|
2962
4079
|
if (!module) {
|
|
2963
4080
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2985,7 +4102,7 @@ export function analyzeMelody(
|
|
|
2985
4102
|
fmin = 65.0,
|
|
2986
4103
|
fmax = 2093.0,
|
|
2987
4104
|
frameLength = 2048,
|
|
2988
|
-
hopLength =
|
|
4105
|
+
hopLength = 256,
|
|
2989
4106
|
threshold = 0.1,
|
|
2990
4107
|
): MelodyResult {
|
|
2991
4108
|
if (!module) {
|
|
@@ -3066,10 +4183,15 @@ export function tempogramRatio(
|
|
|
3066
4183
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3067
4184
|
* @returns Loudness measurement result
|
|
3068
4185
|
*/
|
|
3069
|
-
export function lufs(
|
|
4186
|
+
export function lufs(
|
|
4187
|
+
samples: Float32Array,
|
|
4188
|
+
sampleRate = 22050,
|
|
4189
|
+
options: ValidateOptions = {},
|
|
4190
|
+
): LufsResult {
|
|
3070
4191
|
if (!module) {
|
|
3071
4192
|
throw new Error('Module not initialized. Call init() first.');
|
|
3072
4193
|
}
|
|
4194
|
+
assertSamples('lufs', samples, options.validate !== false);
|
|
3073
4195
|
return module.lufs(samples, sampleRate);
|
|
3074
4196
|
}
|
|
3075
4197
|
|
|
@@ -3080,10 +4202,15 @@ export function lufs(samples: Float32Array, sampleRate = 22050): LufsResult {
|
|
|
3080
4202
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3081
4203
|
* @returns Momentary LUFS values over time
|
|
3082
4204
|
*/
|
|
3083
|
-
export function momentaryLufs(
|
|
4205
|
+
export function momentaryLufs(
|
|
4206
|
+
samples: Float32Array,
|
|
4207
|
+
sampleRate = 22050,
|
|
4208
|
+
options: ValidateOptions = {},
|
|
4209
|
+
): Float32Array {
|
|
3084
4210
|
if (!module) {
|
|
3085
4211
|
throw new Error('Module not initialized. Call init() first.');
|
|
3086
4212
|
}
|
|
4213
|
+
assertSamples('momentaryLufs', samples, options.validate !== false);
|
|
3087
4214
|
return module.momentaryLufs(samples, sampleRate);
|
|
3088
4215
|
}
|
|
3089
4216
|
|
|
@@ -3094,13 +4221,292 @@ export function momentaryLufs(samples: Float32Array, sampleRate = 22050): Float3
|
|
|
3094
4221
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3095
4222
|
* @returns Short-term LUFS values over time
|
|
3096
4223
|
*/
|
|
3097
|
-
export function shortTermLufs(
|
|
4224
|
+
export function shortTermLufs(
|
|
4225
|
+
samples: Float32Array,
|
|
4226
|
+
sampleRate = 22050,
|
|
4227
|
+
options: ValidateOptions = {},
|
|
4228
|
+
): Float32Array {
|
|
3098
4229
|
if (!module) {
|
|
3099
4230
|
throw new Error('Module not initialized. Call init() first.');
|
|
3100
4231
|
}
|
|
4232
|
+
assertSamples('shortTermLufs', samples, options.validate !== false);
|
|
3101
4233
|
return module.shortTermLufs(samples, sampleRate);
|
|
3102
4234
|
}
|
|
3103
4235
|
|
|
4236
|
+
// ============================================================================
|
|
4237
|
+
// Metering — basic / true-peak / clipping / dynamic range
|
|
4238
|
+
// ============================================================================
|
|
4239
|
+
|
|
4240
|
+
/** One contiguous run of clipped samples reported by `meteringDetectClipping`. */
|
|
4241
|
+
export interface ClippingRegion {
|
|
4242
|
+
startSample: number;
|
|
4243
|
+
endSample: number;
|
|
4244
|
+
length: number;
|
|
4245
|
+
peak: number;
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
/** Aggregated clipping report. */
|
|
4249
|
+
export interface ClippingReport {
|
|
4250
|
+
clippedSamples: number;
|
|
4251
|
+
clippingRatio: number;
|
|
4252
|
+
maxClippedPeak: number;
|
|
4253
|
+
regions: ClippingRegion[];
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
/** Sliding-window dynamic range report. */
|
|
4257
|
+
export interface DynamicRangeReport {
|
|
4258
|
+
dynamicRangeDb: number;
|
|
4259
|
+
lowPercentileDb: number;
|
|
4260
|
+
highPercentileDb: number;
|
|
4261
|
+
windowRmsDb: Float32Array;
|
|
4262
|
+
}
|
|
4263
|
+
|
|
4264
|
+
function requireModule() {
|
|
4265
|
+
if (!module) {
|
|
4266
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
4267
|
+
}
|
|
4268
|
+
return module;
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
export function meteringPeakDb(
|
|
4272
|
+
samples: Float32Array,
|
|
4273
|
+
sampleRate = 22050,
|
|
4274
|
+
options: ValidateOptions = {},
|
|
4275
|
+
): number {
|
|
4276
|
+
assertSamples('meteringPeakDb', samples, options.validate !== false);
|
|
4277
|
+
return requireModule().meteringPeakDb(samples, sampleRate);
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
export function meteringRmsDb(
|
|
4281
|
+
samples: Float32Array,
|
|
4282
|
+
sampleRate = 22050,
|
|
4283
|
+
options: ValidateOptions = {},
|
|
4284
|
+
): number {
|
|
4285
|
+
assertSamples('meteringRmsDb', samples, options.validate !== false);
|
|
4286
|
+
return requireModule().meteringRmsDb(samples, sampleRate);
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4289
|
+
export function meteringCrestFactorDb(
|
|
4290
|
+
samples: Float32Array,
|
|
4291
|
+
sampleRate = 22050,
|
|
4292
|
+
options: ValidateOptions = {},
|
|
4293
|
+
): number {
|
|
4294
|
+
assertSamples('meteringCrestFactorDb', samples, options.validate !== false);
|
|
4295
|
+
return requireModule().meteringCrestFactorDb(samples, sampleRate);
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
export function meteringDcOffset(
|
|
4299
|
+
samples: Float32Array,
|
|
4300
|
+
sampleRate = 22050,
|
|
4301
|
+
options: ValidateOptions = {},
|
|
4302
|
+
): number {
|
|
4303
|
+
assertSamples('meteringDcOffset', samples, options.validate !== false);
|
|
4304
|
+
return requireModule().meteringDcOffset(samples, sampleRate);
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
/**
|
|
4308
|
+
* Inter-sample (true) peak in dBFS. `oversampleFactor` must be a power of two
|
|
4309
|
+
* in [1, 16]; pass 0 to use the library default (4).
|
|
4310
|
+
*/
|
|
4311
|
+
export function meteringTruePeakDb(
|
|
4312
|
+
samples: Float32Array,
|
|
4313
|
+
sampleRate = 22050,
|
|
4314
|
+
oversampleFactor = 4,
|
|
4315
|
+
options: ValidateOptions = {},
|
|
4316
|
+
): number {
|
|
4317
|
+
assertSamples('meteringTruePeakDb', samples, options.validate !== false);
|
|
4318
|
+
return requireModule().meteringTruePeakDb(samples, sampleRate, oversampleFactor);
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
/**
|
|
4322
|
+
* Detect contiguous runs of clipped samples.
|
|
4323
|
+
*
|
|
4324
|
+
* @param threshold Linear absolute threshold (default 0.999).
|
|
4325
|
+
* @param minRegionSamples Minimum run length to report (default 1).
|
|
4326
|
+
*/
|
|
4327
|
+
export function meteringDetectClipping(
|
|
4328
|
+
samples: Float32Array,
|
|
4329
|
+
sampleRate = 22050,
|
|
4330
|
+
threshold = 0.999,
|
|
4331
|
+
minRegionSamples = 1,
|
|
4332
|
+
options: ValidateOptions = {},
|
|
4333
|
+
): ClippingReport {
|
|
4334
|
+
assertSamples('meteringDetectClipping', samples, options.validate !== false);
|
|
4335
|
+
return requireModule().meteringDetectClipping(samples, sampleRate, threshold, minRegionSamples);
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
/**
|
|
4339
|
+
* Sliding-window dynamic range. Pass 0 for any parameter to use the library
|
|
4340
|
+
* default (window=3 s, hop=1 s, low=0.10, high=0.95).
|
|
4341
|
+
*/
|
|
4342
|
+
export function meteringDynamicRange(
|
|
4343
|
+
samples: Float32Array,
|
|
4344
|
+
sampleRate = 22050,
|
|
4345
|
+
windowSec = 0,
|
|
4346
|
+
hopSec = 0,
|
|
4347
|
+
lowPercentile = 0,
|
|
4348
|
+
highPercentile = 0,
|
|
4349
|
+
options: ValidateOptions = {},
|
|
4350
|
+
): DynamicRangeReport {
|
|
4351
|
+
assertSamples('meteringDynamicRange', samples, options.validate !== false);
|
|
4352
|
+
return requireModule().meteringDynamicRange(
|
|
4353
|
+
samples,
|
|
4354
|
+
sampleRate,
|
|
4355
|
+
windowSec,
|
|
4356
|
+
hopSec,
|
|
4357
|
+
lowPercentile,
|
|
4358
|
+
highPercentile,
|
|
4359
|
+
);
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
// ============================================================================
|
|
4363
|
+
// Metering — stereo / phase-scope / spectrum
|
|
4364
|
+
// ============================================================================
|
|
4365
|
+
|
|
4366
|
+
/** Mid/side vectorscope point series for a (left, right) stereo pair. */
|
|
4367
|
+
export interface VectorscopeReport {
|
|
4368
|
+
mid: Float32Array;
|
|
4369
|
+
side: Float32Array;
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
/** Phase-scope (Lissajous) point series plus summary stats. */
|
|
4373
|
+
export interface PhaseScopeReport {
|
|
4374
|
+
mid: Float32Array;
|
|
4375
|
+
side: Float32Array;
|
|
4376
|
+
radius: Float32Array;
|
|
4377
|
+
angleRad: Float32Array;
|
|
4378
|
+
correlation: number;
|
|
4379
|
+
averageAbsAngleRad: number;
|
|
4380
|
+
maxRadius: number;
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
/** Options for `meteringSpectrum`. */
|
|
4384
|
+
export interface SpectrumOptions {
|
|
4385
|
+
/** FFT size. Pass 0 / omit for the library default (2048). */
|
|
4386
|
+
nFft?: number;
|
|
4387
|
+
/** Apply fractional-octave smoothing to magnitude. */
|
|
4388
|
+
applyOctaveSmoothing?: boolean;
|
|
4389
|
+
/** Smoothing fraction (e.g. 3 = 1/3-octave). 0 / omit = library default (3). */
|
|
4390
|
+
octaveFraction?: number;
|
|
4391
|
+
/** Linear reference for the dB conversion. 0 / omit = 1.0. */
|
|
4392
|
+
dbRef?: number;
|
|
4393
|
+
/** Linear floor used to avoid log(0). 0 / omit = library default. */
|
|
4394
|
+
dbAmin?: number;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
/** Single-frame magnitude / power / dB spectrum returned by `meteringSpectrum`. */
|
|
4398
|
+
export interface SpectrumReport {
|
|
4399
|
+
frequencies: Float32Array;
|
|
4400
|
+
magnitude: Float32Array;
|
|
4401
|
+
power: Float32Array;
|
|
4402
|
+
db: Float32Array;
|
|
4403
|
+
nFft: number;
|
|
4404
|
+
sampleRate: number;
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
/** Pearson correlation in [-1, 1] between two equal-length channels. */
|
|
4408
|
+
export function meteringStereoCorrelation(
|
|
4409
|
+
left: Float32Array,
|
|
4410
|
+
right: Float32Array,
|
|
4411
|
+
sampleRate = 22050,
|
|
4412
|
+
options: ValidateOptions = {},
|
|
4413
|
+
): number {
|
|
4414
|
+
const validate = options.validate !== false;
|
|
4415
|
+
assertSamples('meteringStereoCorrelation', left, validate, 'left');
|
|
4416
|
+
assertSamples('meteringStereoCorrelation', right, validate, 'right');
|
|
4417
|
+
return requireModule().meteringStereoCorrelation(left, right, sampleRate);
|
|
4418
|
+
}
|
|
4419
|
+
|
|
4420
|
+
/** Side / mid energy ratio: 0 = pure mono, ~1 = wide stereo. */
|
|
4421
|
+
export function meteringStereoWidth(
|
|
4422
|
+
left: Float32Array,
|
|
4423
|
+
right: Float32Array,
|
|
4424
|
+
sampleRate = 22050,
|
|
4425
|
+
options: ValidateOptions = {},
|
|
4426
|
+
): number {
|
|
4427
|
+
const validate = options.validate !== false;
|
|
4428
|
+
assertSamples('meteringStereoWidth', left, validate, 'left');
|
|
4429
|
+
assertSamples('meteringStereoWidth', right, validate, 'right');
|
|
4430
|
+
return requireModule().meteringStereoWidth(left, right, sampleRate);
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
/** Per-sample mid/side point series (one entry per input frame). */
|
|
4434
|
+
export function meteringVectorscope(
|
|
4435
|
+
left: Float32Array,
|
|
4436
|
+
right: Float32Array,
|
|
4437
|
+
sampleRate = 22050,
|
|
4438
|
+
options: ValidateOptions = {},
|
|
4439
|
+
): VectorscopeReport {
|
|
4440
|
+
const validate = options.validate !== false;
|
|
4441
|
+
assertSamples('meteringVectorscope', left, validate, 'left');
|
|
4442
|
+
assertSamples('meteringVectorscope', right, validate, 'right');
|
|
4443
|
+
return requireModule().meteringVectorscope(left, right, sampleRate);
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
/** Phase-scope point series plus summary stats. */
|
|
4447
|
+
export function meteringPhaseScope(
|
|
4448
|
+
left: Float32Array,
|
|
4449
|
+
right: Float32Array,
|
|
4450
|
+
sampleRate = 22050,
|
|
4451
|
+
options: ValidateOptions = {},
|
|
4452
|
+
): PhaseScopeReport {
|
|
4453
|
+
const validate = options.validate !== false;
|
|
4454
|
+
assertSamples('meteringPhaseScope', left, validate, 'left');
|
|
4455
|
+
assertSamples('meteringPhaseScope', right, validate, 'right');
|
|
4456
|
+
return requireModule().meteringPhaseScope(left, right, sampleRate);
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
/** Single-frame spectrum view (uses the first `nFft` samples of `samples`). */
|
|
4460
|
+
export function meteringSpectrum(
|
|
4461
|
+
samples: Float32Array,
|
|
4462
|
+
sampleRate = 22050,
|
|
4463
|
+
options?: SpectrumOptions & ValidateOptions,
|
|
4464
|
+
): SpectrumReport {
|
|
4465
|
+
const validate = options?.validate !== false;
|
|
4466
|
+
assertSamples('meteringSpectrum', samples, validate);
|
|
4467
|
+
return requireModule().meteringSpectrum(samples, sampleRate, options ?? {});
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
// ============================================================================
|
|
4471
|
+
// Editing — 12-TET scale quantizer
|
|
4472
|
+
// ============================================================================
|
|
4473
|
+
|
|
4474
|
+
/**
|
|
4475
|
+
* Snap a MIDI value to the nearest pitch class enabled by `modeMask`.
|
|
4476
|
+
*
|
|
4477
|
+
* `modeMask` is a 12-bit mask. For natural C major use `0b101010110101`.
|
|
4478
|
+
* `referenceMidi` defaults to A4 (69) when passed as 0.
|
|
4479
|
+
*/
|
|
4480
|
+
export function scaleQuantizeMidi(
|
|
4481
|
+
root: number,
|
|
4482
|
+
modeMask: number,
|
|
4483
|
+
midi: number,
|
|
4484
|
+
referenceMidi = 0,
|
|
4485
|
+
): number {
|
|
4486
|
+
assertFiniteScalar('scaleQuantizeMidi', midi, 'midi');
|
|
4487
|
+
assertFiniteScalar('scaleQuantizeMidi', referenceMidi, 'referenceMidi');
|
|
4488
|
+
return requireModule().scaleQuantizeMidi(root, modeMask, midi, referenceMidi);
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
export function scaleCorrectionSemitones(
|
|
4492
|
+
root: number,
|
|
4493
|
+
modeMask: number,
|
|
4494
|
+
midi: number,
|
|
4495
|
+
referenceMidi = 0,
|
|
4496
|
+
): number {
|
|
4497
|
+
assertFiniteScalar('scaleCorrectionSemitones', midi, 'midi');
|
|
4498
|
+
assertFiniteScalar('scaleCorrectionSemitones', referenceMidi, 'referenceMidi');
|
|
4499
|
+
return requireModule().scaleCorrectionSemitones(root, modeMask, midi, referenceMidi);
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
export function scalePitchClassEnabled(
|
|
4503
|
+
root: number,
|
|
4504
|
+
modeMask: number,
|
|
4505
|
+
pitchClass: number,
|
|
4506
|
+
): boolean {
|
|
4507
|
+
return requireModule().scalePitchClassEnabled(root, modeMask, pitchClass);
|
|
4508
|
+
}
|
|
4509
|
+
|
|
3104
4510
|
// ============================================================================
|
|
3105
4511
|
// Core - Resample
|
|
3106
4512
|
// ============================================================================
|
|
@@ -3234,15 +4640,15 @@ export class Audio {
|
|
|
3234
4640
|
return pitchShift(this._samples, this._sampleRate, semitones);
|
|
3235
4641
|
}
|
|
3236
4642
|
|
|
3237
|
-
pitchCorrectToMidi(currentMidi
|
|
4643
|
+
pitchCorrectToMidi(currentMidi = 69.0, targetMidi = 69.0): Float32Array {
|
|
3238
4644
|
return pitchCorrectToMidi(this._samples, this._sampleRate, currentMidi, targetMidi);
|
|
3239
4645
|
}
|
|
3240
4646
|
|
|
3241
|
-
noteStretch(onsetSample
|
|
4647
|
+
noteStretch(onsetSample = 0, offsetSample = 0, stretchRatio = 1.0): Float32Array {
|
|
3242
4648
|
return noteStretch(this._samples, this._sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
3243
4649
|
}
|
|
3244
4650
|
|
|
3245
|
-
voiceChange(pitchSemitones
|
|
4651
|
+
voiceChange(pitchSemitones = 0.0, formantFactor = 1.0): Float32Array {
|
|
3246
4652
|
return voiceChange(this._samples, this._sampleRate, pitchSemitones, formantFactor);
|
|
3247
4653
|
}
|
|
3248
4654
|
|
|
@@ -3290,7 +4696,7 @@ export class Audio {
|
|
|
3290
4696
|
return melSpectrogram(this._samples, this._sampleRate, nFft, hopLength, nMels);
|
|
3291
4697
|
}
|
|
3292
4698
|
|
|
3293
|
-
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc =
|
|
4699
|
+
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc = 20): MfccResult {
|
|
3294
4700
|
return mfcc(this._samples, this._sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
3295
4701
|
}
|
|
3296
4702
|
|
|
@@ -3348,8 +4754,18 @@ export class Audio {
|
|
|
3348
4754
|
fmin = 65.0,
|
|
3349
4755
|
fmax = 2093.0,
|
|
3350
4756
|
threshold = 0.3,
|
|
4757
|
+
fillNa = false,
|
|
3351
4758
|
): PitchResult {
|
|
3352
|
-
return pitchYin(
|
|
4759
|
+
return pitchYin(
|
|
4760
|
+
this._samples,
|
|
4761
|
+
this._sampleRate,
|
|
4762
|
+
frameLength,
|
|
4763
|
+
hopLength,
|
|
4764
|
+
fmin,
|
|
4765
|
+
fmax,
|
|
4766
|
+
threshold,
|
|
4767
|
+
fillNa,
|
|
4768
|
+
);
|
|
3353
4769
|
}
|
|
3354
4770
|
|
|
3355
4771
|
pitchPyin(
|
|
@@ -3358,6 +4774,7 @@ export class Audio {
|
|
|
3358
4774
|
fmin = 65.0,
|
|
3359
4775
|
fmax = 2093.0,
|
|
3360
4776
|
threshold = 0.3,
|
|
4777
|
+
fillNa = false,
|
|
3361
4778
|
): PitchResult {
|
|
3362
4779
|
return pitchPyin(
|
|
3363
4780
|
this._samples,
|
|
@@ -3367,6 +4784,7 @@ export class Audio {
|
|
|
3367
4784
|
fmin,
|
|
3368
4785
|
fmax,
|
|
3369
4786
|
threshold,
|
|
4787
|
+
fillNa,
|
|
3370
4788
|
);
|
|
3371
4789
|
}
|
|
3372
4790
|
|
|
@@ -3412,8 +4830,7 @@ export class StreamAnalyzer {
|
|
|
3412
4830
|
if (!module) {
|
|
3413
4831
|
throw new Error('Module not initialized. Call init() first.');
|
|
3414
4832
|
}
|
|
3415
|
-
|
|
3416
|
-
const args = [
|
|
4833
|
+
this.analyzer = new module.StreamAnalyzer(
|
|
3417
4834
|
config.sampleRate,
|
|
3418
4835
|
config.nFft ?? 2048,
|
|
3419
4836
|
config.hopLength ?? 512,
|
|
@@ -3432,63 +4849,7 @@ export class StreamAnalyzer {
|
|
|
3432
4849
|
config.bpmUpdateIntervalSec ?? 10,
|
|
3433
4850
|
config.window ?? 0,
|
|
3434
4851
|
config.outputFormat ?? 0,
|
|
3435
|
-
|
|
3436
|
-
const isArityError = (error: unknown): boolean => {
|
|
3437
|
-
const message = String((error as { message?: unknown } | null)?.message ?? error);
|
|
3438
|
-
return message.includes('invalid number of parameters');
|
|
3439
|
-
};
|
|
3440
|
-
const createLegacy = (): WasmStreamAnalyzer => {
|
|
3441
|
-
const LegacyStreamAnalyzer = wasmModule.StreamAnalyzer as unknown as new (
|
|
3442
|
-
sampleRate: number,
|
|
3443
|
-
nFft: number,
|
|
3444
|
-
hopLength: number,
|
|
3445
|
-
nMels: number,
|
|
3446
|
-
computeMel: boolean,
|
|
3447
|
-
computeChroma: boolean,
|
|
3448
|
-
computeOnset: boolean,
|
|
3449
|
-
emitEveryNFrames: number,
|
|
3450
|
-
) => WasmStreamAnalyzer;
|
|
3451
|
-
return new LegacyStreamAnalyzer(
|
|
3452
|
-
args[0],
|
|
3453
|
-
args[1],
|
|
3454
|
-
args[2],
|
|
3455
|
-
args[3],
|
|
3456
|
-
args[8],
|
|
3457
|
-
args[9],
|
|
3458
|
-
args[10],
|
|
3459
|
-
args[12],
|
|
3460
|
-
);
|
|
3461
|
-
};
|
|
3462
|
-
const hasExtendedConfig =
|
|
3463
|
-
config.fmin !== undefined ||
|
|
3464
|
-
config.fmax !== undefined ||
|
|
3465
|
-
config.tuningRefHz !== undefined ||
|
|
3466
|
-
config.computeMagnitude !== undefined ||
|
|
3467
|
-
config.computeSpectral !== undefined ||
|
|
3468
|
-
config.magnitudeDownsample !== undefined ||
|
|
3469
|
-
config.keyUpdateIntervalSec !== undefined ||
|
|
3470
|
-
config.bpmUpdateIntervalSec !== undefined ||
|
|
3471
|
-
config.window !== undefined ||
|
|
3472
|
-
config.outputFormat !== undefined;
|
|
3473
|
-
if (hasExtendedConfig) {
|
|
3474
|
-
try {
|
|
3475
|
-
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3476
|
-
} catch (error) {
|
|
3477
|
-
if (!isArityError(error)) {
|
|
3478
|
-
throw error;
|
|
3479
|
-
}
|
|
3480
|
-
this.analyzer = createLegacy();
|
|
3481
|
-
}
|
|
3482
|
-
} else {
|
|
3483
|
-
try {
|
|
3484
|
-
this.analyzer = createLegacy();
|
|
3485
|
-
} catch (error) {
|
|
3486
|
-
if (!isArityError(error)) {
|
|
3487
|
-
throw error;
|
|
3488
|
-
}
|
|
3489
|
-
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3490
|
-
}
|
|
3491
|
-
}
|
|
4852
|
+
);
|
|
3492
4853
|
}
|
|
3493
4854
|
|
|
3494
4855
|
/**
|