@libraz/libsonare 1.7.1 → 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 +3 -0
- package/dist/index.d.ts +286 -184
- package/dist/index.js +65 -4
- package/dist/index.js.map +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 +374 -1
- package/dist/worklet.js +174 -13
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -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 +34 -0
- package/src/realtime_engine.ts +39 -0
- package/src/sonare.js.d.ts +3 -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/dist/worklet.js
CHANGED
|
@@ -544,6 +544,25 @@ function getSonareModule() {
|
|
|
544
544
|
return wrappedModule;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
// src/_chain_config.ts
|
|
548
|
+
function flattenChainConfig(config) {
|
|
549
|
+
const out = {};
|
|
550
|
+
const walk = (node, prefix) => {
|
|
551
|
+
for (const [key, value] of Object.entries(node)) {
|
|
552
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
553
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
554
|
+
out[path] = value;
|
|
555
|
+
} else if (value !== null && typeof value === "object") {
|
|
556
|
+
walk(value, path);
|
|
557
|
+
} else if (value !== void 0) {
|
|
558
|
+
throw new TypeError(`Mastering override '${path}' must be a number or boolean.`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
walk(config, "");
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
|
|
547
566
|
// src/codes.ts
|
|
548
567
|
function resolveOrdinalInRange(value, min, max, enumName) {
|
|
549
568
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) {
|
|
@@ -1111,6 +1130,15 @@ var RealtimeEngine = class {
|
|
|
1111
1130
|
* Returns `-1` when the track, insert, or name is unknown. (The Python binding
|
|
1112
1131
|
* raises a `SonareError` for an unknown id where Node/WASM return the `-1`
|
|
1113
1132
|
* sentinel.)
|
|
1133
|
+
*
|
|
1134
|
+
* This trio is how a mastering processor gets time-varying automation: the
|
|
1135
|
+
* `eq.*`, `dynamics.*`, `saturation.*`, `spectral.*`, `stereo.*`,
|
|
1136
|
+
* `maximizer.*` and `multiband.*` processors are all available as strip
|
|
1137
|
+
* inserts, so placing one on a strip and resolving its parameter here drives
|
|
1138
|
+
* it at audio-block precision, live and offline alike. The whole-signal
|
|
1139
|
+
* stages of the offline mastering chain (`repair.*`, `loudness`, and the
|
|
1140
|
+
* match stages) have no insert form and no automation id: they buffer the
|
|
1141
|
+
* entire signal by construction and do not run on the realtime path.
|
|
1114
1142
|
*/
|
|
1115
1143
|
resolveTrackInsertAutomationId(trackId, insertIndex, paramName) {
|
|
1116
1144
|
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
@@ -1238,6 +1266,33 @@ var RealtimeEngine = class {
|
|
|
1238
1266
|
processPrepared(numFrames) {
|
|
1239
1267
|
this.native.processPrepared(numFrames);
|
|
1240
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Allocates the cue-bus counterpart of {@link prepareChannels}. Needed only
|
|
1271
|
+
* when PFL/AFL monitoring must reach a separate output: `processPrepared`
|
|
1272
|
+
* folds the cue bus into the program output, while
|
|
1273
|
+
* {@link processPreparedWithMonitor} keeps the two apart. Call once, off the
|
|
1274
|
+
* audio thread, with at least as many channels as `prepareChannels` got.
|
|
1275
|
+
*/
|
|
1276
|
+
prepareMonitorChannels(numChannels, maxFrames) {
|
|
1277
|
+
this.native.prepareMonitorChannels(numChannels, maxFrames);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Returns a Float32Array view onto the persistent cue-bus scratch for one
|
|
1281
|
+
* channel (valid for up to `numFrames`). Read it after
|
|
1282
|
+
* {@link processPreparedWithMonitor}. Re-acquire after WASM memory growth.
|
|
1283
|
+
*/
|
|
1284
|
+
getMonitorChannelBuffer(channel, numFrames) {
|
|
1285
|
+
return this.native.getMonitorChannelBuffer(channel, numFrames);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Runs the engine in place over the prepared scratch, writing the cue bus to
|
|
1289
|
+
* the monitor scratch instead of folding it into the program output.
|
|
1290
|
+
* Allocation-free: safe on the AudioWorklet render thread after
|
|
1291
|
+
* `prepareChannels` and `prepareMonitorChannels`.
|
|
1292
|
+
*/
|
|
1293
|
+
processPreparedWithMonitor(numFrames) {
|
|
1294
|
+
this.native.processPreparedWithMonitor(numFrames);
|
|
1295
|
+
}
|
|
1241
1296
|
processWithMonitor(channels) {
|
|
1242
1297
|
return this.native.processWithMonitor(channels);
|
|
1243
1298
|
}
|
|
@@ -2005,6 +2060,73 @@ var RealtimeVoiceChanger = class {
|
|
|
2005
2060
|
}
|
|
2006
2061
|
};
|
|
2007
2062
|
|
|
2063
|
+
// src/streaming_processors.ts
|
|
2064
|
+
var StreamingMasteringChain = class {
|
|
2065
|
+
constructor(config) {
|
|
2066
|
+
const module2 = getSonareModule();
|
|
2067
|
+
const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
|
|
2068
|
+
this.chain = module2.createStreamingMasteringChain({
|
|
2069
|
+
__flatParams: flattenChainConfig(chainConfig),
|
|
2070
|
+
loudnessStaticGainDb,
|
|
2071
|
+
loudnessStaticGainPeakDb
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Initialize processors for the given sample rate and block layout.
|
|
2076
|
+
*
|
|
2077
|
+
* @param sampleRate - Sample rate in Hz
|
|
2078
|
+
* @param maxBlockSize - Maximum block size per process call
|
|
2079
|
+
* @param numChannels - 1 (mono) or 2 (stereo)
|
|
2080
|
+
*/
|
|
2081
|
+
prepare(sampleRate, maxBlockSize, numChannels) {
|
|
2082
|
+
this.chain.prepare(sampleRate, maxBlockSize, numChannels);
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Process one mono block, returning the processed samples (same length).
|
|
2086
|
+
*/
|
|
2087
|
+
processMono(samples) {
|
|
2088
|
+
return this.chain.processMono(samples);
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Process one stereo block, returning the processed channels.
|
|
2092
|
+
*/
|
|
2093
|
+
processStereo(left, right) {
|
|
2094
|
+
if (left.length !== right.length) {
|
|
2095
|
+
throw new Error("Stereo channel lengths must match.");
|
|
2096
|
+
}
|
|
2097
|
+
return this.chain.processStereo(left, right);
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Emit delayed audio and finite processor tails after the final mono block.
|
|
2101
|
+
* Call until this returns an empty array. The initial `latencySamples()`
|
|
2102
|
+
* samples of the concatenated stream are delayed and should be discarded for
|
|
2103
|
+
* time-aligned output.
|
|
2104
|
+
*/
|
|
2105
|
+
flushMono() {
|
|
2106
|
+
return this.chain.flushMono();
|
|
2107
|
+
}
|
|
2108
|
+
/** Stereo counterpart of {@link flushMono}. */
|
|
2109
|
+
flushStereo() {
|
|
2110
|
+
return this.chain.flushStereo();
|
|
2111
|
+
}
|
|
2112
|
+
/** Reset all processor state without rebuilding. */
|
|
2113
|
+
reset() {
|
|
2114
|
+
this.chain.reset();
|
|
2115
|
+
}
|
|
2116
|
+
/** Total reported latency in samples across all active processors. */
|
|
2117
|
+
latencySamples() {
|
|
2118
|
+
return this.chain.latencySamples();
|
|
2119
|
+
}
|
|
2120
|
+
/** Ordered stage names that will run (e.g. `"eq.tilt"`). */
|
|
2121
|
+
stageNames() {
|
|
2122
|
+
return this.chain.stageNames();
|
|
2123
|
+
}
|
|
2124
|
+
/** Release the underlying WASM object. Safe to call only once. */
|
|
2125
|
+
delete() {
|
|
2126
|
+
this.chain.delete();
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2008
2130
|
// src/index.ts
|
|
2009
2131
|
var module = null;
|
|
2010
2132
|
var initPromise = null;
|
|
@@ -3407,10 +3529,12 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3407
3529
|
const clipPageRequestRing = mode === "sab" ? createSonareClipPageRequestRingBuffer(options.clipPageRequestRingCapacity ?? 128) : void 0;
|
|
3408
3530
|
const externalMidiRing = mode === "sab" ? createSonareExternalMidiRingBuffer(options.externalMidiRingCapacity ?? 256) : void 0;
|
|
3409
3531
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
3532
|
+
const cueOutput = options.cueOutput === true;
|
|
3410
3533
|
const processorOptions = {
|
|
3411
3534
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
3412
3535
|
blockSize,
|
|
3413
3536
|
channelCount,
|
|
3537
|
+
cueOutput,
|
|
3414
3538
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
3415
3539
|
commandRingCapacity: commandRing?.capacity,
|
|
3416
3540
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
@@ -3432,8 +3556,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3432
3556
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
3433
3557
|
const node = factory(context, processorName, {
|
|
3434
3558
|
numberOfInputs: 1,
|
|
3435
|
-
|
|
3436
|
-
|
|
3559
|
+
// The cue bus needs its own output; a single-output node keeps the
|
|
3560
|
+
// historical mix where process() folds the cue into the program.
|
|
3561
|
+
numberOfOutputs: cueOutput ? 2 : 1,
|
|
3562
|
+
outputChannelCount: cueOutput ? [channelCount, channelCount] : [channelCount],
|
|
3437
3563
|
processorOptions
|
|
3438
3564
|
});
|
|
3439
3565
|
return new _SonareRealtimeEngineNode(
|
|
@@ -3446,6 +3572,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3446
3572
|
audioWorklet,
|
|
3447
3573
|
clipPageRequestsRealtimeSafe: mode === "sab",
|
|
3448
3574
|
externalMidiRealtimeSafe: mode === "sab",
|
|
3575
|
+
cueOutput,
|
|
3449
3576
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
3450
3577
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
3451
3578
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
@@ -5093,6 +5220,20 @@ function resolveMetronomeConfig(config) {
|
|
|
5093
5220
|
}
|
|
5094
5221
|
|
|
5095
5222
|
// src/worklet/engine-processor.ts
|
|
5223
|
+
function copyPlanesToOutput(output, planes, frames) {
|
|
5224
|
+
for (let ch = 0; ch < output.length; ch++) {
|
|
5225
|
+
const target = output[ch];
|
|
5226
|
+
const source = planes[ch] ?? planes[0];
|
|
5227
|
+
if (source) {
|
|
5228
|
+
target.set(source.subarray(0, Math.min(target.length, frames)));
|
|
5229
|
+
if (target.length > frames) {
|
|
5230
|
+
target.fill(0, frames);
|
|
5231
|
+
}
|
|
5232
|
+
} else {
|
|
5233
|
+
target.fill(0);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5096
5237
|
function captureTransferList(channels) {
|
|
5097
5238
|
const transfers = [];
|
|
5098
5239
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5115,6 +5256,10 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5115
5256
|
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
5116
5257
|
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
5117
5258
|
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
5259
|
+
// Cue-bus plane, allocated only when the host asked for a separate PFL/AFL
|
|
5260
|
+
// output. Empty otherwise, so a single-output host pays no heap and keeps the
|
|
5261
|
+
// historical behaviour where process() folds the cue into the program mix.
|
|
5262
|
+
this.monitorBuffers = [];
|
|
5118
5263
|
this.liveClips = /* @__PURE__ */ new Map();
|
|
5119
5264
|
this.pagedClipProviders = /* @__PURE__ */ new Map();
|
|
5120
5265
|
this.pagedClipPageFrames = /* @__PURE__ */ new Map();
|
|
@@ -5162,6 +5307,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5162
5307
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5163
5308
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
5164
5309
|
}
|
|
5310
|
+
this.cueOutput = options.cueOutput === true;
|
|
5311
|
+
if (this.cueOutput) {
|
|
5312
|
+
this.engine.prepareMonitorChannels(this.channelCount, this.blockSize);
|
|
5313
|
+
this.monitorBuffers = new Array(this.channelCount);
|
|
5314
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5315
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5165
5318
|
if (this.scopeRing) {
|
|
5166
5319
|
const interval = Math.max(1, Math.floor(options.scopeIntervalFrames ?? this.blockSize));
|
|
5167
5320
|
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
@@ -5198,6 +5351,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5198
5351
|
if ((this.channelBuffers[0]?.byteLength ?? 0) === 0) {
|
|
5199
5352
|
this.reacquireChannelBuffers();
|
|
5200
5353
|
}
|
|
5354
|
+
if (this.cueOutput && (this.monitorBuffers[0]?.byteLength ?? 0) === 0) {
|
|
5355
|
+
this.reacquireMonitorBuffers();
|
|
5356
|
+
}
|
|
5201
5357
|
const input = inputs[0];
|
|
5202
5358
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5203
5359
|
const dst = this.channelBuffers[ch];
|
|
@@ -5208,17 +5364,16 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5208
5364
|
dst.fill(0, 0, usableFrames);
|
|
5209
5365
|
}
|
|
5210
5366
|
}
|
|
5211
|
-
this.
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
target.fill(0);
|
|
5367
|
+
if (this.cueOutput) {
|
|
5368
|
+
this.engine.processPreparedWithMonitor(usableFrames);
|
|
5369
|
+
} else {
|
|
5370
|
+
this.engine.processPrepared(usableFrames);
|
|
5371
|
+
}
|
|
5372
|
+
copyPlanesToOutput(output, this.channelBuffers, usableFrames);
|
|
5373
|
+
if (this.cueOutput) {
|
|
5374
|
+
const cue = outputs[1];
|
|
5375
|
+
if (cue) {
|
|
5376
|
+
copyPlanesToOutput(cue, this.monitorBuffers, usableFrames);
|
|
5222
5377
|
}
|
|
5223
5378
|
}
|
|
5224
5379
|
this.publishClipPageRequests();
|
|
@@ -5233,6 +5388,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5233
5388
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
5234
5389
|
}
|
|
5235
5390
|
}
|
|
5391
|
+
reacquireMonitorBuffers() {
|
|
5392
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5393
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5236
5396
|
receiveCommand(command) {
|
|
5237
5397
|
if (!this.closed) {
|
|
5238
5398
|
this.safeApplyCommand(command);
|
|
@@ -6645,6 +6805,7 @@ export {
|
|
|
6645
6805
|
SonareRealtimeEngineWorkletProcessor,
|
|
6646
6806
|
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
6647
6807
|
SonareWorkletProcessor,
|
|
6808
|
+
StreamingMasteringChain,
|
|
6648
6809
|
attachOpfsClipStream,
|
|
6649
6810
|
createSonareClipPageRequestRingBuffer,
|
|
6650
6811
|
createSonareEngineCommandRingBuffer,
|