@libraz/libsonare 1.4.1 → 1.5.1

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.
Files changed (66) hide show
  1. package/README.md +51 -20
  2. package/dist/index.d.ts +5416 -1
  3. package/dist/index.js +938 -583
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare.js +2 -2
  6. package/dist/sonare.wasm +0 -0
  7. package/dist/worklet.d.ts +1083 -5227
  8. package/dist/worklet.js +2683 -2451
  9. package/dist/worklet.js.map +1 -1
  10. package/package.json +4 -9
  11. package/src/clip_page_streamer.ts +298 -0
  12. package/src/effects_mastering.ts +85 -1089
  13. package/src/effects_transform.ts +286 -0
  14. package/src/effects_voice_change.ts +118 -0
  15. package/src/feature_music.ts +5 -2
  16. package/src/feature_spectrogram.ts +42 -2
  17. package/src/features.ts +1 -0
  18. package/src/index.ts +23 -0
  19. package/src/mastering_chain.ts +200 -0
  20. package/src/mastering_core.ts +248 -0
  21. package/src/mastering_dynamics.ts +105 -0
  22. package/src/mastering_repair.ts +161 -0
  23. package/src/mixer.ts +8 -0
  24. package/src/mixing_oneshot.ts +54 -0
  25. package/src/module_state.ts +1 -2
  26. package/src/project.ts +71 -1712
  27. package/src/project_class.ts +871 -0
  28. package/src/project_internal.ts +333 -0
  29. package/src/project_synth.ts +43 -0
  30. package/src/project_types.ts +570 -0
  31. package/src/public_types.ts +6 -1221
  32. package/src/public_types_acoustic.ts +115 -0
  33. package/src/public_types_mastering.ts +333 -0
  34. package/src/public_types_mixing.ts +97 -0
  35. package/src/public_types_music.ts +352 -0
  36. package/src/public_types_realtime.ts +163 -0
  37. package/src/public_types_spectral.ts +194 -0
  38. package/src/realtime_engine.ts +94 -0
  39. package/src/sonare.js.d.ts +117 -38
  40. package/src/stream_analyzer.ts +3 -0
  41. package/src/stream_types.ts +4 -0
  42. package/src/worklet/engine-automation.ts +73 -0
  43. package/src/worklet/engine-capture-facade.ts +80 -0
  44. package/src/worklet/engine-clips.ts +71 -0
  45. package/src/worklet/engine-markers.ts +93 -0
  46. package/src/worklet/engine-mixer-facade.ts +186 -0
  47. package/src/worklet/engine-node.ts +451 -0
  48. package/src/worklet/engine-offline.ts +162 -0
  49. package/src/worklet/engine-options.ts +13 -0
  50. package/src/worklet/engine-parameter-facade.ts +172 -0
  51. package/src/worklet/engine-processor.ts +764 -0
  52. package/src/worklet/engine-register.ts +136 -0
  53. package/src/worklet/engine-strips.ts +315 -0
  54. package/src/worklet/engine-sync.ts +94 -0
  55. package/src/worklet/engine-tempo-facade.ts +141 -0
  56. package/src/worklet/engine.ts +998 -0
  57. package/src/worklet/guards.ts +14 -1
  58. package/src/worklet/messages.ts +60 -20
  59. package/src/worklet/mixer-processor.ts +368 -0
  60. package/src/worklet/protocol.ts +3 -0
  61. package/src/worklet/voice-changer-processor.ts +246 -0
  62. package/src/worklet.ts +20 -3549
  63. package/dist/sonare-rt-module.js +0 -2
  64. package/dist/sonare-rt.js +0 -2
  65. package/dist/sonare-rt.wasm +0 -0
  66. package/src/sonare-rt.d.ts +0 -93
