@libraz/libsonare 1.2.1 → 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 +691 -287
- package/dist/index.js +669 -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 +1605 -309
- package/src/public_types.ts +61 -0
- package/src/sonare.js.d.ts +496 -79
- package/src/worklet.ts +295 -9
- package/src/wasm_types.ts +0 -1259
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,
|
|
@@ -68,15 +71,9 @@ import type {
|
|
|
68
71
|
StreamingPlatform,
|
|
69
72
|
StreamingRetuneConfig,
|
|
70
73
|
TempogramMode,
|
|
74
|
+
VoicePresetId,
|
|
71
75
|
} from './public_types';
|
|
72
76
|
import { KeyProfile as KeyProfileValues, Mode, PitchClass } from './public_types';
|
|
73
|
-
import type {
|
|
74
|
-
AnalyzerStats,
|
|
75
|
-
FrameBuffer,
|
|
76
|
-
StreamConfig,
|
|
77
|
-
StreamFramesI16,
|
|
78
|
-
StreamFramesU8,
|
|
79
|
-
} from './stream_types';
|
|
80
77
|
import type {
|
|
81
78
|
ProgressCallback,
|
|
82
79
|
SonareModule,
|
|
@@ -84,6 +81,7 @@ import type {
|
|
|
84
81
|
WasmAnalysisResult,
|
|
85
82
|
WasmChordAnalysisResult,
|
|
86
83
|
WasmCyclicTempogramResult,
|
|
84
|
+
WasmDecomposeResult,
|
|
87
85
|
WasmEngineAutomationPoint,
|
|
88
86
|
WasmEngineBounceOptions,
|
|
89
87
|
WasmEngineBounceResult,
|
|
@@ -101,13 +99,23 @@ import type {
|
|
|
101
99
|
WasmEngineTransportState,
|
|
102
100
|
WasmFourierTempogramResult,
|
|
103
101
|
WasmFrameResult,
|
|
102
|
+
WasmHpssWithResidualResult,
|
|
104
103
|
WasmKeyCandidateResult,
|
|
104
|
+
WasmLufsResult,
|
|
105
|
+
WasmMatrix2dResult,
|
|
105
106
|
WasmNnlsChromaResult,
|
|
106
107
|
WasmRealtimeEngine,
|
|
107
108
|
WasmStreamAnalyzer,
|
|
108
109
|
WasmTempogramResult,
|
|
109
110
|
WasmTrimResult,
|
|
110
|
-
} from './
|
|
111
|
+
} from './sonare.js';
|
|
112
|
+
import type {
|
|
113
|
+
AnalyzerStats,
|
|
114
|
+
FrameBuffer,
|
|
115
|
+
StreamConfig,
|
|
116
|
+
StreamFramesI16,
|
|
117
|
+
StreamFramesU8,
|
|
118
|
+
} from './stream_types';
|
|
111
119
|
|
|
112
120
|
export type {
|
|
113
121
|
AcousticResult,
|
|
@@ -155,6 +163,7 @@ export type {
|
|
|
155
163
|
PanLaw,
|
|
156
164
|
PanMode,
|
|
157
165
|
PitchResult,
|
|
166
|
+
RealtimeVoiceChangerConfigInput,
|
|
158
167
|
RhythmFeatures,
|
|
159
168
|
Section,
|
|
160
169
|
SendTiming,
|
|
@@ -166,6 +175,7 @@ export type {
|
|
|
166
175
|
StreamingRetuneConfig,
|
|
167
176
|
Timbre,
|
|
168
177
|
TimeSignature,
|
|
178
|
+
VoicePresetId,
|
|
169
179
|
} from './public_types';
|
|
170
180
|
export {
|
|
171
181
|
ChordQuality,
|
|
@@ -174,6 +184,7 @@ export {
|
|
|
174
184
|
PitchClass,
|
|
175
185
|
SectionType,
|
|
176
186
|
} from './public_types';
|
|
187
|
+
export type { ProgressCallback } from './sonare.js';
|
|
177
188
|
export type {
|
|
178
189
|
AnalyzerStats,
|
|
179
190
|
BarChord,
|
|
@@ -185,7 +196,6 @@ export type {
|
|
|
185
196
|
StreamFramesI16,
|
|
186
197
|
StreamFramesU8,
|
|
187
198
|
} from './stream_types';
|
|
188
|
-
export type { ProgressCallback } from './wasm_types';
|
|
189
199
|
|
|
190
200
|
export type EngineClip = WasmEngineClip;
|
|
191
201
|
export type EngineParameterInfo = WasmEngineParameterInfo;
|
|
@@ -202,6 +212,13 @@ export type EngineTelemetry = WasmEngineTelemetry;
|
|
|
202
212
|
export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
|
|
203
213
|
export type EngineTransportState = WasmEngineTransportState;
|
|
204
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
|
+
|
|
205
222
|
export const EXPECTED_ENGINE_ABI_VERSION = 2;
|
|
206
223
|
|
|
207
224
|
export interface EngineCapabilities {
|
|
@@ -222,6 +239,43 @@ export interface MixerRealtimeBuffer {
|
|
|
222
239
|
process: (numSamples?: number) => void;
|
|
223
240
|
}
|
|
224
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
|
+
|
|
225
279
|
function automationCurveCode(curve: AutomationCurve): number {
|
|
226
280
|
switch (curve) {
|
|
227
281
|
case 'linear':
|
|
@@ -253,6 +307,22 @@ function panLawCode(panLaw: PanLaw | number): number {
|
|
|
253
307
|
}
|
|
254
308
|
}
|
|
255
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
|
+
|
|
256
326
|
function meterTapCode(tap: MeterTap | number): number {
|
|
257
327
|
return tap === 'preFader' || tap === 0 ? 0 : 1;
|
|
258
328
|
}
|
|
@@ -268,6 +338,62 @@ function sendTimingCode(timing: SendTiming | number): number {
|
|
|
268
338
|
let module: SonareModule | null = null;
|
|
269
339
|
let initPromise: Promise<void> | null = null;
|
|
270
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
|
+
|
|
271
397
|
// ============================================================================
|
|
272
398
|
// Initialization
|
|
273
399
|
// ============================================================================
|
|
@@ -327,6 +453,60 @@ export function engineAbiVersion(): number {
|
|
|
327
453
|
return module.engineAbiVersion();
|
|
328
454
|
}
|
|
329
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
|
+
|
|
330
510
|
export function engineCapabilities(): EngineCapabilities {
|
|
331
511
|
const abiVersion = engineAbiVersion();
|
|
332
512
|
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function';
|
|
@@ -569,10 +749,10 @@ export class RealtimeEngine {
|
|
|
569
749
|
* Detect BPM from audio samples.
|
|
570
750
|
*
|
|
571
751
|
* @param samples - Audio samples (mono, float32)
|
|
572
|
-
* @param sampleRate - Sample rate in Hz
|
|
752
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
573
753
|
* @returns Detected BPM
|
|
574
754
|
*/
|
|
575
|
-
export function detectBpm(samples: Float32Array, sampleRate
|
|
755
|
+
export function detectBpm(samples: Float32Array, sampleRate = 22050): number {
|
|
576
756
|
if (!module) {
|
|
577
757
|
throw new Error('Module not initialized. Call init() first.');
|
|
578
758
|
}
|
|
@@ -583,12 +763,12 @@ export function detectBpm(samples: Float32Array, sampleRate: number): number {
|
|
|
583
763
|
* Detect musical key from audio samples.
|
|
584
764
|
*
|
|
585
765
|
* @param samples - Audio samples (mono, float32)
|
|
586
|
-
* @param sampleRate - Sample rate in Hz
|
|
766
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
587
767
|
* @returns Detected key
|
|
588
768
|
*/
|
|
589
769
|
export function detectKey(
|
|
590
770
|
samples: Float32Array,
|
|
591
|
-
sampleRate
|
|
771
|
+
sampleRate = 22050,
|
|
592
772
|
options: KeyDetectionOptions = {},
|
|
593
773
|
): Key {
|
|
594
774
|
if (!module) {
|
|
@@ -685,7 +865,7 @@ function keyProfileValue(profile: KeyDetectionOptions['profile'] | undefined): n
|
|
|
685
865
|
|
|
686
866
|
export function detectKeyCandidates(
|
|
687
867
|
samples: Float32Array,
|
|
688
|
-
sampleRate
|
|
868
|
+
sampleRate = 22050,
|
|
689
869
|
options: KeyDetectionOptions = {},
|
|
690
870
|
): KeyCandidate[] {
|
|
691
871
|
if (!module) {
|
|
@@ -711,10 +891,10 @@ export function detectKeyCandidates(
|
|
|
711
891
|
* Detect onset times from audio samples.
|
|
712
892
|
*
|
|
713
893
|
* @param samples - Audio samples (mono, float32)
|
|
714
|
-
* @param sampleRate - Sample rate in Hz
|
|
894
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
715
895
|
* @returns Array of onset times in seconds
|
|
716
896
|
*/
|
|
717
|
-
export function detectOnsets(samples: Float32Array, sampleRate
|
|
897
|
+
export function detectOnsets(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
718
898
|
if (!module) {
|
|
719
899
|
throw new Error('Module not initialized. Call init() first.');
|
|
720
900
|
}
|
|
@@ -725,10 +905,10 @@ export function detectOnsets(samples: Float32Array, sampleRate: number): Float32
|
|
|
725
905
|
* Detect beat times from audio samples.
|
|
726
906
|
*
|
|
727
907
|
* @param samples - Audio samples (mono, float32)
|
|
728
|
-
* @param sampleRate - Sample rate in Hz
|
|
908
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
729
909
|
* @returns Array of beat times in seconds
|
|
730
910
|
*/
|
|
731
|
-
export function detectBeats(samples: Float32Array, sampleRate
|
|
911
|
+
export function detectBeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
732
912
|
if (!module) {
|
|
733
913
|
throw new Error('Module not initialized. Call init() first.');
|
|
734
914
|
}
|
|
@@ -739,10 +919,10 @@ export function detectBeats(samples: Float32Array, sampleRate: number): Float32A
|
|
|
739
919
|
* Detect downbeat times from audio samples.
|
|
740
920
|
*
|
|
741
921
|
* @param samples - Audio samples (mono, float32)
|
|
742
|
-
* @param sampleRate - Sample rate in Hz
|
|
922
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
743
923
|
* @returns Array of downbeat times in seconds
|
|
744
924
|
*/
|
|
745
|
-
export function detectDownbeats(samples: Float32Array, sampleRate
|
|
925
|
+
export function detectDownbeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
746
926
|
if (!module) {
|
|
747
927
|
throw new Error('Module not initialized. Call init() first.');
|
|
748
928
|
}
|
|
@@ -767,13 +947,13 @@ function convertChordAnalysisResult(wasm: WasmChordAnalysisResult): ChordAnalysi
|
|
|
767
947
|
* Detect chords from audio samples.
|
|
768
948
|
*
|
|
769
949
|
* @param samples - Audio samples (mono, float32)
|
|
770
|
-
* @param sampleRate - Sample rate in Hz
|
|
950
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
771
951
|
* @param options - Optional chord detection settings
|
|
772
952
|
* @returns Detected chord segments
|
|
773
953
|
*/
|
|
774
954
|
export function detectChords(
|
|
775
955
|
samples: Float32Array,
|
|
776
|
-
sampleRate
|
|
956
|
+
sampleRate = 22050,
|
|
777
957
|
options: ChordDetectionOptions = {},
|
|
778
958
|
): ChordAnalysisResult {
|
|
779
959
|
if (!module) {
|
|
@@ -857,10 +1037,10 @@ function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult {
|
|
|
857
1037
|
* Perform complete music analysis.
|
|
858
1038
|
*
|
|
859
1039
|
* @param samples - Audio samples (mono, float32)
|
|
860
|
-
* @param sampleRate - Sample rate in Hz
|
|
1040
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
861
1041
|
* @returns Complete analysis result
|
|
862
1042
|
*/
|
|
863
|
-
export function analyze(samples: Float32Array, sampleRate
|
|
1043
|
+
export function analyze(samples: Float32Array, sampleRate = 22050): AnalysisResult {
|
|
864
1044
|
if (!module) {
|
|
865
1045
|
throw new Error('Module not initialized. Call init() first.');
|
|
866
1046
|
}
|
|
@@ -870,7 +1050,7 @@ export function analyze(samples: Float32Array, sampleRate: number): AnalysisResu
|
|
|
870
1050
|
|
|
871
1051
|
export function analyzeImpulseResponse(
|
|
872
1052
|
samples: Float32Array,
|
|
873
|
-
sampleRate
|
|
1053
|
+
sampleRate = 48000,
|
|
874
1054
|
nOctaveBands = 6,
|
|
875
1055
|
): AcousticResult {
|
|
876
1056
|
if (!module) {
|
|
@@ -886,7 +1066,7 @@ export function analyzeImpulseResponse(
|
|
|
886
1066
|
|
|
887
1067
|
export function detectAcoustic(
|
|
888
1068
|
samples: Float32Array,
|
|
889
|
-
sampleRate
|
|
1069
|
+
sampleRate = 48000,
|
|
890
1070
|
nOctaveBands = 6,
|
|
891
1071
|
nThirdOctaveSubbands = 24,
|
|
892
1072
|
minDecayDb = 30.0,
|
|
@@ -910,13 +1090,13 @@ export function detectAcoustic(
|
|
|
910
1090
|
* Perform complete music analysis with progress reporting.
|
|
911
1091
|
*
|
|
912
1092
|
* @param samples - Audio samples (mono, float32)
|
|
913
|
-
* @param sampleRate - Sample rate in Hz
|
|
1093
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
914
1094
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
915
1095
|
* @returns Complete analysis result
|
|
916
1096
|
*/
|
|
917
1097
|
export function analyzeWithProgress(
|
|
918
1098
|
samples: Float32Array,
|
|
919
|
-
sampleRate
|
|
1099
|
+
sampleRate = 22050,
|
|
920
1100
|
onProgress: ProgressCallback,
|
|
921
1101
|
): AnalysisResult {
|
|
922
1102
|
if (!module) {
|
|
@@ -926,6 +1106,154 @@ export function analyzeWithProgress(
|
|
|
926
1106
|
return convertAnalysisResult(result);
|
|
927
1107
|
}
|
|
928
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
|
+
|
|
929
1257
|
// ============================================================================
|
|
930
1258
|
// Effects
|
|
931
1259
|
// ============================================================================
|
|
@@ -934,14 +1262,14 @@ export function analyzeWithProgress(
|
|
|
934
1262
|
* Perform Harmonic-Percussive Source Separation (HPSS).
|
|
935
1263
|
*
|
|
936
1264
|
* @param samples - Audio samples (mono, float32)
|
|
937
|
-
* @param sampleRate - Sample rate in Hz
|
|
1265
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
938
1266
|
* @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
|
|
939
1267
|
* @param kernelPercussive - Vertical median filter size for percussive (default: 31)
|
|
940
1268
|
* @returns Separated harmonic and percussive components
|
|
941
1269
|
*/
|
|
942
1270
|
export function hpss(
|
|
943
1271
|
samples: Float32Array,
|
|
944
|
-
sampleRate
|
|
1272
|
+
sampleRate = 22050,
|
|
945
1273
|
kernelHarmonic = 31,
|
|
946
1274
|
kernelPercussive = 31,
|
|
947
1275
|
): HpssResult {
|
|
@@ -1024,9 +1352,9 @@ export function pitchShift(
|
|
|
1024
1352
|
*/
|
|
1025
1353
|
export function pitchCorrectToMidi(
|
|
1026
1354
|
samples: Float32Array,
|
|
1027
|
-
sampleRate
|
|
1028
|
-
currentMidi
|
|
1029
|
-
targetMidi
|
|
1355
|
+
sampleRate = 22050,
|
|
1356
|
+
currentMidi = 69.0,
|
|
1357
|
+
targetMidi = 69.0,
|
|
1030
1358
|
): Float32Array {
|
|
1031
1359
|
if (!module) {
|
|
1032
1360
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1046,10 +1374,10 @@ export function pitchCorrectToMidi(
|
|
|
1046
1374
|
*/
|
|
1047
1375
|
export function noteStretch(
|
|
1048
1376
|
samples: Float32Array,
|
|
1049
|
-
sampleRate
|
|
1050
|
-
onsetSample
|
|
1051
|
-
offsetSample
|
|
1052
|
-
stretchRatio
|
|
1377
|
+
sampleRate = 22050,
|
|
1378
|
+
onsetSample = 0,
|
|
1379
|
+
offsetSample = 0,
|
|
1380
|
+
stretchRatio = 1.0,
|
|
1053
1381
|
): Float32Array {
|
|
1054
1382
|
if (!module) {
|
|
1055
1383
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1068,13 +1396,15 @@ export function noteStretch(
|
|
|
1068
1396
|
*/
|
|
1069
1397
|
export function voiceChange(
|
|
1070
1398
|
samples: Float32Array,
|
|
1071
|
-
sampleRate
|
|
1072
|
-
pitchSemitones
|
|
1073
|
-
formantFactor
|
|
1399
|
+
sampleRate = 22050,
|
|
1400
|
+
pitchSemitones = 0.0,
|
|
1401
|
+
formantFactor = 1.0,
|
|
1402
|
+
options: ValidateOptions = {},
|
|
1074
1403
|
): Float32Array {
|
|
1075
1404
|
if (!module) {
|
|
1076
1405
|
throw new Error('Module not initialized. Call init() first.');
|
|
1077
1406
|
}
|
|
1407
|
+
assertSamples('voiceChange', samples, options.validate !== false);
|
|
1078
1408
|
return module.voiceChange(samples, sampleRate, pitchSemitones, formantFactor);
|
|
1079
1409
|
}
|
|
1080
1410
|
|
|
@@ -1097,7 +1427,7 @@ export function normalize(samples: Float32Array, sampleRate: number, targetDb =
|
|
|
1097
1427
|
* Apply mastering loudness normalization with a true-peak ceiling.
|
|
1098
1428
|
*
|
|
1099
1429
|
* @param samples - Audio samples (mono, float32)
|
|
1100
|
-
* @param sampleRate - Sample rate in Hz
|
|
1430
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1101
1431
|
* @param targetLufs - Target integrated LUFS (default: -14)
|
|
1102
1432
|
* @param ceilingDb - True/sample peak ceiling in dBFS (default: -1)
|
|
1103
1433
|
* @param truePeakOversample - Oversampling factor used for peak estimation
|
|
@@ -1105,7 +1435,7 @@ export function normalize(samples: Float32Array, sampleRate: number, targetDb =
|
|
|
1105
1435
|
*/
|
|
1106
1436
|
export function mastering(
|
|
1107
1437
|
samples: Float32Array,
|
|
1108
|
-
sampleRate
|
|
1438
|
+
sampleRate = 22050,
|
|
1109
1439
|
targetLufs = -14.0,
|
|
1110
1440
|
ceilingDb = -1.0,
|
|
1111
1441
|
truePeakOversample = 4,
|
|
@@ -1147,7 +1477,7 @@ export function masteringStereoAnalysisNames(): StereoAnalysis[] {
|
|
|
1147
1477
|
export function masteringProcess(
|
|
1148
1478
|
processorName: SoloProcessor,
|
|
1149
1479
|
samples: Float32Array,
|
|
1150
|
-
sampleRate
|
|
1480
|
+
sampleRate = 22050,
|
|
1151
1481
|
params: MasteringProcessorParams = {},
|
|
1152
1482
|
): MasteringResult {
|
|
1153
1483
|
if (!module) {
|
|
@@ -1160,7 +1490,7 @@ export function masteringProcessStereo(
|
|
|
1160
1490
|
processorName: SoloProcessor,
|
|
1161
1491
|
left: Float32Array,
|
|
1162
1492
|
right: Float32Array,
|
|
1163
|
-
sampleRate
|
|
1493
|
+
sampleRate = 22050,
|
|
1164
1494
|
params: MasteringProcessorParams = {},
|
|
1165
1495
|
): MasteringStereoResult {
|
|
1166
1496
|
if (!module) {
|
|
@@ -1176,7 +1506,7 @@ export function masteringPairProcess(
|
|
|
1176
1506
|
processorName: PairProcessor,
|
|
1177
1507
|
source: Float32Array,
|
|
1178
1508
|
reference: Float32Array,
|
|
1179
|
-
sampleRate
|
|
1509
|
+
sampleRate = 22050,
|
|
1180
1510
|
params: MasteringProcessorParams = {},
|
|
1181
1511
|
): MasteringResult {
|
|
1182
1512
|
if (!module) {
|
|
@@ -1189,7 +1519,7 @@ export function masteringPairAnalyze(
|
|
|
1189
1519
|
analysisName: PairAnalysis,
|
|
1190
1520
|
source: Float32Array,
|
|
1191
1521
|
reference: Float32Array,
|
|
1192
|
-
sampleRate
|
|
1522
|
+
sampleRate = 22050,
|
|
1193
1523
|
params: MasteringProcessorParams = {},
|
|
1194
1524
|
): string {
|
|
1195
1525
|
if (!module) {
|
|
@@ -1202,7 +1532,7 @@ export function masteringStereoAnalyze(
|
|
|
1202
1532
|
analysisName: StereoAnalysis,
|
|
1203
1533
|
left: Float32Array,
|
|
1204
1534
|
right: Float32Array,
|
|
1205
|
-
sampleRate
|
|
1535
|
+
sampleRate = 22050,
|
|
1206
1536
|
params: MasteringProcessorParams = {},
|
|
1207
1537
|
): string {
|
|
1208
1538
|
if (!module) {
|
|
@@ -1213,7 +1543,7 @@ export function masteringStereoAnalyze(
|
|
|
1213
1543
|
|
|
1214
1544
|
export function masteringAssistantSuggest(
|
|
1215
1545
|
samples: Float32Array,
|
|
1216
|
-
sampleRate
|
|
1546
|
+
sampleRate = 22050,
|
|
1217
1547
|
params: MasteringProcessorParams = {},
|
|
1218
1548
|
): string {
|
|
1219
1549
|
if (!module) {
|
|
@@ -1224,7 +1554,7 @@ export function masteringAssistantSuggest(
|
|
|
1224
1554
|
|
|
1225
1555
|
export function masteringAudioProfile(
|
|
1226
1556
|
samples: Float32Array,
|
|
1227
|
-
sampleRate
|
|
1557
|
+
sampleRate = 22050,
|
|
1228
1558
|
params: MasteringProcessorParams = {},
|
|
1229
1559
|
): string {
|
|
1230
1560
|
if (!module) {
|
|
@@ -1235,7 +1565,7 @@ export function masteringAudioProfile(
|
|
|
1235
1565
|
|
|
1236
1566
|
export function masteringStreamingPreview(
|
|
1237
1567
|
samples: Float32Array,
|
|
1238
|
-
sampleRate
|
|
1568
|
+
sampleRate = 22050,
|
|
1239
1569
|
platforms: StreamingPlatform[] = [],
|
|
1240
1570
|
): string {
|
|
1241
1571
|
if (!module) {
|
|
@@ -1244,61 +1574,315 @@ export function masteringStreamingPreview(
|
|
|
1244
1574
|
return module.masteringStreamingPreview(samples, sampleRate, platforms);
|
|
1245
1575
|
}
|
|
1246
1576
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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(
|
|
1256
1614
|
samples: Float32Array,
|
|
1257
1615
|
sampleRate: number,
|
|
1258
|
-
|
|
1259
|
-
):
|
|
1260
|
-
|
|
1261
|
-
throw new Error('Module not initialized. Call init() first.');
|
|
1262
|
-
}
|
|
1263
|
-
return module.masteringChain(samples, sampleRate, config as Record<string, unknown>);
|
|
1616
|
+
options: DeclickOptions = {},
|
|
1617
|
+
): Float32Array {
|
|
1618
|
+
return requireModule().masteringRepairDeclick(samples, sampleRate, options);
|
|
1264
1619
|
}
|
|
1265
1620
|
|
|
1266
|
-
/**
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
* @param left - Left channel samples
|
|
1270
|
-
* @param right - Right channel samples
|
|
1271
|
-
* @param sampleRate - Sample rate in Hz
|
|
1272
|
-
* @param config - Chain stage configuration
|
|
1273
|
-
* @returns Processed stereo audio, loudness metadata, and applied stage names
|
|
1274
|
-
*/
|
|
1275
|
-
export function masteringChainStereo(
|
|
1276
|
-
left: Float32Array,
|
|
1277
|
-
right: Float32Array,
|
|
1621
|
+
/** Offline STFT-domain classical denoiser (LogMMSE / MMSE-STSA / SpectralSubtraction). */
|
|
1622
|
+
export function masteringRepairDenoiseClassical(
|
|
1623
|
+
samples: Float32Array,
|
|
1278
1624
|
sampleRate: number,
|
|
1279
|
-
|
|
1280
|
-
):
|
|
1281
|
-
|
|
1282
|
-
throw new Error('Module not initialized. Call init() first.');
|
|
1283
|
-
}
|
|
1284
|
-
if (left.length !== right.length) {
|
|
1285
|
-
throw new Error('Stereo channel lengths must match.');
|
|
1286
|
-
}
|
|
1287
|
-
return module.masteringChainStereo(left, right, sampleRate, config as Record<string, unknown>);
|
|
1625
|
+
options: DenoiseClassicalOptions = {},
|
|
1626
|
+
): Float32Array {
|
|
1627
|
+
return requireModule().masteringRepairDenoiseClassical(samples, sampleRate, options);
|
|
1288
1628
|
}
|
|
1289
1629
|
|
|
1290
|
-
/**
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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
|
|
1294
1855
|
* @param sampleRate - Sample rate in Hz
|
|
1295
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
|
|
1296
1880
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
1297
1881
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1298
1882
|
*/
|
|
1299
1883
|
export function masteringChainWithProgress(
|
|
1300
1884
|
samples: Float32Array,
|
|
1301
|
-
sampleRate
|
|
1885
|
+
sampleRate = 22050,
|
|
1302
1886
|
config: MasteringChainConfig,
|
|
1303
1887
|
onProgress: ProgressCallback,
|
|
1304
1888
|
): MasteringChainResult {
|
|
@@ -1326,7 +1910,7 @@ export function masteringChainWithProgress(
|
|
|
1326
1910
|
export function masteringChainStereoWithProgress(
|
|
1327
1911
|
left: Float32Array,
|
|
1328
1912
|
right: Float32Array,
|
|
1329
|
-
sampleRate
|
|
1913
|
+
sampleRate = 22050,
|
|
1330
1914
|
config: MasteringChainConfig,
|
|
1331
1915
|
onProgress: ProgressCallback,
|
|
1332
1916
|
): MasteringStereoChainResult {
|
|
@@ -1361,14 +1945,14 @@ export function masteringPresetNames(): MasteringPreset[] {
|
|
|
1361
1945
|
* Apply a named mastering preset chain to mono audio.
|
|
1362
1946
|
*
|
|
1363
1947
|
* @param samples - Audio samples (mono, float32)
|
|
1364
|
-
* @param sampleRate - Sample rate in Hz
|
|
1948
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1365
1949
|
* @param presetName - Preset identifier from {@link masteringPresetNames}
|
|
1366
1950
|
* @param overrides - Optional flat overrides (dot-notation, e.g. `'loudness.targetLufs'`) applied on top of the preset. Pass `null` for preset defaults.
|
|
1367
1951
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1368
1952
|
*/
|
|
1369
1953
|
export function masterAudio(
|
|
1370
1954
|
samples: Float32Array,
|
|
1371
|
-
sampleRate
|
|
1955
|
+
sampleRate = 22050,
|
|
1372
1956
|
presetName: MasteringPreset,
|
|
1373
1957
|
overrides: Record<string, number | boolean> | null = null,
|
|
1374
1958
|
): MasteringChainResult {
|
|
@@ -1391,7 +1975,7 @@ export function masterAudio(
|
|
|
1391
1975
|
export function masterAudioStereo(
|
|
1392
1976
|
left: Float32Array,
|
|
1393
1977
|
right: Float32Array,
|
|
1394
|
-
sampleRate
|
|
1978
|
+
sampleRate = 22050,
|
|
1395
1979
|
presetName: MasteringPreset,
|
|
1396
1980
|
overrides: Record<string, number | boolean> | null = null,
|
|
1397
1981
|
): MasteringStereoChainResult {
|
|
@@ -1404,6 +1988,50 @@ export function masterAudioStereo(
|
|
|
1404
1988
|
return module.masterAudioStereo(presetName, left, right, sampleRate, overrides);
|
|
1405
1989
|
}
|
|
1406
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
|
+
|
|
1407
2035
|
export function mixingScenePresetNames(): string[] {
|
|
1408
2036
|
if (!module) {
|
|
1409
2037
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1416,14 +2044,14 @@ export function mixingScenePresetNames(): string[] {
|
|
|
1416
2044
|
* name shared with the Node and Python bindings; the returned JSON loads
|
|
1417
2045
|
* directly into a {@link Mixer} via {@link Mixer.fromSceneJson}.
|
|
1418
2046
|
*
|
|
1419
|
-
* @param
|
|
2047
|
+
* @param presetName - Preset name (see {@link mixingScenePresetNames})
|
|
1420
2048
|
* @returns Scene JSON string
|
|
1421
2049
|
*/
|
|
1422
|
-
export function mixingScenePresetJson(
|
|
2050
|
+
export function mixingScenePresetJson(presetName: string): string {
|
|
1423
2051
|
if (!module) {
|
|
1424
2052
|
throw new Error('Module not initialized. Call init() first.');
|
|
1425
2053
|
}
|
|
1426
|
-
return module.mixingScenePresetJson(
|
|
2054
|
+
return module.mixingScenePresetJson(presetName);
|
|
1427
2055
|
}
|
|
1428
2056
|
|
|
1429
2057
|
export function mixStereo(
|
|
@@ -1472,7 +2100,7 @@ export function mixStereo(
|
|
|
1472
2100
|
* ```
|
|
1473
2101
|
*/
|
|
1474
2102
|
export class StreamingMasteringChain {
|
|
1475
|
-
private chain: import('./
|
|
2103
|
+
private chain: import('./sonare.js').WasmStreamingMasteringChain;
|
|
1476
2104
|
|
|
1477
2105
|
constructor(config: MasteringChainConfig) {
|
|
1478
2106
|
if (!module) {
|
|
@@ -1559,7 +2187,7 @@ export class StreamingMasteringChain {
|
|
|
1559
2187
|
* ```
|
|
1560
2188
|
*/
|
|
1561
2189
|
export class StreamingEqualizer {
|
|
1562
|
-
private eq: import('./
|
|
2190
|
+
private eq: import('./sonare.js').WasmStreamingEqualizer;
|
|
1563
2191
|
|
|
1564
2192
|
constructor(config: StreamingEqualizerConfig = {}) {
|
|
1565
2193
|
if (!module) {
|
|
@@ -1697,7 +2325,7 @@ export class StreamingEqualizer {
|
|
|
1697
2325
|
* the underlying WASM object.
|
|
1698
2326
|
*/
|
|
1699
2327
|
export class StreamingRetune {
|
|
1700
|
-
private retune: import('./
|
|
2328
|
+
private retune: import('./sonare.js').WasmStreamingRetune;
|
|
1701
2329
|
|
|
1702
2330
|
constructor(config: StreamingRetuneConfig = {}) {
|
|
1703
2331
|
if (!module) {
|
|
@@ -1749,32 +2377,207 @@ export class StreamingRetune {
|
|
|
1749
2377
|
}
|
|
1750
2378
|
|
|
1751
2379
|
// ============================================================================
|
|
1752
|
-
//
|
|
2380
|
+
// RealtimeVoiceChanger Class
|
|
1753
2381
|
// ============================================================================
|
|
1754
2382
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
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[] {
|
|
1768
2549
|
if (!module) {
|
|
1769
2550
|
throw new Error('Module not initialized. Call init() first.');
|
|
1770
2551
|
}
|
|
1771
|
-
return module.
|
|
2552
|
+
return module.realtimeVoiceChangerPresetNames() as VoicePresetId[];
|
|
1772
2553
|
}
|
|
1773
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
|
+
|
|
1774
2577
|
/**
|
|
1775
2578
|
* Persistent, scene-based stereo mixer.
|
|
1776
2579
|
*
|
|
1777
|
-
* Build one from a scene JSON string (e.g. {@link
|
|
2580
|
+
* Build one from a scene JSON string (e.g. {@link mixingScenePresetJson} or a
|
|
1778
2581
|
* hand-authored scene), then feed per-strip stereo blocks through
|
|
1779
2582
|
* {@link processStereo} to get the routed stereo master. Strips, sends, buses,
|
|
1780
2583
|
* and inserts are described entirely by the scene; the routing graph is
|
|
@@ -1786,7 +2589,7 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1786
2589
|
*
|
|
1787
2590
|
* @example
|
|
1788
2591
|
* ```typescript
|
|
1789
|
-
* const mixer = Mixer.fromSceneJson(
|
|
2592
|
+
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('basicStereo'), 48000, 512);
|
|
1790
2593
|
* try {
|
|
1791
2594
|
* const out = mixer.processStereo([stripL], [stripR]);
|
|
1792
2595
|
* } finally {
|
|
@@ -1795,9 +2598,9 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1795
2598
|
* ```
|
|
1796
2599
|
*/
|
|
1797
2600
|
export class Mixer {
|
|
1798
|
-
private mixer: import('./
|
|
2601
|
+
private mixer: import('./sonare.js').WasmMixer;
|
|
1799
2602
|
|
|
1800
|
-
private constructor(mixer: import('./
|
|
2603
|
+
private constructor(mixer: import('./sonare.js').WasmMixer) {
|
|
1801
2604
|
this.mixer = mixer;
|
|
1802
2605
|
}
|
|
1803
2606
|
|
|
@@ -1966,6 +2769,31 @@ export class Mixer {
|
|
|
1966
2769
|
return this.mixer.vcaGroupCount();
|
|
1967
2770
|
}
|
|
1968
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
|
+
|
|
1969
2797
|
/**
|
|
1970
2798
|
* Set a strip's solo state. Takes effect on the next process without a
|
|
1971
2799
|
* graph recompile.
|
|
@@ -2179,14 +3007,14 @@ export function trim(samples: Float32Array, sampleRate: number, thresholdDb = -6
|
|
|
2179
3007
|
* Compute Short-Time Fourier Transform (STFT).
|
|
2180
3008
|
*
|
|
2181
3009
|
* @param samples - Audio samples (mono, float32)
|
|
2182
|
-
* @param sampleRate - Sample rate in Hz
|
|
3010
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2183
3011
|
* @param nFft - FFT size (default: 2048)
|
|
2184
3012
|
* @param hopLength - Hop length (default: 512)
|
|
2185
3013
|
* @returns STFT result with magnitude and power spectrograms
|
|
2186
3014
|
*/
|
|
2187
3015
|
export function stft(
|
|
2188
3016
|
samples: Float32Array,
|
|
2189
|
-
sampleRate
|
|
3017
|
+
sampleRate = 22050,
|
|
2190
3018
|
nFft = 2048,
|
|
2191
3019
|
hopLength = 512,
|
|
2192
3020
|
): StftResult {
|
|
@@ -2200,14 +3028,14 @@ export function stft(
|
|
|
2200
3028
|
* Compute STFT and return magnitude in decibels.
|
|
2201
3029
|
*
|
|
2202
3030
|
* @param samples - Audio samples (mono, float32)
|
|
2203
|
-
* @param sampleRate - Sample rate in Hz
|
|
3031
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2204
3032
|
* @param nFft - FFT size (default: 2048)
|
|
2205
3033
|
* @param hopLength - Hop length (default: 512)
|
|
2206
3034
|
* @returns STFT result with dB values
|
|
2207
3035
|
*/
|
|
2208
3036
|
export function stftDb(
|
|
2209
3037
|
samples: Float32Array,
|
|
2210
|
-
sampleRate
|
|
3038
|
+
sampleRate = 22050,
|
|
2211
3039
|
nFft = 2048,
|
|
2212
3040
|
hopLength = 512,
|
|
2213
3041
|
): { nBins: number; nFrames: number; db: Float32Array } {
|
|
@@ -2225,7 +3053,7 @@ export function stftDb(
|
|
|
2225
3053
|
* Compute Mel spectrogram.
|
|
2226
3054
|
*
|
|
2227
3055
|
* @param samples - Audio samples (mono, float32)
|
|
2228
|
-
* @param sampleRate - Sample rate in Hz
|
|
3056
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2229
3057
|
* @param nFft - FFT size (default: 2048)
|
|
2230
3058
|
* @param hopLength - Hop length (default: 512)
|
|
2231
3059
|
* @param nMels - Number of Mel bands (default: 128)
|
|
@@ -2233,7 +3061,7 @@ export function stftDb(
|
|
|
2233
3061
|
*/
|
|
2234
3062
|
export function melSpectrogram(
|
|
2235
3063
|
samples: Float32Array,
|
|
2236
|
-
sampleRate
|
|
3064
|
+
sampleRate = 22050,
|
|
2237
3065
|
nFft = 2048,
|
|
2238
3066
|
hopLength = 512,
|
|
2239
3067
|
nMels = 128,
|
|
@@ -2248,20 +3076,20 @@ export function melSpectrogram(
|
|
|
2248
3076
|
* Compute MFCC (Mel-Frequency Cepstral Coefficients).
|
|
2249
3077
|
*
|
|
2250
3078
|
* @param samples - Audio samples (mono, float32)
|
|
2251
|
-
* @param sampleRate - Sample rate in Hz
|
|
3079
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2252
3080
|
* @param nFft - FFT size (default: 2048)
|
|
2253
3081
|
* @param hopLength - Hop length (default: 512)
|
|
2254
3082
|
* @param nMels - Number of Mel bands (default: 128)
|
|
2255
|
-
* @param nMfcc - Number of MFCC coefficients (default:
|
|
3083
|
+
* @param nMfcc - Number of MFCC coefficients (default: 20)
|
|
2256
3084
|
* @returns MFCC result
|
|
2257
3085
|
*/
|
|
2258
3086
|
export function mfcc(
|
|
2259
3087
|
samples: Float32Array,
|
|
2260
|
-
sampleRate
|
|
3088
|
+
sampleRate = 22050,
|
|
2261
3089
|
nFft = 2048,
|
|
2262
3090
|
hopLength = 512,
|
|
2263
3091
|
nMels = 128,
|
|
2264
|
-
nMfcc =
|
|
3092
|
+
nMfcc = 20,
|
|
2265
3093
|
): MfccResult {
|
|
2266
3094
|
if (!module) {
|
|
2267
3095
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2282,23 +3110,23 @@ export function mfcc(
|
|
|
2282
3110
|
* @param nFrames - Number of time frames
|
|
2283
3111
|
* @param sampleRate - Sample rate in Hz
|
|
2284
3112
|
* @param nFft - FFT size (default: 2048)
|
|
2285
|
-
* @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)
|
|
2286
3115
|
* @returns STFT power spectrogram result
|
|
2287
3116
|
*/
|
|
2288
3117
|
export function melToStft(
|
|
2289
3118
|
melPower: Float32Array,
|
|
2290
3119
|
nMels: number,
|
|
2291
3120
|
nFrames: number,
|
|
2292
|
-
sampleRate
|
|
3121
|
+
sampleRate = 22050,
|
|
2293
3122
|
nFft = 2048,
|
|
2294
|
-
hopLength = 512,
|
|
2295
3123
|
fmin = 0,
|
|
2296
3124
|
fmax = 0,
|
|
2297
3125
|
): StftPowerResult {
|
|
2298
3126
|
if (!module) {
|
|
2299
3127
|
throw new Error('Module not initialized. Call init() first.');
|
|
2300
3128
|
}
|
|
2301
|
-
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft,
|
|
3129
|
+
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft, fmin, fmax);
|
|
2302
3130
|
}
|
|
2303
3131
|
|
|
2304
3132
|
/**
|
|
@@ -2311,6 +3139,8 @@ export function melToStft(
|
|
|
2311
3139
|
* @param sampleRate - Sample rate in Hz
|
|
2312
3140
|
* @param nFft - FFT size (default: 2048)
|
|
2313
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)
|
|
2314
3144
|
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2315
3145
|
* @returns Reconstructed audio samples (mono, float32)
|
|
2316
3146
|
*/
|
|
@@ -2318,12 +3148,12 @@ export function melToAudio(
|
|
|
2318
3148
|
melPower: Float32Array,
|
|
2319
3149
|
nMels: number,
|
|
2320
3150
|
nFrames: number,
|
|
2321
|
-
sampleRate
|
|
3151
|
+
sampleRate = 22050,
|
|
2322
3152
|
nFft = 2048,
|
|
2323
3153
|
hopLength = 512,
|
|
2324
|
-
nIter = 32,
|
|
2325
3154
|
fmin = 0,
|
|
2326
3155
|
fmax = 0,
|
|
3156
|
+
nIter = 32,
|
|
2327
3157
|
): Float32Array {
|
|
2328
3158
|
if (!module) {
|
|
2329
3159
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2335,9 +3165,9 @@ export function melToAudio(
|
|
|
2335
3165
|
sampleRate,
|
|
2336
3166
|
nFft,
|
|
2337
3167
|
hopLength,
|
|
2338
|
-
nIter,
|
|
2339
3168
|
fmin,
|
|
2340
3169
|
fmax,
|
|
3170
|
+
nIter,
|
|
2341
3171
|
);
|
|
2342
3172
|
}
|
|
2343
3173
|
|
|
@@ -2360,114 +3190,318 @@ export function mfccToMel(
|
|
|
2360
3190
|
if (!module) {
|
|
2361
3191
|
throw new Error('Module not initialized. Call init() first.');
|
|
2362
3192
|
}
|
|
2363
|
-
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);
|
|
2364
3434
|
}
|
|
2365
3435
|
|
|
2366
3436
|
/**
|
|
2367
|
-
*
|
|
2368
|
-
* `feature::mfcc_to_audio`.
|
|
2369
|
-
*
|
|
2370
|
-
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
2371
|
-
* @param nMfcc - Number of MFCC coefficients
|
|
2372
|
-
* @param nFrames - Number of time frames
|
|
2373
|
-
* @param nMels - Number of Mel bins (default: 128)
|
|
2374
|
-
* @param sampleRate - Sample rate in Hz
|
|
2375
|
-
* @param nFft - FFT size (default: 2048)
|
|
2376
|
-
* @param hopLength - Hop length (default: 512)
|
|
2377
|
-
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2378
|
-
* @returns Reconstructed audio samples (mono, float32)
|
|
3437
|
+
* Phase-vocoder time-scale modification (rate > 1 faster, < 1 slower).
|
|
2379
3438
|
*/
|
|
2380
|
-
export function
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
nMels: number,
|
|
2385
|
-
sampleRate: number,
|
|
3439
|
+
export function phaseVocoder(
|
|
3440
|
+
samples: Float32Array,
|
|
3441
|
+
rate: number,
|
|
3442
|
+
sampleRate = 22050,
|
|
2386
3443
|
nFft = 2048,
|
|
2387
3444
|
hopLength = 512,
|
|
2388
|
-
nIter = 32,
|
|
2389
|
-
fmin = 0,
|
|
2390
|
-
fmax = 0,
|
|
2391
3445
|
): Float32Array {
|
|
2392
3446
|
if (!module) {
|
|
2393
3447
|
throw new Error('Module not initialized. Call init() first.');
|
|
2394
3448
|
}
|
|
2395
|
-
return module.
|
|
2396
|
-
mfccCoefficients,
|
|
2397
|
-
nMfcc,
|
|
2398
|
-
nFrames,
|
|
2399
|
-
nMels,
|
|
2400
|
-
sampleRate,
|
|
2401
|
-
nFft,
|
|
2402
|
-
hopLength,
|
|
2403
|
-
nIter,
|
|
2404
|
-
fmin,
|
|
2405
|
-
fmax,
|
|
2406
|
-
);
|
|
3449
|
+
return module.phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
|
|
2407
3450
|
}
|
|
2408
3451
|
|
|
2409
|
-
// ============================================================================
|
|
2410
|
-
// Features - Chroma
|
|
2411
|
-
// ============================================================================
|
|
2412
|
-
|
|
2413
3452
|
/**
|
|
2414
|
-
*
|
|
2415
|
-
*
|
|
2416
|
-
* @param samples - Audio samples (mono, float32)
|
|
2417
|
-
* @param sampleRate - Sample rate in Hz
|
|
2418
|
-
* @param nFft - FFT size (default: 2048)
|
|
2419
|
-
* @param hopLength - Hop length (default: 512)
|
|
2420
|
-
* @returns Chroma features result
|
|
3453
|
+
* HPSS into harmonic / percussive / residual signals.
|
|
2421
3454
|
*/
|
|
2422
|
-
export function
|
|
3455
|
+
export function hpssWithResidual(
|
|
2423
3456
|
samples: Float32Array,
|
|
2424
|
-
sampleRate
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
):
|
|
3457
|
+
sampleRate = 22050,
|
|
3458
|
+
kernelHarmonic = 31,
|
|
3459
|
+
kernelPercussive = 31,
|
|
3460
|
+
): WasmHpssWithResidualResult {
|
|
2428
3461
|
if (!module) {
|
|
2429
3462
|
throw new Error('Module not initialized. Call init() first.');
|
|
2430
3463
|
}
|
|
2431
|
-
return module.
|
|
3464
|
+
return module.hpssWithResidual(samples, sampleRate, kernelHarmonic, kernelPercussive);
|
|
2432
3465
|
}
|
|
2433
3466
|
|
|
2434
|
-
// ============================================================================
|
|
2435
|
-
// Features - Spectral
|
|
2436
|
-
// ============================================================================
|
|
2437
|
-
|
|
2438
3467
|
/**
|
|
2439
|
-
*
|
|
2440
|
-
*
|
|
2441
|
-
*
|
|
2442
|
-
* @param sampleRate - Sample rate in Hz
|
|
2443
|
-
* @param nFft - FFT size (default: 2048)
|
|
2444
|
-
* @param hopLength - Hop length (default: 512)
|
|
2445
|
-
* @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`.
|
|
2446
3471
|
*/
|
|
2447
|
-
export function
|
|
3472
|
+
export function lufsInterleaved(
|
|
2448
3473
|
samples: Float32Array,
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
): Float32Array {
|
|
3474
|
+
channels: number,
|
|
3475
|
+
sampleRate = 22050,
|
|
3476
|
+
): WasmLufsResult {
|
|
2453
3477
|
if (!module) {
|
|
2454
3478
|
throw new Error('Module not initialized. Call init() first.');
|
|
2455
3479
|
}
|
|
2456
|
-
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);
|
|
2457
3491
|
}
|
|
2458
3492
|
|
|
2459
3493
|
/**
|
|
2460
3494
|
* Compute spectral bandwidth.
|
|
2461
3495
|
*
|
|
2462
3496
|
* @param samples - Audio samples (mono, float32)
|
|
2463
|
-
* @param sampleRate - Sample rate in Hz
|
|
3497
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2464
3498
|
* @param nFft - FFT size (default: 2048)
|
|
2465
3499
|
* @param hopLength - Hop length (default: 512)
|
|
2466
3500
|
* @returns Spectral bandwidth in Hz for each frame
|
|
2467
3501
|
*/
|
|
2468
3502
|
export function spectralBandwidth(
|
|
2469
3503
|
samples: Float32Array,
|
|
2470
|
-
sampleRate
|
|
3504
|
+
sampleRate = 22050,
|
|
2471
3505
|
nFft = 2048,
|
|
2472
3506
|
hopLength = 512,
|
|
2473
3507
|
): Float32Array {
|
|
@@ -2481,7 +3515,7 @@ export function spectralBandwidth(
|
|
|
2481
3515
|
* Compute spectral rolloff frequency.
|
|
2482
3516
|
*
|
|
2483
3517
|
* @param samples - Audio samples (mono, float32)
|
|
2484
|
-
* @param sampleRate - Sample rate in Hz
|
|
3518
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2485
3519
|
* @param nFft - FFT size (default: 2048)
|
|
2486
3520
|
* @param hopLength - Hop length (default: 512)
|
|
2487
3521
|
* @param rollPercent - Percentage threshold (default: 0.85)
|
|
@@ -2489,7 +3523,7 @@ export function spectralBandwidth(
|
|
|
2489
3523
|
*/
|
|
2490
3524
|
export function spectralRolloff(
|
|
2491
3525
|
samples: Float32Array,
|
|
2492
|
-
sampleRate
|
|
3526
|
+
sampleRate = 22050,
|
|
2493
3527
|
nFft = 2048,
|
|
2494
3528
|
hopLength = 512,
|
|
2495
3529
|
rollPercent = 0.85,
|
|
@@ -2504,14 +3538,14 @@ export function spectralRolloff(
|
|
|
2504
3538
|
* Compute spectral flatness.
|
|
2505
3539
|
*
|
|
2506
3540
|
* @param samples - Audio samples (mono, float32)
|
|
2507
|
-
* @param sampleRate - Sample rate in Hz
|
|
3541
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2508
3542
|
* @param nFft - FFT size (default: 2048)
|
|
2509
3543
|
* @param hopLength - Hop length (default: 512)
|
|
2510
3544
|
* @returns Spectral flatness for each frame (0 = tonal, 1 = noise-like)
|
|
2511
3545
|
*/
|
|
2512
3546
|
export function spectralFlatness(
|
|
2513
3547
|
samples: Float32Array,
|
|
2514
|
-
sampleRate
|
|
3548
|
+
sampleRate = 22050,
|
|
2515
3549
|
nFft = 2048,
|
|
2516
3550
|
hopLength = 512,
|
|
2517
3551
|
): Float32Array {
|
|
@@ -2525,14 +3559,14 @@ export function spectralFlatness(
|
|
|
2525
3559
|
* Compute zero crossing rate.
|
|
2526
3560
|
*
|
|
2527
3561
|
* @param samples - Audio samples (mono, float32)
|
|
2528
|
-
* @param sampleRate - Sample rate in Hz
|
|
3562
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2529
3563
|
* @param frameLength - Frame length (default: 2048)
|
|
2530
3564
|
* @param hopLength - Hop length (default: 512)
|
|
2531
3565
|
* @returns Zero crossing rate for each frame
|
|
2532
3566
|
*/
|
|
2533
3567
|
export function zeroCrossingRate(
|
|
2534
3568
|
samples: Float32Array,
|
|
2535
|
-
sampleRate
|
|
3569
|
+
sampleRate = 22050,
|
|
2536
3570
|
frameLength = 2048,
|
|
2537
3571
|
hopLength = 512,
|
|
2538
3572
|
): Float32Array {
|
|
@@ -2546,14 +3580,14 @@ export function zeroCrossingRate(
|
|
|
2546
3580
|
* Compute RMS energy.
|
|
2547
3581
|
*
|
|
2548
3582
|
* @param samples - Audio samples (mono, float32)
|
|
2549
|
-
* @param sampleRate - Sample rate in Hz
|
|
3583
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2550
3584
|
* @param frameLength - Frame length (default: 2048)
|
|
2551
3585
|
* @param hopLength - Hop length (default: 512)
|
|
2552
3586
|
* @returns RMS energy for each frame
|
|
2553
3587
|
*/
|
|
2554
3588
|
export function rmsEnergy(
|
|
2555
3589
|
samples: Float32Array,
|
|
2556
|
-
sampleRate
|
|
3590
|
+
sampleRate = 22050,
|
|
2557
3591
|
frameLength = 2048,
|
|
2558
3592
|
hopLength = 512,
|
|
2559
3593
|
): Float32Array {
|
|
@@ -2571,54 +3605,76 @@ export function rmsEnergy(
|
|
|
2571
3605
|
* Detect pitch using YIN algorithm.
|
|
2572
3606
|
*
|
|
2573
3607
|
* @param samples - Audio samples (mono, float32)
|
|
2574
|
-
* @param sampleRate - Sample rate in Hz
|
|
3608
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2575
3609
|
* @param frameLength - Frame length (default: 2048)
|
|
2576
3610
|
* @param hopLength - Hop length (default: 512)
|
|
2577
3611
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2578
3612
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2579
3613
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3614
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2580
3615
|
* @returns Pitch detection result
|
|
2581
3616
|
*/
|
|
2582
3617
|
export function pitchYin(
|
|
2583
3618
|
samples: Float32Array,
|
|
2584
|
-
sampleRate
|
|
3619
|
+
sampleRate = 22050,
|
|
2585
3620
|
frameLength = 2048,
|
|
2586
3621
|
hopLength = 512,
|
|
2587
3622
|
fmin = 65.0,
|
|
2588
3623
|
fmax = 2093.0,
|
|
2589
3624
|
threshold = 0.3,
|
|
3625
|
+
fillNa = false,
|
|
2590
3626
|
): PitchResult {
|
|
2591
3627
|
if (!module) {
|
|
2592
3628
|
throw new Error('Module not initialized. Call init() first.');
|
|
2593
3629
|
}
|
|
2594
|
-
return module.pitchYin(
|
|
3630
|
+
return module.pitchYin(
|
|
3631
|
+
samples,
|
|
3632
|
+
sampleRate,
|
|
3633
|
+
frameLength,
|
|
3634
|
+
hopLength,
|
|
3635
|
+
fmin,
|
|
3636
|
+
fmax,
|
|
3637
|
+
threshold,
|
|
3638
|
+
fillNa,
|
|
3639
|
+
);
|
|
2595
3640
|
}
|
|
2596
3641
|
|
|
2597
3642
|
/**
|
|
2598
3643
|
* Detect pitch using pYIN algorithm (probabilistic YIN with HMM smoothing).
|
|
2599
3644
|
*
|
|
2600
3645
|
* @param samples - Audio samples (mono, float32)
|
|
2601
|
-
* @param sampleRate - Sample rate in Hz
|
|
3646
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2602
3647
|
* @param frameLength - Frame length (default: 2048)
|
|
2603
3648
|
* @param hopLength - Hop length (default: 512)
|
|
2604
3649
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2605
3650
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2606
3651
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3652
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2607
3653
|
* @returns Pitch detection result
|
|
2608
3654
|
*/
|
|
2609
3655
|
export function pitchPyin(
|
|
2610
3656
|
samples: Float32Array,
|
|
2611
|
-
sampleRate
|
|
3657
|
+
sampleRate = 22050,
|
|
2612
3658
|
frameLength = 2048,
|
|
2613
3659
|
hopLength = 512,
|
|
2614
3660
|
fmin = 65.0,
|
|
2615
3661
|
fmax = 2093.0,
|
|
2616
3662
|
threshold = 0.3,
|
|
3663
|
+
fillNa = false,
|
|
2617
3664
|
): PitchResult {
|
|
2618
3665
|
if (!module) {
|
|
2619
3666
|
throw new Error('Module not initialized. Call init() first.');
|
|
2620
3667
|
}
|
|
2621
|
-
return module.pitchPyin(
|
|
3668
|
+
return module.pitchPyin(
|
|
3669
|
+
samples,
|
|
3670
|
+
sampleRate,
|
|
3671
|
+
frameLength,
|
|
3672
|
+
hopLength,
|
|
3673
|
+
fmin,
|
|
3674
|
+
fmax,
|
|
3675
|
+
threshold,
|
|
3676
|
+
fillNa,
|
|
3677
|
+
);
|
|
2622
3678
|
}
|
|
2623
3679
|
|
|
2624
3680
|
// ============================================================================
|
|
@@ -2707,11 +3763,11 @@ export function noteToHz(note: string): number {
|
|
|
2707
3763
|
* Convert frame index to time in seconds.
|
|
2708
3764
|
*
|
|
2709
3765
|
* @param frames - Frame index
|
|
2710
|
-
* @param sr - Sample rate in Hz
|
|
2711
|
-
* @param hopLength - Hop length in samples
|
|
3766
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3767
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2712
3768
|
* @returns Time in seconds
|
|
2713
3769
|
*/
|
|
2714
|
-
export function framesToTime(frames: number, sr
|
|
3770
|
+
export function framesToTime(frames: number, sr = 22050, hopLength = 512): number {
|
|
2715
3771
|
if (!module) {
|
|
2716
3772
|
throw new Error('Module not initialized. Call init() first.');
|
|
2717
3773
|
}
|
|
@@ -2722,11 +3778,11 @@ export function framesToTime(frames: number, sr: number, hopLength: number): num
|
|
|
2722
3778
|
* Convert time in seconds to frame index.
|
|
2723
3779
|
*
|
|
2724
3780
|
* @param time - Time in seconds
|
|
2725
|
-
* @param sr - Sample rate in Hz
|
|
2726
|
-
* @param hopLength - Hop length in samples
|
|
3781
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3782
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2727
3783
|
* @returns Frame index
|
|
2728
3784
|
*/
|
|
2729
|
-
export function timeToFrames(time: number, sr
|
|
3785
|
+
export function timeToFrames(time: number, sr = 22050, hopLength = 512): number {
|
|
2730
3786
|
if (!module) {
|
|
2731
3787
|
throw new Error('Module not initialized. Call init() first.');
|
|
2732
3788
|
}
|
|
@@ -2834,18 +3890,18 @@ export function frameSignal(
|
|
|
2834
3890
|
return module.frameSignal(samples, frameLength, hopLength);
|
|
2835
3891
|
}
|
|
2836
3892
|
|
|
2837
|
-
export function padCenter(values: Float32Array,
|
|
3893
|
+
export function padCenter(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2838
3894
|
if (!module) {
|
|
2839
3895
|
throw new Error('Module not initialized. Call init() first.');
|
|
2840
3896
|
}
|
|
2841
|
-
return module.padCenter(values,
|
|
3897
|
+
return module.padCenter(values, targetSize, padValue);
|
|
2842
3898
|
}
|
|
2843
3899
|
|
|
2844
|
-
export function fixLength(values: Float32Array,
|
|
3900
|
+
export function fixLength(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2845
3901
|
if (!module) {
|
|
2846
3902
|
throw new Error('Module not initialized. Call init() first.');
|
|
2847
3903
|
}
|
|
2848
|
-
return module.fixLength(values,
|
|
3904
|
+
return module.fixLength(values, targetSize, padValue);
|
|
2849
3905
|
}
|
|
2850
3906
|
|
|
2851
3907
|
export function fixFrames(frames: Int32Array, xMin = 0, xMax = -1, pad = true): Int32Array {
|
|
@@ -2870,11 +3926,7 @@ export function peakPick(
|
|
|
2870
3926
|
return module.peakPick(values, preMax, postMax, preAvg, postAvg, delta, wait);
|
|
2871
3927
|
}
|
|
2872
3928
|
|
|
2873
|
-
export function vectorNormalize(
|
|
2874
|
-
values: Float32Array,
|
|
2875
|
-
normType = 0,
|
|
2876
|
-
threshold = 1e-12,
|
|
2877
|
-
): Float32Array {
|
|
3929
|
+
export function vectorNormalize(values: Float32Array, normType = 0, threshold = 0.0): Float32Array {
|
|
2878
3930
|
if (!module) {
|
|
2879
3931
|
throw new Error('Module not initialized. Call init() first.');
|
|
2880
3932
|
}
|
|
@@ -2902,7 +3954,7 @@ export function tonnetz(chromagram: Float32Array, nChroma: number, nFrames: numb
|
|
|
2902
3954
|
|
|
2903
3955
|
export function tempogram(
|
|
2904
3956
|
onsetEnvelope: Float32Array,
|
|
2905
|
-
sampleRate
|
|
3957
|
+
sampleRate = 22050,
|
|
2906
3958
|
hopLength = 512,
|
|
2907
3959
|
winLength = 384,
|
|
2908
3960
|
mode: TempogramMode = 'autocorrelation',
|
|
@@ -2915,7 +3967,7 @@ export function tempogram(
|
|
|
2915
3967
|
|
|
2916
3968
|
export function cyclicTempogram(
|
|
2917
3969
|
onsetEnvelope: Float32Array,
|
|
2918
|
-
sampleRate
|
|
3970
|
+
sampleRate = 22050,
|
|
2919
3971
|
hopLength = 512,
|
|
2920
3972
|
winLength = 384,
|
|
2921
3973
|
bpmMin = 60.0,
|
|
@@ -2929,7 +3981,7 @@ export function cyclicTempogram(
|
|
|
2929
3981
|
|
|
2930
3982
|
export function plp(
|
|
2931
3983
|
onsetEnvelope: Float32Array,
|
|
2932
|
-
sampleRate
|
|
3984
|
+
sampleRate = 22050,
|
|
2933
3985
|
hopLength = 512,
|
|
2934
3986
|
tempoMin = 30.0,
|
|
2935
3987
|
tempoMax = 300.0,
|
|
@@ -3014,7 +4066,7 @@ export function vqt(
|
|
|
3014
4066
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3015
4067
|
* @param nFft - FFT size (default: 2048)
|
|
3016
4068
|
* @param hopLength - Hop length (default: 512)
|
|
3017
|
-
* @param minSectionSec - Minimum section duration in seconds (default:
|
|
4069
|
+
* @param minSectionSec - Minimum section duration in seconds (default: 4.0)
|
|
3018
4070
|
* @returns Array of detected sections
|
|
3019
4071
|
*/
|
|
3020
4072
|
export function analyzeSections(
|
|
@@ -3022,7 +4074,7 @@ export function analyzeSections(
|
|
|
3022
4074
|
sampleRate = 22050,
|
|
3023
4075
|
nFft = 2048,
|
|
3024
4076
|
hopLength = 512,
|
|
3025
|
-
minSectionSec =
|
|
4077
|
+
minSectionSec = 4.0,
|
|
3026
4078
|
): Section[] {
|
|
3027
4079
|
if (!module) {
|
|
3028
4080
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -3050,7 +4102,7 @@ export function analyzeMelody(
|
|
|
3050
4102
|
fmin = 65.0,
|
|
3051
4103
|
fmax = 2093.0,
|
|
3052
4104
|
frameLength = 2048,
|
|
3053
|
-
hopLength =
|
|
4105
|
+
hopLength = 256,
|
|
3054
4106
|
threshold = 0.1,
|
|
3055
4107
|
): MelodyResult {
|
|
3056
4108
|
if (!module) {
|
|
@@ -3131,10 +4183,15 @@ export function tempogramRatio(
|
|
|
3131
4183
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3132
4184
|
* @returns Loudness measurement result
|
|
3133
4185
|
*/
|
|
3134
|
-
export function lufs(
|
|
4186
|
+
export function lufs(
|
|
4187
|
+
samples: Float32Array,
|
|
4188
|
+
sampleRate = 22050,
|
|
4189
|
+
options: ValidateOptions = {},
|
|
4190
|
+
): LufsResult {
|
|
3135
4191
|
if (!module) {
|
|
3136
4192
|
throw new Error('Module not initialized. Call init() first.');
|
|
3137
4193
|
}
|
|
4194
|
+
assertSamples('lufs', samples, options.validate !== false);
|
|
3138
4195
|
return module.lufs(samples, sampleRate);
|
|
3139
4196
|
}
|
|
3140
4197
|
|
|
@@ -3145,10 +4202,15 @@ export function lufs(samples: Float32Array, sampleRate = 22050): LufsResult {
|
|
|
3145
4202
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3146
4203
|
* @returns Momentary LUFS values over time
|
|
3147
4204
|
*/
|
|
3148
|
-
export function momentaryLufs(
|
|
4205
|
+
export function momentaryLufs(
|
|
4206
|
+
samples: Float32Array,
|
|
4207
|
+
sampleRate = 22050,
|
|
4208
|
+
options: ValidateOptions = {},
|
|
4209
|
+
): Float32Array {
|
|
3149
4210
|
if (!module) {
|
|
3150
4211
|
throw new Error('Module not initialized. Call init() first.');
|
|
3151
4212
|
}
|
|
4213
|
+
assertSamples('momentaryLufs', samples, options.validate !== false);
|
|
3152
4214
|
return module.momentaryLufs(samples, sampleRate);
|
|
3153
4215
|
}
|
|
3154
4216
|
|
|
@@ -3159,13 +4221,292 @@ export function momentaryLufs(samples: Float32Array, sampleRate = 22050): Float3
|
|
|
3159
4221
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3160
4222
|
* @returns Short-term LUFS values over time
|
|
3161
4223
|
*/
|
|
3162
|
-
export function shortTermLufs(
|
|
4224
|
+
export function shortTermLufs(
|
|
4225
|
+
samples: Float32Array,
|
|
4226
|
+
sampleRate = 22050,
|
|
4227
|
+
options: ValidateOptions = {},
|
|
4228
|
+
): Float32Array {
|
|
3163
4229
|
if (!module) {
|
|
3164
4230
|
throw new Error('Module not initialized. Call init() first.');
|
|
3165
4231
|
}
|
|
4232
|
+
assertSamples('shortTermLufs', samples, options.validate !== false);
|
|
3166
4233
|
return module.shortTermLufs(samples, sampleRate);
|
|
3167
4234
|
}
|
|
3168
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
|
+
|
|
3169
4510
|
// ============================================================================
|
|
3170
4511
|
// Core - Resample
|
|
3171
4512
|
// ============================================================================
|
|
@@ -3299,15 +4640,15 @@ export class Audio {
|
|
|
3299
4640
|
return pitchShift(this._samples, this._sampleRate, semitones);
|
|
3300
4641
|
}
|
|
3301
4642
|
|
|
3302
|
-
pitchCorrectToMidi(currentMidi
|
|
4643
|
+
pitchCorrectToMidi(currentMidi = 69.0, targetMidi = 69.0): Float32Array {
|
|
3303
4644
|
return pitchCorrectToMidi(this._samples, this._sampleRate, currentMidi, targetMidi);
|
|
3304
4645
|
}
|
|
3305
4646
|
|
|
3306
|
-
noteStretch(onsetSample
|
|
4647
|
+
noteStretch(onsetSample = 0, offsetSample = 0, stretchRatio = 1.0): Float32Array {
|
|
3307
4648
|
return noteStretch(this._samples, this._sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
3308
4649
|
}
|
|
3309
4650
|
|
|
3310
|
-
voiceChange(pitchSemitones
|
|
4651
|
+
voiceChange(pitchSemitones = 0.0, formantFactor = 1.0): Float32Array {
|
|
3311
4652
|
return voiceChange(this._samples, this._sampleRate, pitchSemitones, formantFactor);
|
|
3312
4653
|
}
|
|
3313
4654
|
|
|
@@ -3355,7 +4696,7 @@ export class Audio {
|
|
|
3355
4696
|
return melSpectrogram(this._samples, this._sampleRate, nFft, hopLength, nMels);
|
|
3356
4697
|
}
|
|
3357
4698
|
|
|
3358
|
-
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc =
|
|
4699
|
+
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc = 20): MfccResult {
|
|
3359
4700
|
return mfcc(this._samples, this._sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
3360
4701
|
}
|
|
3361
4702
|
|
|
@@ -3413,8 +4754,18 @@ export class Audio {
|
|
|
3413
4754
|
fmin = 65.0,
|
|
3414
4755
|
fmax = 2093.0,
|
|
3415
4756
|
threshold = 0.3,
|
|
4757
|
+
fillNa = false,
|
|
3416
4758
|
): PitchResult {
|
|
3417
|
-
return pitchYin(
|
|
4759
|
+
return pitchYin(
|
|
4760
|
+
this._samples,
|
|
4761
|
+
this._sampleRate,
|
|
4762
|
+
frameLength,
|
|
4763
|
+
hopLength,
|
|
4764
|
+
fmin,
|
|
4765
|
+
fmax,
|
|
4766
|
+
threshold,
|
|
4767
|
+
fillNa,
|
|
4768
|
+
);
|
|
3418
4769
|
}
|
|
3419
4770
|
|
|
3420
4771
|
pitchPyin(
|
|
@@ -3423,6 +4774,7 @@ export class Audio {
|
|
|
3423
4774
|
fmin = 65.0,
|
|
3424
4775
|
fmax = 2093.0,
|
|
3425
4776
|
threshold = 0.3,
|
|
4777
|
+
fillNa = false,
|
|
3426
4778
|
): PitchResult {
|
|
3427
4779
|
return pitchPyin(
|
|
3428
4780
|
this._samples,
|
|
@@ -3432,6 +4784,7 @@ export class Audio {
|
|
|
3432
4784
|
fmin,
|
|
3433
4785
|
fmax,
|
|
3434
4786
|
threshold,
|
|
4787
|
+
fillNa,
|
|
3435
4788
|
);
|
|
3436
4789
|
}
|
|
3437
4790
|
|
|
@@ -3477,8 +4830,7 @@ export class StreamAnalyzer {
|
|
|
3477
4830
|
if (!module) {
|
|
3478
4831
|
throw new Error('Module not initialized. Call init() first.');
|
|
3479
4832
|
}
|
|
3480
|
-
|
|
3481
|
-
const args = [
|
|
4833
|
+
this.analyzer = new module.StreamAnalyzer(
|
|
3482
4834
|
config.sampleRate,
|
|
3483
4835
|
config.nFft ?? 2048,
|
|
3484
4836
|
config.hopLength ?? 512,
|
|
@@ -3497,63 +4849,7 @@ export class StreamAnalyzer {
|
|
|
3497
4849
|
config.bpmUpdateIntervalSec ?? 10,
|
|
3498
4850
|
config.window ?? 0,
|
|
3499
4851
|
config.outputFormat ?? 0,
|
|
3500
|
-
|
|
3501
|
-
const isArityError = (error: unknown): boolean => {
|
|
3502
|
-
const message = String((error as { message?: unknown } | null)?.message ?? error);
|
|
3503
|
-
return message.includes('invalid number of parameters');
|
|
3504
|
-
};
|
|
3505
|
-
const createLegacy = (): WasmStreamAnalyzer => {
|
|
3506
|
-
const LegacyStreamAnalyzer = wasmModule.StreamAnalyzer as unknown as new (
|
|
3507
|
-
sampleRate: number,
|
|
3508
|
-
nFft: number,
|
|
3509
|
-
hopLength: number,
|
|
3510
|
-
nMels: number,
|
|
3511
|
-
computeMel: boolean,
|
|
3512
|
-
computeChroma: boolean,
|
|
3513
|
-
computeOnset: boolean,
|
|
3514
|
-
emitEveryNFrames: number,
|
|
3515
|
-
) => WasmStreamAnalyzer;
|
|
3516
|
-
return new LegacyStreamAnalyzer(
|
|
3517
|
-
args[0],
|
|
3518
|
-
args[1],
|
|
3519
|
-
args[2],
|
|
3520
|
-
args[3],
|
|
3521
|
-
args[8],
|
|
3522
|
-
args[9],
|
|
3523
|
-
args[10],
|
|
3524
|
-
args[12],
|
|
3525
|
-
);
|
|
3526
|
-
};
|
|
3527
|
-
const hasExtendedConfig =
|
|
3528
|
-
config.fmin !== undefined ||
|
|
3529
|
-
config.fmax !== undefined ||
|
|
3530
|
-
config.tuningRefHz !== undefined ||
|
|
3531
|
-
config.computeMagnitude !== undefined ||
|
|
3532
|
-
config.computeSpectral !== undefined ||
|
|
3533
|
-
config.magnitudeDownsample !== undefined ||
|
|
3534
|
-
config.keyUpdateIntervalSec !== undefined ||
|
|
3535
|
-
config.bpmUpdateIntervalSec !== undefined ||
|
|
3536
|
-
config.window !== undefined ||
|
|
3537
|
-
config.outputFormat !== undefined;
|
|
3538
|
-
if (hasExtendedConfig) {
|
|
3539
|
-
try {
|
|
3540
|
-
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3541
|
-
} catch (error) {
|
|
3542
|
-
if (!isArityError(error)) {
|
|
3543
|
-
throw error;
|
|
3544
|
-
}
|
|
3545
|
-
this.analyzer = createLegacy();
|
|
3546
|
-
}
|
|
3547
|
-
} else {
|
|
3548
|
-
try {
|
|
3549
|
-
this.analyzer = createLegacy();
|
|
3550
|
-
} catch (error) {
|
|
3551
|
-
if (!isArityError(error)) {
|
|
3552
|
-
throw error;
|
|
3553
|
-
}
|
|
3554
|
-
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
4852
|
+
);
|
|
3557
4853
|
}
|
|
3558
4854
|
|
|
3559
4855
|
/**
|