@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.
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 +7 -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 +874 -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 +502 -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 +134 -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 +4173 -1621
  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 +483 -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 +654 -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 +528 -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 +165 -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 +893 -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 +3945 -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 +340 -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 +164 -0
  179. package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
  180. package/dist/worklet/engine-node.d.ts +83 -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 +72 -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 +75 -0
  193. package/dist/worklet/engine-strips.d.ts.map +1 -0
  194. package/dist/worklet/engine-sync.d.ts +39 -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 +418 -0
  199. package/dist/worklet/engine.d.ts.map +1 -0
  200. package/dist/worklet/guards.d.ts +53 -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 +331 -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 +3200 -541
  215. package/dist/worklet.js.map +1 -1
  216. package/package.json +23 -12
  217. package/src/_effects_common.ts +47 -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 +101 -22
  226. package/src/effects_note_ops.ts +683 -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 +388 -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 +288 -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 +441 -32
  246. package/src/mastering_dynamics.ts +22 -11
  247. package/src/metering.ts +67 -24
  248. package/src/mixer.ts +251 -22
  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 +271 -271
  259. package/src/public_types.ts +122 -3
  260. package/src/public_types_acoustic.ts +112 -3
  261. package/src/public_types_mastering.ts +275 -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 +196 -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 +491 -5
  268. package/src/quick_analysis.ts +203 -26
  269. package/src/realtime_engine.ts +773 -34
  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 +1158 -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 +202 -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 +800 -32
  287. package/src/worklet/engine-node.ts +99 -29
  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 +332 -93
  291. package/src/worklet/engine-register.ts +32 -18
  292. package/src/worklet/engine-strips.ts +275 -9
  293. package/src/worklet/engine-sync.ts +20 -7
  294. package/src/worklet/engine.ts +394 -48
  295. package/src/worklet/guards.ts +195 -44
  296. package/src/worklet/messages.ts +229 -2
  297. package/src/worklet/mixer-processor.ts +117 -48
  298. package/src/worklet/playback-processor.ts +300 -0
  299. package/src/worklet/protocol.ts +82 -11
  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,19 +95,30 @@ 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>();
100
102
  private readonly trackLaneIds: number[] = [];
101
103
  private readonly trackSends = new Map<number, EngineTrackSend[]>();
102
104
  private readonly trackOutputBus = new Map<number, number>();
105
+ private readonly trackSourceChannelLayout = new Map<number, number>();
103
106
  private readonly laneSidechains = new Map<
104
107
  string,
105
108
  { trackId: number; insertIndex: number; sourceTrackId: number }
106
109
  >();
110
+ private readonly busSidechains = new Map<
111
+ string,
112
+ { busId: number; insertIndex: number; sourceKind: number; sourceId: number }
113
+ >();
114
+ private readonly masterSidechains = new Map<
115
+ number,
116
+ { insertIndex: number; sourceKind: number; sourceId: number }
117
+ >();
107
118
  private readonly buses: EngineBus[] = [];
108
119
  private readonly trackStripJson = new Map<number, string>();
109
120
  private readonly busStripJson = new Map<number, string>();
121
+ private readonly insertParamOverrides: InsertParamOverrideMap = new Map();
110
122
  private masterStripJson: string | undefined;
111
123
  private captureConfig: Omit<SonareEngineSyncCaptureMessage, 'type'> | undefined;
112
124
  private tempoBpm = 120;
@@ -231,6 +243,11 @@ export class SonareEngine {
231
243
  tempo.setTimeSignatureSegments(this.tempoContext, segments);
232
244
  }
233
245
 
246
+ /**
247
+ * A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
248
+ * centred pitch bend on every channel played since the last reset, so
249
+ * controller values set before a loop region are not restored at the wrap.
250
+ */
234
251
  setLoop(startPpq: number, endPpq: number, enabled = true): boolean {
235
252
  return tempo.setLoop(this.tempoContext, startPpq, endPpq, enabled);
236
253
  }
