@libraz/libsonare 1.7.1 → 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 -170
  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 -5837
  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 -7329
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +3816 -1359
  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 -2142
  213. package/dist/worklet.d.ts.map +1 -0
  214. package/dist/worklet.js +2889 -466
  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 +285 -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 +64 -24
  255. package/src/project_class.ts +502 -29
  256. package/src/project_internal.ts +151 -42
  257. package/src/project_synth.ts +67 -1
  258. package/src/project_types.ts +302 -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 +750 -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 +1125 -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 +212 -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 +85 -28
  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 +307 -71
  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 +239 -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 +22 -0
  302. package/src/effects_transform.ts +0 -718
  303. package/src/mastering_repair.ts +0 -273
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Time and pitch transforms over a whole buffer: stretching, shifting, and
3
+ * correction onto a target pitch.
4
+ */
5
+
6
+ import { toVoicedFloat32 } from './_effects_common';
7
+ import { resolveFftOptions } from './_fft_options';
8
+ import { getSonareModule } from './module_state';
9
+ import type { PitchCorrectOptions, VoicedFlags } from './public_types';
10
+ import type { ValidateOptions } from './validation';
11
+ import { assertFiniteScalar, assertSamples } from './validation';
12
+
13
+ function requireModule() {
14
+ return getSonareModule();
15
+ }
16
+
17
+ export interface TimeStretchRequest extends ValidateOptions {
18
+ samples: Float32Array;
19
+ sampleRate?: number;
20
+ rate: number;
21
+ nFft?: number;
22
+ hopLength?: number;
23
+ }
24
+
25
+ export interface PitchShiftRequest extends ValidateOptions {
26
+ samples: Float32Array;
27
+ sampleRate?: number;
28
+ semitones: number;
29
+ nFft?: number;
30
+ hopLength?: number;
31
+ }
32
+
33
+ export interface PitchCorrectToMidiRequest extends ValidateOptions {
34
+ samples: Float32Array;
35
+ sampleRate?: number;
36
+ currentMidi?: number;
37
+ targetMidi?: number;
38
+ }
39
+
40
+ export interface PitchCorrectToMidiTimevaryingRequest extends ValidateOptions {
41
+ samples: Float32Array;
42
+ f0Hz: Float32Array;
43
+ targetMidi: number;
44
+ sampleRate?: number;
45
+ hopLength?: number;
46
+ voiced?: VoicedFlags;
47
+ voicedProb?: Float32Array;
48
+ }
49
+
50
+ export interface PitchCorrectTimevaryingRequest extends PitchCorrectOptions {
51
+ samples: Float32Array;
52
+ f0Hz: Float32Array;
53
+ sampleRate?: number;
54
+ hopLength?: number;
55
+ }
56
+
57
+ /**
58
+ * Time-stretch audio without changing pitch.
59
+ *
60
+ * @param samples - Audio samples (mono, float32)
61
+ * @param sampleRate - Sample rate in Hz (default: 22050)
62
+ * @param rate - Time stretch rate (0.5 = double duration, 2.0 = half duration)
63
+ * @param nFft - FFT size: an even integer >= 2 (default 2048)
64
+ * @param hopLength - Hop in samples, in `(0, nFft / 2]` (default 512), so
65
+ * frames overlap by at least half a window
66
+ * @returns Time-stretched audio
67
+ */
68
+ export function timeStretch(request: TimeStretchRequest): Float32Array;
69
+ export function timeStretch(
70
+ samples: Float32Array,
71
+ sampleRate: number,
72
+ rate: number,
73
+ options?: ValidateOptions,
74
+ ): Float32Array;
75
+ export function timeStretch(
76
+ samples: Float32Array,
77
+ sampleRate: number,
78
+ rate: number,
79
+ nFft?: number,
80
+ hopLength?: number,
81
+ options?: ValidateOptions,
82
+ ): Float32Array;
83
+ export function timeStretch(
84
+ samples: Float32Array | TimeStretchRequest,
85
+ sampleRate?: number,
86
+ rate?: number,
87
+ nFftOrOptions?: number | ValidateOptions,
88
+ hopLength?: number,
89
+ options: ValidateOptions = {},
90
+ ): Float32Array {
91
+ if (
92
+ nFftOrOptions !== undefined &&
93
+ nFftOrOptions !== null &&
94
+ typeof nFftOrOptions !== 'number' &&
95
+ typeof nFftOrOptions !== 'object'
96
+ ) {
97
+ throw new TypeError('timeStretch: nFft must be an integer or options object');
98
+ }
99
+ if (nFftOrOptions === null) {
100
+ throw new TypeError('timeStretch: nFft must be an integer or options object');
101
+ }
102
+ const positionalOptions =
103
+ typeof nFftOrOptions === 'object' && nFftOrOptions !== null ? nFftOrOptions : options;
104
+ const positionalNFft = typeof nFftOrOptions === 'number' ? nFftOrOptions : undefined;
105
+ const request: TimeStretchRequest =
106
+ samples instanceof Float32Array
107
+ ? {
108
+ samples,
109
+ sampleRate,
110
+ rate: rate as number,
111
+ nFft: positionalNFft,
112
+ hopLength,
113
+ ...positionalOptions,
114
+ }
115
+ : samples;
116
+ assertSamples('timeStretch', request.samples, request.validate !== false);
117
+ // Matches the addon, which refuses a non-finite rate here rather than letting
118
+ // the core answer it. Does NOT cover a finite value too wide for a float:
119
+ // Number.isFinite(1e39) is true and the demotion to the f32 parameter makes it
120
+ // an infinity, which only the binding-side narrowing can see.
121
+ assertFiniteScalar('timeStretch', request.rate as number, 'rate');
122
+ const fftOptions = resolveFftOptions('timeStretch', request.nFft, request.hopLength);
123
+ return requireModule().timeStretchEx(
124
+ request.samples,
125
+ request.sampleRate ?? 22050,
126
+ request.rate,
127
+ fftOptions.nFft,
128
+ fftOptions.hopLength,
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Pitch-shift audio without changing duration.
134
+ *
135
+ * @param samples - Audio samples (mono, float32)
136
+ * @param sampleRate - Sample rate in Hz (default: 22050)
137
+ * @param semitones - Pitch shift in semitones (+12 = one octave up, -12 = one octave down)
138
+ * @param nFft - FFT size: an even integer >= 2 (default 2048)
139
+ * @param hopLength - Hop in samples, in `(0, nFft / 2]` (default 512), so
140
+ * frames overlap by at least half a window
141
+ * @returns Pitch-shifted audio
142
+ */
143
+ export function pitchShift(request: PitchShiftRequest): Float32Array;
144
+ export function pitchShift(
145
+ samples: Float32Array,
146
+ sampleRate: number,
147
+ semitones: number,
148
+ options?: ValidateOptions,
149
+ ): Float32Array;
150
+ export function pitchShift(
151
+ samples: Float32Array,
152
+ sampleRate: number,
153
+ semitones: number,
154
+ nFft?: number,
155
+ hopLength?: number,
156
+ options?: ValidateOptions,
157
+ ): Float32Array;
158
+ export function pitchShift(
159
+ samples: Float32Array | PitchShiftRequest,
160
+ sampleRate?: number,
161
+ semitones?: number,
162
+ nFftOrOptions?: number | ValidateOptions,
163
+ hopLength?: number,
164
+ options: ValidateOptions = {},
165
+ ): Float32Array {
166
+ if (
167
+ nFftOrOptions !== undefined &&
168
+ nFftOrOptions !== null &&
169
+ typeof nFftOrOptions !== 'number' &&
170
+ typeof nFftOrOptions !== 'object'
171
+ ) {
172
+ throw new TypeError('pitchShift: nFft must be an integer or options object');
173
+ }
174
+ if (nFftOrOptions === null) {
175
+ throw new TypeError('pitchShift: nFft must be an integer or options object');
176
+ }
177
+ const positionalOptions =
178
+ typeof nFftOrOptions === 'object' && nFftOrOptions !== null ? nFftOrOptions : options;
179
+ const positionalNFft = typeof nFftOrOptions === 'number' ? nFftOrOptions : undefined;
180
+ const request: PitchShiftRequest =
181
+ samples instanceof Float32Array
182
+ ? {
183
+ samples,
184
+ sampleRate,
185
+ semitones: semitones as number,
186
+ nFft: positionalNFft,
187
+ hopLength,
188
+ ...positionalOptions,
189
+ }
190
+ : samples;
191
+ assertSamples('pitchShift', request.samples, request.validate !== false);
192
+ // See timeStretch above for what this does and does not cover.
193
+ assertFiniteScalar('pitchShift', request.semitones as number, 'semitones');
194
+ const fftOptions = resolveFftOptions('pitchShift', request.nFft, request.hopLength);
195
+ return requireModule().pitchShiftEx(
196
+ request.samples,
197
+ request.sampleRate ?? 22050,
198
+ request.semitones,
199
+ fftOptions.nFft,
200
+ fftOptions.hopLength,
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Pitch-correct audio from a current MIDI note to a target MIDI note.
206
+ *
207
+ * Applies one constant, immediate transpose with no retune glide and preserves
208
+ * the input buffer length. The whole interval is applied however large it is:
209
+ * both endpoints are validated to [0, 127], so a two-octave move such as
210
+ * C3 -> C5 transposes by the full 24 semitones. Use
211
+ * {@link pitchCorrectToMidiTimevarying} for a caller-supplied pitch contour.
212
+ *
213
+ * @param samples - Audio samples (mono, float32)
214
+ * @param sampleRate - Sample rate in Hz
215
+ * @param currentMidi - Detected/current MIDI note number
216
+ * @param targetMidi - Desired MIDI note number
217
+ * @returns Pitch-corrected audio
218
+ */
219
+ export function pitchCorrectToMidi(request: PitchCorrectToMidiRequest): Float32Array;
220
+ export function pitchCorrectToMidi(
221
+ samples: Float32Array,
222
+ sampleRate?: number,
223
+ currentMidi?: number,
224
+ targetMidi?: number,
225
+ options?: ValidateOptions,
226
+ ): Float32Array;
227
+ export function pitchCorrectToMidi(
228
+ samples: Float32Array | PitchCorrectToMidiRequest,
229
+ sampleRate = 22050,
230
+ currentMidi = 69.0,
231
+ targetMidi = 69.0,
232
+ options: ValidateOptions = {},
233
+ ): Float32Array {
234
+ const request =
235
+ samples instanceof Float32Array
236
+ ? { samples, sampleRate, currentMidi, targetMidi, ...options }
237
+ : samples;
238
+ assertSamples('pitchCorrectToMidi', request.samples, request.validate !== false);
239
+ return requireModule().pitchCorrectToMidi(
240
+ request.samples,
241
+ request.sampleRate ?? 22050,
242
+ request.currentMidi ?? 69.0,
243
+ request.targetMidi ?? 69.0,
244
+ );
245
+ }
246
+
247
+ /**
248
+ * Contour-following ("time-varying") pitch correction toward a MIDI target.
249
+ *
250
+ * Unlike {@link pitchCorrectToMidi} (a single constant transpose), this follows
251
+ * the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
252
+ * toward `targetMidi`, so vibrato/drift in the source is tracked rather than
253
+ * flattened. `voiced` (truthy = voiced) and `voicedProb` ([0,1]) are optional;
254
+ * omitting them treats every frame as voiced. An `f0Hz` NaN is accepted only
255
+ * when the corresponding `voiced` entry is falsy, matching pYIN output. The
256
+ * `voicedFlag` / `voicedProb` arrays of a {@link PitchResult} can be passed
257
+ * through directly.
258
+ *
259
+ * @param samples - Audio samples (mono, float32)
260
+ * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
261
+ * @param targetMidi - Desired MIDI note number
262
+ * @param sampleRate - Sample rate in Hz
263
+ * @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
264
+ * @param voiced - Optional per-frame voiced flags (truthy = voiced)
265
+ * @param voicedProb - Optional per-frame voicing probability in [0, 1]
266
+ * @returns Pitch-corrected audio
267
+ */
268
+ export function pitchCorrectToMidiTimevarying(
269
+ request: PitchCorrectToMidiTimevaryingRequest,
270
+ ): Float32Array;
271
+ export function pitchCorrectToMidiTimevarying(
272
+ samples: Float32Array,
273
+ f0Hz: Float32Array,
274
+ targetMidi: number,
275
+ sampleRate?: number,
276
+ hopLength?: number,
277
+ voiced?: VoicedFlags,
278
+ voicedProb?: Float32Array,
279
+ options?: ValidateOptions,
280
+ ): Float32Array;
281
+ export function pitchCorrectToMidiTimevarying(
282
+ samples: Float32Array | PitchCorrectToMidiTimevaryingRequest,
283
+ f0Hz?: Float32Array,
284
+ targetMidi?: number,
285
+ sampleRate = 22050,
286
+ hopLength = 512,
287
+ voiced?: VoicedFlags,
288
+ voicedProb?: Float32Array,
289
+ options: ValidateOptions = {},
290
+ ): Float32Array {
291
+ const request: PitchCorrectToMidiTimevaryingRequest =
292
+ samples instanceof Float32Array
293
+ ? {
294
+ samples,
295
+ f0Hz: f0Hz as Float32Array,
296
+ targetMidi: targetMidi as number,
297
+ sampleRate,
298
+ hopLength,
299
+ voiced,
300
+ voicedProb,
301
+ ...options,
302
+ }
303
+ : samples;
304
+ assertSamples('pitchCorrectToMidiTimevarying', request.samples, request.validate !== false);
305
+ if (request.voiced && request.voiced.length !== request.f0Hz.length) {
306
+ throw new RangeError('pitchCorrectToMidiTimevarying: voiced length must match f0Hz length');
307
+ }
308
+ if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
309
+ throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
310
+ }
311
+ const voicedF32 = request.voiced ? toVoicedFloat32(request.voiced) : undefined;
312
+ return requireModule().pitchCorrectToMidiTimevarying(
313
+ request.samples,
314
+ request.sampleRate ?? 22050,
315
+ request.f0Hz,
316
+ request.targetMidi,
317
+ request.hopLength ?? 512,
318
+ voicedF32,
319
+ request.voicedProb,
320
+ );
321
+ }
322
+
323
+ /**
324
+ * Contour-following pitch correction toward a fixed MIDI note OR a musical
325
+ * scale, with tunable retune strength and vibrato preservation.
326
+ *
327
+ * Generalises {@link pitchCorrectToMidiTimevarying}: the same caller-supplied
328
+ * per-frame `f0Hz` contour drives correction, but `options.mode` selects between
329
+ * a fixed-MIDI target (`'midi'`, default) and scale quantisation (`'scale'`),
330
+ * and the retune knobs shape natural-vs-robotic correction. An `f0Hz` NaN is
331
+ * accepted only for a frame marked unvoiced.
332
+ *
333
+ * @param samples - Audio samples (mono, float32)
334
+ * @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
335
+ * @param sampleRate - Sample rate in Hz
336
+ * @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
337
+ * @param options - Target mode + retune knobs + optional voiced/voicedProb arrays
338
+ * @returns Pitch-corrected audio
339
+ */
340
+ export function pitchCorrectTimevarying(request: PitchCorrectTimevaryingRequest): Float32Array;
341
+ export function pitchCorrectTimevarying(
342
+ samples: Float32Array,
343
+ f0Hz: Float32Array,
344
+ sampleRate?: number,
345
+ hopLength?: number,
346
+ options?: PitchCorrectOptions,
347
+ ): Float32Array;
348
+ export function pitchCorrectTimevarying(
349
+ samples: Float32Array | PitchCorrectTimevaryingRequest,
350
+ f0Hz?: Float32Array,
351
+ sampleRate = 22050,
352
+ hopLength = 512,
353
+ options: PitchCorrectOptions = {},
354
+ ): Float32Array {
355
+ const request: PitchCorrectTimevaryingRequest =
356
+ samples instanceof Float32Array
357
+ ? { samples, f0Hz: f0Hz as Float32Array, sampleRate, hopLength, ...options }
358
+ : samples;
359
+ assertSamples('pitchCorrectTimevarying', request.samples, request.validate !== false);
360
+ if (request.voiced && request.voiced.length !== request.f0Hz.length) {
361
+ throw new RangeError('pitchCorrectTimevarying: voiced length must match f0Hz length');
362
+ }
363
+ if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
364
+ throw new RangeError('pitchCorrectTimevarying: voicedProb length must match f0Hz length');
365
+ }
366
+ const nativeOptions = {
367
+ ...request,
368
+ voiced: request.voiced ? toVoicedFloat32(request.voiced) : undefined,
369
+ };
370
+ return requireModule().pitchCorrectTimevarying(
371
+ request.samples,
372
+ request.sampleRate ?? 22050,
373
+ request.f0Hz,
374
+ request.hopLength ?? 512,
375
+ nativeOptions,
376
+ );
377
+ }
package/src/errors.ts CHANGED
@@ -13,6 +13,7 @@ export enum ErrorCode {
13
13
  NotSupported = 6,
14
14
  InvalidState = 7,
15
15
  Cancelled = 8,
16
+ EncodeFailed = 9,
16
17
  Unknown = 99,
17
18
  }
18
19
 
@@ -20,6 +21,10 @@ export enum ErrorCode {
20
21
  * Error thrown by libsonare on a native (C++) failure. Carries a numeric
21
22
  * {@link ErrorCode} `code` plus its canonical `codeName`, so callers can branch
22
23
  * on the cause instead of matching message text.
24
+ *
25
+ * Narrow a caught value with {@link isSonareError} or with `instanceof`; both
26
+ * accept the same values. The Node package exports the same class under the
27
+ * same name.
23
28
  */
24
29
  export class SonareError extends Error {
25
30
  /** Numeric error code, equal to an {@link ErrorCode} value. */
@@ -33,9 +38,26 @@ export class SonareError extends Error {
33
38
  this.code = code;
34
39
  this.codeName = codeName;
35
40
  }
41
+
42
+ /**
43
+ * Brand-based `instanceof`: an error that carries the shape narrows here even
44
+ * when it is not literally an instance of this class. That is not a
45
+ * hypothetical — an error posted from the analysis worker arrives as a
46
+ * structured clone with its prototype gone, which a prototype-based
47
+ * `instanceof` would silently miss. Delegates to {@link isSonareError} so the
48
+ * two never disagree.
49
+ */
50
+ static [Symbol.hasInstance](value: unknown): value is SonareError {
51
+ return isSonareError(value);
52
+ }
36
53
  }
37
54
 
38
- /** Type guard: whether a caught value is a libsonare {@link SonareError}. */
55
+ /**
56
+ * Type guard: whether a caught value is a libsonare {@link SonareError}.
57
+ *
58
+ * Duck-typed on purpose: a value that crossed a worker boundary has lost its
59
+ * prototype, so a prototype check would miss it.
60
+ */
39
61
  export function isSonareError(value: unknown): value is SonareError {
40
62
  return (
41
63
  value instanceof Error &&
@@ -104,6 +104,72 @@ export interface SilenceRequest {
104
104
  hopLength?: number;
105
105
  }
106
106
 
107
+ /** Canonical request form for the common-silence union of several signals. */
108
+ export interface SplitSilenceCommonRequest {
109
+ signals: Float32Array[];
110
+ topDb?: number;
111
+ frameLength?: number;
112
+ hopLength?: number;
113
+ }
114
+
115
+ /**
116
+ * Why {@link splitSilenceCommonWithReport} found the gaps it did.
117
+ *
118
+ * One interval covering everything is the answer to three different situations
119
+ * and the interval list cannot separate them: no take has a quiet moment at all,
120
+ * the takes each have one but not in the same place, or `topDb` was set too loose
121
+ * to see the ones they have.
122
+ *
123
+ * **Read {@link silenceCeilingDb} against the `topDb` that was passed**, which is
124
+ * the whole decision:
125
+ *
126
+ * - ceiling near 0 — a take is sounding continuously. No threshold helps, and a
127
+ * cut point has to come from somewhere other than silence.
128
+ * - ceiling below `topDb` — the threshold was too loose to see the quiet these
129
+ * takes do have. A `topDb` under the reported ceiling finds it.
130
+ * - ceiling at or above `topDb`, and still one interval — every take shows silence
131
+ * at this setting and they do not share any of it. That is the alignment case,
132
+ * and it is what {@link alignTakeToReference} is for.
133
+ *
134
+ * The figures come from the same RMS pass the intervals do, so they can never
135
+ * describe a different measurement.
136
+ */
137
+ export interface SilenceCommonReport {
138
+ /**
139
+ * The largest `topDb` at which EVERY signal still shows silence.
140
+ *
141
+ * A frame counts as silent when it sits at least `topDb` under its own signal's
142
+ * peak RMS, so each signal's deepest dip decides whether any threshold can find
143
+ * silence in it, and the union needs all of them quiet at once — hence the
144
+ * minimum across the signals. 0 for an all-silent signal, where the peak is 0
145
+ * and the ratio has no value; 120 is the floor the dB conversion clamps at,
146
+ * reported for a signal holding a zero-valued frame.
147
+ */
148
+ silenceCeilingDb: number;
149
+ /**
150
+ * How many intervals the most fragmented signal produced alone, counted before
151
+ * the union merges anything.
152
+ *
153
+ * A measure of shape rather than of cause: 1 is a take that sounds once and
154
+ * stops, so a take that is loud then silent counts 1 exactly as a take with no
155
+ * silence does. Use {@link silenceCeilingDb} to tell those apart; use these two
156
+ * to see whether any take has an interior gap at all (`>= 2`) and whether the
157
+ * takes differ in how broken up they are (`maxSignalIntervals !==
158
+ * minSignalIntervals`).
159
+ */
160
+ maxSignalIntervals: number;
161
+ /** How many intervals the least fragmented signal produced alone. */
162
+ minSignalIntervals: number;
163
+ }
164
+
165
+ /** Result of {@link splitSilenceCommonWithReport}. */
166
+ export interface SplitSilenceCommonWithReportResult {
167
+ /** Exactly what {@link splitSilenceCommon} returns for the same arguments. */
168
+ intervals: Int32Array;
169
+ /** Why those are the intervals. */
170
+ report: SilenceCommonReport;
171
+ }
172
+
107
173
  export interface FrameSignalRequest {
108
174
  samples: Float32Array;
109
175
  frameLength: number;
@@ -228,6 +294,12 @@ export interface PlpRequest {
228
294
  /**
229
295
  * Convert frequency in Hz to Mel scale.
230
296
  *
297
+ * A total function, matching the C ABI and librosa: a non-finite `hz`
298
+ * propagates rather than throwing, and a magnitude past the 32-bit float range
299
+ * saturates to an infinity the same way the C conversion does. Out-of-audio
300
+ * frequencies are not refused either — the mapping is defined over the whole
301
+ * real line.
302
+ *
231
303
  * @param hz - Frequency in Hz
232
304
  * @returns Mel frequency
233
305
  */
@@ -236,7 +308,8 @@ export function hzToMel(hz: number): number {
236
308
  }
237
309
 
238
310
  /**
239
- * Convert Mel scale to frequency in Hz.
311
+ * Convert Mel scale to frequency in Hz. Total over the same domain as
312
+ * {@link hzToMel}.
240
313
  *
241
314
  * @param mel - Mel frequency
242
315
  * @returns Frequency in Hz
@@ -248,6 +321,11 @@ export function melToHz(mel: number): number {
248
321
  /**
249
322
  * Convert frequency in Hz to MIDI note number.
250
323
  *
324
+ * Total, like {@link hzToMel}. A non-positive `hz` returns `-Infinity`, the log2
325
+ * limit, and {@link midiToHz} maps that back to 0. A NaN propagates, which is
326
+ * what makes a default `pitchPyin` track — whose unvoiced frames are NaN — safe
327
+ * to map through.
328
+ *
251
329
  * @param hz - Frequency in Hz
252
330
  * @returns MIDI note number (A4 = 440 Hz = 69)
253
331
  */
@@ -256,7 +334,8 @@ export function hzToMidi(hz: number): number {
256
334
  }
257
335
 
258
336
  /**
259
- * Convert MIDI note number to frequency in Hz.
337
+ * Convert MIDI note number to frequency in Hz. Total, like {@link hzToMel};
338
+ * `-Infinity` bottoms out at 0 rather than propagating its sign.
260
339
  *
261
340
  * @param midi - MIDI note number
262
341
  * @returns Frequency in Hz
@@ -268,6 +347,11 @@ export function midiToHz(midi: number): number {
268
347
  /**
269
348
  * Convert frequency in Hz to note name.
270
349
  *
350
+ * Every frequency with no note answers `"?"` rather than throwing: zero,
351
+ * negative, past the representable MIDI range, and non-finite alike. A default
352
+ * `pitchPyin` track fills unvoiced frames with NaN, so mapping one through this
353
+ * yields `"?"` at those frames.
354
+ *
271
355
  * @param hz - Frequency in Hz
272
356
  * @returns Note name (e.g., "A4", "C#5")
273
357
  */
@@ -427,6 +511,47 @@ export function splitSilence(
427
511
  return requireModule().splitSilence(samples, topDb, frameLength, hopLength);
428
512
  }
429
513
 
514
+ /**
515
+ * Lists the intervals where ANY of `signals` is sounding, so every gap
516
+ * between them is silent in all of them -- what several takes of one part
517
+ * share is their silence, not their sound.
518
+ *
519
+ * @returns The union of {@link splitSilence}'s per-signal intervals, merged
520
+ * where they touch. A single signal returns exactly what `splitSilence`
521
+ * does for it.
522
+ */
523
+ export function splitSilenceCommon(request: SplitSilenceCommonRequest): Int32Array {
524
+ return requireModule().splitSilenceCommon(
525
+ request.signals,
526
+ request.topDb ?? 60.0,
527
+ request.frameLength ?? 2048,
528
+ request.hopLength ?? 512,
529
+ );
530
+ }
531
+
532
+ /**
533
+ * {@link splitSilenceCommon} plus the report that says why those are the
534
+ * intervals.
535
+ *
536
+ * Identical intervals, identical refusals, identical defaults; the only
537
+ * difference is the second field. The plain entry point stays because a caller
538
+ * cutting takes has no use for the diagnosis, and one interval covering
539
+ * everything is the answer to three different situations the interval list cannot
540
+ * separate — see {@link SilenceCommonReport} for reading them apart.
541
+ *
542
+ * @returns The union intervals and the report measured on the same RMS pass
543
+ */
544
+ export function splitSilenceCommonWithReport(
545
+ request: SplitSilenceCommonRequest,
546
+ ): SplitSilenceCommonWithReportResult {
547
+ return requireModule().splitSilenceCommonWithReport(
548
+ request.signals,
549
+ request.topDb ?? 60.0,
550
+ request.frameLength ?? 2048,
551
+ request.hopLength ?? 512,
552
+ );
553
+ }
554
+
430
555
  export function frameSignal(request: FrameSignalRequest): WasmFrameResult;
431
556
  export function frameSignal(
432
557
  samples: Float32Array,