@libraz/libsonare 1.7.2 → 1.8.1
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/NOTICE +178 -0
- package/README.md +26 -203
- package/dist/_chain_config.d.ts +14 -0
- package/dist/_chain_config.d.ts.map +1 -0
- package/dist/_effects_common.d.ts +7 -0
- package/dist/_effects_common.d.ts.map +1 -0
- package/dist/_feature_validation.d.ts +8 -0
- package/dist/_feature_validation.d.ts.map +1 -0
- package/dist/_fft_options.d.ts +24 -0
- package/dist/_fft_options.d.ts.map +1 -0
- package/dist/align_take.d.ts +50 -0
- package/dist/align_take.d.ts.map +1 -0
- package/dist/analysis.d.ts +29 -5840
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +874 -722
- package/dist/analysis.js.map +1 -1
- package/dist/analysis_helpers.d.ts +9 -0
- package/dist/analysis_helpers.d.ts.map +1 -0
- package/dist/audio.d.ts +163 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/clip_page_streamer.d.ts +133 -0
- package/dist/clip_page_streamer.d.ts.map +1 -0
- package/dist/codes.d.ts +44 -0
- package/dist/codes.d.ts.map +1 -0
- package/dist/effects_mastering.d.ts +23 -0
- package/dist/effects_mastering.d.ts.map +1 -0
- package/dist/effects_note_ops.d.ts +502 -0
- package/dist/effects_note_ops.d.ts.map +1 -0
- package/dist/effects_percussive.d.ts +185 -0
- package/dist/effects_percussive.d.ts.map +1 -0
- package/dist/effects_separation.d.ts +65 -0
- package/dist/effects_separation.d.ts.map +1 -0
- package/dist/effects_spectral.d.ts +28 -0
- package/dist/effects_spectral.d.ts.map +1 -0
- package/dist/effects_timepitch.d.ts +134 -0
- package/dist/effects_timepitch.d.ts.map +1 -0
- package/dist/effects_voice_change.d.ts +53 -0
- package/dist/effects_voice_change.d.ts.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/feature_core.d.ts +341 -0
- package/dist/feature_core.d.ts.map +1 -0
- package/dist/feature_decompose.d.ts +278 -0
- package/dist/feature_decompose.d.ts.map +1 -0
- package/dist/feature_inverse.d.ts +128 -0
- package/dist/feature_inverse.d.ts.map +1 -0
- package/dist/feature_loudness.d.ts +66 -0
- package/dist/feature_loudness.d.ts.map +1 -0
- package/dist/feature_music.d.ts +307 -0
- package/dist/feature_music.d.ts.map +1 -0
- package/dist/feature_pitch.d.ts +108 -0
- package/dist/feature_pitch.d.ts.map +1 -0
- package/dist/feature_resample.d.ts +16 -0
- package/dist/feature_resample.d.ts.map +1 -0
- package/dist/feature_spectral.d.ts +137 -0
- package/dist/feature_spectral.d.ts.map +1 -0
- package/dist/feature_spectrogram.d.ts +198 -0
- package/dist/feature_spectrogram.d.ts.map +1 -0
- package/dist/features.d.ts +10 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/hrtf/default.shrf +0 -0
- package/dist/index.d.ts +74 -7431
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4173 -1621
- package/dist/index.js.map +1 -1
- package/dist/instrument_types.d.ts +517 -0
- package/dist/instrument_types.d.ts.map +1 -0
- package/dist/live_audio.d.ts +35 -0
- package/dist/live_audio.d.ts.map +1 -0
- package/dist/mastering_chain.d.ts +213 -0
- package/dist/mastering_chain.d.ts.map +1 -0
- package/dist/mastering_core.d.ts +483 -0
- package/dist/mastering_core.d.ts.map +1 -0
- package/dist/mastering_dynamics.d.ts +80 -0
- package/dist/mastering_dynamics.d.ts.map +1 -0
- package/dist/metering.d.ts +287 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/mixer.d.ts +464 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixing_assistant.d.ts +62 -0
- package/dist/mixing_assistant.d.ts.map +1 -0
- package/dist/mixing_oneshot.d.ts +40 -0
- package/dist/mixing_oneshot.d.ts.map +1 -0
- package/dist/module_state.d.ts +15 -0
- package/dist/module_state.d.ts.map +1 -0
- package/dist/opfs_clip_pages.d.ts +28 -0
- package/dist/opfs_clip_pages.d.ts.map +1 -0
- package/dist/playback_renderer.d.ts +128 -0
- package/dist/playback_renderer.d.ts.map +1 -0
- package/dist/polyphony.d.ts +202 -0
- package/dist/polyphony.d.ts.map +1 -0
- package/dist/project.d.ts +8 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project_class.d.ts +562 -0
- package/dist/project_class.d.ts.map +1 -0
- package/dist/project_internal.d.ts +194 -0
- package/dist/project_internal.d.ts.map +1 -0
- package/dist/project_synth.d.ts +74 -0
- package/dist/project_synth.d.ts.map +1 -0
- package/dist/project_types.d.ts +654 -0
- package/dist/project_types.d.ts.map +1 -0
- package/dist/public_types.d.ts +185 -0
- package/dist/public_types.d.ts.map +1 -0
- package/dist/public_types_acoustic.d.ts +215 -0
- package/dist/public_types_acoustic.d.ts.map +1 -0
- package/dist/public_types_mastering.d.ts +528 -0
- package/dist/public_types_mastering.d.ts.map +1 -0
- package/dist/public_types_mixing.d.ts +436 -0
- package/dist/public_types_mixing.d.ts.map +1 -0
- package/dist/public_types_music.d.ts +619 -0
- package/dist/public_types_music.d.ts.map +1 -0
- package/dist/public_types_playback.d.ts +165 -0
- package/dist/public_types_playback.d.ts.map +1 -0
- package/dist/public_types_realtime.d.ts +174 -0
- package/dist/public_types_realtime.d.ts.map +1 -0
- package/dist/public_types_repair.d.ts +424 -0
- package/dist/public_types_repair.d.ts.map +1 -0
- package/dist/public_types_spectral.d.ts +697 -0
- package/dist/public_types_spectral.d.ts.map +1 -0
- package/dist/quick_analysis.d.ts +445 -0
- package/dist/quick_analysis.d.ts.map +1 -0
- package/dist/realtime_engine.d.ts +893 -0
- package/dist/realtime_engine.d.ts.map +1 -0
- package/dist/realtime_voice_changer.d.ts +158 -0
- package/dist/realtime_voice_changer.d.ts.map +1 -0
- package/dist/repair_dereverb.d.ts +187 -0
- package/dist/repair_dereverb.d.ts.map +1 -0
- package/dist/repair_impulsive.d.ts +186 -0
- package/dist/repair_impulsive.d.ts.map +1 -0
- package/dist/repair_noise.d.ts +239 -0
- package/dist/repair_noise.d.ts.map +1 -0
- package/dist/repair_trim.d.ts +123 -0
- package/dist/repair_trim.d.ts.map +1 -0
- package/dist/sample_bank.d.ts +84 -0
- package/dist/sample_bank.d.ts.map +1 -0
- package/dist/scale.d.ts +10 -0
- package/dist/scale.d.ts.map +1 -0
- package/dist/schemas/mixer-scene.schema.json +393 -0
- package/dist/schemas/playback-renderer-config.schema.json +392 -0
- package/dist/sonare-analysis.d.ts +8 -0
- package/dist/sonare-analysis.js +2 -2
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.d.ts +3945 -0
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/stream_analyzer.d.ts +163 -0
- package/dist/stream_analyzer.d.ts.map +1 -0
- package/dist/stream_types.d.ts +214 -0
- package/dist/stream_types.d.ts.map +1 -0
- package/dist/streaming_mixing.d.ts +6 -0
- package/dist/streaming_mixing.d.ts.map +1 -0
- package/dist/streaming_processors.d.ts +340 -0
- package/dist/streaming_processors.d.ts.map +1 -0
- package/dist/transcribe.d.ts +77 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/validation.d.ts +140 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/web_midi.d.ts +77 -0
- package/dist/web_midi.d.ts.map +1 -0
- package/dist/worker.d.ts +5 -48
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +94 -41
- package/dist/worker.js.map +1 -1
- package/dist/worker_client.d.ts +96 -0
- package/dist/worker_client.d.ts.map +1 -0
- package/dist/worker_protocol.d.ts +43 -0
- package/dist/worker_protocol.d.ts.map +1 -0
- package/dist/worklet/audio_types.d.ts +21 -0
- package/dist/worklet/audio_types.d.ts.map +1 -0
- package/dist/worklet/engine-automation.d.ts +29 -0
- package/dist/worklet/engine-automation.d.ts.map +1 -0
- package/dist/worklet/engine-capture-facade.d.ts +35 -0
- package/dist/worklet/engine-capture-facade.d.ts.map +1 -0
- package/dist/worklet/engine-clips.d.ts +23 -0
- package/dist/worklet/engine-clips.d.ts.map +1 -0
- package/dist/worklet/engine-markers.d.ts +40 -0
- package/dist/worklet/engine-markers.d.ts.map +1 -0
- package/dist/worklet/engine-mixer-facade.d.ts +164 -0
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
- package/dist/worklet/engine-node.d.ts +83 -0
- package/dist/worklet/engine-node.d.ts.map +1 -0
- package/dist/worklet/engine-offline.d.ts +81 -0
- package/dist/worklet/engine-offline.d.ts.map +1 -0
- package/dist/worklet/engine-options.d.ts +12 -0
- package/dist/worklet/engine-options.d.ts.map +1 -0
- package/dist/worklet/engine-parameter-facade.d.ts +106 -0
- package/dist/worklet/engine-parameter-facade.d.ts.map +1 -0
- package/dist/worklet/engine-processor.d.ts +72 -0
- package/dist/worklet/engine-processor.d.ts.map +1 -0
- package/dist/worklet/engine-register.d.ts +2 -0
- package/dist/worklet/engine-register.d.ts.map +1 -0
- package/dist/worklet/engine-strips.d.ts +75 -0
- package/dist/worklet/engine-strips.d.ts.map +1 -0
- package/dist/worklet/engine-sync.d.ts +39 -0
- package/dist/worklet/engine-sync.d.ts.map +1 -0
- package/dist/worklet/engine-tempo-facade.d.ts +48 -0
- package/dist/worklet/engine-tempo-facade.d.ts.map +1 -0
- package/dist/worklet/engine.d.ts +418 -0
- package/dist/worklet/engine.d.ts.map +1 -0
- package/dist/worklet/guards.d.ts +53 -0
- package/dist/worklet/guards.d.ts.map +1 -0
- package/dist/worklet/messages.d.ts +710 -0
- package/dist/worklet/messages.d.ts.map +1 -0
- package/dist/worklet/mixer-processor.d.ts +46 -0
- package/dist/worklet/mixer-processor.d.ts.map +1 -0
- package/dist/worklet/playback-processor.d.ts +62 -0
- package/dist/worklet/playback-processor.d.ts.map +1 -0
- package/dist/worklet/protocol.d.ts +331 -0
- package/dist/worklet/protocol.d.ts.map +1 -0
- package/dist/worklet/voice-changer-processor.d.ts +41 -0
- package/dist/worklet/voice-changer-processor.d.ts.map +1 -0
- package/dist/worklet.d.ts +16 -2515
- package/dist/worklet.d.ts.map +1 -0
- package/dist/worklet.js +3200 -541
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -12
- package/src/_effects_common.ts +47 -0
- package/src/_feature_validation.ts +34 -0
- package/src/_fft_options.ts +39 -0
- package/src/align_take.ts +64 -0
- package/src/analysis.ts +56 -3
- package/src/analysis_helpers.ts +7 -0
- package/src/audio.ts +106 -3
- package/src/codes.ts +39 -2
- package/src/effects_mastering.ts +101 -22
- package/src/effects_note_ops.ts +683 -0
- package/src/effects_percussive.ts +217 -0
- package/src/effects_separation.ts +150 -0
- package/src/effects_spectral.ts +60 -0
- package/src/effects_timepitch.ts +388 -0
- package/src/errors.ts +23 -1
- package/src/feature_core.ts +127 -2
- package/src/feature_decompose.ts +633 -0
- package/src/feature_inverse.ts +454 -0
- package/src/feature_loudness.ts +125 -0
- package/src/feature_music.ts +107 -14
- package/src/feature_pitch.ts +96 -1
- package/src/feature_spectral.ts +16 -611
- package/src/feature_spectrogram.ts +63 -450
- package/src/features.ts +36 -22
- package/src/index.ts +288 -30
- package/src/instrument_types.ts +645 -0
- package/src/live_audio.ts +27 -1
- package/src/mastering_chain.ts +184 -0
- package/src/mastering_core.ts +441 -32
- package/src/mastering_dynamics.ts +22 -11
- package/src/metering.ts +67 -24
- package/src/mixer.ts +251 -22
- package/src/mixing_assistant.ts +138 -0
- package/src/mixing_oneshot.ts +10 -5
- package/src/module_state.ts +24 -2
- package/src/playback_renderer.ts +252 -0
- package/src/polyphony.ts +279 -0
- package/src/project.ts +61 -24
- package/src/project_class.ts +450 -27
- package/src/project_internal.ts +149 -42
- package/src/project_synth.ts +67 -1
- package/src/project_types.ts +271 -271
- package/src/public_types.ts +122 -3
- package/src/public_types_acoustic.ts +112 -3
- package/src/public_types_mastering.ts +275 -73
- package/src/public_types_mixing.ts +363 -1
- package/src/public_types_music.ts +312 -2
- package/src/public_types_playback.ts +196 -0
- package/src/public_types_realtime.ts +39 -7
- package/src/public_types_repair.ts +446 -0
- package/src/public_types_spectral.ts +491 -5
- package/src/quick_analysis.ts +203 -26
- package/src/realtime_engine.ts +773 -34
- package/src/realtime_voice_changer.ts +55 -1
- package/src/repair_dereverb.ts +299 -0
- package/src/repair_impulsive.ts +395 -0
- package/src/repair_noise.ts +425 -0
- package/src/repair_trim.ts +226 -0
- package/src/sample_bank.ts +113 -0
- package/src/sonare.js.d.ts +1158 -30
- package/src/stream_analyzer.ts +36 -4
- package/src/stream_types.ts +37 -0
- package/src/streaming_mixing.ts +1 -1
- package/src/streaming_processors.ts +202 -10
- package/src/transcribe.ts +89 -0
- package/src/validation.ts +285 -11
- package/src/web_midi.ts +1 -6
- package/src/worker.ts +18 -2
- package/src/worklet/audio_types.ts +37 -0
- package/src/worklet/engine-mixer-facade.ts +800 -32
- package/src/worklet/engine-node.ts +99 -29
- package/src/worklet/engine-offline.ts +14 -8
- package/src/worklet/engine-parameter-facade.ts +21 -0
- package/src/worklet/engine-processor.ts +332 -93
- package/src/worklet/engine-register.ts +32 -18
- package/src/worklet/engine-strips.ts +275 -9
- package/src/worklet/engine-sync.ts +20 -7
- package/src/worklet/engine.ts +394 -48
- package/src/worklet/guards.ts +195 -44
- package/src/worklet/messages.ts +229 -2
- package/src/worklet/mixer-processor.ts +117 -48
- package/src/worklet/playback-processor.ts +300 -0
- package/src/worklet/protocol.ts +82 -11
- package/src/worklet/voice-changer-processor.ts +17 -11
- package/src/worklet.ts +17 -0
- package/src/effects_transform.ts +0 -718
- package/src/mastering_repair.ts +0 -273
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { sidechainSourceKindCode } from '../codes';
|
|
2
|
+
import type {
|
|
3
|
+
EngineBus,
|
|
4
|
+
EngineTrackLane,
|
|
5
|
+
EngineTrackSend,
|
|
6
|
+
RealtimeEngine,
|
|
7
|
+
SidechainSourceKind,
|
|
8
|
+
} from '../index';
|
|
2
9
|
import { normalizeTrackLanes } from './engine-offline';
|
|
3
|
-
import { buildMixerLanes } from './engine-sync';
|
|
4
|
-
import type { SonareEngineSyncMessage } from './messages';
|
|
10
|
+
import { buildMixerLanes, resolveTargetId } from './engine-sync';
|
|
11
|
+
import type { SonareEngineSyncMessage, SonareEngineSyncMixerInsertParamOverride } from './messages';
|
|
5
12
|
import { ENGINE_MIXER_PARAM_FADER_DB, engineMixerBusTarget } from './protocol';
|
|
6
13
|
|
|
14
|
+
/** A latest by-name insert value retained across a later topology replay. */
|
|
15
|
+
export interface InsertParamOverride {
|
|
16
|
+
target: StripJsonTarget;
|
|
17
|
+
insertIndex: number;
|
|
18
|
+
paramName: string;
|
|
19
|
+
value: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type InsertParamOverrideMap = Map<string, InsertParamOverride>;
|
|
23
|
+
|
|
7
24
|
/**
|
|
8
25
|
* Collaborator surface the mixer/routing setters need from the owning
|
|
9
26
|
* {@link SonareEngine}: the offline engine they mirror writes into, the mutable
|
|
@@ -15,38 +32,507 @@ export interface EngineMixerContext {
|
|
|
15
32
|
readonly trackLaneIds: number[];
|
|
16
33
|
readonly trackSends: Map<number, EngineTrackSend[]>;
|
|
17
34
|
readonly trackOutputBus: Map<number, number>;
|
|
35
|
+
readonly trackSourceChannelLayout: Map<number, number>;
|
|
18
36
|
readonly laneSidechains: Map<
|
|
19
37
|
string,
|
|
20
38
|
{ trackId: number; insertIndex: number; sourceTrackId: number }
|
|
21
39
|
>;
|
|
40
|
+
readonly busSidechains: Map<
|
|
41
|
+
string,
|
|
42
|
+
{ busId: number; insertIndex: number; sourceKind: number; sourceId: number }
|
|
43
|
+
>;
|
|
44
|
+
readonly masterSidechains: Map<
|
|
45
|
+
number,
|
|
46
|
+
{ insertIndex: number; sourceKind: number; sourceId: number }
|
|
47
|
+
>;
|
|
22
48
|
readonly buses: EngineBus[];
|
|
23
49
|
readonly trackStripJson: Map<number, string>;
|
|
24
50
|
readonly busStripJson: Map<number, string>;
|
|
51
|
+
readonly insertParamOverrides: InsertParamOverrideMap;
|
|
52
|
+
clearInsertAutomationLanes(target: StripJsonTarget, forgetResolvedIds?: boolean): void;
|
|
53
|
+
flushOfflineMirror(): void;
|
|
25
54
|
postSync(message: SonareEngineSyncMessage): void;
|
|
26
55
|
ensureTrackLane(target: string | number): number;
|
|
27
56
|
ensureBus(busId: number): number;
|
|
28
57
|
mixerLanes(): EngineTrackLane[];
|
|
29
|
-
syncMixer(
|
|
58
|
+
syncMixer(
|
|
59
|
+
insertBaseResets?: StripJsonTarget[],
|
|
60
|
+
oneShotInsertParamOverrides?: InsertParamOverride[],
|
|
61
|
+
): void;
|
|
30
62
|
sendSmoothedParam(paramId: number, value: number): boolean;
|
|
31
63
|
getMasterStripJson(): string | undefined;
|
|
64
|
+
cacheMasterStripJson(sceneJson: string): void;
|
|
32
65
|
}
|
|
33
66
|
|
|
34
|
-
/**
|
|
35
|
-
export
|
|
36
|
-
|
|
67
|
+
/** Addresses one strip's cached scene JSON, the state a syncMixer re-post replays. */
|
|
68
|
+
export type StripJsonTarget =
|
|
69
|
+
| { kind: 'track'; trackId: number }
|
|
70
|
+
| { kind: 'bus'; busId: number }
|
|
71
|
+
| { kind: 'master' };
|
|
72
|
+
|
|
73
|
+
type SceneObject = Record<string, unknown>;
|
|
74
|
+
|
|
75
|
+
function isSceneObject(value: unknown): value is SceneObject {
|
|
76
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function hasOwn(object: SceneObject, key: string): boolean {
|
|
80
|
+
return Object.getOwnPropertyDescriptor(object, key) !== undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Canonicalizes JSON objects without changing array order or string contents. */
|
|
84
|
+
function canonicalJson(value: unknown): string {
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
return `[${value.map((item) => canonicalJson(item)).join(',')}]`;
|
|
87
|
+
}
|
|
88
|
+
if (value !== null && typeof value === 'object') {
|
|
89
|
+
const object = value as SceneObject;
|
|
90
|
+
return `{${Object.keys(object)
|
|
91
|
+
.sort()
|
|
92
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`)
|
|
93
|
+
.join(',')}}`;
|
|
94
|
+
}
|
|
95
|
+
return JSON.stringify(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function aliasedValue(
|
|
99
|
+
object: SceneObject,
|
|
100
|
+
primary: string,
|
|
101
|
+
legacy: string,
|
|
102
|
+
): { present: boolean; value: unknown } {
|
|
103
|
+
if (hasOwn(object, primary)) {
|
|
104
|
+
return { present: true, value: object[primary] };
|
|
105
|
+
}
|
|
106
|
+
if (hasOwn(object, legacy)) {
|
|
107
|
+
return { present: true, value: object[legacy] };
|
|
108
|
+
}
|
|
109
|
+
return { present: false, value: undefined };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function insertSignature(insert: unknown): string {
|
|
113
|
+
const object = insert !== null && typeof insert === 'object' ? (insert as SceneObject) : {};
|
|
114
|
+
const slot = object.slot === undefined ? 'pre' : object.slot;
|
|
115
|
+
const processor = aliasedValue(object, 'processor', 'processor_name');
|
|
116
|
+
const params = aliasedValue(object, 'params', 'params_json');
|
|
117
|
+
const sidechain = aliasedValue(object, 'sidechainKey', 'sidechain_key');
|
|
118
|
+
const processorValue = typeof processor.value === 'string' ? processor.value : '';
|
|
119
|
+
const sidechainValue = typeof sidechain.value === 'string' ? sidechain.value : '';
|
|
120
|
+
let paramsValue: unknown;
|
|
121
|
+
// A missing params bag and an empty legacy blob decode alike; "{}" is distinct.
|
|
122
|
+
if (!params.present) {
|
|
123
|
+
paramsValue = ['absent'];
|
|
124
|
+
} else if (typeof params.value === 'string') {
|
|
125
|
+
// Canonicalize legacy text like object params; invalid text stays byte-exact.
|
|
126
|
+
if (params.value.length === 0) {
|
|
127
|
+
paramsValue = ['absent'];
|
|
128
|
+
} else {
|
|
129
|
+
try {
|
|
130
|
+
paramsValue = ['params', canonicalJson(JSON.parse(params.value))];
|
|
131
|
+
} catch {
|
|
132
|
+
paramsValue = ['params-invalid', params.value];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
paramsValue = ['params', canonicalJson(params.value)];
|
|
137
|
+
}
|
|
138
|
+
return JSON.stringify([slot, processorValue, paramsValue, sidechainValue]);
|
|
37
139
|
}
|
|
38
140
|
|
|
39
141
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
142
|
+
* Returns the insert identity used to decide whether native state can stay
|
|
143
|
+
* alive across a strip-scene update. Scalar strip fields are intentionally
|
|
144
|
+
* excluded. Track and master inserts use the native pre-then-post indexing;
|
|
145
|
+
* buses retain their scene order.
|
|
42
146
|
*/
|
|
43
|
-
export function
|
|
44
|
-
const
|
|
147
|
+
export function decodedInsertSignature(sceneJson: string, target: StripJsonTarget): string {
|
|
148
|
+
const scene = JSON.parse(sceneJson) as {
|
|
149
|
+
strips?: unknown[];
|
|
150
|
+
buses?: unknown[];
|
|
151
|
+
};
|
|
152
|
+
const entries = target.kind === 'bus' ? scene.buses : scene.strips;
|
|
153
|
+
const strip = Array.isArray(entries) ? entries.find(isSceneObject) : undefined;
|
|
154
|
+
const object = isSceneObject(strip) ? strip : {};
|
|
155
|
+
const inserts = Array.isArray(object.inserts) ? object.inserts.filter(isSceneObject) : [];
|
|
156
|
+
const ordered =
|
|
157
|
+
target.kind === 'bus'
|
|
158
|
+
? inserts
|
|
159
|
+
: [
|
|
160
|
+
...inserts.filter((insert) => insert.slot !== 'post'),
|
|
161
|
+
...inserts.filter((insert) => insert.slot === 'post'),
|
|
162
|
+
];
|
|
163
|
+
return JSON.stringify(ordered.map(insertSignature));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function constructionValue(
|
|
167
|
+
engine: RealtimeEngine,
|
|
168
|
+
target: StripJsonTarget,
|
|
169
|
+
override: InsertParamOverride,
|
|
170
|
+
): number | undefined {
|
|
171
|
+
let parameterId = -1;
|
|
172
|
+
switch (target.kind) {
|
|
173
|
+
case 'track':
|
|
174
|
+
parameterId = engine.resolveTrackInsertAutomationId(
|
|
175
|
+
target.trackId,
|
|
176
|
+
override.insertIndex,
|
|
177
|
+
override.paramName,
|
|
178
|
+
);
|
|
179
|
+
break;
|
|
180
|
+
case 'bus':
|
|
181
|
+
parameterId = engine.resolveBusInsertAutomationId(
|
|
182
|
+
target.busId,
|
|
183
|
+
override.insertIndex,
|
|
184
|
+
override.paramName,
|
|
185
|
+
);
|
|
186
|
+
break;
|
|
187
|
+
case 'master':
|
|
188
|
+
parameterId = engine.resolveMasterInsertAutomationId(
|
|
189
|
+
override.insertIndex,
|
|
190
|
+
override.paramName,
|
|
191
|
+
);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (parameterId < 0) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
return engine.insertParameterConstructedValue(parameterId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Builds one-shot restores for manual insert edits before their bookkeeping is forgotten. */
|
|
201
|
+
export function constructionInsertOverrides(
|
|
202
|
+
engine: RealtimeEngine,
|
|
203
|
+
target: StripJsonTarget,
|
|
204
|
+
overrides: InsertParamOverrideMap,
|
|
205
|
+
): InsertParamOverride[] {
|
|
206
|
+
const restored: InsertParamOverride[] = [];
|
|
207
|
+
for (const override of overrides.values()) {
|
|
208
|
+
if (!sameStripTarget(override.target, target)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const value = constructionValue(engine, target, override);
|
|
212
|
+
if (value !== undefined) {
|
|
213
|
+
restored.push({ ...override, value });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return restored;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function applyInsertParamOverride(engine: RealtimeEngine, override: InsertParamOverride): void {
|
|
220
|
+
switch (override.target.kind) {
|
|
221
|
+
case 'track':
|
|
222
|
+
engine.restoreTrackStripInsertParamByName(
|
|
223
|
+
override.target.trackId,
|
|
224
|
+
override.insertIndex,
|
|
225
|
+
override.paramName,
|
|
226
|
+
override.value,
|
|
227
|
+
);
|
|
228
|
+
break;
|
|
229
|
+
case 'bus':
|
|
230
|
+
engine.restoreBusStripInsertParamByName(
|
|
231
|
+
override.target.busId,
|
|
232
|
+
override.insertIndex,
|
|
233
|
+
override.paramName,
|
|
234
|
+
override.value,
|
|
235
|
+
);
|
|
236
|
+
break;
|
|
237
|
+
case 'master':
|
|
238
|
+
engine.restoreMasterStripInsertParamByName(
|
|
239
|
+
override.insertIndex,
|
|
240
|
+
override.paramName,
|
|
241
|
+
override.value,
|
|
242
|
+
);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Applies construction restores to the control-side mirror before posting them. */
|
|
248
|
+
export function applyConstructionInsertOverrides(
|
|
249
|
+
engine: RealtimeEngine,
|
|
250
|
+
overrides: readonly InsertParamOverride[],
|
|
251
|
+
): void {
|
|
252
|
+
for (const override of overrides) {
|
|
253
|
+
applyInsertParamOverride(engine, override);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Clears the native manual parameter bases for one strip after a replacement. */
|
|
258
|
+
export function clearInsertParameterBases(engine: RealtimeEngine, target: StripJsonTarget): void {
|
|
259
|
+
switch (target.kind) {
|
|
260
|
+
case 'track':
|
|
261
|
+
engine.clearTrackInsertParameterBases(target.trackId);
|
|
262
|
+
break;
|
|
263
|
+
case 'bus':
|
|
264
|
+
engine.clearBusInsertParameterBases(target.busId);
|
|
265
|
+
break;
|
|
266
|
+
case 'master':
|
|
267
|
+
engine.clearMasterInsertParameterBases();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
interface MixerRoutingDraft {
|
|
273
|
+
trackLaneIds: number[];
|
|
274
|
+
trackSends: Map<number, EngineTrackSend[]>;
|
|
275
|
+
trackOutputBus: Map<number, number>;
|
|
276
|
+
trackSourceChannelLayout: Map<number, number>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** An empty construction scene used only within an explicit strip replacement. */
|
|
280
|
+
export function emptyStripJson(target: StripJsonTarget): string {
|
|
281
|
+
switch (target.kind) {
|
|
282
|
+
case 'track':
|
|
283
|
+
return `{"version":1,"strips":[{"id":"track-${target.trackId}"}],"buses":[],"connections":[]}`;
|
|
284
|
+
case 'bus':
|
|
285
|
+
return `{"version":1,"strips":[],"buses":[{"id":"bus-${target.busId}"}],"connections":[]}`;
|
|
286
|
+
case 'master':
|
|
287
|
+
return '{"version":1,"strips":[{"id":"master"}],"buses":[],"connections":[]}';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Drop bindings whose insert was removed by an explicit scene replacement. */
|
|
292
|
+
export function pruneStripSidechains(
|
|
293
|
+
ctx: EngineMixerContext,
|
|
294
|
+
target: StripJsonTarget,
|
|
295
|
+
sceneJson: string,
|
|
296
|
+
): void {
|
|
297
|
+
const scene = JSON.parse(sceneJson) as {
|
|
298
|
+
strips?: Array<{ inserts?: unknown[] }>;
|
|
299
|
+
buses?: Array<{ inserts?: unknown[] }>;
|
|
300
|
+
};
|
|
301
|
+
const entry = target.kind === 'bus' ? scene.buses?.[0] : scene.strips?.[0];
|
|
302
|
+
const insertCount = Array.isArray(entry?.inserts) ? entry.inserts.length : 0;
|
|
303
|
+
if (target.kind === 'track') {
|
|
304
|
+
for (const [key, binding] of ctx.laneSidechains) {
|
|
305
|
+
if (binding.trackId === target.trackId && binding.insertIndex >= insertCount) {
|
|
306
|
+
ctx.laneSidechains.delete(key);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} else if (target.kind === 'bus') {
|
|
310
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
311
|
+
if (binding.busId === target.busId && binding.insertIndex >= insertCount) {
|
|
312
|
+
ctx.busSidechains.delete(key);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
317
|
+
if (binding.insertIndex >= insertCount) {
|
|
318
|
+
ctx.masterSidechains.delete(key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function hasInsertParamOverrides(
|
|
325
|
+
overrides: InsertParamOverrideMap,
|
|
326
|
+
target: StripJsonTarget,
|
|
327
|
+
): boolean {
|
|
328
|
+
for (const override of overrides.values()) {
|
|
329
|
+
if (sameStripTarget(override.target, target)) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Apply and validate one candidate scene without rebuilding it through an empty scene. */
|
|
337
|
+
export function applyFullStripJson(
|
|
338
|
+
engine: RealtimeEngine,
|
|
339
|
+
target: StripJsonTarget,
|
|
340
|
+
sceneJson: string,
|
|
341
|
+
): void {
|
|
342
|
+
const apply = (json: string): void => {
|
|
343
|
+
switch (target.kind) {
|
|
344
|
+
case 'track':
|
|
345
|
+
engine.setTrackStripJson(target.trackId, json);
|
|
346
|
+
break;
|
|
347
|
+
case 'bus':
|
|
348
|
+
engine.setBusStripJson(target.busId, json);
|
|
349
|
+
break;
|
|
350
|
+
case 'master':
|
|
351
|
+
engine.setMasterStripJson(json);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
engine.applyCommandsDueNowPreservingFuture();
|
|
356
|
+
apply(sceneJson);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Builds a collision-safe key for one target/insert/parameter tuple. */
|
|
360
|
+
export function insertParamOverrideKey(override: InsertParamOverride): string {
|
|
361
|
+
const targetId =
|
|
362
|
+
override.target.kind === 'track'
|
|
363
|
+
? override.target.trackId
|
|
364
|
+
: override.target.kind === 'bus'
|
|
365
|
+
? override.target.busId
|
|
366
|
+
: 'master';
|
|
367
|
+
return JSON.stringify([override.target.kind, targetId, override.insertIndex, override.paramName]);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Removes all retained insert values for one strip after a full scene replacement. */
|
|
371
|
+
export function clearInsertParamOverrides(
|
|
372
|
+
overrides: InsertParamOverrideMap,
|
|
373
|
+
target: StripJsonTarget,
|
|
374
|
+
): void {
|
|
375
|
+
for (const [key, override] of overrides) {
|
|
376
|
+
if (sameStripTarget(override.target, target)) {
|
|
377
|
+
overrides.delete(key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function sameStripTarget(left: StripJsonTarget, right: StripJsonTarget): boolean {
|
|
383
|
+
if (left.kind !== right.kind) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
if (left.kind === 'track' && right.kind === 'track') {
|
|
387
|
+
return left.trackId === right.trackId;
|
|
388
|
+
}
|
|
389
|
+
if (left.kind === 'bus' && right.kind === 'bus') {
|
|
390
|
+
return left.busId === right.busId;
|
|
391
|
+
}
|
|
392
|
+
return left.kind === 'master' && right.kind === 'master';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function flattenInsertParamOverride(
|
|
396
|
+
override: InsertParamOverride,
|
|
397
|
+
): SonareEngineSyncMixerInsertParamOverride {
|
|
398
|
+
switch (override.target.kind) {
|
|
399
|
+
case 'track':
|
|
400
|
+
return {
|
|
401
|
+
kind: 'track',
|
|
402
|
+
trackId: override.target.trackId,
|
|
403
|
+
insertIndex: override.insertIndex,
|
|
404
|
+
paramName: override.paramName,
|
|
405
|
+
value: override.value,
|
|
406
|
+
};
|
|
407
|
+
case 'bus':
|
|
408
|
+
return {
|
|
409
|
+
kind: 'bus',
|
|
410
|
+
busId: override.target.busId,
|
|
411
|
+
insertIndex: override.insertIndex,
|
|
412
|
+
paramName: override.paramName,
|
|
413
|
+
value: override.value,
|
|
414
|
+
};
|
|
415
|
+
case 'master':
|
|
416
|
+
return {
|
|
417
|
+
kind: 'master',
|
|
418
|
+
insertIndex: override.insertIndex,
|
|
419
|
+
paramName: override.paramName,
|
|
420
|
+
value: override.value,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Reads a strip's cached scene JSON; undefined when none was ever set. */
|
|
426
|
+
export function cachedStripJson(
|
|
427
|
+
ctx: EngineMixerContext,
|
|
428
|
+
target: StripJsonTarget,
|
|
429
|
+
): string | undefined {
|
|
430
|
+
switch (target.kind) {
|
|
431
|
+
case 'track':
|
|
432
|
+
return ctx.trackStripJson.get(target.trackId);
|
|
433
|
+
case 'bus':
|
|
434
|
+
return ctx.busStripJson.get(target.busId);
|
|
435
|
+
case 'master':
|
|
436
|
+
return ctx.getMasterStripJson();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Replaces a strip's cached scene JSON without syncing it. */
|
|
441
|
+
export function cacheStripJson(
|
|
442
|
+
ctx: EngineMixerContext,
|
|
443
|
+
target: StripJsonTarget,
|
|
444
|
+
sceneJson: string,
|
|
445
|
+
): void {
|
|
446
|
+
switch (target.kind) {
|
|
447
|
+
case 'track':
|
|
448
|
+
ctx.trackStripJson.set(target.trackId, sceneJson);
|
|
449
|
+
return;
|
|
450
|
+
case 'bus':
|
|
451
|
+
ctx.busStripJson.set(target.busId, sceneJson);
|
|
452
|
+
return;
|
|
453
|
+
case 'master':
|
|
454
|
+
ctx.cacheMasterStripJson(sceneJson);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Builds the engine's track-lane descriptors from the current routing stores. */
|
|
460
|
+
export function mixerLanes(ctx: EngineMixerContext): EngineTrackLane[] {
|
|
461
|
+
return buildMixerLanes(
|
|
462
|
+
ctx.trackLaneIds,
|
|
463
|
+
ctx.trackSends,
|
|
464
|
+
ctx.trackOutputBus,
|
|
465
|
+
ctx.trackSourceChannelLayout,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function cloneMixerRouting(ctx: EngineMixerContext): MixerRoutingDraft {
|
|
470
|
+
return {
|
|
471
|
+
trackLaneIds: [...ctx.trackLaneIds],
|
|
472
|
+
trackSends: new Map(
|
|
473
|
+
Array.from(ctx.trackSends, ([trackId, sends]) => [
|
|
474
|
+
trackId,
|
|
475
|
+
sends.map((send) => ({ ...send })),
|
|
476
|
+
]),
|
|
477
|
+
),
|
|
478
|
+
trackOutputBus: new Map(ctx.trackOutputBus),
|
|
479
|
+
trackSourceChannelLayout: new Map(ctx.trackSourceChannelLayout),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function commitMixerRouting(ctx: EngineMixerContext, draft: MixerRoutingDraft): void {
|
|
484
|
+
ctx.trackLaneIds.splice(0, ctx.trackLaneIds.length, ...draft.trackLaneIds);
|
|
485
|
+
ctx.trackSends.clear();
|
|
486
|
+
for (const [trackId, sends] of draft.trackSends) {
|
|
487
|
+
ctx.trackSends.set(
|
|
488
|
+
trackId,
|
|
489
|
+
sends.map((send) => ({ ...send })),
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
ctx.trackOutputBus.clear();
|
|
493
|
+
for (const [trackId, busId] of draft.trackOutputBus) {
|
|
494
|
+
ctx.trackOutputBus.set(trackId, busId);
|
|
495
|
+
}
|
|
496
|
+
ctx.trackSourceChannelLayout.clear();
|
|
497
|
+
for (const [trackId, layout] of draft.trackSourceChannelLayout) {
|
|
498
|
+
ctx.trackSourceChannelLayout.set(trackId, layout);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function buildDraftMixerLanes(draft: MixerRoutingDraft): EngineTrackLane[] {
|
|
503
|
+
return buildMixerLanes(
|
|
504
|
+
draft.trackLaneIds,
|
|
505
|
+
draft.trackSends,
|
|
506
|
+
draft.trackOutputBus,
|
|
507
|
+
draft.trackSourceChannelLayout,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function applyMixerRouting(
|
|
512
|
+
ctx: EngineMixerContext,
|
|
513
|
+
draft: MixerRoutingDraft,
|
|
514
|
+
busesAlreadyApplied: boolean,
|
|
515
|
+
): { lanes: EngineTrackLane[]; buses: EngineBus[] } {
|
|
516
|
+
const lanes = buildDraftMixerLanes(draft);
|
|
45
517
|
const buses = ctx.buses.map((bus) => ({ ...bus }));
|
|
46
|
-
|
|
518
|
+
if (!busesAlreadyApplied) {
|
|
519
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
520
|
+
}
|
|
47
521
|
if (lanes.length > 0) {
|
|
522
|
+
// Not settled: that would snap unrelated insert ramps on a scalar edit.
|
|
48
523
|
ctx.offlineEngine.setTrackLanes(lanes);
|
|
49
524
|
}
|
|
525
|
+
return { lanes, buses };
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function postMixerSync(
|
|
529
|
+
ctx: EngineMixerContext,
|
|
530
|
+
lanes: EngineTrackLane[],
|
|
531
|
+
buses: EngineBus[],
|
|
532
|
+
insertBaseResets: StripJsonTarget[],
|
|
533
|
+
oneShotInsertParamOverrides: InsertParamOverride[],
|
|
534
|
+
): void {
|
|
535
|
+
replayInsertParamOverrides(ctx, lanes, buses);
|
|
50
536
|
const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
|
|
51
537
|
trackId,
|
|
52
538
|
sceneJson,
|
|
@@ -55,6 +541,10 @@ export function syncMixer(ctx: EngineMixerContext): void {
|
|
|
55
541
|
busId,
|
|
56
542
|
sceneJson,
|
|
57
543
|
}));
|
|
544
|
+
const insertParamOverrides = [
|
|
545
|
+
...Array.from(ctx.insertParamOverrides.values(), flattenInsertParamOverride),
|
|
546
|
+
...oneShotInsertParamOverrides.map(flattenInsertParamOverride),
|
|
547
|
+
];
|
|
58
548
|
ctx.postSync({
|
|
59
549
|
type: 'syncMixer',
|
|
60
550
|
lanes,
|
|
@@ -63,9 +553,94 @@ export function syncMixer(ctx: EngineMixerContext): void {
|
|
|
63
553
|
laneSidechains: Array.from(ctx.laneSidechains.values()),
|
|
64
554
|
busStrips,
|
|
65
555
|
masterStripJson: ctx.getMasterStripJson(),
|
|
556
|
+
busSidechains: Array.from(ctx.busSidechains.values()),
|
|
557
|
+
masterSidechains: Array.from(ctx.masterSidechains.values()),
|
|
558
|
+
...(insertParamOverrides.length > 0 ? { insertParamOverrides } : {}),
|
|
559
|
+
...(insertBaseResets.length > 0 ? { insertBaseResets } : {}),
|
|
66
560
|
});
|
|
67
561
|
}
|
|
68
562
|
|
|
563
|
+
function syncMixerDraft(
|
|
564
|
+
ctx: EngineMixerContext,
|
|
565
|
+
draft: MixerRoutingDraft,
|
|
566
|
+
busesAlreadyApplied = false,
|
|
567
|
+
insertBaseResets: StripJsonTarget[] = [],
|
|
568
|
+
oneShotInsertParamOverrides: InsertParamOverride[] = [],
|
|
569
|
+
): void {
|
|
570
|
+
const { lanes, buses } = applyMixerRouting(ctx, draft, busesAlreadyApplied);
|
|
571
|
+
commitMixerRouting(ctx, draft);
|
|
572
|
+
postMixerSync(ctx, lanes, buses, insertBaseResets, oneShotInsertParamOverrides);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Mirrors the current mixer routing into the offline engine and posts the full
|
|
577
|
+
* mixer-sync message (lanes, buses, strip JSON, sidechains) to the worklet.
|
|
578
|
+
*/
|
|
579
|
+
export function syncMixer(
|
|
580
|
+
ctx: EngineMixerContext,
|
|
581
|
+
busesAlreadyApplied = false,
|
|
582
|
+
insertBaseResets: StripJsonTarget[] = [],
|
|
583
|
+
oneShotInsertParamOverrides: InsertParamOverride[] = [],
|
|
584
|
+
): void {
|
|
585
|
+
syncMixerDraft(
|
|
586
|
+
ctx,
|
|
587
|
+
cloneMixerRouting(ctx),
|
|
588
|
+
busesAlreadyApplied,
|
|
589
|
+
insertBaseResets,
|
|
590
|
+
oneShotInsertParamOverrides,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Reapplies retained by-name values after native topology setters clear their
|
|
596
|
+
* smoother slots. This runs once per mixer sync, never once per knob tick.
|
|
597
|
+
*/
|
|
598
|
+
export function replayInsertParamOverrides(
|
|
599
|
+
ctx: EngineMixerContext,
|
|
600
|
+
lanes = mixerLanes(ctx),
|
|
601
|
+
buses = ctx.buses,
|
|
602
|
+
): void {
|
|
603
|
+
const activeTrackIds = new Set(lanes.map((lane) => lane.trackId));
|
|
604
|
+
const activeBusIds = new Set(buses.map((bus) => bus.busId));
|
|
605
|
+
for (const [key, override] of ctx.insertParamOverrides) {
|
|
606
|
+
const active =
|
|
607
|
+
override.target.kind === 'track'
|
|
608
|
+
? activeTrackIds.has(override.target.trackId)
|
|
609
|
+
: override.target.kind === 'bus'
|
|
610
|
+
? activeBusIds.has(override.target.busId)
|
|
611
|
+
: true;
|
|
612
|
+
if (!active) {
|
|
613
|
+
ctx.insertParamOverrides.delete(key);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
switch (override.target.kind) {
|
|
617
|
+
case 'track':
|
|
618
|
+
ctx.offlineEngine.restoreTrackStripInsertParamByName(
|
|
619
|
+
override.target.trackId,
|
|
620
|
+
override.insertIndex,
|
|
621
|
+
override.paramName,
|
|
622
|
+
override.value,
|
|
623
|
+
);
|
|
624
|
+
break;
|
|
625
|
+
case 'bus':
|
|
626
|
+
ctx.offlineEngine.restoreBusStripInsertParamByName(
|
|
627
|
+
override.target.busId,
|
|
628
|
+
override.insertIndex,
|
|
629
|
+
override.paramName,
|
|
630
|
+
override.value,
|
|
631
|
+
);
|
|
632
|
+
break;
|
|
633
|
+
case 'master':
|
|
634
|
+
ctx.offlineEngine.restoreMasterStripInsertParamByName(
|
|
635
|
+
override.insertIndex,
|
|
636
|
+
override.paramName,
|
|
637
|
+
override.value,
|
|
638
|
+
);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
69
644
|
/**
|
|
70
645
|
* Declares the mixer track lanes in an explicit order.
|
|
71
646
|
*
|
|
@@ -74,6 +649,7 @@ export function syncMixer(ctx: EngineMixerContext): void {
|
|
|
74
649
|
* with the already-declared lane ids in their current order and may only
|
|
75
650
|
* append new track ids after them. Entries carrying `sends` replace that
|
|
76
651
|
* track's send list; entries without `sends` leave existing sends untouched.
|
|
652
|
+
* The same omission rule preserves an existing source channel layout.
|
|
77
653
|
*
|
|
78
654
|
* @param lanes Track ids or lane descriptors in the desired lane order.
|
|
79
655
|
*/
|
|
@@ -81,24 +657,51 @@ export function setTrackLanes(
|
|
|
81
657
|
ctx: EngineMixerContext,
|
|
82
658
|
lanes: ReadonlyArray<number | EngineTrackLane>,
|
|
83
659
|
): void {
|
|
84
|
-
const
|
|
660
|
+
const draft = cloneMixerRouting(ctx);
|
|
661
|
+
const { entries, ids } = normalizeTrackLanes(draft.trackLaneIds, lanes);
|
|
85
662
|
for (const entry of entries) {
|
|
86
663
|
if (entry.sends) {
|
|
87
|
-
|
|
664
|
+
draft.trackSends.set(
|
|
88
665
|
entry.trackId,
|
|
89
666
|
entry.sends.map((send) => ({ ...send })),
|
|
90
667
|
);
|
|
91
668
|
}
|
|
92
669
|
if (entry.outputBusId !== undefined) {
|
|
93
670
|
if (entry.outputBusId === 0) {
|
|
94
|
-
|
|
671
|
+
draft.trackOutputBus.delete(entry.trackId);
|
|
95
672
|
} else {
|
|
96
|
-
|
|
673
|
+
draft.trackOutputBus.set(entry.trackId, entry.outputBusId);
|
|
97
674
|
}
|
|
98
675
|
}
|
|
676
|
+
if (entry.sourceChannelLayout !== undefined) {
|
|
677
|
+
draft.trackSourceChannelLayout.set(entry.trackId, entry.sourceChannelLayout);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
draft.trackLaneIds = ids;
|
|
681
|
+
syncMixerDraft(ctx, draft, true);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function ensureTrackLaneInDraft(draft: MixerRoutingDraft, target: string | number): number {
|
|
685
|
+
const trackId = resolveTargetId(target);
|
|
686
|
+
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
687
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
|
|
99
688
|
}
|
|
100
|
-
|
|
101
|
-
|
|
689
|
+
const existing = draft.trackLaneIds.indexOf(trackId);
|
|
690
|
+
if (existing >= 0) {
|
|
691
|
+
return existing;
|
|
692
|
+
}
|
|
693
|
+
draft.trackLaneIds.push(trackId);
|
|
694
|
+
return draft.trackLaneIds.length - 1;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** Ensures a lane exists, committing it only after native routing accepts it. */
|
|
698
|
+
export function ensureTrackLane(ctx: EngineMixerContext, target: string | number): number {
|
|
699
|
+
const draft = cloneMixerRouting(ctx);
|
|
700
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
701
|
+
if (draft.trackLaneIds.length !== ctx.trackLaneIds.length) {
|
|
702
|
+
syncMixerDraft(ctx, draft, true);
|
|
703
|
+
}
|
|
704
|
+
return laneIndex;
|
|
102
705
|
}
|
|
103
706
|
|
|
104
707
|
/**
|
|
@@ -110,14 +713,15 @@ export function setTrackOutputBus(
|
|
|
110
713
|
target: string | number,
|
|
111
714
|
busId: number,
|
|
112
715
|
): void {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
716
|
+
const draft = cloneMixerRouting(ctx);
|
|
717
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
718
|
+
const trackId = draft.trackLaneIds[laneIndex];
|
|
115
719
|
if (busId === 0) {
|
|
116
|
-
|
|
720
|
+
draft.trackOutputBus.delete(trackId);
|
|
117
721
|
} else {
|
|
118
|
-
|
|
722
|
+
draft.trackOutputBus.set(trackId, busId);
|
|
119
723
|
}
|
|
120
|
-
ctx
|
|
724
|
+
syncMixerDraft(ctx, draft, true);
|
|
121
725
|
}
|
|
122
726
|
|
|
123
727
|
/**
|
|
@@ -139,48 +743,212 @@ export function setLaneSidechain(
|
|
|
139
743
|
const sourceIndex = ctx.ensureTrackLane(sourceTarget);
|
|
140
744
|
sourceTrackId = ctx.trackLaneIds[sourceIndex];
|
|
141
745
|
}
|
|
746
|
+
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
142
747
|
if (sourceTrackId === 0) {
|
|
143
748
|
ctx.laneSidechains.delete(key);
|
|
144
749
|
} else {
|
|
145
750
|
ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
146
751
|
}
|
|
147
|
-
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
148
752
|
ctx.postSync({
|
|
149
753
|
type: 'syncMixer',
|
|
754
|
+
sidechainDelta: true,
|
|
150
755
|
lanes: ctx.mixerLanes(),
|
|
151
756
|
laneSidechains: [{ trackId, insertIndex, sourceTrackId }],
|
|
152
757
|
});
|
|
153
758
|
}
|
|
154
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Keys one insert of a bus strip from a track lane or another bus
|
|
762
|
+
* (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding. The
|
|
763
|
+
* binding is cached for resync only once the engine has accepted it.
|
|
764
|
+
*/
|
|
765
|
+
export function setBusSidechain(
|
|
766
|
+
ctx: EngineMixerContext,
|
|
767
|
+
busId: number,
|
|
768
|
+
insertIndex: number,
|
|
769
|
+
kind: SidechainSourceKind | number,
|
|
770
|
+
sourceId: number,
|
|
771
|
+
): void {
|
|
772
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
773
|
+
ctx.ensureBus(busId);
|
|
774
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
775
|
+
ctx.offlineEngine.setBusSidechain(busId, insertIndex, sourceKind, sourceId);
|
|
776
|
+
const key = `${busId}:${insertIndex}`;
|
|
777
|
+
if (sourceId === 0) {
|
|
778
|
+
ctx.busSidechains.delete(key);
|
|
779
|
+
} else {
|
|
780
|
+
ctx.busSidechains.set(key, { busId, insertIndex, sourceKind, sourceId });
|
|
781
|
+
}
|
|
782
|
+
ctx.postSync({
|
|
783
|
+
type: 'syncMixer',
|
|
784
|
+
sidechainDelta: true,
|
|
785
|
+
lanes: ctx.mixerLanes(),
|
|
786
|
+
busSidechains: [{ busId, insertIndex, sourceKind, sourceId }],
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Keys one insert of the master strip from a track lane or a bus. Same source
|
|
792
|
+
* rules as {@link setBusSidechain}.
|
|
793
|
+
*/
|
|
794
|
+
export function setMasterSidechain(
|
|
795
|
+
ctx: EngineMixerContext,
|
|
796
|
+
insertIndex: number,
|
|
797
|
+
kind: SidechainSourceKind | number,
|
|
798
|
+
sourceId: number,
|
|
799
|
+
): void {
|
|
800
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
801
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
802
|
+
ctx.offlineEngine.setMasterSidechain(insertIndex, sourceKind, sourceId);
|
|
803
|
+
if (sourceId === 0) {
|
|
804
|
+
ctx.masterSidechains.delete(insertIndex);
|
|
805
|
+
} else {
|
|
806
|
+
ctx.masterSidechains.set(insertIndex, { insertIndex, sourceKind, sourceId });
|
|
807
|
+
}
|
|
808
|
+
ctx.postSync({
|
|
809
|
+
type: 'syncMixer',
|
|
810
|
+
sidechainDelta: true,
|
|
811
|
+
lanes: ctx.mixerLanes(),
|
|
812
|
+
masterSidechains: [{ insertIndex, sourceKind, sourceId }],
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function ensureSidechainSource(
|
|
817
|
+
ctx: EngineMixerContext,
|
|
818
|
+
sourceKind: number,
|
|
819
|
+
sourceId: number,
|
|
820
|
+
): void {
|
|
821
|
+
if (sourceId === 0) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (sourceKind === 1) {
|
|
825
|
+
ctx.ensureBus(sourceId);
|
|
826
|
+
} else {
|
|
827
|
+
ctx.ensureTrackLane(sourceId);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
155
831
|
export function setSends(
|
|
156
832
|
ctx: EngineMixerContext,
|
|
157
833
|
target: string | number,
|
|
158
834
|
sends: EngineTrackSend[],
|
|
159
835
|
): void {
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
836
|
+
const draft = cloneMixerRouting(ctx);
|
|
837
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
838
|
+
const trackId = draft.trackLaneIds[laneIndex];
|
|
839
|
+
draft.trackSends.set(
|
|
163
840
|
trackId,
|
|
164
841
|
sends.map((send) => ({ ...send })),
|
|
165
842
|
);
|
|
166
|
-
ctx
|
|
843
|
+
syncMixerDraft(ctx, draft, true);
|
|
167
844
|
}
|
|
168
845
|
|
|
169
846
|
export function setTrackBuses(ctx: EngineMixerContext, buses: EngineBus[]): void {
|
|
847
|
+
// Validate and apply before pruning retained scenes. Native rejects a bus
|
|
848
|
+
// removal while a lane still routes to it; that failure must leave JS state
|
|
849
|
+
// untouched so a later valid edit can still replay the existing inserts.
|
|
850
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
851
|
+
const retainedBusIds = new Set(buses.map((bus) => bus.busId));
|
|
852
|
+
for (const bus of ctx.buses) {
|
|
853
|
+
if (!retainedBusIds.has(bus.busId)) {
|
|
854
|
+
ctx.clearInsertAutomationLanes({ kind: 'bus', busId: bus.busId }, true);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
170
857
|
ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
171
|
-
ctx.
|
|
858
|
+
const activeBusIds = new Set(ctx.buses.map((bus) => bus.busId));
|
|
859
|
+
for (const busId of ctx.busStripJson.keys()) {
|
|
860
|
+
if (!activeBusIds.has(busId)) {
|
|
861
|
+
ctx.busStripJson.delete(busId);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
865
|
+
if (
|
|
866
|
+
!activeBusIds.has(binding.busId) ||
|
|
867
|
+
(binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId))
|
|
868
|
+
) {
|
|
869
|
+
ctx.busSidechains.delete(key);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
873
|
+
if (binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
|
|
874
|
+
ctx.masterSidechains.delete(key);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
syncMixer(ctx, true);
|
|
172
878
|
}
|
|
173
879
|
|
|
174
880
|
export function setBusGain(ctx: EngineMixerContext, busId: number, db: number): boolean {
|
|
175
881
|
const busIndex = ctx.ensureBus(busId);
|
|
176
882
|
ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
|
|
177
|
-
|
|
883
|
+
// The reserved smoothed parameter updates both engines. Rebuilding the bus
|
|
884
|
+
// topology for every fader tick would reset insert slots and their tails.
|
|
178
885
|
return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
179
886
|
}
|
|
180
887
|
|
|
888
|
+
/** Control-side state a strip-scene replacement reads and updates. */
|
|
889
|
+
export interface StripSceneContext {
|
|
890
|
+
readonly offlineEngine: RealtimeEngine;
|
|
891
|
+
readonly insertParamOverrides: InsertParamOverrideMap;
|
|
892
|
+
clearInsertAutomationLanes(target: StripJsonTarget): void;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Applies a strip scene to the offline mirror and returns what the realtime
|
|
897
|
+
* sync must repeat: the targets whose manual insert bases it clears and the
|
|
898
|
+
* construction values it restores. A changed insert chain forgets manual edits;
|
|
899
|
+
* an equal chain with manual edits restores each edited parameter's
|
|
900
|
+
* construction value. Native state of equal inserts is kept either way.
|
|
901
|
+
*/
|
|
902
|
+
export function replaceStripScene(
|
|
903
|
+
ctx: StripSceneContext,
|
|
904
|
+
target: StripJsonTarget,
|
|
905
|
+
previousJson: string | undefined,
|
|
906
|
+
sceneJson: string,
|
|
907
|
+
): { insertBaseResets: StripJsonTarget[]; constructionOverrides: InsertParamOverride[] } {
|
|
908
|
+
const chainChanged =
|
|
909
|
+
previousJson !== undefined &&
|
|
910
|
+
decodedInsertSignature(previousJson, target) !== decodedInsertSignature(sceneJson, target);
|
|
911
|
+
const hasOverrides = hasInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
912
|
+
applyFullStripJson(ctx.offlineEngine, target, sceneJson);
|
|
913
|
+
if (!chainChanged && !hasOverrides) {
|
|
914
|
+
return { insertBaseResets: [], constructionOverrides: [] };
|
|
915
|
+
}
|
|
916
|
+
const constructionOverrides = chainChanged
|
|
917
|
+
? []
|
|
918
|
+
: constructionInsertOverrides(ctx.offlineEngine, target, ctx.insertParamOverrides);
|
|
919
|
+
ctx.clearInsertAutomationLanes(target);
|
|
920
|
+
clearInsertParameterBases(ctx.offlineEngine, target);
|
|
921
|
+
applyConstructionInsertOverrides(ctx.offlineEngine, constructionOverrides);
|
|
922
|
+
clearInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
923
|
+
return { insertBaseResets: [target], constructionOverrides };
|
|
924
|
+
}
|
|
925
|
+
|
|
181
926
|
export function setBusStripJson(ctx: EngineMixerContext, busId: number, sceneJson: string): void {
|
|
927
|
+
const target: StripJsonTarget = { kind: 'bus', busId };
|
|
928
|
+
// Parse before declaring a new bus so malformed text cannot declare one.
|
|
929
|
+
decodedInsertSignature(sceneJson, target);
|
|
182
930
|
ctx.ensureBus(busId);
|
|
183
|
-
|
|
931
|
+
const { insertBaseResets, constructionOverrides } = replaceStripScene(
|
|
932
|
+
ctx,
|
|
933
|
+
target,
|
|
934
|
+
ctx.busStripJson.get(busId),
|
|
935
|
+
sceneJson,
|
|
936
|
+
);
|
|
937
|
+
pruneStripSidechains(ctx, target, sceneJson);
|
|
184
938
|
ctx.busStripJson.set(busId, sceneJson);
|
|
185
|
-
ctx.syncMixer();
|
|
939
|
+
ctx.syncMixer(insertBaseResets, constructionOverrides);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/** Applies a master strip scene while retaining native state for equal inserts. */
|
|
943
|
+
export function setMasterStripJson(ctx: EngineMixerContext, sceneJson: string): void {
|
|
944
|
+
const target: StripJsonTarget = { kind: 'master' };
|
|
945
|
+
const { insertBaseResets, constructionOverrides } = replaceStripScene(
|
|
946
|
+
ctx,
|
|
947
|
+
target,
|
|
948
|
+
ctx.getMasterStripJson(),
|
|
949
|
+
sceneJson,
|
|
950
|
+
);
|
|
951
|
+
pruneStripSidechains(ctx, target, sceneJson);
|
|
952
|
+
ctx.cacheMasterStripJson(sceneJson);
|
|
953
|
+
ctx.syncMixer(insertBaseResets, constructionOverrides);
|
|
186
954
|
}
|