@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
package/src/worklet/engine.ts
CHANGED
|
@@ -23,9 +23,11 @@ import type {
|
|
|
23
23
|
PanLaw,
|
|
24
24
|
PanMode,
|
|
25
25
|
SidechainSourceKind,
|
|
26
|
+
UmpWords,
|
|
26
27
|
} from '../index';
|
|
27
28
|
import { RealtimeEngine } from '../index';
|
|
28
29
|
import { createOpfsClipPageProvider, type OpfsClipPageProviderBinding } from '../opfs_clip_pages';
|
|
30
|
+
import type { SurroundPan } from '../public_types';
|
|
29
31
|
import type { ClipPageProvider } from '../realtime_engine';
|
|
30
32
|
import type { EngineAutomationContext } from './engine-automation';
|
|
31
33
|
import * as automation from './engine-automation';
|
|
@@ -102,6 +104,7 @@ export class SonareEngine {
|
|
|
102
104
|
private readonly trackLaneIds: number[] = [];
|
|
103
105
|
private readonly trackSends = new Map<number, EngineTrackSend[]>();
|
|
104
106
|
private readonly trackOutputBus = new Map<number, number>();
|
|
107
|
+
private readonly trackSourceChannelLayout = new Map<number, number>();
|
|
105
108
|
private readonly laneSidechains = new Map<
|
|
106
109
|
string,
|
|
107
110
|
{ trackId: number; insertIndex: number; sourceTrackId: number }
|
|
@@ -242,6 +245,11 @@ export class SonareEngine {
|
|
|
242
245
|
tempo.setTimeSignatureSegments(this.tempoContext, segments);
|
|
243
246
|
}
|
|
244
247
|
|
|
248
|
+
/**
|
|
249
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
250
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
251
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
252
|
+
*/
|
|
245
253
|
setLoop(startPpq: number, endPpq: number, enabled = true): boolean {
|
|
246
254
|
return tempo.setLoop(this.tempoContext, startPpq, endPpq, enabled);
|
|
247
255
|
}
|
|
@@ -321,6 +329,15 @@ export class SonareEngine {
|
|
|
321
329
|
* mixer lane first (like automationParamId) so the offline engine resolves the
|
|
322
330
|
* same strip selector the realtime engine uses.
|
|
323
331
|
*
|
|
332
|
+
* The returned id uses the track's current positional lane selector. When
|
|
333
|
+
* setTrackLanes successfully changes lane order or membership, the engine
|
|
334
|
+
* remaps already queued and published track automation by track id, but it
|
|
335
|
+
* cannot update a numeric id retained by the caller. Re-resolve every track
|
|
336
|
+
* insert id after such a topology change before passing it to
|
|
337
|
+
* setAutomationLane. Use setTrackStripInsertParamByName when the operation
|
|
338
|
+
* needs a stable track identity. Master and bus insert ids are separate and
|
|
339
|
+
* are not invalidated by track-lane changes.
|
|
340
|
+
*
|
|
324
341
|
* @param target Track id (declares a mixer lane on first use).
|
|
325
342
|
* @param insertIndex Index into the strip's combined insert sequence.
|
|
326
343
|
* @param paramName Processor JSON-key parameter name.
|
|
@@ -454,14 +471,24 @@ export class SonareEngine {
|
|
|
454
471
|
return sent;
|
|
455
472
|
}
|
|
456
473
|
|
|
474
|
+
/** Resets the master's integrated loudness accumulator in both engine mirrors. */
|
|
475
|
+
resetMasterLoudnessMeter(renderFrame = -1): boolean {
|
|
476
|
+
this.offlineEngine.resetMasterLoudnessMeter(renderFrame);
|
|
477
|
+
return this.sendMirroredCommand({
|
|
478
|
+
type: SonareEngineCommandType.ResetMasterLoudnessMeter,
|
|
479
|
+
sampleTime: renderFrame,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
457
483
|
/**
|
|
458
484
|
* Declares the mixer track lanes in an explicit order.
|
|
459
485
|
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
* track's send list; entries without `sends` leave
|
|
486
|
+
* A successful call may reorder existing track ids, remove track ids, or add
|
|
487
|
+
* new ones. Existing queued and published track automation follows the track
|
|
488
|
+
* id, while numeric track-insert automation ids retained by the caller do
|
|
489
|
+
* not; re-resolve those ids after every topology change. Entries carrying
|
|
490
|
+
* `sends` replace that track's send list; entries without `sends` leave
|
|
491
|
+
* existing sends untouched.
|
|
465
492
|
*
|
|
466
493
|
* @param lanes Track ids or lane descriptors in the desired lane order.
|
|
467
494
|
*/
|
|
@@ -480,7 +507,9 @@ export class SonareEngine {
|
|
|
480
507
|
/**
|
|
481
508
|
* Keys one insert of a lane strip from another lane's post-strip pre-fader
|
|
482
509
|
* audio (ducking/sidechainRouter inserts). sourceTarget null removes the
|
|
483
|
-
* binding.
|
|
510
|
+
* binding. Throws, leaving the existing bindings unchanged, for a lane keying
|
|
511
|
+
* itself or a binding that would close a cycle over the lane bindings; a
|
|
512
|
+
* refused binding is never recorded, so it is not replayed on resync.
|
|
484
513
|
*/
|
|
485
514
|
setLaneSidechain(
|
|
486
515
|
target: string | number,
|
|
@@ -528,19 +557,18 @@ export class SonareEngine {
|
|
|
528
557
|
}
|
|
529
558
|
|
|
530
559
|
setTrackStripJson(target: string | number, sceneJson: string): void {
|
|
560
|
+
// Parse before declaring a new lane so malformed text cannot declare one.
|
|
561
|
+
mixer.decodedInsertSignature(sceneJson, { kind: 'track', trackId: 0 });
|
|
531
562
|
const laneIndex = this.ensureTrackLane(target);
|
|
532
563
|
const trackId = this.trackLaneIds[laneIndex];
|
|
533
|
-
const {
|
|
564
|
+
const { insertBaseResets, constructionOverrides } = strips.setTrackStripJson(
|
|
534
565
|
this.stripContext,
|
|
535
566
|
trackId,
|
|
536
567
|
sceneJson,
|
|
537
568
|
this.trackStripJson,
|
|
538
569
|
);
|
|
539
|
-
if (resetInserts) {
|
|
540
|
-
this.clearInsertAutomationLanes({ kind: 'track', trackId });
|
|
541
|
-
}
|
|
542
570
|
mixer.pruneStripSidechains(this.mixerContext, { kind: 'track', trackId }, sceneJson);
|
|
543
|
-
this.syncMixer(
|
|
571
|
+
this.syncMixer(insertBaseResets, constructionOverrides);
|
|
544
572
|
}
|
|
545
573
|
|
|
546
574
|
setTrackStripEqBand(target: string | number, bandIndex: number, band: EqBand | string): void {
|
|
@@ -587,6 +615,15 @@ export class SonareEngine {
|
|
|
587
615
|
strips.setTrackStripDualPan(this.stripContext, target, leftPan, rightPan);
|
|
588
616
|
}
|
|
589
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Sets a track lane strip's surround placement (glitch-free, 5 ms placement
|
|
620
|
+
* glide). Takes effect on a destination wider than stereo, so set `channelCount`
|
|
621
|
+
* to the layout's plane count. Omitted fields default to a centered point source.
|
|
622
|
+
*/
|
|
623
|
+
setTrackStripSurroundPan(target: string | number, pan: SurroundPan): void {
|
|
624
|
+
strips.setTrackStripSurroundPan(this.stripContext, target, pan);
|
|
625
|
+
}
|
|
626
|
+
|
|
590
627
|
setTrackStripChannelDelaySamples(target: string | number, delaySamples: number): void {
|
|
591
628
|
strips.setTrackStripChannelDelaySamples(this.stripContext, target, delaySamples);
|
|
592
629
|
}
|
|
@@ -625,18 +662,7 @@ export class SonareEngine {
|
|
|
625
662
|
}
|
|
626
663
|
|
|
627
664
|
setMasterStripJson(sceneJson: string): void {
|
|
628
|
-
|
|
629
|
-
const resetInserts =
|
|
630
|
-
(this.masterStripJson !== undefined && this.masterStripJson !== sceneJson) ||
|
|
631
|
-
mixer.hasInsertParamOverrides(this.insertParamOverrides, target);
|
|
632
|
-
mixer.applyFullStripJson(this.offlineEngine, target, sceneJson, resetInserts);
|
|
633
|
-
if (resetInserts) {
|
|
634
|
-
this.clearInsertAutomationLanes(target);
|
|
635
|
-
}
|
|
636
|
-
mixer.pruneStripSidechains(this.mixerContext, target, sceneJson);
|
|
637
|
-
mixer.clearInsertParamOverrides(this.insertParamOverrides, target);
|
|
638
|
-
this.masterStripJson = sceneJson;
|
|
639
|
-
this.syncMixer(resetInserts ? [target] : []);
|
|
665
|
+
mixer.setMasterStripJson(this.mixerContext, sceneJson);
|
|
640
666
|
}
|
|
641
667
|
|
|
642
668
|
setMasterStripEqBand(bandIndex: number, band: EqBand | string): void {
|
|
@@ -904,7 +930,10 @@ export class SonareEngine {
|
|
|
904
930
|
/**
|
|
905
931
|
* Route a track's MIDI to the external output (drained via {@link onMidiOut})
|
|
906
932
|
* instead of an internal instrument, so the track plays an external device.
|
|
907
|
-
* Pass `external=false` to restore internal-synth playback.
|
|
933
|
+
* Pass `external=false` to restore internal-synth playback. The change takes
|
|
934
|
+
* effect at the next processed block; switching a destination's route first
|
|
935
|
+
* releases its notes and resets its controllers through the old route, and
|
|
936
|
+
* drops its pending MIDI-FX events.
|
|
908
937
|
*/
|
|
909
938
|
setMidiDestinationExternal(trackId: string | number, external: boolean): void {
|
|
910
939
|
strips.setMidiDestinationExternal(this.stripContext, trackId, external);
|
|
@@ -1015,8 +1044,8 @@ export class SonareEngine {
|
|
|
1015
1044
|
);
|
|
1016
1045
|
}
|
|
1017
1046
|
|
|
1018
|
-
pushMidiUmp(trackId: string | number,
|
|
1019
|
-
strips.pushMidiUmp(this.stripContext, trackId,
|
|
1047
|
+
pushMidiUmp(trackId: string | number, words: number | UmpWords, renderFrame = -1): void {
|
|
1048
|
+
strips.pushMidiUmp(this.stripContext, trackId, words, renderFrame);
|
|
1020
1049
|
}
|
|
1021
1050
|
|
|
1022
1051
|
pushMidiSysex(trackId: string | number, data: Uint8Array, renderFrame = -1): void {
|
|
@@ -1151,6 +1180,12 @@ export class SonareEngine {
|
|
|
1151
1180
|
});
|
|
1152
1181
|
}
|
|
1153
1182
|
|
|
1183
|
+
pushMidiInputUmp(words: UmpWords, portTimeSamples = 0): void {
|
|
1184
|
+
const copiedWords = words instanceof Uint32Array ? new Uint32Array(words) : words.slice();
|
|
1185
|
+
this.offlineEngine.pushMidiInputUmp(copiedWords, portTimeSamples);
|
|
1186
|
+
this.postSync({ type: 'syncMidiInputUmp', words: copiedWords, portTimeSamples });
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1154
1189
|
pushMidiPanic(renderFrame = -1): void {
|
|
1155
1190
|
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
1156
1191
|
this.postSync({ type: 'syncMidiPanic', renderFrame });
|
|
@@ -1272,6 +1307,11 @@ export class SonareEngine {
|
|
|
1272
1307
|
return this.realtimeNode.pollScope();
|
|
1273
1308
|
}
|
|
1274
1309
|
|
|
1310
|
+
/** Per-insert gain reduction for a meter target; see {@link SonareRealtimeEngineNode.pollInsertGainReduction}. */
|
|
1311
|
+
pollInsertGainReduction(targetId: number): Promise<number[]> {
|
|
1312
|
+
return this.realtimeNode.pollInsertGainReduction(targetId);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1275
1315
|
destroy(): void {
|
|
1276
1316
|
if (this.destroyed) {
|
|
1277
1317
|
return;
|
|
@@ -1397,8 +1437,11 @@ export class SonareEngine {
|
|
|
1397
1437
|
return mixer.mixerLanes(this.mixerContext);
|
|
1398
1438
|
}
|
|
1399
1439
|
|
|
1400
|
-
private syncMixer(
|
|
1401
|
-
mixer.
|
|
1440
|
+
private syncMixer(
|
|
1441
|
+
insertBaseResets: mixer.StripJsonTarget[] = [],
|
|
1442
|
+
oneShotInsertParamOverrides: mixer.InsertParamOverride[] = [],
|
|
1443
|
+
): void {
|
|
1444
|
+
mixer.syncMixer(this.mixerContext, false, insertBaseResets, oneShotInsertParamOverrides);
|
|
1402
1445
|
}
|
|
1403
1446
|
|
|
1404
1447
|
private insertAutomationTargetKey(target: mixer.StripJsonTarget): string {
|
|
@@ -1494,6 +1537,7 @@ export class SonareEngine {
|
|
|
1494
1537
|
trackLaneIds: this.trackLaneIds,
|
|
1495
1538
|
trackSends: this.trackSends,
|
|
1496
1539
|
trackOutputBus: this.trackOutputBus,
|
|
1540
|
+
trackSourceChannelLayout: this.trackSourceChannelLayout,
|
|
1497
1541
|
laneSidechains: this.laneSidechains,
|
|
1498
1542
|
busSidechains: this.busSidechains,
|
|
1499
1543
|
masterSidechains: this.masterSidechains,
|
|
@@ -1506,7 +1550,8 @@ export class SonareEngine {
|
|
|
1506
1550
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
1507
1551
|
ensureBus: (busId) => this.ensureBus(busId),
|
|
1508
1552
|
mixerLanes: () => this.mixerLanes(),
|
|
1509
|
-
syncMixer: (
|
|
1553
|
+
syncMixer: (insertBaseResets, oneShotInsertParamOverrides) =>
|
|
1554
|
+
this.syncMixer(insertBaseResets, oneShotInsertParamOverrides),
|
|
1510
1555
|
clearInsertAutomationLanes: (target, forgetResolvedIds) =>
|
|
1511
1556
|
this.clearInsertAutomationLanes(target, forgetResolvedIds),
|
|
1512
1557
|
sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
|
|
@@ -1532,6 +1577,7 @@ export class SonareEngine {
|
|
|
1532
1577
|
writeStripJson: (target, sceneJson) =>
|
|
1533
1578
|
mixer.cacheStripJson(this.mixerContext, target, sceneJson),
|
|
1534
1579
|
insertParamOverrides: this.insertParamOverrides,
|
|
1580
|
+
clearInsertAutomationLanes: (target) => this.clearInsertAutomationLanes(target),
|
|
1535
1581
|
};
|
|
1536
1582
|
}
|
|
1537
1583
|
|
|
@@ -1683,17 +1729,7 @@ export class SonareEngine {
|
|
|
1683
1729
|
}
|
|
1684
1730
|
|
|
1685
1731
|
private ensureTrackLane(target: string | number): number {
|
|
1686
|
-
|
|
1687
|
-
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
1688
|
-
throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
|
|
1689
|
-
}
|
|
1690
|
-
const existing = this.trackLaneIds.indexOf(trackId);
|
|
1691
|
-
if (existing >= 0) {
|
|
1692
|
-
return existing;
|
|
1693
|
-
}
|
|
1694
|
-
this.trackLaneIds.push(trackId);
|
|
1695
|
-
this.syncMixer();
|
|
1696
|
-
return this.trackLaneIds.length - 1;
|
|
1732
|
+
return mixer.ensureTrackLane(this.mixerContext, target);
|
|
1697
1733
|
}
|
|
1698
1734
|
|
|
1699
1735
|
private ensureBus(busId: number): number {
|
package/src/worklet/guards.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type {
|
|
|
3
3
|
SonareEngineCaptureRequestMessage,
|
|
4
4
|
SonareEngineCaptureResponseMessageInternal,
|
|
5
5
|
SonareEngineClipPageRequestMessage,
|
|
6
|
+
SonareEngineInsertGainReductionRequestMessage,
|
|
7
|
+
SonareEngineInsertGainReductionResponseMessage,
|
|
6
8
|
SonareEngineSyncMessage,
|
|
7
9
|
SonareEngineTransportRequestMessage,
|
|
8
10
|
SonareEngineTransportResponseMessage,
|
|
@@ -13,9 +15,11 @@ import type {
|
|
|
13
15
|
} from './messages';
|
|
14
16
|
import {
|
|
15
17
|
isRecord,
|
|
18
|
+
SONARE_SCOPE_DEFAULT_BANDS,
|
|
16
19
|
type SonareEngineCommandRecord,
|
|
17
20
|
type SonareEngineTelemetryRecord,
|
|
18
21
|
type SonareWorkletMeterSnapshot,
|
|
22
|
+
type SonareWorkletScopeSnapshot,
|
|
19
23
|
} from './protocol';
|
|
20
24
|
|
|
21
25
|
export function isWorkletMessage(value: unknown): value is SonareWorkletMessage {
|
|
@@ -84,6 +88,7 @@ export const ENGINE_SYNC_MESSAGE_TYPES: Record<SonareEngineSyncMessage['type'],
|
|
|
84
88
|
syncMidiInputPitchBend: true,
|
|
85
89
|
syncMidiInputPolyPressure: true,
|
|
86
90
|
syncMidiInputSource: true,
|
|
91
|
+
syncMidiInputUmp: true,
|
|
87
92
|
syncMidiNoteOff: true,
|
|
88
93
|
syncMidiNoteOn: true,
|
|
89
94
|
syncMidiPanic: true,
|
|
@@ -104,6 +109,7 @@ export const ENGINE_SYNC_MESSAGE_TYPES: Record<SonareEngineSyncMessage['type'],
|
|
|
104
109
|
syncTrackStripPan: true,
|
|
105
110
|
syncTrackStripPanLaw: true,
|
|
106
111
|
syncTrackStripPanMode: true,
|
|
112
|
+
syncTrackStripSurroundPan: true,
|
|
107
113
|
syncWarpVoiceCapacity: true,
|
|
108
114
|
};
|
|
109
115
|
|
|
@@ -252,6 +258,28 @@ export function isEngineTransportResponseMessage(
|
|
|
252
258
|
);
|
|
253
259
|
}
|
|
254
260
|
|
|
261
|
+
export function isEngineInsertGainReductionRequestMessage(
|
|
262
|
+
value: unknown,
|
|
263
|
+
): value is SonareEngineInsertGainReductionRequestMessage {
|
|
264
|
+
return (
|
|
265
|
+
isRecord(value) &&
|
|
266
|
+
value.type === 'insertGainReductionRequest' &&
|
|
267
|
+
typeof value.requestId === 'number' &&
|
|
268
|
+
typeof value.targetId === 'number'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function isEngineInsertGainReductionResponseMessage(
|
|
273
|
+
value: unknown,
|
|
274
|
+
): value is SonareEngineInsertGainReductionResponseMessage {
|
|
275
|
+
return (
|
|
276
|
+
isRecord(value) &&
|
|
277
|
+
value.type === 'insertGainReductionResponse' &&
|
|
278
|
+
typeof value.requestId === 'number' &&
|
|
279
|
+
typeof value.ok === 'boolean'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
255
283
|
export function isRealtimeVoiceChangerMessage(
|
|
256
284
|
value: unknown,
|
|
257
285
|
): value is SonareRealtimeVoiceChangerMessage {
|
|
@@ -331,10 +359,55 @@ export function isMeterSnapshot(value: unknown): value is SonareWorkletMeterSnap
|
|
|
331
359
|
typeof value.rmsDbL === 'number' &&
|
|
332
360
|
typeof value.rmsDbR === 'number' &&
|
|
333
361
|
typeof value.correlation === 'number' &&
|
|
362
|
+
Number.isFinite(value.inputPeakDbL) &&
|
|
363
|
+
Number.isFinite(value.inputPeakDbR) &&
|
|
334
364
|
(typeof value.targetId === 'number' || value.targetId === undefined)
|
|
335
365
|
);
|
|
336
366
|
}
|
|
337
367
|
|
|
368
|
+
export const SONARE_SCOPE_MAX_BANDS = 64;
|
|
369
|
+
export const SONARE_SCOPE_MAX_POINTS = 32;
|
|
370
|
+
|
|
371
|
+
function isPlainFloat32Array(
|
|
372
|
+
value: unknown,
|
|
373
|
+
maxLength: number,
|
|
374
|
+
evenLength = false,
|
|
375
|
+
): value is Float32Array {
|
|
376
|
+
if (
|
|
377
|
+
!(value instanceof Float32Array) ||
|
|
378
|
+
typeof ArrayBuffer === 'undefined' ||
|
|
379
|
+
!(value.buffer instanceof ArrayBuffer) ||
|
|
380
|
+
value.length > maxLength ||
|
|
381
|
+
(evenLength && value.length % 2 !== 0)
|
|
382
|
+
) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
for (const sample of value) {
|
|
386
|
+
if (!Number.isFinite(sample)) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Validates the bounded postMessage representation of a scope snapshot. */
|
|
394
|
+
export function isScopeSnapshot(value: unknown): value is SonareWorkletScopeSnapshot {
|
|
395
|
+
return (
|
|
396
|
+
isRecord(value) &&
|
|
397
|
+
value.type === 'scope' &&
|
|
398
|
+
typeof value.targetId === 'number' &&
|
|
399
|
+
Number.isSafeInteger(value.targetId) &&
|
|
400
|
+
value.targetId >= 0 &&
|
|
401
|
+
value.targetId <= 0xffff_ffff &&
|
|
402
|
+
typeof value.frame === 'number' &&
|
|
403
|
+
Number.isSafeInteger(value.frame) &&
|
|
404
|
+
value.frame >= 0 &&
|
|
405
|
+
isPlainFloat32Array(value.bands, SONARE_SCOPE_MAX_BANDS) &&
|
|
406
|
+
value.bands.length > 0 &&
|
|
407
|
+
isPlainFloat32Array(value.points, SONARE_SCOPE_MAX_POINTS * 2, true)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
338
411
|
/**
|
|
339
412
|
* Resolves an integer option, refusing anything the field's own domain cannot
|
|
340
413
|
* hold instead of rounding it into range.
|
|
@@ -356,6 +429,14 @@ export function requireIntegerOption(
|
|
|
356
429
|
return resolved;
|
|
357
430
|
}
|
|
358
431
|
|
|
432
|
+
/** Resolves scope bands and applies the native tap's fixed 64-band ceiling. */
|
|
433
|
+
export function resolveScopeBandCount(
|
|
434
|
+
value: number | undefined,
|
|
435
|
+
fallback = SONARE_SCOPE_DEFAULT_BANDS,
|
|
436
|
+
): number {
|
|
437
|
+
return Math.min(requireIntegerOption(value, fallback, 'scopeBands', 1), SONARE_SCOPE_MAX_BANDS);
|
|
438
|
+
}
|
|
439
|
+
|
|
359
440
|
/** As {@link requireIntegerOption}, for a field whose domain admits either sign. */
|
|
360
441
|
export function requireInteger(value: number | undefined, fallback: number, name: string): number {
|
|
361
442
|
const resolved = value ?? fallback;
|
package/src/worklet/messages.ts
CHANGED
|
@@ -15,12 +15,14 @@ import type {
|
|
|
15
15
|
PlaybackRendererConfig,
|
|
16
16
|
RealtimeVoiceChangerConfigInput,
|
|
17
17
|
RealtimeVoiceChangerPodConfig,
|
|
18
|
+
UmpWords,
|
|
18
19
|
} from '../index';
|
|
19
|
-
import type { AutomationCurve } from '../public_types';
|
|
20
|
+
import type { AutomationCurve, SurroundPan } from '../public_types';
|
|
20
21
|
import type {
|
|
21
22
|
SonareEngineCommandRecord,
|
|
22
23
|
SonareEngineTelemetryRecord,
|
|
23
24
|
SonareWorkletMeterSnapshot,
|
|
25
|
+
SonareWorkletScopeSnapshot,
|
|
24
26
|
SonareWorkletSpectrumSnapshot,
|
|
25
27
|
} from './protocol';
|
|
26
28
|
|
|
@@ -44,6 +46,18 @@ export interface SonareRealtimeEngineWorkletProcessorOptions {
|
|
|
44
46
|
initialCommands?: SonareEngineCommandRecord[];
|
|
45
47
|
sampleRate?: number;
|
|
46
48
|
blockSize?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Output plane count (default 2). Planes follow the canonical order
|
|
51
|
+
* L R C LFE Ls Rs [Lss Rss] for 6 (5.1) and 8 (7.1) planes.
|
|
52
|
+
*
|
|
53
|
+
* The Web Audio default destination is 2-channel `'speakers'`, so a wider
|
|
54
|
+
* output is mixed down to stereo unless the app raises
|
|
55
|
+
* `audioContext.destination.channelCount` (up to its `maxChannelCount`) to
|
|
56
|
+
* match. Interpretation matters only while the counts differ: `'speakers'`
|
|
57
|
+
* applies the spec's down-mix for mono, stereo, quad and 5.1 sources only,
|
|
58
|
+
* and `'discrete'` copies the first planes and drops the rest. With equal
|
|
59
|
+
* counts the planes pass through unchanged in either mode.
|
|
60
|
+
*/
|
|
47
61
|
channelCount?: number;
|
|
48
62
|
meterIntervalFrames?: number;
|
|
49
63
|
commandSharedBuffer?: SharedArrayBuffer;
|
|
@@ -288,6 +302,7 @@ export interface SonareEngineSyncErrorMessage {
|
|
|
288
302
|
|
|
289
303
|
export type SonareWorkletTransportMessage =
|
|
290
304
|
| SonareWorkletMeterSnapshot
|
|
305
|
+
| SonareWorkletScopeSnapshot
|
|
291
306
|
| SonareWorkletSpectrumSnapshot
|
|
292
307
|
| SonareWorkletExternalMidiMessage
|
|
293
308
|
| SonareEngineClipPageRequestMessage
|
|
@@ -299,6 +314,7 @@ export interface WorkletTransport {
|
|
|
299
314
|
| SonareWorkletTransportMessage
|
|
300
315
|
| SonareEngineCaptureResponseMessageInternal
|
|
301
316
|
| SonareEngineTransportResponseMessage
|
|
317
|
+
| SonareEngineInsertGainReductionResponseMessage
|
|
302
318
|
| SonareEngineSyncErrorMessage,
|
|
303
319
|
transfer?: Transferable[],
|
|
304
320
|
) => void;
|
|
@@ -511,8 +527,8 @@ export interface SonareEngineSyncMixerMessage {
|
|
|
511
527
|
masterStripJson?: string;
|
|
512
528
|
/** Retained by-name insert values, applied after strip and sidechain replay. */
|
|
513
529
|
insertParamOverrides?: SonareEngineSyncMixerInsertParamOverride[];
|
|
514
|
-
/**
|
|
515
|
-
|
|
530
|
+
/** Strips whose manual insert bases are cleared after the strip replay, before insertParamOverrides. */
|
|
531
|
+
insertBaseResets?: Array<
|
|
516
532
|
{ kind: 'track'; trackId: number } | { kind: 'bus'; busId: number } | { kind: 'master' }
|
|
517
533
|
>;
|
|
518
534
|
/** Lane insert sidechain bindings (replayed after lanes/strips). */
|
|
@@ -631,6 +647,12 @@ export interface SonareEngineSyncTrackStripDualPanMessage {
|
|
|
631
647
|
rightPan: number;
|
|
632
648
|
}
|
|
633
649
|
|
|
650
|
+
export interface SonareEngineSyncTrackStripSurroundPanMessage {
|
|
651
|
+
type: 'syncTrackStripSurroundPan';
|
|
652
|
+
trackId: number;
|
|
653
|
+
pan: Required<SurroundPan>;
|
|
654
|
+
}
|
|
655
|
+
|
|
634
656
|
export interface SonareEngineSyncBusStripPanMessage {
|
|
635
657
|
type: 'syncBusStripPan';
|
|
636
658
|
busId: number;
|
|
@@ -719,13 +741,26 @@ export interface SonareEngineSyncMidiCcMessage {
|
|
|
719
741
|
renderFrame: number;
|
|
720
742
|
}
|
|
721
743
|
|
|
722
|
-
export interface
|
|
744
|
+
export interface SonareEngineSyncMidiUmpWordMessage {
|
|
723
745
|
type: 'syncMidiUmp';
|
|
724
746
|
destinationId: number;
|
|
725
747
|
word0: number;
|
|
748
|
+
words?: never;
|
|
726
749
|
renderFrame: number;
|
|
727
750
|
}
|
|
728
751
|
|
|
752
|
+
export interface SonareEngineSyncMidiUmpWordsMessage {
|
|
753
|
+
type: 'syncMidiUmp';
|
|
754
|
+
destinationId: number;
|
|
755
|
+
word0?: never;
|
|
756
|
+
words: UmpWords;
|
|
757
|
+
renderFrame: number;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export type SonareEngineSyncMidiUmpMessage =
|
|
761
|
+
| SonareEngineSyncMidiUmpWordMessage
|
|
762
|
+
| SonareEngineSyncMidiUmpWordsMessage;
|
|
763
|
+
|
|
729
764
|
/**
|
|
730
765
|
* One of the three per-note expression dimensions, addressed to a destination.
|
|
731
766
|
* `data0`/`data1` carry the dimension's own payload the way the live-input
|
|
@@ -794,6 +829,12 @@ export interface SonareEngineSyncMidiInputEventMessage {
|
|
|
794
829
|
portTimeSamples: number;
|
|
795
830
|
}
|
|
796
831
|
|
|
832
|
+
export interface SonareEngineSyncMidiInputUmpMessage {
|
|
833
|
+
type: 'syncMidiInputUmp';
|
|
834
|
+
words: UmpWords;
|
|
835
|
+
portTimeSamples: number;
|
|
836
|
+
}
|
|
837
|
+
|
|
797
838
|
/** Releases the realtime engine and all worklet-owned clip buffers. */
|
|
798
839
|
export interface SonareEngineDestroyMessage {
|
|
799
840
|
type: 'destroy';
|
|
@@ -837,6 +878,7 @@ export type SonareEngineSyncMessage =
|
|
|
837
878
|
| SonareEngineSyncTrackStripPanLawMessage
|
|
838
879
|
| SonareEngineSyncTrackStripPanModeMessage
|
|
839
880
|
| SonareEngineSyncTrackStripDualPanMessage
|
|
881
|
+
| SonareEngineSyncTrackStripSurroundPanMessage
|
|
840
882
|
| SonareEngineSyncBusStripPanMessage
|
|
841
883
|
| SonareEngineSyncBusStripPanLawMessage
|
|
842
884
|
| SonareEngineSyncBusStripPanModeMessage
|
|
@@ -858,6 +900,7 @@ export type SonareEngineSyncMessage =
|
|
|
858
900
|
| SonareEngineSyncMidiInputSourceMessage
|
|
859
901
|
| SonareEngineSyncMidiCcBindingMessage
|
|
860
902
|
| SonareEngineSyncMidiInputEventMessage
|
|
903
|
+
| SonareEngineSyncMidiInputUmpMessage
|
|
861
904
|
| SonareEngineDestroyMessage;
|
|
862
905
|
|
|
863
906
|
export interface WorkletPort {
|
|
@@ -935,3 +978,19 @@ export interface SonareEngineTransportResponseMessage {
|
|
|
935
978
|
state?: EngineTransportState;
|
|
936
979
|
error?: string;
|
|
937
980
|
}
|
|
981
|
+
|
|
982
|
+
/** Asks the worklet for one target's per-insert gain reduction (any meter delivery mode). */
|
|
983
|
+
export interface SonareEngineInsertGainReductionRequestMessage {
|
|
984
|
+
type: 'insertGainReductionRequest';
|
|
985
|
+
requestId: number;
|
|
986
|
+
targetId: number;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Reply to {@link SonareEngineInsertGainReductionRequestMessage}; `values` is present when `ok`. */
|
|
990
|
+
export interface SonareEngineInsertGainReductionResponseMessage {
|
|
991
|
+
type: 'insertGainReductionResponse';
|
|
992
|
+
requestId: number;
|
|
993
|
+
ok: boolean;
|
|
994
|
+
values?: number[];
|
|
995
|
+
error?: string;
|
|
996
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
meterRingFromSharedBuffer,
|
|
16
16
|
type SharedMeterRingWriter,
|
|
17
17
|
type SharedSpectrumRingWriter,
|
|
18
|
+
SONARE_FLOOR_DB,
|
|
18
19
|
SONARE_METER_RING_RECORD_FLOATS,
|
|
19
20
|
type SonareWorkletMeterSnapshot,
|
|
20
21
|
type SonareWorkletSpectrumSnapshot,
|
|
@@ -47,8 +48,8 @@ export class SonareWorkletProcessor {
|
|
|
47
48
|
*
|
|
48
49
|
* `targetId` is always the master and the four LUFS / gain-reduction fields
|
|
49
50
|
* are always unavailable here — the mixer worklet does not run the
|
|
50
|
-
* K-weighting filters
|
|
51
|
-
* set once rather than per interval.
|
|
51
|
+
* K-weighting filters or the engine's pre-trim input tap — so finite floor
|
|
52
|
+
* values are set once rather than per interval.
|
|
52
53
|
*/
|
|
53
54
|
private readonly meterScratch: SonareWorkletMeterSnapshot = {
|
|
54
55
|
type: 'meter',
|
|
@@ -65,6 +66,8 @@ export class SonareWorkletProcessor {
|
|
|
65
66
|
shortTermLufs: Number.NaN,
|
|
66
67
|
integratedLufs: Number.NaN,
|
|
67
68
|
gainReductionDb: Number.NaN,
|
|
69
|
+
inputPeakDbL: SONARE_FLOOR_DB,
|
|
70
|
+
inputPeakDbR: SONARE_FLOOR_DB,
|
|
68
71
|
};
|
|
69
72
|
private spectrumRing?: SharedSpectrumRingWriter;
|
|
70
73
|
private spectrumBands: Float32Array;
|
|
@@ -276,6 +279,8 @@ export class SonareWorkletProcessor {
|
|
|
276
279
|
shortTermLufs: Number.NaN,
|
|
277
280
|
integratedLufs: Number.NaN,
|
|
278
281
|
gainReductionDb: Number.NaN,
|
|
282
|
+
inputPeakDbL: SONARE_FLOOR_DB,
|
|
283
|
+
inputPeakDbR: SONARE_FLOOR_DB,
|
|
279
284
|
};
|
|
280
285
|
// Alternative channels for one record, not a broadcast pair: a transport
|
|
281
286
|
// that supplies both (the engine registration resolves both to
|
|
@@ -309,6 +314,8 @@ export class SonareWorkletProcessor {
|
|
|
309
314
|
ring.records[offset + 11] = meter.shortTermLufs;
|
|
310
315
|
ring.records[offset + 12] = meter.integratedLufs;
|
|
311
316
|
ring.records[offset + 13] = meter.gainReductionDb;
|
|
317
|
+
ring.records[offset + 14] = meter.inputPeakDbL;
|
|
318
|
+
ring.records[offset + 15] = meter.inputPeakDbR;
|
|
312
319
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
313
320
|
// writeIndex is a free-running monotonic counter, so an overflow guard here
|
|
314
321
|
// would fire on essentially every write past the first `capacity` records
|
|
@@ -377,7 +384,7 @@ export class SonareWorkletProcessor {
|
|
|
377
384
|
return;
|
|
378
385
|
}
|
|
379
386
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
380
|
-
const offset = (writeIndex % ring.capacity) *
|
|
387
|
+
const offset = (writeIndex % ring.capacity) * SONARE_METER_RING_RECORD_FLOATS;
|
|
381
388
|
ring.records[offset] = encodeFrameLo(frame);
|
|
382
389
|
ring.records[offset + 1] = encodeFrameHi(frame);
|
|
383
390
|
ring.records[offset + 2] = bands.length;
|