@libraz/libsonare 1.5.5 → 1.6.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 (75) hide show
  1. package/README.md +109 -20
  2. package/dist/analysis.d.ts +5730 -0
  3. package/dist/analysis.js +2464 -0
  4. package/dist/analysis.js.map +1 -0
  5. package/dist/index.d.ts +759 -35
  6. package/dist/index.js +904 -209
  7. package/dist/index.js.map +1 -1
  8. package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
  9. package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
  10. package/dist/sonare-analysis.js +2 -0
  11. package/dist/sonare-analysis.wasm +0 -0
  12. package/dist/sonare.js +1 -1
  13. package/dist/sonare.wasm +0 -0
  14. package/dist/worker.d.ts +65 -0
  15. package/dist/worker.js +707 -0
  16. package/dist/worker.js.map +1 -0
  17. package/dist/worklet.d.ts +574 -86
  18. package/dist/worklet.js +1634 -235
  19. package/dist/worklet.js.map +1 -1
  20. package/package.json +23 -11
  21. package/src/_chain_config.ts +5 -3
  22. package/src/analysis.ts +137 -0
  23. package/src/analysis_helpers.ts +6 -0
  24. package/src/clip_page_streamer.ts +62 -8
  25. package/src/codes.ts +51 -64
  26. package/src/effects_mastering.ts +1 -0
  27. package/src/errors.ts +1 -0
  28. package/src/feature_core.ts +140 -2
  29. package/src/feature_music.ts +17 -2
  30. package/src/feature_pitch.ts +70 -1
  31. package/src/feature_spectral.ts +231 -5
  32. package/src/feature_spectrogram.ts +159 -0
  33. package/src/features.ts +16 -1
  34. package/src/index.ts +101 -7
  35. package/src/mastering_chain.ts +20 -8
  36. package/src/mastering_core.ts +5 -0
  37. package/src/mastering_dynamics.ts +7 -1
  38. package/src/metering.ts +6 -1
  39. package/src/opfs_clip_pages.ts +26 -1
  40. package/src/project.ts +7 -0
  41. package/src/project_class.ts +62 -8
  42. package/src/project_internal.ts +24 -29
  43. package/src/project_types.ts +69 -10
  44. package/src/public_types.ts +68 -0
  45. package/src/public_types_mastering.ts +22 -0
  46. package/src/public_types_mixing.ts +11 -2
  47. package/src/public_types_music.ts +9 -0
  48. package/src/public_types_spectral.ts +33 -0
  49. package/src/quick_analysis.ts +25 -7
  50. package/src/realtime_engine.ts +153 -3
  51. package/src/realtime_voice_changer.ts +51 -77
  52. package/src/sonare-analysis.js.d.ts +8 -0
  53. package/src/sonare.js.d.ts +276 -6
  54. package/src/streaming_processors.ts +15 -0
  55. package/src/web_midi.ts +36 -4
  56. package/src/worker.ts +196 -0
  57. package/src/worker_client.ts +352 -0
  58. package/src/worker_protocol.ts +59 -0
  59. package/src/worklet/engine-automation.ts +2 -1
  60. package/src/worklet/engine-capture-facade.ts +13 -4
  61. package/src/worklet/engine-clips.ts +12 -3
  62. package/src/worklet/engine-node.ts +212 -4
  63. package/src/worklet/engine-offline.ts +7 -2
  64. package/src/worklet/engine-parameter-facade.ts +26 -5
  65. package/src/worklet/engine-processor.ts +383 -42
  66. package/src/worklet/engine-register.ts +8 -0
  67. package/src/worklet/engine-strips.ts +17 -1
  68. package/src/worklet/engine-sync.ts +2 -6
  69. package/src/worklet/engine-tempo-facade.ts +8 -6
  70. package/src/worklet/engine.ts +380 -13
  71. package/src/worklet/guards.ts +22 -1
  72. package/src/worklet/messages.ts +125 -7
  73. package/src/worklet/protocol.ts +301 -17
  74. package/src/worklet/voice-changer-processor.ts +12 -15
  75. package/src/worklet.ts +20 -1
@@ -5,74 +5,6 @@ import type {
5
5
  VoicePresetId,
6
6
  } from './public_types';
7
7
 
