@libraz/libsonare 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Audio analysis library for music information retrieval",
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @example
5
5
  * ```typescript
6
- * import { init, detectBpm, detectKey, analyze } from '@libraz/sonare';
6
+ * import { init, detectBpm, detectKey, analyze } from '@libraz/libsonare';
7
7
  *
8
8
  * await init();
9
9
  *
@@ -60,6 +60,11 @@ import type {
60
60
  PitchResult,
61
61
  RealtimeVoiceChangerConfigInput,
62
62
  RealtimeVoiceChangerPodConfig,
63
+ RirResult,
64
+ RirSynthOptions,
65
+ RoomEstimateOptions,
66
+ RoomEstimateResult,
67
+ RoomMorphOptions,
63
68
  Section,
64
69
  SectionType,
65
70
  SendTiming,
@@ -151,6 +156,7 @@ export type {
151
156
  MasteringStereoResult,
152
157
  MelodyPoint,
153
158
  MelodyResult,
159
+ MelPowerResult,
154
160
  MelSpectrogramResult,
155
161
  MeterTap,
156
162
  MfccResult,
@@ -164,15 +170,24 @@ export type {
164
170
  PanMode,
165
171
  PitchResult,
166
172
  RealtimeVoiceChangerConfigInput,
173
+ RealtimeVoiceChangerPodConfig,
167
174
  RhythmFeatures,
175
+ RirResult,
176
+ RirSynthOptions,
177
+ RoomEstimateOptions,
178
+ RoomEstimateResult,
179
+ RoomGeometryOptions,
180
+ RoomMorphOptions,
168
181
  Section,
169
182
  SendTiming,
170
183
  SoloProcessor,
171
184
  StereoAnalysis,
185
+ StftPowerResult,
172
186
  StftResult,
173
187
  StreamingEqualizerConfig,
174
188
  StreamingPlatform,
175
189
  StreamingRetuneConfig,
190
+ TempogramMode,
176
191
  Timbre,
177
192
  TimeSignature,
178
193
  VoicePresetId,
@@ -712,6 +727,34 @@ export class RealtimeEngine {
712
727
  return this.native.process(channels);
713
728
  }
714
729
 
730
+ /**
731
+ * Allocates persistent per-channel WASM-heap scratch for the zero-copy
732
+ * `getChannelBuffer` / `processPrepared` realtime path. Call once (off the
733
+ * audio thread) before driving `processPrepared` from an AudioWorklet so the
734
+ * render callback never allocates on the C++/JS heap.
735
+ */
736
+ prepareChannels(numChannels: number, maxFrames: number): void {
737
+ this.native.prepareChannels(numChannels, maxFrames);
738
+ }
739
+
740
+ /**
741
+ * Returns a Float32Array view onto the persistent WASM-heap scratch for one
742
+ * channel (valid for up to `numFrames`). Fill it, call `processPrepared`, then
743
+ * read the same view back. Re-acquire after WASM memory growth.
744
+ */
745
+ getChannelBuffer(channel: number, numFrames: number): Float32Array {
746
+ return this.native.getChannelBuffer(channel, numFrames);
747
+ }
748
+
749
+ /**
750
+ * Runs the engine in place over the prepared per-channel scratch buffers.
751
+ * Allocation-free: safe to call on the AudioWorklet render thread after
752
+ * `prepareChannels`.
753
+ */
754
+ processPrepared(numFrames: number): void {
755
+ this.native.processPrepared(numFrames);
756
+ }
757
+
715
758
  processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
716
759
  return this.native.processWithMonitor(channels);
717
760
  }
@@ -1086,6 +1129,57 @@ export function detectAcoustic(
1086
1129
  return result;
1087
1130
  }
1088
1131
 
1132
+ /**
1133
+ * Synthesize a room impulse response from shoebox geometry. `hasError` is true
1134
+ * when the source/listener falls outside the room (the RIR is then empty).
1135
+ */
1136
+ export function synthesizeRir(options: RirSynthOptions = {}): RirResult {
1137
+ if (!module) {
1138
+ throw new Error('Module not initialized. Call init() first.');
1139
+ }
1140
+ if (typeof module.synthesizeRir !== 'function') {
1141
+ throw new Error('libsonare was built without acoustic-simulation support');
1142
+ }
1143
+ return module.synthesizeRir(options);
1144
+ }
1145
+
1146
+ /**
1147
+ * Estimate an equivalent room (volume/dimensions/absorption/DRR) from a
1148
+ * recording or impulse response.
1149
+ */
1150
+ export function estimateRoom(
1151
+ samples: Float32Array,
1152
+ sampleRate = 48000,
1153
+ options: RoomEstimateOptions = {},
1154
+ ): RoomEstimateResult {
1155
+ if (!module) {
1156
+ throw new Error('Module not initialized. Call init() first.');
1157
+ }
1158
+ if (typeof module.estimateRoom !== 'function') {
1159
+ throw new Error('libsonare was built without acoustic-simulation support');
1160
+ }
1161
+ return module.estimateRoom(samples, sampleRate, options);
1162
+ }
1163
+
1164
+ /**
1165
+ * Morph a recording's reverberation toward a target room (creative FX, not
1166
+ * dereverberation). Returns the morphed samples (input length plus the target
1167
+ * room's reverb tail).
1168
+ */
1169
+ export function roomMorph(
1170
+ samples: Float32Array,
1171
+ sampleRate: number,
1172
+ options: RoomMorphOptions = {},
1173
+ ): Float32Array {
1174
+ if (!module) {
1175
+ throw new Error('Module not initialized. Call init() first.');
1176
+ }
1177
+ if (typeof module.roomMorph !== 'function') {
1178
+ throw new Error('libsonare was built without acoustic-simulation support');
1179
+ }
1180
+ return module.roomMorph(samples, sampleRate, options);
1181
+ }
1182
+
1089
1183
  /**
1090
1184
  * Perform complete music analysis with progress reporting.
1091
1185
  *
@@ -1286,10 +1380,15 @@ export function hpss(
1286
1380
  * @param sampleRate - Sample rate in Hz
1287
1381
  * @returns Harmonic component
1288
1382
  */
1289
- export function harmonic(samples: Float32Array, sampleRate: number): Float32Array {
1383
+ export function harmonic(
1384
+ samples: Float32Array,
1385
+ sampleRate: number,
1386
+ options: ValidateOptions = {},
1387
+ ): Float32Array {
1290
1388
  if (!module) {
1291
1389
  throw new Error('Module not initialized. Call init() first.');
1292
1390
  }
1391
+ assertSamples('harmonic', samples, options.validate !== false);
1293
1392
  return module.harmonic(samples, sampleRate);
1294
1393
  }
1295
1394
 
@@ -1300,10 +1399,15 @@ export function harmonic(samples: Float32Array, sampleRate: number): Float32Arra
1300
1399
  * @param sampleRate - Sample rate in Hz
1301
1400
  * @returns Percussive component
1302
1401
  */
1303
- export function percussive(samples: Float32Array, sampleRate: number): Float32Array {
1402
+ export function percussive(
1403
+ samples: Float32Array,
1404
+ sampleRate: number,
1405
+ options: ValidateOptions = {},
1406
+ ): Float32Array {
1304
1407
  if (!module) {
1305
1408
  throw new Error('Module not initialized. Call init() first.');
1306
1409
  }
1410
+ assertSamples('percussive', samples, options.validate !== false);
1307
1411
  return module.percussive(samples, sampleRate);
1308
1412
  }
1309
1413
 
@@ -1315,10 +1419,16 @@ export function percussive(samples: Float32Array, sampleRate: number): Float32Ar
1315
1419
  * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
1316
1420
  * @returns Time-stretched audio
1317
1421
  */
1318
- export function timeStretch(samples: Float32Array, sampleRate: number, rate: number): Float32Array {
1422
+ export function timeStretch(
1423
+ samples: Float32Array,
1424
+ sampleRate: number,
1425
+ rate: number,
1426
+ options: ValidateOptions = {},
1427
+ ): Float32Array {
1319
1428
  if (!module) {
1320
1429
  throw new Error('Module not initialized. Call init() first.');
1321
1430
  }
1431
+ assertSamples('timeStretch', samples, options.validate !== false);
1322
1432
  return module.timeStretch(samples, sampleRate, rate);
1323
1433
  }
1324
1434
 
@@ -1334,10 +1444,12 @@ export function pitchShift(
1334
1444
  samples: Float32Array,
1335
1445
  sampleRate: number,
1336
1446
  semitones: number,
1447
+ options: ValidateOptions = {},
1337
1448
  ): Float32Array {
1338
1449
  if (!module) {
1339
1450
  throw new Error('Module not initialized. Call init() first.');
1340
1451
  }
1452
+ assertSamples('pitchShift', samples, options.validate !== false);
1341
1453
  return module.pitchShift(samples, sampleRate, semitones);
1342
1454
  }
1343
1455
 
@@ -1355,10 +1467,12 @@ export function pitchCorrectToMidi(
1355
1467
  sampleRate = 22050,
1356
1468
  currentMidi = 69.0,
1357
1469
  targetMidi = 69.0,
1470
+ options: ValidateOptions = {},
1358
1471
  ): Float32Array {
1359
1472
  if (!module) {
1360
1473
  throw new Error('Module not initialized. Call init() first.');
1361
1474
  }
1475
+ assertSamples('pitchCorrectToMidi', samples, options.validate !== false);
1362
1476
  return module.pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
1363
1477
  }
1364
1478
 
@@ -1378,10 +1492,12 @@ export function noteStretch(
1378
1492
  onsetSample = 0,
1379
1493
  offsetSample = 0,
1380
1494
  stretchRatio = 1.0,
1495
+ options: ValidateOptions = {},
1381
1496
  ): Float32Array {
1382
1497
  if (!module) {
1383
1498
  throw new Error('Module not initialized. Call init() first.');
1384
1499
  }
1500
+ assertSamples('noteStretch', samples, options.validate !== false);
1385
1501
  return module.noteStretch(samples, sampleRate, onsetSample, offsetSample, stretchRatio);
1386
1502
  }
1387
1503
 
@@ -1408,6 +1524,65 @@ export function voiceChange(
1408
1524
  return module.voiceChange(samples, sampleRate, pitchSemitones, formantFactor);
1409
1525
  }
1410
1526
 
1527
+ /** Options for the offline {@link voiceChangeRealtime} convenience wrapper. */
1528
+ export interface VoiceChangeRealtimeOptions extends ValidateOptions {
1529
+ sampleRate?: number;
1530
+ /** Voice-changer preset id or full config object. */
1531
+ preset?: RealtimeVoiceChangerConfigInput;
1532
+ /** Channel count (1 = mono, 2 = interleaved stereo). */
1533
+ channels?: 1 | 2;
1534
+ /** Block size for the internal render loop (default 512). */
1535
+ blockSize?: number;
1536
+ }
1537
+
1538
+ /**
1539
+ * Applies the realtime voice-changer chain to a whole buffer in one call.
1540
+ *
1541
+ * Constructs and prepares a {@link RealtimeVoiceChanger}, runs the block loop
1542
+ * for the caller, then disposes it — matching the Python `voice_change_realtime`
1543
+ * and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
1544
+ * plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
1545
+ *
1546
+ * @returns The processed buffer (same layout/length as the input).
1547
+ */
1548
+ export function voiceChangeRealtime(
1549
+ samples: Float32Array,
1550
+ options: VoiceChangeRealtimeOptions = {},
1551
+ ): Float32Array {
1552
+ if (!module) {
1553
+ throw new Error('Module not initialized. Call init() first.');
1554
+ }
1555
+ assertSamples('voiceChangeRealtime', samples, options.validate !== false);
1556
+ const channels = options.channels ?? 1;
1557
+ if (channels !== 1 && channels !== 2) {
1558
+ throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
1559
+ }
1560
+ // 48000 matches the Python voice_change_realtime and Node voiceChangeRealtime
1561
+ // convenience wrappers (and the RealtimeVoiceChanger default).
1562
+ const sampleRate = options.sampleRate ?? 48000;
1563
+ const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
1564
+ const changer = new RealtimeVoiceChanger(options.preset ?? 'neutral-monitor');
1565
+ try {
1566
+ changer.prepare(sampleRate, blockSize, channels);
1567
+ const out = new Float32Array(samples.length);
1568
+ if (channels === 1) {
1569
+ for (let offset = 0; offset < samples.length; offset += blockSize) {
1570
+ const block = samples.subarray(offset, Math.min(offset + blockSize, samples.length));
1571
+ out.set(changer.processMono(block), offset);
1572
+ }
1573
+ } else {
1574
+ const frameStride = blockSize * 2;
1575
+ for (let offset = 0; offset < samples.length; offset += frameStride) {
1576
+ const block = samples.subarray(offset, Math.min(offset + frameStride, samples.length));
1577
+ out.set(changer.processInterleaved(block, 2), offset);
1578
+ }
1579
+ }
1580
+ return out;
1581
+ } finally {
1582
+ changer.delete();
1583
+ }
1584
+ }
1585
+
1411
1586
  /**
1412
1587
  * Normalize audio to target peak level.
1413
1588
  *
@@ -1416,10 +1591,16 @@ export function voiceChange(
1416
1591
  * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
1417
1592
  * @returns Normalized audio
1418
1593
  */
1419
- export function normalize(samples: Float32Array, sampleRate: number, targetDb = 0.0): Float32Array {
1594
+ export function normalize(
1595
+ samples: Float32Array,
1596
+ sampleRate: number,
1597
+ targetDb = 0.0,
1598
+ options: ValidateOptions = {},
1599
+ ): Float32Array {
1420
1600
  if (!module) {
1421
1601
  throw new Error('Module not initialized. Call init() first.');
1422
1602
  }
1603
+ assertSamples('normalize', samples, options.validate !== false);
1423
1604
  return module.normalize(samples, sampleRate, targetDb);
1424
1605
  }
1425
1606
 
@@ -4535,7 +4716,7 @@ export function resample(samples: Float32Array, srcSr: number, targetSr: number)
4535
4716
  *
4536
4717
  * @example
4537
4718
  * ```typescript
