@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
@@ -22,7 +22,7 @@ interface NativeExceptionInfo {
22
22
  * Returns null when the thrown value is neither (a genuine JS error), so the
23
23
  * caller rethrows it unchanged.
24
24
  */
25
- function nativeExceptionPtr(error: unknown): number | null {
25
+ export function nativeExceptionPtr(error: unknown): number | null {
26
26
  if (typeof error === 'number') {
27
27
  return error;
28
28
  }
@@ -38,7 +38,16 @@ function nativeExceptionPtr(error: unknown): number | null {
38
38
  /**
39
39
  * Turn a thrown native exception pointer into a {@link SonareError}. The bound
40
40
  * `sonareExceptionInfo` decodes the pointer back into { code, codeName,
41
- * message }.
41
+ * message }, then `sonareReleaseException` drops the reference emscripten's
42
+ * `__cxa_throw` took before rethrowing the pointer into JS.
43
+ *
44
+ * The release is mandatory, not an optimization: no C++ frame catches the
45
+ * exception, so that reference is the only one and nothing else ever drops it.
46
+ * Every rejected input would otherwise leak its exception object for the
47
+ * lifetime of the module — and rejection-as-control-flow (re-validating markers
48
+ * or clips on each edit, a scrub handle seeking every pointer move) is a
49
+ * documented usage of this API. It runs in a `finally` so a decode failure
50
+ * still frees, and after decoding because freeing invalidates the message.
42
51
  */
43
52
  function makeSonareError(raw: SonareModule, thrown: number): SonareError {
44
53
  let code: number = ErrorCode.Unknown;
@@ -55,6 +64,13 @@ function makeSonareError(raw: SonareModule, thrown: number): SonareError {
55
64
  }
56
65
  } catch {
57
66
  // Fall back to the generic message if decoding fails.
67
+ } finally {
68
+ try {
69
+ raw.sonareReleaseException(thrown);
70
+ } catch {
71
+ // A module built before the release binding existed still yields an error
72
+ // object; it just keeps leaking, which is what this replaces.
73
+ }
58
74
  }
59
75
  return new SonareError(code, codeName, message);
60
76
  }
@@ -85,6 +101,12 @@ function wrapModuleErrors(raw: SonareModule): SonareModule {
85
101
  if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
86
102
  return value;
87
103
  }
104
+ // Plain result data carries no native methods, and a Proxy cannot be
105
+ // structured-cloned, so wrapping it would block postMessage to a worker.
106
+ const proto = Object.getPrototypeOf(value);
107
+ if (Array.isArray(value) || proto === Object.prototype || proto === null) {
108
+ return value;
109
+ }
88
110
  const objectValue = value as object;
89
111
  const cached = objectCache.get(objectValue);
90
112
  if (cached) {
@@ -0,0 +1,252 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type {
3
+ PlaybackDiagnostics,
4
+ PlaybackRendererConfig,
5
+ PlaybackRendererOptions,
6
+ RenderPlaybackRequest,
7
+ RenderPlaybackResult,
8
+ } from './public_types_playback';
9
+ import type { WasmHrtfSet, WasmPlaybackLoudnessMeter, WasmPlaybackRenderer } from './sonare.js';
10
+
11
+ function configJsonText(config: PlaybackRendererConfig | string): string {
12
+ return typeof config === 'string' ? config : JSON.stringify(config);
13
+ }
14
+
15
+ /**
16
+ * Reads {@link HrtfSet}'s private `native`: TypeScript's `private` is a
17
+ * compile-time rule, so one reviewed read here beats widening the class with a
18
+ * handle accessor no caller wants.
19
+ */
20
+ function nativeHrtf(hrtf: HrtfSet | undefined): WasmHrtfSet | null {
21
+ if (hrtf === undefined) {
22
+ return null;
23
+ }
24
+ if (!(hrtf instanceof HrtfSet)) {
25
+ throw new TypeError('hrtf must be an HrtfSet');
26
+ }
27
+ return (hrtf as unknown as { native: WasmHrtfSet }).native;
28
+ }
29
+
30
+ /**
31
+ * An HRTF set (SHRF v1): the direct-sound impulse responses and inter-aural
32
+ * time delays a headphones-target {@link PlaybackRenderer} convolves each
33
+ * virtual speaker's signal with.
34
+ *
35
+ * This build embeds no HRTF data. The package ships the default set as the
36
+ * asset `@libraz/libsonare/hrtf/default.shrf`; fetch or read it and pass the
37
+ * bytes to {@link HrtfSet.fromBytes}. A renderer built from a set keeps its
38
+ * own copy, so deleting the set right after construction is safe.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const bytes = new Uint8Array(await (await fetch(hrtfUrl)).arrayBuffer());
43
+ * const hrtf = HrtfSet.fromBytes(bytes);
44
+ * const renderer = new PlaybackRenderer({ config: {}, hrtf, sampleRate: 48000 });
45
+ * hrtf.delete();
46
+ * ```
47
+ */
48
+ export class HrtfSet {
49
+ private native: WasmHrtfSet;
50
+ private released = false;
51
+
52
+ private constructor(native: WasmHrtfSet) {
53
+ this.native = native;
54
+ }
55
+
56
+ /** Builds an HRTF set from SHRF v1 bytes; malformed data throws. */
57
+ static fromBytes(bytes: Uint8Array): HrtfSet {
58
+ return new HrtfSet(getSonareModule().createHrtfSet(bytes));
59
+ }
60
+
61
+ /** Releases the native handle. Idempotent, as the Node facade is. */
62
+ delete(): void {
63
+ if (this.released) {
64
+ return;
65
+ }
66
+ this.released = true;
67
+ this.native.delete();
68
+ }
69
+
70
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
71
+ destroy(): void {
72
+ this.delete();
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Renders decoded movie audio (mono / stereo / 5.1 / 7.1 PCM) to headphones
78
+ * (binaural, head tracking, room model) or to stereo / 5.1 / 7.1 speakers
79
+ * (upmix, loudness alignment, night-mode DRC, dialogue level, speaker
80
+ * calibration, bass management).
81
+ *
82
+ * `config` follows `schemas/playback-renderer-config.schema.json`; see
83
+ * {@link PlaybackRendererConfig}. A headphones target requires `hrtf`.
84
+ *
85
+ * `processPlanar` copies every plane through the embind boundary, so it suits
86
+ * the main thread; like `processInterleaved`, a non-finite sample is replaced
87
+ * with 0 and counted rather than refused. The AudioWorklet path is
88
+ * `SonarePlaybackWorkletProcessor` in the worklet bundle.
89
+ */
90
+ export class PlaybackRenderer {
91
+ private native: WasmPlaybackRenderer;
92
+ private released = false;
93
+
94
+ constructor(options: PlaybackRendererOptions) {
95
+ this.native = getSonareModule().createPlaybackRenderer(
96
+ configJsonText(options.config),
97
+ nativeHrtf(options.hrtf),
98
+ options.sampleRate ?? 48000,
99
+ options.maxBlockSize ?? 1024,
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Renders one planar block; every plane must carry the same frame count, at
105
+ * most `maxBlockSize` (0 is a no-op). With a fixed input layout the plane
106
+ * count must equal {@link inputChannels}; with `input.layout: "auto"` it
107
+ * must be 1, 2, 6 or 8, and a change switches the input layout without
108
+ * changing the latency. Non-finite input samples are replaced with 0 and
109
+ * counted ({@link nonFiniteDiscardCount}).
110
+ */
111
+ processPlanar(planes: Float32Array[]): Float32Array[] {
112
+ return this.native.processPlanar(planes);
113
+ }
114
+
115
+ /** Interleaved variant of {@link processPlanar}. Non-finite input samples are replaced with 0 and counted. */
116
+ processInterleaved(samples: Float32Array, inChannels: number): Float32Array {
117
+ return this.native.processInterleaved(samples, inChannels);
118
+ }
119
+
120
+ /** Applies a complete configuration document; a changed prepare key throws. */
121
+ setConfig(config: PlaybackRendererConfig | string): void {
122
+ this.native.setConfig(configJsonText(config));
123
+ }
124
+
125
+ /** The current complete configuration document. */
126
+ config(): PlaybackRendererConfig {
127
+ return JSON.parse(this.native.configJson()) as PlaybackRendererConfig;
128
+ }
129
+
130
+ /**
131
+ * Publishes the listener head orientation in degrees: right-handed,
132
+ * positive yaw turns the head right, positive pitch looks up, positive roll
133
+ * lowers the right ear. Ignored by a speakers target; a non-finite angle is
134
+ * ignored.
135
+ */
136
+ setHeadOrientation(yawDeg: number, pitchDeg = 0, rollDeg = 0): void {
137
+ this.native.setHeadOrientation(yawDeg, pitchDeg, rollDeg);
138
+ }
139
+
140
+ /**
141
+ * Clears DSP state (filters, FIFOs, dynamics, convolution history, pending
142
+ * input-layout drains). Configuration and head pose are kept. Call it after
143
+ * a seek, from the thread that processes.
144
+ */
145
+ reset(): void {
146
+ this.native.reset();
147
+ }
148
+
149
+ /**
150
+ * Renderer latency in samples (headphones: near ear). Depends only on the
151
+ * target, the sample rate and distance compensation, never on realtime keys
152
+ * or the input layout.
153
+ */
154
+ latencySamples(): number {
155
+ return this.native.latencySamples();
156
+ }
157
+
158
+ /**
159
+ * Channel count of the active input layout. With `input.layout: "auto"`
160
+ * this follows the channel count of the most recent non-empty process call
161
+ * (2 before the first call).
162
+ */
163
+ inputChannels(): number {
164
+ return this.native.inputChannels();
165
+ }
166
+
167
+ /** Channel count of the output target. */
168
+ outputChannels(): number {
169
+ return this.native.outputChannels();
170
+ }
171
+
172
+ /**
173
+ * Inactive stages, per-stage latency, clamps, the active input layout, and
174
+ * the layout-switch / truncated-drain counters, as a plain object.
175
+ */
176
+ diagnostics(): PlaybackDiagnostics {
177
+ return JSON.parse(this.native.diagnosticsJson()) as PlaybackDiagnostics;
178
+ }
179
+
180
+ /** Non-finite input samples replaced with 0 since construction. */
181
+ nonFiniteDiscardCount(): number {
182
+ return this.native.nonFiniteDiscardCount();
183
+ }
184
+
185
+ /** Releases the native handle. Idempotent, as the Node facade is. */
186
+ delete(): void {
187
+ if (this.released) {
188
+ return;
189
+ }
190
+ this.released = true;
191
+ this.native.delete();
192
+ }
193
+
194
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
195
+ destroy(): void {
196
+ this.delete();
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Integrated-loudness meter for multichannel program material (BS.1770
202
+ * channel weights by channel count: 1, 2, 6 or 8), for measuring
203
+ * `loudness.program_lufs` ahead of a {@link PlaybackRenderer}.
204
+ */
205
+ export class PlaybackLoudnessMeter {
206
+ private native: WasmPlaybackLoudnessMeter;
207
+ private released = false;
208
+
209
+ constructor(channels: number, sampleRate: number) {
210
+ this.native = getSonareModule().createPlaybackLoudnessMeter(channels, sampleRate);
211
+ }
212
+
213
+ /** Feeds interleaved frames of any length. */
214
+ pushInterleaved(samples: Float32Array): void {
215
+ this.native.pushInterleaved(samples);
216
+ }
217
+
218
+ /** Integrated loudness of everything pushed so far, in LUFS. */
219
+ integratedLufs(): number {
220
+ return this.native.integratedLufs();
221
+ }
222
+
223
+ /** Releases the native handle. Idempotent, as the Node facade is. */
224
+ delete(): void {
225
+ if (this.released) {
226
+ return;
227
+ }
228
+ this.released = true;
229
+ this.native.delete();
230
+ }
231
+
232
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
233
+ destroy(): void {
234
+ this.delete();
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Offline one-shot playback render: builds a renderer internally, feeds the
240
+ * whole interleaved signal through it, and removes the renderer's own latency
241
+ * so the output aligns with the input frame for frame.
242
+ * An empty or non-finite `samples` is refused with an `InvalidParameter` error.
243
+ */
244
+ export function renderPlayback(request: RenderPlaybackRequest): RenderPlaybackResult {
245
+ return getSonareModule().renderPlayback(
246
+ request.samples,
247
+ request.channels,
248
+ request.sampleRate,
249
+ configJsonText(request.config),
250
+ nativeHrtf(request.hrtf),
251
+ );
252
+ }
@@ -0,0 +1,279 @@
1
+ import { ErrorCode, SonareError } from './errors';
2
+ import { getSonareModule } from './module_state';
3
+ import type {
4
+ NoteEditInput,
5
+ NoteObject,
6
+ PolyphonicAnalysisOptions,
7
+ PolyphonicRenderOptions,
8
+ } from './public_types';
9
+ import type { WasmPolyphonicAnalysis } from './sonare.js';
10
+ import type { ValidateOptions } from './validation';
11
+ import { assertSampleRate, assertSamples } from './validation';
12
+
13
+ /** Canonical request form for {@link analyzePolyphonic}. */
14
+ export interface AnalyzePolyphonicRequest extends PolyphonicAnalysisOptions, ValidateOptions {
15
+ samples: Float32Array;
16
+ /**
17
+ * Sample rate in Hz. Required: every duration in the chain — the ridge
18
+ * minimum, the note minimum, the per-frame windows — is converted to samples
19
+ * with this rate, so a wrong value silently analyses differently.
20
+ */
21
+ sampleRate: number;
22
+ }
23
+
24
+ /**
25
+ * A polyphonic analysis, held as a handle so one note of a chord can be edited
26
+ * and the result re-rendered without analysing the audio again.
27
+ *
28
+ * Created by {@link analyzePolyphonic}. **Release it with {@link destroy} as soon
29
+ * as you are done with it**: the handle owns the source's complex spectrogram plus
30
+ * the claimed bins of every note, which is the input over again plus the claims.
31
+ * That is the price of re-rendering an edit for free, and it is why this is a
32
+ * handle and not a result object — the measurement never crosses into JS.
33
+ *
34
+ * What crosses is what a host acts on: the notes ({@link notes}), each note's
35
+ * pending edit ({@link setNoteEdit}), the per-frame voice count
36
+ * ({@link polyphony}), and per note a pitch, a level and a salience curve. The
37
+ * spectrogram, the masks and the per-bin weights do not, and no method reports a
38
+ * per-bin figure.
39
+ *
40
+ * Using the analysis after it has been released throws `InvalidState` rather than
41
+ * reaching a freed native object.
42
+ */
43
+ export class PolyphonicAnalysis {
44
+ private native: WasmPolyphonicAnalysis | null;
45
+
46
+ /** Analyses the request's audio. {@link analyzePolyphonic} is the same call. */
47
+ constructor(request: AnalyzePolyphonicRequest) {
48
+ assertSamples('analyzePolyphonic', request.samples, request.validate !== false);
49
+ assertSampleRate('analyzePolyphonic', request.sampleRate);
50
+ const module = getSonareModule();
51
+ this.native = module.createPolyphonicAnalysis(
52
+ request.samples,
53
+ request.sampleRate,
54
+ request as unknown as Record<string, unknown>,
55
+ );
56
+ }
57
+
58
+ private handle(): WasmPolyphonicAnalysis {
59
+ if (this.native === null) {
60
+ throw new SonareError(
61
+ ErrorCode.InvalidState,
62
+ 'InvalidState',
63
+ 'PolyphonicAnalysis has been released',
64
+ );
65
+ }
66
+ return this.native;
67
+ }
68
+
69
+ /** Number of notes, which is also the number of claim sets. */
70
+ get noteCount(): number {
71
+ return this.handle().noteCount;
72
+ }
73
+
74
+ /** Number of STFT frames the analysis ran over. */
75
+ get frameCount(): number {
76
+ return this.handle().frameCount;
77
+ }
78
+
79
+ /**
80
+ * Every note, in the order their claim sets are held in — the same
81
+ * {@link NoteObject} shape `extractNotes` returns, so a host that edits through
82
+ * both doors sees one note.
83
+ *
84
+ * Each note carries its sample span, its frame span (in the analysis's own
85
+ * framing), its median pitch, its steadiness, its per-frame `amplitude` and its
86
+ * pending edit. The curves a note does not carry inline have their own accessors:
87
+ * {@link noteF0}, {@link noteSalience}, and {@link noteEnvelope} for the points
88
+ * last set through {@link setNoteEdit}. `amplitude` is {@link noteAmplitude}'s
89
+ * curve, read once per note.
90
+ */
91
+ notes(): NoteObject[] {
92
+ return this.handle().notes();
93
+ }
94
+
95
+ /**
96
+ * Replaces one note's pending edit.
97
+ *
98
+ * The only thing a host writes. Everything else on a note is a measurement, and
99
+ * the order is the pairing with the claim sets, so neither is settable.
100
+ *
101
+ * An omitted field is the identity, so `{}` restores the identity edit. The
102
+ * envelope is `edit.amplitudeEnvelope`, which the handle copies, and its points
103
+ * are per-frame linear gains over the note's span on top of `gainDb` — stretched
104
+ * over whatever length the note renders at, so one entry is a constant gain and
105
+ * the count need not match the note's frame count. Every value must be finite
106
+ * and non-negative, which {@link render} is where it is checked, so one refusal
107
+ * names one place.
108
+ *
109
+ * @param note - Index below {@link noteCount}
110
+ * @throws {SonareError} `InvalidParameter` when `note` is out of range
111
+ */
112
+ setNoteEdit(note: number, edit: NoteEditInput): void {
113
+ this.handle().setNoteEdit(note, edit);
114
+ }
115
+
116
+ /**
117
+ * Voices estimated per frame, before tracking dropped anything — one entry per
118
+ * frame from frame 0.
119
+ *
120
+ * What the estimation saw rather than what survived: a frame reported as three
121
+ * voices with two notes spanning it is the difference between the two stages,
122
+ * which is the figure a host deciding what to edit wants.
123
+ */
124
+ polyphony(): Int32Array {
125
+ return this.handle().polyphony();
126
+ }
127
+
128
+ /**
129
+ * One note's F0 in Hz, per frame over its own span.
130
+ *
131
+ * `frameEnd - frameStart` entries, so the value at index `i` belongs to frame
132
+ * `frameStart + i`. This is the curve the monophonic door makes a caller pass
133
+ * back in; here the handle already holds it, so a curve edit needs nothing from
134
+ * the caller.
135
+ *
136
+ * @throws {SonareError} `InvalidParameter` when `note` is out of range
137
+ */
138
+ noteF0(note: number): Float32Array {
139
+ return this.handle().noteF0(note);
140
+ }
141
+
142
+ /** One note's linear RMS, per frame over its own span. Indexed as {@link noteF0}. */
143
+ noteAmplitude(note: number): Float32Array {
144
+ return this.handle().noteAmplitude(note);
145
+ }
146
+
147
+ /**
148
+ * One note's salience, per frame over its own span. Indexed as {@link noteF0},
149
+ * and the one curve here that is not the note's own: it is the tracked ridge's,
150
+ * so a frame of the note the ridge does not reach reads 0.
151
+ *
152
+ * Salience is what the estimation scored the candidate at, so it says how well
153
+ * the material supported this note rather than how loud the note is —
154
+ * {@link noteAmplitude} is the loud.
155
+ */
156
+ noteSalience(note: number): Float32Array {
157
+ return this.handle().noteSalience(note);
158
+ }
159
+
160
+ /**
161
+ * The stretch fitted for each note, one entry per note in {@link notes}' order.
162
+ *
163
+ * Empty when `estimateInharmonicity` was not set, so an empty array means the
164
+ * fit was never asked for. A non-negative entry is a fitted stretch; **exactly
165
+ * `-1` is the refusal**, and a refused note's claims were placed at the
166
+ * `inharmonicity` the request declared instead.
167
+ *
168
+ * **`0` is a fitted result and means the harmonic series**, which is why the
169
+ * refusal is reported at all: the declared stretch also defaults to 0, so the
170
+ * effective value alone cannot separate a fit that reached the material from one
171
+ * that did not. The fit refuses a chord at the default framing, so the
172
+ * distinction is the usual case rather than an edge one.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const analysis = analyzePolyphonic({ samples, sampleRate, estimateInharmonicity: true });
177
+ * const fitted = analysis.noteInharmonicity();
178
+ * const reached = [...fitted].filter((stretch) => stretch >= 0).length;
179
+ * ```
180
+ */
181
+ noteInharmonicity(): Float32Array {
182
+ return this.handle().noteInharmonicity();
183
+ }
184
+
185
+ /**
186
+ * One note's amplitude envelope points, as last set — the same array
187
+ * `notes()[note].edit.amplitudeEnvelope` carries.
188
+ *
189
+ * Indexed from 0 rather than over the note's span: an envelope is a set of gain
190
+ * points stretched over whatever length the note renders at, not a per-frame
191
+ * signal. The only one of the four curves that is not a measurement, and empty on
192
+ * a note carrying no envelope.
193
+ */
194
+ noteEnvelope(note: number): Float32Array {
195
+ return this.handle().noteEnvelope(note);
196
+ }
197
+
198
+ /**
199
+ * Renders the analysis back to audio with whatever edits its notes carry, at the
200
+ * source's length.
201
+ *
202
+ * Each note's claimed share is inverted, edited, and added to the residual — the
203
+ * part of the input no note claimed. With every edit identity the result is the
204
+ * analysis's own round trip, not the source bit for bit, the STFT round trip's
205
+ * error being neither added to nor removed here.
206
+ *
207
+ * The render is additive per note with no cross-note term, so an unedited note's
208
+ * contribution is identical between two renders. That is also the limit: a host
209
+ * cannot tell from two renders whether a claim set divided the energy correctly.
210
+ *
211
+ * @throws {SonareError} `InvalidParameter` on an option or an edit field the
212
+ * renderer rejects — a non-positive stretch ratio, a negative or non-finite
213
+ * envelope point, or a vibrato or drift edit on a note carrying no usable
214
+ * pitch curve
215
+ */
216
+ render(options: PolyphonicRenderOptions = {}): Float32Array {
217
+ return this.handle().render(options as Record<string, unknown>);
218
+ }
219
+
220
+ /**
221
+ * Releases the underlying WASM object and everything it holds. Idempotent,
222
+ * as the Node facade is; any other method called after this one still
223
+ * throws `InvalidState` rather than reaching a freed native object.
224
+ */
225
+ delete(): void {
226
+ if (this.native === null) {
227
+ return;
228
+ }
229
+ const native = this.native;
230
+ this.native = null;
231
+ native.delete();
232
+ }
233
+
234
+ /** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
235
+ destroy(): void {
236
+ this.delete();
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Analyses audio into editable notes and returns a handle to the analysis.
242
+ *
243
+ * One pass: one STFT, the multi-F0 extraction over it, a claim set per tracked
244
+ * ridge, the apportionment of the bins two notes stand on, and the measured fields
245
+ * of each note. Every note comes back with the identity edit, so rendering the
246
+ * result unchanged reproduces the analysis's own round trip.
247
+ *
248
+ * An analysis finding no notes is not an error. Silence, or material the register
249
+ * of the framing cannot resolve, tracks no ridge; rendering that is the residual
250
+ * alone, which is the whole round trip.
251
+ *
252
+ * **This is for spans, not for whole songs.** The handle holds the source's
253
+ * complex spectrogram plus every note's claimed bins — roughly 1,440 MiB for five
254
+ * minutes of audio against a 2 GiB linear-memory cap — so analyse the passage you
255
+ * are editing and {@link PolyphonicAnalysis.destroy} it when done.
256
+ *
257
+ * @throws {RangeError} on empty samples, a non-finite sample, or a `sampleRate`
258
+ * outside `[8000, 384000]`
259
+ * @throws {SonareError} `InvalidParameter` on a config value the chain rejects,
260
+ * or on audio too short for two STFT frames at the configured `nFft`/`hopLength`
261
+ * (roughly one `hopLength`, ~512 samples at the default)
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * const analysis = analyzePolyphonic({ samples, sampleRate, maxPolyphony: 3 });
266
+ * try {
267
+ * // Transpose the lowest note of the chord up a semitone.
268
+ * const notes = analysis.notes();
269
+ * const lowest = notes.reduce((a, b) => (a.medianHz <= b.medianHz ? a : b));
270
+ * analysis.setNoteEdit(notes.indexOf(lowest), { pitchShiftSemitones: 1 });
271
+ * const edited = analysis.render();
272
+ * } finally {
273
+ * analysis.destroy();
274
+ * }
275
+ * ```
276
+ */
277
+ export function analyzePolyphonic(request: AnalyzePolyphonicRequest): PolyphonicAnalysis {
278
+ return new PolyphonicAnalysis(request);
279
+ }
package/src/project.ts CHANGED
@@ -1,15 +1,66 @@
1
+ export type {
2
+ Articulation,
3
+ BuiltinSynthBinding,
4
+ BuiltinSynthConfig,
5
+ BuiltinSynthWaveform,
6
+ ControllerAxis,
7
+ ControllerBinding,
8
+ ControllerInput,
9
+ MpeDimension,
10
+ NoteTracking,
11
+ SampleDesc,
12
+ SampleDescLoopMode,
13
+ SampleKeyTrack,
14
+ SampleLoopMode,
15
+ SampleZoneDesc,
16
+ Sf2InstrumentConfig,
17
+ Sf2ProgramStatus,
18
+ SourceBackend,
19
+ SynthBodyType,
20
+ SynthEngineMode,
21
+ SynthEnumTables,
22
+ SynthFilterModel,
23
+ SynthFilterOutput,
24
+ SynthModDestination,
25
+ SynthModRouting,
26
+ SynthModSource,
27
+ SynthOscWaveform,
28
+ SynthPatch,
29
+ SynthRetrigger,
30
+ } from './instrument_types';
31
+ export {
32
+ ARTICULATIONS,
33
+ BUILTIN_SYNTH_WAVEFORMS,
34
+ CONTROLLER_AXES,
35
+ CONTROLLER_INPUTS,
36
+ MPE_DIMENSIONS,
37
+ NOTE_TRACKINGS,
38
+ SAMPLE_KEY_TRACKS,
39
+ SAMPLE_LOOP_MODES,
40
+ SYNTH_BODY_TYPES,
41
+ SYNTH_ENGINE_MODES,
42
+ SYNTH_FILTER_MODELS,
43
+ SYNTH_FILTER_OUTPUTS,
44
+ SYNTH_MOD_DESTINATIONS,
45
+ SYNTH_MOD_SOURCES,
46
+ SYNTH_OSC_WAVEFORMS,
47
+ SYNTH_RETRIGGERS,
48
+ } from './instrument_types';
1
49
  export { Project } from './project_class';
2
50
  export {
51
+ controllerProfileNames,
3
52
  projectAbiVersion,
4
53
  synthEnumTables,
54
+ synthGsDrumKitIsVoicedApart,
55
+ synthGsDrumKitName,
56
+ synthGsVariationIsVoicedApart,
5
57
  synthPatchRoundTripForTest,
6
58
  synthPresetNames,
7
59
  synthPresetPatch,
8
60
  } from './project_synth';
9
61
  export type {
10
- BuiltinSynthBinding,
11
- BuiltinSynthConfig,
12
- BuiltinSynthWaveform,
62
+ AlignTakeToReferenceRequest,
63
+ AlignTakeToReferenceResult,
13
64
  ExternalSeparatedStem,
14
65
  ExternalSeparatedStemImportRequest,
15
66
  ExternalSeparatedStemImportResult,
@@ -47,41 +98,27 @@ export type {
47
98
  ProjectMidiRouteResult,
48
99
  ProjectNotePairValidation,
49
100
  ProjectSource,
101
+ ProjectTempoCandidate,
102
+ ProjectTempoOptions,
50
103
  ProjectTempoSegment,
51
104
  ProjectTimeSignatureSegment,
52
105
  ProjectTrack,
53
106
  ProjectTrackDesc,
54
107
  ProjectTrackKind,
108
+ ProjectTranscribeRequest,
55
109
  ProjectWarpAnchor,
56
110
  ProjectWarpMapDesc,
57
111
  ProjectWarpMode,
58
- Sf2InstrumentConfig,
59
- Sf2ProgramStatus,
60
- SourceBackend,
61
- SynthBodyType,
62
- SynthEngineMode,
63
- SynthEnumTables,
64
- SynthFilterModel,
65
- SynthFilterOutput,
66
- SynthModDestination,
67
- SynthModRouting,
68
- SynthModSource,
69
- SynthOscWaveform,
70
- SynthPatch,
112
+ TakeAlignment,
113
+ TranscribeOptions,
114
+ TranscribeResult,
71
115
  } from './project_types';
72
116
  export {
73
117
  AutomationTargetKind,
74
- BUILTIN_SYNTH_WAVEFORMS,
75
118
  EXPECTED_PROJECT_ABI_VERSION,
76
119
  MarkerKind,
77
120
  PROJECT_AUTOMATION_TARGET_OPAQUE,
78
121
  PROJECT_AUTOMATION_TARGET_TRACK_FADER_DB,
79
122
  PROJECT_AUTOMATION_TARGET_TRACK_PAN,
80
- SYNTH_BODY_TYPES,
81
- SYNTH_ENGINE_MODES,
82
- SYNTH_FILTER_MODELS,
83
- SYNTH_FILTER_OUTPUTS,
84
- SYNTH_MOD_DESTINATIONS,
85
- SYNTH_MOD_SOURCES,
86
- SYNTH_OSC_WAVEFORMS,
87
123
  } from './project_types';
124
+ export { SampleBank } from './sample_bank';