@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
@@ -9,6 +9,10 @@ import {
9
9
  isEngineTransportResponseMessage,
10
10
  isExternalMidiBatchMessage,
11
11
  isMeterSnapshot,
12
+ isScopeSnapshot,
13
+ requireChannelCount,
14
+ requireIntegerOption,
15
+ resolveScopeBandCount,
12
16
  } from './guards';
13
17
  import type {
14
18
  SonareEngineCaptureRequestMessage,
@@ -29,6 +33,7 @@ import {
29
33
  createSonareMeterRingBuffer,
30
34
  createSonareScopeRingBuffer,
31
35
  isRecord,
36
+ isUint32Slot,
32
37
  pushSonareEngineCommandRingBuffer,
33
38
  readSonareClipPageRequestRingBuffer,
34
39
  readSonareEngineTelemetryRingBuffer,
@@ -63,30 +68,27 @@ function isTrackMonitorMode(value: number | bigint | undefined): boolean {
63
68
  return typeof mode === 'number' && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
64
69
  }
65
70
 
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
71
  function isValidCommandRecord(command: SonareEngineCommandRecord): boolean {
74
72
  const type = Number(command.type);
75
73
  if (
76
74
  !Number.isSafeInteger(type) ||
77
75
  type < SonareEngineCommandType.SetParam ||
78
76
  (type > SonareEngineCommandType.SeekMarker &&
79
- type !== SonareEngineCommandType.SetTrackMonitorMode)
77
+ type !== SonareEngineCommandType.SetTrackMonitorMode &&
78
+ type !== SonareEngineCommandType.ResetMasterLoudnessMeter)
80
79
  ) {
81
80
  return false;
82
81
  }
83
82
  return (
84
- (command.targetId === undefined || Number.isSafeInteger(command.targetId)) &&
83
+ // The record writer refuses a targetId outside the slot by throwing, and
84
+ // this path answers false instead, so it must reject the same domain here.
85
+ (command.targetId === undefined || isUint32Slot(command.targetId)) &&
85
86
  (command.argFloat === undefined || Number.isFinite(command.argFloat)) &&
86
87
  isFiniteInteger(command.argInt) &&
87
88
  isFiniteInteger(command.sampleTime) &&
89
+ // A track-monitor command cannot infer a lane: its target is required.
88
90
  (type !== SonareEngineCommandType.SetTrackMonitorMode ||
89
- (isTrackMonitorLaneIndex(command.targetId) && isTrackMonitorMode(command.argInt)))
91
+ (isUint32Slot(command.targetId) && isTrackMonitorMode(command.argInt)))
90
92
  );
91
93
  }
92
94
 
@@ -172,6 +174,12 @@ export class SonareRealtimeEngineNode {
172
174
  this.resolveReady();
173
175
  }
