@libraz/libsonare 1.5.5 → 1.7.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 +109 -20
- package/dist/analysis.d.ts +5809 -0
- package/dist/analysis.js +2574 -0
- package/dist/analysis.js.map +1 -0
- package/dist/index.d.ts +884 -55
- package/dist/index.js +1302 -292
- package/dist/index.js.map +1 -1
- package/dist/schemas/realtime-voice-changer-preset-pack.schema.json +27 -0
- package/dist/schemas/realtime-voice-changer-preset.schema.json +140 -0
- package/dist/sonare-analysis.js +2 -0
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worker.d.ts +65 -0
- package/dist/worker.js +734 -0
- package/dist/worker.js.map +1 -0
- package/dist/worklet.d.ts +628 -88
- package/dist/worklet.js +1816 -266
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -11
- package/src/_chain_config.ts +5 -3
- package/src/analysis.ts +137 -0
- package/src/analysis_helpers.ts +40 -29
- package/src/audio.ts +7 -2
- package/src/clip_page_streamer.ts +62 -8
- package/src/codes.ts +87 -64
- package/src/effects_mastering.ts +1 -0
- package/src/effects_transform.ts +179 -10
- package/src/errors.ts +1 -0
- package/src/feature_core.ts +140 -2
- package/src/feature_music.ts +25 -3
- package/src/feature_pitch.ts +70 -1
- package/src/feature_spectral.ts +295 -7
- package/src/feature_spectrogram.ts +203 -3
- package/src/features.ts +16 -1
- package/src/index.ts +120 -8
- package/src/mastering_chain.ts +20 -8
- package/src/mastering_core.ts +5 -0
- package/src/mastering_dynamics.ts +7 -1
- package/src/metering.ts +6 -1
- package/src/mixer.ts +3 -3
- package/src/opfs_clip_pages.ts +26 -1
- package/src/project.ts +13 -0
- package/src/project_class.ts +204 -23
- package/src/project_internal.ts +38 -34
- package/src/project_types.ts +114 -11
- package/src/public_types.ts +68 -0
- package/src/public_types_mastering.ts +22 -0
- package/src/public_types_mixing.ts +27 -2
- package/src/public_types_music.ts +9 -0
- package/src/public_types_realtime.ts +23 -5
- package/src/public_types_spectral.ts +33 -0
- package/src/quick_analysis.ts +46 -10
- package/src/realtime_engine.ts +167 -6
- package/src/realtime_voice_changer.ts +51 -77
- package/src/sonare-analysis.js.d.ts +8 -0
- package/src/sonare.js.d.ts +340 -6
- package/src/stream_analyzer.ts +6 -1
- package/src/streaming_processors.ts +15 -0
- package/src/web_midi.ts +46 -11
- package/src/worker.ts +196 -0
- package/src/worker_client.ts +352 -0
- package/src/worker_protocol.ts +59 -0
- package/src/worklet/engine-automation.ts +2 -1
- package/src/worklet/engine-capture-facade.ts +13 -4
- package/src/worklet/engine-clips.ts +12 -3
- package/src/worklet/engine-node.ts +264 -21
- package/src/worklet/engine-offline.ts +7 -2
- package/src/worklet/engine-parameter-facade.ts +55 -5
- package/src/worklet/engine-processor.ts +435 -62
- package/src/worklet/engine-register.ts +9 -1
- package/src/worklet/engine-strips.ts +19 -3
- package/src/worklet/engine-sync.ts +2 -6
- package/src/worklet/engine-tempo-facade.ts +8 -6
- package/src/worklet/engine.ts +390 -13
- package/src/worklet/guards.ts +121 -8
- package/src/worklet/messages.ts +166 -8
- package/src/worklet/protocol.ts +305 -17
- package/src/worklet/voice-changer-processor.ts +12 -15
- package/src/worklet.ts +20 -1
package/dist/worklet.js
CHANGED
|
@@ -1,3 +1,397 @@
|
|
|
1
|
+
// src/opfs_clip_pages.ts
|
|
2
|
+
var opfsClipPageWorkerSource = `
|
|
3
|
+
const sonareClipPageReadQueues = new Map();
|
|
4
|
+
|
|
5
|
+
function sonareEnqueueClipPageRead(key, task) {
|
|
6
|
+
const previous = sonareClipPageReadQueues.get(key) || Promise.resolve();
|
|
7
|
+
const next = previous.catch(() => undefined).then(task);
|
|
8
|
+
const queued = next.finally(() => {
|
|
9
|
+
if (sonareClipPageReadQueues.get(key) === queued) {
|
|
10
|
+
sonareClipPageReadQueues.delete(key);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
sonareClipPageReadQueues.set(key, queued);
|
|
14
|
+
return next;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
self.onmessage = async (event) => {
|
|
18
|
+
const message = event.data;
|
|
19
|
+
if (!message || message.type !== 'sonare:read-clip-page') return;
|
|
20
|
+
const { requestId, path, pageIndex, numChannels, numSamples, pageFrames, dataOffsetBytes = 0 } = message;
|
|
21
|
+
await sonareEnqueueClipPageRead(String(path), async () => {
|
|
22
|
+
try {
|
|
23
|
+
if (pageIndex < 0) {
|
|
24
|
+
self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const startFrame = pageIndex * pageFrames;
|
|
28
|
+
if (startFrame >= numSamples) {
|
|
29
|
+
self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const root = await self.navigator.storage.getDirectory();
|
|
33
|
+
let dir = root;
|
|
34
|
+
const parts = String(path).split('/').filter(Boolean);
|
|
35
|
+
for (let i = 0; i < parts.length - 1; ++i) {
|
|
36
|
+
dir = await dir.getDirectoryHandle(parts[i]);
|
|
37
|
+
}
|
|
38
|
+
const fileHandle = await dir.getFileHandle(parts[parts.length - 1]);
|
|
39
|
+
const access = await fileHandle.createSyncAccessHandle();
|
|
40
|
+
try {
|
|
41
|
+
const frames = Math.min(pageFrames, numSamples - startFrame);
|
|
42
|
+
const frameBytes = numChannels * 4;
|
|
43
|
+
const bytes = new Uint8Array(frames * frameBytes);
|
|
44
|
+
let bytesReadTotal = 0;
|
|
45
|
+
const readOffset = dataOffsetBytes + startFrame * frameBytes;
|
|
46
|
+
while (bytesReadTotal < bytes.byteLength) {
|
|
47
|
+
const bytesRead = access.read(bytes.subarray(bytesReadTotal), {
|
|
48
|
+
at: readOffset + bytesReadTotal,
|
|
49
|
+
});
|
|
50
|
+
if (bytesRead <= 0) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
bytesReadTotal += bytesRead;
|
|
54
|
+
}
|
|
55
|
+
if (bytesReadTotal !== bytes.byteLength || bytesReadTotal % frameBytes !== 0) {
|
|
56
|
+
self.postMessage({ type: 'sonare:clip-page', requestId, pageIndex, ok: false });
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const framesRead = bytesReadTotal / frameBytes;
|
|
60
|
+
const view = new DataView(bytes.buffer, 0, framesRead * frameBytes);
|
|
61
|
+
const channelBuffers = Array.from({ length: numChannels }, () => new ArrayBuffer(framesRead * 4));
|
|
62
|
+
for (let ch = 0; ch < numChannels; ++ch) {
|
|
63
|
+
const channel = new Float32Array(channelBuffers[ch]);
|
|
64
|
+
for (let frame = 0; frame < framesRead; ++frame) {
|
|
65
|
+
channel[frame] = view.getFloat32((frame * numChannels + ch) * 4, true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
self.postMessage(
|
|
69
|
+
{ type: 'sonare:clip-page', requestId, pageIndex, ok: true, frames: framesRead, channelBuffers },
|
|
70
|
+
channelBuffers,
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
access.close();
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
self.postMessage({
|
|
77
|
+
type: 'sonare:clip-page',
|
|
78
|
+
requestId,
|
|
79
|
+
pageIndex,
|
|
80
|
+
ok: false,
|
|
81
|
+
error: error instanceof Error ? error.message : String(error),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
`;
|
|
87
|
+
function createOpfsClipPageWorker() {
|
|
88
|
+
const blob = new Blob([opfsClipPageWorkerSource], { type: "text/javascript" });
|
|
89
|
+
const url = URL.createObjectURL(blob);
|
|
90
|
+
try {
|
|
91
|
+
return new Worker(url);
|
|
92
|
+
} finally {
|
|
93
|
+
URL.revokeObjectURL(url);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function createOpfsClipPageProvider(engine, options) {
|
|
97
|
+
if (options.numChannels <= 0 || options.numSamples <= 0 || options.pageFrames <= 0) {
|
|
98
|
+
throw new Error("numChannels, numSamples, and pageFrames must be positive");
|
|
99
|
+
}
|
|
100
|
+
const provider = engine.createClipPageProvider(
|
|
101
|
+
options.numChannels,
|
|
102
|
+
options.numSamples,
|
|
103
|
+
options.pageFrames
|
|
104
|
+
);
|
|
105
|
+
const worker = options.worker ?? createOpfsClipPageWorker();
|
|
106
|
+
const ownsWorker = options.worker === void 0 || options.terminateWorkerOnClose === true;
|
|
107
|
+
let nextRequestId = 1;
|
|
108
|
+
let closed = false;
|
|
109
|
+
let readQueue = Promise.resolve();
|
|
110
|
+
const pending = /* @__PURE__ */ new Map();
|
|
111
|
+
const onMessage = (event) => {
|
|
112
|
+
const response = event.data;
|
|
113
|
+
if (response?.type !== "sonare:clip-page") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const entry = pending.get(response.requestId);
|
|
117
|
+
if (!entry) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
pending.delete(response.requestId);
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
entry.resolve(false);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const channels = response.channels ?? response.channelBuffers?.map(
|
|
126
|
+
(buffer) => new Float32Array(buffer, 0, response.frames ?? buffer.byteLength / 4)
|
|
127
|
+
);
|
|
128
|
+
if (!channels || channels.length === 0) {
|
|
129
|
+
entry.resolve(false);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
provider.supply(response.pageIndex, channels);
|
|
134
|
+
options.onPageSupplied?.(response.pageIndex, channels);
|
|
135
|
+
} catch {
|
|
136
|
+
entry.resolve(false);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
entry.resolve(true);
|
|
140
|
+
};
|
|
141
|
+
worker.addEventListener("message", onMessage);
|
|
142
|
+
const supplyPage = (pageIndex) => {
|
|
143
|
+
if (closed) {
|
|
144
|
+
return Promise.reject(new Error("OpfsClipPageProvider is closed"));
|
|
145
|
+
}
|
|
146
|
+
const requestId = nextRequestId++;
|
|
147
|
+
const promise = new Promise((resolve, reject) => {
|
|
148
|
+
pending.set(requestId, { resolve, reject });
|
|
149
|
+
});
|
|
150
|
+
readQueue = readQueue.catch(() => void 0).then(() => {
|
|
151
|
+
if (closed) {
|
|
152
|
+
const entry = pending.get(requestId);
|
|
153
|
+
pending.delete(requestId);
|
|
154
|
+
entry?.reject(new Error("OpfsClipPageProvider is closed"));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
worker.postMessage({
|
|
158
|
+
type: "sonare:read-clip-page",
|
|
159
|
+
requestId,
|
|
160
|
+
path: options.path,
|
|
161
|
+
pageIndex,
|
|
162
|
+
numChannels: options.numChannels,
|
|
163
|
+
numSamples: options.numSamples,
|
|
164
|
+
pageFrames: options.pageFrames,
|
|
165
|
+
dataOffsetBytes: options.dataOffsetBytes ?? 0
|
|
166
|
+
});
|
|
167
|
+
return promise.then(
|
|
168
|
+
() => void 0,
|
|
169
|
+
() => void 0
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
readQueue.catch(() => {
|
|
173
|
+
});
|
|
174
|
+
return promise;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
provider,
|
|
178
|
+
supplyPage,
|
|
179
|
+
supplyRequest(request) {
|
|
180
|
+
return supplyPage(Math.floor(request.sample / options.pageFrames));
|
|
181
|
+
},
|
|
182
|
+
clearPage(pageIndex) {
|
|
183
|
+
if (closed) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
provider.clear(pageIndex);
|
|
187
|
+
options.onPageCleared?.(pageIndex);
|
|
188
|
+
},
|
|
189
|
+
close() {
|
|
190
|
+
if (closed) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
closed = true;
|
|
194
|
+
worker.removeEventListener("message", onMessage);
|
|
195
|
+
for (const entry of pending.values()) {
|
|
196
|
+
entry.reject(new Error("OpfsClipPageProvider is closed"));
|
|
197
|
+
}
|
|
198
|
+
pending.clear();
|
|
199
|
+
provider.destroy();
|
|
200
|
+
options.onClose?.();
|
|
201
|
+
if (ownsWorker) {
|
|
202
|
+
worker.terminate();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/clip_page_streamer.ts
|
|
209
|
+
var ClipPageStreamer = class {
|
|
210
|
+
constructor(engine, options = {}) {
|
|
211
|
+
this.sources = /* @__PURE__ */ new Map();
|
|
212
|
+
this.closed = false;
|
|
213
|
+
this.engine = engine;
|
|
214
|
+
this.readAheadPages = Math.max(0, Math.floor(options.readAheadPages ?? 2));
|
|
215
|
+
this.retainBehindPages = Math.max(0, Math.floor(options.retainBehindPages ?? 1));
|
|
216
|
+
this.maxRequestsPerPump = Math.max(1, Math.floor(options.maxRequestsPerPump ?? 256));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Register a paged clip. Pages already supplied to the provider before
|
|
220
|
+
* registration (for example a primed first page) should be passed in
|
|
221
|
+
* `initialResidentPages` so they participate in eviction.
|
|
222
|
+
*/
|
|
223
|
+
addSource(source, initialResidentPages = []) {
|
|
224
|
+
if (source.pageFrames <= 0 || source.numSamples <= 0) {
|
|
225
|
+
throw new Error("pageFrames and numSamples must be positive");
|
|
226
|
+
}
|
|
227
|
+
const lastPage = Math.ceil(source.numSamples / source.pageFrames) - 1;
|
|
228
|
+
const previous = this.sources.get(source.clipId);
|
|
229
|
+
if (previous) {
|
|
230
|
+
this.resetState(previous);
|
|
231
|
+
}
|
|
232
|
+
this.sources.set(source.clipId, {
|
|
233
|
+
source,
|
|
234
|
+
lastPage,
|
|
235
|
+
generation: 0,
|
|
236
|
+
lastFrontier: null,
|
|
237
|
+
resident: new Map(Array.from(initialResidentPages, (page) => [page, 0]))
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/** Stop tracking a clip. Does not close its binding (the caller owns that). */
|
|
241
|
+
removeSource(clipId) {
|
|
242
|
+
const state = this.sources.get(clipId);
|
|
243
|
+
if (state) {
|
|
244
|
+
this.resetState(state);
|
|
245
|
+
}
|
|
246
|
+
this.sources.delete(clipId);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Explicitly start a new playback generation after a host seek/loop. Resident
|
|
250
|
+
* pages are evicted and any older in-flight fetch is cleared when it settles.
|
|
251
|
+
* The next miss establishes the new bounded window.
|
|
252
|
+
*/
|
|
253
|
+
resetSource(clipId) {
|
|
254
|
+
const state = this.sources.get(clipId);
|
|
255
|
+
if (state) {
|
|
256
|
+
this.resetState(state);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Drain pending page-miss requests, fetch the missing pages plus their
|
|
261
|
+
* read-ahead window, and evict out-of-window pages. Resolves once this round's
|
|
262
|
+
* fetches settle. Concurrent fetches are serialized inside each binding.
|
|
263
|
+
*/
|
|
264
|
+
async pump() {
|
|
265
|
+
if (this.closed) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const frontiers = /* @__PURE__ */ new Map();
|
|
269
|
+
for (let drained = 0; drained < this.maxRequestsPerPump; ++drained) {
|
|
270
|
+
const request = this.engine.popClipPageRequest();
|
|
271
|
+
if (!request) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
const state = this.sources.get(request.clipId);
|
|
275
|
+
if (!state) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const page = request.pageIndex !== void 0 ? request.pageIndex : Math.floor((request.sample ?? Number.NaN) / state.source.pageFrames);
|
|
279
|
+
if (!Number.isInteger(page) || page < 0 || page > state.lastPage) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
frontiers.set(request.clipId, page);
|
|
283
|
+
}
|
|
284
|
+
const fetches = [];
|
|
285
|
+
for (const [clipId, frontier] of frontiers) {
|
|
286
|
+
const state = this.sources.get(clipId);
|
|
287
|
+
if (!state) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
fetches.push(...this.serviceFrontier(state, frontier));
|
|
291
|
+
}
|
|
292
|
+
await Promise.all(fetches);
|
|
293
|
+
}
|
|
294
|
+
/** Close every registered clip's binding and stop tracking. */
|
|
295
|
+
close() {
|
|
296
|
+
if (this.closed) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this.closed = true;
|
|
300
|
+
for (const state of this.sources.values()) {
|
|
301
|
+
this.resetState(state);
|
|
302
|
+
state.source.binding.close();
|
|
303
|
+
}
|
|
304
|
+
this.sources.clear();
|
|
305
|
+
}
|
|
306
|
+
serviceFrontier(state, frontier) {
|
|
307
|
+
if (state.lastFrontier !== null && frontier < state.lastFrontier) {
|
|
308
|
+
this.resetState(state);
|
|
309
|
+
}
|
|
310
|
+
state.lastFrontier = frontier;
|
|
311
|
+
const generation = state.generation;
|
|
312
|
+
const low = Math.max(0, frontier - this.retainBehindPages);
|
|
313
|
+
const high = Math.min(state.lastPage, frontier + this.readAheadPages);
|
|
314
|
+
for (const page of state.resident.keys()) {
|
|
315
|
+
if (page < low || page > high) {
|
|
316
|
+
this.clearPage(state, page);
|
|
317
|
+
state.resident.delete(page);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const fetches = [];
|
|
321
|
+
for (let page = low; page <= high; ++page) {
|
|
322
|
+
if (state.resident.get(page) === generation) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
state.resident.set(page, generation);
|
|
326
|
+
const pageIndex = page;
|
|
327
|
+
fetches.push(
|
|
328
|
+
state.source.binding.supplyPage(pageIndex).then(
|
|
329
|
+
(ok) => {
|
|
330
|
+
if (state.generation !== generation) {
|
|
331
|
+
this.clearPage(state, pageIndex);
|
|
332
|
+
} else if (!ok && state.resident.get(pageIndex) === generation) {
|
|
333
|
+
state.resident.delete(pageIndex);
|
|
334
|
+
}
|
|
335
|
+
return ok;
|
|
336
|
+
},
|
|
337
|
+
(error) => {
|
|
338
|
+
if (state.resident.get(pageIndex) === generation) {
|
|
339
|
+
state.resident.delete(pageIndex);
|
|
340
|
+
}
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return fetches;
|
|
347
|
+
}
|
|
348
|
+
resetState(state) {
|
|
349
|
+
state.generation += 1;
|
|
350
|
+
state.lastFrontier = null;
|
|
351
|
+
for (const page of state.resident.keys()) {
|
|
352
|
+
this.clearPage(state, page);
|
|
353
|
+
}
|
|
354
|
+
state.resident.clear();
|
|
355
|
+
}
|
|
356
|
+
clearPage(state, pageIndex) {
|
|
357
|
+
if (state.source.binding.clearPage) {
|
|
358
|
+
state.source.binding.clearPage(pageIndex);
|
|
359
|
+
} else {
|
|
360
|
+
state.source.binding.provider.clear(pageIndex);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
async function attachOpfsClipStream(streamerOrEngine, engineOrOptions, maybeOptions) {
|
|
365
|
+
if (!(streamerOrEngine instanceof ClipPageStreamer)) {
|
|
366
|
+
return streamerOrEngine.attachOpfsClipStream(engineOrOptions);
|
|
367
|
+
}
|
|
368
|
+
const streamer = streamerOrEngine;
|
|
369
|
+
const engine = engineOrOptions;
|
|
370
|
+
const options = maybeOptions;
|
|
371
|
+
if (!options) {
|
|
372
|
+
throw new Error("attachOpfsClipStream requires options.");
|
|
373
|
+
}
|
|
374
|
+
const { clipId, primePages = 1, ...providerOptions } = options;
|
|
375
|
+
const binding = createOpfsClipPageProvider(engine, providerOptions);
|
|
376
|
+
const lastPage = Math.ceil(providerOptions.numSamples / providerOptions.pageFrames) - 1;
|
|
377
|
+
const primed = [];
|
|
378
|
+
for (let page = 0; page < primePages && page <= lastPage; ++page) {
|
|
379
|
+
if (await binding.supplyPage(page)) {
|
|
380
|
+
primed.push(page);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
streamer.addSource(
|
|
384
|
+
{
|
|
385
|
+
clipId,
|
|
386
|
+
binding,
|
|
387
|
+
pageFrames: providerOptions.pageFrames,
|
|
388
|
+
numSamples: providerOptions.numSamples
|
|
389
|
+
},
|
|
390
|
+
primed
|
|
391
|
+
);
|
|
392
|
+
return { binding, provider: binding.provider };
|
|
393
|
+
}
|
|
394
|
+
|
|
1
395
|
// src/errors.ts
|
|
2
396
|
var SonareError = class extends Error {
|
|
3
397
|
constructor(code, codeName, message) {
|
|
@@ -151,79 +545,85 @@ function getSonareModule() {
|
|
|
151
545
|
}
|
|
152
546
|
|
|
153
547
|
// src/codes.ts
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return 0;
|
|
158
|
-
case "exponential":
|
|
159
|
-
return 1;
|
|
160
|
-
case "hold":
|
|
161
|
-
return 2;
|
|
162
|
-
case "s-curve":
|
|
163
|
-
return 3;
|
|
164
|
-
default:
|
|
165
|
-
throw new Error(`Invalid automation curve: ${curve}`);
|
|
548
|
+
function resolveOrdinalInRange(value, min, max, enumName) {
|
|
549
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) {
|
|
550
|
+
throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
|
|
166
551
|
}
|
|
552
|
+
return value;
|
|
167
553
|
}
|
|
168
|
-
function
|
|
169
|
-
if (typeof
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
default:
|
|
182
|
-
throw new Error(`Invalid pan law: ${panLaw}`);
|
|
554
|
+
function resolveEnumOrdinal(value, values, enumName) {
|
|
555
|
+
if (typeof value === "number") {
|
|
556
|
+
const ordinals = Object.values(values);
|
|
557
|
+
const ordinal = resolveOrdinalInRange(
|
|
558
|
+
value,
|
|
559
|
+
Math.min(...ordinals),
|
|
560
|
+
Math.max(...ordinals),
|
|
561
|
+
enumName
|
|
562
|
+
);
|
|
563
|
+
if (!ordinals.includes(ordinal)) {
|
|
564
|
+
throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
|
|
565
|
+
}
|
|
566
|
+
return ordinal;
|
|
183
567
|
}
|
|
568
|
+
if (typeof value === "string") {
|
|
569
|
+
const ordinal = values[value];
|
|
570
|
+
if (ordinal !== void 0) {
|
|
571
|
+
return ordinal;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
throw new RangeError(`Invalid ${enumName}: ${String(value)}`);
|
|
575
|
+
}
|
|
576
|
+
var AUTOMATION_CURVE_VALUES = {
|
|
577
|
+
linear: 0,
|
|
578
|
+
exponential: 1,
|
|
579
|
+
hold: 2,
|
|
580
|
+
"s-curve": 3
|
|
581
|
+
};
|
|
582
|
+
var PAN_LAW_VALUES = {
|
|
583
|
+
const3db: 0,
|
|
584
|
+
"const-3db": 0,
|
|
585
|
+
"-3db": 0,
|
|
586
|
+
"const4.5db": 1,
|
|
587
|
+
"const-4.5db": 1,
|
|
588
|
+
"-4.5db": 1,
|
|
589
|
+
const6db: 2,
|
|
590
|
+
"const-6db": 2,
|
|
591
|
+
"-6db": 2,
|
|
592
|
+
linear0db: 3,
|
|
593
|
+
"linear-0db": 3,
|
|
594
|
+
linear: 3,
|
|
595
|
+
"0db": 3
|
|
596
|
+
};
|
|
597
|
+
var PAN_MODE_VALUES = {
|
|
598
|
+
balance: 0,
|
|
599
|
+
pan: 1,
|
|
600
|
+
stereopan: 1,
|
|
601
|
+
"stereo-pan": 1,
|
|
602
|
+
dualpan: 2,
|
|
603
|
+
"dual-pan": 2
|
|
604
|
+
};
|
|
605
|
+
var METER_TAP_VALUES = { preFader: 0, postFader: 1 };
|
|
606
|
+
var SEND_TIMING_VALUES = { postFader: 0, preFader: 1 };
|
|
607
|
+
var TRACK_MONITOR_MODE_VALUES = { off: 0, pfl: 1, afl: 2 };
|
|
608
|
+
function automationCurveCode(curve) {
|
|
609
|
+
return resolveEnumOrdinal(curve, AUTOMATION_CURVE_VALUES, "automation curve");
|
|
610
|
+
}
|
|
611
|
+
function panLawCode(panLaw) {
|
|
612
|
+
const normalized = typeof panLaw === "string" ? panLaw.toLowerCase().replace(/_/g, "-") : panLaw;
|
|
613
|
+
return resolveEnumOrdinal(normalized, PAN_LAW_VALUES, "pan law");
|
|
184
614
|
}
|
|
185
615
|
function panModeCode(panMode) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
switch (panMode) {
|
|
190
|
-
case "balance":
|
|
191
|
-
return 0;
|
|
192
|
-
case "stereoPan":
|
|
193
|
-
case "stereo-pan":
|
|
194
|
-
return 1;
|
|
195
|
-
case "dualPan":
|
|
196
|
-
case "dual-pan":
|
|
197
|
-
return 2;
|
|
198
|
-
default:
|
|
199
|
-
throw new Error(`Invalid pan mode: ${panMode}`);
|
|
200
|
-
}
|
|
616
|
+
const normalized = typeof panMode === "string" ? panMode.replace(/_/g, "-").toLowerCase() : panMode;
|
|
617
|
+
return resolveEnumOrdinal(normalized, PAN_MODE_VALUES, "pan mode");
|
|
201
618
|
}
|
|
202
619
|
function meterTapCode(tap) {
|
|
203
|
-
|
|
204
|
-
return tap;
|
|
205
|
-
}
|
|
206
|
-
switch (tap) {
|
|
207
|
-
case "preFader":
|
|
208
|
-
return 0;
|
|
209
|
-
case "postFader":
|
|
210
|
-
return 1;
|
|
211
|
-
default:
|
|
212
|
-
throw new Error(`Invalid meter tap: ${tap}`);
|
|
213
|
-
}
|
|
620
|
+
return resolveEnumOrdinal(tap, METER_TAP_VALUES, "meter tap");
|
|
214
621
|
}
|
|
215
622
|
function sendTimingCode(timing) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
case "postFader":
|
|
221
|
-
return 0;
|
|
222
|
-
case "preFader":
|
|
223
|
-
return 1;
|
|
224
|
-
default:
|
|
225
|
-
throw new Error(`Invalid send timing: ${timing}`);
|
|
226
|
-
}
|
|
623
|
+
return resolveEnumOrdinal(timing, SEND_TIMING_VALUES, "send timing");
|
|
624
|
+
}
|
|
625
|
+
function trackMonitorModeCode(mode) {
|
|
626
|
+
return resolveEnumOrdinal(mode, TRACK_MONITOR_MODE_VALUES, "track monitor mode");
|
|
227
627
|
}
|
|
228
628
|
|
|
229
629
|
// src/realtime_engine.ts
|
|
@@ -244,7 +644,7 @@ function engineCapabilities() {
|
|
|
244
644
|
};
|
|
245
645
|
}
|
|
246
646
|
var RealtimeEngine = class {
|
|
247
|
-
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
|
|
647
|
+
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
248
648
|
const module2 = getSonareModule();
|
|
249
649
|
const capabilities = engineCapabilities();
|
|
250
650
|
if (!capabilities.abiCompatible) {
|
|
@@ -256,11 +656,18 @@ var RealtimeEngine = class {
|
|
|
256
656
|
sampleRate,
|
|
257
657
|
maxBlockSize,
|
|
258
658
|
commandCapacity,
|
|
259
|
-
telemetryCapacity
|
|
659
|
+
telemetryCapacity,
|
|
660
|
+
maxChannels
|
|
260
661
|
);
|
|
261
662
|
}
|
|
262
|
-
prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024) {
|
|
263
|
-
this.native.
|
|
663
|
+
prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
664
|
+
this.native.prepareWithChannels(
|
|
665
|
+
sampleRate,
|
|
666
|
+
maxBlockSize,
|
|
667
|
+
commandCapacity,
|
|
668
|
+
telemetryCapacity,
|
|
669
|
+
maxChannels
|
|
670
|
+
);
|
|
264
671
|
}
|
|
265
672
|
/** Queue a sample-accurate parameter change (engine kSetParam). */
|
|
266
673
|
setParameter(paramId, value, renderFrame = -1) {
|
|
@@ -281,6 +688,10 @@ var RealtimeEngine = class {
|
|
|
281
688
|
setSoloMute(laneIndex, solo, mute, renderFrame = -1) {
|
|
282
689
|
this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
|
|
283
690
|
}
|
|
691
|
+
/** Queue a per-track PFL/AFL monitor tap mode change. */
|
|
692
|
+
setTrackMonitorMode(laneIndex, mode, renderFrame = -1) {
|
|
693
|
+
this.native.setTrackMonitorMode(laneIndex, trackMonitorModeCode(mode), renderFrame);
|
|
694
|
+
}
|
|
284
695
|
setMidiClips(clips) {
|
|
285
696
|
this.native.setMidiClips(clips);
|
|
286
697
|
}
|
|
@@ -386,6 +797,9 @@ var RealtimeEngine = class {
|
|
|
386
797
|
externalMidiDroppedCount() {
|
|
387
798
|
return this.native.externalMidiDroppedCount();
|
|
388
799
|
}
|
|
800
|
+
externalMidiPendingCount() {
|
|
801
|
+
return this.native.externalMidiPendingCount();
|
|
802
|
+
}
|
|
389
803
|
/**
|
|
390
804
|
* Drain queued external-MIDI events, already lowered to MIDI 1.0 byte
|
|
391
805
|
* messages ready to write to a Web MIDI output port. Call once per audio
|
|
@@ -396,6 +810,25 @@ var RealtimeEngine = class {
|
|
|
396
810
|
drainExternalMidi(maxRecords = 1024) {
|
|
397
811
|
return this.native.drainExternalMidi(maxRecords);
|
|
398
812
|
}
|
|
813
|
+
/** Scalar, allocation-free external-MIDI drain for AudioWorklet SAB output. */
|
|
814
|
+
popExternalMidiToScratch() {
|
|
815
|
+
return this.native.popExternalMidiToScratch();
|
|
816
|
+
}
|
|
817
|
+
externalMidiScratchDestinationId() {
|
|
818
|
+
return this.native.externalMidiScratchDestinationId();
|
|
819
|
+
}
|
|
820
|
+
externalMidiScratchRenderFrame() {
|
|
821
|
+
return this.native.externalMidiScratchRenderFrame();
|
|
822
|
+
}
|
|
823
|
+
externalMidiScratchByteWord() {
|
|
824
|
+
return this.native.externalMidiScratchByteWord();
|
|
825
|
+
}
|
|
826
|
+
externalMidiScratchByteCount() {
|
|
827
|
+
return this.native.externalMidiScratchByteCount();
|
|
828
|
+
}
|
|
829
|
+
consumeExternalMidiScratch() {
|
|
830
|
+
this.native.consumeExternalMidiScratch();
|
|
831
|
+
}
|
|
399
832
|
pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
|
|
400
833
|
this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
|
|
401
834
|
}
|
|
@@ -420,6 +853,10 @@ var RealtimeEngine = class {
|
|
|
420
853
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
421
854
|
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
422
855
|
}
|
|
856
|
+
/** Queue one immediate MIDI 1.0 channel-voice UMP word for a destination. */
|
|
857
|
+
pushMidiUmp(destinationId, word0, renderFrame = -1) {
|
|
858
|
+
this.native.pushMidiUmp(destinationId, word0, renderFrame);
|
|
859
|
+
}
|
|
423
860
|
/**
|
|
424
861
|
* Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
|
|
425
862
|
* the full message including the leading 0xF0 and trailing 0xF7 (1..512
|
|
@@ -465,6 +902,10 @@ var RealtimeEngine = class {
|
|
|
465
902
|
settleParameters() {
|
|
466
903
|
this.native.settleParameters();
|
|
467
904
|
}
|
|
905
|
+
/** Drains queued commands on an offline/control-only engine immediately. */
|
|
906
|
+
flushControlCommands() {
|
|
907
|
+
this.native.flushControlCommands();
|
|
908
|
+
}
|
|
468
909
|
seekPpq(ppq, renderFrame = -1) {
|
|
469
910
|
this.native.seekPpq(ppq, renderFrame);
|
|
470
911
|
}
|
|
@@ -719,6 +1160,23 @@ var RealtimeEngine = class {
|
|
|
719
1160
|
popClipPageRequest() {
|
|
720
1161
|
return this.native.popClipPageRequest();
|
|
721
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Moves one native request into the binding's persistent scalar scratch.
|
|
1165
|
+
* This avoids creating an embind JS object in AudioWorklet process().
|
|
1166
|
+
*/
|
|
1167
|
+
popClipPageRequestToScratch() {
|
|
1168
|
+
return this.native.popClipPageRequestToScratch();
|
|
1169
|
+
}
|
|
1170
|
+
clipPageRequestScratchClipId() {
|
|
1171
|
+
return this.native.clipPageRequestScratchClipId();
|
|
1172
|
+
}
|
|
1173
|
+
clipPageRequestScratchSample() {
|
|
1174
|
+
return this.native.clipPageRequestScratchSample();
|
|
1175
|
+
}
|
|
1176
|
+
/** Cumulative page misses dropped because the native bounded request queue was full. */
|
|
1177
|
+
clipPageRequestOverflowCount() {
|
|
1178
|
+
return this.native.clipPageRequestOverflowCount();
|
|
1179
|
+
}
|
|
722
1180
|
setCaptureBuffer(numChannels, capacityFrames) {
|
|
723
1181
|
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
724
1182
|
}
|
|
@@ -747,6 +1205,10 @@ var RealtimeEngine = class {
|
|
|
747
1205
|
capturedAudio() {
|
|
748
1206
|
return this.native.capturedAudio();
|
|
749
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Renders in place, adding engine output to `channels`. Zero each plane first
|
|
1210
|
+
* when it contains no upstream input.
|
|
1211
|
+
*/
|
|
750
1212
|
process(channels) {
|
|
751
1213
|
return this.native.process(channels);
|
|
752
1214
|
}
|
|
@@ -769,6 +1231,7 @@ var RealtimeEngine = class {
|
|
|
769
1231
|
}
|
|
770
1232
|
/**
|
|
771
1233
|
* Runs the engine in place over the prepared per-channel scratch buffers.
|
|
1234
|
+
* Zero each active span first when it contains no upstream input.
|
|
772
1235
|
* Allocation-free: safe to call on the AudioWorklet render thread after
|
|
773
1236
|
* `prepareChannels`.
|
|
774
1237
|
*/
|
|
@@ -790,6 +1253,42 @@ var RealtimeEngine = class {
|
|
|
790
1253
|
drainTelemetry(maxRecords = 1024) {
|
|
791
1254
|
return this.native.drainTelemetry(maxRecords);
|
|
792
1255
|
}
|
|
1256
|
+
popTelemetryToScratch() {
|
|
1257
|
+
return this.native.popTelemetryToScratch();
|
|
1258
|
+
}
|
|
1259
|
+
telemetryScratchType() {
|
|
1260
|
+
return this.native.telemetryScratchType();
|
|
1261
|
+
}
|
|
1262
|
+
telemetryScratchError() {
|
|
1263
|
+
return this.native.telemetryScratchError();
|
|
1264
|
+
}
|
|
1265
|
+
telemetryScratchRenderFrame() {
|
|
1266
|
+
return Number(this.native.telemetryScratchRenderFrame());
|
|
1267
|
+
}
|
|
1268
|
+
telemetryScratchTimelineSample() {
|
|
1269
|
+
return Number(this.native.telemetryScratchTimelineSample());
|
|
1270
|
+
}
|
|
1271
|
+
telemetryScratchAudibleTimelineSample() {
|
|
1272
|
+
return Number(this.native.telemetryScratchAudibleTimelineSample());
|
|
1273
|
+
}
|
|
1274
|
+
telemetryScratchGraphLatencySamplesQ8() {
|
|
1275
|
+
return this.native.telemetryScratchGraphLatencySamplesQ8();
|
|
1276
|
+
}
|
|
1277
|
+
telemetryScratchValue() {
|
|
1278
|
+
return this.native.telemetryScratchValue();
|
|
1279
|
+
}
|
|
1280
|
+
popMeterTelemetryToScratch() {
|
|
1281
|
+
return this.native.popMeterTelemetryToScratch();
|
|
1282
|
+
}
|
|
1283
|
+
meterScratchTargetId() {
|
|
1284
|
+
return this.native.meterScratchTargetId();
|
|
1285
|
+
}
|
|
1286
|
+
meterScratchRenderFrame() {
|
|
1287
|
+
return Number(this.native.meterScratchRenderFrame());
|
|
1288
|
+
}
|
|
1289
|
+
meterScratchValue(field) {
|
|
1290
|
+
return this.native.meterScratchValue(field);
|
|
1291
|
+
}
|
|
793
1292
|
drainMeterTelemetry(maxRecords = 1024) {
|
|
794
1293
|
return this.native.drainMeterTelemetry(maxRecords);
|
|
795
1294
|
}
|
|
@@ -817,6 +1316,30 @@ var RealtimeEngine = class {
|
|
|
817
1316
|
drainScopeTelemetry(maxRecords = 1024) {
|
|
818
1317
|
return this.native.drainScopeTelemetry(maxRecords);
|
|
819
1318
|
}
|
|
1319
|
+
popScopeTelemetryToScratch() {
|
|
1320
|
+
return this.native.popScopeTelemetryToScratch();
|
|
1321
|
+
}
|
|
1322
|
+
scopeScratchTargetId() {
|
|
1323
|
+
return this.native.scopeScratchTargetId();
|
|
1324
|
+
}
|
|
1325
|
+
scopeScratchRenderFrame() {
|
|
1326
|
+
return Number(this.native.scopeScratchRenderFrame());
|
|
1327
|
+
}
|
|
1328
|
+
scopeScratchBandCount() {
|
|
1329
|
+
return this.native.scopeScratchBandCount();
|
|
1330
|
+
}
|
|
1331
|
+
scopeScratchBand(index) {
|
|
1332
|
+
return this.native.scopeScratchBand(index);
|
|
1333
|
+
}
|
|
1334
|
+
scopeScratchPointCount() {
|
|
1335
|
+
return this.native.scopeScratchPointCount();
|
|
1336
|
+
}
|
|
1337
|
+
scopeScratchPointLeft(index) {
|
|
1338
|
+
return this.native.scopeScratchPointLeft(index);
|
|
1339
|
+
}
|
|
1340
|
+
scopeScratchPointRight(index) {
|
|
1341
|
+
return this.native.scopeScratchPointRight(index);
|
|
1342
|
+
}
|
|
820
1343
|
destroy() {
|
|
821
1344
|
this.native.delete();
|
|
822
1345
|
}
|
|
@@ -1087,7 +1610,7 @@ var Mixer = class _Mixer {
|
|
|
1087
1610
|
setPolarityInvert(stripIndex, invertLeft, invertRight) {
|
|
1088
1611
|
this.mixer.setPolarityInvert(stripIndex, invertLeft, invertRight);
|
|
1089
1612
|
}
|
|
1090
|
-
/** Set the strip's pan law. */
|
|
1613
|
+
/** Set the strip's pan law (a {@link PanLawName} alias or raw C ABI ordinal). */
|
|
1091
1614
|
setPanLaw(stripIndex, panLaw) {
|
|
1092
1615
|
this.mixer.setPanLaw(stripIndex, panLawCode(panLaw));
|
|
1093
1616
|
}
|
|
@@ -1274,65 +1797,18 @@ var Mixer = class _Mixer {
|
|
|
1274
1797
|
};
|
|
1275
1798
|
|
|
1276
1799
|
// src/realtime_voice_changer.ts
|
|
1277
|
-
function isFlatVoiceChangerPod(config) {
|
|
1278
|
-
return typeof config === "object" && config !== null && "retuneSemitones" in config;
|
|
1279
|
-
}
|
|
1280
|
-
function flatVoiceChangerPodToNested(pod) {
|
|
1281
|
-
return {
|
|
1282
|
-
inputGainDb: pod.inputGainDb,
|
|
1283
|
-
outputGainDb: pod.outputGainDb,
|
|
1284
|
-
wetMix: pod.wetMix,
|
|
1285
|
-
retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
|
|
1286
|
-
formant: {
|
|
1287
|
-
factor: pod.formantFactor,
|
|
1288
|
-
amount: pod.formantAmount,
|
|
1289
|
-
body: pod.formantBody,
|
|
1290
|
-
brightness: pod.formantBrightness,
|
|
1291
|
-
nasal: pod.formantNasal
|
|
1292
|
-
},
|
|
1293
|
-
eq: {
|
|
1294
|
-
highpassHz: pod.eqHighpassHz,
|
|
1295
|
-
bodyDb: pod.eqBodyDb,
|
|
1296
|
-
presenceDb: pod.eqPresenceDb,
|
|
1297
|
-
airDb: pod.eqAirDb
|
|
1298
|
-
},
|
|
1299
|
-
gate: {
|
|
1300
|
-
thresholdDb: pod.gateThresholdDb,
|
|
1301
|
-
attackMs: pod.gateAttackMs,
|
|
1302
|
-
releaseMs: pod.gateReleaseMs,
|
|
1303
|
-
rangeDb: pod.gateRangeDb
|
|
1304
|
-
},
|
|
1305
|
-
compressor: {
|
|
1306
|
-
thresholdDb: pod.compressorThresholdDb,
|
|
1307
|
-
ratio: pod.compressorRatio,
|
|
1308
|
-
attackMs: pod.compressorAttackMs,
|
|
1309
|
-
releaseMs: pod.compressorReleaseMs,
|
|
1310
|
-
makeupGainDb: pod.compressorMakeupGainDb
|
|
1311
|
-
},
|
|
1312
|
-
deesser: {
|
|
1313
|
-
frequencyHz: pod.deesserFrequencyHz,
|
|
1314
|
-
thresholdDb: pod.deesserThresholdDb,
|
|
1315
|
-
ratio: pod.deesserRatio,
|
|
1316
|
-
rangeDb: pod.deesserRangeDb
|
|
1317
|
-
},
|
|
1318
|
-
reverb: {
|
|
1319
|
-
mix: pod.reverbMix,
|
|
1320
|
-
timeMs: pod.reverbTimeMs,
|
|
1321
|
-
damping: pod.reverbDamping,
|
|
1322
|
-
seed: pod.reverbSeed
|
|
1323
|
-
},
|
|
1324
|
-
limiter: {
|
|
1325
|
-
ceilingDb: pod.limiterCeilingDb,
|
|
1326
|
-
releaseMs: pod.limiterReleaseMs,
|
|
1327
|
-
enableIspLimiter: pod.limiterEnableIspLimiter,
|
|
1328
|
-
ispCeilingDbtp: pod.limiterIspCeilingDbtp
|
|
1329
|
-
}
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
1800
|
var RealtimeVoiceChanger = class {
|
|
1333
|
-
|
|
1801
|
+
/**
|
|
1802
|
+
* Creates a voice changer. Supplying `sampleRate` prepares it immediately,
|
|
1803
|
+
* matching the Node and Python constructors; omitting it preserves the
|
|
1804
|
+
* explicit {@link prepare} lifecycle for callers that configure later.
|
|
1805
|
+
*/
|
|
1806
|
+
constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
|
|
1334
1807
|
const module2 = getSonareModule();
|
|
1335
1808
|
this.changer = module2.createRealtimeVoiceChanger(config);
|
|
1809
|
+
if (sampleRate !== void 0) {
|
|
1810
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
1811
|
+
}
|
|
1336
1812
|
}
|
|
1337
1813
|
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
1338
1814
|
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
@@ -1341,8 +1817,15 @@ var RealtimeVoiceChanger = class {
|
|
|
1341
1817
|
this.changer.reset();
|
|
1342
1818
|
}
|
|
1343
1819
|
setConfig(config) {
|
|
1344
|
-
|
|
1345
|
-
|
|
1820
|
+
this.changer.setConfig(config);
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Apply a flat, pre-normalized config without JSON serialization. Intended
|
|
1824
|
+
* for AudioWorklet control messages whose sender prepared the POD on the
|
|
1825
|
+
* main thread.
|
|
1826
|
+
*/
|
|
1827
|
+
setPodConfig(config) {
|
|
1828
|
+
this.changer.setPodConfig(config);
|
|
1346
1829
|
}
|
|
1347
1830
|
configJson() {
|
|
1348
1831
|
return this.changer.configJson();
|
|
@@ -1350,6 +1833,13 @@ var RealtimeVoiceChanger = class {
|
|
|
1350
1833
|
latencySamples() {
|
|
1351
1834
|
return this.changer.latencySamples();
|
|
1352
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Monotonically increases whenever {@link prepare} can replace the native
|
|
1838
|
+
* scratch buffers. Cached WASM heap views must be reacquired after it changes.
|
|
1839
|
+
*/
|
|
1840
|
+
bufferGeneration() {
|
|
1841
|
+
return this.changer.bufferGeneration();
|
|
1842
|
+
}
|
|
1353
1843
|
processMono(samples) {
|
|
1354
1844
|
return this.changer.processMono(samples);
|
|
1355
1845
|
}
|
|
@@ -1426,10 +1916,12 @@ var RealtimeVoiceChanger = class {
|
|
|
1426
1916
|
createRealtimeMonoBuffer(numSamples) {
|
|
1427
1917
|
let input = this.getMonoInputBuffer(numSamples);
|
|
1428
1918
|
let output = this.getMonoOutputBuffer(numSamples);
|
|
1919
|
+
let generation = this.bufferGeneration();
|
|
1429
1920
|
const reacquireIfDetached = () => {
|
|
1430
|
-
if (input.byteLength === 0 || output.byteLength === 0) {
|
|
1921
|
+
if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
|
|
1431
1922
|
input = this.getMonoInputBuffer(numSamples);
|
|
1432
1923
|
output = this.getMonoOutputBuffer(numSamples);
|
|
1924
|
+
generation = this.bufferGeneration();
|
|
1433
1925
|
}
|
|
1434
1926
|
};
|
|
1435
1927
|
return {
|
|
@@ -1451,10 +1943,12 @@ var RealtimeVoiceChanger = class {
|
|
|
1451
1943
|
createRealtimeInterleavedBuffer(numFrames, numChannels) {
|
|
1452
1944
|
let input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
1453
1945
|
let output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
1946
|
+
let generation = this.bufferGeneration();
|
|
1454
1947
|
const reacquireIfDetached = () => {
|
|
1455
|
-
if (input.byteLength === 0 || output.byteLength === 0) {
|
|
1948
|
+
if (generation !== this.bufferGeneration() || input.byteLength === 0 || output.byteLength === 0) {
|
|
1456
1949
|
input = this.getInterleavedInputBuffer(numFrames, numChannels);
|
|
1457
1950
|
output = this.getInterleavedOutputBuffer(numFrames, numChannels);
|
|
1951
|
+
generation = this.bufferGeneration();
|
|
1458
1952
|
}
|
|
1459
1953
|
};
|
|
1460
1954
|
return {
|
|
@@ -1481,15 +1975,17 @@ var RealtimeVoiceChanger = class {
|
|
|
1481
1975
|
*/
|
|
1482
1976
|
createRealtimePlanarBuffer(numFrames, numChannels) {
|
|
1483
1977
|
let channels = [];
|
|
1978
|
+
let generation = this.bufferGeneration();
|
|
1484
1979
|
const acquire = () => {
|
|
1485
1980
|
channels = [];
|
|
1486
1981
|
for (let ch = 0; ch < numChannels; ch++) {
|
|
1487
1982
|
channels.push(this.getPlanarChannelBuffer(ch, numFrames));
|
|
1488
1983
|
}
|
|
1984
|
+
generation = this.bufferGeneration();
|
|
1489
1985
|
};
|
|
1490
1986
|
acquire();
|
|
1491
1987
|
const reacquireIfDetached = () => {
|
|
1492
|
-
if ((channels[0]?.byteLength ?? 0) === 0) {
|
|
1988
|
+
if (generation !== this.bufferGeneration() || (channels[0]?.byteLength ?? 0) === 0) {
|
|
1493
1989
|
acquire();
|
|
1494
1990
|
}
|
|
1495
1991
|
};
|
|
@@ -1571,7 +2067,7 @@ function resolveParamId(parameters, nodeId, param) {
|
|
|
1571
2067
|
if (byName) {
|
|
1572
2068
|
return byName.id;
|
|
1573
2069
|
}
|
|
1574
|
-
|
|
2070
|
+
throw new RangeError(`Unknown engine parameter ${JSON.stringify(param)} for node ${nodeId}`);
|
|
1575
2071
|
}
|
|
1576
2072
|
function curveCode(curve) {
|
|
1577
2073
|
if (typeof curve === "number") {
|
|
@@ -1636,12 +2132,16 @@ function buildTransportFacade(ctx) {
|
|
|
1636
2132
|
},
|
|
1637
2133
|
seekPpq: (ppq, sampleTime = -1) => {
|
|
1638
2134
|
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
1639
|
-
|
|
2135
|
+
const ok = ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
2136
|
+
ctx.flushOfflineMirror();
|
|
2137
|
+
return ok;
|
|
1640
2138
|
},
|
|
1641
2139
|
seekSeconds: (seconds, sampleTime = -1) => {
|
|
1642
2140
|
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
1643
2141
|
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
1644
|
-
|
|
2142
|
+
const ok = ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
2143
|
+
ctx.flushOfflineMirror();
|
|
2144
|
+
return ok;
|
|
1645
2145
|
},
|
|
1646
2146
|
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
1647
2147
|
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
@@ -1728,6 +2228,10 @@ function decodeFrame(lo, hi) {
|
|
|
1728
2228
|
var SONARE_ENGINE_RING_HEADER_INTS = 5;
|
|
1729
2229
|
var SONARE_ENGINE_COMMAND_RECORD_BYTES = 32;
|
|
1730
2230
|
var SONARE_ENGINE_TELEMETRY_RECORD_BYTES = 48;
|
|
2231
|
+
var SONARE_CLIP_PAGE_REQUEST_RING_HEADER_INTS = 5;
|
|
2232
|
+
var SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S = 2;
|
|
2233
|
+
var SONARE_EXTERNAL_MIDI_RING_HEADER_INTS = 5;
|
|
2234
|
+
var SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S = 4;
|
|
1731
2235
|
var SonareEngineCommandType = /* @__PURE__ */ ((SonareEngineCommandType2) => {
|
|
1732
2236
|
SonareEngineCommandType2[SonareEngineCommandType2["SetParam"] = 0] = "SetParam";
|
|
1733
2237
|
SonareEngineCommandType2[SonareEngineCommandType2["SetParamSmoothed"] = 1] = "SetParamSmoothed";
|
|
@@ -1747,6 +2251,7 @@ var SonareEngineCommandType = /* @__PURE__ */ ((SonareEngineCommandType2) => {
|
|
|
1747
2251
|
SonareEngineCommandType2[SonareEngineCommandType2["SetMetronome"] = 15] = "SetMetronome";
|
|
1748
2252
|
SonareEngineCommandType2[SonareEngineCommandType2["SetMarker"] = 16] = "SetMarker";
|
|
1749
2253
|
SonareEngineCommandType2[SonareEngineCommandType2["SeekMarker"] = 17] = "SeekMarker";
|
|
2254
|
+
SonareEngineCommandType2[SonareEngineCommandType2["SetTrackMonitorMode"] = 26] = "SetTrackMonitorMode";
|
|
1750
2255
|
return SonareEngineCommandType2;
|
|
1751
2256
|
})(SonareEngineCommandType || {});
|
|
1752
2257
|
var SonareEngineTelemetryType = /* @__PURE__ */ ((SonareEngineTelemetryType2) => {
|
|
@@ -1774,6 +2279,8 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
1774
2279
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["InsertAutomationOverflow"] = 16] = "InsertAutomationOverflow";
|
|
1775
2280
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MidiClockOverflow"] = 17] = "MidiClockOverflow";
|
|
1776
2281
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MetronomeOverflow"] = 18] = "MetronomeOverflow";
|
|
2282
|
+
SonareEngineTelemetryError2[SonareEngineTelemetryError2["InvalidCommand"] = 19] = "InvalidCommand";
|
|
2283
|
+
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MaxChannelsExceeded"] = 20] = "MaxChannelsExceeded";
|
|
1777
2284
|
return SonareEngineTelemetryError2;
|
|
1778
2285
|
})(SonareEngineTelemetryError || {});
|
|
1779
2286
|
function toDb(value) {
|
|
@@ -1971,6 +2478,128 @@ function sonareEngineTelemetryRingBufferByteLength(capacity) {
|
|
|
1971
2478
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1972
2479
|
return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_TELEMETRY_RECORD_BYTES;
|
|
1973
2480
|
}
|
|
2481
|
+
function sonareClipPageRequestRingBufferByteLength(capacity) {
|
|
2482
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2483
|
+
return SONARE_CLIP_PAGE_REQUEST_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S * Uint32Array.BYTES_PER_ELEMENT;
|
|
2484
|
+
}
|
|
2485
|
+
function sonareExternalMidiRingBufferByteLength(capacity) {
|
|
2486
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2487
|
+
return SONARE_EXTERNAL_MIDI_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S * Uint32Array.BYTES_PER_ELEMENT;
|
|
2488
|
+
}
|
|
2489
|
+
function externalMidiRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
2490
|
+
const headerBytes = SONARE_EXTERNAL_MIDI_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
|
|
2491
|
+
const header = new Int32Array(sharedBuffer, 0, SONARE_EXTERNAL_MIDI_RING_HEADER_INTS);
|
|
2492
|
+
const existingCapacity = Atomics.load(header, 2);
|
|
2493
|
+
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
2494
|
+
if (sharedBuffer.byteLength < sonareExternalMidiRingBufferByteLength(capacity)) {
|
|
2495
|
+
throw new Error("externalMidiSharedBuffer is too small for the requested ring capacity.");
|
|
2496
|
+
}
|
|
2497
|
+
Atomics.store(header, 2, capacity);
|
|
2498
|
+
Atomics.store(header, 3, SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S);
|
|
2499
|
+
return {
|
|
2500
|
+
header,
|
|
2501
|
+
records: new Uint32Array(
|
|
2502
|
+
sharedBuffer,
|
|
2503
|
+
headerBytes,
|
|
2504
|
+
capacity * SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S
|
|
2505
|
+
),
|
|
2506
|
+
capacity
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
function createSonareExternalMidiRingBuffer(capacity = 256) {
|
|
2510
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2511
|
+
const sharedBuffer = new SharedArrayBuffer(
|
|
2512
|
+
sonareExternalMidiRingBufferByteLength(clampedCapacity)
|
|
2513
|
+
);
|
|
2514
|
+
const ring = externalMidiRingFromSharedBuffer(sharedBuffer, clampedCapacity);
|
|
2515
|
+
Atomics.store(ring.header, 0, 0);
|
|
2516
|
+
Atomics.store(ring.header, 1, 0);
|
|
2517
|
+
Atomics.store(ring.header, 4, 0);
|
|
2518
|
+
return { sharedBuffer, header: ring.header, records: ring.records, capacity: ring.capacity };
|
|
2519
|
+
}
|
|
2520
|
+
function pushSonareExternalMidiRingBuffer(ring, destinationId, renderFrame, byteWord, byteCount) {
|
|
2521
|
+
const destination = Number(destinationId);
|
|
2522
|
+
const frame = Number(renderFrame);
|
|
2523
|
+
const packedBytes = Number(byteWord);
|
|
2524
|
+
const count = Number(byteCount);
|
|
2525
|
+
if (!Number.isSafeInteger(destination) || !Number.isSafeInteger(frame) || frame < 0 || !Number.isSafeInteger(packedBytes) || packedBytes < 0 || packedBytes > 16777215 || !Number.isSafeInteger(count) || count < 1 || count > 3) {
|
|
2526
|
+
Atomics.add(ring.header, 4, 1);
|
|
2527
|
+
return false;
|
|
2528
|
+
}
|
|
2529
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2530
|
+
const readIndex = Atomics.load(ring.header, 1);
|
|
2531
|
+
if (writeIndex - readIndex >= ring.capacity) {
|
|
2532
|
+
Atomics.add(ring.header, 4, 1);
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
const offset = writeIndex % ring.capacity * SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S;
|
|
2536
|
+
Atomics.store(ring.records, offset, destination);
|
|
2537
|
+
Atomics.store(ring.records, offset + 1, frame);
|
|
2538
|
+
Atomics.store(ring.records, offset + 2, packedBytes);
|
|
2539
|
+
Atomics.store(ring.records, offset + 3, count);
|
|
2540
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2541
|
+
return true;
|
|
2542
|
+
}
|
|
2543
|
+
function readSonareExternalMidiRingBuffer(ring) {
|
|
2544
|
+
const readIndex = Atomics.load(ring.header, 1);
|
|
2545
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2546
|
+
const events = [];
|
|
2547
|
+
for (let index = readIndex; index < writeIndex; index++) {
|
|
2548
|
+
const offset = index % ring.capacity * SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S;
|
|
2549
|
+
events.push({
|
|
2550
|
+
destinationId: Atomics.load(ring.records, offset) >>> 0,
|
|
2551
|
+
renderFrame: Atomics.load(ring.records, offset + 1) >>> 0,
|
|
2552
|
+
byteWord: Atomics.load(ring.records, offset + 2) >>> 0,
|
|
2553
|
+
byteCount: Atomics.load(ring.records, offset + 3) >>> 0
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
Atomics.store(ring.header, 1, writeIndex);
|
|
2557
|
+
return { events, dropped: Atomics.load(ring.header, 4) >>> 0 };
|
|
2558
|
+
}
|
|
2559
|
+
function createSonareClipPageRequestRingBuffer(capacity = 128) {
|
|
2560
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2561
|
+
const sharedBuffer = new SharedArrayBuffer(
|
|
2562
|
+
sonareClipPageRequestRingBufferByteLength(clampedCapacity)
|
|
2563
|
+
);
|
|
2564
|
+
const ring = clipPageRequestRingFromSharedBuffer(sharedBuffer, clampedCapacity);
|
|
2565
|
+
Atomics.store(ring.header, 0, 0);
|
|
2566
|
+
Atomics.store(ring.header, 1, 0);
|
|
2567
|
+
Atomics.store(ring.header, 2, ring.capacity);
|
|
2568
|
+
Atomics.store(ring.header, 3, SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S);
|
|
2569
|
+
Atomics.store(ring.header, 4, 0);
|
|
2570
|
+
return { sharedBuffer, header: ring.header, records: ring.records, capacity: ring.capacity };
|
|
2571
|
+
}
|
|
2572
|
+
function pushSonareClipPageRequestRingBuffer(ring, clipId, pageIndex) {
|
|
2573
|
+
if (!Number.isSafeInteger(clipId) || clipId < 0 || clipId > 4294967295 || !Number.isSafeInteger(pageIndex) || pageIndex < 0 || pageIndex > 4294967295) {
|
|
2574
|
+
Atomics.add(ring.header, 4, 1);
|
|
2575
|
+
return false;
|
|
2576
|
+
}
|
|
2577
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2578
|
+
const readIndex = Atomics.load(ring.header, 1);
|
|
2579
|
+
if (writeIndex - readIndex >= ring.capacity) {
|
|
2580
|
+
Atomics.add(ring.header, 4, 1);
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
const offset = writeIndex % ring.capacity * SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S;
|
|
2584
|
+
Atomics.store(ring.records, offset, clipId);
|
|
2585
|
+
Atomics.store(ring.records, offset + 1, pageIndex);
|
|
2586
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
function readSonareClipPageRequestRingBuffer(ring) {
|
|
2590
|
+
const readIndex = Atomics.load(ring.header, 1);
|
|
2591
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2592
|
+
const requests = [];
|
|
2593
|
+
for (let index = readIndex; index < writeIndex; index++) {
|
|
2594
|
+
const offset = index % ring.capacity * SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S;
|
|
2595
|
+
requests.push({
|
|
2596
|
+
clipId: Atomics.load(ring.records, offset),
|
|
2597
|
+
pageIndex: Atomics.load(ring.records, offset + 1)
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
Atomics.store(ring.header, 1, writeIndex);
|
|
2601
|
+
return { requests, dropped: Atomics.load(ring.header, 4) >>> 0 };
|
|
2602
|
+
}
|
|
1974
2603
|
function createSonareEngineCommandRingBuffer(capacity = 128) {
|
|
1975
2604
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1976
2605
|
const sharedBuffer = new SharedArrayBuffer(
|
|
@@ -2111,51 +2740,82 @@ function engineRingFromSharedBuffer(sharedBuffer, recordBytes, fallbackCapacity)
|
|
|
2111
2740
|
capacity
|
|
2112
2741
|
};
|
|
2113
2742
|
}
|
|
2743
|
+
function clipPageRequestRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
2744
|
+
const headerBytes = SONARE_CLIP_PAGE_REQUEST_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
|
|
2745
|
+
const header = new Int32Array(sharedBuffer, 0, SONARE_CLIP_PAGE_REQUEST_RING_HEADER_INTS);
|
|
2746
|
+
const existingCapacity = Atomics.load(header, 2);
|
|
2747
|
+
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
2748
|
+
const minBytes = sonareClipPageRequestRingBufferByteLength(capacity);
|
|
2749
|
+
if (sharedBuffer.byteLength < minBytes) {
|
|
2750
|
+
throw new Error("clipPageRequestSharedBuffer is too small for the requested ring capacity.");
|
|
2751
|
+
}
|
|
2752
|
+
Atomics.store(header, 2, capacity);
|
|
2753
|
+
Atomics.store(header, 3, SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S);
|
|
2754
|
+
return {
|
|
2755
|
+
header,
|
|
2756
|
+
records: new Uint32Array(
|
|
2757
|
+
sharedBuffer,
|
|
2758
|
+
headerBytes,
|
|
2759
|
+
capacity * SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S
|
|
2760
|
+
),
|
|
2761
|
+
capacity
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2114
2764
|
function recordOffset(index, capacity, recordBytes) {
|
|
2115
2765
|
return index % capacity * recordBytes;
|
|
2116
2766
|
}
|
|
2117
|
-
function
|
|
2118
|
-
|
|
2119
|
-
|
|
2767
|
+
function toSafeInteger(value, fallback) {
|
|
2768
|
+
const resolved = typeof value === "bigint" ? Number(value) : value;
|
|
2769
|
+
if (resolved === void 0) {
|
|
2770
|
+
return fallback;
|
|
2120
2771
|
}
|
|
2121
|
-
if (
|
|
2122
|
-
|
|
2772
|
+
if (!Number.isSafeInteger(resolved)) {
|
|
2773
|
+
throw new RangeError("64-bit ring values must be safe integers");
|
|
2123
2774
|
}
|
|
2124
|
-
return
|
|
2775
|
+
return resolved;
|
|
2776
|
+
}
|
|
2777
|
+
function writeInt64Words(view, offset, value) {
|
|
2778
|
+
const integer = toSafeInteger(value, 0);
|
|
2779
|
+
view.setUint32(offset, integer >>> 0, true);
|
|
2780
|
+
view.setInt32(offset + 4, Math.floor(integer / 4294967296), true);
|
|
2781
|
+
}
|
|
2782
|
+
function readInt64Words(view, offset) {
|
|
2783
|
+
return view.getInt32(offset + 4, true) * 4294967296 + view.getUint32(offset, true);
|
|
2125
2784
|
}
|
|
2126
2785
|
function writeEngineCommandRecord(view, offset, command) {
|
|
2127
2786
|
view.setUint32(offset, command.type, true);
|
|
2128
2787
|
view.setUint32(offset + 4, command.targetId ?? 0, true);
|
|
2129
|
-
view
|
|
2788
|
+
writeInt64Words(view, offset + 8, toSafeInteger(command.sampleTime, -1));
|
|
2130
2789
|
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
2131
|
-
view
|
|
2790
|
+
writeInt64Words(view, offset + 24, toSafeInteger(command.argInt, 0));
|
|
2132
2791
|
}
|
|
2133
2792
|
function readEngineCommandRecord(view, offset) {
|
|
2134
2793
|
return {
|
|
2135
2794
|
type: view.getUint32(offset, true),
|
|
2136
2795
|
targetId: view.getUint32(offset + 4, true),
|
|
2137
|
-
sampleTime:
|
|
2796
|
+
sampleTime: readInt64Words(view, offset + 8),
|
|
2138
2797
|
argFloat: view.getFloat64(offset + 16, true),
|
|
2139
|
-
argInt:
|
|
2798
|
+
argInt: readInt64Words(view, offset + 24)
|
|
2140
2799
|
};
|
|
2141
2800
|
}
|
|
2142
2801
|
function writeEngineTelemetryRecord(view, offset, telemetry) {
|
|
2143
2802
|
view.setUint32(offset, telemetry.type, true);
|
|
2144
2803
|
view.setUint32(offset + 4, telemetry.error, true);
|
|
2145
|
-
view
|
|
2146
|
-
view
|
|
2147
|
-
view
|
|
2804
|
+
writeInt64Words(view, offset + 8, telemetry.renderFrame);
|
|
2805
|
+
writeInt64Words(view, offset + 16, telemetry.timelineSample);
|
|
2806
|
+
writeInt64Words(view, offset + 24, telemetry.audibleTimelineSample);
|
|
2148
2807
|
view.setInt32(offset + 32, telemetry.graphLatencySamplesQ8, true);
|
|
2149
2808
|
view.setUint32(offset + 36, telemetry.value, true);
|
|
2150
|
-
view.
|
|
2809
|
+
view.setUint32(offset + 40, 0, true);
|
|
2810
|
+
view.setUint32(offset + 44, 0, true);
|
|
2151
2811
|
}
|
|
2152
2812
|
function readEngineTelemetryRecord(view, offset) {
|
|
2153
2813
|
return {
|
|
2154
2814
|
type: view.getUint32(offset, true),
|
|
2155
2815
|
error: view.getUint32(offset + 4, true),
|
|
2156
|
-
renderFrame:
|
|
2157
|
-
timelineSample:
|
|
2158
|
-
audibleTimelineSample:
|
|
2816
|
+
renderFrame: readInt64Words(view, offset + 8),
|
|
2817
|
+
timelineSample: readInt64Words(view, offset + 16),
|
|
2818
|
+
audibleTimelineSample: readInt64Words(view, offset + 24),
|
|
2159
2819
|
graphLatencySamplesQ8: view.getInt32(offset + 32, true),
|
|
2160
2820
|
value: view.getUint32(offset + 36, true)
|
|
2161
2821
|
};
|
|
@@ -2204,13 +2864,16 @@ function configureCapture(ctx, options) {
|
|
|
2204
2864
|
ctx.postSync({ type: "syncCapture", ...config });
|
|
2205
2865
|
}
|
|
2206
2866
|
function armRecord(ctx, trackId, enabled) {
|
|
2867
|
+
if (trackId !== 0) {
|
|
2868
|
+
throw new RangeError("Capture is global; armRecord only accepts trackId 0");
|
|
2869
|
+
}
|
|
2207
2870
|
if (enabled && !ctx.getCaptureConfig()) {
|
|
2208
2871
|
throw new Error("Capture buffer is not configured");
|
|
2209
2872
|
}
|
|
2210
2873
|
ctx.offlineEngine.armCapture(enabled);
|
|
2211
|
-
return ctx.
|
|
2874
|
+
return ctx.sendCommand({
|
|
2212
2875
|
type: 13 /* ArmRecord */,
|
|
2213
|
-
targetId:
|
|
2876
|
+
targetId: 0,
|
|
2214
2877
|
sampleTime: -1,
|
|
2215
2878
|
argInt: enabled ? 1 : 0
|
|
2216
2879
|
});
|
|
@@ -2219,7 +2882,7 @@ function punch(ctx, inPpq, outPpq) {
|
|
|
2219
2882
|
const inSample = ctx.offlineEngine.sampleAtPpq(inPpq);
|
|
2220
2883
|
const outSample = ctx.offlineEngine.sampleAtPpq(outPpq);
|
|
2221
2884
|
ctx.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
2222
|
-
return ctx.
|
|
2885
|
+
return ctx.sendCommand({
|
|
2223
2886
|
type: 14 /* Punch */,
|
|
2224
2887
|
sampleTime: -1,
|
|
2225
2888
|
argInt: inSample,
|
|
@@ -2245,7 +2908,7 @@ function addClip(ctx, trackId, buffer, startPpq, opts = {}) {
|
|
|
2245
2908
|
const clip = {
|
|
2246
2909
|
...opts,
|
|
2247
2910
|
id,
|
|
2248
|
-
channels: buffer,
|
|
2911
|
+
...Array.isArray(buffer) ? { channels: buffer } : { pageProvider: buffer },
|
|
2249
2912
|
startPpq,
|
|
2250
2913
|
trackId: ctx.resolveTargetId(trackId)
|
|
2251
2914
|
};
|
|
@@ -2292,6 +2955,12 @@ function syncClipsDelta(ctx, upserts, removeIds) {
|
|
|
2292
2955
|
for (const clip of upserts) {
|
|
2293
2956
|
const prepared = clip.id === void 0 ? clip : preparedById.get(clip.id) ?? clip;
|
|
2294
2957
|
const channels = prepared.channels;
|
|
2958
|
+
if (!channels && prepared.pageProvider !== void 0) {
|
|
2959
|
+
if (ctx.commitWorkletClipPageProvider(prepared)) {
|
|
2960
|
+
continue;
|
|
2961
|
+
}
|
|
2962
|
+
throw new Error("A pageProvider on SonareEngine must be created by attachOpfsClipStream().");
|
|
2963
|
+
}
|
|
2295
2964
|
if (prepared.id === void 0 || prepared.warpMode !== "off" || !channels || channels.length === 0 || channels[0].length <= PREBAKED_CLIP_PAGE_THRESHOLD) {
|
|
2296
2965
|
inlineUpserts.push(prepared);
|
|
2297
2966
|
continue;
|
|
@@ -2495,13 +3164,65 @@ function isEngineSyncMessage(value) {
|
|
|
2495
3164
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
2496
3165
|
return false;
|
|
2497
3166
|
}
|
|
2498
|
-
return value.type === "syncClips" || value.type === "syncClipsDelta" || value.type === "syncClipPageProvider" || value.type === "syncClipPage" || value.type === "syncClipPageCommit" || value.type === "syncMidiClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation" || value.type === "syncTempo" || value.type === "syncMixer" || value.type === "syncCapture" || value.type === "syncTrackStripEqBand" || value.type === "syncMasterStripEqBand" || value.type === "syncTrackStripInsertBypassed" || value.type === "syncMasterStripInsertBypassed" || value.type === "syncTrackStripInsertParamByName" || value.type === "syncMasterStripInsertParamByName" || value.type === "syncBusStripInsertParamByName" || value.type === "syncTrackStripPan" || value.type === "syncTrackStripPanLaw" || value.type === "syncTrackStripPanMode" || value.type === "syncTrackStripDualPan" || value.type === "syncTrackStripChannelDelaySamples" || value.type === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiFx" || value.type === "syncClearMidiFx" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiSysex" || value.type === "syncMidiPanic" || value.type === "syncMidiDestinationExternal" || value.type === "syncExternalMidiClock";
|
|
3167
|
+
return value.type === "syncClips" || value.type === "syncClipsDelta" || value.type === "syncClipPageProvider" || value.type === "syncClipPage" || value.type === "syncClipPageClear" || value.type === "syncClipPageCommit" || value.type === "syncClipPageDestroy" || value.type === "syncMidiClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation" || value.type === "syncTempo" || value.type === "syncMixer" || value.type === "syncCapture" || value.type === "syncTrackStripEqBand" || value.type === "syncMasterStripEqBand" || value.type === "syncTrackStripInsertBypassed" || value.type === "syncMasterStripInsertBypassed" || value.type === "syncTrackStripInsertParamByName" || value.type === "syncMasterStripInsertParamByName" || value.type === "syncBusStripInsertParamByName" || value.type === "syncTrackStripPan" || value.type === "syncTrackStripPanLaw" || value.type === "syncTrackStripPanMode" || value.type === "syncTrackStripDualPan" || value.type === "syncTrackStripChannelDelaySamples" || value.type === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiFx" || value.type === "syncClearMidiFx" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiUmp" || value.type === "syncMidiSysex" || value.type === "syncMidiPanic" || value.type === "syncMidiDestinationExternal" || value.type === "syncExternalMidiClock" || value.type === "destroy";
|
|
2499
3168
|
}
|
|
2500
3169
|
function isEngineCaptureRequestMessage(value) {
|
|
2501
3170
|
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
2502
3171
|
}
|
|
3172
|
+
function isCaptureStatus(value) {
|
|
3173
|
+
return isRecord(value) && typeof value.capturedFrames === "number" && Number.isSafeInteger(value.capturedFrames) && value.capturedFrames >= 0 && typeof value.overflowCount === "number" && Number.isSafeInteger(value.overflowCount) && value.overflowCount >= 0 && typeof value.armed === "boolean" && typeof value.punchEnabled === "boolean" && (value.source === "input" || value.source === "output") && typeof value.recordOffsetSamples === "number" && Number.isSafeInteger(value.recordOffsetSamples);
|
|
3174
|
+
}
|
|
3175
|
+
function isCaptureChannel(value) {
|
|
3176
|
+
return value instanceof Float32Array && typeof ArrayBuffer !== "undefined" && value.buffer instanceof ArrayBuffer;
|
|
3177
|
+
}
|
|
3178
|
+
function isCaptureChannels(value) {
|
|
3179
|
+
return Array.isArray(value) && value.every((channel) => isCaptureChannel(channel));
|
|
3180
|
+
}
|
|
3181
|
+
function hasOwn(value, key) {
|
|
3182
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
3183
|
+
}
|
|
3184
|
+
function engineCaptureResponseRequestId(value) {
|
|
3185
|
+
if (!isRecord(value) || value.type !== "captureResponse" || typeof value.requestId !== "number" || !Number.isSafeInteger(value.requestId)) {
|
|
3186
|
+
return void 0;
|
|
3187
|
+
}
|
|
3188
|
+
return value.requestId;
|
|
3189
|
+
}
|
|
2503
3190
|
function isEngineCaptureResponseMessage(value) {
|
|
2504
|
-
|
|
3191
|
+
const requestId = engineCaptureResponseRequestId(value);
|
|
3192
|
+
if (requestId === void 0 || !isRecord(value)) {
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3195
|
+
if (value.ok === false) {
|
|
3196
|
+
return typeof value.error === "string" && !hasOwn(value, "status") && !hasOwn(value, "channels");
|
|
3197
|
+
}
|
|
3198
|
+
if (value.ok !== true || hasOwn(value, "error")) {
|
|
3199
|
+
return false;
|
|
3200
|
+
}
|
|
3201
|
+
const hasStatus = hasOwn(value, "status");
|
|
3202
|
+
const hasChannels = hasOwn(value, "channels");
|
|
3203
|
+
if (hasStatus && hasChannels) {
|
|
3204
|
+
return false;
|
|
3205
|
+
}
|
|
3206
|
+
if (hasStatus) {
|
|
3207
|
+
return isCaptureStatus(value.status);
|
|
3208
|
+
}
|
|
3209
|
+
if (hasChannels) {
|
|
3210
|
+
return isCaptureChannels(value.channels);
|
|
3211
|
+
}
|
|
3212
|
+
return true;
|
|
3213
|
+
}
|
|
3214
|
+
function isEngineCaptureResponseForOperation(response, op) {
|
|
3215
|
+
if (!response.ok) {
|
|
3216
|
+
return true;
|
|
3217
|
+
}
|
|
3218
|
+
switch (op) {
|
|
3219
|
+
case "status":
|
|
3220
|
+
return "status" in response;
|
|
3221
|
+
case "read":
|
|
3222
|
+
return "channels" in response;
|
|
3223
|
+
case "reset":
|
|
3224
|
+
return !("status" in response) && !("channels" in response);
|
|
3225
|
+
}
|
|
2505
3226
|
}
|
|
2506
3227
|
function isEngineTransportRequestMessage(value) {
|
|
2507
3228
|
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
@@ -2521,20 +3242,61 @@ function isEngineTelemetryRecord(value) {
|
|
|
2521
3242
|
function isExternalMidiBatchMessage(value) {
|
|
2522
3243
|
return isRecord(value) && value.type === "externalMidi" && Array.isArray(value.events);
|
|
2523
3244
|
}
|
|
3245
|
+
function isClipPageRequestMessage(value) {
|
|
3246
|
+
return isRecord(value) && value.type === "clipPageRequest" && Array.isArray(value.requests) && value.requests.every(
|
|
3247
|
+
(request) => isRecord(request) && typeof request.clipId === "number" && typeof request.pageIndex === "number"
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
2524
3250
|
function isMeterSnapshot(value) {
|
|
2525
3251
|
return isRecord(value) && value.type === "meter" && typeof value.frame === "number" && typeof value.peakDbL === "number" && typeof value.peakDbR === "number" && typeof value.rmsDbL === "number" && typeof value.rmsDbR === "number" && typeof value.correlation === "number" && (typeof value.targetId === "number" || value.targetId === void 0);
|
|
2526
3252
|
}
|
|
2527
3253
|
|
|
2528
3254
|
// src/worklet/engine-node.ts
|
|
3255
|
+
function isFiniteInteger(value) {
|
|
3256
|
+
if (value === void 0) {
|
|
3257
|
+
return true;
|
|
3258
|
+
}
|
|
3259
|
+
return typeof value === "bigint" || Number.isFinite(value) && Number.isSafeInteger(value);
|
|
3260
|
+
}
|
|
3261
|
+
function isTrackMonitorMode(value) {
|
|
3262
|
+
if (value === void 0) {
|
|
3263
|
+
return false;
|
|
3264
|
+
}
|
|
3265
|
+
const mode = typeof value === "bigint" ? Number(value) : value;
|
|
3266
|
+
return typeof mode === "number" && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
|
|
3267
|
+
}
|
|
3268
|
+
function isTrackMonitorLaneIndex(value) {
|
|
3269
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3270
|
+
}
|
|
3271
|
+
function isValidCommandRecord(command) {
|
|
3272
|
+
const type = Number(command.type);
|
|
3273
|
+
if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */) {
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
return (command.targetId === void 0 || Number.isSafeInteger(command.targetId)) && (command.argFloat === void 0 || Number.isFinite(command.argFloat)) && isFiniteInteger(command.argInt) && isFiniteInteger(command.sampleTime) && (type !== 26 /* SetTrackMonitorMode */ || isTrackMonitorLaneIndex(command.targetId) && isTrackMonitorMode(command.argInt));
|
|
3277
|
+
}
|
|
3278
|
+
var AUDIO_WORKLET_RENDER_QUANTUM = 128;
|
|
3279
|
+
function workletBlockSize(blockSize) {
|
|
3280
|
+
const resolved = blockSize ?? AUDIO_WORKLET_RENDER_QUANTUM;
|
|
3281
|
+
if (!Number.isSafeInteger(resolved) || resolved < AUDIO_WORKLET_RENDER_QUANTUM) {
|
|
3282
|
+
throw new RangeError(
|
|
3283
|
+
`blockSize must be an integer of at least ${AUDIO_WORKLET_RENDER_QUANTUM} frames`
|
|
3284
|
+
);
|
|
3285
|
+
}
|
|
3286
|
+
return resolved;
|
|
3287
|
+
}
|
|
2529
3288
|
var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
2530
|
-
constructor(node, capabilities, commandRing, telemetryRing, meterRing, scopeRing) {
|
|
3289
|
+
constructor(node, capabilities, commandRing, telemetryRing, meterRing, scopeRing, clipPageRequestRing, externalMidiRing) {
|
|
2531
3290
|
this.telemetryReadIndex = 0;
|
|
2532
3291
|
this.meterReadIndex = 0;
|
|
2533
3292
|
this.scopeReadIndex = 0;
|
|
3293
|
+
this.clipPageRequestDroppedRead = 0;
|
|
2534
3294
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
2535
3295
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
2536
3296
|
this.scopeListeners = /* @__PURE__ */ new Set();
|
|
2537
3297
|
this.midiOutListeners = /* @__PURE__ */ new Set();
|
|
3298
|
+
this.clipPageRequestListeners = /* @__PURE__ */ new Set();
|
|
3299
|
+
this.syncErrorListeners = /* @__PURE__ */ new Set();
|
|
2538
3300
|
this.captureRequestId = 1;
|
|
2539
3301
|
this.captureRequests = /* @__PURE__ */ new Map();
|
|
2540
3302
|
this.transportRequestId = 1;
|
|
@@ -2546,6 +3308,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2546
3308
|
this.telemetryRing = telemetryRing;
|
|
2547
3309
|
this.meterRing = meterRing;
|
|
2548
3310
|
this.scopeRing = scopeRing;
|
|
3311
|
+
this.clipPageRequestRing = clipPageRequestRing;
|
|
3312
|
+
this.externalMidiRing = externalMidiRing;
|
|
2549
3313
|
this.ready = new Promise((resolve, reject) => {
|
|
2550
3314
|
this.resolveReady = resolve;
|
|
2551
3315
|
this.rejectReady = reject;
|
|
@@ -2554,14 +3318,19 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2554
3318
|
this.resolveReady();
|
|
2555
3319
|
}
|
|
2556
3320
|
this.node.port.onmessage = (event) => {
|
|
2557
|
-
|
|
2558
|
-
|
|
3321
|
+
const captureRequestId = engineCaptureResponseRequestId(event.data);
|
|
3322
|
+
if (captureRequestId !== void 0) {
|
|
3323
|
+
const pending = this.captureRequests.get(captureRequestId);
|
|
2559
3324
|
if (pending) {
|
|
2560
|
-
this.captureRequests.delete(
|
|
2561
|
-
if (event.data
|
|
3325
|
+
this.captureRequests.delete(captureRequestId);
|
|
3326
|
+
if (!isEngineCaptureResponseMessage(event.data)) {
|
|
3327
|
+
pending.reject(new Error("Malformed capture response."));
|
|
3328
|
+
} else if (!isEngineCaptureResponseForOperation(event.data, pending.op)) {
|
|
3329
|
+
pending.reject(new Error("Capture response does not match request operation."));
|
|
3330
|
+
} else if (event.data.ok) {
|
|
2562
3331
|
pending.resolve(event.data);
|
|
2563
3332
|
} else {
|
|
2564
|
-
pending.reject(new Error(event.data.error
|
|
3333
|
+
pending.reject(new Error(event.data.error));
|
|
2565
3334
|
}
|
|
2566
3335
|
}
|
|
2567
3336
|
} else if (isEngineTransportResponseMessage(event.data)) {
|
|
@@ -2580,6 +3349,17 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2580
3349
|
this.emitMeter(event.data);
|
|
2581
3350
|
} else if (isExternalMidiBatchMessage(event.data)) {
|
|
2582
3351
|
this.emitMidiOut(event.data.events);
|
|
3352
|
+
} else if (isClipPageRequestMessage(event.data)) {
|
|
3353
|
+
this.emitClipPageRequests(event.data);
|
|
3354
|
+
} else if (isRecord(event.data) && event.data.type === "syncError") {
|
|
3355
|
+
const syncError = {
|
|
3356
|
+
type: "syncError",
|
|
3357
|
+
syncType: String(event.data.syncType),
|
|
3358
|
+
message: String(event.data.message ?? "AudioWorklet sync failed")
|
|
3359
|
+
};
|
|
3360
|
+
for (const listener of this.syncErrorListeners) {
|
|
3361
|
+
listener(syncError);
|
|
3362
|
+
}
|
|
2583
3363
|
} else if (isRecord(event.data) && event.data.type === "ready") {
|
|
2584
3364
|
this.resolveReady();
|
|
2585
3365
|
} else if (isRecord(event.data) && event.data.type === "error") {
|
|
@@ -2587,7 +3367,13 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2587
3367
|
}
|
|
2588
3368
|
};
|
|
2589
3369
|
}
|
|
3370
|
+
/** Subscribes to control-plane sync rejections reported by the worklet. */
|
|
3371
|
+
onSyncError(listener) {
|
|
3372
|
+
this.syncErrorListeners.add(listener);
|
|
3373
|
+
return () => this.syncErrorListeners.delete(listener);
|
|
3374
|
+
}
|
|
2590
3375
|
static async create(context, options = {}) {
|
|
3376
|
+
const blockSize = workletBlockSize(options.blockSize);
|
|
2591
3377
|
const processorName = options.processorName ?? "sonare-realtime-engine-processor";
|
|
2592
3378
|
const moduleUrl = options.moduleUrl;
|
|
2593
3379
|
if (moduleUrl && context.audioWorklet?.addModule) {
|
|
@@ -2618,10 +3404,12 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2618
3404
|
const meterRing = mode === "sab" ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : void 0;
|
|
2619
3405
|
const scopeIntervalFrames = Math.max(0, Math.floor(options.scopeIntervalFrames ?? 0));
|
|
2620
3406
|
const scopeRing = mode === "sab" && scopeIntervalFrames > 0 ? createSonareScopeRingBuffer(options.scopeRingCapacity ?? 64, options.scopeBands ?? 48) : void 0;
|
|
3407
|
+
const clipPageRequestRing = mode === "sab" ? createSonareClipPageRequestRingBuffer(options.clipPageRequestRingCapacity ?? 128) : void 0;
|
|
3408
|
+
const externalMidiRing = mode === "sab" ? createSonareExternalMidiRingBuffer(options.externalMidiRingCapacity ?? 256) : void 0;
|
|
2621
3409
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
2622
3410
|
const processorOptions = {
|
|
2623
3411
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
2624
|
-
blockSize
|
|
3412
|
+
blockSize,
|
|
2625
3413
|
channelCount,
|
|
2626
3414
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
2627
3415
|
commandRingCapacity: commandRing?.capacity,
|
|
@@ -2633,6 +3421,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2633
3421
|
scopeRingCapacity: scopeRing?.capacity,
|
|
2634
3422
|
scopeBands: scopeRing?.bands,
|
|
2635
3423
|
scopeIntervalFrames: scopeRing ? scopeIntervalFrames : void 0,
|
|
3424
|
+
clipPageRequestSharedBuffer: clipPageRequestRing?.sharedBuffer,
|
|
3425
|
+
clipPageRequestRingCapacity: clipPageRequestRing?.capacity,
|
|
3426
|
+
externalMidiSharedBuffer: externalMidiRing?.sharedBuffer,
|
|
3427
|
+
externalMidiRingCapacity: externalMidiRing?.capacity,
|
|
2636
3428
|
wasmBinary: options.wasmBinary,
|
|
2637
3429
|
initialSyncMessages: options.initialSyncMessages,
|
|
2638
3430
|
initialCommands: options.initialCommands
|
|
@@ -2652,16 +3444,23 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2652
3444
|
sharedArrayBuffer,
|
|
2653
3445
|
atomics,
|
|
2654
3446
|
audioWorklet,
|
|
3447
|
+
clipPageRequestsRealtimeSafe: mode === "sab",
|
|
3448
|
+
externalMidiRealtimeSafe: mode === "sab",
|
|
2655
3449
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
2656
3450
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
2657
3451
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
2658
3452
|
degradedReason,
|
|
2659
|
-
|
|
3453
|
+
// A processor posts ready/error irrespective of whether its module was
|
|
3454
|
+
// loaded here or registered by the host beforehand. Waiting in both
|
|
3455
|
+
// cases is the only way to surface a failed worklet-side WASM init.
|
|
3456
|
+
readyMessage: true
|
|
2660
3457
|
},
|
|
2661
3458
|
commandRing,
|
|
2662
3459
|
telemetryRing,
|
|
2663
3460
|
meterRing,
|
|
2664
|
-
scopeRing
|
|
3461
|
+
scopeRing,
|
|
3462
|
+
clipPageRequestRing,
|
|
3463
|
+
externalMidiRing
|
|
2665
3464
|
);
|
|
2666
3465
|
}
|
|
2667
3466
|
play(sampleTime = -1) {
|
|
@@ -2678,6 +3477,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2678
3477
|
});
|
|
2679
3478
|
}
|
|
2680
3479
|
seekPpq(ppq, sampleTime = -1) {
|
|
3480
|
+
if (!Number.isFinite(ppq) || !Number.isSafeInteger(sampleTime)) {
|
|
3481
|
+
return false;
|
|
3482
|
+
}
|
|
2681
3483
|
return this.sendCommand({
|
|
2682
3484
|
type: 5 /* TransportSeekPpq */,
|
|
2683
3485
|
sampleTime,
|
|
@@ -2685,7 +3487,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2685
3487
|
});
|
|
2686
3488
|
}
|
|
2687
3489
|
sendCommand(command) {
|
|
2688
|
-
if (this.destroyed) {
|
|
3490
|
+
if (this.destroyed || !isValidCommandRecord(command)) {
|
|
2689
3491
|
return false;
|
|
2690
3492
|
}
|
|
2691
3493
|
if (this.commandRing) {
|
|
@@ -2696,18 +3498,19 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2696
3498
|
}
|
|
2697
3499
|
requestCaptureStatus() {
|
|
2698
3500
|
return this.sendCaptureRequest("status").then((response) => {
|
|
2699
|
-
if (!response.status) {
|
|
3501
|
+
if (!response.ok || !("status" in response)) {
|
|
2700
3502
|
throw new Error("Capture status response is missing status.");
|
|
2701
3503
|
}
|
|
2702
3504
|
return response.status;
|
|
2703
3505
|
});
|
|
2704
3506
|
}
|
|
2705
3507
|
requestCapturedAudio() {
|
|
2706
|
-
return this.sendCaptureRequest("read").then(
|
|
2707
|
-
(response
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3508
|
+
return this.sendCaptureRequest("read").then((response) => {
|
|
3509
|
+
if (!response.ok || !("channels" in response)) {
|
|
3510
|
+
throw new Error("Capture read response is missing channels.");
|
|
3511
|
+
}
|
|
3512
|
+
return response.channels;
|
|
3513
|
+
});
|
|
2711
3514
|
}
|
|
2712
3515
|
requestCaptureReset() {
|
|
2713
3516
|
return this.sendCaptureRequest("reset").then(() => void 0);
|
|
@@ -2759,22 +3562,73 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2759
3562
|
}
|
|
2760
3563
|
return read.scopes;
|
|
2761
3564
|
}
|
|
3565
|
+
/** Drain lowered MIDI-1 records from the SAB ring on the main thread. */
|
|
3566
|
+
pollMidiOut() {
|
|
3567
|
+
if (!this.externalMidiRing) {
|
|
3568
|
+
return [];
|
|
3569
|
+
}
|
|
3570
|
+
const read = readSonareExternalMidiRingBuffer(this.externalMidiRing);
|
|
3571
|
+
const events = read.events.map((event) => {
|
|
3572
|
+
const bytes = [];
|
|
3573
|
+
for (let index = 0; index < event.byteCount; index++) {
|
|
3574
|
+
bytes.push(event.byteWord >>> 8 * index & 255);
|
|
3575
|
+
}
|
|
3576
|
+
return {
|
|
3577
|
+
destinationId: event.destinationId,
|
|
3578
|
+
renderFrame: event.renderFrame,
|
|
3579
|
+
bytes
|
|
3580
|
+
};
|
|
3581
|
+
});
|
|
3582
|
+
if (events.length > 0) {
|
|
3583
|
+
this.emitMidiOut(events);
|
|
3584
|
+
}
|
|
3585
|
+
return events;
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Drains bounded paged-clip misses from the SAB ring and forwards one batch
|
|
3589
|
+
* to subscribers. In postMessage mode the legacy handler remains available,
|
|
3590
|
+
* but that degraded path is not realtime-safe for OPFS streaming.
|
|
3591
|
+
*/
|
|
3592
|
+
pollClipPageRequests() {
|
|
3593
|
+
if (!this.clipPageRequestRing) {
|
|
3594
|
+
return void 0;
|
|
3595
|
+
}
|
|
3596
|
+
const read = readSonareClipPageRequestRingBuffer(this.clipPageRequestRing);
|
|
3597
|
+
const dropped = read.dropped - this.clipPageRequestDroppedRead >>> 0;
|
|
3598
|
+
this.clipPageRequestDroppedRead = read.dropped;
|
|
3599
|
+
if (read.requests.length === 0 && dropped === 0) {
|
|
3600
|
+
return void 0;
|
|
3601
|
+
}
|
|
3602
|
+
const message = {
|
|
3603
|
+
type: "clipPageRequest",
|
|
3604
|
+
requests: read.requests,
|
|
3605
|
+
...dropped > 0 ? { dropped } : {}
|
|
3606
|
+
};
|
|
3607
|
+
this.emitClipPageRequests(message);
|
|
3608
|
+
return message;
|
|
3609
|
+
}
|
|
2762
3610
|
onTelemetry(callback) {
|
|
2763
3611
|
this.telemetryListeners.add(callback);
|
|
3612
|
+
this.startRingPolling();
|
|
2764
3613
|
return () => {
|
|
2765
3614
|
this.telemetryListeners.delete(callback);
|
|
3615
|
+
this.stopRingPollingIfUnused();
|
|
2766
3616
|
};
|
|
2767
3617
|
}
|
|
2768
3618
|
onMeter(callback) {
|
|
2769
3619
|
this.meterListeners.add(callback);
|
|
3620
|
+
this.startRingPolling();
|
|
2770
3621
|
return () => {
|
|
2771
3622
|
this.meterListeners.delete(callback);
|
|
3623
|
+
this.stopRingPollingIfUnused();
|
|
2772
3624
|
};
|
|
2773
3625
|
}
|
|
2774
3626
|
onScope(callback) {
|
|
2775
3627
|
this.scopeListeners.add(callback);
|
|
3628
|
+
this.startRingPolling();
|
|
2776
3629
|
return () => {
|
|
2777
3630
|
this.scopeListeners.delete(callback);
|
|
3631
|
+
this.stopRingPollingIfUnused();
|
|
2778
3632
|
};
|
|
2779
3633
|
}
|
|
2780
3634
|
/**
|
|
@@ -2784,8 +3638,20 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2784
3638
|
*/
|
|
2785
3639
|
onMidiOut(callback) {
|
|
2786
3640
|
this.midiOutListeners.add(callback);
|
|
3641
|
+
this.startRingPolling();
|
|
2787
3642
|
return () => {
|
|
2788
3643
|
this.midiOutListeners.delete(callback);
|
|
3644
|
+
this.stopRingPollingIfUnused();
|
|
3645
|
+
};
|
|
3646
|
+
}
|
|
3647
|
+
/**
|
|
3648
|
+
* Subscribe to bounded batches of paged-clip misses from the worklet. The
|
|
3649
|
+
* callback runs on the main thread and may initiate asynchronous OPFS I/O.
|
|
3650
|
+
*/
|
|
3651
|
+
onClipPageRequests(callback) {
|
|
3652
|
+
this.clipPageRequestListeners.add(callback);
|
|
3653
|
+
return () => {
|
|
3654
|
+
this.clipPageRequestListeners.delete(callback);
|
|
2789
3655
|
};
|
|
2790
3656
|
}
|
|
2791
3657
|
destroy() {
|
|
@@ -2793,7 +3659,11 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2793
3659
|
return;
|
|
2794
3660
|
}
|
|
2795
3661
|
this.destroyed = true;
|
|
2796
|
-
this.
|
|
3662
|
+
if (this.ringPollTimer !== void 0) {
|
|
3663
|
+
clearInterval(this.ringPollTimer);
|
|
3664
|
+
this.ringPollTimer = void 0;
|
|
3665
|
+
}
|
|
3666
|
+
this.node.port.postMessage({ type: "destroy" });
|
|
2797
3667
|
this.node.disconnect();
|
|
2798
3668
|
for (const pending of this.captureRequests.values()) {
|
|
2799
3669
|
pending.reject(new Error("Realtime engine node is destroyed."));
|
|
@@ -2807,12 +3677,34 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2807
3677
|
this.meterListeners.clear();
|
|
2808
3678
|
this.scopeListeners.clear();
|
|
2809
3679
|
this.midiOutListeners.clear();
|
|
3680
|
+
this.clipPageRequestListeners.clear();
|
|
2810
3681
|
}
|
|
2811
3682
|
emitTelemetry(telemetry) {
|
|
2812
3683
|
for (const listener of this.telemetryListeners) {
|
|
2813
3684
|
listener(telemetry);
|
|
2814
3685
|
}
|
|
2815
3686
|
}
|
|
3687
|
+
startRingPolling() {
|
|
3688
|
+
if (this.ringPollTimer !== void 0 || !this.telemetryRing && !this.meterRing && !this.scopeRing && !this.externalMidiRing) {
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
const poll = () => {
|
|
3692
|
+
if (!this.destroyed) {
|
|
3693
|
+
this.pollTelemetry();
|
|
3694
|
+
this.pollMeters();
|
|
3695
|
+
this.pollScope();
|
|
3696
|
+
this.pollMidiOut();
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
poll();
|
|
3700
|
+
this.ringPollTimer = setInterval(poll, 16);
|
|
3701
|
+
}
|
|
3702
|
+
stopRingPollingIfUnused() {
|
|
3703
|
+
if (this.ringPollTimer !== void 0 && this.telemetryListeners.size === 0 && this.meterListeners.size === 0 && this.scopeListeners.size === 0 && this.midiOutListeners.size === 0) {
|
|
3704
|
+
clearInterval(this.ringPollTimer);
|
|
3705
|
+
this.ringPollTimer = void 0;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
2816
3708
|
emitMeter(meter) {
|
|
2817
3709
|
for (const listener of this.meterListeners) {
|
|
2818
3710
|
listener(meter);
|
|
@@ -2823,6 +3715,11 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2823
3715
|
listener(events);
|
|
2824
3716
|
}
|
|
2825
3717
|
}
|
|
3718
|
+
emitClipPageRequests(message) {
|
|
3719
|
+
for (const listener of this.clipPageRequestListeners) {
|
|
3720
|
+
listener(message);
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
2826
3723
|
emitScope(scope) {
|
|
2827
3724
|
for (const listener of this.scopeListeners) {
|
|
2828
3725
|
listener(scope);
|
|
@@ -2833,10 +3730,17 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2833
3730
|
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
2834
3731
|
}
|
|
2835
3732
|
const requestId = this.captureRequestId++;
|
|
3733
|
+
let rejectRequest;
|
|
2836
3734
|
const promise = new Promise((resolve, reject) => {
|
|
2837
|
-
|
|
3735
|
+
rejectRequest = reject;
|
|
3736
|
+
this.captureRequests.set(requestId, { op, resolve, reject });
|
|
2838
3737
|
});
|
|
2839
|
-
|
|
3738
|
+
try {
|
|
3739
|
+
this.node.port.postMessage({ type: "captureRequest", requestId, op });
|
|
3740
|
+
} catch (error) {
|
|
3741
|
+
this.captureRequests.delete(requestId);
|
|
3742
|
+
rejectRequest(error);
|
|
3743
|
+
}
|
|
2840
3744
|
return promise;
|
|
2841
3745
|
}
|
|
2842
3746
|
sendTransportRequest() {
|
|
@@ -2856,7 +3760,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2856
3760
|
function setParam(ctx, nodeId, param, value) {
|
|
2857
3761
|
const paramId = ctx.resolveParamId(nodeId, param);
|
|
2858
3762
|
ctx.offlineEngine.setParameter(paramId, value);
|
|
2859
|
-
return ctx.
|
|
3763
|
+
return ctx.sendCommand({
|
|
2860
3764
|
type: 0 /* SetParam */,
|
|
2861
3765
|
targetId: paramId,
|
|
2862
3766
|
sampleTime: -1,
|
|
@@ -2866,13 +3770,28 @@ function setParam(ctx, nodeId, param, value) {
|
|
|
2866
3770
|
function setSoloMute(ctx, target, solo, mute) {
|
|
2867
3771
|
const laneIndex = ctx.ensureTrackLane(target);
|
|
2868
3772
|
ctx.offlineEngine.setSoloMute(laneIndex, solo, mute);
|
|
2869
|
-
return ctx.
|
|
3773
|
+
return ctx.sendCommand({
|
|
2870
3774
|
type: 10 /* SetSoloMute */,
|
|
2871
3775
|
targetId: laneIndex,
|
|
2872
3776
|
sampleTime: -1,
|
|
2873
3777
|
argInt: (mute ? 1 : 0) | (solo ? 2 : 0)
|
|
2874
3778
|
});
|
|
2875
3779
|
}
|
|
3780
|
+
function setTrackMonitorMode(ctx, target, mode, renderFrame = -1) {
|
|
3781
|
+
const modeOrdinal = trackMonitorModeCode(mode);
|
|
3782
|
+
const laneIndex = ctx.ensureTrackLane(target);
|
|
3783
|
+
ctx.offlineEngine.setTrackMonitorMode(
|
|
3784
|
+
laneIndex,
|
|
3785
|
+
modeOrdinal,
|
|
3786
|
+
renderFrame
|
|
3787
|
+
);
|
|
3788
|
+
return ctx.sendCommand({
|
|
3789
|
+
type: 26 /* SetTrackMonitorMode */,
|
|
3790
|
+
targetId: laneIndex,
|
|
3791
|
+
sampleTime: renderFrame,
|
|
3792
|
+
argInt: modeOrdinal
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
2876
3795
|
function automationParamId(ctx, target, kind) {
|
|
2877
3796
|
const paramKind = kind === "pan" ? ENGINE_MIXER_PARAM_PAN : ENGINE_MIXER_PARAM_FADER_DB;
|
|
2878
3797
|
if (target === "master") {
|
|
@@ -2908,6 +3827,15 @@ function listParameters(ctx) {
|
|
|
2908
3827
|
}
|
|
2909
3828
|
return parameters;
|
|
2910
3829
|
}
|
|
3830
|
+
function addParameter(ctx, info) {
|
|
3831
|
+
ctx.offlineEngine.addParameter(info);
|
|
3832
|
+
ctx.postSync({ type: "syncParameters", parameters: listParameters(ctx) });
|
|
3833
|
+
}
|
|
3834
|
+
function clearParameters(ctx) {
|
|
3835
|
+
ctx.offlineEngine.clearParameters();
|
|
3836
|
+
ctx.automationLanes.clear();
|
|
3837
|
+
ctx.postSync({ type: "syncParameters", parameters: [] });
|
|
3838
|
+
}
|
|
2911
3839
|
|
|
2912
3840
|
// src/worklet/engine-strips.ts
|
|
2913
3841
|
function trackIdFor(ctx, target) {
|
|
@@ -3022,6 +3950,11 @@ function pushMidiCc(ctx, trackId, group, channel, controller, value, renderFrame
|
|
|
3022
3950
|
renderFrame
|
|
3023
3951
|
});
|
|
3024
3952
|
}
|
|
3953
|
+
function pushMidiUmp(ctx, trackId, word0, renderFrame) {
|
|
3954
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
3955
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
|
|
3956
|
+
ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
|
|
3957
|
+
}
|
|
3025
3958
|
function setBuiltinInstrument(ctx, trackId, config) {
|
|
3026
3959
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
3027
3960
|
ctx.offlineEngine.setBuiltinInstrument(config, destinationId);
|
|
@@ -3082,11 +4015,6 @@ function setTempo(ctx, bpm) {
|
|
|
3082
4015
|
ctx.setTempoSegments([{ startPpq: 0, bpm }]);
|
|
3083
4016
|
ctx.offlineEngine.setTempo(bpm);
|
|
3084
4017
|
postTempoSync(ctx);
|
|
3085
|
-
ctx.realtimeNode.sendCommand({
|
|
3086
|
-
type: 6 /* SetTempoMap */,
|
|
3087
|
-
sampleTime: -1,
|
|
3088
|
-
argFloat: bpm
|
|
3089
|
-
});
|
|
3090
4018
|
}
|
|
3091
4019
|
function setTempoSegments(ctx, segments) {
|
|
3092
4020
|
const copied = segments.map((segment) => ({ ...segment }));
|
|
@@ -3113,7 +4041,7 @@ function setTimeSignatureSegments(ctx, segments) {
|
|
|
3113
4041
|
}
|
|
3114
4042
|
function setLoop(ctx, startPpq, endPpq, enabled = true) {
|
|
3115
4043
|
ctx.offlineEngine.setLoop(startPpq, endPpq, enabled);
|
|
3116
|
-
return ctx.
|
|
4044
|
+
return ctx.sendCommand({
|
|
3117
4045
|
type: 7 /* SetLoop */,
|
|
3118
4046
|
targetId: enabled ? 1 : 0,
|
|
3119
4047
|
sampleTime: -1,
|
|
@@ -3134,6 +4062,19 @@ function cachedTransportState(ctx) {
|
|
|
3134
4062
|
}
|
|
3135
4063
|
|
|
3136
4064
|
// src/worklet/engine.ts
|
|
4065
|
+
var MAX_PENDING_WORKLET_CLIP_PAGE_REQUESTS = 256;
|
|
4066
|
+
function transferableAudioBuffers(channels) {
|
|
4067
|
+
const transfers = [];
|
|
4068
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4069
|
+
for (const channel of channels) {
|
|
4070
|
+
const buffer = channel.buffer;
|
|
4071
|
+
if (buffer instanceof ArrayBuffer && !seen.has(buffer)) {
|
|
4072
|
+
seen.add(buffer);
|
|
4073
|
+
transfers.push(buffer);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
return transfers;
|
|
4077
|
+
}
|
|
3137
4078
|
var SonareEngine = class _SonareEngine {
|
|
3138
4079
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
3139
4080
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
@@ -3157,6 +4098,11 @@ var SonareEngine = class _SonareEngine {
|
|
|
3157
4098
|
this.nextMarkerId = 1;
|
|
3158
4099
|
this.transportPlaying = false;
|
|
3159
4100
|
this.pendingInstrumentSync = [];
|
|
4101
|
+
// One latest frontier per clip is sufficient: ClipPageStreamer expands it to
|
|
4102
|
+
// the bounded read window. A Map both coalesces repeated page misses and
|
|
4103
|
+
// places a hard cap on work queued while OPFS I/O is stalled.
|
|
4104
|
+
this.workletClipPageRequests = /* @__PURE__ */ new Map();
|
|
4105
|
+
this.workletPageProviderClipIds = /* @__PURE__ */ new Map();
|
|
3160
4106
|
this.destroyed = false;
|
|
3161
4107
|
this.context = context;
|
|
3162
4108
|
this.realtimeNode = realtimeNode;
|
|
@@ -3166,10 +4112,17 @@ var SonareEngine = class _SonareEngine {
|
|
|
3166
4112
|
this.sampleRate = sampleRate;
|
|
3167
4113
|
this.offlineBlockSize = offlineBlockSize;
|
|
3168
4114
|
this.offlineChannelCount = offlineChannelCount;
|
|
4115
|
+
this.unsubscribeWorkletClipRequests = this.realtimeNode.onClipPageRequests((message) => {
|
|
4116
|
+
for (const request of message.requests) {
|
|
4117
|
+
this.enqueueWorkletClipPageRequest(request);
|
|
4118
|
+
}
|
|
4119
|
+
this.pumpWorkletClipPages();
|
|
4120
|
+
});
|
|
3169
4121
|
this.transport = buildTransportFacade({
|
|
3170
4122
|
sampleRate: this.sampleRate,
|
|
3171
4123
|
realtimeNode: this.realtimeNode,
|
|
3172
4124
|
offlineEngine: this.offlineEngine,
|
|
4125
|
+
flushOfflineMirror: () => this.flushOfflineMirror(),
|
|
3173
4126
|
setTransportPlaying: (playing) => {
|
|
3174
4127
|
this.transportPlaying = playing;
|
|
3175
4128
|
},
|
|
@@ -3187,8 +4140,14 @@ var SonareEngine = class _SonareEngine {
|
|
|
3187
4140
|
Math.floor(options.offlineChannelCount ?? options.channelCount ?? 2)
|
|
3188
4141
|
);
|
|
3189
4142
|
const realtimeNode = await SonareRealtimeEngineNode.create(context, options);
|
|
4143
|
+
try {
|
|
4144
|
+
await realtimeNode.ready;
|
|
4145
|
+
} catch (error) {
|
|
4146
|
+
realtimeNode.destroy();
|
|
4147
|
+
throw error;
|
|
4148
|
+
}
|
|
3190
4149
|
const offlineEngine = options.offlineEngine ?? new RealtimeEngine(sampleRate, blockSize);
|
|
3191
|
-
|
|
4150
|
+
const engine = new _SonareEngine(
|
|
3192
4151
|
context,
|
|
3193
4152
|
realtimeNode,
|
|
3194
4153
|
offlineEngine,
|
|
@@ -3196,6 +4155,8 @@ var SonareEngine = class _SonareEngine {
|
|
|
3196
4155
|
blockSize,
|
|
3197
4156
|
channelCount
|
|
3198
4157
|
);
|
|
4158
|
+
engine.syncParameters();
|
|
4159
|
+
return engine;
|
|
3199
4160
|
}
|
|
3200
4161
|
async suspend() {
|
|
3201
4162
|
if (this.destroyed) {
|
|
@@ -3333,9 +4294,21 @@ var SonareEngine = class _SonareEngine {
|
|
|
3333
4294
|
listParameters() {
|
|
3334
4295
|
return listParameters(this.parameterContext);
|
|
3335
4296
|
}
|
|
4297
|
+
/** Registers a custom parameter on the offline mirror and worklet engine. */
|
|
4298
|
+
addParameter(info) {
|
|
4299
|
+
addParameter(this.parameterContext, info);
|
|
4300
|
+
}
|
|
4301
|
+
/** Clears custom parameters and their automation lanes on both engines. */
|
|
4302
|
+
clearParameters() {
|
|
4303
|
+
clearParameters(this.parameterContext);
|
|
4304
|
+
}
|
|
3336
4305
|
setSoloMute(target, solo, mute) {
|
|
3337
4306
|
return setSoloMute(this.parameterContext, target, solo, mute);
|
|
3338
4307
|
}
|
|
4308
|
+
/** Queues a per-track PFL/AFL monitor tap mode change. */
|
|
4309
|
+
setTrackMonitorMode(target, mode, renderFrame = -1) {
|
|
4310
|
+
return setTrackMonitorMode(this.parameterContext, target, mode, renderFrame);
|
|
4311
|
+
}
|
|
3339
4312
|
setStripGain(target, db) {
|
|
3340
4313
|
return this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
3341
4314
|
}
|
|
@@ -3468,6 +4441,76 @@ var SonareEngine = class _SonareEngine {
|
|
|
3468
4441
|
setMasterChain(sceneJson) {
|
|
3469
4442
|
this.setMasterStripJson(sceneJson);
|
|
3470
4443
|
}
|
|
4444
|
+
/**
|
|
4445
|
+
* Creates and primes an OPFS-backed page provider for this live worklet
|
|
4446
|
+
* engine. Pass the returned `provider` to {@link addClip} in place of a
|
|
4447
|
+
* `Float32Array[]`; the `clipId` in `options` must equal that clip's explicit
|
|
4448
|
+
* `opts.id`. Subsequent cache misses are fetched on the main thread and
|
|
4449
|
+
* supplied back to the worklet through its bounded pull protocol.
|
|
4450
|
+
*/
|
|
4451
|
+
async attachOpfsClipStream(options) {
|
|
4452
|
+
if (this.destroyed) {
|
|
4453
|
+
throw new Error("SonareEngine is destroyed.");
|
|
4454
|
+
}
|
|
4455
|
+
if (!this.capabilities.clipPageRequestsRealtimeSafe) {
|
|
4456
|
+
throw new Error(
|
|
4457
|
+
"OPFS clip streaming requires SharedArrayBuffer clip-page requests; the postMessage fallback is not realtime-safe."
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
const { clipId, primePages = 1, ...providerOptions } = options;
|
|
4461
|
+
if ([...this.workletPageProviderClipIds.values()].includes(clipId)) {
|
|
4462
|
+
throw new Error(`An OPFS stream is already attached for clip ${clipId}.`);
|
|
4463
|
+
}
|
|
4464
|
+
const streamer = this.ensureWorkletClipStreamer();
|
|
4465
|
+
this.postSync({
|
|
4466
|
+
type: "syncClipPageProvider",
|
|
4467
|
+
clipId,
|
|
4468
|
+
numChannels: providerOptions.numChannels,
|
|
4469
|
+
numSamples: providerOptions.numSamples,
|
|
4470
|
+
pageFrames: providerOptions.pageFrames
|
|
4471
|
+
});
|
|
4472
|
+
let binding;
|
|
4473
|
+
binding = createOpfsClipPageProvider(this.offlineEngine, {
|
|
4474
|
+
...providerOptions,
|
|
4475
|
+
onPageSupplied: (pageIndex, channels) => {
|
|
4476
|
+
this.postSync(
|
|
4477
|
+
{ type: "syncClipPage", clipId, pageIndex, channels },
|
|
4478
|
+
transferableAudioBuffers(channels)
|
|
4479
|
+
);
|
|
4480
|
+
},
|
|
4481
|
+
onPageCleared: (pageIndex) => {
|
|
4482
|
+
this.postSync({ type: "syncClipPageClear", clipId, pageIndex });
|
|
4483
|
+
},
|
|
4484
|
+
onClose: () => {
|
|
4485
|
+
this.workletPageProviderClipIds.delete(binding.provider.id);
|
|
4486
|
+
this.postSync({ type: "syncClipPageDestroy", clipId });
|
|
4487
|
+
}
|
|
4488
|
+
});
|
|
4489
|
+
this.workletPageProviderClipIds.set(binding.provider.id, clipId);
|
|
4490
|
+
const lastPage = Math.ceil(providerOptions.numSamples / providerOptions.pageFrames) - 1;
|
|
4491
|
+
const primed = [];
|
|
4492
|
+
try {
|
|
4493
|
+
for (let page = 0; page < primePages && page <= lastPage; ++page) {
|
|
4494
|
+
if (await binding.supplyPage(page)) {
|
|
4495
|
+
primed.push(page);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
streamer.addSource(
|
|
4499
|
+
{
|
|
4500
|
+
clipId,
|
|
4501
|
+
binding,
|
|
4502
|
+
pageFrames: providerOptions.pageFrames,
|
|
4503
|
+
numSamples: providerOptions.numSamples
|
|
4504
|
+
},
|
|
4505
|
+
primed
|
|
4506
|
+
);
|
|
4507
|
+
this.startWorkletClipPagePolling();
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
binding.close();
|
|
4510
|
+
throw error;
|
|
4511
|
+
}
|
|
4512
|
+
return { binding, provider: binding.provider };
|
|
4513
|
+
}
|
|
3471
4514
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
3472
4515
|
return addClip(this.clipContext, trackId, buffer, startPpq, opts);
|
|
3473
4516
|
}
|
|
@@ -3529,9 +4572,59 @@ var SonareEngine = class _SonareEngine {
|
|
|
3529
4572
|
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
3530
4573
|
pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
|
|
3531
4574
|
}
|
|
4575
|
+
pushMidiUmp(trackId, word0, renderFrame = -1) {
|
|
4576
|
+
pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
|
|
4577
|
+
}
|
|
3532
4578
|
pushMidiSysex(trackId, data, renderFrame = -1) {
|
|
3533
4579
|
pushMidiSysex(this.stripContext, trackId, data, renderFrame);
|
|
3534
4580
|
}
|
|
4581
|
+
bindMidiCc(channel, controller, paramId, options = {}) {
|
|
4582
|
+
const minValue = options.minValue ?? 0;
|
|
4583
|
+
const maxValue = options.maxValue ?? 1;
|
|
4584
|
+
this.offlineEngine.bindMidiCc(channel, controller, paramId, { minValue, maxValue });
|
|
4585
|
+
this.postSync({ type: "syncMidiCcBinding", channel, controller, paramId, minValue, maxValue });
|
|
4586
|
+
}
|
|
4587
|
+
setMidiInputSource(destinationId = 0) {
|
|
4588
|
+
this.offlineEngine.setMidiInputSource(destinationId);
|
|
4589
|
+
this.postSync({ type: "syncMidiInputSource", destinationId });
|
|
4590
|
+
}
|
|
4591
|
+
clearMidiInputSource() {
|
|
4592
|
+
this.offlineEngine.clearMidiInputSource();
|
|
4593
|
+
this.postSync({ type: "syncClearMidiInputSource" });
|
|
4594
|
+
}
|
|
4595
|
+
pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
|
|
4596
|
+
this.offlineEngine.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
|
|
4597
|
+
this.postSync({
|
|
4598
|
+
type: "syncMidiInputNoteOn",
|
|
4599
|
+
group,
|
|
4600
|
+
channel,
|
|
4601
|
+
data0: note,
|
|
4602
|
+
data1: velocity,
|
|
4603
|
+
portTimeSamples
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4606
|
+
pushMidiInputNoteOff(group, channel, note, velocity = 0, portTimeSamples = 0) {
|
|
4607
|
+
this.offlineEngine.pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
|
|
4608
|
+
this.postSync({
|
|
4609
|
+
type: "syncMidiInputNoteOff",
|
|
4610
|
+
group,
|
|
4611
|
+
channel,
|
|
4612
|
+
data0: note,
|
|
4613
|
+
data1: velocity,
|
|
4614
|
+
portTimeSamples
|
|
4615
|
+
});
|
|
4616
|
+
}
|
|
4617
|
+
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
4618
|
+
this.offlineEngine.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
4619
|
+
this.postSync({
|
|
4620
|
+
type: "syncMidiInputCc",
|
|
4621
|
+
group,
|
|
4622
|
+
channel,
|
|
4623
|
+
data0: controller,
|
|
4624
|
+
data1: value,
|
|
4625
|
+
portTimeSamples
|
|
4626
|
+
});
|
|
4627
|
+
}
|
|
3535
4628
|
pushMidiPanic(renderFrame = -1) {
|
|
3536
4629
|
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
3537
4630
|
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
@@ -3539,6 +4632,10 @@ var SonareEngine = class _SonareEngine {
|
|
|
3539
4632
|
configureCapture(options) {
|
|
3540
4633
|
configureCapture(this.captureContext, options);
|
|
3541
4634
|
}
|
|
4635
|
+
/**
|
|
4636
|
+
* Arms the engine-global capture path. `trackId` is retained for source
|
|
4637
|
+
* compatibility and must be `0`; per-track capture is not implemented.
|
|
4638
|
+
*/
|
|
3542
4639
|
armRecord(trackId, enabled) {
|
|
3543
4640
|
return armRecord(this.captureContext, trackId, enabled);
|
|
3544
4641
|
}
|
|
@@ -3557,7 +4654,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
3557
4654
|
setMetronome(opts) {
|
|
3558
4655
|
this.offlineEngine.setMetronome(opts);
|
|
3559
4656
|
this.postSync({ type: "syncMetronome", config: opts });
|
|
3560
|
-
this.
|
|
4657
|
+
this.sendMirroredCommand({
|
|
3561
4658
|
type: 15 /* SetMetronome */,
|
|
3562
4659
|
sampleTime: -1,
|
|
3563
4660
|
argInt: opts.enabled ? 1 : 0
|
|
@@ -3627,12 +4724,101 @@ var SonareEngine = class _SonareEngine {
|
|
|
3627
4724
|
if (this.destroyed) {
|
|
3628
4725
|
return;
|
|
3629
4726
|
}
|
|
4727
|
+
this.unsubscribeWorkletClipRequests?.();
|
|
4728
|
+
this.unsubscribeWorkletClipRequests = void 0;
|
|
4729
|
+
this.workletClipStreamer?.close();
|
|
4730
|
+
this.workletClipStreamer = void 0;
|
|
4731
|
+
if (this.workletClipPagePollTimer !== void 0) {
|
|
4732
|
+
clearInterval(this.workletClipPagePollTimer);
|
|
4733
|
+
this.workletClipPagePollTimer = void 0;
|
|
4734
|
+
}
|
|
4735
|
+
this.workletClipPageRequests.clear();
|
|
3630
4736
|
this.destroyed = true;
|
|
3631
4737
|
this.transport.stop();
|
|
3632
4738
|
this.realtimeNode.pollTelemetry();
|
|
3633
4739
|
this.realtimeNode.destroy();
|
|
3634
4740
|
this.offlineEngine.destroy();
|
|
3635
4741
|
}
|
|
4742
|
+
ensureWorkletClipStreamer() {
|
|
4743
|
+
if (!this.workletClipStreamer) {
|
|
4744
|
+
this.workletClipStreamer = new ClipPageStreamer({
|
|
4745
|
+
popClipPageRequest: () => this.popWorkletClipPageRequest()
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
return this.workletClipStreamer;
|
|
4749
|
+
}
|
|
4750
|
+
/**
|
|
4751
|
+
* SAB requests have no postMessage wake-up by design. Polling on the main
|
|
4752
|
+
* thread is therefore intentionally outside the audio callback; 8 ms keeps
|
|
4753
|
+
* the bounded OPFS prefetch frontier responsive without adding worklet GC.
|
|
4754
|
+
*/
|
|
4755
|
+
startWorkletClipPagePolling() {
|
|
4756
|
+
if (this.workletClipPagePollTimer !== void 0) {
|
|
4757
|
+
return;
|
|
4758
|
+
}
|
|
4759
|
+
const poll = () => {
|
|
4760
|
+
if (!this.destroyed) {
|
|
4761
|
+
this.realtimeNode.pollClipPageRequests();
|
|
4762
|
+
}
|
|
4763
|
+
};
|
|
4764
|
+
poll();
|
|
4765
|
+
this.workletClipPagePollTimer = setInterval(poll, 8);
|
|
4766
|
+
}
|
|
4767
|
+
pumpWorkletClipPages() {
|
|
4768
|
+
const streamer = this.workletClipStreamer;
|
|
4769
|
+
if (!streamer || this.workletClipPump) {
|
|
4770
|
+
return;
|
|
4771
|
+
}
|
|
4772
|
+
this.workletClipPump = streamer.pump().catch((error) => {
|
|
4773
|
+
console.error("Sonare OPFS clip-page supply failed:", error);
|
|
4774
|
+
}).finally(() => {
|
|
4775
|
+
this.workletClipPump = void 0;
|
|
4776
|
+
if (this.workletClipPageRequests.size > 0) {
|
|
4777
|
+
this.pumpWorkletClipPages();
|
|
4778
|
+
}
|
|
4779
|
+
});
|
|
4780
|
+
}
|
|
4781
|
+
enqueueWorkletClipPageRequest(request) {
|
|
4782
|
+
if (!Number.isInteger(request.clipId) || request.clipId < 0 || !Number.isInteger(request.pageIndex) || (request.pageIndex ?? -1) < 0) {
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4785
|
+
this.workletClipPageRequests.delete(request.clipId);
|
|
4786
|
+
if (this.workletClipPageRequests.size >= MAX_PENDING_WORKLET_CLIP_PAGE_REQUESTS) {
|
|
4787
|
+
const oldest = this.workletClipPageRequests.keys().next().value;
|
|
4788
|
+
if (oldest !== void 0) {
|
|
4789
|
+
this.workletClipPageRequests.delete(oldest);
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
this.workletClipPageRequests.set(request.clipId, request);
|
|
4793
|
+
}
|
|
4794
|
+
popWorkletClipPageRequest() {
|
|
4795
|
+
const entry = this.workletClipPageRequests.entries().next().value;
|
|
4796
|
+
if (!entry) {
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
const [clipId, request] = entry;
|
|
4800
|
+
this.workletClipPageRequests.delete(clipId);
|
|
4801
|
+
return request;
|
|
4802
|
+
}
|
|
4803
|
+
commitWorkletClipPageProvider(clip) {
|
|
4804
|
+
const providerId = typeof clip.pageProvider === "object" && clip.pageProvider !== null ? clip.pageProvider.id : clip.pageProvider;
|
|
4805
|
+
if (providerId === void 0) {
|
|
4806
|
+
return false;
|
|
4807
|
+
}
|
|
4808
|
+
const clipId = this.workletPageProviderClipIds.get(providerId);
|
|
4809
|
+
if (clipId === void 0) {
|
|
4810
|
+
return false;
|
|
4811
|
+
}
|
|
4812
|
+
if (clip.id !== clipId) {
|
|
4813
|
+
throw new Error(`OPFS stream clipId ${clipId} must match addClip(..., { id: ${clipId} }).`);
|
|
4814
|
+
}
|
|
4815
|
+
this.postSync({
|
|
4816
|
+
type: "syncClipPageCommit",
|
|
4817
|
+
clipId,
|
|
4818
|
+
clip: { ...clip, channels: void 0, pageProvider: void 0 }
|
|
4819
|
+
});
|
|
4820
|
+
return true;
|
|
4821
|
+
}
|
|
3636
4822
|
mixerLanes() {
|
|
3637
4823
|
return mixerLanes(this.mixerContext);
|
|
3638
4824
|
}
|
|
@@ -3665,12 +4851,27 @@ var SonareEngine = class _SonareEngine {
|
|
|
3665
4851
|
if (this.destroyed) {
|
|
3666
4852
|
return;
|
|
3667
4853
|
}
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
4854
|
+
try {
|
|
4855
|
+
if (transfer && transfer.length > 0) {
|
|
4856
|
+
this.realtimeNode.node.port.postMessage(message, transfer);
|
|
4857
|
+
} else {
|
|
4858
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
4859
|
+
}
|
|
4860
|
+
} finally {
|
|
4861
|
+
this.flushOfflineMirror();
|
|
3672
4862
|
}
|
|
3673
4863
|
}
|
|
4864
|
+
// The offline engine is a control-thread mirror. It has no render loop to
|
|
4865
|
+
// drain its command ring, so drain it after each control operation. Never
|
|
4866
|
+
// call this from the worklet/audio path: it is intentionally control-only.
|
|
4867
|
+
flushOfflineMirror() {
|
|
4868
|
+
this.offlineEngine.flushControlCommands();
|
|
4869
|
+
}
|
|
4870
|
+
sendMirroredCommand(command) {
|
|
4871
|
+
const accepted = this.realtimeNode.sendCommand(command);
|
|
4872
|
+
this.flushOfflineMirror();
|
|
4873
|
+
return accepted;
|
|
4874
|
+
}
|
|
3674
4875
|
// Collaborator surface handed to the mixer/routing free functions so they can
|
|
3675
4876
|
// mutate the routing stores (held by reference), mirror into the offline
|
|
3676
4877
|
// engine, post mixer-sync messages, and declare lanes/buses without a
|
|
@@ -3725,13 +4926,13 @@ var SonareEngine = class _SonareEngine {
|
|
|
3725
4926
|
return {
|
|
3726
4927
|
offlineEngine: this.offlineEngine,
|
|
3727
4928
|
realtimeNode: this.realtimeNode,
|
|
4929
|
+
sendCommand: (command) => this.sendMirroredCommand(command),
|
|
3728
4930
|
offlineChannelCount: this.offlineChannelCount,
|
|
3729
4931
|
postSync: (message) => this.postSync(message),
|
|
3730
4932
|
getCaptureConfig: () => this.captureConfig,
|
|
3731
4933
|
setCaptureConfig: (config) => {
|
|
3732
4934
|
this.captureConfig = config;
|
|
3733
|
-
}
|
|
3734
|
-
resolveTargetId: (target) => this.resolveTargetId(target)
|
|
4935
|
+
}
|
|
3735
4936
|
};
|
|
3736
4937
|
}
|
|
3737
4938
|
// Collaborator surface handed to the parameter / automation-id resolution
|
|
@@ -3741,7 +4942,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
3741
4942
|
get parameterContext() {
|
|
3742
4943
|
return {
|
|
3743
4944
|
offlineEngine: this.offlineEngine,
|
|
3744
|
-
|
|
4945
|
+
sendCommand: (command) => this.sendMirroredCommand(command),
|
|
4946
|
+
postSync: (message) => this.postSync(message),
|
|
4947
|
+
automationLanes: this.automationLanes,
|
|
3745
4948
|
trackLaneIds: this.trackLaneIds,
|
|
3746
4949
|
resolveParamId: (nodeId, param) => this.resolveParamId(nodeId, param),
|
|
3747
4950
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
@@ -3755,6 +4958,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
3755
4958
|
return {
|
|
3756
4959
|
offlineEngine: this.offlineEngine,
|
|
3757
4960
|
realtimeNode: this.realtimeNode,
|
|
4961
|
+
sendCommand: (command) => this.sendMirroredCommand(command),
|
|
3758
4962
|
postSync: (message) => this.postSync(message),
|
|
3759
4963
|
getTempoBpm: () => this.tempoBpm,
|
|
3760
4964
|
setTempoBpm: (bpm) => {
|
|
@@ -3789,7 +4993,8 @@ var SonareEngine = class _SonareEngine {
|
|
|
3789
4993
|
allocateClipId: () => this.nextClipId++,
|
|
3790
4994
|
postSync: (message, transfer) => this.postSync(message, transfer),
|
|
3791
4995
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
3792
|
-
resolveTargetId: (target) => this.resolveTargetId(target)
|
|
4996
|
+
resolveTargetId: (target) => this.resolveTargetId(target),
|
|
4997
|
+
commitWorkletClipPageProvider: (clip) => this.commitWorkletClipPageProvider(clip)
|
|
3793
4998
|
};
|
|
3794
4999
|
}
|
|
3795
5000
|
// Collaborator surface handed to the marker free functions so they can mutate
|
|
@@ -3804,7 +5009,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
3804
5009
|
this.nextMarkerId = value;
|
|
3805
5010
|
},
|
|
3806
5011
|
postSync: (message) => this.postSync(message),
|
|
3807
|
-
sendCommand: (command) => this.
|
|
5012
|
+
sendCommand: (command) => this.sendMirroredCommand(command),
|
|
3808
5013
|
setLoop: (startPpq, endPpq, enabled) => this.setLoop(startPpq, endPpq, enabled)
|
|
3809
5014
|
};
|
|
3810
5015
|
}
|
|
@@ -3820,7 +5025,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
3820
5025
|
// sample-accurate smoothed-param command to the realtime runtime.
|
|
3821
5026
|
sendSmoothedParam(paramId, value) {
|
|
3822
5027
|
this.offlineEngine.setParameter(paramId, value);
|
|
3823
|
-
return this.
|
|
5028
|
+
return this.sendMirroredCommand({
|
|
3824
5029
|
type: 1 /* SetParamSmoothed */,
|
|
3825
5030
|
targetId: paramId,
|
|
3826
5031
|
sampleTime: -1,
|
|
@@ -3830,6 +5035,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
3830
5035
|
resolveParamId(nodeId, param) {
|
|
3831
5036
|
return resolveParamId(this.listParameters(), nodeId, param);
|
|
3832
5037
|
}
|
|
5038
|
+
syncParameters() {
|
|
5039
|
+
const parameters = this.listParameters();
|
|
5040
|
+
if (parameters.length > 0) {
|
|
5041
|
+
this.postSync({ type: "syncParameters", parameters });
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
3833
5044
|
resolveTargetId(target) {
|
|
3834
5045
|
return resolveTargetId(target);
|
|
3835
5046
|
}
|
|
@@ -3865,21 +5076,38 @@ var SonareEngine = class _SonareEngine {
|
|
|
3865
5076
|
var DEFAULT_METRONOME_CONFIG = {
|
|
3866
5077
|
beatGain: 0.35,
|
|
3867
5078
|
accentGain: 0.7,
|
|
3868
|
-
clickSamples: 0
|
|
5079
|
+
clickSamples: 0,
|
|
5080
|
+
clickSeconds: 0
|
|
3869
5081
|
};
|
|
3870
5082
|
function resolveMetronomeConfig(config) {
|
|
3871
5083
|
const resolved = {
|
|
3872
5084
|
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
3873
5085
|
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
3874
|
-
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
5086
|
+
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples,
|
|
5087
|
+
clickSeconds: config.clickSeconds ?? DEFAULT_METRONOME_CONFIG.clickSeconds
|
|
3875
5088
|
};
|
|
3876
|
-
if (!Number.isFinite(resolved.beatGain) || resolved.beatGain < 0 || !Number.isFinite(resolved.accentGain) || resolved.accentGain < 0 || !Number.isInteger(resolved.clickSamples) || resolved.clickSamples < 0 || resolved.clickSamples > 384e3 || config.clickSeconds !== void 0 && (!Number.isFinite(config.clickSeconds) || config.clickSeconds < 0 || config.clickSeconds > 1)) {
|
|
5089
|
+
if (!Number.isFinite(resolved.beatGain) || resolved.beatGain < 0 || !Number.isFinite(resolved.accentGain) || resolved.accentGain < 0 || !Number.isInteger(resolved.clickSamples) || resolved.clickSamples < 0 || !Number.isFinite(resolved.clickSeconds) || resolved.clickSeconds < 0 || resolved.clickSamples > 384e3 || config.clickSeconds !== void 0 && (!Number.isFinite(config.clickSeconds) || config.clickSeconds < 0 || config.clickSeconds > 1)) {
|
|
3877
5090
|
throw new RangeError("invalid metronome gains or click length");
|
|
3878
5091
|
}
|
|
3879
5092
|
return resolved;
|
|
3880
5093
|
}
|
|
3881
5094
|
|
|
3882
5095
|
// src/worklet/engine-processor.ts
|
|
5096
|
+
function captureTransferList(channels) {
|
|
5097
|
+
const transfers = [];
|
|
5098
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5099
|
+
for (const channel of channels) {
|
|
5100
|
+
const buffer = channel.buffer;
|
|
5101
|
+
if (!(buffer instanceof ArrayBuffer)) {
|
|
5102
|
+
throw new TypeError("capture response channels must use plain ArrayBuffers");
|
|
5103
|
+
}
|
|
5104
|
+
if (!seen.has(buffer)) {
|
|
5105
|
+
seen.add(buffer);
|
|
5106
|
+
transfers.push(buffer);
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
return transfers;
|
|
5110
|
+
}
|
|
3883
5111
|
var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletProcessor {
|
|
3884
5112
|
constructor(options = {}, transport) {
|
|
3885
5113
|
this.closed = false;
|
|
@@ -3889,7 +5117,15 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
3889
5117
|
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
3890
5118
|
this.liveClips = /* @__PURE__ */ new Map();
|
|
3891
5119
|
this.pagedClipProviders = /* @__PURE__ */ new Map();
|
|
5120
|
+
this.pagedClipPageFrames = /* @__PURE__ */ new Map();
|
|
3892
5121
|
this.pendingPagedClips = /* @__PURE__ */ new Map();
|
|
5122
|
+
// The worklet drains at most this many distinct page misses per render
|
|
5123
|
+
// quantum. The fixed scalar buffers avoid retaining an unbounded queue on
|
|
5124
|
+
// the audio thread; any remaining native requests stay queued for the next
|
|
5125
|
+
// quantum and render silence meanwhile.
|
|
5126
|
+
this.clipPageRequestClipIds = new Float64Array(64);
|
|
5127
|
+
this.clipPageRequestPageIndices = new Float64Array(64);
|
|
5128
|
+
this.clipPageRequestOverflowReported = 0;
|
|
3893
5129
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
3894
5130
|
this.blockSize = options.blockSize ?? 128;
|
|
3895
5131
|
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
@@ -3906,7 +5142,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
3906
5142
|
options.scopeRingCapacity,
|
|
3907
5143
|
options.scopeBands
|
|
3908
5144
|
) : void 0;
|
|
3909
|
-
this.
|
|
5145
|
+
this.clipPageRequestRing = options.clipPageRequestSharedBuffer ? clipPageRequestRingFromSharedBuffer(
|
|
5146
|
+
options.clipPageRequestSharedBuffer,
|
|
5147
|
+
options.clipPageRequestRingCapacity
|
|
5148
|
+
) : void 0;
|
|
5149
|
+
this.externalMidiRing = options.externalMidiSharedBuffer ? externalMidiRingFromSharedBuffer(
|
|
5150
|
+
options.externalMidiSharedBuffer,
|
|
5151
|
+
options.externalMidiRingCapacity
|
|
5152
|
+
) : void 0;
|
|
5153
|
+
this.engine = new RealtimeEngine(
|
|
5154
|
+
this.sampleRate,
|
|
5155
|
+
this.blockSize,
|
|
5156
|
+
1024,
|
|
5157
|
+
1024,
|
|
5158
|
+
this.channelCount
|
|
5159
|
+
);
|
|
3910
5160
|
this.engine.prepareChannels(this.channelCount, this.blockSize);
|
|
3911
5161
|
this.channelBuffers = new Array(this.channelCount);
|
|
3912
5162
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
@@ -3971,6 +5221,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
3971
5221
|
target.fill(0);
|
|
3972
5222
|
}
|
|
3973
5223
|
}
|
|
5224
|
+
this.publishClipPageRequests();
|
|
3974
5225
|
this.publishTelemetry();
|
|
3975
5226
|
this.publishMeters();
|
|
3976
5227
|
this.publishScope();
|
|
@@ -3984,7 +5235,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
3984
5235
|
}
|
|
3985
5236
|
receiveCommand(command) {
|
|
3986
5237
|
if (!this.closed) {
|
|
3987
|
-
this.
|
|
5238
|
+
this.safeApplyCommand(command);
|
|
3988
5239
|
}
|
|
3989
5240
|
}
|
|
3990
5241
|
// Applies an out-of-band control-plane sync message on the AudioWorklet
|
|
@@ -3995,35 +5246,64 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
3995
5246
|
if (this.closed) {
|
|
3996
5247
|
return;
|
|
3997
5248
|
}
|
|
5249
|
+
try {
|
|
5250
|
+
this.applySync(message);
|
|
5251
|
+
} catch (error) {
|
|
5252
|
+
this.transport?.postMessage?.({
|
|
5253
|
+
type: "syncError",
|
|
5254
|
+
syncType: message.type,
|
|
5255
|
+
message: error instanceof Error ? error.message : String(error)
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
applySync(message) {
|
|
3998
5260
|
switch (message.type) {
|
|
3999
|
-
case "
|
|
5261
|
+
case "destroy":
|
|
5262
|
+
this.destroy();
|
|
5263
|
+
return;
|
|
5264
|
+
case "syncClips": {
|
|
5265
|
+
this.engine.setClips(message.clips);
|
|
5266
|
+
this.clearPagedClipProviders();
|
|
4000
5267
|
this.liveClips.clear();
|
|
4001
5268
|
for (const clip of message.clips) {
|
|
4002
5269
|
if (clip.id !== void 0) {
|
|
4003
5270
|
this.liveClips.set(clip.id, clip);
|
|
4004
5271
|
}
|
|
4005
5272
|
}
|
|
4006
|
-
this.engine.setClips(message.clips);
|
|
4007
5273
|
break;
|
|
4008
|
-
|
|
5274
|
+
}
|
|
5275
|
+
case "syncClipsDelta": {
|
|
5276
|
+
const nextClips = new Map(this.liveClips);
|
|
4009
5277
|
for (const clipId of message.removeIds) {
|
|
4010
|
-
|
|
5278
|
+
nextClips.delete(clipId);
|
|
4011
5279
|
}
|
|
4012
5280
|
for (const clip of message.upserts) {
|
|
4013
5281
|
if (clip.id !== void 0) {
|
|
4014
|
-
|
|
5282
|
+
nextClips.set(clip.id, clip);
|
|
4015
5283
|
}
|
|
4016
5284
|
}
|
|
4017
|
-
this.engine.setClips(Array.from(
|
|
5285
|
+
this.engine.setClips(Array.from(nextClips.values()));
|
|
5286
|
+
for (const clipId of message.removeIds) {
|
|
5287
|
+
this.removePagedClipProvider(clipId);
|
|
5288
|
+
}
|
|
5289
|
+
this.liveClips.clear();
|
|
5290
|
+
for (const [clipId, clip] of nextClips) {
|
|
5291
|
+
this.liveClips.set(clipId, clip);
|
|
5292
|
+
}
|
|
4018
5293
|
break;
|
|
5294
|
+
}
|
|
4019
5295
|
case "syncClipPageProvider": {
|
|
4020
5296
|
const provider = this.engine.createClipPageProvider(
|
|
4021
5297
|
message.numChannels,
|
|
4022
5298
|
message.numSamples,
|
|
4023
5299
|
message.pageFrames
|
|
4024
5300
|
);
|
|
5301
|
+
this.removePagedClipProvider(message.clipId);
|
|
4025
5302
|
this.pagedClipProviders.set(message.clipId, provider.id);
|
|
4026
|
-
this.
|
|
5303
|
+
this.pagedClipPageFrames.set(message.clipId, message.pageFrames);
|
|
5304
|
+
if (message.clip) {
|
|
5305
|
+
this.pendingPagedClips.set(message.clipId, message.clip);
|
|
5306
|
+
}
|
|
4027
5307
|
break;
|
|
4028
5308
|
}
|
|
4029
5309
|
case "syncClipPage": {
|
|
@@ -4033,16 +5313,34 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4033
5313
|
}
|
|
4034
5314
|
break;
|
|
4035
5315
|
}
|
|
5316
|
+
case "syncClipPageClear": {
|
|
5317
|
+
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
5318
|
+
if (providerId !== void 0) {
|
|
5319
|
+
this.engine.clearClipPage(providerId, message.pageIndex);
|
|
5320
|
+
}
|
|
5321
|
+
break;
|
|
5322
|
+
}
|
|
4036
5323
|
case "syncClipPageCommit": {
|
|
4037
5324
|
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
4038
|
-
const clip = this.pendingPagedClips.get(message.clipId);
|
|
5325
|
+
const clip = message.clip ?? this.pendingPagedClips.get(message.clipId);
|
|
4039
5326
|
if (providerId !== void 0 && clip) {
|
|
4040
|
-
|
|
5327
|
+
const nextClip = { ...clip, pageProvider: providerId };
|
|
5328
|
+
const nextClips = new Map(this.liveClips);
|
|
5329
|
+
nextClips.set(message.clipId, nextClip);
|
|
5330
|
+
this.engine.setClips(Array.from(nextClips.values()));
|
|
5331
|
+
this.liveClips.set(message.clipId, nextClip);
|
|
4041
5332
|
this.pendingPagedClips.delete(message.clipId);
|
|
4042
|
-
this.engine.setClips(Array.from(this.liveClips.values()));
|
|
4043
5333
|
}
|
|
4044
5334
|
break;
|
|
4045
5335
|
}
|
|
5336
|
+
case "syncClipPageDestroy": {
|
|
5337
|
+
const nextClips = new Map(this.liveClips);
|
|
5338
|
+
nextClips.delete(message.clipId);
|
|
5339
|
+
this.engine.setClips(Array.from(nextClips.values()));
|
|
5340
|
+
this.liveClips.delete(message.clipId);
|
|
5341
|
+
this.removePagedClipProvider(message.clipId);
|
|
5342
|
+
break;
|
|
5343
|
+
}
|
|
4046
5344
|
case "syncMidiClips":
|
|
4047
5345
|
this.engine.setMidiClips(message.clips);
|
|
4048
5346
|
break;
|
|
@@ -4050,12 +5348,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4050
5348
|
this.engine.setMarkers(message.markers);
|
|
4051
5349
|
break;
|
|
4052
5350
|
case "syncMetronome":
|
|
4053
|
-
|
|
4054
|
-
|
|
5351
|
+
{
|
|
5352
|
+
const config = resolveMetronomeConfig(message.config);
|
|
5353
|
+
this.engine.setMetronome(message.config);
|
|
5354
|
+
this.metronomeConfig = config;
|
|
5355
|
+
}
|
|
4055
5356
|
break;
|
|
4056
5357
|
case "syncAutomation":
|
|
4057
5358
|
this.engine.setAutomationLane(message.paramId, message.points);
|
|
4058
5359
|
break;
|
|
5360
|
+
case "syncParameters":
|
|
5361
|
+
this.engine.clearParameters();
|
|
5362
|
+
for (const info of message.parameters) {
|
|
5363
|
+
this.engine.addParameter(info);
|
|
5364
|
+
}
|
|
5365
|
+
break;
|
|
4059
5366
|
case "syncTempo":
|
|
4060
5367
|
if (message.tempoSegments) {
|
|
4061
5368
|
this.engine.setTempoSegments(message.tempoSegments);
|
|
@@ -4202,6 +5509,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4202
5509
|
message.renderFrame
|
|
4203
5510
|
);
|
|
4204
5511
|
break;
|
|
5512
|
+
case "syncMidiUmp":
|
|
5513
|
+
this.engine.pushMidiUmp(message.destinationId, message.word0, message.renderFrame);
|
|
5514
|
+
break;
|
|
4205
5515
|
case "syncMidiSysex":
|
|
4206
5516
|
this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
|
|
4207
5517
|
break;
|
|
@@ -4214,6 +5524,45 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4214
5524
|
case "syncExternalMidiClock":
|
|
4215
5525
|
this.engine.setExternalMidiClockEnabled(message.enabled);
|
|
4216
5526
|
break;
|
|
5527
|
+
case "syncMidiInputSource":
|
|
5528
|
+
this.engine.setMidiInputSource(message.destinationId ?? 0);
|
|
5529
|
+
break;
|
|
5530
|
+
case "syncClearMidiInputSource":
|
|
5531
|
+
this.engine.clearMidiInputSource();
|
|
5532
|
+
break;
|
|
5533
|
+
case "syncMidiCcBinding":
|
|
5534
|
+
this.engine.bindMidiCc(message.channel, message.controller, message.paramId, {
|
|
5535
|
+
minValue: message.minValue,
|
|
5536
|
+
maxValue: message.maxValue
|
|
5537
|
+
});
|
|
5538
|
+
break;
|
|
5539
|
+
case "syncMidiInputNoteOn":
|
|
5540
|
+
this.engine.pushMidiInputNoteOn(
|
|
5541
|
+
message.group,
|
|
5542
|
+
message.channel,
|
|
5543
|
+
message.data0,
|
|
5544
|
+
message.data1,
|
|
5545
|
+
message.portTimeSamples
|
|
5546
|
+
);
|
|
5547
|
+
break;
|
|
5548
|
+
case "syncMidiInputNoteOff":
|
|
5549
|
+
this.engine.pushMidiInputNoteOff(
|
|
5550
|
+
message.group,
|
|
5551
|
+
message.channel,
|
|
5552
|
+
message.data0,
|
|
5553
|
+
message.data1,
|
|
5554
|
+
message.portTimeSamples
|
|
5555
|
+
);
|
|
5556
|
+
break;
|
|
5557
|
+
case "syncMidiInputCc":
|
|
5558
|
+
this.engine.pushMidiInputCc(
|
|
5559
|
+
message.group,
|
|
5560
|
+
message.channel,
|
|
5561
|
+
message.data0,
|
|
5562
|
+
message.data1,
|
|
5563
|
+
message.portTimeSamples
|
|
5564
|
+
);
|
|
5565
|
+
break;
|
|
4217
5566
|
}
|
|
4218
5567
|
}
|
|
4219
5568
|
receiveCaptureRequest(message) {
|
|
@@ -4239,22 +5588,17 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4239
5588
|
return;
|
|
4240
5589
|
}
|
|
4241
5590
|
if (message.op === "read") {
|
|
4242
|
-
const
|
|
4243
|
-
const
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
type: "captureResponse",
|
|
4254
|
-
requestId: message.requestId,
|
|
4255
|
-
ok: true,
|
|
4256
|
-
channels
|
|
4257
|
-
});
|
|
5591
|
+
const channels = Array.from(this.engine.capturedAudio());
|
|
5592
|
+
const transfer = captureTransferList(channels);
|
|
5593
|
+
this.transport?.postMessage?.(
|
|
5594
|
+
{
|
|
5595
|
+
type: "captureResponse",
|
|
5596
|
+
requestId: message.requestId,
|
|
5597
|
+
ok: true,
|
|
5598
|
+
channels
|
|
5599
|
+
},
|
|
5600
|
+
transfer
|
|
5601
|
+
);
|
|
4258
5602
|
return;
|
|
4259
5603
|
}
|
|
4260
5604
|
this.engine.resetCapture();
|
|
@@ -4294,6 +5638,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4294
5638
|
}
|
|
4295
5639
|
destroy() {
|
|
4296
5640
|
if (!this.closed) {
|
|
5641
|
+
this.clearPagedClipProviders();
|
|
4297
5642
|
this.engine.destroy();
|
|
4298
5643
|
this.closed = true;
|
|
4299
5644
|
}
|
|
@@ -4307,7 +5652,22 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4307
5652
|
if (!command) {
|
|
4308
5653
|
return;
|
|
4309
5654
|
}
|
|
5655
|
+
this.safeApplyCommand(command);
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
safeApplyCommand(command) {
|
|
5659
|
+
try {
|
|
4310
5660
|
this.applyCommand(command);
|
|
5661
|
+
} catch {
|
|
5662
|
+
this.publishTelemetryRecord({
|
|
5663
|
+
type: 1 /* Error */,
|
|
5664
|
+
error: 19 /* InvalidCommand */,
|
|
5665
|
+
renderFrame: 0,
|
|
5666
|
+
timelineSample: 0,
|
|
5667
|
+
audibleTimelineSample: 0,
|
|
5668
|
+
graphLatencySamplesQ8: 0,
|
|
5669
|
+
value: Number(command.type)
|
|
5670
|
+
});
|
|
4311
5671
|
}
|
|
4312
5672
|
}
|
|
4313
5673
|
applyCommand(command) {
|
|
@@ -4339,9 +5699,6 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4339
5699
|
case 5 /* TransportSeekPpq */:
|
|
4340
5700
|
this.engine.seekPpq(Number(command.argFloat ?? 0), sampleTime);
|
|
4341
5701
|
break;
|
|
4342
|
-
case 6 /* SetTempoMap */:
|
|
4343
|
-
this.engine.setTempo(Number(command.argFloat ?? 120));
|
|
4344
|
-
break;
|
|
4345
5702
|
case 7 /* SetLoop */:
|
|
4346
5703
|
this.engine.setLoop(
|
|
4347
5704
|
Number(command.argFloat ?? 0),
|
|
@@ -4364,7 +5721,8 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4364
5721
|
enabled: Boolean(command.argInt),
|
|
4365
5722
|
beatGain: this.metronomeConfig.beatGain,
|
|
4366
5723
|
accentGain: this.metronomeConfig.accentGain,
|
|
4367
|
-
clickSamples: this.metronomeConfig.clickSamples
|
|
5724
|
+
clickSamples: this.metronomeConfig.clickSamples,
|
|
5725
|
+
clickSeconds: this.metronomeConfig.clickSeconds
|
|
4368
5726
|
});
|
|
4369
5727
|
break;
|
|
4370
5728
|
case 17 /* SeekMarker */:
|
|
@@ -4378,6 +5736,19 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4378
5736
|
sampleTime
|
|
4379
5737
|
);
|
|
4380
5738
|
break;
|
|
5739
|
+
case 26 /* SetTrackMonitorMode */: {
|
|
5740
|
+
const rawMode = command.argInt;
|
|
5741
|
+
const mode = typeof rawMode === "bigint" ? Number(rawMode) : rawMode;
|
|
5742
|
+
if (typeof mode !== "number" || !Number.isSafeInteger(mode) || mode < 0 || mode > 2) {
|
|
5743
|
+
throw new RangeError(`Invalid track monitor mode: ${String(rawMode)}`);
|
|
5744
|
+
}
|
|
5745
|
+
const laneIndex = command.targetId;
|
|
5746
|
+
if (typeof laneIndex !== "number" || !Number.isSafeInteger(laneIndex) || laneIndex < 0 || laneIndex > 4294967295) {
|
|
5747
|
+
throw new RangeError(`Invalid track monitor lane index: ${String(laneIndex)}`);
|
|
5748
|
+
}
|
|
5749
|
+
this.engine.setTrackMonitorMode(laneIndex, mode, sampleTime);
|
|
5750
|
+
break;
|
|
5751
|
+
}
|
|
4381
5752
|
default:
|
|
4382
5753
|
this.publishTelemetryRecord({
|
|
4383
5754
|
type: 1 /* Error */,
|
|
@@ -4392,10 +5763,34 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4392
5763
|
}
|
|
4393
5764
|
}
|
|
4394
5765
|
publishTelemetry() {
|
|
5766
|
+
const ring = this.telemetryRing;
|
|
5767
|
+
if (ring) {
|
|
5768
|
+
for (let count = 0; count < 64 && this.engine.popTelemetryToScratch(); count++) {
|
|
5769
|
+
this.writeTelemetryScratch(ring);
|
|
5770
|
+
}
|
|
5771
|
+
return;
|
|
5772
|
+
}
|
|
4395
5773
|
for (const item of this.engine.drainTelemetry(64)) {
|
|
4396
5774
|
this.publishTelemetryRecord(telemetryFromEngine(item));
|
|
4397
5775
|
}
|
|
4398
5776
|
}
|
|
5777
|
+
writeTelemetryScratch(ring) {
|
|
5778
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
5779
|
+
const offset = writeIndex % ring.capacity * SONARE_ENGINE_TELEMETRY_RECORD_BYTES;
|
|
5780
|
+
ring.view.setUint32(offset, this.engine.telemetryScratchType(), true);
|
|
5781
|
+
ring.view.setUint32(offset + 4, this.engine.telemetryScratchError(), true);
|
|
5782
|
+
writeInt64Words(ring.view, offset + 8, this.engine.telemetryScratchRenderFrame());
|
|
5783
|
+
writeInt64Words(ring.view, offset + 16, this.engine.telemetryScratchTimelineSample());
|
|
5784
|
+
writeInt64Words(ring.view, offset + 24, this.engine.telemetryScratchAudibleTimelineSample());
|
|
5785
|
+
ring.view.setInt32(offset + 32, this.engine.telemetryScratchGraphLatencySamplesQ8(), true);
|
|
5786
|
+
ring.view.setUint32(offset + 36, this.engine.telemetryScratchValue(), true);
|
|
5787
|
+
ring.view.setUint32(offset + 40, 0, true);
|
|
5788
|
+
ring.view.setUint32(offset + 44, 0, true);
|
|
5789
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
5790
|
+
if (writeIndex + 1 > ring.capacity) {
|
|
5791
|
+
Atomics.store(ring.header, 4, writeIndex + 1 - ring.capacity);
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
4399
5794
|
publishTelemetryRecord(record) {
|
|
4400
5795
|
if (this.telemetryRing) {
|
|
4401
5796
|
writeSonareEngineTelemetryRingBuffer(this.telemetryRing, record);
|
|
@@ -4418,6 +5813,19 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4418
5813
|
if (this.meterIntervalFrames <= 0 || !this.transport && !this.meterRing) {
|
|
4419
5814
|
return;
|
|
4420
5815
|
}
|
|
5816
|
+
if (this.meterRing) {
|
|
5817
|
+
for (let count = 0; count < 64 && this.engine.popMeterTelemetryToScratch(); count++) {
|
|
5818
|
+
const frame = this.engine.meterScratchRenderFrame();
|
|
5819
|
+
if (frame !== this.lastMeterFrame && frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
5820
|
+
continue;
|
|
5821
|
+
}
|
|
5822
|
+
if (frame !== this.lastMeterFrame) {
|
|
5823
|
+
this.lastMeterFrame = frame;
|
|
5824
|
+
}
|
|
5825
|
+
this.writeMeterScratch(this.meterRing);
|
|
5826
|
+
}
|
|
5827
|
+
return;
|
|
5828
|
+
}
|
|
4421
5829
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
4422
5830
|
const meter = meterFromEngine(item);
|
|
4423
5831
|
if (meter.frame !== this.lastMeterFrame && meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
@@ -4434,6 +5842,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4434
5842
|
}
|
|
4435
5843
|
}
|
|
4436
5844
|
}
|
|
5845
|
+
writeMeterScratch(ring) {
|
|
5846
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
5847
|
+
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
5848
|
+
const frame = Number(this.engine.meterScratchRenderFrame());
|
|
5849
|
+
ring.records[offset] = encodeFrameLo(frame);
|
|
5850
|
+
ring.records[offset + 1] = encodeFrameHi(frame);
|
|
5851
|
+
ring.records[offset + 2] = this.engine.meterScratchTargetId();
|
|
5852
|
+
for (let field = 0; field < 11; field++) {
|
|
5853
|
+
ring.records[offset + 3 + field] = this.engine.meterScratchValue(field);
|
|
5854
|
+
}
|
|
5855
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
5856
|
+
}
|
|
4437
5857
|
writeMeterRing(meter) {
|
|
4438
5858
|
const ring = this.meterRing;
|
|
4439
5859
|
if (!ring) {
|
|
@@ -4465,45 +5885,160 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
4465
5885
|
if (!ring) {
|
|
4466
5886
|
return;
|
|
4467
5887
|
}
|
|
4468
|
-
for (
|
|
4469
|
-
this.
|
|
5888
|
+
for (let count = 0; count < 64 && this.engine.popScopeTelemetryToScratch(); count++) {
|
|
5889
|
+
this.writeScopeScratch(ring);
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5892
|
+
writeScopeScratch(ring) {
|
|
5893
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
5894
|
+
const base = writeIndex % ring.capacity * ring.recordFloats;
|
|
5895
|
+
const frame = Number(this.engine.scopeScratchRenderFrame());
|
|
5896
|
+
ring.records[base] = encodeFrameLo(frame);
|
|
5897
|
+
ring.records[base + 1] = encodeFrameHi(frame);
|
|
5898
|
+
ring.records[base + 2] = this.engine.scopeScratchTargetId();
|
|
5899
|
+
const bandCount = Math.min(ring.bands, this.engine.scopeScratchBandCount());
|
|
5900
|
+
ring.records[base + 3] = bandCount;
|
|
5901
|
+
const pointCount = Math.min(ring.maxPoints, this.engine.scopeScratchPointCount());
|
|
5902
|
+
ring.records[base + 4] = pointCount;
|
|
5903
|
+
const bandsBase = base + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS;
|
|
5904
|
+
for (let i = 0; i < bandCount; i++) {
|
|
5905
|
+
ring.records[bandsBase + i] = this.engine.scopeScratchBand(i);
|
|
5906
|
+
}
|
|
5907
|
+
const pointsBase = bandsBase + ring.bands;
|
|
5908
|
+
for (let i = 0; i < pointCount; i++) {
|
|
5909
|
+
ring.records[pointsBase + 2 * i] = this.engine.scopeScratchPointLeft(i);
|
|
5910
|
+
ring.records[pointsBase + 2 * i + 1] = this.engine.scopeScratchPointRight(i);
|
|
4470
5911
|
}
|
|
5912
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
4471
5913
|
}
|
|
4472
5914
|
// Drains queued external-MIDI events (already lowered to MIDI 1.0 bytes) and
|
|
4473
5915
|
// forwards them to the main thread for delivery to Web MIDI output ports.
|
|
4474
5916
|
// One batch per render block; skipped entirely when nothing is queued, so an
|
|
4475
5917
|
// all-internal project never allocates or posts here.
|
|
4476
5918
|
publishExternalMidi() {
|
|
5919
|
+
const ring = this.externalMidiRing;
|
|
5920
|
+
if (ring) {
|
|
5921
|
+
for (let count = 0; count < 256 && this.engine.popExternalMidiToScratch(); count++) {
|
|
5922
|
+
pushSonareExternalMidiRingBuffer(
|
|
5923
|
+
ring,
|
|
5924
|
+
this.engine.externalMidiScratchDestinationId(),
|
|
5925
|
+
this.engine.externalMidiScratchRenderFrame(),
|
|
5926
|
+
this.engine.externalMidiScratchByteWord(),
|
|
5927
|
+
this.engine.externalMidiScratchByteCount()
|
|
5928
|
+
);
|
|
5929
|
+
this.engine.consumeExternalMidiScratch();
|
|
5930
|
+
}
|
|
5931
|
+
return;
|
|
5932
|
+
}
|
|
4477
5933
|
if (!this.transport?.postMessage) {
|
|
4478
5934
|
return;
|
|
4479
5935
|
}
|
|
5936
|
+
if (this.engine.externalMidiPendingCount() === 0) {
|
|
5937
|
+
return;
|
|
5938
|
+
}
|
|
4480
5939
|
const events = this.engine.drainExternalMidi(256);
|
|
4481
5940
|
if (events.length === 0) {
|
|
4482
5941
|
return;
|
|
4483
5942
|
}
|
|
4484
5943
|
this.transport.postMessage({ type: "externalMidi", events });
|
|
4485
5944
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
const
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
const bandsBase = base + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS;
|
|
4497
|
-
for (let i = 0; i < bandCount; i++) {
|
|
4498
|
-
ring.records[bandsBase + i] = record.bands[i];
|
|
5945
|
+
/**
|
|
5946
|
+
* Forward a bounded batch of native page misses to the main thread. This
|
|
5947
|
+
* deliberately runs after audio rendering: a cache miss is silence for this
|
|
5948
|
+
* block, and OPFS I/O must never delay `process()`.
|
|
5949
|
+
*/
|
|
5950
|
+
publishClipPageRequests() {
|
|
5951
|
+
const ring = this.clipPageRequestRing;
|
|
5952
|
+
const transport = this.transport;
|
|
5953
|
+
if (!ring && !transport?.postMessage) {
|
|
5954
|
+
return;
|
|
4499
5955
|
}
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
5956
|
+
let count = 0;
|
|
5957
|
+
let drained = 0;
|
|
5958
|
+
while (drained < this.clipPageRequestClipIds.length) {
|
|
5959
|
+
drained += 1;
|
|
5960
|
+
let clipId;
|
|
5961
|
+
let sample;
|
|
5962
|
+
if (ring) {
|
|
5963
|
+
if (!this.engine.popClipPageRequestToScratch()) {
|
|
5964
|
+
break;
|
|
5965
|
+
}
|
|
5966
|
+
clipId = this.engine.clipPageRequestScratchClipId();
|
|
5967
|
+
sample = this.engine.clipPageRequestScratchSample();
|
|
5968
|
+
} else {
|
|
5969
|
+
const request = this.engine.popClipPageRequest();
|
|
5970
|
+
if (!request) {
|
|
5971
|
+
break;
|
|
5972
|
+
}
|
|
5973
|
+
clipId = request.clipId;
|
|
5974
|
+
sample = request.sample;
|
|
5975
|
+
}
|
|
5976
|
+
const pageFrames = this.pagedClipPageFrames.get(clipId);
|
|
5977
|
+
if (!pageFrames) {
|
|
5978
|
+
continue;
|
|
5979
|
+
}
|
|
5980
|
+
const pageIndex = Math.floor(sample / pageFrames);
|
|
5981
|
+
let duplicate = false;
|
|
5982
|
+
for (let i = 0; i < count; ++i) {
|
|
5983
|
+
if (this.clipPageRequestClipIds[i] === clipId && this.clipPageRequestPageIndices[i] === pageIndex) {
|
|
5984
|
+
duplicate = true;
|
|
5985
|
+
break;
|
|
5986
|
+
}
|
|
5987
|
+
}
|
|
5988
|
+
if (!duplicate) {
|
|
5989
|
+
this.clipPageRequestClipIds[count] = clipId;
|
|
5990
|
+
this.clipPageRequestPageIndices[count] = pageIndex;
|
|
5991
|
+
count += 1;
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
const overflowCount = this.engine.clipPageRequestOverflowCount();
|
|
5995
|
+
const dropped = overflowCount - this.clipPageRequestOverflowReported >>> 0;
|
|
5996
|
+
this.clipPageRequestOverflowReported = overflowCount;
|
|
5997
|
+
if (ring) {
|
|
5998
|
+
if (dropped > 0) {
|
|
5999
|
+
Atomics.add(ring.header, 4, dropped);
|
|
6000
|
+
}
|
|
6001
|
+
for (let i = 0; i < count; ++i) {
|
|
6002
|
+
pushSonareClipPageRequestRingBuffer(
|
|
6003
|
+
ring,
|
|
6004
|
+
this.clipPageRequestClipIds[i],
|
|
6005
|
+
this.clipPageRequestPageIndices[i]
|
|
6006
|
+
);
|
|
6007
|
+
}
|
|
6008
|
+
return;
|
|
6009
|
+
}
|
|
6010
|
+
if (count === 0 && dropped === 0) {
|
|
6011
|
+
return;
|
|
6012
|
+
}
|
|
6013
|
+
if (!transport?.postMessage) {
|
|
6014
|
+
return;
|
|
6015
|
+
}
|
|
6016
|
+
const requests = new Array(count);
|
|
6017
|
+
for (let i = 0; i < count; ++i) {
|
|
6018
|
+
requests[i] = {
|
|
6019
|
+
clipId: this.clipPageRequestClipIds[i],
|
|
6020
|
+
pageIndex: this.clipPageRequestPageIndices[i]
|
|
6021
|
+
};
|
|
6022
|
+
}
|
|
6023
|
+
transport.postMessage({
|
|
6024
|
+
type: "clipPageRequest",
|
|
6025
|
+
requests,
|
|
6026
|
+
...dropped > 0 ? { dropped } : {}
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
removePagedClipProvider(clipId) {
|
|
6030
|
+
const providerId = this.pagedClipProviders.get(clipId);
|
|
6031
|
+
if (providerId !== void 0) {
|
|
6032
|
+
this.engine.destroyClipPageProvider(providerId);
|
|
6033
|
+
}
|
|
6034
|
+
this.pagedClipProviders.delete(clipId);
|
|
6035
|
+
this.pagedClipPageFrames.delete(clipId);
|
|
6036
|
+
this.pendingPagedClips.delete(clipId);
|
|
6037
|
+
}
|
|
6038
|
+
clearPagedClipProviders() {
|
|
6039
|
+
for (const clipId of this.pagedClipProviders.keys()) {
|
|
6040
|
+
this.removePagedClipProvider(clipId);
|
|
4505
6041
|
}
|
|
4506
|
-
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
4507
6042
|
}
|
|
4508
6043
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
4509
6044
|
const ring = engineRingFromSharedBuffer(
|
|
@@ -4536,6 +6071,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
4536
6071
|
constructor(options) {
|
|
4537
6072
|
super();
|
|
4538
6073
|
this.pendingMessages = [];
|
|
6074
|
+
this.pendingMessagesOverflowed = false;
|
|
4539
6075
|
const port = this.port;
|
|
4540
6076
|
const processorOptions = options?.processorOptions ?? {};
|
|
4541
6077
|
void this.initializeEmbind(processorOptions, port);
|
|
@@ -4543,6 +6079,8 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
4543
6079
|
if (!this.bridge) {
|
|
4544
6080
|
if (this.pendingMessages.length < 1024) {
|
|
4545
6081
|
this.pendingMessages.push(event.data);
|
|
6082
|
+
} else {
|
|
6083
|
+
this.pendingMessagesOverflowed = true;
|
|
4546
6084
|
}
|
|
4547
6085
|
return;
|
|
4548
6086
|
}
|
|
@@ -4604,8 +6142,11 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
4604
6142
|
moduleFactory
|
|
4605
6143
|
});
|
|
4606
6144
|
}
|
|
6145
|
+
if (this.pendingMessagesOverflowed) {
|
|
6146
|
+
throw new Error("AudioWorklet initialization message buffer overflowed.");
|
|
6147
|
+
}
|
|
4607
6148
|
this.bridge = new SonareRealtimeEngineWorkletProcessor(options, {
|
|
4608
|
-
postMessage: (message) => port?.postMessage?.(message),
|
|
6149
|
+
postMessage: (message, transfer) => port?.postMessage?.(message, transfer),
|
|
4609
6150
|
onMeter: (meter) => port?.postMessage?.(meter)
|
|
4610
6151
|
});
|
|
4611
6152
|
for (const message of options.initialSyncMessages ?? []) {
|
|
@@ -4919,6 +6460,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
4919
6460
|
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
4920
6461
|
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
4921
6462
|
this.monoOutput = this.changer.getMonoOutputBuffer(this.blockSize);
|
|
6463
|
+
this.bufferGeneration = this.changer.bufferGeneration();
|
|
4922
6464
|
this.planarChannels = [];
|
|
4923
6465
|
if (this.channelCount > 1) {
|
|
4924
6466
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
@@ -4927,19 +6469,16 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
4927
6469
|
}
|
|
4928
6470
|
}
|
|
4929
6471
|
/**
|
|
4930
|
-
* Handles a control-plane message from the main thread.
|
|
4931
|
-
*
|
|
4932
|
-
*
|
|
4933
|
-
* DSP coefficient recomputation here. `setConfig` MUST NOT be deferred
|
|
4934
|
-
* into `process()` because that would block the audio thread for longer
|
|
4935
|
-
* than one render quantum (e.g. 128 samples / 44.1 kHz = ~2.9 ms).
|
|
6472
|
+
* Handles a control-plane message from the main thread. AudioWorklet port
|
|
6473
|
+
* handlers run on the same audio rendering thread as `process()`: this path
|
|
6474
|
+
* therefore accepts only a pre-normalized POD and never parses JSON.
|
|
4936
6475
|
*/
|
|
4937
6476
|
receiveMessage(message) {
|
|
4938
6477
|
if (this.destroyed) {
|
|
4939
6478
|
return;
|
|
4940
6479
|
}
|
|
4941
6480
|
if (message.type === "setConfig") {
|
|
4942
|
-
this.changer.
|
|
6481
|
+
this.changer.setPodConfig(message.config);
|
|
4943
6482
|
} else if (message.type === "reset") {
|
|
4944
6483
|
this.changer.reset();
|
|
4945
6484
|
} else if (message.type === "destroy") {
|
|
@@ -4951,7 +6490,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
4951
6490
|
if (this.destroyed || !output || output.length === 0) {
|
|
4952
6491
|
return !this.destroyed;
|
|
4953
6492
|
}
|
|
4954
|
-
if (this.monoInput.byteLength === 0) {
|
|
6493
|
+
if (this.bufferGeneration !== this.changer.bufferGeneration() || this.monoInput.byteLength === 0) {
|
|
4955
6494
|
this.reacquireBuffers();
|
|
4956
6495
|
}
|
|
4957
6496
|
const input = inputs[0];
|
|
@@ -4968,10 +6507,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
4968
6507
|
} else {
|
|
4969
6508
|
this.monoInput.fill(0, 0, frames2);
|
|
4970
6509
|
}
|
|
4971
|
-
this.changer.
|
|
4972
|
-
this.monoInput.subarray(0, frames2),
|
|
4973
|
-
this.monoOutput.subarray(0, frames2)
|
|
4974
|
-
);
|
|
6510
|
+
this.changer.processPreparedMono(frames2);
|
|
4975
6511
|
output[0].set(this.monoOutput.subarray(0, frames2));
|
|
4976
6512
|
return true;
|
|
4977
6513
|
}
|
|
@@ -5017,6 +6553,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
5017
6553
|
this.planarChannels[ch] = this.changer.getPlanarChannelBuffer(ch, this.blockSize);
|
|
5018
6554
|
}
|
|
5019
6555
|
}
|
|
6556
|
+
this.bufferGeneration = this.changer.bufferGeneration();
|
|
5020
6557
|
}
|
|
5021
6558
|
/**
|
|
5022
6559
|
* Returns the number of frames we can actually process given the
|
|
@@ -5089,9 +6626,13 @@ function registerSonareRealtimeVoiceChangerWorkletProcessor(name = "sonare-realt
|
|
|
5089
6626
|
scope.registerProcessor(name, RegisteredSonareRealtimeVoiceChangerWorkletProcessor);
|
|
5090
6627
|
}
|
|
5091
6628
|
export {
|
|
6629
|
+
SONARE_CLIP_PAGE_REQUEST_RING_HEADER_INTS,
|
|
6630
|
+
SONARE_CLIP_PAGE_REQUEST_RING_RECORD_UINT32S,
|
|
5092
6631
|
SONARE_ENGINE_COMMAND_RECORD_BYTES,
|
|
5093
6632
|
SONARE_ENGINE_RING_HEADER_INTS,
|
|
5094
6633
|
SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
|
|
6634
|
+
SONARE_EXTERNAL_MIDI_RING_HEADER_INTS,
|
|
6635
|
+
SONARE_EXTERNAL_MIDI_RING_RECORD_UINT32S,
|
|
5095
6636
|
SONARE_METER_RING_HEADER_INTS,
|
|
5096
6637
|
SONARE_METER_RING_RECORD_FLOATS,
|
|
5097
6638
|
SONARE_SCOPE_RING_HEADER_INTS,
|
|
@@ -5104,8 +6645,11 @@ export {
|
|
|
5104
6645
|
SonareRealtimeEngineWorkletProcessor,
|
|
5105
6646
|
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
5106
6647
|
SonareWorkletProcessor,
|
|
6648
|
+
attachOpfsClipStream,
|
|
6649
|
+
createSonareClipPageRequestRingBuffer,
|
|
5107
6650
|
createSonareEngineCommandRingBuffer,
|
|
5108
6651
|
createSonareEngineTelemetryRingBuffer,
|
|
6652
|
+
createSonareExternalMidiRingBuffer,
|
|
5109
6653
|
createSonareMeterRingBuffer,
|
|
5110
6654
|
createSonareScopeRingBuffer,
|
|
5111
6655
|
createSonareSpectrumRingBuffer,
|
|
@@ -5115,16 +6659,22 @@ export {
|
|
|
5115
6659
|
init,
|
|
5116
6660
|
isInitialized,
|
|
5117
6661
|
popSonareEngineCommandRingBuffer,
|
|
6662
|
+
pushSonareClipPageRequestRingBuffer,
|
|
5118
6663
|
pushSonareEngineCommandRingBuffer,
|
|
6664
|
+
pushSonareExternalMidiRingBuffer,
|
|
6665
|
+
readSonareClipPageRequestRingBuffer,
|
|
5119
6666
|
readSonareEngineTelemetryRingBuffer,
|
|
6667
|
+
readSonareExternalMidiRingBuffer,
|
|
5120
6668
|
readSonareMeterRingBuffer,
|
|
5121
6669
|
readSonareScopeRingBuffer,
|
|
5122
6670
|
readSonareSpectrumRingBuffer,
|
|
5123
6671
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
5124
6672
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
5125
6673
|
registerSonareWorkletProcessor,
|
|
6674
|
+
sonareClipPageRequestRingBufferByteLength,
|
|
5126
6675
|
sonareEngineCommandRingBufferByteLength,
|
|
5127
6676
|
sonareEngineTelemetryRingBufferByteLength,
|
|
6677
|
+
sonareExternalMidiRingBufferByteLength,
|
|
5128
6678
|
sonareMeterRingBufferByteLength,
|
|
5129
6679
|
sonareScopeRingBufferByteLength,
|
|
5130
6680
|
sonareSpectrumRingBufferByteLength,
|