@libraz/libsonare 1.7.2 → 1.8.0

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.
Files changed (303) hide show
  1. package/NOTICE +178 -0
  2. package/README.md +26 -203
  3. package/dist/_chain_config.d.ts +14 -0
  4. package/dist/_chain_config.d.ts.map +1 -0
  5. package/dist/_effects_common.d.ts +6 -0
  6. package/dist/_effects_common.d.ts.map +1 -0
  7. package/dist/_feature_validation.d.ts +8 -0
  8. package/dist/_feature_validation.d.ts.map +1 -0
  9. package/dist/_fft_options.d.ts +24 -0
  10. package/dist/_fft_options.d.ts.map +1 -0
  11. package/dist/align_take.d.ts +50 -0
  12. package/dist/align_take.d.ts.map +1 -0
  13. package/dist/analysis.d.ts +29 -5840
  14. package/dist/analysis.d.ts.map +1 -0
  15. package/dist/analysis.js +873 -722
  16. package/dist/analysis.js.map +1 -1
  17. package/dist/analysis_helpers.d.ts +9 -0
  18. package/dist/analysis_helpers.d.ts.map +1 -0
  19. package/dist/audio.d.ts +163 -0
  20. package/dist/audio.d.ts.map +1 -0
  21. package/dist/clip_page_streamer.d.ts +133 -0
  22. package/dist/clip_page_streamer.d.ts.map +1 -0
  23. package/dist/codes.d.ts +44 -0
  24. package/dist/codes.d.ts.map +1 -0
  25. package/dist/effects_mastering.d.ts +23 -0
  26. package/dist/effects_mastering.d.ts.map +1 -0
  27. package/dist/effects_note_ops.d.ts +477 -0
  28. package/dist/effects_note_ops.d.ts.map +1 -0
  29. package/dist/effects_percussive.d.ts +185 -0
  30. package/dist/effects_percussive.d.ts.map +1 -0
  31. package/dist/effects_separation.d.ts +65 -0
  32. package/dist/effects_separation.d.ts.map +1 -0
  33. package/dist/effects_spectral.d.ts +28 -0
  34. package/dist/effects_spectral.d.ts.map +1 -0
  35. package/dist/effects_timepitch.d.ts +129 -0
  36. package/dist/effects_timepitch.d.ts.map +1 -0
  37. package/dist/effects_voice_change.d.ts +53 -0
  38. package/dist/effects_voice_change.d.ts.map +1 -0
  39. package/dist/errors.d.ts +51 -0
  40. package/dist/errors.d.ts.map +1 -0
  41. package/dist/feature_core.d.ts +341 -0
  42. package/dist/feature_core.d.ts.map +1 -0
  43. package/dist/feature_decompose.d.ts +278 -0
  44. package/dist/feature_decompose.d.ts.map +1 -0
  45. package/dist/feature_inverse.d.ts +128 -0
  46. package/dist/feature_inverse.d.ts.map +1 -0
  47. package/dist/feature_loudness.d.ts +66 -0
  48. package/dist/feature_loudness.d.ts.map +1 -0
  49. package/dist/feature_music.d.ts +307 -0
  50. package/dist/feature_music.d.ts.map +1 -0
  51. package/dist/feature_pitch.d.ts +108 -0
  52. package/dist/feature_pitch.d.ts.map +1 -0
  53. package/dist/feature_resample.d.ts +16 -0
  54. package/dist/feature_resample.d.ts.map +1 -0
  55. package/dist/feature_spectral.d.ts +137 -0
  56. package/dist/feature_spectral.d.ts.map +1 -0
  57. package/dist/feature_spectrogram.d.ts +198 -0
  58. package/dist/feature_spectrogram.d.ts.map +1 -0
  59. package/dist/features.d.ts +10 -0
  60. package/dist/features.d.ts.map +1 -0
  61. package/dist/hrtf/default.shrf +0 -0
  62. package/dist/index.d.ts +74 -7431
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +3758 -1362
  65. package/dist/index.js.map +1 -1
  66. package/dist/instrument_types.d.ts +517 -0
  67. package/dist/instrument_types.d.ts.map +1 -0
  68. package/dist/live_audio.d.ts +35 -0
  69. package/dist/live_audio.d.ts.map +1 -0
  70. package/dist/mastering_chain.d.ts +213 -0
  71. package/dist/mastering_chain.d.ts.map +1 -0
  72. package/dist/mastering_core.d.ts +457 -0
  73. package/dist/mastering_core.d.ts.map +1 -0
  74. package/dist/mastering_dynamics.d.ts +80 -0
  75. package/dist/mastering_dynamics.d.ts.map +1 -0
  76. package/dist/metering.d.ts +287 -0
  77. package/dist/metering.d.ts.map +1 -0
  78. package/dist/mixer.d.ts +464 -0
  79. package/dist/mixer.d.ts.map +1 -0
  80. package/dist/mixing_assistant.d.ts +62 -0
  81. package/dist/mixing_assistant.d.ts.map +1 -0
  82. package/dist/mixing_oneshot.d.ts +40 -0
  83. package/dist/mixing_oneshot.d.ts.map +1 -0
  84. package/dist/module_state.d.ts +15 -0
  85. package/dist/module_state.d.ts.map +1 -0
  86. package/dist/opfs_clip_pages.d.ts +28 -0
  87. package/dist/opfs_clip_pages.d.ts.map +1 -0
  88. package/dist/playback_renderer.d.ts +128 -0
  89. package/dist/playback_renderer.d.ts.map +1 -0
  90. package/dist/polyphony.d.ts +202 -0
  91. package/dist/polyphony.d.ts.map +1 -0
  92. package/dist/project.d.ts +8 -0
  93. package/dist/project.d.ts.map +1 -0
  94. package/dist/project_class.d.ts +562 -0
  95. package/dist/project_class.d.ts.map +1 -0
  96. package/dist/project_internal.d.ts +194 -0
  97. package/dist/project_internal.d.ts.map +1 -0
  98. package/dist/project_synth.d.ts +74 -0
  99. package/dist/project_synth.d.ts.map +1 -0
  100. package/dist/project_types.d.ts +652 -0
  101. package/dist/project_types.d.ts.map +1 -0
  102. package/dist/public_types.d.ts +185 -0
  103. package/dist/public_types.d.ts.map +1 -0
  104. package/dist/public_types_acoustic.d.ts +215 -0
  105. package/dist/public_types_acoustic.d.ts.map +1 -0
  106. package/dist/public_types_mastering.d.ts +510 -0
  107. package/dist/public_types_mastering.d.ts.map +1 -0
  108. package/dist/public_types_mixing.d.ts +436 -0
  109. package/dist/public_types_mixing.d.ts.map +1 -0
  110. package/dist/public_types_music.d.ts +619 -0
  111. package/dist/public_types_music.d.ts.map +1 -0
  112. package/dist/public_types_playback.d.ts +164 -0
  113. package/dist/public_types_playback.d.ts.map +1 -0
  114. package/dist/public_types_realtime.d.ts +174 -0
  115. package/dist/public_types_realtime.d.ts.map +1 -0
  116. package/dist/public_types_repair.d.ts +424 -0
  117. package/dist/public_types_repair.d.ts.map +1 -0
  118. package/dist/public_types_spectral.d.ts +697 -0
  119. package/dist/public_types_spectral.d.ts.map +1 -0
  120. package/dist/quick_analysis.d.ts +445 -0
  121. package/dist/quick_analysis.d.ts.map +1 -0
  122. package/dist/realtime_engine.d.ts +850 -0
  123. package/dist/realtime_engine.d.ts.map +1 -0
  124. package/dist/realtime_voice_changer.d.ts +158 -0
  125. package/dist/realtime_voice_changer.d.ts.map +1 -0
  126. package/dist/repair_dereverb.d.ts +187 -0
  127. package/dist/repair_dereverb.d.ts.map +1 -0
  128. package/dist/repair_impulsive.d.ts +186 -0
  129. package/dist/repair_impulsive.d.ts.map +1 -0
  130. package/dist/repair_noise.d.ts +239 -0
  131. package/dist/repair_noise.d.ts.map +1 -0
  132. package/dist/repair_trim.d.ts +123 -0
  133. package/dist/repair_trim.d.ts.map +1 -0
  134. package/dist/sample_bank.d.ts +84 -0
  135. package/dist/sample_bank.d.ts.map +1 -0
  136. package/dist/scale.d.ts +10 -0
  137. package/dist/scale.d.ts.map +1 -0
  138. package/dist/schemas/mixer-scene.schema.json +393 -0
  139. package/dist/schemas/playback-renderer-config.schema.json +392 -0
  140. package/dist/sonare-analysis.d.ts +8 -0
  141. package/dist/sonare-analysis.js +2 -2
  142. package/dist/sonare-analysis.wasm +0 -0
  143. package/dist/sonare.d.ts +3909 -0
  144. package/dist/sonare.js +2 -2
  145. package/dist/sonare.wasm +0 -0
  146. package/dist/stream_analyzer.d.ts +163 -0
  147. package/dist/stream_analyzer.d.ts.map +1 -0
  148. package/dist/stream_types.d.ts +214 -0
  149. package/dist/stream_types.d.ts.map +1 -0
  150. package/dist/streaming_mixing.d.ts +6 -0
  151. package/dist/streaming_mixing.d.ts.map +1 -0
  152. package/dist/streaming_processors.d.ts +335 -0
  153. package/dist/streaming_processors.d.ts.map +1 -0
  154. package/dist/transcribe.d.ts +77 -0
  155. package/dist/transcribe.d.ts.map +1 -0
  156. package/dist/validation.d.ts +140 -0
  157. package/dist/validation.d.ts.map +1 -0
  158. package/dist/web_midi.d.ts +77 -0
  159. package/dist/web_midi.d.ts.map +1 -0
  160. package/dist/worker.d.ts +5 -48
  161. package/dist/worker.d.ts.map +1 -0
  162. package/dist/worker.js +94 -41
  163. package/dist/worker.js.map +1 -1
  164. package/dist/worker_client.d.ts +96 -0
  165. package/dist/worker_client.d.ts.map +1 -0
  166. package/dist/worker_protocol.d.ts +43 -0
  167. package/dist/worker_protocol.d.ts.map +1 -0
  168. package/dist/worklet/audio_types.d.ts +21 -0
  169. package/dist/worklet/audio_types.d.ts.map +1 -0
  170. package/dist/worklet/engine-automation.d.ts +29 -0
  171. package/dist/worklet/engine-automation.d.ts.map +1 -0
  172. package/dist/worklet/engine-capture-facade.d.ts +35 -0
  173. package/dist/worklet/engine-capture-facade.d.ts.map +1 -0
  174. package/dist/worklet/engine-clips.d.ts +23 -0
  175. package/dist/worklet/engine-clips.d.ts.map +1 -0
  176. package/dist/worklet/engine-markers.d.ts +40 -0
  177. package/dist/worklet/engine-markers.d.ts.map +1 -0
  178. package/dist/worklet/engine-mixer-facade.d.ts +128 -0
  179. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
  180. package/dist/worklet/engine-node.d.ts +81 -0
  181. package/dist/worklet/engine-node.d.ts.map +1 -0
  182. package/dist/worklet/engine-offline.d.ts +81 -0
  183. package/dist/worklet/engine-offline.d.ts.map +1 -0
  184. package/dist/worklet/engine-options.d.ts +12 -0
  185. package/dist/worklet/engine-options.d.ts.map +1 -0
  186. package/dist/worklet/engine-parameter-facade.d.ts +106 -0
  187. package/dist/worklet/engine-parameter-facade.d.ts.map +1 -0
  188. package/dist/worklet/engine-processor.d.ts +68 -0
  189. package/dist/worklet/engine-processor.d.ts.map +1 -0
  190. package/dist/worklet/engine-register.d.ts +2 -0
  191. package/dist/worklet/engine-register.d.ts.map +1 -0
  192. package/dist/worklet/engine-strips.d.ts +73 -0
  193. package/dist/worklet/engine-strips.d.ts.map +1 -0
  194. package/dist/worklet/engine-sync.d.ts +37 -0
  195. package/dist/worklet/engine-sync.d.ts.map +1 -0
  196. package/dist/worklet/engine-tempo-facade.d.ts +48 -0
  197. package/dist/worklet/engine-tempo-facade.d.ts.map +1 -0
  198. package/dist/worklet/engine.d.ts +397 -0
  199. package/dist/worklet/engine.d.ts.map +1 -0
  200. package/dist/worklet/guards.d.ts +47 -0
  201. package/dist/worklet/guards.d.ts.map +1 -0
  202. package/dist/worklet/messages.d.ts +710 -0
  203. package/dist/worklet/messages.d.ts.map +1 -0
  204. package/dist/worklet/mixer-processor.d.ts +46 -0
  205. package/dist/worklet/mixer-processor.d.ts.map +1 -0
  206. package/dist/worklet/playback-processor.d.ts +62 -0
  207. package/dist/worklet/playback-processor.d.ts.map +1 -0
  208. package/dist/worklet/protocol.d.ts +323 -0
  209. package/dist/worklet/protocol.d.ts.map +1 -0
  210. package/dist/worklet/voice-changer-processor.d.ts +41 -0
  211. package/dist/worklet/voice-changer-processor.d.ts.map +1 -0
  212. package/dist/worklet.d.ts +16 -2515
  213. package/dist/worklet.d.ts.map +1 -0
  214. package/dist/worklet.js +2763 -501
  215. package/dist/worklet.js.map +1 -1
  216. package/package.json +23 -12
  217. package/src/_effects_common.ts +17 -0
  218. package/src/_feature_validation.ts +34 -0
  219. package/src/_fft_options.ts +39 -0
  220. package/src/align_take.ts +64 -0
  221. package/src/analysis.ts +56 -3
  222. package/src/analysis_helpers.ts +7 -0
  223. package/src/audio.ts +106 -3
  224. package/src/codes.ts +39 -2
  225. package/src/effects_mastering.ts +97 -22
  226. package/src/effects_note_ops.ts +635 -0
  227. package/src/effects_percussive.ts +217 -0
  228. package/src/effects_separation.ts +150 -0
  229. package/src/effects_spectral.ts +60 -0
  230. package/src/effects_timepitch.ts +377 -0
  231. package/src/errors.ts +23 -1
  232. package/src/feature_core.ts +127 -2
  233. package/src/feature_decompose.ts +633 -0
  234. package/src/feature_inverse.ts +454 -0
  235. package/src/feature_loudness.ts +125 -0
  236. package/src/feature_music.ts +107 -14
  237. package/src/feature_pitch.ts +96 -1
  238. package/src/feature_spectral.ts +16 -611
  239. package/src/feature_spectrogram.ts +63 -450
  240. package/src/features.ts +36 -22
  241. package/src/index.ts +282 -30
  242. package/src/instrument_types.ts +645 -0
  243. package/src/live_audio.ts +27 -1
  244. package/src/mastering_chain.ts +184 -0
  245. package/src/mastering_core.ts +346 -32
  246. package/src/mastering_dynamics.ts +22 -11
  247. package/src/metering.ts +67 -24
  248. package/src/mixer.ts +212 -3
  249. package/src/mixing_assistant.ts +138 -0
  250. package/src/mixing_oneshot.ts +10 -5
  251. package/src/module_state.ts +24 -2
  252. package/src/playback_renderer.ts +252 -0
  253. package/src/polyphony.ts +279 -0
  254. package/src/project.ts +61 -24
  255. package/src/project_class.ts +450 -27
  256. package/src/project_internal.ts +149 -42
  257. package/src/project_synth.ts +67 -1
  258. package/src/project_types.ts +268 -270
  259. package/src/public_types.ts +122 -3
  260. package/src/public_types_acoustic.ts +112 -3
  261. package/src/public_types_mastering.ts +254 -73
  262. package/src/public_types_mixing.ts +363 -1
  263. package/src/public_types_music.ts +312 -2
  264. package/src/public_types_playback.ts +195 -0
  265. package/src/public_types_realtime.ts +39 -7
  266. package/src/public_types_repair.ts +446 -0
  267. package/src/public_types_spectral.ts +487 -1
  268. package/src/quick_analysis.ts +203 -26
  269. package/src/realtime_engine.ts +711 -28
  270. package/src/realtime_voice_changer.ts +55 -1
  271. package/src/repair_dereverb.ts +299 -0
  272. package/src/repair_impulsive.ts +395 -0
  273. package/src/repair_noise.ts +425 -0
  274. package/src/repair_trim.ts +226 -0
  275. package/src/sample_bank.ts +113 -0
  276. package/src/sonare.js.d.ts +1122 -30
  277. package/src/stream_analyzer.ts +36 -4
  278. package/src/stream_types.ts +37 -0
  279. package/src/streaming_mixing.ts +1 -1
  280. package/src/streaming_processors.ts +194 -10
  281. package/src/transcribe.ts +89 -0
  282. package/src/validation.ts +285 -11
  283. package/src/web_midi.ts +1 -6
  284. package/src/worker.ts +18 -2
  285. package/src/worklet/audio_types.ts +37 -0
  286. package/src/worklet/engine-mixer-facade.ts +444 -10
  287. package/src/worklet/engine-node.ts +78 -26
  288. package/src/worklet/engine-offline.ts +14 -8
  289. package/src/worklet/engine-parameter-facade.ts +21 -0
  290. package/src/worklet/engine-processor.ts +272 -83
  291. package/src/worklet/engine-register.ts +32 -18
  292. package/src/worklet/engine-strips.ts +280 -9
  293. package/src/worklet/engine-sync.ts +14 -6
  294. package/src/worklet/engine.ts +365 -31
  295. package/src/worklet/guards.ts +140 -44
  296. package/src/worklet/messages.ts +227 -2
  297. package/src/worklet/mixer-processor.ts +109 -47
  298. package/src/worklet/playback-processor.ts +300 -0
  299. package/src/worklet/protocol.ts +53 -3
  300. package/src/worklet/voice-changer-processor.ts +17 -11
  301. package/src/worklet.ts +17 -0
  302. package/src/effects_transform.ts +0 -718
  303. package/src/mastering_repair.ts +0 -273
