@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
package/src/mixer.ts
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import {
|
|
2
|
+
automationCurveCode,
|
|
3
|
+
meterTapCode,
|
|
4
|
+
panLawCode,
|
|
5
|
+
panModeCode,
|
|
6
|
+
sendTimingCode,
|
|
7
|
+
} from './codes';
|
|
8
|
+
import { getSonareModule } from './module_state';
|
|
9
|
+
import type {
|
|
10
|
+
AutomationCurve,
|
|
11
|
+
GoniometerPoint,
|
|
12
|
+
MeterTap,
|
|
13
|
+
MixerProcessResult,
|
|
14
|
+
MixMeterSnapshot,
|
|
15
|
+
PanLaw,
|
|
16
|
+
PanMode,
|
|
17
|
+
SendTiming,
|
|
18
|
+
} from './public_types';
|
|
19
|
+
|
|
20
|
+
export interface MixerRealtimeBuffer {
|
|
21
|
+
leftInputs: Float32Array[];
|
|
22
|
+
rightInputs: Float32Array[];
|
|
23
|
+
outLeft: Float32Array;
|
|
24
|
+
outRight: Float32Array;
|
|
25
|
+
process: (numSamples?: number) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Mixer Class (scene-based persistent mixer)
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Persistent, scene-based stereo mixer.
|
|
34
|
+
*
|
|
35
|
+
* Build one from a scene JSON string (e.g. {@link mixingScenePresetJson} or a
|
|
36
|
+
* hand-authored scene), then feed per-strip stereo blocks through
|
|
37
|
+
* {@link processStereo} to get the routed stereo master. Strips, sends, buses,
|
|
38
|
+
* and inserts are described entirely by the scene; the routing graph is
|
|
39
|
+
* compiled lazily on the first {@link processStereo} call (or eagerly via
|
|
40
|
+
* {@link compile}).
|
|
41
|
+
*
|
|
42
|
+
* Call {@link delete} (or use a `try/finally`) to release the underlying WASM
|
|
43
|
+
* object — the embind handle is not garbage-collected automatically.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('basicStereo'), 48000, 512);
|
|
48
|
+
* try {
|
|
49
|
+
* const out = mixer.processStereo([stripL], [stripR]);
|
|
50
|
+
* } finally {
|
|
51
|
+
* mixer.delete();
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export class Mixer {
|
|
56
|
+
private mixer: import('./sonare.js').WasmMixer;
|
|
57
|
+
|
|
58
|
+
private constructor(mixer: import('./sonare.js').WasmMixer) {
|
|
59
|
+
this.mixer = mixer;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build a mixer from a scene JSON string.
|
|
64
|
+
*
|
|
65
|
+
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
66
|
+
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
67
|
+
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
68
|
+
*/
|
|
69
|
+
static fromSceneJson(json: string, sampleRate = 48000, blockSize = 512): Mixer {
|
|
70
|
+
const module = getSonareModule();
|
|
71
|
+
return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Rebuild and compile the routing graph from the current scene topology. */
|
|
75
|
+
compile(): void {
|
|
76
|
+
this.mixer.compile();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mix one block of per-strip stereo audio into the stereo master.
|
|
81
|
+
*
|
|
82
|
+
* @param leftChannels - `leftChannels[i]` is the left channel of strip `i`
|
|
83
|
+
* @param rightChannels - `rightChannels[i]` is the right channel of strip `i`
|
|
84
|
+
* @returns Mixed stereo master (`left`, `right`, `sampleRate`)
|
|
85
|
+
*/
|
|
86
|
+
processStereo(leftChannels: Float32Array[], rightChannels: Float32Array[]): MixerProcessResult {
|
|
87
|
+
if (leftChannels.length !== rightChannels.length) {
|
|
88
|
+
throw new Error('leftChannels and rightChannels must have the same length.');
|
|
89
|
+
}
|
|
90
|
+
return this.mixer.processStereo(leftChannels, rightChannels);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mix one block into caller-owned output arrays.
|
|
95
|
+
*
|
|
96
|
+
* This avoids allocating the result object and result `Float32Array`s. It is
|
|
97
|
+
* intended for realtime bridges such as AudioWorklet; the input channel count
|
|
98
|
+
* must match the scene strip count and all arrays must have the same length.
|
|
99
|
+
*/
|
|
100
|
+
processStereoInto(
|
|
101
|
+
leftChannels: Float32Array[],
|
|
102
|
+
rightChannels: Float32Array[],
|
|
103
|
+
outLeft: Float32Array,
|
|
104
|
+
outRight: Float32Array,
|
|
105
|
+
): void {
|
|
106
|
+
if (leftChannels.length !== rightChannels.length) {
|
|
107
|
+
throw new Error('leftChannels and rightChannels must have the same length.');
|
|
108
|
+
}
|
|
109
|
+
if (outLeft.length !== outRight.length) {
|
|
110
|
+
throw new Error('outLeft and outRight must have the same length.');
|
|
111
|
+
}
|
|
112
|
+
this.mixer.processStereoInto(leftChannels, rightChannels, outLeft, outRight);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create reusable WASM-heap input/output views for realtime-style processing.
|
|
117
|
+
*
|
|
118
|
+
* Fill `leftInputs[i]` / `rightInputs[i]`, call `process()`, then read
|
|
119
|
+
* `outLeft` / `outRight`. The views are owned by this mixer and become invalid
|
|
120
|
+
* after {@link delete}.
|
|
121
|
+
*/
|
|
122
|
+
createRealtimeBuffer(): MixerRealtimeBuffer {
|
|
123
|
+
const stripCount = this.stripCount();
|
|
124
|
+
let leftInputs: Float32Array[] = [];
|
|
125
|
+
let rightInputs: Float32Array[] = [];
|
|
126
|
+
let outLeft = this.mixer.outputLeftView();
|
|
127
|
+
let outRight = this.mixer.outputRightView();
|
|
128
|
+
const acquire = (): void => {
|
|
129
|
+
leftInputs = [];
|
|
130
|
+
rightInputs = [];
|
|
131
|
+
for (let index = 0; index < stripCount; index++) {
|
|
132
|
+
leftInputs.push(this.mixer.inputLeftView(index));
|
|
133
|
+
rightInputs.push(this.mixer.inputRightView(index));
|
|
134
|
+
}
|
|
135
|
+
outLeft = this.mixer.outputLeftView();
|
|
136
|
+
outRight = this.mixer.outputRightView();
|
|
137
|
+
};
|
|
138
|
+
acquire();
|
|
139
|
+
// The cached heap views can detach if WASM linear memory grows (the embind
|
|
140
|
+
// module is built ALLOW_MEMORY_GROWTH). Re-acquire them if detached
|
|
141
|
+
// (byteLength === 0) before use, mirroring the worklet RT path.
|
|
142
|
+
const reacquireIfDetached = (): void => {
|
|
143
|
+
if (outLeft.byteLength === 0 || (leftInputs[0]?.byteLength ?? 1) === 0) {
|
|
144
|
+
acquire();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
get leftInputs(): Float32Array[] {
|
|
149
|
+
reacquireIfDetached();
|
|
150
|
+
return leftInputs;
|
|
151
|
+
},
|
|
152
|
+
get rightInputs(): Float32Array[] {
|
|
153
|
+
reacquireIfDetached();
|
|
154
|
+
return rightInputs;
|
|
155
|
+
},
|
|
156
|
+
get outLeft(): Float32Array {
|
|
157
|
+
reacquireIfDetached();
|
|
158
|
+
return outLeft;
|
|
159
|
+
},
|
|
160
|
+
get outRight(): Float32Array {
|
|
161
|
+
reacquireIfDetached();
|
|
162
|
+
return outRight;
|
|
163
|
+
},
|
|
164
|
+
process: (numSamples = outLeft.length) => {
|
|
165
|
+
reacquireIfDetached();
|
|
166
|
+
this.mixer.processPreparedStereo(numSamples);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
172
|
+
stripCount(): number {
|
|
173
|
+
return this.mixer.stripCount();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Schedule sample-accurate insert-parameter automation on a strip's insert.
|
|
178
|
+
*
|
|
179
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
180
|
+
* @param insertIndex - Index into the strip's combined insert sequence
|
|
181
|
+
* (`[pre-inserts... post-inserts...]`)
|
|
182
|
+
* @param paramId - Processor-specific parameter id
|
|
183
|
+
* @param samplePos - Absolute samples from the start of processing (the mixer
|
|
184
|
+
* advances an internal position from 0 on the first {@link processStereo}
|
|
185
|
+
* call; recompiling resets it to 0)
|
|
186
|
+
* @param value - Target parameter value
|
|
187
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
188
|
+
* @throws If the strip index is out of range or the schedule call fails
|
|
189
|
+
* (unknown curve, out-of-range insert index, or full event lane)
|
|
190
|
+
*/
|
|
191
|
+
scheduleInsertAutomation(
|
|
192
|
+
stripIndex: number,
|
|
193
|
+
insertIndex: number,
|
|
194
|
+
paramId: number,
|
|
195
|
+
samplePos: number,
|
|
196
|
+
value: number,
|
|
197
|
+
curve: AutomationCurve = 'linear',
|
|
198
|
+
): void {
|
|
199
|
+
this.mixer.scheduleInsertAutomation(
|
|
200
|
+
stripIndex,
|
|
201
|
+
insertIndex,
|
|
202
|
+
paramId,
|
|
203
|
+
samplePos,
|
|
204
|
+
value,
|
|
205
|
+
automationCurveCode(curve),
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resolve a strip's index in `[0, stripCount())` from its scene id, or `null`
|
|
211
|
+
* when no strip with that id exists (matches the Node binding's `number | null`).
|
|
212
|
+
*/
|
|
213
|
+
stripById(id: string): number | null {
|
|
214
|
+
const index = this.mixer.stripById(id);
|
|
215
|
+
return index < 0 ? null : index;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
220
|
+
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
221
|
+
* {@link compile} (or {@link processStereo}) to rebuild.
|
|
222
|
+
*/
|
|
223
|
+
addBus(id: string, role = 'aux'): void {
|
|
224
|
+
this.mixer.addBus(id, role);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Remove a bus by id. Marks the routing graph dirty. */
|
|
228
|
+
removeBus(id: string): void {
|
|
229
|
+
this.mixer.removeBus(id);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Number of buses in the mixer topology. */
|
|
233
|
+
busCount(): number {
|
|
234
|
+
return this.mixer.busCount();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Add a VCA group with the given gain offset (dB). `members` is a list of
|
|
239
|
+
* strip ids governed by the group (may be empty).
|
|
240
|
+
*/
|
|
241
|
+
addVcaGroup(id: string, gainDb = 0.0, members: string[] = []): void {
|
|
242
|
+
this.mixer.addVcaGroup(id, gainDb, members);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Set an existing VCA group's gain in dB. */
|
|
246
|
+
setVcaGroupGainDb(id: string, gainDb: number): void {
|
|
247
|
+
this.mixer.setVcaGroupGainDb(id, gainDb);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Remove a VCA group by id. */
|
|
251
|
+
removeVcaGroup(id: string): void {
|
|
252
|
+
this.mixer.removeVcaGroup(id);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Number of VCA groups in the mixer topology. */
|
|
256
|
+
vcaGroupCount(): number {
|
|
257
|
+
return this.mixer.vcaGroupCount();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Set the strip's input trim in dB. */
|
|
261
|
+
setInputTrimDb(stripIndex: number, db: number): void {
|
|
262
|
+
this.mixer.setInputTrimDb(stripIndex, db);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Set the strip's fader level in dB. */
|
|
266
|
+
setFaderDb(stripIndex: number, db: number): void {
|
|
267
|
+
this.mixer.setFaderDb(stripIndex, db);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Set the strip's pan position.
|
|
272
|
+
*
|
|
273
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
274
|
+
* @param pan - Pan position in `[-1, 1]`
|
|
275
|
+
* @param panMode - Optional pan mode. When omitted the strip's current pan
|
|
276
|
+
* mode is kept (passes `SONARE_PAN_MODE_KEEP`), so a plain pan nudge does
|
|
277
|
+
* not reset a scene-defined `'stereoPan'` / `'dualPan'` mode back to
|
|
278
|
+
* balance. Pass `'balance'` (or `0`) explicitly to force balance mode.
|
|
279
|
+
*/
|
|
280
|
+
setPan(stripIndex: number, pan: number, panMode?: PanMode | number): void {
|
|
281
|
+
// SONARE_PAN_MODE_KEEP (-1) = keep the strip's current pan mode.
|
|
282
|
+
const mode = panMode === undefined ? -1 : panModeCode(panMode);
|
|
283
|
+
this.mixer.setPan(stripIndex, pan, mode);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Set the strip's stereo width. */
|
|
287
|
+
setWidth(stripIndex: number, width: number): void {
|
|
288
|
+
this.mixer.setWidth(stripIndex, width);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Set the strip's mute state. */
|
|
292
|
+
setMuted(stripIndex: number, muted: boolean): void {
|
|
293
|
+
this.mixer.setMuted(stripIndex, muted);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Set a strip's solo state. Takes effect on the next process without a
|
|
298
|
+
* graph recompile.
|
|
299
|
+
*/
|
|
300
|
+
setSoloed(stripIndex: number, soloed: boolean): void {
|
|
301
|
+
this.mixer.setSoloed(stripIndex, soloed);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Mark a strip solo-safe so it is never implied-muted by another strip's
|
|
306
|
+
* solo. Takes effect on the next process without a graph recompile.
|
|
307
|
+
*/
|
|
308
|
+
setSoloSafe(stripIndex: number, soloSafe: boolean): void {
|
|
309
|
+
this.mixer.setSoloSafe(stripIndex, soloSafe);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Invert the polarity of the left and/or right channel of a strip. */
|
|
313
|
+
setPolarityInvert(stripIndex: number, invertLeft: boolean, invertRight: boolean): void {
|
|
314
|
+
this.mixer.setPolarityInvert(stripIndex, invertLeft, invertRight);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Set the strip's pan law. */
|
|
318
|
+
setPanLaw(stripIndex: number, panLaw: PanLaw | number): void {
|
|
319
|
+
this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Set a per-strip channel delay in samples. This changes the strip's reported
|
|
324
|
+
* latency; recompile to re-run latency compensation.
|
|
325
|
+
*/
|
|
326
|
+
setChannelDelaySamples(stripIndex: number, delaySamples: number): void {
|
|
327
|
+
this.mixer.setChannelDelaySamples(stripIndex, delaySamples);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Set the strip's live VCA gain offset in dB (not persisted to the scene). */
|
|
331
|
+
setVcaOffsetDb(stripIndex: number, offsetDb: number): void {
|
|
332
|
+
this.mixer.setVcaOffsetDb(stripIndex, offsetDb);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Set independent left/right pan positions (dual-pan mode). */
|
|
336
|
+
setDualPan(stripIndex: number, leftPan: number, rightPan: number): void {
|
|
337
|
+
this.mixer.setDualPan(stripIndex, leftPan, rightPan);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Add a send to a strip after construction.
|
|
342
|
+
*
|
|
343
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
344
|
+
* @param id - Send id
|
|
345
|
+
* @param destinationBusId - Destination bus id
|
|
346
|
+
* @param sendDb - Initial send level in dB
|
|
347
|
+
* @param timing - `'preFader'` or `'postFader'` (default: `'postFader'`)
|
|
348
|
+
* @returns The new send's index
|
|
349
|
+
*/
|
|
350
|
+
addSend(
|
|
351
|
+
stripIndex: number,
|
|
352
|
+
id: string,
|
|
353
|
+
destinationBusId: string,
|
|
354
|
+
sendDb = 0.0,
|
|
355
|
+
timing: SendTiming | number = 'postFader',
|
|
356
|
+
): number {
|
|
357
|
+
return this.mixer.addSend(stripIndex, id, destinationBusId, sendDb, sendTimingCode(timing));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Set the send level (in dB) for an existing send by index. */
|
|
361
|
+
setSendDb(stripIndex: number, sendIndex: number, sendDb: number): void {
|
|
362
|
+
this.mixer.setSendDb(stripIndex, sendIndex, sendDb);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Remove an existing send from a strip by index.
|
|
367
|
+
*
|
|
368
|
+
* Sends are addressed in add order. After removal, sends with a higher index
|
|
369
|
+
* than `sendIndex` shift down by one. Recompile (or process) before reading
|
|
370
|
+
* results so the routing graph rebuilds.
|
|
371
|
+
*
|
|
372
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
373
|
+
* @param sendIndex - Send index in add order
|
|
374
|
+
*/
|
|
375
|
+
removeSend(stripIndex: number, sendIndex: number): void {
|
|
376
|
+
this.mixer.removeSend(stripIndex, sendIndex);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Read a strip's meter snapshot at the given tap point.
|
|
381
|
+
*
|
|
382
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
383
|
+
* @param tap - `'preFader'` or `'postFader'` (default: `'postFader'`)
|
|
384
|
+
*/
|
|
385
|
+
meterTap(stripIndex: number, tap: MeterTap = 'postFader'): MixMeterSnapshot {
|
|
386
|
+
return this.mixer.meterTap(stripIndex, meterTapCode(tap));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Read a strip's meter snapshot.
|
|
391
|
+
*
|
|
392
|
+
* With no `tap` argument this reads the strip's own (post-fader) meter,
|
|
393
|
+
* matching the Node/Python tap-less `stripMeter` contract. Pass an optional
|
|
394
|
+
* `tap` (`'preFader'` / `'postFader'`) to read the tap-selectable snapshot
|
|
395
|
+
* instead — the same backing call as {@link meterTap}.
|
|
396
|
+
*
|
|
397
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
398
|
+
* @param tap - Optional tap point (`'preFader'` / `'postFader'`); when omitted
|
|
399
|
+
* the tap-less post-fader strip meter is read.
|
|
400
|
+
*/
|
|
401
|
+
stripMeter(stripIndex: number, tap?: MeterTap | number): MixMeterSnapshot {
|
|
402
|
+
if (tap === undefined) {
|
|
403
|
+
return this.mixer.stripMeter(stripIndex);
|
|
404
|
+
}
|
|
405
|
+
return this.mixer.meterTap(stripIndex, meterTapCode(tap));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Schedule sample-accurate fader automation on a strip.
|
|
410
|
+
*
|
|
411
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
412
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
413
|
+
* @param faderDb - Target fader level in dB
|
|
414
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
415
|
+
*/
|
|
416
|
+
scheduleFaderAutomation(
|
|
417
|
+
stripIndex: number,
|
|
418
|
+
samplePos: number,
|
|
419
|
+
faderDb: number,
|
|
420
|
+
curve: AutomationCurve = 'linear',
|
|
421
|
+
): void {
|
|
422
|
+
this.mixer.scheduleFaderAutomation(stripIndex, samplePos, faderDb, automationCurveCode(curve));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Schedule sample-accurate pan automation on a strip.
|
|
427
|
+
*
|
|
428
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
429
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
430
|
+
* @param pan - Target pan position
|
|
431
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
432
|
+
*/
|
|
433
|
+
schedulePanAutomation(
|
|
434
|
+
stripIndex: number,
|
|
435
|
+
samplePos: number,
|
|
436
|
+
pan: number,
|
|
437
|
+
curve: AutomationCurve = 'linear',
|
|
438
|
+
): void {
|
|
439
|
+
this.mixer.schedulePanAutomation(stripIndex, samplePos, pan, automationCurveCode(curve));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Schedule sample-accurate width automation on a strip.
|
|
444
|
+
*
|
|
445
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
446
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
447
|
+
* @param width - Target stereo width
|
|
448
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
449
|
+
*/
|
|
450
|
+
scheduleWidthAutomation(
|
|
451
|
+
stripIndex: number,
|
|
452
|
+
samplePos: number,
|
|
453
|
+
width: number,
|
|
454
|
+
curve: AutomationCurve = 'linear',
|
|
455
|
+
): void {
|
|
456
|
+
this.mixer.scheduleWidthAutomation(stripIndex, samplePos, width, automationCurveCode(curve));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Schedule sample-accurate send-level automation on a strip's send.
|
|
461
|
+
*
|
|
462
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
463
|
+
* @param sendIndex - Send index in the strip's add order
|
|
464
|
+
* @param samplePos - Absolute samples from the start of processing
|
|
465
|
+
* @param db - Target send level in dB
|
|
466
|
+
* @param curve - Interpolation curve (default: `'linear'`)
|
|
467
|
+
*/
|
|
468
|
+
scheduleSendAutomation(
|
|
469
|
+
stripIndex: number,
|
|
470
|
+
sendIndex: number,
|
|
471
|
+
samplePos: number,
|
|
472
|
+
db: number,
|
|
473
|
+
curve: AutomationCurve = 'linear',
|
|
474
|
+
): void {
|
|
475
|
+
this.mixer.scheduleSendAutomation(
|
|
476
|
+
stripIndex,
|
|
477
|
+
sendIndex,
|
|
478
|
+
samplePos,
|
|
479
|
+
db,
|
|
480
|
+
automationCurveCode(curve),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
486
|
+
* (oldest to newest).
|
|
487
|
+
*/
|
|
488
|
+
readGoniometerLatest(stripIndex: number, maxPoints: number): GoniometerPoint[] {
|
|
489
|
+
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Serialize the current scene (strips, buses, sends, connections) to JSON. */
|
|
493
|
+
toSceneJson(): string {
|
|
494
|
+
return this.mixer.toSceneJson();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Maximum processor tail length (samples) in the compiled mixer graph. Lazily
|
|
499
|
+
* compiles the routing graph if the topology is dirty.
|
|
500
|
+
*/
|
|
501
|
+
tailSamples(): number {
|
|
502
|
+
return this.mixer.tailSamples();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Drain delayed / tail audio by processing a zero-input block of `numSamples`
|
|
507
|
+
* frames after the host stops feeding strip inputs. Returns the mixed stereo
|
|
508
|
+
* master (`left`, `right`, `sampleRate`).
|
|
509
|
+
*/
|
|
510
|
+
drainTailStereo(numSamples: number): MixerProcessResult {
|
|
511
|
+
return this.mixer.drainTailStereo(numSamples);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Release the underlying WASM object. Safe to call only once. */
|
|
515
|
+
delete(): void {
|
|
516
|
+
this.mixer.delete();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
520
|
+
destroy(): void {
|
|
521
|
+
this.delete();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SonareModule } from './sonare.js';
|
|
2
|
+
|
|
3
|
+
let wasmModule: SonareModule | null = null;
|
|
4
|
+
|
|
5
|
+
export function setSonareModule(module: SonareModule): void {
|
|
6
|
+
wasmModule = module;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getSonareModule(): SonareModule {
|
|
10
|
+
if (!wasmModule) {
|
|
11
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
12
|
+
}
|
|
13
|
+
return wasmModule;
|
|
14
|
+
}
|