@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
@@ -53,6 +53,7 @@ export function streamAnalyzerConfigDefaults(): StreamConfigDefaults {
53
53
  */
54
54
  export class StreamAnalyzer {
55
55
  private analyzer: WasmStreamAnalyzer;
56
+ private released = false;
56
57
 
57
58
  /**
58
59
  * Create a new StreamAnalyzer.
@@ -107,6 +108,9 @@ export class StreamAnalyzer {
107
108
  /**
108
109
  * Process audio samples.
109
110
  *
111
+ * Feeding a finalized analyzer is an invalid-state error; call `reset()`
112
+ * first to start a new stream.
113
+ *
110
114
  * @param samples - Audio samples (mono, float32)
111
115
  */
112
116
  process(samples: Float32Array): void {
@@ -115,7 +119,8 @@ export class StreamAnalyzer {
115
119
 
116
120
  /**
117
121
  * Process audio samples with a contiguous explicit sample offset. A gap,
118
- * seek, or switch from `process()` requires `reset()` first.
122
+ * seek, or switch from `process()` requires `reset()` first, as does feeding
123
+ * a finalized analyzer.
119
124
  *
120
125
  * @param samples - Audio samples (mono, float32)
121
126
  * @param sampleOffset - Cumulative sample count at start of this chunk
@@ -126,6 +131,12 @@ export class StreamAnalyzer {
126
131
 
127
132
  /**
128
133
  * Drain any high-rate resampler tail, then zero-pad the final partial frame.
134
+ *
135
+ * Repeating a successful call is a no-op, and a call that fails leaves the
136
+ * stream un-finalized so a retry resumes from the same point. Call `reset()`
137
+ * before reusing the analyzer for another stream: more audio fed to a
138
+ * finalized analyzer is rejected rather than silently analyzed without the
139
+ * overlap context the finalized tail consumed.
129
140
  */
130
141
  finalize(): void {
131
142
  this.analyzer.finalize();
@@ -194,6 +205,7 @@ export class StreamAnalyzer {
194
205
  droppedOutputFrames: s.droppedOutputFrames,
195
206
  droppedChordProgressionEntries: s.droppedChordProgressionEntries,
196
207
  droppedBarProgressionEntries: s.droppedBarProgressionEntries,
208
+ nonFiniteDiscardBlocks: s.nonFiniteDiscardBlocks,
197
209
  estimate: {
198
210
  bpm: s.estimate.bpm,
199
211
  bpmConfidence: s.estimate.bpmConfidence,
@@ -274,7 +286,15 @@ export class StreamAnalyzer {
274
286
  /**
275
287
  * Set normalization gain for loud/compressed audio.
276
288
  *
277
- * @param gain - Gain factor to apply (e.g., 0.5 for -6dB reduction)
289
+ * Throws for a value outside 0.01..100 rather than clamping into it. The
290
+ * usual recipe (`gain = targetLevel / measuredLevel`) can land outside that
291
+ * range for a buffer that is not in the conventional ±1 float domain — an
292
+ * integer-scaled one asks for about 3e-4 — and no getter exposes the
293
+ * effective gain, so a clamped request would leave the analysis far off
294
+ * target undetectably. Convert such a buffer before feeding it instead.
295
+ *
296
+ * @param gain - Gain factor to apply (e.g., 0.5 for -6dB reduction, range
297
+ * 0.01..100)
278
298
  */
279
299
  setNormalizationGain(gain: number): void {
280
300
  this.analyzer.setNormalizationGain(gain);
@@ -283,7 +303,10 @@ export class StreamAnalyzer {
283
303
  /**
284
304
  * Set tuning reference frequency for non-standard tuning.
285
305
  *
286
- * @param refHz - Reference frequency for A4 (default 440 Hz)
306
+ * Throws for a value outside 220..880 Hz rather than clamping into it, so
307
+ * this and `tuningRefHz` at create time accept exactly the same range.
308
+ *
309
+ * @param refHz - Reference frequency for A4 (default 440 Hz, range 220..880)
287
310
  * @example
288
311
  * // If audio is 1 semitone sharp (A4 = 466.16 Hz)
289
312
  * analyzer.setTuningRefHz(466.16);
@@ -294,11 +317,20 @@ export class StreamAnalyzer {
294
317
  this.analyzer.setTuningRefHz(refHz);
295
318
  }
296
319
 
297
- /** Release the underlying WASM object. Safe to call only once. */
320
+ /** Release the underlying WASM object. Idempotent, as the Node facade is. */
298
321
  delete(): void {
322
+ if (this.released) {
323
+ return;
324
+ }
325
+ this.released = true;
299
326
  this.analyzer.delete();
300
327
  }
301
328
 
329
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
330
+ destroy(): void {
331
+ this.delete();
332
+ }
333
+
302
334
  /** Alias for {@link delete}, kept for backward compatibility (historical name). */
303
335
  dispose(): void {
304
336
  this.delete();
@@ -14,9 +14,21 @@ export interface ChordChange {
14
14
  * A chord detected at bar boundary (beat-synchronized)
15
15
  */
16
16
  export interface BarChord {
17
+ /**
18
+ * Bar number, not the index of this entry in the array: bars with no
19
+ * confident chord are not recorded and the oldest entries are dropped at the
20
+ * history cap. Group bars by pattern position with this, never with the array
21
+ * index. In `votedPattern` it is the pattern position instead.
22
+ */
17
23
  barIndex: number;
18
24
  root: PitchClass;
19
25
  quality: ChordQuality;
26
+ /**
27
+ * Start of the bar, on the same timeline as `StreamFrame.timestamp`
28
+ * (including a `sampleOffset` anchor). Consecutive bars are `barDuration`
29
+ * apart rather than snapped to the analysis frame grid. Unused in
30
+ * `votedPattern`.
31
+ */
20
32
  startTime: number;
21
33
  confidence: number;
22
34
  }
@@ -35,6 +47,11 @@ export interface PatternScore {
35
47
  export interface ProgressiveEstimate {
36
48
  bpm: number;
37
49
  bpmConfidence: number;
50
+ /**
51
+ * Tempo candidates the most recent BPM estimate chose from; 0 until an
52
+ * estimate has run. Same quantity as the batch analysis result's field of the
53
+ * same name.
54
+ */
38
55
  bpmCandidateCount: number;
39
56
  key: PitchClass;
40
57
  keyMinor: boolean;
@@ -54,6 +71,11 @@ export interface ProgressiveEstimate {
54
71
  allPatternScores: PatternScore[];
55
72
  accumulatedSeconds: number;
56
73
  usedFrames: number;
74
+ /**
75
+ * True when the key or BPM was re-estimated since the previous stats
76
+ * snapshot. One change sets it on exactly one snapshot however the caller
77
+ * chunks its input, and a call that produced no frame does not repeat it.
78
+ */
57
79
  updated: boolean;
58
80
  }
59
81
 
@@ -68,6 +90,19 @@ export interface AnalyzerStats {
68
90
  droppedOutputFrames: number;
69
91
  droppedChordProgressionEntries: number;
70
92
  droppedBarProgressionEntries: number;
93
+ /**
94
+ * Blocks in which a non-finite input sample was replaced before it could
95
+ * reach the analyzer's recursive state.
96
+ *
97
+ * Unlike the drop counts above, nothing is missing from the output: every
98
+ * estimate is produced as usual and simply stops describing the input, so
99
+ * this is the only report that the stream was degraded. The unit is one
100
+ * {@link StreamAnalyzer.process} call, never a sample, so a block carrying a
101
+ * thousand NaNs adds one. Cleared by {@link StreamAnalyzer.reset} alongside
102
+ * the drop counts, because that call rebuilds the timeline and the count
103
+ * describes a segment rather than the analyzer.
104
+ */
105
+ nonFiniteDiscardBlocks: number;
71
106
  estimate: ProgressiveEstimate;
72
107
  }
73
108
 
@@ -164,6 +199,8 @@ export interface StreamConfig {
164
199
  nMels?: number;
165
200
  fmin?: number;
166
201
  fmax?: number;
202
+ /** A4 tuning reference in Hz. Defaults to 440; must be within 220..880, the
203
+ * same range `setTuningRefHz` accepts live. */
167
204
  tuningRefHz?: number;
168
205
  /** Unsupported: no read path surfaces per-frame magnitude spectra. */
169
206
  computeMagnitude?: boolean;
@@ -1,4 +1,4 @@
1
- export type { MixerRealtimeBuffer } from './mixer';
1
+ export type { MixerMeterSnapshot, MixerRealtimeBuffer, StripMeteringOptions } from './mixer';
2
2
  export { Mixer } from './mixer';
3
3
  export type {
4
4
  RealtimeVoiceChangerInterleavedBuffer,
@@ -4,6 +4,7 @@ import type {
4
4
  EqBand,
5
5
  EqMatchOptions,
6
6
  EqSpectrumSnapshot,
7
+ EqStereoPlacement,
7
8
  StreamingEqualizerConfig,
8
9
  StreamingMasteringChainConfig,
9
10
  StreamingRetuneConfig,
@@ -21,6 +22,14 @@ type EqPhaseMode =
21
22
  | 'linear_phase'
22
23
  | number;
23
24
 
25
+ const EQ_PLACEMENTS: Record<string, number> = {
26
+ stereo: 0,
27
+ left: 1,
28
+ right: 2,
29
+ mid: 3,
30
+ side: 4,
31
+ };
32
+
24
33
  const EQ_PHASE_MODES: Record<string, number> = {
25
34
  zero: 1,
26
35
  'zero-latency': 1,
@@ -41,10 +50,29 @@ const EQ_PHASE_MODES: Record<string, number> = {
41
50
  * Block-by-block streaming variant of {@link masteringChain}.
42
51
  *
43
52
  * Maintains processor state across {@link processMono}/{@link processStereo}
44
- * calls. Only ProcessorBase-backed stages are supported. Configurations that
45
- * enable `repair.denoise` throw at construction. An enabled `loudness` stage
46
- * also throws unless {@link StreamingMasteringChainConfig.loudnessStaticGainDb}
47
- * supplies a precomputed normalization gain.
53
+ * calls. Only ProcessorBase-backed stages are supported: `eq.tilt`,
54
+ * `dynamics.deesser`, `dynamics.transientShaper`, `dynamics.compressor`,
55
+ * `dynamics.multibandComp`, `saturation.tape`, `saturation.exciter`,
56
+ * `spectral.airBand`, `stereo.imager` (stereo only), `stereo.monoMaker`
57
+ * (stereo only), `maximizer.truePeakLimiter`. Configurations that enable ANY of
58
+ * the five whole-signal repair stages (`repair.declick`, `repair.declip`,
59
+ * `repair.decrackle`, `repair.dehum`, `repair.dereverb`) throw at construction.
60
+ *
61
+ * `repair.denoise` runs here, but only with a noise estimator that is recursive
62
+ * in time. Its default ranks every frame of the whole signal by energy, which a
63
+ * stream never reaches the end of, so it is refused by name rather than
64
+ * substituted; set `repair.denoise.noiseEstimator` to `1` (MCRA), `2` (IMCRA)
65
+ * or `3` (speech-presence probability). The two minimum-tracking estimators
66
+ * (`1` and `2`) seed their noise floor from the first frame they see and hold it
67
+ * for the half second their minimum window spans, so a stream opened in the
68
+ * middle of the programme is over-suppressed until it turns over; `3` tracks no
69
+ * minimum and is unaffected. Prefer `3` past that opening too: `1` and `2`
70
+ * over-report the floor for as long as the programme stays intermittent, and on
71
+ * a gated tone they leave the result below the untreated input.
72
+ *
73
+ * An enabled `loudness` stage also throws unless
74
+ * {@link StreamingMasteringChainConfig.loudnessStaticGainDb} supplies a
75
+ * precomputed normalization gain.
48
76
  *
49
77
  * Call {@link delete} (or use a `try/finally`) to release the underlying WASM
50
78
  * object — the embind handle is not garbage-collected automatically.
@@ -80,6 +108,7 @@ const EQ_PHASE_MODES: Record<string, number> = {
80
108
  */
81
109
  export class StreamingMasteringChain {
82
110
  private chain: import('./sonare.js').WasmStreamingMasteringChain;
111
+ private released = false;
83
112
 
84
113
  constructor(config: StreamingMasteringChainConfig) {
85
114
  const module = getSonareModule();
@@ -142,6 +171,14 @@ export class StreamingMasteringChain {
142
171
  this.chain.reset();
143
172
  }
144
173
 
174
+ /**
175
+ * Set one realtime-safe full-chain parameter at a serialized block boundary.
176
+ * Structural, disabled-stage and unknown parameters are rejected.
177
+ */
178
+ setParameter(key: string, value: number): void {
179
+ this.chain.setParameter(key, value);
180
+ }
181
+
145
182
  /** Total reported latency in samples across all active processors. */
146
183
  latencySamples(): number {
147
184
  return this.chain.latencySamples();
@@ -152,10 +189,78 @@ export class StreamingMasteringChain {
152
189
  return this.chain.stageNames();
153
190
  }
154
191
 
155
- /** Release the underlying WASM object. Safe to call only once. */
192
+ /**
193
+ * Samples a stage replaced with a finite in-domain one, keeping the output
194
+ * finite and in range.
195
+ *
196
+ * A non-finite sample supplied by the caller is rejected before any stage
197
+ * runs, so a replacement is always of a value a stage itself produced.
198
+ *
199
+ * Only the true-peak limiters replace anything, so with the maximizer's
200
+ * limiter and the loudness stage both disabled a zero here means no stage was
201
+ * able to replace anything rather than that nothing needed replacing.
202
+ *
203
+ * Cumulative over every block since {@link prepare}, and aggregated over the
204
+ * stages and channels, so it identifies neither which block nor which stage.
205
+ * Read it per block and compare against the previous reading to localize one.
206
+ *
207
+ * {@link prepare} rebuilds the stages and so clears it; {@link reset} does
208
+ * not, because it drops processor state without rebuilding.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * chain.processMono(block);
213
+ * if (chain.nonFiniteSubstitutionCount() > previous) {
214
+ * // the block just produced is not derived from `block` everywhere
215
+ * }
216
+ * ```
217
+ */
218
+ nonFiniteSubstitutionCount(): number {
219
+ return this.chain.nonFiniteSubstitutionCount();
220
+ }
221
+
222
+ /**
223
+ * Processing calls in which a stage discarded its own recursive state
224
+ * because a non-finite value had reached it.
225
+ *
226
+ * The companion to {@link nonFiniteSubstitutionCount}, and not the same
227
+ * measurement -- a caller who assumes they are will read one and think
228
+ * they have the other. That one counts SAMPLES a stage replaced and so
229
+ * sums across stages; a discard is a whole stage returning to its
230
+ * post-reset value and is counted once per call however many stages did
231
+ * it. A stage may run more than once per call, which is why this is a
232
+ * delta over the call and never a sum.
233
+ *
234
+ * Non-finite input is rejected before any stage runs, so what a stage
235
+ * discards is always state it produced itself -- a finite sample large
236
+ * enough to overflow inside a filter, most often. Unlike the substitution
237
+ * count every stage can contribute, so a zero here means no stage
238
+ * discarded rather than that none could.
239
+ *
240
+ * Both {@link processMono}/{@link processStereo} and
241
+ * {@link flushMono}/{@link flushStereo} count, since a flush drives the
242
+ * same stages. {@link prepare} rebuilds the stages and so clears it (as it
243
+ * does {@link nonFiniteSubstitutionCount}, so the two counters on one
244
+ * handle share an epoch); {@link reset} does not, because it drops
245
+ * processor state without rebuilding.
246
+ */
247
+ nonFiniteDiscardCount(): number {
248
+ return this.chain.nonFiniteDiscardCount();
249
+ }
250
+
251
+ /** Release the underlying WASM object. Idempotent, as the Node facade is. */
156
252
  delete(): void {
253
+ if (this.released) {
254
+ return;
255
+ }
256
+ this.released = true;
157
257
  this.chain.delete();
158
258
  }
259
+
260
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
261
+ destroy(): void {
262
+ this.delete();
263
+ }
159
264
  }
160
265
 
161
266
  // ============================================================================
@@ -185,6 +290,7 @@ export class StreamingMasteringChain {
185
290
  */
186
291
  export class StreamingEqualizer {
187
292
  private eq: import('./sonare.js').WasmStreamingEqualizer;
293
+ private released = false;
188
294
 
189
295
  constructor(config: StreamingEqualizerConfig = {}) {
190
296
  const module = getSonareModule();
@@ -267,6 +373,33 @@ export class StreamingEqualizer {
267
373
  return this.eq.latencySamples();
268
374
  }
269
375
 
376
+ /**
377
+ * Number of blocks in which the EQ discarded recursive state because a
378
+ * non-finite value had reached it.
379
+ *
380
+ * Advisory telemetry, and the only thing that separates a degraded EQ from
381
+ * a clean one. A discard returns the affected filter cells to their
382
+ * post-reset value, so the EQ recovers in silence and the output stays
383
+ * finite and in range while carrying samples unrelated to the input;
384
+ * nothing else reports that this happened.
385
+ *
386
+ * The count covers every IIR plane the band layout uses -- stereo, per
387
+ * channel, and mid/side -- together with the automatic output gain and the
388
+ * detector state the dynamic bands drive. Linear-phase bands are not
389
+ * included and have nothing to include: an FIR keeps no recursive state,
390
+ * so a non-finite sample leaves its history on its own.
391
+ *
392
+ * Unlike a mixer strip's meters, nothing here lags: this EQ has no meter of
393
+ * its own, so a discard is always attributed to the block that carried it.
394
+ *
395
+ * Cumulative since this handle was created and never cleared, so two
396
+ * readings bracket a span of audio. The unit is one processed block, never
397
+ * a channel or a plane.
398
+ */
399
+ nonFiniteDiscardCount(): number {
400
+ return this.eq.nonFiniteDiscardCount();
401
+ }
402
+
270
403
  /**
271
404
  * Process one mono block, returning the equalized samples (same length).
272
405
  */
@@ -287,6 +420,39 @@ export class StreamingEqualizer {
287
420
  return this.eq.processStereo(left, right);
288
421
  }
289
422
 
423
+ /**
424
+ * The composite magnitude of the bands, in dB, at each requested frequency —
425
+ * the curve to draw over {@link spectrum}.
426
+ *
427
+ * Built from the same coefficient design, tilt expansion and cut-slope
428
+ * cascade the audio path uses, so it states what the equalizer does rather
429
+ * than what its settings look like, and it carries the output gain, the gain
430
+ * scale and whatever each dynamic band is applying at the moment of the call.
431
+ * Disabled, bypassed and — when anything is soloed — unsoloed bands drop out,
432
+ * and a soloed band is drawn as the band pass it is heard as.
433
+ *
434
+ * `placement` selects which signal path the curve is for. A band placed on
435
+ * `'Stereo'` is on every path; one placed elsewhere appears only on its own,
436
+ * a mid band having no per-channel magnitude to fold into a left or right
437
+ * curve. Frequencies are clamped to [0 Hz, Nyquist].
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * const freqs = new Float32Array([100, 1000, 10000]);
442
+ * const db = eq.magnitudeResponse(freqs);
443
+ * ```
444
+ */
445
+ magnitudeResponse(
446
+ frequenciesHz: Float32Array,
447
+ placement: EqStereoPlacement = 'Stereo',
448
+ ): Float32Array {
449
+ const value = EQ_PLACEMENTS[placement.toLowerCase()];
450
+ if (value === undefined) {
451
+ throw new Error(`unknown EQ band placement: ${placement}`);
452
+ }
453
+ return this.eq.magnitudeResponse(value, frequenciesHz);
454
+ }
455
+
290
456
  /**
291
457
  * Read the latest pre/post spectrum snapshot for metering. `seq` increments
292
458
  * each time a new snapshot is published.
@@ -306,10 +472,19 @@ export class StreamingEqualizer {
306
472
  this.eq.match(source, reference, options as Record<string, unknown>);
307
473
  }
308
474
 
309
- /** Release the underlying WASM object. Safe to call only once. */
475
+ /** Release the underlying WASM object. Idempotent, as the Node facade is. */
310
476
  delete(): void {
477
+ if (this.released) {
478
+ return;
479
+ }
480
+ this.released = true;
311
481
  this.eq.delete();
312
482
  }
483
+
484
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
485
+ destroy(): void {
486
+ this.delete();
487
+ }
313
488
  }
314
489
 
315
490
  // ============================================================================
@@ -325,6 +500,7 @@ export class StreamingEqualizer {
325
500
  */
326
501
  export class StreamingRetune {
327
502
  private retune: import('./sonare.js').WasmStreamingRetune;
503
+ private released = false;
328
504
 
329
505
  constructor(config: StreamingRetuneConfig = {}) {
330
506
  const module = getSonareModule();
@@ -345,14 +521,16 @@ export class StreamingRetune {
345
521
  }
346
522
 
347
523
  /**
348
- * Update retune settings. Changing `grainSize` takes effect after the next
349
- * {@link prepare} call.
524
+ * Update the live controls; omitted keys keep their current value. Changing
525
+ * `grainSize` takes effect after the next {@link prepare} call, and an
526
+ * omitted `grainSize` keeps whatever was last requested — including the `0`
527
+ * sentinel, so a re-{@link prepare} at another sample rate re-derives it.
350
528
  */
351
529
  setConfig(config: StreamingRetuneConfig): void {
352
530
  this.retune.setConfig(config as Record<string, unknown>);
353
531
  }
354
532
 
355
- /** Current native config. */
533
+ /** The currently applied controls, with `grainSize` as the effective one. */
356
534
  config(): Required<StreamingRetuneConfig> {
357
535
  return this.retune.config();
358
536
  }
@@ -362,13 +540,27 @@ export class StreamingRetune {
362
540
  return this.retune.grainSize();
363
541
  }
364
542
 
543
+ /** Fixed overlap-add latency in samples (one grain); 0 before prepare. */
544
+ latencySamples(): number {
545
+ return this.retune.latencySamples();
546
+ }
547
+
365
548
  /** Process one mono block, returning the shifted samples (same length). */
366
549
  processMono(samples: Float32Array): Float32Array {
367
550
  return this.retune.processMono(samples);
368
551
  }
369
552
 
370
- /** Release the underlying WASM object. Safe to call only once. */
553
+ /** Release the underlying WASM object. Idempotent, as the Node facade is. */
371
554
  delete(): void {
555
+ if (this.released) {
556
+ return;
557
+ }
558
+ this.released = true;
372
559
  this.retune.delete();
373
560
  }
561
+
562
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
563
+ destroy(): void {
564
+ this.delete();
565
+ }
374
566
  }
@@ -0,0 +1,89 @@
1
+ import { projectModule } from './project_internal';
2
+ import type { TranscribeOptions, TranscribeResult } from './project_types';
3
+ import { assertSampleRate, assertSamples } from './validation';
4
+
5
+ /**
6
+ * Canonical request form for {@link transcribe}.
7
+ *
8
+ * Deliberately does NOT extend `ValidateOptions`. That option's contract is
9
+ * that skipping the JS-side scan is safe because the native layer re-validates
10
+ * with an equivalent result -- and here it would not be equivalent: the
11
+ * transcription C ABI (`sonare_transcribe`) does re-validate the buffer,
12
+ * including a non-finite scan, but a rejection there surfaces as a
13
+ * `SonareError` `InvalidParameter`, not the `RangeError` this function
14
+ * documents and every other empty/non-finite/rate check on this surface
15
+ * raises. A `{ validate: false }` here would silently change the thrown error
16
+ * class instead of skipping a redundant check. The scan is also nearly free
17
+ * against this pipeline, which already reads every sample several times.
18
+ */
19
+ export interface TranscribeRequest extends TranscribeOptions {
20
+ /** Mono source audio. Must be non-empty and all-finite. */
21
+ samples: Float32Array;
22
+ /** Sample rate of `samples` in Hz, `[8000, 384000]`. */
23
+ sampleRate: number;
24
+ /**
25
+ * Tempo the PPQ grid is built on, in BPM. **Omit to have it detected** from
26
+ * `samples`, which costs an onset/tempo pass; a detector that finds nothing
27
+ * usable falls back to 120 rather than failing the transcription.
28
+ *
29
+ * Supplying a tempo is not a claim about the audio — it is the coordinate
30
+ * system the events come back in. Twice the tempo is twice as many beats per
31
+ * second, so the same audio lands on twice the ppq.
32
+ */
33
+ tempoBpm?: number;
34
+ }
35
+
36
+ /**
37
+ * Transcribes mono audio into MIDI events on a constant-tempo grid.
38
+ *
39
+ * This joins parts that already exist rather than adding a detector: the note
40
+ * spans and their measured pitch and level come from the monophonic (pYIN) or
41
+ * polyphonic (multi-F0) chain, and the output is the flat
42
+ * {@link ProjectMidiEvent} shape {@link Project.setMidiEvents} takes, so
43
+ * nothing is left to convert.
44
+ *
45
+ * Finding no notes is not an error. Silence, and material the chain cannot
46
+ * resolve, come back with an empty `events` array and `noteCount` 0, and
47
+ * `tempoBpm` still reports the tempo that was used or detected.
48
+ *
49
+ * Three things are deliberately **not** done here, because the library already
50
+ * does each of them somewhere else and a second implementation would drift:
51
+ *
52
+ * - **Quantizing to a grid** — {@link Project.bakeMidiFx}'s `quantizePpq` /
53
+ * `quantizeStrength`.
54
+ * - **Detecting and installing a tempo map** — {@link Project.autoTempo}. The
55
+ * `tempoBpm` fallback here builds one constant-tempo grid for this call and
56
+ * installs nothing; use {@link Project.transcribeToClip} to transcribe onto a
57
+ * project's real map.
58
+ * - **Annotating key and chords** — {@link Project.annotateKeys} /
59
+ * {@link Project.annotateChords}.
60
+ *
61
+ * The tuning reference is likewise not measured — see
62
+ * {@link TranscribeOptions.referenceHz}.
63
+ *
64
+ * @throws {RangeError} on empty `samples`, a non-finite sample, or a
65
+ * `sampleRate` outside `[8000, 384000]`
66
+ * @throws {SonareError} `InvalidParameter` on an option outside its domain —
67
+ * a non-negative `velocityFloorDb`, a `fixedVelocity` outside `[1, 127]`, a
68
+ * `group` or `channel` outside `[0, 15]`, an `fmax` at or below `fmin`, or a
69
+ * written `0` on any field but `group` and `channel` — or `NotSupported`
70
+ * when the library was built without the pitch editor
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const { events, noteCount, tempoBpm } = transcribe({ samples, sampleRate, tempoBpm: 120 });
75
+ * const project = new Project();
76
+ * const { clipId } = project.addMidiClip(0, 16); // 4 bars at 4/4, in quarter notes (PPQ)
77
+ * project.setMidiEvents(clipId, events);
78
+ * ```
79
+ */
80
+ export function transcribe(request: TranscribeRequest): TranscribeResult {
81
+ assertSamples('transcribe', request.samples, true);
82
+ assertSampleRate('transcribe', request.sampleRate);
83
+ return projectModule().transcribe(
84
+ request.samples,
85
+ request.sampleRate,
86
+ request.tempoBpm,
87
+ request as unknown as TranscribeOptions,
88
+ );
89
+ }