@libraz/libsonare 1.7.1 → 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 +3 -0
- package/dist/index.d.ts +286 -184
- package/dist/index.js +65 -4
- package/dist/index.js.map +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 +374 -1
- package/dist/worklet.js +174 -13
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -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 +34 -0
- package/src/realtime_engine.ts +39 -0
- package/src/sonare.js.d.ts +3 -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
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/project.ts
CHANGED
|
@@ -40,6 +40,9 @@ export type {
|
|
|
40
40
|
ProjectMidiCcBindingKind,
|
|
41
41
|
ProjectMidiClipResult,
|
|
42
42
|
ProjectMidiEvent,
|
|
43
|
+
ProjectMidiFxBakeRequest,
|
|
44
|
+
ProjectMidiFxBakeResult,
|
|
45
|
+
ProjectMidiFxPreviewRequest,
|
|
43
46
|
ProjectMidiRouteConfig,
|
|
44
47
|
ProjectMidiRouteResult,
|
|
45
48
|
ProjectNotePairValidation,
|
package/src/project_class.ts
CHANGED
|
@@ -35,6 +35,9 @@ import type {
|
|
|
35
35
|
ProjectMidiCcBinding,
|
|
36
36
|
ProjectMidiClipResult,
|
|
37
37
|
ProjectMidiEvent,
|
|
38
|
+
ProjectMidiFxBakeRequest,
|
|
39
|
+
ProjectMidiFxBakeResult,
|
|
40
|
+
ProjectMidiFxPreviewRequest,
|
|
38
41
|
ProjectMidiRouteConfig,
|
|
39
42
|
ProjectMidiRouteResult,
|
|
40
43
|
ProjectNotePairValidation,
|
|
@@ -52,6 +55,28 @@ import type {
|
|
|
52
55
|
SynthPatch,
|
|
53
56
|
} from './project_types';
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Folds the positional and request call forms of `bakeMidiFx` into one shape,
|
|
60
|
+
* so defaults, validation and errors cannot diverge between them.
|
|
61
|
+
*/
|
|
62
|
+
function normalizeMidiFxBakeRequest(
|
|
63
|
+
clipIdOrRequest: number | ProjectMidiFxBakeRequest,
|
|
64
|
+
configJson?: string,
|
|
65
|
+
): Required<ProjectMidiFxBakeRequest> {
|
|
66
|
+
if (typeof clipIdOrRequest === 'number') {
|
|
67
|
+
return {
|
|
68
|
+
clipId: clipIdOrRequest,
|
|
69
|
+
configJson: configJson ?? '',
|
|
70
|
+
withSourceIndex: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
clipId: clipIdOrRequest.clipId,
|
|
75
|
+
configJson: clipIdOrRequest.configJson,
|
|
76
|
+
withSourceIndex: clipIdOrRequest.withSourceIndex ?? false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
function validateAssistSidecarUint32(value: unknown, field: string): number {
|
|
56
81
|
if (
|
|
57
82
|
typeof value !== 'number' ||
|
|
@@ -584,8 +609,33 @@ export class Project {
|
|
|
584
609
|
* Destructively bake a MIDI-FX chain into all stored events. Large clips are
|
|
585
610
|
* drained without truncation; failure leaves the original clip unchanged.
|
|
586
611
|
*/
|
|
587
|
-
bakeMidiFx(clipId: number, configJson: string): void
|
|
588
|
-
|
|
612
|
+
bakeMidiFx(clipId: number, configJson: string): void;
|
|
613
|
+
/**
|
|
614
|
+
* Request form. Setting `withSourceIndex` also returns per-event provenance,
|
|
615
|
+
* so a selection or an editorial annotation can be carried across the bake.
|
|
616
|
+
*/
|
|
617
|
+
bakeMidiFx(request: ProjectMidiFxBakeRequest): ProjectMidiFxBakeResult;
|
|
618
|
+
bakeMidiFx(
|
|
619
|
+
clipIdOrRequest: number | ProjectMidiFxBakeRequest,
|
|
620
|
+
configJson?: string,
|
|
621
|
+
): ProjectMidiFxBakeResult {
|
|
622
|
+
const request = normalizeMidiFxBakeRequest(clipIdOrRequest, configJson);
|
|
623
|
+
if (!request.withSourceIndex) {
|
|
624
|
+
this.native.bakeMidiFx(request.clipId, request.configJson);
|
|
625
|
+
return {};
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
sourceIndex: this.native.bakeMidiFxWithSourceIndex(request.clipId, request.configJson),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Count the events {@link bakeMidiFx} would produce for this clip and
|
|
634
|
+
* configuration, without mutating the project. The transform is
|
|
635
|
+
* deterministic, so the count matches what the bake goes on to produce.
|
|
636
|
+
*/
|
|
637
|
+
previewMidiFxCount(request: ProjectMidiFxPreviewRequest): number {
|
|
638
|
+
return this.native.previewMidiFxCount(request.clipId, request.configJson);
|
|
589
639
|
}
|
|
590
640
|
|
|
591
641
|
/** Backward alias for {@link bakeMidiFx}. */
|
package/src/project_internal.ts
CHANGED
|
@@ -85,6 +85,8 @@ export interface WasmProject {
|
|
|
85
85
|
bank: number,
|
|
86
86
|
) => void;
|
|
87
87
|
bakeMidiFx: (clipId: number, configJson: string) => void;
|
|
88
|
+
bakeMidiFxWithSourceIndex: (clipId: number, configJson: string) => Int32Array;
|
|
89
|
+
previewMidiFxCount: (clipId: number, configJson: string) => number;
|
|
88
90
|
setMidiFx: (clipId: number, configJson: string) => void;
|
|
89
91
|
validateMidiNotes: (clipId: number) => ProjectNotePairValidation;
|
|
90
92
|
analyzeTempo: (audio: Float32Array, sampleRate: number) => ProjectTempoCandidate[];
|
package/src/project_types.ts
CHANGED
|
@@ -665,6 +665,40 @@ export interface ProjectNotePairValidation {
|
|
|
665
665
|
unmatchedNoteOffs: number;
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Request form of {@link Project.bakeMidiFx}. The positional
|
|
670
|
+
* `(clipId, configJson)` call stays supported and normalizes to this shape.
|
|
671
|
+
*/
|
|
672
|
+
export interface ProjectMidiFxBakeRequest {
|
|
673
|
+
/** Target MIDI clip id. */
|
|
674
|
+
clipId: number;
|
|
675
|
+
/** MIDI-FX chain configuration as JSON. */
|
|
676
|
+
configJson: string;
|
|
677
|
+
/** Return per-event provenance in the result. Default false. */
|
|
678
|
+
withSourceIndex?: boolean;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Result of the request form of {@link Project.bakeMidiFx}. */
|
|
682
|
+
export interface ProjectMidiFxBakeResult {
|
|
683
|
+
/**
|
|
684
|
+
* One entry per transformed event in canonical order: the index of the input
|
|
685
|
+
* event it derives from, or -1 for an event with no originating input. Chord
|
|
686
|
+
* and arpeggiator fan-out makes several outputs share one source index, so a
|
|
687
|
+
* caller that treats the first output per index as the same event and the
|
|
688
|
+
* rest as newly generated can carry a selection across the bake. Present only
|
|
689
|
+
* when the request set `withSourceIndex`.
|
|
690
|
+
*/
|
|
691
|
+
sourceIndex?: Int32Array;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Request form of {@link Project.previewMidiFxCount}. */
|
|
695
|
+
export interface ProjectMidiFxPreviewRequest {
|
|
696
|
+
/** Target MIDI clip id. */
|
|
697
|
+
clipId: number;
|
|
698
|
+
/** MIDI-FX chain configuration as JSON. */
|
|
699
|
+
configJson: string;
|
|
700
|
+
}
|
|
701
|
+
|
|
668
702
|
/** One compile diagnostic (mirrors SonareProjectDiagnostic). */
|
|
669
703
|
export interface ProjectDiagnostic {
|
|
670
704
|
code: number;
|
package/src/realtime_engine.ts
CHANGED
|
@@ -823,6 +823,15 @@ export class RealtimeEngine {
|
|
|
823
823
|
* Returns `-1` when the track, insert, or name is unknown. (The Python binding
|
|
824
824
|
* raises a `SonareError` for an unknown id where Node/WASM return the `-1`
|
|
825
825
|
* sentinel.)
|
|
826
|
+
*
|
|
827
|
+
* This trio is how a mastering processor gets time-varying automation: the
|
|
828
|
+
* `eq.*`, `dynamics.*`, `saturation.*`, `spectral.*`, `stereo.*`,
|
|
829
|
+
* `maximizer.*` and `multiband.*` processors are all available as strip
|
|
830
|
+
* inserts, so placing one on a strip and resolving its parameter here drives
|
|
831
|
+
* it at audio-block precision, live and offline alike. The whole-signal
|
|
832
|
+
* stages of the offline mastering chain (`repair.*`, `loudness`, and the
|
|
833
|
+
* match stages) have no insert form and no automation id: they buffer the
|
|
834
|
+
* entire signal by construction and do not run on the realtime path.
|
|
826
835
|
*/
|
|
827
836
|
resolveTrackInsertAutomationId(trackId: number, insertIndex: number, paramName: string): number {
|
|
828
837
|
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
@@ -984,6 +993,36 @@ export class RealtimeEngine {
|
|
|
984
993
|
this.native.processPrepared(numFrames);
|
|
985
994
|
}
|
|
986
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Allocates the cue-bus counterpart of {@link prepareChannels}. Needed only
|
|
998
|
+
* when PFL/AFL monitoring must reach a separate output: `processPrepared`
|
|
999
|
+
* folds the cue bus into the program output, while
|
|
1000
|
+
* {@link processPreparedWithMonitor} keeps the two apart. Call once, off the
|
|
1001
|
+
* audio thread, with at least as many channels as `prepareChannels` got.
|
|
1002
|
+
*/
|
|
1003
|
+
prepareMonitorChannels(numChannels: number, maxFrames: number): void {
|
|
1004
|
+
this.native.prepareMonitorChannels(numChannels, maxFrames);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Returns a Float32Array view onto the persistent cue-bus scratch for one
|
|
1009
|
+
* channel (valid for up to `numFrames`). Read it after
|
|
1010
|
+
* {@link processPreparedWithMonitor}. Re-acquire after WASM memory growth.
|
|
1011
|
+
*/
|
|
1012
|
+
getMonitorChannelBuffer(channel: number, numFrames: number): Float32Array {
|
|
1013
|
+
return this.native.getMonitorChannelBuffer(channel, numFrames);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Runs the engine in place over the prepared scratch, writing the cue bus to
|
|
1018
|
+
* the monitor scratch instead of folding it into the program output.
|
|
1019
|
+
* Allocation-free: safe on the AudioWorklet render thread after
|
|
1020
|
+
* `prepareChannels` and `prepareMonitorChannels`.
|
|
1021
|
+
*/
|
|
1022
|
+
processPreparedWithMonitor(numFrames: number): void {
|
|
1023
|
+
this.native.processPreparedWithMonitor(numFrames);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
987
1026
|
processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
|
|
988
1027
|
return this.native.processWithMonitor(channels);
|
|
989
1028
|
}
|
package/src/sonare.js.d.ts
CHANGED
|
@@ -1114,6 +1114,9 @@ export interface WasmRealtimeEngine {
|
|
|
1114
1114
|
prepareChannels: (numChannels: number, maxFrames: number) => void;
|
|
1115
1115
|
getChannelBuffer: (channel: number, numFrames: number) => Float32Array;
|
|
1116
1116
|
processPrepared: (numFrames: number) => void;
|
|
1117
|
+
prepareMonitorChannels: (numChannels: number, maxFrames: number) => void;
|
|
1118
|
+
getMonitorChannelBuffer: (channel: number, numFrames: number) => Float32Array;
|
|
1119
|
+
processPreparedWithMonitor: (numFrames: number) => void;
|
|
1117
1120
|
processWithMonitor: (channels: Float32Array[]) => WasmEngineProcessWithMonitorResult;
|
|
1118
1121
|
renderOffline: (channels: Float32Array[], blockSize: number) => Float32Array[];
|
|
1119
1122
|
bounceOffline: (options: WasmEngineBounceOptions) => WasmEngineBounceResult;
|
|
@@ -49,6 +49,24 @@ const EQ_PHASE_MODES: Record<string, number> = {
|
|
|
49
49
|
* Call {@link delete} (or use a `try/finally`) to release the underlying WASM
|
|
50
50
|
* object — the embind handle is not garbage-collected automatically.
|
|
51
51
|
*
|
|
52
|
+
* Reachable from the AudioWorklet realm through the `sonare/worklet` entry, but
|
|
53
|
+
* the realtime contract is the caller's to keep:
|
|
54
|
+
*
|
|
55
|
+
* - {@link prepare} builds the processors and allocates. Call it once from a
|
|
56
|
+
* message handler, never from `AudioWorkletProcessor.process()`.
|
|
57
|
+
* - {@link processMono}/{@link processStereo} return fresh arrays. On the render
|
|
58
|
+
* thread, reuse the returned reference for the block rather than retaining it.
|
|
59
|
+
* - An enabled `loudness` stage needs `loudnessStaticGainDb` measured offline,
|
|
60
|
+
* because whole-signal integrated LUFS cannot be measured block by block. Pass
|
|
61
|
+
* `loudnessStaticGainPeakDb` too and the static gain is clamped exactly as the
|
|
62
|
+
* offline chain clamps it, so the live preview matches the render.
|
|
63
|
+
* - {@link flush} output starts {@link latencySamples} samples early; discard
|
|
64
|
+
* that many leading samples when time alignment matters.
|
|
65
|
+
*
|
|
66
|
+
* The chain is a host-side stage, not an engine insert: it does not participate
|
|
67
|
+
* in the engine's PDC or bypass, so latency compensation against other engine
|
|
68
|
+
* outputs is also the caller's.
|
|
69
|
+
*
|
|
52
70
|
* @example
|
|
53
71
|
* ```typescript
|
|
54
72
|
* const chain = new StreamingMasteringChain({ eq: { tiltDb: 1.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';
|