@@ -310,6 +327,15 @@ export class SonareEngine {
310
327
  * mixer lane first (like automationParamId) so the offline engine resolves the
311
328
  * same strip selector the realtime engine uses.
312
329
  *
330
+ * The returned id uses the track's current positional lane selector. When
331
+ * setTrackLanes successfully changes lane order or membership, the engine
332
+ * remaps already queued and published track automation by track id, but it
333
+ * cannot update a numeric id retained by the caller. Re-resolve every track
334
+ * insert id after such a topology change before passing it to
335
+ * setAutomationLane. Use setTrackStripInsertParamByName when the operation
336
+ * needs a stable track identity. Master and bus insert ids are separate and
337
+ * are not invalidated by track-lane changes.
338
+ *
313
339
  * @param target Track id (declares a mixer lane on first use).
314
340
  * @param insertIndex Index into the strip's combined insert sequence.
315
341
  * @param paramName Processor JSON-key parameter name.
@@ -320,12 +346,17 @@ export class SonareEngine {
320
346
  insertIndex: number,
321
347
  paramName: string,
322
348
  ): number {
323
- return parameter.resolveTrackInsertAutomationId(
349
+ const id = parameter.resolveTrackInsertAutomationId(
324
350
  this.parameterContext,
325
351
  target,
326
352
  insertIndex,
327
353
  paramName,
328
354
  );
355
+ if (id >= 0) {
356
+ const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
357
+ this.rememberInsertAutomationId({ kind: 'track', trackId }, id);
358
+ }
359
+ return id;
329
360
  }
330
361
 
331
362
  /**
@@ -337,7 +368,15 @@ export class SonareEngine {
337
368
  * @returns Reserved insert-automation id, or -1 when insert/key unknown.
338
369
  */
339
370
  resolveMasterInsertAutomationId(insertIndex: number, paramName: string): number {
340
- return parameter.resolveMasterInsertAutomationId(this.parameterContext, insertIndex, paramName);
371
+ const id = parameter.resolveMasterInsertAutomationId(
372
+ this.parameterContext,
373
+ insertIndex,
374
+ paramName,
375
+ );
376
+ if (id >= 0) {
377
+ this.rememberInsertAutomationId({ kind: 'master' }, id);
378
+ }
379
+ return id;
341
380
  }
342
381
 
343
382
  /**
@@ -351,12 +390,30 @@ export class SonareEngine {
351
390
  * @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
352
391
  */
353
392
  resolveBusInsertAutomationId(busId: number, insertIndex: number, paramName: string): number {
354
- return parameter.resolveBusInsertAutomationId(
393
+ const id = parameter.resolveBusInsertAutomationId(
355
394
  this.parameterContext,
356
395
  busId,
357
396
  insertIndex,
358
397
  paramName,
359
398
  );
399
+ if (id >= 0) {
400
+ this.rememberInsertAutomationId({ kind: 'bus', busId }, id);
401
+ }
402
+ return id;
403
+ }
404
+
405
+ /**
406
+ * Resolves a hosted instrument's continuous parameter to its reserved
407
+ * instrument-automation id, so an instrument parameter follows a breakpoint
408
+ * lane at audio-block precision like a strip insert. Bind the instrument
409
+ * (`setSynthInstrument`) before resolving.
410
+ *
411
+ * @param destinationId MIDI destination the instrument is bound to.
412
+ * @param paramName Instrument JSON-key parameter name (e.g. `cutoffHz`).
413
+ * @returns Reserved instrument-automation id, or -1 when destination/key unknown.
414
+ */
415
+ resolveInstrumentAutomationId(destinationId: number, paramName: string): number {
416
+ return parameter.resolveInstrumentAutomationId(this.parameterContext, destinationId, paramName);
360
417
  }
361
418
 
362
419
  /**
@@ -397,21 +454,39 @@ export class SonareEngine {
397
454
  }
398
455
 
399
456
  setStripGain(target: string | number, db: number): boolean {
400
- return this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
457
+ const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
458
+ if (sent && target === 'master') {
459
+ strips.cacheMasterStripScalar(this.stripContext, 'faderDb', db);
460
+ }
461
+ return sent;
401
462
  }
402
463
 
403
464
  setStripPan(target: string | number, pan: number): boolean {
404
- return this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
465
+ const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
466
+ if (sent && target === 'master') {
467
+ strips.cacheMasterStripScalar(this.stripContext, 'pan', pan);
468
+ }
469
+ return sent;
470
+ }
471
+
472
+ /** Resets the master's integrated loudness accumulator in both engine mirrors. */
473
+ resetMasterLoudnessMeter(renderFrame = -1): boolean {
474
+ this.offlineEngine.resetMasterLoudnessMeter(renderFrame);
475
+ return this.sendMirroredCommand({
476
+ type: SonareEngineCommandType.ResetMasterLoudnessMeter,
477
+ sampleTime: renderFrame,
478
+ });
405
479
  }
406
480
 
407
481
  /**
408
482
  * Declares the mixer track lanes in an explicit order.
409
483
  *
410
- * Lane indices are append-only: once a track id occupies a lane, its index
411
- * stays fixed for the engine's lifetime. The given list must therefore start
412
- * with the already-declared lane ids in their current order and may only
413
- * append new track ids after them. Entries carrying `sends` replace that
414
- * track's send list; entries without `sends` leave existing sends untouched.
484
+ * A successful call may reorder existing track ids, remove track ids, or add
485
+ * new ones. Existing queued and published track automation follows the track
486
+ * id, while numeric track-insert automation ids retained by the caller do
487
+ * not; re-resolve those ids after every topology change. Entries carrying
488
+ * `sends` replace that track's send list; entries without `sends` leave
489
+ * existing sends untouched.
415
490
  *
416
491
  * @param lanes Track ids or lane descriptors in the desired lane order.
417
492
  */
@@ -448,15 +523,48 @@ export class SonareEngine {
448
523
  mixer.setTrackBuses(this.mixerContext, buses);
449
524
  }
450
525
 
526
+ /**
527
+ * Keys one insert of a bus strip from a track lane or another bus
528
+ * (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
529
+ */
530
+ setBusSidechain(
531
+ busId: number,
532
+ insertIndex: number,
533
+ sourceKind: SidechainSourceKind | number,
534
+ sourceId: number,
535
+ ): void {
536
+ mixer.setBusSidechain(this.mixerContext, busId, insertIndex, sourceKind, sourceId);
537
+ }
538
+
539
+ /**
540
+ * Keys one insert of the master strip from a track lane or a bus. Same
541
+ * source rules as {@link setBusSidechain}.
542
+ */
543
+ setMasterSidechain(
544
+ insertIndex: number,
545
+ sourceKind: SidechainSourceKind | number,
546
+ sourceId: number,
547
+ ): void {
548
+ mixer.setMasterSidechain(this.mixerContext, insertIndex, sourceKind, sourceId);
549
+ }
550
+
451
551
  setBusGain(busId: number, db: number): boolean {
452
552
  return mixer.setBusGain(this.mixerContext, busId, db);
453
553
  }
454
554
 
455
555
  setTrackStripJson(target: string | number, sceneJson: string): void {
556
+ // Parse before declaring a new lane so malformed text cannot declare one.
557
+ mixer.decodedInsertSignature(sceneJson, { kind: 'track', trackId: 0 });
456
558
  const laneIndex = this.ensureTrackLane(target);
457
559
  const trackId = this.trackLaneIds[laneIndex];
458
- strips.setTrackStripJson(this.stripContext, trackId, sceneJson, this.trackStripJson);
459
- this.syncMixer();
560
+ const { insertBaseResets, constructionOverrides } = strips.setTrackStripJson(
561
+ this.stripContext,
562
+ trackId,
563
+ sceneJson,
564
+ this.trackStripJson,
565
+ );
566
+ mixer.pruneStripSidechains(this.mixerContext, { kind: 'track', trackId }, sceneJson);
567
+ this.syncMixer(insertBaseResets, constructionOverrides);
460
568
  }
461
569
 
462
570
  setTrackStripEqBand(target: string | number, bandIndex: number, band: EqBand | string): void {
@@ -541,9 +649,7 @@ export class SonareEngine {
541
649
  }
542
650
 
543
651
  setMasterStripJson(sceneJson: string): void {
544
- this.offlineEngine.setMasterStripJson(sceneJson);
545
- this.masterStripJson = sceneJson;
546
- this.syncMixer();
652
+ mixer.setMasterStripJson(this.mixerContext, sceneJson);
547
653
  }
548
654
 
549
655
  setMasterStripEqBand(bandIndex: number, band: EqBand | string): void {
@@ -572,6 +678,58 @@ export class SonareEngine {
572
678
  strips.setBusStripInsertParamByName(this.stripContext, busId, insertIndex, paramName, value);
573
679
  }
574
680
 
681
+ /**
682
+ * Bus-strip counterpart of {@link setStripInsertBypassed}. A bus is addressed
683
+ * by its numeric id, which the `target` of the track/master form cannot
684
+ * express, so it is its own method exactly like
685
+ * {@link setBusStripInsertParamByName}. Bypassing keeps the insert's internal
686
+ * state (reverb tail, compressor envelope) unless `resetOnBypass` is set;
687
+ * re-posting the bus scene JSON, the only workaround before this existed,
688
+ * rebuilds the chain and loses it.
689
+ */
690
+ setBusStripInsertBypassed(
691
+ busId: number,
692
+ insertIndex: number,
693
+ bypassed: boolean,
694
+ resetOnBypass = false,
695
+ ): void {
696
+ this.ensureBus(busId);
697
+ strips.setBusStripInsertBypassed(
698
+ this.stripContext,
699
+ busId,
700
+ insertIndex,
701
+ bypassed,
702
+ resetOnBypass,
703
+ );
704
+ }
705
+
706
+ /** Bus-strip counterpart of {@link setTrackStripEqBand}. */
707
+ setBusStripEqBand(busId: number, bandIndex: number, band: EqBand | string): void {
708
+ this.ensureBus(busId);
709
+ strips.setBusStripEqBand(this.stripContext, busId, bandIndex, band);
710
+ }
711
+
712
+ /** Bus-strip counterpart of {@link setTrackStripPan}; refused on a surround bus. */
713
+ setBusStripPan(busId: number, pan: number): void {
714
+ this.ensureBus(busId);
715
+ strips.setBusStripPan(this.stripContext, busId, pan);
716
+ }
717
+
718
+ setBusStripPanLaw(busId: number, panLaw: PanLaw | number): void {
719
+ this.ensureBus(busId);
720
+ strips.setBusStripPanLaw(this.stripContext, busId, panLaw);
721
+ }
722
+
723
+ setBusStripPanMode(busId: number, panMode: PanMode | number): void {
724
+ this.ensureBus(busId);
725
+ strips.setBusStripPanMode(this.stripContext, busId, panMode);
726
+ }
727
+
728
+ setBusStripDualPan(busId: number, leftPan: number, rightPan: number): void {
729
+ this.ensureBus(busId);
730
+ strips.setBusStripDualPan(this.stripContext, busId, leftPan, rightPan);
731
+ }
732
+
575
733
  setStripInsertParamByName(
576
734
  target: string | number,
577
735
  insertIndex: number,
@@ -664,6 +822,52 @@ export class SonareEngine {
664
822
  return { binding, provider: binding.provider };
665
823
  }
666
824
 
825
+ /**
826
+ * Sets the clip-page look-ahead window in timeline frames on both the worklet
827
+ * engine and this thread's offline engine.
828
+ *
829
+ * The audio thread reports the pages it is *about to* read that are not
830
+ * resident yet, so the sliding-window streamer can service them before the
831
+ * playhead reaches them. Without look-ahead a page miss is only reported
832
+ * after the read already produced silence, which costs one block of silence
833
+ * at every page boundary that was not primed.
834
+ *
835
+ * Defaults to half a second at the engine's sample rate. `0` disables it.
836
+ * Safe to call during playback.
837
+ */
838
+ setClipPagePrefetchFrames(frames: number): void {
839
+ if (this.destroyed) {
840
+ throw new Error('SonareEngine is destroyed.');
841
+ }
842
+ if (!Number.isFinite(frames) || frames < 0) {
843
+ throw new Error('clip page prefetch frames must be a finite value >= 0.');
844
+ }
845
+ this.offlineEngine.setClipPagePrefetchFrames(frames);
846
+ this.postSync({ type: 'syncClipPagePrefetchFrames', frames });
847
+ }
848
+
849
+ /**
850
+ * Sets the number of concurrent time-stretch voices on both this thread's
851
+ * offline engine and the live worklet engine. `voices` must be an integer
852
+ * in `[0, 64]`; a non-integer, negative, or larger value throws and leaves
853
+ * the capacity unchanged on both engines. Default is 8. Capacity 0 disables
854
+ * time-stretch, so every warped clip plays resampled instead. A change
855
+ * applied while the engine is running restarts the splice state of any clip
856
+ * stretching through a voice at that moment.
857
+ */
858
+ setWarpVoiceCapacity(voices: number): void {
859
+ if (this.destroyed) {
860
+ throw new Error('SonareEngine is destroyed.');
861
+ }
862
+ this.offlineEngine.setWarpVoiceCapacity(voices);
863
+ this.postSync({ type: 'syncWarpVoiceCapacity', voices });
864
+ }
865
+
866
+ /** Reads the current time-stretch voice capacity (default 8). */
867
+ warpVoiceCapacity(): number {
868
+ return this.offlineEngine.warpVoiceCapacity();
869
+ }
870
+
667
871
  addClip(
668
872
  trackId: string | number,
669
873
  buffer: Float32Array[] | ClipPageProvider,
@@ -703,6 +907,8 @@ export class SonareEngine {
703
907
  gain?: number;
704
908
  polyphony?: number;
705
909
  preferModelForModeledFamilies?: boolean;
910
+ clearBankRig?: boolean;
911
+ gsEfxRealization?: 'modern' | 'classic';
706
912
  } = {},
707
913
  ): void {
708
914
  strips.setSf2Instrument(this.stripContext, trackId, config);
@@ -711,7 +917,10 @@ export class SonareEngine {
711
917
  /**
712
918
  * Route a track's MIDI to the external output (drained via {@link onMidiOut})
713
919
  * instead of an internal instrument, so the track plays an external device.
714
- * Pass `external=false` to restore internal-synth playback.
920
+ * Pass `external=false` to restore internal-synth playback. The change takes
921
+ * effect at the next processed block; switching a destination's route first
922
+ * releases its notes and resets its controllers through the old route, and
923
+ * drops its pending MIDI-FX events.
715
924
  */
716
925
  setMidiDestinationExternal(trackId: string | number, external: boolean): void {
717
926
  strips.setMidiDestinationExternal(this.stripContext, trackId, external);
@@ -776,6 +985,52 @@ export class SonareEngine {
776
985
  strips.pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
777
986
  }
778
987
 
988
+ pushMidiPitchBend(
989
+ trackId: string | number,
990
+ group: number,
991
+ channel: number,
992
+ bend14: number,
993
+ renderFrame = -1,
994
+ ): void {
995
+ strips.pushMidiPitchBend(this.stripContext, trackId, group, channel, bend14, renderFrame);
996
+ }
997
+
998
+ pushMidiChannelPressure(
999
+ trackId: string | number,
1000
+ group: number,
1001
+ channel: number,
1002
+ pressure: number,
1003
+ renderFrame = -1,
1004
+ ): void {
1005
+ strips.pushMidiChannelPressure(
1006
+ this.stripContext,
1007
+ trackId,
1008
+ group,
1009
+ channel,
1010
+ pressure,
1011
+ renderFrame,
1012
+ );
1013
+ }
1014
+
1015
+ pushMidiPolyPressure(
1016
+ trackId: string | number,
1017
+ group: number,
1018
+ channel: number,
1019
+ note: number,
1020
+ pressure: number,
1021
+ renderFrame = -1,
1022
+ ): void {
1023
+ strips.pushMidiPolyPressure(
1024
+ this.stripContext,
1025
+ trackId,
1026
+ group,
1027
+ channel,
1028
+ note,
1029
+ pressure,
1030
+ renderFrame,
1031
+ );
1032
+ }
1033
+
779
1034
  pushMidiUmp(trackId: string | number, word0: number, renderFrame = -1): void {
780
1035
  strips.pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
781
1036
  }
@@ -860,6 +1115,58 @@ export class SonareEngine {
860
1115
  });
861
1116
  }
862
1117
 
1118
+ pushMidiInputPitchBend(
1119
+ group: number,
1120
+ channel: number,
1121
+ bend14: number,
1122
+ portTimeSamples = 0,
1123
+ ): void {
1124
+ this.offlineEngine.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
1125
+ this.postSync({
1126
+ type: 'syncMidiInputPitchBend',
1127
+ group,
1128
+ channel,
1129
+ data0: bend14,
1130
+ data1: 0,
1131
+ portTimeSamples,
1132
+ });
1133
+ }
1134
+
1135
+ pushMidiInputChannelPressure(
1136
+ group: number,
1137
+ channel: number,
1138
+ pressure: number,
1139
+ portTimeSamples = 0,
1140
+ ): void {
1141
+ this.offlineEngine.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
1142
+ this.postSync({
1143
+ type: 'syncMidiInputChannelPressure',
1144
+ group,
1145
+ channel,
1146
+ data0: pressure,
1147
+ data1: 0,
1148
+ portTimeSamples,
1149
+ });
1150
+ }
1151
+
1152
+ pushMidiInputPolyPressure(
1153
+ group: number,
1154
+ channel: number,
1155
+ note: number,
1156
+ pressure: number,
1157
+ portTimeSamples = 0,
1158
+ ): void {
1159
+ this.offlineEngine.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
1160
+ this.postSync({
1161
+ type: 'syncMidiInputPolyPressure',
1162
+ group,
1163
+ channel,
1164
+ data0: note,
1165
+ data1: pressure,
1166
+ portTimeSamples,
1167
+ });
1168
+ }
1169
+
863
1170
  pushMidiPanic(renderFrame = -1): void {
864
1171
  this.offlineEngine.pushMidiPanic(renderFrame);
865
1172
  this.postSync({ type: 'syncMidiPanic', renderFrame });
@@ -1106,8 +1413,45 @@ export class SonareEngine {
1106
1413
  return mixer.mixerLanes(this.mixerContext);
1107
1414
  }
1108
1415
 
1109
- private syncMixer(): void {
1110
- mixer.syncMixer(this.mixerContext);
1416
+ private syncMixer(
1417
+ insertBaseResets: mixer.StripJsonTarget[] = [],
1418
+ oneShotInsertParamOverrides: mixer.InsertParamOverride[] = [],
1419
+ ): void {
1420
+ mixer.syncMixer(this.mixerContext, false, insertBaseResets, oneShotInsertParamOverrides);
1421
+ }
1422
+
1423
+ private insertAutomationTargetKey(target: mixer.StripJsonTarget): string {
1424
+ return target.kind === 'track'
1425
+ ? `track:${target.trackId}`
1426
+ : target.kind === 'bus'
1427
+ ? `bus:${target.busId}`
1428
+ : 'master';
1429
+ }
1430
+
1431
+ private rememberInsertAutomationId(target: mixer.StripJsonTarget, id: number): void {
1432
+ const key = this.insertAutomationTargetKey(target);
1433
+ const ids = this.insertAutomationIdsByTarget.get(key) ?? new Set<number>();
1434
+ ids.add(id);
1435
+ this.insertAutomationIdsByTarget.set(key, ids);
1436
+ }
1437
+
1438
+ private clearInsertAutomationLanes(
1439
+ target: mixer.StripJsonTarget,
1440
+ forgetResolvedIds = false,
1441
+ ): void {
1442
+ const key = this.insertAutomationTargetKey(target);
1443
+ const ids = this.insertAutomationIdsByTarget.get(key);
1444
+ if (!ids) {
1445
+ return;
1446
+ }
1447
+ for (const id of ids) {
1448
+ if (this.automationLanes.has(id)) {
1449
+ this.setAutomationLane(id, []);
1450
+ }
1451
+ }
1452
+ if (forgetResolvedIds) {
1453
+ this.insertAutomationIdsByTarget.delete(key);
1454
+ }
1111
1455
  }
1112
1456
 
1113
1457
  private postInstrumentSync(message: SonareEngineInstrumentSyncMessage): void {
@@ -1138,22 +1482,17 @@ export class SonareEngine {
1138
1482
  if (this.destroyed) {
1139
1483
  return;
1140
1484
  }
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();
1485
+ if (transfer && transfer.length > 0) {
1486
+ this.realtimeNode.node.port.postMessage(message, transfer);
1487
+ } else {
1488
+ this.realtimeNode.node.port.postMessage(message);
1149
1489
  }
1150
1490
  }
1151
1491
 
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.
1492
+ // The control-only mirror applies due commands immediately while preserving
1493
+ // scheduled ones for its next render block.
1155
1494
  private flushOfflineMirror(): void {
1156
- this.offlineEngine.flushControlCommands();
1495
+ this.offlineEngine.applyCommandsDueNowPreservingFuture();
1157
1496
  }
1158
1497
 
1159
1498
  private sendMirroredCommand(
@@ -1174,17 +1513,28 @@ export class SonareEngine {
1174
1513
  trackLaneIds: this.trackLaneIds,
1175
1514
  trackSends: this.trackSends,
1176
1515
  trackOutputBus: this.trackOutputBus,
1516
+ trackSourceChannelLayout: this.trackSourceChannelLayout,
1177
1517
  laneSidechains: this.laneSidechains,
1518
+ busSidechains: this.busSidechains,
1519
+ masterSidechains: this.masterSidechains,
1178
1520
  buses: this.buses,
1179
1521
  trackStripJson: this.trackStripJson,
1180
1522
  busStripJson: this.busStripJson,
1523
+ insertParamOverrides: this.insertParamOverrides,
1524
+ flushOfflineMirror: () => this.flushOfflineMirror(),
1181
1525
  postSync: (message) => this.postSync(message),
1182
1526
  ensureTrackLane: (target) => this.ensureTrackLane(target),
1183
1527
  ensureBus: (busId) => this.ensureBus(busId),
1184
1528
  mixerLanes: () => this.mixerLanes(),
1185
- syncMixer: () => this.syncMixer(),
1529
+ syncMixer: (insertBaseResets, oneShotInsertParamOverrides) =>
1530
+ this.syncMixer(insertBaseResets, oneShotInsertParamOverrides),
1531
+ clearInsertAutomationLanes: (target, forgetResolvedIds) =>
1532
+ this.clearInsertAutomationLanes(target, forgetResolvedIds),
1186
1533
  sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
1187
1534
  getMasterStripJson: () => this.masterStripJson,
1535
+ cacheMasterStripJson: (sceneJson) => {
1536
+ this.masterStripJson = sceneJson;
1537
+ },
1188
1538
  };
1189
1539
  }
1190
1540
 
@@ -1199,6 +1549,11 @@ export class SonareEngine {
1199
1549
  postInstrumentSync: (message) => this.postInstrumentSync(message),
1200
1550
  ensureTrackLane: (target) => this.ensureTrackLane(target),
1201
1551
  resolveTargetId: (target) => this.resolveTargetId(target),
1552
+ readStripJson: (target) => mixer.cachedStripJson(this.mixerContext, target),
1553
+ writeStripJson: (target, sceneJson) =>
1554
+ mixer.cacheStripJson(this.mixerContext, target, sceneJson),
1555
+ insertParamOverrides: this.insertParamOverrides,
1556
+ clearInsertAutomationLanes: (target) => this.clearInsertAutomationLanes(target),
1202
1557
  };
1203
1558
  }
1204
1559
 
@@ -1350,29 +1705,20 @@ export class SonareEngine {
1350
1705
  }
1351
1706
 
1352
1707
  private ensureTrackLane(target: string | number): number {
1353
- const trackId = this.resolveTargetId(target);
1354
- if (!Number.isInteger(trackId) || trackId <= 0) {
1355
- throw new Error(`Invalid track id for mixer lane: ${String(target)}`);
1356
- }
1357
- const existing = this.trackLaneIds.indexOf(trackId);
1358
- if (existing >= 0) {
1359
- return existing;
1360
- }
1361
- this.trackLaneIds.push(trackId);
1362
- this.syncMixer();
1363
- return this.trackLaneIds.length - 1;
1708
+ return mixer.ensureTrackLane(this.mixerContext, target);
1364
1709
  }
1365
1710
 
1366
1711
  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)}`);
1712
+ // Tested on the raw id: truncating first leaves the integrality check
1713
+ // unreachable, and registers a bus under an id the caller never named.
1714
+ if (!Number.isInteger(busId) || busId <= 0) {
1715
+ throw new RangeError(`Invalid bus id for mixer bus: ${String(busId)}`);
1370
1716
  }
1371
- const existing = this.buses.findIndex((bus) => bus.busId === resolved);
1717
+ const existing = this.buses.findIndex((bus) => bus.busId === busId);
1372
1718
  if (existing >= 0) {
1373
1719
  return existing;
1374
1720
  }
1375
- this.buses.push({ busId: resolved });
1721
+ this.buses.push({ busId });
1376
1722
  this.syncMixer();
1377
1723
  return this.buses.length - 1;
1378
1724
  }