@libraz/libsonare 1.0.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/LICENSE +190 -0
- package/README.md +65 -0
- package/README.npm.md +65 -0
- package/dist/index.d.ts +831 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1002 -0
- package/dist/index.js.map +1 -0
- package/dist/sonare.js +4056 -0
- package/dist/sonare.wasm +0 -0
- package/package.json +82 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sonare - Audio Analysis Library
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { init, detectBpm, detectKey, analyze } from '@libraz/sonare';
|
|
7
|
+
*
|
|
8
|
+
* await init();
|
|
9
|
+
*
|
|
10
|
+
* // Detect BPM from audio samples
|
|
11
|
+
* const bpm = detectBpm(samples, sampleRate);
|
|
12
|
+
*
|
|
13
|
+
* // Detect musical key
|
|
14
|
+
* const key = detectKey(samples, sampleRate);
|
|
15
|
+
*
|
|
16
|
+
* // Full analysis
|
|
17
|
+
* const result = analyze(samples, sampleRate);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Public Types
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Pitch class enum (C=0, C#=1, ..., B=11)
|
|
25
|
+
*/
|
|
26
|
+
export const PitchClass = {
|
|
27
|
+
C: 0,
|
|
28
|
+
Cs: 1,
|
|
29
|
+
D: 2,
|
|
30
|
+
Ds: 3,
|
|
31
|
+
E: 4,
|
|
32
|
+
F: 5,
|
|
33
|
+
Fs: 6,
|
|
34
|
+
G: 7,
|
|
35
|
+
Gs: 8,
|
|
36
|
+
A: 9,
|
|
37
|
+
As: 10,
|
|
38
|
+
B: 11,
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Musical mode
|
|
42
|
+
*/
|
|
43
|
+
export const Mode = {
|
|
44
|
+
Major: 0,
|
|
45
|
+
Minor: 1,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Chord quality
|
|
49
|
+
*/
|
|
50
|
+
export const ChordQuality = {
|
|
51
|
+
Major: 0,
|
|
52
|
+
Minor: 1,
|
|
53
|
+
Diminished: 2,
|
|
54
|
+
Augmented: 3,
|
|
55
|
+
Dominant7: 4,
|
|
56
|
+
Major7: 5,
|
|
57
|
+
Minor7: 6,
|
|
58
|
+
Sus2: 7,
|
|
59
|
+
Sus4: 8,
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Section type
|
|
63
|
+
*/
|
|
64
|
+
export const SectionType = {
|
|
65
|
+
Intro: 0,
|
|
66
|
+
Verse: 1,
|
|
67
|
+
PreChorus: 2,
|
|
68
|
+
Chorus: 3,
|
|
69
|
+
Bridge: 4,
|
|
70
|
+
Instrumental: 5,
|
|
71
|
+
Outro: 6,
|
|
72
|
+
};
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Module State
|
|
75
|
+
// ============================================================================
|
|
76
|
+
let module = null;
|
|
77
|
+
let initPromise = null;
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Initialization
|
|
80
|
+
// ============================================================================
|
|
81
|
+
/**
|
|
82
|
+
* Initialize the WASM module.
|
|
83
|
+
* Must be called before using any analysis functions.
|
|
84
|
+
*
|
|
85
|
+
* @param options - Optional module configuration
|
|
86
|
+
* @returns Promise that resolves when initialization is complete
|
|
87
|
+
*/
|
|
88
|
+
export async function init(options) {
|
|
89
|
+
if (module) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (initPromise) {
|
|
93
|
+
return initPromise;
|
|
94
|
+
}
|
|
95
|
+
initPromise = (async () => {
|
|
96
|
+
const createModule = (await import('./sonare.js')).default;
|
|
97
|
+
module = await createModule(options);
|
|
98
|
+
})();
|
|
99
|
+
return initPromise;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if the module is initialized.
|
|
103
|
+
*/
|
|
104
|
+
export function isInitialized() {
|
|
105
|
+
return module !== null;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the library version.
|
|
109
|
+
*/
|
|
110
|
+
export function version() {
|
|
111
|
+
if (!module) {
|
|
112
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
113
|
+
}
|
|
114
|
+
return module.version();
|
|
115
|
+
}
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Quick API (High-level Analysis)
|
|
118
|
+
// ============================================================================
|
|
119
|
+
/**
|
|
120
|
+
* Detect BPM from audio samples.
|
|
121
|
+
*
|
|
122
|
+
* @param samples - Audio samples (mono, float32)
|
|
123
|
+
* @param sampleRate - Sample rate in Hz
|
|
124
|
+
* @returns Detected BPM
|
|
125
|
+
*/
|
|
126
|
+
export function detectBpm(samples, sampleRate) {
|
|
127
|
+
if (!module) {
|
|
128
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
129
|
+
}
|
|
130
|
+
return module.detectBpm(samples, sampleRate);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Detect musical key from audio samples.
|
|
134
|
+
*
|
|
135
|
+
* @param samples - Audio samples (mono, float32)
|
|
136
|
+
* @param sampleRate - Sample rate in Hz
|
|
137
|
+
* @returns Detected key
|
|
138
|
+
*/
|
|
139
|
+
export function detectKey(samples, sampleRate) {
|
|
140
|
+
if (!module) {
|
|
141
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
142
|
+
}
|
|
143
|
+
const result = module.detectKey(samples, sampleRate);
|
|
144
|
+
return {
|
|
145
|
+
root: result.root,
|
|
146
|
+
mode: result.mode,
|
|
147
|
+
confidence: result.confidence,
|
|
148
|
+
name: result.name,
|
|
149
|
+
shortName: result.shortName,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Detect onset times from audio samples.
|
|
154
|
+
*
|
|
155
|
+
* @param samples - Audio samples (mono, float32)
|
|
156
|
+
* @param sampleRate - Sample rate in Hz
|
|
157
|
+
* @returns Array of onset times in seconds
|
|
158
|
+
*/
|
|
159
|
+
export function detectOnsets(samples, sampleRate) {
|
|
160
|
+
if (!module) {
|
|
161
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
162
|
+
}
|
|
163
|
+
return module.detectOnsets(samples, sampleRate);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Detect beat times from audio samples.
|
|
167
|
+
*
|
|
168
|
+
* @param samples - Audio samples (mono, float32)
|
|
169
|
+
* @param sampleRate - Sample rate in Hz
|
|
170
|
+
* @returns Array of beat times in seconds
|
|
171
|
+
*/
|
|
172
|
+
export function detectBeats(samples, sampleRate) {
|
|
173
|
+
if (!module) {
|
|
174
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
175
|
+
}
|
|
176
|
+
return module.detectBeats(samples, sampleRate);
|
|
177
|
+
}
|
|
178
|
+
// Helper to convert WASM result to typed result
|
|
179
|
+
function convertAnalysisResult(wasm) {
|
|
180
|
+
return {
|
|
181
|
+
bpm: wasm.bpm,
|
|
182
|
+
bpmConfidence: wasm.bpmConfidence,
|
|
183
|
+
key: {
|
|
184
|
+
root: wasm.key.root,
|
|
185
|
+
mode: wasm.key.mode,
|
|
186
|
+
confidence: wasm.key.confidence,
|
|
187
|
+
name: wasm.key.name,
|
|
188
|
+
shortName: wasm.key.shortName,
|
|
189
|
+
},
|
|
190
|
+
timeSignature: wasm.timeSignature,
|
|
191
|
+
beats: wasm.beats,
|
|
192
|
+
chords: wasm.chords.map((c) => ({
|
|
193
|
+
root: c.root,
|
|
194
|
+
quality: c.quality,
|
|
195
|
+
start: c.start,
|
|
196
|
+
end: c.end,
|
|
197
|
+
confidence: c.confidence,
|
|
198
|
+
name: c.name,
|
|
199
|
+
})),
|
|
200
|
+
sections: wasm.sections.map((s) => ({
|
|
201
|
+
type: s.type,
|
|
202
|
+
start: s.start,
|
|
203
|
+
end: s.end,
|
|
204
|
+
energyLevel: s.energyLevel,
|
|
205
|
+
confidence: s.confidence,
|
|
206
|
+
name: s.name,
|
|
207
|
+
})),
|
|
208
|
+
timbre: wasm.timbre,
|
|
209
|
+
dynamics: wasm.dynamics,
|
|
210
|
+
rhythm: wasm.rhythm,
|
|
211
|
+
form: wasm.form,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Perform complete music analysis.
|
|
216
|
+
*
|
|
217
|
+
* @param samples - Audio samples (mono, float32)
|
|
218
|
+
* @param sampleRate - Sample rate in Hz
|
|
219
|
+
* @returns Complete analysis result
|
|
220
|
+
*/
|
|
221
|
+
export function analyze(samples, sampleRate) {
|
|
222
|
+
if (!module) {
|
|
223
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
224
|
+
}
|
|
225
|
+
const result = module.analyze(samples, sampleRate);
|
|
226
|
+
return convertAnalysisResult(result);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Perform complete music analysis with progress reporting.
|
|
230
|
+
*
|
|
231
|
+
* @param samples - Audio samples (mono, float32)
|
|
232
|
+
* @param sampleRate - Sample rate in Hz
|
|
233
|
+
* @param onProgress - Progress callback (progress: 0-1, stage: string)
|
|
234
|
+
* @returns Complete analysis result
|
|
235
|
+
*/
|
|
236
|
+
export function analyzeWithProgress(samples, sampleRate, onProgress) {
|
|
237
|
+
if (!module) {
|
|
238
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
239
|
+
}
|
|
240
|
+
const result = module.analyzeWithProgress(samples, sampleRate, onProgress);
|
|
241
|
+
return convertAnalysisResult(result);
|
|
242
|
+
}
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// Effects
|
|
245
|
+
// ============================================================================
|
|
246
|
+
/**
|
|
247
|
+
* Perform Harmonic-Percussive Source Separation (HPSS).
|
|
248
|
+
*
|
|
249
|
+
* @param samples - Audio samples (mono, float32)
|
|
250
|
+
* @param sampleRate - Sample rate in Hz
|
|
251
|
+
* @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
|
|
252
|
+
* @param kernelPercussive - Vertical median filter size for percussive (default: 31)
|
|
253
|
+
* @returns Separated harmonic and percussive components
|
|
254
|
+
*/
|
|
255
|
+
export function hpss(samples, sampleRate, kernelHarmonic = 31, kernelPercussive = 31) {
|
|
256
|
+
if (!module) {
|
|
257
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
258
|
+
}
|
|
259
|
+
return module.hpss(samples, sampleRate, kernelHarmonic, kernelPercussive);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Extract harmonic component from audio.
|
|
263
|
+
*
|
|
264
|
+
* @param samples - Audio samples (mono, float32)
|
|
265
|
+
* @param sampleRate - Sample rate in Hz
|
|
266
|
+
* @returns Harmonic component
|
|
267
|
+
*/
|
|
268
|
+
export function harmonic(samples, sampleRate) {
|
|
269
|
+
if (!module) {
|
|
270
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
271
|
+
}
|
|
272
|
+
return module.harmonic(samples, sampleRate);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Extract percussive component from audio.
|
|
276
|
+
*
|
|
277
|
+
* @param samples - Audio samples (mono, float32)
|
|
278
|
+
* @param sampleRate - Sample rate in Hz
|
|
279
|
+
* @returns Percussive component
|
|
280
|
+
*/
|
|
281
|
+
export function percussive(samples, sampleRate) {
|
|
282
|
+
if (!module) {
|
|
283
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
284
|
+
}
|
|
285
|
+
return module.percussive(samples, sampleRate);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Time-stretch audio without changing pitch.
|
|
289
|
+
*
|
|
290
|
+
* @param samples - Audio samples (mono, float32)
|
|
291
|
+
* @param sampleRate - Sample rate in Hz
|
|
292
|
+
* @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
|
|
293
|
+
* @returns Time-stretched audio
|
|
294
|
+
*/
|
|
295
|
+
export function timeStretch(samples, sampleRate, rate) {
|
|
296
|
+
if (!module) {
|
|
297
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
298
|
+
}
|
|
299
|
+
return module.timeStretch(samples, sampleRate, rate);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Pitch-shift audio without changing duration.
|
|
303
|
+
*
|
|
304
|
+
* @param samples - Audio samples (mono, float32)
|
|
305
|
+
* @param sampleRate - Sample rate in Hz
|
|
306
|
+
* @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
|
|
307
|
+
* @returns Pitch-shifted audio
|
|
308
|
+
*/
|
|
309
|
+
export function pitchShift(samples, sampleRate, semitones) {
|
|
310
|
+
if (!module) {
|
|
311
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
312
|
+
}
|
|
313
|
+
return module.pitchShift(samples, sampleRate, semitones);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Normalize audio to target peak level.
|
|
317
|
+
*
|
|
318
|
+
* @param samples - Audio samples (mono, float32)
|
|
319
|
+
* @param sampleRate - Sample rate in Hz
|
|
320
|
+
* @param targetDb - Target peak level in dB (default: 0 dB = full scale)
|
|
321
|
+
* @returns Normalized audio
|
|
322
|
+
*/
|
|
323
|
+
export function normalize(samples, sampleRate, targetDb = 0.0) {
|
|
324
|
+
if (!module) {
|
|
325
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
326
|
+
}
|
|
327
|
+
return module.normalize(samples, sampleRate, targetDb);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Trim silence from beginning and end of audio.
|
|
331
|
+
*
|
|
332
|
+
* @param samples - Audio samples (mono, float32)
|
|
333
|
+
* @param sampleRate - Sample rate in Hz
|
|
334
|
+
* @param thresholdDb - Silence threshold in dB (default: -60 dB)
|
|
335
|
+
* @returns Trimmed audio
|
|
336
|
+
*/
|
|
337
|
+
export function trim(samples, sampleRate, thresholdDb = -60.0) {
|
|
338
|
+
if (!module) {
|
|
339
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
340
|
+
}
|
|
341
|
+
return module.trim(samples, sampleRate, thresholdDb);
|
|
342
|
+
}
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Features - Spectrogram
|
|
345
|
+
// ============================================================================
|
|
346
|
+
/**
|
|
347
|
+
* Compute Short-Time Fourier Transform (STFT).
|
|
348
|
+
*
|
|
349
|
+
* @param samples - Audio samples (mono, float32)
|
|
350
|
+
* @param sampleRate - Sample rate in Hz
|
|
351
|
+
* @param nFft - FFT size (default: 2048)
|
|
352
|
+
* @param hopLength - Hop length (default: 512)
|
|
353
|
+
* @returns STFT result with magnitude and power spectrograms
|
|
354
|
+
*/
|
|
355
|
+
export function stft(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
356
|
+
if (!module) {
|
|
357
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
358
|
+
}
|
|
359
|
+
return module.stft(samples, sampleRate, nFft, hopLength);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Compute STFT and return magnitude in decibels.
|
|
363
|
+
*
|
|
364
|
+
* @param samples - Audio samples (mono, float32)
|
|
365
|
+
* @param sampleRate - Sample rate in Hz
|
|
366
|
+
* @param nFft - FFT size (default: 2048)
|
|
367
|
+
* @param hopLength - Hop length (default: 512)
|
|
368
|
+
* @returns STFT result with dB values
|
|
369
|
+
*/
|
|
370
|
+
export function stftDb(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
371
|
+
if (!module) {
|
|
372
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
373
|
+
}
|
|
374
|
+
return module.stftDb(samples, sampleRate, nFft, hopLength);
|
|
375
|
+
}
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// Features - Mel Spectrogram
|
|
378
|
+
// ============================================================================
|
|
379
|
+
/**
|
|
380
|
+
* Compute Mel spectrogram.
|
|
381
|
+
*
|
|
382
|
+
* @param samples - Audio samples (mono, float32)
|
|
383
|
+
* @param sampleRate - Sample rate in Hz
|
|
384
|
+
* @param nFft - FFT size (default: 2048)
|
|
385
|
+
* @param hopLength - Hop length (default: 512)
|
|
386
|
+
* @param nMels - Number of Mel bands (default: 128)
|
|
387
|
+
* @returns Mel spectrogram result
|
|
388
|
+
*/
|
|
389
|
+
export function melSpectrogram(samples, sampleRate, nFft = 2048, hopLength = 512, nMels = 128) {
|
|
390
|
+
if (!module) {
|
|
391
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
392
|
+
}
|
|
393
|
+
return module.melSpectrogram(samples, sampleRate, nFft, hopLength, nMels);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Compute MFCC (Mel-Frequency Cepstral Coefficients).
|
|
397
|
+
*
|
|
398
|
+
* @param samples - Audio samples (mono, float32)
|
|
399
|
+
* @param sampleRate - Sample rate in Hz
|
|
400
|
+
* @param nFft - FFT size (default: 2048)
|
|
401
|
+
* @param hopLength - Hop length (default: 512)
|
|
402
|
+
* @param nMels - Number of Mel bands (default: 128)
|
|
403
|
+
* @param nMfcc - Number of MFCC coefficients (default: 13)
|
|
404
|
+
* @returns MFCC result
|
|
405
|
+
*/
|
|
406
|
+
export function mfcc(samples, sampleRate, nFft = 2048, hopLength = 512, nMels = 128, nMfcc = 13) {
|
|
407
|
+
if (!module) {
|
|
408
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
409
|
+
}
|
|
410
|
+
return module.mfcc(samples, sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
411
|
+
}
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Features - Chroma
|
|
414
|
+
// ============================================================================
|
|
415
|
+
/**
|
|
416
|
+
* Compute chromagram (pitch class distribution).
|
|
417
|
+
*
|
|
418
|
+
* @param samples - Audio samples (mono, float32)
|
|
419
|
+
* @param sampleRate - Sample rate in Hz
|
|
420
|
+
* @param nFft - FFT size (default: 2048)
|
|
421
|
+
* @param hopLength - Hop length (default: 512)
|
|
422
|
+
* @returns Chroma features result
|
|
423
|
+
*/
|
|
424
|
+
export function chroma(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
425
|
+
if (!module) {
|
|
426
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
427
|
+
}
|
|
428
|
+
return module.chroma(samples, sampleRate, nFft, hopLength);
|
|
429
|
+
}
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// Features - Spectral
|
|
432
|
+
// ============================================================================
|
|
433
|
+
/**
|
|
434
|
+
* Compute spectral centroid (center of mass of spectrum).
|
|
435
|
+
*
|
|
436
|
+
* @param samples - Audio samples (mono, float32)
|
|
437
|
+
* @param sampleRate - Sample rate in Hz
|
|
438
|
+
* @param nFft - FFT size (default: 2048)
|
|
439
|
+
* @param hopLength - Hop length (default: 512)
|
|
440
|
+
* @returns Spectral centroid in Hz for each frame
|
|
441
|
+
*/
|
|
442
|
+
export function spectralCentroid(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
443
|
+
if (!module) {
|
|
444
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
445
|
+
}
|
|
446
|
+
return module.spectralCentroid(samples, sampleRate, nFft, hopLength);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Compute spectral bandwidth.
|
|
450
|
+
*
|
|
451
|
+
* @param samples - Audio samples (mono, float32)
|
|
452
|
+
* @param sampleRate - Sample rate in Hz
|
|
453
|
+
* @param nFft - FFT size (default: 2048)
|
|
454
|
+
* @param hopLength - Hop length (default: 512)
|
|
455
|
+
* @returns Spectral bandwidth in Hz for each frame
|
|
456
|
+
*/
|
|
457
|
+
export function spectralBandwidth(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
458
|
+
if (!module) {
|
|
459
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
460
|
+
}
|
|
461
|
+
return module.spectralBandwidth(samples, sampleRate, nFft, hopLength);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Compute spectral rolloff frequency.
|
|
465
|
+
*
|
|
466
|
+
* @param samples - Audio samples (mono, float32)
|
|
467
|
+
* @param sampleRate - Sample rate in Hz
|
|
468
|
+
* @param nFft - FFT size (default: 2048)
|
|
469
|
+
* @param hopLength - Hop length (default: 512)
|
|
470
|
+
* @param rollPercent - Percentage threshold (default: 0.85)
|
|
471
|
+
* @returns Rolloff frequency in Hz for each frame
|
|
472
|
+
*/
|
|
473
|
+
export function spectralRolloff(samples, sampleRate, nFft = 2048, hopLength = 512, rollPercent = 0.85) {
|
|
474
|
+
if (!module) {
|
|
475
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
476
|
+
}
|
|
477
|
+
return module.spectralRolloff(samples, sampleRate, nFft, hopLength, rollPercent);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Compute spectral flatness.
|
|
481
|
+
*
|
|
482
|
+
* @param samples - Audio samples (mono, float32)
|
|
483
|
+
* @param sampleRate - Sample rate in Hz
|
|
484
|
+
* @param nFft - FFT size (default: 2048)
|
|
485
|
+
* @param hopLength - Hop length (default: 512)
|
|
486
|
+
* @returns Spectral flatness for each frame (0 = tonal, 1 = noise-like)
|
|
487
|
+
*/
|
|
488
|
+
export function spectralFlatness(samples, sampleRate, nFft = 2048, hopLength = 512) {
|
|
489
|
+
if (!module) {
|
|
490
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
491
|
+
}
|
|
492
|
+
return module.spectralFlatness(samples, sampleRate, nFft, hopLength);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Compute zero crossing rate.
|
|
496
|
+
*
|
|
497
|
+
* @param samples - Audio samples (mono, float32)
|
|
498
|
+
* @param sampleRate - Sample rate in Hz
|
|
499
|
+
* @param frameLength - Frame length (default: 2048)
|
|
500
|
+
* @param hopLength - Hop length (default: 512)
|
|
501
|
+
* @returns Zero crossing rate for each frame
|
|
502
|
+
*/
|
|
503
|
+
export function zeroCrossingRate(samples, sampleRate, frameLength = 2048, hopLength = 512) {
|
|
504
|
+
if (!module) {
|
|
505
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
506
|
+
}
|
|
507
|
+
return module.zeroCrossingRate(samples, sampleRate, frameLength, hopLength);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Compute RMS energy.
|
|
511
|
+
*
|
|
512
|
+
* @param samples - Audio samples (mono, float32)
|
|
513
|
+
* @param sampleRate - Sample rate in Hz
|
|
514
|
+
* @param frameLength - Frame length (default: 2048)
|
|
515
|
+
* @param hopLength - Hop length (default: 512)
|
|
516
|
+
* @returns RMS energy for each frame
|
|
517
|
+
*/
|
|
518
|
+
export function rmsEnergy(samples, sampleRate, frameLength = 2048, hopLength = 512) {
|
|
519
|
+
if (!module) {
|
|
520
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
521
|
+
}
|
|
522
|
+
return module.rmsEnergy(samples, sampleRate, frameLength, hopLength);
|
|
523
|
+
}
|
|
524
|
+
// ============================================================================
|
|
525
|
+
// Features - Pitch
|
|
526
|
+
// ============================================================================
|
|
527
|
+
/**
|
|
528
|
+
* Detect pitch using YIN algorithm.
|
|
529
|
+
*
|
|
530
|
+
* @param samples - Audio samples (mono, float32)
|
|
531
|
+
* @param sampleRate - Sample rate in Hz
|
|
532
|
+
* @param frameLength - Frame length (default: 2048)
|
|
533
|
+
* @param hopLength - Hop length (default: 512)
|
|
534
|
+
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
535
|
+
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
536
|
+
* @param threshold - YIN threshold (default: 0.3)
|
|
537
|
+
* @returns Pitch detection result
|
|
538
|
+
*/
|
|
539
|
+
export function pitchYin(samples, sampleRate, frameLength = 2048, hopLength = 512, fmin = 65.0, fmax = 2093.0, threshold = 0.3) {
|
|
540
|
+
if (!module) {
|
|
541
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
542
|
+
}
|
|
543
|
+
return module.pitchYin(samples, sampleRate, frameLength, hopLength, fmin, fmax, threshold);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Detect pitch using pYIN algorithm (probabilistic YIN with HMM smoothing).
|
|
547
|
+
*
|
|
548
|
+
* @param samples - Audio samples (mono, float32)
|
|
549
|
+
* @param sampleRate - Sample rate in Hz
|
|
550
|
+
* @param frameLength - Frame length (default: 2048)
|
|
551
|
+
* @param hopLength - Hop length (default: 512)
|
|
552
|
+
* @param fmin - Minimum frequency in Hz (default: 65)
|
|
553
|
+
* @param fmax - Maximum frequency in Hz (default: 2093)
|
|
554
|
+
* @param threshold - YIN threshold (default: 0.3)
|
|
555
|
+
* @returns Pitch detection result
|
|
556
|
+
*/
|
|
557
|
+
export function pitchPyin(samples, sampleRate, frameLength = 2048, hopLength = 512, fmin = 65.0, fmax = 2093.0, threshold = 0.3) {
|
|
558
|
+
if (!module) {
|
|
559
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
560
|
+
}
|
|
561
|
+
return module.pitchPyin(samples, sampleRate, frameLength, hopLength, fmin, fmax, threshold);
|
|
562
|
+
}
|
|
563
|
+
// ============================================================================
|
|
564
|
+
// Core - Unit Conversion
|
|
565
|
+
// ============================================================================
|
|
566
|
+
/**
|
|
567
|
+
* Convert frequency in Hz to Mel scale.
|
|
568
|
+
*
|
|
569
|
+
* @param hz - Frequency in Hz
|
|
570
|
+
* @returns Mel frequency
|
|
571
|
+
*/
|
|
572
|
+
export function hzToMel(hz) {
|
|
573
|
+
if (!module) {
|
|
574
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
575
|
+
}
|
|
576
|
+
return module.hzToMel(hz);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Convert Mel scale to frequency in Hz.
|
|
580
|
+
*
|
|
581
|
+
* @param mel - Mel frequency
|
|
582
|
+
* @returns Frequency in Hz
|
|
583
|
+
*/
|
|
584
|
+
export function melToHz(mel) {
|
|
585
|
+
if (!module) {
|
|
586
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
587
|
+
}
|
|
588
|
+
return module.melToHz(mel);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Convert frequency in Hz to MIDI note number.
|
|
592
|
+
*
|
|
593
|
+
* @param hz - Frequency in Hz
|
|
594
|
+
* @returns MIDI note number (A4 = 440 Hz = 69)
|
|
595
|
+
*/
|
|
596
|
+
export function hzToMidi(hz) {
|
|
597
|
+
if (!module) {
|
|
598
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
599
|
+
}
|
|
600
|
+
return module.hzToMidi(hz);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Convert MIDI note number to frequency in Hz.
|
|
604
|
+
*
|
|
605
|
+
* @param midi - MIDI note number
|
|
606
|
+
* @returns Frequency in Hz
|
|
607
|
+
*/
|
|
608
|
+
export function midiToHz(midi) {
|
|
609
|
+
if (!module) {
|
|
610
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
611
|
+
}
|
|
612
|
+
return module.midiToHz(midi);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Convert frequency in Hz to note name.
|
|
616
|
+
*
|
|
617
|
+
* @param hz - Frequency in Hz
|
|
618
|
+
* @returns Note name (e.g., "A4", "C#5")
|
|
619
|
+
*/
|
|
620
|
+
export function hzToNote(hz) {
|
|
621
|
+
if (!module) {
|
|
622
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
623
|
+
}
|
|
624
|
+
return module.hzToNote(hz);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Convert note name to frequency in Hz.
|
|
628
|
+
*
|
|
629
|
+
* @param note - Note name (e.g., "A4", "C#5")
|
|
630
|
+
* @returns Frequency in Hz
|
|
631
|
+
*/
|
|
632
|
+
export function noteToHz(note) {
|
|
633
|
+
if (!module) {
|
|
634
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
635
|
+
}
|
|
636
|
+
return module.noteToHz(note);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Convert frame index to time in seconds.
|
|
640
|
+
*
|
|
641
|
+
* @param frames - Frame index
|
|
642
|
+
* @param sr - Sample rate in Hz
|
|
643
|
+
* @param hopLength - Hop length in samples
|
|
644
|
+
* @returns Time in seconds
|
|
645
|
+
*/
|
|
646
|
+
export function framesToTime(frames, sr, hopLength) {
|
|
647
|
+
if (!module) {
|
|
648
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
649
|
+
}
|
|
650
|
+
return module.framesToTime(frames, sr, hopLength);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Convert time in seconds to frame index.
|
|
654
|
+
*
|
|
655
|
+
* @param time - Time in seconds
|
|
656
|
+
* @param sr - Sample rate in Hz
|
|
657
|
+
* @param hopLength - Hop length in samples
|
|
658
|
+
* @returns Frame index
|
|
659
|
+
*/
|
|
660
|
+
export function timeToFrames(time, sr, hopLength) {
|
|
661
|
+
if (!module) {
|
|
662
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
663
|
+
}
|
|
664
|
+
return module.timeToFrames(time, sr, hopLength);
|
|
665
|
+
}
|
|
666
|
+
// ============================================================================
|
|
667
|
+
// Core - Resample
|
|
668
|
+
// ============================================================================
|
|
669
|
+
/**
|
|
670
|
+
* Resample audio to a different sample rate.
|
|
671
|
+
*
|
|
672
|
+
* @param samples - Audio samples (mono, float32)
|
|
673
|
+
* @param srcSr - Source sample rate in Hz
|
|
674
|
+
* @param targetSr - Target sample rate in Hz
|
|
675
|
+
* @returns Resampled audio
|
|
676
|
+
*/
|
|
677
|
+
export function resample(samples, srcSr, targetSr) {
|
|
678
|
+
if (!module) {
|
|
679
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
680
|
+
}
|
|
681
|
+
return module.resample(samples, srcSr, targetSr);
|
|
682
|
+
}
|
|
683
|
+
// ============================================================================
|
|
684
|
+
// Audio Class
|
|
685
|
+
// ============================================================================
|
|
686
|
+
/**
|
|
687
|
+
* Wrapper around audio data that exposes all analysis and feature functions as instance methods.
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* ```typescript
|
|
691
|
+
* import { init, Audio } from '@libraz/sonare';
|
|
692
|
+
*
|
|
693
|
+
* await init();
|
|
694
|
+
*
|
|
695
|
+
* const audio = Audio.fromBuffer(samples, 44100);
|
|
696
|
+
* console.log('BPM:', audio.detectBpm());
|
|
697
|
+
* console.log('Key:', audio.detectKey().name);
|
|
698
|
+
*
|
|
699
|
+
* const mel = audio.melSpectrogram();
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
export class Audio {
|
|
703
|
+
constructor(samples, sampleRate) {
|
|
704
|
+
this._samples = samples;
|
|
705
|
+
this._sampleRate = sampleRate;
|
|
706
|
+
}
|
|
707
|
+
/** Create an Audio instance from raw sample data. */
|
|
708
|
+
static fromBuffer(samples, sampleRate) {
|
|
709
|
+
return new Audio(samples, sampleRate);
|
|
710
|
+
}
|
|
711
|
+
/** The raw audio samples. */
|
|
712
|
+
get data() {
|
|
713
|
+
return this._samples;
|
|
714
|
+
}
|
|
715
|
+
/** Number of samples. */
|
|
716
|
+
get length() {
|
|
717
|
+
return this._samples.length;
|
|
718
|
+
}
|
|
719
|
+
/** Sample rate in Hz. */
|
|
720
|
+
get sampleRate() {
|
|
721
|
+
return this._sampleRate;
|
|
722
|
+
}
|
|
723
|
+
/** Duration in seconds. */
|
|
724
|
+
get duration() {
|
|
725
|
+
return this._samples.length / this._sampleRate;
|
|
726
|
+
}
|
|
727
|
+
// -- Analysis --
|
|
728
|
+
detectBpm() {
|
|
729
|
+
return detectBpm(this._samples, this._sampleRate);
|
|
730
|
+
}
|
|
731
|
+
detectKey() {
|
|
732
|
+
return detectKey(this._samples, this._sampleRate);
|
|
733
|
+
}
|
|
734
|
+
detectOnsets() {
|
|
735
|
+
return detectOnsets(this._samples, this._sampleRate);
|
|
736
|
+
}
|
|
737
|
+
detectBeats() {
|
|
738
|
+
return detectBeats(this._samples, this._sampleRate);
|
|
739
|
+
}
|
|
740
|
+
analyze() {
|
|
741
|
+
return analyze(this._samples, this._sampleRate);
|
|
742
|
+
}
|
|
743
|
+
analyzeWithProgress(onProgress) {
|
|
744
|
+
return analyzeWithProgress(this._samples, this._sampleRate, onProgress);
|
|
745
|
+
}
|
|
746
|
+
// -- Effects --
|
|
747
|
+
hpss(kernelHarmonic = 31, kernelPercussive = 31) {
|
|
748
|
+
return hpss(this._samples, this._sampleRate, kernelHarmonic, kernelPercussive);
|
|
749
|
+
}
|
|
750
|
+
harmonic() {
|
|
751
|
+
return harmonic(this._samples, this._sampleRate);
|
|
752
|
+
}
|
|
753
|
+
percussive() {
|
|
754
|
+
return percussive(this._samples, this._sampleRate);
|
|
755
|
+
}
|
|
756
|
+
timeStretch(rate) {
|
|
757
|
+
return timeStretch(this._samples, this._sampleRate, rate);
|
|
758
|
+
}
|
|
759
|
+
pitchShift(semitones) {
|
|
760
|
+
return pitchShift(this._samples, this._sampleRate, semitones);
|
|
761
|
+
}
|
|
762
|
+
normalize(targetDb = 0.0) {
|
|
763
|
+
return normalize(this._samples, this._sampleRate, targetDb);
|
|
764
|
+
}
|
|
765
|
+
trim(thresholdDb = -60.0) {
|
|
766
|
+
return trim(this._samples, this._sampleRate, thresholdDb);
|
|
767
|
+
}
|
|
768
|
+
// -- Features --
|
|
769
|
+
stft(nFft = 2048, hopLength = 512) {
|
|
770
|
+
return stft(this._samples, this._sampleRate, nFft, hopLength);
|
|
771
|
+
}
|
|
772
|
+
stftDb(nFft = 2048, hopLength = 512) {
|
|
773
|
+
return stftDb(this._samples, this._sampleRate, nFft, hopLength);
|
|
774
|
+
}
|
|
775
|
+
melSpectrogram(nFft = 2048, hopLength = 512, nMels = 128) {
|
|
776
|
+
return melSpectrogram(this._samples, this._sampleRate, nFft, hopLength, nMels);
|
|
777
|
+
}
|
|
778
|
+
mfcc(nFft = 2048, hopLength = 512, nMels = 128, nMfcc = 13) {
|
|
779
|
+
return mfcc(this._samples, this._sampleRate, nFft, hopLength, nMels, nMfcc);
|
|
780
|
+
}
|
|
781
|
+
chroma(nFft = 2048, hopLength = 512) {
|
|
782
|
+
return chroma(this._samples, this._sampleRate, nFft, hopLength);
|
|
783
|
+
}
|
|
784
|
+
spectralCentroid(nFft = 2048, hopLength = 512) {
|
|
785
|
+
return spectralCentroid(this._samples, this._sampleRate, nFft, hopLength);
|
|
786
|
+
}
|
|
787
|
+
spectralBandwidth(nFft = 2048, hopLength = 512) {
|
|
788
|
+
return spectralBandwidth(this._samples, this._sampleRate, nFft, hopLength);
|
|
789
|
+
}
|
|
790
|
+
spectralRolloff(nFft = 2048, hopLength = 512, rollPercent = 0.85) {
|
|
791
|
+
return spectralRolloff(this._samples, this._sampleRate, nFft, hopLength, rollPercent);
|
|
792
|
+
}
|
|
793
|
+
spectralFlatness(nFft = 2048, hopLength = 512) {
|
|
794
|
+
return spectralFlatness(this._samples, this._sampleRate, nFft, hopLength);
|
|
795
|
+
}
|
|
796
|
+
zeroCrossingRate(frameLength = 2048, hopLength = 512) {
|
|
797
|
+
return zeroCrossingRate(this._samples, this._sampleRate, frameLength, hopLength);
|
|
798
|
+
}
|
|
799
|
+
rmsEnergy(frameLength = 2048, hopLength = 512) {
|
|
800
|
+
return rmsEnergy(this._samples, this._sampleRate, frameLength, hopLength);
|
|
801
|
+
}
|
|
802
|
+
pitchYin(frameLength = 2048, hopLength = 512, fmin = 65.0, fmax = 2093.0, threshold = 0.3) {
|
|
803
|
+
return pitchYin(this._samples, this._sampleRate, frameLength, hopLength, fmin, fmax, threshold);
|
|
804
|
+
}
|
|
805
|
+
pitchPyin(frameLength = 2048, hopLength = 512, fmin = 65.0, fmax = 2093.0, threshold = 0.3) {
|
|
806
|
+
return pitchPyin(this._samples, this._sampleRate, frameLength, hopLength, fmin, fmax, threshold);
|
|
807
|
+
}
|
|
808
|
+
resample(targetSr) {
|
|
809
|
+
return resample(this._samples, this._sampleRate, targetSr);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// ============================================================================
|
|
813
|
+
// StreamAnalyzer Class
|
|
814
|
+
// ============================================================================
|
|
815
|
+
/**
|
|
816
|
+
* Real-time streaming audio analyzer.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```typescript
|
|
820
|
+
* import { init, StreamAnalyzer } from '@libraz/sonare';
|
|
821
|
+
*
|
|
822
|
+
* await init();
|
|
823
|
+
*
|
|
824
|
+
* const analyzer = new StreamAnalyzer({ sampleRate: 44100 });
|
|
825
|
+
*
|
|
826
|
+
* // In audio processing callback
|
|
827
|
+
* analyzer.process(samples);
|
|
828
|
+
*
|
|
829
|
+
* // Get current analysis state
|
|
830
|
+
* const stats = analyzer.stats();
|
|
831
|
+
* console.log('BPM:', stats.estimate.bpm);
|
|
832
|
+
* console.log('Key:', stats.estimate.key);
|
|
833
|
+
* console.log('Chord progression:', stats.estimate.chordProgression);
|
|
834
|
+
* ```
|
|
835
|
+
*/
|
|
836
|
+
export class StreamAnalyzer {
|
|
837
|
+
/**
|
|
838
|
+
* Create a new StreamAnalyzer.
|
|
839
|
+
*
|
|
840
|
+
* @param config - Configuration options
|
|
841
|
+
*/
|
|
842
|
+
constructor(config) {
|
|
843
|
+
if (!module) {
|
|
844
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
845
|
+
}
|
|
846
|
+
this.analyzer = new module.StreamAnalyzer(config.sampleRate, config.nFft ?? 2048, config.hopLength ?? 512, config.nMels ?? 128, config.computeMel ?? true, config.computeChroma ?? true, config.computeOnset ?? true, config.emitEveryNFrames ?? 1);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Process audio samples.
|
|
850
|
+
*
|
|
851
|
+
* @param samples - Audio samples (mono, float32)
|
|
852
|
+
*/
|
|
853
|
+
process(samples) {
|
|
854
|
+
this.analyzer.process(samples);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Process audio samples with explicit sample offset.
|
|
858
|
+
*
|
|
859
|
+
* @param samples - Audio samples (mono, float32)
|
|
860
|
+
* @param sampleOffset - Cumulative sample count at start of this chunk
|
|
861
|
+
*/
|
|
862
|
+
processWithOffset(samples, sampleOffset) {
|
|
863
|
+
this.analyzer.processWithOffset(samples, sampleOffset);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Get the number of frames available to read.
|
|
867
|
+
*/
|
|
868
|
+
availableFrames() {
|
|
869
|
+
return this.analyzer.availableFrames();
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Read processed frames as Structure of Arrays.
|
|
873
|
+
*
|
|
874
|
+
* @param maxFrames - Maximum number of frames to read
|
|
875
|
+
* @returns Frame buffer with analysis results
|
|
876
|
+
*/
|
|
877
|
+
readFrames(maxFrames) {
|
|
878
|
+
return this.analyzer.readFramesSoa(maxFrames);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Reset the analyzer state.
|
|
882
|
+
*
|
|
883
|
+
* @param baseSampleOffset - Starting sample offset (default 0)
|
|
884
|
+
*/
|
|
885
|
+
reset(baseSampleOffset = 0) {
|
|
886
|
+
this.analyzer.reset(baseSampleOffset);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Get current statistics and progressive estimates.
|
|
890
|
+
*
|
|
891
|
+
* @returns Analyzer statistics including BPM, key, and chord progression
|
|
892
|
+
*/
|
|
893
|
+
stats() {
|
|
894
|
+
const s = this.analyzer.stats();
|
|
895
|
+
return {
|
|
896
|
+
totalFrames: s.totalFrames,
|
|
897
|
+
totalSamples: s.totalSamples,
|
|
898
|
+
durationSeconds: s.durationSeconds,
|
|
899
|
+
estimate: {
|
|
900
|
+
bpm: s.estimate.bpm,
|
|
901
|
+
bpmConfidence: s.estimate.bpmConfidence,
|
|
902
|
+
bpmCandidateCount: s.estimate.bpmCandidateCount,
|
|
903
|
+
key: s.estimate.key,
|
|
904
|
+
keyMinor: s.estimate.keyMinor,
|
|
905
|
+
keyConfidence: s.estimate.keyConfidence,
|
|
906
|
+
chordRoot: s.estimate.chordRoot,
|
|
907
|
+
chordQuality: s.estimate.chordQuality,
|
|
908
|
+
chordConfidence: s.estimate.chordConfidence,
|
|
909
|
+
chordProgression: s.estimate.chordProgression.map((c) => ({
|
|
910
|
+
root: c.root,
|
|
911
|
+
quality: c.quality,
|
|
912
|
+
startTime: c.startTime,
|
|
913
|
+
confidence: c.confidence,
|
|
914
|
+
})),
|
|
915
|
+
barChordProgression: s.estimate.barChordProgression.map((c) => ({
|
|
916
|
+
barIndex: c.barIndex,
|
|
917
|
+
root: c.root,
|
|
918
|
+
quality: c.quality,
|
|
919
|
+
startTime: c.startTime,
|
|
920
|
+
confidence: c.confidence,
|
|
921
|
+
})),
|
|
922
|
+
currentBar: s.estimate.currentBar,
|
|
923
|
+
barDuration: s.estimate.barDuration,
|
|
924
|
+
votedPattern: (s.estimate.votedPattern || []).map((c) => ({
|
|
925
|
+
barIndex: c.barIndex,
|
|
926
|
+
root: c.root,
|
|
927
|
+
quality: c.quality,
|
|
928
|
+
startTime: c.startTime,
|
|
929
|
+
confidence: c.confidence,
|
|
930
|
+
})),
|
|
931
|
+
patternLength: s.estimate.patternLength,
|
|
932
|
+
detectedPatternName: s.estimate.detectedPatternName || '',
|
|
933
|
+
detectedPatternScore: s.estimate.detectedPatternScore || 0,
|
|
934
|
+
allPatternScores: (s.estimate.allPatternScores || []).map((p) => ({
|
|
935
|
+
name: p.name,
|
|
936
|
+
score: p.score,
|
|
937
|
+
})),
|
|
938
|
+
accumulatedSeconds: s.estimate.accumulatedSeconds,
|
|
939
|
+
usedFrames: s.estimate.usedFrames,
|
|
940
|
+
updated: s.estimate.updated,
|
|
941
|
+
},
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Get total frames processed.
|
|
946
|
+
*/
|
|
947
|
+
frameCount() {
|
|
948
|
+
return this.analyzer.frameCount();
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Get current time position in seconds.
|
|
952
|
+
*/
|
|
953
|
+
currentTime() {
|
|
954
|
+
return this.analyzer.currentTime();
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Get the sample rate.
|
|
958
|
+
*/
|
|
959
|
+
sampleRate() {
|
|
960
|
+
return this.analyzer.sampleRate();
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Set the expected total duration for pattern lock timing.
|
|
964
|
+
*
|
|
965
|
+
* @param durationSeconds - Total duration in seconds
|
|
966
|
+
*/
|
|
967
|
+
setExpectedDuration(durationSeconds) {
|
|
968
|
+
this.analyzer.setExpectedDuration(durationSeconds);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Set normalization gain for loud/compressed audio.
|
|
972
|
+
*
|
|
973
|
+
* @param gain - Gain factor to apply (e.g., 0.5 for -6dB reduction)
|
|
974
|
+
*/
|
|
975
|
+
setNormalizationGain(gain) {
|
|
976
|
+
this.analyzer.setNormalizationGain(gain);
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Set tuning reference frequency for non-standard tuning.
|
|
980
|
+
*
|
|
981
|
+
* @param refHz - Reference frequency for A4 (default 440 Hz)
|
|
982
|
+
* @example
|
|
983
|
+
* // If audio is 1 semitone sharp (A4 = 466.16 Hz)
|
|
984
|
+
* analyzer.setTuningRefHz(466.16);
|
|
985
|
+
* // If audio is 1 semitone flat (A4 = 415.30 Hz)
|
|
986
|
+
* analyzer.setTuningRefHz(415.30);
|
|
987
|
+
*/
|
|
988
|
+
setTuningRefHz(refHz) {
|
|
989
|
+
this.analyzer.setTuningRefHz(refHz);
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Release resources. Call when done using the analyzer.
|
|
993
|
+
*/
|
|
994
|
+
dispose() {
|
|
995
|
+
this.analyzer.delete();
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
// ============================================================================
|
|
999
|
+
// Re-exports
|
|
1000
|
+
// ============================================================================
|
|
1001
|
+
export { PitchClass as Pitch };
|
|
1002
|
+
//# sourceMappingURL=index.js.map
|