174
176
  this.node.port.onmessage = (event: MessageEvent<unknown>) => {
177
+ // A host that kept its own reference to this handler (or a port
178
+ // implementation that ignores the detach in destroy()) must not be able
179
+ // to reach listeners of a node the host already disposed.
180
+ if (this.destroyed) {
181
+ return;
182
+ }
175
183
  const captureRequestId = engineCaptureResponseRequestId(event.data);
176
184
  if (captureRequestId !== undefined) {
177
185
  const pending = this.captureRequests.get(captureRequestId);
@@ -201,6 +209,8 @@ export class SonareRealtimeEngineNode {
201
209
  this.emitTelemetry(event.data);
202
210
  } else if (isMeterSnapshot(event.data)) {
203
211
  this.emitMeter(event.data);
212
+ } else if (isScopeSnapshot(event.data)) {
213
+ this.emitScope(event.data);
204
214
  } else if (isExternalMidiBatchMessage(event.data)) {
205
215
  this.emitMidiOut(event.data.events);
206
216
  } else if (isClipPageRequestMessage(event.data)) {
@@ -208,7 +218,7 @@ export class SonareRealtimeEngineNode {
208
218
  } else if (isRecord(event.data) && event.data.type === 'syncError') {
209
219
  const syncError: SonareEngineSyncErrorMessage = {
210
220
  type: 'syncError',
211
- syncType: String(event.data.syncType) as SonareEngineSyncErrorMessage['syncType'],
221
+ syncType: String(event.data.syncType),
212
222
  message: String(event.data.message ?? 'AudioWorklet sync failed'),
213
223
  };
214
224
  for (const listener of this.syncErrorListeners) {
@@ -268,44 +278,93 @@ export class SonareRealtimeEngineNode {
268
278
  );
269
279
  }
270
280
 
281
+ const meterIntervalFrames = requireIntegerOption(
282
+ options.meterIntervalFrames,
283
+ 2048,
284
+ 'meterIntervalFrames',
285
+ 0,
286
+ );
287
+
288
+ // Resolved before the transport branch, so a capacity the ring could not
289
+ // hold is refused by name whichever transport the caller ends up with.
290
+ const commandRingCapacity = requireIntegerOption(
291
+ options.commandRingCapacity,
292
+ 128,
293
+ 'commandRingCapacity',
294
+ 1,
295
+ );
296
+ if (commandRingCapacity > 65536) {
297
+ throw new RangeError('commandRingCapacity must be at most 65536');
298
+ }
299
+ const telemetryRingCapacity = requireIntegerOption(
300
+ options.telemetryRingCapacity,
301
+ 128,
302
+ 'telemetryRingCapacity',
303
+ 1,
304
+ );
305
+ const meterRingCapacity = requireIntegerOption(
306
+ options.meterRingCapacity,
307
+ 128,
308
+ 'meterRingCapacity',
309
+ 1,
310
+ );
311
+ const scopeRingCapacity = requireIntegerOption(
312
+ options.scopeRingCapacity,
313
+ 64,
314
+ 'scopeRingCapacity',
315
+ 1,
316
+ );
317
+ const scopeBands = resolveScopeBandCount(options.scopeBands);
318
+ const clipPageRequestRingCapacity = requireIntegerOption(
319
+ options.clipPageRequestRingCapacity,
320
+ 128,
321
+ 'clipPageRequestRingCapacity',
322
+ 1,
323
+ );
324
+ const externalMidiRingCapacity = requireIntegerOption(
325
+ options.externalMidiRingCapacity,
326
+ 256,
327
+ 'externalMidiRingCapacity',
328
+ 1,
329
+ );
271
330
  const commandRing =
272
- mode === 'sab'
273
- ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128)
274
- : undefined;
331
+ mode === 'sab' ? createSonareEngineCommandRingBuffer(commandRingCapacity) : undefined;
275
332
  const telemetryRing =
276
- mode === 'sab'
277
- ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128)
278
- : undefined;
333
+ mode === 'sab' ? createSonareEngineTelemetryRingBuffer(telemetryRingCapacity) : undefined;
279
334
  // Meter ring: the engine publishes meters into a SAB ring. Lock-free meter
280
335
  // delivery matches the telemetry path and keeps the audio render callback
281
336
  // allocation-free in SAB mode.
282
- const meterRing =
283
- mode === 'sab' ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : undefined;
337
+ const meterRing = mode === 'sab' ? createSonareMeterRingBuffer(meterRingCapacity) : undefined;
284
338
  // Scope ring (FFT spectrum + goniometer): opt-in. The per-block FFT is
285
339
  // 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));
340
+ // requests scope telemetry via scopeIntervalFrames > 0. Zero is the off
341
+ // switch, so the interval's floor is 0 rather than the capacities' 1.
342
+ const scopeIntervalFrames = requireIntegerOption(
343
+ options.scopeIntervalFrames,
344
+ 0,
345
+ 'scopeIntervalFrames',
346
+ 0,
347
+ );
288
348
  const scopeRing =
289
349
  mode === 'sab' && scopeIntervalFrames > 0
290
- ? createSonareScopeRingBuffer(options.scopeRingCapacity ?? 64, options.scopeBands ?? 48)
350
+ ? createSonareScopeRingBuffer(scopeRingCapacity, scopeBands)
291
351
  : undefined;
292
352
  // Paged-clip cache misses are a worklet-to-main-thread control signal. A
293
353
  // dedicated SPSC SAB ring keeps process() free of both embind request
294
354
  // objects and postMessage structured cloning.
295
355
  const clipPageRequestRing =
296
356
  mode === 'sab'
297
- ? createSonareClipPageRequestRingBuffer(options.clipPageRequestRingCapacity ?? 128)
357
+ ? createSonareClipPageRequestRingBuffer(clipPageRequestRingCapacity)
298
358
  : undefined;
299
359
  const externalMidiRing =
300
- mode === 'sab'
301
- ? createSonareExternalMidiRingBuffer(options.externalMidiRingCapacity ?? 256)
302
- : undefined;
303
- const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
360
+ mode === 'sab' ? createSonareExternalMidiRingBuffer(externalMidiRingCapacity) : undefined;
361
+ const channelCount = requireChannelCount(options.channelCount, 2);
304
362
  const cueOutput = options.cueOutput === true;
305
363
  const processorOptions: SonareRealtimeEngineWorkletProcessorOptions = {
306
364
  sampleRate: options.sampleRate ?? context.sampleRate,
307
365
  blockSize,
308
366
  channelCount,
367
+ meterIntervalFrames,
309
368
  cueOutput,
310
369
  commandSharedBuffer: commandRing?.sharedBuffer,
311
370
  commandRingCapacity: commandRing?.capacity,
@@ -315,8 +374,8 @@ export class SonareRealtimeEngineNode {
315
374
  meterRingCapacity: meterRing?.capacity,
316
375
  scopeSharedBuffer: scopeRing?.sharedBuffer,
317
376
  scopeRingCapacity: scopeRing?.capacity,
318
- scopeBands: scopeRing?.bands,
319
- scopeIntervalFrames: scopeRing ? scopeIntervalFrames : undefined,
377
+ scopeBands: scopeIntervalFrames > 0 ? (scopeRing?.bands ?? scopeBands) : undefined,
378
+ scopeIntervalFrames: scopeIntervalFrames > 0 ? scopeIntervalFrames : undefined,
320
379
  clipPageRequestSharedBuffer: clipPageRequestRing?.sharedBuffer,
321
380
  clipPageRequestRingCapacity: clipPageRequestRing?.capacity,
322
381
  externalMidiSharedBuffer: externalMidiRing?.sharedBuffer,
@@ -370,6 +429,11 @@ export class SonareRealtimeEngineNode {
370
429
  return this.sendCommand({ type: SonareEngineCommandType.TransportPlay, sampleTime });
371
430
  }
372
431
 
432
+ /** Queues a reset of the master's integrated-loudness accumulator. */
433
+ resetMasterLoudnessMeter(sampleTime = -1): boolean {
434
+ return this.sendCommand({ type: SonareEngineCommandType.ResetMasterLoudnessMeter, sampleTime });
435
+ }
436
+
373
437
  stop(sampleTime = -1): boolean {
374
438
  return this.sendCommand({ type: SonareEngineCommandType.TransportStop, sampleTime });
375
439
  }
@@ -600,6 +664,12 @@ export class SonareRealtimeEngineNode {
600
664
  this.scopeListeners.clear();
601
665
  this.midiOutListeners.clear();
602
666
  this.clipPageRequestListeners.clear();
667
+ this.syncErrorListeners.clear();
668
+ // The port outlives destroy(): the worklet tears itself down asynchronously
669
+ // after the 'destroy' message above, so a syncError already in flight would
670
+ // still be dispatched here. Detaching the handler is what makes the
671
+ // listener sets stay empty rather than being refilled by a late message.
672
+ this.node.port.onmessage = null;
603
673
  }
604
674
 
605
675
  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.