@@ -0,0 +1,286 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type {
3
+ HpssResult,
4
+ NoteStretchOptions,
5
+ PitchCorrectOptions,
6
+ SpectralEditOptions,
7
+ SpectralRegionOp,
8
+ } from './public_types';
9
+ import type { ValidateOptions } from './validation';
10
+ import { assertSampleRate, assertSamples } from './validation';
11
+
12
+ function requireModule() {
13
+ return getSonareModule();
14
+ }
15
+
16
+ // ============================================================================
17
+ // Effects
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Perform Harmonic-Percussive Source Separation (HPSS).
22
+ *
23
+ * @param samples - Audio samples (mono, float32)
24
+ * @param sampleRate - Sample rate in Hz (default: 22050)
25
+ * @param kernelHarmonic - Horizontal median filter size for harmonic (default: 31)
26
+ * @param kernelPercussive - Vertical median filter size for percussive (default: 31)
27
+ * @returns Separated harmonic and percussive components
28
+ */
29
+ export function hpss(
30
+ samples: Float32Array,
31
+ sampleRate = 22050,
32
+ kernelHarmonic = 31,
33
+ kernelPercussive = 31,
34
+ ): HpssResult {
35
+ return requireModule().hpss(samples, sampleRate, kernelHarmonic, kernelPercussive);
36
+ }
37
+
38
+ /**
39
+ * Extract harmonic component from audio.
40
+ *
41
+ * @param samples - Audio samples (mono, float32)
42
+ * @param sampleRate - Sample rate in Hz
43
+ * @returns Harmonic component
44
+ */
45
+ export function harmonic(
46
+ samples: Float32Array,
47
+ sampleRate: number,
48
+ options: ValidateOptions = {},
49
+ ): Float32Array {
50
+ assertSamples('harmonic', samples, options.validate !== false);
51
+ return requireModule().harmonic(samples, sampleRate);
52
+ }
53
+
54
+ /**
55
+ * Extract percussive component from audio.
56
+ *
57
+ * @param samples - Audio samples (mono, float32)
58
+ * @param sampleRate - Sample rate in Hz
59
+ * @returns Percussive component
60
+ */
61
+ export function percussive(
62
+ samples: Float32Array,
63
+ sampleRate: number,
64
+ options: ValidateOptions = {},
65
+ ): Float32Array {
66
+ assertSamples('percussive', samples, options.validate !== false);
67
+ return requireModule().percussive(samples, sampleRate);
68
+ }
69
+
70
+ /**
71
+ * Time-stretch audio without changing pitch.
72
+ *
73
+ * @param samples - Audio samples (mono, float32)
74
+ * @param sampleRate - Sample rate in Hz
75
+ * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
76
+ * @returns Time-stretched audio
77
+ */
78
+ export function timeStretch(
79
+ samples: Float32Array,
80
+ sampleRate: number,
81
+ rate: number,
82
+ options: ValidateOptions = {},
83
+ ): Float32Array {
84
+ assertSamples('timeStretch', samples, options.validate !== false);
85
+ return requireModule().timeStretch(samples, sampleRate, rate);
86
+ }
87
+
88
+ /**
89
+ * Pitch-shift audio without changing duration.
90
+ *
91
+ * @param samples - Audio samples (mono, float32)
92
+ * @param sampleRate - Sample rate in Hz
93
+ * @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
94
+ * @returns Pitch-shifted audio
95
+ */
96
+ export function pitchShift(
97
+ samples: Float32Array,
98
+ sampleRate: number,
99
+ semitones: number,
100
+ options: ValidateOptions = {},
101
+ ): Float32Array {
102
+ assertSamples('pitchShift', samples, options.validate !== false);
103
+ return requireModule().pitchShift(samples, sampleRate, semitones);
104
+ }
105
+
106
+ /**
107
+ * Pitch-correct audio from a current MIDI note to a target MIDI note.
108
+ *
109
+ * @param samples - Audio samples (mono, float32)
110
+ * @param sampleRate - Sample rate in Hz
111
+ * @param currentMidi - Detected/current MIDI note number
112
+ * @param targetMidi - Desired MIDI note number
113
+ * @returns Pitch-corrected audio
114
+ */
115
+ export function pitchCorrectToMidi(
116
+ samples: Float32Array,
117
+ sampleRate = 22050,
118
+ currentMidi = 69.0,
119
+ targetMidi = 69.0,
120
+ options: ValidateOptions = {},
121
+ ): Float32Array {
122
+ assertSamples('pitchCorrectToMidi', samples, options.validate !== false);
123
+ return requireModule().pitchCorrectToMidi(samples, sampleRate, currentMidi, targetMidi);
124
+ }
125
+
126
+ /**
127
+ * Contour-following ("time-varying") pitch correction toward a MIDI target.
128
+ *
129
+ * Unlike {@link pitchCorrectToMidi} (a single constant transpose), this follows
130
+ * the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
131
+ * toward `targetMidi`, so vibrato/drift in the source is tracked rather than
132
+ * flattened. `voiced` (non-zero = voiced) and `voicedProb` ([0,1]) are optional;
133
+ * omitting them treats every frame as voiced.
134
+ *
135
+ * @param samples - Audio samples (mono, float32)
136
+ * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
137
+ * @param targetMidi - Desired MIDI note number
138
+ * @param sampleRate - Sample rate in Hz
139
+ * @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
140
+ * @param voiced - Optional per-frame voiced flags (non-zero = voiced)
141
+ * @param voicedProb - Optional per-frame voicing probability in [0, 1]
142
+ * @returns Pitch-corrected audio
143
+ */
144
+ export function pitchCorrectToMidiTimevarying(
145
+ samples: Float32Array,
146
+ f0Hz: Float32Array,
147
+ targetMidi: number,
148
+ sampleRate = 22050,
149
+ hopLength = 512,
150
+ voiced?: Int32Array,
151
+ voicedProb?: Float32Array,
152
+ options: ValidateOptions = {},
153
+ ): Float32Array {
154
+ assertSamples('pitchCorrectToMidiTimevarying', samples, options.validate !== false);
155
+ if (voiced && voiced.length !== f0Hz.length) {
156
+ throw new RangeError('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length');
157
+ }
158
+ if (voicedProb && voicedProb.length !== f0Hz.length) {
159
+ throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
160
+ }
161
+ // The embind layer reads the companion arrays as Float32Array (voiced uses
162
+ // 0.0/1.0); convert here so a single native conversion path suffices.
163
+ const voicedF32 = voiced ? Float32Array.from(voiced) : undefined;
164
+ return requireModule().pitchCorrectToMidiTimevarying(
165
+ samples,
166
+ sampleRate,
167
+ f0Hz,
168
+ targetMidi,
169
+ hopLength,
170
+ voicedF32,
171
+ voicedProb,
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Contour-following pitch correction toward a fixed MIDI note OR a musical
177
+ * scale, with tunable retune strength and vibrato preservation.
178
+ *
179
+ * Generalises {@link pitchCorrectToMidiTimevarying}: the same caller-supplied
180
+ * per-frame `f0Hz` contour drives correction, but `options.mode` selects between
181
+ * a fixed-MIDI target (`'midi'`, default) and scale quantisation (`'scale'`),
182
+ * and the retune knobs shape natural-vs-robotic correction.
183
+ *
184
+ * @param samples - Audio samples (mono, float32)
185
+ * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
186
+ * @param sampleRate - Sample rate in Hz
187
+ * @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
188
+ * @param options - Target mode + retune knobs + optional voiced/voicedProb arrays
189
+ * @returns Pitch-corrected audio
190
+ */
191
+ export function pitchCorrectTimevarying(
192
+ samples: Float32Array,
193
+ f0Hz: Float32Array,
194
+ sampleRate = 22050,
195
+ hopLength = 512,
196
+ options: PitchCorrectOptions = {},
197
+ ): Float32Array {
198
+ assertSamples('pitchCorrectTimevarying', samples, options.validate !== false);
199
+ if (options.voiced && options.voiced.length !== f0Hz.length) {
200
+ throw new RangeError('pitchCorrectTimevarying: voiced length must match f0Hz length');
201
+ }
202
+ if (options.voicedProb && options.voicedProb.length !== f0Hz.length) {
203
+ throw new RangeError('pitchCorrectTimevarying: voicedProb length must match f0Hz length');
204
+ }
205
+ // The embind layer reads the companion arrays as Float32Array (voiced uses
206
+ // 0.0/1.0); convert here so a single native conversion path suffices.
207
+ const nativeOptions = {
208
+ ...options,
209
+ voiced: options.voiced ? Float32Array.from(options.voiced) : undefined,
210
+ };
211
+ return requireModule().pitchCorrectTimevarying(
212
+ samples,
213
+ sampleRate,
214
+ f0Hz,
215
+ hopLength,
216
+ nativeOptions,
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Time-stretch a note region between two sample offsets without changing pitch.
222
+ *
223
+ * @param samples - Audio samples (mono, float32)
224
+ * @param sampleRate - Sample rate in Hz
225
+ * @param onsetSample - Note onset position in samples
226
+ * @param offsetSample - Note offset position in samples
227
+ * @param stretchRatio - Stretch ratio (0.5 = double duration, 2.0 = half duration)
228
+ * @returns Audio with the note region stretched
229
+ */
230
+ export function noteStretch(
231
+ samples: Float32Array,
232
+ sampleRate = 22050,
233
+ options: NoteStretchOptions & ValidateOptions = {},
234
+ ): Float32Array {
235
+ assertSamples('noteStretch', samples, options.validate !== false);
236
+ return requireModule().noteStretch(
237
+ samples,
238
+ sampleRate,
239
+ options.onsetSample ?? 0,
240
+ options.offsetSample ?? 0,
241
+ options.stretchRatio ?? 1.0,
242
+ );
243
+ }
244
+
245
+ /**
246
+ * Normalize audio to target peak level.
247
+ *
248
+ * @param samples - Audio samples (mono, float32)
249
+ * @param sampleRate - Sample rate in Hz
250
+ * @param targetDb - Target peak level in dB (default: 0 dB = full scale)
251
+ * @returns Normalized audio
252
+ */
253
+ export function normalize(
254
+ samples: Float32Array,
255
+ sampleRate: number,
256
+ targetDb = 0.0,
257
+ options: ValidateOptions = {},
258
+ ): Float32Array {
259
+ assertSamples('normalize', samples, options.validate !== false);
260
+ return requireModule().normalize(samples, sampleRate, targetDb);
261
+ }
262
+
263
+ /**
264
+ * Apply region-based spectral edits (gain/attenuate/mute/heal) to mono audio.
265
+ *
266
+ * Each op is a time x frequency rectangle applied in array order over a single
267
+ * STFT buffer, so a later op observes the result of earlier ops. The output has
268
+ * the same length and sample rate as the input; an empty `ops` list is an
269
+ * identity transform (within the iSTFT's own tolerance).
270
+ *
271
+ * @param samples - Audio samples (mono, float32)
272
+ * @param sampleRate - Sample rate in Hz
273
+ * @param ops - Region edit ops applied in order ({@link SpectralRegionOp})
274
+ * @param options - STFT + heal configuration ({@link SpectralEditOptions})
275
+ * @returns Edited audio
276
+ */
277
+ export function spectralEdit(
278
+ samples: Float32Array,
279
+ sampleRate: number,
280
+ ops: SpectralRegionOp[] = [],
281
+ options: SpectralEditOptions & ValidateOptions = {},
282
+ ): Float32Array {
283
+ assertSamples('spectralEdit', samples, options.validate !== false);
284
+ assertSampleRate('spectralEdit', sampleRate);
285
+ return requireModule().spectralEdit(samples, sampleRate, ops, options as Record<string, unknown>);
286
+ }
@@ -0,0 +1,118 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type { RealtimeVoiceChangerConfigInput } from './public_types';
3
+ import { RealtimeVoiceChanger } from './streaming_mixing';
4
+ import type { ValidateOptions } from './validation';
5
+ import { assertSamples } from './validation';
6
+
7
+ function requireModule() {
8
+ return getSonareModule();
9
+ }
10
+
11
+ /** Options for {@link voiceChange}. All fields are optional. */
12
+ export interface VoiceChangeOptions extends ValidateOptions {
13
+ /** Pitch shift in semitones (negative = down). Default 0. */
14
+ pitchSemitones?: number;
15
+ /** Formant scale factor (>1 brightens, <1 darkens). Default 1. */
16
+ formantFactor?: number;
17
+ }
18
+
19
+ /**
20
+ * Apply a voice change by shifting pitch and formants independently.
21
+ *
22
+ * @param samples - Audio samples (mono, float32)
23
+ * @param sampleRate - Sample rate in Hz
24
+ * @param options - Pitch/formant settings ({@link VoiceChangeOptions})
25
+ * @returns Voice-changed audio
26
+ */
27
+ export function voiceChange(
28
+ samples: Float32Array,
29
+ sampleRate = 22050,
30
+ options: VoiceChangeOptions = {},
31
+ ): Float32Array {
32
+ assertSamples('voiceChange', samples, options.validate !== false);
33
+ return requireModule().voiceChange(
34
+ samples,
35
+ sampleRate,
36
+ options.pitchSemitones ?? 0.0,
37
+ options.formantFactor ?? 1.0,
38
+ );
39
+ }
40
+
41
+ /** Options for the offline {@link voiceChangeRealtime} convenience wrapper. */
42
+ export interface VoiceChangeRealtimeOptions extends ValidateOptions {
43
+ /** Channel count (1 = mono, 2 = interleaved stereo). */
44
+ channels?: 1 | 2;
45
+ /** Block size for the internal render loop (default 512). */
46
+ blockSize?: number;
47
+ }
48
+
49
+ function latencyCompensatedVoiceChange(
50
+ changer: RealtimeVoiceChanger,
51
+ samples: Float32Array,
52
+ channels: 1 | 2,
53
+ blockFrames: number,
54
+ ): Float32Array {
55
+ const latencyFrames = Math.max(0, changer.latencySamples());
56
+ if (channels === 1) {
57
+ const total = samples.length + latencyFrames;
58
+ const input = new Float32Array(total);
59
+ input.set(samples);
60
+ const processed = new Float32Array(total);
61
+ for (let offset = 0; offset < total; offset += blockFrames) {
62
+ const block = input.subarray(offset, Math.min(offset + blockFrames, total));
63
+ processed.set(changer.processMono(block), offset);
64
+ }
65
+ return processed.slice(latencyFrames, latencyFrames + samples.length);
66
+ }
67
+
68
+ const frames = samples.length / 2;
69
+ const totalFrames = frames + latencyFrames;
70
+ const input = new Float32Array(totalFrames * 2);
71
+ input.set(samples);
72
+ const processed = new Float32Array(totalFrames * 2);
73
+ const frameStride = blockFrames * 2;
74
+ for (let offset = 0; offset < input.length; offset += frameStride) {
75
+ const block = input.subarray(offset, Math.min(offset + frameStride, input.length));
76
+ processed.set(changer.processInterleaved(block, 2), offset);
77
+ }
78
+ const start = latencyFrames * 2;
79
+ return processed.slice(start, start + samples.length);
80
+ }
81
+
82
+ /**
83
+ * Applies the realtime voice-changer chain to a whole buffer in one call.
84
+ *
85
+ * Constructs and prepares a {@link RealtimeVoiceChanger}, runs the block loop
86
+ * for the caller, then disposes it — matching the Python `voice_change_realtime`
87
+ * and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
88
+ * plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
89
+ *
90
+ * @param samples - Audio samples (mono, or interleaved stereo when channels=2)
91
+ * @param sampleRate - Sample rate in Hz (default 48000, matching Python/Node)
92
+ * @param preset - Voice-changer preset id or full config object
93
+ * @param options - Channel count and block size ({@link VoiceChangeRealtimeOptions})
94
+ * @returns The processed buffer (same layout/length as the input).
95
+ */
96
+ export function voiceChangeRealtime(
97
+ samples: Float32Array,
98
+ sampleRate = 48000,
99
+ preset: RealtimeVoiceChangerConfigInput = 'neutral-monitor',
100
+ options: VoiceChangeRealtimeOptions = {},
101
+ ): Float32Array {
102
+ assertSamples('voiceChangeRealtime', samples, options.validate !== false);
103
+ const channels = options.channels ?? 1;
104
+ if (channels !== 1 && channels !== 2) {
105
+ throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
106
+ }
107
+ if (channels === 2 && samples.length % 2 !== 0) {
108
+ throw new Error('voiceChangeRealtime: stereo input length must be a multiple of 2.');
109
+ }
110
+ const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
111
+ const changer = new RealtimeVoiceChanger(preset);
112
+ try {
113
+ changer.prepare(sampleRate, blockSize, channels);
114
+ return latencyCompensatedVoiceChange(changer, samples, channels, blockSize);
115
+ } finally {
116
+ changer.delete();
117
+ }
118
+ }
@@ -366,19 +366,22 @@ export function fourierTempogram(
366
366
  * @param winLength - Window length in frames (default: 384)
367
367
  * @param sampleRate - Sample rate in Hz (default: 22050)
368
368
  * @param hopLength - Hop length (default: 512)
369
- * @returns Tempogram ratio features
369
+ * @param factors - Lag ratios to evaluate. When omitted or empty, the library
370
+ * default {0.5, 1, 2, 3, 4} is used.
371
+ * @returns Tempogram ratio features (one value per factor)
370
372
  */
371
373
  export function tempogramRatio(
372
374
  tempogramData: Float32Array,
373
375
  winLength = 384,
374
376
  sampleRate = 22050,
375
377
  hopLength = 512,
378
+ factors?: Float32Array | number[],
376
379
  options: GuardedOptions = {},
377
380
  ): Float32Array {
378
381
  assertSampleRate('tempogramRatio', sampleRate);
379
382
  assertSamples('tempogramRatio', tempogramData, options.validate !== false, 'tempogramData');
380
383
  validatePositiveIntegers('tempogramRatio', { winLength, hopLength });
381
- return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength);
384
+ return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength, factors);
382
385
  }
383
386
 
384
387
  /**
@@ -160,6 +160,27 @@ export function chromaCens(
160
160
  return requireModule().chromaCens(samples, sampleRate, hopLength, nChroma);
161
161
  }
162
162
 
163
+ /**
164
+ * Compute a constant-Q chromagram (librosa.feature.chroma_cqt).
165
+ *
166
+ * @param samples - Audio samples (mono, float32)
167
+ * @param sampleRate - Sample rate in Hz (default: 22050)
168
+ * @param hopLength - Hop length (default: 512)
169
+ * @param nChroma - Number of chroma bins (default: 12)
170
+ * @returns Chroma result
171
+ */
172
+ export function chromaCqt(
173
+ samples: Float32Array,
174
+ sampleRate = 22050,
175
+ hopLength = 512,
176
+ nChroma = 12,
177
+ options: GuardedOptions = {},
178
+ ): ChromaResult {
179
+ validateSpectrogramSamples('chromaCqt', samples, sampleRate, options);
180
+ validatePositiveIntegers('chromaCqt', { hopLength, nChroma });
181
+ return requireModule().chromaCqt(samples, sampleRate, hopLength, nChroma);
182
+ }
183
+
163
184
  /**
164
185
  * Compute low-frequency bass chroma.
165
186
  *
@@ -237,6 +258,7 @@ export function melSpectrogram(
237
258
  * @param fmin - Minimum Mel frequency in Hz (default: 0 = librosa default)
238
259
  * @param fmax - Maximum Mel frequency in Hz (default: 0 = sampleRate / 2)
239
260
  * @param htk - Use the HTK Mel formula instead of Slaney (default: false)
261
+ * @param lifter - Cepstral liftering coefficient (default: 0 = no liftering)
240
262
  * @returns MFCC result
241
263
  */
242
264
  export function mfcc(
@@ -249,12 +271,24 @@ export function mfcc(
249
271
  fmin = 0,
250
272
  fmax = 0,
251
273
  htk = false,
274
+ lifter = 0,
252
275
  options: GuardedOptions = {},
253
276
  ): MfccResult {
254
277
  validateSpectrogramSamples('mfcc', samples, sampleRate, options);
255
278
  validatePositiveIntegers('mfcc', { nFft, hopLength, nMels, nMfcc });
256
279
  validateMelFrequencyRange('mfcc', fmin, fmax, sampleRate);
257
- return requireModule().mfcc(samples, sampleRate, nFft, hopLength, nMels, nMfcc, fmin, fmax, htk);
280
+ return requireModule().mfcc(
281
+ samples,
282
+ sampleRate,
283
+ nFft,
284
+ hopLength,
285
+ nMels,
286
+ nMfcc,
287
+ fmin,
288
+ fmax,
289
+ htk,
290
+ lifter,
291
+ );
258
292
  }
259
293
 
260
294
  // ============================================================================
@@ -433,7 +467,13 @@ export function mfccToAudio(
433
467
  // ============================================================================
434
468
 
435
469
  /**
436
- * Compute chromagram (pitch class distribution).
470
+ * Compute STFT chromagram (librosa.feature.chroma_stft).
471
+ *
472
+ * The chroma filterbank uses a fixed tuning of 0 (concert A440). Unlike
473
+ * librosa.feature.chroma_stft — which estimates tuning from the signal when none
474
+ * is given — this does NOT auto-estimate and exposes no tuning argument, so
475
+ * sharp/flat (non-A440) recordings smear across pitch classes. Estimate tuning
476
+ * separately via {@link estimateTuning} if a non-A440 reference matters.
437
477
  *
438
478
  * @param samples - Audio samples (mono, float32)
439
479
  * @param sampleRate - Sample rate in Hz (default: 22050)
package/src/features.ts CHANGED
@@ -72,6 +72,7 @@ export {
72
72
  bassChroma,
73
73
  chroma,
74
74
  chromaCens,
75
+ chromaCqt,
75
76
  melSpectrogram,
76
77
  melToAudio,
77
78
  melToStft,
package/src/index.ts CHANGED
@@ -29,6 +29,14 @@ import type {
29
29
 
30
30
  export type { BrowserAudioDecodeOptions } from './audio';
31
31
  export { Audio } from './audio';
32
+ export type {
33
+ ClipPageStreamerEngine,
34
+ ClipPageStreamerOptions,
35
+ ClipPageStreamSource,
36
+ OpfsClipStream,
37
+ OpfsClipStreamOptions,
38
+ } from './clip_page_streamer';
39
+ export { attachOpfsClipStream, ClipPageStreamer } from './clip_page_streamer';
32
40
  export type {
33
41
  CompressorDetector,
34
42
  CompressorOptions,
@@ -97,6 +105,7 @@ export {
97
105
  normalize,
98
106
  noteStretch,
99
107
  percussive,
108
+ pitchCorrectTimevarying,
100
109
  pitchCorrectToMidi,
101
110
  pitchCorrectToMidiTimevarying,
102
111
  pitchShift,
@@ -114,6 +123,7 @@ export {
114
123
  bassChroma,
115
124
  chroma,
116
125
  chromaCens,
126
+ chromaCqt,
117
127
  cqt,
118
128
  cyclicTempogram,
119
129
  dbToAmplitude,
@@ -343,6 +353,7 @@ export type {
343
353
  PairProcessor,
344
354
  PanLaw,
345
355
  PanMode,
356
+ PitchCorrectOptions,
346
357
  PitchResult,
347
358
  RealtimeVoiceChangerConfigInput,
348
359
  RealtimeVoiceChangerPodConfig,
@@ -550,6 +561,18 @@ export function version(): string {
550
561
  return module.version();
551
562
  }
552
563
 
564
+ /**
565
+ * Aggregate native ABI version: the per-subsystem ABI macros folded into one
566
+ * 32-bit value. It bumps whenever any flat C POD layout changes, so callers can
567
+ * detect an incompatible prebuilt binary. Matches the Node/Python `abiVersion()`.
568
+ */
569
+ export function abiVersion(): number {
570
+ if (!module) {
571
+ throw new Error('Module not initialized. Call init() first.');
572
+ }
573
+ return module.abiVersion();
574
+ }
575
+
553
576
  export function engineAbiVersion(): number {
554
577
  if (!module) {
555
578
  throw new Error('Module not initialized. Call init() first.');