@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.
Files changed (43) hide show
  1. package/README.md +38 -1
  2. package/dist/index.d.ts +1 -2842
  3. package/dist/index.js +3602 -1934
  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 +45 -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 +188 -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 +367 -0
@@ -0,0 +1,667 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type { SynthPatch } from './project';
3
+ import type {
4
+ WasmClipPageRequest,
5
+ WasmEngineAutomationPoint,
6
+ WasmEngineBounceOptions,
7
+ WasmEngineBounceResult,
8
+ WasmEngineCaptureStatus,
9
+ WasmEngineClip,
10
+ WasmEngineFreezeOptions,
11
+ WasmEngineFreezeResult,
12
+ WasmEngineGraphSpec,
13
+ WasmEngineMarker,
14
+ WasmEngineMeterTelemetry,
15
+ WasmEngineMetronomeConfig,
16
+ WasmEngineParameterInfo,
17
+ WasmEngineProcessWithMonitorResult,
18
+ WasmEngineTelemetry,
19
+ WasmEngineTransportState,
20
+ WasmRealtimeEngine,
21
+ } from './sonare.js';
22
+
23
+ export type EngineClip = WasmEngineClip;
24
+ export type ClipPageRequest = WasmClipPageRequest;
25
+ export type EngineParameterInfo = WasmEngineParameterInfo;
26
+ export type EngineAutomationPoint = WasmEngineAutomationPoint;
27
+ export type EngineMarker = WasmEngineMarker;
28
+ export type EngineMetronomeConfig = WasmEngineMetronomeConfig;
29
+ export type EngineGraphSpec = WasmEngineGraphSpec;
30
+ export type EngineCaptureStatus = WasmEngineCaptureStatus;
31
+ export type EngineBounceOptions = WasmEngineBounceOptions;
32
+ export type EngineBounceResult = WasmEngineBounceResult;
33
+ export type EngineFreezeOptions = WasmEngineFreezeOptions;
34
+ export type EngineFreezeResult = WasmEngineFreezeResult;
35
+ export type EngineTelemetry = WasmEngineTelemetry;
36
+ export type EngineMeterTelemetry = WasmEngineMeterTelemetry;
37
+ export type EngineTransportState = WasmEngineTransportState;
38
+
39
+ export const EXPECTED_ENGINE_ABI_VERSION = 3;
40
+
41
+ /** Options for {@link RealtimeEngine.bindMidiCc}. All fields are optional. */
42
+ export interface MidiCcBindOptions {
43
+ /** Lower end of the mapped parameter range. Default `0`. */
44
+ minValue?: number;
45
+ /** Upper end of the mapped parameter range. Default `1`. */
46
+ maxValue?: number;
47
+ }
48
+
49
+ export interface EngineCapabilities {
50
+ engineAbiVersion: number;
51
+ expectedEngineAbiVersion: number;
52
+ abiCompatible: boolean;
53
+ sharedArrayBuffer: boolean;
54
+ atomics: boolean;
55
+ audioWorklet: boolean;
56
+ mode: 'sab' | 'postMessage';
57
+ }
58
+
59
+ export function engineCapabilities(): EngineCapabilities {
60
+ const abiVersion = getSonareModule().engineAbiVersion();
61
+ const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === 'function';
62
+ const atomics = typeof globalThis.Atomics === 'object';
63
+ const audioWorklet =
64
+ typeof AudioWorkletNode !== 'undefined' ||
65
+ typeof (globalThis as typeof globalThis & { AudioWorkletProcessor?: unknown })
66
+ .AudioWorkletProcessor !== 'undefined';
67
+ return {
68
+ engineAbiVersion: abiVersion,
69
+ expectedEngineAbiVersion: EXPECTED_ENGINE_ABI_VERSION,
70
+ abiCompatible: abiVersion === EXPECTED_ENGINE_ABI_VERSION,
71
+ sharedArrayBuffer,
72
+ atomics,
73
+ audioWorklet,
74
+ mode: sharedArrayBuffer && atomics ? 'sab' : 'postMessage',
75
+ };
76
+ }
77
+
78
+ // Methods added to the embind RealtimeEngine that the generated `sonare.js`
79
+ // declarations only gain after a WASM rebuild. The native handle is cast to this
80
+ // shape so the wrapper can reach them without a stale type error.
81
+ interface WasmRealtimeEngineExt {
82
+ setBuiltinInstrument: (destinationId: number, config: object) => void;
83
+ setSynthInstrument: (destinationId: number, patch: object | string) => void;
84
+ loadSoundFont: (data: Uint8Array) => void;
85
+ setSf2Instrument: (destinationId: number, config: object) => void;
86
+ clearMidiInstrument: (destinationId: number) => void;
87
+ midiInstrumentCount: () => number;
88
+ bindMidiCc: (
89
+ channel: number,
90
+ controller: number,
91
+ paramId: number,
92
+ minValue: number,
93
+ maxValue: number,
94
+ ) => void;
95
+ clearMidiCcBindings: () => void;
96
+ midiCcBindingCount: () => number;
97
+ setMidiFx: (destinationId: number, configJson: string) => void;
98
+ clearMidiFx: (destinationId: number) => void;
99
+ setMidiInputSource: (destinationId: number) => void;
100
+ clearMidiInputSource: () => void;
101
+ midiInputPendingCount: () => number;
102
+ createClipPageProvider: (numChannels: number, numSamples: number, pageFrames: number) => number;
103
+ supplyClipPage: (providerId: number, pageIndex: number, channels: Float32Array[]) => void;
104
+ clearClipPage: (providerId: number, pageIndex: number) => void;
105
+ destroyClipPageProvider: (providerId: number) => void;
106
+ popClipPageRequest: () => ClipPageRequest | null;
107
+ pushMidiInputNoteOn: (
108
+ group: number,
109
+ channel: number,
110
+ note: number,
111
+ velocity: number,
112
+ portTimeSamples: number,
113
+ ) => void;
114
+ pushMidiInputNoteOff: (
115
+ group: number,
116
+ channel: number,
117
+ note: number,
118
+ velocity: number,
119
+ portTimeSamples: number,
120
+ ) => void;
121
+ pushMidiInputCc: (
122
+ group: number,
123
+ channel: number,
124
+ controller: number,
125
+ value: number,
126
+ portTimeSamples: number,
127
+ ) => void;
128
+ pushMidiNoteOn: (
129
+ destinationId: number,
130
+ group: number,
131
+ channel: number,
132
+ note: number,
133
+ velocity: number,
134
+ renderFrame: number,
135
+ ) => void;
136
+ pushMidiNoteOff: (
137
+ destinationId: number,
138
+ group: number,
139
+ channel: number,
140
+ note: number,
141
+ velocity: number,
142
+ renderFrame: number,
143
+ ) => void;
144
+ pushMidiCc: (
145
+ destinationId: number,
146
+ group: number,
147
+ channel: number,
148
+ controller: number,
149
+ value: number,
150
+ renderFrame: number,
151
+ ) => void;
152
+ pushMidiPanic: (renderFrame: number) => void;
153
+ clearParameters: () => void;
154
+ }
155
+
156
+ export class RealtimeEngine {
157
+ private native: WasmRealtimeEngine;
158
+
159
+ private nativeExt(): WasmRealtimeEngineExt {
160
+ return this.native as unknown as WasmRealtimeEngineExt;
161
+ }
162
+
163
+ constructor(
164
+ sampleRate = 48000,
165
+ maxBlockSize = 128,
166
+ commandCapacity = 1024,
167
+ telemetryCapacity = 1024,
168
+ ) {
169
+ const module = getSonareModule();
170
+ const capabilities = engineCapabilities();
171
+ if (!capabilities.abiCompatible) {
172
+ throw new Error(
173
+ `Engine ABI mismatch: wasm=${capabilities.engineAbiVersion}, expected=${capabilities.expectedEngineAbiVersion}`,
174
+ );
175
+ }
176
+ this.native = new module.RealtimeEngine(
177
+ sampleRate,
178
+ maxBlockSize,
179
+ commandCapacity,
180
+ telemetryCapacity,
181
+ );
182
+ }
183
+
184
+ prepare(
185
+ sampleRate: number,
186
+ maxBlockSize: number,
187
+ commandCapacity = 1024,
188
+ telemetryCapacity = 1024,
189
+ ): void {
190
+ this.native.prepare(sampleRate, maxBlockSize, commandCapacity, telemetryCapacity);
191
+ }
192
+
193
+ /** Queue a sample-accurate parameter change (engine kSetParam). */
194
+ setParameter(paramId: number, value: number, renderFrame = -1): void {
195
+ this.native.setParameter(paramId, value, renderFrame);
196
+ }
197
+
198
+ /** Queue a smoothed parameter change (engine kSetParamSmoothed). */
199
+ setParameterSmoothed(paramId: number, value: number, renderFrame = -1): void {
200
+ this.native.setParameterSmoothed(paramId, value, renderFrame);
201
+ }
202
+
203
+ setBuiltinInstrument(
204
+ config: { destinationId?: number } & Record<string, unknown> = {},
205
+ destinationId = config.destinationId ?? 0,
206
+ ): void {
207
+ this.nativeExt().setBuiltinInstrument(destinationId, config);
208
+ }
209
+
210
+ /**
211
+ * Bind the patch-driven NativeSynth to a realtime MIDI destination. `patch`
212
+ * is a {@link SynthPatch} or a preset-name string (`'saw-lead'` /
213
+ * `'va:saw-lead'`; see {@link synthPresetNames}), resolving exactly like
214
+ * {@link Project.bounceWithSynthInstrument}. Live note/CC commands and
215
+ * scheduled MIDI clips routed to that destination render through the synth.
216
+ * Unknown preset names throw. An object patch's `destinationId` is a JS
217
+ * binding convenience, not part of the NativeSynth patch itself.
218
+ */
219
+ setSynthInstrument(
220
+ patch: SynthPatch | string = {},
221
+ destinationId = (typeof patch === 'object' ? patch.destinationId : undefined) ?? 0,
222
+ ): void {
223
+ this.nativeExt().setSynthInstrument(destinationId, patch);
224
+ }
225
+
226
+ /**
227
+ * Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
228
+ * bound with {@link setSf2Instrument}. The host fetches the `.sf2` and
229
+ * passes the raw bytes; they are copied into linear memory for the call and
230
+ * not referenced afterwards. Replaces any previously loaded SoundFont.
231
+ */
232
+ loadSoundFont(data: Uint8Array): void {
233
+ this.nativeExt().loadSoundFont(data);
234
+ }
235
+
236
+ /**
237
+ * Bind a GS-compatible SoundFont player to a realtime MIDI destination, fed
238
+ * by the engine's loaded SoundFont ({@link loadSoundFont}). Live note/CC
239
+ * commands and scheduled MIDI clips routed to that destination render
240
+ * through the player (16 MIDI channels, channel 10 drums, GS NRPN part
241
+ * edits, GS/GM SysEx resets). Without a loaded SoundFont — or for programs
242
+ * the SoundFont does not cover — notes play through the built-in
243
+ * synthesizer GM fallback bank (the data-free floor).
244
+ */
245
+ setSf2Instrument(
246
+ config: { destinationId?: number; gain?: number; polyphony?: number } = {},
247
+ destinationId = config.destinationId ?? 0,
248
+ ): void {
249
+ this.nativeExt().setSf2Instrument(destinationId, config);
250
+ }
251
+
252
+ clearMidiInstrument(destinationId = 0): void {
253
+ this.nativeExt().clearMidiInstrument(destinationId);
254
+ }
255
+
256
+ midiInstrumentCount(): number {
257
+ return this.nativeExt().midiInstrumentCount();
258
+ }
259
+
260
+ /**
261
+ * Bind a live MIDI CC to an engine automation parameter. The MIDI event still
262
+ * reaches the destination instrument; when bound, its 7-bit value is also
263
+ * mapped into [minValue, maxValue] for `paramId`.
264
+ */
265
+ bindMidiCc(
266
+ channel: number,
267
+ controller: number,
268
+ paramId: number,
269
+ options: MidiCcBindOptions = {},
270
+ ): void {
271
+ this.nativeExt().bindMidiCc(
272
+ channel,
273
+ controller,
274
+ paramId,
275
+ options.minValue ?? 0,
276
+ options.maxValue ?? 1,
277
+ );
278
+ }
279
+
280
+ clearMidiCcBindings(): void {
281
+ this.nativeExt().clearMidiCcBindings();
282
+ }
283
+
284
+ midiCcBindingCount(): number {
285
+ return this.nativeExt().midiCcBindingCount();
286
+ }
287
+
288
+ /** Install/replace a live non-destructive MIDI-FX insert for one destination. */
289
+ setMidiFx(destinationId: number, configJson: string): void {
290
+ this.nativeExt().setMidiFx(destinationId, configJson);
291
+ }
292
+
293
+ clearMidiFx(destinationId = 0): void {
294
+ this.nativeExt().clearMidiFx(destinationId);
295
+ }
296
+
297
+ /** Enable the engine-owned live MIDI input source for a destination. */
298
+ setMidiInputSource(destinationId = 0): void {
299
+ this.nativeExt().setMidiInputSource(destinationId);
300
+ }
301
+
302
+ clearMidiInputSource(): void {
303
+ this.nativeExt().clearMidiInputSource();
304
+ }
305
+
306
+ midiInputPendingCount(): number {
307
+ return this.nativeExt().midiInputPendingCount();
308
+ }
309
+
310
+ pushMidiInputNoteOn(
311
+ group: number,
312
+ channel: number,
313
+ note: number,
314
+ velocity: number,
315
+ portTimeSamples = 0,
316
+ ): void {
317
+ this.nativeExt().pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
318
+ }
319
+
320
+ pushMidiInputNoteOff(
321
+ group: number,
322
+ channel: number,
323
+ note: number,
324
+ velocity = 0,
325
+ portTimeSamples = 0,
326
+ ): void {
327
+ this.nativeExt().pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
328
+ }
329
+
330
+ pushMidiInputCc(
331
+ group: number,
332
+ channel: number,
333
+ controller: number,
334
+ value: number,
335
+ portTimeSamples = 0,
336
+ ): void {
337
+ this.nativeExt().pushMidiInputCc(group, channel, controller, value, portTimeSamples);
338
+ }
339
+
340
+ pushMidiNoteOn(
341
+ destinationId: number,
342
+ group: number,
343
+ channel: number,
344
+ note: number,
345
+ velocity: number,
346
+ renderFrame = -1,
347
+ ): void {
348
+ this.nativeExt().pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
349
+ }
350
+
351
+ pushMidiNoteOff(
352
+ destinationId: number,
353
+ group: number,
354
+ channel: number,
355
+ note: number,
356
+ velocity = 0,
357
+ renderFrame = -1,
358
+ ): void {
359
+ this.nativeExt().pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
360
+ }
361
+
362
+ /**
363
+ * Queue an immediate (live) MIDI control change to a MIDI destination
364
+ * (engine kMidiCcImmediate). `group`/`channel` are 0..15; `controller`/`value`
365
+ * are 7-bit (0..127). `renderFrame` is the frame to fire at, or -1 for
366
+ * immediate. Mirrors the Node/Python/C-ABI `pushMidiCc`.
367
+ */
368
+ pushMidiCc(
369
+ destinationId: number,
370
+ group: number,
371
+ channel: number,
372
+ controller: number,
373
+ value: number,
374
+ renderFrame = -1,
375
+ ): void {
376
+ this.nativeExt().pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
377
+ }
378
+
379
+ /**
380
+ * Queue a MIDI panic (all-notes-off) releasing every sounding note at
381
+ * `renderFrame` (-1 = immediate). Mirrors the C-ABI `pushMidiPanic`.
382
+ */
383
+ pushMidiPanic(renderFrame = -1): void {
384
+ this.nativeExt().pushMidiPanic(renderFrame);
385
+ }
386
+
387
+ /**
388
+ * Remove all registered parameters (and their automation lanes). Control-thread
389
+ * only; not realtime-safe. Mirrors the C-ABI `clearParameters`.
390
+ */
391
+ clearParameters(): void {
392
+ this.nativeExt().clearParameters();
393
+ }
394
+
395
+ /** Read back the current transport state snapshot. */
396
+ getTransportState(): EngineTransportState {
397
+ return this.native.getTransportState();
398
+ }
399
+
400
+ play(renderFrame = -1): void {
401
+ this.native.play(renderFrame);
402
+ }
403
+
404
+ stop(renderFrame = -1): void {
405
+ this.native.stop(renderFrame);
406
+ }
407
+
408
+ seekSample(timelineSample: number, renderFrame = -1): void {
409
+ this.native.seekSample(timelineSample, renderFrame);
410
+ }
411
+
412
+ seekPpq(ppq: number, renderFrame = -1): void {
413
+ this.native.seekPpq(ppq, renderFrame);
414
+ }
415
+
416
+ setTempo(bpm: number): void {
417
+ this.native.setTempo(bpm);
418
+ }
419
+
420
+ setTimeSignature(numerator: number, denominator: number): void {
421
+ this.native.setTimeSignature(numerator, denominator);
422
+ }
423
+
424
+ setLoop(startPpq: number, endPpq: number, enabled = true): void {
425
+ this.native.setLoop(startPpq, endPpq, enabled);
426
+ }
427
+
428
+ addParameter(info: EngineParameterInfo): void {
429
+ this.native.addParameter(info);
430
+ }
431
+
432
+ parameterCount(): number {
433
+ return this.native.parameterCount();
434
+ }
435
+
436
+ parameterInfoByIndex(index: number): EngineParameterInfo {
437
+ return this.native.parameterInfoByIndex(index);
438
+ }
439
+
440
+ parameterInfo(id: number): EngineParameterInfo {
441
+ return this.native.parameterInfo(id);
442
+ }
443
+
444
+ setAutomationLane(paramId: number, points: EngineAutomationPoint[]): void {
445
+ this.native.setAutomationLane(paramId, points);
446
+ }
447
+
448
+ automationLaneCount(): number {
449
+ return this.native.automationLaneCount();
450
+ }
451
+
452
+ setMarkers(markers: EngineMarker[]): void {
453
+ this.native.setMarkers(markers);
454
+ }
455
+
456
+ markerCount(): number {
457
+ return this.native.markerCount();
458
+ }
459
+
460
+ markerByIndex(index: number): EngineMarker {
461
+ return this.native.markerByIndex(index);
462
+ }
463
+
464
+ marker(id: number): EngineMarker {
465
+ return this.native.marker(id);
466
+ }
467
+
468
+ seekMarker(markerId: number, renderFrame = -1): void {
469
+ this.native.seekMarker(markerId, renderFrame);
470
+ }
471
+
472
+ setLoopFromMarkers(startMarkerId: number, endMarkerId: number): void {
473
+ this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
474
+ }
475
+
476
+ setMetronome(config: EngineMetronomeConfig): void {
477
+ this.native.setMetronome(config);
478
+ }
479
+
480
+ metronome(): Required<EngineMetronomeConfig> {
481
+ return this.native.metronome();
482
+ }
483
+
484
+ countInEndSample(startSample: number, bars: number): number {
485
+ return Number(this.native.countInEndSample(startSample, bars));
486
+ }
487
+
488
+ setGraph(spec: EngineGraphSpec): void {
489
+ this.native.setGraph(spec);
490
+ }
491
+
492
+ graphNodeCount(): number {
493
+ return this.native.graphNodeCount();
494
+ }
495
+
496
+ graphConnectionCount(): number {
497
+ return this.native.graphConnectionCount();
498
+ }
499
+
500
+ setClips(clips: EngineClip[]): void {
501
+ this.native.setClips(
502
+ clips.map((clip) => ({
503
+ ...clip,
504
+ pageProvider:
505
+ typeof clip.pageProvider === 'object' && clip.pageProvider !== null
506
+ ? clip.pageProvider.id
507
+ : clip.pageProvider,
508
+ })),
509
+ );
510
+ }
511
+
512
+ clipCount(): number {
513
+ return this.native.clipCount();
514
+ }
515
+
516
+ createClipPageProvider(
517
+ numChannels: number,
518
+ numSamples: number,
519
+ pageFrames: number,
520
+ ): ClipPageProvider {
521
+ const id = this.nativeExt().createClipPageProvider(numChannels, numSamples, pageFrames);
522
+ return new ClipPageProvider(this, id);
523
+ }
524
+
525
+ supplyClipPage(providerId: number, pageIndex: number, channels: Float32Array[]): void {
526
+ this.nativeExt().supplyClipPage(providerId, pageIndex, channels);
527
+ }
528
+
529
+ clearClipPage(providerId: number, pageIndex: number): void {
530
+ this.nativeExt().clearClipPage(providerId, pageIndex);
531
+ }
532
+
533
+ destroyClipPageProvider(providerId: number): void {
534
+ this.nativeExt().destroyClipPageProvider(providerId);
535
+ }
536
+
537
+ popClipPageRequest(): ClipPageRequest | null {
538
+ return this.nativeExt().popClipPageRequest();
539
+ }
540
+
541
+ setCaptureBuffer(numChannels: number, capacityFrames: number): void {
542
+ this.native.setCaptureBuffer(numChannels, capacityFrames);
543
+ }
544
+
545
+ armCapture(armed = true): void {
546
+ this.native.armCapture(armed);
547
+ }
548
+
549
+ setCapturePunch(startSample: number, endSample: number, enabled = true): void {
550
+ this.native.setCapturePunch(startSample, endSample, enabled);
551
+ }
552
+
553
+ setCaptureSource(source: EngineCaptureStatus['source']): void {
554
+ this.native.setCaptureSource(source);
555
+ }
556
+
557
+ setRecordOffsetSamples(offsetSamples: number): void {
558
+ this.native.setRecordOffsetSamples(offsetSamples);
559
+ }
560
+
561
+ setInputMonitor(enabled: boolean, gain = 1): void {
562
+ this.native.setInputMonitor(enabled, gain);
563
+ }
564
+
565
+ resetCapture(): void {
566
+ this.native.resetCapture();
567
+ }
568
+
569
+ captureStatus(): EngineCaptureStatus {
570
+ return this.native.captureStatus();
571
+ }
572
+
573
+ capturedAudio(): Float32Array[] {
574
+ return this.native.capturedAudio();
575
+ }
576
+
577
+ process(channels: Float32Array[]): Float32Array[] {
578
+ return this.native.process(channels);
579
+ }
580
+
581
+ /**
582
+ * Allocates persistent per-channel WASM-heap scratch for the zero-copy
583
+ * `getChannelBuffer` / `processPrepared` realtime path. Call once (off the
584
+ * audio thread) before driving `processPrepared` from an AudioWorklet so the
585
+ * render callback never allocates on the C++/JS heap.
586
+ */
587
+ prepareChannels(numChannels: number, maxFrames: number): void {
588
+ this.native.prepareChannels(numChannels, maxFrames);
589
+ }
590
+
591
+ /**
592
+ * Returns a Float32Array view onto the persistent WASM-heap scratch for one
593
+ * channel (valid for up to `numFrames`). Fill it, call `processPrepared`, then
594
+ * read the same view back. Re-acquire after WASM memory growth.
595
+ */
596
+ getChannelBuffer(channel: number, numFrames: number): Float32Array {
597
+ return this.native.getChannelBuffer(channel, numFrames);
598
+ }
599
+
600
+ /**
601
+ * Runs the engine in place over the prepared per-channel scratch buffers.
602
+ * Allocation-free: safe to call on the AudioWorklet render thread after
603
+ * `prepareChannels`.
604
+ */
605
+ processPrepared(numFrames: number): void {
606
+ this.native.processPrepared(numFrames);
607
+ }
608
+
609
+ processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
610
+ return this.native.processWithMonitor(channels);
611
+ }
612
+
613
+ renderOffline(channels: Float32Array[], blockSize = 128): Float32Array[] {
614
+ return this.native.renderOffline(channels, blockSize);
615
+ }
616
+
617
+ bounceOffline(options: EngineBounceOptions): EngineBounceResult {
618
+ return this.native.bounceOffline(options);
619
+ }
620
+
621
+ freezeOffline(options: EngineFreezeOptions): EngineFreezeResult {
622
+ return this.native.freezeOffline(options);
623
+ }
624
+
625
+ drainTelemetry(maxRecords = 1024): EngineTelemetry[] {
626
+ return this.native.drainTelemetry(maxRecords);
627
+ }
628
+
629
+ drainMeterTelemetry(maxRecords = 1024): EngineMeterTelemetry[] {
630
+ return this.native.drainMeterTelemetry(maxRecords);
631
+ }
632
+
633
+ destroy(): void {
634
+ this.native.delete();
635
+ }
636
+ }
637
+
638
+ export class ClipPageProvider {
639
+ private disposed = false;
640
+
641
+ constructor(
642
+ private readonly engine: RealtimeEngine,
643
+ readonly id: number,
644
+ ) {}
645
+
646
+ supply(pageIndex: number, channels: Float32Array[]): void {
647
+ if (this.disposed) {
648
+ throw new Error('ClipPageProvider is destroyed');
649
+ }
650
+ this.engine.supplyClipPage(this.id, pageIndex, channels);
651
+ }
652
+
653
+ clear(pageIndex: number): void {
654
+ if (this.disposed) {
655
+ return;
656
+ }
657
+ this.engine.clearClipPage(this.id, pageIndex);
658
+ }
659
+
660
+ destroy(): void {
661
+ if (this.disposed) {
662
+ return;
663
+ }
664
+ this.disposed = true;
665
+ this.engine.destroyClipPageProvider(this.id);
666
+ }
667
+ }