@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
@@ -22,6 +22,7 @@ import type {
22
22
  MidiCcBindOptions,
23
23
  PanLaw,
24
24
  PanMode,
25
+ SidechainSourceKind,
25
26
  } from '../index';
26
27
  import { RealtimeEngine } from '../index';
27
28
  import { createOpfsClipPageProvider, type OpfsClipPageProviderBinding } from '../opfs_clip_pages';
@@ -34,7 +35,7 @@ import type { EngineClipContext } from './engine-clips';
34
35
  import * as clips from './engine-clips';
35
36
  import type { EngineMarkerContext } from './engine-markers';
36
37
  import * as markers from './engine-markers';
37
- import type { EngineMixerContext } from './engine-mixer-facade';
38
+ import type { EngineMixerContext, InsertParamOverrideMap } from './engine-mixer-facade';
38
39
  import * as mixer from './engine-mixer-facade';
39
40
  import { SonareRealtimeEngineNode } from './engine-node';
40
41
  import { buildTransportFacade, type CaptureOptions } from './engine-offline';
@@ -94,6 +95,7 @@ export class SonareEngine {
94
95
  private readonly offlineBlockSize: number;
95
96
  private readonly offlineChannelCount: number;
96
97
  private readonly automationLanes = new Map<number, EngineAutomationPoint[]>();
98
+ private readonly insertAutomationIdsByTarget = new Map<string, Set<number>>();
97
99
  private readonly clips = new Map<number, EngineClip>();
98
100
  private readonly midiClips = new Map<number, EngineMidiClipSchedule>();
99
101
  private readonly markers = new Map<number, EngineMarker>();
@@ -104,9 +106,18 @@ export class SonareEngine {
104
106
  string,
105
107
  { trackId: number; insertIndex: number; sourceTrackId: number }
106
108
  >();
109
+ private readonly busSidechains = new Map<
110
+ string,
111
+ { busId: number; insertIndex: number; sourceKind: number; sourceId: number }
112
+ >();
113
+ private readonly masterSidechains = new Map<
114
+ number,
115
+ { insertIndex: number; sourceKind: number; sourceId: number }
116
+ >();
107
117
  private readonly buses: EngineBus[] = [];
108
118
  private readonly trackStripJson = new Map<number, string>();
109
119
  private readonly busStripJson = new Map<number, string>();
120
+ private readonly insertParamOverrides: InsertParamOverrideMap = new Map();
110
121
  private masterStripJson: string | undefined;
111
122
  private captureConfig: Omit<SonareEngineSyncCaptureMessage, 'type'> | undefined;
112
123
  private tempoBpm = 120;
@@ -320,12 +331,17 @@ export class SonareEngine {
320
331
  insertIndex: number,
321
332
  paramName: string,
322
333
  ): number {
323
- return parameter.resolveTrackInsertAutomationId(
334
+ const id = parameter.resolveTrackInsertAutomationId(
324
335
  this.parameterContext,
325
336
  target,
326
337
  insertIndex,
327
338
  paramName,
328
339
  );
340
+ if (id >= 0) {
341
+ const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
342
+ this.rememberInsertAutomationId({ kind: 'track', trackId }, id);
343
+ }
344
+ return id;
329
345
  }
330
346
 
331
347
  /**
@@ -337,7 +353,15 @@ export class SonareEngine {
337
353
  * @returns Reserved insert-automation id, or -1 when insert/key unknown.
338
354
  */
339
355
  resolveMasterInsertAutomationId(insertIndex: number, paramName: string): number {
340
- return parameter.resolveMasterInsertAutomationId(this.parameterContext, insertIndex, paramName);
356
+ const id = parameter.resolveMasterInsertAutomationId(
357
+ this.parameterContext,
358
+ insertIndex,
359
+ paramName,
360
+ );
361
+ if (id >= 0) {
362
+ this.rememberInsertAutomationId({ kind: 'master' }, id);
363
+ }
364
+ return id;
341
365
  }
342
366
 
343
367
  /**
@@ -351,12 +375,30 @@ export class SonareEngine {
351
375
  * @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
352
376
  */
353
377
  resolveBusInsertAutomationId(busId: number, insertIndex: number, paramName: string): number {
354
- return parameter.resolveBusInsertAutomationId(
378
+ const id = parameter.resolveBusInsertAutomationId(
355
379
  this.parameterContext,
356
380
  busId,
357
381
  insertIndex,
358
382
  paramName,
359
383
  );
384
+ if (id >= 0) {
385
+ this.rememberInsertAutomationId({ kind: 'bus', busId }, id);
386
+ }
387
+ return id;
388
+ }
389
+
390
+ /**
391
+ * Resolves a hosted instrument's continuous parameter to its reserved
392
+ * instrument-automation id, so an instrument parameter follows a breakpoint
393
+ * lane at audio-block precision like a strip insert. Bind the instrument
394
+ * (`setSynthInstrument`) before resolving.
395
+ *
396
+ * @param destinationId MIDI destination the instrument is bound to.
397
+ * @param paramName Instrument JSON-key parameter name (e.g. `cutoffHz`).
398
+ * @returns Reserved instrument-automation id, or -1 when destination/key unknown.
399
+ */
400
+ resolveInstrumentAutomationId(destinationId: number, paramName: string): number {
401
+ return parameter.resolveInstrumentAutomationId(this.parameterContext, destinationId, paramName);
360
402
  }
361
403
 
362
404
  /**
@@ -397,11 +439,19 @@ export class SonareEngine {
397
439
  }
398
440
 
399
441
  setStripGain(target: string | number, db: number): boolean {
400
- return this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
442
+ const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
443
+ if (sent && target === 'master') {
444
+ strips.cacheMasterStripScalar(this.stripContext, 'faderDb', db);
445
+ }
446
+ return sent;
401
447
  }
402
448
 
403
449
  setStripPan(target: string | number, pan: number): boolean {
404
- return this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
450
+ const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
451
+ if (sent && target === 'master') {
452
+ strips.cacheMasterStripScalar(this.stripContext, 'pan', pan);
453
+ }
454
+ return sent;
405
455
  }
406
456
 
407
457
  /**
@@ -448,6 +498,31 @@ export class SonareEngine {
448
498
  mixer.setTrackBuses(this.mixerContext, buses);
449
499
  }
450
500
 
501
+ /**
502
+ * Keys one insert of a bus strip from a track lane or another bus
503
+ * (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
504
+ */
505
+ setBusSidechain(
506
+ busId: number,
507
+ insertIndex: number,
508
+ sourceKind: SidechainSourceKind | number,
509
+ sourceId: number,
510
+ ): void {
511
+ mixer.setBusSidechain(this.mixerContext, busId, insertIndex, sourceKind, sourceId);
512
+ }
513
+
514
+ /**
515
+ * Keys one insert of the master strip from a track lane or a bus. Same
516
+ * source rules as {@link setBusSidechain}.
517
+ */
518
+ setMasterSidechain(
519
+ insertIndex: number,
520
+ sourceKind: SidechainSourceKind | number,
521
+ sourceId: number,
522
+ ): void {
523
+ mixer.setMasterSidechain(this.mixerContext, insertIndex, sourceKind, sourceId);
524
+ }
525
+
451
526
  setBusGain(busId: number, db: number): boolean {
452
527
  return mixer.setBusGain(this.mixerContext, busId, db);
453
528
  }
@@ -455,8 +530,17 @@ export class SonareEngine {
455
530
  setTrackStripJson(target: string | number, sceneJson: string): void {
456
531
  const laneIndex = this.ensureTrackLane(target);
457
532
  const trackId = this.trackLaneIds[laneIndex];
458
- strips.setTrackStripJson(this.stripContext, trackId, sceneJson, this.trackStripJson);
459
- this.syncMixer();
533
+ const { resetInserts } = strips.setTrackStripJson(
534
+ this.stripContext,
535
+ trackId,
536
+ sceneJson,
537
+ this.trackStripJson,
538
+ );
539
+ if (resetInserts) {
540
+ this.clearInsertAutomationLanes({ kind: 'track', trackId });
541
+ }
542
+ mixer.pruneStripSidechains(this.mixerContext, { kind: 'track', trackId }, sceneJson);
543
+ this.syncMixer(resetInserts ? [{ kind: 'track', trackId }] : []);
460
544
  }
461
545
 
462
546
  setTrackStripEqBand(target: string | number, bandIndex: number, band: EqBand | string): void {
@@ -541,9 +625,18 @@ export class SonareEngine {
541
625
  }
542
626
 
543
627
  setMasterStripJson(sceneJson: string): void {
544
- this.offlineEngine.setMasterStripJson(sceneJson);
628
+ const target = { kind: 'master' } as const;
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);
545
638
  this.masterStripJson = sceneJson;
546
- this.syncMixer();
639
+ this.syncMixer(resetInserts ? [target] : []);
547
640
  }
548
641
 
549
642
  setMasterStripEqBand(bandIndex: number, band: EqBand | string): void {
@@ -572,6 +665,58 @@ export class SonareEngine {
572
665
  strips.setBusStripInsertParamByName(this.stripContext, busId, insertIndex, paramName, value);
573
666
  }
574
667
 
668
+ /**
669
+ * Bus-strip counterpart of {@link setStripInsertBypassed}. A bus is addressed
670
+ * by its numeric id, which the `target` of the track/master form cannot
671
+ * express, so it is its own method exactly like
672
+ * {@link setBusStripInsertParamByName}. Bypassing keeps the insert's internal
673
+ * state (reverb tail, compressor envelope) unless `resetOnBypass` is set;
674
+ * re-posting the bus scene JSON, the only workaround before this existed,
675
+ * rebuilds the chain and loses it.
676
+ */
677
+ setBusStripInsertBypassed(
678
+ busId: number,
679
+ insertIndex: number,
680
+ bypassed: boolean,
681
+ resetOnBypass = false,
682
+ ): void {
683
+ this.ensureBus(busId);
684
+ strips.setBusStripInsertBypassed(
685
+ this.stripContext,
686
+ busId,
687
+ insertIndex,
688
+ bypassed,
689
+ resetOnBypass,
690
+ );
691
+ }
692
+
693
+ /** Bus-strip counterpart of {@link setTrackStripEqBand}. */
694
+ setBusStripEqBand(busId: number, bandIndex: number, band: EqBand | string): void {
695
+ this.ensureBus(busId);
696
+ strips.setBusStripEqBand(this.stripContext, busId, bandIndex, band);
697
+ }
698
+
699
+ /** Bus-strip counterpart of {@link setTrackStripPan}; refused on a surround bus. */
700
+ setBusStripPan(busId: number, pan: number): void {
701
+ this.ensureBus(busId);
702
+ strips.setBusStripPan(this.stripContext, busId, pan);
703
+ }
704
+
705
+ setBusStripPanLaw(busId: number, panLaw: PanLaw | number): void {
706
+ this.ensureBus(busId);
707
+ strips.setBusStripPanLaw(this.stripContext, busId, panLaw);
708
+ }
709
+
710
+ setBusStripPanMode(busId: number, panMode: PanMode | number): void {
711
+ this.ensureBus(busId);
712
+ strips.setBusStripPanMode(this.stripContext, busId, panMode);
713
+ }
714
+
715
+ setBusStripDualPan(busId: number, leftPan: number, rightPan: number): void {
716
+ this.ensureBus(busId);
717
+ strips.setBusStripDualPan(this.stripContext, busId, leftPan, rightPan);
718
+ }
719
+
575
720
  setStripInsertParamByName(
576
721
  target: string | number,
577
722
  insertIndex: number,
@@ -664,6 +809,52 @@ export class SonareEngine {
664
809
  return { binding, provider: binding.provider };
665
810
  }
666
811
 
812
+ /**
813
+ * Sets the clip-page look-ahead window in timeline frames on both the worklet
814
+ * engine and this thread's offline engine.
815
+ *
816
+ * The audio thread reports the pages it is *about to* read that are not
817
+ * resident yet, so the sliding-window streamer can service them before the
818
+ * playhead reaches them. Without look-ahead a page miss is only reported
819
+ * after the read already produced silence, which costs one block of silence
820
+ * at every page boundary that was not primed.
821
+ *
822
+ * Defaults to half a second at the engine's sample rate. `0` disables it.
823
+ * Safe to call during playback.
824
+ */
825
+ setClipPagePrefetchFrames(frames: number): void {
826
+ if (this.destroyed) {
827
+ throw new Error('SonareEngine is destroyed.');
828
+ }
829
+ if (!Number.isFinite(frames) || frames < 0) {
830
+ throw new Error('clip page prefetch frames must be a finite value >= 0.');
831
+ }
832
+ this.offlineEngine.setClipPagePrefetchFrames(frames);
833
+ this.postSync({ type: 'syncClipPagePrefetchFrames', frames });
834
+ }
835
+
836
+ /**
837
+ * Sets the number of concurrent time-stretch voices on both this thread's
838
+ * offline engine and the live worklet engine. `voices` must be an integer
839
+ * in `[0, 64]`; a non-integer, negative, or larger value throws and leaves
840
+ * the capacity unchanged on both engines. Default is 8. Capacity 0 disables
841
+ * time-stretch, so every warped clip plays resampled instead. A change
842
+ * applied while the engine is running restarts the splice state of any clip
843
+ * stretching through a voice at that moment.
844
+ */
845
+ setWarpVoiceCapacity(voices: number): void {
846
+ if (this.destroyed) {
847
+ throw new Error('SonareEngine is destroyed.');
848
+ }
849
+ this.offlineEngine.setWarpVoiceCapacity(voices);
850
+ this.postSync({ type: 'syncWarpVoiceCapacity', voices });
851
+ }
852
+
853
+ /** Reads the current time-stretch voice capacity (default 8). */
854
+ warpVoiceCapacity(): number {
855
+ return this.offlineEngine.warpVoiceCapacity();
856
+ }
857
+
667
858
  addClip(
668
859
  trackId: string | number,
669
860
  buffer: Float32Array[] | ClipPageProvider,
@@ -703,6 +894,8 @@ export class SonareEngine {
703
894
  gain?: number;
704
895
  polyphony?: number;
705
896
  preferModelForModeledFamilies?: boolean;
897
+ clearBankRig?: boolean;
898
+ gsEfxRealization?: 'modern' | 'classic';
706
899
  } = {},
707
900
  ): void {
708
901
  strips.setSf2Instrument(this.stripContext, trackId, config);
@@ -776,6 +969,52 @@ export class SonareEngine {
776
969
  strips.pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
777
970
  }
778
971
 
972
+ pushMidiPitchBend(
973
+ trackId: string | number,
974
+ group: number,
975
+ channel: number,
976
+ bend14: number,
977
+ renderFrame = -1,
978
+ ): void {
979
+ strips.pushMidiPitchBend(this.stripContext, trackId, group, channel, bend14, renderFrame);
980
+ }
981
+
982
+ pushMidiChannelPressure(
983
+ trackId: string | number,
984
+ group: number,
985
+ channel: number,
986
+ pressure: number,
987
+ renderFrame = -1,
988
+ ): void {
989
+ strips.pushMidiChannelPressure(
990
+ this.stripContext,
991
+ trackId,
992
+ group,
993
+ channel,
994
+ pressure,
995
+ renderFrame,
996
+ );
997
+ }
998
+
999
+ pushMidiPolyPressure(
1000
+ trackId: string | number,
1001
+ group: number,
1002
+ channel: number,
1003
+ note: number,
1004
+ pressure: number,
1005
+ renderFrame = -1,
1006
+ ): void {
1007
+ strips.pushMidiPolyPressure(
1008
+ this.stripContext,
1009
+ trackId,
1010
+ group,
1011
+ channel,
1012
+ note,
1013
+ pressure,
1014
+ renderFrame,
1015
+ );
1016
+ }
1017
+
779
1018
  pushMidiUmp(trackId: string | number, word0: number, renderFrame = -1): void {
780
1019
  strips.pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
781
1020
  }
@@ -860,6 +1099,58 @@ export class SonareEngine {
860
1099
  });
861
1100
  }
862
1101
 
1102
+ pushMidiInputPitchBend(
1103
+ group: number,
1104
+ channel: number,
1105
+ bend14: number,
1106
+ portTimeSamples = 0,
1107
+ ): void {
1108
+ this.offlineEngine.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
1109
+ this.postSync({
1110
+ type: 'syncMidiInputPitchBend',
1111
+ group,
1112
+ channel,
1113
+ data0: bend14,
1114
+ data1: 0,
1115
+ portTimeSamples,
1116
+ });
1117
+ }
1118
+
1119
+ pushMidiInputChannelPressure(
1120
+ group: number,
1121
+ channel: number,
1122
+ pressure: number,
1123
+ portTimeSamples = 0,
1124
+ ): void {
1125
+ this.offlineEngine.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
1126
+ this.postSync({
1127
+ type: 'syncMidiInputChannelPressure',
1128
+ group,
1129
+ channel,
1130
+ data0: pressure,
1131
+ data1: 0,
1132
+ portTimeSamples,
1133
+ });
1134
+ }
1135
+
1136
+ pushMidiInputPolyPressure(
1137
+ group: number,
1138
+ channel: number,
1139
+ note: number,
1140
+ pressure: number,
1141
+ portTimeSamples = 0,
1142
+ ): void {
1143
+ this.offlineEngine.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
1144
+ this.postSync({
1145
+ type: 'syncMidiInputPolyPressure',
1146
+ group,
1147
+ channel,
1148
+ data0: note,
1149
+ data1: pressure,
1150
+ portTimeSamples,
1151
+ });
1152
+ }
1153
+
863
1154
  pushMidiPanic(renderFrame = -1): void {
864
1155
  this.offlineEngine.pushMidiPanic(renderFrame);
865
1156
  this.postSync({ type: 'syncMidiPanic', renderFrame });
@@ -1106,8 +1397,42 @@ export class SonareEngine {
1106
1397
  return mixer.mixerLanes(this.mixerContext);
1107
1398
  }
1108
1399
 
1109
- private syncMixer(): void {
1110
- mixer.syncMixer(this.mixerContext);
1400
+ private syncMixer(forceInsertResets: mixer.StripJsonTarget[] = []): void {
1401
+ mixer.syncMixer(this.mixerContext, false, forceInsertResets);
1402
+ }
1403
+
1404
+ private insertAutomationTargetKey(target: mixer.StripJsonTarget): string {
1405
+ return target.kind === 'track'
1406
+ ? `track:${target.trackId}`
1407
+ : target.kind === 'bus'
1408
+ ? `bus:${target.busId}`
1409
+ : 'master';
1410
+ }
1411
+
1412
+ private rememberInsertAutomationId(target: mixer.StripJsonTarget, id: number): void {
1413
+ const key = this.insertAutomationTargetKey(target);
1414
+ const ids = this.insertAutomationIdsByTarget.get(key) ?? new Set<number>();
1415
+ ids.add(id);
1416
+ this.insertAutomationIdsByTarget.set(key, ids);
1417
+ }
1418
+
1419
+ private clearInsertAutomationLanes(
1420
+ target: mixer.StripJsonTarget,
1421
+ forgetResolvedIds = false,
1422
+ ): void {
1423
+ const key = this.insertAutomationTargetKey(target);
1424
+ const ids = this.insertAutomationIdsByTarget.get(key);
1425
+ if (!ids) {
1426
+ return;
1427
+ }
1428
+ for (const id of ids) {
1429
+ if (this.automationLanes.has(id)) {
1430
+ this.setAutomationLane(id, []);
1431
+ }
1432
+ }
1433
+ if (forgetResolvedIds) {
1434
+ this.insertAutomationIdsByTarget.delete(key);
1435
+ }
1111
1436
  }
1112
1437
 
1113
1438
  private postInstrumentSync(message: SonareEngineInstrumentSyncMessage): void {
@@ -1138,22 +1463,17 @@ export class SonareEngine {
1138
1463
  if (this.destroyed) {
1139
1464
  return;
1140
1465
  }
1141
- try {
1142
- if (transfer && transfer.length > 0) {
1143
- this.realtimeNode.node.port.postMessage(message, transfer);
1144
- } else {
1145
- this.realtimeNode.node.port.postMessage(message);
1146
- }
1147
- } finally {
1148
- this.flushOfflineMirror();
1466
+ if (transfer && transfer.length > 0) {
1467
+ this.realtimeNode.node.port.postMessage(message, transfer);
1468
+ } else {
1469
+ this.realtimeNode.node.port.postMessage(message);
1149
1470
  }
1150
1471
  }
1151
1472
 
1152
- // The offline engine is a control-thread mirror. It has no render loop to
1153
- // drain its command ring, so drain it after each control operation. Never
1154
- // call this from the worklet/audio path: it is intentionally control-only.
1473
+ // The control-only mirror applies due commands immediately while preserving
1474
+ // scheduled ones for its next render block.
1155
1475
  private flushOfflineMirror(): void {
1156
- this.offlineEngine.flushControlCommands();
1476
+ this.offlineEngine.applyCommandsDueNowPreservingFuture();
1157
1477
  }
1158
1478
 
1159
1479
  private sendMirroredCommand(
@@ -1175,16 +1495,25 @@ export class SonareEngine {
1175
1495
  trackSends: this.trackSends,
1176
1496
  trackOutputBus: this.trackOutputBus,
1177
1497
  laneSidechains: this.laneSidechains,
1498
+ busSidechains: this.busSidechains,
1499
+ masterSidechains: this.masterSidechains,
1178
1500
  buses: this.buses,
1179
1501
  trackStripJson: this.trackStripJson,
1180
1502
  busStripJson: this.busStripJson,
1503
+ insertParamOverrides: this.insertParamOverrides,
1504
+ flushOfflineMirror: () => this.flushOfflineMirror(),
1181
1505
  postSync: (message) => this.postSync(message),
1182
1506
  ensureTrackLane: (target) => this.ensureTrackLane(target),
1183
1507
  ensureBus: (busId) => this.ensureBus(busId),
1184
1508
  mixerLanes: () => this.mixerLanes(),
1185
- syncMixer: () => this.syncMixer(),
1509
+ syncMixer: (forceInsertResets) => this.syncMixer(forceInsertResets),
1510
+ clearInsertAutomationLanes: (target, forgetResolvedIds) =>
1511
+ this.clearInsertAutomationLanes(target, forgetResolvedIds),
1186
1512
  sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
1187
1513
  getMasterStripJson: () => this.masterStripJson,
1514
+ cacheMasterStripJson: (sceneJson) => {
1515
+ this.masterStripJson = sceneJson;
1516
+ },
1188
1517
  };
1189
1518
  }
1190
1519
 
@@ -1199,6 +1528,10 @@ export class SonareEngine {
1199
1528
  postInstrumentSync: (message) => this.postInstrumentSync(message),
1200
1529
  ensureTrackLane: (target) => this.ensureTrackLane(target),
1201
1530
  resolveTargetId: (target) => this.resolveTargetId(target),
1531
+ readStripJson: (target) => mixer.cachedStripJson(this.mixerContext, target),
1532
+ writeStripJson: (target, sceneJson) =>
1533
+ mixer.cacheStripJson(this.mixerContext, target, sceneJson),
1534
+ insertParamOverrides: this.insertParamOverrides,
1202
1535
  };
1203
1536
  }
1204
1537
 
@@ -1352,7 +1685,7 @@ export class SonareEngine {
1352
1685
  private ensureTrackLane(target: string | number): number {
1353
1686
  const trackId = this.resolveTargetId(target);
1354
1687
  if (!Number.isInteger(trackId) || trackId <= 0) {
1355
- throw new Error(`Invalid track id for mixer lane: ${String(target)}`);
1688
+ throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
1356
1689
  }
1357
1690
  const existing = this.trackLaneIds.indexOf(trackId);
1358
1691
  if (existing >= 0) {
@@ -1364,15 +1697,16 @@ export class SonareEngine {
1364
1697
  }
1365
1698
 
1366
1699
  private ensureBus(busId: number): number {
1367
- const resolved = Math.trunc(busId);
1368
- if (!Number.isInteger(resolved) || resolved <= 0) {
1369
- throw new Error(`Invalid bus id for mixer bus: ${String(busId)}`);
1700
+ // Tested on the raw id: truncating first leaves the integrality check
1701
+ // unreachable, and registers a bus under an id the caller never named.
1702
+ if (!Number.isInteger(busId) || busId <= 0) {
1703
+ throw new RangeError(`Invalid bus id for mixer bus: ${String(busId)}`);
1370
1704
  }
1371
- const existing = this.buses.findIndex((bus) => bus.busId === resolved);
1705
+ const existing = this.buses.findIndex((bus) => bus.busId === busId);
1372
1706
  if (existing >= 0) {
1373
1707
  return existing;
1374
1708
  }
1375
- this.buses.push({ busId: resolved });
1709
+ this.buses.push({ busId });
1376
1710
  this.syncMixer();
1377
1711
  return this.buses.length - 1;
1378
1712
  }