@libraz/libsonare 1.2.3 → 1.3.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 (43) hide show
  1. package/README.md +38 -1
  2. package/dist/index.d.ts +1 -2842
  3. package/dist/index.js +3602 -1934
  4. package/dist/index.js.map +1 -1
  5. package/dist/sonare-rt-module.js +1 -1
  6. package/dist/sonare-rt.js +1 -1
  7. package/dist/sonare-rt.wasm +0 -0
  8. package/dist/sonare.js +1 -1
  9. package/dist/sonare.wasm +0 -0
  10. package/dist/worklet.d.ts +4816 -483
  11. package/dist/worklet.js +747 -440
  12. package/dist/worklet.js.map +1 -1
  13. package/package.json +2 -1
  14. package/src/analysis_helpers.ts +152 -0
  15. package/src/audio.ts +493 -0
  16. package/src/codes.ts +56 -0
  17. package/src/effects_mastering.ts +964 -0
  18. package/src/feature_core.ts +248 -0
  19. package/src/feature_music.ts +419 -0
  20. package/src/feature_pitch.ts +80 -0
  21. package/src/feature_resample.ts +21 -0
  22. package/src/feature_spectral.ts +330 -0
  23. package/src/feature_spectrogram.ts +454 -0
  24. package/src/features.ts +84 -0
  25. package/src/index.ts +341 -4963
  26. package/src/live_audio.ts +45 -0
  27. package/src/metering.ts +380 -0
  28. package/src/mixer.ts +523 -0
  29. package/src/module_state.ts +14 -0
  30. package/src/opfs_clip_pages.ts +188 -0
  31. package/src/project.ts +1614 -0
  32. package/src/public_types.ts +177 -2
  33. package/src/quick_analysis.ts +508 -0
  34. package/src/realtime_engine.ts +667 -0
  35. package/src/realtime_voice_changer.ts +275 -0
  36. package/src/scale.ts +42 -0
  37. package/src/sonare.js.d.ts +302 -4
  38. package/src/stream_analyzer.ts +275 -0
  39. package/src/stream_types.ts +26 -1
  40. package/src/streaming_mixing.ts +18 -0
  41. package/src/streaming_processors.ts +335 -0
  42. package/src/validation.ts +82 -0
  43. package/src/web_midi.ts +367 -0
