@libraz/libsonare 1.7.0 → 1.7.2
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 +33 -0
- package/dist/analysis.d.ts +55 -3
- package/dist/analysis.js +10 -0
- package/dist/analysis.js.map +1 -1
- package/dist/index.d.ts +368 -199
- package/dist/index.js +111 -6
- package/dist/index.js.map +1 -1
- package/dist/sonare-analysis.js +1 -1
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worker.js.map +1 -1
- package/dist/worklet.d.ts +379 -9
- package/dist/worklet.js +174 -13
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/_chain_config.ts +6 -1
- package/src/effects_mastering.ts +5 -0
- package/src/effects_transform.ts +23 -12
- package/src/index.ts +10 -0
- package/src/mastering_core.ts +70 -0
- package/src/metering.ts +18 -0
- package/src/project.ts +3 -0
- package/src/project_class.ts +52 -2
- package/src/project_internal.ts +2 -0
- package/src/project_types.ts +39 -8
- package/src/public_types_mastering.ts +9 -0
- package/src/public_types_spectral.ts +16 -2
- package/src/realtime_engine.ts +39 -0
- package/src/sonare.js.d.ts +26 -0
- package/src/streaming_processors.ts +18 -0
- package/src/worklet/engine-node.ts +7 -2
- package/src/worklet/engine-processor.ts +59 -12
- package/src/worklet/messages.ts +12 -0
- package/src/worklet.ts +5 -0
|
@@ -301,10 +301,12 @@ export class SonareRealtimeEngineNode {
|
|
|
301
301
|
? createSonareExternalMidiRingBuffer(options.externalMidiRingCapacity ?? 256)
|
|
302
302
|
: undefined;
|
|
303
303
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
304
|
+
const cueOutput = options.cueOutput === true;
|
|
304
305
|
const processorOptions: SonareRealtimeEngineWorkletProcessorOptions = {
|
|
305
306
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
306
307
|
blockSize,
|
|
307
308
|
channelCount,
|
|
309
|
+
cueOutput,
|
|
308
310
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
309
311
|
commandRingCapacity: commandRing?.capacity,
|
|
310
312
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
@@ -329,8 +331,10 @@ export class SonareRealtimeEngineNode {
|
|
|
329
331
|
new AudioWorkletNode(ctx, name, nodeOptions));
|
|
330
332
|
const node = factory(context, processorName, {
|
|
331
333
|
numberOfInputs: 1,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
+
// The cue bus needs its own output; a single-output node keeps the
|
|
335
|
+
// historical mix where process() folds the cue into the program.
|
|
336
|
+
numberOfOutputs: cueOutput ? 2 : 1,
|
|
337
|
+
outputChannelCount: cueOutput ? [channelCount, channelCount] : [channelCount],
|
|
334
338
|
processorOptions,
|
|
335
339
|
});
|
|
336
340
|
return new SonareRealtimeEngineNode(
|
|
@@ -343,6 +347,7 @@ export class SonareRealtimeEngineNode {
|
|
|
343
347
|
audioWorklet,
|
|
344
348
|
clipPageRequestsRealtimeSafe: mode === 'sab',
|
|
345
349
|
externalMidiRealtimeSafe: mode === 'sab',
|
|
350
|
+
cueOutput,
|
|
346
351
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
347
352
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
348
353
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
@@ -46,6 +46,30 @@ import {
|
|
|
46
46
|
writeSonareEngineTelemetryRingBuffer,
|
|
47
47
|
} from './protocol';
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Copies one plane per output channel, zero-filling the tail past `frames` and
|
|
51
|
+
* any channel the source does not cover. Shared by the program and cue outputs
|
|
52
|
+
* so the two cannot drift in their padding behaviour. Allocation-free.
|
|
53
|
+
*/
|
|
54
|
+
function copyPlanesToOutput(
|
|
55
|
+
output: Float32Array[],
|
|
56
|
+
planes: readonly Float32Array[],
|
|
57
|
+
frames: number,
|
|
58
|
+
): void {
|
|
59
|
+
for (let ch = 0; ch < output.length; ch++) {
|
|
60
|
+
const target = output[ch];
|
|
61
|
+
const source = planes[ch] ?? planes[0];
|
|
62
|
+
if (source) {
|
|
63
|
+
target.set(source.subarray(0, Math.min(target.length, frames)));
|
|
64
|
+
if (target.length > frames) {
|
|
65
|
+
target.fill(0, frames);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
target.fill(0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
49
73
|
function captureTransferList(channels: readonly Float32Array[]): Transferable[] {
|
|
50
74
|
const transfers: ArrayBuffer[] = [];
|
|
51
75
|
const seen = new Set<ArrayBuffer>();
|
|
@@ -94,6 +118,11 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
94
118
|
// allocated per render quantum (the old engine.process() round-tripped fresh
|
|
95
119
|
// arrays on both heaps every block, an RT-safety hazard).
|
|
96
120
|
private channelBuffers: Float32Array[];
|
|
121
|
+
// Cue-bus plane, allocated only when the host asked for a separate PFL/AFL
|
|
122
|
+
// output. Empty otherwise, so a single-output host pays no heap and keeps the
|
|
123
|
+
// historical behaviour where process() folds the cue into the program mix.
|
|
124
|
+
private monitorBuffers: Float32Array[] = [];
|
|
125
|
+
private readonly cueOutput: boolean;
|
|
97
126
|
private readonly liveClips = new Map<number, EngineClip>();
|
|
98
127
|
private readonly pagedClipProviders = new Map<number, number>();
|
|
99
128
|
private readonly pagedClipPageFrames = new Map<number, number>();
|
|
@@ -160,6 +189,14 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
160
189
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
161
190
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
162
191
|
}
|
|
192
|
+
this.cueOutput = options.cueOutput === true;
|
|
193
|
+
if (this.cueOutput) {
|
|
194
|
+
this.engine.prepareMonitorChannels(this.channelCount, this.blockSize);
|
|
195
|
+
this.monitorBuffers = new Array(this.channelCount);
|
|
196
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
197
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
163
200
|
// Arm the engine's scope producer only when a scope ring was provided. The
|
|
164
201
|
// band count follows the ring's record layout so writeScopeRing never
|
|
165
202
|
// overruns its slot.
|
|
@@ -213,6 +250,9 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
213
250
|
if ((this.channelBuffers[0]?.byteLength ?? 0) === 0) {
|
|
214
251
|
this.reacquireChannelBuffers();
|
|
215
252
|
}
|
|
253
|
+
if (this.cueOutput && (this.monitorBuffers[0]?.byteLength ?? 0) === 0) {
|
|
254
|
+
this.reacquireMonitorBuffers();
|
|
255
|
+
}
|
|
216
256
|
|
|
217
257
|
const input = inputs[0];
|
|
218
258
|
// Write the AudioWorklet input straight into the engine's WASM-heap views;
|
|
@@ -227,19 +267,20 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
227
267
|
}
|
|
228
268
|
}
|
|
229
269
|
|
|
230
|
-
// Run the engine in place over the prepared scratch (allocation-free).
|
|
231
|
-
|
|
270
|
+
// Run the engine in place over the prepared scratch (allocation-free). The
|
|
271
|
+
// monitor variant keeps the cue bus out of the program planes so it can go
|
|
272
|
+
// to its own output; the plain call folds it in, as it always has.
|
|
273
|
+
if (this.cueOutput) {
|
|
274
|
+
this.engine.processPreparedWithMonitor(usableFrames);
|
|
275
|
+
} else {
|
|
276
|
+
this.engine.processPrepared(usableFrames);
|
|
277
|
+
}
|
|
232
278
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
if (target.length > usableFrames) {
|
|
239
|
-
target.fill(0, usableFrames);
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
target.fill(0);
|
|
279
|
+
copyPlanesToOutput(output, this.channelBuffers, usableFrames);
|
|
280
|
+
if (this.cueOutput) {
|
|
281
|
+
const cue = outputs[1];
|
|
282
|
+
if (cue) {
|
|
283
|
+
copyPlanesToOutput(cue, this.monitorBuffers, usableFrames);
|
|
243
284
|
}
|
|
244
285
|
}
|
|
245
286
|
this.publishClipPageRequests();
|
|
@@ -256,6 +297,12 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
256
297
|
}
|
|
257
298
|
}
|
|
258
299
|
|
|
300
|
+
private reacquireMonitorBuffers(): void {
|
|
301
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
302
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
259
306
|
receiveCommand(command: SonareEngineCommandRecord): void {
|
|
260
307
|
if (!this.closed) {
|
|
261
308
|
this.safeApplyCommand(command);
|
package/src/worklet/messages.ts
CHANGED
|
@@ -67,6 +67,13 @@ export interface SonareRealtimeEngineWorkletProcessorOptions {
|
|
|
67
67
|
/** Lock-free worklet-to-main-thread MIDI-1 output ring. */
|
|
68
68
|
externalMidiSharedBuffer?: SharedArrayBuffer;
|
|
69
69
|
externalMidiRingCapacity?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Route the PFL/AFL cue bus to the processor's SECOND output instead of
|
|
72
|
+
* folding it into the program output. Off by default, so an existing
|
|
73
|
+
* single-output host keeps its current mix sample for sample. The node must
|
|
74
|
+
* be constructed with two outputs for the cue to be audible.
|
|
75
|
+
*/
|
|
76
|
+
cueOutput?: boolean;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
export interface SonareRealtimeVoiceChangerWorkletProcessorOptions {
|
|
@@ -105,6 +112,11 @@ export interface SonareRealtimeEngineNodeCapabilities {
|
|
|
105
112
|
clipPageRequestsRealtimeSafe: boolean;
|
|
106
113
|
/** True when external MIDI uses the SAB output ring rather than postMessage. */
|
|
107
114
|
externalMidiRealtimeSafe: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* True when the node carries a second output fed by the PFL/AFL cue bus. When
|
|
117
|
+
* false the cue is folded into the program output, as it always was.
|
|
118
|
+
*/
|
|
119
|
+
cueOutput: boolean;
|
|
108
120
|
engineAbiVersion?: number;
|
|
109
121
|
expectedEngineAbiVersion?: number;
|
|
110
122
|
abiCompatible?: boolean;
|
package/src/worklet.ts
CHANGED
|
@@ -10,6 +10,11 @@ export { attachOpfsClipStream } from './clip_page_streamer';
|
|
|
10
10
|
// module singleton. Re-export the lifecycle so that realm can initialize its
|
|
11
11
|
// own wasm instance, independent of the main-thread `index` module.
|
|
12
12
|
export { init, isInitialized } from './index';
|
|
13
|
+
// Host-side mastering preview inside the worklet realm. Kept here rather than
|
|
14
|
+
// left main-thread-only so a live preview does not have to round-trip audio to
|
|
15
|
+
// the main thread; see the class doc for the prepare/loudness/latency contract.
|
|
16
|
+
export type { StreamingMasteringChainConfig } from './public_types';
|
|
17
|
+
export { StreamingMasteringChain } from './streaming_processors';
|
|
13
18
|
export { SonareEngine } from './worklet/engine';
|
|
14
19
|
export { SonareRealtimeEngineNode } from './worklet/engine-node';
|
|
15
20
|
export type { SonareEngineOptions } from './worklet/engine-options';
|