@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
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Percussive event editing: locating struck sounds in audio and rendering an
3
+ * edited set of them back.
4
+ */
5
+
6
+ import { getSonareModule } from './module_state';
7
+ import type { PercussiveEvent, PercussiveEventInput } from './public_types';
8
+ import type { ValidateOptions } from './validation';
9
+ import { assertPercussiveSeparation, assertSampleRate, assertSamples } from './validation';
10
+
11
+ function requireModule() {
12
+ return getSonareModule();
13
+ }
14
+
15
+ /**
16
+ * The separation a percussive event's signal is lifted out with. Extraction
17
+ * measures events against it and rendering has to repeat it, so both calls take
18
+ * the same four fields and a render must be given what the extraction was.
19
+ */
20
+ export interface PercussiveSeparationOptions {
21
+ /**
22
+ * FFT size and hop the separation and the onset detector share. They cannot be
23
+ * set apart: an event measured on one framing and lifted out on another is not
24
+ * the same signal. Default 2048 and 512. The pair must overlap-add — `nFft`
25
+ * even and at least 2, `hopLength` no more than half of it — because the
26
+ * separation inverts an STFT. A negative value is rejected on its own, before
27
+ * the zero-is-default rule could swallow it.
28
+ */
29
+ nFft?: number;
30
+ /** Hop length in samples. Default 512. */
31
+ hopLength?: number;
32
+ /**
33
+ * Median filter lengths the separation runs, along time and along frequency. A
34
+ * longer harmonic kernel calls more of a sustained sound harmonic. Default 31;
35
+ * any other value must be odd and positive, so an even one is rejected rather
36
+ * than rounded. 1 is legal and degenerate rather than an error: a length-1
37
+ * median is the identity, so both components come back as the source.
38
+ */
39
+ hpssKernelHarmonic?: number;
40
+ /** Vertical median filter length, under the same rule. Default 31. */
41
+ hpssKernelPercussive?: number;
42
+ }
43
+
44
+ /** Canonical request form for {@link extractPercussiveEvents}. */
45
+ export interface ExtractPercussiveEventsRequest
46
+ extends PercussiveSeparationOptions,
47
+ ValidateOptions {
48
+ samples: Float32Array | readonly number[];
49
+ /**
50
+ * Sample rate in Hz. Required: `maxEventMs` is converted to samples with this
51
+ * rate, so a wrong/omitted value caps the spans differently.
52
+ */
53
+ sampleRate: number;
54
+ /**
55
+ * Minimum frames between consecutive onsets. Default 1, and a whole number:
56
+ * 0 is how the default is spelled, so a fractional wait is refused rather
57
+ * than truncated onto it. Negative is rejected.
58
+ */
59
+ onsetWait?: number;
60
+ /**
61
+ * Offset added to the detector's adaptive threshold; raising it finds fewer,
62
+ * stronger hits and lowering it finds more. Default 0.06, so exactly zero is
63
+ * the one value not selectable here — a negative one is accepted and puts the
64
+ * threshold below the default, which is the direction a caller reaching for
65
+ * zero wanted anyway.
66
+ */
67
+ onsetDelta?: number;
68
+ /**
69
+ * Caps a span that no onset follows. It binds at the end of a phrase and at the
70
+ * end of the track; anywhere else the next onset closes the span first. Default
71
+ * 500 ms.
72
+ */
73
+ maxEventMs?: number;
74
+ /**
75
+ * Drops an event whose `percussiveRatio` falls below this; must be in `[0, 1]`.
76
+ * 0 is both the default and the meaningful "keep everything". Raising it is
77
+ * useful on material that is mostly drums and wrong on a dense mix, where it
78
+ * also drops real hits sitting over a loud sustain.
79
+ */
80
+ minPercussiveRatio?: number;
81
+ }
82
+
83
+ /** Canonical request form for {@link renderPercussiveEvents}. */
84
+ export interface RenderPercussiveEventsRequest
85
+ extends PercussiveSeparationOptions,
86
+ ValidateOptions {
87
+ samples: Float32Array | readonly number[];
88
+ /**
89
+ * Sample rate in Hz. Required: `fadeMs` is converted to samples with this rate,
90
+ * so a wrong/omitted value changes the fade length.
91
+ */
92
+ sampleRate: number;
93
+ /** The events to render, with their edits. Source spans must not overlap. */
94
+ events: readonly PercussiveEventInput[];
95
+ /**
96
+ * Fade-out at the tail of each lifted span. Default 5 ms, so a zero-length
97
+ * fade is unreachable here rather than rejected — and a hard cut is not a thing
98
+ * to want anyway, because what the fade shapes is the signal being subtracted,
99
+ * so squaring it off leaves a step. There is deliberately
100
+ * no matching fade-in: a span opens in front of its transient, where the
101
+ * percussive component is near-silent, so cutting square there costs nothing
102
+ * and keeps a muted hit's attack from surviving inside a fade.
103
+ */
104
+ fadeMs?: number;
105
+ }
106
+
107
+ /**
108
+ * Extract editable percussive events from audio alone.
109
+ *
110
+ * Each event is a struck sound located in time: a span in source samples, its
111
+ * detector strength, the percussive peak over the span, the share of the span's
112
+ * energy the separation called percussive, and an identity
113
+ * {@link PercussiveEventEdit}. Edit the events and hand them to
114
+ * {@link renderPercussiveEvents} to apply the result — the source audio is never
115
+ * mutated, and a set whose edits are all identity renders back to the input bit
116
+ * for bit.
117
+ *
118
+ * Onsets are detected on the percussive component rather than on the source, so
119
+ * a harmonic attack is attenuated before the detector sees it instead of being
120
+ * filtered out afterwards. Each onset opens a span that the next one closes,
121
+ * capped by `maxEventMs` and never running past the end of the audio.
122
+ *
123
+ * Each onset is backtracked to the transient's start, which is not optional and
124
+ * is why there is no knob for it: peak-picking lands after the attack, and a span
125
+ * that opened there would report the next hit's peak and leave its own attack
126
+ * behind when muted.
127
+ *
128
+ * An event carries no pitch and is never associated with a {@link NoteObject} —
129
+ * a struck sound has no steady F0 to edit, so the two models are extracted by
130
+ * separate calls.
131
+ *
132
+ * @param request - Audio, its sample rate, and the separation, peak-picking and
133
+ * span options
134
+ * @returns One {@link PercussiveEvent} per detected hit, in time order; an empty
135
+ * array when nothing was detected
136
+ * @throws RangeError when the samples or sample rate fail the shared input checks
137
+ * @throws SonareError (`InvalidParameter`) on a kernel size that is not an
138
+ * integer within the 32-bit range, a framing size that is negative or outside
139
+ * that range, a framing that breaks constant overlap-add, an `onsetWait` that
140
+ * is fractional, negative or non-finite, a negative or non-finite `onsetDelta`
141
+ * / `maxEventMs`, or a `minPercussiveRatio` outside `[0, 1]`
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * const events = extractPercussiveEvents({ samples, sampleRate });
146
+ * // Drop the second hit and push the third 10 ms late.
147
+ * events[1].edit.muted = true;
148
+ * events[2].edit.timeOffsetSamples = Math.round(0.01 * sampleRate);
149
+ * const edited = renderPercussiveEvents({ samples, sampleRate, events });
150
+ * ```
151
+ */
152
+ export function extractPercussiveEvents(
153
+ request: ExtractPercussiveEventsRequest,
154
+ ): PercussiveEvent[] {
155
+ assertSamples('extractPercussiveEvents', request.samples, request.validate !== false);
156
+ assertSampleRate('extractPercussiveEvents', request.sampleRate);
157
+ assertPercussiveSeparation('extractPercussiveEvents', request);
158
+ return requireModule().extractPercussiveEvents(request.samples, request.sampleRate, request);
159
+ }
160
+
161
+ /**
162
+ * Render edited percussive events back over their source audio.
163
+ *
164
+ * Per event the lifted signal is the percussive component over
165
+ * `[onsetSample, offsetSample)` under the tail fade. It is subtracted where it
166
+ * sits and, unless the event is muted, added back at the shifted position scaled
167
+ * by the gain. Only that signal moves, so muting a hit leaves the harmonic
168
+ * content under it sounding and moving one does not drag its neighbours' sustain
169
+ * along.
170
+ *
171
+ * Each event's span and `edit` are read; `strength`, `peakAmplitude` and
172
+ * `percussiveRatio` are ignored, so an extracted event can be passed back as-is,
173
+ * or an event can be built by hand from the span alone. A set whose edits are all
174
+ * identity reproduces the input bit for bit and runs no separation at all.
175
+ *
176
+ * Pass the separation the events were extracted with: a different one lifts a
177
+ * different signal out of the span than the one the events describe. It is
178
+ * validated even when every edit is the identity and no separation runs, so an
179
+ * unusable framing is an error on every set rather than on some of them.
180
+ *
181
+ * Overlap is checked on the source spans only. Where `timeOffsetSamples` lands an
182
+ * event is not, so two moved events may be written over each other, and a shift
183
+ * that pushes the signal past either end is truncated there rather than wrapped.
184
+ *
185
+ * @param request - Source audio, the events to render, the separation and the
186
+ * tail fade
187
+ * @returns The rendered audio, the same length and sample rate as the input
188
+ * @throws RangeError when the samples or sample rate fail the shared input checks
189
+ * @throws SonareError (`InvalidParameter`) on an event whose span is empty,
190
+ * reversed or outside the audio, overlapping source spans, a non-finite
191
+ * `gainDb`, a kernel size that is not an integer within the 32-bit range, a
192
+ * framing that breaks constant overlap-add, or a negative or non-finite
193
+ * `fadeMs`
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * const events = extractPercussiveEvents({ samples, sampleRate });
198
+ *
199
+ * // Lift the loudest hit by 3 dB and leave the rest untouched.
200
+ * const loudest = events.reduce((a, b) => (a.strength >= b.strength ? a : b));
201
+ * const edited = events.map((event) =>
202
+ * event === loudest ? { ...event, edit: { ...event.edit, gainDb: 3 } } : event,
203
+ * );
204
+ * const rendered = renderPercussiveEvents({ samples, sampleRate, events: edited });
205
+ * ```
206
+ */
207
+ export function renderPercussiveEvents(request: RenderPercussiveEventsRequest): Float32Array {
208
+ assertSamples('renderPercussiveEvents', request.samples, request.validate !== false);
209
+ assertSampleRate('renderPercussiveEvents', request.sampleRate);
210
+ assertPercussiveSeparation('renderPercussiveEvents', request);
211
+ return requireModule().renderPercussiveEvents(
212
+ request.samples,
213
+ request.sampleRate,
214
+ request.events,
215
+ request,
216
+ );
217
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Harmonic/percussive separation: the masked split and the two shortcuts
3
+ * that return a single component.
4
+ */
5
+
6
+ import { resolveFftOptions } from './_fft_options';
7
+ import { getSonareModule } from './module_state';
8
+ import type { HpssResult } from './public_types';
9
+ import type { ValidateOptions } from './validation';
10
+ import { assertHpssKernels, assertSamples } from './validation';
11
+
12
+ function requireModule() {
13
+ return getSonareModule();
14
+ }
15
+
16
+ function resolveHardMask(value: unknown, fnName: string): boolean {
17
+ if (value === undefined) {
18
+ return false;
19
+ }
20
+ if (typeof value !== 'boolean') {
21
+ throw new TypeError(`${fnName}: hardMask must be a boolean`);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ /** Canonical request form for HPSS. */
27
+ export interface HpssRequest {
28
+ samples: Float32Array;
29
+ sampleRate?: number;
30
+ /**
31
+ * Horizontal median filter size, in STFT frames: a positive odd integer at
32
+ * most 524287. Default 31. The ceiling is 524288 and an even kernel is
33
+ * refused, so 524287 is the largest legal value.
34
+ */
35
+ kernelHarmonic?: number;
36
+ /** Vertical median filter size, in STFT bins, under the same rule. Default 31. */
37
+ kernelPercussive?: number;
38
+ nFft?: number;
39
+ hopLength?: number;
40
+ hardMask?: boolean;
41
+ }
42
+
43
+ export interface HarmonicRequest extends ValidateOptions {
44
+ samples: Float32Array;
45
+ sampleRate?: number;
46
+ }
47
+
48
+ export interface PercussiveRequest extends ValidateOptions {
49
+ samples: Float32Array;
50
+ sampleRate?: number;
51
+ }
52
+
53
+ /**
54
+ * Perform Harmonic-Percussive Source Separation (HPSS).
55
+ *
56
+ * @param samples - Audio samples (mono, float32)
57
+ * @param sampleRate - Sample rate in Hz (default: 22050)
58
+ * @param kernelHarmonic - Horizontal median filter size in STFT frames; a
59
+ * positive odd integer at most 524287 (default: 31)
60
+ * @param kernelPercussive - Vertical median filter size in STFT bins, under the
61
+ * same rule (default: 31)
62
+ * @returns Separated harmonic and percussive components
63
+ * @throws SonareError (`InvalidParameter`) on a kernel that is not an integer
64
+ * within the signed 32-bit range, or one the core rejects as even,
65
+ * non-positive or above its ceiling
66
+ */
67
+ export function hpss(request: HpssRequest): HpssResult;
68
+ export function hpss(
69
+ samples: Float32Array,
70
+ sampleRate?: number,
71
+ kernelHarmonic?: number,
72
+ kernelPercussive?: number,
73
+ nFft?: number,
74
+ hopLength?: number,
75
+ hardMask?: boolean,
76
+ ): HpssResult;
77
+ export function hpss(
78
+ samples: Float32Array | HpssRequest,
79
+ sampleRate = 22050,
80
+ kernelHarmonic = 31,
81
+ kernelPercussive = 31,
82
+ nFft?: number,
83
+ hopLength?: number,
84
+ hardMask?: boolean,
85
+ ): HpssResult {
86
+ const request =
87
+ samples instanceof Float32Array
88
+ ? { samples, sampleRate, kernelHarmonic, kernelPercussive, nFft, hopLength, hardMask }
89
+ : samples;
90
+ const fftOptions = resolveFftOptions('hpss', request.nFft, request.hopLength);
91
+ const resolvedHardMask = resolveHardMask(request.hardMask, 'hpss');
92
+ const resolvedKernelHarmonic = request.kernelHarmonic ?? 31;
93
+ const resolvedKernelPercussive = request.kernelPercussive ?? 31;
94
+ assertHpssKernels('hpss', resolvedKernelHarmonic, resolvedKernelPercussive);
95
+ return requireModule().hpssEx(
96
+ request.samples,
97
+ request.sampleRate ?? 22050,
98
+ resolvedKernelHarmonic,
99
+ resolvedKernelPercussive,
100
+ fftOptions.nFft,
101
+ fftOptions.hopLength,
102
+ resolvedHardMask,
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Extract harmonic component from audio.
108
+ *
109
+ * @param samples - Audio samples (mono, float32)
110
+ * @param sampleRate - Sample rate in Hz
111
+ * @returns Harmonic component
112
+ */
113
+ export function harmonic(request: HarmonicRequest): Float32Array;
114
+ export function harmonic(
115
+ samples: Float32Array,
116
+ sampleRate?: number,
117
+ options?: ValidateOptions,
118
+ ): Float32Array;
119
+ export function harmonic(
120
+ samples: Float32Array | HarmonicRequest,
121
+ sampleRate = 22050,
122
+ options: ValidateOptions = {},
123
+ ): Float32Array {
124
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
125
+ assertSamples('harmonic', request.samples, request.validate !== false);
126
+ return requireModule().harmonic(request.samples, request.sampleRate ?? 22050);
127
+ }
128
+
129
+ /**
130
+ * Extract percussive component from audio.
131
+ *
132
+ * @param samples - Audio samples (mono, float32)
133
+ * @param sampleRate - Sample rate in Hz
134
+ * @returns Percussive component
135
+ */
136
+ export function percussive(request: PercussiveRequest): Float32Array;
137
+ export function percussive(
138
+ samples: Float32Array,
139
+ sampleRate?: number,
140
+ options?: ValidateOptions,
141
+ ): Float32Array;
142
+ export function percussive(
143
+ samples: Float32Array | PercussiveRequest,
144
+ sampleRate = 22050,
145
+ options: ValidateOptions = {},
146
+ ): Float32Array {
147
+ const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
148
+ assertSamples('percussive', request.samples, request.validate !== false);
149
+ return requireModule().percussive(request.samples, request.sampleRate ?? 22050);
150
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Region-based spectral editing: time x frequency rectangles applied over an
3
+ * STFT and resynthesized.
4
+ */
5
+
6
+ import { getSonareModule } from './module_state';
7
+ import type { SpectralEditOptions, SpectralRegionOp } from './public_types';
8
+ import type { ValidateOptions } from './validation';
9
+ import { assertSampleRate, assertSamples } from './validation';
10
+
11
+ function requireModule() {
12
+ return getSonareModule();
13
+ }
14
+
15
+ export interface SpectralEditRequest extends SpectralEditOptions, ValidateOptions {
16
+ samples: Float32Array;
17
+ sampleRate: number;
18
+ ops?: SpectralRegionOp[];
19
+ }
20
+
21
+ /**
22
+ * Apply region-based spectral edits (gain/attenuate/mute/heal) to mono audio.
23
+ *
24
+ * Each op is a time x frequency rectangle applied in array order over a single
25
+ * STFT buffer, so a later op observes the result of earlier ops. The output has
26
+ * the same length and sample rate as the input; an empty `ops` list is an
27
+ * identity transform (within the iSTFT's own tolerance).
28
+ *
29
+ * @param samples - Audio samples (mono, float32)
30
+ * @param sampleRate - Sample rate in Hz
31
+ * @param ops - Region edit ops applied in order ({@link SpectralRegionOp})
32
+ * @param options - STFT + heal configuration ({@link SpectralEditOptions})
33
+ * @returns Edited audio
34
+ */
35
+ export function spectralEdit(request: SpectralEditRequest): Float32Array;
36
+ export function spectralEdit(
37
+ samples: Float32Array,
38
+ sampleRate: number,
39
+ ops?: SpectralRegionOp[],
40
+ options?: SpectralEditOptions & ValidateOptions,
41
+ ): Float32Array;
42
+ export function spectralEdit(
43
+ samples: Float32Array | SpectralEditRequest,
44
+ sampleRate?: number,
45
+ ops: SpectralRegionOp[] = [],
46
+ options: SpectralEditOptions & ValidateOptions = {},
47
+ ): Float32Array {
48
+ const request: SpectralEditRequest =
49
+ samples instanceof Float32Array
50
+ ? { samples, sampleRate: sampleRate as number, ops, ...options }
51
+ : samples;
52
+ assertSamples('spectralEdit', request.samples, request.validate !== false);
53
+ assertSampleRate('spectralEdit', request.sampleRate);
54
+ return requireModule().spectralEdit(
55
+ request.samples,
56
+ request.sampleRate,
57
+ request.ops ?? [],
58
+ request as unknown as Record<string, unknown>,
59
+ );
60
+ }