@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
@@ -9,6 +9,8 @@ import {
9
9
  isEngineTransportResponseMessage,
10
10
  isExternalMidiBatchMessage,
11
11
  isMeterSnapshot,
12
+ requireChannelCount,
13
+ requireIntegerOption,
12
14
  } from './guards';
13
15
  import type {
14
16
  SonareEngineCaptureRequestMessage,
@@ -29,6 +31,7 @@ import {
29
31
  createSonareMeterRingBuffer,
30
32
  createSonareScopeRingBuffer,
31
33
  isRecord,
34
+ isUint32Slot,
32
35
  pushSonareEngineCommandRingBuffer,
33
36
  readSonareClipPageRequestRingBuffer,
34
37
  readSonareEngineTelemetryRingBuffer,
@@ -63,13 +66,6 @@ function isTrackMonitorMode(value: number | bigint | undefined): boolean {
63
66
  return typeof mode === 'number' && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
64
67
  }
65
68
 
66
- /** A track-monitor command cannot infer a lane: its target is a uint32 index. */
67
- function isTrackMonitorLaneIndex(value: number | undefined): boolean {
68
- return (
69
- typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= 0xffff_ffff
70
- );
71
- }
72
-
73
69
  function isValidCommandRecord(command: SonareEngineCommandRecord): boolean {
74
70
  const type = Number(command.type);
75
71
  if (
@@ -81,12 +77,15 @@ function isValidCommandRecord(command: SonareEngineCommandRecord): boolean {
81
77
  return false;
82
78
  }
83
79
  return (
84
- (command.targetId === undefined || Number.isSafeInteger(command.targetId)) &&
80
+ // The record writer refuses a targetId outside the slot by throwing, and
81
+ // this path answers false instead, so it must reject the same domain here.
82
+ (command.targetId === undefined || isUint32Slot(command.targetId)) &&
85
83
  (command.argFloat === undefined || Number.isFinite(command.argFloat)) &&
86
84
  isFiniteInteger(command.argInt) &&
87
85
  isFiniteInteger(command.sampleTime) &&
86
+ // A track-monitor command cannot infer a lane: its target is required.
88
87
  (type !== SonareEngineCommandType.SetTrackMonitorMode ||
89
- (isTrackMonitorLaneIndex(command.targetId) && isTrackMonitorMode(command.argInt)))
88
+ (isUint32Slot(command.targetId) && isTrackMonitorMode(command.argInt)))
90
89
  );
91
90
  }
92
91
 
@@ -172,6 +171,12 @@ export class SonareRealtimeEngineNode {
172
171
  this.resolveReady();
173
172
  }
174
173
  this.node.port.onmessage = (event: MessageEvent<unknown>) => {
174
+ // A host that kept its own reference to this handler (or a port
175
+ // implementation that ignores the detach in destroy()) must not be able
176
+ // to reach listeners of a node the host already disposed.
177
+ if (this.destroyed) {
178
+ return;
179
+ }
175
180
  const captureRequestId = engineCaptureResponseRequestId(event.data);
176
181
  if (captureRequestId !== undefined) {
177
182
  const pending = this.captureRequests.get(captureRequestId);
@@ -208,7 +213,7 @@ export class SonareRealtimeEngineNode {
208
213
  } else if (isRecord(event.data) && event.data.type === 'syncError') {
209
214
  const syncError: SonareEngineSyncErrorMessage = {
210
215
  type: 'syncError',
211
- syncType: String(event.data.syncType) as SonareEngineSyncErrorMessage['syncType'],
216
+ syncType: String(event.data.syncType),
212
217
  message: String(event.data.message ?? 'AudioWorklet sync failed'),
213
218
  };
214
219
  for (const listener of this.syncErrorListeners) {
@@ -268,39 +273,80 @@ export class SonareRealtimeEngineNode {
268
273
  );
269
274
  }
270
275
 
276
+ // Resolved before the transport branch, so a capacity the ring could not
277
+ // hold is refused by name whichever transport the caller ends up with.
278
+ const commandRingCapacity = requireIntegerOption(
279
+ options.commandRingCapacity,
280
+ 128,
281
+ 'commandRingCapacity',
282
+ 1,
283
+ );
284
+ if (commandRingCapacity > 65536) {
285
+ throw new RangeError('commandRingCapacity must be at most 65536');
286
+ }
287
+ const telemetryRingCapacity = requireIntegerOption(
288
+ options.telemetryRingCapacity,
289
+ 128,
290
+ 'telemetryRingCapacity',
291
+ 1,
292
+ );
293
+ const meterRingCapacity = requireIntegerOption(
294
+ options.meterRingCapacity,
295
+ 128,
296
+ 'meterRingCapacity',
297
+ 1,
298
+ );
299
+ const scopeRingCapacity = requireIntegerOption(
300
+ options.scopeRingCapacity,
301
+ 64,
302
+ 'scopeRingCapacity',
303
+ 1,
304
+ );
305
+ const scopeBands = requireIntegerOption(options.scopeBands, 48, 'scopeBands', 1);
306
+ const clipPageRequestRingCapacity = requireIntegerOption(
307
+ options.clipPageRequestRingCapacity,
308
+ 128,
309
+ 'clipPageRequestRingCapacity',
310
+ 1,
311
+ );
312
+ const externalMidiRingCapacity = requireIntegerOption(
313
+ options.externalMidiRingCapacity,
314
+ 256,
315
+ 'externalMidiRingCapacity',
316
+ 1,
317
+ );
271
318
  const commandRing =
272
- mode === 'sab'
273
- ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128)
274
- : undefined;
319
+ mode === 'sab' ? createSonareEngineCommandRingBuffer(commandRingCapacity) : undefined;
275
320
  const telemetryRing =
276
- mode === 'sab'
277
- ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128)
278
- : undefined;
321
+ mode === 'sab' ? createSonareEngineTelemetryRingBuffer(telemetryRingCapacity) : undefined;
279
322
  // Meter ring: the engine publishes meters into a SAB ring. Lock-free meter
280
323
  // delivery matches the telemetry path and keeps the audio render callback
281
324
  // allocation-free in SAB mode.
282
- const meterRing =
283
- mode === 'sab' ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : undefined;
325
+ const meterRing = mode === 'sab' ? createSonareMeterRingBuffer(meterRingCapacity) : undefined;
284
326
  // Scope ring (FFT spectrum + goniometer): opt-in. The per-block FFT is
285
327
  // heavier than the meter path, so it is created only when the caller
286
- // requests scope telemetry via scopeIntervalFrames > 0.
287
- const scopeIntervalFrames = Math.max(0, Math.floor(options.scopeIntervalFrames ?? 0));
328
+ // requests scope telemetry via scopeIntervalFrames > 0. Zero is the off
329
+ // switch, so the interval's floor is 0 rather than the capacities' 1.
330
+ const scopeIntervalFrames = requireIntegerOption(
331
+ options.scopeIntervalFrames,
332
+ 0,
333
+ 'scopeIntervalFrames',
334
+ 0,
335
+ );
288
336
  const scopeRing =
289
337
  mode === 'sab' && scopeIntervalFrames > 0
290
- ? createSonareScopeRingBuffer(options.scopeRingCapacity ?? 64, options.scopeBands ?? 48)
338
+ ? createSonareScopeRingBuffer(scopeRingCapacity, scopeBands)
291
339
  : undefined;
292
340
  // Paged-clip cache misses are a worklet-to-main-thread control signal. A
293
341
  // dedicated SPSC SAB ring keeps process() free of both embind request
294
342
  // objects and postMessage structured cloning.
295
343
  const clipPageRequestRing =
296
344
  mode === 'sab'
297
- ? createSonareClipPageRequestRingBuffer(options.clipPageRequestRingCapacity ?? 128)
345
+ ? createSonareClipPageRequestRingBuffer(clipPageRequestRingCapacity)
298
346
  : undefined;
299
347
  const externalMidiRing =
300
- mode === 'sab'
301
- ? createSonareExternalMidiRingBuffer(options.externalMidiRingCapacity ?? 256)
302
- : undefined;
303
- const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
348
+ mode === 'sab' ? createSonareExternalMidiRingBuffer(externalMidiRingCapacity) : undefined;
349
+ const channelCount = requireChannelCount(options.channelCount, 2);
304
350
  const cueOutput = options.cueOutput === true;
305
351
  const processorOptions: SonareRealtimeEngineWorkletProcessorOptions = {
306
352
  sampleRate: options.sampleRate ?? context.sampleRate,
@@ -600,6 +646,12 @@ export class SonareRealtimeEngineNode {
600
646
  this.scopeListeners.clear();
601
647
  this.midiOutListeners.clear();
602
648
  this.clipPageRequestListeners.clear();
649
+ this.syncErrorListeners.clear();
650
+ // The port outlives destroy(): the worklet tears itself down asynchronously
651
+ // after the 'destroy' message above, so a syncError already in flight would
652
+ // still be dispatched here. Detaching the handler is what makes the
653
+ // listener sets stay empty rather than being refilled by a late message.
654
+ this.node.port.onmessage = null;
603
655
  }
604
656
 
605
657
  private emitTelemetry(telemetry: SonareEngineTelemetryRecord): void {
@@ -1,4 +1,5 @@
1
1
  import type { EngineCaptureStatus, EngineMarker, EngineTrackLane, RealtimeEngine } from '../index';
2
+ import { requireChannelCount, requireInteger, requireIntegerOption } from './guards';
2
3
  import type { SonareEngineSyncCaptureMessage, SonareEngineTransportFacade } from './messages';
3
4
 
4
5
  /** Capture configuration options accepted by the engine's `configureCapture`. */
@@ -12,20 +13,22 @@ export interface CaptureOptions {
12
13
 
13
14
  /**
14
15
  * Normalizes capture options into the resolved config carried by the
15
- * `syncCapture` message, applying integer truncation and defaults.
16
+ * `syncCapture` message, applying defaults and refusing a non-integer count.
16
17
  *
17
18
  * @param options Raw capture options.
18
19
  * @param defaultChannels Channel count to use when `options.channels` is unset.
20
+ * @throws RangeError if a frame count, channel count or offset is not an integer.
19
21
  */
20
22
  export function buildCaptureConfig(
21
23
  options: CaptureOptions,
22
24
  defaultChannels: number,
23
25
  ): Omit<SonareEngineSyncCaptureMessage, 'type'> {
24
26
  return {
25
- bufferFrames: Math.trunc(options.bufferFrames),
26
- channels: Math.trunc(options.channels ?? defaultChannels),
27
+ // bufferFrames has no default; NaN makes the guard refuse an absent value.
28
+ bufferFrames: requireIntegerOption(options.bufferFrames, Number.NaN, 'bufferFrames', 1),
29
+ channels: requireChannelCount(options.channels, defaultChannels),
27
30
  source: options.source ?? 'output',
28
- recordOffsetSamples: Math.trunc(options.recordOffsetSamples ?? 0),
31
+ recordOffsetSamples: requireInteger(options.recordOffsetSamples, 0, 'recordOffsetSamples'),
29
32
  inputMonitor: {
30
33
  enabled: Boolean(options.inputMonitor?.enabled),
31
34
  gain: options.inputMonitor?.gain ?? 1,
@@ -111,7 +114,7 @@ export function normalizeTrackLanes(
111
114
  const ids: number[] = [];
112
115
  for (const entry of entries) {
113
116
  if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
114
- throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
117
+ throw new RangeError(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
115
118
  }
116
119
  ids.push(entry.trackId);
117
120
  }
@@ -145,12 +148,15 @@ export function resolveMarkerSet(
145
148
  const seen = new Set<number>();
146
149
  let counter = nextMarkerId;
147
150
  for (const marker of markers) {
148
- if (!Number.isFinite(marker.ppq)) {
149
- throw new Error(`Invalid marker ppq: ${String(marker.ppq)}`);
151
+ // Non-negative as well as finite, which is the domain the native setter
152
+ // enforces; checking only finiteness here hands a negative ppq on to be
153
+ // refused a layer down, under that layer's name for the argument.
154
+ if (!Number.isFinite(marker.ppq) || marker.ppq < 0) {
155
+ throw new RangeError(`Invalid marker ppq: ${String(marker.ppq)}`);
150
156
  }
151
157
  if (marker.id !== undefined) {
152
158
  if (!Number.isInteger(marker.id) || marker.id <= 0) {
153
- throw new Error(`Invalid marker id: ${String(marker.id)}`);
159
+ throw new RangeError(`Invalid marker id: ${String(marker.id)}`);
154
160
  }
155
161
  if (seen.has(marker.id)) {
156
162
  throw new Error(`Duplicate marker id: ${marker.id}`);
@@ -190,6 +190,27 @@ export function resolveBusInsertAutomationId(
190
190
  return ctx.offlineEngine.resolveBusInsertAutomationId(busId, insertIndex, paramName);
191
191
  }
192
192
 
193
+ /**
194
+ * Resolves a hosted instrument's continuous parameter to its reserved
195
+ * instrument-automation id, so an instrument parameter is driven from a
196
+ * breakpoint lane at audio-block precision like a strip insert.
197
+ *
198
+ * The instrument must already be bound on the offline engine for the
199
+ * destination (the worklet mirrors every instrument sync onto it), otherwise
200
+ * the resolve returns -1.
201
+ *
202
+ * @param destinationId MIDI destination the instrument is bound to.
203
+ * @param paramName Instrument JSON-key parameter name (e.g. `cutoffHz`).
204
+ * @returns Reserved instrument-automation id, or -1 when destination/key unknown.
205
+ */
206
+ export function resolveInstrumentAutomationId(
207
+ ctx: EngineParameterContext,
208
+ destinationId: number,
209
+ paramName: string,
210
+ ): number {
211
+ return ctx.offlineEngine.resolveInstrumentAutomationId(destinationId, paramName);
212
+ }
213
+
193
214
  /**
194
215
  * Returns the number of automation lanes installed on the engine, including
195
216
  * lanes whose breakpoint list is currently empty.