4538
- * import { init, Audio } from '@libraz/sonare';
4719
+ * import { init, Audio } from '@libraz/libsonare';
4539
4720
  *
4540
4721
  * await init();
4541
4722
  *
@@ -4802,7 +4983,7 @@ export class Audio {
4802
4983
  *
4803
4984
  * @example
4804
4985
  * ```typescript
4805
- * import { init, StreamAnalyzer } from '@libraz/sonare';
4986
+ * import { init, StreamAnalyzer } from '@libraz/libsonare';
4806
4987
  *
4807
4988
  * await init();
4808
4989
  *
@@ -4831,7 +5012,7 @@ export class StreamAnalyzer {
4831
5012
  throw new Error('Module not initialized. Call init() first.');
4832
5013
  }
4833
5014
  this.analyzer = new module.StreamAnalyzer(
4834
- config.sampleRate,
5015
+ config.sampleRate ?? 44100,
4835
5016
  config.nFft ?? 2048,
4836
5017
  config.hopLength ?? 512,
4837
5018
  config.nMels ?? 128,
@@ -421,6 +421,69 @@ export interface AcousticResult {
421
421
  isBlind: boolean;
422
422
  }
423
423
 
424
+ /** Shoebox geometry + placement shared by RIR synthesis and the room morph. */
425
+ export interface RoomGeometryOptions {
426
+ lengthM?: number;
427
+ widthM?: number;
428
+ heightM?: number;
429
+ absorption?: number;
430
+ sourceX?: number;
431
+ sourceY?: number;
432
+ sourceZ?: number;
433
+ listenerX?: number;
434
+ listenerY?: number;
435
+ listenerZ?: number;
436
+ ismOrder?: number;
437
+ seed?: number;
438
+ maxSeconds?: number;
439
+ }
440
+
441
+ export interface RirSynthOptions extends RoomGeometryOptions {
442
+ sampleRate?: number;
443
+ /** Use the Eyring statistical late-tail model (default true); false = Sabine. */
444
+ preferEyring?: boolean;
445
+ /** Early/late crossover in ms (0 = auto, ~sqrt(V) ms). */
446
+ mixingTimeMs?: number;
447
+ /** Equal-power crossfade width around the mixing time in ms (0 = default). */
448
+ crossfadeMs?: number;
449
+ }
450
+
451
+ export interface RirResult {
452
+ rir: Float32Array;
453
+ sampleRate: number;
454
+ hasError: boolean;
455
+ }
456
+
457
+ export interface RoomEstimateOptions {
458
+ aspectHintLw?: number;
459
+ aspectHintLh?: number;
460
+ referenceAbsorption?: number;
461
+ preferEyring?: boolean;
462
+ nOctaveBands?: number;
463
+ /** Analyzer routing: 0 = auto, 1 = blind, 2 = impulse-response. */
464
+ mode?: number;
465
+ /** Analyzer decay-fit span in dB (0 = library default). */
466
+ minDecayDb?: number;
467
+ /** Analyzer noise-floor margin in dB (0 = library default). */
468
+ noiseFloorMarginDb?: number;
469
+ }
470
+
471
+ export interface RoomEstimateResult {
472
+ volume: number;
473
+ length: number;
474
+ width: number;
475
+ height: number;
476
+ drrDb: number;
477
+ confidence: number;
478
+ absorptionBands: Float32Array;
479
+ rt60Bands: Float32Array;
480
+ }
481
+
482
+ export interface RoomMorphOptions extends RoomGeometryOptions {
483
+ wet?: number;
484
+ sourceTailSuppression?: number;
485
+ }
486
+
424
487
  /**
425
488
  * HPSS (Harmonic-Percussive Source Separation) result
426
489
  */
@@ -914,6 +977,10 @@ export interface RealtimeVoiceChangerPodConfig {
914
977
  reverb_seed: number;
915
978
  limiter_ceiling_db: number;
916
979
  limiter_release_ms: number;
980
+ /** Non-zero enables the 4x-oversampled inter-sample-peak limiter (default enabled). */
981
+ limiter_enable_isp_limiter: boolean;
982
+ /** True-peak ceiling in dBTP applied by the ISP limiter (default -1.0). */
983
+ limiter_isp_ceiling_dbtp: number;
917
984
  }
918
985
 
919
986
  /** Options for {@link StreamingEqualizer.match}. */
@@ -60,6 +60,8 @@ export interface WasmRealtimeVoiceChangerPodConfig {
60
60
  reverb_seed: number;
61
61
  limiter_ceiling_db: number;
62
62
  limiter_release_ms: number;
63
+ limiter_enable_isp_limiter: boolean;
64
+ limiter_isp_ceiling_dbtp: number;
63
65
  }
64
66
 
65
67
  export interface WasmBeatResult {
@@ -199,6 +201,62 @@ export interface WasmAcousticResult {
199
201
  isBlind: boolean;
200
202
  }
201
203
 
204
+ export interface WasmRoomGeometryOptions {
205
+ lengthM?: number;
206
+ widthM?: number;
207
+ heightM?: number;
208
+ absorption?: number;
209
+ sourceX?: number;
210
+ sourceY?: number;
211
+ sourceZ?: number;
212
+ listenerX?: number;
213
+ listenerY?: number;
214
+ listenerZ?: number;
215
+ ismOrder?: number;
216
+ seed?: number;
217
+ maxSeconds?: number;
218
+ }
219
+
220
+ export interface WasmRirSynthOptions extends WasmRoomGeometryOptions {
221
+ sampleRate?: number;
222
+ preferEyring?: boolean;
223
+ mixingTimeMs?: number;
224
+ crossfadeMs?: number;
225
+ }
226
+
227
+ export interface WasmRirResult {
228
+ rir: Float32Array;
229
+ sampleRate: number;
230
+ hasError: boolean;
231
+ }
232
+
233
+ export interface WasmRoomEstimateOptions {
234
+ aspectHintLw?: number;
235
+ aspectHintLh?: number;
236
+ referenceAbsorption?: number;
237
+ preferEyring?: boolean;
238
+ nOctaveBands?: number;
239
+ mode?: number;
240
+ minDecayDb?: number;
241
+ noiseFloorMarginDb?: number;
242
+ }
243
+
244
+ export interface WasmRoomEstimateResult {
245
+ volume: number;
246
+ length: number;
247
+ width: number;
248
+ height: number;
249
+ drrDb: number;
250
+ confidence: number;
251
+ absorptionBands: Float32Array;
252
+ rt60Bands: Float32Array;
253
+ }
254
+
255
+ export interface WasmRoomMorphOptions extends WasmRoomGeometryOptions {
256
+ wet?: number;
257
+ sourceTailSuppression?: number;
258
+ }
259
+
202
260
  export interface WasmHpssResult {
203
261
  harmonic: Float32Array;
204
262
  percussive: Float32Array;
@@ -450,6 +508,8 @@ export interface WasmEngineMetronomeConfig {
450
508
  beatGain?: number;
451
509
  accentGain?: number;
452
510
  clickSamples?: number;
511
+ /** Optional click length in seconds; > 0 overrides the engine 2 ms default. */
512
+ clickSeconds?: number;
453
513
  }
454
514
 
455
515
  export interface WasmEngineGraphNode {
@@ -528,6 +588,12 @@ export interface WasmEngineTransportState {
528
588
  loopStartPpq: number;
529
589
  loopEndPpq: number;
530
590
  sampleRate: number;
591
+ /** PPQ of the current bar's downbeat (derived from the tempo map). */
592
+ barStartPpq: number;
593
+ /** Zero-based index of the current bar. */
594
+ barCount: number;
595
+ /** Time signature in effect at the current PPQ. */
596
+ timeSignature: { numerator: number; denominator: number; confidence: number };
531
597
  }
532
598
 
533
599
  export interface WasmEngineBounceOptions {
@@ -615,6 +681,9 @@ export interface WasmRealtimeEngine {
615
681
  captureStatus: () => WasmEngineCaptureStatus;
616
682
  capturedAudio: () => Float32Array[];
617
683
  process: (channels: Float32Array[]) => Float32Array[];
684
+ prepareChannels: (numChannels: number, maxFrames: number) => void;
685
+ getChannelBuffer: (channel: number, numFrames: number) => Float32Array;
686
+ processPrepared: (numFrames: number) => void;
618
687
  processWithMonitor: (channels: Float32Array[]) => WasmEngineProcessWithMonitorResult;
619
688
  renderOffline: (channels: Float32Array[], blockSize: number) => Float32Array[];
620
689
  bounceOffline: (options: WasmEngineBounceOptions) => WasmEngineBounceResult;
@@ -690,6 +759,19 @@ export interface SonareModule {
690
759
  minDecayDb: number,
691
760
  noiseFloorMarginDb: number,
692
761
  ) => WasmAcousticResult;
762
+ // Acoustic-simulation entry points are present only in builds compiled with
763
+ // SONARE_WITH_ACOUSTIC_SIM; absent otherwise (the wrappers throw a clear error).
764
+ synthesizeRir?: (options: WasmRirSynthOptions) => WasmRirResult;
765
+ estimateRoom?: (
766
+ samples: Float32Array,
767
+ sampleRate: number,
768
+ options: WasmRoomEstimateOptions,
769
+ ) => WasmRoomEstimateResult;
770
+ roomMorph?: (
771
+ samples: Float32Array,
772
+ sampleRate: number,
773
+ options: WasmRoomMorphOptions,
774
+ ) => Float32Array;
693
775
  analyzeWithProgress: (
694
776
  samples: Float32Array,
695
777
  sampleRate: number,
@@ -1703,6 +1785,8 @@ export interface WasmAnalyzerStats {
1703
1785
 
1704
1786
  export interface WasmFrameBuffer {
1705
1787
  nFrames: number;
1788
+ /** Number of mel bands; flat `mel` is `[nFrames * nMels]` row-major. */
1789
+ nMels: number;
1706
1790
  timestamps: Float32Array;
1707
1791
  mel: Float32Array;
1708
1792
  chroma: Float32Array;
@@ -72,6 +72,8 @@ export interface AnalyzerStats {
72
72
  */
73
73
  export interface FrameBuffer {
74
74
  nFrames: number;
75
+ /** Number of mel bands; flat `mel` is `[nFrames * nMels]` row-major. */
76
+ nMels: number;
75
77
  timestamps: Float32Array;
76
78
  mel: Float32Array;
77
79
  chroma: Float32Array;
@@ -112,7 +114,8 @@ export interface StreamFramesI16 {
112
114
  * Configuration for StreamAnalyzer
113
115
  */
114
116
  export interface StreamConfig {
115
- sampleRate: number;
117
+ /** Sample rate in Hz. Optional for parity with the Node/Python bindings (default 44100). */
118
+ sampleRate?: number;
116
119
  nFft?: number;
117
120
  hopLength?: number;
118
121
  nMels?: number;