@libraz/libsonare 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +164 -2
- package/dist/index.d.ts +1122 -18
- package/dist/index.js +1124 -14
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +2 -0
- package/dist/sonare-rt.js +2 -0
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +447 -0
- package/dist/worklet.js +2078 -0
- package/dist/worklet.js.map +1 -0
- package/package.json +14 -4
- package/src/index.ts +1895 -63
- package/src/public_types.ts +451 -0
- package/src/sonare-rt.d.ts +93 -0
- package/src/sonare.js.d.ts +710 -2
- package/src/stream_types.ts +35 -0
- package/src/wasm_types.ts +695 -2
- package/src/worklet.ts +2140 -0
package/src/index.ts
CHANGED
|
@@ -19,61 +19,155 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type {
|
|
22
|
+
AcousticResult,
|
|
22
23
|
AnalysisResult,
|
|
24
|
+
AutomationCurve,
|
|
25
|
+
ChordAnalysisResult,
|
|
26
|
+
ChordDetectionOptions,
|
|
23
27
|
ChordQuality,
|
|
24
28
|
ChromaResult,
|
|
29
|
+
CqtResult,
|
|
30
|
+
EqBand,
|
|
31
|
+
EqMatchOptions,
|
|
32
|
+
EqSpectrumSnapshot,
|
|
33
|
+
GoniometerPoint,
|
|
25
34
|
HpssResult,
|
|
26
35
|
Key,
|
|
36
|
+
KeyCandidate,
|
|
37
|
+
KeyDetectionOptions,
|
|
38
|
+
KeyProfileName,
|
|
39
|
+
LufsResult,
|
|
27
40
|
MasteringChainConfig,
|
|
28
41
|
MasteringChainResult,
|
|
42
|
+
MasteringPreset,
|
|
29
43
|
MasteringProcessorParams,
|
|
30
44
|
MasteringResult,
|
|
31
45
|
MasteringStereoChainResult,
|
|
32
46
|
MasteringStereoResult,
|
|
47
|
+
MelodyResult,
|
|
48
|
+
MelPowerResult,
|
|
33
49
|
MelSpectrogramResult,
|
|
50
|
+
MeterTap,
|
|
34
51
|
MfccResult,
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
MixerProcessResult,
|
|
53
|
+
MixMeterSnapshot,
|
|
54
|
+
MixOptions,
|
|
55
|
+
MixResult,
|
|
56
|
+
PairAnalysis,
|
|
57
|
+
PairProcessor,
|
|
58
|
+
PanLaw,
|
|
37
59
|
PitchResult,
|
|
60
|
+
Section,
|
|
38
61
|
SectionType,
|
|
62
|
+
SendTiming,
|
|
63
|
+
SoloProcessor,
|
|
64
|
+
StereoAnalysis,
|
|
65
|
+
StftPowerResult,
|
|
39
66
|
StftResult,
|
|
67
|
+
StreamingEqualizerConfig,
|
|
68
|
+
StreamingPlatform,
|
|
69
|
+
TempogramMode,
|
|
40
70
|
} from './public_types';
|
|
41
|
-
import
|
|
71
|
+
import { KeyProfile as KeyProfileValues, Mode, PitchClass } from './public_types';
|
|
72
|
+
import type {
|
|
73
|
+
AnalyzerStats,
|
|
74
|
+
FrameBuffer,
|
|
75
|
+
StreamConfig,
|
|
76
|
+
StreamFramesI16,
|
|
77
|
+
StreamFramesU8,
|
|
78
|
+
} from './stream_types';
|
|
42
79
|
import type {
|
|
43
80
|
ProgressCallback,
|
|
44
81
|
SonareModule,
|
|
82
|
+
WasmAcousticResult,
|
|
45
83
|
WasmAnalysisResult,
|
|
84
|
+
WasmChordAnalysisResult,
|
|
85
|
+
WasmCyclicTempogramResult,
|
|
86
|
+
WasmEngineAutomationPoint,
|
|
87
|
+
WasmEngineBounceOptions,
|
|
88
|
+
WasmEngineBounceResult,
|
|
89
|
+
WasmEngineCaptureStatus,
|
|
90
|
+
WasmEngineClip,
|
|
91
|
+
WasmEngineFreezeOptions,
|
|
92
|
+
WasmEngineFreezeResult,
|
|
93
|
+
WasmEngineGraphSpec,
|
|
94
|
+
WasmEngineMarker,
|
|
95
|
+
WasmEngineMeterTelemetry,
|
|
96
|
+
WasmEngineMetronomeConfig,
|
|
97
|
+
WasmEngineParameterInfo,
|
|
98
|
+
WasmEngineProcessWithMonitorResult,
|
|
99
|
+
WasmEngineTelemetry,
|
|
100
|
+
WasmEngineTransportState,
|
|
101
|
+
WasmFourierTempogramResult,
|
|
46
102
|
WasmFrameResult,
|
|
103
|
+
WasmKeyCandidateResult,
|
|
104
|
+
WasmNnlsChromaResult,
|
|
105
|
+
WasmRealtimeEngine,
|
|
47
106
|
WasmStreamAnalyzer,
|
|
48
107
|
WasmTempogramResult,
|
|
49
108
|
WasmTrimResult,
|
|
50
109
|
} from './wasm_types';
|
|
51
110
|
|
|
52
111
|
export type {
|
|
112
|
+
AcousticResult,
|
|
53
113
|
AnalysisResult,
|
|
114
|
+
AutomationCurve,
|
|
54
115
|
Beat,
|
|
55
116
|
Chord,
|
|
117
|
+
ChordAnalysisResult,
|
|
118
|
+
ChordDetectionOptions,
|
|
56
119
|
ChromaResult,
|
|
120
|
+
CqtResult,
|
|
57
121
|
Dynamics,
|
|
122
|
+
EqBand,
|
|
123
|
+
EqBandPhase,
|
|
124
|
+
EqBandType,
|
|
125
|
+
EqCoeffMode,
|
|
126
|
+
EqMatchOptions,
|
|
127
|
+
EqSpectrumSnapshot,
|
|
128
|
+
EqStereoPlacement,
|
|
129
|
+
GoniometerPoint,
|
|
58
130
|
HpssResult,
|
|
59
131
|
Key,
|
|
132
|
+
KeyCandidate,
|
|
133
|
+
KeyDetectionOptions,
|
|
134
|
+
KeyProfileName,
|
|
135
|
+
LufsResult,
|
|
60
136
|
MasteringChainConfig,
|
|
61
137
|
MasteringChainResult,
|
|
138
|
+
MasteringPreset,
|
|
62
139
|
MasteringProcessorParams,
|
|
63
140
|
MasteringResult,
|
|
64
141
|
MasteringStereoChainResult,
|
|
65
142
|
MasteringStereoResult,
|
|
143
|
+
MelodyPoint,
|
|
144
|
+
MelodyResult,
|
|
66
145
|
MelSpectrogramResult,
|
|
146
|
+
MeterTap,
|
|
67
147
|
MfccResult,
|
|
148
|
+
MixerProcessResult,
|
|
149
|
+
MixMeterSnapshot,
|
|
150
|
+
MixOptions,
|
|
151
|
+
MixResult,
|
|
152
|
+
PairAnalysis,
|
|
153
|
+
PairProcessor,
|
|
154
|
+
PanLaw,
|
|
155
|
+
PanMode,
|
|
68
156
|
PitchResult,
|
|
69
157
|
RhythmFeatures,
|
|
70
158
|
Section,
|
|
159
|
+
SendTiming,
|
|
160
|
+
SoloProcessor,
|
|
161
|
+
StereoAnalysis,
|
|
71
162
|
StftResult,
|
|
163
|
+
StreamingEqualizerConfig,
|
|
164
|
+
StreamingPlatform,
|
|
72
165
|
Timbre,
|
|
73
166
|
TimeSignature,
|
|
74
167
|
} from './public_types';
|
|
75
168
|
export {
|
|
76
169
|
ChordQuality,
|
|
170
|
+
KeyProfile,
|
|
77
171
|
Mode,
|
|
78
172
|
PitchClass,
|
|
79
173
|
SectionType,
|
|
@@ -86,9 +180,85 @@ export type {
|
|
|
86
180
|
PatternScore,
|
|
87
181
|
ProgressiveEstimate,
|
|
88
182
|
StreamConfig,
|
|
183
|
+
StreamFramesI16,
|
|
184
|
+
StreamFramesU8,
|
|
89
185
|
} from './stream_types';
|
|
90
186
|
export type { ProgressCallback } from './wasm_types';
|
|
91
187
|
|
|
188
|
+
export type EngineClip = WasmEngineClip;
|
|
189
|
+
export type EngineParameterInfo = WasmEngineParameterInfo;
|
|
190
|
+
export type EngineAutomationPoint = WasmEngineAutomationPoint;
|
|
191
|
+
export type EngineMarker = WasmEngineMarker;
|
|
192
|
+
export type EngineMetronomeConfig = WasmEngineMetronomeConfig;
|
|
193
|
+
export type EngineGraphSpec = WasmEngineGraphSpec;
|
|
194
|
+
export type EngineCaptureStatus = WasmEngineCaptureStatus;
|
|
195
|
+
export type EngineBounceOptions = WasmEngineBounceOptions;
|
|
196
|
+
export type EngineBounceResult = WasmEngineBounceResult;
|
|
197
|
+
export type EngineFreezeOptions = WasmEngineFreezeOptions;
|
|
198
|
+
export type EngineFreezeResult = WasmEngineFreezeResult;
|
|
199
|
+
export type EngineTelemetry = WasmEngineTelemetry;
|
|
200
|
+
export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
|
|
201
|
+
export type EngineTransportState = WasmEngineTransportState;
|
|
202
|
+
|
|
203
|
+
export const EXPECTED_ENGINE_ABI_VERSION = 2;
|
|
204
|
+
|
|
205
|
+
export interface EngineCapabilities {
|
|
206
|
+
engineAbiVersion: number;
|
|
207
|
+
expectedEngineAbiVersion: number;
|
|
208
|
+
abiCompatible: boolean;
|
|
209
|
+
sharedArrayBuffer: boolean;
|
|
210
|
+
atomics: boolean;
|
|
211
|
+
audioWorklet: boolean;
|
|
212
|
+
mode: 'sab' | 'postMessage';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface MixerRealtimeBuffer {
|
|
216
|
+
leftInputs: Float32Array[];
|
|
217
|
+
rightInputs: Float32Array[];
|
|
218
|
+
outLeft: Float32Array;
|
|
219
|
+
outRight: Float32Array;
|
|
220
|
+
process: (numSamples?: number) => void;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function automationCurveCode(curve: AutomationCurve): number {
|
|
224
|
+
switch (curve) {
|
|
225
|
+
case 'linear':
|
|
226
|
+
return 0;
|
|
227
|
+
case 'exponential':
|
|
228
|
+
return 1;
|
|
229
|
+
case 'hold':
|
|
230
|
+
return 2;
|
|
231
|
+
case 's-curve':
|
|
232
|
+
return 3;
|
|
233
|
+
default:
|
|
234
|
+
throw new Error(`Invalid automation curve: ${curve}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function panLawCode(panLaw: PanLaw | number): number {
|
|
239
|
+
if (typeof panLaw === 'number') {
|
|
240
|
+
return panLaw;
|
|
241
|
+
}
|
|
242
|
+
switch (panLaw) {
|
|
243
|
+
case 'const4.5dB':
|
|
244
|
+
return 1;
|
|
245
|
+
case 'const6dB':
|
|
246
|
+
return 2;
|
|
247
|
+
case 'linear0dB':
|
|
248
|
+
return 3;
|
|
249
|
+
default:
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function meterTapCode(tap: MeterTap | number): number {
|
|
255
|
+
return tap === 'preFader' || tap === 0 ? 0 : 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function sendTimingCode(timing: SendTiming | number): number {
|
|
259
|
+
return timing === 'preFader' || timing === 0 ? 0 : 1;
|
|
260
|
+
}
|
|
261
|
+
|
|
92
262
|
// ============================================================================
|
|
93
263
|
// Module State
|
|
94
264
|
// ============================================================================
|
|
@@ -148,6 +318,247 @@ export function version(): string {
|
|
|
148
318
|
return module.version();
|
|
149
319
|
}
|
|
150
320
|
|
|
321
|
+
export function engineAbiVersion(): number {
|
|
322
|
+
if (!module) {
|
|
323
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
324
|
+
}
|
|
325
|
+
return module.engineAbiVersion();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function engineCapabilities(): EngineCapabilities {
|
|
329
|
+
const abiVersion = engineAbiVersion();
|
|
330
|
+
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function';
|
|
331
|
+
const atomics = typeof globalThis.Atomics === 'object';
|
|
332
|
+
const audioWorklet =
|
|
333
|
+
typeof AudioWorkletNode !== 'undefined' ||
|
|
334
|
+
typeof (globalThis as typeof globalThis & { AudioWorkletProcessor?: unknown })
|
|
335
|
+
.AudioWorkletProcessor !== 'undefined';
|
|
336
|
+
return {
|
|
337
|
+
engineAbiVersion: abiVersion,
|
|
338
|
+
expectedEngineAbiVersion: EXPECTED_ENGINE_ABI_VERSION,
|
|
339
|
+
abiCompatible: abiVersion === EXPECTED_ENGINE_ABI_VERSION,
|
|
340
|
+
sharedArrayBuffer,
|
|
341
|
+
atomics,
|
|
342
|
+
audioWorklet,
|
|
343
|
+
mode: sharedArrayBuffer && atomics ? 'sab' : 'postMessage',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export class RealtimeEngine {
|
|
348
|
+
private native: WasmRealtimeEngine;
|
|
349
|
+
|
|
350
|
+
constructor(
|
|
351
|
+
sampleRate = 48000,
|
|
352
|
+
maxBlockSize = 128,
|
|
353
|
+
commandCapacity = 1024,
|
|
354
|
+
telemetryCapacity = 1024,
|
|
355
|
+
) {
|
|
356
|
+
if (!module) {
|
|
357
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
358
|
+
}
|
|
359
|
+
const capabilities = engineCapabilities();
|
|
360
|
+
if (!capabilities.abiCompatible) {
|
|
361
|
+
throw new Error(
|
|
362
|
+
`Engine ABI mismatch: wasm=${capabilities.engineAbiVersion}, expected=${capabilities.expectedEngineAbiVersion}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
this.native = new module.RealtimeEngine(
|
|
366
|
+
sampleRate,
|
|
367
|
+
maxBlockSize,
|
|
368
|
+
commandCapacity,
|
|
369
|
+
telemetryCapacity,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
prepare(
|
|
374
|
+
sampleRate: number,
|
|
375
|
+
maxBlockSize: number,
|
|
376
|
+
commandCapacity = 1024,
|
|
377
|
+
telemetryCapacity = 1024,
|
|
378
|
+
): void {
|
|
379
|
+
this.native.prepare(sampleRate, maxBlockSize, commandCapacity, telemetryCapacity);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Queue a sample-accurate parameter change (engine kSetParam). */
|
|
383
|
+
setParameter(paramId: number, value: number, renderFrame = -1): void {
|
|
384
|
+
this.native.setParameter(paramId, value, renderFrame);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Queue a smoothed parameter change (engine kSetParamSmoothed). */
|
|
388
|
+
setParameterSmoothed(paramId: number, value: number, renderFrame = -1): void {
|
|
389
|
+
this.native.setParameterSmoothed(paramId, value, renderFrame);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Read back the current transport state snapshot. */
|
|
393
|
+
getTransportState(): EngineTransportState {
|
|
394
|
+
return this.native.getTransportState();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
play(renderFrame = -1): void {
|
|
398
|
+
this.native.play(renderFrame);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
stop(renderFrame = -1): void {
|
|
402
|
+
this.native.stop(renderFrame);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
seekSample(timelineSample: number, renderFrame = -1): void {
|
|
406
|
+
this.native.seekSample(timelineSample, renderFrame);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
seekPpq(ppq: number, renderFrame = -1): void {
|
|
410
|
+
this.native.seekPpq(ppq, renderFrame);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
setTempo(bpm: number): void {
|
|
414
|
+
this.native.setTempo(bpm);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setTimeSignature(numerator: number, denominator: number): void {
|
|
418
|
+
this.native.setTimeSignature(numerator, denominator);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
setLoop(startPpq: number, endPpq: number, enabled = true): void {
|
|
422
|
+
this.native.setLoop(startPpq, endPpq, enabled);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
addParameter(info: EngineParameterInfo): void {
|
|
426
|
+
this.native.addParameter(info);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
parameterCount(): number {
|
|
430
|
+
return this.native.parameterCount();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
parameterInfoByIndex(index: number): EngineParameterInfo {
|
|
434
|
+
return this.native.parameterInfoByIndex(index);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
parameterInfo(id: number): EngineParameterInfo {
|
|
438
|
+
return this.native.parameterInfo(id);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
setAutomationLane(paramId: number, points: EngineAutomationPoint[]): void {
|
|
442
|
+
this.native.setAutomationLane(paramId, points);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
automationLaneCount(): number {
|
|
446
|
+
return this.native.automationLaneCount();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
setMarkers(markers: EngineMarker[]): void {
|
|
450
|
+
this.native.setMarkers(markers);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
markerCount(): number {
|
|
454
|
+
return this.native.markerCount();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
markerByIndex(index: number): EngineMarker {
|
|
458
|
+
return this.native.markerByIndex(index);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
marker(id: number): EngineMarker {
|
|
462
|
+
return this.native.marker(id);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
seekMarker(markerId: number, renderFrame = -1): void {
|
|
466
|
+
this.native.seekMarker(markerId, renderFrame);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
setLoopFromMarkers(startMarkerId: number, endMarkerId: number): void {
|
|
470
|
+
this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
setMetronome(config: EngineMetronomeConfig): void {
|
|
474
|
+
this.native.setMetronome(config);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
metronome(): Required<EngineMetronomeConfig> {
|
|
478
|
+
return this.native.metronome();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
countInEndSample(startSample: number, bars: number): number {
|
|
482
|
+
return Number(this.native.countInEndSample(startSample, bars));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
setGraph(spec: EngineGraphSpec): void {
|
|
486
|
+
this.native.setGraph(spec);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
graphNodeCount(): number {
|
|
490
|
+
return this.native.graphNodeCount();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
graphConnectionCount(): number {
|
|
494
|
+
return this.native.graphConnectionCount();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
setClips(clips: EngineClip[]): void {
|
|
498
|
+
this.native.setClips(clips);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
clipCount(): number {
|
|
502
|
+
return this.native.clipCount();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
setCaptureBuffer(numChannels: number, capacityFrames: number): void {
|
|
506
|
+
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
armCapture(armed = true): void {
|
|
510
|
+
this.native.armCapture(armed);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
setCapturePunch(startSample: number, endSample: number, enabled = true): void {
|
|
514
|
+
this.native.setCapturePunch(startSample, endSample, enabled);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
resetCapture(): void {
|
|
518
|
+
this.native.resetCapture();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
captureStatus(): EngineCaptureStatus {
|
|
522
|
+
return this.native.captureStatus();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
capturedAudio(): Float32Array[] {
|
|
526
|
+
return this.native.capturedAudio();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
process(channels: Float32Array[]): Float32Array[] {
|
|
530
|
+
return this.native.process(channels);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
|
|
534
|
+
return this.native.processWithMonitor(channels);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
renderOffline(channels: Float32Array[], blockSize = 128): Float32Array[] {
|
|
538
|
+
return this.native.renderOffline(channels, blockSize);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
bounceOffline(options: EngineBounceOptions): EngineBounceResult {
|
|
542
|
+
return this.native.bounceOffline(options);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
freezeOffline(options: EngineFreezeOptions): EngineFreezeResult {
|
|
546
|
+
return this.native.freezeOffline(options);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
drainTelemetry(maxRecords = 1024): EngineTelemetry[] {
|
|
550
|
+
return this.native.drainTelemetry(maxRecords);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
drainMeterTelemetry(maxRecords = 1024): EngineMeterTelemetry[] {
|
|
554
|
+
return this.native.drainMeterTelemetry(maxRecords);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
destroy(): void {
|
|
558
|
+
this.native.delete();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
151
562
|
// ============================================================================
|
|
152
563
|
// Quick API (High-level Analysis)
|
|
153
564
|
// ============================================================================
|
|
@@ -173,11 +584,26 @@ export function detectBpm(samples: Float32Array, sampleRate: number): number {
|
|
|
173
584
|
* @param sampleRate - Sample rate in Hz
|
|
174
585
|
* @returns Detected key
|
|
175
586
|
*/
|
|
176
|
-
export function detectKey(
|
|
587
|
+
export function detectKey(
|
|
588
|
+
samples: Float32Array,
|
|
589
|
+
sampleRate: number,
|
|
590
|
+
options: KeyDetectionOptions = {},
|
|
591
|
+
): Key {
|
|
177
592
|
if (!module) {
|
|
178
593
|
throw new Error('Module not initialized. Call init() first.');
|
|
179
594
|
}
|
|
180
|
-
const result = module.
|
|
595
|
+
const result = module._detectKeyWithOptions(
|
|
596
|
+
samples,
|
|
597
|
+
sampleRate,
|
|
598
|
+
options.nFft ?? 4096,
|
|
599
|
+
options.hopLength ?? 512,
|
|
600
|
+
options.useHpss ?? false,
|
|
601
|
+
options.loudnessWeighted ?? false,
|
|
602
|
+
options.highPassHz ?? 0,
|
|
603
|
+
keyModeValues(options.modes),
|
|
604
|
+
keyProfileValue(options.profile),
|
|
605
|
+
options.genreHint ?? '',
|
|
606
|
+
);
|
|
181
607
|
return {
|
|
182
608
|
root: result.root as PitchClass,
|
|
183
609
|
mode: result.mode as Mode,
|
|
@@ -187,6 +613,98 @@ export function detectKey(samples: Float32Array, sampleRate: number): Key {
|
|
|
187
613
|
};
|
|
188
614
|
}
|
|
189
615
|
|
|
616
|
+
function convertKeyCandidate(wasm: WasmKeyCandidateResult): KeyCandidate {
|
|
617
|
+
return {
|
|
618
|
+
key: {
|
|
619
|
+
root: wasm.key.root as PitchClass,
|
|
620
|
+
mode: wasm.key.mode as Mode,
|
|
621
|
+
confidence: wasm.key.confidence,
|
|
622
|
+
name: wasm.key.name,
|
|
623
|
+
shortName: wasm.key.shortName,
|
|
624
|
+
},
|
|
625
|
+
correlation: wasm.correlation,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function keyModeValues(modes: KeyDetectionOptions['modes'] | undefined): number[] {
|
|
630
|
+
if (!modes) {
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
if (modes === 'major-minor') {
|
|
634
|
+
return [Mode.Major, Mode.Minor];
|
|
635
|
+
}
|
|
636
|
+
if (modes === 'all' || modes === 'modal') {
|
|
637
|
+
return [
|
|
638
|
+
Mode.Major,
|
|
639
|
+
Mode.Minor,
|
|
640
|
+
Mode.Dorian,
|
|
641
|
+
Mode.Phrygian,
|
|
642
|
+
Mode.Lydian,
|
|
643
|
+
Mode.Mixolydian,
|
|
644
|
+
Mode.Locrian,
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
const names = {
|
|
648
|
+
major: Mode.Major,
|
|
649
|
+
minor: Mode.Minor,
|
|
650
|
+
dorian: Mode.Dorian,
|
|
651
|
+
phrygian: Mode.Phrygian,
|
|
652
|
+
lydian: Mode.Lydian,
|
|
653
|
+
mixolydian: Mode.Mixolydian,
|
|
654
|
+
locrian: Mode.Locrian,
|
|
655
|
+
} as const;
|
|
656
|
+
return modes.map((mode) => (typeof mode === 'number' ? mode : names[mode]));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function keyProfileValue(profile: KeyDetectionOptions['profile'] | undefined): number {
|
|
660
|
+
if (profile === undefined) {
|
|
661
|
+
return -1;
|
|
662
|
+
}
|
|
663
|
+
if (typeof profile === 'number') {
|
|
664
|
+
return profile;
|
|
665
|
+
}
|
|
666
|
+
const names: Record<KeyProfileName, number> = {
|
|
667
|
+
ks: KeyProfileValues.KrumhanslSchmuckler,
|
|
668
|
+
krumhansl: KeyProfileValues.KrumhanslSchmuckler,
|
|
669
|
+
temperley: KeyProfileValues.Temperley,
|
|
670
|
+
shaath: KeyProfileValues.Shaath,
|
|
671
|
+
keyfinder: KeyProfileValues.Shaath,
|
|
672
|
+
'faraldo-edmt': KeyProfileValues.FaraldoEDMT,
|
|
673
|
+
edmt: KeyProfileValues.FaraldoEDMT,
|
|
674
|
+
'faraldo-edma': KeyProfileValues.FaraldoEDMA,
|
|
675
|
+
edma: KeyProfileValues.FaraldoEDMA,
|
|
676
|
+
'faraldo-edmm': KeyProfileValues.FaraldoEDMM,
|
|
677
|
+
edmm: KeyProfileValues.FaraldoEDMM,
|
|
678
|
+
'bellman-budge': KeyProfileValues.BellmanBudge,
|
|
679
|
+
bellman: KeyProfileValues.BellmanBudge,
|
|
680
|
+
};
|
|
681
|
+
return names[profile];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function detectKeyCandidates(
|
|
685
|
+
samples: Float32Array,
|
|
686
|
+
sampleRate: number,
|
|
687
|
+
options: KeyDetectionOptions = {},
|
|
688
|
+
): KeyCandidate[] {
|
|
689
|
+
if (!module) {
|
|
690
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
691
|
+
}
|
|
692
|
+
return module
|
|
693
|
+
._detectKeyCandidates(
|
|
694
|
+
samples,
|
|
695
|
+
sampleRate,
|
|
696
|
+
options.nFft ?? 4096,
|
|
697
|
+
options.hopLength ?? 512,
|
|
698
|
+
options.useHpss ?? false,
|
|
699
|
+
options.loudnessWeighted ?? false,
|
|
700
|
+
options.highPassHz ?? 0,
|
|
701
|
+
keyModeValues(options.modes),
|
|
702
|
+
keyProfileValue(options.profile),
|
|
703
|
+
options.genreHint ?? '',
|
|
704
|
+
)
|
|
705
|
+
.map(convertKeyCandidate);
|
|
706
|
+
}
|
|
707
|
+
|
|
190
708
|
/**
|
|
191
709
|
* Detect onset times from audio samples.
|
|
192
710
|
*
|
|
@@ -215,6 +733,81 @@ export function detectBeats(samples: Float32Array, sampleRate: number): Float32A
|
|
|
215
733
|
return module.detectBeats(samples, sampleRate);
|
|
216
734
|
}
|
|
217
735
|
|
|
736
|
+
/**
|
|
737
|
+
* Detect downbeat times from audio samples.
|
|
738
|
+
*
|
|
739
|
+
* @param samples - Audio samples (mono, float32)
|
|
740
|
+
* @param sampleRate - Sample rate in Hz
|
|
741
|
+
* @returns Array of downbeat times in seconds
|
|
742
|
+
*/
|
|
743
|
+
export function detectDownbeats(samples: Float32Array, sampleRate: number): Float32Array {
|
|
744
|
+
if (!module) {
|
|
745
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
746
|
+
}
|
|
747
|
+
return module.detectDownbeats(samples, sampleRate);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function convertChordAnalysisResult(wasm: WasmChordAnalysisResult): ChordAnalysisResult {
|
|
751
|
+
return {
|
|
752
|
+
chords: wasm.chords.map((c) => ({
|
|
753
|
+
root: c.root as PitchClass,
|
|
754
|
+
bass: c.bass as PitchClass,
|
|
755
|
+
quality: c.quality as ChordQuality,
|
|
756
|
+
start: c.start,
|
|
757
|
+
end: c.end,
|
|
758
|
+
confidence: c.confidence,
|
|
759
|
+
name: c.name,
|
|
760
|
+
})),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Detect chords from audio samples.
|
|
766
|
+
*
|
|
767
|
+
* @param samples - Audio samples (mono, float32)
|
|
768
|
+
* @param sampleRate - Sample rate in Hz
|
|
769
|
+
* @param options - Optional chord detection settings
|
|
770
|
+
* @returns Detected chord segments
|
|
771
|
+
*/
|
|
772
|
+
export function detectChords(
|
|
773
|
+
samples: Float32Array,
|
|
774
|
+
sampleRate: number,
|
|
775
|
+
options: ChordDetectionOptions = {},
|
|
776
|
+
): ChordAnalysisResult {
|
|
777
|
+
if (!module) {
|
|
778
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
779
|
+
}
|
|
780
|
+
const result = module.detectChords(
|
|
781
|
+
samples,
|
|
782
|
+
sampleRate,
|
|
783
|
+
options.minDuration ?? 0.3,
|
|
784
|
+
options.smoothingWindow ?? 2.0,
|
|
785
|
+
options.threshold ?? 0.5,
|
|
786
|
+
options.useTriadsOnly ?? false,
|
|
787
|
+
options.nFft ?? 2048,
|
|
788
|
+
options.hopLength ?? 512,
|
|
789
|
+
options.useBeatSync ?? true,
|
|
790
|
+
options.useHmm ?? false,
|
|
791
|
+
options.hmmBeamWidth ?? 24,
|
|
792
|
+
options.useKeyContext ?? false,
|
|
793
|
+
options.keyRoot ?? PitchClass.C,
|
|
794
|
+
options.keyMode ?? Mode.Major,
|
|
795
|
+
options.detectInversions ?? false,
|
|
796
|
+
chordChromaMethodValue(options.chromaMethod ?? 'stft'),
|
|
797
|
+
);
|
|
798
|
+
return convertChordAnalysisResult(result);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function chordChromaMethodValue(method: 'stft' | 'nnls'): number {
|
|
802
|
+
if (method === 'stft') {
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
805
|
+
if (method === 'nnls') {
|
|
806
|
+
return 1;
|
|
807
|
+
}
|
|
808
|
+
throw new Error(`Invalid chord chroma method: ${method}`);
|
|
809
|
+
}
|
|
810
|
+
|
|
218
811
|
// Helper to convert WASM result to typed result
|
|
219
812
|
function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult {
|
|
220
813
|
const beatTimes = new Float32Array(wasm.beats.length);
|
|
@@ -236,6 +829,7 @@ function convertAnalysisResult(wasm: WasmAnalysisResult): AnalysisResult {
|
|
|
236
829
|
beats: wasm.beats,
|
|
237
830
|
chords: wasm.chords.map((c) => ({
|
|
238
831
|
root: c.root as PitchClass,
|
|
832
|
+
bass: c.bass as PitchClass,
|
|
239
833
|
quality: c.quality as ChordQuality,
|
|
240
834
|
start: c.start,
|
|
241
835
|
end: c.end,
|
|
@@ -272,6 +866,44 @@ export function analyze(samples: Float32Array, sampleRate: number): AnalysisResu
|
|
|
272
866
|
return convertAnalysisResult(result);
|
|
273
867
|
}
|
|
274
868
|
|
|
869
|
+
export function analyzeImpulseResponse(
|
|
870
|
+
samples: Float32Array,
|
|
871
|
+
sampleRate: number,
|
|
872
|
+
nOctaveBands = 6,
|
|
873
|
+
): AcousticResult {
|
|
874
|
+
if (!module) {
|
|
875
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
876
|
+
}
|
|
877
|
+
const result: WasmAcousticResult = module.analyzeImpulseResponse(
|
|
878
|
+
samples,
|
|
879
|
+
sampleRate,
|
|
880
|
+
nOctaveBands,
|
|
881
|
+
);
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export function detectAcoustic(
|
|
886
|
+
samples: Float32Array,
|
|
887
|
+
sampleRate: number,
|
|
888
|
+
nOctaveBands = 6,
|
|
889
|
+
nThirdOctaveSubbands = 24,
|
|
890
|
+
minDecayDb = 30.0,
|
|
891
|
+
noiseFloorMarginDb = 10.0,
|
|
892
|
+
): AcousticResult {
|
|
893
|
+
if (!module) {
|
|
894
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
895
|
+
}
|
|
896
|
+
const result: WasmAcousticResult = module.detectAcoustic(
|
|
897
|
+
samples,
|
|
898
|
+
sampleRate,
|
|
899
|
+
nOctaveBands,
|
|
900
|
+
nThirdOctaveSubbands,
|
|
901
|
+
minDecayDb,
|
|
902
|
+
noiseFloorMarginDb,
|
|
903
|
+
);
|
|
904
|
+
return result;
|
|
905
|
+
}
|
|
906
|
+
|
|
275
907
|
/**
|
|
276
908
|
* Perform complete music analysis with progress reporting.
|
|
277
909
|
*
|
|
@@ -380,73 +1012,138 @@ export function pitchShift(
|
|
|
380
1012
|
}
|
|
381
1013
|
|
|
382
1014
|
/**
|
|
383
|
-
*
|
|
1015
|
+
* Pitch-correct audio from a current MIDI note to a target MIDI note.
|
|
384
1016
|
*
|
|
385
1017
|
* @param samples - Audio samples (mono, float32)
|
|
386
1018
|
* @param sampleRate - Sample rate in Hz
|
|
387
|
-
* @param
|
|
388
|
-
* @
|
|
1019
|
+
* @param currentMidi - Detected/current MIDI note number
|
|
1020
|
+
* @param targetMidi - Desired MIDI note number
|
|
1021
|
+
* @returns Pitch-corrected audio
|
|
389
1022
|
*/
|
|
390
|
-
export function
|
|
1023
|
+
export function pitchCorrectToMidi(
|
|
1024
|
+
samples: Float32Array,
|
|
1025
|
+
sampleRate: number,
|
|
1026
|
+
currentMidi: number,
|
|
1027
|
+
targetMidi: number,
|
|
1028
|
+
): Float32Array {
|
|
391
1029
|
if (!module) {
|
|
392
1030
|
throw new Error('Module not initialized. Call init() first.');
|
|
393
1031
|
}
|
|
394
|
-
return module.
|
|
1032
|
+
return module.pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
|
|
395
1033
|
}
|
|
396
1034
|
|
|
397
1035
|
/**
|
|
398
|
-
*
|
|
1036
|
+
* Time-stretch a note region between two sample offsets without changing pitch.
|
|
399
1037
|
*
|
|
400
1038
|
* @param samples - Audio samples (mono, float32)
|
|
401
1039
|
* @param sampleRate - Sample rate in Hz
|
|
402
|
-
* @param
|
|
403
|
-
* @param
|
|
404
|
-
* @param
|
|
405
|
-
* @returns
|
|
1040
|
+
* @param onsetSample - Note onset position in samples
|
|
1041
|
+
* @param offsetSample - Note offset position in samples
|
|
1042
|
+
* @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
|
|
1043
|
+
* @returns Audio with the note region stretched
|
|
406
1044
|
*/
|
|
407
|
-
export function
|
|
1045
|
+
export function noteStretch(
|
|
408
1046
|
samples: Float32Array,
|
|
409
1047
|
sampleRate: number,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
):
|
|
414
|
-
if (!module) {
|
|
415
|
-
throw new Error('Module not initialized. Call init() first.');
|
|
416
|
-
}
|
|
417
|
-
return module.mastering(samples, sampleRate, targetLufs, ceilingDb, truePeakOversample);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export function masteringProcessorNames(): string[] {
|
|
1048
|
+
onsetSample: number,
|
|
1049
|
+
offsetSample: number,
|
|
1050
|
+
stretchRatio: number,
|
|
1051
|
+
): Float32Array {
|
|
421
1052
|
if (!module) {
|
|
422
1053
|
throw new Error('Module not initialized. Call init() first.');
|
|
423
1054
|
}
|
|
424
|
-
return module.
|
|
1055
|
+
return module.noteStretch(samples, sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
425
1056
|
}
|
|
426
1057
|
|
|
427
|
-
|
|
1058
|
+
/**
|
|
1059
|
+
* Apply a voice change by shifting pitch and formants independently.
|
|
1060
|
+
*
|
|
1061
|
+
* @param samples - Audio samples (mono, float32)
|
|
1062
|
+
* @param sampleRate - Sample rate in Hz
|
|
1063
|
+
* @param pitchSemitones - Pitch shift in semitones
|
|
1064
|
+
* @param formantFactor - Formant scaling factor (1.0 = unchanged)
|
|
1065
|
+
* @returns Voice-changed audio
|
|
1066
|
+
*/
|
|
1067
|
+
export function voiceChange(
|
|
1068
|
+
samples: Float32Array,
|
|
1069
|
+
sampleRate: number,
|
|
1070
|
+
pitchSemitones: number,
|
|
1071
|
+
formantFactor: number,
|
|
1072
|
+
): Float32Array {
|
|
428
1073
|
if (!module) {
|
|
429
1074
|
throw new Error('Module not initialized. Call init() first.');
|
|
430
1075
|
}
|
|
431
|
-
return module.
|
|
1076
|
+
return module.voiceChange(samples, sampleRate, pitchSemitones, formantFactor);
|
|
432
1077
|
}
|
|
433
1078
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* Normalize audio to target peak level.
|
|
1081
|
+
*
|
|
1082
|
+
* @param samples - Audio samples (mono, float32)
|
|
1083
|
+
* @param sampleRate - Sample rate in Hz
|
|
1084
|
+
* @param targetDb - Target peak level in dB (default: 0 dB = full scale)
|
|
1085
|
+
* @returns Normalized audio
|
|
1086
|
+
*/
|
|
1087
|
+
export function normalize(samples: Float32Array, sampleRate: number, targetDb = 0.0): Float32Array {
|
|
1088
|
+
if (!module) {
|
|
1089
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1090
|
+
}
|
|
1091
|
+
return module.normalize(samples, sampleRate, targetDb);
|
|
439
1092
|
}
|
|
440
1093
|
|
|
441
|
-
|
|
1094
|
+
/**
|
|
1095
|
+
* Apply mastering loudness normalization with a true-peak ceiling.
|
|
1096
|
+
*
|
|
1097
|
+
* @param samples - Audio samples (mono, float32)
|
|
1098
|
+
* @param sampleRate - Sample rate in Hz
|
|
1099
|
+
* @param targetLufs - Target integrated LUFS (default: -14)
|
|
1100
|
+
* @param ceilingDb - True/sample peak ceiling in dBFS (default: -1)
|
|
1101
|
+
* @param truePeakOversample - Oversampling factor used for peak estimation
|
|
1102
|
+
* @returns Processed audio and loudness metadata
|
|
1103
|
+
*/
|
|
1104
|
+
export function mastering(
|
|
1105
|
+
samples: Float32Array,
|
|
1106
|
+
sampleRate: number,
|
|
1107
|
+
targetLufs = -14.0,
|
|
1108
|
+
ceilingDb = -1.0,
|
|
1109
|
+
truePeakOversample = 4,
|
|
1110
|
+
): MasteringResult {
|
|
442
1111
|
if (!module) {
|
|
443
1112
|
throw new Error('Module not initialized. Call init() first.');
|
|
444
1113
|
}
|
|
445
|
-
return module.
|
|
1114
|
+
return module.mastering(samples, sampleRate, targetLufs, ceilingDb, truePeakOversample);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
export function masteringProcessorNames(): SoloProcessor[] {
|
|
1118
|
+
if (!module) {
|
|
1119
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1120
|
+
}
|
|
1121
|
+
return module.masteringProcessorNames() as SoloProcessor[];
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export function masteringPairProcessorNames(): PairProcessor[] {
|
|
1125
|
+
if (!module) {
|
|
1126
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1127
|
+
}
|
|
1128
|
+
return module.masteringPairProcessorNames() as PairProcessor[];
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
export function masteringPairAnalysisNames(): PairAnalysis[] {
|
|
1132
|
+
if (!module) {
|
|
1133
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1134
|
+
}
|
|
1135
|
+
return module.masteringPairAnalysisNames() as PairAnalysis[];
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
export function masteringStereoAnalysisNames(): StereoAnalysis[] {
|
|
1139
|
+
if (!module) {
|
|
1140
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1141
|
+
}
|
|
1142
|
+
return module.masteringStereoAnalysisNames() as StereoAnalysis[];
|
|
446
1143
|
}
|
|
447
1144
|
|
|
448
1145
|
export function masteringProcess(
|
|
449
|
-
processorName:
|
|
1146
|
+
processorName: SoloProcessor,
|
|
450
1147
|
samples: Float32Array,
|
|
451
1148
|
sampleRate: number,
|
|
452
1149
|
params: MasteringProcessorParams = {},
|
|
@@ -458,7 +1155,7 @@ export function masteringProcess(
|
|
|
458
1155
|
}
|
|
459
1156
|
|
|
460
1157
|
export function masteringProcessStereo(
|
|
461
|
-
processorName:
|
|
1158
|
+
processorName: SoloProcessor,
|
|
462
1159
|
left: Float32Array,
|
|
463
1160
|
right: Float32Array,
|
|
464
1161
|
sampleRate: number,
|
|
@@ -474,7 +1171,7 @@ export function masteringProcessStereo(
|
|
|
474
1171
|
}
|
|
475
1172
|
|
|
476
1173
|
export function masteringPairProcess(
|
|
477
|
-
processorName:
|
|
1174
|
+
processorName: PairProcessor,
|
|
478
1175
|
source: Float32Array,
|
|
479
1176
|
reference: Float32Array,
|
|
480
1177
|
sampleRate: number,
|
|
@@ -487,7 +1184,7 @@ export function masteringPairProcess(
|
|
|
487
1184
|
}
|
|
488
1185
|
|
|
489
1186
|
export function masteringPairAnalyze(
|
|
490
|
-
analysisName:
|
|
1187
|
+
analysisName: PairAnalysis,
|
|
491
1188
|
source: Float32Array,
|
|
492
1189
|
reference: Float32Array,
|
|
493
1190
|
sampleRate: number,
|
|
@@ -500,7 +1197,7 @@ export function masteringPairAnalyze(
|
|
|
500
1197
|
}
|
|
501
1198
|
|
|
502
1199
|
export function masteringStereoAnalyze(
|
|
503
|
-
analysisName:
|
|
1200
|
+
analysisName: StereoAnalysis,
|
|
504
1201
|
left: Float32Array,
|
|
505
1202
|
right: Float32Array,
|
|
506
1203
|
sampleRate: number,
|
|
@@ -512,6 +1209,39 @@ export function masteringStereoAnalyze(
|
|
|
512
1209
|
return module.masteringStereoAnalyze(analysisName, left, right, sampleRate, params);
|
|
513
1210
|
}
|
|
514
1211
|
|
|
1212
|
+
export function masteringAssistantSuggest(
|
|
1213
|
+
samples: Float32Array,
|
|
1214
|
+
sampleRate: number,
|
|
1215
|
+
params: MasteringProcessorParams = {},
|
|
1216
|
+
): string {
|
|
1217
|
+
if (!module) {
|
|
1218
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1219
|
+
}
|
|
1220
|
+
return module.masteringAssistantSuggest(samples, sampleRate, params);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
export function masteringAudioProfile(
|
|
1224
|
+
samples: Float32Array,
|
|
1225
|
+
sampleRate: number,
|
|
1226
|
+
params: MasteringProcessorParams = {},
|
|
1227
|
+
): string {
|
|
1228
|
+
if (!module) {
|
|
1229
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1230
|
+
}
|
|
1231
|
+
return module.masteringAudioProfile(samples, sampleRate, params);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
export function masteringStreamingPreview(
|
|
1235
|
+
samples: Float32Array,
|
|
1236
|
+
sampleRate: number,
|
|
1237
|
+
platforms: StreamingPlatform[] = [],
|
|
1238
|
+
): string {
|
|
1239
|
+
if (!module) {
|
|
1240
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1241
|
+
}
|
|
1242
|
+
return module.masteringStreamingPreview(samples, sampleRate, platforms);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
515
1245
|
/**
|
|
516
1246
|
* Apply a configurable mastering chain in WASM.
|
|
517
1247
|
*
|
|
@@ -618,11 +1348,11 @@ export function masteringChainStereoWithProgress(
|
|
|
618
1348
|
*
|
|
619
1349
|
* @returns Preset names in display order (e.g. "pop", "edm", "aiMusic")
|
|
620
1350
|
*/
|
|
621
|
-
export function masteringPresetNames():
|
|
1351
|
+
export function masteringPresetNames(): MasteringPreset[] {
|
|
622
1352
|
if (!module) {
|
|
623
1353
|
throw new Error('Module not initialized. Call init() first.');
|
|
624
1354
|
}
|
|
625
|
-
return module.masteringPresetNames();
|
|
1355
|
+
return module.masteringPresetNames() as MasteringPreset[];
|
|
626
1356
|
}
|
|
627
1357
|
|
|
628
1358
|
/**
|
|
@@ -637,7 +1367,7 @@ export function masteringPresetNames(): string[] {
|
|
|
637
1367
|
export function masterAudio(
|
|
638
1368
|
samples: Float32Array,
|
|
639
1369
|
sampleRate: number,
|
|
640
|
-
presetName:
|
|
1370
|
+
presetName: MasteringPreset,
|
|
641
1371
|
overrides: Record<string, number | boolean> | null = null,
|
|
642
1372
|
): MasteringChainResult {
|
|
643
1373
|
if (!module) {
|
|
@@ -660,7 +1390,7 @@ export function masterAudioStereo(
|
|
|
660
1390
|
left: Float32Array,
|
|
661
1391
|
right: Float32Array,
|
|
662
1392
|
sampleRate: number,
|
|
663
|
-
presetName:
|
|
1393
|
+
presetName: MasteringPreset,
|
|
664
1394
|
overrides: Record<string, number | boolean> | null = null,
|
|
665
1395
|
): MasteringStereoChainResult {
|
|
666
1396
|
if (!module) {
|
|
@@ -672,6 +1402,48 @@ export function masterAudioStereo(
|
|
|
672
1402
|
return module.masterAudioStereo(presetName, left, right, sampleRate, overrides);
|
|
673
1403
|
}
|
|
674
1404
|
|
|
1405
|
+
export function mixingScenePresetNames(): string[] {
|
|
1406
|
+
if (!module) {
|
|
1407
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1408
|
+
}
|
|
1409
|
+
return module.mixingScenePresetNames();
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* Get a built-in mixing scene preset serialized as JSON. This is the canonical
|
|
1414
|
+
* name shared with the Node and Python bindings; the returned JSON loads
|
|
1415
|
+
* directly into a {@link Mixer} via {@link Mixer.fromSceneJson}.
|
|
1416
|
+
*
|
|
1417
|
+
* @param preset - Preset name (see {@link mixingScenePresetNames})
|
|
1418
|
+
* @returns Scene JSON string
|
|
1419
|
+
*/
|
|
1420
|
+
export function mixingScenePresetJson(preset: string): string {
|
|
1421
|
+
if (!module) {
|
|
1422
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1423
|
+
}
|
|
1424
|
+
return module.mixingScenePresetJson(preset);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
export function mixStereo(
|
|
1428
|
+
leftChannels: Float32Array[],
|
|
1429
|
+
rightChannels: Float32Array[],
|
|
1430
|
+
sampleRate = 48000,
|
|
1431
|
+
options: MixOptions = {},
|
|
1432
|
+
): MixResult {
|
|
1433
|
+
if (!module) {
|
|
1434
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1435
|
+
}
|
|
1436
|
+
if (leftChannels.length === 0 || leftChannels.length !== rightChannels.length) {
|
|
1437
|
+
throw new Error('leftChannels and rightChannels must have the same non-zero length.');
|
|
1438
|
+
}
|
|
1439
|
+
return module.mixStereo(
|
|
1440
|
+
leftChannels,
|
|
1441
|
+
rightChannels,
|
|
1442
|
+
sampleRate,
|
|
1443
|
+
options as Record<string, unknown>,
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
675
1447
|
// ============================================================================
|
|
676
1448
|
// StreamingMasteringChain Class
|
|
677
1449
|
// ============================================================================
|
|
@@ -738,24 +1510,584 @@ export class StreamingMasteringChain {
|
|
|
738
1510
|
return this.chain.processStereo(left, right);
|
|
739
1511
|
}
|
|
740
1512
|
|
|
741
|
-
/** Reset all processor state without rebuilding. */
|
|
742
|
-
reset(): void {
|
|
743
|
-
this.chain.reset();
|
|
1513
|
+
/** Reset all processor state without rebuilding. */
|
|
1514
|
+
reset(): void {
|
|
1515
|
+
this.chain.reset();
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/** Total reported latency in samples across all active processors. */
|
|
1519
|
+
latencySamples(): number {
|
|
1520
|
+
return this.chain.latencySamples();
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/** Ordered stage names that will run (e.g. `"eq.tilt"`). */
|
|
1524
|
+
stageNames(): string[] {
|
|
1525
|
+
return this.chain.stageNames();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/** Release the underlying WASM object. Safe to call only once. */
|
|
1529
|
+
delete(): void {
|
|
1530
|
+
this.chain.delete();
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// ============================================================================
|
|
1535
|
+
// StreamingEqualizer Class
|
|
1536
|
+
// ============================================================================
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* Block-by-block streaming equalizer wrapping the unified C++
|
|
1540
|
+
* `EqualizerProcessor` (up to 24 bands, RBJ/Vicanek biquads, dynamic EQ,
|
|
1541
|
+
* linear-phase FIR, mid/side processing, and auto-gain).
|
|
1542
|
+
*
|
|
1543
|
+
* State is maintained across {@link processMono}/{@link processStereo} calls.
|
|
1544
|
+
* Call {@link delete} (or use `try/finally`) to release the underlying WASM
|
|
1545
|
+
* object — the embind handle is not garbage-collected automatically.
|
|
1546
|
+
*
|
|
1547
|
+
* @example
|
|
1548
|
+
* ```typescript
|
|
1549
|
+
* const eq = new StreamingEqualizer({ sampleRate: 48000, maxBlockSize: 512 });
|
|
1550
|
+
* try {
|
|
1551
|
+
* eq.setBand(0, { type: 'HighShelf', frequencyHz: 8000, gainDb: 6, enabled: true });
|
|
1552
|
+
* const out = eq.processStereo(left, right);
|
|
1553
|
+
* const snapshot = eq.spectrum();
|
|
1554
|
+
* } finally {
|
|
1555
|
+
* eq.delete();
|
|
1556
|
+
* }
|
|
1557
|
+
* ```
|
|
1558
|
+
*/
|
|
1559
|
+
export class StreamingEqualizer {
|
|
1560
|
+
private eq: import('./wasm_types').WasmStreamingEqualizer;
|
|
1561
|
+
|
|
1562
|
+
constructor(config: StreamingEqualizerConfig = {}) {
|
|
1563
|
+
if (!module) {
|
|
1564
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1565
|
+
}
|
|
1566
|
+
this.eq = module.createEqualizer(config as Record<string, unknown>);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* Configure the band at `index` (0..23). Omitted fields use C++ defaults.
|
|
1571
|
+
*/
|
|
1572
|
+
setBand(index: number, band: EqBand): void {
|
|
1573
|
+
this.eq.setBand(index, band as Record<string, unknown>);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
/** Disable and reset every band. */
|
|
1577
|
+
clear(): void {
|
|
1578
|
+
this.eq.clear();
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/**
|
|
1582
|
+
* Set the global phase mode: 1=ZeroLatency, 2=NaturalPhase, 3=LinearPhase.
|
|
1583
|
+
*/
|
|
1584
|
+
setPhaseMode(mode: number): void {
|
|
1585
|
+
this.eq.setPhaseMode(mode);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/** Enable or disable output auto-gain compensation. */
|
|
1589
|
+
setAutoGain(enabled: boolean): void {
|
|
1590
|
+
this.eq.setAutoGain(enabled);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/** Set all-band EQ gain scale as a 0.0..2.0 multiplier. */
|
|
1594
|
+
setGainScale(scale: number): void {
|
|
1595
|
+
this.eq.setGainScale(scale);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
/** Set post-EQ output gain in dB. */
|
|
1599
|
+
setOutputGainDb(gainDb: number): void {
|
|
1600
|
+
this.eq.setOutputGainDb(gainDb);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/** Set post-EQ stereo balance in -1.0..1.0; mono input ignores pan. */
|
|
1604
|
+
setOutputPan(pan: number): void {
|
|
1605
|
+
this.eq.setOutputPan(pan);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
/**
|
|
1609
|
+
* Provide a mono external sidechain key for dynamic bands that opt into
|
|
1610
|
+
* `external_sidechain`. The samples are copied into an owned buffer.
|
|
1611
|
+
*/
|
|
1612
|
+
setSidechainMono(samples: Float32Array): void {
|
|
1613
|
+
this.eq.setSidechainMono(samples);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Provide a stereo external sidechain key. Both channels must match length.
|
|
1618
|
+
*/
|
|
1619
|
+
setSidechainStereo(left: Float32Array, right: Float32Array): void {
|
|
1620
|
+
if (left.length !== right.length) {
|
|
1621
|
+
throw new Error('Sidechain channel lengths must match.');
|
|
1622
|
+
}
|
|
1623
|
+
this.eq.setSidechainStereo(left, right);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
/** Release any borrowed external sidechain buffers. */
|
|
1627
|
+
clearSidechain(): void {
|
|
1628
|
+
this.eq.clearSidechain();
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/** Auto-gain applied on the most recent block, in dB. */
|
|
1632
|
+
lastAutoGainDb(): number {
|
|
1633
|
+
return this.eq.lastAutoGainDb();
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
/** Reported processing latency in samples (non-zero for linear-phase bands). */
|
|
1637
|
+
latencySamples(): number {
|
|
1638
|
+
return this.eq.latencySamples();
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/**
|
|
1642
|
+
* Process one mono block, returning the equalized samples (same length).
|
|
1643
|
+
*/
|
|
1644
|
+
processMono(samples: Float32Array): Float32Array {
|
|
1645
|
+
return this.eq.processMono(samples);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* Process one stereo block, returning the equalized channels.
|
|
1650
|
+
*/
|
|
1651
|
+
processStereo(
|
|
1652
|
+
left: Float32Array,
|
|
1653
|
+
right: Float32Array,
|
|
1654
|
+
): { left: Float32Array; right: Float32Array } {
|
|
1655
|
+
if (left.length !== right.length) {
|
|
1656
|
+
throw new Error('Stereo channel lengths must match.');
|
|
1657
|
+
}
|
|
1658
|
+
return this.eq.processStereo(left, right);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Read the latest pre/post spectrum snapshot for metering. `seq` increments
|
|
1663
|
+
* each time a new snapshot is published.
|
|
1664
|
+
*/
|
|
1665
|
+
spectrum(): EqSpectrumSnapshot {
|
|
1666
|
+
return this.eq.spectrum();
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Configure bands so the source spectrum matches the reference spectrum.
|
|
1671
|
+
*
|
|
1672
|
+
* @param source - Source audio (mono samples)
|
|
1673
|
+
* @param reference - Reference audio (mono samples)
|
|
1674
|
+
* @param options - `sampleRate` (default 48000) and `maxBands` (default 8)
|
|
1675
|
+
*/
|
|
1676
|
+
match(source: Float32Array, reference: Float32Array, options: EqMatchOptions = {}): void {
|
|
1677
|
+
this.eq.match(source, reference, options as Record<string, unknown>);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/** Release the underlying WASM object. Safe to call only once. */
|
|
1681
|
+
delete(): void {
|
|
1682
|
+
this.eq.delete();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// ============================================================================
|
|
1687
|
+
// Mixer Class (scene-based persistent mixer)
|
|
1688
|
+
// ============================================================================
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Get a built-in mixing scene preset serialized as JSON, normalized through the
|
|
1692
|
+
* C mixer API (the same path {@link Mixer.fromSceneJson} uses to load it).
|
|
1693
|
+
*
|
|
1694
|
+
* @deprecated Use {@link mixingScenePresetJson}, the canonical name shared with
|
|
1695
|
+
* the Node and Python bindings. This alias is retained for backwards
|
|
1696
|
+
* compatibility and may be removed in a future release. Both functions return a
|
|
1697
|
+
* scene JSON string that loads cleanly into a {@link Mixer}.
|
|
1698
|
+
*
|
|
1699
|
+
* @param preset - Preset name (see {@link mixingScenePresetNames})
|
|
1700
|
+
* @returns Scene JSON string
|
|
1701
|
+
*/
|
|
1702
|
+
export function mixerScenePresetJson(preset: string): string {
|
|
1703
|
+
if (!module) {
|
|
1704
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1705
|
+
}
|
|
1706
|
+
return module.mixerPresetJson(preset);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* Persistent, scene-based stereo mixer.
|
|
1711
|
+
*
|
|
1712
|
+
* Build one from a scene JSON string (e.g. {@link mixerScenePresetJson} or a
|
|
1713
|
+
* hand-authored scene), then feed per-strip stereo blocks through
|
|
1714
|
+
* {@link processStereo} to get the routed stereo master. Strips, sends, buses,
|
|
1715
|
+
* and inserts are described entirely by the scene; the routing graph is
|
|
1716
|
+
* compiled lazily on the first {@link processStereo} call (or eagerly via
|
|
1717
|
+
* {@link compile}).
|
|
1718
|
+
*
|
|
1719
|
+
* Call {@link delete} (or use a `try/finally`) to release the underlying WASM
|
|
1720
|
+
* object — the embind handle is not garbage-collected automatically.
|
|
1721
|
+
*
|
|
1722
|
+
* @example
|
|
1723
|
+
* ```typescript
|
|
1724
|
+
* const mixer = Mixer.fromSceneJson(mixerScenePresetJson('basicStereo'), 48000, 512);
|
|
1725
|
+
* try {
|
|
1726
|
+
* const out = mixer.processStereo([stripL], [stripR]);
|
|
1727
|
+
* } finally {
|
|
1728
|
+
* mixer.delete();
|
|
1729
|
+
* }
|
|
1730
|
+
* ```
|
|
1731
|
+
*/
|
|
1732
|
+
export class Mixer {
|
|
1733
|
+
private mixer: import('./wasm_types').WasmMixer;
|
|
1734
|
+
|
|
1735
|
+
private constructor(mixer: import('./wasm_types').WasmMixer) {
|
|
1736
|
+
this.mixer = mixer;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Build a mixer from a scene JSON string.
|
|
1741
|
+
*
|
|
1742
|
+
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
1743
|
+
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
1744
|
+
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
1745
|
+
*/
|
|
1746
|
+
static fromSceneJson(json: string, sampleRate = 48000, blockSize = 512): Mixer {
|
|
1747
|
+
if (!module) {
|
|
1748
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
1749
|
+
}
|
|
1750
|
+
return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize));
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/** Rebuild and compile the routing graph from the current scene topology. */
|
|
1754
|
+
compile(): void {
|
|
1755
|
+
this.mixer.compile();
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/**
|
|
1759
|
+
* Mix one block of per-strip stereo audio into the stereo master.
|
|
1760
|
+
*
|
|
1761
|
+
* @param leftChannels - `leftChannels[i]` is the left channel of strip `i`
|
|
1762
|
+
* @param rightChannels - `rightChannels[i]` is the right channel of strip `i`
|
|
1763
|
+
* @returns Mixed stereo master (`left`, `right`, `sampleRate`)
|
|
1764
|
+
*/
|
|
1765
|
+
processStereo(leftChannels: Float32Array[], rightChannels: Float32Array[]): MixerProcessResult {
|
|
1766
|
+
if (leftChannels.length !== rightChannels.length) {
|
|
1767
|
+
throw new Error('leftChannels and rightChannels must have the same length.');
|
|
1768
|
+
}
|
|
1769
|
+
return this.mixer.processStereo(leftChannels, rightChannels);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* Mix one block into caller-owned output arrays.
|
|
1774
|
+
*
|
|
1775
|
+
* This avoids allocating the result object and result `Float32Array`s. It is
|
|
1776
|
+
* intended for realtime bridges such as AudioWorklet; the input channel count
|
|
1777
|
+
* must match the scene strip count and all arrays must have the same length.
|
|
1778
|
+
*/
|
|
1779
|
+
processStereoInto(
|
|
1780
|
+
leftChannels: Float32Array[],
|
|
1781
|
+
rightChannels: Float32Array[],
|
|
1782
|
+
outLeft: Float32Array,
|
|
1783
|
+
outRight: Float32Array,
|
|
1784
|
+
): void {
|
|
1785
|
+
if (leftChannels.length !== rightChannels.length) {
|
|
1786
|
+
throw new Error('leftChannels and rightChannels must have the same length.');
|
|
1787
|
+
}
|
|
1788
|
+
if (outLeft.length !== outRight.length) {
|
|
1789
|
+
throw new Error('outLeft and outRight must have the same length.');
|
|
1790
|
+
}
|
|
1791
|
+
this.mixer.processStereoInto(leftChannels, rightChannels, outLeft, outRight);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Create reusable WASM-heap input/output views for realtime-style processing.
|
|
1796
|
+
*
|
|
1797
|
+
* Fill `leftInputs[i]` / `rightInputs[i]`, call `process()`, then read
|
|
1798
|
+
* `outLeft` / `outRight`. The views are owned by this mixer and become invalid
|
|
1799
|
+
* after {@link delete}.
|
|
1800
|
+
*/
|
|
1801
|
+
createRealtimeBuffer(): MixerRealtimeBuffer {
|
|
1802
|
+
const stripCount = this.stripCount();
|
|
1803
|
+
const leftInputs: Float32Array[] = [];
|
|
1804
|
+
const rightInputs: Float32Array[] = [];
|
|
1805
|
+
for (let index = 0; index < stripCount; index++) {
|
|
1806
|
+
leftInputs.push(this.mixer.inputLeftView(index));
|
|
1807
|
+
rightInputs.push(this.mixer.inputRightView(index));
|
|
1808
|
+
}
|
|
1809
|
+
const outLeft = this.mixer.outputLeftView();
|
|
1810
|
+
const outRight = this.mixer.outputRightView();
|
|
1811
|
+
return {
|
|
1812
|
+
leftInputs,
|
|
1813
|
+
rightInputs,
|
|
1814
|
+
outLeft,
|
|
1815
|
+
outRight,
|
|
1816
|
+
process: (numSamples = outLeft.length) => this.mixer.processPreparedStereo(numSamples),
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
1821
|
+
stripCount(): number {
|
|
1822
|
+
return this.mixer.stripCount();
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Schedule sample-accurate insert-parameter automation on a strip's insert.
|
|
1827
|
+
*
|
|
1828
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
1829
|
+
* @param insertIndex - Index into the strip's combined insert sequence
|
|
1830
|
+
* (`[pre-inserts... post-inserts...]`)
|
|
1831
|
+
* @param paramId - Processor-specific parameter id
|
|
1832
|
+
* @param samplePos - Absolute samples from the start of processing (the mixer
|
|
1833
|
+
* advances an internal position from 0 on the first {@link processStereo}
|
|
1834
|
+
* call; recompiling resets it to 0)
|
|
1835
|
+
* @param value - Target parameter value
|
|
1836
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
1837
|
+
* @throws If the strip index is out of range or the schedule call fails
|
|
1838
|
+
* (unknown curve, out-of-range insert index, or full event lane)
|
|
1839
|
+
*/
|
|
1840
|
+
scheduleInsertAutomation(
|
|
1841
|
+
stripIndex: number,
|
|
1842
|
+
insertIndex: number,
|
|
1843
|
+
paramId: number,
|
|
1844
|
+
samplePos: number,
|
|
1845
|
+
value: number,
|
|
1846
|
+
curve: AutomationCurve = 'linear',
|
|
1847
|
+
): void {
|
|
1848
|
+
this.mixer.scheduleInsertAutomation(
|
|
1849
|
+
stripIndex,
|
|
1850
|
+
insertIndex,
|
|
1851
|
+
paramId,
|
|
1852
|
+
samplePos,
|
|
1853
|
+
value,
|
|
1854
|
+
automationCurveCode(curve),
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
/**
|
|
1859
|
+
* Resolve a strip's index in `[0, stripCount())` from its scene id, or `null`
|
|
1860
|
+
* when no strip with that id exists (matches the Node binding's `number | null`).
|
|
1861
|
+
*/
|
|
1862
|
+
stripById(id: string): number | null {
|
|
1863
|
+
const index = this.mixer.stripById(id);
|
|
1864
|
+
return index < 0 ? null : index;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
/**
|
|
1868
|
+
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
1869
|
+
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
1870
|
+
* {@link compile} (or {@link processStereo}) to rebuild.
|
|
1871
|
+
*/
|
|
1872
|
+
addBus(id: string, role = 'aux'): void {
|
|
1873
|
+
this.mixer.addBus(id, role);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/** Remove a bus by id. Marks the routing graph dirty. */
|
|
1877
|
+
removeBus(id: string): void {
|
|
1878
|
+
this.mixer.removeBus(id);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
/** Number of buses in the mixer topology. */
|
|
1882
|
+
busCount(): number {
|
|
1883
|
+
return this.mixer.busCount();
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* Add a VCA group with the given gain offset (dB). `members` is a list of
|
|
1888
|
+
* strip ids governed by the group (may be empty).
|
|
1889
|
+
*/
|
|
1890
|
+
addVcaGroup(id: string, gainDb = 0.0, members: string[] = []): void {
|
|
1891
|
+
this.mixer.addVcaGroup(id, gainDb, members);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/** Remove a VCA group by id. */
|
|
1895
|
+
removeVcaGroup(id: string): void {
|
|
1896
|
+
this.mixer.removeVcaGroup(id);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/** Number of VCA groups in the mixer topology. */
|
|
1900
|
+
vcaGroupCount(): number {
|
|
1901
|
+
return this.mixer.vcaGroupCount();
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
/**
|
|
1905
|
+
* Set a strip's solo state. Takes effect on the next process without a
|
|
1906
|
+
* graph recompile.
|
|
1907
|
+
*/
|
|
1908
|
+
setSoloed(stripIndex: number, soloed: boolean): void {
|
|
1909
|
+
this.mixer.setSoloed(stripIndex, soloed);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* Mark a strip solo-safe so it is never implied-muted by another strip's
|
|
1914
|
+
* solo. Takes effect on the next process without a graph recompile.
|
|
1915
|
+
*/
|
|
1916
|
+
setSoloSafe(stripIndex: number, soloSafe: boolean): void {
|
|
1917
|
+
this.mixer.setSoloSafe(stripIndex, soloSafe);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/** Invert the polarity of the left and/or right channel of a strip. */
|
|
1921
|
+
setPolarityInvert(stripIndex: number, invertLeft: boolean, invertRight: boolean): void {
|
|
1922
|
+
this.mixer.setPolarityInvert(stripIndex, invertLeft, invertRight);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/** Set the strip's pan law. */
|
|
1926
|
+
setPanLaw(stripIndex: number, panLaw: PanLaw | number): void {
|
|
1927
|
+
this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Set a per-strip channel delay in samples. This changes the strip's reported
|
|
1932
|
+
* latency; recompile to re-run latency compensation.
|
|
1933
|
+
*/
|
|
1934
|
+
setChannelDelaySamples(stripIndex: number, delaySamples: number): void {
|
|
1935
|
+
this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/** Set the strip's live VCA gain offset in dB (not persisted to the scene). */
|
|
1939
|
+
setVcaOffsetDb(stripIndex: number, offsetDb: number): void {
|
|
1940
|
+
this.mixer.setVcaOffsetDb(stripIndex, offsetDb);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/** Set independent left/right pan positions (dual-pan mode). */
|
|
1944
|
+
setDualPan(stripIndex: number, leftPan: number, rightPan: number): void {
|
|
1945
|
+
this.mixer.setDualPan(stripIndex, leftPan, rightPan);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
/**
|
|
1949
|
+
* Add a send to a strip after construction.
|
|
1950
|
+
*
|
|
1951
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
1952
|
+
* @param id - Send id
|
|
1953
|
+
* @param destinationBusId - Destination bus id
|
|
1954
|
+
* @param sendDb - Initial send level in dB
|
|
1955
|
+
* @param timing - `'preFader'` or `'postFader'` (default: `'postFader'`)
|
|
1956
|
+
* @returns The new send's index
|
|
1957
|
+
*/
|
|
1958
|
+
addSend(
|
|
1959
|
+
stripIndex: number,
|
|
1960
|
+
id: string,
|
|
1961
|
+
destinationBusId: string,
|
|
1962
|
+
sendDb: number,
|
|
1963
|
+
timing: SendTiming | number = 'postFader',
|
|
1964
|
+
): number {
|
|
1965
|
+
return this.mixer.addSend(stripIndex, id, destinationBusId, sendDb, sendTimingCode(timing));
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/** Set the send level (in dB) for an existing send by index. */
|
|
1969
|
+
setSendDb(stripIndex: number, sendIndex: number, sendDb: number): void {
|
|
1970
|
+
this.mixer.setSendDb(stripIndex, sendIndex, sendDb);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* Read a strip's meter snapshot at the given tap point.
|
|
1975
|
+
*
|
|
1976
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
1977
|
+
* @param tap - `'preFader'` or `'postFader'` (default: `'postFader'`)
|
|
1978
|
+
*/
|
|
1979
|
+
meterTap(stripIndex: number, tap: MeterTap = 'postFader'): MixMeterSnapshot {
|
|
1980
|
+
return this.mixer.meterTap(stripIndex, meterTapCode(tap));
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* Read a strip's meter snapshot. Alias of {@link meterTap}, provided for
|
|
1985
|
+
* cross-binding (Node/Python) parity.
|
|
1986
|
+
*
|
|
1987
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
1988
|
+
* @param tap - `'preFader'` or `'postFader'` (default: `'postFader'`)
|
|
1989
|
+
*/
|
|
1990
|
+
stripMeter(stripIndex: number, tap: MeterTap = 'postFader'): MixMeterSnapshot {
|
|
1991
|
+
return this.mixer.stripMeter(stripIndex, meterTapCode(tap));
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
/**
|
|
1995
|
+
* Schedule sample-accurate fader automation on a strip.
|
|
1996
|
+
*
|
|
1997
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
1998
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
1999
|
+
* @param faderDb - Target fader level in dB
|
|
2000
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
2001
|
+
*/
|
|
2002
|
+
scheduleFaderAutomation(
|
|
2003
|
+
stripIndex: number,
|
|
2004
|
+
samplePos: number,
|
|
2005
|
+
faderDb: number,
|
|
2006
|
+
curve: AutomationCurve = 'linear',
|
|
2007
|
+
): void {
|
|
2008
|
+
this.mixer.scheduleFaderAutomation(stripIndex, samplePos, faderDb, automationCurveCode(curve));
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
/**
|
|
2012
|
+
* Schedule sample-accurate pan automation on a strip.
|
|
2013
|
+
*
|
|
2014
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2015
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
2016
|
+
* @param pan - Target pan position
|
|
2017
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
2018
|
+
*/
|
|
2019
|
+
schedulePanAutomation(
|
|
2020
|
+
stripIndex: number,
|
|
2021
|
+
samplePos: number,
|
|
2022
|
+
pan: number,
|
|
2023
|
+
curve: AutomationCurve = 'linear',
|
|
2024
|
+
): void {
|
|
2025
|
+
this.mixer.schedulePanAutomation(stripIndex, samplePos, pan, automationCurveCode(curve));
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* Schedule sample-accurate width automation on a strip.
|
|
2030
|
+
*
|
|
2031
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2032
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
2033
|
+
* @param width - Target stereo width
|
|
2034
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
2035
|
+
*/
|
|
2036
|
+
scheduleWidthAutomation(
|
|
2037
|
+
stripIndex: number,
|
|
2038
|
+
samplePos: number,
|
|
2039
|
+
width: number,
|
|
2040
|
+
curve: AutomationCurve = 'linear',
|
|
2041
|
+
): void {
|
|
2042
|
+
this.mixer.scheduleWidthAutomation(stripIndex, samplePos, width, automationCurveCode(curve));
|
|
744
2043
|
}
|
|
745
2044
|
|
|
746
|
-
/**
|
|
747
|
-
|
|
748
|
-
|
|
2045
|
+
/**
|
|
2046
|
+
* Schedule sample-accurate send-level automation on a strip's send.
|
|
2047
|
+
*
|
|
2048
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2049
|
+
* @param sendIndex - Send index in the strip's add order
|
|
2050
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
2051
|
+
* @param db - Target send level in dB
|
|
2052
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
2053
|
+
*/
|
|
2054
|
+
scheduleSendAutomation(
|
|
2055
|
+
stripIndex: number,
|
|
2056
|
+
sendIndex: number,
|
|
2057
|
+
samplePos: number,
|
|
2058
|
+
db: number,
|
|
2059
|
+
curve: AutomationCurve = 'linear',
|
|
2060
|
+
): void {
|
|
2061
|
+
this.mixer.scheduleSendAutomation(
|
|
2062
|
+
stripIndex,
|
|
2063
|
+
sendIndex,
|
|
2064
|
+
samplePos,
|
|
2065
|
+
db,
|
|
2066
|
+
automationCurveCode(curve),
|
|
2067
|
+
);
|
|
749
2068
|
}
|
|
750
2069
|
|
|
751
|
-
/**
|
|
752
|
-
|
|
753
|
-
|
|
2070
|
+
/**
|
|
2071
|
+
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
2072
|
+
* (oldest to newest).
|
|
2073
|
+
*/
|
|
2074
|
+
readGoniometerLatest(stripIndex: number, maxPoints: number): GoniometerPoint[] {
|
|
2075
|
+
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/** Serialize the current scene (strips, buses, sends, connections) to JSON. */
|
|
2079
|
+
toSceneJson(): string {
|
|
2080
|
+
return this.mixer.toSceneJson();
|
|
754
2081
|
}
|
|
755
2082
|
|
|
756
2083
|
/** Release the underlying WASM object. Safe to call only once. */
|
|
757
2084
|
delete(): void {
|
|
758
|
-
this.
|
|
2085
|
+
this.mixer.delete();
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
2089
|
+
destroy(): void {
|
|
2090
|
+
this.delete();
|
|
759
2091
|
}
|
|
760
2092
|
}
|
|
761
2093
|
|
|
@@ -872,6 +2204,143 @@ export function mfcc(
|
|
|
872
2204
|
return module.mfcc(samples, sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
873
2205
|
}
|
|
874
2206
|
|
|
2207
|
+
// ============================================================================
|
|
2208
|
+
// Features - Inverse reconstruction
|
|
2209
|
+
// ============================================================================
|
|
2210
|
+
|
|
2211
|
+
/**
|
|
2212
|
+
* Approximate inverse of a Mel filterbank: Mel power spectrogram -> STFT power
|
|
2213
|
+
* spectrogram. Mirrors `feature::mel_to_stft`.
|
|
2214
|
+
*
|
|
2215
|
+
* @param melPower - Mel power spectrogram [nMels x nFrames] row-major
|
|
2216
|
+
* @param nMels - Number of Mel bands
|
|
2217
|
+
* @param nFrames - Number of time frames
|
|
2218
|
+
* @param sampleRate - Sample rate in Hz
|
|
2219
|
+
* @param nFft - FFT size (default: 2048)
|
|
2220
|
+
* @param hopLength - Hop length (default: 512)
|
|
2221
|
+
* @returns STFT power spectrogram result
|
|
2222
|
+
*/
|
|
2223
|
+
export function melToStft(
|
|
2224
|
+
melPower: Float32Array,
|
|
2225
|
+
nMels: number,
|
|
2226
|
+
nFrames: number,
|
|
2227
|
+
sampleRate: number,
|
|
2228
|
+
nFft = 2048,
|
|
2229
|
+
hopLength = 512,
|
|
2230
|
+
fmin = 0,
|
|
2231
|
+
fmax = 0,
|
|
2232
|
+
): StftPowerResult {
|
|
2233
|
+
if (!module) {
|
|
2234
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2235
|
+
}
|
|
2236
|
+
return module.melToStft(melPower, nMels, nFrames, sampleRate, nFft, hopLength, fmin, fmax);
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
/**
|
|
2240
|
+
* Reconstruct audio from a Mel power spectrogram via Griffin-Lim. Mirrors
|
|
2241
|
+
* `feature::mel_to_audio`.
|
|
2242
|
+
*
|
|
2243
|
+
* @param melPower - Mel power spectrogram [nMels x nFrames] row-major
|
|
2244
|
+
* @param nMels - Number of Mel bands
|
|
2245
|
+
* @param nFrames - Number of time frames
|
|
2246
|
+
* @param sampleRate - Sample rate in Hz
|
|
2247
|
+
* @param nFft - FFT size (default: 2048)
|
|
2248
|
+
* @param hopLength - Hop length (default: 512)
|
|
2249
|
+
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2250
|
+
* @returns Reconstructed audio samples (mono, float32)
|
|
2251
|
+
*/
|
|
2252
|
+
export function melToAudio(
|
|
2253
|
+
melPower: Float32Array,
|
|
2254
|
+
nMels: number,
|
|
2255
|
+
nFrames: number,
|
|
2256
|
+
sampleRate: number,
|
|
2257
|
+
nFft = 2048,
|
|
2258
|
+
hopLength = 512,
|
|
2259
|
+
nIter = 32,
|
|
2260
|
+
fmin = 0,
|
|
2261
|
+
fmax = 0,
|
|
2262
|
+
): Float32Array {
|
|
2263
|
+
if (!module) {
|
|
2264
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2265
|
+
}
|
|
2266
|
+
return module.melToAudio(
|
|
2267
|
+
melPower,
|
|
2268
|
+
nMels,
|
|
2269
|
+
nFrames,
|
|
2270
|
+
sampleRate,
|
|
2271
|
+
nFft,
|
|
2272
|
+
hopLength,
|
|
2273
|
+
nIter,
|
|
2274
|
+
fmin,
|
|
2275
|
+
fmax,
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
/**
|
|
2280
|
+
* Invert MFCC coefficients back to a Mel power spectrogram. Mirrors
|
|
2281
|
+
* `feature::mfcc_to_mel`.
|
|
2282
|
+
*
|
|
2283
|
+
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
2284
|
+
* @param nMfcc - Number of MFCC coefficients
|
|
2285
|
+
* @param nFrames - Number of time frames
|
|
2286
|
+
* @param nMels - Number of Mel bins to reconstruct (default: 128)
|
|
2287
|
+
* @returns Mel power spectrogram result
|
|
2288
|
+
*/
|
|
2289
|
+
export function mfccToMel(
|
|
2290
|
+
mfccCoefficients: Float32Array,
|
|
2291
|
+
nMfcc: number,
|
|
2292
|
+
nFrames: number,
|
|
2293
|
+
nMels = 128,
|
|
2294
|
+
): MelPowerResult {
|
|
2295
|
+
if (!module) {
|
|
2296
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2297
|
+
}
|
|
2298
|
+
return module.mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/**
|
|
2302
|
+
* Reconstruct audio directly from MFCC coefficients via Griffin-Lim. Mirrors
|
|
2303
|
+
* `feature::mfcc_to_audio`.
|
|
2304
|
+
*
|
|
2305
|
+
* @param mfccCoefficients - MFCC matrix [nMfcc x nFrames] row-major
|
|
2306
|
+
* @param nMfcc - Number of MFCC coefficients
|
|
2307
|
+
* @param nFrames - Number of time frames
|
|
2308
|
+
* @param nMels - Number of Mel bins (default: 128)
|
|
2309
|
+
* @param sampleRate - Sample rate in Hz
|
|
2310
|
+
* @param nFft - FFT size (default: 2048)
|
|
2311
|
+
* @param hopLength - Hop length (default: 512)
|
|
2312
|
+
* @param nIter - Griffin-Lim iterations (default: 32)
|
|
2313
|
+
* @returns Reconstructed audio samples (mono, float32)
|
|
2314
|
+
*/
|
|
2315
|
+
export function mfccToAudio(
|
|
2316
|
+
mfccCoefficients: Float32Array,
|
|
2317
|
+
nMfcc: number,
|
|
2318
|
+
nFrames: number,
|
|
2319
|
+
nMels: number,
|
|
2320
|
+
sampleRate: number,
|
|
2321
|
+
nFft = 2048,
|
|
2322
|
+
hopLength = 512,
|
|
2323
|
+
nIter = 32,
|
|
2324
|
+
fmin = 0,
|
|
2325
|
+
fmax = 0,
|
|
2326
|
+
): Float32Array {
|
|
2327
|
+
if (!module) {
|
|
2328
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2329
|
+
}
|
|
2330
|
+
return module.mfccToAudio(
|
|
2331
|
+
mfccCoefficients,
|
|
2332
|
+
nMfcc,
|
|
2333
|
+
nFrames,
|
|
2334
|
+
nMels,
|
|
2335
|
+
sampleRate,
|
|
2336
|
+
nFft,
|
|
2337
|
+
hopLength,
|
|
2338
|
+
nIter,
|
|
2339
|
+
fmin,
|
|
2340
|
+
fmax,
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
875
2344
|
// ============================================================================
|
|
876
2345
|
// Features - Chroma
|
|
877
2346
|
// ============================================================================
|
|
@@ -1371,11 +2840,26 @@ export function tempogram(
|
|
|
1371
2840
|
sampleRate: number,
|
|
1372
2841
|
hopLength = 512,
|
|
1373
2842
|
winLength = 384,
|
|
2843
|
+
mode: TempogramMode = 'autocorrelation',
|
|
1374
2844
|
): WasmTempogramResult {
|
|
1375
2845
|
if (!module) {
|
|
1376
2846
|
throw new Error('Module not initialized. Call init() first.');
|
|
1377
2847
|
}
|
|
1378
|
-
return module.tempogram(onsetEnvelope, sampleRate, hopLength, winLength);
|
|
2848
|
+
return module.tempogram(onsetEnvelope, sampleRate, hopLength, winLength, mode);
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
export function cyclicTempogram(
|
|
2852
|
+
onsetEnvelope: Float32Array,
|
|
2853
|
+
sampleRate: number,
|
|
2854
|
+
hopLength = 512,
|
|
2855
|
+
winLength = 384,
|
|
2856
|
+
bpmMin = 60.0,
|
|
2857
|
+
nBins = 60,
|
|
2858
|
+
): WasmCyclicTempogramResult {
|
|
2859
|
+
if (!module) {
|
|
2860
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2861
|
+
}
|
|
2862
|
+
return module.cyclicTempogram(onsetEnvelope, sampleRate, hopLength, winLength, bpmMin, nBins);
|
|
1379
2863
|
}
|
|
1380
2864
|
|
|
1381
2865
|
export function plp(
|
|
@@ -1392,6 +2876,231 @@ export function plp(
|
|
|
1392
2876
|
return module.plp(onsetEnvelope, sampleRate, hopLength, tempoMin, tempoMax, winLength);
|
|
1393
2877
|
}
|
|
1394
2878
|
|
|
2879
|
+
/**
|
|
2880
|
+
* Compute NNLS (non-negative least squares) chromagram.
|
|
2881
|
+
*
|
|
2882
|
+
* @param samples - Audio samples (mono, float32)
|
|
2883
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2884
|
+
* @returns NNLS chroma result
|
|
2885
|
+
*/
|
|
2886
|
+
export function nnlsChroma(samples: Float32Array, sampleRate = 22050): WasmNnlsChromaResult {
|
|
2887
|
+
if (!module) {
|
|
2888
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2889
|
+
}
|
|
2890
|
+
return module.nnlsChroma(samples, sampleRate);
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
/**
|
|
2894
|
+
* Compute the Constant-Q Transform magnitude.
|
|
2895
|
+
*
|
|
2896
|
+
* @param samples - Audio samples (mono, float32)
|
|
2897
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2898
|
+
* @param hopLength - Hop length (default: 512)
|
|
2899
|
+
* @param fmin - Minimum frequency in Hz (default: 32.70319566257483, C1)
|
|
2900
|
+
* @param nBins - Number of frequency bins (default: 84)
|
|
2901
|
+
* @param binsPerOctave - Bins per octave (default: 12)
|
|
2902
|
+
* @returns CQT magnitude result
|
|
2903
|
+
*/
|
|
2904
|
+
export function cqt(
|
|
2905
|
+
samples: Float32Array,
|
|
2906
|
+
sampleRate = 22050,
|
|
2907
|
+
hopLength = 512,
|
|
2908
|
+
fmin = 32.70319566257483,
|
|
2909
|
+
nBins = 84,
|
|
2910
|
+
binsPerOctave = 12,
|
|
2911
|
+
): CqtResult {
|
|
2912
|
+
if (!module) {
|
|
2913
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2914
|
+
}
|
|
2915
|
+
return module.cqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
/**
|
|
2919
|
+
* Compute the Variable-Q Transform magnitude (gamma controls Q).
|
|
2920
|
+
*
|
|
2921
|
+
* @param samples - Audio samples (mono, float32)
|
|
2922
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2923
|
+
* @param hopLength - Hop length (default: 512)
|
|
2924
|
+
* @param fmin - Minimum frequency in Hz (default: 32.70319566257483, C1)
|
|
2925
|
+
* @param nBins - Number of frequency bins (default: 84)
|
|
2926
|
+
* @param binsPerOctave - Bins per octave (default: 12)
|
|
2927
|
+
* @param gamma - Bandwidth offset; 0 is equivalent to CQT (default: 0)
|
|
2928
|
+
* @returns VQT magnitude result (same shape as CQT)
|
|
2929
|
+
*/
|
|
2930
|
+
export function vqt(
|
|
2931
|
+
samples: Float32Array,
|
|
2932
|
+
sampleRate = 22050,
|
|
2933
|
+
hopLength = 512,
|
|
2934
|
+
fmin = 32.70319566257483,
|
|
2935
|
+
nBins = 84,
|
|
2936
|
+
binsPerOctave = 12,
|
|
2937
|
+
gamma = 0,
|
|
2938
|
+
): CqtResult {
|
|
2939
|
+
if (!module) {
|
|
2940
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2941
|
+
}
|
|
2942
|
+
return module.vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
/**
|
|
2946
|
+
* Detect song-structure sections (intro/verse/chorus/...).
|
|
2947
|
+
*
|
|
2948
|
+
* @param samples - Audio samples (mono, float32)
|
|
2949
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2950
|
+
* @param nFft - FFT size (default: 2048)
|
|
2951
|
+
* @param hopLength - Hop length (default: 512)
|
|
2952
|
+
* @param minSectionSec - Minimum section duration in seconds (default: 8.0)
|
|
2953
|
+
* @returns Array of detected sections
|
|
2954
|
+
*/
|
|
2955
|
+
export function analyzeSections(
|
|
2956
|
+
samples: Float32Array,
|
|
2957
|
+
sampleRate = 22050,
|
|
2958
|
+
nFft = 2048,
|
|
2959
|
+
hopLength = 512,
|
|
2960
|
+
minSectionSec = 8.0,
|
|
2961
|
+
): Section[] {
|
|
2962
|
+
if (!module) {
|
|
2963
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2964
|
+
}
|
|
2965
|
+
return module
|
|
2966
|
+
.analyzeSections(samples, sampleRate, nFft, hopLength, minSectionSec)
|
|
2967
|
+
.map((s) => ({ ...s, type: s.type as SectionType }));
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
/**
|
|
2971
|
+
* Extract the melody contour from monophonic audio via YIN.
|
|
2972
|
+
*
|
|
2973
|
+
* @param samples - Audio samples (mono, float32)
|
|
2974
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
2975
|
+
* @param fmin - Minimum frequency in Hz (default: 65.0)
|
|
2976
|
+
* @param fmax - Maximum frequency in Hz (default: 2093.0)
|
|
2977
|
+
* @param frameLength - Frame length in samples (default: 2048)
|
|
2978
|
+
* @param hopLength - Hop length (default: 512)
|
|
2979
|
+
* @param threshold - YIN threshold; lower is stricter (default: 0.1)
|
|
2980
|
+
* @returns Melody contour with per-frame pitch points and summary stats
|
|
2981
|
+
*/
|
|
2982
|
+
export function analyzeMelody(
|
|
2983
|
+
samples: Float32Array,
|
|
2984
|
+
sampleRate = 22050,
|
|
2985
|
+
fmin = 65.0,
|
|
2986
|
+
fmax = 2093.0,
|
|
2987
|
+
frameLength = 2048,
|
|
2988
|
+
hopLength = 512,
|
|
2989
|
+
threshold = 0.1,
|
|
2990
|
+
): MelodyResult {
|
|
2991
|
+
if (!module) {
|
|
2992
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
2993
|
+
}
|
|
2994
|
+
return module.analyzeMelody(samples, sampleRate, fmin, fmax, frameLength, hopLength, threshold);
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* Compute the onset strength envelope.
|
|
2999
|
+
*
|
|
3000
|
+
* @param samples - Audio samples (mono, float32)
|
|
3001
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3002
|
+
* @param nFft - FFT size (default: 2048)
|
|
3003
|
+
* @param hopLength - Hop length (default: 512)
|
|
3004
|
+
* @param nMels - Number of Mel bands (default: 128)
|
|
3005
|
+
* @returns Onset envelope for each frame
|
|
3006
|
+
*/
|
|
3007
|
+
export function onsetEnvelope(
|
|
3008
|
+
samples: Float32Array,
|
|
3009
|
+
sampleRate = 22050,
|
|
3010
|
+
nFft = 2048,
|
|
3011
|
+
hopLength = 512,
|
|
3012
|
+
nMels = 128,
|
|
3013
|
+
): Float32Array {
|
|
3014
|
+
if (!module) {
|
|
3015
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3016
|
+
}
|
|
3017
|
+
return module.onsetEnvelope(samples, sampleRate, nFft, hopLength, nMels);
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/**
|
|
3021
|
+
* Compute the Fourier tempogram from an onset envelope.
|
|
3022
|
+
*
|
|
3023
|
+
* @param onsetEnvelope - Onset strength envelope (float32)
|
|
3024
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3025
|
+
* @param hopLength - Hop length (default: 512)
|
|
3026
|
+
* @param winLength - Window length in frames (default: 384)
|
|
3027
|
+
* @returns Fourier tempogram result
|
|
3028
|
+
*/
|
|
3029
|
+
export function fourierTempogram(
|
|
3030
|
+
onsetEnvelope: Float32Array,
|
|
3031
|
+
sampleRate = 22050,
|
|
3032
|
+
hopLength = 512,
|
|
3033
|
+
winLength = 384,
|
|
3034
|
+
): WasmFourierTempogramResult {
|
|
3035
|
+
if (!module) {
|
|
3036
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3037
|
+
}
|
|
3038
|
+
return module.fourierTempogram(onsetEnvelope, sampleRate, hopLength, winLength);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
/**
|
|
3042
|
+
* Compute tempogram ratio features.
|
|
3043
|
+
*
|
|
3044
|
+
* @param tempogramData - Tempogram data (float32)
|
|
3045
|
+
* @param winLength - Window length in frames (default: 384)
|
|
3046
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3047
|
+
* @param hopLength - Hop length (default: 512)
|
|
3048
|
+
* @returns Tempogram ratio features
|
|
3049
|
+
*/
|
|
3050
|
+
export function tempogramRatio(
|
|
3051
|
+
tempogramData: Float32Array,
|
|
3052
|
+
winLength = 384,
|
|
3053
|
+
sampleRate = 22050,
|
|
3054
|
+
hopLength = 512,
|
|
3055
|
+
): Float32Array {
|
|
3056
|
+
if (!module) {
|
|
3057
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3058
|
+
}
|
|
3059
|
+
return module.tempogramRatio(tempogramData, winLength, sampleRate, hopLength);
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Measure loudness (EBU R128 / ITU-R BS.1770).
|
|
3064
|
+
*
|
|
3065
|
+
* @param samples - Audio samples (mono, float32)
|
|
3066
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3067
|
+
* @returns Loudness measurement result
|
|
3068
|
+
*/
|
|
3069
|
+
export function lufs(samples: Float32Array, sampleRate = 22050): LufsResult {
|
|
3070
|
+
if (!module) {
|
|
3071
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3072
|
+
}
|
|
3073
|
+
return module.lufs(samples, sampleRate);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
/**
|
|
3077
|
+
* Compute the momentary loudness (LUFS) over time.
|
|
3078
|
+
*
|
|
3079
|
+
* @param samples - Audio samples (mono, float32)
|
|
3080
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3081
|
+
* @returns Momentary LUFS values over time
|
|
3082
|
+
*/
|
|
3083
|
+
export function momentaryLufs(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
3084
|
+
if (!module) {
|
|
3085
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3086
|
+
}
|
|
3087
|
+
return module.momentaryLufs(samples, sampleRate);
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
/**
|
|
3091
|
+
* Compute the short-term loudness (LUFS) over time.
|
|
3092
|
+
*
|
|
3093
|
+
* @param samples - Audio samples (mono, float32)
|
|
3094
|
+
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
3095
|
+
* @returns Short-term LUFS values over time
|
|
3096
|
+
*/
|
|
3097
|
+
export function shortTermLufs(samples: Float32Array, sampleRate = 22050): Float32Array {
|
|
3098
|
+
if (!module) {
|
|
3099
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
3100
|
+
}
|
|
3101
|
+
return module.shortTermLufs(samples, sampleRate);
|
|
3102
|
+
}
|
|
3103
|
+
|
|
1395
3104
|
// ============================================================================
|
|
1396
3105
|
// Core - Resample
|
|
1397
3106
|
// ============================================================================
|
|
@@ -1471,8 +3180,12 @@ export class Audio {
|
|
|
1471
3180
|
return detectBpm(this._samples, this._sampleRate);
|
|
1472
3181
|
}
|
|
1473
3182
|
|
|
1474
|
-
detectKey(): Key {
|
|
1475
|
-
return detectKey(this._samples, this._sampleRate);
|
|
3183
|
+
detectKey(options: KeyDetectionOptions = {}): Key {
|
|
3184
|
+
return detectKey(this._samples, this._sampleRate, options);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
detectKeyCandidates(options: KeyDetectionOptions = {}): KeyCandidate[] {
|
|
3188
|
+
return detectKeyCandidates(this._samples, this._sampleRate, options);
|
|
1476
3189
|
}
|
|
1477
3190
|
|
|
1478
3191
|
detectOnsets(): Float32Array {
|
|
@@ -1483,6 +3196,14 @@ export class Audio {
|
|
|
1483
3196
|
return detectBeats(this._samples, this._sampleRate);
|
|
1484
3197
|
}
|
|
1485
3198
|
|
|
3199
|
+
detectDownbeats(): Float32Array {
|
|
3200
|
+
return detectDownbeats(this._samples, this._sampleRate);
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
detectChords(options: ChordDetectionOptions = {}): ChordAnalysisResult {
|
|
3204
|
+
return detectChords(this._samples, this._sampleRate, options);
|
|
3205
|
+
}
|
|
3206
|
+
|
|
1486
3207
|
analyze(): AnalysisResult {
|
|
1487
3208
|
return analyze(this._samples, this._sampleRate);
|
|
1488
3209
|
}
|
|
@@ -1513,6 +3234,18 @@ export class Audio {
|
|
|
1513
3234
|
return pitchShift(this._samples, this._sampleRate, semitones);
|
|
1514
3235
|
}
|
|
1515
3236
|
|
|
3237
|
+
pitchCorrectToMidi(currentMidi: number, targetMidi: number): Float32Array {
|
|
3238
|
+
return pitchCorrectToMidi(this._samples, this._sampleRate, currentMidi, targetMidi);
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
noteStretch(onsetSample: number, offsetSample: number, stretchRatio: number): Float32Array {
|
|
3242
|
+
return noteStretch(this._samples, this._sampleRate, onsetSample, offsetSample, stretchRatio);
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
voiceChange(pitchSemitones: number, formantFactor: number): Float32Array {
|
|
3246
|
+
return voiceChange(this._samples, this._sampleRate, pitchSemitones, formantFactor);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
1516
3249
|
normalize(targetDb = 0.0): Float32Array {
|
|
1517
3250
|
return normalize(this._samples, this._sampleRate, targetDb);
|
|
1518
3251
|
}
|
|
@@ -1526,13 +3259,16 @@ export class Audio {
|
|
|
1526
3259
|
}
|
|
1527
3260
|
|
|
1528
3261
|
masterAudio(
|
|
1529
|
-
presetName:
|
|
3262
|
+
presetName: MasteringPreset,
|
|
1530
3263
|
overrides: Record<string, number | boolean> | null = null,
|
|
1531
3264
|
): MasteringChainResult {
|
|
1532
3265
|
return masterAudio(this._samples, this._sampleRate, presetName, overrides);
|
|
1533
3266
|
}
|
|
1534
3267
|
|
|
1535
|
-
masteringProcess(
|
|
3268
|
+
masteringProcess(
|
|
3269
|
+
processorName: SoloProcessor,
|
|
3270
|
+
params: MasteringProcessorParams = {},
|
|
3271
|
+
): MasteringResult {
|
|
1536
3272
|
return masteringProcess(processorName, this._samples, this._sampleRate, params);
|
|
1537
3273
|
}
|
|
1538
3274
|
|
|
@@ -1562,6 +3298,26 @@ export class Audio {
|
|
|
1562
3298
|
return chroma(this._samples, this._sampleRate, nFft, hopLength);
|
|
1563
3299
|
}
|
|
1564
3300
|
|
|
3301
|
+
nnlsChroma(): WasmNnlsChromaResult {
|
|
3302
|
+
return nnlsChroma(this._samples, this._sampleRate);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
onsetEnvelope(nFft = 2048, hopLength = 512, nMels = 128): Float32Array {
|
|
3306
|
+
return onsetEnvelope(this._samples, this._sampleRate, nFft, hopLength, nMels);
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
lufs(): LufsResult {
|
|
3310
|
+
return lufs(this._samples, this._sampleRate);
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
momentaryLufs(): Float32Array {
|
|
3314
|
+
return momentaryLufs(this._samples, this._sampleRate);
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
shortTermLufs(): Float32Array {
|
|
3318
|
+
return shortTermLufs(this._samples, this._sampleRate);
|
|
3319
|
+
}
|
|
3320
|
+
|
|
1565
3321
|
spectralCentroid(nFft = 2048, hopLength = 512): Float32Array {
|
|
1566
3322
|
return spectralCentroid(this._samples, this._sampleRate, nFft, hopLength);
|
|
1567
3323
|
}
|
|
@@ -1656,16 +3412,83 @@ export class StreamAnalyzer {
|
|
|
1656
3412
|
if (!module) {
|
|
1657
3413
|
throw new Error('Module not initialized. Call init() first.');
|
|
1658
3414
|
}
|
|
1659
|
-
|
|
3415
|
+
const wasmModule = module;
|
|
3416
|
+
const args = [
|
|
1660
3417
|
config.sampleRate,
|
|
1661
3418
|
config.nFft ?? 2048,
|
|
1662
3419
|
config.hopLength ?? 512,
|
|
1663
3420
|
config.nMels ?? 128,
|
|
3421
|
+
config.fmin ?? 0,
|
|
3422
|
+
config.fmax ?? 0,
|
|
3423
|
+
config.tuningRefHz ?? 440,
|
|
3424
|
+
config.computeMagnitude ?? true,
|
|
1664
3425
|
config.computeMel ?? true,
|
|
1665
3426
|
config.computeChroma ?? true,
|
|
1666
3427
|
config.computeOnset ?? true,
|
|
3428
|
+
config.computeSpectral ?? true,
|
|
1667
3429
|
config.emitEveryNFrames ?? 1,
|
|
1668
|
-
|
|
3430
|
+
config.magnitudeDownsample ?? 1,
|
|
3431
|
+
config.keyUpdateIntervalSec ?? 5,
|
|
3432
|
+
config.bpmUpdateIntervalSec ?? 10,
|
|
3433
|
+
config.window ?? 0,
|
|
3434
|
+
config.outputFormat ?? 0,
|
|
3435
|
+
] as const;
|
|
3436
|
+
const isArityError = (error: unknown): boolean => {
|
|
3437
|
+
const message = String((error as { message?: unknown } | null)?.message ?? error);
|
|
3438
|
+
return message.includes('invalid number of parameters');
|
|
3439
|
+
};
|
|
3440
|
+
const createLegacy = (): WasmStreamAnalyzer => {
|
|
3441
|
+
const LegacyStreamAnalyzer = wasmModule.StreamAnalyzer as unknown as new (
|
|
3442
|
+
sampleRate: number,
|
|
3443
|
+
nFft: number,
|
|
3444
|
+
hopLength: number,
|
|
3445
|
+
nMels: number,
|
|
3446
|
+
computeMel: boolean,
|
|
3447
|
+
computeChroma: boolean,
|
|
3448
|
+
computeOnset: boolean,
|
|
3449
|
+
emitEveryNFrames: number,
|
|
3450
|
+
) => WasmStreamAnalyzer;
|
|
3451
|
+
return new LegacyStreamAnalyzer(
|
|
3452
|
+
args[0],
|
|
3453
|
+
args[1],
|
|
3454
|
+
args[2],
|
|
3455
|
+
args[3],
|
|
3456
|
+
args[8],
|
|
3457
|
+
args[9],
|
|
3458
|
+
args[10],
|
|
3459
|
+
args[12],
|
|
3460
|
+
);
|
|
3461
|
+
};
|
|
3462
|
+
const hasExtendedConfig =
|
|
3463
|
+
config.fmin !== undefined ||
|
|
3464
|
+
config.fmax !== undefined ||
|
|
3465
|
+
config.tuningRefHz !== undefined ||
|
|
3466
|
+
config.computeMagnitude !== undefined ||
|
|
3467
|
+
config.computeSpectral !== undefined ||
|
|
3468
|
+
config.magnitudeDownsample !== undefined ||
|
|
3469
|
+
config.keyUpdateIntervalSec !== undefined ||
|
|
3470
|
+
config.bpmUpdateIntervalSec !== undefined ||
|
|
3471
|
+
config.window !== undefined ||
|
|
3472
|
+
config.outputFormat !== undefined;
|
|
3473
|
+
if (hasExtendedConfig) {
|
|
3474
|
+
try {
|
|
3475
|
+
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3476
|
+
} catch (error) {
|
|
3477
|
+
if (!isArityError(error)) {
|
|
3478
|
+
throw error;
|
|
3479
|
+
}
|
|
3480
|
+
this.analyzer = createLegacy();
|
|
3481
|
+
}
|
|
3482
|
+
} else {
|
|
3483
|
+
try {
|
|
3484
|
+
this.analyzer = createLegacy();
|
|
3485
|
+
} catch (error) {
|
|
3486
|
+
if (!isArityError(error)) {
|
|
3487
|
+
throw error;
|
|
3488
|
+
}
|
|
3489
|
+
this.analyzer = new wasmModule.StreamAnalyzer(...args);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
1669
3492
|
}
|
|
1670
3493
|
|
|
1671
3494
|
/**
|
|
@@ -1704,6 +3527,14 @@ export class StreamAnalyzer {
|
|
|
1704
3527
|
return this.analyzer.readFramesSoa(maxFrames);
|
|
1705
3528
|
}
|
|
1706
3529
|
|
|
3530
|
+
readFramesU8(maxFrames: number): StreamFramesU8 {
|
|
3531
|
+
return this.analyzer.readFramesU8(maxFrames) as StreamFramesU8;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
readFramesI16(maxFrames: number): StreamFramesI16 {
|
|
3535
|
+
return this.analyzer.readFramesI16(maxFrames) as StreamFramesI16;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
1707
3538
|
/**
|
|
1708
3539
|
* Reset the analyzer state.
|
|
1709
3540
|
*
|
|
@@ -1734,6 +3565,7 @@ export class StreamAnalyzer {
|
|
|
1734
3565
|
chordRoot: s.estimate.chordRoot as PitchClass,
|
|
1735
3566
|
chordQuality: s.estimate.chordQuality as ChordQuality,
|
|
1736
3567
|
chordConfidence: s.estimate.chordConfidence,
|
|
3568
|
+
chordStartTime: s.estimate.chordStartTime,
|
|
1737
3569
|
chordProgression: s.estimate.chordProgression.map((c) => ({
|
|
1738
3570
|
root: c.root as PitchClass,
|
|
1739
3571
|
quality: c.quality as ChordQuality,
|