@libraz/libsonare 1.2.3 → 1.3.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 (43) hide show
  1. package/README.md +38 -1
  2. package/dist/index.d.ts +1 -2842
  3. package/dist/index.js +3667 -1984
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare-rt-module.js +1 -1
  6. package/dist/sonare-rt.js +1 -1
  7. package/dist/sonare-rt.wasm +0 -0
  8. package/dist/sonare.js +1 -1
  9. package/dist/sonare.wasm +0 -0
  10. package/dist/worklet.d.ts +4816 -483
  11. package/dist/worklet.js +747 -440
  12. package/dist/worklet.js.map +1 -1
  13. package/package.json +2 -1
  14. package/src/analysis_helpers.ts +152 -0
  15. package/src/audio.ts +493 -0
  16. package/src/codes.ts +56 -0
  17. package/src/effects_mastering.ts +964 -0
  18. package/src/feature_core.ts +248 -0
  19. package/src/feature_music.ts +419 -0
  20. package/src/feature_pitch.ts +80 -0
  21. package/src/feature_resample.ts +21 -0
  22. package/src/feature_spectral.ts +330 -0
  23. package/src/feature_spectrogram.ts +454 -0
  24. package/src/features.ts +84 -0
  25. package/src/index.ts +341 -4963
  26. package/src/live_audio.ts +47 -0
  27. package/src/metering.ts +380 -0
  28. package/src/mixer.ts +523 -0
  29. package/src/module_state.ts +14 -0
  30. package/src/opfs_clip_pages.ts +203 -0
  31. package/src/project.ts +1614 -0
  32. package/src/public_types.ts +177 -2
  33. package/src/quick_analysis.ts +508 -0
  34. package/src/realtime_engine.ts +667 -0
  35. package/src/realtime_voice_changer.ts +275 -0
  36. package/src/scale.ts +42 -0
  37. package/src/sonare.js.d.ts +302 -4
  38. package/src/stream_analyzer.ts +275 -0
  39. package/src/stream_types.ts +26 -1
  40. package/src/streaming_mixing.ts +18 -0
  41. package/src/streaming_processors.ts +335 -0
  42. package/src/validation.ts +82 -0
  43. package/src/web_midi.ts +366 -0
