@libraz/libsonare 1.5.0 → 1.5.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 +1 -1
- package/dist/index.d.ts +85 -53
- package/dist/index.js +86 -25
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.js +7 -0
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/clip_page_streamer.ts +63 -15
- package/src/effects_voice_change.ts +7 -7
- package/src/feature_music.ts +5 -2
- package/src/index.ts +12 -0
- package/src/mixer.ts +8 -0
- package/src/project_class.ts +17 -7
- package/src/project_internal.ts +1 -0
- package/src/public_types_realtime.ts +39 -39
- package/src/sonare.js.d.ts +45 -38
- package/src/stream_analyzer.ts +3 -0
- package/src/stream_types.ts +4 -0
package/package.json
CHANGED
|
@@ -47,8 +47,12 @@ export interface ClipPageStreamerOptions {
|
|
|
47
47
|
interface SourceState {
|
|
48
48
|
source: ClipPageStreamSource;
|
|
49
49
|
lastPage: number;
|
|
50
|
-
/**
|
|
51
|
-
|
|
50
|
+
/** Playback-window generation; advanced on reset or backward discontinuity. */
|
|
51
|
+
generation: number;
|
|
52
|
+
/** Most recently serviced playback frontier, or null before the first miss. */
|
|
53
|
+
lastFrontier: number | null;
|
|
54
|
+
/** Resident page -> generation, so in-flight stale fetches cannot alias. */
|
|
55
|
+
resident: Map<number, number>;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
@@ -92,18 +96,40 @@ export class ClipPageStreamer {
|
|
|
92
96
|
throw new Error('pageFrames and numSamples must be positive');
|
|
93
97
|
}
|
|
94
98
|
const lastPage = Math.ceil(source.numSamples / source.pageFrames) - 1;
|
|
99
|
+
const previous = this.sources.get(source.clipId);
|
|
100
|
+
if (previous) {
|
|
101
|
+
this.resetState(previous);
|
|
102
|
+
}
|
|
95
103
|
this.sources.set(source.clipId, {
|
|
96
104
|
source,
|
|
97
105
|
lastPage,
|
|
98
|
-
|
|
106
|
+
generation: 0,
|
|
107
|
+
lastFrontier: null,
|
|
108
|
+
resident: new Map(Array.from(initialResidentPages, (page) => [page, 0])),
|
|
99
109
|
});
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
/** Stop tracking a clip. Does not close its binding (the caller owns that). */
|
|
103
113
|
removeSource(clipId: number): void {
|
|
114
|
+
const state = this.sources.get(clipId);
|
|
115
|
+
if (state) {
|
|
116
|
+
this.resetState(state);
|
|
117
|
+
}
|
|
104
118
|
this.sources.delete(clipId);
|
|
105
119
|
}
|
|
106
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Explicitly start a new playback generation after a host seek/loop. Resident
|
|
123
|
+
* pages are evicted and any older in-flight fetch is cleared when it settles.
|
|
124
|
+
* The next miss establishes the new bounded window.
|
|
125
|
+
*/
|
|
126
|
+
resetSource(clipId: number): void {
|
|
127
|
+
const state = this.sources.get(clipId);
|
|
128
|
+
if (state) {
|
|
129
|
+
this.resetState(state);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
107
133
|
/**
|
|
108
134
|
* Drain pending page-miss requests, fetch the missing pages plus their
|
|
109
135
|
* read-ahead window, and evict out-of-window pages. Resolves once this round's
|
|
@@ -113,9 +139,10 @@ export class ClipPageStreamer {
|
|
|
113
139
|
if (this.closed) {
|
|
114
140
|
return;
|
|
115
141
|
}
|
|
116
|
-
// Collapse this round's misses to the
|
|
117
|
-
//
|
|
118
|
-
//
|
|
142
|
+
// Collapse this round's misses to the latest observed frontier per clip.
|
|
143
|
+
// Queue order reflects playback order, so a backward seek/loop request that
|
|
144
|
+
// follows a stale high-page miss must win even though its page index is
|
|
145
|
+
// smaller. Multiple channel misses still collapse to one service window.
|
|
119
146
|
const frontiers = new Map<number, number>();
|
|
120
147
|
for (let drained = 0; drained < this.maxRequestsPerPump; ++drained) {
|
|
121
148
|
const request = this.engine.popClipPageRequest();
|
|
@@ -127,10 +154,7 @@ export class ClipPageStreamer {
|
|
|
127
154
|
continue;
|
|
128
155
|
}
|
|
129
156
|
const page = Math.floor(request.sample / state.source.pageFrames);
|
|
130
|
-
|
|
131
|
-
if (previous === undefined || page > previous) {
|
|
132
|
-
frontiers.set(request.clipId, page);
|
|
133
|
-
}
|
|
157
|
+
frontiers.set(request.clipId, page);
|
|
134
158
|
}
|
|
135
159
|
|
|
136
160
|
const fetches: Promise<unknown>[] = [];
|
|
@@ -151,18 +175,27 @@ export class ClipPageStreamer {
|
|
|
151
175
|
}
|
|
152
176
|
this.closed = true;
|
|
153
177
|
for (const state of this.sources.values()) {
|
|
178
|
+
this.resetState(state);
|
|
154
179
|
state.source.binding.close();
|
|
155
180
|
}
|
|
156
181
|
this.sources.clear();
|
|
157
182
|
}
|
|
158
183
|
|
|
159
184
|
private serviceFrontier(state: SourceState, frontier: number): Promise<unknown>[] {
|
|
185
|
+
// A lower frontier after a previously serviced high page is a seek/loop
|
|
186
|
+
// discontinuity. Advance generation before scheduling its new window so an
|
|
187
|
+
// older asynchronous fetch can never become resident in the new window.
|
|
188
|
+
if (state.lastFrontier !== null && frontier < state.lastFrontier) {
|
|
189
|
+
this.resetState(state);
|
|
190
|
+
}
|
|
191
|
+
state.lastFrontier = frontier;
|
|
192
|
+
const generation = state.generation;
|
|
160
193
|
const low = Math.max(0, frontier - this.retainBehindPages);
|
|
161
194
|
const high = Math.min(state.lastPage, frontier + this.readAheadPages);
|
|
162
195
|
|
|
163
196
|
// Evict pages outside the window first so a burst of fetches never exceeds
|
|
164
197
|
// the bound by transiently holding old pages alongside new ones.
|
|
165
|
-
for (const page of state.resident) {
|
|
198
|
+
for (const page of state.resident.keys()) {
|
|
166
199
|
if (page < low || page > high) {
|
|
167
200
|
state.source.binding.provider.clear(page);
|
|
168
201
|
state.resident.delete(page);
|
|
@@ -171,23 +204,29 @@ export class ClipPageStreamer {
|
|
|
171
204
|
|
|
172
205
|
const fetches: Promise<unknown>[] = [];
|
|
173
206
|
for (let page = low; page <= high; ++page) {
|
|
174
|
-
if (state.resident.
|
|
207
|
+
if (state.resident.get(page) === generation) {
|
|
175
208
|
continue;
|
|
176
209
|
}
|
|
177
210
|
// Mark resident eagerly so the same page is not fetched twice across
|
|
178
211
|
// overlapping windows; drop it again if the fetch reports a miss.
|
|
179
|
-
state.resident.
|
|
212
|
+
state.resident.set(page, generation);
|
|
180
213
|
const pageIndex = page;
|
|
181
214
|
fetches.push(
|
|
182
215
|
state.source.binding.supplyPage(pageIndex).then(
|
|
183
216
|
(ok) => {
|
|
184
|
-
if (
|
|
217
|
+
if (state.generation !== generation) {
|
|
218
|
+
// The fetch crossed a seek/reset boundary. supplyPage may have
|
|
219
|
+
// installed it after resetState's clear pass, so clear it again.
|
|
220
|
+
state.source.binding.provider.clear(pageIndex);
|
|
221
|
+
} else if (!ok && state.resident.get(pageIndex) === generation) {
|
|
185
222
|
state.resident.delete(pageIndex);
|
|
186
223
|
}
|
|
187
224
|
return ok;
|
|
188
225
|
},
|
|
189
226
|
(error) => {
|
|
190
|
-
state.resident.
|
|
227
|
+
if (state.resident.get(pageIndex) === generation) {
|
|
228
|
+
state.resident.delete(pageIndex);
|
|
229
|
+
}
|
|
191
230
|
throw error;
|
|
192
231
|
},
|
|
193
232
|
),
|
|
@@ -195,6 +234,15 @@ export class ClipPageStreamer {
|
|
|
195
234
|
}
|
|
196
235
|
return fetches;
|
|
197
236
|
}
|
|
237
|
+
|
|
238
|
+
private resetState(state: SourceState): void {
|
|
239
|
+
state.generation += 1;
|
|
240
|
+
state.lastFrontier = null;
|
|
241
|
+
for (const page of state.resident.keys()) {
|
|
242
|
+
state.source.binding.provider.clear(page);
|
|
243
|
+
}
|
|
244
|
+
state.resident.clear();
|
|
245
|
+
}
|
|
198
246
|
}
|
|
199
247
|
|
|
200
248
|
export interface OpfsClipStreamOptions extends OpfsClipPageProviderOptions {
|
|
@@ -40,9 +40,6 @@ export function voiceChange(
|
|
|
40
40
|
|
|
41
41
|
/** Options for the offline {@link voiceChangeRealtime} convenience wrapper. */
|
|
42
42
|
export interface VoiceChangeRealtimeOptions extends ValidateOptions {
|
|
43
|
-
sampleRate?: number;
|
|
44
|
-
/** Voice-changer preset id or full config object. */
|
|
45
|
-
preset?: RealtimeVoiceChangerConfigInput;
|
|
46
43
|
/** Channel count (1 = mono, 2 = interleaved stereo). */
|
|
47
44
|
channels?: 1 | 2;
|
|
48
45
|
/** Block size for the internal render loop (default 512). */
|
|
@@ -90,10 +87,16 @@ function latencyCompensatedVoiceChange(
|
|
|
90
87
|
* and Node `voiceChangeRealtime` convenience wrappers. For mono, `samples` is a
|
|
91
88
|
* plain mono buffer; for stereo, `samples` is interleaved (L0,R0,L1,R1,...).
|
|
92
89
|
*
|
|
90
|
+
* @param samples - Audio samples (mono, or interleaved stereo when channels=2)
|
|
91
|
+
* @param sampleRate - Sample rate in Hz (default 48000, matching Python/Node)
|
|
92
|
+
* @param preset - Voice-changer preset id or full config object
|
|
93
|
+
* @param options - Channel count and block size ({@link VoiceChangeRealtimeOptions})
|
|
93
94
|
* @returns The processed buffer (same layout/length as the input).
|
|
94
95
|
*/
|
|
95
96
|
export function voiceChangeRealtime(
|
|
96
97
|
samples: Float32Array,
|
|
98
|
+
sampleRate = 48000,
|
|
99
|
+
preset: RealtimeVoiceChangerConfigInput = 'neutral-monitor',
|
|
97
100
|
options: VoiceChangeRealtimeOptions = {},
|
|
98
101
|
): Float32Array {
|
|
99
102
|
assertSamples('voiceChangeRealtime', samples, options.validate !== false);
|
|
@@ -104,11 +107,8 @@ export function voiceChangeRealtime(
|
|
|
104
107
|
if (channels === 2 && samples.length % 2 !== 0) {
|
|
105
108
|
throw new Error('voiceChangeRealtime: stereo input length must be a multiple of 2.');
|
|
106
109
|
}
|
|
107
|
-
// 48000 matches the Python voice_change_realtime and Node voiceChangeRealtime
|
|
108
|
-
// convenience wrappers (and the RealtimeVoiceChanger default).
|
|
109
|
-
const sampleRate = options.sampleRate ?? 48000;
|
|
110
110
|
const blockSize = Math.max(1, Math.floor(options.blockSize ?? 512));
|
|
111
|
-
const changer = new RealtimeVoiceChanger(
|
|
111
|
+
const changer = new RealtimeVoiceChanger(preset);
|
|
112
112
|
try {
|
|
113
113
|
changer.prepare(sampleRate, blockSize, channels);
|
|
114
114
|
return latencyCompensatedVoiceChange(changer, samples, channels, blockSize);
|
package/src/feature_music.ts
CHANGED
|
@@ -366,19 +366,22 @@ export function fourierTempogram(
|
|
|
366
366
|
* @param winLength - Window length in frames (default: 384)
|
|
367
367
|
* @param sampleRate - Sample rate in Hz (default: 22050)
|
|
368
368
|
* @param hopLength - Hop length (default: 512)
|
|
369
|
-
* @
|
|
369
|
+
* @param factors - Lag ratios to evaluate. When omitted or empty, the library
|
|
370
|
+
* default {0.5, 1, 2, 3, 4} is used.
|
|
371
|
+
* @returns Tempogram ratio features (one value per factor)
|
|
370
372
|
*/
|
|
371
373
|
export function tempogramRatio(
|
|
372
374
|
tempogramData: Float32Array,
|
|
373
375
|
winLength = 384,
|
|
374
376
|
sampleRate = 22050,
|
|
375
377
|
hopLength = 512,
|
|
378
|
+
factors?: Float32Array | number[],
|
|
376
379
|
options: GuardedOptions = {},
|
|
377
380
|
): Float32Array {
|
|
378
381
|
assertSampleRate('tempogramRatio', sampleRate);
|
|
379
382
|
assertSamples('tempogramRatio', tempogramData, options.validate !== false, 'tempogramData');
|
|
380
383
|
validatePositiveIntegers('tempogramRatio', { winLength, hopLength });
|
|
381
|
-
return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength);
|
|
384
|
+
return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength, factors);
|
|
382
385
|
}
|
|
383
386
|
|
|
384
387
|
/**
|
package/src/index.ts
CHANGED
|
@@ -561,6 +561,18 @@ export function version(): string {
|
|
|
561
561
|
return module.version();
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Aggregate native ABI version: the per-subsystem ABI macros folded into one
|
|
566
|
+
* 32-bit value. It bumps whenever any flat C POD layout changes, so callers can
|
|
567
|
+
* detect an incompatible prebuilt binary. Matches the Node/Python `abiVersion()`.
|
|
568
|
+
*/
|
|
569
|
+
export function abiVersion(): number {
|
|
570
|
+
if (!module) {
|
|
571
|
+
throw new Error('Module not initialized. Call init() first.');
|
|
572
|
+
}
|
|
573
|
+
return module.abiVersion();
|
|
574
|
+
}
|
|
575
|
+
|
|
564
576
|
export function engineAbiVersion(): number {
|
|
565
577
|
if (!module) {
|
|
566
578
|
throw new Error('Module not initialized. Call init() first.');
|
package/src/mixer.ts
CHANGED
|
@@ -522,6 +522,14 @@ export class Mixer {
|
|
|
522
522
|
return this.mixer.tailSamples();
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Reported latency (samples) of the compiled mixer graph, for aligning
|
|
527
|
+
* dry/wet material. Lazily compiles the routing graph if the topology is dirty.
|
|
528
|
+
*/
|
|
529
|
+
latencySamples(): number {
|
|
530
|
+
return this.mixer.latencySamples();
|
|
531
|
+
}
|
|
532
|
+
|
|
525
533
|
/**
|
|
526
534
|
* Drain delayed / tail audio by processing a zero-input block of `numSamples`
|
|
527
535
|
* frames after the host stops feeding strip inputs. Returns the mixed stereo
|
package/src/project_class.ts
CHANGED
|
@@ -542,8 +542,10 @@ export class Project {
|
|
|
542
542
|
* built-in oscillator synth so a MIDI-only arrangement bounces to audible
|
|
543
543
|
* audio. Pass a {@link BuiltinSynthBinding} (or an array of them) to choose
|
|
544
544
|
* the patch and MIDI destination; omit it (or pass `{}`) for one
|
|
545
|
-
* default-destination sine patch.
|
|
546
|
-
*
|
|
545
|
+
* default-destination sine patch. Because the parameter defaults to `{}`,
|
|
546
|
+
* omission and explicit `undefined` both create that one default binding.
|
|
547
|
+
* Use an explicitly empty array `[]` (or runtime `null`) for zero bindings,
|
|
548
|
+
* so MIDI tracks render silently.
|
|
547
549
|
*
|
|
548
550
|
* Like {@link bounce}, omitting `totalFrames` auto-derives the render length
|
|
549
551
|
* from the arrangement plus the synth's release tail.
|
|
@@ -572,9 +574,10 @@ export class Project {
|
|
|
572
574
|
* string (`'saw-lead'` / `'va:saw-lead'`; see {@link synthPresetNames}), or
|
|
573
575
|
* an array of either; each object entry may carry a `destinationId` binding
|
|
574
576
|
* convenience (default 0), which is not part of the NativeSynth patch itself.
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
577
|
+
* Because the parameter defaults to `{}`, omission and explicit `undefined`
|
|
578
|
+
* both create one default binding. Use an explicitly empty array `[]` (or
|
|
579
|
+
* runtime `null`) for zero bindings. Unknown preset names throw.
|
|
580
|
+
* Deterministic for a fixed project + options + patch.
|
|
578
581
|
*/
|
|
579
582
|
bounceWithSynthInstrument(
|
|
580
583
|
instrument: SynthPatch | string | ReadonlyArray<SynthPatch | string> = {},
|
|
@@ -624,8 +627,10 @@ export class Project {
|
|
|
624
627
|
* honored. Programs the SoundFont does not cover — including bouncing with
|
|
625
628
|
* no SoundFont loaded at all — play through the built-in synthesizer GM
|
|
626
629
|
* fallback bank (the data-free floor; see {@link soundFontManifest} for the
|
|
627
|
-
* per-program backend).
|
|
628
|
-
* `
|
|
630
|
+
* per-program backend). Because the parameter defaults to `{}`, omission and
|
|
631
|
+
* explicit `undefined` both create one default binding. Use an explicitly
|
|
632
|
+
* empty array `[]` (or runtime `null`) for zero bindings, so MIDI tracks
|
|
633
|
+
* render silently.
|
|
629
634
|
*/
|
|
630
635
|
bounceWithSf2Instrument(
|
|
631
636
|
instrument: Sf2InstrumentConfig | ReadonlyArray<Sf2InstrumentConfig> = {},
|
|
@@ -815,6 +820,11 @@ export class Project {
|
|
|
815
820
|
return this.native.trackCount();
|
|
816
821
|
}
|
|
817
822
|
|
|
823
|
+
/** Number of clips in the project. */
|
|
824
|
+
clipCount(): number {
|
|
825
|
+
return this.native.clipCount();
|
|
826
|
+
}
|
|
827
|
+
|
|
818
828
|
/** Number of audio sources registered on the project. */
|
|
819
829
|
sourceCount(): number {
|
|
820
830
|
return this.native.sourceCount();
|
package/src/project_internal.ts
CHANGED
|
@@ -146,6 +146,7 @@ export interface WasmProject {
|
|
|
146
146
|
markerByIndex: (index: number) => ProjectMarker;
|
|
147
147
|
markerCount: () => number;
|
|
148
148
|
trackCount: () => number;
|
|
149
|
+
clipCount: () => number;
|
|
149
150
|
sourceCount: () => number;
|
|
150
151
|
tempoSegmentCount: () => number;
|
|
151
152
|
timeSignatureCount: () => number;
|
|
@@ -117,47 +117,47 @@ export interface RealtimeVoiceChangerPreset {
|
|
|
117
117
|
export type RealtimeVoiceChangerConfigInput = VoicePresetId | RealtimeVoiceChangerPreset;
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Flat (POD) realtime voice-changer configuration.
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* Flat (POD) realtime voice-changer configuration. Keys are camelCase to match
|
|
121
|
+
* the Node addon getter, so a preset config reads identically across both JS
|
|
122
|
+
* surfaces (the underlying C ABI / Python POD uses the snake_case equivalents).
|
|
123
123
|
*/
|
|
124
124
|
export interface RealtimeVoiceChangerPodConfig {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
125
|
+
inputGainDb: number;
|
|
126
|
+
outputGainDb: number;
|
|
127
|
+
wetMix: number;
|
|
128
|
+
retuneSemitones: number;
|
|
129
|
+
retuneMix: number;
|
|
130
|
+
retuneGrainSize: number;
|
|
131
|
+
formantFactor: number;
|
|
132
|
+
formantAmount: number;
|
|
133
|
+
formantBody: number;
|
|
134
|
+
formantBrightness: number;
|
|
135
|
+
formantNasal: number;
|
|
136
|
+
eqHighpassHz: number;
|
|
137
|
+
eqBodyDb: number;
|
|
138
|
+
eqPresenceDb: number;
|
|
139
|
+
eqAirDb: number;
|
|
140
|
+
gateThresholdDb: number;
|
|
141
|
+
gateAttackMs: number;
|
|
142
|
+
gateReleaseMs: number;
|
|
143
|
+
gateRangeDb: number;
|
|
144
|
+
compressorThresholdDb: number;
|
|
145
|
+
compressorRatio: number;
|
|
146
|
+
compressorAttackMs: number;
|
|
147
|
+
compressorReleaseMs: number;
|
|
148
|
+
compressorMakeupGainDb: number;
|
|
149
|
+
deesserFrequencyHz: number;
|
|
150
|
+
deesserThresholdDb: number;
|
|
151
|
+
deesserRatio: number;
|
|
152
|
+
deesserRangeDb: number;
|
|
153
|
+
reverbMix: number;
|
|
154
|
+
reverbTimeMs: number;
|
|
155
|
+
reverbDamping: number;
|
|
156
|
+
reverbSeed: number;
|
|
157
|
+
limiterCeilingDb: number;
|
|
158
|
+
limiterReleaseMs: number;
|
|
159
159
|
/** Non-zero enables the 4x-oversampled inter-sample-peak limiter (default enabled). */
|
|
160
|
-
|
|
160
|
+
limiterEnableIspLimiter: boolean;
|
|
161
161
|
/** True-peak ceiling in dBTP applied by the ISP limiter (default -1.0). */
|
|
162
|
-
|
|
162
|
+
limiterIspCeilingDbtp: number;
|
|
163
163
|
}
|
package/src/sonare.js.d.ts
CHANGED
|
@@ -26,45 +26,45 @@ export interface WasmKeyCandidateResult {
|
|
|
26
26
|
correlation: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Flat POD mirror of SonareRealtimeVoiceChangerConfig.
|
|
30
|
-
// C ABI / Python POD
|
|
29
|
+
// Flat POD mirror of SonareRealtimeVoiceChangerConfig. Keys are camelCase to
|
|
30
|
+
// match the Node addon getter (the underlying C ABI / Python POD is snake_case).
|
|
31
31
|
export interface WasmRealtimeVoiceChangerPodConfig {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
32
|
+
inputGainDb: number;
|
|
33
|
+
outputGainDb: number;
|
|
34
|
+
wetMix: number;
|
|
35
|
+
retuneSemitones: number;
|
|
36
|
+
retuneMix: number;
|
|
37
|
+
retuneGrainSize: number;
|
|
38
|
+
formantFactor: number;
|
|
39
|
+
formantAmount: number;
|
|
40
|
+
formantBody: number;
|
|
41
|
+
formantBrightness: number;
|
|
42
|
+
formantNasal: number;
|
|
43
|
+
eqHighpassHz: number;
|
|
44
|
+
eqBodyDb: number;
|
|
45
|
+
eqPresenceDb: number;
|
|
46
|
+
eqAirDb: number;
|
|
47
|
+
gateThresholdDb: number;
|
|
48
|
+
gateAttackMs: number;
|
|
49
|
+
gateReleaseMs: number;
|
|
50
|
+
gateRangeDb: number;
|
|
51
|
+
compressorThresholdDb: number;
|
|
52
|
+
compressorRatio: number;
|
|
53
|
+
compressorAttackMs: number;
|
|
54
|
+
compressorReleaseMs: number;
|
|
55
|
+
compressorMakeupGainDb: number;
|
|
56
|
+
deesserFrequencyHz: number;
|
|
57
|
+
deesserThresholdDb: number;
|
|
58
|
+
deesserRatio: number;
|
|
59
|
+
deesserRangeDb: number;
|
|
60
|
+
reverbMix: number;
|
|
61
|
+
reverbTimeMs: number;
|
|
62
|
+
reverbDamping: number;
|
|
63
|
+
reverbSeed: number;
|
|
64
|
+
limiterCeilingDb: number;
|
|
65
|
+
limiterReleaseMs: number;
|
|
66
|
+
limiterEnableIspLimiter: boolean;
|
|
67
|
+
limiterIspCeilingDbtp: number;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export interface WasmBeatResult {
|
|
@@ -194,6 +194,7 @@ export interface WasmStreamConfigDefaults {
|
|
|
194
194
|
computeSpectral: boolean;
|
|
195
195
|
emitEveryNFrames: number;
|
|
196
196
|
magnitudeDownsample: number;
|
|
197
|
+
maxPendingFrames: number;
|
|
197
198
|
keyUpdateIntervalSec: number;
|
|
198
199
|
bpmUpdateIntervalSec: number;
|
|
199
200
|
window: number;
|
|
@@ -1156,6 +1157,7 @@ export interface SonareModule {
|
|
|
1156
1157
|
detectKeyCandidates: (samples: Float32Array, sampleRate: number) => WasmKeyCandidateResult[];
|
|
1157
1158
|
hasFfmpegSupport: () => boolean;
|
|
1158
1159
|
version: () => string;
|
|
1160
|
+
abiVersion: () => number;
|
|
1159
1161
|
engineAbiVersion: () => number;
|
|
1160
1162
|
voiceChangerAbiVersion: () => number;
|
|
1161
1163
|
voiceCharacterPresetId: (preset: number) => string | null;
|
|
@@ -1948,6 +1950,7 @@ export interface SonareModule {
|
|
|
1948
1950
|
winLength: number,
|
|
1949
1951
|
sampleRate: number,
|
|
1950
1952
|
hopLength: number,
|
|
1953
|
+
factors?: Float32Array | number[],
|
|
1951
1954
|
) => Float32Array;
|
|
1952
1955
|
lufs: (samples: Float32Array, sampleRate: number) => WasmLufsResult;
|
|
1953
1956
|
momentaryLufs: (samples: Float32Array, sampleRate: number) => Float32Array;
|
|
@@ -2024,6 +2027,7 @@ export interface SonareModule {
|
|
|
2024
2027
|
computeSpectral: boolean,
|
|
2025
2028
|
emitEveryNFrames: number,
|
|
2026
2029
|
magnitudeDownsample: number,
|
|
2030
|
+
maxPendingFrames: number,
|
|
2027
2031
|
keyUpdateIntervalSec: number,
|
|
2028
2032
|
bpmUpdateIntervalSec: number,
|
|
2029
2033
|
window: number,
|
|
@@ -2234,6 +2238,7 @@ export interface WasmMixer {
|
|
|
2234
2238
|
vcaGroupCount: () => number;
|
|
2235
2239
|
toSceneJson: () => string;
|
|
2236
2240
|
tailSamples: () => number;
|
|
2241
|
+
latencySamples: () => number;
|
|
2237
2242
|
drainTailStereo: (numSamples: number) => {
|
|
2238
2243
|
left: Float32Array;
|
|
2239
2244
|
right: Float32Array;
|
|
@@ -2297,6 +2302,8 @@ export interface WasmAnalyzerStats {
|
|
|
2297
2302
|
totalFrames: number;
|
|
2298
2303
|
totalSamples: number;
|
|
2299
2304
|
durationSeconds: number;
|
|
2305
|
+
pendingFrames: number;
|
|
2306
|
+
droppedOutputFrames: number;
|
|
2300
2307
|
estimate: WasmProgressiveEstimate;
|
|
2301
2308
|
}
|
|
2302
2309
|
|
package/src/stream_analyzer.ts
CHANGED
|
@@ -71,6 +71,7 @@ export class StreamAnalyzer {
|
|
|
71
71
|
config.computeSpectral ?? defaults.computeSpectral,
|
|
72
72
|
config.emitEveryNFrames ?? defaults.emitEveryNFrames,
|
|
73
73
|
config.magnitudeDownsample ?? defaults.magnitudeDownsample,
|
|
74
|
+
config.maxPendingFrames ?? defaults.maxPendingFrames,
|
|
74
75
|
config.keyUpdateIntervalSec ?? defaults.keyUpdateIntervalSec,
|
|
75
76
|
config.bpmUpdateIntervalSec ?? defaults.bpmUpdateIntervalSec,
|
|
76
77
|
config.window ?? defaults.window,
|
|
@@ -163,6 +164,8 @@ export class StreamAnalyzer {
|
|
|
163
164
|
totalFrames: s.totalFrames,
|
|
164
165
|
totalSamples: s.totalSamples,
|
|
165
166
|
durationSeconds: s.durationSeconds,
|
|
167
|
+
pendingFrames: s.pendingFrames,
|
|
168
|
+
droppedOutputFrames: s.droppedOutputFrames,
|
|
166
169
|
estimate: {
|
|
167
170
|
bpm: s.estimate.bpm,
|
|
168
171
|
bpmConfidence: s.estimate.bpmConfidence,
|
package/src/stream_types.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface AnalyzerStats {
|
|
|
64
64
|
totalFrames: number;
|
|
65
65
|
totalSamples: number;
|
|
66
66
|
durationSeconds: number;
|
|
67
|
+
pendingFrames: number;
|
|
68
|
+
droppedOutputFrames: number;
|
|
67
69
|
estimate: ProgressiveEstimate;
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -159,6 +161,8 @@ export interface StreamConfig {
|
|
|
159
161
|
computeSpectral?: boolean;
|
|
160
162
|
emitEveryNFrames?: number;
|
|
161
163
|
magnitudeDownsample?: number;
|
|
164
|
+
/** Maximum unread frames; overflow drops the oldest frame. */
|
|
165
|
+
maxPendingFrames?: number;
|
|
162
166
|
keyUpdateIntervalSec?: number;
|
|
163
167
|
bpmUpdateIntervalSec?: number;
|
|
164
168
|
window?: number;
|