@libraz/libsonare 1.7.2 → 1.8.0
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/NOTICE +178 -0
- package/README.md +26 -203
- package/dist/_chain_config.d.ts +14 -0
- package/dist/_chain_config.d.ts.map +1 -0
- package/dist/_effects_common.d.ts +6 -0
- package/dist/_effects_common.d.ts.map +1 -0
- package/dist/_feature_validation.d.ts +8 -0
- package/dist/_feature_validation.d.ts.map +1 -0
- package/dist/_fft_options.d.ts +24 -0
- package/dist/_fft_options.d.ts.map +1 -0
- package/dist/align_take.d.ts +50 -0
- package/dist/align_take.d.ts.map +1 -0
- package/dist/analysis.d.ts +29 -5840
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +873 -722
- package/dist/analysis.js.map +1 -1
- package/dist/analysis_helpers.d.ts +9 -0
- package/dist/analysis_helpers.d.ts.map +1 -0
- package/dist/audio.d.ts +163 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/clip_page_streamer.d.ts +133 -0
- package/dist/clip_page_streamer.d.ts.map +1 -0
- package/dist/codes.d.ts +44 -0
- package/dist/codes.d.ts.map +1 -0
- package/dist/effects_mastering.d.ts +23 -0
- package/dist/effects_mastering.d.ts.map +1 -0
- package/dist/effects_note_ops.d.ts +477 -0
- package/dist/effects_note_ops.d.ts.map +1 -0
- package/dist/effects_percussive.d.ts +185 -0
- package/dist/effects_percussive.d.ts.map +1 -0
- package/dist/effects_separation.d.ts +65 -0
- package/dist/effects_separation.d.ts.map +1 -0
- package/dist/effects_spectral.d.ts +28 -0
- package/dist/effects_spectral.d.ts.map +1 -0
- package/dist/effects_timepitch.d.ts +129 -0
- package/dist/effects_timepitch.d.ts.map +1 -0
- package/dist/effects_voice_change.d.ts +53 -0
- package/dist/effects_voice_change.d.ts.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/feature_core.d.ts +341 -0
- package/dist/feature_core.d.ts.map +1 -0
- package/dist/feature_decompose.d.ts +278 -0
- package/dist/feature_decompose.d.ts.map +1 -0
- package/dist/feature_inverse.d.ts +128 -0
- package/dist/feature_inverse.d.ts.map +1 -0
- package/dist/feature_loudness.d.ts +66 -0
- package/dist/feature_loudness.d.ts.map +1 -0
- package/dist/feature_music.d.ts +307 -0
- package/dist/feature_music.d.ts.map +1 -0
- package/dist/feature_pitch.d.ts +108 -0
- package/dist/feature_pitch.d.ts.map +1 -0
- package/dist/feature_resample.d.ts +16 -0
- package/dist/feature_resample.d.ts.map +1 -0
- package/dist/feature_spectral.d.ts +137 -0
- package/dist/feature_spectral.d.ts.map +1 -0
- package/dist/feature_spectrogram.d.ts +198 -0
- package/dist/feature_spectrogram.d.ts.map +1 -0
- package/dist/features.d.ts +10 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/hrtf/default.shrf +0 -0
- package/dist/index.d.ts +74 -7431
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3758 -1362
- package/dist/index.js.map +1 -1
- package/dist/instrument_types.d.ts +517 -0
- package/dist/instrument_types.d.ts.map +1 -0
- package/dist/live_audio.d.ts +35 -0
- package/dist/live_audio.d.ts.map +1 -0
- package/dist/mastering_chain.d.ts +213 -0
- package/dist/mastering_chain.d.ts.map +1 -0
- package/dist/mastering_core.d.ts +457 -0
- package/dist/mastering_core.d.ts.map +1 -0
- package/dist/mastering_dynamics.d.ts +80 -0
- package/dist/mastering_dynamics.d.ts.map +1 -0
- package/dist/metering.d.ts +287 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/mixer.d.ts +464 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixing_assistant.d.ts +62 -0
- package/dist/mixing_assistant.d.ts.map +1 -0
- package/dist/mixing_oneshot.d.ts +40 -0
- package/dist/mixing_oneshot.d.ts.map +1 -0
- package/dist/module_state.d.ts +15 -0
- package/dist/module_state.d.ts.map +1 -0
- package/dist/opfs_clip_pages.d.ts +28 -0
- package/dist/opfs_clip_pages.d.ts.map +1 -0
- package/dist/playback_renderer.d.ts +128 -0
- package/dist/playback_renderer.d.ts.map +1 -0
- package/dist/polyphony.d.ts +202 -0
- package/dist/polyphony.d.ts.map +1 -0
- package/dist/project.d.ts +8 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project_class.d.ts +562 -0
- package/dist/project_class.d.ts.map +1 -0
- package/dist/project_internal.d.ts +194 -0
- package/dist/project_internal.d.ts.map +1 -0
- package/dist/project_synth.d.ts +74 -0
- package/dist/project_synth.d.ts.map +1 -0
- package/dist/project_types.d.ts +652 -0
- package/dist/project_types.d.ts.map +1 -0
- package/dist/public_types.d.ts +185 -0
- package/dist/public_types.d.ts.map +1 -0
- package/dist/public_types_acoustic.d.ts +215 -0
- package/dist/public_types_acoustic.d.ts.map +1 -0
- package/dist/public_types_mastering.d.ts +510 -0
- package/dist/public_types_mastering.d.ts.map +1 -0
- package/dist/public_types_mixing.d.ts +436 -0
- package/dist/public_types_mixing.d.ts.map +1 -0
- package/dist/public_types_music.d.ts +619 -0
- package/dist/public_types_music.d.ts.map +1 -0
- package/dist/public_types_playback.d.ts +164 -0
- package/dist/public_types_playback.d.ts.map +1 -0
- package/dist/public_types_realtime.d.ts +174 -0
- package/dist/public_types_realtime.d.ts.map +1 -0
- package/dist/public_types_repair.d.ts +424 -0
- package/dist/public_types_repair.d.ts.map +1 -0
- package/dist/public_types_spectral.d.ts +697 -0
- package/dist/public_types_spectral.d.ts.map +1 -0
- package/dist/quick_analysis.d.ts +445 -0
- package/dist/quick_analysis.d.ts.map +1 -0
- package/dist/realtime_engine.d.ts +850 -0
- package/dist/realtime_engine.d.ts.map +1 -0
- package/dist/realtime_voice_changer.d.ts +158 -0
- package/dist/realtime_voice_changer.d.ts.map +1 -0
- package/dist/repair_dereverb.d.ts +187 -0
- package/dist/repair_dereverb.d.ts.map +1 -0
- package/dist/repair_impulsive.d.ts +186 -0
- package/dist/repair_impulsive.d.ts.map +1 -0
- package/dist/repair_noise.d.ts +239 -0
- package/dist/repair_noise.d.ts.map +1 -0
- package/dist/repair_trim.d.ts +123 -0
- package/dist/repair_trim.d.ts.map +1 -0
- package/dist/sample_bank.d.ts +84 -0
- package/dist/sample_bank.d.ts.map +1 -0
- package/dist/scale.d.ts +10 -0
- package/dist/scale.d.ts.map +1 -0
- package/dist/schemas/mixer-scene.schema.json +393 -0
- package/dist/schemas/playback-renderer-config.schema.json +392 -0
- package/dist/sonare-analysis.d.ts +8 -0
- package/dist/sonare-analysis.js +2 -2
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.d.ts +3909 -0
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/stream_analyzer.d.ts +163 -0
- package/dist/stream_analyzer.d.ts.map +1 -0
- package/dist/stream_types.d.ts +214 -0
- package/dist/stream_types.d.ts.map +1 -0
- package/dist/streaming_mixing.d.ts +6 -0
- package/dist/streaming_mixing.d.ts.map +1 -0
- package/dist/streaming_processors.d.ts +335 -0
- package/dist/streaming_processors.d.ts.map +1 -0
- package/dist/transcribe.d.ts +77 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/validation.d.ts +140 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/web_midi.d.ts +77 -0
- package/dist/web_midi.d.ts.map +1 -0
- package/dist/worker.d.ts +5 -48
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +94 -41
- package/dist/worker.js.map +1 -1
- package/dist/worker_client.d.ts +96 -0
- package/dist/worker_client.d.ts.map +1 -0
- package/dist/worker_protocol.d.ts +43 -0
- package/dist/worker_protocol.d.ts.map +1 -0
- package/dist/worklet/audio_types.d.ts +21 -0
- package/dist/worklet/audio_types.d.ts.map +1 -0
- package/dist/worklet/engine-automation.d.ts +29 -0
- package/dist/worklet/engine-automation.d.ts.map +1 -0
- package/dist/worklet/engine-capture-facade.d.ts +35 -0
- package/dist/worklet/engine-capture-facade.d.ts.map +1 -0
- package/dist/worklet/engine-clips.d.ts +23 -0
- package/dist/worklet/engine-clips.d.ts.map +1 -0
- package/dist/worklet/engine-markers.d.ts +40 -0
- package/dist/worklet/engine-markers.d.ts.map +1 -0
- package/dist/worklet/engine-mixer-facade.d.ts +128 -0
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
- package/dist/worklet/engine-node.d.ts +81 -0
- package/dist/worklet/engine-node.d.ts.map +1 -0
- package/dist/worklet/engine-offline.d.ts +81 -0
- package/dist/worklet/engine-offline.d.ts.map +1 -0
- package/dist/worklet/engine-options.d.ts +12 -0
- package/dist/worklet/engine-options.d.ts.map +1 -0
- package/dist/worklet/engine-parameter-facade.d.ts +106 -0
- package/dist/worklet/engine-parameter-facade.d.ts.map +1 -0
- package/dist/worklet/engine-processor.d.ts +68 -0
- package/dist/worklet/engine-processor.d.ts.map +1 -0
- package/dist/worklet/engine-register.d.ts +2 -0
- package/dist/worklet/engine-register.d.ts.map +1 -0
- package/dist/worklet/engine-strips.d.ts +73 -0
- package/dist/worklet/engine-strips.d.ts.map +1 -0
- package/dist/worklet/engine-sync.d.ts +37 -0
- package/dist/worklet/engine-sync.d.ts.map +1 -0
- package/dist/worklet/engine-tempo-facade.d.ts +48 -0
- package/dist/worklet/engine-tempo-facade.d.ts.map +1 -0
- package/dist/worklet/engine.d.ts +397 -0
- package/dist/worklet/engine.d.ts.map +1 -0
- package/dist/worklet/guards.d.ts +47 -0
- package/dist/worklet/guards.d.ts.map +1 -0
- package/dist/worklet/messages.d.ts +710 -0
- package/dist/worklet/messages.d.ts.map +1 -0
- package/dist/worklet/mixer-processor.d.ts +46 -0
- package/dist/worklet/mixer-processor.d.ts.map +1 -0
- package/dist/worklet/playback-processor.d.ts +62 -0
- package/dist/worklet/playback-processor.d.ts.map +1 -0
- package/dist/worklet/protocol.d.ts +323 -0
- package/dist/worklet/protocol.d.ts.map +1 -0
- package/dist/worklet/voice-changer-processor.d.ts +41 -0
- package/dist/worklet/voice-changer-processor.d.ts.map +1 -0
- package/dist/worklet.d.ts +16 -2515
- package/dist/worklet.d.ts.map +1 -0
- package/dist/worklet.js +2763 -501
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -12
- package/src/_effects_common.ts +17 -0
- package/src/_feature_validation.ts +34 -0
- package/src/_fft_options.ts +39 -0
- package/src/align_take.ts +64 -0
- package/src/analysis.ts +56 -3
- package/src/analysis_helpers.ts +7 -0
- package/src/audio.ts +106 -3
- package/src/codes.ts +39 -2
- package/src/effects_mastering.ts +97 -22
- package/src/effects_note_ops.ts +635 -0
- package/src/effects_percussive.ts +217 -0
- package/src/effects_separation.ts +150 -0
- package/src/effects_spectral.ts +60 -0
- package/src/effects_timepitch.ts +377 -0
- package/src/errors.ts +23 -1
- package/src/feature_core.ts +127 -2
- package/src/feature_decompose.ts +633 -0
- package/src/feature_inverse.ts +454 -0
- package/src/feature_loudness.ts +125 -0
- package/src/feature_music.ts +107 -14
- package/src/feature_pitch.ts +96 -1
- package/src/feature_spectral.ts +16 -611
- package/src/feature_spectrogram.ts +63 -450
- package/src/features.ts +36 -22
- package/src/index.ts +282 -30
- package/src/instrument_types.ts +645 -0
- package/src/live_audio.ts +27 -1
- package/src/mastering_chain.ts +184 -0
- package/src/mastering_core.ts +346 -32
- package/src/mastering_dynamics.ts +22 -11
- package/src/metering.ts +67 -24
- package/src/mixer.ts +212 -3
- package/src/mixing_assistant.ts +138 -0
- package/src/mixing_oneshot.ts +10 -5
- package/src/module_state.ts +24 -2
- package/src/playback_renderer.ts +252 -0
- package/src/polyphony.ts +279 -0
- package/src/project.ts +61 -24
- package/src/project_class.ts +450 -27
- package/src/project_internal.ts +149 -42
- package/src/project_synth.ts +67 -1
- package/src/project_types.ts +268 -270
- package/src/public_types.ts +122 -3
- package/src/public_types_acoustic.ts +112 -3
- package/src/public_types_mastering.ts +254 -73
- package/src/public_types_mixing.ts +363 -1
- package/src/public_types_music.ts +312 -2
- package/src/public_types_playback.ts +195 -0
- package/src/public_types_realtime.ts +39 -7
- package/src/public_types_repair.ts +446 -0
- package/src/public_types_spectral.ts +487 -1
- package/src/quick_analysis.ts +203 -26
- package/src/realtime_engine.ts +711 -28
- package/src/realtime_voice_changer.ts +55 -1
- package/src/repair_dereverb.ts +299 -0
- package/src/repair_impulsive.ts +395 -0
- package/src/repair_noise.ts +425 -0
- package/src/repair_trim.ts +226 -0
- package/src/sample_bank.ts +113 -0
- package/src/sonare.js.d.ts +1122 -30
- package/src/stream_analyzer.ts +36 -4
- package/src/stream_types.ts +37 -0
- package/src/streaming_mixing.ts +1 -1
- package/src/streaming_processors.ts +194 -10
- package/src/transcribe.ts +89 -0
- package/src/validation.ts +285 -11
- package/src/web_midi.ts +1 -6
- package/src/worker.ts +18 -2
- package/src/worklet/audio_types.ts +37 -0
- package/src/worklet/engine-mixer-facade.ts +444 -10
- package/src/worklet/engine-node.ts +78 -26
- package/src/worklet/engine-offline.ts +14 -8
- package/src/worklet/engine-parameter-facade.ts +21 -0
- package/src/worklet/engine-processor.ts +272 -83
- package/src/worklet/engine-register.ts +32 -18
- package/src/worklet/engine-strips.ts +280 -9
- package/src/worklet/engine-sync.ts +14 -6
- package/src/worklet/engine.ts +365 -31
- package/src/worklet/guards.ts +140 -44
- package/src/worklet/messages.ts +227 -2
- package/src/worklet/mixer-processor.ts +109 -47
- package/src/worklet/playback-processor.ts +300 -0
- package/src/worklet/protocol.ts +53 -3
- package/src/worklet/voice-changer-processor.ts +17 -11
- package/src/worklet.ts +17 -0
- package/src/effects_transform.ts +0 -718
- package/src/mastering_repair.ts +0 -273
|
@@ -72,8 +72,17 @@ export interface MasteringDynamicsTransientShaperRequest extends TransientShaper
|
|
|
72
72
|
sampleRate: number;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/**
|
|
76
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Result envelope returned by offline mastering dynamics processors.
|
|
77
|
+
*
|
|
78
|
+
* Named for what it is rather than for the module it lives in. {@link
|
|
79
|
+
* DynamicsResult} is the *analysis* shape on every binding, so having that one
|
|
80
|
+
* identifier mean two disjoint field lists across the Node and WASM packages
|
|
81
|
+
* made a shared TypeScript module type-check against one and fail against the
|
|
82
|
+
* other — with the identifier resolving either way, so only the member list
|
|
83
|
+
* gave it away.
|
|
84
|
+
*/
|
|
85
|
+
export interface DynamicsProcessorResult {
|
|
77
86
|
samples: Float32Array;
|
|
78
87
|
latencySamples: number;
|
|
79
88
|
}
|
|
@@ -87,17 +96,17 @@ const COMPRESSOR_DETECTOR_MAP: Record<CompressorDetector, number> = {
|
|
|
87
96
|
/** Offline feed-forward compressor (soft knee, optional auto-makeup / sidechain HPF). */
|
|
88
97
|
export function masteringDynamicsCompressor(
|
|
89
98
|
request: MasteringDynamicsCompressorRequest,
|
|
90
|
-
):
|
|
99
|
+
): DynamicsProcessorResult;
|
|
91
100
|
export function masteringDynamicsCompressor(
|
|
92
101
|
samples: Float32Array,
|
|
93
102
|
sampleRate: number,
|
|
94
103
|
options?: CompressorOptions,
|
|
95
|
-
):
|
|
104
|
+
): DynamicsProcessorResult;
|
|
96
105
|
export function masteringDynamicsCompressor(
|
|
97
106
|
samples: Float32Array | MasteringDynamicsCompressorRequest,
|
|
98
107
|
sampleRate?: number,
|
|
99
108
|
options: CompressorOptions = {},
|
|
100
|
-
):
|
|
109
|
+
): DynamicsProcessorResult {
|
|
101
110
|
const request =
|
|
102
111
|
samples instanceof Float32Array
|
|
103
112
|
? { samples, sampleRate: sampleRate as number, ...options }
|
|
@@ -121,17 +130,19 @@ export function masteringDynamicsCompressor(
|
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
/** Offline noise gate (hysteresis, hold, optional key HPF). */
|
|
124
|
-
export function masteringDynamicsGate(
|
|
133
|
+
export function masteringDynamicsGate(
|
|
134
|
+
request: MasteringDynamicsGateRequest,
|
|
135
|
+
): DynamicsProcessorResult;
|
|
125
136
|
export function masteringDynamicsGate(
|
|
126
137
|
samples: Float32Array,
|
|
127
138
|
sampleRate: number,
|
|
128
139
|
options?: GateOptions,
|
|
129
|
-
):
|
|
140
|
+
): DynamicsProcessorResult;
|
|
130
141
|
export function masteringDynamicsGate(
|
|
131
142
|
samples: Float32Array | MasteringDynamicsGateRequest,
|
|
132
143
|
sampleRate?: number,
|
|
133
144
|
options: GateOptions = {},
|
|
134
|
-
):
|
|
145
|
+
): DynamicsProcessorResult {
|
|
135
146
|
const request =
|
|
136
147
|
samples instanceof Float32Array
|
|
137
148
|
? { samples, sampleRate: sampleRate as number, ...options }
|
|
@@ -143,17 +154,17 @@ export function masteringDynamicsGate(
|
|
|
143
154
|
/** Offline transient shaper (envelope-difference attack/sustain control). */
|
|
144
155
|
export function masteringDynamicsTransientShaper(
|
|
145
156
|
request: MasteringDynamicsTransientShaperRequest,
|
|
146
|
-
):
|
|
157
|
+
): DynamicsProcessorResult;
|
|
147
158
|
export function masteringDynamicsTransientShaper(
|
|
148
159
|
samples: Float32Array,
|
|
149
160
|
sampleRate: number,
|
|
150
161
|
options?: TransientShaperOptions,
|
|
151
|
-
):
|
|
162
|
+
): DynamicsProcessorResult;
|
|
152
163
|
export function masteringDynamicsTransientShaper(
|
|
153
164
|
samples: Float32Array | MasteringDynamicsTransientShaperRequest,
|
|
154
165
|
sampleRate?: number,
|
|
155
166
|
options: TransientShaperOptions = {},
|
|
156
|
-
):
|
|
167
|
+
): DynamicsProcessorResult {
|
|
157
168
|
const request =
|
|
158
169
|
samples instanceof Float32Array
|
|
159
170
|
? { samples, sampleRate: sampleRate as number, ...options }
|
package/src/metering.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { ErrorCode, SonareError } from './errors';
|
|
2
2
|
import { getSonareModule } from './module_state';
|
|
3
3
|
import type { ValidateOptions } from './validation';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
assertInterleavedSamples,
|
|
6
|
+
assertNonNegativeInteger,
|
|
7
|
+
assertPositiveInteger,
|
|
8
|
+
assertSamples,
|
|
9
|
+
assertSamplesInWindow,
|
|
10
|
+
} from './validation';
|
|
11
|
+
|
|
12
|
+
// The FFT size the library falls back to when `nFft` is 0 or omitted. Mirrored
|
|
13
|
+
// here so the windowed pre-scan covers exactly the span the call will read; a
|
|
14
|
+
// test pins it against the `nFft` the library reports back for a 0 request.
|
|
15
|
+
const DEFAULT_SPECTRUM_N_FFT = 2048;
|
|
5
16
|
|
|
6
17
|
/**
|
|
7
18
|
* Validates a true-peak oversample factor: `0` (meaning "use the default 4") or
|
|
@@ -267,9 +278,7 @@ export function meteringDetectClipping(
|
|
|
267
278
|
const request = samples instanceof Float32Array ? { samples, sampleRate, ...options } : samples;
|
|
268
279
|
assertSamples('meteringDetectClipping', request.samples, request.validate !== false);
|
|
269
280
|
const minRegionSamples = request.minRegionSamples ?? 1;
|
|
270
|
-
|
|
271
|
-
throw new RangeError('meteringDetectClipping: minRegionSamples must be a non-negative integer');
|
|
272
|
-
}
|
|
281
|
+
assertNonNegativeInteger('meteringDetectClipping', minRegionSamples, 'minRegionSamples');
|
|
273
282
|
return requireModule().meteringDetectClipping(
|
|
274
283
|
request.samples,
|
|
275
284
|
request.sampleRate ?? 22050,
|
|
@@ -447,10 +456,14 @@ export function meteringStereoCorrelation(
|
|
|
447
456
|
}
|
|
448
457
|
|
|
449
458
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
459
|
+
* Stereo width as `sqrt(side_energy / mid_energy)` in `[0, +Infinity)`: the
|
|
460
|
+
* side/mid RMS *amplitude* ratio, not the energy ratio. 0 = pure mono, ~1 =
|
|
461
|
+
* wide stereo, larger = increasingly decorrelated / out-of-phase. The value is
|
|
462
|
+
* unbounded and returns `Infinity` when the mid channel is silent (a
|
|
463
|
+
* mono-collapsed / fully out-of-phase signal).
|
|
464
|
+
*
|
|
465
|
+
* Convert to dB with `20 * Math.log10(value)`; `10 * Math.log10` would
|
|
466
|
+
* understate the true energy ratio by half.
|
|
454
467
|
*/
|
|
455
468
|
export function meteringStereoWidth(request: MeteringStereoRequest): number;
|
|
456
469
|
export function meteringStereoWidth(
|
|
@@ -651,6 +664,12 @@ export function meteringSpectrum(
|
|
|
651
664
|
* `nFft`-length FFT), for spectrum-analyzer "moment" snapshots that must not be
|
|
652
665
|
* time-averaged like {@link meteringSpectrum}. The analysis frame spans
|
|
653
666
|
* `[frameOffset, frameOffset + nFft)`; samples past the end are zero-padded.
|
|
667
|
+
*
|
|
668
|
+
* The frame is also the only span validated: a non-finite sample inside it is
|
|
669
|
+
* rejected, while one outside it neither reaches the FFT nor refuses the call.
|
|
670
|
+
* The emptiness and `sampleRate` checks still cover the whole buffer. Cost per
|
|
671
|
+
* call is therefore set by `nFft` rather than by the length of the buffer, so an
|
|
672
|
+
* analyzer may poll a long recording frame by frame.
|
|
654
673
|
*/
|
|
655
674
|
export function meteringSpectrumFrame(request: MeteringSpectrumFrameRequest): SpectrumReport;
|
|
656
675
|
export function meteringSpectrumFrame(
|
|
@@ -667,7 +686,14 @@ export function meteringSpectrumFrame(
|
|
|
667
686
|
): SpectrumReport {
|
|
668
687
|
const request =
|
|
669
688
|
samples instanceof Float32Array ? { samples, sampleRate, frameOffset, ...options } : samples;
|
|
670
|
-
|
|
689
|
+
const nFft = request.nFft ?? 0;
|
|
690
|
+
assertSamplesInWindow(
|
|
691
|
+
'meteringSpectrumFrame',
|
|
692
|
+
request.samples,
|
|
693
|
+
request.validate !== false,
|
|
694
|
+
request.frameOffset ?? 0,
|
|
695
|
+
nFft > 0 ? nFft : DEFAULT_SPECTRUM_N_FFT,
|
|
696
|
+
);
|
|
671
697
|
return requireModule().meteringSpectrumFrame(
|
|
672
698
|
request.samples,
|
|
673
699
|
request.sampleRate ?? 22050,
|
|
@@ -676,7 +702,14 @@ export function meteringSpectrumFrame(
|
|
|
676
702
|
);
|
|
677
703
|
}
|
|
678
704
|
|
|
679
|
-
/**
|
|
705
|
+
/**
|
|
706
|
+
* Compute per-channel min/max waveform buckets from interleaved audio.
|
|
707
|
+
*
|
|
708
|
+
* A non-finite sample is rejected rather than skipped, and `{ validate: false }`
|
|
709
|
+
* does not change that — it only skips the JS pre-scan that names the offending
|
|
710
|
+
* index. A bucket whose samples are not finite has no min/max to report, and the
|
|
711
|
+
* `0`/`0` it would otherwise carry is what a waveform display draws as silence.
|
|
712
|
+
*/
|
|
680
713
|
export function waveformPeaks(request: WaveformPeaksRequest): WaveformPeaksReport;
|
|
681
714
|
export function waveformPeaks(
|
|
682
715
|
samples: Float32Array,
|
|
@@ -692,18 +725,23 @@ export function waveformPeaks(
|
|
|
692
725
|
samples instanceof Float32Array
|
|
693
726
|
? { samples, channels: channels as number, ...options }
|
|
694
727
|
: samples;
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
728
|
+
assertInterleavedSamples(
|
|
729
|
+
'waveformPeaks',
|
|
730
|
+
request.samples,
|
|
731
|
+
request.channels,
|
|
732
|
+
request.validate !== false,
|
|
733
|
+
);
|
|
699
734
|
const samplesPerBucket = request.samplesPerBucket ?? 512;
|
|
700
|
-
|
|
701
|
-
throw new RangeError('waveformPeaks: samplesPerBucket must be > 0');
|
|
702
|
-
}
|
|
735
|
+
assertPositiveInteger('waveformPeaks', samplesPerBucket, 'samplesPerBucket');
|
|
703
736
|
return requireModule().waveformPeaks(request.samples, request.channels, samplesPerBucket);
|
|
704
737
|
}
|
|
705
738
|
|
|
706
|
-
/**
|
|
739
|
+
/**
|
|
740
|
+
* Compute waveform peak buckets for several zoom levels.
|
|
741
|
+
*
|
|
742
|
+
* Shares {@link waveformPeaks}' bucket kernel, so a non-finite sample is
|
|
743
|
+
* rejected here on the same rule.
|
|
744
|
+
*/
|
|
707
745
|
export function waveformPeakPyramid(request: WaveformPeakPyramidRequest): WaveformPeaksReport[];
|
|
708
746
|
export function waveformPeakPyramid(
|
|
709
747
|
samples: Float32Array,
|
|
@@ -719,13 +757,18 @@ export function waveformPeakPyramid(
|
|
|
719
757
|
samples instanceof Float32Array
|
|
720
758
|
? { samples, channels: channels as number, ...options }
|
|
721
759
|
: samples;
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
760
|
+
assertInterleavedSamples(
|
|
761
|
+
'waveformPeakPyramid',
|
|
762
|
+
request.samples,
|
|
763
|
+
request.channels,
|
|
764
|
+
request.validate !== false,
|
|
765
|
+
);
|
|
726
766
|
const levels = request.samplesPerBucketLevels ?? [512, 1024, 2048, 4096];
|
|
727
|
-
if (levels.length === 0
|
|
728
|
-
throw new RangeError('waveformPeakPyramid: samplesPerBucketLevels must be
|
|
767
|
+
if (levels.length === 0) {
|
|
768
|
+
throw new RangeError('waveformPeakPyramid: samplesPerBucketLevels must not be empty');
|
|
729
769
|
}
|
|
770
|
+
levels.forEach((level, index) => {
|
|
771
|
+
assertPositiveInteger('waveformPeakPyramid', level, `samplesPerBucketLevels[${index}]`);
|
|
772
|
+
});
|
|
730
773
|
return requireModule().waveformPeakPyramid(request.samples, request.channels, levels);
|
|
731
774
|
}
|
package/src/mixer.ts
CHANGED
|
@@ -18,6 +18,49 @@ import type {
|
|
|
18
18
|
SurroundPan,
|
|
19
19
|
} from './public_types';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* One master-output meter reading. All dB fields are finite and floored at
|
|
23
|
+
* -120; `truePeakDb*` is an inter-sample peak from the ITU-R BS.1770-4
|
|
24
|
+
* polyphase reconstruction at 4x, not a sample peak. That reconstruction is a
|
|
25
|
+
* streaming measurement: its centered stencil needs a few future samples a
|
|
26
|
+
* realtime path does not have, so each block's last samples read marginally low
|
|
27
|
+
* (about 0.1 dB across 64..8192-sample blocks on a near-Nyquist tone, always
|
|
28
|
+
* under-reading). Use `meteringTruePeakDb` over the whole signal for an exact
|
|
29
|
+
* dBTP number.
|
|
30
|
+
*/
|
|
31
|
+
export interface MixerMeterSnapshot {
|
|
32
|
+
peakDbL: number;
|
|
33
|
+
peakDbR: number;
|
|
34
|
+
rmsDbL: number;
|
|
35
|
+
rmsDbR: number;
|
|
36
|
+
correlation: number;
|
|
37
|
+
truePeakDbL: number;
|
|
38
|
+
truePeakDbR: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Meter configuration for a strip added with {@link Mixer.addStrip}.
|
|
43
|
+
*
|
|
44
|
+
* The field names and defaults are the scene document's `strips[].metering`
|
|
45
|
+
* object, so a strip added imperatively and one declared in a scene describe the
|
|
46
|
+
* same thing. A strip's meters size their buffers when the strip is built, so
|
|
47
|
+
* this is the only place the configuration can be chosen — there is no setter.
|
|
48
|
+
* A full meter costs about 646 KB at 48 kHz and a strip carries two of them.
|
|
49
|
+
*/
|
|
50
|
+
export interface StripMeteringOptions {
|
|
51
|
+
/** Both meters; `false` drops them (about 145 KB for the strip instead of 1.4 MB). Default `true`. */
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
/** LUFS measurement; `false` takes one meter to about 83 KB. Default `true`. */
|
|
54
|
+
lufs?: boolean;
|
|
55
|
+
/** Inter-sample (true) peak measurement. Default `true`. */
|
|
56
|
+
truePeak?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Requested true-peak oversampling factor in `[0, 16]`; the meter resolves it
|
|
59
|
+
* to the nearest of 2x / 4x / 8x. `0` selects the library default (4x).
|
|
60
|
+
*/
|
|
61
|
+
truePeakOversample?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
21
64
|
export interface MixerRealtimeBuffer {
|
|
22
65
|
leftInputs: Float32Array[];
|
|
23
66
|
rightInputs: Float32Array[];
|
|
@@ -45,7 +88,7 @@ export interface MixerRealtimeBuffer {
|
|
|
45
88
|
*
|
|
46
89
|
* @example
|
|
47
90
|
* ```typescript
|
|
48
|
-
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('
|
|
91
|
+
* const mixer = Mixer.fromSceneJson(mixingScenePresetJson('vocalReverbSend'), 48000, 512);
|
|
49
92
|
* try {
|
|
50
93
|
* const out = mixer.processStereo([stripL], [stripR]);
|
|
51
94
|
* } finally {
|
|
@@ -55,6 +98,7 @@ export interface MixerRealtimeBuffer {
|
|
|
55
98
|
*/
|
|
56
99
|
export class Mixer {
|
|
57
100
|
private mixer: import('./sonare.js').WasmMixer;
|
|
101
|
+
private released = false;
|
|
58
102
|
private readonly blockSize: number;
|
|
59
103
|
|
|
60
104
|
private constructor(mixer: import('./sonare.js').WasmMixer, blockSize: number) {
|
|
@@ -65,6 +109,13 @@ export class Mixer {
|
|
|
65
109
|
/**
|
|
66
110
|
* Build a mixer from a scene JSON string.
|
|
67
111
|
*
|
|
112
|
+
* A strip's meters are sized when the strip is built, so this is where their
|
|
113
|
+
* configuration is chosen: an optional `metering` object on the strip
|
|
114
|
+
* (`enabled` / `lufs` / `truePeak` / `truePeakOversample`) selects it, and
|
|
115
|
+
* leaving it out keeps the full default (LUFS + true peak at 4x, about 1.4 MB
|
|
116
|
+
* per strip at 48 kHz). `{"enabled": false}` drops both meters for a strip
|
|
117
|
+
* whose snapshots are never read.
|
|
118
|
+
*
|
|
68
119
|
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
69
120
|
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
70
121
|
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
@@ -185,6 +236,60 @@ export class Mixer {
|
|
|
185
236
|
};
|
|
186
237
|
}
|
|
187
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Turn the master-output meter on or off.
|
|
241
|
+
*
|
|
242
|
+
* While on, every {@link MixerRealtimeBuffer.process} call meters the stereo
|
|
243
|
+
* master it just produced, so a caller reads {@link meterSnapshot} instead of
|
|
244
|
+
* copying the output and measuring it again. `truePeakDb*` is an inter-sample
|
|
245
|
+
* peak taken after oversampling (ITU-R BS.1770-4 Annex 2 requires at least
|
|
246
|
+
* 4x), which is a different and higher quantity than the sample peak.
|
|
247
|
+
*
|
|
248
|
+
* Enabling resets the meter, so a reading never mixes in audio from a period
|
|
249
|
+
* when metering was off.
|
|
250
|
+
*
|
|
251
|
+
* @param enabled - Whether to meter the master output.
|
|
252
|
+
* @param truePeakOversample - 0 (= 4x) or a power of two in [1, 16].
|
|
253
|
+
*/
|
|
254
|
+
configureMeter(enabled: boolean, truePeakOversample = 4): void {
|
|
255
|
+
this.mixer.configureMeter(enabled, truePeakOversample);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Latest master-output meter reading, describing the most recently metered
|
|
260
|
+
* block. All dB fields are finite and floored at -120.
|
|
261
|
+
*
|
|
262
|
+
* @throws When the meter has never been enabled.
|
|
263
|
+
*/
|
|
264
|
+
meterSnapshot(): MixerMeterSnapshot {
|
|
265
|
+
return this.mixer.meterSnapshot();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Latch the latest meter reading into the mixer's internal scratch so
|
|
270
|
+
* {@link meterScratchValue} can read it back one number at a time.
|
|
271
|
+
*
|
|
272
|
+
* This is the allocation-free form of {@link meterSnapshot}, for an audio
|
|
273
|
+
* render callback that must not create a JS object per interval. It returns
|
|
274
|
+
* `false` instead of throwing when the meter has never been enabled.
|
|
275
|
+
*
|
|
276
|
+
* @returns Whether a reading was latched.
|
|
277
|
+
*/
|
|
278
|
+
latchMeterSnapshot(): boolean {
|
|
279
|
+
return this.mixer.latchMeterSnapshot();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Read one field of the snapshot latched by {@link latchMeterSnapshot}.
|
|
284
|
+
*
|
|
285
|
+
* @param field - `0` peakDbL, `1` peakDbR, `2` rmsDbL, `3` rmsDbR,
|
|
286
|
+
* `4` correlation, `5` truePeakDbL, `6` truePeakDbR. Any other index
|
|
287
|
+
* reads `0`.
|
|
288
|
+
*/
|
|
289
|
+
meterScratchValue(field: number): number {
|
|
290
|
+
return this.mixer.meterScratchValue(field);
|
|
291
|
+
}
|
|
292
|
+
|
|
188
293
|
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
189
294
|
stripCount(): number {
|
|
190
295
|
return this.mixer.stripCount();
|
|
@@ -232,6 +337,18 @@ export class Mixer {
|
|
|
232
337
|
return index < 0 ? null : index;
|
|
233
338
|
}
|
|
234
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Add a channel strip to the mixer topology. `metering` configures the strip's
|
|
342
|
+
* pre/post taps; omitting it keeps the full default (LUFS + true peak at 4x,
|
|
343
|
+
* about 1.4 MB per strip at 48 kHz). Marks the routing graph dirty; call
|
|
344
|
+
* {@link compile} (or {@link processStereo}) to rebuild.
|
|
345
|
+
*
|
|
346
|
+
* @throws If the id is already taken, or `truePeakOversample` is outside `[0, 16]`
|
|
347
|
+
*/
|
|
348
|
+
addStrip(id: string, metering: StripMeteringOptions = {}): void {
|
|
349
|
+
this.mixer.addStrip(id, metering);
|
|
350
|
+
}
|
|
351
|
+
|
|
235
352
|
/**
|
|
236
353
|
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
237
354
|
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
@@ -310,6 +427,31 @@ export class Mixer {
|
|
|
310
427
|
this.mixer.setWidth(stripIndex, width);
|
|
311
428
|
}
|
|
312
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Snap the strip's input-trim, fader, pan and width smoothers to the values
|
|
432
|
+
* already set on it, so the next processed block opens at those values
|
|
433
|
+
* instead of gliding to them over the smoothing window (~5 ms).
|
|
434
|
+
*
|
|
435
|
+
* Call it after configuring a strip and before rendering a finite buffer: a
|
|
436
|
+
* strip is smoothed for a live fader, and an offline render that does not
|
|
437
|
+
* settle carries that glide as a level and image sweep across the head of
|
|
438
|
+
* its output. Unlike a reset it clears nothing — automation, meters and
|
|
439
|
+
* insert state are untouched.
|
|
440
|
+
*
|
|
441
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```typescript
|
|
445
|
+
* mixer.setFaderDb(0, -3);
|
|
446
|
+
* mixer.setPan(0, 0.3);
|
|
447
|
+
* mixer.settle(0);
|
|
448
|
+
* const { left, right } = mixer.processStereo([dryLeft], [dryRight]);
|
|
449
|
+
* ```
|
|
450
|
+
*/
|
|
451
|
+
settle(stripIndex: number): void {
|
|
452
|
+
this.mixer.settle(stripIndex);
|
|
453
|
+
}
|
|
454
|
+
|
|
313
455
|
/** Set the strip's mute state. */
|
|
314
456
|
setMuted(stripIndex: number, muted: boolean): void {
|
|
315
457
|
this.mixer.setMuted(stripIndex, muted);
|
|
@@ -361,7 +503,10 @@ export class Mixer {
|
|
|
361
503
|
|
|
362
504
|
/**
|
|
363
505
|
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
364
|
-
*
|
|
506
|
+
*
|
|
507
|
+
* Applied when the engine's track mixer renders this strip's lane into a
|
|
508
|
+
* destination with more than two channels. This stereo-only mixer's own
|
|
509
|
+
* block entry points ignore it.
|
|
365
510
|
*/
|
|
366
511
|
setSurroundPan(stripIndex: number, pan: SurroundPan): void {
|
|
367
512
|
this.mixer.setSurroundPan(stripIndex, pan);
|
|
@@ -440,6 +585,61 @@ export class Mixer {
|
|
|
440
585
|
return this.mixer.busMeter(busId);
|
|
441
586
|
}
|
|
442
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Number of blocks in which the strip discarded recursive state because a
|
|
590
|
+
* non-finite value had reached it.
|
|
591
|
+
*
|
|
592
|
+
* Advisory telemetry, and the only thing that separates a degraded strip
|
|
593
|
+
* from a clean one. A discard returns the affected state to its
|
|
594
|
+
* post-reset value, so the strip recovers in silence and the output stays
|
|
595
|
+
* finite and in range while carrying samples unrelated to the input;
|
|
596
|
+
* nothing else reports that this happened.
|
|
597
|
+
*
|
|
598
|
+
* The count covers the strip's own state, its EQ, every insert it owns and
|
|
599
|
+
* both of its meters. None of those is separately addressable here, so a
|
|
600
|
+
* discard inside one is observable only through this number -- and a
|
|
601
|
+
* meter that loses its loudness window then reports the floor, which is
|
|
602
|
+
* exactly what a genuinely silent strip reports, so nothing else
|
|
603
|
+
* distinguishes the two.
|
|
604
|
+
*
|
|
605
|
+
* A meter's own discard lags by one block: it checks its loudness state at
|
|
606
|
+
* the top of a block, before consuming that block's samples, so the block
|
|
607
|
+
* that corrupts it is not the block the count moves on -- read this again
|
|
608
|
+
* after one more block has processed. The EQ and inserts have no such lag;
|
|
609
|
+
* they discard at the end of their own process, in the same block that
|
|
610
|
+
* carried the poison.
|
|
611
|
+
*
|
|
612
|
+
* Cumulative since the strip was created and never cleared, so two
|
|
613
|
+
* readings bracket a span of audio. The unit is one processed block, never
|
|
614
|
+
* a channel, so a stereo block that discards on both channels adds one and
|
|
615
|
+
* the number does not depend on a dimension the caller did not choose.
|
|
616
|
+
*
|
|
617
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
618
|
+
*/
|
|
619
|
+
stripNonFiniteDiscardCount(stripIndex: number): number {
|
|
620
|
+
return this.mixer.stripNonFiniteDiscardCount(stripIndex);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Number of blocks in which a bus discarded recursive state because a
|
|
625
|
+
* non-finite value had reached it. Same contract as
|
|
626
|
+
* {@link stripNonFiniteDiscardCount}, for a bus: covers every insert the
|
|
627
|
+
* bus owns and its meter, neither separately addressable, so a discard
|
|
628
|
+
* inside one is observable only here. Cumulative across graph recompiles
|
|
629
|
+
* -- the count lives with the bus, not the compiled node, so an unrelated
|
|
630
|
+
* edit elsewhere in the mixer does not reset it.
|
|
631
|
+
*
|
|
632
|
+
* A bus's DSP record is created by the first {@link compile}. Throws for a
|
|
633
|
+
* bus that has been declared with {@link addBus} but never compiled --
|
|
634
|
+
* reading zero there would read as clean, and it is not. Also throws for
|
|
635
|
+
* an unknown bus id.
|
|
636
|
+
*
|
|
637
|
+
* @param busId - Bus id, as passed to {@link addBus} or declared in scene JSON
|
|
638
|
+
*/
|
|
639
|
+
busNonFiniteDiscardCount(busId: string): number {
|
|
640
|
+
return this.mixer.busNonFiniteDiscardCount(busId);
|
|
641
|
+
}
|
|
642
|
+
|
|
443
643
|
/**
|
|
444
644
|
* Schedule sample-accurate fader automation on a strip.
|
|
445
645
|
*
|
|
@@ -519,6 +719,11 @@ export class Mixer {
|
|
|
519
719
|
/**
|
|
520
720
|
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
521
721
|
* (oldest to newest).
|
|
722
|
+
*
|
|
723
|
+
* `maxPoints` must be a finite non-negative integer; anything else throws an
|
|
724
|
+
* `InvalidParameter` error. It is a request rather than an allocation size —
|
|
725
|
+
* a value beyond the strip's goniometer ring simply returns every point the
|
|
726
|
+
* ring holds.
|
|
522
727
|
*/
|
|
523
728
|
readGoniometerLatest(stripIndex: number, maxPoints: number): GoniometerPoint[] {
|
|
524
729
|
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
@@ -559,8 +764,12 @@ export class Mixer {
|
|
|
559
764
|
return this.mixer.drainTailStereo(numSamples);
|
|
560
765
|
}
|
|
561
766
|
|
|
562
|
-
/** Release the underlying WASM object.
|
|
767
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
563
768
|
delete(): void {
|
|
769
|
+
if (this.released) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this.released = true;
|
|
564
773
|
this.mixer.delete();
|
|
565
774
|
}
|
|
566
775
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { getSonareModule } from './module_state';
|
|
2
|
+
import type { MixAssistantOptions, MixAssistantResult, MixAssistantTrack } from './public_types';
|
|
3
|
+
import { assertSampleRate } from './validation';
|
|
4
|
+
|
|
5
|
+
function requireModule() {
|
|
6
|
+
return getSonareModule();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Inputs for the {@link suggestMixScene} / {@link suggestMixSceneJson} facades. */
|
|
10
|
+
export interface SuggestMixSceneRequest {
|
|
11
|
+
/** Tracks to mix, in the order their profiles are reported. */
|
|
12
|
+
tracks: MixAssistantTrack[];
|
|
13
|
+
/**
|
|
14
|
+
* Shared sample rate in Hz for every track. Required: every band edge,
|
|
15
|
+
* high-pass corner, sibilance band and alignment lag is derived from it, so a
|
|
16
|
+
* guessed rate would silently misread 44.1 kHz material by 8.8% and report
|
|
17
|
+
* `channelDelaySamples` and `durationSec` wrong with it.
|
|
18
|
+
*/
|
|
19
|
+
sampleRate: number;
|
|
20
|
+
/** Assistant tunables; every field falls back to the core default. */
|
|
21
|
+
options?: MixAssistantOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The four parallel arrays the embind entry points take. */
|
|
25
|
+
interface PlanarTracks {
|
|
26
|
+
left: Float32Array[];
|
|
27
|
+
right: (Float32Array | null)[];
|
|
28
|
+
ids: string[];
|
|
29
|
+
names: (string | null)[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Splits the request's track list into the planar per-track arrays the binding
|
|
34
|
+
* takes, rejecting the shapes that would otherwise reach the analysis as a
|
|
35
|
+
* missing buffer or a nameless strip.
|
|
36
|
+
*/
|
|
37
|
+
function planarTracks(tracks: MixAssistantTrack[]): PlanarTracks {
|
|
38
|
+
if (!Array.isArray(tracks)) {
|
|
39
|
+
throw new Error('tracks must be an array.');
|
|
40
|
+
}
|
|
41
|
+
const left: Float32Array[] = [];
|
|
42
|
+
const right: (Float32Array | null)[] = [];
|
|
43
|
+
const ids: string[] = [];
|
|
44
|
+
const names: (string | null)[] = [];
|
|
45
|
+
for (let index = 0; index < tracks.length; index++) {
|
|
46
|
+
const track = tracks[index];
|
|
47
|
+
if (track === null || typeof track !== 'object') {
|
|
48
|
+
throw new Error(`tracks[${index}] must be an object.`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof track.id !== 'string' || track.id.length === 0) {
|
|
51
|
+
throw new Error(`tracks[${index}].id must be a non-empty string.`);
|
|
52
|
+
}
|
|
53
|
+
if (!(track.left instanceof Float32Array)) {
|
|
54
|
+
throw new Error(`tracks[${index}].left must be a Float32Array.`);
|
|
55
|
+
}
|
|
56
|
+
if (track.right !== undefined && !(track.right instanceof Float32Array)) {
|
|
57
|
+
throw new Error(`tracks[${index}].right must be a Float32Array when present.`);
|
|
58
|
+
}
|
|
59
|
+
left.push(track.left);
|
|
60
|
+
right.push(track.right ?? null);
|
|
61
|
+
ids.push(track.id);
|
|
62
|
+
names.push(track.name ?? null);
|
|
63
|
+
}
|
|
64
|
+
return { left, right, ids, names };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function suggestJson(fnName: string, request: SuggestMixSceneRequest, sceneOnly: boolean): string {
|
|
68
|
+
// Required, not defaulted: this surface used to invent 48000, which read
|
|
69
|
+
// 44.1 kHz material 8.8% off across every band edge and every lag without
|
|
70
|
+
// saying so. Node and Python both demand it, and a request ported from either
|
|
71
|
+
// must not change behaviour by arriving here.
|
|
72
|
+
assertSampleRate(fnName, request.sampleRate);
|
|
73
|
+
const { left, right, ids, names } = planarTracks(request.tracks);
|
|
74
|
+
const sampleRate = request.sampleRate;
|
|
75
|
+
const params = (request.options ?? {}) as Record<string, number | boolean>;
|
|
76
|
+
const module = requireModule();
|
|
77
|
+
return sceneOnly
|
|
78
|
+
? module.mixingAssistantSuggestSceneJson(left, right, ids, names, sampleRate, params)
|
|
79
|
+
: module.mixingAssistantSuggest(left, right, ids, names, sampleRate, params);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Analyze a set of tracks and suggest a mixer scene.
|
|
84
|
+
*
|
|
85
|
+
* Offline only: the pipeline runs an STFT per track and evaluates every track
|
|
86
|
+
* pair, so it is measured in milliseconds per track and must never be called
|
|
87
|
+
* from an audio callback.
|
|
88
|
+
*
|
|
89
|
+
* The assistant suggests, it does not apply. Nothing is processed and no audio
|
|
90
|
+
* is returned; realizing the suggestion means handing the scene to
|
|
91
|
+
* {@link Mixer.fromSceneJson} as an explicit second step, for which
|
|
92
|
+
* {@link suggestMixSceneJson} returns the scene already serialized.
|
|
93
|
+
*
|
|
94
|
+
* Degenerate input is not an error: no tracks, all-silent tracks or tracks too
|
|
95
|
+
* short to measure yield an empty scene and an empty `explanation`.
|
|
96
|
+
*
|
|
97
|
+
* @param request - Tracks, shared sample rate and assistant options
|
|
98
|
+
* @returns The suggested scene, per-track profiles, cross-track measurements
|
|
99
|
+
* and the explanation behind each change
|
|
100
|
+
*/
|
|
101
|
+
export function suggestMixScene(request: SuggestMixSceneRequest): MixAssistantResult {
|
|
102
|
+
return JSON.parse(suggestJson('suggestMixScene', request, false)) as MixAssistantResult;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Suggest a mixer scene and return only the scene, as JSON.
|
|
107
|
+
*
|
|
108
|
+
* The same analysis as {@link suggestMixScene}, serialized in the schema
|
|
109
|
+
* {@link Mixer.fromSceneJson} reads, so a caller that only wants to apply the
|
|
110
|
+
* suggestion neither digs the scene out of the fuller result nor re-serializes
|
|
111
|
+
* it.
|
|
112
|
+
*
|
|
113
|
+
* @param request - Tracks, shared sample rate and assistant options
|
|
114
|
+
* @returns Scene JSON string
|
|
115
|
+
*/
|
|
116
|
+
export function suggestMixSceneJson(request: SuggestMixSceneRequest): string {
|
|
117
|
+
return suggestJson('suggestMixSceneJson', request, true);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Source-class identifiers the assistant can report, in enum order.
|
|
122
|
+
*
|
|
123
|
+
* The index of a name in this list is the value
|
|
124
|
+
* {@link mixSourceClassFromName} resolves it to.
|
|
125
|
+
*/
|
|
126
|
+
export function mixSourceClassNames(): string[] {
|
|
127
|
+
return Array.from(requireModule().mixingAssistantSourceClassNames());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Resolve a source-class identifier to its index in {@link mixSourceClassNames}.
|
|
132
|
+
*
|
|
133
|
+
* @param name - Source-class identifier, e.g. `"kick"`
|
|
134
|
+
* @returns The index, or -1 when the name is unknown
|
|
135
|
+
*/
|
|
136
|
+
export function mixSourceClassFromName(name: string): number {
|
|
137
|
+
return requireModule().mixingAssistantSourceClassFromName(name);
|
|
138
|
+
}
|
package/src/mixing_oneshot.ts
CHANGED
|
@@ -32,11 +32,16 @@ export interface MixStereoRequest extends MixOptions {
|
|
|
32
32
|
* One-shot stereo mix of multiple strips through the routing graph + master bus.
|
|
33
33
|
*
|
|
34
34
|
* Each returned per-strip meter reflects only this single one-shot block. The
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
35
|
+
* LUFS fields (`momentaryLufs`, `shortTermLufs`, `integratedLufs`) are
|
|
36
|
+
* integrators whose windows that block does not fill, so they read the -120 dB
|
|
37
|
+
* floor sentinel; drive a streaming {@link Mixer} block-by-block if you need
|
|
38
|
+
* meaningful loudness.
|
|
39
|
+
*
|
|
40
|
+
* The true-peak fields are not integrators and need no streaming: each is a
|
|
41
|
+
* max-hold over the block just processed and is valid from the first one,
|
|
42
|
+
* flooring only on silence. This facade mixes the whole input as a single block,
|
|
43
|
+
* which is the whole-signal case, so the block-edge under-read documented on
|
|
44
|
+
* `MixMeterSnapshot.truePeakDbL` does not apply to the reading here.
|
|
40
45
|
*
|
|
41
46
|
* @param leftChannels - Per-strip left input buffers (all the same length)
|
|
42
47
|
* @param rightChannels - Per-strip right input buffers (all the same length)
|