@libraz/libsonare 1.8.0 → 1.8.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 +5 -5
- package/dist/_effects_common.d.ts +2 -1
- package/dist/_effects_common.d.ts.map +1 -1
- package/dist/analysis.js +3 -0
- package/dist/analysis.js.map +1 -1
- package/dist/effects_mastering.d.ts +2 -2
- package/dist/effects_mastering.d.ts.map +1 -1
- package/dist/effects_note_ops.d.ts +55 -30
- package/dist/effects_note_ops.d.ts.map +1 -1
- package/dist/effects_timepitch.d.ts +17 -12
- package/dist/effects_timepitch.d.ts.map +1 -1
- package/dist/feature_decompose.d.ts +1 -1
- package/dist/feature_decompose.d.ts.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2098 -155
- package/dist/index.js.map +1 -1
- package/dist/instrument_types.d.ts +27 -3
- package/dist/instrument_types.d.ts.map +1 -1
- package/dist/mastering_core.d.ts +30 -3
- package/dist/mastering_core.d.ts.map +1 -1
- package/dist/mixer.d.ts +2 -2
- package/dist/mixer.d.ts.map +1 -1
- package/dist/project.d.ts +5 -4
- package/dist/project.d.ts.map +1 -1
- package/dist/project_class.d.ts +57 -3
- package/dist/project_class.d.ts.map +1 -1
- package/dist/project_internal.d.ts +35 -1
- package/dist/project_internal.d.ts.map +1 -1
- package/dist/project_types.d.ts +17 -6
- package/dist/project_types.d.ts.map +1 -1
- package/dist/public_types.d.ts +1 -0
- package/dist/public_types.d.ts.map +1 -1
- package/dist/public_types_mastering.d.ts +19 -1
- package/dist/public_types_mastering.d.ts.map +1 -1
- package/dist/public_types_playback.d.ts +1 -0
- package/dist/public_types_playback.d.ts.map +1 -1
- package/dist/public_types_spectral.d.ts +5 -5
- package/dist/public_types_spectral.d.ts.map +1 -1
- package/dist/public_types_vocal_edit.d.ts +250 -0
- package/dist/public_types_vocal_edit.d.ts.map +1 -0
- package/dist/public_types_vocal_project.d.ts +63 -0
- package/dist/public_types_vocal_project.d.ts.map +1 -0
- package/dist/realtime_engine.d.ts +106 -16
- package/dist/realtime_engine.d.ts.map +1 -1
- package/dist/repair_dereverb.d.ts +1 -0
- package/dist/repair_dereverb.d.ts.map +1 -1
- package/dist/repair_noise.d.ts +1 -0
- package/dist/repair_noise.d.ts.map +1 -1
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.d.ts +89 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/streaming_processors.d.ts +5 -0
- package/dist/streaming_processors.d.ts.map +1 -1
- package/dist/vocal_edit.d.ts +131 -0
- package/dist/vocal_edit.d.ts.map +1 -0
- package/dist/vocal_edit_worker.d.ts +51 -0
- package/dist/vocal_edit_worker.d.ts.map +1 -0
- package/dist/vocal_edit_worker.js +1462 -0
- package/dist/vocal_edit_worker.js.map +1 -0
- package/dist/vocal_edit_worker_client.d.ts +118 -0
- package/dist/vocal_edit_worker_client.d.ts.map +1 -0
- package/dist/vocal_edit_worker_protocol.d.ts +139 -0
- package/dist/vocal_edit_worker_protocol.d.ts.map +1 -0
- package/dist/vocal_project.d.ts +17 -0
- package/dist/vocal_project.d.ts.map +1 -0
- package/dist/web_midi.d.ts +4 -0
- package/dist/web_midi.d.ts.map +1 -1
- package/dist/worker.js.map +1 -1
- package/dist/worklet/engine-mixer-facade.d.ts +42 -5
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -1
- package/dist/worklet/engine-node.d.ts +10 -0
- package/dist/worklet/engine-node.d.ts.map +1 -1
- package/dist/worklet/engine-processor.d.ts +6 -1
- package/dist/worklet/engine-processor.d.ts.map +1 -1
- package/dist/worklet/engine-register.d.ts.map +1 -1
- package/dist/worklet/engine-strips.d.ts +8 -4
- package/dist/worklet/engine-strips.d.ts.map +1 -1
- package/dist/worklet/engine-sync.d.ts +4 -2
- package/dist/worklet/engine-sync.d.ts.map +1 -1
- package/dist/worklet/engine.d.ts +42 -9
- package/dist/worklet/engine.d.ts.map +1 -1
- package/dist/worklet/guards.d.ts +10 -2
- package/dist/worklet/guards.d.ts.map +1 -1
- package/dist/worklet/messages.d.ts +54 -9
- package/dist/worklet/messages.d.ts.map +1 -1
- package/dist/worklet/mixer-processor.d.ts +2 -2
- package/dist/worklet/mixer-processor.d.ts.map +1 -1
- package/dist/worklet/protocol.d.ts +20 -2
- package/dist/worklet/protocol.d.ts.map +1 -1
- package/dist/worklet.d.ts +1 -1
- package/dist/worklet.d.ts.map +1 -1
- package/dist/worklet.js +808 -204
- package/dist/worklet.js.map +1 -1
- package/package.json +6 -1
- package/src/_effects_common.ts +36 -6
- package/src/effects_mastering.ts +4 -0
- package/src/effects_note_ops.ts +92 -44
- package/src/effects_timepitch.ts +42 -31
- package/src/feature_decompose.ts +2 -2
- package/src/index.ts +39 -0
- package/src/instrument_types.ts +29 -3
- package/src/mastering_core.ts +98 -2
- package/src/mixer.ts +41 -21
- package/src/project.ts +8 -1
- package/src/project_class.ts +136 -1
- package/src/project_internal.ts +90 -0
- package/src/project_types.ts +18 -6
- package/src/public_types.ts +1 -0
- package/src/public_types_mastering.ts +23 -0
- package/src/public_types_playback.ts +1 -0
- package/src/public_types_spectral.ts +5 -5
- package/src/public_types_vocal_edit.ts +289 -0
- package/src/public_types_vocal_project.ts +69 -0
- package/src/realtime_engine.ts +157 -24
- package/src/repair_dereverb.ts +1 -0
- package/src/repair_noise.ts +1 -0
- package/src/sonare.js.d.ts +89 -0
- package/src/streaming_processors.ts +8 -0
- package/src/vocal_edit.ts +885 -0
- package/src/vocal_edit_worker.ts +832 -0
- package/src/vocal_edit_worker_client.ts +812 -0
- package/src/vocal_edit_worker_protocol.ts +206 -0
- package/src/vocal_project.ts +327 -0
- package/src/web_midi.ts +100 -5
- package/src/worklet/engine-mixer-facade.ts +422 -87
- package/src/worklet/engine-node.ts +69 -4
- package/src/worklet/engine-processor.ts +129 -34
- package/src/worklet/engine-register.ts +3 -0
- package/src/worklet/engine-strips.ts +36 -14
- package/src/worklet/engine-sync.ts +6 -1
- package/src/worklet/engine.ts +76 -40
- package/src/worklet/guards.ts +81 -0
- package/src/worklet/messages.ts +63 -4
- package/src/worklet/mixer-processor.ts +10 -3
- package/src/worklet/protocol.ts +39 -8
- package/src/worklet.ts +2 -0
|
@@ -5,12 +5,15 @@ import {
|
|
|
5
5
|
isClipPageRequestMessage,
|
|
6
6
|
isEngineCaptureResponseForOperation,
|
|
7
7
|
isEngineCaptureResponseMessage,
|
|
8
|
+
isEngineInsertGainReductionResponseMessage,
|
|
8
9
|
isEngineTelemetryRecord,
|
|
9
10
|
isEngineTransportResponseMessage,
|
|
10
11
|
isExternalMidiBatchMessage,
|
|
11
12
|
isMeterSnapshot,
|
|
13
|
+
isScopeSnapshot,
|
|
12
14
|
requireChannelCount,
|
|
13
15
|
requireIntegerOption,
|
|
16
|
+
resolveScopeBandCount,
|
|
14
17
|
} from './guards';
|
|
15
18
|
import type {
|
|
16
19
|
SonareEngineCaptureRequestMessage,
|
|
@@ -72,7 +75,8 @@ function isValidCommandRecord(command: SonareEngineCommandRecord): boolean {
|
|
|
72
75
|
!Number.isSafeInteger(type) ||
|
|
73
76
|
type < SonareEngineCommandType.SetParam ||
|
|
74
77
|
(type > SonareEngineCommandType.SeekMarker &&
|
|
75
|
-
type !== SonareEngineCommandType.SetTrackMonitorMode
|
|
78
|
+
type !== SonareEngineCommandType.SetTrackMonitorMode &&
|
|
79
|
+
type !== SonareEngineCommandType.ResetMasterLoudnessMeter)
|
|
76
80
|
) {
|
|
77
81
|
return false;
|
|
78
82
|
}
|
|
@@ -141,6 +145,14 @@ export class SonareRealtimeEngineNode {
|
|
|
141
145
|
reject: (reason?: unknown) => void;
|
|
142
146
|
}
|
|
143
147
|
>();
|
|
148
|
+
private insertGainReductionRequestId = 1;
|
|
149
|
+
private readonly insertGainReductionRequests = new Map<
|
|
150
|
+
number,
|
|
151
|
+
{
|
|
152
|
+
resolve: (values: number[]) => void;
|
|
153
|
+
reject: (reason?: unknown) => void;
|
|
154
|
+
}
|
|
155
|
+
>();
|
|
144
156
|
private resolveReady!: () => void;
|
|
145
157
|
private rejectReady!: (reason?: unknown) => void;
|
|
146
158
|
private destroyed = false;
|
|
@@ -202,10 +214,22 @@ export class SonareRealtimeEngineNode {
|
|
|
202
214
|
pending.reject(new Error(event.data.error ?? 'Transport request failed'));
|
|
203
215
|
}
|
|
204
216
|
}
|
|
217
|
+
} else if (isEngineInsertGainReductionResponseMessage(event.data)) {
|
|
218
|
+
const pending = this.insertGainReductionRequests.get(event.data.requestId);
|
|
219
|
+
if (pending) {
|
|
220
|
+
this.insertGainReductionRequests.delete(event.data.requestId);
|
|
221
|
+
if (event.data.ok && Array.isArray(event.data.values)) {
|
|
222
|
+
pending.resolve(event.data.values);
|
|
223
|
+
} else {
|
|
224
|
+
pending.reject(new Error(event.data.error ?? 'Insert gain reduction request failed'));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
205
227
|
} else if (isEngineTelemetryRecord(event.data)) {
|
|
206
228
|
this.emitTelemetry(event.data);
|
|
207
229
|
} else if (isMeterSnapshot(event.data)) {
|
|
208
230
|
this.emitMeter(event.data);
|
|
231
|
+
} else if (isScopeSnapshot(event.data)) {
|
|
232
|
+
this.emitScope(event.data);
|
|
209
233
|
} else if (isExternalMidiBatchMessage(event.data)) {
|
|
210
234
|
this.emitMidiOut(event.data.events);
|
|
211
235
|
} else if (isClipPageRequestMessage(event.data)) {
|
|
@@ -273,6 +297,13 @@ export class SonareRealtimeEngineNode {
|
|
|
273
297
|
);
|
|
274
298
|
}
|
|
275
299
|
|
|
300
|
+
const meterIntervalFrames = requireIntegerOption(
|
|
301
|
+
options.meterIntervalFrames,
|
|
302
|
+
2048,
|
|
303
|
+
'meterIntervalFrames',
|
|
304
|
+
0,
|
|
305
|
+
);
|
|
306
|
+
|
|
276
307
|
// Resolved before the transport branch, so a capacity the ring could not
|
|
277
308
|
// hold is refused by name whichever transport the caller ends up with.
|
|
278
309
|
const commandRingCapacity = requireIntegerOption(
|
|
@@ -302,7 +333,7 @@ export class SonareRealtimeEngineNode {
|
|
|
302
333
|
'scopeRingCapacity',
|
|
303
334
|
1,
|
|
304
335
|
);
|
|
305
|
-
const scopeBands =
|
|
336
|
+
const scopeBands = resolveScopeBandCount(options.scopeBands);
|
|
306
337
|
const clipPageRequestRingCapacity = requireIntegerOption(
|
|
307
338
|
options.clipPageRequestRingCapacity,
|
|
308
339
|
128,
|
|
@@ -352,6 +383,7 @@ export class SonareRealtimeEngineNode {
|
|
|
352
383
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
353
384
|
blockSize,
|
|
354
385
|
channelCount,
|
|
386
|
+
meterIntervalFrames,
|
|
355
387
|
cueOutput,
|
|
356
388
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
357
389
|
commandRingCapacity: commandRing?.capacity,
|
|
@@ -361,8 +393,8 @@ export class SonareRealtimeEngineNode {
|
|
|
361
393
|
meterRingCapacity: meterRing?.capacity,
|
|
362
394
|
scopeSharedBuffer: scopeRing?.sharedBuffer,
|
|
363
395
|
scopeRingCapacity: scopeRing?.capacity,
|
|
364
|
-
scopeBands: scopeRing?.bands,
|
|
365
|
-
scopeIntervalFrames:
|
|
396
|
+
scopeBands: scopeIntervalFrames > 0 ? (scopeRing?.bands ?? scopeBands) : undefined,
|
|
397
|
+
scopeIntervalFrames: scopeIntervalFrames > 0 ? scopeIntervalFrames : undefined,
|
|
366
398
|
clipPageRequestSharedBuffer: clipPageRequestRing?.sharedBuffer,
|
|
367
399
|
clipPageRequestRingCapacity: clipPageRequestRing?.capacity,
|
|
368
400
|
externalMidiSharedBuffer: externalMidiRing?.sharedBuffer,
|
|
@@ -416,6 +448,11 @@ export class SonareRealtimeEngineNode {
|
|
|
416
448
|
return this.sendCommand({ type: SonareEngineCommandType.TransportPlay, sampleTime });
|
|
417
449
|
}
|
|
418
450
|
|
|
451
|
+
/** Queues a reset of the master's integrated-loudness accumulator. */
|
|
452
|
+
resetMasterLoudnessMeter(sampleTime = -1): boolean {
|
|
453
|
+
return this.sendCommand({ type: SonareEngineCommandType.ResetMasterLoudnessMeter, sampleTime });
|
|
454
|
+
}
|
|
455
|
+
|
|
419
456
|
stop(sampleTime = -1): boolean {
|
|
420
457
|
return this.sendCommand({ type: SonareEngineCommandType.TransportStop, sampleTime });
|
|
421
458
|
}
|
|
@@ -641,6 +678,10 @@ export class SonareRealtimeEngineNode {
|
|
|
641
678
|
pending.reject(new Error('Realtime engine node is destroyed.'));
|
|
642
679
|
}
|
|
643
680
|
this.transportRequests.clear();
|
|
681
|
+
for (const pending of this.insertGainReductionRequests.values()) {
|
|
682
|
+
pending.reject(new Error('Realtime engine node is destroyed.'));
|
|
683
|
+
}
|
|
684
|
+
this.insertGainReductionRequests.clear();
|
|
644
685
|
this.telemetryListeners.clear();
|
|
645
686
|
this.meterListeners.clear();
|
|
646
687
|
this.scopeListeners.clear();
|
|
@@ -710,6 +751,30 @@ export class SonareRealtimeEngineNode {
|
|
|
710
751
|
}
|
|
711
752
|
}
|
|
712
753
|
|
|
754
|
+
/**
|
|
755
|
+
* Reads one target's per-insert gain reduction (dB, each <= 0, pre-to-post insert order)
|
|
756
|
+
* from the worklet. Works in both ring and postMessage meter modes; rejects when the
|
|
757
|
+
* target is invalid or mixing is not compiled in.
|
|
758
|
+
*/
|
|
759
|
+
pollInsertGainReduction(targetId: number): Promise<number[]> {
|
|
760
|
+
if (this.destroyed) {
|
|
761
|
+
return Promise.reject(new Error('Realtime engine node is destroyed.'));
|
|
762
|
+
}
|
|
763
|
+
const requestId = this.insertGainReductionRequestId++;
|
|
764
|
+
let rejectRequest!: (reason?: unknown) => void;
|
|
765
|
+
const promise = new Promise<number[]>((resolve, reject) => {
|
|
766
|
+
rejectRequest = reject;
|
|
767
|
+
this.insertGainReductionRequests.set(requestId, { resolve, reject });
|
|
768
|
+
});
|
|
769
|
+
try {
|
|
770
|
+
this.node.port.postMessage({ type: 'insertGainReductionRequest', requestId, targetId });
|
|
771
|
+
} catch (error) {
|
|
772
|
+
this.insertGainReductionRequests.delete(requestId);
|
|
773
|
+
rejectRequest(error);
|
|
774
|
+
}
|
|
775
|
+
return promise;
|
|
776
|
+
}
|
|
777
|
+
|
|
713
778
|
private emitScope(scope: SonareWorkletScopeSnapshot): void {
|
|
714
779
|
for (const listener of this.scopeListeners) {
|
|
715
780
|
listener(scope);
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { EngineClip } from '../index';
|
|
2
2
|
import { RealtimeEngine } from '../index';
|
|
3
3
|
import { copyPlanesToOutput, type WorkletInput, type WorkletOutput } from './audio_types';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
requireChannelCount,
|
|
6
|
+
requireIntegerOption,
|
|
7
|
+
resolveScopeBandCount,
|
|
8
|
+
SONARE_SCOPE_MAX_BANDS,
|
|
9
|
+
SONARE_SCOPE_MAX_POINTS,
|
|
10
|
+
} from './guards';
|
|
6
11
|
import {
|
|
7
12
|
DEFAULT_METRONOME_CONFIG,
|
|
8
13
|
type ResolvedMetronomeConfig,
|
|
9
14
|
resolveMetronomeConfig,
|
|
10
15
|
type SonareEngineCaptureRequestMessage,
|
|
11
16
|
type SonareEngineCaptureResponseMessageInternal,
|
|
17
|
+
type SonareEngineInsertGainReductionRequestMessage,
|
|
18
|
+
type SonareEngineInsertGainReductionResponseMessage,
|
|
12
19
|
type SonareEngineSyncMessage,
|
|
13
20
|
type SonareEngineTransportRequestMessage,
|
|
14
21
|
type SonareEngineTransportResponseMessage,
|
|
@@ -41,6 +48,8 @@ import {
|
|
|
41
48
|
type SonareEngineTelemetryRecord,
|
|
42
49
|
type SonareEngineTelemetryRingBuffer,
|
|
43
50
|
SonareEngineTelemetryType,
|
|
51
|
+
type SonareWorkletMeterSnapshot,
|
|
52
|
+
type SonareWorkletScopeSnapshot,
|
|
44
53
|
scopeRingFromSharedBuffer,
|
|
45
54
|
telemetryFromEngine,
|
|
46
55
|
writeInt64Words,
|
|
@@ -84,6 +93,9 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
84
93
|
private transport?: WorkletTransport;
|
|
85
94
|
private meterIntervalFrames: number;
|
|
86
95
|
private lastMeterFrame = Number.NEGATIVE_INFINITY;
|
|
96
|
+
private scopeIntervalFrames: number;
|
|
97
|
+
private scopeEnabled: boolean;
|
|
98
|
+
private scopeBands: number;
|
|
87
99
|
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
88
100
|
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
89
101
|
private metronomeConfig: ResolvedMetronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
@@ -146,6 +158,16 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
146
158
|
options.scopeBands,
|
|
147
159
|
)
|
|
148
160
|
: undefined;
|
|
161
|
+
this.scopeIntervalFrames = requireIntegerOption(
|
|
162
|
+
options.scopeIntervalFrames,
|
|
163
|
+
this.scopeRing ? this.blockSize : 0,
|
|
164
|
+
'scopeIntervalFrames',
|
|
165
|
+
0,
|
|
166
|
+
);
|
|
167
|
+
this.scopeBands = resolveScopeBandCount(options.scopeBands);
|
|
168
|
+
this.scopeEnabled =
|
|
169
|
+
this.scopeIntervalFrames > 0 &&
|
|
170
|
+
(this.scopeRing !== undefined || typeof this.transport?.postMessage === 'function');
|
|
149
171
|
this.clipPageRequestRing = options.clipPageRequestSharedBuffer
|
|
150
172
|
? clipPageRequestRingFromSharedBuffer(
|
|
151
173
|
options.clipPageRequestSharedBuffer,
|
|
@@ -183,18 +205,12 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
183
205
|
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
184
206
|
}
|
|
185
207
|
}
|
|
186
|
-
// Arm the
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const interval = requireIntegerOption(
|
|
192
|
-
options.scopeIntervalFrames,
|
|
193
|
-
this.blockSize,
|
|
194
|
-
'scopeIntervalFrames',
|
|
195
|
-
0,
|
|
208
|
+
// Arm the native scope producer only when a SAB ring or postMessage sink exists.
|
|
209
|
+
if (this.scopeEnabled) {
|
|
210
|
+
this.engine.configureScopeTelemetry(
|
|
211
|
+
this.scopeIntervalFrames,
|
|
212
|
+
Math.min(this.scopeRing?.bands ?? this.scopeBands, SONARE_SCOPE_MAX_BANDS),
|
|
196
213
|
);
|
|
197
|
-
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
198
214
|
}
|
|
199
215
|
}
|
|
200
216
|
|
|
@@ -496,37 +512,32 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
496
512
|
// Complete earlier immediate edits in message order. Scheduled
|
|
497
513
|
// commands remain queued for their render frame.
|
|
498
514
|
this.engine.applyCommandsDueNowPreservingFuture();
|
|
499
|
-
this.engine.settleInsertParameters();
|
|
500
515
|
if (message.buses) {
|
|
501
516
|
this.engine.setTrackBuses(message.buses);
|
|
502
517
|
}
|
|
503
518
|
this.engine.setTrackLanes(message.lanes);
|
|
504
|
-
for (const
|
|
505
|
-
|
|
519
|
+
for (const strip of message.trackStrips ?? []) {
|
|
520
|
+
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
521
|
+
}
|
|
522
|
+
for (const strip of message.busStrips ?? []) {
|
|
523
|
+
this.engine.setBusStripJson(strip.busId, strip.sceneJson);
|
|
524
|
+
}
|
|
525
|
+
if (message.masterStripJson) {
|
|
526
|
+
this.engine.setMasterStripJson(message.masterStripJson);
|
|
527
|
+
}
|
|
528
|
+
for (const target of message.insertBaseResets ?? []) {
|
|
506
529
|
switch (target.kind) {
|
|
507
530
|
case 'track':
|
|
508
531
|
this.engine.clearTrackInsertParameterBases(target.trackId);
|
|
509
|
-
this.engine.setTrackStripJson(target.trackId, emptyJson);
|
|
510
532
|
break;
|
|
511
533
|
case 'bus':
|
|
512
534
|
this.engine.clearBusInsertParameterBases(target.busId);
|
|
513
|
-
this.engine.setBusStripJson(target.busId, emptyJson);
|
|
514
535
|
break;
|
|
515
536
|
case 'master':
|
|
516
537
|
this.engine.clearMasterInsertParameterBases();
|
|
517
|
-
this.engine.setMasterStripJson(emptyJson);
|
|
518
538
|
break;
|
|
519
539
|
}
|
|
520
540
|
}
|
|
521
|
-
for (const strip of message.trackStrips ?? []) {
|
|
522
|
-
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
523
|
-
}
|
|
524
|
-
for (const strip of message.busStrips ?? []) {
|
|
525
|
-
this.engine.setBusStripJson(strip.busId, strip.sceneJson);
|
|
526
|
-
}
|
|
527
|
-
if (message.masterStripJson) {
|
|
528
|
-
this.engine.setMasterStripJson(message.masterStripJson);
|
|
529
|
-
}
|
|
530
541
|
for (const binding of message.laneSidechains ?? []) {
|
|
531
542
|
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
532
543
|
}
|
|
@@ -674,6 +685,9 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
674
685
|
case 'syncTrackStripDualPan':
|
|
675
686
|
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
676
687
|
break;
|
|
688
|
+
case 'syncTrackStripSurroundPan':
|
|
689
|
+
this.engine.setTrackStripSurroundPan(message.trackId, message.pan);
|
|
690
|
+
break;
|
|
677
691
|
case 'syncBusStripPan':
|
|
678
692
|
this.engine.setBusStripPan(message.busId, message.pan);
|
|
679
693
|
break;
|
|
@@ -766,7 +780,11 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
766
780
|
);
|
|
767
781
|
break;
|
|
768
782
|
case 'syncMidiUmp':
|
|
769
|
-
this.engine.pushMidiUmp(
|
|
783
|
+
this.engine.pushMidiUmp(
|
|
784
|
+
message.destinationId,
|
|
785
|
+
message.words ?? [message.word0],
|
|
786
|
+
message.renderFrame,
|
|
787
|
+
);
|
|
770
788
|
break;
|
|
771
789
|
case 'syncMidiSysex':
|
|
772
790
|
this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
|
|
@@ -844,6 +862,9 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
844
862
|
message.portTimeSamples,
|
|
845
863
|
);
|
|
846
864
|
break;
|
|
865
|
+
case 'syncMidiInputUmp':
|
|
866
|
+
this.engine.pushMidiInputUmp(message.words, message.portTimeSamples);
|
|
867
|
+
break;
|
|
847
868
|
}
|
|
848
869
|
}
|
|
849
870
|
|
|
@@ -923,6 +944,27 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
923
944
|
}
|
|
924
945
|
}
|
|
925
946
|
|
|
947
|
+
receiveInsertGainReductionRequest(message: SonareEngineInsertGainReductionRequestMessage): void {
|
|
948
|
+
if (this.closed) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
this.transport?.postMessage?.({
|
|
953
|
+
type: 'insertGainReductionResponse',
|
|
954
|
+
requestId: message.requestId,
|
|
955
|
+
ok: true,
|
|
956
|
+
values: this.engine.meterTargetInsertGainReduction(message.targetId),
|
|
957
|
+
} satisfies SonareEngineInsertGainReductionResponseMessage);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
this.transport?.postMessage?.({
|
|
960
|
+
type: 'insertGainReductionResponse',
|
|
961
|
+
requestId: message.requestId,
|
|
962
|
+
ok: false,
|
|
963
|
+
error: error instanceof Error ? error.message : String(error),
|
|
964
|
+
} satisfies SonareEngineInsertGainReductionResponseMessage);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
926
968
|
destroy(): void {
|
|
927
969
|
if (!this.closed) {
|
|
928
970
|
this.clearPagedClipProviders();
|
|
@@ -1056,6 +1098,9 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1056
1098
|
this.engine.setTrackMonitorMode(laneIndex, mode as 0 | 1 | 2, sampleTime);
|
|
1057
1099
|
break;
|
|
1058
1100
|
}
|
|
1101
|
+
case SonareEngineCommandType.ResetMasterLoudnessMeter:
|
|
1102
|
+
this.engine.resetMasterLoudnessMeter(sampleTime);
|
|
1103
|
+
break;
|
|
1059
1104
|
default:
|
|
1060
1105
|
this.publishTelemetryRecord({
|
|
1061
1106
|
type: SonareEngineTelemetryType.Error,
|
|
@@ -1140,6 +1185,8 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1140
1185
|
}
|
|
1141
1186
|
return;
|
|
1142
1187
|
}
|
|
1188
|
+
const delivered: SonareWorkletMeterSnapshot[] = [];
|
|
1189
|
+
const newestByTarget = new Map<number, SonareWorkletMeterSnapshot>();
|
|
1143
1190
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
1144
1191
|
const meter = meterFromEngine(item);
|
|
1145
1192
|
if (
|
|
@@ -1151,6 +1198,15 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1151
1198
|
if (meter.frame !== this.lastMeterFrame) {
|
|
1152
1199
|
this.lastMeterFrame = meter.frame;
|
|
1153
1200
|
}
|
|
1201
|
+
delivered.push(meter);
|
|
1202
|
+
newestByTarget.set(meter.targetId, meter);
|
|
1203
|
+
}
|
|
1204
|
+
for (const meter of delivered) {
|
|
1205
|
+
// The board holds only the latest block, so only the newest record per
|
|
1206
|
+
// target in this drain can carry it; the ring layout is fixed and cannot.
|
|
1207
|
+
if (newestByTarget.get(meter.targetId) === meter) {
|
|
1208
|
+
meter.insertGainReductionDb = this.engine.meterTargetInsertGainReduction(meter.targetId);
|
|
1209
|
+
}
|
|
1154
1210
|
// The ring branch returned above, so this is the structured-clone
|
|
1155
1211
|
// fallback. `onMeter` and `postMessage` are alternative channels for the
|
|
1156
1212
|
// same record, not a broadcast pair: the AudioWorklet registration
|
|
@@ -1175,6 +1231,8 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1175
1231
|
for (let field = 0; field < 11; field++) {
|
|
1176
1232
|
ring.records[offset + 3 + field] = this.engine.meterScratchValue(field);
|
|
1177
1233
|
}
|
|
1234
|
+
ring.records[offset + 14] = this.engine.meterScratchInputPeakDbL();
|
|
1235
|
+
ring.records[offset + 15] = this.engine.meterScratchInputPeakDbR();
|
|
1178
1236
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1179
1237
|
}
|
|
1180
1238
|
|
|
@@ -1192,16 +1250,53 @@ export class SonareRealtimeEngineWorkletProcessor {
|
|
|
1192
1250
|
// so header slot 3 is left at its initial 0.
|
|
1193
1251
|
|
|
1194
1252
|
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
1195
|
-
// the lock-free SAB scope ring
|
|
1196
|
-
//
|
|
1253
|
+
// the lock-free SAB scope ring or the bounded postMessage fallback. The SAB
|
|
1254
|
+
// path remains allocation-free; postMessage materialises only records that
|
|
1255
|
+
// are ready to publish.
|
|
1197
1256
|
private publishScope(): void {
|
|
1198
|
-
|
|
1199
|
-
if (!ring) {
|
|
1257
|
+
if (!this.scopeEnabled) {
|
|
1200
1258
|
return;
|
|
1201
1259
|
}
|
|
1260
|
+
const ring = this.scopeRing;
|
|
1261
|
+
const transport = this.transport;
|
|
1202
1262
|
for (let count = 0; count < 64 && this.engine.popScopeTelemetryToScratch(); count++) {
|
|
1203
|
-
|
|
1263
|
+
if (ring) {
|
|
1264
|
+
this.writeScopeScratch(ring);
|
|
1265
|
+
} else {
|
|
1266
|
+
this.postScopeScratch(transport);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
private postScopeScratch(transport: WorkletTransport | undefined): void {
|
|
1272
|
+
if (!transport?.postMessage) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const bandCount = Math.min(
|
|
1276
|
+
SONARE_SCOPE_MAX_BANDS,
|
|
1277
|
+
Math.max(0, Math.trunc(Number(this.engine.scopeScratchBandCount()))),
|
|
1278
|
+
);
|
|
1279
|
+
const pointCount = Math.min(
|
|
1280
|
+
SONARE_SCOPE_MAX_POINTS,
|
|
1281
|
+
Math.max(0, Math.trunc(Number(this.engine.scopeScratchPointCount()))),
|
|
1282
|
+
);
|
|
1283
|
+
const bands = new Float32Array(bandCount);
|
|
1284
|
+
for (let i = 0; i < bandCount; i++) {
|
|
1285
|
+
bands[i] = this.engine.scopeScratchBand(i);
|
|
1286
|
+
}
|
|
1287
|
+
const points = new Float32Array(pointCount * 2);
|
|
1288
|
+
for (let i = 0; i < pointCount; i++) {
|
|
1289
|
+
points[2 * i] = this.engine.scopeScratchPointLeft(i);
|
|
1290
|
+
points[2 * i + 1] = this.engine.scopeScratchPointRight(i);
|
|
1204
1291
|
}
|
|
1292
|
+
const scope: SonareWorkletScopeSnapshot = {
|
|
1293
|
+
type: 'scope',
|
|
1294
|
+
targetId: this.engine.scopeScratchTargetId(),
|
|
1295
|
+
frame: Number(this.engine.scopeScratchRenderFrame()),
|
|
1296
|
+
bands,
|
|
1297
|
+
points,
|
|
1298
|
+
};
|
|
1299
|
+
transport.postMessage(scope, [bands.buffer, points.buffer]);
|
|
1205
1300
|
}
|
|
1206
1301
|
|
|
1207
1302
|
private writeScopeScratch(ring: SharedScopeRingWriter): void {
|
|
@@ -5,6 +5,7 @@ import { SonareRealtimeEngineWorkletProcessor } from './engine-processor';
|
|
|
5
5
|
import {
|
|
6
6
|
isEngineCaptureRequestMessage,
|
|
7
7
|
isEngineCommandRecord,
|
|
8
|
+
isEngineInsertGainReductionRequestMessage,
|
|
8
9
|
isEngineSyncMessage,
|
|
9
10
|
isEngineTransportRequestMessage,
|
|
10
11
|
} from './guards';
|
|
@@ -82,6 +83,8 @@ export function registerSonareRealtimeEngineWorkletProcessor(
|
|
|
82
83
|
bridge.receiveCaptureRequest(data);
|
|
83
84
|
} else if (isEngineTransportRequestMessage(data)) {
|
|
84
85
|
bridge.receiveTransportRequest(data);
|
|
86
|
+
} else if (isEngineInsertGainReductionRequestMessage(data)) {
|
|
87
|
+
bridge.receiveInsertGainReductionRequest(data);
|
|
85
88
|
} else if (isRecord(data) && typeof data.type === 'string' && data.type.startsWith('sync')) {
|
|
86
89
|
// A sync the guard does not accept would otherwise vanish, leaving the
|
|
87
90
|
// live engine out of step with the offline mirror and nothing to see.
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { panLawCode, panModeCode } from '../codes';
|
|
2
|
-
import type { EqBand, PanLawInput, PanMode, RealtimeEngine } from '../index';
|
|
2
|
+
import type { EqBand, PanLawInput, PanMode, RealtimeEngine, UmpWords } from '../index';
|
|
3
|
+
import type { SurroundPan } from '../public_types';
|
|
3
4
|
import type { InsertParamOverrideMap } from './engine-mixer-facade';
|
|
4
5
|
import {
|
|
5
|
-
applyFullStripJson,
|
|
6
|
-
clearInsertParamOverrides,
|
|
7
6
|
emptyStripJson,
|
|
8
|
-
hasInsertParamOverrides,
|
|
9
7
|
type InsertParamOverride,
|
|
10
8
|
insertParamOverrideKey,
|
|
9
|
+
replaceStripScene,
|
|
11
10
|
type StripJsonTarget,
|
|
12
11
|
} from './engine-mixer-facade';
|
|
13
12
|
import type { SonareEngineInstrumentSyncMessage, SonareEngineSyncMessage } from './messages';
|
|
@@ -27,6 +26,7 @@ export interface EngineStripContext {
|
|
|
27
26
|
readStripJson(target: StripJsonTarget): string | undefined;
|
|
28
27
|
writeStripJson(target: StripJsonTarget, sceneJson: string): void;
|
|
29
28
|
readonly insertParamOverrides: InsertParamOverrideMap;
|
|
29
|
+
clearInsertAutomationLanes(target: StripJsonTarget): void;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function trackIdFor(ctx: EngineStripContext, target: string | number): number {
|
|
@@ -92,15 +92,11 @@ export function setTrackStripJson(
|
|
|
92
92
|
trackId: number,
|
|
93
93
|
sceneJson: string,
|
|
94
94
|
trackStripJson: Map<number, string>,
|
|
95
|
-
): {
|
|
95
|
+
): { insertBaseResets: StripJsonTarget[]; constructionOverrides: InsertParamOverride[] } {
|
|
96
96
|
const target: StripJsonTarget = { kind: 'track', trackId };
|
|
97
|
-
const
|
|
98
|
-
(trackStripJson.has(trackId) && trackStripJson.get(trackId) !== sceneJson) ||
|
|
99
|
-
hasInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
100
|
-
applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
|
|
101
|
-
clearInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
97
|
+
const result = replaceStripScene(ctx, target, trackStripJson.get(trackId), sceneJson);
|
|
102
98
|
trackStripJson.set(trackId, sceneJson);
|
|
103
|
-
return
|
|
99
|
+
return result;
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
export function setTrackStripEqBand(
|
|
@@ -210,6 +206,26 @@ export function setTrackStripDualPan(
|
|
|
210
206
|
ctx.postSync({ type: 'syncTrackStripDualPan', trackId, leftPan, rightPan });
|
|
211
207
|
}
|
|
212
208
|
|
|
209
|
+
export function setTrackStripSurroundPan(
|
|
210
|
+
ctx: EngineStripContext,
|
|
211
|
+
target: string | number,
|
|
212
|
+
pan: SurroundPan,
|
|
213
|
+
): void {
|
|
214
|
+
const trackId = trackIdFor(ctx, target);
|
|
215
|
+
ctx.offlineEngine.setTrackStripSurroundPan(trackId, pan);
|
|
216
|
+
const resolved = {
|
|
217
|
+
azimuth: pan.azimuth ?? 0,
|
|
218
|
+
elevation: pan.elevation ?? 0,
|
|
219
|
+
divergence: pan.divergence ?? 0,
|
|
220
|
+
lfe: pan.lfe ?? 0,
|
|
221
|
+
distance: pan.distance === undefined || pan.distance <= 0 ? 1 : pan.distance,
|
|
222
|
+
};
|
|
223
|
+
mergeStripJson(ctx, { kind: 'track', trackId }, (entry) => {
|
|
224
|
+
entry.surroundPan = resolved;
|
|
225
|
+
});
|
|
226
|
+
ctx.postSync({ type: 'syncTrackStripSurroundPan', trackId, pan: resolved });
|
|
227
|
+
}
|
|
228
|
+
|
|
213
229
|
export function setTrackStripChannelDelaySamples(
|
|
214
230
|
ctx: EngineStripContext,
|
|
215
231
|
target: string | number,
|
|
@@ -504,12 +520,18 @@ export function pushMidiPolyPressure(
|
|
|
504
520
|
export function pushMidiUmp(
|
|
505
521
|
ctx: EngineStripContext,
|
|
506
522
|
trackId: string | number,
|
|
507
|
-
word0: number,
|
|
523
|
+
word0: number | UmpWords,
|
|
508
524
|
renderFrame: number,
|
|
509
525
|
): void {
|
|
510
526
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
511
|
-
|
|
512
|
-
|
|
527
|
+
if (typeof word0 === 'number') {
|
|
528
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
|
|
529
|
+
ctx.postSync({ type: 'syncMidiUmp', destinationId, word0, renderFrame });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const words = word0 instanceof Uint32Array ? new Uint32Array(word0) : word0.slice();
|
|
533
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, words, renderFrame);
|
|
534
|
+
ctx.postSync({ type: 'syncMidiUmp', destinationId, words, renderFrame });
|
|
513
535
|
}
|
|
514
536
|
|
|
515
537
|
export function setBuiltinInstrument(
|
|
@@ -11,25 +11,30 @@ import type { SonareEngineSyncTempoMessage } from './messages';
|
|
|
11
11
|
* Builds the ordered mixer-lane descriptors for a sync message.
|
|
12
12
|
*
|
|
13
13
|
* Each declared track id is paired with its current send list and output bus
|
|
14
|
-
* (both omitted when absent), defensively copying the
|
|
14
|
+
* (both omitted when absent) and source channel layout, defensively copying the
|
|
15
|
+
* send entries.
|
|
15
16
|
*
|
|
16
17
|
* @param trackLaneIds Lane order (track ids) declared on the engine.
|
|
17
18
|
* @param trackSends Per-track send lists.
|
|
18
19
|
* @param trackOutputBus Per-track output bus routing.
|
|
20
|
+
* @param trackSourceChannelLayout Per-track source channel layouts.
|
|
19
21
|
* @returns Lane descriptors in lane order.
|
|
20
22
|
*/
|
|
21
23
|
export function buildMixerLanes(
|
|
22
24
|
trackLaneIds: readonly number[],
|
|
23
25
|
trackSends: ReadonlyMap<number, EngineTrackSend[]>,
|
|
24
26
|
trackOutputBus: ReadonlyMap<number, number>,
|
|
27
|
+
trackSourceChannelLayout: ReadonlyMap<number, number>,
|
|
25
28
|
): EngineTrackLane[] {
|
|
26
29
|
return trackLaneIds.map((trackId) => {
|
|
27
30
|
const sends = trackSends.get(trackId);
|
|
28
31
|
const outputBusId = trackOutputBus.get(trackId);
|
|
32
|
+
const sourceChannelLayout = trackSourceChannelLayout.get(trackId);
|
|
29
33
|
return {
|
|
30
34
|
trackId,
|
|
31
35
|
...(sends && sends.length > 0 ? { sends: sends.map((send) => ({ ...send })) } : {}),
|
|
32
36
|
...(outputBusId !== undefined ? { outputBusId } : {}),
|
|
37
|
+
...(sourceChannelLayout !== undefined ? { sourceChannelLayout } : {}),
|
|
33
38
|
};
|
|
34
39
|
});
|
|
35
40
|
}
|