@libraz/libsonare 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -7,7 +7,13 @@
7
7
  [![License](https://img.shields.io/github/license/libraz/libsonare)](https://github.com/libraz/libsonare/blob/main/LICENSE)
8
8
  [![PyPI](https://img.shields.io/pypi/v/libsonare?label=PyPI)](https://pypi.org/project/libsonare/)
9
9
 
10
- Fast, dependency-free audio analysis library for browser and Node.js via WebAssembly.
10
+ A dependency-free audio DSP toolkit for browser and Node.js via WebAssembly
11
+ librosa-compatible analysis plus broadcast-grade mastering, mixing, and editing.
12
+ The same C++ processors run client-side in the browser: 77 named mastering DSP
13
+ processors implemented against published references (ITU-R BS.1770-4 true-peak
14
+ limiting, Linkwitz-Riley crossovers, Vicanek matched-Z biquads, ADAA-antialiased
15
+ saturation), with analysis defaults matching librosa — Apache-2.0, no Python,
16
+ no model weights.
11
17
 
12
18
  > **Audio input:** This package expects already-decoded `Float32Array` mono
13
19
  > samples (it does not bundle a file decoder). Use the Web Audio API in the
@@ -34,10 +40,37 @@ const key = detectKey(samples, sampleRate);
34
40
  const result = analyze(samples, sampleRate);
35
41
  console.log(`BPM: ${result.bpm}, Key: ${result.key.name}`);
36
42
 
43
+ // Advanced key options are opt-in; defaults preserve existing behavior.
44
+ const keyWithOptions = detectKey(samples, sampleRate, {
45
+ useHpss: true,
46
+ loudnessWeighted: true,
47
+ highPassHz: 80,
48
+ nFft: 4096,
49
+ hopLength: 512,
50
+ });
51
+
37
52
  // Audio class API
38
53
  const audio = Audio.fromBuffer(samples, sampleRate);
39
54
  console.log(`BPM: ${audio.detectBpm()}`);
40
55
  console.log(`Key: ${audio.detectKey().name}`);
56
+ const audioKeyWithOptions = audio.detectKey({ useHpss: true, highPassHz: 80 });
57
+ ```
58
+
59
+ ### Room acoustics
60
+
61
+ Use `detectAcoustic` for blind RT60/EDT estimation from ordinary audio.
62
+ Use `analyzeImpulseResponse` when you have a measured impulse response and need
63
+ clarity metrics (`c50`, `c80`, `d50`). Blind mode returns `NaN` for clarity
64
+ metrics because they are not reliable without an impulse response.
65
+
66
+ ```typescript
67
+ import { init, analyzeImpulseResponse, detectAcoustic } from '@libraz/libsonare';
68
+
69
+ await init();
70
+
71
+ const blind = detectAcoustic(samples, sampleRate, 6, 24, 30.0, 10.0);
72
+ const room = analyzeImpulseResponse(irSamples, sampleRate);
73
+ console.log(blind.rt60, room.c50);
41
74
  ```
42
75
 
43
76
  ### Decoding files in the browser
@@ -169,7 +202,7 @@ import { init, masterAudio, masteringPresetNames } from '@libraz/libsonare';
169
202
 
170
203
  await init();
171
204
 
172
- masteringPresetNames(); // ['pop', 'edm', 'acoustic', 'hipHop', 'aiMusic', 'speech']
205
+ masteringPresetNames(); // ['pop', 'edm', 'acoustic', 'hipHop', 'aiMusic', 'speech', 'streaming', 'youtube', 'broadcast', 'podcast', 'audiobook', 'cinema', 'jpop', 'ambient', 'lofi', 'classical', 'drumAndBass', 'techno', 'metal', 'trap', 'rnb', 'jazz', 'kpop', 'trance', 'gameOst']
173
206
 
174
207
  const result = masterAudio(samples, sampleRate, 'aiMusic', {
175
208
  // optional flat overrides applied on top of the preset (dot notation)
@@ -178,6 +211,135 @@ const result = masterAudio(samples, sampleRate, 'aiMusic', {
178
211
  console.log(result.outputLufs, result.appliedGainDb);
179
212
  ```
180
213
 
214
+ ### Mixing
215
+
216
+ ```typescript
217
+ import { init, Mixer, mixStereo, mixingScenePresetJson } from '@libraz/libsonare';
218
+
219
+ await init();
220
+
221
+ const sceneJson = mixingScenePresetJson('vocalReverbSend');
222
+ const offline = mixStereo([vocalL, musicL], [vocalR, musicR], sampleRate, {
223
+ inputTrimDb: [3, 0],
224
+ faderDb: [-3, -12],
225
+ pan: [0, -0.2],
226
+ width: [1, 0.9],
227
+ });
228
+
229
+ const mixer = Mixer.fromSceneJson(sceneJson, sampleRate, 512);
230
+ const block = mixer.processStereo([vocalBlockL, returnBlockL], [vocalBlockR, returnBlockR]);
231
+ console.log(offline.meters[0].maxTruePeakDb, block.left.length);
232
+
233
+ const outL = new Float32Array(512);
234
+ const outR = new Float32Array(512);
235
+ mixer.processStereoInto([vocalBlockL, returnBlockL], [vocalBlockR, returnBlockR], outL, outR);
236
+
237
+ const realtime = mixer.createRealtimeBuffer();
238
+ realtime.leftInputs[0].set(vocalBlockL);
239
+ realtime.rightInputs[0].set(vocalBlockR);
240
+ realtime.leftInputs[1].set(returnBlockL);
241
+ realtime.rightInputs[1].set(returnBlockR);
242
+ realtime.process();
243
+ console.log(realtime.outLeft[0], realtime.outRight[0]);
244
+ mixer.delete();
245
+ ```
246
+
247
+ ### AudioWorklet bridge
248
+
249
+ The package exposes an optional worklet entry that uses the same `sonare.wasm`
250
+ as the offline API. The bridge processes fixed 128-sample render quanta and
251
+ treats each AudioWorklet input as one stereo mixer strip.
252
+
253
+ ```typescript
254
+ // worklet.ts, loaded with audioContext.audioWorklet.addModule(...)
255
+ import { init, mixingScenePresetJson } from '@libraz/libsonare';
256
+ import { registerSonareWorkletProcessor } from '@libraz/libsonare/worklet';
257
+
258
+ await init();
259
+ registerSonareWorkletProcessor();
260
+ ```
261
+
262
+ ```typescript
263
+ // main thread
264
+ import { mixingScenePresetJson } from '@libraz/libsonare';
265
+
266
+ await audioContext.audioWorklet.addModule('/worklet.js');
267
+ const sceneJson = mixingScenePresetJson('vocalReverbSend');
268
+ const node = new AudioWorkletNode(audioContext, 'sonare-worklet-processor', {
269
+ numberOfInputs: 2,
270
+ numberOfOutputs: 1,
271
+ outputChannelCount: [2],
272
+ processorOptions: {
273
+ sceneJson,
274
+ sampleRate: audioContext.sampleRate,
275
+ blockSize: 128,
276
+ spectrumIntervalFrames: 2048,
277
+ spectrumBands: 16,
278
+ },
279
+ });
280
+
281
+ node.port.postMessage({
282
+ type: 'scheduleInsertAutomation',
283
+ stripIndex: 0,
284
+ insertIndex: 0,
285
+ paramId: 0,
286
+ samplePos: 0,
287
+ value: 0,
288
+ curve: 'linear',
289
+ });
290
+
291
+ node.port.onmessage = (event) => {
292
+ if (event.data?.type === 'meter') {
293
+ console.log(event.data.peakDbL, event.data.rmsDbL, event.data.correlation);
294
+ } else if (event.data?.type === 'spectrum') {
295
+ console.log(event.data.frame, event.data.bands);
296
+ }
297
+ };
298
+ ```
299
+
300
+ For cross-origin-isolated pages, meters and spectrum snapshots can use optional
301
+ SharedArrayBuffer rings instead of per-message `postMessage`:
302
+
303
+ ```typescript
304
+ import {
305
+ createSonareMeterRingBuffer,
306
+ createSonareSpectrumRingBuffer,
307
+ readSonareMeterRingBuffer,
308
+ readSonareSpectrumRingBuffer,
309
+ } from '@libraz/libsonare/worklet';
310
+
311
+ const meterRing = createSonareMeterRingBuffer(128);
312
+ const spectrumRing = createSonareSpectrumRingBuffer(64, 16);
313
+ const node = new AudioWorkletNode(audioContext, 'sonare-worklet-processor', {
314
+ numberOfInputs: 2,
315
+ numberOfOutputs: 1,
316
+ outputChannelCount: [2],
317
+ processorOptions: {
318
+ sceneJson,
319
+ sampleRate: audioContext.sampleRate,
320
+ blockSize: 128,
321
+ meterSharedBuffer: meterRing.sharedBuffer,
322
+ spectrumIntervalFrames: 2048,
323
+ spectrumSharedBuffer: spectrumRing.sharedBuffer,
324
+ },
325
+ });
326
+
327
+ let nextMeterRead = 0;
328
+ let nextSpectrumRead = 0;
329
+ function readMeters() {
330
+ const result = readSonareMeterRingBuffer(meterRing, nextMeterRead);
331
+ nextMeterRead = result.nextReadIndex;
332
+ for (const meter of result.meters) {
333
+ console.log(meter.frame, meter.peakDbL, meter.rmsDbL);
334
+ }
335
+ const spectra = readSonareSpectrumRingBuffer(spectrumRing, nextSpectrumRead);
336
+ nextSpectrumRead = spectra.nextReadIndex;
337
+ for (const spectrum of spectra.spectra) {
338
+ console.log(spectrum.frame, spectrum.bands);
339
+ }
340
+ }
341
+ ```
342
+
181
343
  ### Progress callback
182
344
 
183
345
  `masteringChainWithProgress` (and its stereo variant) is `masteringChain` with