@@ -0,0 +1,47 @@
1
+ import type { SonareRealtimeEngineNode } from './worklet';
2
+
3
+ export interface BindMicrophoneInputOptions extends MediaStreamConstraints {
4
+ stream?: MediaStream;
5
+ stopTracksOnClose?: boolean;
6
+ }
7
+
8
+ export interface MicrophoneInputBinding {
9
+ stream: MediaStream;
10
+ source: MediaStreamAudioSourceNode;
11
+ close(): void;
12
+ }
13
+
14
+ export async function bindMicrophoneInput(
15
+ context: AudioContext,
16
+ engine: SonareRealtimeEngineNode | AudioWorkletNode,
17
+ options: BindMicrophoneInputOptions = {},
18
+ ): Promise<MicrophoneInputBinding> {
19
+ const { stream: providedStream, stopTracksOnClose = true, ...constraints } = options;
20
+ const stream =
21
+ providedStream ??
22
+ (await navigator.mediaDevices.getUserMedia({
23
+ ...constraints,
24
+ audio: constraints.audio ?? true,
25
+ video: constraints.video ?? false,
26
+ }));
27
+ const source = context.createMediaStreamSource(stream);
28
+ const node = 'node' in engine ? engine.node : engine;
29
+ source.connect(node);
30
+ let closed = false;
31
+ return {
32
+ stream,
33
+ source,
34
+ close() {
35
+ if (closed) {
36
+ return;
37
+ }
38
+ closed = true;
39
+ source.disconnect();
40
+ if (stopTracksOnClose) {
41
+ for (const track of stream.getAudioTracks()) {
42
+ track.stop();
43
+ }
44
+ }
45
+ },
46
+ };
47
+ }
@@ -0,0 +1,380 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type { ValidateOptions } from './validation';
3
+ import { assertSamples } from './validation';
4
+
5
+ // ============================================================================
6
+ // Metering — basic / true-peak / clipping / dynamic range
7
+ // ============================================================================
8
+
9
+ /** One contiguous run of clipped samples reported by `meteringDetectClipping`. */
10
+ export interface ClippingRegion {
11
+ startSample: number;
12
+ endSample: number;
13
+ length: number;
14
+ peak: number;
15
+ }
16
+
17
+ /** Aggregated clipping report. */
18
+ export interface ClippingReport {
19
+ clippedSamples: number;
20
+ clippingRatio: number;
21
+ maxClippedPeak: number;
22
+ regions: ClippingRegion[];
23
+ }
24
+
25
+ /** Sliding-window dynamic range report. */
26
+ export interface DynamicRangeReport {
27
+ dynamicRangeDb: number;
28
+ lowPercentileDb: number;
29
+ highPercentileDb: number;
30
+ windowRmsDb: Float32Array;
31
+ }
32
+
33
+ /** Options for {@link meteringDetectClipping}. All fields are optional. */
34
+ export interface MeteringDetectClippingOptions extends ValidateOptions {
35
+ /** Linear absolute threshold. Default 0.999. */
36
+ threshold?: number;
37
+ /** Minimum run length to report. Default 1. */
38
+ minRegionSamples?: number;
39
+ }
40
+
41
+ /** Options for {@link meteringDynamicRange}. All fields are optional. */
42
+ export interface MeteringDynamicRangeOptions extends ValidateOptions {
43
+ /** Window length in seconds (0 = library default, 3 s). Default 0. */
44
+ windowSec?: number;
45
+ /** Hop length in seconds (0 = library default, 1 s). Default 0. */
46
+ hopSec?: number;
47
+ /** Low percentile in [0,1] (negative = library default, 0.10). Default -1. */
48
+ lowPercentile?: number;
49
+ /** High percentile in [0,1] (negative = library default, 0.95). Default -1. */
50
+ highPercentile?: number;
51
+ }
52
+
53
+ function requireModule() {
54
+ return getSonareModule();
55
+ }
56
+
57
+ export function meteringPeakDb(
58
+ samples: Float32Array,
59
+ sampleRate = 22050,
60
+ options: ValidateOptions = {},
61
+ ): number {
62
+ assertSamples('meteringPeakDb', samples, options.validate !== false);
63
+ return requireModule().meteringPeakDb(samples, sampleRate);
64
+ }
65
+
66
+ export function meteringRmsDb(
67
+ samples: Float32Array,
68
+ sampleRate = 22050,
69
+ options: ValidateOptions = {},
70
+ ): number {
71
+ assertSamples('meteringRmsDb', samples, options.validate !== false);
72
+ return requireModule().meteringRmsDb(samples, sampleRate);
73
+ }
74
+
75
+ export function meteringCrestFactorDb(
76
+ samples: Float32Array,
77
+ sampleRate = 22050,
78
+ options: ValidateOptions = {},
79
+ ): number {
80
+ assertSamples('meteringCrestFactorDb', samples, options.validate !== false);
81
+ return requireModule().meteringCrestFactorDb(samples, sampleRate);
82
+ }
83
+
84
+ export function meteringDcOffset(
85
+ samples: Float32Array,
86
+ sampleRate = 22050,
87
+ options: ValidateOptions = {},
88
+ ): number {
89
+ assertSamples('meteringDcOffset', samples, options.validate !== false);
90
+ return requireModule().meteringDcOffset(samples, sampleRate);
91
+ }
92
+
93
+ /**
94
+ * Inter-sample (true) peak in dBFS. `oversampleFactor` must be a power of two
95
+ * in [1, 16]; pass 0 to use the library default (4).
96
+ */
97
+ export function meteringTruePeakDb(
98
+ samples: Float32Array,
99
+ sampleRate = 22050,
100
+ oversampleFactor = 4,
101
+ options: ValidateOptions = {},
102
+ ): number {
103
+ assertSamples('meteringTruePeakDb', samples, options.validate !== false);
104
+ const factor = oversampleFactor === 0 ? 4 : oversampleFactor;
105
+ if (factor < 1 || factor > 16 || (factor & (factor - 1)) !== 0) {
106
+ throw new RangeError(
107
+ 'meteringTruePeakDb: oversampleFactor must be 0 or a power of two from 1 to 16',
108
+ );
109
+ }
110
+ return requireModule().meteringTruePeakDb(samples, sampleRate, oversampleFactor);
111
+ }
112
+
113
+ /**
114
+ * Detect contiguous runs of clipped samples.
115
+ *
116
+ * @param threshold Linear absolute threshold (default 0.999).
117
+ * @param minRegionSamples Minimum run length to report (default 1).
118
+ */
119
+ export function meteringDetectClipping(
120
+ samples: Float32Array,
121
+ sampleRate = 22050,
122
+ options: MeteringDetectClippingOptions = {},
123
+ ): ClippingReport {
124
+ assertSamples('meteringDetectClipping', samples, options.validate !== false);
125
+ return requireModule().meteringDetectClipping(
126
+ samples,
127
+ sampleRate,
128
+ options.threshold ?? 0.999,
129
+ options.minRegionSamples ?? 1,
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Sliding-window dynamic range. Pass 0 for window/hop to use the library
135
+ * default (window=3 s, hop=1 s). The percentiles use a NEGATIVE sentinel for
136
+ * "use the library default" (low=0.10, high=0.95) because 0 is a literal 0th
137
+ * percentile; omitted percentiles therefore default to -1.
138
+ */
139
+ export function meteringDynamicRange(
140
+ samples: Float32Array,
141
+ sampleRate = 22050,
142
+ options: MeteringDynamicRangeOptions = {},
143
+ ): DynamicRangeReport {
144
+ assertSamples('meteringDynamicRange', samples, options.validate !== false);
145
+ return requireModule().meteringDynamicRange(
146
+ samples,
147
+ sampleRate,
148
+ options.windowSec ?? 0,
149
+ options.hopSec ?? 0,
150
+ options.lowPercentile ?? -1,
151
+ options.highPercentile ?? -1,
152
+ );
153
+ }
154
+
155
+ // ============================================================================
156
+ // Metering — stereo / phase-scope / spectrum
157
+ // ============================================================================
158
+
159
+ /** Mid/side vectorscope point series for a (left, right) stereo pair. */
160
+ export interface VectorscopeReport {
161
+ mid: Float32Array;
162
+ side: Float32Array;
163
+ }
164
+
165
+ /** Phase-scope (Lissajous) point series plus summary stats. */
166
+ export interface PhaseScopeReport {
167
+ mid: Float32Array;
168
+ side: Float32Array;
169
+ radius: Float32Array;
170
+ angleRad: Float32Array;
171
+ correlation: number;
172
+ averageAbsAngleRad: number;
173
+ maxRadius: number;
174
+ }
175
+
176
+ /** Options for `meteringSpectrum`. */
177
+ export interface SpectrumOptions {
178
+ /** FFT size. Pass 0 / omit for the library default (2048). */
179
+ nFft?: number;
180
+ /** Apply fractional-octave smoothing to magnitude. */
181
+ applyOctaveSmoothing?: boolean;
182
+ /** Smoothing fraction (e.g. 3 = 1/3-octave). 0 / omit = library default (3). */
183
+ octaveFraction?: number;
184
+ /** Linear reference for the dB conversion. 0 / omit = 1.0. */
185
+ dbRef?: number;
186
+ /** Linear floor used to avoid log(0). 0 / omit = library default. */
187
+ dbAmin?: number;
188
+ }
189
+
190
+ /** Single-frame magnitude / power / dB spectrum returned by `meteringSpectrum`. */
191
+ export interface SpectrumReport {
192
+ frequencies: Float32Array;
193
+ magnitude: Float32Array;
194
+ power: Float32Array;
195
+ db: Float32Array;
196
+ nFft: number;
197
+ sampleRate: number;
198
+ }
199
+
200
+ /** Options for {@link waveformPeaks}. All fields are optional. */
201
+ export interface WaveformPeaksOptions extends ValidateOptions {
202
+ /** Bucket width in frames. Default 512. */
203
+ samplesPerBucket?: number;
204
+ }
205
+
206
+ /** Options for {@link waveformPeakPyramid}. All fields are optional. */
207
+ export interface WaveformPeakPyramidOptions extends ValidateOptions {
208
+ /** Bucket widths in frames, one per zoom level. Default [512, 1024, 2048, 4096]. */
209
+ samplesPerBucketLevels?: number[];
210
+ }
211
+
212
+ /** Per-channel min/max waveform buckets. Arrays are channel-major. */
213
+ export interface WaveformPeaksReport {
214
+ min: Float32Array;
215
+ max: Float32Array;
216
+ channels: number;
217
+ bucketCount: number;
218
+ samplesPerBucket: number;
219
+ }
220
+
221
+ /** Pearson correlation in [-1, 1] between two equal-length channels. */
222
+ export function meteringStereoCorrelation(
223
+ left: Float32Array,
224
+ right: Float32Array,
225
+ sampleRate = 22050,
226
+ options: ValidateOptions = {},
227
+ ): number {
228
+ const validate = options.validate !== false;
229
+ assertSamples('meteringStereoCorrelation', left, validate, 'left');
230
+ assertSamples('meteringStereoCorrelation', right, validate, 'right');
231
+ return requireModule().meteringStereoCorrelation(left, right, sampleRate);
232
+ }
233
+
234
+ /**
235
+ * Side / mid energy ratio, clamped to `[0, 2]`: 0 = pure mono, ~1 = wide
236
+ * stereo, 2 = fully decorrelated / out-of-phase.
237
+ */
238
+ export function meteringStereoWidth(
239
+ left: Float32Array,
240
+ right: Float32Array,
241
+ sampleRate = 22050,
242
+ options: ValidateOptions = {},
243
+ ): number {
244
+ const validate = options.validate !== false;
245
+ assertSamples('meteringStereoWidth', left, validate, 'left');
246
+ assertSamples('meteringStereoWidth', right, validate, 'right');
247
+ return requireModule().meteringStereoWidth(left, right, sampleRate);
248
+ }
249
+
250
+ /** Per-sample mid/side point series (one entry per input frame). */
251
+ export function meteringVectorscope(
252
+ left: Float32Array,
253
+ right: Float32Array,
254
+ sampleRate = 22050,
255
+ options: ValidateOptions = {},
256
+ ): VectorscopeReport {
257
+ const validate = options.validate !== false;
258
+ assertSamples('meteringVectorscope', left, validate, 'left');
259
+ assertSamples('meteringVectorscope', right, validate, 'right');
260
+ return requireModule().meteringVectorscope(left, right, sampleRate);
261
+ }
262
+
263
+ /**
264
+ * Display-sized mid/side vectorscope. Like {@link meteringVectorscope} but the
265
+ * point series is deterministically decimated to at most `maxPoints` points
266
+ * (`0`, or a value `>= length`, yields one point per input sample). Mirrors the
267
+ * Node/Python decimated vectorscope.
268
+ */
269
+ export function meteringVectorscopeDecimated(
270
+ left: Float32Array,
271
+ right: Float32Array,
272
+ sampleRate = 22050,
273
+ maxPoints = 0,
274
+ options: ValidateOptions = {},
275
+ ): VectorscopeReport {
276
+ const validate = options.validate !== false;
277
+ assertSamples('meteringVectorscopeDecimated', left, validate, 'left');
278
+ assertSamples('meteringVectorscopeDecimated', right, validate, 'right');
279
+ return requireModule().meteringVectorscopeDecimated(left, right, sampleRate, maxPoints);
280
+ }
281
+
282
+ /** Phase-scope point series plus summary stats. */
283
+ export function meteringPhaseScope(
284
+ left: Float32Array,
285
+ right: Float32Array,
286
+ sampleRate = 22050,
287
+ options: ValidateOptions = {},
288
+ ): PhaseScopeReport {
289
+ const validate = options.validate !== false;
290
+ assertSamples('meteringPhaseScope', left, validate, 'left');
291
+ assertSamples('meteringPhaseScope', right, validate, 'right');
292
+ return requireModule().meteringPhaseScope(left, right, sampleRate);
293
+ }
294
+
295
+ /**
296
+ * Display-sized phase scope. Like {@link meteringPhaseScope} but the point
297
+ * series is deterministically decimated to at most `maxPoints` points (`0`, or
298
+ * a value `>= length`, yields one point per input sample). The summary stats are
299
+ * always computed over the full-resolution signal. Mirrors the Node/Python
300
+ * decimated phase scope.
301
+ */
302
+ export function meteringPhaseScopeDecimated(
303
+ left: Float32Array,
304
+ right: Float32Array,
305
+ sampleRate = 22050,
306
+ maxPoints = 0,
307
+ options: ValidateOptions = {},
308
+ ): PhaseScopeReport {
309
+ const validate = options.validate !== false;
310
+ assertSamples('meteringPhaseScopeDecimated', left, validate, 'left');
311
+ assertSamples('meteringPhaseScopeDecimated', right, validate, 'right');
312
+ return requireModule().meteringPhaseScopeDecimated(left, right, sampleRate, maxPoints);
313
+ }
314
+
315
+ /**
316
+ * Welch-averaged magnitude / power / dB spectrum over the WHOLE signal (split
317
+ * into Hann-windowed, 50%-overlapping `nFft`-length frames whose power spectra
318
+ * are averaged). For a true single-frame snapshot, use
319
+ * {@link meteringSpectrumFrame}.
320
+ */
321
+ export function meteringSpectrum(
322
+ samples: Float32Array,
323
+ sampleRate = 22050,
324
+ options?: SpectrumOptions & ValidateOptions,
325
+ ): SpectrumReport {
326
+ const validate = options?.validate !== false;
327
+ assertSamples('meteringSpectrum', samples, validate);
328
+ return requireModule().meteringSpectrum(samples, sampleRate, options ?? {});
329
+ }
330
+
331
+ /**
332
+ * True single-frame magnitude / power / dB spectrum (one Hann-windowed
333
+ * `nFft`-length FFT), for spectrum-analyzer "moment" snapshots that must not be
334
+ * time-averaged like {@link meteringSpectrum}. The analysis frame spans
335
+ * `[frameOffset, frameOffset + nFft)`; samples past the end are zero-padded.
336
+ */
337
+ export function meteringSpectrumFrame(
338
+ samples: Float32Array,
339
+ sampleRate = 22050,
340
+ frameOffset = 0,
341
+ options?: SpectrumOptions & ValidateOptions,
342
+ ): SpectrumReport {
343
+ const validate = options?.validate !== false;
344
+ assertSamples('meteringSpectrumFrame', samples, validate);
345
+ return requireModule().meteringSpectrumFrame(samples, sampleRate, frameOffset, options ?? {});
346
+ }
347
+
348
+ /** Compute per-channel min/max waveform buckets from interleaved audio. */
349
+ export function waveformPeaks(
350
+ samples: Float32Array,
351
+ channels: number,
352
+ options: WaveformPeaksOptions = {},
353
+ ): WaveformPeaksReport {
354
+ assertSamples('waveformPeaks', samples, options.validate !== false);
355
+ if (channels <= 0 || samples.length % channels !== 0) {
356
+ throw new RangeError('waveformPeaks: samples length must be a multiple of channels');
357
+ }
358
+ const samplesPerBucket = options.samplesPerBucket ?? 512;
359
+ if (samplesPerBucket <= 0) {
360
+ throw new RangeError('waveformPeaks: samplesPerBucket must be > 0');
361
+ }
362
+ return requireModule().waveformPeaks(samples, channels, samplesPerBucket);
363
+ }
364
+
365
+ /** Compute waveform peak buckets for several zoom levels. */
366
+ export function waveformPeakPyramid(
367
+ samples: Float32Array,
368
+ channels: number,
369
+ options: WaveformPeakPyramidOptions = {},
370
+ ): WaveformPeaksReport[] {
371
+ assertSamples('waveformPeakPyramid', samples, options.validate !== false);
372
+ if (channels <= 0 || samples.length % channels !== 0) {
373
+ throw new RangeError('waveformPeakPyramid: samples length must be a multiple of channels');
374
+ }
375
+ const levels = options.samplesPerBucketLevels ?? [512, 1024, 2048, 4096];
376
+ if (levels.length === 0 || levels.some((level) => level <= 0)) {
377
+ throw new RangeError('waveformPeakPyramid: samplesPerBucketLevels must be non-empty and > 0');
378
+ }
379
+ return requireModule().waveformPeakPyramid(samples, channels, levels);
380
+ }