@libraz/libsonare 1.4.0 → 1.5.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.
Files changed (65) hide show
  1. package/README.md +50 -19
  2. package/dist/index.d.ts +5384 -1
  3. package/dist/index.js +884 -574
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare.js +1 -1
  6. package/dist/sonare.wasm +0 -0
  7. package/dist/worklet.d.ts +1084 -5220
  8. package/dist/worklet.js +2677 -2452
  9. package/dist/worklet.js.map +1 -1
  10. package/package.json +4 -9
  11. package/src/clip_page_streamer.ts +250 -0
  12. package/src/effects_mastering.ts +85 -1082
  13. package/src/effects_transform.ts +286 -0
  14. package/src/effects_voice_change.ts +118 -0
  15. package/src/feature_music.ts +13 -9
  16. package/src/feature_spectrogram.ts +42 -2
  17. package/src/features.ts +1 -0
  18. package/src/index.ts +11 -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/mixing_oneshot.ts +54 -0
  24. package/src/module_state.ts +1 -2
  25. package/src/project.ts +71 -1688
  26. package/src/project_class.ts +861 -0
  27. package/src/project_internal.ts +332 -0
  28. package/src/project_synth.ts +43 -0
  29. package/src/project_types.ts +570 -0
  30. package/src/public_types.ts +6 -1217
  31. package/src/public_types_acoustic.ts +115 -0
  32. package/src/public_types_mastering.ts +333 -0
  33. package/src/public_types_mixing.ts +97 -0
  34. package/src/public_types_music.ts +352 -0
  35. package/src/public_types_realtime.ts +163 -0
  36. package/src/public_types_spectral.ts +194 -0
  37. package/src/quick_analysis.ts +18 -14
  38. package/src/realtime_engine.ts +94 -0
  39. package/src/realtime_voice_changer.ts +2 -1
  40. package/src/sonare.js.d.ts +74 -0
  41. package/src/worklet/engine-automation.ts +73 -0
  42. package/src/worklet/engine-capture-facade.ts +80 -0
  43. package/src/worklet/engine-clips.ts +71 -0
  44. package/src/worklet/engine-markers.ts +93 -0
  45. package/src/worklet/engine-mixer-facade.ts +186 -0
  46. package/src/worklet/engine-node.ts +451 -0
  47. package/src/worklet/engine-offline.ts +162 -0
  48. package/src/worklet/engine-options.ts +13 -0
  49. package/src/worklet/engine-parameter-facade.ts +172 -0
  50. package/src/worklet/engine-processor.ts +764 -0
  51. package/src/worklet/engine-register.ts +136 -0
  52. package/src/worklet/engine-strips.ts +315 -0
  53. package/src/worklet/engine-sync.ts +94 -0
  54. package/src/worklet/engine-tempo-facade.ts +141 -0
  55. package/src/worklet/engine.ts +998 -0
  56. package/src/worklet/guards.ts +14 -1
  57. package/src/worklet/messages.ts +60 -20
  58. package/src/worklet/mixer-processor.ts +368 -0
  59. package/src/worklet/protocol.ts +3 -0
  60. package/src/worklet/voice-changer-processor.ts +246 -0
  61. package/src/worklet.ts +20 -3549
  62. package/dist/sonare-rt-module.js +0 -2
  63. package/dist/sonare-rt.js +0 -2
  64. package/dist/sonare-rt.wasm +0 -0
  65. 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