@@ -0,0 +1,275 @@
1
+ import { getSonareModule } from './module_state';
2
+ import type { RealtimeVoiceChangerConfigInput, VoicePresetId } from './public_types';
3
+
4
+ /**
5
+ * Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} mono
6
+ * processing. The `input` / `output` `Float32Array`s are typed-memory views
7
+ * onto the WASM heap — write samples into `input`, call `process()`, then
8
+ * read from `output`. The views are owned by the {@link RealtimeVoiceChanger}
9
+ * and remain valid until `delete()` is called on it.
10
+ */
11
+ export interface RealtimeVoiceChangerMonoBuffer {
12
+ input: Float32Array;
13
+ output: Float32Array;
14
+ process: () => void;
15
+ }
16
+
17
+ /**
18
+ * Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} interleaved
19
+ * multi-channel processing. Layout is L0,R0,L1,R1,... for stereo. The views
20
+ * are owned by the {@link RealtimeVoiceChanger}.
21
+ */
22
+ export interface RealtimeVoiceChangerInterleavedBuffer {
23
+ input: Float32Array;
24
+ output: Float32Array;
25
+ channels: number;
26
+ process: () => void;
27
+ }
28
+
29
+ /**
30
+ * Zero-copy realtime buffer for {@link RealtimeVoiceChanger} planar stereo
31
+ * processing. Each entry in `channels` is a heap-backed `Float32Array` for one
32
+ * channel (matching AudioWorklet's native layout). Process happens in place:
33
+ * write samples into each channel view, call `process()`, then read back from
34
+ * the same views.
35
+ */
36
+ export interface RealtimeVoiceChangerPlanarBuffer {
37
+ channels: Float32Array[];
38
+ process: () => void;
39
+ }
40
+
41
+ // ============================================================================
42
+ // RealtimeVoiceChanger Class
43
+ // ============================================================================
44
+
45
+ export class RealtimeVoiceChanger {
46
+ private changer: import('./sonare.js').WasmRealtimeVoiceChanger;
47
+
48
+ constructor(config: RealtimeVoiceChangerConfigInput = 'neutral-monitor') {
49
+ const module = getSonareModule();
50
+ this.changer = module.createRealtimeVoiceChanger(config as Record<string, unknown> | string);
51
+ }
52
+
53
+ prepare(sampleRate: number, maxBlockSize = 128, channels = 1): void {
54
+ this.changer.prepare(sampleRate, maxBlockSize, channels);
55
+ }
56
+
57
+ reset(): void {
58
+ this.changer.reset();
59
+ }
60
+
61
+ setConfig(config: RealtimeVoiceChangerConfigInput): void {
62
+ this.changer.setConfig(config as Record<string, unknown> | string);
63
+ }
64
+
65
+ configJson(): string {
66
+ return this.changer.configJson();
67
+ }
68
+
69
+ latencySamples(): number {
70
+ return this.changer.latencySamples();
71
+ }
72
+
73
+ processMono(samples: Float32Array): Float32Array {
74
+ return this.changer.processMono(samples);
75
+ }
76
+
77
+ processMonoInto(samples: Float32Array, output: Float32Array): void {
78
+ this.changer.processMonoInto(samples, output);
79
+ }
80
+
81
+ processInterleaved(samples: Float32Array, channels: number): Float32Array {
82
+ return this.changer.processInterleaved(samples, channels);
83
+ }
84
+
85
+ processInterleavedInto(samples: Float32Array, channels: number, output: Float32Array): void {
86
+ this.changer.processInterleavedInto(samples, channels, output);
87
+ }
88
+
89
+ /**
90
+ * Acquire a typed-memory view onto the WASM heap for mono input.
91
+ *
92
+ * Write your input samples into the returned `Float32Array` directly (e.g.
93
+ * via `input.set(source)`); no copy crosses the JS↔C++ bridge until
94
+ * {@link processPreparedMono} is called. The view is owned by this
95
+ * RealtimeVoiceChanger and becomes invalid after {@link delete}; it may
96
+ * also be invalidated if you later call this method with a larger
97
+ * `numSamples` value (the underlying buffer may be reallocated).
98
+ */
99
+ getMonoInputBuffer(numSamples: number): Float32Array {
100
+ return this.changer.getMonoInputBuffer(numSamples);
101
+ }
102
+
103
+ /** Mono output view counterpart to {@link getMonoInputBuffer}. */
104
+ getMonoOutputBuffer(numSamples: number): Float32Array {
105
+ return this.changer.getMonoOutputBuffer(numSamples);
106
+ }
107
+
108
+ /**
109
+ * Process the previously-acquired mono input buffer in place. The output
110
+ * appears in the buffer returned by {@link getMonoOutputBuffer}. No JS↔C++
111
+ * sample-level crossings happen on this call — it just hands control to
112
+ * the underlying DSP on already-on-heap data.
113
+ */
114
+ processPreparedMono(numSamples: number): void {
115
+ this.changer.processPreparedMono(numSamples);
116
+ }
117
+
118
+ /** Interleaved input view (layout L0,R0,L1,R1,...). */
119
+ getInterleavedInputBuffer(numFrames: number, numChannels: number): Float32Array {
120
+ return this.changer.getInterleavedInputBuffer(numFrames, numChannels);
121
+ }
122
+
123
+ /** Interleaved output view counterpart. */
124
+ getInterleavedOutputBuffer(numFrames: number, numChannels: number): Float32Array {
125
+ return this.changer.getInterleavedOutputBuffer(numFrames, numChannels);
126
+ }
127
+
128
+ /**
129
+ * Process the previously-acquired interleaved buffer in place. Output
130
+ * appears in the buffer returned by {@link getInterleavedOutputBuffer}.
131
+ */
132
+ processPreparedInterleaved(numFrames: number, numChannels: number): void {
133
+ this.changer.processPreparedInterleaved(numFrames, numChannels);
134
+ }
135
+
136
+ /**
137
+ * Planar-channel input/output view (one Float32Array per channel). Matches
138
+ * AudioWorklet's native layout; processing happens in place.
139
+ */
140
+ getPlanarChannelBuffer(channel: number, numFrames: number): Float32Array {
141
+ return this.changer.getPlanarChannelBuffer(channel, numFrames);
142
+ }
143
+
144
+ /**
145
+ * Process the previously-acquired planar channel buffers in place. Each
146
+ * channel must have been obtained from {@link getPlanarChannelBuffer}
147
+ * with the same `numFrames`. Output replaces input in the same buffers.
148
+ */
149
+ processPreparedPlanar(numFrames: number): void {
150
+ this.changer.processPreparedPlanar(numFrames);
151
+ }
152
+
153
+ /**
154
+ * Convenience factory for the mono zero-copy path: returns the input/output
155
+ * heap views plus a `process()` thunk wired to the same `numSamples`. The
156
+ * views are reused across calls and become invalid after {@link delete}.
157
+ */
158
+ createRealtimeMonoBuffer(numSamples: number): RealtimeVoiceChangerMonoBuffer {
159
+ let input = this.getMonoInputBuffer(numSamples);
160
+ let output = this.getMonoOutputBuffer(numSamples);
161
+ // The cached heap views can detach if WASM linear memory grows (the embind
162
+ // module is built ALLOW_MEMORY_GROWTH). Re-acquire them if detached
163
+ // (byteLength === 0) before use, mirroring the worklet RT path. In the
164
+ // common no-growth case this is a cheap branch with no allocation.
165
+ const reacquireIfDetached = (): void => {
166
+ if (input.byteLength === 0 || output.byteLength === 0) {
167
+ input = this.getMonoInputBuffer(numSamples);
168
+ output = this.getMonoOutputBuffer(numSamples);
169
+ }
170
+ };
171
+ return {
172
+ get input(): Float32Array {
173
+ reacquireIfDetached();
174
+ return input;
175
+ },
176
+ get output(): Float32Array {
177
+ reacquireIfDetached();
178
+ return output;
179
+ },
180
+ process: () => {
181
+ reacquireIfDetached();
182
+ this.processPreparedMono(numSamples);
183
+ },
184
+ };
185
+ }
186
+
187
+ /** Same as {@link createRealtimeMonoBuffer} but for interleaved I/O. */
188
+ createRealtimeInterleavedBuffer(
189
+ numFrames: number,
190
+ numChannels: number,
191
+ ): RealtimeVoiceChangerInterleavedBuffer {
192
+ let input = this.getInterleavedInputBuffer(numFrames, numChannels);
193
+ let output = this.getInterleavedOutputBuffer(numFrames, numChannels);
194
+ // Re-acquire detached views after WASM memory growth (see
195
+ // createRealtimeMonoBuffer for rationale).
196
+ const reacquireIfDetached = (): void => {
197
+ if (input.byteLength === 0 || output.byteLength === 0) {
198
+ input = this.getInterleavedInputBuffer(numFrames, numChannels);
199
+ output = this.getInterleavedOutputBuffer(numFrames, numChannels);
200
+ }
201
+ };
202
+ return {
203
+ get input(): Float32Array {
204
+ reacquireIfDetached();
205
+ return input;
206
+ },
207
+ get output(): Float32Array {
208
+ reacquireIfDetached();
209
+ return output;
210
+ },
211
+ channels: numChannels,
212
+ process: () => {
213
+ reacquireIfDetached();
214
+ this.processPreparedInterleaved(numFrames, numChannels);
215
+ },
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Convenience factory for the planar zero-copy path. Acquires one
221
+ * heap-backed Float32Array per channel and returns a `process()` thunk
222
+ * wired to the same `numFrames`. Buffers are reused across calls and
223
+ * become invalid after {@link delete}.
224
+ */
225
+ createRealtimePlanarBuffer(
226
+ numFrames: number,
227
+ numChannels: number,
228
+ ): RealtimeVoiceChangerPlanarBuffer {
229
+ let channels: Float32Array[] = [];
230
+ const acquire = (): void => {
231
+ channels = [];
232
+ for (let ch = 0; ch < numChannels; ch++) {
233
+ channels.push(this.getPlanarChannelBuffer(ch, numFrames));
234
+ }
235
+ };
236
+ acquire();
237
+ // Re-acquire detached views after WASM memory growth (see
238
+ // createRealtimeMonoBuffer for rationale).
239
+ const reacquireIfDetached = (): void => {
240
+ if ((channels[0]?.byteLength ?? 0) === 0) {
241
+ acquire();
242
+ }
243
+ };
244
+ return {
245
+ get channels(): Float32Array[] {
246
+ reacquireIfDetached();
247
+ return channels;
248
+ },
249
+ process: () => {
250
+ reacquireIfDetached();
251
+ this.processPreparedPlanar(numFrames);
252
+ },
253
+ };
254
+ }
255
+
256
+ delete(): void {
257
+ this.changer.delete();
258
+ }
259
+ }
260
+
261
+ export function realtimeVoiceChangerPresetNames(): VoicePresetId[] {
262
+ return getSonareModule().realtimeVoiceChangerPresetNames() as VoicePresetId[];
263
+ }
264
+
265
+ export function realtimeVoiceChangerPresetJson(name: VoicePresetId): string {
266
+ return getSonareModule().realtimeVoiceChangerPresetJson(name);
267
+ }
268
+
269
+ export function validateRealtimeVoiceChangerPresetJson(json: string): {
270
+ ok: boolean;
271
+ normalizedJson?: string;
272
+ error?: string;
273
+ } {
274
+ return getSonareModule().validateRealtimeVoiceChangerPresetJson(json);
275
+ }
package/src/scale.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { getSonareModule } from './module_state';
2
+ import { assertFiniteScalar } from './validation';
3
+
4
+ // ============================================================================
5
+ // Editing — 12-TET scale quantizer
6
+ // ============================================================================
7
+
8
+ /**
9
+ * Snap a MIDI value to the nearest pitch class enabled by `modeMask`.
10
+ *
11
+ * `modeMask` is a 12-bit mask. For natural C major use `0b101010110101`.
12
+ * `referenceMidi` defaults to A4 (69) when passed as 0.
13
+ */
14
+ export function scaleQuantizeMidi(
15
+ root: number,
16
+ modeMask: number,
17
+ midi: number,
18
+ referenceMidi = 0,
19
+ ): number {
20
+ assertFiniteScalar('scaleQuantizeMidi', midi, 'midi');
21
+ assertFiniteScalar('scaleQuantizeMidi', referenceMidi, 'referenceMidi');
22
+ return getSonareModule().scaleQuantizeMidi(root, modeMask, midi, referenceMidi);
23
+ }
24
+
25
+ export function scaleCorrectionSemitones(
26
+ root: number,
27
+ modeMask: number,
28
+ midi: number,
29
+ referenceMidi = 0,
30
+ ): number {
31
+ assertFiniteScalar('scaleCorrectionSemitones', midi, 'midi');
32
+ assertFiniteScalar('scaleCorrectionSemitones', referenceMidi, 'referenceMidi');
33
+ return getSonareModule().scaleCorrectionSemitones(root, modeMask, midi, referenceMidi);
34
+ }
35
+
36
+ export function scalePitchClassEnabled(
37
+ root: number,
38
+ modeMask: number,
39
+ pitchClass: number,
40
+ ): boolean {
41
+ return getSonareModule().scalePitchClassEnabled(root, modeMask, pitchClass);
42
+ }