@libraz/libsonare 1.4.1 → 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.
- package/README.md +50 -19
- package/dist/index.d.ts +5384 -1
- package/dist/index.js +867 -573
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +1083 -5227
- package/dist/worklet.js +2677 -2452
- package/dist/worklet.js.map +1 -1
- package/package.json +4 -9
- package/src/clip_page_streamer.ts +250 -0
- package/src/effects_mastering.ts +85 -1089
- package/src/effects_transform.ts +286 -0
- package/src/effects_voice_change.ts +118 -0
- package/src/feature_spectrogram.ts +42 -2
- package/src/features.ts +1 -0
- package/src/index.ts +11 -0
- package/src/mastering_chain.ts +200 -0
- package/src/mastering_core.ts +248 -0
- package/src/mastering_dynamics.ts +105 -0
- package/src/mastering_repair.ts +161 -0
- package/src/mixing_oneshot.ts +54 -0
- package/src/module_state.ts +1 -2
- package/src/project.ts +71 -1712
- package/src/project_class.ts +861 -0
- package/src/project_internal.ts +332 -0
- package/src/project_synth.ts +43 -0
- package/src/project_types.ts +570 -0
- package/src/public_types.ts +6 -1221
- package/src/public_types_acoustic.ts +115 -0
- package/src/public_types_mastering.ts +333 -0
- package/src/public_types_mixing.ts +97 -0
- package/src/public_types_music.ts +352 -0
- package/src/public_types_realtime.ts +163 -0
- package/src/public_types_spectral.ts +194 -0
- package/src/realtime_engine.ts +94 -0
- package/src/sonare.js.d.ts +72 -0
- package/src/worklet/engine-automation.ts +73 -0
- package/src/worklet/engine-capture-facade.ts +80 -0
- package/src/worklet/engine-clips.ts +71 -0
- package/src/worklet/engine-markers.ts +93 -0
- package/src/worklet/engine-mixer-facade.ts +186 -0
- package/src/worklet/engine-node.ts +451 -0
- package/src/worklet/engine-offline.ts +162 -0
- package/src/worklet/engine-options.ts +13 -0
- package/src/worklet/engine-parameter-facade.ts +172 -0
- package/src/worklet/engine-processor.ts +764 -0
- package/src/worklet/engine-register.ts +136 -0
- package/src/worklet/engine-strips.ts +315 -0
- package/src/worklet/engine-sync.ts +94 -0
- package/src/worklet/engine-tempo-facade.ts +141 -0
- package/src/worklet/engine.ts +998 -0
- package/src/worklet/guards.ts +14 -1
- package/src/worklet/messages.ts +60 -20
- package/src/worklet/mixer-processor.ts +368 -0
- package/src/worklet/protocol.ts +3 -0
- package/src/worklet/voice-changer-processor.ts +246 -0
- package/src/worklet.ts +20 -3549
- package/dist/sonare-rt-module.js +0 -2
- package/dist/sonare-rt.js +0 -2
- package/dist/sonare-rt.wasm +0 -0
- 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
|
+
}
|
|
@@ -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(
|
|
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 (
|
|
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
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,
|