@@ -1,9 +1,26 @@
1
- import type { EngineBus, EngineTrackLane, EngineTrackSend, RealtimeEngine } from '../index';
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
10
  import { buildMixerLanes } from './engine-sync';
4
- import type { SonareEngineSyncMessage } from './messages';
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
@@ -19,16 +36,230 @@ export interface EngineMixerContext {
19
36
  string,
20
37
  { trackId: number; insertIndex: number; sourceTrackId: number }
21
38
  >;
39
+ readonly busSidechains: Map<
40
+ string,
41
+ { busId: number; insertIndex: number; sourceKind: number; sourceId: number }
42
+ >;
43
+ readonly masterSidechains: Map<
44
+ number,
45
+ { insertIndex: number; sourceKind: number; sourceId: number }
46
+ >;
22
47
  readonly buses: EngineBus[];
23
48
  readonly trackStripJson: Map<number, string>;
24
49
  readonly busStripJson: Map<number, string>;
50
+ readonly insertParamOverrides: InsertParamOverrideMap;
51
+ clearInsertAutomationLanes(target: StripJsonTarget, forgetResolvedIds?: boolean): void;
52
+ flushOfflineMirror(): void;
25
53
  postSync(message: SonareEngineSyncMessage): void;
26
54
  ensureTrackLane(target: string | number): number;
27
55
  ensureBus(busId: number): number;
28
56
  mixerLanes(): EngineTrackLane[];
29
- syncMixer(): void;
57
+ syncMixer(forceInsertResets?: StripJsonTarget[]): void;
30
58
  sendSmoothedParam(paramId: number, value: number): boolean;
31
59
  getMasterStripJson(): string | undefined;
60
+ cacheMasterStripJson(sceneJson: string): void;
61
+ }
62
+
63
+ /** Addresses one strip's cached scene JSON, the state a syncMixer re-post replays. */
64
+ export type StripJsonTarget =
65
+ | { kind: 'track'; trackId: number }
66
+ | { kind: 'bus'; busId: number }
67
+ | { kind: 'master' };
68
+
69
+ /** An empty construction scene used only within an explicit strip replacement. */
70
+ export function emptyStripJson(target: StripJsonTarget): string {
71
+ switch (target.kind) {
72
+ case 'track':
73
+ return `{"version":1,"strips":[{"id":"track-${target.trackId}"}],"buses":[],"connections":[]}`;
74
+ case 'bus':
75
+ return `{"version":1,"strips":[],"buses":[{"id":"bus-${target.busId}"}],"connections":[]}`;
76
+ case 'master':
77
+ return '{"version":1,"strips":[{"id":"master"}],"buses":[],"connections":[]}';
78
+ }
79
+ }
80
+
81
+ /** Drop bindings whose insert was removed by an explicit scene replacement. */
82
+ export function pruneStripSidechains(
83
+ ctx: EngineMixerContext,
84
+ target: StripJsonTarget,
85
+ sceneJson: string,
86
+ ): void {
87
+ const scene = JSON.parse(sceneJson) as {
88
+ strips?: Array<{ inserts?: unknown[] }>;
89
+ buses?: Array<{ inserts?: unknown[] }>;
90
+ };
91
+ const entry = target.kind === 'bus' ? scene.buses?.[0] : scene.strips?.[0];
92
+ const insertCount = Array.isArray(entry?.inserts) ? entry.inserts.length : 0;
93
+ if (target.kind === 'track') {
94
+ for (const [key, binding] of ctx.laneSidechains) {
95
+ if (binding.trackId === target.trackId && binding.insertIndex >= insertCount) {
96
+ ctx.laneSidechains.delete(key);
97
+ }
98
+ }
99
+ } else if (target.kind === 'bus') {
100
+ for (const [key, binding] of ctx.busSidechains) {
101
+ if (binding.busId === target.busId && binding.insertIndex >= insertCount) {
102
+ ctx.busSidechains.delete(key);
103
+ }
104
+ }
105
+ } else {
106
+ for (const [key, binding] of ctx.masterSidechains) {
107
+ if (binding.insertIndex >= insertCount) {
108
+ ctx.masterSidechains.delete(key);
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ export function hasInsertParamOverrides(
115
+ overrides: InsertParamOverrideMap,
116
+ target: StripJsonTarget,
117
+ ): boolean {
118
+ for (const override of overrides.values()) {
119
+ if (sameStripTarget(override.target, target)) {
120
+ return true;
121
+ }
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /** Apply the replacement first to validate it, then force fresh insert state. */
127
+ export function applyFullStripJson(
128
+ engine: RealtimeEngine,
129
+ target: StripJsonTarget,
130
+ sceneJson: string,
131
+ resetInserts: boolean,
132
+ ): void {
133
+ const apply = (json: string): void => {
134
+ switch (target.kind) {
135
+ case 'track':
136
+ engine.setTrackStripJson(target.trackId, json);
137
+ break;
138
+ case 'bus':
139
+ engine.setBusStripJson(target.busId, json);
140
+ break;
141
+ case 'master':
142
+ engine.setMasterStripJson(json);
143
+ break;
144
+ }
145
+ };
146
+ engine.applyCommandsDueNowPreservingFuture();
147
+ apply(sceneJson);
148
+ if (resetInserts) {
149
+ switch (target.kind) {
150
+ case 'track':
151
+ engine.clearTrackInsertParameterBases(target.trackId);
152
+ break;
153
+ case 'bus':
154
+ engine.clearBusInsertParameterBases(target.busId);
155
+ break;
156
+ case 'master':
157
+ engine.clearMasterInsertParameterBases();
158
+ break;
159
+ }
160
+ apply(emptyStripJson(target));
161
+ apply(sceneJson);
162
+ }
163
+ }
164
+
165
+ /** Builds a collision-safe key for one target/insert/parameter tuple. */
166
+ export function insertParamOverrideKey(override: InsertParamOverride): string {
167
+ const targetId =
168
+ override.target.kind === 'track'
169
+ ? override.target.trackId
170
+ : override.target.kind === 'bus'
171
+ ? override.target.busId
172
+ : 'master';
173
+ return JSON.stringify([override.target.kind, targetId, override.insertIndex, override.paramName]);
174
+ }
175
+
176
+ /** Removes all retained insert values for one strip after a full scene replacement. */
177
+ export function clearInsertParamOverrides(
178
+ overrides: InsertParamOverrideMap,
179
+ target: StripJsonTarget,
180
+ ): void {
181
+ for (const [key, override] of overrides) {
182
+ if (sameStripTarget(override.target, target)) {
183
+ overrides.delete(key);
184
+ }
185
+ }
186
+ }
187
+
188
+ function sameStripTarget(left: StripJsonTarget, right: StripJsonTarget): boolean {
189
+ if (left.kind !== right.kind) {
190
+ return false;
191
+ }
192
+ if (left.kind === 'track' && right.kind === 'track') {
193
+ return left.trackId === right.trackId;
194
+ }
195
+ if (left.kind === 'bus' && right.kind === 'bus') {
196
+ return left.busId === right.busId;
197
+ }
198
+ return left.kind === 'master' && right.kind === 'master';
199
+ }
200
+
201
+ function flattenInsertParamOverride(
202
+ override: InsertParamOverride,
203
+ ): SonareEngineSyncMixerInsertParamOverride {
204
+ switch (override.target.kind) {
205
+ case 'track':
206
+ return {
207
+ kind: 'track',
208
+ trackId: override.target.trackId,
209
+ insertIndex: override.insertIndex,
210
+ paramName: override.paramName,
211
+ value: override.value,
212
+ };
213
+ case 'bus':
214
+ return {
215
+ kind: 'bus',
216
+ busId: override.target.busId,
217
+ insertIndex: override.insertIndex,
218
+ paramName: override.paramName,
219
+ value: override.value,
220
+ };
221
+ case 'master':
222
+ return {
223
+ kind: 'master',
224
+ insertIndex: override.insertIndex,
225
+ paramName: override.paramName,
226
+ value: override.value,
227
+ };
228
+ }
229
+ }
230
+
231
+ /** Reads a strip's cached scene JSON; undefined when none was ever set. */
232
+ export function cachedStripJson(
233
+ ctx: EngineMixerContext,
234
+ target: StripJsonTarget,
235
+ ): string | undefined {
236
+ switch (target.kind) {
237
+ case 'track':
238
+ return ctx.trackStripJson.get(target.trackId);
239
+ case 'bus':
240
+ return ctx.busStripJson.get(target.busId);
241
+ case 'master':
242
+ return ctx.getMasterStripJson();
243
+ }
244
+ }
245
+
246
+ /** Replaces a strip's cached scene JSON without syncing it. */
247
+ export function cacheStripJson(
248
+ ctx: EngineMixerContext,
249
+ target: StripJsonTarget,
250
+ sceneJson: string,
251
+ ): void {
252
+ switch (target.kind) {
253
+ case 'track':
254
+ ctx.trackStripJson.set(target.trackId, sceneJson);
255
+ return;
256
+ case 'bus':
257
+ ctx.busStripJson.set(target.busId, sceneJson);
258
+ return;
259
+ case 'master':
260
+ ctx.cacheMasterStripJson(sceneJson);
261
+ return;
262
+ }
32
263
  }
33
264
 
34
265
  /** Builds the engine's track-lane descriptors from the current routing stores. */
@@ -40,13 +271,47 @@ export function mixerLanes(ctx: EngineMixerContext): EngineTrackLane[] {
40
271
  * Mirrors the current mixer routing into the offline engine and posts the full
41
272
  * mixer-sync message (lanes, buses, strip JSON, sidechains) to the worklet.
42
273
  */
43
- export function syncMixer(ctx: EngineMixerContext): void {
274
+ export function syncMixer(
275
+ ctx: EngineMixerContext,
276
+ busesAlreadyApplied = false,
277
+ forceInsertResets: StripJsonTarget[] = [],
278
+ ): void {
44
279
  const lanes = mixerLanes(ctx);
45
280
  const buses = ctx.buses.map((bus) => ({ ...bus }));
46
- ctx.offlineEngine.setTrackBuses(buses);
281
+ if (!busesAlreadyApplied) {
282
+ ctx.offlineEngine.setTrackBuses(buses);
283
+ }
47
284
  if (lanes.length > 0) {
285
+ ctx.offlineEngine.settleInsertParameters();
48
286
  ctx.offlineEngine.setTrackLanes(lanes);
49
287
  }
288
+ if (forceInsertResets.length > 0) {
289
+ // The temporary empty strip drops native sidechain bindings. Restore the
290
+ // offline mirror in the same order the worklet restores them below.
291
+ for (const binding of ctx.laneSidechains.values()) {
292
+ ctx.offlineEngine.setLaneSidechain(
293
+ binding.trackId,
294
+ binding.insertIndex,
295
+ binding.sourceTrackId,
296
+ );
297
+ }
298
+ for (const binding of ctx.busSidechains.values()) {
299
+ ctx.offlineEngine.setBusSidechain(
300
+ binding.busId,
301
+ binding.insertIndex,
302
+ binding.sourceKind,
303
+ binding.sourceId,
304
+ );
305
+ }
306
+ for (const binding of ctx.masterSidechains.values()) {
307
+ ctx.offlineEngine.setMasterSidechain(
308
+ binding.insertIndex,
309
+ binding.sourceKind,
310
+ binding.sourceId,
311
+ );
312
+ }
313
+ }
314
+ replayInsertParamOverrides(ctx, lanes, buses);
50
315
  const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
51
316
  trackId,
52
317
  sceneJson,
@@ -55,6 +320,10 @@ export function syncMixer(ctx: EngineMixerContext): void {
55
320
  busId,
56
321
  sceneJson,
57
322
  }));
323
+ const insertParamOverrides = Array.from(
324
+ ctx.insertParamOverrides.values(),
325
+ flattenInsertParamOverride,
326
+ );
58
327
  ctx.postSync({
59
328
  type: 'syncMixer',
60
329
  lanes,
@@ -63,9 +332,63 @@ export function syncMixer(ctx: EngineMixerContext): void {
63
332
  laneSidechains: Array.from(ctx.laneSidechains.values()),
64
333
  busStrips,
65
334
  masterStripJson: ctx.getMasterStripJson(),
335
+ busSidechains: Array.from(ctx.busSidechains.values()),
336
+ masterSidechains: Array.from(ctx.masterSidechains.values()),
337
+ ...(insertParamOverrides.length > 0 ? { insertParamOverrides } : {}),
338
+ ...(forceInsertResets.length > 0 ? { forceInsertResets } : {}),
66
339
  });
67
340
  }
68
341
 
342
+ /**
343
+ * Reapplies retained by-name values after native topology setters clear their
344
+ * smoother slots. This runs once per mixer sync, never once per knob tick.
345
+ */
346
+ export function replayInsertParamOverrides(
347
+ ctx: EngineMixerContext,
348
+ lanes = mixerLanes(ctx),
349
+ buses = ctx.buses,
350
+ ): void {
351
+ const activeTrackIds = new Set(lanes.map((lane) => lane.trackId));
352
+ const activeBusIds = new Set(buses.map((bus) => bus.busId));
353
+ for (const [key, override] of ctx.insertParamOverrides) {
354
+ const active =
355
+ override.target.kind === 'track'
356
+ ? activeTrackIds.has(override.target.trackId)
357
+ : override.target.kind === 'bus'
358
+ ? activeBusIds.has(override.target.busId)
359
+ : true;
360
+ if (!active) {
361
+ ctx.insertParamOverrides.delete(key);
362
+ continue;
363
+ }
364
+ switch (override.target.kind) {
365
+ case 'track':
366
+ ctx.offlineEngine.restoreTrackStripInsertParamByName(
367
+ override.target.trackId,
368
+ override.insertIndex,
369
+ override.paramName,
370
+ override.value,
371
+ );
372
+ break;
373
+ case 'bus':
374
+ ctx.offlineEngine.restoreBusStripInsertParamByName(
375
+ override.target.busId,
376
+ override.insertIndex,
377
+ override.paramName,
378
+ override.value,
379
+ );
380
+ break;
381
+ case 'master':
382
+ ctx.offlineEngine.restoreMasterStripInsertParamByName(
383
+ override.insertIndex,
384
+ override.paramName,
385
+ override.value,
386
+ );
387
+ break;
388
+ }
389
+ }
390
+ }
391
+
69
392
  /**
70
393
  * Declares the mixer track lanes in an explicit order.
71
394
  *
@@ -139,19 +462,91 @@ export function setLaneSidechain(
139
462
  const sourceIndex = ctx.ensureTrackLane(sourceTarget);
140
463
  sourceTrackId = ctx.trackLaneIds[sourceIndex];
141
464
  }
465
+ ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
142
466
  if (sourceTrackId === 0) {
143
467
  ctx.laneSidechains.delete(key);
144
468
  } else {
145
469
  ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
146
470
  }
147
- ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
148
471
  ctx.postSync({
149
472
  type: 'syncMixer',
473
+ sidechainDelta: true,
150
474
  lanes: ctx.mixerLanes(),
151
475
  laneSidechains: [{ trackId, insertIndex, sourceTrackId }],
152
476
  });
153
477
  }
154
478
 
479
+ /**
480
+ * Keys one insert of a bus strip from a track lane or another bus
481
+ * (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding. The
482
+ * binding is cached for resync only once the engine has accepted it.
483
+ */
484
+ export function setBusSidechain(
485
+ ctx: EngineMixerContext,
486
+ busId: number,
487
+ insertIndex: number,
488
+ kind: SidechainSourceKind | number,
489
+ sourceId: number,
490
+ ): void {
491
+ const sourceKind = sidechainSourceKindCode(kind);
492
+ ctx.ensureBus(busId);
493
+ ensureSidechainSource(ctx, sourceKind, sourceId);
494
+ ctx.offlineEngine.setBusSidechain(busId, insertIndex, sourceKind, sourceId);
495
+ const key = `${busId}:${insertIndex}`;
496
+ if (sourceId === 0) {
497
+ ctx.busSidechains.delete(key);
498
+ } else {
499
+ ctx.busSidechains.set(key, { busId, insertIndex, sourceKind, sourceId });
500
+ }
501
+ ctx.postSync({
502
+ type: 'syncMixer',
503
+ sidechainDelta: true,
504
+ lanes: ctx.mixerLanes(),
505
+ busSidechains: [{ busId, insertIndex, sourceKind, sourceId }],
506
+ });
507
+ }
508
+
509
+ /**
510
+ * Keys one insert of the master strip from a track lane or a bus. Same source
511
+ * rules as {@link setBusSidechain}.
512
+ */
513
+ export function setMasterSidechain(
514
+ ctx: EngineMixerContext,
515
+ insertIndex: number,
516
+ kind: SidechainSourceKind | number,
517
+ sourceId: number,
518
+ ): void {
519
+ const sourceKind = sidechainSourceKindCode(kind);
520
+ ensureSidechainSource(ctx, sourceKind, sourceId);
521
+ ctx.offlineEngine.setMasterSidechain(insertIndex, sourceKind, sourceId);
522
+ if (sourceId === 0) {
523
+ ctx.masterSidechains.delete(insertIndex);
524
+ } else {
525
+ ctx.masterSidechains.set(insertIndex, { insertIndex, sourceKind, sourceId });
526
+ }
527
+ ctx.postSync({
528
+ type: 'syncMixer',
529
+ sidechainDelta: true,
530
+ lanes: ctx.mixerLanes(),
531
+ masterSidechains: [{ insertIndex, sourceKind, sourceId }],
532
+ });
533
+ }
534
+
535
+ function ensureSidechainSource(
536
+ ctx: EngineMixerContext,
537
+ sourceKind: number,
538
+ sourceId: number,
539
+ ): void {
540
+ if (sourceId === 0) {
541
+ return;
542
+ }
543
+ if (sourceKind === 1) {
544
+ ctx.ensureBus(sourceId);
545
+ } else {
546
+ ctx.ensureTrackLane(sourceId);
547
+ }
548
+ }
549
+
155
550
  export function setSends(
156
551
  ctx: EngineMixerContext,
157
552
  target: string | number,
@@ -167,20 +562,59 @@ export function setSends(
167
562
  }
168
563
 
169
564
  export function setTrackBuses(ctx: EngineMixerContext, buses: EngineBus[]): void {
565
+ // Validate and apply before pruning retained scenes. Native rejects a bus
566
+ // removal while a lane still routes to it; that failure must leave JS state
567
+ // untouched so a later valid edit can still replay the existing inserts.
568
+ ctx.offlineEngine.setTrackBuses(buses);
569
+ const retainedBusIds = new Set(buses.map((bus) => bus.busId));
570
+ for (const bus of ctx.buses) {
571
+ if (!retainedBusIds.has(bus.busId)) {
572
+ ctx.clearInsertAutomationLanes({ kind: 'bus', busId: bus.busId }, true);
573
+ }
574
+ }
170
575
  ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
171
- ctx.syncMixer();
576
+ const activeBusIds = new Set(ctx.buses.map((bus) => bus.busId));
577
+ for (const busId of ctx.busStripJson.keys()) {
578
+ if (!activeBusIds.has(busId)) {
579
+ ctx.busStripJson.delete(busId);
580
+ }
581
+ }
582
+ for (const [key, binding] of ctx.busSidechains) {
583
+ if (
584
+ !activeBusIds.has(binding.busId) ||
585
+ (binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId))
586
+ ) {
587
+ ctx.busSidechains.delete(key);
588
+ }
589
+ }
590
+ for (const [key, binding] of ctx.masterSidechains) {
591
+ if (binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
592
+ ctx.masterSidechains.delete(key);
593
+ }
594
+ }
595
+ syncMixer(ctx, true);
172
596
  }
173
597
 
174
598
  export function setBusGain(ctx: EngineMixerContext, busId: number, db: number): boolean {
175
599
  const busIndex = ctx.ensureBus(busId);
176
600
  ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
177
- ctx.offlineEngine.setTrackBuses(ctx.buses);
601
+ // The reserved smoothed parameter updates both engines. Rebuilding the bus
602
+ // topology for every fader tick would reset insert slots and their tails.
178
603
  return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
179
604
  }
180
605
 
181
606
  export function setBusStripJson(ctx: EngineMixerContext, busId: number, sceneJson: string): void {
182
607
  ctx.ensureBus(busId);
183
- ctx.offlineEngine.setBusStripJson(busId, sceneJson);
608
+ const target: StripJsonTarget = { kind: 'bus', busId };
609
+ const resetInserts =
610
+ (ctx.busStripJson.has(busId) && ctx.busStripJson.get(busId) !== sceneJson) ||
611
+ hasInsertParamOverrides(ctx.insertParamOverrides, target);
612
+ applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
613
+ if (resetInserts) {
614
+ ctx.clearInsertAutomationLanes(target);
615
+ }
616
+ pruneStripSidechains(ctx, target, sceneJson);
184
617
  ctx.busStripJson.set(busId, sceneJson);
185
- ctx.syncMixer();
618
+ clearInsertParamOverrides(ctx.insertParamOverrides, target);
619
+ ctx.syncMixer(resetInserts ? [target] : []);
186
620
  }