8
- /**
9
- * True when `config` is a flat POD (the shape `realtimeVoiceChangerPresetConfig`
10
- * returns) rather than a preset name or a nested preset. `retuneSemitones` only
11
- * exists on the flat POD; the nested form spells it `retune.semitones`.
12
- */
13
- function isFlatVoiceChangerPod(config: unknown): config is RealtimeVoiceChangerPodConfig {
14
- return typeof config === 'object' && config !== null && 'retuneSemitones' in config;
15
- }
16
-
17
- /**
18
- * Rewrite the flat 36-field POD into the nested shape the native config parser
19
- * reads. Passing the flat POD straight to `setConfig` would leave every nested
20
- * field at its default (only the three root fields are read flat), silently
21
- * discarding retune/EQ/reverb/etc.
22
- */
23
- function flatVoiceChangerPodToNested(pod: RealtimeVoiceChangerPodConfig): Record<string, unknown> {
24
- return {
25
- inputGainDb: pod.inputGainDb,
26
- outputGainDb: pod.outputGainDb,
27
- wetMix: pod.wetMix,
28
- retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
29
- formant: {
30
- factor: pod.formantFactor,
31
- amount: pod.formantAmount,
32
- body: pod.formantBody,
33
- brightness: pod.formantBrightness,
34
- nasal: pod.formantNasal,
35
- },
36
- eq: {
37
- highpassHz: pod.eqHighpassHz,
38
- bodyDb: pod.eqBodyDb,
39
- presenceDb: pod.eqPresenceDb,
40
- airDb: pod.eqAirDb,
41
- },
42
- gate: {
43
- thresholdDb: pod.gateThresholdDb,
44
- attackMs: pod.gateAttackMs,
45
- releaseMs: pod.gateReleaseMs,
46
- rangeDb: pod.gateRangeDb,
47
- },
48
- compressor: {
49
- thresholdDb: pod.compressorThresholdDb,
50
- ratio: pod.compressorRatio,
51
- attackMs: pod.compressorAttackMs,
52
- releaseMs: pod.compressorReleaseMs,
53
- makeupGainDb: pod.compressorMakeupGainDb,
54
- },
55
- deesser: {
56
- frequencyHz: pod.deesserFrequencyHz,
57
- thresholdDb: pod.deesserThresholdDb,
58
- ratio: pod.deesserRatio,
59
- rangeDb: pod.deesserRangeDb,
60
- },
61
- reverb: {
62
- mix: pod.reverbMix,
63
- timeMs: pod.reverbTimeMs,
64
- damping: pod.reverbDamping,
65
- seed: pod.reverbSeed,
66
- },
67
- limiter: {
68
- ceilingDb: pod.limiterCeilingDb,
69
- releaseMs: pod.limiterReleaseMs,
70
- enableIspLimiter: pod.limiterEnableIspLimiter,
71
- ispCeilingDbtp: pod.limiterIspCeilingDbtp,
72
- },
73
- };
74
- }
75
-
76
8
  /**
77
9
  * Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} mono
78
10
  * processing. The `input` / `output` `Float32Array`s are typed-memory views
@@ -117,9 +49,22 @@ export interface RealtimeVoiceChangerPlanarBuffer {
117
49
  export class RealtimeVoiceChanger {
118
50
  private changer: import('./sonare.js').WasmRealtimeVoiceChanger;
119
51
 
120
- constructor(config: RealtimeVoiceChangerConfigInput = 'neutral-monitor') {
52
+ /**
53
+ * Creates a voice changer. Supplying `sampleRate` prepares it immediately,
54
+ * matching the Node and Python constructors; omitting it preserves the
55
+ * explicit {@link prepare} lifecycle for callers that configure later.
56
+ */
57
+ constructor(
58
+ config: RealtimeVoiceChangerConfigInput | RealtimeVoiceChangerPodConfig = 'neutral-monitor',
59
+ sampleRate?: number,
60
+ maxBlockSize = 128,
61
+ channels = 1,
62
+ ) {
121
63
  const module = getSonareModule();
122
64
  this.changer = module.createRealtimeVoiceChanger(config as Record<string, unknown> | string);
65
+ if (sampleRate !== undefined) {
66
+ this.changer.prepare(sampleRate, maxBlockSize, channels);
67
+ }
123
68
  }
124
69
 
