@libraz/libsonare 1.5.3 → 1.5.5
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 +69 -740
- package/dist/index.d.ts +131 -39
- package/dist/index.js +247 -66
- package/dist/index.js.map +1 -1
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +23 -1
- package/dist/worklet.js +109 -8
- package/dist/worklet.js.map +1 -1
- package/package.json +14 -3
- package/src/_chain_config.ts +0 -20
- package/src/analysis_helpers.ts +23 -0
- package/src/audio.ts +5 -3
- package/src/codes.ts +27 -4
- package/src/effects_transform.ts +32 -20
- package/src/feature_core.ts +19 -1
- package/src/feature_music.ts +32 -18
- package/src/feature_pitch.ts +5 -5
- package/src/feature_spectral.ts +3 -3
- package/src/feature_spectrogram.ts +27 -5
- package/src/index.ts +3 -0
- package/src/mastering_core.ts +2 -0
- package/src/metering.ts +49 -6
- package/src/mixer.ts +14 -1
- package/src/project_class.ts +25 -5
- package/src/project_internal.ts +2 -0
- package/src/public_types_mastering.ts +6 -0
- package/src/public_types_music.ts +4 -0
- package/src/public_types_spectral.ts +10 -3
- package/src/quick_analysis.ts +23 -5
- package/src/realtime_engine.ts +9 -1
- package/src/realtime_voice_changer.ts +79 -3
- package/src/sonare.js.d.ts +33 -3
- package/src/streaming_processors.ts +7 -1
- package/src/validation.ts +6 -0
- package/src/worklet/messages.ts +16 -2
- package/src/worklet/protocol.ts +2 -0
|
@@ -34,6 +34,7 @@ export interface ChromaSpectrogramRequest extends GuardedOptions {
|
|
|
34
34
|
sampleRate?: number;
|
|
35
35
|
hopLength?: number;
|
|
36
36
|
nChroma?: number;
|
|
37
|
+
binsPerOctave?: number;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export interface MelSpectrogramRequest extends SpectrogramRequest {
|
|
@@ -53,6 +54,8 @@ export interface MfccToMelRequest extends GuardedOptions {
|
|
|
53
54
|
nMfcc: number;
|
|
54
55
|
nFrames: number;
|
|
55
56
|
nMels?: number;
|
|
57
|
+
/** Lifter used by the forward MFCC transform; zero means no liftering. */
|
|
58
|
+
lifter?: number;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
/** Canonical request form for reconstruction from a Mel power spectrogram. */
|
|
@@ -262,6 +265,7 @@ export function chromaCens(
|
|
|
262
265
|
sampleRate?: number,
|
|
263
266
|
hopLength?: number,
|
|
264
267
|
nChroma?: number,
|
|
268
|
+
binsPerOctave?: number,
|
|
265
269
|
options?: GuardedOptions,
|
|
266
270
|
): ChromaResult;
|
|
267
271
|
export function chromaCens(
|
|
@@ -269,6 +273,7 @@ export function chromaCens(
|
|
|
269
273
|
sampleRate = 22050,
|
|
270
274
|
hopLength = 512,
|
|
271
275
|
nChroma = 12,
|
|
276
|
+
binsPerOctave = 36,
|
|
272
277
|
options: GuardedOptions = {},
|
|
273
278
|
): ChromaResult {
|
|
274
279
|
if (!(samples instanceof Float32Array)) {
|
|
@@ -278,12 +283,16 @@ export function chromaCens(
|
|
|
278
283
|
request.sampleRate,
|
|
279
284
|
request.hopLength,
|
|
280
285
|
request.nChroma,
|
|
286
|
+
request.binsPerOctave,
|
|
281
287
|
request,
|
|
282
288
|
);
|
|
283
289
|
}
|
|
284
290
|
validateSpectrogramSamples('chromaCens', samples, sampleRate, options);
|
|
285
|
-
validatePositiveIntegers('chromaCens', { hopLength, nChroma });
|
|
286
|
-
|
|
291
|
+
validatePositiveIntegers('chromaCens', { hopLength, nChroma, binsPerOctave });
|
|
292
|
+
if (binsPerOctave % nChroma !== 0) {
|
|
293
|
+
throw new RangeError('chromaCens: binsPerOctave must be a multiple of nChroma');
|
|
294
|
+
}
|
|
295
|
+
return requireModule().chromaCens(samples, sampleRate, hopLength, nChroma, binsPerOctave);
|
|
287
296
|
}
|
|
288
297
|
|
|
289
298
|
/**
|
|
@@ -301,6 +310,7 @@ export function chromaCqt(
|
|
|
301
310
|
sampleRate?: number,
|
|
302
311
|
hopLength?: number,
|
|
303
312
|
nChroma?: number,
|
|
313
|
+
binsPerOctave?: number,
|
|
304
314
|
options?: GuardedOptions,
|
|
305
315
|
): ChromaResult;
|
|
306
316
|
export function chromaCqt(
|
|
@@ -308,6 +318,7 @@ export function chromaCqt(
|
|
|
308
318
|
sampleRate = 22050,
|
|
309
319
|
hopLength = 512,
|
|
310
320
|
nChroma = 12,
|
|
321
|
+
binsPerOctave = 36,
|
|
311
322
|
options: GuardedOptions = {},
|
|
312
323
|
): ChromaResult {
|
|
313
324
|
if (!(samples instanceof Float32Array)) {
|
|
@@ -317,12 +328,16 @@ export function chromaCqt(
|
|
|
317
328
|
request.sampleRate,
|
|
318
329
|
request.hopLength,
|
|
319
330
|
request.nChroma,
|
|
331
|
+
request.binsPerOctave,
|
|
320
332
|
request,
|
|
321
333
|
);
|
|
322
334
|
}
|
|
323
335
|
validateSpectrogramSamples('chromaCqt', samples, sampleRate, options);
|
|
324
|
-
validatePositiveIntegers('chromaCqt', { hopLength, nChroma });
|
|
325
|
-
|
|
336
|
+
validatePositiveIntegers('chromaCqt', { hopLength, nChroma, binsPerOctave });
|
|
337
|
+
if (binsPerOctave % nChroma !== 0) {
|
|
338
|
+
throw new RangeError('chromaCqt: binsPerOctave must be a multiple of nChroma');
|
|
339
|
+
}
|
|
340
|
+
return requireModule().chromaCqt(samples, sampleRate, hopLength, nChroma, binsPerOctave);
|
|
326
341
|
}
|
|
327
342
|
|
|
328
343
|
/**
|
|
@@ -664,6 +679,7 @@ export function mfccToMel(
|
|
|
664
679
|
nMfcc: number,
|
|
665
680
|
nFrames: number,
|
|
666
681
|
nMels?: number,
|
|
682
|
+
lifter?: number,
|
|
667
683
|
options?: GuardedOptions,
|
|
668
684
|
): MelPowerResult;
|
|
669
685
|
export function mfccToMel(
|
|
@@ -671,6 +687,7 @@ export function mfccToMel(
|
|
|
671
687
|
nMfcc = 0,
|
|
672
688
|
nFrames = 0,
|
|
673
689
|
nMels = 128,
|
|
690
|
+
lifter = 0,
|
|
674
691
|
options: GuardedOptions = {},
|
|
675
692
|
): MelPowerResult {
|
|
676
693
|
if (!(mfccCoefficients instanceof Float32Array)) {
|
|
@@ -680,6 +697,7 @@ export function mfccToMel(
|
|
|
680
697
|
request.nMfcc,
|
|
681
698
|
request.nFrames,
|
|
682
699
|
request.nMels,
|
|
700
|
+
request.lifter,
|
|
683
701
|
request,
|
|
684
702
|
);
|
|
685
703
|
}
|
|
@@ -693,7 +711,7 @@ export function mfccToMel(
|
|
|
693
711
|
options,
|
|
694
712
|
);
|
|
695
713
|
validatePositiveIntegers('mfccToMel', { nMels });
|
|
696
|
-
return requireModule().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels);
|
|
714
|
+
return requireModule().mfccToMel(mfccCoefficients, nMfcc, nFrames, nMels, lifter);
|
|
697
715
|
}
|
|
698
716
|
|
|
699
717
|
/**
|
|
@@ -726,6 +744,7 @@ export function mfccToAudio(
|
|
|
726
744
|
fmax?: number,
|
|
727
745
|
nIter?: number,
|
|
728
746
|
htk?: boolean,
|
|
747
|
+
lifter?: number,
|
|
729
748
|
options?: GuardedOptions,
|
|
730
749
|
): Float32Array;
|
|
731
750
|
export function mfccToAudio(
|
|
@@ -740,6 +759,7 @@ export function mfccToAudio(
|
|
|
740
759
|
fmax = 0,
|
|
741
760
|
nIter = 32,
|
|
742
761
|
htk = false,
|
|
762
|
+
lifter = 0,
|
|
743
763
|
options: GuardedOptions = {},
|
|
744
764
|
): Float32Array {
|
|
745
765
|
if (!(mfccCoefficients instanceof Float32Array)) {
|
|
@@ -756,6 +776,7 @@ export function mfccToAudio(
|
|
|
756
776
|
request.fmax,
|
|
757
777
|
request.nIter,
|
|
758
778
|
request.htk,
|
|
779
|
+
request.lifter,
|
|
759
780
|
request,
|
|
760
781
|
);
|
|
761
782
|
}
|
|
@@ -783,6 +804,7 @@ export function mfccToAudio(
|
|
|
783
804
|
fmax,
|
|
784
805
|
nIter,
|
|
785
806
|
htk,
|
|
807
|
+
lifter,
|
|
786
808
|
);
|
|
787
809
|
}
|
|
788
810
|
|
package/src/index.ts
CHANGED
|
@@ -312,6 +312,7 @@ export type {
|
|
|
312
312
|
MeteringDynamicRangeOptions,
|
|
313
313
|
MeteringDynamicRangeRequest,
|
|
314
314
|
MeteringSamplesRequest,
|
|
315
|
+
MeteringSilenceRatioRequest,
|
|
315
316
|
MeteringSpectrumFrameRequest,
|
|
316
317
|
MeteringSpectrumRequest,
|
|
317
318
|
MeteringStereoDecimatedRequest,
|
|
@@ -336,6 +337,7 @@ export {
|
|
|
336
337
|
meteringPhaseScope,
|
|
337
338
|
meteringPhaseScopeDecimated,
|
|
338
339
|
meteringRmsDb,
|
|
340
|
+
meteringSilenceRatio,
|
|
339
341
|
meteringSpectrum,
|
|
340
342
|
meteringSpectrumFrame,
|
|
341
343
|
meteringStereoCorrelation,
|
|
@@ -522,6 +524,7 @@ export type {
|
|
|
522
524
|
DetectKeyRequest,
|
|
523
525
|
DynamicsAnalysisResult,
|
|
524
526
|
EstimateRoomRequest,
|
|
527
|
+
MusicAnalyzeRequest,
|
|
525
528
|
RhythmAnalysisResult,
|
|
526
529
|
RoomMorphRequest,
|
|
527
530
|
SamplesRequest,
|
package/src/mastering_core.ts
CHANGED
package/src/metering.ts
CHANGED
|
@@ -128,6 +128,43 @@ export function meteringRmsDb(
|
|
|
128
128
|
return requireModule().meteringRmsDb(request.samples, request.sampleRate ?? 22050);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export interface MeteringSilenceRatioRequest extends MeteringSamplesRequest {
|
|
132
|
+
thresholdDb?: number;
|
|
133
|
+
frameLength?: number;
|
|
134
|
+
hopLength?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function meteringSilenceRatio(request: MeteringSilenceRatioRequest): number;
|
|
138
|
+
export function meteringSilenceRatio(
|
|
139
|
+
samples: Float32Array,
|
|
140
|
+
sampleRate?: number,
|
|
141
|
+
thresholdDb?: number,
|
|
142
|
+
frameLength?: number,
|
|
143
|
+
hopLength?: number,
|
|
144
|
+
options?: ValidateOptions,
|
|
145
|
+
): number;
|
|
146
|
+
export function meteringSilenceRatio(
|
|
147
|
+
samples: Float32Array | MeteringSilenceRatioRequest,
|
|
148
|
+
sampleRate = 22050,
|
|
149
|
+
thresholdDb = -45,
|
|
150
|
+
frameLength = 1024,
|
|
151
|
+
hopLength = 256,
|
|
152
|
+
options: ValidateOptions = {},
|
|
153
|
+
): number {
|
|
154
|
+
const request =
|
|
155
|
+
samples instanceof Float32Array
|
|
156
|
+
? { samples, sampleRate, thresholdDb, frameLength, hopLength, ...options }
|
|
157
|
+
: samples;
|
|
158
|
+
assertSamples('meteringSilenceRatio', request.samples, request.validate !== false);
|
|
159
|
+
return requireModule().meteringSilenceRatio(
|
|
160
|
+
request.samples,
|
|
161
|
+
request.sampleRate ?? 22050,
|
|
162
|
+
request.thresholdDb ?? -45,
|
|
163
|
+
request.frameLength ?? 1024,
|
|
164
|
+
request.hopLength ?? 256,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
131
168
|
export function meteringCrestFactorDb(request: MeteringSamplesRequest): number;
|
|
132
169
|
export function meteringCrestFactorDb(
|
|
133
170
|
samples: Float32Array,
|
|
@@ -206,16 +243,20 @@ export function meteringDetectClipping(
|
|
|
206
243
|
): ClippingReport {
|
|
207
244
|
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
208
245
|
assertSamples('meteringDetectClipping', request.samples, request.validate !== false);
|
|
246
|
+
const minRegionSamples = request.minRegionSamples ?? 1;
|
|
247
|
+
if (!Number.isInteger(minRegionSamples) || minRegionSamples < 0) {
|
|
248
|
+
throw new RangeError('meteringDetectClipping: minRegionSamples must be a non-negative integer');
|
|
249
|
+
}
|
|
209
250
|
return requireModule().meteringDetectClipping(
|
|
210
251
|
request.samples,
|
|
211
252
|
request.sampleRate ?? 22050,
|
|
212
253
|
request.threshold ?? 0.999,
|
|
213
|
-
|
|
254
|
+
minRegionSamples,
|
|
214
255
|
);
|
|
215
256
|
}
|
|
216
257
|
|
|
217
258
|
/**
|
|
218
|
-
* Sliding-window dynamic range. Pass 0 for window/hop to use the library
|
|
259
|
+
* Sliding-window dynamic range for mono audio. Pass 0 for window/hop to use the library
|
|
219
260
|
* default (window=3 s, hop=1 s). The percentiles use a NEGATIVE sentinel for
|
|
220
261
|
* "use the library default" (low=0.10, high=0.95) because 0 is a literal 0th
|
|
221
262
|
* percentile; omitted percentiles therefore default to -1.
|
|
@@ -354,7 +395,7 @@ export interface WaveformPeaksReport {
|
|
|
354
395
|
samplesPerBucket: number;
|
|
355
396
|
}
|
|
356
397
|
|
|
357
|
-
/**
|
|
398
|
+
/** Uncentered correlation (cosine similarity) in [-1, 1] between equal-length channels. */
|
|
358
399
|
export function meteringStereoCorrelation(request: MeteringStereoRequest): number;
|
|
359
400
|
export function meteringStereoCorrelation(
|
|
360
401
|
left: Float32Array,
|
|
@@ -383,8 +424,10 @@ export function meteringStereoCorrelation(
|
|
|
383
424
|
}
|
|
384
425
|
|
|
385
426
|
/**
|
|
386
|
-
* Side / mid energy ratio
|
|
387
|
-
*
|
|
427
|
+
* Side / mid energy ratio in `[0, +Infinity)`: 0 = pure mono, ~1 = wide stereo,
|
|
428
|
+
* larger = increasingly decorrelated / out-of-phase. The value is unbounded and
|
|
429
|
+
* returns `Infinity` when the mid channel is silent (a mono-collapsed / fully
|
|
430
|
+
* out-of-phase signal).
|
|
388
431
|
*/
|
|
389
432
|
export function meteringStereoWidth(request: MeteringStereoRequest): number;
|
|
390
433
|
export function meteringStereoWidth(
|
|
@@ -559,7 +602,7 @@ export function meteringPhaseScopeDecimated(
|
|
|
559
602
|
}
|
|
560
603
|
|
|
561
604
|
/**
|
|
562
|
-
* Welch-averaged magnitude / power / dB spectrum over the WHOLE signal (split
|
|
605
|
+
* Welch-averaged magnitude / power / dB spectrum over the WHOLE mono signal (split
|
|
563
606
|
* into Hann-windowed, 50%-overlapping `nFft`-length frames whose power spectra
|
|
564
607
|
* are averaged). For a true single-frame snapshot, use
|
|
565
608
|
* {@link meteringSpectrumFrame}.
|
package/src/mixer.ts
CHANGED
|
@@ -74,7 +74,10 @@ export class Mixer {
|
|
|
74
74
|
return new Mixer(module.createMixerFromSceneJson(json, sampleRate, blockSize), blockSize);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Rebuild and compile the routing graph without resetting its absolute
|
|
79
|
+
* automation sample position or queued strip automation.
|
|
80
|
+
*/
|
|
78
81
|
compile(): void {
|
|
79
82
|
this.mixer.compile();
|
|
80
83
|
}
|
|
@@ -261,6 +264,11 @@ export class Mixer {
|
|
|
261
264
|
this.mixer.setVcaGroupGainDb(id, gainDb);
|
|
262
265
|
}
|
|
263
266
|
|
|
267
|
+
/** Replace an existing VCA group's strip membership. */
|
|
268
|
+
setVcaGroupMembers(id: string, members: string[]): void {
|
|
269
|
+
this.mixer.setVcaGroupMembers(id, members);
|
|
270
|
+
}
|
|
271
|
+
|
|
264
272
|
/** Remove a VCA group by id. */
|
|
265
273
|
removeVcaGroup(id: string): void {
|
|
266
274
|
this.mixer.removeVcaGroup(id);
|
|
@@ -427,6 +435,11 @@ export class Mixer {
|
|
|
427
435
|
return this.mixer.meterTap(stripIndex, meterTapCode(tap));
|
|
428
436
|
}
|
|
429
437
|
|
|
438
|
+
/** Read the post-insert meter for a compiled bus, including master. */
|
|
439
|
+
busMeter(busId: string): MixMeterSnapshot {
|
|
440
|
+
return this.mixer.busMeter(busId);
|
|
441
|
+
}
|
|
442
|
+
|
|
430
443
|
/**
|
|
431
444
|
* Schedule sample-accurate fader automation on a strip.
|
|
432
445
|
*
|
package/src/project_class.ts
CHANGED
|
@@ -427,6 +427,19 @@ export class Project {
|
|
|
427
427
|
this.native.redo();
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
/** Clear the undo/redo history without changing the current project state. */
|
|
431
|
+
clearHistory(): void {
|
|
432
|
+
this.native.clearHistory();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Cap the undo history depth (clamped to >= 1); evicts oldest entries beyond the cap. */
|
|
436
|
+
setMaxUndoDepth(depth: number): void {
|
|
437
|
+
if (!Number.isInteger(depth) || depth < 1) {
|
|
438
|
+
throw new RangeError('Project.setMaxUndoDepth: depth must be an integer >= 1');
|
|
439
|
+
}
|
|
440
|
+
this.native.setMaxUndoDepth(depth);
|
|
441
|
+
}
|
|
442
|
+
|
|
430
443
|
/** Replace a MIDI clip's entire event list. */
|
|
431
444
|
setMidiEvents(
|
|
432
445
|
clipId: number,
|
|
@@ -436,7 +449,11 @@ export class Project {
|
|
|
436
449
|
this.native.setMidiEvents(clipId, events);
|
|
437
450
|
}
|
|
438
451
|
|
|
439
|
-
/**
|
|
452
|
+
/**
|
|
453
|
+
* Import an in-memory SMF buffer; returns the first added clip id.
|
|
454
|
+
* Malformed or partially truncated tracks are rejected instead of installing
|
|
455
|
+
* a silently shortened clip.
|
|
456
|
+
*/
|
|
440
457
|
importSmf(data: Uint8Array): number {
|
|
441
458
|
return this.native.importSmf(data);
|
|
442
459
|
}
|
|
@@ -485,7 +502,10 @@ export class Project {
|
|
|
485
502
|
this.native.setProgramOnChannel(clipId, group, channel, program, bank);
|
|
486
503
|
}
|
|
487
504
|
|
|
488
|
-
/**
|
|
505
|
+
/**
|
|
506
|
+
* Destructively bake a MIDI-FX chain into all stored events. Large clips are
|
|
507
|
+
* drained without truncation; failure leaves the original clip unchanged.
|
|
508
|
+
*/
|
|
489
509
|
bakeMidiFx(clipId: number, configJson: string): void {
|
|
490
510
|
this.native.bakeMidiFx(clipId, configJson);
|
|
491
511
|
}
|
|
@@ -497,9 +517,9 @@ export class Project {
|
|
|
497
517
|
|
|
498
518
|
/**
|
|
499
519
|
* Pre-flight check for hanging / unmatched notes in a MIDI clip: reports
|
|
500
|
-
* whether every note-on
|
|
501
|
-
*
|
|
502
|
-
* or not a MIDI clip.
|
|
520
|
+
* whether every note-on in the exported half-open playback window has a
|
|
521
|
+
* matching note-off (FIFO per group+channel+note). Useful before bouncing to
|
|
522
|
+
* catch a stuck note. Throws if `clipId` is unknown or not a MIDI clip.
|
|
503
523
|
*/
|
|
504
524
|
validateMidiNotes(clipId: number): ProjectNotePairValidation {
|
|
505
525
|
return this.native.validateMidiNotes(clipId);
|
package/src/project_internal.ts
CHANGED
|
@@ -58,6 +58,8 @@ export interface WasmProject {
|
|
|
58
58
|
setTrackPan: (trackId: number, pan: number) => void;
|
|
59
59
|
undo: () => void;
|
|
60
60
|
redo: () => void;
|
|
61
|
+
clearHistory: () => void;
|
|
62
|
+
setMaxUndoDepth: (depth: number) => void;
|
|
61
63
|
setMidiEvents: (
|
|
62
64
|
clipId: number,
|
|
63
65
|
events: ReadonlyArray<ProjectMidiEvent | readonly [number, number, number]>,
|
|
@@ -138,6 +138,8 @@ export interface MasteringResult {
|
|
|
138
138
|
inputLufs: number;
|
|
139
139
|
outputLufs: number;
|
|
140
140
|
appliedGainDb: number;
|
|
141
|
+
/** True when peak headroom prevented the requested LUFS target. */
|
|
142
|
+
loudnessTargetLimited?: boolean;
|
|
141
143
|
latencySamples?: number;
|
|
142
144
|
}
|
|
143
145
|
|
|
@@ -329,6 +331,7 @@ export interface MasteringChainConfig {
|
|
|
329
331
|
monoMaker?: {
|
|
330
332
|
enabled?: boolean;
|
|
331
333
|
amount?: number;
|
|
334
|
+
frequencyHz?: number;
|
|
332
335
|
};
|
|
333
336
|
};
|
|
334
337
|
maximizer?: {
|
|
@@ -407,6 +410,8 @@ export interface MasteringChainResult {
|
|
|
407
410
|
outputTruePeakDbtp: number;
|
|
408
411
|
/** EBU Tech 3342 Loudness Range of the output (LU). */
|
|
409
412
|
outputLra: number;
|
|
413
|
+
/** True when peak headroom prevented the requested LUFS target. */
|
|
414
|
+
loudnessTargetLimited: boolean;
|
|
410
415
|
/** Per-stage gain reductions for the dynamics/maximizer stages (a subset of `stages`). */
|
|
411
416
|
stageGainReductions: StageGainReduction[];
|
|
412
417
|
}
|
|
@@ -422,6 +427,7 @@ export interface MasteringChainStereoResult {
|
|
|
422
427
|
/** See {@link MasteringChainResult} for field semantics. */
|
|
423
428
|
outputTruePeakDbtp: number;
|
|
424
429
|
outputLra: number;
|
|
430
|
+
loudnessTargetLimited: boolean;
|
|
425
431
|
stageGainReductions: StageGainReduction[];
|
|
426
432
|
}
|
|
427
433
|
|
|
@@ -233,6 +233,10 @@ export interface Beat {
|
|
|
233
233
|
export interface Chord {
|
|
234
234
|
root: PitchClass;
|
|
235
235
|
bass: PitchClass;
|
|
236
|
+
/** Canonical core spelling; stable across all language bindings. */
|
|
237
|
+
rootName: string;
|
|
238
|
+
/** Canonical core spelling; stable across all language bindings. */
|
|
239
|
+
bassName: string;
|
|
236
240
|
quality: ChordQuality;
|
|
237
241
|
start: number;
|
|
238
242
|
end: number;
|
|
@@ -30,15 +30,16 @@ export interface PitchCorrectOptions extends ValidateOptions {
|
|
|
30
30
|
export interface NoteStretchOptions {
|
|
31
31
|
/** Note onset position in samples (selects the region). Default 0. */
|
|
32
32
|
onsetSample?: number;
|
|
33
|
-
/** Note offset position in samples (selects the region).
|
|
33
|
+
/** Note offset position in samples (selects the region). Defaults to the input length. */
|
|
34
34
|
offsetSample?: number;
|
|
35
|
-
/** Stretch ratio (0.5 =
|
|
35
|
+
/** Stretch ratio (0.5 = half duration, 2.0 = double duration). Default 1. */
|
|
36
36
|
stretchRatio?: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Options for `noteMove`. */
|
|
40
40
|
export interface NoteMoveOptions {
|
|
41
41
|
onsetSample?: number;
|
|
42
|
+
/** Defaults to the input length. */
|
|
42
43
|
offsetSample?: number;
|
|
43
44
|
targetOnsetSample?: number;
|
|
44
45
|
}
|
|
@@ -47,7 +48,7 @@ export interface NoteMoveOptions {
|
|
|
47
48
|
export type SpectralEditMode = 'gain' | 'attenuate' | 'mute' | 'heal';
|
|
48
49
|
|
|
49
50
|
/** Analysis/synthesis window used by `spectralEdit`. */
|
|
50
|
-
export type SpectralEditWindow = 'hann' | 'hamming' | 'blackman' | 'rectangular';
|
|
51
|
+
export type SpectralEditWindow = 'hann' | 'hamming' | 'blackman' | 'rectangular' | 'rect';
|
|
51
52
|
|
|
52
53
|
/** One time x frequency rectangle edit op for `spectralEdit`. */
|
|
53
54
|
export interface SpectralRegionOp {
|
|
@@ -186,8 +187,14 @@ export interface PitchResult {
|
|
|
186
187
|
*/
|
|
187
188
|
export interface LufsResult {
|
|
188
189
|
integratedLufs: number;
|
|
190
|
+
/** Final complete 400 ms window, not Max-M. */
|
|
189
191
|
momentaryLufs: number;
|
|
192
|
+
/** Final complete 3 s window, not Max-S. */
|
|
190
193
|
shortTermLufs: number;
|
|
194
|
+
/** Maximum 400 ms window (EBU R128 Max-M). */
|
|
195
|
+
maxMomentaryLufs: number;
|
|
196
|
+
/** Maximum 3 s window (EBU R128 Max-S). */
|
|
197
|
+
maxShortTermLufs: number;
|
|
191
198
|
loudnessRange: number;
|
|
192
199
|
}
|
|
193
200
|
|
package/src/quick_analysis.ts
CHANGED
|
@@ -378,20 +378,38 @@ export function chordFunctionalAnalysis(
|
|
|
378
378
|
* the UI responsive for long inputs, drive this from a Web Worker and use
|
|
379
379
|
* {@link analyzeWithProgress} to report progress.
|
|
380
380
|
*/
|
|
381
|
-
export
|
|
381
|
+
export interface MusicAnalyzeOptions {
|
|
382
|
+
nFft?: number;
|
|
383
|
+
hopLength?: number;
|
|
384
|
+
bpmMin?: number;
|
|
385
|
+
bpmMax?: number;
|
|
386
|
+
startBpm?: number;
|
|
387
|
+
useTriadsOnly?: boolean;
|
|
388
|
+
useHpss?: boolean;
|
|
389
|
+
chromaHighpassHz?: number;
|
|
390
|
+
useBassWeighted?: boolean;
|
|
391
|
+
chromaHopMultiplier?: number;
|
|
392
|
+
useChordHmm?: boolean;
|
|
393
|
+
useChordKeyContext?: boolean;
|
|
394
|
+
chordHmmBeamWidth?: number;
|
|
395
|
+
detectChordInversions?: boolean;
|
|
396
|
+
}
|
|
397
|
+
export interface MusicAnalyzeRequest extends SamplesRequest, MusicAnalyzeOptions {}
|
|
398
|
+
|
|
399
|
+
export function analyze(request: MusicAnalyzeRequest): AnalysisResult;
|
|
382
400
|
export function analyze(
|
|
383
401
|
samples: Float32Array,
|
|
384
402
|
sampleRate?: number,
|
|
385
|
-
options?: GuardedOptions,
|
|
403
|
+
options?: GuardedOptions & MusicAnalyzeOptions,
|
|
386
404
|
): AnalysisResult;
|
|
387
405
|
export function analyze(
|
|
388
|
-
samples: Float32Array |
|
|
406
|
+
samples: Float32Array | MusicAnalyzeRequest,
|
|
389
407
|
sampleRate = 22050,
|
|
390
|
-
options: GuardedOptions = {},
|
|
408
|
+
options: GuardedOptions & MusicAnalyzeOptions = {},
|
|
391
409
|
): AnalysisResult {
|
|
392
410
|
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
393
411
|
validateAnalysisInput('analyze', request.samples, request.sampleRate ?? 22050, request);
|
|
394
|
-
const result = requireModule().analyze(request.samples, request.sampleRate ?? 22050);
|
|
412
|
+
const result = requireModule().analyze(request.samples, request.sampleRate ?? 22050, request);
|
|
395
413
|
return convertAnalysisResult(result);
|
|
396
414
|
}
|
|
397
415
|
|
package/src/realtime_engine.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { panLawCode, panModeCode, sendTimingCode } from './codes';
|
|
2
2
|
import { ErrorCode, SonareError } from './errors';
|
|
3
3
|
import { getSonareModule } from './module_state';
|
|
4
|
-
import type { SynthPatch } from './project';
|
|
4
|
+
import type { ProjectMidiCcBinding, SynthPatch } from './project';
|
|
5
5
|
import type { EqBand, PanLaw, PanMode, SendTiming } from './public_types';
|
|
6
6
|
import type {
|
|
7
7
|
WasmClipPageRequest,
|
|
@@ -290,6 +290,11 @@ export class RealtimeEngine {
|
|
|
290
290
|
);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/** Bind a 7/14-bit CC, RPN, or NRPN descriptor to a live parameter. */
|
|
294
|
+
bindMidiCcBinding(binding: ProjectMidiCcBinding): void {
|
|
295
|
+
this.native.bindMidiCcBinding(binding);
|
|
296
|
+
}
|
|
297
|
+
|
|
293
298
|
clearMidiCcBindings(): void {
|
|
294
299
|
this.native.clearMidiCcBindings();
|
|
295
300
|
}
|
|
@@ -479,6 +484,7 @@ export class RealtimeEngine {
|
|
|
479
484
|
this.native.seekPpq(ppq, renderFrame);
|
|
480
485
|
}
|
|
481
486
|
|
|
487
|
+
/** Set a finite tempo in the range (0, 100000] BPM. */
|
|
482
488
|
setTempo(bpm: number): void {
|
|
483
489
|
this.native.setTempo(bpm);
|
|
484
490
|
}
|
|
@@ -551,6 +557,7 @@ export class RealtimeEngine {
|
|
|
551
557
|
this.native.setLoopFromMarkers(startMarkerId, endMarkerId);
|
|
552
558
|
}
|
|
553
559
|
|
|
560
|
+
/** Set a metronome config; click lengths are limited to one second. */
|
|
554
561
|
setMetronome(config: EngineMetronomeConfig): void {
|
|
555
562
|
this.native.setMetronome(config);
|
|
556
563
|
}
|
|
@@ -833,6 +840,7 @@ export class RealtimeEngine {
|
|
|
833
840
|
this.native.setCaptureSource(source);
|
|
834
841
|
}
|
|
835
842
|
|
|
843
|
+
/** Positive values delay capture relative to the punch window. */
|
|
836
844
|
setRecordOffsetSamples(offsetSamples: number): void {
|
|
837
845
|
this.native.setRecordOffsetSamples(offsetSamples);
|
|
838
846
|
}
|
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
import { getSonareModule } from './module_state';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
RealtimeVoiceChangerConfigInput,
|
|
4
|
+
RealtimeVoiceChangerPodConfig,
|
|
5
|
+
VoicePresetId,
|
|
6
|
+
} from './public_types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* True when `config` is a flat POD (the shape `realtimeVoiceChangerPresetConfig`
|
|
10
|
+
* returns) rather than a preset name or a nested preset. `retuneSemitones` only
|
|
11
|
+
* exists on the flat POD; the nested form spells it `retune.semitones`.
|
|
12
|
+
*/
|
|
13
|
+
function isFlatVoiceChangerPod(config: unknown): config is RealtimeVoiceChangerPodConfig {
|
|
14
|
+
return typeof config === 'object' && config !== null && 'retuneSemitones' in config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Rewrite the flat 36-field POD into the nested shape the native config parser
|
|
19
|
+
* reads. Passing the flat POD straight to `setConfig` would leave every nested
|
|
20
|
+
* field at its default (only the three root fields are read flat), silently
|
|
21
|
+
* discarding retune/EQ/reverb/etc.
|
|
22
|
+
*/
|
|
23
|
+
function flatVoiceChangerPodToNested(pod: RealtimeVoiceChangerPodConfig): Record<string, unknown> {
|
|
24
|
+
return {
|
|
25
|
+
inputGainDb: pod.inputGainDb,
|
|
26
|
+
outputGainDb: pod.outputGainDb,
|
|
27
|
+
wetMix: pod.wetMix,
|
|
28
|
+
retune: { semitones: pod.retuneSemitones, mix: pod.retuneMix, grainSize: pod.retuneGrainSize },
|
|
29
|
+
formant: {
|
|
30
|
+
factor: pod.formantFactor,
|
|
31
|
+
amount: pod.formantAmount,
|
|
32
|
+
body: pod.formantBody,
|
|
33
|
+
brightness: pod.formantBrightness,
|
|
34
|
+
nasal: pod.formantNasal,
|
|
35
|
+
},
|
|
36
|
+
eq: {
|
|
37
|
+
highpassHz: pod.eqHighpassHz,
|
|
38
|
+
bodyDb: pod.eqBodyDb,
|
|
39
|
+
presenceDb: pod.eqPresenceDb,
|
|
40
|
+
airDb: pod.eqAirDb,
|
|
41
|
+
},
|
|
42
|
+
gate: {
|
|
43
|
+
thresholdDb: pod.gateThresholdDb,
|
|
44
|
+
attackMs: pod.gateAttackMs,
|
|
45
|
+
releaseMs: pod.gateReleaseMs,
|
|
46
|
+
rangeDb: pod.gateRangeDb,
|
|
47
|
+
},
|
|
48
|
+
compressor: {
|
|
49
|
+
thresholdDb: pod.compressorThresholdDb,
|
|
50
|
+
ratio: pod.compressorRatio,
|
|
51
|
+
attackMs: pod.compressorAttackMs,
|
|
52
|
+
releaseMs: pod.compressorReleaseMs,
|
|
53
|
+
makeupGainDb: pod.compressorMakeupGainDb,
|
|
54
|
+
},
|
|
55
|
+
deesser: {
|
|
56
|
+
frequencyHz: pod.deesserFrequencyHz,
|
|
57
|
+
thresholdDb: pod.deesserThresholdDb,
|
|
58
|
+
ratio: pod.deesserRatio,
|
|
59
|
+
rangeDb: pod.deesserRangeDb,
|
|
60
|
+
},
|
|
61
|
+
reverb: {
|
|
62
|
+
mix: pod.reverbMix,
|
|
63
|
+
timeMs: pod.reverbTimeMs,
|
|
64
|
+
damping: pod.reverbDamping,
|
|
65
|
+
seed: pod.reverbSeed,
|
|
66
|
+
},
|
|
67
|
+
limiter: {
|
|
68
|
+
ceilingDb: pod.limiterCeilingDb,
|
|
69
|
+
releaseMs: pod.limiterReleaseMs,
|
|
70
|
+
enableIspLimiter: pod.limiterEnableIspLimiter,
|
|
71
|
+
ispCeilingDbtp: pod.limiterIspCeilingDbtp,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
3
75
|
|
|
4
76
|
/**
|
|
5
77
|
* Zero-copy realtime buffer pair for {@link RealtimeVoiceChanger} mono
|
|
@@ -58,8 +130,12 @@ export class RealtimeVoiceChanger {
|
|
|
58
130
|
this.changer.reset();
|
|
59
131
|
}
|
|
60
132
|
|
|
61
|
-
setConfig(config: RealtimeVoiceChangerConfigInput): void {
|
|
62
|
-
|
|
133
|
+
setConfig(config: RealtimeVoiceChangerConfigInput | RealtimeVoiceChangerPodConfig): void {
|
|
134
|
+
// A flat POD (from realtimeVoiceChangerPresetConfig) is rewritten to the
|
|
135
|
+
// nested shape the native parser reads; a preset name or nested preset is
|
|
136
|
+
// passed through unchanged.
|
|
137
|
+
const resolved = isFlatVoiceChangerPod(config) ? flatVoiceChangerPodToNested(config) : config;
|
|
138
|
+
this.changer.setConfig(resolved as Record<string, unknown> | string);
|
|
63
139
|
}
|
|
64
140
|
|
|
65
141
|
configJson(): string {
|