+ sampleRate?: number;
44
+ /** Voice-changer preset id or full config object. */
45
+ preset?: RealtimeVoiceChangerConfigInput;
46
+ /** Channel count (1 = mono, 2 = interleaved stereo). */
47
+ channels?: 1 | 2;
48
+ /** Block size for the internal render loop (default 512). */
49
+ blockSize?: number;
50
+ }
51
+
52
+ function latencyCompensatedVoiceChange(
53
+ changer: RealtimeVoiceChanger,
54
+ samples: Float32Array,
55
+ channels: 1 | 2,
56
+ blockFrames: number,
57
+ ): Float32Array {
58
+ const latencyFrames = Math.max(0, changer.latencySamples());
59
+ if (channels === 1) {
60
+ const total = samples.length + latencyFrames;
61
+ const input = new Float32Array(total);
62
+ input.set(samples);
63
+ const processed = new Float32Array(total);
64
+ for (let offset = 0; offset < total; offset += blockFrames) {
65
+ const block = input.subarray(offset, Math.min(offset + blockFrames, total));
66
+ processed.set(changer.processMono(block), offset);
67
+ }
68
+ return processed.slice(latencyFrames, latencyFrames + samples.length);
69
+ }
70
+
71
+ const frames = samples.length / 2;
72
+ const totalFrames = frames + latencyFrames;
73
+ const input = new Float32Array(totalFrames * 2);
74
+ input.set(samples);
75
+ const processed = new Float32Array(totalFrames * 2);
76
+ const frameStride = blockFrames * 2;
77
+ for (let offset = 0; offset < input.length; offset += frameStride) {
78
+ const block = input.subarray(offset, Math.min(offset + frameStride, input.length));
79
+ processed.set(changer.processInterleaved(block, 2), offset);
80
+ }
81
+ const start = latencyFrames * 2;
82
+ return processed.slice(start, start + samples.length);
83
+ }
84
+
85
+ /**
86
+ * Applies the realtime voice-changer chain to a whole buffer in one call.
87
+ *
88
+ * Constructs and prepares a {@link RealtimeVoiceChanger}, runs the block loop
89
+ * for the caller, then disposes it — matching the Python `voice_change_realtime`
90
+ * and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
91
+ * plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
92
+ *
93
+ * @returns The processed buffer (same layout/length as the input).
94
+ */
95
+ export function voiceChangeRealtime(
96
+ samples: Float32Array,
97
+ options: VoiceChangeRealtimeOptions = {},
98
+ ): Float32Array {
99
+ assertSamples('voiceChangeRealtime', samples, options.validate !== false);
100
+ const channels = options.channels ?? 1;
101
+ if (channels !== 1 && channels !== 2) {
102
+ throw new Error('voiceChangeRealtime: channels must be 1 or 2.');
103
+ }
104
+ if (channels === 2 && samples.length % 2 !== 0) {
105
+ throw new Error('voiceChangeRealtime: stereo input length must be a multiple of 2.');
106
+ }
107
+ // 48000 matches the Python voice_change_realtime and Node voiceChangeRealtime
108
+ // convenience wrappers (and the RealtimeVoiceChanger default).
109
+ const sampleRate = options.sampleRate ?? 48000;
110
+ const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
111
+ const changer = new RealtimeVoiceChanger(options.preset ?? 'neutral-monitor');
112
+ try {
113
+ changer.prepare(sampleRate, blockSize, channels);
114
+ return latencyCompensatedVoiceChange(changer, samples, channels, blockSize);
115
+ } finally {
116
+ changer.delete();
117
+ }
118
+ }
@@ -204,15 +204,19 @@ export function analyzeSections(
204
204
  if ((options.minSectionSec ?? 4.0) <= 0) {
205
205
  throw new RangeError('analyzeSections: minSectionSec must be positive');
206
206
  }
207
- return requireModule()
208
- .analyzeSections(
209
- samples,
210
- sampleRate,
211
- options.nFft ?? 2048,
212
- options.hopLength ?? 512,
213
- options.minSectionSec ?? 4.0,
214
- )
215
- .map((s) => ({ ...s, type: s.type as SectionType }));
207
+ // The embind value marshalling returns an array whose constructor is not this
208
+ // realm's Array; chaining .map() onto it propagates that constructor via
209
+ // Symbol.species, leaving a result that structuredClone (and so postMessage to
210
+ // a Worker) rejects with "could not be cloned". Array.from() re-roots it as a
211
+ // plain Array before mapping.
212
+ const sections = requireModule().analyzeSections(
213
+ samples,
214
+ sampleRate,
215
+ options.nFft ?? 2048,
216
+ options.hopLength ?? 512,
217
+ options.minSectionSec ?? 4.0,
218
+ );
219
+ return Array.from(sections, (s) => ({ ...s, type: s.type as SectionType }));
216
220
  }
217
221
 
218
222
  /** Options for {@link analyzeMelody}. All fields are optional. */
@@ -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,