125
70
  prepare(sampleRate: number, maxBlockSize = 128, channels = 1): void {
@@ -131,11 +76,18 @@ export class RealtimeVoiceChanger {
131
76
  }
132
77
 
133
78
  setConfig(config: RealtimeVoiceChangerConfigInput | RealtimeVoiceChangerPodConfig): void {
134
- // A flat POD (from realtimeVoiceChangerPresetConfig) is rewritten to the
135
- // nested shape the native parser reads; a preset name or nested preset is
136
- // passed through unchanged.
137
- const resolved = isFlatVoiceChangerPod(config) ? flatVoiceChangerPodToNested(config) : config;
138
- this.changer.setConfig(resolved as Record<string, unknown> | string);
79
+ // The shared native parser recognizes the flat POD produced by
80
+ // realtimeVoiceChangerPresetConfig, so bindings never duplicate its 36-field mapping.
81
+ this.changer.setConfig(config as Record<string, unknown> | string);
82
+ }
83
+
84
+ /**
85
+ * Apply a flat, pre-normalized config without JSON serialization. Intended
86
+ * for AudioWorklet control messages whose sender prepared the POD on the
87
+ * main thread.
88
+ */
89
+ setPodConfig(config: RealtimeVoiceChangerPodConfig): void {
90
+ this.changer.setPodConfig(config);
139
91
  }
140
92
 
141
93
  configJson(): string {
@@ -146,6 +98,14 @@ export class RealtimeVoiceChanger {
146
98
  return this.changer.latencySamples();
147
99
  }
148
100
 
101
+ /**
102
+ * Monotonically increases whenever {@link prepare} can replace the native
103
+ * scratch buffers. Cached WASM heap views must be reacquired after it changes.
104
+ */
105
+ bufferGeneration(): number {
106
+ return this.changer.bufferGeneration();
107
+ }
108
+
149
109
  processMono(samples: Float32Array): Float32Array {
150
110
  return this.changer.processMono(samples);
151
111
  }
@@ -234,14 +194,20 @@ export class RealtimeVoiceChanger {
234
194
  createRealtimeMonoBuffer(numSamples: number): RealtimeVoiceChangerMonoBuffer {
235
195
  let input = this.getMonoInputBuffer(numSamples);
236
196
  let output = this.getMonoOutputBuffer(numSamples);
197
+ let generation = this.bufferGeneration();
237
198
  // The cached heap views can detach if WASM linear memory grows (the embind
238
199
  // module is built ALLOW_MEMORY_GROWTH). Re-acquire them if detached
239
200
  // (byteLength === 0) before use, mirroring the worklet RT path. In the
240
201
  // common no-growth case this is a cheap branch with no allocation.
241
202
  const reacquireIfDetached = (): void => {
242
- if (input.byteLength === 0 || output.byteLength === 0) {
203
+ if (
204
+ generation !== this.bufferGeneration() ||
205
+ input.byteLength === 0 ||
206
+ output.byteLength === 0
207
+ ) {
243
208
  input = this.getMonoInputBuffer(numSamples);
244
209
  output = this.getMonoOutputBuffer(numSamples);
210
+ generation = this.bufferGeneration();
245
211
  }
246
212
  };
247
213
  return {
@@ -267,12 +233,18 @@ export class RealtimeVoiceChanger {
267
233
  ): RealtimeVoiceChangerInterleavedBuffer {
268
234
  let input = this.getInterleavedInputBuffer(numFrames, numChannels);
269
235
  let output = this.getInterleavedOutputBuffer(numFrames, numChannels);
236
+ let generation = this.bufferGeneration();
270
237
  // Re-acquire detached views after WASM memory growth (see
271
238
  // createRealtimeMonoBuffer for rationale).
272
239
  const reacquireIfDetached = (): void => {
273
- if (input.byteLength === 0 || output.byteLength === 0) {
240
+ if (
241
+ generation !== this.bufferGeneration() ||
242
+ input.byteLength === 0 ||
243
+ output.byteLength === 0
244
+ ) {
274
245
  input = this.getInterleavedInputBuffer(numFrames, numChannels);
275
246
  output = this.getInterleavedOutputBuffer(numFrames, numChannels);
247
+ generation = this.bufferGeneration();
276
248
  }
277
249
  };
278
250
  return {
@@ -303,17 +275,19 @@ export class RealtimeVoiceChanger {
303
275
  numChannels: number,
304
276
  ): RealtimeVoiceChangerPlanarBuffer {
305
277
  let channels: Float32Array[] = [];
278
+ let generation = this.bufferGeneration();
306
279
  const acquire = (): void => {
307
280
  channels = [];
308
281
  for (let ch = 0; ch < numChannels; ch++) {
309
282
  channels.push(this.getPlanarChannelBuffer(ch, numFrames));
310
283
  }
284
+ generation = this.bufferGeneration();
311
285
  };
312
286
  acquire();
313
287
  // Re-acquire detached views after WASM memory growth (see
314
288
  // createRealtimeMonoBuffer for rationale).
315
289
  const reacquireIfDetached = (): void => {
316
- if ((channels[0]?.byteLength ?? 0) === 0) {
290
+ if (generation !== this.bufferGeneration() || (channels[0]?.byteLength ?? 0) === 0) {
317
291
  acquire();
318
292
  }
319
293
  };
@@ -0,0 +1,8 @@
1
+ import type { SonareModule } from './sonare.js';
2
+
3
+ declare const createSonareAnalysis: (options?: {
4
+ locateFile?: (path: string, prefix: string) => string;
5
+ wasmBinary?: ArrayBuffer | Uint8Array;
6
+ }) => Promise<SonareModule>;
7
+
8
+ export default createSonareAnalysis;