@libraz/libsonare 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -4
- package/dist/index.d.ts +820 -296
- package/dist/index.js +770 -128
- 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 +101 -3
- package/dist/worklet.js +706 -69
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +1764 -287
- package/src/public_types.ts +128 -0
- package/src/sonare.js.d.ts +580 -79
- package/src/stream_types.ts +4 -1
- package/src/worklet.ts +796 -66
- package/src/wasm_types.ts +0 -1259
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @example
|
|
5
5
|
* ```typescript
|
|
6
|
-
* import { init, detectBpm, detectKey, analyze } from '@libraz/
|
|
6
|
+
* import { init, detectBpm, detectKey, analyze } from '@libraz/libsonare';
|
|
7
7
|
*
|
|
8
8
|
* await init();
|
|
9
9
|
*
|
|
@@ -56,7 +56,15 @@ import type {
|
|
|
56
56
|
PairAnalysis,
|
|
57
57
|
PairProcessor,
|
|
58
58
|
PanLaw,
|
|
59
|
+
PanMode,
|
|
59
60
|
PitchResult,
|
|
61
|
+
RealtimeVoiceChangerConfigInput,
|
|
62
|
+
RealtimeVoiceChangerPodConfig,
|
|
63
|
+
RirResult,
|
|
64
|
+
RirSynthOptions,
|
|
65
|
+
RoomEstimateOptions,
|
|
66
|
+
RoomEstimateResult,
|
|
67
|
+
RoomMorphOptions,
|
|
60
68
|
Section,
|
|
61
69
|
SectionType,
|
|
62
70
|
SendTiming,
|
|
@@ -68,15 +76,9 @@ import type {
|
|
|
68
76
|
StreamingPlatform,
|
|
69
77
|
StreamingRetuneConfig,
|
|
70
78
|
TempogramMode,
|
|
79
|
+
VoicePresetId,
|
|
71
80
|
} from './public_types';
|
|
72
81
|
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
82
|
import type {
|
|
81
83
|
ProgressCallback,
|
|
82
84
|
SonareModule,
|
|
@@ -84,6 +86,7 @@ import type {
|
|
|
84
86
|
WasmAnalysisResult,
|
|
85
87
|
WasmChordAnalysisResult,
|
|
86
88
|
WasmCyclicTempogramResult,
|
|
89
|
+
WasmDecomposeResult,
|
|
87
90
|
WasmEngineAutomationPoint,
|
|
88
91
|
WasmEngineBounceOptions,
|
|
89
92
|
WasmEngineBounceResult,
|
|
@@ -101,13 +104,23 @@ import type {
|
|
|
101
104
|
WasmEngineTransportState,
|
|
102
105
|
WasmFourierTempogramResult,
|
|
103
106
|
WasmFrameResult,
|
|
107
|
+
WasmHpssWithResidualResult,
|
|
104
108
|
WasmKeyCandidateResult,
|
|
109
|
+
WasmLufsResult,
|
|
110
|
+
WasmMatrix2dResult,
|
|
105
111
|
WasmNnlsChromaResult,
|
|
106
112
|
WasmRealtimeEngine,
|
|
107
113
|
WasmStreamAnalyzer,
|
|
108
114
|
WasmTempogramResult,
|
|
109
115
|
WasmTrimResult,
|
|
110
|
-
} from './
|
|
116
|
+
} from './sonare.js';
|
|
117
|
+
import type {
|
|
118
|
+
AnalyzerStats,
|
|
119
|
+
FrameBuffer,
|
|
120
|
+
StreamConfig,
|
|
121
|
+
StreamFramesI16,
|
|
122
|
+
StreamFramesU8,
|
|
123
|
+
} from './stream_types';
|
|
111
124
|
|
|
112
125
|
export type {
|
|
113
126
|
AcousticResult,
|
|
@@ -143,6 +156,7 @@ export type {
|
|
|
143
156
|
MasteringStereoResult,
|
|
144
157
|
MelodyPoint,
|
|
145
158
|
MelodyResult,
|
|
159
|
+
MelPowerResult,
|
|
146
160
|
MelSpectrogramResult,
|
|
147
161
|
MeterTap,
|
|
148
162
|
MfccResult,
|
|
@@ -155,17 +169,28 @@ export type {
|
|
|
155
169
|
PanLaw,
|
|
156
170
|
PanMode,
|
|
157
171
|
PitchResult,
|
|
172
|
+
RealtimeVoiceChangerConfigInput,
|
|
173
|
+
RealtimeVoiceChangerPodConfig,
|
|
158
174
|
RhythmFeatures,
|
|
175
|
+
RirResult,
|
|
176
|
+
RirSynthOptions,
|
|
177
|
+
RoomEstimateOptions,
|
|
178
|
+
RoomEstimateResult,
|
|
179
|
+
RoomGeometryOptions,
|
|
180
|
+
RoomMorphOptions,
|
|
159
181
|
Section,
|
|
160
182
|
SendTiming,
|
|
161
183
|
SoloProcessor,
|
|
162
184
|
StereoAnalysis,
|
|
185
|
+
StftPowerResult,
|
|
163
186
|
StftResult,
|
|
164
187
|
StreamingEqualizerConfig,
|
|
165
188
|
StreamingPlatform,
|
|
166
189
|
StreamingRetuneConfig,
|
|
190
|
+
TempogramMode,
|
|
167
191
|
Timbre,
|
|
168
192
|
TimeSignature,
|
|
193
|
+
VoicePresetId,
|
|
169
194
|
} from './public_types';
|
|
170
195
|
export {
|
|
171
196
|
ChordQuality,
|
|
@@ -174,6 +199,7 @@ export {
|
|
|
174
199
|
PitchClass,
|
|
175
200
|
SectionType,
|
|
176
201
|
} from './public_types';
|
|
202
|
+
export type { ProgressCallback } from './sonare.js';
|
|
177
203
|
export type {
|
|
178
204
|
AnalyzerStats,
|
|
179
205
|
BarChord,
|
|
@@ -185,7 +211,6 @@ export type {
|
|
|
185
211
|
StreamFramesI16,
|
|
186
212
|
StreamFramesU8,
|
|
187
213
|
} from './stream_types';
|
|
188
|
-
export type { ProgressCallback } from './wasm_types';
|
|
189
214
|
|
|
190
215
|
export type EngineClip = WasmEngineClip;
|
|
191
216
|
export type EngineParameterInfo = WasmEngineParameterInfo;
|
|
@@ -202,6 +227,13 @@ export type EngineTelemetry = WasmEngineTelemetry;
|
|
|
202
227
|
export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
|
|
203
228
|
export type EngineTransportState = WasmEngineTransportState;
|
|
204
229
|
|
|
230
|
+
/** Row-major 2-D matrix as a flat buffer plus its dimensions. */
|
|
231
|
+
export type Matrix2dResult = WasmMatrix2dResult;
|
|
232
|
+
/** NMF factor matrices { w, h } from {@link decompose}. */
|
|
233
|
+
export type DecomposeResult = WasmDecomposeResult;
|
|
234
|
+
/** Harmonic / percussive / residual signals from {@link hpssWithResidual}. */
|
|
235
|
+
export type HpssWithResidualResult = WasmHpssWithResidualResult;
|
|
236
|
+
|
|
205
237
|
export const EXPECTED_ENGINE_ABI_VERSION = 2;
|
|
206
238
|
|
|
207
239
|
export interface EngineCapabilities {
|
|
@@ -222,6 +254,43 @@ export interface MixerRealtimeBuffer {
|
|
|
222
254
|
process: (numSamples?: number) => void;
|
|
223
255
|
}
|
|
224
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} mono
|
|
259
|
+
* processing. The `input` / `output` `Float32Array`s are typed-memory views
|
|
260
|
+
* onto the WASM heap — write samples into `input`, call `process()`, then
|
|
261
|
+
* read from `output`. The views are owned by the {@link RealtimeVoiceChanger}
|
|
262
|
+
* and remain valid until `delete()` is called on it.
|
|
263
|
+
*/
|
|
264
|
+
export interface RealtimeVoiceChangerMonoBuffer {
|
|
265
|
+
input: Float32Array;
|
|
266
|
+
output: Float32Array;
|
|
267
|
+
process: () => void;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} interleaved
|
|
272
|
+
* multi-channel processing. Layout is L0,R0,L1,R1,... for stereo. The views
|
|
273
|
+
* are owned by the {@link RealtimeVoiceChanger}.
|
|
274
|
+
*/
|
|
275
|
+
export interface RealtimeVoiceChangerInterleavedBuffer {
|
|
276
|
+
input: Float32Array;
|
|
277
|
+
output: Float32Array;
|
|
278
|
+
channels: number;
|
|
279
|
+
process: () => void;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Zero-copy realtime buffer for {@link RealtimeVoiceChanger} planar stereo
|
|
284
|
+
* processing. Each entry in `channels` is a heap-backed `Float32Array` for one
|
|
285
|
+
* channel (matching AudioWorklet's native layout). Process happens in place:
|
|
286
|
+
* write samples into each channel view, call `process()`, then read back from
|
|
287
|
+
* the same views.
|
|
288
|
+
*/
|
|
289
|
+
export interface RealtimeVoiceChangerPlanarBuffer {
|
|
290
|
+
channels: Float32Array[];
|
|
291
|
+
process: () => void;
|
|
292
|
+
}
|
|
293
|
+
|
|
225
294
|
function automationCurveCode(curve: AutomationCurve): number {
|
|
226
295
|
switch (curve) {
|
|
227
296
|
case 'linear':
|
|
@@ -253,6 +322,22 @@ function panLawCode(panLaw: PanLaw | number): number {
|
|
|
253
322
|
}
|
|
254
323
|
}
|
|
255
324
|
|
|
325
|
+
function panModeCode(panMode: PanMode | number): number {
|
|
326
|
+
if (typeof panMode === 'number') {
|
|
327
|
+
return panMode;
|
|
328
|
+
}
|
|
329
|
+
switch (panMode) {
|
|
330
|
+
case 'stereoPan':
|
|
331
|
+
case 'stereo-pan':
|
|
332
|
+
return 1;
|
|
333
|
+
case 'dualPan':
|
|
334
|
+
case 'dual-pan':
|
|
335
|
+
return 2;
|
|
336
|
+
default:
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
256
341
|
function meterTapCode(tap: MeterTap | number): number {
|
|
257
342
|
return tap === 'preFader' || tap === 0 ? 0 : 1;
|
|
258
343
|
}
|
|
@@ -268,6 +353,62 @@ function sendTimingCode(timing: SendTiming | number): number {
|
|
|
268
353
|
let module: SonareModule | null = null;
|
|
269
354
|
let initPromise: Promise<void> | null = null;
|
|
270
355
|
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Input validation helpers (empty + NaN/Inf guards for sample buffers)
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Per-call validation options accepted by guarded wrappers. Empty-buffer
|
|
362
|
+
* checks are always performed; pass `{ validate: false }` to opt out of the
|
|
363
|
+
* O(n) NaN/Inf scan on hot paths.
|
|
364
|
+
*/
|
|
365
|
+
export interface ValidateOptions {
|
|
366
|
+
validate?: boolean;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function assertNonEmptySamples(
|
|
370
|
+
fnName: string,
|
|
371
|
+
samples: ArrayLike<number>,
|
|
372
|
+
argName = 'samples',
|
|
373
|
+
): void {
|
|
374
|
+
if (samples.length === 0) {
|
|
375
|
+
throw new RangeError(`${fnName}: ${argName} must not be empty`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function assertFiniteSamples(
|
|
380
|
+
fnName: string,
|
|
381
|
+
samples: ArrayLike<number>,
|
|
382
|
+
validate: boolean,
|
|
383
|
+
argName = 'samples',
|
|
384
|
+
): void {
|
|
385
|
+
if (!validate) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
for (let i = 0; i < samples.length; i++) {
|
|
389
|
+
const v = samples[i] as number;
|
|
390
|
+
if (!Number.isFinite(v)) {
|
|
391
|
+
throw new RangeError(`${fnName}: ${argName} contains NaN or Inf at index ${i}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function assertSamples(
|
|
397
|
+
fnName: string,
|
|
398
|
+
samples: ArrayLike<number>,
|
|
399
|
+
validate: boolean,
|
|
400
|
+
argName = 'samples',
|
|
401
|
+
): void {
|
|
402
|
+
assertNonEmptySamples(fnName, samples, argName);
|
|
403
|
+
assertFiniteSamples(fnName, samples, validate, argName);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function assertFiniteScalar(fnName: string, value: number, argName: string): void {
|
|
407
|
+
if (!Number.isFinite(value)) {
|
|
408
|
+
throw new RangeError(`${fnName}: ${argName} must be a finite number`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
271
412
|
// ============================================================================
|
|
272
413
|
// Initialization
|
|
273
414
|
// ============================================================================
|
|
@@ -327,6 +468,60 @@ export function engineAbiVersion(): number {
|
|
|
327
468
|
return module.engineAbiVersion();
|
|
328
469
|
}
|
|
329
470
|
|
|
471
|
+
export function voiceChangerAbiVersion(): number {
|
|
472
|
+
if (!module) {
|
|
473
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
474
|
+
}
|
|
475
|
+
return module.voiceChangerAbiVersion();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Canonical ordinal order of the built-in voice-character presets, matching the
|
|
479
|
+
// C ABI SonareVoiceCharacterPreset enum and SONARE_REALTIME_VOICE_CHANGER_PRESET_IDS.
|
|
480
|
+
const VOICE_PRESET_ORDINALS: readonly VoicePresetId[] = [
|
|
481
|
+
'neutral-monitor',
|
|
482
|
+
'bright-idol',
|
|
483
|
+
'soft-whisper',
|
|
484
|
+
'deep-narrator',
|
|
485
|
+
'robot-mascot',
|
|
486
|
+
'dark-villain',
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
function resolveVoicePresetOrdinal(preset: VoicePresetId | number): number {
|
|
490
|
+
if (typeof preset === 'number') {
|
|
491
|
+
return preset;
|
|
492
|
+
}
|
|
493
|
+
const ordinal = VOICE_PRESET_ORDINALS.indexOf(preset);
|
|
494
|
+
if (ordinal < 0) {
|
|
495
|
+
throw new Error(`Unknown voice character preset: ${preset}`);
|
|
496
|
+
}
|
|
497
|
+
return ordinal;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Map a voice-character preset ordinal (or canonical id) to its canonical id
|
|
502
|
+
* string (e.g. `'bright-idol'`). Returns `null` for an out-of-range ordinal.
|
|
503
|
+
*/
|
|
504
|
+
export function voiceCharacterPresetId(preset: VoicePresetId | number): string | null {
|
|
505
|
+
if (!module) {
|
|
506
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
507
|
+
}
|
|
508
|
+
return module.voiceCharacterPresetId(resolveVoicePresetOrdinal(preset));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Return the canonical (normalized) flat POD config for a built-in voice
|
|
513
|
+
* preset, skipping the JSON round-trip. Accepts a canonical preset id or its
|
|
514
|
+
* integer ordinal. Returns `null` for an out-of-range ordinal.
|
|
515
|
+
*/
|
|
516
|
+
export function realtimeVoiceChangerPresetConfig(
|
|
517
|
+
preset: VoicePresetId | number,
|
|
518
|
+
): RealtimeVoiceChangerPodConfig | null {
|
|
519
|
+
if (!module) {
|
|
520
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
521
|
+
}
|
|
522
|
+
return module.realtimeVoiceChangerPresetConfig(resolveVoicePresetOrdinal(preset));
|
|
523
|
+
}
|
|
524
|
+
|
|
330
525
|
export function engineCapabilities(): EngineCapabilities {
|
|
331
526
|
const abiVersion = engineAbiVersion();
|
|
332
527
|
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function';
|
|
@@ -532,6 +727,34 @@ export class RealtimeEngine {
|
|
|
532
727
|
return this.native.process(channels);
|
|
533
728
|
}
|
|
534
729
|
|
|
730
|
+
/**
|
|
731
|
+
* Allocates persistent per-channel WASM-heap scratch for the zero-copy
|
|
732
|
+
* `getChannelBuffer` / `processPrepared` realtime path. Call once (off the
|
|
733
|
+
* audio thread) before driving `processPrepared` from an AudioWorklet so the
|
|
734
|
+
* render callback never allocates on the C++/JS heap.
|
|
735
|
+
*/
|
|
736
|
+
prepareChannels(numChannels: number, maxFrames: number): void {
|
|
737
|
+
this.native.prepareChannels(numChannels, maxFrames);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Returns a Float32Array view onto the persistent WASM-heap scratch for one
|
|
742
|
+
* channel (valid for up to `numFrames`). Fill it, call `processPrepared`, then
|
|
743
|
+
* read the same view back. Re-acquire after WASM memory growth.
|
|
744
|
+
*/
|
|
745
|
+
getChannelBuffer(channel: number, numFrames: number): Float32Array {
|
|
746
|
+
return this.native.getChannelBuffer(channel, numFrames);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Runs the engine in place over the prepared per-channel scratch buffers.
|
|
751
|
+
* Allocation-free: safe to call on the AudioWorklet render thread after
|
|
752
|
+
* `prepareChannels`.
|
|
753
|
+
*/
|
|
754
|
+
processPrepared(numFrames: number): void {
|
|
755
|
+
this.native.processPrepared(numFrames);
|
|
756
|
+
}
|
|
757
|
+
|
|
535
758
|
processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
|
|
536
759
|
return this.native.processWithMonitor(channels);
|
|
537
760
|
}
|
|
@@ -569,10 +792,10 @@ export class RealtimeEngine {
|
|
|
569
792
|
* Detect BPM from audio samples.
|
|
570
793
|
*
|
|
571
794
|
* @param samples - Audio samples (mono, float32)
|
|
572
|
-
* @param sampleRate - Sample rate in Hz
|
|
795
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
573
796
|
* @returns Detected BPM
|
|
574
797
|
*/
|
|
575
|
-
export function detectBpm(samples: Float32Array, sampleRate
|
|
798
|
+
export function detectBpm(samples: Float32Array, sampleRate = 22050): number {
|
|
576
799
|
if (!module) {
|
|
577
800
|
throw new Error('Module not initialized. Call init() first.');
|
|
578
801
|
}
|
|
@@ -583,12 +806,12 @@ export function detectBpm(samples: Float32Array, sampleRate: number): number {
|
|
|
583
806
|
* Detect musical key from audio samples.
|
|
584
807
|
*
|
|
585
808
|
* @param samples - Audio samples (mono, float32)
|
|
586
|
-
* @param sampleRate - Sample rate in Hz
|
|
809
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
587
810
|
* @returns Detected key
|
|
588
811
|
*/
|
|
589
812
|
export function detectKey(
|
|
590
813
|
samples: Float32Array,
|
|
591
|
-
sampleRate
|
|
814
|
+
sampleRate = 22050,
|
|
592
815
|
options: KeyDetectionOptions = {},
|
|
593
816
|
): Key {
|
|
594
817
|
if (!module) {
|
|
@@ -685,7 +908,7 @@ function keyProfileValue(profile: KeyDetectionOptions['profile'] | undefined): n
|
|
|
685
908
|
|
|
686
909
|
export function detectKeyCandidates(
|
|
687
910
|
samples: Float32Array,
|
|
688
|
-
sampleRate
|
|
911
|
+
sampleRate = 22050,
|
|
689
912
|
options: KeyDetectionOptions = {},
|
|
690
913
|
): KeyCandidate[] {
|
|
691
914
|
if (!module) {
|
|
@@ -711,10 +934,10 @@ export function detectKeyCandidates(
|
|
|
711
934
|
* Detect onset times from audio samples.
|
|
712
935
|
*
|
|
713
936
|
* @param samples - Audio samples (mono, float32)
|
|
714
|
-
* @param sampleRate - Sample rate in Hz
|
|
937
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
715
938
|
* @returns Array of onset times in seconds
|
|
716
939
|
*/
|
|
717
|
-
export function detectOnsets(samples: Float32Array, sampleRate
|
|
940
|
+
export function detectOnsets(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
718
941
|
if (!module) {
|
|
719
942
|
throw new Error('Module not initialized. Call init() first.');
|
|
720
943
|
}
|
|
@@ -725,10 +948,10 @@ export function detectOnsets(samples: Float32Array, sampleRate: number): Float32
|
|
|
725
948
|
* Detect beat times from audio samples.
|
|
726
949
|
*
|
|
727
950
|
* @param samples - Audio samples (mono, float32)
|
|
728
|
-
* @param sampleRate - Sample rate in Hz
|
|
951
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
729
952
|
* @returns Array of beat times in seconds
|
|
730
953
|
*/
|
|
731
|
-
export function detectBeats(samples: Float32Array, sampleRate
|
|
954
|
+
export function detectBeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
732
955
|
if (!module) {
|
|
733
956
|
throw new Error('Module not initialized. Call init() first.');
|
|
734
957
|
}
|
|
@@ -739,10 +962,10 @@ export function detectBeats(samples: Float32Array, sampleRate: number): Float32A
|
|
|
739
962
|
* Detect downbeat times from audio samples.
|
|
740
963
|
*
|
|
741
964
|
* @param samples - Audio samples (mono, float32)
|
|
742
|
-
* @param sampleRate - Sample rate in Hz
|
|
965
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
743
966
|
* @returns Array of downbeat times in seconds
|
|
744
967
|
*/
|
|
745
|
-
export function detectDownbeats(samples: Float32Array, sampleRate
|
|
968
|
+
export function detectDownbeats(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
746
969
|
if (!module) {
|
|
747
970
|
throw new Error('Module not initialized. Call init() first.');
|
|
748
971
|
}
|
|
@@ -767,13 +990,13 @@ function convertChordAnalysisResult(wasm: WasmChordAnalysisResult): ChordAnalysi
|
|
|
767
990
|
* Detect chords from audio samples.
|
|
768
991
|
*
|
|
769
992
|
* @param samples - Audio samples (mono, float32)
|
|
770
|
-
* @param sampleRate - Sample rate in Hz
|
|
993
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
771
994
|
* @param options - Optional chord detection settings
|
|
772
995
|
* @returns Detected chord segments
|
|
773
996
|
*/
|
|
774
997
|
export function detectChords(
|
|
775
998
|
samples: Float32Array,
|
|
776
|
-
sampleRate
|
|
999
|
+
sampleRate = 22050,
|
|
777
1000
|
options: ChordDetectionOptions = {},
|
|
778
1001
|
): ChordAnalysisResult {
|
|
779
1002
|
if (!module) {
|
|
@@ -857,10 +1080,10 @@ function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult {
|
|
|
857
1080
|
* Perform complete music analysis.
|
|
858
1081
|
*
|
|
859
1082
|
* @param samples - Audio samples (mono, float32)
|
|
860
|
-
* @param sampleRate - Sample rate in Hz
|
|
1083
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
861
1084
|
* @returns Complete analysis result
|
|
862
1085
|
*/
|
|
863
|
-
export function analyze(samples: Float32Array, sampleRate
|
|
1086
|
+
export function analyze(samples: Float32Array, sampleRate = 22050): AnalysisResult {
|
|
864
1087
|
if (!module) {
|
|
865
1088
|
throw new Error('Module not initialized. Call init() first.');
|
|
866
1089
|
}
|
|
@@ -870,7 +1093,7 @@ export function analyze(samples: Float32Array, sampleRate: number): AnalysisResu
|
|
|
870
1093
|
|
|
871
1094
|
export function analyzeImpulseResponse(
|
|
872
1095
|
samples: Float32Array,
|
|
873
|
-
sampleRate
|
|
1096
|
+
sampleRate = 48000,
|
|
874
1097
|
nOctaveBands = 6,
|
|
875
1098
|
): AcousticResult {
|
|
876
1099
|
if (!module) {
|
|
@@ -886,7 +1109,7 @@ export function analyzeImpulseResponse(
|
|
|
886
1109
|
|
|
887
1110
|
export function detectAcoustic(
|
|
888
1111
|
samples: Float32Array,
|
|
889
|
-
sampleRate
|
|
1112
|
+
sampleRate = 48000,
|
|
890
1113
|
nOctaveBands = 6,
|
|
891
1114
|
nThirdOctaveSubbands = 24,
|
|
892
1115
|
minDecayDb = 30.0,
|
|
@@ -906,17 +1129,68 @@ export function detectAcoustic(
|
|
|
906
1129
|
return result;
|
|
907
1130
|
}
|
|
908
1131
|
|
|
1132
|
+
/**
|
|
1133
|
+
* Synthesize a room impulse response from shoebox geometry. `hasError` is true
|
|
1134
|
+
* when the source/listener falls outside the room (the RIR is then empty).
|
|
1135
|
+
*/
|
|
1136
|
+
export function synthesizeRir(options: RirSynthOptions = {}): RirResult {
|
|
1137
|
+
if (!module) {
|
|
1138
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1139
|
+
}
|
|
1140
|
+
if (typeof module.synthesizeRir !== 'function') {
|
|
1141
|
+
throw new Error('libsonare was built without acoustic-simulation support');
|
|
1142
|
+
}
|
|
1143
|
+
return module.synthesizeRir(options);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Estimate an equivalent room (volume/dimensions/absorption/DRR) from a
|
|
1148
|
+
* recording or impulse response.
|
|
1149
|
+
*/
|
|
1150
|
+
export function estimateRoom(
|
|
1151
|
+
samples: Float32Array,
|
|
1152
|
+
sampleRate = 48000,
|
|
1153
|
+
options: RoomEstimateOptions = {},
|
|
1154
|
+
): RoomEstimateResult {
|
|
1155
|
+
if (!module) {
|
|
1156
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1157
|
+
}
|
|
1158
|
+
if (typeof module.estimateRoom !== 'function') {
|
|
1159
|
+
throw new Error('libsonare was built without acoustic-simulation support');
|
|
1160
|
+
}
|
|
1161
|
+
return module.estimateRoom(samples, sampleRate, options);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Morph a recording's reverberation toward a target room (creative FX, not
|
|
1166
|
+
* dereverberation). Returns the morphed samples (input length plus the target
|
|
1167
|
+
* room's reverb tail).
|
|
1168
|
+
*/
|
|
1169
|
+
export function roomMorph(
|
|
1170
|
+
samples: Float32Array,
|
|
1171
|
+
sampleRate: number,
|
|
1172
|
+
options: RoomMorphOptions = {},
|
|
1173
|
+
): Float32Array {
|
|
1174
|
+
if (!module) {
|
|
1175
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1176
|
+
}
|
|
1177
|
+
if (typeof module.roomMorph !== 'function') {
|
|
1178
|
+
throw new Error('libsonare was built without acoustic-simulation support');
|
|
1179
|
+
}
|
|
1180
|
+
return module.roomMorph(samples, sampleRate, options);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
909
1183
|
/**
|
|
910
1184
|
* Perform complete music analysis with progress reporting.
|
|
911
1185
|
*
|
|
912
1186
|
* @param samples - Audio samples (mono, float32)
|
|
913
|
-
* @param sampleRate - Sample rate in Hz
|
|
1187
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
914
1188
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
915
1189
|
* @returns Complete analysis result
|
|
916
1190
|
*/
|
|
917
1191
|
export function analyzeWithProgress(
|
|
918
1192
|
samples: Float32Array,
|
|
919
|
-
sampleRate
|
|
1193
|
+
sampleRate = 22050,
|
|
920
1194
|
onProgress: ProgressCallback,
|
|
921
1195
|
): AnalysisResult {
|
|
922
1196
|
if (!module) {
|
|
@@ -926,6 +1200,154 @@ export function analyzeWithProgress(
|
|
|
926
1200
|
return convertAnalysisResult(result);
|
|
927
1201
|
}
|
|
928
1202
|
|
|
1203
|
+
export interface BpmCandidate {
|
|
1204
|
+
bpm: number;
|
|
1205
|
+
confidence: number;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
export interface BpmAnalysisResult {
|
|
1209
|
+
bpm: number;
|
|
1210
|
+
confidence: number;
|
|
1211
|
+
candidates: BpmCandidate[];
|
|
1212
|
+
autocorrelation: Float32Array;
|
|
1213
|
+
tempogram: Float32Array;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
export interface RhythmAnalysisResult {
|
|
1217
|
+
timeSignature: { numerator: number; denominator: number; confidence: number };
|
|
1218
|
+
syncopation: number;
|
|
1219
|
+
grooveType: string;
|
|
1220
|
+
patternRegularity: number;
|
|
1221
|
+
tempoStability: number;
|
|
1222
|
+
bpm: number;
|
|
1223
|
+
beatIntervals: Float32Array;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export interface DynamicsAnalysisResult {
|
|
1227
|
+
dynamicRangeDb: number;
|
|
1228
|
+
peakDb: number;
|
|
1229
|
+
rmsDb: number;
|
|
1230
|
+
crestFactor: number;
|
|
1231
|
+
loudnessRangeDb: number;
|
|
1232
|
+
isCompressed: boolean;
|
|
1233
|
+
/** Loudness curve timestamps (seconds), parallel to {@link loudnessRmsDb}. */
|
|
1234
|
+
loudnessTimes: Float32Array;
|
|
1235
|
+
/** Loudness curve RMS values (dB), parallel to {@link loudnessTimes}. */
|
|
1236
|
+
loudnessRmsDb: Float32Array;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/** Timbre metrics for one analysis window. Entries are ordered by time in `timbreOverTime`. */
|
|
1240
|
+
export interface TimbreFrame {
|
|
1241
|
+
brightness: number;
|
|
1242
|
+
warmth: number;
|
|
1243
|
+
density: number;
|
|
1244
|
+
roughness: number;
|
|
1245
|
+
complexity: number;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
export interface TimbreAnalysisResult extends TimbreFrame {
|
|
1249
|
+
spectralCentroid: Float32Array;
|
|
1250
|
+
spectralFlatness: Float32Array;
|
|
1251
|
+
spectralRolloff: Float32Array;
|
|
1252
|
+
/** Time-varying timbre metrics, one entry per analysis window. */
|
|
1253
|
+
timbreOverTime: TimbreFrame[];
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Detailed BPM analysis (BPM, confidence, alternate candidates, autocorrelation,
|
|
1258
|
+
* tempogram). Matches the Node `analyzeBpm` / Python `analyze_bpm` surface.
|
|
1259
|
+
*/
|
|
1260
|
+
export function analyzeBpm(
|
|
1261
|
+
samples: Float32Array,
|
|
1262
|
+
sampleRate = 22050,
|
|
1263
|
+
bpmMin = 30.0,
|
|
1264
|
+
bpmMax = 300.0,
|
|
1265
|
+
startBpm = 120.0,
|
|
1266
|
+
nFft = 2048,
|
|
1267
|
+
hopLength = 512,
|
|
1268
|
+
maxCandidates = 5,
|
|
1269
|
+
): BpmAnalysisResult {
|
|
1270
|
+
if (!module) {
|
|
1271
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1272
|
+
}
|
|
1273
|
+
return module.analyzeBpm(
|
|
1274
|
+
samples,
|
|
1275
|
+
sampleRate,
|
|
1276
|
+
bpmMin,
|
|
1277
|
+
bpmMax,
|
|
1278
|
+
startBpm,
|
|
1279
|
+
nFft,
|
|
1280
|
+
hopLength,
|
|
1281
|
+
maxCandidates,
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/**
|
|
1286
|
+
* Detailed rhythm analysis (time signature, groove, syncopation, beat intervals).
|
|
1287
|
+
*/
|
|
1288
|
+
export function analyzeRhythm(
|
|
1289
|
+
samples: Float32Array,
|
|
1290
|
+
sampleRate = 22050,
|
|
1291
|
+
bpmMin = 60.0,
|
|
1292
|
+
bpmMax = 200.0,
|
|
1293
|
+
startBpm = 120.0,
|
|
1294
|
+
nFft = 2048,
|
|
1295
|
+
hopLength = 512,
|
|
1296
|
+
): RhythmAnalysisResult {
|
|
1297
|
+
if (!module) {
|
|
1298
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1299
|
+
}
|
|
1300
|
+
return module.analyzeRhythm(samples, sampleRate, bpmMin, bpmMax, startBpm, nFft, hopLength);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Dynamics analysis (RMS, peak, crest factor, LRA, loudness curve).
|
|
1305
|
+
*/
|
|
1306
|
+
export function analyzeDynamics(
|
|
1307
|
+
samples: Float32Array,
|
|
1308
|
+
sampleRate = 22050,
|
|
1309
|
+
windowSec = 0.4,
|
|
1310
|
+
hopLength = 512,
|
|
1311
|
+
compressionThreshold = 6.0,
|
|
1312
|
+
): DynamicsAnalysisResult {
|
|
1313
|
+
if (!module) {
|
|
1314
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1315
|
+
}
|
|
1316
|
+
return module.analyzeDynamics(samples, sampleRate, windowSec, hopLength, compressionThreshold);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* Timbre analysis (brightness/warmth/density/roughness/complexity plus spectral
|
|
1321
|
+
* features and per-window timbre frames).
|
|
1322
|
+
*/
|
|
1323
|
+
export function analyzeTimbre(
|
|
1324
|
+
samples: Float32Array,
|
|
1325
|
+
sampleRate = 22050,
|
|
1326
|
+
nFft = 2048,
|
|
1327
|
+
hopLength = 512,
|
|
1328
|
+
nMels = 128,
|
|
1329
|
+
nMfcc = 13,
|
|
1330
|
+
windowSec = 0.5,
|
|
1331
|
+
): TimbreAnalysisResult {
|
|
1332
|
+
if (!module) {
|
|
1333
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1334
|
+
}
|
|
1335
|
+
return module.analyzeTimbre(samples, sampleRate, nFft, hopLength, nMels, nMfcc, windowSec);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Whether this WASM build was compiled with FFmpeg support. Mirrors Node /
|
|
1340
|
+
* Python `hasFfmpegSupport`. In the published WASM binding this currently
|
|
1341
|
+
* always returns `false` (FFmpeg is not bundled into the .wasm), but the API
|
|
1342
|
+
* exists so caller code can branch on capabilities portably.
|
|
1343
|
+
*/
|
|
1344
|
+
export function hasFfmpegSupport(): boolean {
|
|
1345
|
+
if (!module) {
|
|
1346
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1347
|
+
}
|
|
1348
|
+
return module.hasFfmpegSupport();
|
|
1349
|
+
}
|
|
1350
|
+
|
|
929
1351
|
// ============================================================================
|
|
930
1352
|
// Effects
|
|
931
1353
|
// ============================================================================
|
|
@@ -934,14 +1356,14 @@ export function analyzeWithProgress(
|
|
|
934
1356
|
* Perform Harmonic-Percussive Source Separation (HPSS).
|
|
935
1357
|
*
|
|
936
1358
|
* @param samples - Audio samples (mono, float32)
|
|
937
|
-
* @param sampleRate - Sample rate in Hz
|
|
1359
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
938
1360
|
* @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
|
|
939
1361
|
* @param kernelPercussive - Vertical median filter size for percussive (default: 31)
|
|
940
1362
|
* @returns Separated harmonic and percussive components
|
|
941
1363
|
*/
|
|
942
1364
|
export function hpss(
|
|
943
1365
|
samples: Float32Array,
|
|
944
|
-
sampleRate
|
|
1366
|
+
sampleRate = 22050,
|
|
945
1367
|
kernelHarmonic = 31,
|
|
946
1368
|
kernelPercussive = 31,
|
|
947
1369
|
): HpssResult {
|
|
@@ -958,10 +1380,15 @@ export function hpss(
|
|
|
958
1380
|
* @param sampleRate - Sample rate in Hz
|
|
959
1381
|
* @returns Harmonic component
|
|
960
1382
|
*/
|
|
961
|
-
export function harmonic(
|
|
1383
|
+
export function harmonic(
|
|
1384
|
+
samples: Float32Array,
|
|
1385
|
+
sampleRate: number,
|
|
1386
|
+
options: ValidateOptions = {},
|
|
1387
|
+
): Float32Array {
|
|
962
1388
|
if (!module) {
|
|
963
1389
|
throw new Error('Module not initialized. Call init() first.');
|
|
964
1390
|
}
|
|
1391
|
+
assertSamples('harmonic', samples, options.validate !== false);
|
|
965
1392
|
return module.harmonic(samples, sampleRate);
|
|
966
1393
|
}
|
|
967
1394
|
|
|
@@ -972,10 +1399,15 @@ export function harmonic(samples: Float32Array, sampleRate: number): Float32Arra
|
|
|
972
1399
|
* @param sampleRate - Sample rate in Hz
|
|
973
1400
|
* @returns Percussive component
|
|
974
1401
|
*/
|
|
975
|
-
export function percussive(
|
|
1402
|
+
export function percussive(
|
|
1403
|
+
samples: Float32Array,
|
|
1404
|
+
sampleRate: number,
|
|
1405
|
+
options: ValidateOptions = {},
|
|
1406
|
+
): Float32Array {
|
|
976
1407
|
if (!module) {
|
|
977
1408
|
throw new Error('Module not initialized. Call init() first.');
|
|
978
1409
|
}
|
|
1410
|
+
assertSamples('percussive', samples, options.validate !== false);
|
|
979
1411
|
return module.percussive(samples, sampleRate);
|
|
980
1412
|
}
|
|
981
1413
|
|
|
@@ -987,10 +1419,16 @@ export function percussive(samples: Float32Array, sampleRate: number): Float32Ar
|
|
|
987
1419
|
* @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
|
|
988
1420
|
* @returns Time-stretched audio
|
|
989
1421
|
*/
|
|
990
|
-
export function timeStretch(
|
|
1422
|
+
export function timeStretch(
|
|
1423
|
+
samples: Float32Array,
|
|
1424
|
+
sampleRate: number,
|
|
1425
|
+
rate: number,
|
|
1426
|
+
options: ValidateOptions = {},
|
|
1427
|
+
): Float32Array {
|
|
991
1428
|
if (!module) {
|
|
992
1429
|
throw new Error('Module not initialized. Call init() first.');
|
|
993
1430
|
}
|
|
1431
|
+
assertSamples('timeStretch', samples, options.validate !== false);
|
|
994
1432
|
return module.timeStretch(samples, sampleRate, rate);
|
|
995
1433
|
}
|
|
996
1434
|
|
|
@@ -1006,10 +1444,12 @@ export function pitchShift(
|
|
|
1006
1444
|
samples: Float32Array,
|
|
1007
1445
|
sampleRate: number,
|
|
1008
1446
|
semitones: number,
|
|
1447
|
+
options: ValidateOptions = {},
|
|
1009
1448
|
): Float32Array {
|
|
1010
1449
|
if (!module) {
|
|
1011
1450
|
throw new Error('Module not initialized. Call init() first.');
|
|
1012
1451
|
}
|
|
1452
|
+
assertSamples('pitchShift', samples, options.validate !== false);
|
|
1013
1453
|
return module.pitchShift(samples, sampleRate, semitones);
|
|
1014
1454
|
}
|
|
1015
1455
|
|
|
@@ -1024,13 +1464,15 @@ export function pitchShift(
|
|
|
1024
1464
|
*/
|
|
1025
1465
|
export function pitchCorrectToMidi(
|
|
1026
1466
|
samples: Float32Array,
|
|
1027
|
-
sampleRate
|
|
1028
|
-
currentMidi
|
|
1029
|
-
targetMidi
|
|
1467
|
+
sampleRate = 22050,
|
|
1468
|
+
currentMidi = 69.0,
|
|
1469
|
+
targetMidi = 69.0,
|
|
1470
|
+
options: ValidateOptions = {},
|
|
1030
1471
|
): Float32Array {
|
|
1031
1472
|
if (!module) {
|
|
1032
1473
|
throw new Error('Module not initialized. Call init() first.');
|
|
1033
1474
|
}
|
|
1475
|
+
assertSamples('pitchCorrectToMidi', samples, options.validate !== false);
|
|
1034
1476
|
return module.pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
|
|
1035
1477
|
}
|
|
1036
1478
|
|
|
@@ -1046,14 +1488,16 @@ export function pitchCorrectToMidi(
|
|
|
1046
1488
|
*/
|
|
1047
1489
|
export function noteStretch(
|
|
1048
1490
|
samples: Float32Array,
|
|
1049
|
-
sampleRate
|
|
1050
|
-
onsetSample
|
|
1051
|
-
offsetSample
|
|
1052
|
-
stretchRatio
|
|
1491
|
+
sampleRate = 22050,
|
|
1492
|
+
onsetSample = 0,
|
|
1493
|
+
offsetSample = 0,
|
|
1494
|
+
stretchRatio = 1.0,
|
|
1495
|
+
options: ValidateOptions = {},
|
|
1053
1496
|
): Float32Array {
|
|
1054
1497
|
if (!module) {
|
|
1055
1498
|
throw new Error('Module not initialized. Call init() first.');
|
|
1056
1499
|
}
|
|
1500
|
+
assertSamples('noteStretch', samples, options.validate !== false);
|
|
1057
1501
|
return module.noteStretch(samples, sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
1058
1502
|
}
|
|
1059
1503
|
|
|
@@ -1068,36 +1512,103 @@ export function noteStretch(
|
|
|
1068
1512
|
*/
|
|
1069
1513
|
export function voiceChange(
|
|
1070
1514
|
samples: Float32Array,
|
|
1071
|
-
sampleRate
|
|
1072
|
-
pitchSemitones
|
|
1073
|
-
formantFactor
|
|
1515
|
+
sampleRate = 22050,
|
|
1516
|
+
pitchSemitones = 0.0,
|
|
1517
|
+
formantFactor = 1.0,
|
|
1518
|
+
options: ValidateOptions = {},
|
|
1074
1519
|
): Float32Array {
|
|
1075
1520
|
if (!module) {
|
|
1076
1521
|
throw new Error('Module not initialized. Call init() first.');
|
|
1077
1522
|
}
|
|
1523
|
+
assertSamples('voiceChange', samples, options.validate !== false);
|
|
1078
1524
|
return module.voiceChange(samples, sampleRate, pitchSemitones, formantFactor);
|
|
1079
1525
|
}
|
|
1080
1526
|
|
|
1527
|
+
/** Options for the offline {@link voiceChangeRealtime} convenience wrapper. */
|
|
1528
|
+
export interface VoiceChangeRealtimeOptions extends ValidateOptions {
|
|
1529
|
+
sampleRate?: number;
|
|
1530
|
+
/** Voice-changer preset id or full config object. */
|
|
1531
|
+
preset?: RealtimeVoiceChangerConfigInput;
|
|
1532
|
+
/** Channel count (1 = mono, 2 = interleaved stereo). */
|
|
1533
|
+
channels?: 1 | 2;
|
|
1534
|
+
/** Block size for the internal render loop (default 512). */
|
|
1535
|
+
blockSize?: number;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1081
1538
|
/**
|
|
1082
|
-
*
|
|
1539
|
+
* Applies the realtime voice-changer chain to a whole buffer in one call.
|
|
1083
1540
|
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1541
|
+
* Constructs and prepares a {@link RealtimeVoiceChanger}, runs the block loop
|
|
1542
|
+
* for the caller, then disposes it — matching the Python `voice_change_realtime`
|
|
1543
|
+
* and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
|
|
1544
|
+
* plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
|
|
1545
|
+
*
|
|
1546
|
+
* @returns The processed buffer (same layout/length as the input).
|
|
1088
1547
|
*/
|
|
1089
|
-
export function
|
|
1548
|
+
export function voiceChangeRealtime(
|
|
1549
|
+
samples: Float32Array,
|
|
1550
|
+
options: VoiceChangeRealtimeOptions = {},
|
|
1551
|
+
): Float32Array {
|
|
1090
1552
|
if (!module) {
|
|
1091
1553
|
throw new Error('Module not initialized. Call init() first.');
|
|
1092
1554
|
}
|
|
1093
|
-
|
|
1555
|
+
assertSamples('voiceChangeRealtime', samples, options.validate !== false);
|
|
1556
|
+
const channels = options.channels ?? 1;
|
|
1557
|
+
if (channels !== 1 && channels !== 2) {
|
|
1558
|
+
throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
|
|
1559
|
+
}
|
|
1560
|
+
// 48000 matches the Python voice_change_realtime and Node voiceChangeRealtime
|
|
1561
|
+
// convenience wrappers (and the RealtimeVoiceChanger default).
|
|
1562
|
+
const sampleRate = options.sampleRate ?? 48000;
|
|
1563
|
+
const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
|
|
1564
|
+
const changer = new RealtimeVoiceChanger(options.preset ?? 'neutral-monitor');
|
|
1565
|
+
try {
|
|
1566
|
+
changer.prepare(sampleRate, blockSize, channels);
|
|
1567
|
+
const out = new Float32Array(samples.length);
|
|
1568
|
+
if (channels === 1) {
|
|
1569
|
+
for (let offset = 0; offset < samples.length; offset += blockSize) {
|
|
1570
|
+
const block = samples.subarray(offset, Math.min(offset + blockSize, samples.length));
|
|
1571
|
+
out.set(changer.processMono(block), offset);
|
|
1572
|
+
}
|
|
1573
|
+
} else {
|
|
1574
|
+
const frameStride = blockSize * 2;
|
|
1575
|
+
for (let offset = 0; offset < samples.length; offset += frameStride) {
|
|
1576
|
+
const block = samples.subarray(offset, Math.min(offset + frameStride, samples.length));
|
|
1577
|
+
out.set(changer.processInterleaved(block, 2), offset);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
return out;
|
|
1581
|
+
} finally {
|
|
1582
|
+
changer.delete();
|
|
1583
|
+
}
|
|
1094
1584
|
}
|
|
1095
1585
|
|
|
1096
1586
|
/**
|
|
1097
|
-
*
|
|
1587
|
+
* Normalize audio to target peak level.
|
|
1098
1588
|
*
|
|
1099
1589
|
* @param samples - Audio samples (mono, float32)
|
|
1100
1590
|
* @param sampleRate - Sample rate in Hz
|
|
1591
|
+
* @param targetDb - Target peak level in dB (default: 0 dB = full scale)
|
|
1592
|
+
* @returns Normalized audio
|
|
1593
|
+
*/
|
|
1594
|
+
export function normalize(
|
|
1595
|
+
samples: Float32Array,
|
|
1596
|
+
sampleRate: number,
|
|
1597
|
+
targetDb = 0.0,
|
|
1598
|
+
options: ValidateOptions = {},
|
|
1599
|
+
): Float32Array {
|
|
1600
|
+
if (!module) {
|
|
1601
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1602
|
+
}
|
|
1603
|
+
assertSamples('normalize', samples, options.validate !== false);
|
|
1604
|
+
return module.normalize(samples, sampleRate, targetDb);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* Apply mastering loudness normalization with a true-peak ceiling.
|
|
1609
|
+
*
|
|
1610
|
+
* @param samples - Audio samples (mono, float32)
|
|
1611
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1101
1612
|
* @param targetLufs - Target integrated LUFS (default: -14)
|
|
1102
1613
|
* @param ceilingDb - True/sample peak ceiling in dBFS (default: -1)
|
|
1103
1614
|
* @param truePeakOversample - Oversampling factor used for peak estimation
|
|
@@ -1105,7 +1616,7 @@ export function normalize(samples: Float32Array, sampleRate: number, targetDb =
|
|
|
1105
1616
|
*/
|
|
1106
1617
|
export function mastering(
|
|
1107
1618
|
samples: Float32Array,
|
|
1108
|
-
sampleRate
|
|
1619
|
+
sampleRate = 22050,
|
|
1109
1620
|
targetLufs = -14.0,
|
|
1110
1621
|
ceilingDb = -1.0,
|
|
1111
1622
|
truePeakOversample = 4,
|
|
@@ -1147,7 +1658,7 @@ export function masteringStereoAnalysisNames(): StereoAnalysis[] {
|
|
|
1147
1658
|
export function masteringProcess(
|
|
1148
1659
|
processorName: SoloProcessor,
|
|
1149
1660
|
samples: Float32Array,
|
|
1150
|
-
sampleRate
|
|
1661
|
+
sampleRate = 22050,
|
|
1151
1662
|
params: MasteringProcessorParams = {},
|
|
1152
1663
|
): MasteringResult {
|
|
1153
1664
|
if (!module) {
|
|
@@ -1160,7 +1671,7 @@ export function masteringProcessStereo(
|
|
|
1160
1671
|
processorName: SoloProcessor,
|
|
1161
1672
|
left: Float32Array,
|
|
1162
1673
|
right: Float32Array,
|
|
1163
|
-
sampleRate
|
|
1674
|
+
sampleRate = 22050,
|
|
1164
1675
|
params: MasteringProcessorParams = {},
|
|
1165
1676
|
): MasteringStereoResult {
|
|
1166
1677
|
if (!module) {
|
|
@@ -1176,7 +1687,7 @@ export function masteringPairProcess(
|
|
|
1176
1687
|
processorName: PairProcessor,
|
|
1177
1688
|
source: Float32Array,
|
|
1178
1689
|
reference: Float32Array,
|
|
1179
|
-
sampleRate
|
|
1690
|
+
sampleRate = 22050,
|
|
1180
1691
|
params: MasteringProcessorParams = {},
|
|
1181
1692
|
): MasteringResult {
|
|
1182
1693
|
if (!module) {
|
|
@@ -1189,7 +1700,7 @@ export function masteringPairAnalyze(
|
|
|
1189
1700
|
analysisName: PairAnalysis,
|
|
1190
1701
|
source: Float32Array,
|
|
1191
1702
|
reference: Float32Array,
|
|
1192
|
-
sampleRate
|
|
1703
|
+
sampleRate = 22050,
|
|
1193
1704
|
params: MasteringProcessorParams = {},
|
|
1194
1705
|
): string {
|
|
1195
1706
|
if (!module) {
|
|
@@ -1202,7 +1713,7 @@ export function masteringStereoAnalyze(
|
|
|
1202
1713
|
analysisName: StereoAnalysis,
|
|
1203
1714
|
left: Float32Array,
|
|
1204
1715
|
right: Float32Array,
|
|
1205
|
-
sampleRate
|
|
1716
|
+
sampleRate = 22050,
|
|
1206
1717
|
params: MasteringProcessorParams = {},
|
|
1207
1718
|
): string {
|
|
1208
1719
|
if (!module) {
|
|
@@ -1213,7 +1724,7 @@ export function masteringStereoAnalyze(
|
|
|
1213
1724
|
|
|
1214
1725
|
export function masteringAssistantSuggest(
|
|
1215
1726
|
samples: Float32Array,
|
|
1216
|
-
sampleRate
|
|
1727
|
+
sampleRate = 22050,
|
|
1217
1728
|
params: MasteringProcessorParams = {},
|
|
1218
1729
|
): string {
|
|
1219
1730
|
if (!module) {
|
|
@@ -1224,7 +1735,7 @@ export function masteringAssistantSuggest(
|
|
|
1224
1735
|
|
|
1225
1736
|
export function masteringAudioProfile(
|
|
1226
1737
|
samples: Float32Array,
|
|
1227
|
-
sampleRate
|
|
1738
|
+
sampleRate = 22050,
|
|
1228
1739
|
params: MasteringProcessorParams = {},
|
|
1229
1740
|
): string {
|
|
1230
1741
|
if (!module) {
|
|
@@ -1235,7 +1746,7 @@ export function masteringAudioProfile(
|
|
|
1235
1746
|
|
|
1236
1747
|
export function masteringStreamingPreview(
|
|
1237
1748
|
samples: Float32Array,
|
|
1238
|
-
sampleRate
|
|
1749
|
+
sampleRate = 22050,
|
|
1239
1750
|
platforms: StreamingPlatform[] = [],
|
|
1240
1751
|
): string {
|
|
1241
1752
|
if (!module) {
|
|
@@ -1244,17 +1755,271 @@ export function masteringStreamingPreview(
|
|
|
1244
1755
|
return module.masteringStreamingPreview(samples, sampleRate, platforms);
|
|
1245
1756
|
}
|
|
1246
1757
|
|
|
1758
|
+
// ============================================================================
|
|
1759
|
+
// Mastering repair (declick, denoise_classical, declip, decrackle, dehum,
|
|
1760
|
+
// dereverb_classical, trim_silence) — hand-written bindings.
|
|
1761
|
+
// ============================================================================
|
|
1762
|
+
|
|
1763
|
+
/** Options for `masteringRepairDeclick`. */
|
|
1764
|
+
export interface DeclickOptions {
|
|
1765
|
+
threshold?: number;
|
|
1766
|
+
neighborRatio?: number;
|
|
1767
|
+
maxClickSamples?: number;
|
|
1768
|
+
lpcOrder?: number;
|
|
1769
|
+
residualRatio?: number;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/** Algorithms accepted by `masteringRepairDenoiseClassical`. */
|
|
1773
|
+
export type DenoiseClassicalMode = 'logMmse' | 'mmseStsa' | 'spectralSubtraction';
|
|
1774
|
+
|
|
1775
|
+
/** Noise PSD estimators accepted by `masteringRepairDenoiseClassical`. */
|
|
1776
|
+
export type DenoiseClassicalNoiseEstimator = 'quantile' | 'mcra' | 'imcra';
|
|
1777
|
+
|
|
1778
|
+
/** Options for `masteringRepairDenoiseClassical`. */
|
|
1779
|
+
export interface DenoiseClassicalOptions {
|
|
1780
|
+
mode?: DenoiseClassicalMode;
|
|
1781
|
+
noiseEstimator?: DenoiseClassicalNoiseEstimator;
|
|
1782
|
+
nFft?: number;
|
|
1783
|
+
hopLength?: number;
|
|
1784
|
+
ddAlpha?: number;
|
|
1785
|
+
gainFloor?: number;
|
|
1786
|
+
overSubtraction?: number;
|
|
1787
|
+
spectralFloor?: number;
|
|
1788
|
+
noiseEstimationQuantile?: number;
|
|
1789
|
+
speechPresenceGain?: boolean;
|
|
1790
|
+
gainSmoothing?: boolean;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/** Offline LPC-based declicker. */
|
|
1794
|
+
export function masteringRepairDeclick(
|
|
1795
|
+
samples: Float32Array,
|
|
1796
|
+
sampleRate: number,
|
|
1797
|
+
options: DeclickOptions = {},
|
|
1798
|
+
): Float32Array {
|
|
1799
|
+
return requireModule().masteringRepairDeclick(samples, sampleRate, options);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/** Offline STFT-domain classical denoiser (LogMMSE / MMSE-STSA / SpectralSubtraction). */
|
|
1803
|
+
export function masteringRepairDenoiseClassical(
|
|
1804
|
+
samples: Float32Array,
|
|
1805
|
+
sampleRate: number,
|
|
1806
|
+
options: DenoiseClassicalOptions = {},
|
|
1807
|
+
): Float32Array {
|
|
1808
|
+
return requireModule().masteringRepairDenoiseClassical(samples, sampleRate, options);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/** Options for `masteringRepairDeclip`. */
|
|
1812
|
+
export interface DeclipOptions {
|
|
1813
|
+
clipThreshold?: number;
|
|
1814
|
+
lpcOrder?: number;
|
|
1815
|
+
iterations?: number;
|
|
1816
|
+
lpcBlend?: number;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
/** Algorithms accepted by `masteringRepairDecrackle`. */
|
|
1820
|
+
export type DecrackleMode = 'median' | 'waveletShrinkage';
|
|
1821
|
+
|
|
1822
|
+
/** Options for `masteringRepairDecrackle`. */
|
|
1823
|
+
export interface DecrackleOptions {
|
|
1824
|
+
threshold?: number;
|
|
1825
|
+
mode?: DecrackleMode;
|
|
1826
|
+
levels?: number;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/** Options for `masteringRepairDehum`. */
|
|
1830
|
+
export interface DehumOptions {
|
|
1831
|
+
fundamentalHz?: number;
|
|
1832
|
+
harmonics?: number;
|
|
1833
|
+
q?: number;
|
|
1834
|
+
adaptive?: boolean;
|
|
1835
|
+
searchRangeHz?: number;
|
|
1836
|
+
adaptation?: number;
|
|
1837
|
+
frameSize?: number;
|
|
1838
|
+
pllBandwidth?: number;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
/** Options for `masteringRepairDereverbClassical`. */
|
|
1842
|
+
export interface DereverbClassicalOptions {
|
|
1843
|
+
threshold?: number;
|
|
1844
|
+
attenuation?: number;
|
|
1845
|
+
nFft?: number;
|
|
1846
|
+
hopLength?: number;
|
|
1847
|
+
t60Sec?: number;
|
|
1848
|
+
lateDelayMs?: number;
|
|
1849
|
+
overSubtraction?: number;
|
|
1850
|
+
spectralFloor?: number;
|
|
1851
|
+
wpeEnabled?: boolean;
|
|
1852
|
+
wpeIterations?: number;
|
|
1853
|
+
wpeTaps?: number;
|
|
1854
|
+
wpeStrength?: number;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
/** Trimming modes accepted by `masteringRepairTrimSilence`. */
|
|
1858
|
+
export type TrimSilenceMode = 'peak' | 'lufsGated';
|
|
1859
|
+
|
|
1860
|
+
/** Options for `masteringRepairTrimSilence`. */
|
|
1861
|
+
export interface TrimSilenceOptions {
|
|
1862
|
+
threshold?: number;
|
|
1863
|
+
paddingSamples?: number;
|
|
1864
|
+
mode?: TrimSilenceMode;
|
|
1865
|
+
gateLufs?: number;
|
|
1866
|
+
windowMs?: number;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/** Offline LPC-based declipper. */
|
|
1870
|
+
export function masteringRepairDeclip(
|
|
1871
|
+
samples: Float32Array,
|
|
1872
|
+
sampleRate: number,
|
|
1873
|
+
options: DeclipOptions = {},
|
|
1874
|
+
): Float32Array {
|
|
1875
|
+
return requireModule().masteringRepairDeclip(samples, sampleRate, options);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/** Offline crackle suppressor (median or wavelet-shrinkage). */
|
|
1879
|
+
export function masteringRepairDecrackle(
|
|
1880
|
+
samples: Float32Array,
|
|
1881
|
+
sampleRate: number,
|
|
1882
|
+
options: DecrackleOptions = {},
|
|
1883
|
+
): Float32Array {
|
|
1884
|
+
return requireModule().masteringRepairDecrackle(samples, sampleRate, options);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/** Offline mains-hum remover. */
|
|
1888
|
+
export function masteringRepairDehum(
|
|
1889
|
+
samples: Float32Array,
|
|
1890
|
+
sampleRate: number,
|
|
1891
|
+
options: DehumOptions = {},
|
|
1892
|
+
): Float32Array {
|
|
1893
|
+
return requireModule().masteringRepairDehum(samples, sampleRate, options);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/** Offline classical dereverberator (spectral subtraction + optional WPE). */
|
|
1897
|
+
export function masteringRepairDereverbClassical(
|
|
1898
|
+
samples: Float32Array,
|
|
1899
|
+
sampleRate: number,
|
|
1900
|
+
options: DereverbClassicalOptions = {},
|
|
1901
|
+
): Float32Array {
|
|
1902
|
+
return requireModule().masteringRepairDereverbClassical(samples, sampleRate, options);
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
/** Offline silence trimmer (peak threshold or LUFS-gated). */
|
|
1906
|
+
export function masteringRepairTrimSilence(
|
|
1907
|
+
samples: Float32Array,
|
|
1908
|
+
sampleRate: number,
|
|
1909
|
+
options: TrimSilenceOptions = {},
|
|
1910
|
+
): Float32Array {
|
|
1911
|
+
return requireModule().masteringRepairTrimSilence(samples, sampleRate, options);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// ============================================================================
|
|
1915
|
+
// Mastering — offline dynamics processors (compressor / gate / transient_shaper)
|
|
1916
|
+
// ============================================================================
|
|
1917
|
+
|
|
1918
|
+
/** Compressor sidechain detector mode. */
|
|
1919
|
+
export type CompressorDetector = 'peak' | 'rms' | 'log_rms';
|
|
1920
|
+
|
|
1921
|
+
/** Options for `masteringDynamicsCompressor`. */
|
|
1922
|
+
export interface CompressorOptions extends ValidateOptions {
|
|
1923
|
+
thresholdDb?: number;
|
|
1924
|
+
ratio?: number;
|
|
1925
|
+
attackMs?: number;
|
|
1926
|
+
releaseMs?: number;
|
|
1927
|
+
kneeDb?: number;
|
|
1928
|
+
makeupGainDb?: number;
|
|
1929
|
+
autoMakeup?: boolean;
|
|
1930
|
+
detector?: CompressorDetector | number;
|
|
1931
|
+
sidechainHpfEnabled?: boolean;
|
|
1932
|
+
sidechainHpfHz?: number;
|
|
1933
|
+
pdrTimeMs?: number;
|
|
1934
|
+
pdrReleaseScale?: number;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
/** Options for `masteringDynamicsGate`. */
|
|
1938
|
+
export interface GateOptions extends ValidateOptions {
|
|
1939
|
+
thresholdDb?: number;
|
|
1940
|
+
attackMs?: number;
|
|
1941
|
+
releaseMs?: number;
|
|
1942
|
+
rangeDb?: number;
|
|
1943
|
+
holdMs?: number;
|
|
1944
|
+
closeThresholdDb?: number;
|
|
1945
|
+
keyHpfHz?: number;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/** Options for `masteringDynamicsTransientShaper`. */
|
|
1949
|
+
export interface TransientShaperOptions extends ValidateOptions {
|
|
1950
|
+
attackGainDb?: number;
|
|
1951
|
+
sustainGainDb?: number;
|
|
1952
|
+
fastAttackMs?: number;
|
|
1953
|
+
fastReleaseMs?: number;
|
|
1954
|
+
slowAttackMs?: number;
|
|
1955
|
+
slowReleaseMs?: number;
|
|
1956
|
+
sensitivity?: number;
|
|
1957
|
+
maxGainDb?: number;
|
|
1958
|
+
gainSmoothingMs?: number;
|
|
1959
|
+
lookaheadMs?: number;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/** Result envelope returned by offline mastering dynamics processors. */
|
|
1963
|
+
export interface DynamicsResult {
|
|
1964
|
+
samples: Float32Array;
|
|
1965
|
+
latencySamples: number;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
const COMPRESSOR_DETECTOR_MAP: Record<CompressorDetector, number> = {
|
|
1969
|
+
peak: 0,
|
|
1970
|
+
rms: 1,
|
|
1971
|
+
log_rms: 2,
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1974
|
+
/** Offline feed-forward compressor (soft knee, optional auto-makeup / sidechain HPF). */
|
|
1975
|
+
export function masteringDynamicsCompressor(
|
|
1976
|
+
samples: Float32Array,
|
|
1977
|
+
sampleRate: number,
|
|
1978
|
+
options: CompressorOptions = {},
|
|
1979
|
+
): DynamicsResult {
|
|
1980
|
+
assertSamples('masteringDynamicsCompressor', samples, options.validate !== false);
|
|
1981
|
+
const detector =
|
|
1982
|
+
typeof options.detector === 'string'
|
|
1983
|
+
? COMPRESSOR_DETECTOR_MAP[options.detector]
|
|
1984
|
+
: options.detector;
|
|
1985
|
+
const opts: Record<string, unknown> = { ...options };
|
|
1986
|
+
if (detector !== undefined) {
|
|
1987
|
+
opts.detector = detector;
|
|
1988
|
+
}
|
|
1989
|
+
return requireModule().masteringDynamicsCompressor(samples, sampleRate, opts);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
/** Offline noise gate (hysteresis, hold, optional key HPF). */
|
|
1993
|
+
export function masteringDynamicsGate(
|
|
1994
|
+
samples: Float32Array,
|
|
1995
|
+
sampleRate: number,
|
|
1996
|
+
options: GateOptions = {},
|
|
1997
|
+
): DynamicsResult {
|
|
1998
|
+
assertSamples('masteringDynamicsGate', samples, options.validate !== false);
|
|
1999
|
+
return requireModule().masteringDynamicsGate(samples, sampleRate, options);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/** Offline transient shaper (envelope-difference attack/sustain control). */
|
|
2003
|
+
export function masteringDynamicsTransientShaper(
|
|
2004
|
+
samples: Float32Array,
|
|
2005
|
+
sampleRate: number,
|
|
2006
|
+
options: TransientShaperOptions = {},
|
|
2007
|
+
): DynamicsResult {
|
|
2008
|
+
assertSamples('masteringDynamicsTransientShaper', samples, options.validate !== false);
|
|
2009
|
+
return requireModule().masteringDynamicsTransientShaper(samples, sampleRate, options);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1247
2012
|
/**
|
|
1248
2013
|
* Apply a configurable mastering chain in WASM.
|
|
1249
2014
|
*
|
|
1250
2015
|
* @param samples - Audio samples (mono, float32)
|
|
1251
|
-
* @param sampleRate - Sample rate in Hz
|
|
2016
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1252
2017
|
* @param config - Chain stage configuration
|
|
1253
2018
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1254
2019
|
*/
|
|
1255
2020
|
export function masteringChain(
|
|
1256
2021
|
samples: Float32Array,
|
|
1257
|
-
sampleRate
|
|
2022
|
+
sampleRate = 22050,
|
|
1258
2023
|
config: MasteringChainConfig,
|
|
1259
2024
|
): MasteringChainResult {
|
|
1260
2025
|
if (!module) {
|
|
@@ -1275,7 +2040,7 @@ export function masteringChain(
|
|
|
1275
2040
|
export function masteringChainStereo(
|
|
1276
2041
|
left: Float32Array,
|
|
1277
2042
|
right: Float32Array,
|
|
1278
|
-
sampleRate
|
|
2043
|
+
sampleRate = 22050,
|
|
1279
2044
|
config: MasteringChainConfig,
|
|
1280
2045
|
): MasteringStereoChainResult {
|
|
1281
2046
|
if (!module) {
|
|
@@ -1291,14 +2056,14 @@ export function masteringChainStereo(
|
|
|
1291
2056
|
* Apply a configurable mastering chain in WASM with progress reporting.
|
|
1292
2057
|
*
|
|
1293
2058
|
* @param samples - Audio samples (mono, float32)
|
|
1294
|
-
* @param sampleRate - Sample rate in Hz
|
|
2059
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1295
2060
|
* @param config - Chain stage configuration
|
|
1296
2061
|
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
1297
2062
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1298
2063
|
*/
|
|
1299
2064
|
export function masteringChainWithProgress(
|
|
1300
2065
|
samples: Float32Array,
|
|
1301
|
-
sampleRate
|
|
2066
|
+
sampleRate = 22050,
|
|
1302
2067
|
config: MasteringChainConfig,
|
|
1303
2068
|
onProgress: ProgressCallback,
|
|
1304
2069
|
): MasteringChainResult {
|
|
@@ -1326,7 +2091,7 @@ export function masteringChainWithProgress(
|
|
|
1326
2091
|
export function masteringChainStereoWithProgress(
|
|
1327
2092
|
left: Float32Array,
|
|
1328
2093
|
right: Float32Array,
|
|
1329
|
-
sampleRate
|
|
2094
|
+
sampleRate = 22050,
|
|
1330
2095
|
config: MasteringChainConfig,
|
|
1331
2096
|
onProgress: ProgressCallback,
|
|
1332
2097
|
): MasteringStereoChainResult {
|
|
@@ -1361,14 +2126,14 @@ export function masteringPresetNames(): MasteringPreset[] {
|
|
|
1361
2126
|
* Apply a named mastering preset chain to mono audio.
|
|
1362
2127
|
*
|
|
1363
2128
|
* @param samples - Audio samples (mono, float32)
|
|
1364
|
-
* @param sampleRate - Sample rate in Hz
|
|
2129
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
1365
2130
|
* @param presetName - Preset identifier from {@link masteringPresetNames}
|
|
1366
2131
|
* @param overrides - Optional flat overrides (dot-notation, e.g. `'loudness.targetLufs'`) applied on top of the preset. Pass `null` for preset defaults.
|
|
1367
2132
|
* @returns Processed audio, loudness metadata, and applied stage names
|
|
1368
2133
|
*/
|
|
1369
2134
|
export function masterAudio(
|
|
1370
2135
|
samples: Float32Array,
|
|
1371
|
-
sampleRate
|
|
2136
|
+
sampleRate = 22050,
|
|
1372
2137
|
presetName: MasteringPreset,
|
|
1373
2138
|
overrides: Record<string, number | boolean> | null = null,
|
|
1374
2139
|
): MasteringChainResult {
|
|
@@ -1391,7 +2156,7 @@ export function masterAudio(
|
|
|
1391
2156
|
export function masterAudioStereo(
|
|
1392
2157
|
left: Float32Array,
|
|
1393
2158
|
right: Float32Array,
|
|
1394
|
-
sampleRate
|
|
2159
|
+
sampleRate = 22050,
|
|
1395
2160
|
presetName: MasteringPreset,
|
|
1396
2161
|
overrides: Record<string, number | boolean> | null = null,
|
|
1397
2162
|
): MasteringStereoChainResult {
|
|
@@ -1404,6 +2169,50 @@ export function masterAudioStereo(
|
|
|
1404
2169
|
return module.masterAudioStereo(presetName, left, right, sampleRate, overrides);
|
|
1405
2170
|
}
|
|
1406
2171
|
|
|
2172
|
+
/**
|
|
2173
|
+
* Mono `masterAudio` with per-stage progress reporting. `onProgress` is invoked
|
|
2174
|
+
* with `(progress, stage)` between each chain stage (progress is in [0,1]).
|
|
2175
|
+
*/
|
|
2176
|
+
export function masterAudioWithProgress(
|
|
2177
|
+
samples: Float32Array,
|
|
2178
|
+
sampleRate = 22050,
|
|
2179
|
+
presetName: MasteringPreset,
|
|
2180
|
+
onProgress: ProgressCallback,
|
|
2181
|
+
overrides: Record<string, number | boolean> | null = null,
|
|
2182
|
+
): MasteringChainResult {
|
|
2183
|
+
if (!module) {
|
|
2184
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2185
|
+
}
|
|
2186
|
+
return module.masterAudioWithProgress(presetName, samples, sampleRate, overrides, onProgress);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* Stereo `masterAudio` with per-stage progress reporting.
|
|
2191
|
+
*/
|
|
2192
|
+
export function masterAudioStereoWithProgress(
|
|
2193
|
+
left: Float32Array,
|
|
2194
|
+
right: Float32Array,
|
|
2195
|
+
sampleRate = 22050,
|
|
2196
|
+
presetName: MasteringPreset,
|
|
2197
|
+
onProgress: ProgressCallback,
|
|
2198
|
+
overrides: Record<string, number | boolean> | null = null,
|
|
2199
|
+
): MasteringStereoChainResult {
|
|
2200
|
+
if (!module) {
|
|
2201
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2202
|
+
}
|
|
2203
|
+
if (left.length !== right.length) {
|
|
2204
|
+
throw new Error('Stereo channel lengths must match.');
|
|
2205
|
+
}
|
|
2206
|
+
return module.masterAudioStereoWithProgress(
|
|
2207
|
+
presetName,
|
|
2208
|
+
left,
|
|
2209
|
+
right,
|
|
2210
|
+
sampleRate,
|
|
2211
|
+
overrides,
|
|
2212
|
+
onProgress,
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
1407
2216
|
export function mixingScenePresetNames(): string[] {
|
|
1408
2217
|
if (!module) {
|
|
1409
2218
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -1416,14 +2225,14 @@ export function mixingScenePresetNames(): string[] {
|
|
|
1416
2225
|
* name shared with the Node and Python bindings; the returned JSON loads
|
|
1417
2226
|
* directly into a {@link Mixer} via {@link Mixer.fromSceneJson}.
|
|
1418
2227
|
*
|
|
1419
|
-
* @param
|
|
2228
|
+
* @param presetName - Preset name (see {@link mixingScenePresetNames})
|
|
1420
2229
|
* @returns Scene JSON string
|
|
1421
2230
|
*/
|
|
1422
|
-
export function mixingScenePresetJson(
|
|
2231
|
+
export function mixingScenePresetJson(presetName: string): string {
|
|
1423
2232
|
if (!module) {
|
|
1424
2233
|
throw new Error('Module not initialized. Call init() first.');
|
|
1425
2234
|
}
|
|
1426
|
-
return module.mixingScenePresetJson(
|
|
2235
|
+
return module.mixingScenePresetJson(presetName);
|
|
1427
2236
|
}
|
|
1428
2237
|
|
|
1429
2238
|
export function mixStereo(
|
|
@@ -1472,7 +2281,7 @@ export function mixStereo(
|
|
|
1472
2281
|
* ```
|
|
1473
2282
|
*/
|
|
1474
2283
|
export class StreamingMasteringChain {
|
|
1475
|
-
private chain: import('./
|
|
2284
|
+
private chain: import('./sonare.js').WasmStreamingMasteringChain;
|
|
1476
2285
|
|
|
1477
2286
|
constructor(config: MasteringChainConfig) {
|
|
1478
2287
|
if (!module) {
|
|
@@ -1559,7 +2368,7 @@ export class StreamingMasteringChain {
|
|
|
1559
2368
|
* ```
|
|
1560
2369
|
*/
|
|
1561
2370
|
export class StreamingEqualizer {
|
|
1562
|
-
private eq: import('./
|
|
2371
|
+
private eq: import('./sonare.js').WasmStreamingEqualizer;
|
|
1563
2372
|
|
|
1564
2373
|
constructor(config: StreamingEqualizerConfig = {}) {
|
|
1565
2374
|
if (!module) {
|
|
@@ -1697,7 +2506,7 @@ export class StreamingEqualizer {
|
|
|
1697
2506
|
* the underlying WASM object.
|
|
1698
2507
|
*/
|
|
1699
2508
|
export class StreamingRetune {
|
|
1700
|
-
private retune: import('./
|
|
2509
|
+
private retune: import('./sonare.js').WasmStreamingRetune;
|
|
1701
2510
|
|
|
1702
2511
|
constructor(config: StreamingRetuneConfig = {}) {
|
|
1703
2512
|
if (!module) {
|
|
@@ -1749,32 +2558,207 @@ export class StreamingRetune {
|
|
|
1749
2558
|
}
|
|
1750
2559
|
|
|
1751
2560
|
// ============================================================================
|
|
1752
|
-
//
|
|
2561
|
+
// RealtimeVoiceChanger Class
|
|
1753
2562
|
// ============================================================================
|
|
1754
2563
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2564
|
+
export class RealtimeVoiceChanger {
|
|
2565
|
+
private changer: import('./sonare.js').WasmRealtimeVoiceChanger;
|
|
2566
|
+
|
|
2567
|
+
constructor(config: RealtimeVoiceChangerConfigInput = 'neutral-monitor') {
|
|
2568
|
+
if (!module) {
|
|
2569
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2570
|
+
}
|
|
2571
|
+
this.changer = module.createRealtimeVoiceChanger(config as Record<string, unknown> | string);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
prepare(sampleRate: number, maxBlockSize = 128, channels = 1): void {
|
|
2575
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
reset(): void {
|
|
2579
|
+
this.changer.reset();
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
setConfig(config: RealtimeVoiceChangerConfigInput): void {
|
|
2583
|
+
this.changer.setConfig(config as Record<string, unknown> | string);
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
configJson(): string {
|
|
2587
|
+
return this.changer.configJson();
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
latencySamples(): number {
|
|
2591
|
+
return this.changer.latencySamples();
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
processMono(samples: Float32Array): Float32Array {
|
|
2595
|
+
return this.changer.processMono(samples);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
processMonoInto(samples: Float32Array, output: Float32Array): void {
|
|
2599
|
+
this.changer.processMonoInto(samples, output);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
processInterleaved(samples: Float32Array, channels: number): Float32Array {
|
|
2603
|
+
return this.changer.processInterleaved(samples, channels);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
processInterleavedInto(samples: Float32Array, channels: number, output: Float32Array): void {
|
|
2607
|
+
this.changer.processInterleavedInto(samples, channels, output);
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
/**
|
|
2611
|
+
* Acquire a typed-memory view onto the WASM heap for mono input.
|
|
2612
|
+
*
|
|
2613
|
+
* Write your input samples into the returned `Float32Array` directly (e.g.
|
|
2614
|
+
* via `input.set(source)`); no copy crosses the JS↔C++ bridge until
|
|
2615
|
+
* {@link processPreparedMono} is called. The view is owned by this
|
|
2616
|
+
* RealtimeVoiceChanger and becomes invalid after {@link delete}; it may
|
|
2617
|
+
* also be invalidated if you later call this method with a larger
|
|
2618
|
+
* `numSamples` value (the underlying buffer may be reallocated).
|
|
2619
|
+
*/
|
|
2620
|
+
getMonoInputBuffer(numSamples: number): Float32Array {
|
|
2621
|
+
return this.changer.getMonoInputBuffer(numSamples);
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
/** Mono output view counterpart to {@link getMonoInputBuffer}. */
|
|
2625
|
+
getMonoOutputBuffer(numSamples: number): Float32Array {
|
|
2626
|
+
return this.changer.getMonoOutputBuffer(numSamples);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
/**
|
|
2630
|
+
* Process the previously-acquired mono input buffer in place. The output
|
|
2631
|
+
* appears in the buffer returned by {@link getMonoOutputBuffer}. No JS↔C++
|
|
2632
|
+
* sample-level crossings happen on this call — it just hands control to
|
|
2633
|
+
* the underlying DSP on already-on-heap data.
|
|
2634
|
+
*/
|
|
2635
|
+
processPreparedMono(numSamples: number): void {
|
|
2636
|
+
this.changer.processPreparedMono(numSamples);
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
/** Interleaved input view (layout L0,R0,L1,R1,...). */
|
|
2640
|
+
getInterleavedInputBuffer(numFrames: number, numChannels: number): Float32Array {
|
|
2641
|
+
return this.changer.getInterleavedInputBuffer(numFrames, numChannels);
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
/** Interleaved output view counterpart. */
|
|
2645
|
+
getInterleavedOutputBuffer(numFrames: number, numChannels: number): Float32Array {
|
|
2646
|
+
return this.changer.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
/**
|
|
2650
|
+
* Process the previously-acquired interleaved buffer in place. Output
|
|
2651
|
+
* appears in the buffer returned by {@link getInterleavedOutputBuffer}.
|
|
2652
|
+
*/
|
|
2653
|
+
processPreparedInterleaved(numFrames: number, numChannels: number): void {
|
|
2654
|
+
this.changer.processPreparedInterleaved(numFrames, numChannels);
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
/**
|
|
2658
|
+
* Planar-channel input/output view (one Float32Array per channel). Matches
|
|
2659
|
+
* AudioWorklet's native layout; processing happens in place.
|
|
2660
|
+
*/
|
|
2661
|
+
getPlanarChannelBuffer(channel: number, numFrames: number): Float32Array {
|
|
2662
|
+
return this.changer.getPlanarChannelBuffer(channel, numFrames);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* Process the previously-acquired planar channel buffers in place. Each
|
|
2667
|
+
* channel must have been obtained from {@link getPlanarChannelBuffer}
|
|
2668
|
+
* with the same `numFrames`. Output replaces input in the same buffers.
|
|
2669
|
+
*/
|
|
2670
|
+
processPreparedPlanar(numFrames: number): void {
|
|
2671
|
+
this.changer.processPreparedPlanar(numFrames);
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
/**
|
|
2675
|
+
* Convenience factory for the mono zero-copy path: returns the input/output
|
|
2676
|
+
* heap views plus a `process()` thunk wired to the same `numSamples`. The
|
|
2677
|
+
* views are reused across calls and become invalid after {@link delete}.
|
|
2678
|
+
*/
|
|
2679
|
+
createRealtimeMonoBuffer(numSamples: number): RealtimeVoiceChangerMonoBuffer {
|
|
2680
|
+
const input = this.getMonoInputBuffer(numSamples);
|
|
2681
|
+
const output = this.getMonoOutputBuffer(numSamples);
|
|
2682
|
+
return {
|
|
2683
|
+
input,
|
|
2684
|
+
output,
|
|
2685
|
+
process: () => this.processPreparedMono(numSamples),
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
/** Same as {@link createRealtimeMonoBuffer} but for interleaved I/O. */
|
|
2690
|
+
createRealtimeInterleavedBuffer(
|
|
2691
|
+
numFrames: number,
|
|
2692
|
+
numChannels: number,
|
|
2693
|
+
): RealtimeVoiceChangerInterleavedBuffer {
|
|
2694
|
+
const input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
2695
|
+
const output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
2696
|
+
return {
|
|
2697
|
+
input,
|
|
2698
|
+
output,
|
|
2699
|
+
channels: numChannels,
|
|
2700
|
+
process: () => this.processPreparedInterleaved(numFrames, numChannels),
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Convenience factory for the planar zero-copy path. Acquires one
|
|
2706
|
+
* heap-backed Float32Array per channel and returns a `process()` thunk
|
|
2707
|
+
* wired to the same `numFrames`. Buffers are reused across calls and
|
|
2708
|
+
* become invalid after {@link delete}.
|
|
2709
|
+
*/
|
|
2710
|
+
createRealtimePlanarBuffer(
|
|
2711
|
+
numFrames: number,
|
|
2712
|
+
numChannels: number,
|
|
2713
|
+
): RealtimeVoiceChangerPlanarBuffer {
|
|
2714
|
+
const channels: Float32Array[] = [];
|
|
2715
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
2716
|
+
channels.push(this.getPlanarChannelBuffer(ch, numFrames));
|
|
2717
|
+
}
|
|
2718
|
+
return {
|
|
2719
|
+
channels,
|
|
2720
|
+
process: () => this.processPreparedPlanar(numFrames),
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
delete(): void {
|
|
2725
|
+
this.changer.delete();
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
export function realtimeVoiceChangerPresetNames(): VoicePresetId[] {
|
|
2730
|
+
if (!module) {
|
|
2731
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2732
|
+
}
|
|
2733
|
+
return module.realtimeVoiceChangerPresetNames() as VoicePresetId[];
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
export function realtimeVoiceChangerPresetJson(name: VoicePresetId): string {
|
|
2737
|
+
if (!module) {
|
|
2738
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2739
|
+
}
|
|
2740
|
+
return module.realtimeVoiceChangerPresetJson(name);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
export function validateRealtimeVoiceChangerPresetJson(json: string): {
|
|
2744
|
+
ok: boolean;
|
|
2745
|
+
normalizedJson?: string;
|
|
2746
|
+
error?: string;
|
|
2747
|
+
} {
|
|
1768
2748
|
if (!module) {
|
|
1769
2749
|
throw new Error('Module not initialized. Call init() first.');
|
|
1770
2750
|
}
|
|
1771
|
-
return module.
|
|
2751
|
+
return module.validateRealtimeVoiceChangerPresetJson(json);
|
|
1772
2752
|
}
|
|
1773
2753
|
|
|
2754
|
+
// ============================================================================
|
|
2755
|
+
// Mixer Class (scene-based persistent mixer)
|
|
2756
|
+
// ============================================================================
|
|
2757
|
+
|
|
1774
2758
|
/**
|
|
1775
2759
|
* Persistent, scene-based stereo mixer.
|
|
1776
2760
|
*
|
|
1777
|
-
* Build one from a scene JSON string (e.g. {@link
|
|
2761
|
+
* Build one from a scene JSON string (e.g. {@link mixingScenePresetJson} or a
|
|
1778
2762
|
* hand-authored scene), then feed per-strip stereo blocks through
|
|
1779
2763
|
* {@link processStereo} to get the routed stereo master. Strips, sends, buses,
|
|
1780
2764
|
* and inserts are described entirely by the scene; the routing graph is
|
|
@@ -1786,7 +2770,7 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1786
2770
|
*
|
|
1787
2771
|
* @example
|
|
1788
2772
|
* ```typescript
|
|
1789
|
-
* const mixer = Mixer.fromSceneJson(
|
|
2773
|
+
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('basicStereo'), 48000, 512);
|
|
1790
2774
|
* try {
|
|
1791
2775
|
* const out = mixer.processStereo([stripL], [stripR]);
|
|
1792
2776
|
* } finally {
|
|
@@ -1795,9 +2779,9 @@ export function mixerScenePresetJson(preset: string): string {
|
|
|
1795
2779
|
* ```
|
|
1796
2780
|
*/
|
|
1797
2781
|
export class Mixer {
|
|
1798
|
-
private mixer: import('./
|
|
2782
|
+
private mixer: import('./sonare.js').WasmMixer;
|
|
1799
2783
|
|
|
1800
|
-
private constructor(mixer: import('./
|
|
2784
|
+
private constructor(mixer: import('./sonare.js').WasmMixer) {
|
|
1801
2785
|
this.mixer = mixer;
|
|
1802
2786
|
}
|
|
1803
2787
|
|
|
@@ -1966,6 +2950,31 @@ export class Mixer {
|
|
|
1966
2950
|
return this.mixer.vcaGroupCount();
|
|
1967
2951
|
}
|
|
1968
2952
|
|
|
2953
|
+
/** Set the strip's input trim in dB. */
|
|
2954
|
+
setInputTrimDb(stripIndex: number, db: number): void {
|
|
2955
|
+
this.mixer.setInputTrimDb(stripIndex, db);
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
/** Set the strip's fader level in dB. */
|
|
2959
|
+
setFaderDb(stripIndex: number, db: number): void {
|
|
2960
|
+
this.mixer.setFaderDb(stripIndex, db);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
/** Set the strip's pan position. */
|
|
2964
|
+
setPan(stripIndex: number, pan: number, panMode: PanMode | number = 0): void {
|
|
2965
|
+
this.mixer.setPan(stripIndex, pan, panModeCode(panMode));
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
/** Set the strip's stereo width. */
|
|
2969
|
+
setWidth(stripIndex: number, width: number): void {
|
|
2970
|
+
this.mixer.setWidth(stripIndex, width);
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
/** Set the strip's mute state. */
|
|
2974
|
+
setMuted(stripIndex: number, muted: boolean): void {
|
|
2975
|
+
this.mixer.setMuted(stripIndex, muted);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
1969
2978
|
/**
|
|
1970
2979
|
* Set a strip's solo state. Takes effect on the next process without a
|
|
1971
2980
|
* graph recompile.
|
|
@@ -2179,14 +3188,14 @@ export function trim(samples: Float32Array, sampleRate: number, thresholdDb = -6
|
|
|
2179
3188
|
* Compute Short-Time Fourier Transform (STFT).
|
|
2180
3189
|
*
|
|
2181
3190
|
* @param samples - Audio samples (mono, float32)
|
|
2182
|
-
* @param sampleRate - Sample rate in Hz
|
|
3191
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2183
3192
|
* @param nFft - FFT size (default: 2048)
|
|
2184
3193
|
* @param hopLength - Hop length (default: 512)
|
|
2185
3194
|
* @returns STFT result with magnitude and power spectrograms
|
|
2186
3195
|
*/
|
|
2187
3196
|
export function stft(
|
|
2188
3197
|
samples: Float32Array,
|
|
2189
|
-
sampleRate
|
|
3198
|
+
sampleRate = 22050,
|
|
2190
3199
|
nFft = 2048,
|
|
2191
3200
|
hopLength = 512,
|
|
2192
3201
|
): StftResult {
|
|
@@ -2200,14 +3209,14 @@ export function stft(
|
|
|
2200
3209
|
* Compute STFT and return magnitude in decibels.
|
|
2201
3210
|
*
|
|
2202
3211
|
* @param samples - Audio samples (mono, float32)
|
|
2203
|
-
* @param sampleRate - Sample rate in Hz
|
|
3212
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2204
3213
|
* @param nFft - FFT size (default: 2048)
|
|
2205
3214
|
* @param hopLength - Hop length (default: 512)
|
|
2206
3215
|
* @returns STFT result with dB values
|
|
2207
3216
|
*/
|
|
2208
3217
|
export function stftDb(
|
|
2209
3218
|
samples: Float32Array,
|
|
2210
|
-
sampleRate
|
|
3219
|
+
sampleRate = 22050,
|
|
2211
3220
|
nFft = 2048,
|
|
2212
3221
|
hopLength = 512,
|
|
2213
3222
|
): { nBins: number; nFrames: number; db: Float32Array } {
|
|
@@ -2225,7 +3234,7 @@ export function stftDb(
|
|
|
2225
3234
|
* Compute Mel spectrogram.
|
|
2226
3235
|
*
|
|
2227
3236
|
* @param samples - Audio samples (mono, float32)
|
|
2228
|
-
* @param sampleRate - Sample rate in Hz
|
|
3237
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2229
3238
|
* @param nFft - FFT size (default: 2048)
|
|
2230
3239
|
* @param hopLength - Hop length (default: 512)
|
|
2231
3240
|
* @param nMels - Number of Mel bands (default: 128)
|
|
@@ -2233,7 +3242,7 @@ export function stftDb(
|
|
|
2233
3242
|
*/
|
|
2234
3243
|
export function melSpectrogram(
|
|
2235
3244
|
samples: Float32Array,
|
|
2236
|
-
sampleRate
|
|
3245
|
+
sampleRate = 22050,
|
|
2237
3246
|
nFft = 2048,
|
|
2238
3247
|
hopLength = 512,
|
|
2239
3248
|
nMels = 128,
|
|
@@ -2248,20 +3257,20 @@ export function melSpectrogram(
|
|
|
2248
3257
|
* Compute MFCC (Mel-Frequency Cepstral Coefficients).
|
|
2249
3258
|
*
|
|
2250
3259
|
* @param samples - Audio samples (mono, float32)
|
|
2251
|
-
* @param sampleRate - Sample rate in Hz
|
|
3260
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2252
3261
|
* @param nFft - FFT size (default: 2048)
|
|
2253
3262
|
* @param hopLength - Hop length (default: 512)
|
|
2254
3263
|
* @param nMels - Number of Mel bands (default: 128)
|
|
2255
|
-
* @param nMfcc - Number of MFCC coefficients (default:
|
|
3264
|
+
* @param nMfcc - Number of MFCC coefficients (default: 20)
|
|
2256
3265
|
* @returns MFCC result
|
|
2257
3266
|
*/
|
|
2258
3267
|
export function mfcc(
|
|
2259
3268
|
samples: Float32Array,
|
|
2260
|
-
sampleRate
|
|
3269
|
+
sampleRate = 22050,
|
|
2261
3270
|
nFft = 2048,
|
|
2262
3271
|
hopLength = 512,
|
|
2263
3272
|
nMels = 128,
|
|
2264
|
-
nMfcc =
|
|
3273
|
+
nMfcc = 20,
|
|
2265
3274
|
): MfccResult {
|
|
2266
3275
|
if (!module) {
|
|
2267
3276
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2282,23 +3291,23 @@ export function mfcc(
|
|
|
2282
3291
|
* @param nFrames - Number of time frames
|
|
2283
3292
|
* @param sampleRate - Sample rate in Hz
|
|
2284
3293
|
* @param nFft - FFT size (default: 2048)
|
|
2285
|
-
* @param
|
|
3294
|
+
* @param fmin - Lower Mel band edge in Hz (default: 0)
|
|
3295
|
+
* @param fmax - Upper Mel band edge in Hz (default: sr/2 when 0)
|
|
2286
3296
|
* @returns STFT power spectrogram result
|
|
2287
3297
|
*/
|
|
2288
3298
|
export function melToStft(
|
|
2289
3299
|
melPower: Float32Array,
|
|
2290
3300
|
nMels: number,
|
|
2291
3301
|
nFrames: number,
|
|
2292
|
-
sampleRate
|
|
3302
|
+
sampleRate = 22050,
|
|
2293
3303
|
nFft = 2048,
|
|
2294
|
-
hopLength = 512,
|
|
2295
3304
|
fmin = 0,
|
|
2296
3305
|
fmax = 0,
|
|
2297
3306
|
): StftPowerResult {
|
|
2298
3307
|
if (!module) {
|
|
2299
3308
|
throw new Error('Module not initialized. Call init() first.');
|
|
2300
3309
|
}
|
|
2301
|
-
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft,
|
|
3310
|
+
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft, fmin, fmax);
|
|
2302
3311
|
}
|
|
2303
3312
|
|
|
2304
3313
|
/**
|
|
@@ -2311,6 +3320,8 @@ export function melToStft(
|
|
|
2311
3320
|
* @param sampleRate - Sample rate in Hz
|
|
2312
3321
|
* @param nFft - FFT size (default: 2048)
|
|
2313
3322
|
* @param hopLength - Hop length (default: 512)
|
|
3323
|
+
* @param fmin - Minimum Mel frequency in Hz (default: 0)
|
|
3324
|
+
* @param fmax - Maximum Mel frequency in Hz (default: 0 = sr/2)
|
|
2314
3325
|
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2315
3326
|
* @returns Reconstructed audio samples (mono, float32)
|
|
2316
3327
|
*/
|
|
@@ -2318,12 +3329,12 @@ export function melToAudio(
|
|
|
2318
3329
|
melPower: Float32Array,
|
|
2319
3330
|
nMels: number,
|
|
2320
3331
|
nFrames: number,
|
|
2321
|
-
sampleRate
|
|
3332
|
+
sampleRate = 22050,
|
|
2322
3333
|
nFft = 2048,
|
|
2323
3334
|
hopLength = 512,
|
|
2324
|
-
nIter = 32,
|
|
2325
3335
|
fmin = 0,
|
|
2326
3336
|
fmax = 0,
|
|
3337
|
+
nIter = 32,
|
|
2327
3338
|
): Float32Array {
|
|
2328
3339
|
if (!module) {
|
|
2329
3340
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -2335,9 +3346,9 @@ export function melToAudio(
|
|
|
2335
3346
|
sampleRate,
|
|
2336
3347
|
nFft,
|
|
2337
3348
|
hopLength,
|
|
2338
|
-
nIter,
|
|
2339
3349
|
fmin,
|
|
2340
3350
|
fmax,
|
|
3351
|
+
nIter,
|
|
2341
3352
|
);
|
|
2342
3353
|
}
|
|
2343
3354
|
|
|
@@ -2360,114 +3371,318 @@ export function mfccToMel(
|
|
|
2360
3371
|
if (!module) {
|
|
2361
3372
|
throw new Error('Module not initialized. Call init() first.');
|
|
2362
3373
|
}
|
|
2363
|
-
return module.mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
3374
|
+
return module.mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
/**
|
|
3378
|
+
* Reconstruct audio directly from MFCC coefficients via Griffin-Lim. Mirrors
|
|
3379
|
+
* `feature::mfcc_to_audio`.
|
|
3380
|
+
*
|
|
3381
|
+
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
3382
|
+
* @param nMfcc - Number of MFCC coefficients
|
|
3383
|
+
* @param nFrames - Number of time frames
|
|
3384
|
+
* @param nMels - Number of Mel bins (default: 128)
|
|
3385
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3386
|
+
* @param nFft - FFT size (default: 2048)
|
|
3387
|
+
* @param hopLength - Hop length (default: 512)
|
|
3388
|
+
* @param fmin - Minimum Mel frequency in Hz (default: 0)
|
|
3389
|
+
* @param fmax - Maximum Mel frequency in Hz (default: 0 = sr/2)
|
|
3390
|
+
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
3391
|
+
* @returns Reconstructed audio samples (mono, float32)
|
|
3392
|
+
*/
|
|
3393
|
+
export function mfccToAudio(
|
|
3394
|
+
mfccCoefficients: Float32Array,
|
|
3395
|
+
nMfcc: number,
|
|
3396
|
+
nFrames: number,
|
|
3397
|
+
nMels = 128,
|
|
3398
|
+
sampleRate = 22050,
|
|
3399
|
+
nFft = 2048,
|
|
3400
|
+
hopLength = 512,
|
|
3401
|
+
fmin = 0,
|
|
3402
|
+
fmax = 0,
|
|
3403
|
+
nIter = 32,
|
|
3404
|
+
): Float32Array {
|
|
3405
|
+
if (!module) {
|
|
3406
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3407
|
+
}
|
|
3408
|
+
return module.mfccToAudio(
|
|
3409
|
+
mfccCoefficients,
|
|
3410
|
+
nMfcc,
|
|
3411
|
+
nFrames,
|
|
3412
|
+
nMels,
|
|
3413
|
+
sampleRate,
|
|
3414
|
+
nFft,
|
|
3415
|
+
hopLength,
|
|
3416
|
+
fmin,
|
|
3417
|
+
fmax,
|
|
3418
|
+
nIter,
|
|
3419
|
+
);
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
// ============================================================================
|
|
3423
|
+
// Features - Chroma
|
|
3424
|
+
// ============================================================================
|
|
3425
|
+
|
|
3426
|
+
/**
|
|
3427
|
+
* Compute chromagram (pitch class distribution).
|
|
3428
|
+
*
|
|
3429
|
+
* @param samples - Audio samples (mono, float32)
|
|
3430
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3431
|
+
* @param nFft - FFT size (default: 2048)
|
|
3432
|
+
* @param hopLength - Hop length (default: 512)
|
|
3433
|
+
* @returns Chroma features result
|
|
3434
|
+
*/
|
|
3435
|
+
export function chroma(
|
|
3436
|
+
samples: Float32Array,
|
|
3437
|
+
sampleRate = 22050,
|
|
3438
|
+
nFft = 2048,
|
|
3439
|
+
hopLength = 512,
|
|
3440
|
+
): ChromaResult {
|
|
3441
|
+
if (!module) {
|
|
3442
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3443
|
+
}
|
|
3444
|
+
return module.chroma(samples, sampleRate, nFft, hopLength);
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
// ============================================================================
|
|
3448
|
+
// Features - Spectral
|
|
3449
|
+
// ============================================================================
|
|
3450
|
+
|
|
3451
|
+
/**
|
|
3452
|
+
* Compute spectral centroid (center of mass of spectrum).
|
|
3453
|
+
*
|
|
3454
|
+
* @param samples - Audio samples (mono, float32)
|
|
3455
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3456
|
+
* @param nFft - FFT size (default: 2048)
|
|
3457
|
+
* @param hopLength - Hop length (default: 512)
|
|
3458
|
+
* @returns Spectral centroid in Hz for each frame
|
|
3459
|
+
*/
|
|
3460
|
+
export function spectralCentroid(
|
|
3461
|
+
samples: Float32Array,
|
|
3462
|
+
sampleRate = 22050,
|
|
3463
|
+
nFft = 2048,
|
|
3464
|
+
hopLength = 512,
|
|
3465
|
+
): Float32Array {
|
|
3466
|
+
if (!module) {
|
|
3467
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3468
|
+
}
|
|
3469
|
+
return module.spectralCentroid(samples, sampleRate, nFft, hopLength);
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
/**
|
|
3473
|
+
* Compute spectral contrast (librosa.feature.spectral_contrast).
|
|
3474
|
+
*
|
|
3475
|
+
* @returns Matrix2d of shape (nBands + 1) x nFrames.
|
|
3476
|
+
*/
|
|
3477
|
+
export function spectralContrast(
|
|
3478
|
+
samples: Float32Array,
|
|
3479
|
+
sampleRate = 22050,
|
|
3480
|
+
nFft = 2048,
|
|
3481
|
+
hopLength = 512,
|
|
3482
|
+
nBands = 6,
|
|
3483
|
+
fmin = 200.0,
|
|
3484
|
+
quantile = 0.02,
|
|
3485
|
+
): WasmMatrix2dResult {
|
|
3486
|
+
if (!module) {
|
|
3487
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3488
|
+
}
|
|
3489
|
+
return module.spectralContrast(samples, sampleRate, nFft, hopLength, nBands, fmin, quantile);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
/**
|
|
3493
|
+
* Fit per-frame polynomial coefficients (librosa.feature.poly_features).
|
|
3494
|
+
*
|
|
3495
|
+
* @returns Matrix2d of shape (order + 1) x nFrames.
|
|
3496
|
+
*/
|
|
3497
|
+
export function polyFeatures(
|
|
3498
|
+
samples: Float32Array,
|
|
3499
|
+
sampleRate = 22050,
|
|
3500
|
+
nFft = 2048,
|
|
3501
|
+
hopLength = 512,
|
|
3502
|
+
order = 1,
|
|
3503
|
+
): WasmMatrix2dResult {
|
|
3504
|
+
if (!module) {
|
|
3505
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3506
|
+
}
|
|
3507
|
+
return module.polyFeatures(samples, sampleRate, nFft, hopLength, order);
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
/**
|
|
3511
|
+
* Locate zero-crossing indices of a signal (librosa.zero_crossings).
|
|
3512
|
+
*/
|
|
3513
|
+
export function zeroCrossings(
|
|
3514
|
+
samples: Float32Array,
|
|
3515
|
+
threshold = 1e-10,
|
|
3516
|
+
refMagnitude = false,
|
|
3517
|
+
pad = true,
|
|
3518
|
+
zeroPos = true,
|
|
3519
|
+
): Int32Array {
|
|
3520
|
+
if (!module) {
|
|
3521
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3522
|
+
}
|
|
3523
|
+
return module.zeroCrossings(samples, threshold, refMagnitude, pad, zeroPos);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
/**
|
|
3527
|
+
* Estimate the global tuning offset from a set of frequencies
|
|
3528
|
+
* (librosa.pitch_tuning). Returns a deviation in fractions of a bin.
|
|
3529
|
+
*/
|
|
3530
|
+
export function pitchTuning(
|
|
3531
|
+
frequencies: Float32Array,
|
|
3532
|
+
resolution = 0.01,
|
|
3533
|
+
binsPerOctave = 12,
|
|
3534
|
+
): number {
|
|
3535
|
+
if (!module) {
|
|
3536
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3537
|
+
}
|
|
3538
|
+
return module.pitchTuning(frequencies, resolution, binsPerOctave);
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
/**
|
|
3542
|
+
* Estimate the tuning offset of an audio signal (librosa.estimate_tuning).
|
|
3543
|
+
*/
|
|
3544
|
+
export function estimateTuning(
|
|
3545
|
+
samples: Float32Array,
|
|
3546
|
+
sampleRate = 22050,
|
|
3547
|
+
nFft = 2048,
|
|
3548
|
+
hopLength = 512,
|
|
3549
|
+
resolution = 0.01,
|
|
3550
|
+
binsPerOctave = 12,
|
|
3551
|
+
): number {
|
|
3552
|
+
if (!module) {
|
|
3553
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3554
|
+
}
|
|
3555
|
+
return module.estimateTuning(samples, sampleRate, nFft, hopLength, resolution, binsPerOctave);
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
/**
|
|
3559
|
+
* Non-negative matrix factorisation of a flattened [nFeatures x nFrames]
|
|
3560
|
+
* spectrogram (librosa.decompose.decompose). Returns the W and H factors.
|
|
3561
|
+
*/
|
|
3562
|
+
export function decompose(
|
|
3563
|
+
s: Float32Array,
|
|
3564
|
+
nFeatures: number,
|
|
3565
|
+
nFrames: number,
|
|
3566
|
+
nComponents: number,
|
|
3567
|
+
nIter = 50,
|
|
3568
|
+
beta = 2.0,
|
|
3569
|
+
): WasmDecomposeResult {
|
|
3570
|
+
if (!module) {
|
|
3571
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3572
|
+
}
|
|
3573
|
+
return module.decompose(s, nFeatures, nFrames, nComponents, nIter, beta);
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
/**
|
|
3577
|
+
* Nearest-neighbour filtering of a flattened [nFeatures x nFrames] spectrogram
|
|
3578
|
+
* (librosa.decompose.nn_filter).
|
|
3579
|
+
*/
|
|
3580
|
+
export function nnFilter(
|
|
3581
|
+
s: Float32Array,
|
|
3582
|
+
nFeatures: number,
|
|
3583
|
+
nFrames: number,
|
|
3584
|
+
aggregate = 'mean',
|
|
3585
|
+
k = 7,
|
|
3586
|
+
width = 1,
|
|
3587
|
+
): WasmMatrix2dResult {
|
|
3588
|
+
if (!module) {
|
|
3589
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3590
|
+
}
|
|
3591
|
+
return module.nnFilter(s, nFeatures, nFrames, aggregate, k, width);
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
/**
|
|
3595
|
+
* Reorder/concatenate a signal by interval slices (librosa.effects.remix).
|
|
3596
|
+
*
|
|
3597
|
+
* @param intervals - Flat (start, end) sample pairs (even length).
|
|
3598
|
+
*/
|
|
3599
|
+
export function remix(
|
|
3600
|
+
samples: Float32Array,
|
|
3601
|
+
intervals: Int32Array | ArrayLike<number>,
|
|
3602
|
+
sampleRate = 22050,
|
|
3603
|
+
alignZeros = false,
|
|
3604
|
+
): Float32Array {
|
|
3605
|
+
if (!module) {
|
|
3606
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3607
|
+
}
|
|
3608
|
+
// Sample indices must reach the native side as exact 32-bit integers. Passing
|
|
3609
|
+
// a Float32Array (or a number[] holding fractional/large values) would round
|
|
3610
|
+
// boundaries above 2^24 and misalign the slice. Coerce to an Int32Array,
|
|
3611
|
+
// truncating toward zero, so callers can hand us any numeric array safely.
|
|
3612
|
+
const intervalsI32 =
|
|
3613
|
+
intervals instanceof Int32Array ? intervals : Int32Array.from(intervals, (v) => Math.trunc(v));
|
|
3614
|
+
return module.remix(samples, intervalsI32, sampleRate, alignZeros);
|
|
2364
3615
|
}
|
|
2365
3616
|
|
|
2366
3617
|
/**
|
|
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)
|
|
3618
|
+
* Phase-vocoder time-scale modification (rate > 1 faster, < 1 slower).
|
|
2379
3619
|
*/
|
|
2380
|
-
export function
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
nMels: number,
|
|
2385
|
-
sampleRate: number,
|
|
3620
|
+
export function phaseVocoder(
|
|
3621
|
+
samples: Float32Array,
|
|
3622
|
+
rate: number,
|
|
3623
|
+
sampleRate = 22050,
|
|
2386
3624
|
nFft = 2048,
|
|
2387
3625
|
hopLength = 512,
|
|
2388
|
-
nIter = 32,
|
|
2389
|
-
fmin = 0,
|
|
2390
|
-
fmax = 0,
|
|
2391
3626
|
): Float32Array {
|
|
2392
3627
|
if (!module) {
|
|
2393
3628
|
throw new Error('Module not initialized. Call init() first.');
|
|
2394
3629
|
}
|
|
2395
|
-
return module.
|
|
2396
|
-
mfccCoefficients,
|
|
2397
|
-
nMfcc,
|
|
2398
|
-
nFrames,
|
|
2399
|
-
nMels,
|
|
2400
|
-
sampleRate,
|
|
2401
|
-
nFft,
|
|
2402
|
-
hopLength,
|
|
2403
|
-
nIter,
|
|
2404
|
-
fmin,
|
|
2405
|
-
fmax,
|
|
2406
|
-
);
|
|
3630
|
+
return module.phaseVocoder(samples, sampleRate, rate, nFft, hopLength);
|
|
2407
3631
|
}
|
|
2408
3632
|
|
|
2409
|
-
// ============================================================================
|
|
2410
|
-
// Features - Chroma
|
|
2411
|
-
// ============================================================================
|
|
2412
|
-
|
|
2413
3633
|
/**
|
|
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
|
|
3634
|
+
* HPSS into harmonic / percussive / residual signals.
|
|
2421
3635
|
*/
|
|
2422
|
-
export function
|
|
3636
|
+
export function hpssWithResidual(
|
|
2423
3637
|
samples: Float32Array,
|
|
2424
|
-
sampleRate
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
):
|
|
3638
|
+
sampleRate = 22050,
|
|
3639
|
+
kernelHarmonic = 31,
|
|
3640
|
+
kernelPercussive = 31,
|
|
3641
|
+
): WasmHpssWithResidualResult {
|
|
2428
3642
|
if (!module) {
|
|
2429
3643
|
throw new Error('Module not initialized. Call init() first.');
|
|
2430
3644
|
}
|
|
2431
|
-
return module.
|
|
3645
|
+
return module.hpssWithResidual(samples, sampleRate, kernelHarmonic, kernelPercussive);
|
|
2432
3646
|
}
|
|
2433
3647
|
|
|
2434
|
-
// ============================================================================
|
|
2435
|
-
// Features - Spectral
|
|
2436
|
-
// ============================================================================
|
|
2437
|
-
|
|
2438
3648
|
/**
|
|
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
|
|
3649
|
+
* Channel-weighted multichannel integrated loudness + LRA (ITU-R BS.1770 /
|
|
3650
|
+
* EBU R128) from an interleaved buffer of `frames * channels` samples. The
|
|
3651
|
+
* per-channel frame count is derived from the buffer length and `channels`.
|
|
2446
3652
|
*/
|
|
2447
|
-
export function
|
|
3653
|
+
export function lufsInterleaved(
|
|
2448
3654
|
samples: Float32Array,
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
): Float32Array {
|
|
3655
|
+
channels: number,
|
|
3656
|
+
sampleRate = 22050,
|
|
3657
|
+
): WasmLufsResult {
|
|
2453
3658
|
if (!module) {
|
|
2454
3659
|
throw new Error('Module not initialized. Call init() first.');
|
|
2455
3660
|
}
|
|
2456
|
-
return module.
|
|
3661
|
+
return module.lufsInterleaved(samples, channels, sampleRate);
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
/**
|
|
3665
|
+
* Standards-compliant EBU R128 loudness range (LRA) in LU.
|
|
3666
|
+
*/
|
|
3667
|
+
export function ebur128LoudnessRange(samples: Float32Array, sampleRate = 22050): number {
|
|
3668
|
+
if (!module) {
|
|
3669
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3670
|
+
}
|
|
3671
|
+
return module.ebur128LoudnessRange(samples, sampleRate);
|
|
2457
3672
|
}
|
|
2458
3673
|
|
|
2459
3674
|
/**
|
|
2460
3675
|
* Compute spectral bandwidth.
|
|
2461
3676
|
*
|
|
2462
3677
|
* @param samples - Audio samples (mono, float32)
|
|
2463
|
-
* @param sampleRate - Sample rate in Hz
|
|
3678
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2464
3679
|
* @param nFft - FFT size (default: 2048)
|
|
2465
3680
|
* @param hopLength - Hop length (default: 512)
|
|
2466
3681
|
* @returns Spectral bandwidth in Hz for each frame
|
|
2467
3682
|
*/
|
|
2468
3683
|
export function spectralBandwidth(
|
|
2469
3684
|
samples: Float32Array,
|
|
2470
|
-
sampleRate
|
|
3685
|
+
sampleRate = 22050,
|
|
2471
3686
|
nFft = 2048,
|
|
2472
3687
|
hopLength = 512,
|
|
2473
3688
|
): Float32Array {
|
|
@@ -2481,7 +3696,7 @@ export function spectralBandwidth(
|
|
|
2481
3696
|
* Compute spectral rolloff frequency.
|
|
2482
3697
|
*
|
|
2483
3698
|
* @param samples - Audio samples (mono, float32)
|
|
2484
|
-
* @param sampleRate - Sample rate in Hz
|
|
3699
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2485
3700
|
* @param nFft - FFT size (default: 2048)
|
|
2486
3701
|
* @param hopLength - Hop length (default: 512)
|
|
2487
3702
|
* @param rollPercent - Percentage threshold (default: 0.85)
|
|
@@ -2489,7 +3704,7 @@ export function spectralBandwidth(
|
|
|
2489
3704
|
*/
|
|
2490
3705
|
export function spectralRolloff(
|
|
2491
3706
|
samples: Float32Array,
|
|
2492
|
-
sampleRate
|
|
3707
|
+
sampleRate = 22050,
|
|
2493
3708
|
nFft = 2048,
|
|
2494
3709
|
hopLength = 512,
|
|
2495
3710
|
rollPercent = 0.85,
|
|
@@ -2504,14 +3719,14 @@ export function spectralRolloff(
|
|
|
2504
3719
|
* Compute spectral flatness.
|
|
2505
3720
|
*
|
|
2506
3721
|
* @param samples - Audio samples (mono, float32)
|
|
2507
|
-
* @param sampleRate - Sample rate in Hz
|
|
3722
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2508
3723
|
* @param nFft - FFT size (default: 2048)
|
|
2509
3724
|
* @param hopLength - Hop length (default: 512)
|
|
2510
3725
|
* @returns Spectral flatness for each frame (0 = tonal, 1 = noise-like)
|
|
2511
3726
|
*/
|
|
2512
3727
|
export function spectralFlatness(
|
|
2513
3728
|
samples: Float32Array,
|
|
2514
|
-
sampleRate
|
|
3729
|
+
sampleRate = 22050,
|
|
2515
3730
|
nFft = 2048,
|
|
2516
3731
|
hopLength = 512,
|
|
2517
3732
|
): Float32Array {
|
|
@@ -2525,14 +3740,14 @@ export function spectralFlatness(
|
|
|
2525
3740
|
* Compute zero crossing rate.
|
|
2526
3741
|
*
|
|
2527
3742
|
* @param samples - Audio samples (mono, float32)
|
|
2528
|
-
* @param sampleRate - Sample rate in Hz
|
|
3743
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2529
3744
|
* @param frameLength - Frame length (default: 2048)
|
|
2530
3745
|
* @param hopLength - Hop length (default: 512)
|
|
2531
3746
|
* @returns Zero crossing rate for each frame
|
|
2532
3747
|
*/
|
|
2533
3748
|
export function zeroCrossingRate(
|
|
2534
3749
|
samples: Float32Array,
|
|
2535
|
-
sampleRate
|
|
3750
|
+
sampleRate = 22050,
|
|
2536
3751
|
frameLength = 2048,
|
|
2537
3752
|
hopLength = 512,
|
|
2538
3753
|
): Float32Array {
|
|
@@ -2546,14 +3761,14 @@ export function zeroCrossingRate(
|
|
|
2546
3761
|
* Compute RMS energy.
|
|
2547
3762
|
*
|
|
2548
3763
|
* @param samples - Audio samples (mono, float32)
|
|
2549
|
-
* @param sampleRate - Sample rate in Hz
|
|
3764
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2550
3765
|
* @param frameLength - Frame length (default: 2048)
|
|
2551
3766
|
* @param hopLength - Hop length (default: 512)
|
|
2552
3767
|
* @returns RMS energy for each frame
|
|
2553
3768
|
*/
|
|
2554
3769
|
export function rmsEnergy(
|
|
2555
3770
|
samples: Float32Array,
|
|
2556
|
-
sampleRate
|
|
3771
|
+
sampleRate = 22050,
|
|
2557
3772
|
frameLength = 2048,
|
|
2558
3773
|
hopLength = 512,
|
|
2559
3774
|
): Float32Array {
|
|
@@ -2571,54 +3786,76 @@ export function rmsEnergy(
|
|
|
2571
3786
|
* Detect pitch using YIN algorithm.
|
|
2572
3787
|
*
|
|
2573
3788
|
* @param samples - Audio samples (mono, float32)
|
|
2574
|
-
* @param sampleRate - Sample rate in Hz
|
|
3789
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2575
3790
|
* @param frameLength - Frame length (default: 2048)
|
|
2576
3791
|
* @param hopLength - Hop length (default: 512)
|
|
2577
3792
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2578
3793
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2579
3794
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3795
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2580
3796
|
* @returns Pitch detection result
|
|
2581
3797
|
*/
|
|
2582
3798
|
export function pitchYin(
|
|
2583
3799
|
samples: Float32Array,
|
|
2584
|
-
sampleRate
|
|
3800
|
+
sampleRate = 22050,
|
|
2585
3801
|
frameLength = 2048,
|
|
2586
3802
|
hopLength = 512,
|
|
2587
3803
|
fmin = 65.0,
|
|
2588
3804
|
fmax = 2093.0,
|
|
2589
3805
|
threshold = 0.3,
|
|
3806
|
+
fillNa = false,
|
|
2590
3807
|
): PitchResult {
|
|
2591
3808
|
if (!module) {
|
|
2592
3809
|
throw new Error('Module not initialized. Call init() first.');
|
|
2593
3810
|
}
|
|
2594
|
-
return module.pitchYin(
|
|
3811
|
+
return module.pitchYin(
|
|
3812
|
+
samples,
|
|
3813
|
+
sampleRate,
|
|
3814
|
+
frameLength,
|
|
3815
|
+
hopLength,
|
|
3816
|
+
fmin,
|
|
3817
|
+
fmax,
|
|
3818
|
+
threshold,
|
|
3819
|
+
fillNa,
|
|
3820
|
+
);
|
|
2595
3821
|
}
|
|
2596
3822
|
|
|
2597
3823
|
/**
|
|
2598
3824
|
* Detect pitch using pYIN algorithm (probabilistic YIN with HMM smoothing).
|
|
2599
3825
|
*
|
|
2600
3826
|
* @param samples - Audio samples (mono, float32)
|
|
2601
|
-
* @param sampleRate - Sample rate in Hz
|
|
3827
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2602
3828
|
* @param frameLength - Frame length (default: 2048)
|
|
2603
3829
|
* @param hopLength - Hop length (default: 512)
|
|
2604
3830
|
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
2605
3831
|
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
2606
3832
|
* @param threshold - YIN threshold (default: 0.3)
|
|
3833
|
+
* @param fillNa - If true, return 0 for unvoiced f0 frames; otherwise keep NaN (default: false)
|
|
2607
3834
|
* @returns Pitch detection result
|
|
2608
3835
|
*/
|
|
2609
3836
|
export function pitchPyin(
|
|
2610
3837
|
samples: Float32Array,
|
|
2611
|
-
sampleRate
|
|
3838
|
+
sampleRate = 22050,
|
|
2612
3839
|
frameLength = 2048,
|
|
2613
3840
|
hopLength = 512,
|
|
2614
3841
|
fmin = 65.0,
|
|
2615
3842
|
fmax = 2093.0,
|
|
2616
3843
|
threshold = 0.3,
|
|
3844
|
+
fillNa = false,
|
|
2617
3845
|
): PitchResult {
|
|
2618
3846
|
if (!module) {
|
|
2619
3847
|
throw new Error('Module not initialized. Call init() first.');
|
|
2620
3848
|
}
|
|
2621
|
-
return module.pitchPyin(
|
|
3849
|
+
return module.pitchPyin(
|
|
3850
|
+
samples,
|
|
3851
|
+
sampleRate,
|
|
3852
|
+
frameLength,
|
|
3853
|
+
hopLength,
|
|
3854
|
+
fmin,
|
|
3855
|
+
fmax,
|
|
3856
|
+
threshold,
|
|
3857
|
+
fillNa,
|
|
3858
|
+
);
|
|
2622
3859
|
}
|
|
2623
3860
|
|
|
2624
3861
|
// ============================================================================
|
|
@@ -2707,11 +3944,11 @@ export function noteToHz(note: string): number {
|
|
|
2707
3944
|
* Convert frame index to time in seconds.
|
|
2708
3945
|
*
|
|
2709
3946
|
* @param frames - Frame index
|
|
2710
|
-
* @param sr - Sample rate in Hz
|
|
2711
|
-
* @param hopLength - Hop length in samples
|
|
3947
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3948
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2712
3949
|
* @returns Time in seconds
|
|
2713
3950
|
*/
|
|
2714
|
-
export function framesToTime(frames: number, sr
|
|
3951
|
+
export function framesToTime(frames: number, sr = 22050, hopLength = 512): number {
|
|
2715
3952
|
if (!module) {
|
|
2716
3953
|
throw new Error('Module not initialized. Call init() first.');
|
|
2717
3954
|
}
|
|
@@ -2722,11 +3959,11 @@ export function framesToTime(frames: number, sr: number, hopLength: number): num
|
|
|
2722
3959
|
* Convert time in seconds to frame index.
|
|
2723
3960
|
*
|
|
2724
3961
|
* @param time - Time in seconds
|
|
2725
|
-
* @param sr - Sample rate in Hz
|
|
2726
|
-
* @param hopLength - Hop length in samples
|
|
3962
|
+
* @param sr - Sample rate in Hz (default: 22050)
|
|
3963
|
+
* @param hopLength - Hop length in samples (default: 512)
|
|
2727
3964
|
* @returns Frame index
|
|
2728
3965
|
*/
|
|
2729
|
-
export function timeToFrames(time: number, sr
|
|
3966
|
+
export function timeToFrames(time: number, sr = 22050, hopLength = 512): number {
|
|
2730
3967
|
if (!module) {
|
|
2731
3968
|
throw new Error('Module not initialized. Call init() first.');
|
|
2732
3969
|
}
|
|
@@ -2834,18 +4071,18 @@ export function frameSignal(
|
|
|
2834
4071
|
return module.frameSignal(samples, frameLength, hopLength);
|
|
2835
4072
|
}
|
|
2836
4073
|
|
|
2837
|
-
export function padCenter(values: Float32Array,
|
|
4074
|
+
export function padCenter(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2838
4075
|
if (!module) {
|
|
2839
4076
|
throw new Error('Module not initialized. Call init() first.');
|
|
2840
4077
|
}
|
|
2841
|
-
return module.padCenter(values,
|
|
4078
|
+
return module.padCenter(values, targetSize, padValue);
|
|
2842
4079
|
}
|
|
2843
4080
|
|
|
2844
|
-
export function fixLength(values: Float32Array,
|
|
4081
|
+
export function fixLength(values: Float32Array, targetSize: number, padValue = 0.0): Float32Array {
|
|
2845
4082
|
if (!module) {
|
|
2846
4083
|
throw new Error('Module not initialized. Call init() first.');
|
|
2847
4084
|
}
|
|
2848
|
-
return module.fixLength(values,
|
|
4085
|
+
return module.fixLength(values, targetSize, padValue);
|
|
2849
4086
|
}
|
|
2850
4087
|
|
|
2851
4088
|
export function fixFrames(frames: Int32Array, xMin = 0, xMax = -1, pad = true): Int32Array {
|
|
@@ -2870,11 +4107,7 @@ export function peakPick(
|
|
|
2870
4107
|
return module.peakPick(values, preMax, postMax, preAvg, postAvg, delta, wait);
|
|
2871
4108
|
}
|
|
2872
4109
|
|
|
2873
|
-
export function vectorNormalize(
|
|
2874
|
-
values: Float32Array,
|
|
2875
|
-
normType = 0,
|
|
2876
|
-
threshold = 1e-12,
|
|
2877
|
-
): Float32Array {
|
|
4110
|
+
export function vectorNormalize(values: Float32Array, normType = 0, threshold = 0.0): Float32Array {
|
|
2878
4111
|
if (!module) {
|
|
2879
4112
|
throw new Error('Module not initialized. Call init() first.');
|
|
2880
4113
|
}
|
|
@@ -2902,7 +4135,7 @@ export function tonnetz(chromagram: Float32Array, nChroma: number, nFrames: numb
|
|
|
2902
4135
|
|
|
2903
4136
|
export function tempogram(
|
|
2904
4137
|
onsetEnvelope: Float32Array,
|
|
2905
|
-
sampleRate
|
|
4138
|
+
sampleRate = 22050,
|
|
2906
4139
|
hopLength = 512,
|
|
2907
4140
|
winLength = 384,
|
|
2908
4141
|
mode: TempogramMode = 'autocorrelation',
|
|
@@ -2915,7 +4148,7 @@ export function tempogram(
|
|
|
2915
4148
|
|
|
2916
4149
|
export function cyclicTempogram(
|
|
2917
4150
|
onsetEnvelope: Float32Array,
|
|
2918
|
-
sampleRate
|
|
4151
|
+
sampleRate = 22050,
|
|
2919
4152
|
hopLength = 512,
|
|
2920
4153
|
winLength = 384,
|
|
2921
4154
|
bpmMin = 60.0,
|
|
@@ -2929,7 +4162,7 @@ export function cyclicTempogram(
|
|
|
2929
4162
|
|
|
2930
4163
|
export function plp(
|
|
2931
4164
|
onsetEnvelope: Float32Array,
|
|
2932
|
-
sampleRate
|
|
4165
|
+
sampleRate = 22050,
|
|
2933
4166
|
hopLength = 512,
|
|
2934
4167
|
tempoMin = 30.0,
|
|
2935
4168
|
tempoMax = 300.0,
|
|
@@ -3014,7 +4247,7 @@ export function vqt(
|
|
|
3014
4247
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3015
4248
|
* @param nFft - FFT size (default: 2048)
|
|
3016
4249
|
* @param hopLength - Hop length (default: 512)
|
|
3017
|
-
* @param minSectionSec - Minimum section duration in seconds (default:
|
|
4250
|
+
* @param minSectionSec - Minimum section duration in seconds (default: 4.0)
|
|
3018
4251
|
* @returns Array of detected sections
|
|
3019
4252
|
*/
|
|
3020
4253
|
export function analyzeSections(
|
|
@@ -3022,7 +4255,7 @@ export function analyzeSections(
|
|
|
3022
4255
|
sampleRate = 22050,
|
|
3023
4256
|
nFft = 2048,
|
|
3024
4257
|
hopLength = 512,
|
|
3025
|
-
minSectionSec =
|
|
4258
|
+
minSectionSec = 4.0,
|
|
3026
4259
|
): Section[] {
|
|
3027
4260
|
if (!module) {
|
|
3028
4261
|
throw new Error('Module not initialized. Call init() first.');
|
|
@@ -3050,7 +4283,7 @@ export function analyzeMelody(
|
|
|
3050
4283
|
fmin = 65.0,
|
|
3051
4284
|
fmax = 2093.0,
|
|
3052
4285
|
frameLength = 2048,
|
|
3053
|
-
hopLength =
|
|
4286
|
+
hopLength = 256,
|
|
3054
4287
|
threshold = 0.1,
|
|
3055
4288
|
): MelodyResult {
|
|
3056
4289
|
if (!module) {
|
|
@@ -3131,10 +4364,15 @@ export function tempogramRatio(
|
|
|
3131
4364
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3132
4365
|
* @returns Loudness measurement result
|
|
3133
4366
|
*/
|
|
3134
|
-
export function lufs(
|
|
4367
|
+
export function lufs(
|
|
4368
|
+
samples: Float32Array,
|
|
4369
|
+
sampleRate = 22050,
|
|
4370
|
+
options: ValidateOptions = {},
|
|
4371
|
+
): LufsResult {
|
|
3135
4372
|
if (!module) {
|
|
3136
4373
|
throw new Error('Module not initialized. Call init() first.');
|
|
3137
4374
|
}
|
|
4375
|
+
assertSamples('lufs', samples, options.validate !== false);
|
|
3138
4376
|
return module.lufs(samples, sampleRate);
|
|
3139
4377
|
}
|
|
3140
4378
|
|
|
@@ -3145,10 +4383,15 @@ export function lufs(samples: Float32Array, sampleRate = 22050): LufsResult {
|
|
|
3145
4383
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3146
4384
|
* @returns Momentary LUFS values over time
|
|
3147
4385
|
*/
|
|
3148
|
-
export function momentaryLufs(
|
|
4386
|
+
export function momentaryLufs(
|
|
4387
|
+
samples: Float32Array,
|
|
4388
|
+
sampleRate = 22050,
|
|
4389
|
+
options: ValidateOptions = {},
|
|
4390
|
+
): Float32Array {
|
|
3149
4391
|
if (!module) {
|
|
3150
4392
|
throw new Error('Module not initialized. Call init() first.');
|
|
3151
4393
|
}
|
|
4394
|
+
assertSamples('momentaryLufs', samples, options.validate !== false);
|
|
3152
4395
|
return module.momentaryLufs(samples, sampleRate);
|
|
3153
4396
|
}
|
|
3154
4397
|
|
|
@@ -3159,13 +4402,292 @@ export function momentaryLufs(samples: Float32Array, sampleRate = 22050): Float3
|
|
|
3159
4402
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3160
4403
|
* @returns Short-term LUFS values over time
|
|
3161
4404
|
*/
|
|
3162
|
-
export function shortTermLufs(
|
|
4405
|
+
export function shortTermLufs(
|
|
4406
|
+
samples: Float32Array,
|
|
4407
|
+
sampleRate = 22050,
|
|
4408
|
+
options: ValidateOptions = {},
|
|
4409
|
+
): Float32Array {
|
|
3163
4410
|
if (!module) {
|
|
3164
4411
|
throw new Error('Module not initialized. Call init() first.');
|
|
3165
4412
|
}
|
|
4413
|
+
assertSamples('shortTermLufs', samples, options.validate !== false);
|
|
3166
4414
|
return module.shortTermLufs(samples, sampleRate);
|
|
3167
4415
|
}
|
|
3168
4416
|
|
|
4417
|
+
// ============================================================================
|
|
4418
|
+
// Metering — basic / true-peak / clipping / dynamic range
|
|
4419
|
+
// ============================================================================
|
|
4420
|
+
|
|
4421
|
+
/** One contiguous run of clipped samples reported by `meteringDetectClipping`. */
|
|
4422
|
+
export interface ClippingRegion {
|
|
4423
|
+
startSample: number;
|
|
4424
|
+
endSample: number;
|
|
4425
|
+
length: number;
|
|
4426
|
+
peak: number;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
/** Aggregated clipping report. */
|
|
4430
|
+
export interface ClippingReport {
|
|
4431
|
+
clippedSamples: number;
|
|
4432
|
+
clippingRatio: number;
|
|
4433
|
+
maxClippedPeak: number;
|
|
4434
|
+
regions: ClippingRegion[];
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
/** Sliding-window dynamic range report. */
|
|
4438
|
+
export interface DynamicRangeReport {
|
|
4439
|
+
dynamicRangeDb: number;
|
|
4440
|
+
lowPercentileDb: number;
|
|
4441
|
+
highPercentileDb: number;
|
|
4442
|
+
windowRmsDb: Float32Array;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
function requireModule() {
|
|
4446
|
+
if (!module) {
|
|
4447
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
4448
|
+
}
|
|
4449
|
+
return module;
|
|
4450
|
+
}
|
|
4451
|
+
|
|
4452
|
+
export function meteringPeakDb(
|
|
4453
|
+
samples: Float32Array,
|
|
4454
|
+
sampleRate = 22050,
|
|
4455
|
+
options: ValidateOptions = {},
|
|
4456
|
+
): number {
|
|
4457
|
+
assertSamples('meteringPeakDb', samples, options.validate !== false);
|
|
4458
|
+
return requireModule().meteringPeakDb(samples, sampleRate);
|
|
4459
|
+
}
|
|
4460
|
+
|
|
4461
|
+
export function meteringRmsDb(
|
|
4462
|
+
samples: Float32Array,
|
|
4463
|
+
sampleRate = 22050,
|
|
4464
|
+
options: ValidateOptions = {},
|
|
4465
|
+
): number {
|
|
4466
|
+
assertSamples('meteringRmsDb', samples, options.validate !== false);
|
|
4467
|
+
return requireModule().meteringRmsDb(samples, sampleRate);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
export function meteringCrestFactorDb(
|
|
4471
|
+
samples: Float32Array,
|
|
4472
|
+
sampleRate = 22050,
|
|
4473
|
+
options: ValidateOptions = {},
|
|
4474
|
+
): number {
|
|
4475
|
+
assertSamples('meteringCrestFactorDb', samples, options.validate !== false);
|
|
4476
|
+
return requireModule().meteringCrestFactorDb(samples, sampleRate);
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
export function meteringDcOffset(
|
|
4480
|
+
samples: Float32Array,
|
|
4481
|
+
sampleRate = 22050,
|
|
4482
|
+
options: ValidateOptions = {},
|
|
4483
|
+
): number {
|
|
4484
|
+
assertSamples('meteringDcOffset', samples, options.validate !== false);
|
|
4485
|
+
return requireModule().meteringDcOffset(samples, sampleRate);
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
/**
|
|
4489
|
+
* Inter-sample (true) peak in dBFS. `oversampleFactor` must be a power of two
|
|
4490
|
+
* in [1, 16]; pass 0 to use the library default (4).
|
|
4491
|
+
*/
|
|
4492
|
+
export function meteringTruePeakDb(
|
|
4493
|
+
samples: Float32Array,
|
|
4494
|
+
sampleRate = 22050,
|
|
4495
|
+
oversampleFactor = 4,
|
|
4496
|
+
options: ValidateOptions = {},
|
|
4497
|
+
): number {
|
|
4498
|
+
assertSamples('meteringTruePeakDb', samples, options.validate !== false);
|
|
4499
|
+
return requireModule().meteringTruePeakDb(samples, sampleRate, oversampleFactor);
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
/**
|
|
4503
|
+
* Detect contiguous runs of clipped samples.
|
|
4504
|
+
*
|
|
4505
|
+
* @param threshold Linear absolute threshold (default 0.999).
|
|
4506
|
+
* @param minRegionSamples Minimum run length to report (default 1).
|
|
4507
|
+
*/
|
|
4508
|
+
export function meteringDetectClipping(
|
|
4509
|
+
samples: Float32Array,
|
|
4510
|
+
sampleRate = 22050,
|
|
4511
|
+
threshold = 0.999,
|
|
4512
|
+
minRegionSamples = 1,
|
|
4513
|
+
options: ValidateOptions = {},
|
|
4514
|
+
): ClippingReport {
|
|
4515
|
+
assertSamples('meteringDetectClipping', samples, options.validate !== false);
|
|
4516
|
+
return requireModule().meteringDetectClipping(samples, sampleRate, threshold, minRegionSamples);
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
/**
|
|
4520
|
+
* Sliding-window dynamic range. Pass 0 for any parameter to use the library
|
|
4521
|
+
* default (window=3 s, hop=1 s, low=0.10, high=0.95).
|
|
4522
|
+
*/
|
|
4523
|
+
export function meteringDynamicRange(
|
|
4524
|
+
samples: Float32Array,
|
|
4525
|
+
sampleRate = 22050,
|
|
4526
|
+
windowSec = 0,
|
|
4527
|
+
hopSec = 0,
|
|
4528
|
+
lowPercentile = 0,
|
|
4529
|
+
highPercentile = 0,
|
|
4530
|
+
options: ValidateOptions = {},
|
|
4531
|
+
): DynamicRangeReport {
|
|
4532
|
+
assertSamples('meteringDynamicRange', samples, options.validate !== false);
|
|
4533
|
+
return requireModule().meteringDynamicRange(
|
|
4534
|
+
samples,
|
|
4535
|
+
sampleRate,
|
|
4536
|
+
windowSec,
|
|
4537
|
+
hopSec,
|
|
4538
|
+
lowPercentile,
|
|
4539
|
+
highPercentile,
|
|
4540
|
+
);
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
// ============================================================================
|
|
4544
|
+
// Metering — stereo / phase-scope / spectrum
|
|
4545
|
+
// ============================================================================
|
|
4546
|
+
|
|
4547
|
+
/** Mid/side vectorscope point series for a (left, right) stereo pair. */
|
|
4548
|
+
export interface VectorscopeReport {
|
|
4549
|
+
mid: Float32Array;
|
|
4550
|
+
side: Float32Array;
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
/** Phase-scope (Lissajous) point series plus summary stats. */
|
|
4554
|
+
export interface PhaseScopeReport {
|
|
4555
|
+
mid: Float32Array;
|
|
4556
|
+
side: Float32Array;
|
|
4557
|
+
radius: Float32Array;
|
|
4558
|
+
angleRad: Float32Array;
|
|
4559
|
+
correlation: number;
|
|
4560
|
+
averageAbsAngleRad: number;
|
|
4561
|
+
maxRadius: number;
|
|
4562
|
+
}
|
|
4563
|
+
|
|
4564
|
+
/** Options for `meteringSpectrum`. */
|
|
4565
|
+
export interface SpectrumOptions {
|
|
4566
|
+
/** FFT size. Pass 0 / omit for the library default (2048). */
|
|
4567
|
+
nFft?: number;
|
|
4568
|
+
/** Apply fractional-octave smoothing to magnitude. */
|
|
4569
|
+
applyOctaveSmoothing?: boolean;
|
|
4570
|
+
/** Smoothing fraction (e.g. 3 = 1/3-octave). 0 / omit = library default (3). */
|
|
4571
|
+
octaveFraction?: number;
|
|
4572
|
+
/** Linear reference for the dB conversion. 0 / omit = 1.0. */
|
|
4573
|
+
dbRef?: number;
|
|
4574
|
+
/** Linear floor used to avoid log(0). 0 / omit = library default. */
|
|
4575
|
+
dbAmin?: number;
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
/** Single-frame magnitude / power / dB spectrum returned by `meteringSpectrum`. */
|
|
4579
|
+
export interface SpectrumReport {
|
|
4580
|
+
frequencies: Float32Array;
|
|
4581
|
+
magnitude: Float32Array;
|
|
4582
|
+
power: Float32Array;
|
|
4583
|
+
db: Float32Array;
|
|
4584
|
+
nFft: number;
|
|
4585
|
+
sampleRate: number;
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
/** Pearson correlation in [-1, 1] between two equal-length channels. */
|
|
4589
|
+
export function meteringStereoCorrelation(
|
|
4590
|
+
left: Float32Array,
|
|
4591
|
+
right: Float32Array,
|
|
4592
|
+
sampleRate = 22050,
|
|
4593
|
+
options: ValidateOptions = {},
|
|
4594
|
+
): number {
|
|
4595
|
+
const validate = options.validate !== false;
|
|
4596
|
+
assertSamples('meteringStereoCorrelation', left, validate, 'left');
|
|
4597
|
+
assertSamples('meteringStereoCorrelation', right, validate, 'right');
|
|
4598
|
+
return requireModule().meteringStereoCorrelation(left, right, sampleRate);
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
/** Side / mid energy ratio: 0 = pure mono, ~1 = wide stereo. */
|
|
4602
|
+
export function meteringStereoWidth(
|
|
4603
|
+
left: Float32Array,
|
|
4604
|
+
right: Float32Array,
|
|
4605
|
+
sampleRate = 22050,
|
|
4606
|
+
options: ValidateOptions = {},
|
|
4607
|
+
): number {
|
|
4608
|
+
const validate = options.validate !== false;
|
|
4609
|
+
assertSamples('meteringStereoWidth', left, validate, 'left');
|
|
4610
|
+
assertSamples('meteringStereoWidth', right, validate, 'right');
|
|
4611
|
+
return requireModule().meteringStereoWidth(left, right, sampleRate);
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
/** Per-sample mid/side point series (one entry per input frame). */
|
|
4615
|
+
export function meteringVectorscope(
|
|
4616
|
+
left: Float32Array,
|
|
4617
|
+
right: Float32Array,
|
|
4618
|
+
sampleRate = 22050,
|
|
4619
|
+
options: ValidateOptions = {},
|
|
4620
|
+
): VectorscopeReport {
|
|
4621
|
+
const validate = options.validate !== false;
|
|
4622
|
+
assertSamples('meteringVectorscope', left, validate, 'left');
|
|
4623
|
+
assertSamples('meteringVectorscope', right, validate, 'right');
|
|
4624
|
+
return requireModule().meteringVectorscope(left, right, sampleRate);
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4627
|
+
/** Phase-scope point series plus summary stats. */
|
|
4628
|
+
export function meteringPhaseScope(
|
|
4629
|
+
left: Float32Array,
|
|
4630
|
+
right: Float32Array,
|
|
4631
|
+
sampleRate = 22050,
|
|
4632
|
+
options: ValidateOptions = {},
|
|
4633
|
+
): PhaseScopeReport {
|
|
4634
|
+
const validate = options.validate !== false;
|
|
4635
|
+
assertSamples('meteringPhaseScope', left, validate, 'left');
|
|
4636
|
+
assertSamples('meteringPhaseScope', right, validate, 'right');
|
|
4637
|
+
return requireModule().meteringPhaseScope(left, right, sampleRate);
|
|
4638
|
+
}
|
|
4639
|
+
|
|
4640
|
+
/** Single-frame spectrum view (uses the first `nFft` samples of `samples`). */
|
|
4641
|
+
export function meteringSpectrum(
|
|
4642
|
+
samples: Float32Array,
|
|
4643
|
+
sampleRate = 22050,
|
|
4644
|
+
options?: SpectrumOptions & ValidateOptions,
|
|
4645
|
+
): SpectrumReport {
|
|
4646
|
+
const validate = options?.validate !== false;
|
|
4647
|
+
assertSamples('meteringSpectrum', samples, validate);
|
|
4648
|
+
return requireModule().meteringSpectrum(samples, sampleRate, options ?? {});
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
// ============================================================================
|
|
4652
|
+
// Editing — 12-TET scale quantizer
|
|
4653
|
+
// ============================================================================
|
|
4654
|
+
|
|
4655
|
+
/**
|
|
4656
|
+
* Snap a MIDI value to the nearest pitch class enabled by `modeMask`.
|
|
4657
|
+
*
|
|
4658
|
+
* `modeMask` is a 12-bit mask. For natural C major use `0b101010110101`.
|
|
4659
|
+
* `referenceMidi` defaults to A4 (69) when passed as 0.
|
|
4660
|
+
*/
|
|
4661
|
+
export function scaleQuantizeMidi(
|
|
4662
|
+
root: number,
|
|
4663
|
+
modeMask: number,
|
|
4664
|
+
midi: number,
|
|
4665
|
+
referenceMidi = 0,
|
|
4666
|
+
): number {
|
|
4667
|
+
assertFiniteScalar('scaleQuantizeMidi', midi, 'midi');
|
|
4668
|
+
assertFiniteScalar('scaleQuantizeMidi', referenceMidi, 'referenceMidi');
|
|
4669
|
+
return requireModule().scaleQuantizeMidi(root, modeMask, midi, referenceMidi);
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
export function scaleCorrectionSemitones(
|
|
4673
|
+
root: number,
|
|
4674
|
+
modeMask: number,
|
|
4675
|
+
midi: number,
|
|
4676
|
+
referenceMidi = 0,
|
|
4677
|
+
): number {
|
|
4678
|
+
assertFiniteScalar('scaleCorrectionSemitones', midi, 'midi');
|
|
4679
|
+
assertFiniteScalar('scaleCorrectionSemitones', referenceMidi, 'referenceMidi');
|
|
4680
|
+
return requireModule().scaleCorrectionSemitones(root, modeMask, midi, referenceMidi);
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
export function scalePitchClassEnabled(
|
|
4684
|
+
root: number,
|
|
4685
|
+
modeMask: number,
|
|
4686
|
+
pitchClass: number,
|
|
4687
|
+
): boolean {
|
|
4688
|
+
return requireModule().scalePitchClassEnabled(root, modeMask, pitchClass);
|
|
4689
|
+
}
|
|
4690
|
+
|
|
3169
4691
|
// ============================================================================
|
|
3170
4692
|
// Core - Resample
|
|
3171
4693
|
// ============================================================================
|
|
@@ -3194,7 +4716,7 @@ export function resample(samples: Float32Array, srcSr: number, targetSr: number)
|
|
|
3194
4716
|
*
|
|
3195
4717
|
* @example
|
|
3196
4718
|
* ```typescript
|
|
3197
|
-
* import { init, Audio } from '@libraz/
|
|
4719
|
+
* import { init, Audio } from '@libraz/libsonare';
|
|
3198
4720
|
*
|
|
3199
4721
|
* await init();
|
|
3200
4722
|
*
|
|
@@ -3299,15 +4821,15 @@ export class Audio {
|
|
|
3299
4821
|
return pitchShift(this._samples, this._sampleRate, semitones);
|
|
3300
4822
|
}
|
|
3301
4823
|
|
|
3302
|
-
pitchCorrectToMidi(currentMidi
|
|
4824
|
+
pitchCorrectToMidi(currentMidi = 69.0, targetMidi = 69.0): Float32Array {
|
|
3303
4825
|
return pitchCorrectToMidi(this._samples, this._sampleRate, currentMidi, targetMidi);
|
|
3304
4826
|
}
|
|
3305
4827
|
|
|
3306
|
-
noteStretch(onsetSample
|
|
4828
|
+
noteStretch(onsetSample = 0, offsetSample = 0, stretchRatio = 1.0): Float32Array {
|
|
3307
4829
|
return noteStretch(this._samples, this._sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
3308
4830
|
}
|
|
3309
4831
|
|
|
3310
|
-
voiceChange(pitchSemitones
|
|
4832
|
+
voiceChange(pitchSemitones = 0.0, formantFactor = 1.0): Float32Array {
|
|
3311
4833
|
return voiceChange(this._samples, this._sampleRate, pitchSemitones, formantFactor);
|
|
3312
4834
|
}
|
|
3313
4835
|
|
|
@@ -3355,7 +4877,7 @@ export class Audio {
|
|
|
3355
4877
|
return melSpectrogram(this._samples, this._sampleRate, nFft, hopLength, nMels);
|
|
3356
4878
|
}
|
|
3357
4879
|
|
|
3358
|
-
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc =
|
|
4880
|
+
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc = 20): MfccResult {
|
|
3359
4881
|
return mfcc(this._samples, this._sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
3360
4882
|
}
|
|
3361
4883
|
|
|
@@ -3413,8 +4935,18 @@ export class Audio {
|
|
|
3413
4935
|
fmin = 65.0,
|
|
3414
4936
|
fmax = 2093.0,
|
|
3415
4937
|
threshold = 0.3,
|
|
4938
|
+
fillNa = false,
|
|
3416
4939
|
): PitchResult {
|
|
3417
|
-
return pitchYin(
|
|
4940
|
+
return pitchYin(
|
|
4941
|
+
this._samples,
|
|
4942
|
+
this._sampleRate,
|
|
4943
|
+
frameLength,
|
|
4944
|
+
hopLength,
|
|
4945
|
+
fmin,
|
|
4946
|
+
fmax,
|
|
4947
|
+
threshold,
|
|
4948
|
+
fillNa,
|
|
4949
|
+
);
|
|
3418
4950
|
}
|
|
3419
4951
|
|
|
3420
4952
|
pitchPyin(
|
|
@@ -3423,6 +4955,7 @@ export class Audio {
|
|
|
3423
4955
|
fmin = 65.0,
|
|
3424
4956
|
fmax = 2093.0,
|
|
3425
4957
|
threshold = 0.3,
|
|
4958
|
+
fillNa = false,
|
|
3426
4959
|
): PitchResult {
|
|
3427
4960
|
return pitchPyin(
|
|
3428
4961
|
this._samples,
|
|
@@ -3432,6 +4965,7 @@ export class Audio {
|
|
|
3432
4965
|
fmin,
|
|
3433
4966
|
fmax,
|
|
3434
4967
|
threshold,
|
|
4968
|
+
fillNa,
|
|
3435
4969
|
);
|
|
3436
4970
|
}
|
|
3437
4971
|
|
|
@@ -3449,7 +4983,7 @@ export class Audio {
|
|
|
3449
4983
|
*
|
|
3450
4984
|
* @example
|
|
3451
4985
|
* ```typescript
|
|
3452
|
-
* import { init, StreamAnalyzer } from '@libraz/
|
|
4986
|
+
* import { init, StreamAnalyzer } from '@libraz/libsonare';
|
|
3453
4987
|
*
|
|
3454
4988
|
* await init();
|
|
3455
4989
|
*
|
|
@@ -3477,9 +5011,8 @@ export class StreamAnalyzer {
|
|
|
3477
5011
|
if (!module) {
|
|
3478
5012
|
throw new Error('Module not initialized. Call init() first.');
|
|
3479
5013
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
config.sampleRate,
|
|
5014
|
+
this.analyzer = new module.StreamAnalyzer(
|
|
5015
|
+
config.sampleRate ?? 44100,
|
|
3483
5016
|
config.nFft ?? 2048,
|
|
3484
5017
|
config.hopLength ?? 512,
|
|
3485
5018
|
config.nMels ?? 128,
|
|
@@ -3497,63 +5030,7 @@ export class StreamAnalyzer {
|
|
|
3497
5030
|
config.bpmUpdateIntervalSec ?? 10,
|
|
3498
5031
|
config.window ?? 0,
|
|
3499
5032
|
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
|
-
}
|
|
5033
|
+
);
|
|
3557
5034
|
}
|
|
3558
5035
|
|
|
3559
5036
|
/**
|