@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,8 +1,29 @@
1
- import { panLawCode, panModeCode, sendTimingCode, trackMonitorModeCode } from './codes';
1
+ import {
2
+ panLawCode,
3
+ panModeCode,
4
+ sendTimingCode,
5
+ sidechainSourceKindCode,
6
+ trackMonitorModeCode,
7
+ } from './codes';
2
8
  import { ErrorCode, SonareError } from './errors';
3
9
  import { getSonareModule } from './module_state';
4
- import type { ProjectMidiCcBinding, SynthPatch } from './project';
5
- import type { EqBand, PanLawInput, PanMode, SendTiming } from './public_types';
10
+ import type {
11
+ Articulation,
12
+ ControllerBinding,
13
+ MpeDimension,
14
+ NoteTracking,
15
+ ProjectMidiCcBinding,
16
+ SynthPatch,
17
+ } from './project';
18
+ import { normalizeSynthInstrument } from './project_internal';
19
+ import type {
20
+ EqBand,
21
+ PanLawInput,
22
+ PanMode,
23
+ SendTiming,
24
+ SidechainSourceKind,
25
+ UmpWords,
26
+ } from './public_types';
6
27
  import type {
7
28
  WasmClipPageRequest,
8
29
  WasmEngineAutomationPoint,
@@ -94,15 +115,37 @@ export interface EngineBus {
94
115
  * to stereo.
95
116
  */
96
117
  channelLayout?: number;
118
+ /**
119
+ * Bus this bus's output sums into instead of the master mix (bus-to-bus
120
+ * routing); 0 or absent keeps it on the master mix.
121
+ */
122
+ outputBusId?: number;
123
+ /**
124
+ * Sends to other buses, in the same shape as a track lane's sends. A
125
+ * pre-fader send taps before `gainDb`, a post-fader one after it.
126
+ */
127
+ sends?: EngineTrackSend[];
97
128
  }
98
129
 
99
130
  export interface EngineMidiEvent {
100
- renderFrame: number;
131
+ /** Absolute render frame for this event. Default `0`. */
132
+ renderFrame?: number;
101
133
  word0?: number;
102
134
  word1?: number;
103
135
  word2?: number;
104
136
  word3?: number;
105
137
  wordCount?: number;
138
+ /**
139
+ * Redundant with `word0`, which already carries the UMP group in bits 24..27.
140
+ * The engine reads the group from `word0` — the form that reaches a device or
141
+ * a file — so packing it there is sufficient and a value here that contradicts
142
+ * `word0` is ignored. Must still be in `[0, 15]`; anything else is rejected as
143
+ * a malformed event. Default `0`.
144
+ *
145
+ * Utility (`word0` type nibble `0x0`) and UMP Stream (`0xF`) messages have no
146
+ * group field — those bits are Reserved and `form`/`status` respectively — so
147
+ * they always read as group `0` and packing a group into them has no effect.
148
+ */
106
149
  group?: number;
107
150
  sysexHandle?: number;
108
151
  data0?: number;
@@ -119,6 +162,20 @@ export interface EngineMidiClipSchedule {
119
162
  loop?: boolean;
120
163
  loopLengthSamples?: number;
121
164
  events: EngineMidiEvent[];
165
+ /**
166
+ * Linear gain applied to the destination instrument's rendered audio while
167
+ * this clip is the most recently started active clip on it. Absent defaults
168
+ * to `1` (unity).
169
+ */
170
+ gain?: number;
171
+ /**
172
+ * Linear fade lengths over the clip's full length (not per internal loop
173
+ * repeat). Absent defaults to `0` (no fade). `fadeOutSamples` above `0` is
174
+ * rejected when `lengthSamples` is absent or `<= 0` (open-ended): an
175
+ * open-ended clip has no end to fade out towards.
176
+ */
177
+ fadeInSamples?: number;
178
+ fadeOutSamples?: number;
122
179
  }
123
180
 
124
181
  export const EXPECTED_ENGINE_ABI_VERSION = 3;
@@ -131,6 +188,76 @@ export interface MidiCcBindOptions {
131
188
  maxValue?: number;
132
189
  }
133
190
 
191
+ /** Request form of {@link RealtimeEngine.renderOffline}. */
192
+ export interface RenderOfflineRequest {
193
+ /** One buffer per output plane; their common length is the render span. */
194
+ channels: Float32Array[];
195
+ /** Render block size. Default `128`. */
196
+ blockSize?: number;
197
+ /**
198
+ * Whether this call ends the timeline. `true` (the default, and what a
199
+ * one-shot bounce wants) releases every sounding note and flushes the PDC /
200
+ * alignment delay lines before returning. `false` renders one CHUNK of a
201
+ * longer timeline: a note held across the chunk boundary keeps sounding into
202
+ * the next call and the delay lines carry their history over, so consecutive
203
+ * chunks concatenate to exactly what one continuous render of the same span
204
+ * produces. Call {@link RealtimeEngine.finishOfflineRender} once after the
205
+ * last chunk.
206
+ *
207
+ * Sample-exact concatenation requires every chunk to use the same `blockSize`
208
+ * and a frame count that is a whole number of blocks: each call restarts the
209
+ * block grid at its own frame 0 and renders a short final block for the
210
+ * remainder, and the clip / automation / MIDI-clip snapshots are frozen once
211
+ * per block, so a chunk that ends mid-block shifts every later block
212
+ * boundary. Audio stays continuous either way; only bit-identity is lost.
213
+ */
214
+ finalize?: boolean;
215
+ }
216
+
217
+ const UMP_WORD_MIN = -0x80000000;
218
+ const UMP_WORD_MAX = 0xffffffff;
219
+
220
+ // A word may be spelled `(0x4 << 28) | …`, which is a signed int once bit 31 is
221
+ // set, so the signed 32-bit range is accepted alongside the unsigned one.
222
+ function assertUmpWords(fnName: string, words: UmpWords): UmpWords {
223
+ if (!(words instanceof Uint32Array) && !Array.isArray(words)) {
224
+ throw new TypeError(`${fnName}: words must be a Uint32Array or a number array`);
225
+ }
226
+ if (words.length < 1 || words.length > 4) {
227
+ throw new RangeError(`${fnName}: words must hold 1 to 4 words`);
228
+ }
229
+ for (let i = 0; i < words.length; i++) {
230
+ const word = words[i];
231
+ if (
232
+ typeof word !== 'number' ||
233
+ !Number.isInteger(word) ||
234
+ word < UMP_WORD_MIN ||
235
+ word > UMP_WORD_MAX
236
+ ) {
237
+ throw new RangeError(`${fnName}: words[${i}] must be an integer 32-bit word`);
238
+ }
239
+ }
240
+ return words;
241
+ }
242
+
243
+ /**
244
+ * One normalizer for both call forms, so the request object and the positional
245
+ * overload cannot drift in their defaults.
246
+ */
247
+ function normalizeRenderOfflineRequest(
248
+ channelsOrRequest: Float32Array[] | RenderOfflineRequest,
249
+ blockSize: number,
250
+ ): { channels: Float32Array[]; blockSize: number; finalize: boolean } {
251
+ const request = Array.isArray(channelsOrRequest)
252
+ ? { channels: channelsOrRequest, blockSize }
253
+ : channelsOrRequest;
254
+ return {
255
+ channels: request.channels,
256
+ blockSize: request.blockSize ?? 128,
257
+ finalize: request.finalize ?? true,
258
+ };
259
+ }
260
+
134
261
  export interface EngineCapabilities {
135
262
  engineAbiVersion: number;
136
263
  expectedEngineAbiVersion: number;
@@ -162,6 +289,7 @@ export function engineCapabilities(): EngineCapabilities {
162
289
 
163
290
  export class RealtimeEngine {
164
291
  private native: WasmRealtimeEngine;
292
+ private released = false;
165
293
 
166
294
  constructor(
167
295
  sampleRate = 48000,
@@ -186,6 +314,15 @@ export class RealtimeEngine {
186
314
  );
187
315
  }
188
316
 
317
+ /**
318
+ * Size the engine's queues and scratch for a sample rate and block size.
319
+ *
320
+ * `commandCapacity` must not exceed 65536 and `telemetryCapacity` must not
321
+ * exceed 16384; a larger value throws and leaves the engine untouched. The
322
+ * telemetry number is not a queue depth paid for one-for-one: the engine
323
+ * reserves that many meter records per metered lane, so its memory cost is
324
+ * far larger than the number given here.
325
+ */
189
326
  prepare(
190
327
  sampleRate: number,
191
328
  maxBlockSize: number,
@@ -249,12 +386,16 @@ export class RealtimeEngine {
249
386
  * scheduled MIDI clips routed to that destination render through the synth.
250
387
  * Unknown preset names throw. An object patch's `destinationId` is a JS
251
388
  * binding convenience, not part of the NativeSynth patch itself.
389
+ *
390
+ * An `engineMode: 'sample'` patch also carries the {@link SampleBank} its
391
+ * keymap names. The synth takes a share of the bank, so it may be released
392
+ * right after this call; a sample patch bound without one renders silence.
252
393
  */
253
394
  setSynthInstrument(
254
395
  patch: SynthPatch | string = {},
255
396
  destinationId = (typeof patch === 'object' ? patch.destinationId : undefined) ?? 0,
256
397
  ): void {
257
- this.native.setSynthInstrument(destinationId, patch);
398
+ this.native.setSynthInstrument(destinationId, normalizeSynthInstrument(patch));
258
399
  }
259
400
 
260
401
  /**
@@ -282,6 +423,8 @@ export class RealtimeEngine {
282
423
  gain?: number;
283
424
  polyphony?: number;
284
425
  preferModelForModeledFamilies?: boolean;
426
+ clearBankRig?: boolean;
427
+ gsEfxRealization?: 'modern' | 'classic';
285
428
  } = {},
286
429
  destinationId = config.destinationId ?? 0,
287
430
  ): void {
@@ -329,6 +472,135 @@ export class RealtimeEngine {
329
472
  return this.native.midiCcBindingCount();
330
473
  }
331
474
 
475
+ /**
476
+ * Replace a destination instrument's controller profile with a named preset
477
+ * (see {@link controllerProfileNames}). Installing a profile drops every
478
+ * channel's accumulated axis values: the new bindings say nothing about what
479
+ * the old ones had reached. An unknown name throws, and so does a destination
480
+ * with no instrument or one whose instrument holds no profile.
481
+ */
482
+ setControllerProfile(destinationId: number, presetName: string): void {
483
+ this.native.setControllerProfile(destinationId, presetName);
484
+ }
485
+
486
+ /** Add one {@link ControllerBinding} on top of the destination's current profile. */
487
+ bindController(destinationId: number, binding: ControllerBinding): void {
488
+ this.native.bindController(destinationId, binding);
489
+ }
490
+
491
+ /**
492
+ * Drop every binding of the destination's controller profile. The instrument
493
+ * keeps a profile; it resolves nothing until something is bound again.
494
+ */
495
+ clearControllerBindings(destinationId: number): void {
496
+ this.native.clearControllerBindings(destinationId);
497
+ }
498
+
499
+ controllerBindingCount(destinationId: number): number {
500
+ return this.native.controllerBindingCount(destinationId);
501
+ }
502
+
503
+ /**
504
+ * Whether note-on velocity is expression for this instrument. No fixed
505
+ * default is possible — a wind controller ships sending breath-derived
506
+ * velocity on one model and a constant on the next — so each preset states it
507
+ * and a host building its own profile sets it. When false the synth takes
508
+ * every note at full scale and the bound axes carry the dynamics alone.
509
+ */
510
+ setControllerVelocityMeaningful(destinationId: number, meaningful: boolean): void {
511
+ this.native.setControllerVelocityMeaningful(destinationId, meaningful);
512
+ }
513
+
514
+ controllerVelocityMeaningful(destinationId: number): boolean {
515
+ return this.native.controllerVelocityMeaningful(destinationId);
516
+ }
517
+
518
+ /**
519
+ * Say which note a value addressed to a whole MIDI channel belongs to when
520
+ * several are sounding on it, for one per-note dimension
521
+ * ({@link MPE_DIMENSIONS}, {@link NOTE_TRACKINGS}).
522
+ *
523
+ * Set per dimension because the useful answers differ: pressure following the
524
+ * newest note while bend reaches every one is a real configuration, not a
525
+ * mistake. MPE poses this question and declines to answer it, so this is a
526
+ * choice rather than a rule — and it is read only inside an MPE zone, and
527
+ * only while more than one note is sounding on the channel, which an MPE
528
+ * sender avoids by giving each note its own member channel.
529
+ *
530
+ * Both arguments are required and are a name or its ordinal; an unknown
531
+ * spelling is refused rather than resolved to a default, as are a destination
532
+ * with no instrument and one whose instrument holds no controller profile.
533
+ */
534
+ setControllerNoteTracking(
535
+ destinationId: number,
536
+ dimension: MpeDimension | number,
537
+ tracking: NoteTracking | number,
538
+ ): void {
539
+ this.native.setControllerNoteTracking(destinationId, dimension, tracking);
540
+ }
541
+
542
+ /**
543
+ * Read back {@link setControllerNoteTracking} for one dimension, as the
544
+ * canonical name.
545
+ */
546
+ controllerNoteTracking(
547
+ destinationId: number,
548
+ dimension: MpeDimension | number,
549
+ ): NoteTracking | number {
550
+ return this.native.controllerNoteTracking(destinationId, dimension);
551
+ }
552
+
553
+ /**
554
+ * Set how one MIDI channel (0–15) of a destination's instrument treats a
555
+ * note-on while another note on that channel is still held: `'poly'` takes a
556
+ * new voice each time, `'mono-retrigger'` stops and restarts the note (what
557
+ * GS MONO MODE and CC126 mean), `'mono-legato'` carries the sounding voice
558
+ * and only moves its pitch — a wind player's slur, which no MIDI message can
559
+ * reach by design.
560
+ *
561
+ * `'mono-legato'` is a request, not a guarantee: an engine whose exciter is
562
+ * spent at the onset — anything struck or plucked — and a target pitch below
563
+ * what the engine's delay line can hold both fall back to an ordinary note,
564
+ * which {@link legatoFallbackCount} counts. A channel outside [0,15] and an
565
+ * articulation outside the enum are refused rather than clamped, and so is a
566
+ * destination with no instrument or one whose instrument has no articulation
567
+ * of its own.
568
+ */
569
+ setArticulation(
570
+ destinationId: number,
571
+ channel: number,
572
+ articulation: Articulation | number,
573
+ ): void {
574
+ this.native.setArticulation(destinationId, channel, articulation);
575
+ }
576
+
577
+ /**
578
+ * Read back {@link setArticulation} as the canonical name. An ordinal this
579
+ * build cannot spell is handed back as the number, the way every other enum
580
+ * leaves this surface.
581
+ */
582
+ articulation(destinationId: number, channel: number): Articulation | number {
583
+ return this.native.articulation(destinationId, channel);
584
+ }
585
+
586
+ /**
587
+ * How many times a legato continuation was asked for on this destination and
588
+ * refused, so the note started a voice of its own instead. Counted rather
589
+ * than inferred: a refusal sounds like an ordinary note, so nothing in the
590
+ * audio separates "this engine declines legato" from "the mode was never
591
+ * set". Saturates at 4294967295 rather than wrapping — matching the C ABI, so
592
+ * the same phrase reports the same number on every surface — after which it
593
+ * reads as "at least this many".
594
+ *
595
+ * Throws on a destination with no instrument, and on one whose instrument has
596
+ * no articulation of its own — the same two refusals
597
+ * {@link setArticulation} keeps apart. Reporting 0 for the second would read
598
+ * as "every slur took", which is the reading this counter exists to prevent.
599
+ */
600
+ legatoFallbackCount(destinationId: number): number {
601
+ return this.native.legatoFallbackCount(destinationId);
602
+ }
603
+
332
604
  /** Install/replace a live non-destructive MIDI-FX insert for one destination. */
333
605
  setMidiFx(destinationId: number, configJson: string): void {
334
606
  this.native.setMidiFx(destinationId, configJson);
@@ -383,6 +655,12 @@ export class RealtimeEngine {
383
655
  * block / animation frame. `maxRecords` caps the number of output events
384
656
  * returned — the shared unit across every surface. Events past the cap stay
385
657
  * queued for the next call (lossless); call again to drain the rest.
658
+ *
659
+ * One queued record lowers to at most 4 MIDI 1.0 messages (a MIDI 2.0
660
+ * registered or assignable controller becomes CC 101/100 or 99/98 plus Data
661
+ * Entry 6/38), so a positive `maxRecords` below 4 could never consume a record
662
+ * and is rejected with an `InvalidParameter` `SonareError` instead of
663
+ * returning nothing forever.
386
664
  */
387
665
  drainExternalMidi(maxRecords = 1024): WasmExternalMidiEvent[] {
388
666
  return this.native.drainExternalMidi(maxRecords);
@@ -398,7 +676,11 @@ export class RealtimeEngine {
398
676
  }
399
677
 
400
678
  externalMidiScratchRenderFrame(): number {
401
- return this.native.externalMidiScratchRenderFrame();
679
+ // embind marshals the int64 render frame as a BigInt; the declared `number`
680
+ // has to be a real number or the first consumer that does arithmetic on it
681
+ // dies with "Cannot mix BigInt". Same normalization as the telemetry, meter
682
+ // and scope scratch frames.
683
+ return Number(this.native.externalMidiScratchRenderFrame());
402
684
  }
403
685
 
404
686
  externalMidiScratchByteWord(): number {
@@ -443,6 +725,50 @@ export class RealtimeEngine {
443
725
  this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
444
726
  }
445
727
 
728
+ /**
729
+ * Push a live MIDI pitch bend to the engine-owned MIDI input source.
730
+ *
731
+ * `bend14` is unsigned 14-bit with centre 8192 (0..16383) — the dimension is
732
+ * not 7-bit, so a value past 16383 is refused rather than narrowed. The input
733
+ * source must be enabled with {@link setMidiInputSource} first.
734
+ */
735
+ pushMidiInputPitchBend(
736
+ group: number,
737
+ channel: number,
738
+ bend14: number,
739
+ portTimeSamples = 0,
740
+ ): void {
741
+ this.native.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
742
+ }
743
+
744
+ /**
745
+ * Push a live MIDI channel pressure to the engine-owned MIDI input source.
746
+ * `pressure` is 7-bit (0..127). Under MPE this is the member channel's
747
+ * per-note pressure.
748
+ */
749
+ pushMidiInputChannelPressure(
750
+ group: number,
751
+ channel: number,
752
+ pressure: number,
753
+ portTimeSamples = 0,
754
+ ): void {
755
+ this.native.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
756
+ }
757
+
758
+ /**
759
+ * Push a live MIDI polyphonic key pressure to the engine-owned MIDI input
760
+ * source. `note` and `pressure` are 7-bit (0..127).
761
+ */
762
+ pushMidiInputPolyPressure(
763
+ group: number,
764
+ channel: number,
765
+ note: number,
766
+ pressure: number,
767
+ portTimeSamples = 0,
768
+ ): void {
769
+ this.native.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
770
+ }
771
+
446
772
  pushMidiNoteOn(
447
773
  destinationId: number,
448
774
  group: number,
@@ -482,9 +808,76 @@ export class RealtimeEngine {
482
808
  this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
483
809
  }
484
810
 
485
- /** Queue one immediate MIDI 1.0 channel-voice UMP word for a destination. */
486
- pushMidiUmp(destinationId: number, word0: number, renderFrame = -1): void {
487
- this.native.pushMidiUmp(destinationId, word0, renderFrame);
811
+ /**
812
+ * Queue an immediate (live) MIDI pitch bend to a MIDI destination. `bend14`
813
+ * is unsigned 14-bit with centre 8192 (0..16383); `renderFrame` is the frame
814
+ * to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
815
+ * `pushMidiPitchBend`.
816
+ */
817
+ pushMidiPitchBend(
818
+ destinationId: number,
819
+ group: number,
820
+ channel: number,
821
+ bend14: number,
822
+ renderFrame = -1,
823
+ ): void {
824
+ this.native.pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame);
825
+ }
826
+
827
+ /**
828
+ * Queue an immediate (live) MIDI channel pressure to a MIDI destination.
829
+ * `pressure` is 7-bit (0..127); `renderFrame` is the frame to fire at, or -1
830
+ * for immediate. Mirrors the Node/Python/C-ABI `pushMidiChannelPressure`.
831
+ */
832
+ pushMidiChannelPressure(
833
+ destinationId: number,
834
+ group: number,
835
+ channel: number,
836
+ pressure: number,
837
+ renderFrame = -1,
838
+ ): void {
839
+ this.native.pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame);
840
+ }
841
+
842
+ /**
843
+ * Queue an immediate (live) MIDI polyphonic key pressure to a MIDI
844
+ * destination. `note` and `pressure` are 7-bit (0..127); `renderFrame` is the
845
+ * frame to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
846
+ * `pushMidiPolyPressure`.
847
+ */
848
+ pushMidiPolyPressure(
849
+ destinationId: number,
850
+ group: number,
851
+ channel: number,
852
+ note: number,
853
+ pressure: number,
854
+ renderFrame = -1,
855
+ ): void {
856
+ this.native.pushMidiPolyPressure(destinationId, group, channel, note, pressure, renderFrame);
857
+ }
858
+
859
+ /**
860
+ * Queue an immediate (live) raw UMP message to a MIDI destination. `words` is
861
+ * 1 to 4 words, most significant first, and its length must match the message
862
+ * type of `words[0]`. MIDI 2.0 channel-voice messages (MT 0x4) arrive at full
863
+ * width; SysEx7 / data messages (MT 0x3 / 0x5) are refused, use
864
+ * {@link pushMidiSysex}. Throws when the slot ring or command queue is full
865
+ * (retry after a process block). `renderFrame` is the render-frame time to
866
+ * apply, or -1 for immediate. A bare number is accepted as a one-word
867
+ * message.
868
+ */
869
+ pushMidiUmp(destinationId: number, words: UmpWords | number, renderFrame = -1): void {
870
+ const list = typeof words === 'number' ? [words] : words;
871
+ this.native.pushMidiUmp(destinationId, assertUmpWords('pushMidiUmp', list), renderFrame);
872
+ }
873
+
874
+ /**
875
+ * Push one raw UMP message (1 to 4 words) to the engine-owned MIDI input
876
+ * source. The message rules match {@link pushMidiUmp}. `portTimeSamples` is
877
+ * the port timestamp in samples.
878
+ */
879
+ pushMidiInputUmp(words: UmpWords, portTimeSamples = 0): void {
880
+ this.native.pushMidiInputUmp(assertUmpWords('pushMidiInputUmp', words), portTimeSamples);
488
881
  }
489
882
 
490
883
  /**
@@ -540,11 +933,21 @@ export class RealtimeEngine {
540
933
  this.native.settleParameters();
541
934
  }
542
935
 
936
+ /** Snap only insert automation slots after structural replay. */
937
+ settleInsertParameters(): void {
938
+ this.native.settleInsertParameters();
939
+ }
940
+
543
941
  /** Drains queued commands on an offline/control-only engine immediately. */
544
942
  flushControlCommands(): void {
545
943
  this.native.flushControlCommands();
546
944
  }
547
945
 
946
+ /** Applies commands already due on a control-only mirror, retaining future commands. */
947
+ applyCommandsDueNowPreservingFuture(): void {
948
+ this.native.applyCommandsDueNowPreservingFuture();
949
+ }
950
+
548
951
  seekPpq(ppq: number, renderFrame = -1): void {
549
952
  this.native.seekPpq(ppq, renderFrame);
550
953
  }
@@ -582,11 +985,11 @@ export class RealtimeEngine {
582
985
  return this.native.parameterCount();
583
986
  }
584
987
 
585
- parameterInfoByIndex(index: number): EngineParameterInfo {
988
+ parameterInfoByIndex(index: number): Required<EngineParameterInfo> {
586
989
  return this.native.parameterInfoByIndex(index);
587
990
  }
588
991
 
589
- parameterInfo(id: number): EngineParameterInfo {
992
+ parameterInfo(id: number): Required<EngineParameterInfo> {
590
993
  return this.native.parameterInfo(id);
591
994
  }
592
995
 
@@ -671,6 +1074,19 @@ export class RealtimeEngine {
671
1074
  return this.native.clipCount();
672
1075
  }
673
1076
 
1077
+ /**
1078
+ * Normalizes each send's pre/post tap point to the integer the native layer
1079
+ * reads (defaults to post-fader when omitted). Shared by track lanes and
1080
+ * buses, which carry the same send shape.
1081
+ */
1082
+ private static normalizeSends(sends: EngineTrackSend[]): EngineTrackSend[] {
1083
+ return sends.map((send) => ({
1084
+ ...send,
1085
+ // Post-fader (0) is the default for an omitted sendTiming.
1086
+ sendTiming: send.sendTiming === undefined ? 0 : sendTimingCode(send.sendTiming),
1087
+ }));
1088
+ }
1089
+
674
1090
  setTrackLanes(lanes: Array<number | EngineTrackLane>): void {
675
1091
  this.native.setTrackLanes(
676
1092
  lanes.map((lane) => {
@@ -680,16 +1096,7 @@ export class RealtimeEngine {
680
1096
  if (!lane.sends) {
681
1097
  return lane;
682
1098
  }
683
- // Normalize each send's pre/post tap point to the integer the native
684
- // layer reads (defaults to post-fader when omitted).
685
- return {
686
- ...lane,
687
- sends: lane.sends.map((send) => ({
688
- ...send,
689
- // Post-fader (0) is the default for an omitted sendTiming.
690
- sendTiming: send.sendTiming === undefined ? 0 : sendTimingCode(send.sendTiming),
691
- })),
692
- };
1099
+ return { ...lane, sends: RealtimeEngine.normalizeSends(lane.sends) };
693
1100
  }),
694
1101
  );
695
1102
  }
@@ -703,7 +1110,42 @@ export class RealtimeEngine {
703
1110
  }
704
1111
 
705
1112
  setTrackBuses(buses: EngineBus[]): void {
706
- this.native.setTrackBuses(buses);
1113
+ // Array.isArray guards a caller-fabricated array-like (e.g. `{ length }`)
1114
+ // meant to probe the native array-length read: passing it through
1115
+ // unmodified lets that guard see the real (missing) length rather than
1116
+ // failing here on a `.map` that array-likes do not implement.
1117
+ this.native.setTrackBuses(
1118
+ Array.isArray(buses)
1119
+ ? buses.map((bus) =>
1120
+ bus.sends ? { ...bus, sends: RealtimeEngine.normalizeSends(bus.sends) } : bus,
1121
+ )
1122
+ : buses,
1123
+ );
1124
+ }
1125
+
1126
+ /**
1127
+ * Keys one insert of a bus strip from a track lane or another bus
1128
+ * (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
1129
+ */
1130
+ setBusSidechain(
1131
+ busId: number,
1132
+ insertIndex: number,
1133
+ sourceKind: SidechainSourceKind | number,
1134
+ sourceId: number,
1135
+ ): void {
1136
+ this.native.setBusSidechain(busId, insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
1137
+ }
1138
+
1139
+ /**
1140
+ * Keys one insert of the master strip from a track lane or a bus. Same
1141
+ * source rules as {@link setBusSidechain}.
1142
+ */
1143
+ setMasterSidechain(
1144
+ insertIndex: number,
1145
+ sourceKind: SidechainSourceKind | number,
1146
+ sourceId: number,
1147
+ ): void {
1148
+ this.native.setMasterSidechain(insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
707
1149
  }
708
1150
 
709
1151
  setBusStripJson(busId: number, sceneJson: string): void {
@@ -747,6 +1189,19 @@ export class RealtimeEngine {
747
1189
  this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
748
1190
  }
749
1191
 
1192
+ /** Bus-strip counterpart of {@link setTrackStripEqBand}. */
1193
+ setBusStripEqBand(busId: number, bandIndex: number, band: EqBand | string): void {
1194
+ this.native.setBusStripEqBandJson(
1195
+ busId,
1196
+ bandIndex,
1197
+ typeof band === 'string' ? band : JSON.stringify(band),
1198
+ );
1199
+ }
1200
+
1201
+ setBusStripEqBandJson(busId: number, bandIndex: number, bandJson: string): void {
1202
+ this.native.setBusStripEqBandJson(busId, bandIndex, bandJson);
1203
+ }
1204
+
750
1205
  setMasterStripJson(sceneJson: string): void {
751
1206
  try {
752
1207
  JSON.parse(sceneJson);
@@ -778,10 +1233,12 @@ export class RealtimeEngine {
778
1233
 
779
1234
  /**
780
1235
  * Changes one track-strip insert parameter in realtime, addressed by the
781
- * processor's JSON-key parameter name (see {@link masteringInsertParamInfo}).
782
- * Applied at the next block head via the engine command queue; safe during
783
- * playback. Throws if the track, insert, or name is unknown, the param is not
784
- * realtime-safe, or the command queue is full.
1236
+ * processor's JSON-key parameter name — one of the entries
1237
+ * {@link masteringInsertParamInfo} reports with a non-null `id`; a
1238
+ * construction-only entry (`id` null) takes effect only when the insert is
1239
+ * built. Applied at the next block head via the engine command queue; safe
1240
+ * during playback. Throws if the track, insert, or name is unknown, the
1241
+ * param is not realtime-safe, or the command queue is full.
785
1242
  */
786
1243
  setTrackStripInsertParamByName(
787
1244
  trackId: number,
@@ -792,11 +1249,43 @@ export class RealtimeEngine {
792
1249
  this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
793
1250
  }
794
1251
 
1252
+ /** Apply a live insert edit on this engine's owning thread without draining its command queue. */
1253
+ applyTrackStripInsertParamByNameNow(
1254
+ trackId: number,
1255
+ insertIndex: number,
1256
+ paramName: string,
1257
+ value: number,
1258
+ ): boolean {
1259
+ return this.native.applyTrackStripInsertParamByNameNow(trackId, insertIndex, paramName, value);
1260
+ }
1261
+
1262
+ /** Restore a retained insert value exactly after a strip scene is replayed. */
1263
+ restoreTrackStripInsertParamByName(
1264
+ trackId: number,
1265
+ insertIndex: number,
1266
+ paramName: string,
1267
+ value: number,
1268
+ ): void {
1269
+ this.native.restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
1270
+ }
1271
+
795
1272
  /** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
796
1273
  setMasterStripInsertParamByName(insertIndex: number, paramName: string, value: number): void {
797
1274
  this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
798
1275
  }
799
1276
 
1277
+ applyMasterStripInsertParamByNameNow(
1278
+ insertIndex: number,
1279
+ paramName: string,
1280
+ value: number,
1281
+ ): boolean {
1282
+ return this.native.applyMasterStripInsertParamByNameNow(insertIndex, paramName, value);
1283
+ }
1284
+
1285
+ restoreMasterStripInsertParamByName(insertIndex: number, paramName: string, value: number): void {
1286
+ this.native.restoreMasterStripInsertParamByName(insertIndex, paramName, value);
1287
+ }
1288
+
800
1289
  /** Bus-strip counterpart of {@link setTrackStripInsertParamByName}. */
801
1290
  setBusStripInsertParamByName(
802
1291
  busId: number,
@@ -807,6 +1296,42 @@ export class RealtimeEngine {
807
1296
  this.native.setBusStripInsertParamByName(busId, insertIndex, paramName, value);
808
1297
  }
809
1298
 
1299
+ applyBusStripInsertParamByNameNow(
1300
+ busId: number,
1301
+ insertIndex: number,
1302
+ paramName: string,
1303
+ value: number,
1304
+ ): boolean {
1305
+ return this.native.applyBusStripInsertParamByNameNow(busId, insertIndex, paramName, value);
1306
+ }
1307
+
1308
+ restoreBusStripInsertParamByName(
1309
+ busId: number,
1310
+ insertIndex: number,
1311
+ paramName: string,
1312
+ value: number,
1313
+ ): void {
1314
+ this.native.restoreBusStripInsertParamByName(busId, insertIndex, paramName, value);
1315
+ }
1316
+
1317
+ /**
1318
+ * Forgets the remembered manual insert-parameter values of one track strip
1319
+ * and discards its queued insert edits. Call before {@link setTrackStripJson}
1320
+ * replaces the strip when its old values must not carry over; the setter
1321
+ * never does this itself, since a queued edit may already target the new chain.
1322
+ */
1323
+ clearTrackInsertParameterBases(trackId: number): void {
1324
+ this.native.clearTrackInsertParameterBases(trackId);
1325
+ }
1326
+
1327
+ clearBusInsertParameterBases(busId: number): void {
1328
+ this.native.clearBusInsertParameterBases(busId);
1329
+ }
1330
+
1331
+ clearMasterInsertParameterBases(): void {
1332
+ this.native.clearMasterInsertParameterBases();
1333
+ }
1334
+
810
1335
  /** Bus-strip counterpart of {@link setTrackStripInsertBypassed}. */
811
1336
  setBusStripInsertBypassed(
812
1337
  busId: number,
@@ -845,6 +1370,42 @@ export class RealtimeEngine {
845
1370
  return this.native.resolveBusInsertAutomationId(busId, insertIndex, paramName);
846
1371
  }
847
1372
 
1373
+ /**
1374
+ * Resolves a hosted instrument's continuous parameter (by its JSON-key name)
1375
+ * to the reserved automation id usable with `setAutomationLane` /
1376
+ * `setParameter`, so an instrument parameter is driven at audio-block
1377
+ * precision exactly like a strip insert. Returns `-1` when the destination
1378
+ * has no bound instrument, the instrument exposes no automatable parameters,
1379
+ * or the name is unknown.
1380
+ *
1381
+ * For the NativeSynth ({@link setSynthInstrument}) the names are the
1382
+ * continuous {@link SynthPatch} fields: `gain`, `busDrive`, `cutoffHz`,
1383
+ * `resonanceQ`, `drive`, `keyTrack`, `envToCutoffCents`, `velToCutoffCents`,
1384
+ * `ampAttackMs`, `ampDecayMs`, `ampSustain`, `ampReleaseMs`,
1385
+ * `filterAttackMs`, `filterDecayMs`, `filterSustain`, `filterReleaseMs`,
1386
+ * `lfoRateHz`, `lfoToPitchCents`, `lfo2RateHz`, `glideMs`, `bodyMix`,
1387
+ * `stereoSpread`, `detuneCents`, `driftCents`, `pitchOffsetCents`,
1388
+ * `hpCutoffHz`, `sampleHoldHz`, `bitDepth`.
1389
+ *
1390
+ * Structural fields (`preset`, `engineMode`, `waveform`, `filterModel`,
1391
+ * `unison`, `polyphony`, `body`, `modRoutings`) are not automatable and
1392
+ * return `-1`: they resize voice pools or swap DSP topology, which is not
1393
+ * audio-thread safe. Rebind the instrument with a new patch instead.
1394
+ *
1395
+ * `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
1396
+ * `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
1397
+ * sounding from the next block; the rest are cached into per-voice state at
1398
+ * note-on and take effect from the next note, so a lane that moves one of
1399
+ * them under a held note looks inert until the next one speaks — that is the
1400
+ * behaviour, not a dropped write.
1401
+ *
1402
+ * The id survives an unbind/rebind of the same destination and applies
1403
+ * nothing while that destination is unbound.
1404
+ */
1405
+ resolveInstrumentAutomationId(destinationId: number, paramName: string): number {
1406
+ return this.native.resolveInstrumentAutomationId(destinationId, paramName);
1407
+ }
1408
+
848
1409
  /** Sets a track lane strip's pan position in realtime (glitch-free). */
849
1410
  setTrackStripPan(trackId: number, pan: number): void {
850
1411
  this.native.setTrackStripPan(trackId, pan);
@@ -865,6 +1426,29 @@ export class RealtimeEngine {
865
1426
  this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
866
1427
  }
867
1428
 
1429
+ /**
1430
+ * Sets a bus strip's output pan position in realtime (glitch-free). Throws
1431
+ * for an unknown bus or one wider than stereo.
1432
+ */
1433
+ setBusStripPan(busId: number, pan: number): void {
1434
+ this.native.setBusStripPan(busId, pan);
1435
+ }
1436
+
1437
+ /** Sets a bus strip's pan law in realtime. */
1438
+ setBusStripPanLaw(busId: number, panLaw: PanLawInput): void {
1439
+ this.native.setBusStripPanLaw(busId, panLawCode(panLaw));
1440
+ }
1441
+
1442
+ /** Sets a bus strip's pan mode in realtime. */
1443
+ setBusStripPanMode(busId: number, panMode: PanMode | number): void {
1444
+ this.native.setBusStripPanMode(busId, panModeCode(panMode));
1445
+ }
1446
+
1447
+ /** Sets a bus strip's dual-pan left/right positions in realtime. */
1448
+ setBusStripDualPan(busId: number, leftPan: number, rightPan: number): void {
1449
+ this.native.setBusStripDualPan(busId, leftPan, rightPan);
1450
+ }
1451
+
868
1452
  /**
869
1453
  * Sets a track lane strip's inter-channel alignment delay (whole samples).
870
1454
  * Adjusts strip latency, so PDC and reported graph latency are refreshed.
@@ -919,6 +1503,57 @@ export class RealtimeEngine {
919
1503
  return this.native.clipPageRequestOverflowCount();
920
1504
  }
921
1505
 
1506
+ /** Cumulative warp-stretch requests dropped because the native queue was full. */
1507
+ warpStretchOverflowCount(): number {
1508
+ return this.native.warpStretchOverflowCount();
1509
+ }
1510
+
1511
+ /**
1512
+ * Sets the number of concurrent time-stretch voices. `voices` must be an
1513
+ * integer in `[0, 64]`; a non-integer, negative, or larger value throws and
1514
+ * leaves the capacity unchanged. Default is 8. Capacity 0 disables
1515
+ * time-stretch, so every warped clip plays resampled instead and none of
1516
+ * that counts toward {@link warpStretchOverflowCount}. A change applied
1517
+ * while the engine is running restarts the splice state of any clip
1518
+ * stretching through a voice at that moment. Control-thread only.
1519
+ */
1520
+ setWarpVoiceCapacity(voices: number): void {
1521
+ this.native.setWarpVoiceCapacity(voices);
1522
+ }
1523
+
1524
+ /** Reads the current time-stretch voice capacity (default 8). */
1525
+ warpVoiceCapacity(): number {
1526
+ return this.native.warpVoiceCapacity();
1527
+ }
1528
+
1529
+ /**
1530
+ * Sets the clip-page look-ahead window in timeline frames.
1531
+ *
1532
+ * The player reports the pages it is *about to* read that are not resident
1533
+ * yet, so a streaming host can service them before the audio thread reaches
1534
+ * them. Without look-ahead a page miss is only reported after the read
1535
+ * already produced silence, which costs one block of silence at every page
1536
+ * boundary the host has not primed — the reason a sliding-window streamer
1537
+ * cannot keep a live playhead fed from miss reports alone.
1538
+ *
1539
+ * Look-ahead requests drain through the same `popClipPageRequest` queue and
1540
+ * are queued *after* the block's genuine misses, so a host that keeps only
1541
+ * the newest request per clip (as {@link ClipPageStreamer} does) tracks the
1542
+ * look-ahead frontier.
1543
+ *
1544
+ * `prepare` defaults this to half a second at the engine's sample rate. `0`
1545
+ * disables the look-ahead. A clip whose pages are all resident produces no
1546
+ * requests at all, with or without look-ahead. Safe to call during playback.
1547
+ */
1548
+ setClipPagePrefetchFrames(frames: number): void {
1549
+ this.native.setClipPagePrefetchFrames(frames);
1550
+ }
1551
+
1552
+ /** Current clip-page look-ahead window in timeline frames. */
1553
+ clipPagePrefetchFrames(): number {
1554
+ return this.native.clipPagePrefetchFrames();
1555
+ }
1556
+
922
1557
  setCaptureBuffer(numChannels: number, capacityFrames: number): void {
923
1558
  this.native.setCaptureBuffer(numChannels, capacityFrames);
924
1559
  }
@@ -1027,14 +1662,52 @@ export class RealtimeEngine {
1027
1662
  return this.native.processWithMonitor(channels);
1028
1663
  }
1029
1664
 
1030
- renderOffline(channels: Float32Array[], blockSize = 128): Float32Array[] {
1031
- return this.native.renderOffline(channels, blockSize);
1665
+ /**
1666
+ * Render `channels` offline from the current transport position. Requesting
1667
+ * more planes than `prepare` reserved throws an `InvalidParameter`
1668
+ * `SonareError` rather than returning silence that reads as a finished render.
1669
+ *
1670
+ * Set `finalize: false` to render one chunk of a longer timeline; see
1671
+ * {@link RenderOfflineRequest.finalize} and {@link finishOfflineRender}.
1672
+ */
1673
+ renderOffline(request: RenderOfflineRequest): Float32Array[];
1674
+ renderOffline(channels: Float32Array[], blockSize?: number): Float32Array[];
1675
+ renderOffline(
1676
+ channelsOrRequest: Float32Array[] | RenderOfflineRequest,
1677
+ blockSize = 128,
1678
+ ): Float32Array[] {
1679
+ const request = normalizeRenderOfflineRequest(channelsOrRequest, blockSize);
1680
+ return this.native.renderOffline(request.channels, request.blockSize, request.finalize);
1681
+ }
1682
+
1683
+ /**
1684
+ * End a chunked offline render: release every note the sequencer still holds
1685
+ * and flush the PDC / alignment delay lines. Required after
1686
+ * `renderOffline({ finalize: false })`; the finalizing form does it itself.
1687
+ *
1688
+ * Skipping it leaves every note still sounding at the last chunk held. On an
1689
+ * engine-internal instrument the tail simply never releases; on a destination
1690
+ * marked external ({@link RealtimeEngine.setMidiDestinationExternal}) the
1691
+ * note-ons already left through the external MIDI queue, so the note-offs
1692
+ * emitted here are the only ones the receiving device will get and the notes
1693
+ * otherwise hang outside the engine.
1694
+ */
1695
+ finishOfflineRender(): void {
1696
+ this.native.finishOfflineRender();
1032
1697
  }
1033
1698
 
1699
+ /**
1700
+ * Bounce the timeline to an interleaved buffer. `numChannels` above the
1701
+ * prepared channel count throws an `InvalidParameter` `SonareError`.
1702
+ */
1034
1703
  bounceOffline(options: EngineBounceOptions): EngineBounceResult {
1035
1704
  return this.native.bounceOffline(options);
1036
1705
  }
1037
1706
 
1707
+ /**
1708
+ * Freeze the current graph to audio. `numChannels` above the prepared channel
1709
+ * count throws an `InvalidParameter` `SonareError`.
1710
+ */
1038
1711
  freezeOffline(options: EngineFreezeOptions): EngineFreezeResult {
1039
1712
  return this.native.freezeOffline(options);
1040
1713
  }
@@ -1144,9 +1817,19 @@ export class RealtimeEngine {
1144
1817
  return this.native.scopeScratchPointRight(index);
1145
1818
  }
1146
1819
 
1820
+ /** Release the underlying WASM object. Idempotent, as the Node facade is. */
1147
1821
  destroy(): void {
1822
+ if (this.released) {
1823
+ return;
1824
+ }
1825
+ this.released = true;
1148
1826
  this.native.delete();
1149
1827
  }
1828
+
1829
+ /** Alias for {@link destroy}, matching embind's own release method name. */
1830
+ delete(): void {
1831
+ this.destroy();
1832
+ }
1150
1833
  }
1151
1834
 
1152
1835
  export class ClipPageProvider {