@libraz/libsonare 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -1
- package/dist/index.d.ts +1 -2842
- package/dist/index.js +3667 -1984
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +1 -1
- package/dist/sonare-rt.js +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +4816 -483
- package/dist/worklet.js +747 -440
- package/dist/worklet.js.map +1 -1
- package/package.json +2 -1
- package/src/analysis_helpers.ts +152 -0
- package/src/audio.ts +493 -0
- package/src/codes.ts +56 -0
- package/src/effects_mastering.ts +964 -0
- package/src/feature_core.ts +248 -0
- package/src/feature_music.ts +419 -0
- package/src/feature_pitch.ts +80 -0
- package/src/feature_resample.ts +21 -0
- package/src/feature_spectral.ts +330 -0
- package/src/feature_spectrogram.ts +454 -0
- package/src/features.ts +84 -0
- package/src/index.ts +341 -4963
- package/src/live_audio.ts +47 -0
- package/src/metering.ts +380 -0
- package/src/mixer.ts +523 -0
- package/src/module_state.ts +14 -0
- package/src/opfs_clip_pages.ts +203 -0
- package/src/project.ts +1614 -0
- package/src/public_types.ts +177 -2
- package/src/quick_analysis.ts +508 -0
- package/src/realtime_engine.ts +667 -0
- package/src/realtime_voice_changer.ts +275 -0
- package/src/scale.ts +42 -0
- package/src/sonare.js.d.ts +302 -4
- package/src/stream_analyzer.ts +275 -0
- package/src/stream_types.ts +26 -1
- package/src/streaming_mixing.ts +18 -0
- package/src/streaming_processors.ts +335 -0
- package/src/validation.ts +82 -0
- package/src/web_midi.ts +366 -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
|
+
}
|