@libraz/libsonare 1.5.0 → 1.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libraz/libsonare",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Audio analysis library for music information retrieval",
@@ -47,8 +47,12 @@ export interface ClipPageStreamerOptions {
47
47
  interface SourceState {
48
48
  source: ClipPageStreamSource;
49
49
  lastPage: number;
50
- /** Page indices currently supplied to the provider (the resident set). */
51
- resident: Set<number>;
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
- resident: new Set(initialResidentPages),
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 furthest-advanced frontier per clip:
117
- // multiple channels of one clip miss the same page, and a run of misses
118
- // walks forward, so only the latest position needs servicing.
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
- const previous = frontiers.get(request.clipId);
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.has(page)) {
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.add(page);
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 (!ok) {
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.delete(pageIndex);
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(options.preset ?? 'neutral-monitor');
111
+ const changer = new RealtimeVoiceChanger(preset);
112
112
  try {
113
113
  changer.prepare(sampleRate, blockSize, channels);
114
114
  return latencyCompensatedVoiceChange(changer, samples, channels, blockSize);
@@ -180,6 +180,111 @@ export function vqt(
180
180
  return requireModule().vqt(samples, sampleRate, hopLength, fmin, nBins, binsPerOctave, gamma);
181
181
  }
182
182
 
183
+ function validateCqtInverse(
184
+ fnName: string,
185
+ magnitude: Float32Array,
186
+ nBins: number,
187
+ nFrames: number,
188
+ sampleRate: number,
189
+ hopLength: number,
190
+ fmin: number,
191
+ binsPerOctave: number,
192
+ nIter: number,
193
+ options: GuardedOptions,
194
+ ): void {
195
+ assertSampleRate(fnName, sampleRate);
196
+ validatePositiveIntegers(fnName, { nBins, nFrames, hopLength, binsPerOctave, nIter });
197
+ if (nIter > 256) {
198
+ throw new RangeError(`${fnName}: nIter must be at most 256`);
199
+ }
200
+ validateFrequencyBounds(fnName, fmin);
201
+ if (fmin === 0) {
202
+ throw new RangeError(`${fnName}: fmin must be positive`);
203
+ }
204
+ if (magnitude.length !== nBins * nFrames) {
205
+ throw new RangeError(`${fnName}: magnitude length must equal nBins * nFrames`);
206
+ }
207
+ assertSamples(fnName, magnitude, options.validate !== false);
208
+ }
209
+
210
+ /** Reconstruct mono audio from row-major CQT magnitude via Griffin-Lim. */
211
+ export function cqtToAudio(
212
+ magnitude: Float32Array,
213
+ nBins: number,
214
+ nFrames: number,
215
+ sampleRate = 22050,
216
+ hopLength = 512,
217
+ fmin = 32.70319566257483,
218
+ binsPerOctave = 12,
219
+ nIter = 32,
220
+ options: GuardedOptions = {},
221
+ ): Float32Array {
222
+ validateCqtInverse(
223
+ 'cqtToAudio',
224
+ magnitude,
225
+ nBins,
226
+ nFrames,
227
+ sampleRate,
228
+ hopLength,
229
+ fmin,
230
+ binsPerOctave,
231
+ nIter,
232
+ options,
233
+ );
234
+ return requireModule().cqtToAudio(
235
+ magnitude,
236
+ nBins,
237
+ nFrames,
238
+ sampleRate,
239
+ hopLength,
240
+ fmin,
241
+ binsPerOctave,
242
+ nIter,
243
+ );
244
+ }
245
+
246
+ /** Reconstruct mono audio from row-major VQT magnitude via Griffin-Lim. */
247
+ export function vqtToAudio(
248
+ magnitude: Float32Array,
249
+ nBins: number,
250
+ nFrames: number,
251
+ sampleRate = 22050,
252
+ hopLength = 512,
253
+ fmin = 32.70319566257483,
254
+ binsPerOctave = 12,
255
+ gamma = 0,
256
+ nIter = 32,
257
+ options: GuardedOptions = {},
258
+ ): Float32Array {
259
+ validateCqtInverse(
260
+ 'vqtToAudio',
261
+ magnitude,
262
+ nBins,
263
+ nFrames,
264
+ sampleRate,
265
+ hopLength,
266
+ fmin,
267
+ binsPerOctave,
268
+ nIter,
269
+ options,
270
+ );
271
+ assertFiniteScalar('vqtToAudio', gamma, 'gamma');
272
+ if (gamma < 0) {
273
+ throw new RangeError('vqtToAudio: gamma must be non-negative');
274
+ }
275
+ return requireModule().vqtToAudio(
276
+ magnitude,
277
+ nBins,
278
+ nFrames,
279
+ sampleRate,
280
+ hopLength,
281
+ fmin,
282
+ binsPerOctave,
283
+ gamma,
284
+ nIter,
285
+ );
286
+ }
287
+
183
288
  /**
184
289
  * Detect song-structure sections (intro/verse/chorus/...).
185
290
  *
@@ -366,19 +471,22 @@ export function fourierTempogram(
366
471
  * @param winLength - Window length in frames (default: 384)
367
472
  * @param sampleRate - Sample rate in Hz (default: 22050)
368
473
  * @param hopLength - Hop length (default: 512)
369
- * @returns Tempogram ratio features
474
+ * @param factors - Lag ratios to evaluate. When omitted or empty, the library
475
+ * default {0.5, 1, 2, 3, 4} is used.
476
+ * @returns Tempogram ratio features (one value per factor)
370
477
  */
371
478
  export function tempogramRatio(
372
479
  tempogramData: Float32Array,
373
480
  winLength = 384,
374
481
  sampleRate = 22050,
375
482
  hopLength = 512,
483
+ factors?: Float32Array | number[],
376
484
  options: GuardedOptions = {},
377
485
  ): Float32Array {
378
486
  assertSampleRate('tempogramRatio', sampleRate);
379
487
  assertSamples('tempogramRatio', tempogramData, options.validate !== false, 'tempogramData');
380
488
  validatePositiveIntegers('tempogramRatio', { winLength, hopLength });
381
- return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength);
489
+ return requireModule().tempogramRatio(tempogramData, winLength, sampleRate, hopLength, factors);
382
490
  }
383
491
 
384
492
  /**
package/src/features.ts CHANGED
@@ -33,6 +33,7 @@ export {
33
33
  analyzeMelody,
34
34
  analyzeSections,
35
35
  cqt,
36
+ cqtToAudio,
36
37
  fourierTempogram,
37
38
  hybridCqt,
38
39
  lufs,
@@ -44,6 +45,7 @@ export {
44
45
  shortTermLufs,
45
46
  tempogramRatio,
46
47
  vqt,
48
+ vqtToAudio,
47
49
  } from './feature_music';
48
50
  export { pitchPyin, pitchYin } from './feature_pitch';
49
51
  export { resample } from './feature_resample';
package/src/index.ts CHANGED
@@ -125,6 +125,7 @@ export {
125
125
  chromaCens,
126
126
  chromaCqt,
127
127
  cqt,
128
+ cqtToAudio,
128
129
  cyclicTempogram,
129
130
  dbToAmplitude,
130
131
  dbToPower,
@@ -193,6 +194,7 @@ export {
193
194
  trimSilence,
194
195
  vectorNormalize,
195
196
  vqt,
197
+ vqtToAudio,
196
198
  zeroCrossingRate,
197
199
  zeroCrossings,
198
200
  } from './features';
@@ -445,6 +447,7 @@ export type {
445
447
  EngineTrackLane,
446
448
  EngineTrackSend,
447
449
  EngineTransportState,
450
+ ExternalMidiEvent,
448
451
  MidiCcBindOptions,
449
452
  } from './realtime_engine';
450
453
  export { EXPECTED_ENGINE_ABI_VERSION, engineCapabilities, RealtimeEngine } from './realtime_engine';
@@ -561,6 +564,18 @@ export function version(): string {
561
564
  return module.version();
562
565
  }
563
566
 
567
+ /**
568
+ * Aggregate native ABI version: the per-subsystem ABI macros folded into one
569
+ * 32-bit value. It bumps whenever any flat C POD layout changes, so callers can
570
+ * detect an incompatible prebuilt binary. Matches the Node/Python `abiVersion()`.
571
+ */
572
+ export function abiVersion(): number {
573
+ if (!module) {
574
+ throw new Error('Module not initialized. Call init() first.');
575
+ }
576
+ return module.abiVersion();
577
+ }
578
+
564
579
  export function engineAbiVersion(): number {
565
580
  if (!module) {
566
581
  throw new Error('Module not initialized. Call init() first.');
@@ -129,6 +129,16 @@ export interface MasteringProcessorCatalogEntry {
129
129
  realtimeInsertable: boolean;
130
130
  /** True for processors with no mono implementation (stereo-only). */
131
131
  stereoOnly: boolean;
132
+ /**
133
+ * Reported latency for the default 48 kHz / 512-sample probe configuration.
134
+ * Zero for offline processors; configuration-dependent values are estimates.
135
+ */
136
+ latencySamples: number;
137
+ /**
138
+ * Audible decay length for the same default prepared probe. Zero for
139
+ * offline, dry-only, and no-tail processors.
140
+ */
141
+ tailSamples: number;
132
142
  /**
133
143
  * How the mixer wraps the processor on a >2-channel (surround) bus insert:
134
144
  * "multichannel" (one full-buffer call) or "stereoPairOnly" (front L/R pair,
package/src/mixer.ts CHANGED
@@ -515,13 +515,21 @@ export class Mixer {
515
515
  }
516
516
 
517
517
  /**
518
- * Maximum processor tail length (samples) in the compiled mixer graph. Lazily
518
+ * Longest audible serial processor-tail path to the master, in samples. Lazily
519
519
  * compiles the routing graph if the topology is dirty.
520
520
  */
521
521
  tailSamples(): number {
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
@@ -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. An explicitly empty array `[]` (or
546
- * `undefined` / `null`) produces zero bindings, so MIDI tracks render silently.
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
- * An explicitly empty array (or `undefined` / `null`) produces zero bindings.
576
- * Unknown preset names throw. Deterministic for a fixed project + options +
577
- * patch.
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). An explicitly empty array `[]` (or `undefined` /
628
- * `null`) produces zero bindings, so MIDI tracks render silently.
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();
@@ -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;
@@ -140,6 +140,7 @@ export interface KeyCandidate {
140
140
  export interface ChordDetectionOptions extends ValidateOptions {
141
141
  minDuration?: number;
142
142
  smoothingWindow?: number;
143
+ /** Final-template correlation threshold in [0, 1]; below it emits Unknown / N.C. */
143
144
  threshold?: number;
144
145
  useTriadsOnly?: boolean;
145
146
  nFft?: 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. Field names mirror the
121
- * C ABI `SonareRealtimeVoiceChangerConfig` / Python POD exactly (snake_case),
122
- * so a config can be round-tripped across bindings without renaming.
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
- input_gain_db: number;
126
- output_gain_db: number;
127
- wet_mix: number;
128
- retune_semitones: number;
129
- retune_mix: number;
130
- retune_grain_size: number;
131
- formant_factor: number;
132
- formant_amount: number;
133
- formant_body: number;
134
- formant_brightness: number;
135
- formant_nasal: number;
136
- eq_highpass_hz: number;
137
- eq_body_db: number;
138
- eq_presence_db: number;
139
- eq_air_db: number;
140
- gate_threshold_db: number;
141
- gate_attack_ms: number;
142
- gate_release_ms: number;
143
- gate_range_db: number;
144
- compressor_threshold_db: number;
145
- compressor_ratio: number;
146
- compressor_attack_ms: number;
147
- compressor_release_ms: number;
148
- compressor_makeup_gain_db: number;
149
- deesser_frequency_hz: number;
150
- deesser_threshold_db: number;
151
- deesser_ratio: number;
152
- deesser_range_db: number;
153
- reverb_mix: number;
154
- reverb_time_ms: number;
155
- reverb_damping: number;
156
- reverb_seed: number;
157
- limiter_ceiling_db: number;
158
- limiter_release_ms: number;
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
- limiter_enable_isp_limiter: boolean;
160
+ limiterEnableIspLimiter: boolean;
161
161
  /** True-peak ceiling in dBTP applied by the ISP limiter (default -1.0). */
162
- limiter_isp_ceiling_dbtp: number;
162
+ limiterIspCeilingDbtp: number;
163
163
  }