@libraz/libsonare 1.7.0 → 1.7.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/README.md +33 -0
- package/dist/analysis.d.ts +55 -3
- package/dist/analysis.js +10 -0
- package/dist/analysis.js.map +1 -1
- package/dist/index.d.ts +368 -199
- package/dist/index.js +111 -6
- package/dist/index.js.map +1 -1
- package/dist/sonare-analysis.js +1 -1
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worker.js.map +1 -1
- package/dist/worklet.d.ts +379 -9
- package/dist/worklet.js +174 -13
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/_chain_config.ts +6 -1
- package/src/effects_mastering.ts +5 -0
- package/src/effects_transform.ts +23 -12
- package/src/index.ts +10 -0
- package/src/mastering_core.ts +70 -0
- package/src/metering.ts +18 -0
- package/src/project.ts +3 -0
- package/src/project_class.ts +52 -2
- package/src/project_internal.ts +2 -0
- package/src/project_types.ts +39 -8
- package/src/public_types_mastering.ts +9 -0
- package/src/public_types_spectral.ts +16 -2
- package/src/realtime_engine.ts +39 -0
- package/src/sonare.js.d.ts +26 -0
- package/src/streaming_processors.ts +18 -0
- package/src/worklet/engine-node.ts +7 -2
- package/src/worklet/engine-processor.ts +59 -12
- package/src/worklet/messages.ts +12 -0
- package/src/worklet.ts +5 -0
package/package.json
CHANGED
package/src/_chain_config.ts
CHANGED
|
@@ -6,7 +6,12 @@ type ChainSection = { [key: string]: number | boolean | ChainSection | undefined
|
|
|
6
6
|
* Flattens a nested {@link MasteringChainConfig} into the dot-notation
|
|
7
7
|
* `{ "module.processor.param": value }` map the core consumes. Internal helper
|
|
8
8
|
* shared by the mastering-chain / master-audio entry points. The core owns
|
|
9
|
-
* legacy
|
|
9
|
+
* legacy leaf-name aliases, so this remains a structural flattening step.
|
|
10
|
+
*
|
|
11
|
+
* A key the caller already wrote in dot notation carries through untouched, so
|
|
12
|
+
* both spellings a {@link MasteringChainConfig} accepts reach the core as the
|
|
13
|
+
* same parameter — matching what the Python binding documents. An unknown key
|
|
14
|
+
* in either spelling is rejected by the core, not here.
|
|
10
15
|
*/
|
|
11
16
|
export function flattenChainConfig(config: MasteringChainConfig): Record<string, number | boolean> {
|
|
12
17
|
const out: Record<string, number | boolean> = {};
|
package/src/effects_mastering.ts
CHANGED
|
@@ -41,12 +41,16 @@ export type {
|
|
|
41
41
|
MasteringRealtimeCost,
|
|
42
42
|
MasteringSamplesParamsRequest,
|
|
43
43
|
MasteringStereoAnalyzeRequest,
|
|
44
|
+
MasteringStereoParamsRequest,
|
|
44
45
|
MasteringStreamingPreviewRequest,
|
|
46
|
+
MasteringStreamingPreviewStereoRequest,
|
|
45
47
|
} from './mastering_core';
|
|
46
48
|
export {
|
|
47
49
|
mastering,
|
|
48
50
|
masteringAssistantSuggest,
|
|
51
|
+
masteringAssistantSuggestStereo,
|
|
49
52
|
masteringAudioProfile,
|
|
53
|
+
masteringAudioProfileStereo,
|
|
50
54
|
masteringInsertNames,
|
|
51
55
|
masteringInsertParamInfo,
|
|
52
56
|
masteringInsertParamNames,
|
|
@@ -61,6 +65,7 @@ export {
|
|
|
61
65
|
masteringStereoAnalysisNames,
|
|
62
66
|
masteringStereoAnalyze,
|
|
63
67
|
masteringStreamingPreview,
|
|
68
|
+
masteringStreamingPreviewStereo,
|
|
64
69
|
} from './mastering_core';
|
|
65
70
|
export type {
|
|
66
71
|
CompressorDetector,
|
package/src/effects_transform.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
PitchCorrectOptions,
|
|
7
7
|
SpectralEditOptions,
|
|
8
8
|
SpectralRegionOp,
|
|
9
|
+
VoicedFlags,
|
|
9
10
|
} from './public_types';
|
|
10
11
|
import type { ValidateOptions } from './validation';
|
|
11
12
|
import { assertSampleRate, assertSamples } from './validation';
|
|
@@ -14,6 +15,18 @@ function requireModule() {
|
|
|
14
15
|
return getSonareModule();
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
// The embind layer reads the companion voicing array as Float32Array. A flag is
|
|
19
|
+
// a decision, not a magnitude: collapse to 1/0 on truthiness, which is the same
|
|
20
|
+
// reduction the Node facade applies, so both surfaces agree on every accepted
|
|
21
|
+
// input type.
|
|
22
|
+
function toVoicedFloat32(voiced: VoicedFlags): Float32Array {
|
|
23
|
+
const out = new Float32Array(voiced.length);
|
|
24
|
+
for (let index = 0; index < voiced.length; index += 1) {
|
|
25
|
+
out[index] = voiced[index] ? 1 : 0;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
function resolveEffectFftOptions(
|
|
18
31
|
fnName: string,
|
|
19
32
|
nFft: unknown,
|
|
@@ -114,7 +127,7 @@ export interface PitchCorrectToMidiTimevaryingRequest extends ValidateOptions {
|
|
|
114
127
|
targetMidi: number;
|
|
115
128
|
sampleRate?: number;
|
|
116
129
|
hopLength?: number;
|
|
117
|
-
voiced?:
|
|
130
|
+
voiced?: VoicedFlags;
|
|
118
131
|
voicedProb?: Float32Array;
|
|
119
132
|
}
|
|
120
133
|
|
|
@@ -423,16 +436,18 @@ export function pitchCorrectToMidi(
|
|
|
423
436
|
* Unlike {@link pitchCorrectToMidi} (a single constant transpose), this follows
|
|
424
437
|
* the caller-supplied per-frame `f0Hz` contour and retunes every voiced frame
|
|
425
438
|
* toward `targetMidi`, so vibrato/drift in the source is tracked rather than
|
|
426
|
-
* flattened. `voiced` (
|
|
439
|
+
* flattened. `voiced` (truthy = voiced) and `voicedProb` ([0,1]) are optional;
|
|
427
440
|
* omitting them treats every frame as voiced. An `f0Hz` NaN is accepted only
|
|
428
|
-
* when the corresponding `voiced` entry is
|
|
441
|
+
* when the corresponding `voiced` entry is falsy, matching pYIN output. The
|
|
442
|
+
* `voicedFlag` / `voicedProb` arrays of a {@link PitchResult} can be passed
|
|
443
|
+
* through directly.
|
|
429
444
|
*
|
|
430
445
|
* @param samples - Audio samples (mono, float32)
|
|
431
446
|
* @param f0Hz - Per-frame measured F0 in Hz (one entry per analysis frame)
|
|
432
447
|
* @param targetMidi - Desired MIDI note number
|
|
433
448
|
* @param sampleRate - Sample rate in Hz
|
|
434
449
|
* @param hopLength - F0 hop in samples (frame i covers sample i*hopLength)
|
|
435
|
-
* @param voiced - Optional per-frame voiced flags (
|
|
450
|
+
* @param voiced - Optional per-frame voiced flags (truthy = voiced)
|
|
436
451
|
* @param voicedProb - Optional per-frame voicing probability in [0, 1]
|
|
437
452
|
* @returns Pitch-corrected audio
|
|
438
453
|
*/
|
|
@@ -445,7 +460,7 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
445
460
|
targetMidi: number,
|
|
446
461
|
sampleRate?: number,
|
|
447
462
|
hopLength?: number,
|
|
448
|
-
voiced?:
|
|
463
|
+
voiced?: VoicedFlags,
|
|
449
464
|
voicedProb?: Float32Array,
|
|
450
465
|
options?: ValidateOptions,
|
|
451
466
|
): Float32Array;
|
|
@@ -455,7 +470,7 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
455
470
|
targetMidi?: number,
|
|
456
471
|
sampleRate = 22050,
|
|
457
472
|
hopLength = 512,
|
|
458
|
-
voiced?:
|
|
473
|
+
voiced?: VoicedFlags,
|
|
459
474
|
voicedProb?: Float32Array,
|
|
460
475
|
options: ValidateOptions = {},
|
|
461
476
|
): Float32Array {
|
|
@@ -479,9 +494,7 @@ export function pitchCorrectToMidiTimevarying(
|
|
|
479
494
|
if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
|
|
480
495
|
throw new RangeError('pitchCorrectToMidiTimevarying: voicedProb length must match f0Hz length');
|
|
481
496
|
}
|
|
482
|
-
|
|
483
|
-
// 0.0/1.0); convert here so a single native conversion path suffices.
|
|
484
|
-
const voicedF32 = request.voiced ? Float32Array.from(request.voiced) : undefined;
|
|
497
|
+
const voicedF32 = request.voiced ? toVoicedFloat32(request.voiced) : undefined;
|
|
485
498
|
return requireModule().pitchCorrectToMidiTimevarying(
|
|
486
499
|
request.samples,
|
|
487
500
|
request.sampleRate ?? 22050,
|
|
@@ -536,11 +549,9 @@ export function pitchCorrectTimevarying(
|
|
|
536
549
|
if (request.voicedProb && request.voicedProb.length !== request.f0Hz.length) {
|
|
537
550
|
throw new RangeError('pitchCorrectTimevarying: voicedProb length must match f0Hz length');
|
|
538
551
|
}
|
|
539
|
-
// The embind layer reads the companion arrays as Float32Array (voiced uses
|
|
540
|
-
// 0.0/1.0); convert here so a single native conversion path suffices.
|
|
541
552
|
const nativeOptions = {
|
|
542
553
|
...request,
|
|
543
|
-
voiced: request.voiced ?
|
|
554
|
+
voiced: request.voiced ? toVoicedFloat32(request.voiced) : undefined,
|
|
544
555
|
};
|
|
545
556
|
return requireModule().pitchCorrectTimevarying(
|
|
546
557
|
request.samples,
|
package/src/index.ts
CHANGED
|
@@ -78,7 +78,9 @@ export type {
|
|
|
78
78
|
MasteringRepairTrimSilenceRequest,
|
|
79
79
|
MasteringSamplesParamsRequest,
|
|
80
80
|
MasteringStereoAnalyzeRequest,
|
|
81
|
+
MasteringStereoParamsRequest,
|
|
81
82
|
MasteringStreamingPreviewRequest,
|
|
83
|
+
MasteringStreamingPreviewStereoRequest,
|
|
82
84
|
MixStereoRequest,
|
|
83
85
|
TransientShaperOptions,
|
|
84
86
|
TrimSilenceMode,
|
|
@@ -97,7 +99,9 @@ export {
|
|
|
97
99
|
masterAudioWithProgress,
|
|
98
100
|
mastering,
|
|
99
101
|
masteringAssistantSuggest,
|
|
102
|
+
masteringAssistantSuggestStereo,
|
|
100
103
|
masteringAudioProfile,
|
|
104
|
+
masteringAudioProfileStereo,
|
|
101
105
|
masteringChain,
|
|
102
106
|
masteringChainStereo,
|
|
103
107
|
masteringChainStereoWithProgress,
|
|
@@ -127,6 +131,7 @@ export {
|
|
|
127
131
|
masteringStereoAnalysisNames,
|
|
128
132
|
masteringStereoAnalyze,
|
|
129
133
|
masteringStreamingPreview,
|
|
134
|
+
masteringStreamingPreviewStereo,
|
|
130
135
|
mixingScenePresetJson,
|
|
131
136
|
mixingScenePresetNames,
|
|
132
137
|
mixStereo,
|
|
@@ -375,6 +380,7 @@ export type {
|
|
|
375
380
|
} from './metering';
|
|
376
381
|
export {
|
|
377
382
|
meteringCrestFactorDb,
|
|
383
|
+
meteringCrestFactorDbStereo,
|
|
378
384
|
meteringDcOffset,
|
|
379
385
|
meteringDetectClipping,
|
|
380
386
|
meteringDynamicRange,
|
|
@@ -432,6 +438,9 @@ export type {
|
|
|
432
438
|
ProjectMarker,
|
|
433
439
|
ProjectMidiClipResult,
|
|
434
440
|
ProjectMidiEvent,
|
|
441
|
+
ProjectMidiFxBakeRequest,
|
|
442
|
+
ProjectMidiFxBakeResult,
|
|
443
|
+
ProjectMidiFxPreviewRequest,
|
|
435
444
|
ProjectNotePairValidation,
|
|
436
445
|
ProjectSource,
|
|
437
446
|
ProjectTrack,
|
|
@@ -566,6 +575,7 @@ export type {
|
|
|
566
575
|
TempogramMode,
|
|
567
576
|
Timbre,
|
|
568
577
|
TimeSignature,
|
|
578
|
+
VoicedFlags,
|
|
569
579
|
VoicePresetId,
|
|
570
580
|
} from './public_types';
|
|
571
581
|
export {
|
package/src/mastering_core.ts
CHANGED
|
@@ -77,6 +77,22 @@ export interface MasteringStreamingPreviewRequest {
|
|
|
77
77
|
platforms?: StreamingPlatform[];
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/** Canonical request form for the stereo analysis entry points. */
|
|
81
|
+
export interface MasteringStereoParamsRequest {
|
|
82
|
+
left: Float32Array;
|
|
83
|
+
right: Float32Array;
|
|
84
|
+
sampleRate?: number;
|
|
85
|
+
params?: MasteringProcessorParams;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Canonical request form for the stereo streaming-platform preview. */
|
|
89
|
+
export interface MasteringStreamingPreviewStereoRequest {
|
|
90
|
+
left: Float32Array;
|
|
91
|
+
right: Float32Array;
|
|
92
|
+
sampleRate?: number;
|
|
93
|
+
platforms?: StreamingPlatform[];
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
/**
|
|
81
97
|
* Apply mastering loudness normalization with a true-peak ceiling.
|
|
82
98
|
*
|
|
@@ -479,3 +495,57 @@ export function masteringStreamingPreview(
|
|
|
479
495
|
request.platforms ?? [],
|
|
480
496
|
);
|
|
481
497
|
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Suggest a mastering chain for a stereo pair, as shared JSON.
|
|
501
|
+
*
|
|
502
|
+
* Profiles through {@link masteringAudioProfileStereo}, so the loudness stage
|
|
503
|
+
* of the suggestion is built on the channel-summed program rather than a
|
|
504
|
+
* downmix that reads roughly 6 dB low.
|
|
505
|
+
*/
|
|
506
|
+
export function masteringAssistantSuggestStereo(request: MasteringStereoParamsRequest): string {
|
|
507
|
+
return requireModule().masteringAssistantSuggestStereo(
|
|
508
|
+
request.left,
|
|
509
|
+
request.right,
|
|
510
|
+
request.sampleRate ?? 22050,
|
|
511
|
+
request.params ?? {},
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Mastering assistant profile of a stereo pair, as shared JSON.
|
|
517
|
+
*
|
|
518
|
+
* Only the `loudness` block is measured from the two channels: integrated LUFS
|
|
519
|
+
* and LRA come from the channel-summed program and the true peak is the larger
|
|
520
|
+
* of the two. The spectral, dynamics and tempo fields describe shape and timing
|
|
521
|
+
* rather than absolute level and are measured on the downmix, which keeps them
|
|
522
|
+
* comparable with {@link masteringAudioProfile}.
|
|
523
|
+
*/
|
|
524
|
+
export function masteringAudioProfileStereo(request: MasteringStereoParamsRequest): string {
|
|
525
|
+
return requireModule().masteringAudioProfileStereo(
|
|
526
|
+
request.left,
|
|
527
|
+
request.right,
|
|
528
|
+
request.sampleRate ?? 22050,
|
|
529
|
+
request.params ?? {},
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Preview streaming-platform normalization for a stereo pair, as shared JSON.
|
|
535
|
+
*
|
|
536
|
+
* Measures the integrated loudness with BS.1770 channel summing and reports the
|
|
537
|
+
* larger of the two channel true peaks. Passing a `0.5 * (left + right)` downmix
|
|
538
|
+
* to {@link masteringStreamingPreview} instead reads roughly 6 dB low on
|
|
539
|
+
* decorrelated material, and both the normalization gain and the ceiling-risk
|
|
540
|
+
* flag follow from that measurement.
|
|
541
|
+
*/
|
|
542
|
+
export function masteringStreamingPreviewStereo(
|
|
543
|
+
request: MasteringStreamingPreviewStereoRequest,
|
|
544
|
+
): string {
|
|
545
|
+
return requireModule().masteringStreamingPreviewStereo(
|
|
546
|
+
request.left,
|
|
547
|
+
request.right,
|
|
548
|
+
request.sampleRate ?? 22050,
|
|
549
|
+
request.platforms ?? [],
|
|
550
|
+
);
|
|
551
|
+
}
|
package/src/metering.ts
CHANGED
|
@@ -186,6 +186,24 @@ export function meteringCrestFactorDb(
|
|
|
186
186
|
return requireModule().meteringCrestFactorDb(request.samples, request.sampleRate ?? 22050);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Crest factor in dB across both channels of a stereo pair.
|
|
191
|
+
*
|
|
192
|
+
* Takes the peak across both channels and the RMS over both together. An
|
|
193
|
+
* out-of-phase pair cancels in the `0.5 * (left + right)` downmix
|
|
194
|
+
* {@link meteringCrestFactorDb} would need, which understates its RMS and so
|
|
195
|
+
* overstates the crest factor.
|
|
196
|
+
*/
|
|
197
|
+
export function meteringCrestFactorDbStereo(request: MeteringStereoRequest): number {
|
|
198
|
+
assertSamples('meteringCrestFactorDbStereo', request.left, request.validate !== false);
|
|
199
|
+
assertSamples('meteringCrestFactorDbStereo', request.right, request.validate !== false);
|
|
200
|
+
return requireModule().meteringCrestFactorDbStereo(
|
|
201
|
+
request.left,
|
|
202
|
+
request.right,
|
|
203
|
+
request.sampleRate ?? 22050,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
189
207
|
export function meteringDcOffset(request: MeteringSamplesRequest): number;
|
|
190
208
|
export function meteringDcOffset(
|
|
191
209
|
samples: Float32Array,
|
package/src/project.ts
CHANGED
|
@@ -40,6 +40,9 @@ export type {
|
|
|
40
40
|
ProjectMidiCcBindingKind,
|
|
41
41
|
ProjectMidiClipResult,
|
|
42
42
|
ProjectMidiEvent,
|
|
43
|
+
ProjectMidiFxBakeRequest,
|
|
44
|
+
ProjectMidiFxBakeResult,
|
|
45
|
+
ProjectMidiFxPreviewRequest,
|
|
43
46
|
ProjectMidiRouteConfig,
|
|
44
47
|
ProjectMidiRouteResult,
|
|
45
48
|
ProjectNotePairValidation,
|
package/src/project_class.ts
CHANGED
|
@@ -35,6 +35,9 @@ import type {
|
|
|
35
35
|
ProjectMidiCcBinding,
|
|
36
36
|
ProjectMidiClipResult,
|
|
37
37
|
ProjectMidiEvent,
|
|
38
|
+
ProjectMidiFxBakeRequest,
|
|
39
|
+
ProjectMidiFxBakeResult,
|
|
40
|
+
ProjectMidiFxPreviewRequest,
|
|
38
41
|
ProjectMidiRouteConfig,
|
|
39
42
|
ProjectMidiRouteResult,
|
|
40
43
|
ProjectNotePairValidation,
|
|
@@ -52,6 +55,28 @@ import type {
|
|
|
52
55
|
SynthPatch,
|
|
53
56
|
} from './project_types';
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Folds the positional and request call forms of `bakeMidiFx` into one shape,
|
|
60
|
+
* so defaults, validation and errors cannot diverge between them.
|
|
61
|
+
*/
|
|
62
|
+
function normalizeMidiFxBakeRequest(
|
|
63
|
+
clipIdOrRequest: number | ProjectMidiFxBakeRequest,
|
|
64
|
+
configJson?: string,
|
|
65
|
+
): Required<ProjectMidiFxBakeRequest> {
|
|
66
|
+
if (typeof clipIdOrRequest === 'number') {
|
|
67
|
+
return {
|
|
68
|
+
clipId: clipIdOrRequest,
|
|
69
|
+
configJson: configJson ?? '',
|
|
70
|
+
withSourceIndex: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
clipId: clipIdOrRequest.clipId,
|
|
75
|
+
configJson: clipIdOrRequest.configJson,
|
|
76
|
+
withSourceIndex: clipIdOrRequest.withSourceIndex ?? false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
function validateAssistSidecarUint32(value: unknown, field: string): number {
|
|
56
81
|
if (
|
|
57
82
|
typeof value !== 'number' ||
|
|
@@ -584,8 +609,33 @@ export class Project {
|
|
|
584
609
|
* Destructively bake a MIDI-FX chain into all stored events. Large clips are
|
|
585
610
|
* drained without truncation; failure leaves the original clip unchanged.
|
|
586
611
|
*/
|
|
587
|
-
bakeMidiFx(clipId: number, configJson: string): void
|
|
588
|
-
|
|
612
|
+
bakeMidiFx(clipId: number, configJson: string): void;
|
|
613
|
+
/**
|
|
614
|
+
* Request form. Setting `withSourceIndex` also returns per-event provenance,
|
|
615
|
+
* so a selection or an editorial annotation can be carried across the bake.
|
|
616
|
+
*/
|
|
617
|
+
bakeMidiFx(request: ProjectMidiFxBakeRequest): ProjectMidiFxBakeResult;
|
|
618
|
+
bakeMidiFx(
|
|
619
|
+
clipIdOrRequest: number | ProjectMidiFxBakeRequest,
|
|
620
|
+
configJson?: string,
|
|
621
|
+
): ProjectMidiFxBakeResult {
|
|
622
|
+
const request = normalizeMidiFxBakeRequest(clipIdOrRequest, configJson);
|
|
623
|
+
if (!request.withSourceIndex) {
|
|
624
|
+
this.native.bakeMidiFx(request.clipId, request.configJson);
|
|
625
|
+
return {};
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
sourceIndex: this.native.bakeMidiFxWithSourceIndex(request.clipId, request.configJson),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Count the events {@link bakeMidiFx} would produce for this clip and
|
|
634
|
+
* configuration, without mutating the project. The transform is
|
|
635
|
+
* deterministic, so the count matches what the bake goes on to produce.
|
|
636
|
+
*/
|
|
637
|
+
previewMidiFxCount(request: ProjectMidiFxPreviewRequest): number {
|
|
638
|
+
return this.native.previewMidiFxCount(request.clipId, request.configJson);
|
|
589
639
|
}
|
|
590
640
|
|
|
591
641
|
/** Backward alias for {@link bakeMidiFx}. */
|
package/src/project_internal.ts
CHANGED
|
@@ -85,6 +85,8 @@ export interface WasmProject {
|
|
|
85
85
|
bank: number,
|
|
86
86
|
) => void;
|
|
87
87
|
bakeMidiFx: (clipId: number, configJson: string) => void;
|
|
88
|
+
bakeMidiFxWithSourceIndex: (clipId: number, configJson: string) => Int32Array;
|
|
89
|
+
previewMidiFxCount: (clipId: number, configJson: string) => number;
|
|
88
90
|
setMidiFx: (clipId: number, configJson: string) => void;
|
|
89
91
|
validateMidiNotes: (clipId: number) => ProjectNotePairValidation;
|
|
90
92
|
analyzeTempo: (audio: Float32Array, sampleRate: number) => ProjectTempoCandidate[];
|
package/src/project_types.ts
CHANGED
|
@@ -300,12 +300,11 @@ export interface SynthModRouting {
|
|
|
300
300
|
*
|
|
301
301
|
* The patch starts from a BASE — the named `preset` (see
|
|
302
302
|
* {@link synthPresetNames}; a `"va:"` routing prefix is accepted) or, when
|
|
303
|
-
* `preset` is omitted, the default subtractive patch.
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* base value. A non-empty `modRoutings` REPLACES the base mod matrix.
|
|
303
|
+
* `preset` is omitted, the default subtractive patch. Omitting a numeric field
|
|
304
|
+
* keeps the base value; supplying one overrides it (clamped to its audible
|
|
305
|
+
* range), including an explicit `0` such as `stereoSpread: 0`. The enum fields
|
|
306
|
+
* reserve `'default'` as keep. A `modRoutings` array REPLACES the base mod
|
|
307
|
+
* matrix, and an empty array clears it, while omitting the key keeps it.
|
|
309
308
|
*
|
|
310
309
|
* Mode-specific deep parameters (FM operator stacks, modal mode tables,
|
|
311
310
|
* drawbar registrations, kit pieces, piano strings) travel inside the named
|
|
@@ -341,12 +340,10 @@ export interface SynthPatch {
|
|
|
341
340
|
velToCutoffCents?: number;
|
|
342
341
|
ampAttackMs?: number;
|
|
343
342
|
ampDecayMs?: number;
|
|
344
|
-
/** 0 / omit keeps the base value; explicit zero sustain is not representable. */
|
|
345
343
|
ampSustain?: number;
|
|
346
344
|
ampReleaseMs?: number;
|
|
347
345
|
filterAttackMs?: number;
|
|
348
346
|
filterDecayMs?: number;
|
|
349
|
-
/** 0 / omit keeps the base value; explicit zero sustain is not representable. */
|
|
350
347
|
filterSustain?: number;
|
|
351
348
|
filterReleaseMs?: number;
|
|
352
349
|
lfoRateHz?: number;
|
|
@@ -668,6 +665,40 @@ export interface ProjectNotePairValidation {
|
|
|
668
665
|
unmatchedNoteOffs: number;
|
|
669
666
|
}
|
|
670
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Request form of {@link Project.bakeMidiFx}. The positional
|
|
670
|
+
* `(clipId, configJson)` call stays supported and normalizes to this shape.
|
|
671
|
+
*/
|
|
672
|
+
export interface ProjectMidiFxBakeRequest {
|
|
673
|
+
/** Target MIDI clip id. */
|
|
674
|
+
clipId: number;
|
|
675
|
+
/** MIDI-FX chain configuration as JSON. */
|
|
676
|
+
configJson: string;
|
|
677
|
+
/** Return per-event provenance in the result. Default false. */
|
|
678
|
+
withSourceIndex?: boolean;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Result of the request form of {@link Project.bakeMidiFx}. */
|
|
682
|
+
export interface ProjectMidiFxBakeResult {
|
|
683
|
+
/**
|
|
684
|
+
* One entry per transformed event in canonical order: the index of the input
|
|
685
|
+
* event it derives from, or -1 for an event with no originating input. Chord
|
|
686
|
+
* and arpeggiator fan-out makes several outputs share one source index, so a
|
|
687
|
+
* caller that treats the first output per index as the same event and the
|
|
688
|
+
* rest as newly generated can carry a selection across the bake. Present only
|
|
689
|
+
* when the request set `withSourceIndex`.
|
|
690
|
+
*/
|
|
691
|
+
sourceIndex?: Int32Array;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Request form of {@link Project.previewMidiFxCount}. */
|
|
695
|
+
export interface ProjectMidiFxPreviewRequest {
|
|
696
|
+
/** Target MIDI clip id. */
|
|
697
|
+
clipId: number;
|
|
698
|
+
/** MIDI-FX chain configuration as JSON. */
|
|
699
|
+
configJson: string;
|
|
700
|
+
}
|
|
701
|
+
|
|
671
702
|
/** One compile diagnostic (mirrors SonareProjectDiagnostic). */
|
|
672
703
|
export interface ProjectDiagnostic {
|
|
673
704
|
code: number;
|
|
@@ -352,6 +352,15 @@ export interface MasteringChainConfig {
|
|
|
352
352
|
releaseMs?: number;
|
|
353
353
|
applyGainAtInputRate?: boolean;
|
|
354
354
|
};
|
|
355
|
+
/**
|
|
356
|
+
* Dot-notation spelling of any leaf above, e.g. `'loudness.targetLufs': -20`
|
|
357
|
+
* beside or instead of `loudness: { targetLufs: -20 }`. It is the form the C
|
|
358
|
+
* ABI carries parameters in, so a caller assembling overrides dynamically can
|
|
359
|
+
* emit it directly; the core validates the key and rejects an unknown one.
|
|
360
|
+
* The nested spelling is canonical — prefer it in hand-written code, where it
|
|
361
|
+
* is checked field by field while a dotted key is only checked at run time.
|
|
362
|
+
*/
|
|
363
|
+
[flatKey: `${string}.${string}`]: number | boolean | undefined;
|
|
355
364
|
}
|
|
356
365
|
|
|
357
366
|
/**
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { ValidateOptions } from './validation';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Per-frame voicing decision, one entry per `f0Hz` frame. A truthy or non-zero
|
|
5
|
+
* entry marks the frame voiced. The union covers what the analysis side hands
|
|
6
|
+
* back — `PitchResult.voicedFlag` is a `boolean[]` — as well as the typed and
|
|
7
|
+
* plain numeric arrays a caller may build directly, so a pitch track can be fed
|
|
8
|
+
* straight into pitch correction without a conversion step.
|
|
9
|
+
*/
|
|
10
|
+
export type VoicedFlags =
|
|
11
|
+
| Int32Array
|
|
12
|
+
| Uint8Array
|
|
13
|
+
| Float32Array
|
|
14
|
+
| readonly number[]
|
|
15
|
+
| readonly boolean[];
|
|
16
|
+
|
|
3
17
|
/** Options for `pitchCorrectTimevarying`. All fields are optional. */
|
|
4
18
|
export interface PitchCorrectOptions extends ValidateOptions {
|
|
5
19
|
/** `'midi'` retunes toward `targetMidi`; `'scale'` snaps to the key. Default `'midi'`. */
|
|
@@ -20,8 +34,8 @@ export interface PitchCorrectOptions extends ValidateOptions {
|
|
|
20
34
|
retuneSpeedMs?: number;
|
|
21
35
|
/** Corrections below this are bypassed to preserve vibrato (cents). Default 20. */
|
|
22
36
|
vibratoThresholdCents?: number;
|
|
23
|
-
/** Per-frame voiced flags (
|
|
24
|
-
voiced?:
|
|
37
|
+
/** Per-frame voiced flags (truthy = voiced); omit to treat all frames as voiced. */
|
|
38
|
+
voiced?: VoicedFlags;
|
|
25
39
|
/** Per-frame voicing probability in `[0, 1]`; omit to derive from `voiced`. */
|
|
26
40
|
voicedProb?: Float32Array;
|
|
27
41
|
}
|
package/src/realtime_engine.ts
CHANGED
|
@@ -823,6 +823,15 @@ export class RealtimeEngine {
|
|
|
823
823
|
* Returns `-1` when the track, insert, or name is unknown. (The Python binding
|
|
824
824
|
* raises a `SonareError` for an unknown id where Node/WASM return the `-1`
|
|
825
825
|
* sentinel.)
|
|
826
|
+
*
|
|
827
|
+
* This trio is how a mastering processor gets time-varying automation: the
|
|
828
|
+
* `eq.*`, `dynamics.*`, `saturation.*`, `spectral.*`, `stereo.*`,
|
|
829
|
+
* `maximizer.*` and `multiband.*` processors are all available as strip
|
|
830
|
+
* inserts, so placing one on a strip and resolving its parameter here drives
|
|
831
|
+
* it at audio-block precision, live and offline alike. The whole-signal
|
|
832
|
+
* stages of the offline mastering chain (`repair.*`, `loudness`, and the
|
|
833
|
+
* match stages) have no insert form and no automation id: they buffer the
|
|
834
|
+
* entire signal by construction and do not run on the realtime path.
|
|
826
835
|
*/
|
|
827
836
|
resolveTrackInsertAutomationId(trackId: number, insertIndex: number, paramName: string): number {
|
|
828
837
|
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
@@ -984,6 +993,36 @@ export class RealtimeEngine {
|
|
|
984
993
|
this.native.processPrepared(numFrames);
|
|
985
994
|
}
|
|
986
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Allocates the cue-bus counterpart of {@link prepareChannels}. Needed only
|
|
998
|
+
* when PFL/AFL monitoring must reach a separate output: `processPrepared`
|
|
999
|
+
* folds the cue bus into the program output, while
|
|
1000
|
+
* {@link processPreparedWithMonitor} keeps the two apart. Call once, off the
|
|
1001
|
+
* audio thread, with at least as many channels as `prepareChannels` got.
|
|
1002
|
+
*/
|
|
1003
|
+
prepareMonitorChannels(numChannels: number, maxFrames: number): void {
|
|
1004
|
+
this.native.prepareMonitorChannels(numChannels, maxFrames);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Returns a Float32Array view onto the persistent cue-bus scratch for one
|
|
1009
|
+
* channel (valid for up to `numFrames`). Read it after
|
|
1010
|
+
* {@link processPreparedWithMonitor}. Re-acquire after WASM memory growth.
|
|
1011
|
+
*/
|
|
1012
|
+
getMonitorChannelBuffer(channel: number, numFrames: number): Float32Array {
|
|
1013
|
+
return this.native.getMonitorChannelBuffer(channel, numFrames);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Runs the engine in place over the prepared scratch, writing the cue bus to
|
|
1018
|
+
* the monitor scratch instead of folding it into the program output.
|
|
1019
|
+
* Allocation-free: safe on the AudioWorklet render thread after
|
|
1020
|
+
* `prepareChannels` and `prepareMonitorChannels`.
|
|
1021
|
+
*/
|
|
1022
|
+
processPreparedWithMonitor(numFrames: number): void {
|
|
1023
|
+
this.native.processPreparedWithMonitor(numFrames);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
987
1026
|
processWithMonitor(channels: Float32Array[]): WasmEngineProcessWithMonitorResult {
|
|
988
1027
|
return this.native.processWithMonitor(channels);
|
|
989
1028
|
}
|
package/src/sonare.js.d.ts
CHANGED
|
@@ -1114,6 +1114,9 @@ export interface WasmRealtimeEngine {
|
|
|
1114
1114
|
prepareChannels: (numChannels: number, maxFrames: number) => void;
|
|
1115
1115
|
getChannelBuffer: (channel: number, numFrames: number) => Float32Array;
|
|
1116
1116
|
processPrepared: (numFrames: number) => void;
|
|
1117
|
+
prepareMonitorChannels: (numChannels: number, maxFrames: number) => void;
|
|
1118
|
+
getMonitorChannelBuffer: (channel: number, numFrames: number) => Float32Array;
|
|
1119
|
+
processPreparedWithMonitor: (numFrames: number) => void;
|
|
1117
1120
|
processWithMonitor: (channels: Float32Array[]) => WasmEngineProcessWithMonitorResult;
|
|
1118
1121
|
renderOffline: (channels: Float32Array[], blockSize: number) => Float32Array[];
|
|
1119
1122
|
bounceOffline: (options: WasmEngineBounceOptions) => WasmEngineBounceResult;
|
|
@@ -1328,6 +1331,11 @@ export interface SonareModule {
|
|
|
1328
1331
|
hopLength: number,
|
|
1329
1332
|
) => number;
|
|
1330
1333
|
meteringCrestFactorDb: (samples: Float32Array, sampleRate: number) => number;
|
|
1334
|
+
meteringCrestFactorDbStereo: (
|
|
1335
|
+
left: Float32Array,
|
|
1336
|
+
right: Float32Array,
|
|
1337
|
+
sampleRate: number,
|
|
1338
|
+
) => number;
|
|
1331
1339
|
meteringDcOffset: (samples: Float32Array, sampleRate: number) => number;
|
|
1332
1340
|
meteringTruePeakDb: (
|
|
1333
1341
|
samples: Float32Array,
|
|
@@ -1693,6 +1701,24 @@ export interface SonareModule {
|
|
|
1693
1701
|
sampleRate: number,
|
|
1694
1702
|
platforms: Array<{ name: string; targetLufs: number; ceilingDb: number }>,
|
|
1695
1703
|
) => string;
|
|
1704
|
+
masteringAssistantSuggestStereo: (
|
|
1705
|
+
left: Float32Array,
|
|
1706
|
+
right: Float32Array,
|
|
1707
|
+
sampleRate: number,
|
|
1708
|
+
params: Record<string, number | boolean>,
|
|
1709
|
+
) => string;
|
|
1710
|
+
masteringAudioProfileStereo: (
|
|
1711
|
+
left: Float32Array,
|
|
1712
|
+
right: Float32Array,
|
|
1713
|
+
sampleRate: number,
|
|
1714
|
+
params: Record<string, number | boolean>,
|
|
1715
|
+
) => string;
|
|
1716
|
+
masteringStreamingPreviewStereo: (
|
|
1717
|
+
left: Float32Array,
|
|
1718
|
+
right: Float32Array,
|
|
1719
|
+
sampleRate: number,
|
|
1720
|
+
platforms: Array<{ name: string; targetLufs: number; ceilingDb: number }>,
|
|
1721
|
+
) => string;
|
|
1696
1722
|
masteringRepairDeclick: (
|
|
1697
1723
|
samples: Float32Array,
|
|
1698
1724
|
sampleRate: number,
|
|
@@ -49,6 +49,24 @@ const EQ_PHASE_MODES: Record<string, number> = {
|
|
|
49
49
|
* Call {@link delete} (or use a `try/finally`) to release the underlying WASM
|
|
50
50
|
* object — the embind handle is not garbage-collected automatically.
|
|
51
51
|
*
|
|
52
|
+
* Reachable from the AudioWorklet realm through the `sonare/worklet` entry, but
|
|
53
|
+
* the realtime contract is the caller's to keep:
|
|
54
|
+
*
|
|
55
|
+
* - {@link prepare} builds the processors and allocates. Call it once from a
|
|
56
|
+
* message handler, never from `AudioWorkletProcessor.process()`.
|
|
57
|
+
* - {@link processMono}/{@link processStereo} return fresh arrays. On the render
|
|
58
|
+
* thread, reuse the returned reference for the block rather than retaining it.
|
|
59
|
+
* - An enabled `loudness` stage needs `loudnessStaticGainDb` measured offline,
|
|
60
|
+
* because whole-signal integrated LUFS cannot be measured block by block. Pass
|
|
61
|
+
* `loudnessStaticGainPeakDb` too and the static gain is clamped exactly as the
|
|
62
|
+
* offline chain clamps it, so the live preview matches the render.
|
|
63
|
+
* - {@link flush} output starts {@link latencySamples} samples early; discard
|
|
64
|
+
* that many leading samples when time alignment matters.
|
|
65
|
+
*
|
|
66
|
+
* The chain is a host-side stage, not an engine insert: it does not participate
|
|
67
|
+
* in the engine's PDC or bypass, so latency compensation against other engine
|
|
68
|
+
* outputs is also the caller's.
|
|
69
|
+
*
|
|
52
70
|
* @example
|
|
53
71
|
* ```typescript
|
|
54
72
|
* const chain = new StreamingMasteringChain({ eq: { tiltDb: 1.0 } });
|