@libraz/libsonare 1.2.3 → 1.3.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 +38 -1
- package/dist/index.d.ts +1 -2842
- package/dist/index.js +3602 -1934
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +1 -1
- package/dist/sonare-rt.js +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +4816 -483
- package/dist/worklet.js +747 -440
- package/dist/worklet.js.map +1 -1
- package/package.json +2 -1
- package/src/analysis_helpers.ts +152 -0
- package/src/audio.ts +493 -0
- package/src/codes.ts +56 -0
- package/src/effects_mastering.ts +964 -0
- package/src/feature_core.ts +248 -0
- package/src/feature_music.ts +419 -0
- package/src/feature_pitch.ts +80 -0
- package/src/feature_resample.ts +21 -0
- package/src/feature_spectral.ts +330 -0
- package/src/feature_spectrogram.ts +454 -0
- package/src/features.ts +84 -0
- package/src/index.ts +341 -4963
- package/src/live_audio.ts +45 -0
- package/src/metering.ts +380 -0
- package/src/mixer.ts +523 -0
- package/src/module_state.ts +14 -0
- package/src/opfs_clip_pages.ts +188 -0
- package/src/project.ts +1614 -0
- package/src/public_types.ts +177 -2
- package/src/quick_analysis.ts +508 -0
- package/src/realtime_engine.ts +667 -0
- package/src/realtime_voice_changer.ts +275 -0
- package/src/scale.ts +42 -0
- package/src/sonare.js.d.ts +302 -4
- package/src/stream_analyzer.ts +275 -0
- package/src/stream_types.ts +26 -1
- package/src/streaming_mixing.ts +18 -0
- package/src/streaming_processors.ts +335 -0
- package/src/validation.ts +82 -0
- package/src/web_midi.ts +367 -0
|
@@ -0,0 +1,45 @@
|
|
|
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 =
|
|
20
|
+
options.stream ??
|
|
21
|
+
(await navigator.mediaDevices.getUserMedia({
|
|
22
|
+
audio: options.audio ?? true,
|
|
23
|
+
video: false,
|
|
24
|
+
}));
|
|
25
|
+
const source = context.createMediaStreamSource(stream);
|
|
26
|
+
const node = 'node' in engine ? engine.node : engine;
|
|
27
|
+
source.connect(node);
|
|
28
|
+
let closed = false;
|
|
29
|
+
return {
|
|
30
|
+
stream,
|
|
31
|
+
source,
|
|
32
|
+
close() {
|
|
33
|
+
if (closed) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
closed = true;
|
|
37
|
+
source.disconnect();
|
|
38
|
+
if (options.stopTracksOnClose !== false) {
|
|
39
|
+
for (const track of stream.getAudioTracks()) {
|
|
40
|
+
track.stop();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/src/metering.ts
ADDED
|
@@ -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
|
+
}
|