@libraz/libsonare 1.7.2 → 1.8.1
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 +7 -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 +874 -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 +502 -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 +134 -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 +4173 -1621
- 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 +483 -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 +654 -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 +528 -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 +165 -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 +893 -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 +3945 -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 +340 -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 +164 -0
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
- package/dist/worklet/engine-node.d.ts +83 -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 +72 -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 +75 -0
- package/dist/worklet/engine-strips.d.ts.map +1 -0
- package/dist/worklet/engine-sync.d.ts +39 -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 +418 -0
- package/dist/worklet/engine.d.ts.map +1 -0
- package/dist/worklet/guards.d.ts +53 -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 +331 -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 +3200 -541
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -12
- package/src/_effects_common.ts +47 -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 +101 -22
- package/src/effects_note_ops.ts +683 -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 +388 -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 +288 -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 +441 -32
- package/src/mastering_dynamics.ts +22 -11
- package/src/metering.ts +67 -24
- package/src/mixer.ts +251 -22
- 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 +271 -271
- package/src/public_types.ts +122 -3
- package/src/public_types_acoustic.ts +112 -3
- package/src/public_types_mastering.ts +275 -73
- package/src/public_types_mixing.ts +363 -1
- package/src/public_types_music.ts +312 -2
- package/src/public_types_playback.ts +196 -0
- package/src/public_types_realtime.ts +39 -7
- package/src/public_types_repair.ts +446 -0
- package/src/public_types_spectral.ts +491 -5
- package/src/quick_analysis.ts +203 -26
- package/src/realtime_engine.ts +773 -34
- 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 +1158 -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 +202 -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 +800 -32
- package/src/worklet/engine-node.ts +99 -29
- package/src/worklet/engine-offline.ts +14 -8
- package/src/worklet/engine-parameter-facade.ts +21 -0
- package/src/worklet/engine-processor.ts +332 -93
- package/src/worklet/engine-register.ts +32 -18
- package/src/worklet/engine-strips.ts +275 -9
- package/src/worklet/engine-sync.ts +20 -7
- package/src/worklet/engine.ts +394 -48
- package/src/worklet/guards.ts +195 -44
- package/src/worklet/messages.ts +229 -2
- package/src/worklet/mixer-processor.ts +117 -48
- package/src/worklet/playback-processor.ts +300 -0
- package/src/worklet/protocol.ts +82 -11
- 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
package/dist/worklet.js
CHANGED
|
@@ -400,7 +400,21 @@ var SonareError = class extends Error {
|
|
|
400
400
|
this.code = code;
|
|
401
401
|
this.codeName = codeName;
|
|
402
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Brand-based `instanceof`: an error that carries the shape narrows here even
|
|
405
|
+
* when it is not literally an instance of this class. That is not a
|
|
406
|
+
* hypothetical — an error posted from the analysis worker arrives as a
|
|
407
|
+
* structured clone with its prototype gone, which a prototype-based
|
|
408
|
+
* `instanceof` would silently miss. Delegates to {@link isSonareError} so the
|
|
409
|
+
* two never disagree.
|
|
410
|
+
*/
|
|
411
|
+
static [Symbol.hasInstance](value) {
|
|
412
|
+
return isSonareError(value);
|
|
413
|
+
}
|
|
403
414
|
};
|
|
415
|
+
function isSonareError(value) {
|
|
416
|
+
return value instanceof Error && value.name === "SonareError" && typeof value.code === "number";
|
|
417
|
+
}
|
|
404
418
|
|
|
405
419
|
// src/module_state.ts
|
|
406
420
|
var wrappedModule = null;
|
|
@@ -428,6 +442,11 @@ function makeSonareError(raw, thrown) {
|
|
|
428
442
|
message = info.message || message;
|
|
429
443
|
}
|
|
430
444
|
} catch {
|
|
445
|
+
} finally {
|
|
446
|
+
try {
|
|
447
|
+
raw.sonareReleaseException(thrown);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
431
450
|
}
|
|
432
451
|
return new SonareError(code, codeName, message);
|
|
433
452
|
}
|
|
@@ -448,6 +467,10 @@ function wrapModuleErrors(raw) {
|
|
|
448
467
|
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
|
|
449
468
|
return value;
|
|
450
469
|
}
|
|
470
|
+
const proto = Object.getPrototypeOf(value);
|
|
471
|
+
if (Array.isArray(value) || proto === Object.prototype || proto === null) {
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
451
474
|
const objectValue = value;
|
|
452
475
|
const cached = objectCache.get(objectValue);
|
|
453
476
|
if (cached) {
|
|
@@ -544,25 +567,6 @@ function getSonareModule() {
|
|
|
544
567
|
return wrappedModule;
|
|
545
568
|
}
|
|
546
569
|
|
|
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
|
-
|
|
566
570
|
// src/codes.ts
|
|
567
571
|
function resolveOrdinalInRange(value, min, max, enumName) {
|
|
568
572
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) {
|
|
@@ -598,6 +602,10 @@ var AUTOMATION_CURVE_VALUES = {
|
|
|
598
602
|
hold: 2,
|
|
599
603
|
"s-curve": 3
|
|
600
604
|
};
|
|
605
|
+
var PROJECT_AUTOMATION_CURVE_VALUES = {
|
|
606
|
+
...AUTOMATION_CURVE_VALUES,
|
|
607
|
+
scurve: 3
|
|
608
|
+
};
|
|
601
609
|
var PAN_LAW_VALUES = {
|
|
602
610
|
const3db: 0,
|
|
603
611
|
"const-3db": 0,
|
|
@@ -623,6 +631,7 @@ var PAN_MODE_VALUES = {
|
|
|
623
631
|
};
|
|
624
632
|
var METER_TAP_VALUES = { preFader: 0, postFader: 1 };
|
|
625
633
|
var SEND_TIMING_VALUES = { postFader: 0, preFader: 1 };
|
|
634
|
+
var SIDECHAIN_SOURCE_KIND_VALUES = { track: 0, bus: 1 };
|
|
626
635
|
var TRACK_MONITOR_MODE_VALUES = { off: 0, pfl: 1, afl: 2 };
|
|
627
636
|
function automationCurveCode(curve) {
|
|
628
637
|
return resolveEnumOrdinal(curve, AUTOMATION_CURVE_VALUES, "automation curve");
|
|
@@ -641,12 +650,276 @@ function meterTapCode(tap) {
|
|
|
641
650
|
function sendTimingCode(timing) {
|
|
642
651
|
return resolveEnumOrdinal(timing, SEND_TIMING_VALUES, "send timing");
|
|
643
652
|
}
|
|
653
|
+
function sidechainSourceKindCode(kind) {
|
|
654
|
+
return resolveEnumOrdinal(kind, SIDECHAIN_SOURCE_KIND_VALUES, "sidechain source kind");
|
|
655
|
+
}
|
|
644
656
|
function trackMonitorModeCode(mode) {
|
|
645
657
|
return resolveEnumOrdinal(mode, TRACK_MONITOR_MODE_VALUES, "track monitor mode");
|
|
646
658
|
}
|
|
647
659
|
|
|
660
|
+
// src/sample_bank.ts
|
|
661
|
+
var SampleBank = class {
|
|
662
|
+
/** Create an empty bank. */
|
|
663
|
+
constructor() {
|
|
664
|
+
this.released = false;
|
|
665
|
+
this.native = new (projectModule()).SampleBank();
|
|
666
|
+
this.nativeId = this.native.id;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Copy mono float frames into the bank and return the new sample's index,
|
|
670
|
+
* which {@link SampleZoneDesc.sampleIndex} names. The frames are copied, so
|
|
671
|
+
* the array may be reused afterwards.
|
|
672
|
+
*
|
|
673
|
+
* Loop points are clamped inside the sample and a loop mode whose loop
|
|
674
|
+
* survives the clamp empty is dropped, so a malformed loop plays as an
|
|
675
|
+
* unlooped sample rather than as a wrap over nothing. An empty array, and a
|
|
676
|
+
* bank that would exceed 67,108,864 sample points, throw.
|
|
677
|
+
*
|
|
678
|
+
* A NaN or Inf frame, `fineTuneCents` or `sourceRate` throws too, and the
|
|
679
|
+
* bank is left unchanged. Such a value is unattributable once stored: the
|
|
680
|
+
* reader's interpolation spreads one bad frame across the whole sustain, and
|
|
681
|
+
* a bad tuning offset renders the voice silent with no error raised.
|
|
682
|
+
*/
|
|
683
|
+
addSample(data, desc = {}) {
|
|
684
|
+
return this.native.addSample(data, desc);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Append a key/velocity rectangle to a keymap set, creating any sets below
|
|
688
|
+
* it. A patch names a set; the first zone in it covering a note is the one
|
|
689
|
+
* that sounds.
|
|
690
|
+
*
|
|
691
|
+
* Every bound defaults on its own (see {@link SampleZoneDesc}), so an empty
|
|
692
|
+
* rectangle is the whole keyboard at every velocity and narrowing one axis
|
|
693
|
+
* leaves the other whole. A `sampleIndex` the bank does not have, an inverted
|
|
694
|
+
* key or velocity range, and a `setIndex` at or above 4096 all throw.
|
|
695
|
+
*/
|
|
696
|
+
addZone(zone = {}) {
|
|
697
|
+
const { setIndex, ...rest } = zone;
|
|
698
|
+
this.native.addZone(setIndex ?? 0, rest);
|
|
699
|
+
}
|
|
700
|
+
/** Samples added so far. */
|
|
701
|
+
sampleCount() {
|
|
702
|
+
return this.native.sampleCount();
|
|
703
|
+
}
|
|
704
|
+
/** Keymap sets the bank has (one past the highest index used). */
|
|
705
|
+
setCount() {
|
|
706
|
+
return this.native.setCount();
|
|
707
|
+
}
|
|
708
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
709
|
+
delete() {
|
|
710
|
+
if (this.released) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
this.released = true;
|
|
714
|
+
this.native.delete();
|
|
715
|
+
}
|
|
716
|
+
/** Alias for {@link SampleBank.delete}, provided for cross-binding (Node) compatibility. */
|
|
717
|
+
destroy() {
|
|
718
|
+
this.delete();
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/project_internal.ts
|
|
723
|
+
function normalizeSynthInstrument(patch) {
|
|
724
|
+
if (patch === null || typeof patch !== "object") {
|
|
725
|
+
return patch;
|
|
726
|
+
}
|
|
727
|
+
const { sampleBank, ...rest } = patch;
|
|
728
|
+
if (!sampleBank) {
|
|
729
|
+
return rest;
|
|
730
|
+
}
|
|
731
|
+
if (!(sampleBank instanceof SampleBank)) {
|
|
732
|
+
throw new TypeError("sampleBank must be a SampleBank instance");
|
|
733
|
+
}
|
|
734
|
+
if (sampleBank.released) {
|
|
735
|
+
throw new TypeError("sampleBank is destroyed");
|
|
736
|
+
}
|
|
737
|
+
return { ...rest, sampleBankId: sampleBank.nativeId };
|
|
738
|
+
}
|
|
739
|
+
function projectModule() {
|
|
740
|
+
const candidate = getSonareModule();
|
|
741
|
+
if (typeof candidate.projectAbiVersion !== "function" || candidate.Project === void 0) {
|
|
742
|
+
throw new Error("libsonare was built without arrangement (headless DAW) support");
|
|
743
|
+
}
|
|
744
|
+
return candidate;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/_chain_config.ts
|
|
748
|
+
function flattenChainConfig(config) {
|
|
749
|
+
const out = {};
|
|
750
|
+
const walk = (node, prefix) => {
|
|
751
|
+
for (const [key, value] of Object.entries(node)) {
|
|
752
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
753
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
754
|
+
out[path] = value;
|
|
755
|
+
} else if (value !== null && typeof value === "object") {
|
|
756
|
+
walk(value, path);
|
|
757
|
+
} else if (value !== void 0) {
|
|
758
|
+
throw new TypeError(`Mastering override '${path}' must be a number or boolean.`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
walk(config, "");
|
|
763
|
+
return out;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// src/playback_renderer.ts
|
|
767
|
+
function configJsonText(config) {
|
|
768
|
+
return typeof config === "string" ? config : JSON.stringify(config);
|
|
769
|
+
}
|
|
770
|
+
function nativeHrtf(hrtf) {
|
|
771
|
+
if (hrtf === void 0) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
if (!(hrtf instanceof HrtfSet)) {
|
|
775
|
+
throw new TypeError("hrtf must be an HrtfSet");
|
|
776
|
+
}
|
|
777
|
+
return hrtf.native;
|
|
778
|
+
}
|
|
779
|
+
var HrtfSet = class _HrtfSet {
|
|
780
|
+
constructor(native) {
|
|
781
|
+
this.released = false;
|
|
782
|
+
this.native = native;
|
|
783
|
+
}
|
|
784
|
+
/** Builds an HRTF set from SHRF v1 bytes; malformed data throws. */
|
|
785
|
+
static fromBytes(bytes) {
|
|
786
|
+
return new _HrtfSet(getSonareModule().createHrtfSet(bytes));
|
|
787
|
+
}
|
|
788
|
+
/** Releases the native handle. Idempotent, as the Node facade is. */
|
|
789
|
+
delete() {
|
|
790
|
+
if (this.released) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
this.released = true;
|
|
794
|
+
this.native.delete();
|
|
795
|
+
}
|
|
796
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
797
|
+
destroy() {
|
|
798
|
+
this.delete();
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
var PlaybackRenderer = class {
|
|
802
|
+
constructor(options) {
|
|
803
|
+
this.released = false;
|
|
804
|
+
this.native = getSonareModule().createPlaybackRenderer(
|
|
805
|
+
configJsonText(options.config),
|
|
806
|
+
nativeHrtf(options.hrtf),
|
|
807
|
+
options.sampleRate ?? 48e3,
|
|
808
|
+
options.maxBlockSize ?? 1024
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Renders one planar block; every plane must carry the same frame count, at
|
|
813
|
+
* most `maxBlockSize` (0 is a no-op). With a fixed input layout the plane
|
|
814
|
+
* count must equal {@link inputChannels}; with `input.layout: "auto"` it
|
|
815
|
+
* must be 1, 2, 6 or 8, and a change switches the input layout without
|
|
816
|
+
* changing the latency. Non-finite input samples are replaced with 0 and
|
|
817
|
+
* counted ({@link nonFiniteDiscardCount}).
|
|
818
|
+
*/
|
|
819
|
+
processPlanar(planes) {
|
|
820
|
+
return this.native.processPlanar(planes);
|
|
821
|
+
}
|
|
822
|
+
/** Interleaved variant of {@link processPlanar}. Non-finite input samples are replaced with 0 and counted. */
|
|
823
|
+
processInterleaved(samples, inChannels) {
|
|
824
|
+
return this.native.processInterleaved(samples, inChannels);
|
|
825
|
+
}
|
|
826
|
+
/** Applies a complete configuration document; a changed prepare key throws. */
|
|
827
|
+
setConfig(config) {
|
|
828
|
+
this.native.setConfig(configJsonText(config));
|
|
829
|
+
}
|
|
830
|
+
/** The current complete configuration document. */
|
|
831
|
+
config() {
|
|
832
|
+
return JSON.parse(this.native.configJson());
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Publishes the listener head orientation in degrees: right-handed,
|
|
836
|
+
* positive yaw turns the head right, positive pitch looks up, positive roll
|
|
837
|
+
* lowers the right ear. Ignored by a speakers target; a non-finite angle is
|
|
838
|
+
* ignored.
|
|
839
|
+
*/
|
|
840
|
+
setHeadOrientation(yawDeg, pitchDeg = 0, rollDeg = 0) {
|
|
841
|
+
this.native.setHeadOrientation(yawDeg, pitchDeg, rollDeg);
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Clears DSP state (filters, FIFOs, dynamics, convolution history, pending
|
|
845
|
+
* input-layout drains). Configuration and head pose are kept. Call it after
|
|
846
|
+
* a seek, from the thread that processes.
|
|
847
|
+
*/
|
|
848
|
+
reset() {
|
|
849
|
+
this.native.reset();
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Renderer latency in samples (headphones: near ear). Depends only on the
|
|
853
|
+
* target, the sample rate and distance compensation, never on realtime keys
|
|
854
|
+
* or the input layout.
|
|
855
|
+
*/
|
|
856
|
+
latencySamples() {
|
|
857
|
+
return this.native.latencySamples();
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Channel count of the active input layout. With `input.layout: "auto"`
|
|
861
|
+
* this follows the channel count of the most recent non-empty process call
|
|
862
|
+
* (2 before the first call).
|
|
863
|
+
*/
|
|
864
|
+
inputChannels() {
|
|
865
|
+
return this.native.inputChannels();
|
|
866
|
+
}
|
|
867
|
+
/** Channel count of the output target. */
|
|
868
|
+
outputChannels() {
|
|
869
|
+
return this.native.outputChannels();
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Inactive stages, per-stage latency, clamps, the active input layout, and
|
|
873
|
+
* the layout-switch / truncated-drain counters, as a plain object.
|
|
874
|
+
*/
|
|
875
|
+
diagnostics() {
|
|
876
|
+
return JSON.parse(this.native.diagnosticsJson());
|
|
877
|
+
}
|
|
878
|
+
/** Non-finite input samples replaced with 0 since construction. */
|
|
879
|
+
nonFiniteDiscardCount() {
|
|
880
|
+
return this.native.nonFiniteDiscardCount();
|
|
881
|
+
}
|
|
882
|
+
/** Releases the native handle. Idempotent, as the Node facade is. */
|
|
883
|
+
delete() {
|
|
884
|
+
if (this.released) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
this.released = true;
|
|
888
|
+
this.native.delete();
|
|
889
|
+
}
|
|
890
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
891
|
+
destroy() {
|
|
892
|
+
this.delete();
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
|
|
648
896
|
// src/realtime_engine.ts
|
|
649
897
|
var EXPECTED_ENGINE_ABI_VERSION = 3;
|
|
898
|
+
var UMP_WORD_MIN = -2147483648;
|
|
899
|
+
var UMP_WORD_MAX = 4294967295;
|
|
900
|
+
function assertUmpWords(fnName, words) {
|
|
901
|
+
if (!(words instanceof Uint32Array) && !Array.isArray(words)) {
|
|
902
|
+
throw new TypeError(`${fnName}: words must be a Uint32Array or a number array`);
|
|
903
|
+
}
|
|
904
|
+
if (words.length < 1 || words.length > 4) {
|
|
905
|
+
throw new RangeError(`${fnName}: words must hold 1 to 4 words`);
|
|
906
|
+
}
|
|
907
|
+
for (let i = 0; i < words.length; i++) {
|
|
908
|
+
const word = words[i];
|
|
909
|
+
if (typeof word !== "number" || !Number.isInteger(word) || word < UMP_WORD_MIN || word > UMP_WORD_MAX) {
|
|
910
|
+
throw new RangeError(`${fnName}: words[${i}] must be an integer 32-bit word`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return words;
|
|
914
|
+
}
|
|
915
|
+
function normalizeRenderOfflineRequest(channelsOrRequest, blockSize) {
|
|
916
|
+
const request = Array.isArray(channelsOrRequest) ? { channels: channelsOrRequest, blockSize } : channelsOrRequest;
|
|
917
|
+
return {
|
|
918
|
+
channels: request.channels,
|
|
919
|
+
blockSize: request.blockSize ?? 128,
|
|
920
|
+
finalize: request.finalize ?? true
|
|
921
|
+
};
|
|
922
|
+
}
|
|
650
923
|
function engineCapabilities() {
|
|
651
924
|
const abiVersion = getSonareModule().engineAbiVersion();
|
|
652
925
|
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === "function";
|
|
@@ -662,8 +935,9 @@ function engineCapabilities() {
|
|
|
662
935
|
mode: sharedArrayBuffer && atomics ? "sab" : "postMessage"
|
|
663
936
|
};
|
|
664
937
|
}
|
|
665
|
-
var RealtimeEngine = class {
|
|
938
|
+
var RealtimeEngine = class _RealtimeEngine {
|
|
666
939
|
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
940
|
+
this.released = false;
|
|
667
941
|
const module2 = getSonareModule();
|
|
668
942
|
const capabilities = engineCapabilities();
|
|
669
943
|
if (!capabilities.abiCompatible) {
|
|
@@ -679,6 +953,15 @@ var RealtimeEngine = class {
|
|
|
679
953
|
maxChannels
|
|
680
954
|
);
|
|
681
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Size the engine's queues and scratch for a sample rate and block size.
|
|
958
|
+
*
|
|
959
|
+
* `commandCapacity` must not exceed 65536 and `telemetryCapacity` must not
|
|
960
|
+
* exceed 16384; a larger value throws and leaves the engine untouched. The
|
|
961
|
+
* telemetry number is not a queue depth paid for one-for-one: the engine
|
|
962
|
+
* reserves that many meter records per metered lane, so its memory cost is
|
|
963
|
+
* far larger than the number given here.
|
|
964
|
+
*/
|
|
682
965
|
prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
683
966
|
this.native.prepareWithChannels(
|
|
684
967
|
sampleRate,
|
|
@@ -725,9 +1008,13 @@ var RealtimeEngine = class {
|
|
|
725
1008
|
* scheduled MIDI clips routed to that destination render through the synth.
|
|
726
1009
|
* Unknown preset names throw. An object patch's `destinationId` is a JS
|
|
727
1010
|
* binding convenience, not part of the NativeSynth patch itself.
|
|
1011
|
+
*
|
|
1012
|
+
* An `engineMode: 'sample'` patch also carries the {@link SampleBank} its
|
|
1013
|
+
* keymap names. The synth takes a share of the bank, so it may be released
|
|
1014
|
+
* right after this call; a sample patch bound without one renders silence.
|
|
728
1015
|
*/
|
|
729
1016
|
setSynthInstrument(patch = {}, destinationId = (typeof patch === "object" ? patch.destinationId : void 0) ?? 0) {
|
|
730
|
-
this.native.setSynthInstrument(destinationId, patch);
|
|
1017
|
+
this.native.setSynthInstrument(destinationId, normalizeSynthInstrument(patch));
|
|
731
1018
|
}
|
|
732
1019
|
/**
|
|
733
1020
|
* Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
|
|
@@ -780,6 +1067,113 @@ var RealtimeEngine = class {
|
|
|
780
1067
|
midiCcBindingCount() {
|
|
781
1068
|
return this.native.midiCcBindingCount();
|
|
782
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Replace a destination instrument's controller profile with a named preset
|
|
1072
|
+
* (see {@link controllerProfileNames}). Installing a profile drops every
|
|
1073
|
+
* channel's accumulated axis values: the new bindings say nothing about what
|
|
1074
|
+
* the old ones had reached. An unknown name throws, and so does a destination
|
|
1075
|
+
* with no instrument or one whose instrument holds no profile.
|
|
1076
|
+
*/
|
|
1077
|
+
setControllerProfile(destinationId, presetName) {
|
|
1078
|
+
this.native.setControllerProfile(destinationId, presetName);
|
|
1079
|
+
}
|
|
1080
|
+
/** Add one {@link ControllerBinding} on top of the destination's current profile. */
|
|
1081
|
+
bindController(destinationId, binding) {
|
|
1082
|
+
this.native.bindController(destinationId, binding);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Drop every binding of the destination's controller profile. The instrument
|
|
1086
|
+
* keeps a profile; it resolves nothing until something is bound again.
|
|
1087
|
+
*/
|
|
1088
|
+
clearControllerBindings(destinationId) {
|
|
1089
|
+
this.native.clearControllerBindings(destinationId);
|
|
1090
|
+
}
|
|
1091
|
+
controllerBindingCount(destinationId) {
|
|
1092
|
+
return this.native.controllerBindingCount(destinationId);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Whether note-on velocity is expression for this instrument. No fixed
|
|
1096
|
+
* default is possible — a wind controller ships sending breath-derived
|
|
1097
|
+
* velocity on one model and a constant on the next — so each preset states it
|
|
1098
|
+
* and a host building its own profile sets it. When false the synth takes
|
|
1099
|
+
* every note at full scale and the bound axes carry the dynamics alone.
|
|
1100
|
+
*/
|
|
1101
|
+
setControllerVelocityMeaningful(destinationId, meaningful) {
|
|
1102
|
+
this.native.setControllerVelocityMeaningful(destinationId, meaningful);
|
|
1103
|
+
}
|
|
1104
|
+
controllerVelocityMeaningful(destinationId) {
|
|
1105
|
+
return this.native.controllerVelocityMeaningful(destinationId);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Say which note a value addressed to a whole MIDI channel belongs to when
|
|
1109
|
+
* several are sounding on it, for one per-note dimension
|
|
1110
|
+
* ({@link MPE_DIMENSIONS}, {@link NOTE_TRACKINGS}).
|
|
1111
|
+
*
|
|
1112
|
+
* Set per dimension because the useful answers differ: pressure following the
|
|
1113
|
+
* newest note while bend reaches every one is a real configuration, not a
|
|
1114
|
+
* mistake. MPE poses this question and declines to answer it, so this is a
|
|
1115
|
+
* choice rather than a rule — and it is read only inside an MPE zone, and
|
|
1116
|
+
* only while more than one note is sounding on the channel, which an MPE
|
|
1117
|
+
* sender avoids by giving each note its own member channel.
|
|
1118
|
+
*
|
|
1119
|
+
* Both arguments are required and are a name or its ordinal; an unknown
|
|
1120
|
+
* spelling is refused rather than resolved to a default, as are a destination
|
|
1121
|
+
* with no instrument and one whose instrument holds no controller profile.
|
|
1122
|
+
*/
|
|
1123
|
+
setControllerNoteTracking(destinationId, dimension, tracking) {
|
|
1124
|
+
this.native.setControllerNoteTracking(destinationId, dimension, tracking);
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Read back {@link setControllerNoteTracking} for one dimension, as the
|
|
1128
|
+
* canonical name.
|
|
1129
|
+
*/
|
|
1130
|
+
controllerNoteTracking(destinationId, dimension) {
|
|
1131
|
+
return this.native.controllerNoteTracking(destinationId, dimension);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Set how one MIDI channel (0–15) of a destination's instrument treats a
|
|
1135
|
+
* note-on while another note on that channel is still held: `'poly'` takes a
|
|
1136
|
+
* new voice each time, `'mono-retrigger'` stops and restarts the note (what
|
|
1137
|
+
* GS MONO MODE and CC126 mean), `'mono-legato'` carries the sounding voice
|
|
1138
|
+
* and only moves its pitch — a wind player's slur, which no MIDI message can
|
|
1139
|
+
* reach by design.
|
|
1140
|
+
*
|
|
1141
|
+
* `'mono-legato'` is a request, not a guarantee: an engine whose exciter is
|
|
1142
|
+
* spent at the onset — anything struck or plucked — and a target pitch below
|
|
1143
|
+
* what the engine's delay line can hold both fall back to an ordinary note,
|
|
1144
|
+
* which {@link legatoFallbackCount} counts. A channel outside [0,15] and an
|
|
1145
|
+
* articulation outside the enum are refused rather than clamped, and so is a
|
|
1146
|
+
* destination with no instrument or one whose instrument has no articulation
|
|
1147
|
+
* of its own.
|
|
1148
|
+
*/
|
|
1149
|
+
setArticulation(destinationId, channel, articulation) {
|
|
1150
|
+
this.native.setArticulation(destinationId, channel, articulation);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Read back {@link setArticulation} as the canonical name. An ordinal this
|
|
1154
|
+
* build cannot spell is handed back as the number, the way every other enum
|
|
1155
|
+
* leaves this surface.
|
|
1156
|
+
*/
|
|
1157
|
+
articulation(destinationId, channel) {
|
|
1158
|
+
return this.native.articulation(destinationId, channel);
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* How many times a legato continuation was asked for on this destination and
|
|
1162
|
+
* refused, so the note started a voice of its own instead. Counted rather
|
|
1163
|
+
* than inferred: a refusal sounds like an ordinary note, so nothing in the
|
|
1164
|
+
* audio separates "this engine declines legato" from "the mode was never
|
|
1165
|
+
* set". Saturates at 4294967295 rather than wrapping — matching the C ABI, so
|
|
1166
|
+
* the same phrase reports the same number on every surface — after which it
|
|
1167
|
+
* reads as "at least this many".
|
|
1168
|
+
*
|
|
1169
|
+
* Throws on a destination with no instrument, and on one whose instrument has
|
|
1170
|
+
* no articulation of its own — the same two refusals
|
|
1171
|
+
* {@link setArticulation} keeps apart. Reporting 0 for the second would read
|
|
1172
|
+
* as "every slur took", which is the reading this counter exists to prevent.
|
|
1173
|
+
*/
|
|
1174
|
+
legatoFallbackCount(destinationId) {
|
|
1175
|
+
return this.native.legatoFallbackCount(destinationId);
|
|
1176
|
+
}
|
|
783
1177
|
/** Install/replace a live non-destructive MIDI-FX insert for one destination. */
|
|
784
1178
|
setMidiFx(destinationId, configJson) {
|
|
785
1179
|
this.native.setMidiFx(destinationId, configJson);
|
|
@@ -800,7 +1194,10 @@ var RealtimeEngine = class {
|
|
|
800
1194
|
/**
|
|
801
1195
|
* Route a destination's (track lane's) MIDI to the external output queue
|
|
802
1196
|
* instead of the internal instrument rack, so the track plays an external
|
|
803
|
-
* device. Clearing it restores internal-synth playback.
|
|
1197
|
+
* device. Clearing it restores internal-synth playback. Control-thread only.
|
|
1198
|
+
* The change takes effect at the next processed block; switching a
|
|
1199
|
+
* destination's route first releases its notes and resets its controllers
|
|
1200
|
+
* through the old route, and drops its pending MIDI-FX events. Single writer.
|
|
804
1201
|
*/
|
|
805
1202
|
setMidiDestinationExternal(destinationId, external) {
|
|
806
1203
|
this.native.setMidiDestinationExternal(destinationId, external);
|
|
@@ -825,6 +1222,12 @@ var RealtimeEngine = class {
|
|
|
825
1222
|
* block / animation frame. `maxRecords` caps the number of output events
|
|
826
1223
|
* returned — the shared unit across every surface. Events past the cap stay
|
|
827
1224
|
* queued for the next call (lossless); call again to drain the rest.
|
|
1225
|
+
*
|
|
1226
|
+
* One queued record lowers to at most 4 MIDI 1.0 messages (a MIDI 2.0
|
|
1227
|
+
* registered or assignable controller becomes CC 101/100 or 99/98 plus Data
|
|
1228
|
+
* Entry 6/38), so a positive `maxRecords` below 4 could never consume a record
|
|
1229
|
+
* and is rejected with an `InvalidParameter` `SonareError` instead of
|
|
1230
|
+
* returning nothing forever.
|
|
828
1231
|
*/
|
|
829
1232
|
drainExternalMidi(maxRecords = 1024) {
|
|
830
1233
|
return this.native.drainExternalMidi(maxRecords);
|
|
@@ -837,7 +1240,7 @@ var RealtimeEngine = class {
|
|
|
837
1240
|
return this.native.externalMidiScratchDestinationId();
|
|
838
1241
|
}
|
|
839
1242
|
externalMidiScratchRenderFrame() {
|
|
840
|
-
return this.native.externalMidiScratchRenderFrame();
|
|
1243
|
+
return Number(this.native.externalMidiScratchRenderFrame());
|
|
841
1244
|
}
|
|
842
1245
|
externalMidiScratchByteWord() {
|
|
843
1246
|
return this.native.externalMidiScratchByteWord();
|
|
@@ -857,6 +1260,31 @@ var RealtimeEngine = class {
|
|
|
857
1260
|
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
858
1261
|
this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
859
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Push a live MIDI pitch bend to the engine-owned MIDI input source.
|
|
1265
|
+
*
|
|
1266
|
+
* `bend14` is unsigned 14-bit with centre 8192 (0..16383) — the dimension is
|
|
1267
|
+
* not 7-bit, so a value past 16383 is refused rather than narrowed. The input
|
|
1268
|
+
* source must be enabled with {@link setMidiInputSource} first.
|
|
1269
|
+
*/
|
|
1270
|
+
pushMidiInputPitchBend(group, channel, bend14, portTimeSamples = 0) {
|
|
1271
|
+
this.native.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Push a live MIDI channel pressure to the engine-owned MIDI input source.
|
|
1275
|
+
* `pressure` is 7-bit (0..127). Under MPE this is the member channel's
|
|
1276
|
+
* per-note pressure.
|
|
1277
|
+
*/
|
|
1278
|
+
pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples = 0) {
|
|
1279
|
+
this.native.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Push a live MIDI polyphonic key pressure to the engine-owned MIDI input
|
|
1283
|
+
* source. `note` and `pressure` are 7-bit (0..127).
|
|
1284
|
+
*/
|
|
1285
|
+
pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples = 0) {
|
|
1286
|
+
this.native.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
|
|
1287
|
+
}
|
|
860
1288
|
pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
|
|
861
1289
|
this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
862
1290
|
}
|
|
@@ -872,15 +1300,62 @@ var RealtimeEngine = class {
|
|
|
872
1300
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
873
1301
|
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
874
1302
|
}
|
|
875
|
-
/**
|
|
876
|
-
|
|
877
|
-
|
|
1303
|
+
/**
|
|
1304
|
+
* Queue an immediate (live) MIDI pitch bend to a MIDI destination. `bend14`
|
|
1305
|
+
* is unsigned 14-bit with centre 8192 (0..16383); `renderFrame` is the frame
|
|
1306
|
+
* to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
|
|
1307
|
+
* `pushMidiPitchBend`.
|
|
1308
|
+
*/
|
|
1309
|
+
pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame = -1) {
|
|
1310
|
+
this.native.pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame);
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Queue an immediate (live) MIDI channel pressure to a MIDI destination.
|
|
1314
|
+
* `pressure` is 7-bit (0..127); `renderFrame` is the frame to fire at, or -1
|
|
1315
|
+
* for immediate. Mirrors the Node/Python/C-ABI `pushMidiChannelPressure`.
|
|
1316
|
+
*/
|
|
1317
|
+
pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame = -1) {
|
|
1318
|
+
this.native.pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Queue an immediate (live) MIDI polyphonic key pressure to a MIDI
|
|
1322
|
+
* destination. `note` and `pressure` are 7-bit (0..127); `renderFrame` is the
|
|
1323
|
+
* frame to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
|
|
1324
|
+
* `pushMidiPolyPressure`.
|
|
1325
|
+
*/
|
|
1326
|
+
pushMidiPolyPressure(destinationId, group, channel, note, pressure, renderFrame = -1) {
|
|
1327
|
+
this.native.pushMidiPolyPressure(destinationId, group, channel, note, pressure, renderFrame);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Queue an immediate (live) raw UMP message to a MIDI destination. `words` is
|
|
1331
|
+
* 1 to 4 words, most significant first, and its length must match the message
|
|
1332
|
+
* type of `words[0]`. MIDI 2.0 channel-voice messages (MT 0x4) arrive at full
|
|
1333
|
+
* width; SysEx7 / data messages (MT 0x3 / 0x5) are refused, use
|
|
1334
|
+
* {@link pushMidiSysex}. Throws when the slot ring or command queue is full
|
|
1335
|
+
* (retry after a process block). `renderFrame` is the render-frame time to
|
|
1336
|
+
* apply, or -1 for immediate. A bare number is accepted as a one-word
|
|
1337
|
+
* message.
|
|
1338
|
+
*/
|
|
1339
|
+
pushMidiUmp(destinationId, words, renderFrame = -1) {
|
|
1340
|
+
const list = typeof words === "number" ? [words] : words;
|
|
1341
|
+
this.native.pushMidiUmp(destinationId, assertUmpWords("pushMidiUmp", list), renderFrame);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Push one raw UMP message (1 to 4 words) to the engine-owned MIDI input
|
|
1345
|
+
* source. The message rules match {@link pushMidiUmp}. `portTimeSamples` is
|
|
1346
|
+
* the port timestamp in samples.
|
|
1347
|
+
*/
|
|
1348
|
+
pushMidiInputUmp(words, portTimeSamples = 0) {
|
|
1349
|
+
this.native.pushMidiInputUmp(assertUmpWords("pushMidiInputUmp", words), portTimeSamples);
|
|
878
1350
|
}
|
|
879
1351
|
/**
|
|
880
1352
|
* Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
|
|
881
1353
|
* the full message including the leading 0xF0 and trailing 0xF7 (1..512
|
|
882
|
-
* bytes). `renderFrame` is the frame to fire at, or -1 for immediate.
|
|
883
|
-
* the
|
|
1354
|
+
* bytes). `renderFrame` is the frame to fire at, or -1 for immediate. Throws
|
|
1355
|
+
* `InvalidParameter` when the destination instrument cannot prepare
|
|
1356
|
+
* the SysEx (retrying cannot help), and `OutOfMemory` when the payload
|
|
1357
|
+
* slots or the command queue are full (retry after a processed block).
|
|
1358
|
+
* Mirrors the Node/Python/C-ABI `pushMidiSysex`.
|
|
884
1359
|
*/
|
|
885
1360
|
pushMidiSysex(destinationId, data, renderFrame = -1) {
|
|
886
1361
|
this.native.pushMidiSysex(destinationId, data, renderFrame);
|
|
@@ -903,12 +1378,30 @@ var RealtimeEngine = class {
|
|
|
903
1378
|
getTransportState() {
|
|
904
1379
|
return this.native.getTransportState();
|
|
905
1380
|
}
|
|
1381
|
+
/** Queues an integrated-loudness reset; short-term and momentary windows are retained. */
|
|
1382
|
+
resetMasterLoudnessMeter(renderFrame = -1) {
|
|
1383
|
+
this.native.resetMasterLoudnessMeter(renderFrame);
|
|
1384
|
+
}
|
|
1385
|
+
/** Reads the immutable factory value for a resolved insert parameter id. */
|
|
1386
|
+
insertParameterConstructedValue(paramId) {
|
|
1387
|
+
return this.native.insertParameterConstructedValue(paramId);
|
|
1388
|
+
}
|
|
906
1389
|
play(renderFrame = -1) {
|
|
907
1390
|
this.native.play(renderFrame);
|
|
908
1391
|
}
|
|
1392
|
+
/**
|
|
1393
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
1394
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
1395
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
1396
|
+
*/
|
|
909
1397
|
stop(renderFrame = -1) {
|
|
910
1398
|
this.native.stop(renderFrame);
|
|
911
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
1402
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
1403
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
1404
|
+
*/
|
|
912
1405
|
seekSample(timelineSample, renderFrame = -1) {
|
|
913
1406
|
this.native.seekSample(timelineSample, renderFrame);
|
|
914
1407
|
}
|
|
@@ -921,10 +1414,23 @@ var RealtimeEngine = class {
|
|
|
921
1414
|
settleParameters() {
|
|
922
1415
|
this.native.settleParameters();
|
|
923
1416
|
}
|
|
1417
|
+
/** Snap only insert automation slots after structural replay. */
|
|
1418
|
+
settleInsertParameters() {
|
|
1419
|
+
this.native.settleInsertParameters();
|
|
1420
|
+
}
|
|
924
1421
|
/** Drains queued commands on an offline/control-only engine immediately. */
|
|
925
1422
|
flushControlCommands() {
|
|
926
1423
|
this.native.flushControlCommands();
|
|
927
1424
|
}
|
|
1425
|
+
/** Applies commands already due on a control-only mirror, retaining future commands. */
|
|
1426
|
+
applyCommandsDueNowPreservingFuture() {
|
|
1427
|
+
this.native.applyCommandsDueNowPreservingFuture();
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
1431
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
1432
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
1433
|
+
*/
|
|
928
1434
|
seekPpq(ppq, renderFrame = -1) {
|
|
929
1435
|
this.native.seekPpq(ppq, renderFrame);
|
|
930
1436
|
}
|
|
@@ -944,6 +1450,11 @@ var RealtimeEngine = class {
|
|
|
944
1450
|
sampleAtPpq(ppq) {
|
|
945
1451
|
return Number(this.native.sampleAtPpq(ppq));
|
|
946
1452
|
}
|
|
1453
|
+
/**
|
|
1454
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
1455
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
1456
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
1457
|
+
*/
|
|
947
1458
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
948
1459
|
this.native.setLoop(startPpq, endPpq, enabled);
|
|
949
1460
|
}
|
|
@@ -1020,22 +1531,27 @@ var RealtimeEngine = class {
|
|
|
1020
1531
|
clipCount() {
|
|
1021
1532
|
return this.native.clipCount();
|
|
1022
1533
|
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Normalizes each send's pre/post tap point to the integer the native layer
|
|
1536
|
+
* reads (defaults to post-fader when omitted). Shared by track lanes and
|
|
1537
|
+
* buses, which carry the same send shape.
|
|
1538
|
+
*/
|
|
1539
|
+
static normalizeSends(sends) {
|
|
1540
|
+
return sends.map((send) => ({
|
|
1541
|
+
...send,
|
|
1542
|
+
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1543
|
+
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1544
|
+
}));
|
|
1545
|
+
}
|
|
1023
1546
|
setTrackLanes(lanes) {
|
|
1024
1547
|
this.native.setTrackLanes(
|
|
1025
1548
|
lanes.map((lane) => {
|
|
1026
1549
|
if (typeof lane === "number") {
|
|
1027
1550
|
return { trackId: lane };
|
|
1028
1551
|
}
|
|
1029
|
-
if (!lane.sends) {
|
|
1030
|
-
return lane;
|
|
1031
|
-
}
|
|
1032
1552
|
return {
|
|
1033
1553
|
...lane,
|
|
1034
|
-
sends: lane.sends.
|
|
1035
|
-
...send,
|
|
1036
|
-
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1037
|
-
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1038
|
-
}))
|
|
1554
|
+
sends: lane.sends ? _RealtimeEngine.normalizeSends(lane.sends) : void 0
|
|
1039
1555
|
};
|
|
1040
1556
|
})
|
|
1041
1557
|
);
|
|
@@ -1048,7 +1564,26 @@ var RealtimeEngine = class {
|
|
|
1048
1564
|
this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
1049
1565
|
}
|
|
1050
1566
|
setTrackBuses(buses) {
|
|
1051
|
-
this.native.setTrackBuses(
|
|
1567
|
+
this.native.setTrackBuses(
|
|
1568
|
+
Array.isArray(buses) ? buses.map((bus) => ({
|
|
1569
|
+
...bus,
|
|
1570
|
+
sends: bus.sends ? _RealtimeEngine.normalizeSends(bus.sends) : void 0
|
|
1571
|
+
})) : buses
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Keys one insert of a bus strip from a track lane or another bus
|
|
1576
|
+
* (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
|
|
1577
|
+
*/
|
|
1578
|
+
setBusSidechain(busId, insertIndex, sourceKind, sourceId) {
|
|
1579
|
+
this.native.setBusSidechain(busId, insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Keys one insert of the master strip from a track lane or a bus. Same
|
|
1583
|
+
* source rules as {@link setBusSidechain}.
|
|
1584
|
+
*/
|
|
1585
|
+
setMasterSidechain(insertIndex, sourceKind, sourceId) {
|
|
1586
|
+
this.native.setMasterSidechain(insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
|
|
1052
1587
|
}
|
|
1053
1588
|
setBusStripJson(busId, sceneJson) {
|
|
1054
1589
|
try {
|
|
@@ -1081,6 +1616,17 @@ var RealtimeEngine = class {
|
|
|
1081
1616
|
setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1082
1617
|
this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
1083
1618
|
}
|
|
1619
|
+
/** Bus-strip counterpart of {@link setTrackStripEqBand}. */
|
|
1620
|
+
setBusStripEqBand(busId, bandIndex, band) {
|
|
1621
|
+
this.native.setBusStripEqBandJson(
|
|
1622
|
+
busId,
|
|
1623
|
+
bandIndex,
|
|
1624
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
setBusStripEqBandJson(busId, bandIndex, bandJson) {
|
|
1628
|
+
this.native.setBusStripEqBandJson(busId, bandIndex, bandJson);
|
|
1629
|
+
}
|
|
1084
1630
|
setMasterStripJson(sceneJson) {
|
|
1085
1631
|
try {
|
|
1086
1632
|
JSON.parse(sceneJson);
|
|
@@ -1104,22 +1650,59 @@ var RealtimeEngine = class {
|
|
|
1104
1650
|
}
|
|
1105
1651
|
/**
|
|
1106
1652
|
* Changes one track-strip insert parameter in realtime, addressed by the
|
|
1107
|
-
* processor's JSON-key parameter name
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1653
|
+
* processor's JSON-key parameter name — one of the entries
|
|
1654
|
+
* {@link masteringInsertParamInfo} reports with a non-null `id`; a
|
|
1655
|
+
* construction-only entry (`id` null) takes effect only when the insert is
|
|
1656
|
+
* built. Applied at the next block head via the engine command queue; safe
|
|
1657
|
+
* during playback. Throws if the track, insert, or name is unknown, the
|
|
1658
|
+
* param is not realtime-safe, or the command queue is full.
|
|
1111
1659
|
*/
|
|
1112
1660
|
setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1113
1661
|
this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1114
1662
|
}
|
|
1663
|
+
/** Apply a live insert edit on this engine's owning thread without draining its command queue. */
|
|
1664
|
+
applyTrackStripInsertParamByNameNow(trackId, insertIndex, paramName, value) {
|
|
1665
|
+
return this.native.applyTrackStripInsertParamByNameNow(trackId, insertIndex, paramName, value);
|
|
1666
|
+
}
|
|
1667
|
+
/** Restore a retained insert value exactly after a strip scene is replayed. */
|
|
1668
|
+
restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1669
|
+
this.native.restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1670
|
+
}
|
|
1115
1671
|
/** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1116
1672
|
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1117
1673
|
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1118
1674
|
}
|
|
1675
|
+
applyMasterStripInsertParamByNameNow(insertIndex, paramName, value) {
|
|
1676
|
+
return this.native.applyMasterStripInsertParamByNameNow(insertIndex, paramName, value);
|
|
1677
|
+
}
|
|
1678
|
+
restoreMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1679
|
+
this.native.restoreMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1680
|
+
}
|
|
1119
1681
|
/** Bus-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1120
1682
|
setBusStripInsertParamByName(busId, insertIndex, paramName, value) {
|
|
1121
1683
|
this.native.setBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
1122
1684
|
}
|
|
1685
|
+
applyBusStripInsertParamByNameNow(busId, insertIndex, paramName, value) {
|
|
1686
|
+
return this.native.applyBusStripInsertParamByNameNow(busId, insertIndex, paramName, value);
|
|
1687
|
+
}
|
|
1688
|
+
restoreBusStripInsertParamByName(busId, insertIndex, paramName, value) {
|
|
1689
|
+
this.native.restoreBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Forgets the remembered manual insert-parameter values of one track strip
|
|
1693
|
+
* and discards its queued insert edits. Call before {@link setTrackStripJson}
|
|
1694
|
+
* replaces the strip when its old values must not carry over; the setter
|
|
1695
|
+
* never does this itself, since a queued edit may already target the new chain.
|
|
1696
|
+
*/
|
|
1697
|
+
clearTrackInsertParameterBases(trackId) {
|
|
1698
|
+
this.native.clearTrackInsertParameterBases(trackId);
|
|
1699
|
+
}
|
|
1700
|
+
clearBusInsertParameterBases(busId) {
|
|
1701
|
+
this.native.clearBusInsertParameterBases(busId);
|
|
1702
|
+
}
|
|
1703
|
+
clearMasterInsertParameterBases() {
|
|
1704
|
+
this.native.clearMasterInsertParameterBases();
|
|
1705
|
+
}
|
|
1123
1706
|
/** Bus-strip counterpart of {@link setTrackStripInsertBypassed}. */
|
|
1124
1707
|
setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1125
1708
|
this.native.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
@@ -1139,6 +1722,16 @@ var RealtimeEngine = class {
|
|
|
1139
1722
|
* stages of the offline mastering chain (`repair.*`, `loudness`, and the
|
|
1140
1723
|
* match stages) have no insert form and no automation id: they buffer the
|
|
1141
1724
|
* entire signal by construction and do not run on the realtime path.
|
|
1725
|
+
*
|
|
1726
|
+
* The returned id uses the track's current positional lane selector. When
|
|
1727
|
+
* `setTrackLanes` successfully changes lane order or membership, the engine
|
|
1728
|
+
* remaps already queued and published track automation by track id, but it
|
|
1729
|
+
* cannot update a numeric id retained by the caller. Re-resolve every track
|
|
1730
|
+
* insert id after such a topology change before passing it to
|
|
1731
|
+
* `setAutomationLane`, `setParameter`, or `setParameterSmoothed`. Use
|
|
1732
|
+
* `setTrackStripInsertParamByName` when the operation needs a stable track
|
|
1733
|
+
* identity. Master and bus insert ids are separate and are not invalidated by
|
|
1734
|
+
* track-lane changes.
|
|
1142
1735
|
*/
|
|
1143
1736
|
resolveTrackInsertAutomationId(trackId, insertIndex, paramName) {
|
|
1144
1737
|
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
@@ -1149,6 +1742,41 @@ var RealtimeEngine = class {
|
|
|
1149
1742
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
1150
1743
|
return this.native.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
1151
1744
|
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Resolves a hosted instrument's continuous parameter (by its JSON-key name)
|
|
1747
|
+
* to the reserved automation id usable with `setAutomationLane` /
|
|
1748
|
+
* `setParameter`, so an instrument parameter is driven at audio-block
|
|
1749
|
+
* precision exactly like a strip insert. Returns `-1` when the destination
|
|
1750
|
+
* has no bound instrument, the instrument exposes no automatable parameters,
|
|
1751
|
+
* or the name is unknown.
|
|
1752
|
+
*
|
|
1753
|
+
* For the NativeSynth ({@link setSynthInstrument}) the names are the
|
|
1754
|
+
* continuous {@link SynthPatch} fields: `gain`, `busDrive`, `cutoffHz`,
|
|
1755
|
+
* `resonanceQ`, `drive`, `keyTrack`, `envToCutoffCents`, `velToCutoffCents`,
|
|
1756
|
+
* `ampAttackMs`, `ampDecayMs`, `ampSustain`, `ampReleaseMs`,
|
|
1757
|
+
* `filterAttackMs`, `filterDecayMs`, `filterSustain`, `filterReleaseMs`,
|
|
1758
|
+
* `lfoRateHz`, `lfoToPitchCents`, `lfo2RateHz`, `glideMs`, `bodyMix`,
|
|
1759
|
+
* `stereoSpread`, `detuneCents`, `driftCents`, `pitchOffsetCents`,
|
|
1760
|
+
* `hpCutoffHz`, `sampleHoldHz`, `bitDepth`.
|
|
1761
|
+
*
|
|
1762
|
+
* Structural fields (`preset`, `engineMode`, `waveform`, `filterModel`,
|
|
1763
|
+
* `unison`, `polyphony`, `body`, `modRoutings`) are not automatable and
|
|
1764
|
+
* return `-1`: they resize voice pools or swap DSP topology, which is not
|
|
1765
|
+
* audio-thread safe. Rebind the instrument with a new patch instead.
|
|
1766
|
+
*
|
|
1767
|
+
* `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
|
|
1768
|
+
* `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
|
|
1769
|
+
* sounding from the next block; the rest are cached into per-voice state at
|
|
1770
|
+
* note-on and take effect from the next note, so a lane that moves one of
|
|
1771
|
+
* them under a held note looks inert until the next one speaks — that is the
|
|
1772
|
+
* behaviour, not a dropped write.
|
|
1773
|
+
*
|
|
1774
|
+
* The id survives an unbind/rebind of the same destination and applies
|
|
1775
|
+
* nothing while that destination is unbound.
|
|
1776
|
+
*/
|
|
1777
|
+
resolveInstrumentAutomationId(destinationId, paramName) {
|
|
1778
|
+
return this.native.resolveInstrumentAutomationId(destinationId, paramName);
|
|
1779
|
+
}
|
|
1152
1780
|
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
1153
1781
|
setTrackStripPan(trackId, pan) {
|
|
1154
1782
|
this.native.setTrackStripPan(trackId, pan);
|
|
@@ -1166,11 +1794,30 @@ var RealtimeEngine = class {
|
|
|
1166
1794
|
this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
1167
1795
|
}
|
|
1168
1796
|
/**
|
|
1169
|
-
* Sets a
|
|
1170
|
-
*
|
|
1797
|
+
* Sets a bus strip's output pan position in realtime (glitch-free). Throws
|
|
1798
|
+
* for an unknown bus or one wider than stereo.
|
|
1171
1799
|
*/
|
|
1172
|
-
|
|
1173
|
-
this.native.
|
|
1800
|
+
setBusStripPan(busId, pan) {
|
|
1801
|
+
this.native.setBusStripPan(busId, pan);
|
|
1802
|
+
}
|
|
1803
|
+
/** Sets a bus strip's pan law in realtime. */
|
|
1804
|
+
setBusStripPanLaw(busId, panLaw) {
|
|
1805
|
+
this.native.setBusStripPanLaw(busId, panLawCode(panLaw));
|
|
1806
|
+
}
|
|
1807
|
+
/** Sets a bus strip's pan mode in realtime. */
|
|
1808
|
+
setBusStripPanMode(busId, panMode) {
|
|
1809
|
+
this.native.setBusStripPanMode(busId, panModeCode(panMode));
|
|
1810
|
+
}
|
|
1811
|
+
/** Sets a bus strip's dual-pan left/right positions in realtime. */
|
|
1812
|
+
setBusStripDualPan(busId, leftPan, rightPan) {
|
|
1813
|
+
this.native.setBusStripDualPan(busId, leftPan, rightPan);
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Sets a track lane strip's inter-channel alignment delay (whole samples).
|
|
1817
|
+
* Adjusts strip latency, so PDC and reported graph latency are refreshed.
|
|
1818
|
+
*/
|
|
1819
|
+
setTrackStripChannelDelaySamples(trackId, delaySamples) {
|
|
1820
|
+
this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
1174
1821
|
}
|
|
1175
1822
|
createClipPageProvider(numChannels, numSamples, pageFrames) {
|
|
1176
1823
|
const id = this.native.createClipPageProvider(numChannels, numSamples, pageFrames);
|
|
@@ -1205,6 +1852,52 @@ var RealtimeEngine = class {
|
|
|
1205
1852
|
clipPageRequestOverflowCount() {
|
|
1206
1853
|
return this.native.clipPageRequestOverflowCount();
|
|
1207
1854
|
}
|
|
1855
|
+
/** Cumulative warp-stretch requests dropped because the native queue was full. */
|
|
1856
|
+
warpStretchOverflowCount() {
|
|
1857
|
+
return this.native.warpStretchOverflowCount();
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Sets the number of concurrent time-stretch voices. `voices` must be an
|
|
1861
|
+
* integer in `[0, 64]`; a non-integer, negative, or larger value throws and
|
|
1862
|
+
* leaves the capacity unchanged. Default is 8. Capacity 0 disables
|
|
1863
|
+
* time-stretch, so every warped clip plays resampled instead and none of
|
|
1864
|
+
* that counts toward {@link warpStretchOverflowCount}. A change applied
|
|
1865
|
+
* while the engine is running restarts the splice state of any clip
|
|
1866
|
+
* stretching through a voice at that moment. Control-thread only.
|
|
1867
|
+
*/
|
|
1868
|
+
setWarpVoiceCapacity(voices) {
|
|
1869
|
+
this.native.setWarpVoiceCapacity(voices);
|
|
1870
|
+
}
|
|
1871
|
+
/** Reads the current time-stretch voice capacity (default 8). */
|
|
1872
|
+
warpVoiceCapacity() {
|
|
1873
|
+
return this.native.warpVoiceCapacity();
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Sets the clip-page look-ahead window in timeline frames.
|
|
1877
|
+
*
|
|
1878
|
+
* The player reports the pages it is *about to* read that are not resident
|
|
1879
|
+
* yet, so a streaming host can service them before the audio thread reaches
|
|
1880
|
+
* them. Without look-ahead a page miss is only reported after the read
|
|
1881
|
+
* already produced silence, which costs one block of silence at every page
|
|
1882
|
+
* boundary the host has not primed — the reason a sliding-window streamer
|
|
1883
|
+
* cannot keep a live playhead fed from miss reports alone.
|
|
1884
|
+
*
|
|
1885
|
+
* Look-ahead requests drain through the same `popClipPageRequest` queue and
|
|
1886
|
+
* are queued *after* the block's genuine misses, so a host that keeps only
|
|
1887
|
+
* the newest request per clip (as {@link ClipPageStreamer} does) tracks the
|
|
1888
|
+
* look-ahead frontier.
|
|
1889
|
+
*
|
|
1890
|
+
* `prepare` defaults this to half a second at the engine's sample rate. `0`
|
|
1891
|
+
* disables the look-ahead. A clip whose pages are all resident produces no
|
|
1892
|
+
* requests at all, with or without look-ahead. Safe to call during playback.
|
|
1893
|
+
*/
|
|
1894
|
+
setClipPagePrefetchFrames(frames) {
|
|
1895
|
+
this.native.setClipPagePrefetchFrames(frames);
|
|
1896
|
+
}
|
|
1897
|
+
/** Current clip-page look-ahead window in timeline frames. */
|
|
1898
|
+
clipPagePrefetchFrames() {
|
|
1899
|
+
return this.native.clipPagePrefetchFrames();
|
|
1900
|
+
}
|
|
1208
1901
|
setCaptureBuffer(numChannels, capacityFrames) {
|
|
1209
1902
|
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
1210
1903
|
}
|
|
@@ -1296,12 +1989,36 @@ var RealtimeEngine = class {
|
|
|
1296
1989
|
processWithMonitor(channels) {
|
|
1297
1990
|
return this.native.processWithMonitor(channels);
|
|
1298
1991
|
}
|
|
1299
|
-
renderOffline(
|
|
1300
|
-
|
|
1992
|
+
renderOffline(channelsOrRequest, blockSize = 128) {
|
|
1993
|
+
const request = normalizeRenderOfflineRequest(channelsOrRequest, blockSize);
|
|
1994
|
+
return this.native.renderOffline(request.channels, request.blockSize, request.finalize);
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* End a chunked offline render: release every note the sequencer still holds
|
|
1998
|
+
* and flush the PDC / alignment delay lines. Required after
|
|
1999
|
+
* `renderOffline({ finalize: false })`; the finalizing form does it itself.
|
|
2000
|
+
*
|
|
2001
|
+
* Skipping it leaves every note still sounding at the last chunk held. On an
|
|
2002
|
+
* engine-internal instrument the tail simply never releases; on a destination
|
|
2003
|
+
* marked external ({@link RealtimeEngine.setMidiDestinationExternal}) the
|
|
2004
|
+
* note-ons already left through the external MIDI queue, so the note-offs
|
|
2005
|
+
* emitted here are the only ones the receiving device will get and the notes
|
|
2006
|
+
* otherwise hang outside the engine.
|
|
2007
|
+
*/
|
|
2008
|
+
finishOfflineRender() {
|
|
2009
|
+
this.native.finishOfflineRender();
|
|
1301
2010
|
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Bounce the timeline to an interleaved buffer. `numChannels` above the
|
|
2013
|
+
* prepared channel count throws an `InvalidParameter` `SonareError`.
|
|
2014
|
+
*/
|
|
1302
2015
|
bounceOffline(options) {
|
|
1303
2016
|
return this.native.bounceOffline(options);
|
|
1304
2017
|
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Freeze the current graph to audio. `numChannels` above the prepared channel
|
|
2020
|
+
* count throws an `InvalidParameter` `SonareError`.
|
|
2021
|
+
*/
|
|
1305
2022
|
freezeOffline(options) {
|
|
1306
2023
|
return this.native.freezeOffline(options);
|
|
1307
2024
|
}
|
|
@@ -1341,6 +2058,12 @@ var RealtimeEngine = class {
|
|
|
1341
2058
|
meterScratchRenderFrame() {
|
|
1342
2059
|
return Number(this.native.meterScratchRenderFrame());
|
|
1343
2060
|
}
|
|
2061
|
+
meterScratchInputPeakDbL() {
|
|
2062
|
+
return this.native.meterScratchInputPeakDbL();
|
|
2063
|
+
}
|
|
2064
|
+
meterScratchInputPeakDbR() {
|
|
2065
|
+
return this.native.meterScratchInputPeakDbR();
|
|
2066
|
+
}
|
|
1344
2067
|
meterScratchValue(field) {
|
|
1345
2068
|
return this.native.meterScratchValue(field);
|
|
1346
2069
|
}
|
|
@@ -1350,9 +2073,10 @@ var RealtimeEngine = class {
|
|
|
1350
2073
|
/**
|
|
1351
2074
|
* Drains pending meter telemetry as per-plane (wide) records for a surround
|
|
1352
2075
|
* target. Use this for a surround mix target; {@link drainMeterTelemetry}
|
|
1353
|
-
* stays the stereo fast path. The two share one queue
|
|
1354
|
-
* target
|
|
1355
|
-
* this wide drain is for an
|
|
2076
|
+
* stays the stereo fast path. The two share one queue and each consumes every
|
|
2077
|
+
* target's records, so an engine uses only one of them. The live AudioWorklet
|
|
2078
|
+
* path owns the queue via the stereo drain, so this wide drain is for an
|
|
2079
|
+
* offline (non-worklet) engine instance; per-plane
|
|
1356
2080
|
* surround meters are not delivered over the live worklet meter ring.
|
|
1357
2081
|
*/
|
|
1358
2082
|
drainMeterTelemetryWide(maxRecords = 1024) {
|
|
@@ -1395,9 +2119,18 @@ var RealtimeEngine = class {
|
|
|
1395
2119
|
scopeScratchPointRight(index) {
|
|
1396
2120
|
return this.native.scopeScratchPointRight(index);
|
|
1397
2121
|
}
|
|
2122
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1398
2123
|
destroy() {
|
|
2124
|
+
if (this.released) {
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
this.released = true;
|
|
1399
2128
|
this.native.delete();
|
|
1400
2129
|
}
|
|
2130
|
+
/** Alias for {@link destroy}, matching embind's own release method name. */
|
|
2131
|
+
delete() {
|
|
2132
|
+
this.destroy();
|
|
2133
|
+
}
|
|
1401
2134
|
};
|
|
1402
2135
|
var ClipPageProvider = class {
|
|
1403
2136
|
constructor(engine, id) {
|
|
@@ -1429,12 +2162,20 @@ var ClipPageProvider = class {
|
|
|
1429
2162
|
// src/mixer.ts
|
|
1430
2163
|
var Mixer = class _Mixer {
|
|
1431
2164
|
constructor(mixer, blockSize) {
|
|
2165
|
+
this.released = false;
|
|
1432
2166
|
this.mixer = mixer;
|
|
1433
2167
|
this.blockSize = blockSize;
|
|
1434
2168
|
}
|
|
1435
2169
|
/**
|
|
1436
2170
|
* Build a mixer from a scene JSON string.
|
|
1437
2171
|
*
|
|
2172
|
+
* A strip's meters are sized when the strip is built, so this is where their
|
|
2173
|
+
* configuration is chosen: an optional `metering` object on the strip
|
|
2174
|
+
* (`enabled` / `lufs` / `truePeak` / `truePeakOversample`) selects it, and
|
|
2175
|
+
* leaving it out keeps the full default (LUFS + true peak at 4x, about 1.4 MB
|
|
2176
|
+
* per strip at 48 kHz). `{"enabled": false}` drops both meters for a strip
|
|
2177
|
+
* whose snapshots are never read.
|
|
2178
|
+
*
|
|
1438
2179
|
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
1439
2180
|
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
1440
2181
|
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
@@ -1497,50 +2238,113 @@ var Mixer = class _Mixer {
|
|
|
1497
2238
|
* after {@link delete}.
|
|
1498
2239
|
*/
|
|
1499
2240
|
createRealtimeBuffer() {
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
let rightInputs = [];
|
|
2241
|
+
const leftInputs = [];
|
|
2242
|
+
const rightInputs = [];
|
|
1503
2243
|
let outLeft = this.mixer.outputLeftView();
|
|
1504
2244
|
let outRight = this.mixer.outputRightView();
|
|
2245
|
+
let acquiredStripCount = -1;
|
|
2246
|
+
const viewsDetached = () => outLeft.byteLength === 0 || outRight.byteLength === 0;
|
|
1505
2247
|
const acquire = () => {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2248
|
+
const stripCount = this.stripCount();
|
|
2249
|
+
const detached = viewsDetached();
|
|
2250
|
+
if (detached) {
|
|
2251
|
+
leftInputs.length = 0;
|
|
2252
|
+
rightInputs.length = 0;
|
|
2253
|
+
} else {
|
|
2254
|
+
leftInputs.length = Math.min(leftInputs.length, stripCount);
|
|
2255
|
+
rightInputs.length = Math.min(rightInputs.length, stripCount);
|
|
2256
|
+
}
|
|
2257
|
+
for (let index = leftInputs.length; index < stripCount; index++) {
|
|
1509
2258
|
leftInputs.push(this.mixer.inputLeftView(index));
|
|
2259
|
+
}
|
|
2260
|
+
for (let index = rightInputs.length; index < stripCount; index++) {
|
|
1510
2261
|
rightInputs.push(this.mixer.inputRightView(index));
|
|
1511
2262
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
2263
|
+
if (detached) {
|
|
2264
|
+
outLeft = this.mixer.outputLeftView();
|
|
2265
|
+
outRight = this.mixer.outputRightView();
|
|
2266
|
+
}
|
|
2267
|
+
acquiredStripCount = stripCount;
|
|
1514
2268
|
};
|
|
1515
2269
|
acquire();
|
|
1516
|
-
const
|
|
1517
|
-
if (
|
|
2270
|
+
const acquireIfNeeded = () => {
|
|
2271
|
+
if (acquiredStripCount !== this.stripCount() || viewsDetached()) {
|
|
1518
2272
|
acquire();
|
|
1519
2273
|
}
|
|
1520
2274
|
};
|
|
1521
2275
|
return {
|
|
1522
2276
|
get leftInputs() {
|
|
1523
|
-
|
|
2277
|
+
acquireIfNeeded();
|
|
1524
2278
|
return leftInputs;
|
|
1525
2279
|
},
|
|
1526
2280
|
get rightInputs() {
|
|
1527
|
-
|
|
2281
|
+
acquireIfNeeded();
|
|
1528
2282
|
return rightInputs;
|
|
1529
2283
|
},
|
|
1530
2284
|
get outLeft() {
|
|
1531
|
-
|
|
2285
|
+
acquireIfNeeded();
|
|
1532
2286
|
return outLeft;
|
|
1533
2287
|
},
|
|
1534
2288
|
get outRight() {
|
|
1535
|
-
|
|
2289
|
+
acquireIfNeeded();
|
|
1536
2290
|
return outRight;
|
|
1537
2291
|
},
|
|
1538
|
-
process: (numSamples
|
|
1539
|
-
|
|
1540
|
-
this.mixer.processPreparedStereo(numSamples);
|
|
2292
|
+
process: (numSamples) => {
|
|
2293
|
+
acquireIfNeeded();
|
|
2294
|
+
this.mixer.processPreparedStereo(numSamples ?? outLeft.length);
|
|
1541
2295
|
}
|
|
1542
2296
|
};
|
|
1543
2297
|
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Turn the master-output meter on or off.
|
|
2300
|
+
*
|
|
2301
|
+
* While on, every {@link MixerRealtimeBuffer.process} call meters the stereo
|
|
2302
|
+
* master it just produced, so a caller reads {@link meterSnapshot} instead of
|
|
2303
|
+
* copying the output and measuring it again. `truePeakDb*` is an inter-sample
|
|
2304
|
+
* peak taken after oversampling (ITU-R BS.1770-4 Annex 2 requires at least
|
|
2305
|
+
* 4x), which is a different and higher quantity than the sample peak.
|
|
2306
|
+
*
|
|
2307
|
+
* Enabling resets the meter, so a reading never mixes in audio from a period
|
|
2308
|
+
* when metering was off.
|
|
2309
|
+
*
|
|
2310
|
+
* @param enabled - Whether to meter the master output.
|
|
2311
|
+
* @param truePeakOversample - 0 (= 4x) or a power of two in [1, 16].
|
|
2312
|
+
*/
|
|
2313
|
+
configureMeter(enabled, truePeakOversample = 4) {
|
|
2314
|
+
this.mixer.configureMeter(enabled, truePeakOversample);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Latest master-output meter reading, describing the most recently metered
|
|
2318
|
+
* block. All dB fields are finite and floored at -120.
|
|
2319
|
+
*
|
|
2320
|
+
* @throws When the meter has never been enabled.
|
|
2321
|
+
*/
|
|
2322
|
+
meterSnapshot() {
|
|
2323
|
+
return this.mixer.meterSnapshot();
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Latch the latest meter reading into the mixer's internal scratch so
|
|
2327
|
+
* {@link meterScratchValue} can read it back one number at a time.
|
|
2328
|
+
*
|
|
2329
|
+
* This is the allocation-free form of {@link meterSnapshot}, for an audio
|
|
2330
|
+
* render callback that must not create a JS object per interval. It returns
|
|
2331
|
+
* `false` instead of throwing when the meter has never been enabled.
|
|
2332
|
+
*
|
|
2333
|
+
* @returns Whether a reading was latched.
|
|
2334
|
+
*/
|
|
2335
|
+
latchMeterSnapshot() {
|
|
2336
|
+
return this.mixer.latchMeterSnapshot();
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Read one field of the snapshot latched by {@link latchMeterSnapshot}.
|
|
2340
|
+
*
|
|
2341
|
+
* @param field - `0` peakDbL, `1` peakDbR, `2` rmsDbL, `3` rmsDbR,
|
|
2342
|
+
* `4` correlation, `5` truePeakDbL, `6` truePeakDbR. Any other index
|
|
2343
|
+
* reads `0`.
|
|
2344
|
+
*/
|
|
2345
|
+
meterScratchValue(field) {
|
|
2346
|
+
return this.mixer.meterScratchValue(field);
|
|
2347
|
+
}
|
|
1544
2348
|
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
1545
2349
|
stripCount() {
|
|
1546
2350
|
return this.mixer.stripCount();
|
|
@@ -1578,6 +2382,17 @@ var Mixer = class _Mixer {
|
|
|
1578
2382
|
const index = this.mixer.stripById(id);
|
|
1579
2383
|
return index < 0 ? null : index;
|
|
1580
2384
|
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Add a channel strip to the mixer topology. `metering` configures the strip's
|
|
2387
|
+
* pre/post taps; omitting it keeps the full default (LUFS + true peak at 4x,
|
|
2388
|
+
* about 1.4 MB per strip at 48 kHz). Marks the routing graph dirty; call
|
|
2389
|
+
* {@link compile} (or {@link processStereo}) to rebuild.
|
|
2390
|
+
*
|
|
2391
|
+
* @throws If the id is already taken, or `truePeakOversample` is outside `[0, 16]`
|
|
2392
|
+
*/
|
|
2393
|
+
addStrip(id, metering = {}) {
|
|
2394
|
+
this.mixer.addStrip(id, metering);
|
|
2395
|
+
}
|
|
1581
2396
|
/**
|
|
1582
2397
|
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
1583
2398
|
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
@@ -1643,6 +2458,30 @@ var Mixer = class _Mixer {
|
|
|
1643
2458
|
setWidth(stripIndex, width) {
|
|
1644
2459
|
this.mixer.setWidth(stripIndex, width);
|
|
1645
2460
|
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Snap the strip's input-trim, fader, pan and width smoothers to the values
|
|
2463
|
+
* already set on it, so the next processed block opens at those values
|
|
2464
|
+
* instead of gliding to them over the smoothing window (~5 ms).
|
|
2465
|
+
*
|
|
2466
|
+
* Call it after configuring a strip and before rendering a finite buffer: a
|
|
2467
|
+
* strip is smoothed for a live fader, and an offline render that does not
|
|
2468
|
+
* settle carries that glide as a level and image sweep across the head of
|
|
2469
|
+
* its output. Unlike a reset it clears nothing — automation, meters and
|
|
2470
|
+
* insert state are untouched.
|
|
2471
|
+
*
|
|
2472
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2473
|
+
*
|
|
2474
|
+
* @example
|
|
2475
|
+
* ```typescript
|
|
2476
|
+
* mixer.setFaderDb(0, -3);
|
|
2477
|
+
* mixer.setPan(0, 0.3);
|
|
2478
|
+
* mixer.settle(0);
|
|
2479
|
+
* const { left, right } = mixer.processStereo([dryLeft], [dryRight]);
|
|
2480
|
+
* ```
|
|
2481
|
+
*/
|
|
2482
|
+
settle(stripIndex) {
|
|
2483
|
+
this.mixer.settle(stripIndex);
|
|
2484
|
+
}
|
|
1646
2485
|
/** Set the strip's mute state. */
|
|
1647
2486
|
setMuted(stripIndex, muted) {
|
|
1648
2487
|
this.mixer.setMuted(stripIndex, muted);
|
|
@@ -1686,7 +2525,10 @@ var Mixer = class _Mixer {
|
|
|
1686
2525
|
}
|
|
1687
2526
|
/**
|
|
1688
2527
|
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
1689
|
-
*
|
|
2528
|
+
*
|
|
2529
|
+
* Applied when the engine's track mixer renders this strip's lane into a
|
|
2530
|
+
* destination with more than two channels. This stereo-only mixer's own
|
|
2531
|
+
* block entry points ignore it.
|
|
1690
2532
|
*/
|
|
1691
2533
|
setSurroundPan(stripIndex, pan) {
|
|
1692
2534
|
this.mixer.setSurroundPan(stripIndex, pan);
|
|
@@ -1752,6 +2594,59 @@ var Mixer = class _Mixer {
|
|
|
1752
2594
|
busMeter(busId) {
|
|
1753
2595
|
return this.mixer.busMeter(busId);
|
|
1754
2596
|
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Number of blocks in which the strip discarded recursive state because a
|
|
2599
|
+
* non-finite value had reached it.
|
|
2600
|
+
*
|
|
2601
|
+
* Advisory telemetry, and the only thing that separates a degraded strip
|
|
2602
|
+
* from a clean one. A discard returns the affected state to its
|
|
2603
|
+
* post-reset value, so the strip recovers in silence and the output stays
|
|
2604
|
+
* finite and in range while carrying samples unrelated to the input;
|
|
2605
|
+
* nothing else reports that this happened.
|
|
2606
|
+
*
|
|
2607
|
+
* The count covers the strip's own state, its EQ, every insert it owns and
|
|
2608
|
+
* both of its meters. None of those is separately addressable here, so a
|
|
2609
|
+
* discard inside one is observable only through this number -- and a
|
|
2610
|
+
* meter that loses its loudness window then reports the floor, which is
|
|
2611
|
+
* exactly what a genuinely silent strip reports, so nothing else
|
|
2612
|
+
* distinguishes the two.
|
|
2613
|
+
*
|
|
2614
|
+
* A meter's own discard lags by one block: it checks its loudness state at
|
|
2615
|
+
* the top of a block, before consuming that block's samples, so the block
|
|
2616
|
+
* that corrupts it is not the block the count moves on -- read this again
|
|
2617
|
+
* after one more block has processed. The EQ and inserts have no such lag;
|
|
2618
|
+
* they discard at the end of their own process, in the same block that
|
|
2619
|
+
* carried the poison.
|
|
2620
|
+
*
|
|
2621
|
+
* Cumulative since the strip was created and never cleared, so two
|
|
2622
|
+
* readings bracket a span of audio. The unit is one processed block, never
|
|
2623
|
+
* a channel, so a stereo block that discards on both channels adds one and
|
|
2624
|
+
* the number does not depend on a dimension the caller did not choose.
|
|
2625
|
+
*
|
|
2626
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2627
|
+
*/
|
|
2628
|
+
stripNonFiniteDiscardCount(stripIndex) {
|
|
2629
|
+
return this.mixer.stripNonFiniteDiscardCount(stripIndex);
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Number of blocks in which a bus discarded recursive state because a
|
|
2633
|
+
* non-finite value had reached it. Same contract as
|
|
2634
|
+
* {@link stripNonFiniteDiscardCount}, for a bus: covers every insert the
|
|
2635
|
+
* bus owns and its meter, neither separately addressable, so a discard
|
|
2636
|
+
* inside one is observable only here. Cumulative across graph recompiles
|
|
2637
|
+
* -- the count lives with the bus, not the compiled node, so an unrelated
|
|
2638
|
+
* edit elsewhere in the mixer does not reset it.
|
|
2639
|
+
*
|
|
2640
|
+
* A bus's DSP record is created by the first {@link compile}. Throws for a
|
|
2641
|
+
* bus that has been declared with {@link addBus} but never compiled --
|
|
2642
|
+
* reading zero there would read as clean, and it is not. Also throws for
|
|
2643
|
+
* an unknown bus id.
|
|
2644
|
+
*
|
|
2645
|
+
* @param busId - Bus id, as passed to {@link addBus} or declared in scene JSON
|
|
2646
|
+
*/
|
|
2647
|
+
busNonFiniteDiscardCount(busId) {
|
|
2648
|
+
return this.mixer.busNonFiniteDiscardCount(busId);
|
|
2649
|
+
}
|
|
1755
2650
|
/**
|
|
1756
2651
|
* Schedule sample-accurate fader automation on a strip.
|
|
1757
2652
|
*
|
|
@@ -1806,6 +2701,11 @@ var Mixer = class _Mixer {
|
|
|
1806
2701
|
/**
|
|
1807
2702
|
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
1808
2703
|
* (oldest to newest).
|
|
2704
|
+
*
|
|
2705
|
+
* `maxPoints` must be a finite non-negative integer; anything else throws an
|
|
2706
|
+
* `InvalidParameter` error. It is a request rather than an allocation size —
|
|
2707
|
+
* a value beyond the strip's goniometer ring simply returns every point the
|
|
2708
|
+
* ring holds.
|
|
1809
2709
|
*/
|
|
1810
2710
|
readGoniometerLatest(stripIndex, maxPoints) {
|
|
1811
2711
|
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
@@ -1841,8 +2741,12 @@ var Mixer = class _Mixer {
|
|
|
1841
2741
|
}
|
|
1842
2742
|
return this.mixer.drainTailStereo(numSamples);
|
|
1843
2743
|
}
|
|
1844
|
-
/** Release the underlying WASM object.
|
|
2744
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1845
2745
|
delete() {
|
|
2746
|
+
if (this.released) {
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
this.released = true;
|
|
1846
2750
|
this.mixer.delete();
|
|
1847
2751
|
}
|
|
1848
2752
|
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
@@ -1859,10 +2763,19 @@ var RealtimeVoiceChanger = class {
|
|
|
1859
2763
|
* explicit {@link prepare} lifecycle for callers that configure later.
|
|
1860
2764
|
*/
|
|
1861
2765
|
constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
|
|
2766
|
+
this.released = false;
|
|
1862
2767
|
const module2 = getSonareModule();
|
|
1863
2768
|
this.changer = module2.createRealtimeVoiceChanger(config);
|
|
1864
2769
|
if (sampleRate !== void 0) {
|
|
1865
|
-
|
|
2770
|
+
let prepared = false;
|
|
2771
|
+
try {
|
|
2772
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
2773
|
+
prepared = true;
|
|
2774
|
+
} finally {
|
|
2775
|
+
if (!prepared) {
|
|
2776
|
+
this.changer.delete();
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
1866
2779
|
}
|
|
1867
2780
|
}
|
|
1868
2781
|
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
@@ -1888,6 +2801,34 @@ var RealtimeVoiceChanger = class {
|
|
|
1888
2801
|
latencySamples() {
|
|
1889
2802
|
return this.changer.latencySamples();
|
|
1890
2803
|
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Channel-blocks in which the chain discarded its own state because a
|
|
2806
|
+
* non-finite value had reached it.
|
|
2807
|
+
*
|
|
2808
|
+
* Advisory telemetry, and the only thing that separates a degraded stream
|
|
2809
|
+
* from a clean one. Every stage of this chain leaves an in-domain finite
|
|
2810
|
+
* value where a non-finite one was — the input scrub and the
|
|
2811
|
+
* inter-sample-peak limiter substitute silence, the sample-domain limiter
|
|
2812
|
+
* folds an infinity onto its ceiling — so the output stays finite, in range
|
|
2813
|
+
* and free of any error while carrying samples unrelated to the input. A
|
|
2814
|
+
* non-zero count is what says the samples in between were not computed from
|
|
2815
|
+
* what you supplied.
|
|
2816
|
+
*
|
|
2817
|
+
* Monotonic for the lifetime of the instance. The unit is one processed
|
|
2818
|
+
* block, never a channel, so a stereo block that discards on both channels
|
|
2819
|
+
* adds one and the number does not depend on a dimension you did not choose.
|
|
2820
|
+
*
|
|
2821
|
+
* @example
|
|
2822
|
+
* ```ts
|
|
2823
|
+
* changer.processInterleaved(block, 2);
|
|
2824
|
+
* if (changer.nonFiniteDiscardCount() > 0) {
|
|
2825
|
+
* // the audio just produced is not a function of `block`
|
|
2826
|
+
* }
|
|
2827
|
+
* ```
|
|
2828
|
+
*/
|
|
2829
|
+
nonFiniteDiscardCount() {
|
|
2830
|
+
return this.changer.nonFiniteDiscardCount();
|
|
2831
|
+
}
|
|
1891
2832
|
/**
|
|
1892
2833
|
* Monotonically increases whenever {@link prepare} can replace the native
|
|
1893
2834
|
* scratch buffers. Cached WASM heap views must be reacquired after it changes.
|
|
@@ -2055,14 +2996,24 @@ var RealtimeVoiceChanger = class {
|
|
|
2055
2996
|
}
|
|
2056
2997
|
};
|
|
2057
2998
|
}
|
|
2999
|
+
/** Releases the native handle. Idempotent, as the Node facade is. */
|
|
2058
3000
|
delete() {
|
|
3001
|
+
if (this.released) {
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
this.released = true;
|
|
2059
3005
|
this.changer.delete();
|
|
2060
3006
|
}
|
|
3007
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
3008
|
+
destroy() {
|
|
3009
|
+
this.delete();
|
|
3010
|
+
}
|
|
2061
3011
|
};
|
|
2062
3012
|
|
|
2063
3013
|
// src/streaming_processors.ts
|
|
2064
3014
|
var StreamingMasteringChain = class {
|
|
2065
3015
|
constructor(config) {
|
|
3016
|
+
this.released = false;
|
|
2066
3017
|
const module2 = getSonareModule();
|
|
2067
3018
|
const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
|
|
2068
3019
|
this.chain = module2.createStreamingMasteringChain({
|
|
@@ -2113,6 +3064,13 @@ var StreamingMasteringChain = class {
|
|
|
2113
3064
|
reset() {
|
|
2114
3065
|
this.chain.reset();
|
|
2115
3066
|
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Set one realtime-safe full-chain parameter at a serialized block boundary.
|
|
3069
|
+
* Structural, disabled-stage and unknown parameters are rejected.
|
|
3070
|
+
*/
|
|
3071
|
+
setParameter(key, value) {
|
|
3072
|
+
this.chain.setParameter(key, value);
|
|
3073
|
+
}
|
|
2116
3074
|
/** Total reported latency in samples across all active processors. */
|
|
2117
3075
|
latencySamples() {
|
|
2118
3076
|
return this.chain.latencySamples();
|
|
@@ -2121,10 +3079,75 @@ var StreamingMasteringChain = class {
|
|
|
2121
3079
|
stageNames() {
|
|
2122
3080
|
return this.chain.stageNames();
|
|
2123
3081
|
}
|
|
2124
|
-
/**
|
|
3082
|
+
/**
|
|
3083
|
+
* Samples a stage replaced with a finite in-domain one, keeping the output
|
|
3084
|
+
* finite and in range.
|
|
3085
|
+
*
|
|
3086
|
+
* A non-finite sample supplied by the caller is rejected before any stage
|
|
3087
|
+
* runs, so a replacement is always of a value a stage itself produced.
|
|
3088
|
+
*
|
|
3089
|
+
* Only the true-peak limiters replace anything, so with the maximizer's
|
|
3090
|
+
* limiter and the loudness stage both disabled a zero here means no stage was
|
|
3091
|
+
* able to replace anything rather than that nothing needed replacing.
|
|
3092
|
+
*
|
|
3093
|
+
* Cumulative over every block since {@link prepare}, and aggregated over the
|
|
3094
|
+
* stages and channels, so it identifies neither which block nor which stage.
|
|
3095
|
+
* Read it per block and compare against the previous reading to localize one.
|
|
3096
|
+
*
|
|
3097
|
+
* {@link prepare} rebuilds the stages and so clears it; {@link reset} does
|
|
3098
|
+
* not, because it drops processor state without rebuilding.
|
|
3099
|
+
*
|
|
3100
|
+
* @example
|
|
3101
|
+
* ```typescript
|
|
3102
|
+
* chain.processMono(block);
|
|
3103
|
+
* if (chain.nonFiniteSubstitutionCount() > previous) {
|
|
3104
|
+
* // the block just produced is not derived from `block` everywhere
|
|
3105
|
+
* }
|
|
3106
|
+
* ```
|
|
3107
|
+
*/
|
|
3108
|
+
nonFiniteSubstitutionCount() {
|
|
3109
|
+
return this.chain.nonFiniteSubstitutionCount();
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Processing calls in which a stage discarded its own recursive state
|
|
3113
|
+
* because a non-finite value had reached it.
|
|
3114
|
+
*
|
|
3115
|
+
* The companion to {@link nonFiniteSubstitutionCount}, and not the same
|
|
3116
|
+
* measurement -- a caller who assumes they are will read one and think
|
|
3117
|
+
* they have the other. That one counts SAMPLES a stage replaced and so
|
|
3118
|
+
* sums across stages; a discard is a whole stage returning to its
|
|
3119
|
+
* post-reset value and is counted once per call however many stages did
|
|
3120
|
+
* it. A stage may run more than once per call, which is why this is a
|
|
3121
|
+
* delta over the call and never a sum.
|
|
3122
|
+
*
|
|
3123
|
+
* Non-finite input is rejected before any stage runs, so what a stage
|
|
3124
|
+
* discards is always state it produced itself -- a finite sample large
|
|
3125
|
+
* enough to overflow inside a filter, most often. Unlike the substitution
|
|
3126
|
+
* count every stage can contribute, so a zero here means no stage
|
|
3127
|
+
* discarded rather than that none could.
|
|
3128
|
+
*
|
|
3129
|
+
* Both {@link processMono}/{@link processStereo} and
|
|
3130
|
+
* {@link flushMono}/{@link flushStereo} count, since a flush drives the
|
|
3131
|
+
* same stages. {@link prepare} rebuilds the stages and so clears it (as it
|
|
3132
|
+
* does {@link nonFiniteSubstitutionCount}, so the two counters on one
|
|
3133
|
+
* handle share an epoch); {@link reset} does not, because it drops
|
|
3134
|
+
* processor state without rebuilding.
|
|
3135
|
+
*/
|
|
3136
|
+
nonFiniteDiscardCount() {
|
|
3137
|
+
return this.chain.nonFiniteDiscardCount();
|
|
3138
|
+
}
|
|
3139
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
2125
3140
|
delete() {
|
|
3141
|
+
if (this.released) {
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
this.released = true;
|
|
2126
3145
|
this.chain.delete();
|
|
2127
3146
|
}
|
|
3147
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
3148
|
+
destroy() {
|
|
3149
|
+
this.delete();
|
|
3150
|
+
}
|
|
2128
3151
|
};
|
|
2129
3152
|
|
|
2130
3153
|
// src/index.ts
|
|
@@ -2154,14 +3177,16 @@ function isInitialized() {
|
|
|
2154
3177
|
}
|
|
2155
3178
|
|
|
2156
3179
|
// src/worklet/engine-sync.ts
|
|
2157
|
-
function buildMixerLanes(trackLaneIds, trackSends, trackOutputBus) {
|
|
3180
|
+
function buildMixerLanes(trackLaneIds, trackSends, trackOutputBus, trackSourceChannelLayout) {
|
|
2158
3181
|
return trackLaneIds.map((trackId) => {
|
|
2159
3182
|
const sends = trackSends.get(trackId);
|
|
2160
3183
|
const outputBusId = trackOutputBus.get(trackId);
|
|
3184
|
+
const sourceChannelLayout = trackSourceChannelLayout.get(trackId);
|
|
2161
3185
|
return {
|
|
2162
3186
|
trackId,
|
|
2163
3187
|
...sends && sends.length > 0 ? { sends: sends.map((send) => ({ ...send })) } : {},
|
|
2164
|
-
...outputBusId !== void 0 ? { outputBusId } : {}
|
|
3188
|
+
...outputBusId !== void 0 ? { outputBusId } : {},
|
|
3189
|
+
...sourceChannelLayout !== void 0 ? { sourceChannelLayout } : {}
|
|
2165
3190
|
};
|
|
2166
3191
|
});
|
|
2167
3192
|
}
|
|
@@ -2175,11 +3200,11 @@ function buildTempoSync(tempoBpm, timeSignature, tempoSegments, timeSignatureSeg
|
|
|
2175
3200
|
};
|
|
2176
3201
|
}
|
|
2177
3202
|
function resolveTargetId(target) {
|
|
2178
|
-
|
|
2179
|
-
|
|
3203
|
+
const value = typeof target === "number" ? target : target.trim() === "" ? Number.NaN : Number(target);
|
|
3204
|
+
if (!Number.isInteger(value)) {
|
|
3205
|
+
throw new RangeError(`target id must be an integer, got ${JSON.stringify(target)}`);
|
|
2180
3206
|
}
|
|
2181
|
-
|
|
2182
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
3207
|
+
return value;
|
|
2183
3208
|
}
|
|
2184
3209
|
function resolveParamId(parameters, nodeId, param) {
|
|
2185
3210
|
if (typeof param === "number") {
|
|
@@ -2222,101 +3247,6 @@ function setAutomationLane(ctx, paramId, points) {
|
|
|
2222
3247
|
ctx.postSync({ type: "syncAutomation", paramId, points: sorted });
|
|
2223
3248
|
}
|
|
2224
3249
|
|
|
2225
|
-
// src/worklet/engine-offline.ts
|
|
2226
|
-
function buildCaptureConfig(options, defaultChannels) {
|
|
2227
|
-
return {
|
|
2228
|
-
bufferFrames: Math.trunc(options.bufferFrames),
|
|
2229
|
-
channels: Math.trunc(options.channels ?? defaultChannels),
|
|
2230
|
-
source: options.source ?? "output",
|
|
2231
|
-
recordOffsetSamples: Math.trunc(options.recordOffsetSamples ?? 0),
|
|
2232
|
-
inputMonitor: {
|
|
2233
|
-
enabled: Boolean(options.inputMonitor?.enabled),
|
|
2234
|
-
gain: options.inputMonitor?.gain ?? 1
|
|
2235
|
-
}
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
|
-
function buildTransportFacade(ctx) {
|
|
2239
|
-
return {
|
|
2240
|
-
play: (sampleTime = -1) => {
|
|
2241
|
-
const ok = ctx.realtimeNode.play(sampleTime);
|
|
2242
|
-
if (ok) {
|
|
2243
|
-
ctx.setTransportPlaying(true);
|
|
2244
|
-
}
|
|
2245
|
-
return ok;
|
|
2246
|
-
},
|
|
2247
|
-
stop: (sampleTime = -1) => {
|
|
2248
|
-
const ok = ctx.realtimeNode.stop(sampleTime);
|
|
2249
|
-
if (ok) {
|
|
2250
|
-
ctx.setTransportPlaying(false);
|
|
2251
|
-
ctx.flushPendingInstrumentSync();
|
|
2252
|
-
}
|
|
2253
|
-
return ok;
|
|
2254
|
-
},
|
|
2255
|
-
seekPpq: (ppq, sampleTime = -1) => {
|
|
2256
|
-
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
2257
|
-
const ok = ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
2258
|
-
ctx.flushOfflineMirror();
|
|
2259
|
-
return ok;
|
|
2260
|
-
},
|
|
2261
|
-
seekSeconds: (seconds, sampleTime = -1) => {
|
|
2262
|
-
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
2263
|
-
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
2264
|
-
const ok = ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
2265
|
-
ctx.flushOfflineMirror();
|
|
2266
|
-
return ok;
|
|
2267
|
-
},
|
|
2268
|
-
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
2269
|
-
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
2270
|
-
setLoop: (startPpq, endPpq, enabled = true) => ctx.setLoop(startPpq, endPpq, enabled)
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
|
-
function normalizeTrackLanes(existing, lanes) {
|
|
2274
|
-
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
2275
|
-
const ids = [];
|
|
2276
|
-
for (const entry of entries) {
|
|
2277
|
-
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
2278
|
-
throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
2279
|
-
}
|
|
2280
|
-
ids.push(entry.trackId);
|
|
2281
|
-
}
|
|
2282
|
-
if (new Set(ids).size !== ids.length) {
|
|
2283
|
-
throw new Error("Duplicate track id in mixer lane list");
|
|
2284
|
-
}
|
|
2285
|
-
for (let index = 0; index < existing.length; index++) {
|
|
2286
|
-
if (ids[index] !== existing[index]) {
|
|
2287
|
-
throw new Error(
|
|
2288
|
-
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
2289
|
-
);
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
return { entries, ids };
|
|
2293
|
-
}
|
|
2294
|
-
function resolveMarkerSet(markers, nextMarkerId) {
|
|
2295
|
-
const resolved = [];
|
|
2296
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2297
|
-
let counter = nextMarkerId;
|
|
2298
|
-
for (const marker2 of markers) {
|
|
2299
|
-
if (!Number.isFinite(marker2.ppq)) {
|
|
2300
|
-
throw new Error(`Invalid marker ppq: ${String(marker2.ppq)}`);
|
|
2301
|
-
}
|
|
2302
|
-
if (marker2.id !== void 0) {
|
|
2303
|
-
if (!Number.isInteger(marker2.id) || marker2.id <= 0) {
|
|
2304
|
-
throw new Error(`Invalid marker id: ${String(marker2.id)}`);
|
|
2305
|
-
}
|
|
2306
|
-
if (seen.has(marker2.id)) {
|
|
2307
|
-
throw new Error(`Duplicate marker id: ${marker2.id}`);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
const id = marker2.id ?? counter++;
|
|
2311
|
-
seen.add(id);
|
|
2312
|
-
if (id >= counter) {
|
|
2313
|
-
counter = id + 1;
|
|
2314
|
-
}
|
|
2315
|
-
resolved.push({ id, ppq: marker2.ppq, name: marker2.name ?? "" });
|
|
2316
|
-
}
|
|
2317
|
-
return { resolved, nextMarkerId: counter };
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
3250
|
// src/worklet/protocol.ts
|
|
2321
3251
|
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
2322
3252
|
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
@@ -2331,10 +3261,12 @@ function engineMixerMasterTarget(paramKind) {
|
|
|
2331
3261
|
return ENGINE_MIXER_TARGET_BASE | 255 << 8 | paramKind & 255;
|
|
2332
3262
|
}
|
|
2333
3263
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
2334
|
-
var SONARE_METER_RING_RECORD_FLOATS =
|
|
3264
|
+
var SONARE_METER_RING_RECORD_FLOATS = 16;
|
|
2335
3265
|
var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
|
|
2336
3266
|
var SONARE_SCOPE_RING_HEADER_INTS = 6;
|
|
2337
3267
|
var SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS = 5;
|
|
3268
|
+
var SONARE_SCOPE_DEFAULT_BANDS = 48;
|
|
3269
|
+
var SONARE_SCOPE_DEFAULT_MAX_POINTS = 32;
|
|
2338
3270
|
var SONARE_FRAME_LANE_BASE = 16777216;
|
|
2339
3271
|
function encodeFrameLo(frame) {
|
|
2340
3272
|
const f = Math.max(0, Math.floor(frame));
|
|
@@ -2374,6 +3306,7 @@ var SonareEngineCommandType = /* @__PURE__ */ ((SonareEngineCommandType2) => {
|
|
|
2374
3306
|
SonareEngineCommandType2[SonareEngineCommandType2["SetMarker"] = 16] = "SetMarker";
|
|
2375
3307
|
SonareEngineCommandType2[SonareEngineCommandType2["SeekMarker"] = 17] = "SeekMarker";
|
|
2376
3308
|
SonareEngineCommandType2[SonareEngineCommandType2["SetTrackMonitorMode"] = 26] = "SetTrackMonitorMode";
|
|
3309
|
+
SonareEngineCommandType2[SonareEngineCommandType2["ResetMasterLoudnessMeter"] = 28] = "ResetMasterLoudnessMeter";
|
|
2377
3310
|
return SonareEngineCommandType2;
|
|
2378
3311
|
})(SonareEngineCommandType || {});
|
|
2379
3312
|
var SonareEngineTelemetryType = /* @__PURE__ */ ((SonareEngineTelemetryType2) => {
|
|
@@ -2403,11 +3336,10 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
2403
3336
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MetronomeOverflow"] = 18] = "MetronomeOverflow";
|
|
2404
3337
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["InvalidCommand"] = 19] = "InvalidCommand";
|
|
2405
3338
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MaxChannelsExceeded"] = 20] = "MaxChannelsExceeded";
|
|
3339
|
+
SonareEngineTelemetryError2[SonareEngineTelemetryError2["ParameterBaseOverflow"] = 21] = "ParameterBaseOverflow";
|
|
2406
3340
|
return SonareEngineTelemetryError2;
|
|
2407
3341
|
})(SonareEngineTelemetryError || {});
|
|
2408
|
-
|
|
2409
|
-
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
2410
|
-
}
|
|
3342
|
+
var SONARE_FLOOR_DB = -120;
|
|
2411
3343
|
function isRecord(value) {
|
|
2412
3344
|
return typeof value === "object" && value !== null;
|
|
2413
3345
|
}
|
|
@@ -2447,7 +3379,9 @@ function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
|
2447
3379
|
momentaryLufs: ring.records[offset + 10],
|
|
2448
3380
|
shortTermLufs: ring.records[offset + 11],
|
|
2449
3381
|
integratedLufs: ring.records[offset + 12],
|
|
2450
|
-
gainReductionDb: ring.records[offset + 13]
|
|
3382
|
+
gainReductionDb: ring.records[offset + 13],
|
|
3383
|
+
inputPeakDbL: ring.records[offset + 14],
|
|
3384
|
+
inputPeakDbR: ring.records[offset + 15]
|
|
2451
3385
|
});
|
|
2452
3386
|
}
|
|
2453
3387
|
return { nextReadIndex: writeIndex, meters };
|
|
@@ -2499,13 +3433,13 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
|
2499
3433
|
function sonareScopeRingRecordFloats(bands, maxPoints) {
|
|
2500
3434
|
return SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands + 2 * maxPoints;
|
|
2501
3435
|
}
|
|
2502
|
-
function sonareScopeRingBufferByteLength(capacity, bands =
|
|
3436
|
+
function sonareScopeRingBufferByteLength(capacity, bands = SONARE_SCOPE_DEFAULT_BANDS, maxPoints = SONARE_SCOPE_DEFAULT_MAX_POINTS) {
|
|
2503
3437
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2504
3438
|
const clampedBands = Math.max(1, Math.floor(bands));
|
|
2505
3439
|
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
2506
3440
|
return SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * sonareScopeRingRecordFloats(clampedBands, clampedPoints) * Float32Array.BYTES_PER_ELEMENT;
|
|
2507
3441
|
}
|
|
2508
|
-
function createSonareScopeRingBuffer(capacity = 64, bands =
|
|
3442
|
+
function createSonareScopeRingBuffer(capacity = 64, bands = SONARE_SCOPE_DEFAULT_BANDS, maxPoints = SONARE_SCOPE_DEFAULT_MAX_POINTS) {
|
|
2509
3443
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
2510
3444
|
const clampedBands = Math.max(1, Math.floor(bands));
|
|
2511
3445
|
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
@@ -2572,8 +3506,14 @@ function scopeRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBands
|
|
|
2572
3506
|
const existingBands = Atomics.load(header, 3);
|
|
2573
3507
|
const existingMaxPoints = Atomics.load(header, 4);
|
|
2574
3508
|
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
2575
|
-
const bands = Math.max(
|
|
2576
|
-
|
|
3509
|
+
const bands = Math.max(
|
|
3510
|
+
1,
|
|
3511
|
+
Math.floor(existingBands || fallbackBands || SONARE_SCOPE_DEFAULT_BANDS)
|
|
3512
|
+
);
|
|
3513
|
+
const maxPoints = Math.max(
|
|
3514
|
+
0,
|
|
3515
|
+
Math.floor(existingMaxPoints || (fallbackMaxPoints ?? SONARE_SCOPE_DEFAULT_MAX_POINTS))
|
|
3516
|
+
);
|
|
2577
3517
|
const recordFloats = sonareScopeRingRecordFloats(bands, maxPoints);
|
|
2578
3518
|
const minBytes = sonareScopeRingBufferByteLength(capacity, bands, maxPoints);
|
|
2579
3519
|
if (sharedBuffer.byteLength < minBytes) {
|
|
@@ -2886,6 +3826,16 @@ function clipPageRequestRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
|
2886
3826
|
function recordOffset(index, capacity, recordBytes) {
|
|
2887
3827
|
return index % capacity * recordBytes;
|
|
2888
3828
|
}
|
|
3829
|
+
function isUint32Slot(value) {
|
|
3830
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3831
|
+
}
|
|
3832
|
+
function toUint32Slot(value, fallback, name) {
|
|
3833
|
+
const resolved = value ?? fallback;
|
|
3834
|
+
if (!isUint32Slot(resolved)) {
|
|
3835
|
+
throw new RangeError(`${name} must be an integer within [0, 4294967295]`);
|
|
3836
|
+
}
|
|
3837
|
+
return resolved;
|
|
3838
|
+
}
|
|
2889
3839
|
function toSafeInteger(value, fallback) {
|
|
2890
3840
|
const resolved = typeof value === "bigint" ? Number(value) : value;
|
|
2891
3841
|
if (resolved === void 0) {
|
|
@@ -2905,8 +3855,8 @@ function readInt64Words(view, offset) {
|
|
|
2905
3855
|
return view.getInt32(offset + 4, true) * 4294967296 + view.getUint32(offset, true);
|
|
2906
3856
|
}
|
|
2907
3857
|
function writeEngineCommandRecord(view, offset, command) {
|
|
2908
|
-
view.setUint32(offset, command.type, true);
|
|
2909
|
-
view.setUint32(offset + 4, command.targetId
|
|
3858
|
+
view.setUint32(offset, toUint32Slot(command.type, 0, "type"), true);
|
|
3859
|
+
view.setUint32(offset + 4, toUint32Slot(command.targetId, 0, "targetId"), true);
|
|
2910
3860
|
writeInt64Words(view, offset + 8, toSafeInteger(command.sampleTime, -1));
|
|
2911
3861
|
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
2912
3862
|
writeInt64Words(view, offset + 24, toSafeInteger(command.argInt, 0));
|
|
@@ -2968,34 +3918,349 @@ function meterFromEngine(meter) {
|
|
|
2968
3918
|
momentaryLufs: meter.momentaryLufs,
|
|
2969
3919
|
shortTermLufs: meter.shortTermLufs,
|
|
2970
3920
|
integratedLufs: meter.integratedLufs,
|
|
2971
|
-
gainReductionDb: meter.gainReductionDb
|
|
3921
|
+
gainReductionDb: meter.gainReductionDb,
|
|
3922
|
+
inputPeakDbL: meter.inputPeakDbL,
|
|
3923
|
+
inputPeakDbR: meter.inputPeakDbR
|
|
2972
3924
|
};
|
|
2973
3925
|
}
|
|
2974
3926
|
function magnitudeToDb(value) {
|
|
2975
3927
|
return value > 1e-12 ? 20 * Math.log10(value) : -120;
|
|
2976
3928
|
}
|
|
2977
3929
|
|
|
2978
|
-
// src/worklet/
|
|
2979
|
-
function
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
ctx.offlineEngine.setCaptureSource(config.source);
|
|
2983
|
-
ctx.offlineEngine.setRecordOffsetSamples(config.recordOffsetSamples);
|
|
2984
|
-
ctx.offlineEngine.setInputMonitor(config.inputMonitor.enabled, config.inputMonitor.gain);
|
|
2985
|
-
ctx.setCaptureConfig(config);
|
|
2986
|
-
ctx.postSync({ type: "syncCapture", ...config });
|
|
2987
|
-
}
|
|
2988
|
-
function armRecord(ctx, trackId, enabled) {
|
|
2989
|
-
if (trackId !== 0) {
|
|
2990
|
-
throw new RangeError("Capture is global; armRecord only accepts trackId 0");
|
|
2991
|
-
}
|
|
2992
|
-
if (enabled && !ctx.getCaptureConfig()) {
|
|
2993
|
-
throw new Error("Capture buffer is not configured");
|
|
3930
|
+
// src/worklet/guards.ts
|
|
3931
|
+
function isWorkletMessage(value) {
|
|
3932
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3933
|
+
return false;
|
|
2994
3934
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3935
|
+
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
3936
|
+
}
|
|
3937
|
+
function isEngineCommandRecord(value) {
|
|
3938
|
+
return isRecord(value) && typeof value.type === "number";
|
|
3939
|
+
}
|
|
3940
|
+
var ENGINE_SYNC_MESSAGE_TYPES = {
|
|
3941
|
+
destroy: true,
|
|
3942
|
+
syncAutomation: true,
|
|
3943
|
+
syncBuiltinInstrument: true,
|
|
3944
|
+
syncBusStripDualPan: true,
|
|
3945
|
+
syncBusStripEqBand: true,
|
|
3946
|
+
syncBusStripInsertBypassed: true,
|
|
3947
|
+
syncBusStripInsertParamByName: true,
|
|
3948
|
+
syncBusStripPan: true,
|
|
3949
|
+
syncBusStripPanLaw: true,
|
|
3950
|
+
syncBusStripPanMode: true,
|
|
3951
|
+
syncCapture: true,
|
|
3952
|
+
syncClearMidiFx: true,
|
|
3953
|
+
syncClearMidiInputSource: true,
|
|
3954
|
+
syncClipPage: true,
|
|
3955
|
+
syncClipPageClear: true,
|
|
3956
|
+
syncClipPageCommit: true,
|
|
3957
|
+
syncClipPageDestroy: true,
|
|
3958
|
+
syncClipPagePrefetchFrames: true,
|
|
3959
|
+
syncClipPageProvider: true,
|
|
3960
|
+
syncClips: true,
|
|
3961
|
+
syncClipsDelta: true,
|
|
3962
|
+
syncExternalMidiClock: true,
|
|
3963
|
+
syncLoadSoundFont: true,
|
|
3964
|
+
syncMarkers: true,
|
|
3965
|
+
syncMasterStripEqBand: true,
|
|
3966
|
+
syncMasterStripInsertBypassed: true,
|
|
3967
|
+
syncMasterStripInsertParamByName: true,
|
|
3968
|
+
syncMetronome: true,
|
|
3969
|
+
syncMidiCc: true,
|
|
3970
|
+
syncMidiCcBinding: true,
|
|
3971
|
+
syncMidiChannelPressure: true,
|
|
3972
|
+
syncMidiClips: true,
|
|
3973
|
+
syncMidiDestinationExternal: true,
|
|
3974
|
+
syncMidiFx: true,
|
|
3975
|
+
syncMidiInputChannelPressure: true,
|
|
3976
|
+
syncMidiInputCc: true,
|
|
3977
|
+
syncMidiInputNoteOff: true,
|
|
3978
|
+
syncMidiInputNoteOn: true,
|
|
3979
|
+
syncMidiInputPitchBend: true,
|
|
3980
|
+
syncMidiInputPolyPressure: true,
|
|
3981
|
+
syncMidiInputSource: true,
|
|
3982
|
+
syncMidiNoteOff: true,
|
|
3983
|
+
syncMidiNoteOn: true,
|
|
3984
|
+
syncMidiPanic: true,
|
|
3985
|
+
syncMidiPitchBend: true,
|
|
3986
|
+
syncMidiPolyPressure: true,
|
|
3987
|
+
syncMidiSysex: true,
|
|
3988
|
+
syncMidiUmp: true,
|
|
3989
|
+
syncMixer: true,
|
|
3990
|
+
syncParameters: true,
|
|
3991
|
+
syncSf2Instrument: true,
|
|
3992
|
+
syncSynthInstrument: true,
|
|
3993
|
+
syncTempo: true,
|
|
3994
|
+
syncTrackStripChannelDelaySamples: true,
|
|
3995
|
+
syncTrackStripDualPan: true,
|
|
3996
|
+
syncTrackStripEqBand: true,
|
|
3997
|
+
syncTrackStripInsertBypassed: true,
|
|
3998
|
+
syncTrackStripInsertParamByName: true,
|
|
3999
|
+
syncTrackStripPan: true,
|
|
4000
|
+
syncTrackStripPanLaw: true,
|
|
4001
|
+
syncTrackStripPanMode: true,
|
|
4002
|
+
syncWarpVoiceCapacity: true
|
|
4003
|
+
};
|
|
4004
|
+
var engineSyncMessageTypes = new Set(Object.keys(ENGINE_SYNC_MESSAGE_TYPES));
|
|
4005
|
+
function isEngineSyncMessage(value) {
|
|
4006
|
+
return isRecord(value) && typeof value.type === "string" && engineSyncMessageTypes.has(value.type);
|
|
4007
|
+
}
|
|
4008
|
+
function isEngineCaptureRequestMessage(value) {
|
|
4009
|
+
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
4010
|
+
}
|
|
4011
|
+
function isCaptureStatus(value) {
|
|
4012
|
+
return isRecord(value) && typeof value.capturedFrames === "number" && Number.isSafeInteger(value.capturedFrames) && value.capturedFrames >= 0 && typeof value.overflowCount === "number" && Number.isSafeInteger(value.overflowCount) && value.overflowCount >= 0 && typeof value.armed === "boolean" && typeof value.punchEnabled === "boolean" && (value.source === "input" || value.source === "output") && typeof value.recordOffsetSamples === "number" && Number.isSafeInteger(value.recordOffsetSamples);
|
|
4013
|
+
}
|
|
4014
|
+
function isCaptureChannel(value) {
|
|
4015
|
+
return value instanceof Float32Array && typeof ArrayBuffer !== "undefined" && value.buffer instanceof ArrayBuffer;
|
|
4016
|
+
}
|
|
4017
|
+
function isCaptureChannels(value) {
|
|
4018
|
+
return Array.isArray(value) && value.every((channel) => isCaptureChannel(channel));
|
|
4019
|
+
}
|
|
4020
|
+
function hasOwn(value, key) {
|
|
4021
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
4022
|
+
}
|
|
4023
|
+
function engineCaptureResponseRequestId(value) {
|
|
4024
|
+
if (!isRecord(value) || value.type !== "captureResponse" || typeof value.requestId !== "number" || !Number.isSafeInteger(value.requestId)) {
|
|
4025
|
+
return void 0;
|
|
4026
|
+
}
|
|
4027
|
+
return value.requestId;
|
|
4028
|
+
}
|
|
4029
|
+
function isEngineCaptureResponseMessage(value) {
|
|
4030
|
+
const requestId = engineCaptureResponseRequestId(value);
|
|
4031
|
+
if (requestId === void 0 || !isRecord(value)) {
|
|
4032
|
+
return false;
|
|
4033
|
+
}
|
|
4034
|
+
if (value.ok === false) {
|
|
4035
|
+
return typeof value.error === "string" && !hasOwn(value, "status") && !hasOwn(value, "channels");
|
|
4036
|
+
}
|
|
4037
|
+
if (value.ok !== true || hasOwn(value, "error")) {
|
|
4038
|
+
return false;
|
|
4039
|
+
}
|
|
4040
|
+
const hasStatus = hasOwn(value, "status");
|
|
4041
|
+
const hasChannels = hasOwn(value, "channels");
|
|
4042
|
+
if (hasStatus && hasChannels) {
|
|
4043
|
+
return false;
|
|
4044
|
+
}
|
|
4045
|
+
if (hasStatus) {
|
|
4046
|
+
return isCaptureStatus(value.status);
|
|
4047
|
+
}
|
|
4048
|
+
if (hasChannels) {
|
|
4049
|
+
return isCaptureChannels(value.channels);
|
|
4050
|
+
}
|
|
4051
|
+
return true;
|
|
4052
|
+
}
|
|
4053
|
+
function isEngineCaptureResponseForOperation(response, op) {
|
|
4054
|
+
if (!response.ok) {
|
|
4055
|
+
return true;
|
|
4056
|
+
}
|
|
4057
|
+
switch (op) {
|
|
4058
|
+
case "status":
|
|
4059
|
+
return "status" in response;
|
|
4060
|
+
case "read":
|
|
4061
|
+
return "channels" in response;
|
|
4062
|
+
case "reset":
|
|
4063
|
+
return !("status" in response) && !("channels" in response);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
function isEngineTransportRequestMessage(value) {
|
|
4067
|
+
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
4068
|
+
}
|
|
4069
|
+
function isEngineTransportResponseMessage(value) {
|
|
4070
|
+
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
4071
|
+
}
|
|
4072
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
4073
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
4074
|
+
return false;
|
|
4075
|
+
}
|
|
4076
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
4077
|
+
}
|
|
4078
|
+
var isOptionalNumber = (value) => value === void 0 || typeof value === "number";
|
|
4079
|
+
function isPlaybackMessage(value) {
|
|
4080
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
4081
|
+
return false;
|
|
4082
|
+
}
|
|
4083
|
+
switch (value.type) {
|
|
4084
|
+
case "config":
|
|
4085
|
+
return typeof value.config === "string";
|
|
4086
|
+
case "orientation":
|
|
4087
|
+
return typeof value.yaw === "number" && isOptionalNumber(value.pitch) && isOptionalNumber(value.roll);
|
|
4088
|
+
case "reset":
|
|
4089
|
+
case "diagnostics":
|
|
4090
|
+
case "destroy":
|
|
4091
|
+
return true;
|
|
4092
|
+
default:
|
|
4093
|
+
return false;
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
function isEngineTelemetryRecord(value) {
|
|
4097
|
+
return isRecord(value) && typeof value.type === "number" && typeof value.error === "number" && typeof value.renderFrame === "number" && typeof value.timelineSample === "number" && typeof value.audibleTimelineSample === "number" && typeof value.graphLatencySamplesQ8 === "number" && typeof value.value === "number";
|
|
4098
|
+
}
|
|
4099
|
+
function isExternalMidiBatchMessage(value) {
|
|
4100
|
+
return isRecord(value) && value.type === "externalMidi" && Array.isArray(value.events);
|
|
4101
|
+
}
|
|
4102
|
+
function isClipPageRequestMessage(value) {
|
|
4103
|
+
return isRecord(value) && value.type === "clipPageRequest" && Array.isArray(value.requests) && value.requests.every(
|
|
4104
|
+
(request) => isRecord(request) && typeof request.clipId === "number" && typeof request.pageIndex === "number"
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
function isMeterSnapshot(value) {
|
|
4108
|
+
return isRecord(value) && value.type === "meter" && typeof value.frame === "number" && typeof value.peakDbL === "number" && typeof value.peakDbR === "number" && typeof value.rmsDbL === "number" && typeof value.rmsDbR === "number" && typeof value.correlation === "number" && Number.isFinite(value.inputPeakDbL) && Number.isFinite(value.inputPeakDbR) && (typeof value.targetId === "number" || value.targetId === void 0);
|
|
4109
|
+
}
|
|
4110
|
+
var SONARE_SCOPE_MAX_BANDS = 64;
|
|
4111
|
+
var SONARE_SCOPE_MAX_POINTS = 32;
|
|
4112
|
+
function isPlainFloat32Array(value, maxLength, evenLength = false) {
|
|
4113
|
+
if (!(value instanceof Float32Array) || typeof ArrayBuffer === "undefined" || !(value.buffer instanceof ArrayBuffer) || value.length > maxLength || evenLength && value.length % 2 !== 0) {
|
|
4114
|
+
return false;
|
|
4115
|
+
}
|
|
4116
|
+
for (const sample of value) {
|
|
4117
|
+
if (!Number.isFinite(sample)) {
|
|
4118
|
+
return false;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
return true;
|
|
4122
|
+
}
|
|
4123
|
+
function isScopeSnapshot(value) {
|
|
4124
|
+
return isRecord(value) && value.type === "scope" && typeof value.targetId === "number" && Number.isSafeInteger(value.targetId) && value.targetId >= 0 && value.targetId <= 4294967295 && typeof value.frame === "number" && Number.isSafeInteger(value.frame) && value.frame >= 0 && isPlainFloat32Array(value.bands, SONARE_SCOPE_MAX_BANDS) && value.bands.length > 0 && isPlainFloat32Array(value.points, SONARE_SCOPE_MAX_POINTS * 2, true);
|
|
4125
|
+
}
|
|
4126
|
+
function requireIntegerOption(value, fallback, name, minimum) {
|
|
4127
|
+
const resolved = value ?? fallback;
|
|
4128
|
+
if (!Number.isSafeInteger(resolved) || resolved < minimum) {
|
|
4129
|
+
throw new RangeError(`${name} must be an integer of at least ${minimum}`);
|
|
4130
|
+
}
|
|
4131
|
+
return resolved;
|
|
4132
|
+
}
|
|
4133
|
+
function resolveScopeBandCount(value, fallback = SONARE_SCOPE_DEFAULT_BANDS) {
|
|
4134
|
+
return Math.min(requireIntegerOption(value, fallback, "scopeBands", 1), SONARE_SCOPE_MAX_BANDS);
|
|
4135
|
+
}
|
|
4136
|
+
function requireInteger(value, fallback, name) {
|
|
4137
|
+
const resolved = value ?? fallback;
|
|
4138
|
+
if (!Number.isSafeInteger(resolved)) {
|
|
4139
|
+
throw new RangeError(`${name} must be an integer`);
|
|
4140
|
+
}
|
|
4141
|
+
return resolved;
|
|
4142
|
+
}
|
|
4143
|
+
function requireChannelCount(channelCount, fallback) {
|
|
4144
|
+
return requireIntegerOption(channelCount, fallback, "channelCount", 1);
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
// src/worklet/engine-offline.ts
|
|
4148
|
+
function buildCaptureConfig(options, defaultChannels) {
|
|
4149
|
+
return {
|
|
4150
|
+
// bufferFrames has no default; NaN makes the guard refuse an absent value.
|
|
4151
|
+
bufferFrames: requireIntegerOption(options.bufferFrames, Number.NaN, "bufferFrames", 1),
|
|
4152
|
+
channels: requireChannelCount(options.channels, defaultChannels),
|
|
4153
|
+
source: options.source ?? "output",
|
|
4154
|
+
recordOffsetSamples: requireInteger(options.recordOffsetSamples, 0, "recordOffsetSamples"),
|
|
4155
|
+
inputMonitor: {
|
|
4156
|
+
enabled: Boolean(options.inputMonitor?.enabled),
|
|
4157
|
+
gain: options.inputMonitor?.gain ?? 1
|
|
4158
|
+
}
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
function buildTransportFacade(ctx) {
|
|
4162
|
+
return {
|
|
4163
|
+
play: (sampleTime = -1) => {
|
|
4164
|
+
const ok = ctx.realtimeNode.play(sampleTime);
|
|
4165
|
+
if (ok) {
|
|
4166
|
+
ctx.setTransportPlaying(true);
|
|
4167
|
+
}
|
|
4168
|
+
return ok;
|
|
4169
|
+
},
|
|
4170
|
+
stop: (sampleTime = -1) => {
|
|
4171
|
+
const ok = ctx.realtimeNode.stop(sampleTime);
|
|
4172
|
+
if (ok) {
|
|
4173
|
+
ctx.setTransportPlaying(false);
|
|
4174
|
+
ctx.flushPendingInstrumentSync();
|
|
4175
|
+
}
|
|
4176
|
+
return ok;
|
|
4177
|
+
},
|
|
4178
|
+
seekPpq: (ppq, sampleTime = -1) => {
|
|
4179
|
+
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
4180
|
+
const ok = ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
4181
|
+
ctx.flushOfflineMirror();
|
|
4182
|
+
return ok;
|
|
4183
|
+
},
|
|
4184
|
+
seekSeconds: (seconds, sampleTime = -1) => {
|
|
4185
|
+
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
4186
|
+
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
4187
|
+
const ok = ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
4188
|
+
ctx.flushOfflineMirror();
|
|
4189
|
+
return ok;
|
|
4190
|
+
},
|
|
4191
|
+
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
4192
|
+
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
4193
|
+
setLoop: (startPpq, endPpq, enabled = true) => ctx.setLoop(startPpq, endPpq, enabled)
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
function normalizeTrackLanes(existing, lanes) {
|
|
4197
|
+
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
4198
|
+
const ids = [];
|
|
4199
|
+
for (const entry of entries) {
|
|
4200
|
+
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
4201
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
4202
|
+
}
|
|
4203
|
+
ids.push(entry.trackId);
|
|
4204
|
+
}
|
|
4205
|
+
if (new Set(ids).size !== ids.length) {
|
|
4206
|
+
throw new Error("Duplicate track id in mixer lane list");
|
|
4207
|
+
}
|
|
4208
|
+
for (let index = 0; index < existing.length; index++) {
|
|
4209
|
+
if (ids[index] !== existing[index]) {
|
|
4210
|
+
throw new Error(
|
|
4211
|
+
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
return { entries, ids };
|
|
4216
|
+
}
|
|
4217
|
+
function resolveMarkerSet(markers, nextMarkerId) {
|
|
4218
|
+
const resolved = [];
|
|
4219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4220
|
+
let counter = nextMarkerId;
|
|
4221
|
+
for (const marker2 of markers) {
|
|
4222
|
+
if (!Number.isFinite(marker2.ppq) || marker2.ppq < 0) {
|
|
4223
|
+
throw new RangeError(`Invalid marker ppq: ${String(marker2.ppq)}`);
|
|
4224
|
+
}
|
|
4225
|
+
if (marker2.id !== void 0) {
|
|
4226
|
+
if (!Number.isInteger(marker2.id) || marker2.id <= 0) {
|
|
4227
|
+
throw new RangeError(`Invalid marker id: ${String(marker2.id)}`);
|
|
4228
|
+
}
|
|
4229
|
+
if (seen.has(marker2.id)) {
|
|
4230
|
+
throw new Error(`Duplicate marker id: ${marker2.id}`);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
const id = marker2.id ?? counter++;
|
|
4234
|
+
seen.add(id);
|
|
4235
|
+
if (id >= counter) {
|
|
4236
|
+
counter = id + 1;
|
|
4237
|
+
}
|
|
4238
|
+
resolved.push({ id, ppq: marker2.ppq, name: marker2.name ?? "" });
|
|
4239
|
+
}
|
|
4240
|
+
return { resolved, nextMarkerId: counter };
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
// src/worklet/engine-capture-facade.ts
|
|
4244
|
+
function configureCapture(ctx, options) {
|
|
4245
|
+
const config = buildCaptureConfig(options, ctx.offlineChannelCount);
|
|
4246
|
+
ctx.offlineEngine.setCaptureBuffer(config.channels, config.bufferFrames);
|
|
4247
|
+
ctx.offlineEngine.setCaptureSource(config.source);
|
|
4248
|
+
ctx.offlineEngine.setRecordOffsetSamples(config.recordOffsetSamples);
|
|
4249
|
+
ctx.offlineEngine.setInputMonitor(config.inputMonitor.enabled, config.inputMonitor.gain);
|
|
4250
|
+
ctx.setCaptureConfig(config);
|
|
4251
|
+
ctx.postSync({ type: "syncCapture", ...config });
|
|
4252
|
+
}
|
|
4253
|
+
function armRecord(ctx, trackId, enabled) {
|
|
4254
|
+
if (trackId !== 0) {
|
|
4255
|
+
throw new RangeError("Capture is global; armRecord only accepts trackId 0");
|
|
4256
|
+
}
|
|
4257
|
+
if (enabled && !ctx.getCaptureConfig()) {
|
|
4258
|
+
throw new Error("Capture buffer is not configured");
|
|
4259
|
+
}
|
|
4260
|
+
ctx.offlineEngine.armCapture(enabled);
|
|
4261
|
+
return ctx.sendCommand({
|
|
4262
|
+
type: 13 /* ArmRecord */,
|
|
4263
|
+
targetId: 0,
|
|
2999
4264
|
sampleTime: -1,
|
|
3000
4265
|
argInt: enabled ? 1 : 0
|
|
3001
4266
|
});
|
|
@@ -3167,16 +4432,348 @@ function syncMarkers(ctx) {
|
|
|
3167
4432
|
}
|
|
3168
4433
|
|
|
3169
4434
|
// src/worklet/engine-mixer-facade.ts
|
|
4435
|
+
function isSceneObject(value) {
|
|
4436
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4437
|
+
}
|
|
4438
|
+
function hasOwn2(object, key) {
|
|
4439
|
+
return Object.getOwnPropertyDescriptor(object, key) !== void 0;
|
|
4440
|
+
}
|
|
4441
|
+
function canonicalJson(value) {
|
|
4442
|
+
if (Array.isArray(value)) {
|
|
4443
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
4444
|
+
}
|
|
4445
|
+
if (value !== null && typeof value === "object") {
|
|
4446
|
+
const object = value;
|
|
4447
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(",")}}`;
|
|
4448
|
+
}
|
|
4449
|
+
return JSON.stringify(value);
|
|
4450
|
+
}
|
|
4451
|
+
function aliasedValue(object, primary, legacy) {
|
|
4452
|
+
if (hasOwn2(object, primary)) {
|
|
4453
|
+
return { present: true, value: object[primary] };
|
|
4454
|
+
}
|
|
4455
|
+
if (hasOwn2(object, legacy)) {
|
|
4456
|
+
return { present: true, value: object[legacy] };
|
|
4457
|
+
}
|
|
4458
|
+
return { present: false, value: void 0 };
|
|
4459
|
+
}
|
|
4460
|
+
function insertSignature(insert) {
|
|
4461
|
+
const object = insert !== null && typeof insert === "object" ? insert : {};
|
|
4462
|
+
const slot = object.slot === void 0 ? "pre" : object.slot;
|
|
4463
|
+
const processor = aliasedValue(object, "processor", "processor_name");
|
|
4464
|
+
const params = aliasedValue(object, "params", "params_json");
|
|
4465
|
+
const sidechain = aliasedValue(object, "sidechainKey", "sidechain_key");
|
|
4466
|
+
const processorValue = typeof processor.value === "string" ? processor.value : "";
|
|
4467
|
+
const sidechainValue = typeof sidechain.value === "string" ? sidechain.value : "";
|
|
4468
|
+
let paramsValue;
|
|
4469
|
+
if (!params.present) {
|
|
4470
|
+
paramsValue = ["absent"];
|
|
4471
|
+
} else if (typeof params.value === "string") {
|
|
4472
|
+
if (params.value.length === 0) {
|
|
4473
|
+
paramsValue = ["absent"];
|
|
4474
|
+
} else {
|
|
4475
|
+
try {
|
|
4476
|
+
paramsValue = ["params", canonicalJson(JSON.parse(params.value))];
|
|
4477
|
+
} catch {
|
|
4478
|
+
paramsValue = ["params-invalid", params.value];
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
} else {
|
|
4482
|
+
paramsValue = ["params", canonicalJson(params.value)];
|
|
4483
|
+
}
|
|
4484
|
+
return JSON.stringify([slot, processorValue, paramsValue, sidechainValue]);
|
|
4485
|
+
}
|
|
4486
|
+
function decodedInsertSignature(sceneJson, target) {
|
|
4487
|
+
const scene = JSON.parse(sceneJson);
|
|
4488
|
+
const entries = target.kind === "bus" ? scene.buses : scene.strips;
|
|
4489
|
+
const strip = Array.isArray(entries) ? entries.find(isSceneObject) : void 0;
|
|
4490
|
+
const object = isSceneObject(strip) ? strip : {};
|
|
4491
|
+
const inserts = Array.isArray(object.inserts) ? object.inserts.filter(isSceneObject) : [];
|
|
4492
|
+
const ordered = target.kind === "bus" ? inserts : [
|
|
4493
|
+
...inserts.filter((insert) => insert.slot !== "post"),
|
|
4494
|
+
...inserts.filter((insert) => insert.slot === "post")
|
|
4495
|
+
];
|
|
4496
|
+
return JSON.stringify(ordered.map(insertSignature));
|
|
4497
|
+
}
|
|
4498
|
+
function constructionValue(engine, target, override) {
|
|
4499
|
+
let parameterId = -1;
|
|
4500
|
+
switch (target.kind) {
|
|
4501
|
+
case "track":
|
|
4502
|
+
parameterId = engine.resolveTrackInsertAutomationId(
|
|
4503
|
+
target.trackId,
|
|
4504
|
+
override.insertIndex,
|
|
4505
|
+
override.paramName
|
|
4506
|
+
);
|
|
4507
|
+
break;
|
|
4508
|
+
case "bus":
|
|
4509
|
+
parameterId = engine.resolveBusInsertAutomationId(
|
|
4510
|
+
target.busId,
|
|
4511
|
+
override.insertIndex,
|
|
4512
|
+
override.paramName
|
|
4513
|
+
);
|
|
4514
|
+
break;
|
|
4515
|
+
case "master":
|
|
4516
|
+
parameterId = engine.resolveMasterInsertAutomationId(
|
|
4517
|
+
override.insertIndex,
|
|
4518
|
+
override.paramName
|
|
4519
|
+
);
|
|
4520
|
+
break;
|
|
4521
|
+
}
|
|
4522
|
+
if (parameterId < 0) {
|
|
4523
|
+
return void 0;
|
|
4524
|
+
}
|
|
4525
|
+
return engine.insertParameterConstructedValue(parameterId);
|
|
4526
|
+
}
|
|
4527
|
+
function constructionInsertOverrides(engine, target, overrides) {
|
|
4528
|
+
const restored = [];
|
|
4529
|
+
for (const override of overrides.values()) {
|
|
4530
|
+
if (!sameStripTarget(override.target, target)) {
|
|
4531
|
+
continue;
|
|
4532
|
+
}
|
|
4533
|
+
const value = constructionValue(engine, target, override);
|
|
4534
|
+
if (value !== void 0) {
|
|
4535
|
+
restored.push({ ...override, value });
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
return restored;
|
|
4539
|
+
}
|
|
4540
|
+
function applyInsertParamOverride(engine, override) {
|
|
4541
|
+
switch (override.target.kind) {
|
|
4542
|
+
case "track":
|
|
4543
|
+
engine.restoreTrackStripInsertParamByName(
|
|
4544
|
+
override.target.trackId,
|
|
4545
|
+
override.insertIndex,
|
|
4546
|
+
override.paramName,
|
|
4547
|
+
override.value
|
|
4548
|
+
);
|
|
4549
|
+
break;
|
|
4550
|
+
case "bus":
|
|
4551
|
+
engine.restoreBusStripInsertParamByName(
|
|
4552
|
+
override.target.busId,
|
|
4553
|
+
override.insertIndex,
|
|
4554
|
+
override.paramName,
|
|
4555
|
+
override.value
|
|
4556
|
+
);
|
|
4557
|
+
break;
|
|
4558
|
+
case "master":
|
|
4559
|
+
engine.restoreMasterStripInsertParamByName(
|
|
4560
|
+
override.insertIndex,
|
|
4561
|
+
override.paramName,
|
|
4562
|
+
override.value
|
|
4563
|
+
);
|
|
4564
|
+
break;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
function applyConstructionInsertOverrides(engine, overrides) {
|
|
4568
|
+
for (const override of overrides) {
|
|
4569
|
+
applyInsertParamOverride(engine, override);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
function clearInsertParameterBases(engine, target) {
|
|
4573
|
+
switch (target.kind) {
|
|
4574
|
+
case "track":
|
|
4575
|
+
engine.clearTrackInsertParameterBases(target.trackId);
|
|
4576
|
+
break;
|
|
4577
|
+
case "bus":
|
|
4578
|
+
engine.clearBusInsertParameterBases(target.busId);
|
|
4579
|
+
break;
|
|
4580
|
+
case "master":
|
|
4581
|
+
engine.clearMasterInsertParameterBases();
|
|
4582
|
+
break;
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
function emptyStripJson(target) {
|
|
4586
|
+
switch (target.kind) {
|
|
4587
|
+
case "track":
|
|
4588
|
+
return `{"version":1,"strips":[{"id":"track-${target.trackId}"}],"buses":[],"connections":[]}`;
|
|
4589
|
+
case "bus":
|
|
4590
|
+
return `{"version":1,"strips":[],"buses":[{"id":"bus-${target.busId}"}],"connections":[]}`;
|
|
4591
|
+
case "master":
|
|
4592
|
+
return '{"version":1,"strips":[{"id":"master"}],"buses":[],"connections":[]}';
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
function pruneStripSidechains(ctx, target, sceneJson) {
|
|
4596
|
+
const scene = JSON.parse(sceneJson);
|
|
4597
|
+
const entry = target.kind === "bus" ? scene.buses?.[0] : scene.strips?.[0];
|
|
4598
|
+
const insertCount = Array.isArray(entry?.inserts) ? entry.inserts.length : 0;
|
|
4599
|
+
if (target.kind === "track") {
|
|
4600
|
+
for (const [key, binding] of ctx.laneSidechains) {
|
|
4601
|
+
if (binding.trackId === target.trackId && binding.insertIndex >= insertCount) {
|
|
4602
|
+
ctx.laneSidechains.delete(key);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
} else if (target.kind === "bus") {
|
|
4606
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
4607
|
+
if (binding.busId === target.busId && binding.insertIndex >= insertCount) {
|
|
4608
|
+
ctx.busSidechains.delete(key);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
} else {
|
|
4612
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
4613
|
+
if (binding.insertIndex >= insertCount) {
|
|
4614
|
+
ctx.masterSidechains.delete(key);
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
function hasInsertParamOverrides(overrides, target) {
|
|
4620
|
+
for (const override of overrides.values()) {
|
|
4621
|
+
if (sameStripTarget(override.target, target)) {
|
|
4622
|
+
return true;
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
return false;
|
|
4626
|
+
}
|
|
4627
|
+
function applyFullStripJson(engine, target, sceneJson) {
|
|
4628
|
+
const apply = (json) => {
|
|
4629
|
+
switch (target.kind) {
|
|
4630
|
+
case "track":
|
|
4631
|
+
engine.setTrackStripJson(target.trackId, json);
|
|
4632
|
+
break;
|
|
4633
|
+
case "bus":
|
|
4634
|
+
engine.setBusStripJson(target.busId, json);
|
|
4635
|
+
break;
|
|
4636
|
+
case "master":
|
|
4637
|
+
engine.setMasterStripJson(json);
|
|
4638
|
+
break;
|
|
4639
|
+
}
|
|
4640
|
+
};
|
|
4641
|
+
engine.applyCommandsDueNowPreservingFuture();
|
|
4642
|
+
apply(sceneJson);
|
|
4643
|
+
}
|
|
4644
|
+
function insertParamOverrideKey(override) {
|
|
4645
|
+
const targetId = override.target.kind === "track" ? override.target.trackId : override.target.kind === "bus" ? override.target.busId : "master";
|
|
4646
|
+
return JSON.stringify([override.target.kind, targetId, override.insertIndex, override.paramName]);
|
|
4647
|
+
}
|
|
4648
|
+
function clearInsertParamOverrides(overrides, target) {
|
|
4649
|
+
for (const [key, override] of overrides) {
|
|
4650
|
+
if (sameStripTarget(override.target, target)) {
|
|
4651
|
+
overrides.delete(key);
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
}
|
|
4655
|
+
function sameStripTarget(left, right) {
|
|
4656
|
+
if (left.kind !== right.kind) {
|
|
4657
|
+
return false;
|
|
4658
|
+
}
|
|
4659
|
+
if (left.kind === "track" && right.kind === "track") {
|
|
4660
|
+
return left.trackId === right.trackId;
|
|
4661
|
+
}
|
|
4662
|
+
if (left.kind === "bus" && right.kind === "bus") {
|
|
4663
|
+
return left.busId === right.busId;
|
|
4664
|
+
}
|
|
4665
|
+
return left.kind === "master" && right.kind === "master";
|
|
4666
|
+
}
|
|
4667
|
+
function flattenInsertParamOverride(override) {
|
|
4668
|
+
switch (override.target.kind) {
|
|
4669
|
+
case "track":
|
|
4670
|
+
return {
|
|
4671
|
+
kind: "track",
|
|
4672
|
+
trackId: override.target.trackId,
|
|
4673
|
+
insertIndex: override.insertIndex,
|
|
4674
|
+
paramName: override.paramName,
|
|
4675
|
+
value: override.value
|
|
4676
|
+
};
|
|
4677
|
+
case "bus":
|
|
4678
|
+
return {
|
|
4679
|
+
kind: "bus",
|
|
4680
|
+
busId: override.target.busId,
|
|
4681
|
+
insertIndex: override.insertIndex,
|
|
4682
|
+
paramName: override.paramName,
|
|
4683
|
+
value: override.value
|
|
4684
|
+
};
|
|
4685
|
+
case "master":
|
|
4686
|
+
return {
|
|
4687
|
+
kind: "master",
|
|
4688
|
+
insertIndex: override.insertIndex,
|
|
4689
|
+
paramName: override.paramName,
|
|
4690
|
+
value: override.value
|
|
4691
|
+
};
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
function cachedStripJson(ctx, target) {
|
|
4695
|
+
switch (target.kind) {
|
|
4696
|
+
case "track":
|
|
4697
|
+
return ctx.trackStripJson.get(target.trackId);
|
|
4698
|
+
case "bus":
|
|
4699
|
+
return ctx.busStripJson.get(target.busId);
|
|
4700
|
+
case "master":
|
|
4701
|
+
return ctx.getMasterStripJson();
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
function cacheStripJson(ctx, target, sceneJson) {
|
|
4705
|
+
switch (target.kind) {
|
|
4706
|
+
case "track":
|
|
4707
|
+
ctx.trackStripJson.set(target.trackId, sceneJson);
|
|
4708
|
+
return;
|
|
4709
|
+
case "bus":
|
|
4710
|
+
ctx.busStripJson.set(target.busId, sceneJson);
|
|
4711
|
+
return;
|
|
4712
|
+
case "master":
|
|
4713
|
+
ctx.cacheMasterStripJson(sceneJson);
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
3170
4717
|
function mixerLanes(ctx) {
|
|
3171
|
-
return buildMixerLanes(
|
|
4718
|
+
return buildMixerLanes(
|
|
4719
|
+
ctx.trackLaneIds,
|
|
4720
|
+
ctx.trackSends,
|
|
4721
|
+
ctx.trackOutputBus,
|
|
4722
|
+
ctx.trackSourceChannelLayout
|
|
4723
|
+
);
|
|
4724
|
+
}
|
|
4725
|
+
function cloneMixerRouting(ctx) {
|
|
4726
|
+
return {
|
|
4727
|
+
trackLaneIds: [...ctx.trackLaneIds],
|
|
4728
|
+
trackSends: new Map(
|
|
4729
|
+
Array.from(ctx.trackSends, ([trackId, sends]) => [
|
|
4730
|
+
trackId,
|
|
4731
|
+
sends.map((send) => ({ ...send }))
|
|
4732
|
+
])
|
|
4733
|
+
),
|
|
4734
|
+
trackOutputBus: new Map(ctx.trackOutputBus),
|
|
4735
|
+
trackSourceChannelLayout: new Map(ctx.trackSourceChannelLayout)
|
|
4736
|
+
};
|
|
4737
|
+
}
|
|
4738
|
+
function commitMixerRouting(ctx, draft) {
|
|
4739
|
+
ctx.trackLaneIds.splice(0, ctx.trackLaneIds.length, ...draft.trackLaneIds);
|
|
4740
|
+
ctx.trackSends.clear();
|
|
4741
|
+
for (const [trackId, sends] of draft.trackSends) {
|
|
4742
|
+
ctx.trackSends.set(
|
|
4743
|
+
trackId,
|
|
4744
|
+
sends.map((send) => ({ ...send }))
|
|
4745
|
+
);
|
|
4746
|
+
}
|
|
4747
|
+
ctx.trackOutputBus.clear();
|
|
4748
|
+
for (const [trackId, busId] of draft.trackOutputBus) {
|
|
4749
|
+
ctx.trackOutputBus.set(trackId, busId);
|
|
4750
|
+
}
|
|
4751
|
+
ctx.trackSourceChannelLayout.clear();
|
|
4752
|
+
for (const [trackId, layout] of draft.trackSourceChannelLayout) {
|
|
4753
|
+
ctx.trackSourceChannelLayout.set(trackId, layout);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
function buildDraftMixerLanes(draft) {
|
|
4757
|
+
return buildMixerLanes(
|
|
4758
|
+
draft.trackLaneIds,
|
|
4759
|
+
draft.trackSends,
|
|
4760
|
+
draft.trackOutputBus,
|
|
4761
|
+
draft.trackSourceChannelLayout
|
|
4762
|
+
);
|
|
3172
4763
|
}
|
|
3173
|
-
function
|
|
3174
|
-
const lanes =
|
|
4764
|
+
function applyMixerRouting(ctx, draft, busesAlreadyApplied) {
|
|
4765
|
+
const lanes = buildDraftMixerLanes(draft);
|
|
3175
4766
|
const buses = ctx.buses.map((bus) => ({ ...bus }));
|
|
3176
|
-
|
|
4767
|
+
if (!busesAlreadyApplied) {
|
|
4768
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
4769
|
+
}
|
|
3177
4770
|
if (lanes.length > 0) {
|
|
3178
4771
|
ctx.offlineEngine.setTrackLanes(lanes);
|
|
3179
4772
|
}
|
|
4773
|
+
return { lanes, buses };
|
|
4774
|
+
}
|
|
4775
|
+
function postMixerSync(ctx, lanes, buses, insertBaseResets, oneShotInsertParamOverrides) {
|
|
4776
|
+
replayInsertParamOverrides(ctx, lanes, buses);
|
|
3180
4777
|
const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
|
|
3181
4778
|
trackId,
|
|
3182
4779
|
sceneJson
|
|
@@ -3185,6 +4782,10 @@ function syncMixer(ctx) {
|
|
|
3185
4782
|
busId,
|
|
3186
4783
|
sceneJson
|
|
3187
4784
|
}));
|
|
4785
|
+
const insertParamOverrides = [
|
|
4786
|
+
...Array.from(ctx.insertParamOverrides.values(), flattenInsertParamOverride),
|
|
4787
|
+
...oneShotInsertParamOverrides.map(flattenInsertParamOverride)
|
|
4788
|
+
];
|
|
3188
4789
|
ctx.postSync({
|
|
3189
4790
|
type: "syncMixer",
|
|
3190
4791
|
lanes,
|
|
@@ -3192,38 +4793,117 @@ function syncMixer(ctx) {
|
|
|
3192
4793
|
trackStrips,
|
|
3193
4794
|
laneSidechains: Array.from(ctx.laneSidechains.values()),
|
|
3194
4795
|
busStrips,
|
|
3195
|
-
masterStripJson: ctx.getMasterStripJson()
|
|
4796
|
+
masterStripJson: ctx.getMasterStripJson(),
|
|
4797
|
+
busSidechains: Array.from(ctx.busSidechains.values()),
|
|
4798
|
+
masterSidechains: Array.from(ctx.masterSidechains.values()),
|
|
4799
|
+
...insertParamOverrides.length > 0 ? { insertParamOverrides } : {},
|
|
4800
|
+
...insertBaseResets.length > 0 ? { insertBaseResets } : {}
|
|
3196
4801
|
});
|
|
3197
4802
|
}
|
|
4803
|
+
function syncMixerDraft(ctx, draft, busesAlreadyApplied = false, insertBaseResets = [], oneShotInsertParamOverrides = []) {
|
|
4804
|
+
const { lanes, buses } = applyMixerRouting(ctx, draft, busesAlreadyApplied);
|
|
4805
|
+
commitMixerRouting(ctx, draft);
|
|
4806
|
+
postMixerSync(ctx, lanes, buses, insertBaseResets, oneShotInsertParamOverrides);
|
|
4807
|
+
}
|
|
4808
|
+
function syncMixer(ctx, busesAlreadyApplied = false, insertBaseResets = [], oneShotInsertParamOverrides = []) {
|
|
4809
|
+
syncMixerDraft(
|
|
4810
|
+
ctx,
|
|
4811
|
+
cloneMixerRouting(ctx),
|
|
4812
|
+
busesAlreadyApplied,
|
|
4813
|
+
insertBaseResets,
|
|
4814
|
+
oneShotInsertParamOverrides
|
|
4815
|
+
);
|
|
4816
|
+
}
|
|
4817
|
+
function replayInsertParamOverrides(ctx, lanes = mixerLanes(ctx), buses = ctx.buses) {
|
|
4818
|
+
const activeTrackIds = new Set(lanes.map((lane) => lane.trackId));
|
|
4819
|
+
const activeBusIds = new Set(buses.map((bus) => bus.busId));
|
|
4820
|
+
for (const [key, override] of ctx.insertParamOverrides) {
|
|
4821
|
+
const active = override.target.kind === "track" ? activeTrackIds.has(override.target.trackId) : override.target.kind === "bus" ? activeBusIds.has(override.target.busId) : true;
|
|
4822
|
+
if (!active) {
|
|
4823
|
+
ctx.insertParamOverrides.delete(key);
|
|
4824
|
+
continue;
|
|
4825
|
+
}
|
|
4826
|
+
switch (override.target.kind) {
|
|
4827
|
+
case "track":
|
|
4828
|
+
ctx.offlineEngine.restoreTrackStripInsertParamByName(
|
|
4829
|
+
override.target.trackId,
|
|
4830
|
+
override.insertIndex,
|
|
4831
|
+
override.paramName,
|
|
4832
|
+
override.value
|
|
4833
|
+
);
|
|
4834
|
+
break;
|
|
4835
|
+
case "bus":
|
|
4836
|
+
ctx.offlineEngine.restoreBusStripInsertParamByName(
|
|
4837
|
+
override.target.busId,
|
|
4838
|
+
override.insertIndex,
|
|
4839
|
+
override.paramName,
|
|
4840
|
+
override.value
|
|
4841
|
+
);
|
|
4842
|
+
break;
|
|
4843
|
+
case "master":
|
|
4844
|
+
ctx.offlineEngine.restoreMasterStripInsertParamByName(
|
|
4845
|
+
override.insertIndex,
|
|
4846
|
+
override.paramName,
|
|
4847
|
+
override.value
|
|
4848
|
+
);
|
|
4849
|
+
break;
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
3198
4853
|
function setTrackLanes(ctx, lanes) {
|
|
3199
|
-
const
|
|
4854
|
+
const draft = cloneMixerRouting(ctx);
|
|
4855
|
+
const { entries, ids } = normalizeTrackLanes(draft.trackLaneIds, lanes);
|
|
3200
4856
|
for (const entry of entries) {
|
|
3201
4857
|
if (entry.sends) {
|
|
3202
|
-
|
|
4858
|
+
draft.trackSends.set(
|
|
3203
4859
|
entry.trackId,
|
|
3204
4860
|
entry.sends.map((send) => ({ ...send }))
|
|
3205
4861
|
);
|
|
3206
4862
|
}
|
|
3207
4863
|
if (entry.outputBusId !== void 0) {
|
|
3208
4864
|
if (entry.outputBusId === 0) {
|
|
3209
|
-
|
|
4865
|
+
draft.trackOutputBus.delete(entry.trackId);
|
|
3210
4866
|
} else {
|
|
3211
|
-
|
|
4867
|
+
draft.trackOutputBus.set(entry.trackId, entry.outputBusId);
|
|
3212
4868
|
}
|
|
3213
4869
|
}
|
|
4870
|
+
if (entry.sourceChannelLayout !== void 0) {
|
|
4871
|
+
draft.trackSourceChannelLayout.set(entry.trackId, entry.sourceChannelLayout);
|
|
4872
|
+
}
|
|
3214
4873
|
}
|
|
3215
|
-
|
|
3216
|
-
ctx
|
|
4874
|
+
draft.trackLaneIds = ids;
|
|
4875
|
+
syncMixerDraft(ctx, draft, true);
|
|
4876
|
+
}
|
|
4877
|
+
function ensureTrackLaneInDraft(draft, target) {
|
|
4878
|
+
const trackId = resolveTargetId(target);
|
|
4879
|
+
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
4880
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
|
|
4881
|
+
}
|
|
4882
|
+
const existing = draft.trackLaneIds.indexOf(trackId);
|
|
4883
|
+
if (existing >= 0) {
|
|
4884
|
+
return existing;
|
|
4885
|
+
}
|
|
4886
|
+
draft.trackLaneIds.push(trackId);
|
|
4887
|
+
return draft.trackLaneIds.length - 1;
|
|
4888
|
+
}
|
|
4889
|
+
function ensureTrackLane(ctx, target) {
|
|
4890
|
+
const draft = cloneMixerRouting(ctx);
|
|
4891
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
4892
|
+
if (draft.trackLaneIds.length !== ctx.trackLaneIds.length) {
|
|
4893
|
+
syncMixerDraft(ctx, draft, true);
|
|
4894
|
+
}
|
|
4895
|
+
return laneIndex;
|
|
3217
4896
|
}
|
|
3218
4897
|
function setTrackOutputBus(ctx, target, busId) {
|
|
3219
|
-
const
|
|
3220
|
-
const
|
|
4898
|
+
const draft = cloneMixerRouting(ctx);
|
|
4899
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
4900
|
+
const trackId = draft.trackLaneIds[laneIndex];
|
|
3221
4901
|
if (busId === 0) {
|
|
3222
|
-
|
|
4902
|
+
draft.trackOutputBus.delete(trackId);
|
|
3223
4903
|
} else {
|
|
3224
|
-
|
|
4904
|
+
draft.trackOutputBus.set(trackId, busId);
|
|
3225
4905
|
}
|
|
3226
|
-
ctx
|
|
4906
|
+
syncMixerDraft(ctx, draft, true);
|
|
3227
4907
|
}
|
|
3228
4908
|
function setLaneSidechain(ctx, target, insertIndex, sourceTarget) {
|
|
3229
4909
|
const laneIndex = ctx.ensureTrackLane(target);
|
|
@@ -3234,143 +4914,144 @@ function setLaneSidechain(ctx, target, insertIndex, sourceTarget) {
|
|
|
3234
4914
|
const sourceIndex = ctx.ensureTrackLane(sourceTarget);
|
|
3235
4915
|
sourceTrackId = ctx.trackLaneIds[sourceIndex];
|
|
3236
4916
|
}
|
|
4917
|
+
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3237
4918
|
if (sourceTrackId === 0) {
|
|
3238
4919
|
ctx.laneSidechains.delete(key);
|
|
3239
4920
|
} else {
|
|
3240
4921
|
ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3241
4922
|
}
|
|
3242
|
-
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3243
4923
|
ctx.postSync({
|
|
3244
4924
|
type: "syncMixer",
|
|
4925
|
+
sidechainDelta: true,
|
|
3245
4926
|
lanes: ctx.mixerLanes(),
|
|
3246
4927
|
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
3247
4928
|
});
|
|
3248
4929
|
}
|
|
3249
|
-
function
|
|
3250
|
-
const
|
|
3251
|
-
const trackId = ctx.trackLaneIds[laneIndex];
|
|
3252
|
-
ctx.trackSends.set(
|
|
3253
|
-
trackId,
|
|
3254
|
-
sends.map((send) => ({ ...send }))
|
|
3255
|
-
);
|
|
3256
|
-
ctx.syncMixer();
|
|
3257
|
-
}
|
|
3258
|
-
function setTrackBuses(ctx, buses) {
|
|
3259
|
-
ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
3260
|
-
ctx.syncMixer();
|
|
3261
|
-
}
|
|
3262
|
-
function setBusGain(ctx, busId, db) {
|
|
3263
|
-
const busIndex = ctx.ensureBus(busId);
|
|
3264
|
-
ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
|
|
3265
|
-
ctx.offlineEngine.setTrackBuses(ctx.buses);
|
|
3266
|
-
return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
3267
|
-
}
|
|
3268
|
-
function setBusStripJson(ctx, busId, sceneJson) {
|
|
4930
|
+
function setBusSidechain(ctx, busId, insertIndex, kind, sourceId) {
|
|
4931
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
3269
4932
|
ctx.ensureBus(busId);
|
|
3270
|
-
ctx
|
|
3271
|
-
ctx.
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3278
|
-
return false;
|
|
3279
|
-
}
|
|
3280
|
-
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
3281
|
-
}
|
|
3282
|
-
function isEngineCommandRecord(value) {
|
|
3283
|
-
return isRecord(value) && typeof value.type === "number";
|
|
3284
|
-
}
|
|
3285
|
-
function isEngineSyncMessage(value) {
|
|
3286
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3287
|
-
return false;
|
|
3288
|
-
}
|
|
3289
|
-
return value.type === "syncClips" || value.type === "syncClipsDelta" || value.type === "syncClipPageProvider" || value.type === "syncClipPage" || value.type === "syncClipPageClear" || value.type === "syncClipPageCommit" || value.type === "syncClipPageDestroy" || value.type === "syncMidiClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation" || value.type === "syncTempo" || value.type === "syncMixer" || value.type === "syncCapture" || value.type === "syncTrackStripEqBand" || value.type === "syncMasterStripEqBand" || value.type === "syncTrackStripInsertBypassed" || value.type === "syncMasterStripInsertBypassed" || value.type === "syncTrackStripInsertParamByName" || value.type === "syncMasterStripInsertParamByName" || value.type === "syncBusStripInsertParamByName" || value.type === "syncTrackStripPan" || value.type === "syncTrackStripPanLaw" || value.type === "syncTrackStripPanMode" || value.type === "syncTrackStripDualPan" || value.type === "syncTrackStripChannelDelaySamples" || value.type === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiFx" || value.type === "syncClearMidiFx" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiUmp" || value.type === "syncMidiSysex" || value.type === "syncMidiPanic" || value.type === "syncMidiDestinationExternal" || value.type === "syncExternalMidiClock" || value.type === "destroy";
|
|
3290
|
-
}
|
|
3291
|
-
function isEngineCaptureRequestMessage(value) {
|
|
3292
|
-
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
3293
|
-
}
|
|
3294
|
-
function isCaptureStatus(value) {
|
|
3295
|
-
return isRecord(value) && typeof value.capturedFrames === "number" && Number.isSafeInteger(value.capturedFrames) && value.capturedFrames >= 0 && typeof value.overflowCount === "number" && Number.isSafeInteger(value.overflowCount) && value.overflowCount >= 0 && typeof value.armed === "boolean" && typeof value.punchEnabled === "boolean" && (value.source === "input" || value.source === "output") && typeof value.recordOffsetSamples === "number" && Number.isSafeInteger(value.recordOffsetSamples);
|
|
3296
|
-
}
|
|
3297
|
-
function isCaptureChannel(value) {
|
|
3298
|
-
return value instanceof Float32Array && typeof ArrayBuffer !== "undefined" && value.buffer instanceof ArrayBuffer;
|
|
3299
|
-
}
|
|
3300
|
-
function isCaptureChannels(value) {
|
|
3301
|
-
return Array.isArray(value) && value.every((channel) => isCaptureChannel(channel));
|
|
3302
|
-
}
|
|
3303
|
-
function hasOwn(value, key) {
|
|
3304
|
-
return Object.prototype.hasOwnProperty.call(value, key);
|
|
3305
|
-
}
|
|
3306
|
-
function engineCaptureResponseRequestId(value) {
|
|
3307
|
-
if (!isRecord(value) || value.type !== "captureResponse" || typeof value.requestId !== "number" || !Number.isSafeInteger(value.requestId)) {
|
|
3308
|
-
return void 0;
|
|
4933
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
4934
|
+
ctx.offlineEngine.setBusSidechain(busId, insertIndex, sourceKind, sourceId);
|
|
4935
|
+
const key = `${busId}:${insertIndex}`;
|
|
4936
|
+
if (sourceId === 0) {
|
|
4937
|
+
ctx.busSidechains.delete(key);
|
|
4938
|
+
} else {
|
|
4939
|
+
ctx.busSidechains.set(key, { busId, insertIndex, sourceKind, sourceId });
|
|
3309
4940
|
}
|
|
3310
|
-
|
|
4941
|
+
ctx.postSync({
|
|
4942
|
+
type: "syncMixer",
|
|
4943
|
+
sidechainDelta: true,
|
|
4944
|
+
lanes: ctx.mixerLanes(),
|
|
4945
|
+
busSidechains: [{ busId, insertIndex, sourceKind, sourceId }]
|
|
4946
|
+
});
|
|
3311
4947
|
}
|
|
3312
|
-
function
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
if (value.ok !== true || hasOwn(value, "error")) {
|
|
3321
|
-
return false;
|
|
3322
|
-
}
|
|
3323
|
-
const hasStatus = hasOwn(value, "status");
|
|
3324
|
-
const hasChannels = hasOwn(value, "channels");
|
|
3325
|
-
if (hasStatus && hasChannels) {
|
|
3326
|
-
return false;
|
|
3327
|
-
}
|
|
3328
|
-
if (hasStatus) {
|
|
3329
|
-
return isCaptureStatus(value.status);
|
|
3330
|
-
}
|
|
3331
|
-
if (hasChannels) {
|
|
3332
|
-
return isCaptureChannels(value.channels);
|
|
4948
|
+
function setMasterSidechain(ctx, insertIndex, kind, sourceId) {
|
|
4949
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
4950
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
4951
|
+
ctx.offlineEngine.setMasterSidechain(insertIndex, sourceKind, sourceId);
|
|
4952
|
+
if (sourceId === 0) {
|
|
4953
|
+
ctx.masterSidechains.delete(insertIndex);
|
|
4954
|
+
} else {
|
|
4955
|
+
ctx.masterSidechains.set(insertIndex, { insertIndex, sourceKind, sourceId });
|
|
3333
4956
|
}
|
|
3334
|
-
|
|
4957
|
+
ctx.postSync({
|
|
4958
|
+
type: "syncMixer",
|
|
4959
|
+
sidechainDelta: true,
|
|
4960
|
+
lanes: ctx.mixerLanes(),
|
|
4961
|
+
masterSidechains: [{ insertIndex, sourceKind, sourceId }]
|
|
4962
|
+
});
|
|
3335
4963
|
}
|
|
3336
|
-
function
|
|
3337
|
-
if (
|
|
3338
|
-
return
|
|
4964
|
+
function ensureSidechainSource(ctx, sourceKind, sourceId) {
|
|
4965
|
+
if (sourceId === 0) {
|
|
4966
|
+
return;
|
|
3339
4967
|
}
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
return "channels" in response;
|
|
3345
|
-
case "reset":
|
|
3346
|
-
return !("status" in response) && !("channels" in response);
|
|
4968
|
+
if (sourceKind === 1) {
|
|
4969
|
+
ctx.ensureBus(sourceId);
|
|
4970
|
+
} else {
|
|
4971
|
+
ctx.ensureTrackLane(sourceId);
|
|
3347
4972
|
}
|
|
3348
4973
|
}
|
|
3349
|
-
function
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
4974
|
+
function setSends(ctx, target, sends) {
|
|
4975
|
+
const draft = cloneMixerRouting(ctx);
|
|
4976
|
+
const laneIndex = ensureTrackLaneInDraft(draft, target);
|
|
4977
|
+
const trackId = draft.trackLaneIds[laneIndex];
|
|
4978
|
+
draft.trackSends.set(
|
|
4979
|
+
trackId,
|
|
4980
|
+
sends.map((send) => ({ ...send }))
|
|
4981
|
+
);
|
|
4982
|
+
syncMixerDraft(ctx, draft, true);
|
|
3354
4983
|
}
|
|
3355
|
-
function
|
|
3356
|
-
|
|
3357
|
-
|
|
4984
|
+
function setTrackBuses(ctx, buses) {
|
|
4985
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
4986
|
+
const retainedBusIds = new Set(buses.map((bus) => bus.busId));
|
|
4987
|
+
for (const bus of ctx.buses) {
|
|
4988
|
+
if (!retainedBusIds.has(bus.busId)) {
|
|
4989
|
+
ctx.clearInsertAutomationLanes({ kind: "bus", busId: bus.busId }, true);
|
|
4990
|
+
}
|
|
3358
4991
|
}
|
|
3359
|
-
|
|
4992
|
+
ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
4993
|
+
const activeBusIds = new Set(ctx.buses.map((bus) => bus.busId));
|
|
4994
|
+
for (const busId of ctx.busStripJson.keys()) {
|
|
4995
|
+
if (!activeBusIds.has(busId)) {
|
|
4996
|
+
ctx.busStripJson.delete(busId);
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
5000
|
+
if (!activeBusIds.has(binding.busId) || binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
|
|
5001
|
+
ctx.busSidechains.delete(key);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
5005
|
+
if (binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
|
|
5006
|
+
ctx.masterSidechains.delete(key);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
syncMixer(ctx, true);
|
|
3360
5010
|
}
|
|
3361
|
-
function
|
|
3362
|
-
|
|
5011
|
+
function setBusGain(ctx, busId, db) {
|
|
5012
|
+
const busIndex = ctx.ensureBus(busId);
|
|
5013
|
+
ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
|
|
5014
|
+
return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
3363
5015
|
}
|
|
3364
|
-
function
|
|
3365
|
-
|
|
5016
|
+
function replaceStripScene(ctx, target, previousJson, sceneJson) {
|
|
5017
|
+
const chainChanged = previousJson !== void 0 && decodedInsertSignature(previousJson, target) !== decodedInsertSignature(sceneJson, target);
|
|
5018
|
+
const hasOverrides = hasInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
5019
|
+
applyFullStripJson(ctx.offlineEngine, target, sceneJson);
|
|
5020
|
+
if (!chainChanged && !hasOverrides) {
|
|
5021
|
+
return { insertBaseResets: [], constructionOverrides: [] };
|
|
5022
|
+
}
|
|
5023
|
+
const constructionOverrides = chainChanged ? [] : constructionInsertOverrides(ctx.offlineEngine, target, ctx.insertParamOverrides);
|
|
5024
|
+
ctx.clearInsertAutomationLanes(target);
|
|
5025
|
+
clearInsertParameterBases(ctx.offlineEngine, target);
|
|
5026
|
+
applyConstructionInsertOverrides(ctx.offlineEngine, constructionOverrides);
|
|
5027
|
+
clearInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
5028
|
+
return { insertBaseResets: [target], constructionOverrides };
|
|
3366
5029
|
}
|
|
3367
|
-
function
|
|
3368
|
-
|
|
3369
|
-
|
|
5030
|
+
function setBusStripJson(ctx, busId, sceneJson) {
|
|
5031
|
+
const target = { kind: "bus", busId };
|
|
5032
|
+
decodedInsertSignature(sceneJson, target);
|
|
5033
|
+
ctx.ensureBus(busId);
|
|
5034
|
+
const { insertBaseResets, constructionOverrides } = replaceStripScene(
|
|
5035
|
+
ctx,
|
|
5036
|
+
target,
|
|
5037
|
+
ctx.busStripJson.get(busId),
|
|
5038
|
+
sceneJson
|
|
3370
5039
|
);
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
5040
|
+
pruneStripSidechains(ctx, target, sceneJson);
|
|
5041
|
+
ctx.busStripJson.set(busId, sceneJson);
|
|
5042
|
+
ctx.syncMixer(insertBaseResets, constructionOverrides);
|
|
5043
|
+
}
|
|
5044
|
+
function setMasterStripJson(ctx, sceneJson) {
|
|
5045
|
+
const target = { kind: "master" };
|
|
5046
|
+
const { insertBaseResets, constructionOverrides } = replaceStripScene(
|
|
5047
|
+
ctx,
|
|
5048
|
+
target,
|
|
5049
|
+
ctx.getMasterStripJson(),
|
|
5050
|
+
sceneJson
|
|
5051
|
+
);
|
|
5052
|
+
pruneStripSidechains(ctx, target, sceneJson);
|
|
5053
|
+
ctx.cacheMasterStripJson(sceneJson);
|
|
5054
|
+
ctx.syncMixer(insertBaseResets, constructionOverrides);
|
|
3374
5055
|
}
|
|
3375
5056
|
|
|
3376
5057
|
// src/worklet/engine-node.ts
|
|
@@ -3387,15 +5068,17 @@ function isTrackMonitorMode(value) {
|
|
|
3387
5068
|
const mode = typeof value === "bigint" ? Number(value) : value;
|
|
3388
5069
|
return typeof mode === "number" && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
|
|
3389
5070
|
}
|
|
3390
|
-
function isTrackMonitorLaneIndex(value) {
|
|
3391
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3392
|
-
}
|
|
3393
5071
|
function isValidCommandRecord(command) {
|
|
3394
5072
|
const type = Number(command.type);
|
|
3395
|
-
if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */) {
|
|
5073
|
+
if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */ && type !== 28 /* ResetMasterLoudnessMeter */) {
|
|
3396
5074
|
return false;
|
|
3397
5075
|
}
|
|
3398
|
-
return (
|
|
5076
|
+
return (
|
|
5077
|
+
// The record writer refuses a targetId outside the slot by throwing, and
|
|
5078
|
+
// this path answers false instead, so it must reject the same domain here.
|
|
5079
|
+
(command.targetId === void 0 || isUint32Slot(command.targetId)) && (command.argFloat === void 0 || Number.isFinite(command.argFloat)) && isFiniteInteger(command.argInt) && isFiniteInteger(command.sampleTime) && // A track-monitor command cannot infer a lane: its target is required.
|
|
5080
|
+
(type !== 26 /* SetTrackMonitorMode */ || isUint32Slot(command.targetId) && isTrackMonitorMode(command.argInt))
|
|
5081
|
+
);
|
|
3399
5082
|
}
|
|
3400
5083
|
var AUDIO_WORKLET_RENDER_QUANTUM = 128;
|
|
3401
5084
|
function workletBlockSize(blockSize) {
|
|
@@ -3440,6 +5123,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3440
5123
|
this.resolveReady();
|
|
3441
5124
|
}
|
|
3442
5125
|
this.node.port.onmessage = (event) => {
|
|
5126
|
+
if (this.destroyed) {
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
3443
5129
|
const captureRequestId = engineCaptureResponseRequestId(event.data);
|
|
3444
5130
|
if (captureRequestId !== void 0) {
|
|
3445
5131
|
const pending = this.captureRequests.get(captureRequestId);
|
|
@@ -3469,6 +5155,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3469
5155
|
this.emitTelemetry(event.data);
|
|
3470
5156
|
} else if (isMeterSnapshot(event.data)) {
|
|
3471
5157
|
this.emitMeter(event.data);
|
|
5158
|
+
} else if (isScopeSnapshot(event.data)) {
|
|
5159
|
+
this.emitScope(event.data);
|
|
3472
5160
|
} else if (isExternalMidiBatchMessage(event.data)) {
|
|
3473
5161
|
this.emitMidiOut(event.data.events);
|
|
3474
5162
|
} else if (isClipPageRequestMessage(event.data)) {
|
|
@@ -3521,19 +5209,71 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3521
5209
|
"SharedArrayBuffer mode requested but SharedArrayBuffer/Atomics are unavailable."
|
|
3522
5210
|
);
|
|
3523
5211
|
}
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
const
|
|
3531
|
-
|
|
5212
|
+
const meterIntervalFrames = requireIntegerOption(
|
|
5213
|
+
options.meterIntervalFrames,
|
|
5214
|
+
2048,
|
|
5215
|
+
"meterIntervalFrames",
|
|
5216
|
+
0
|
|
5217
|
+
);
|
|
5218
|
+
const commandRingCapacity = requireIntegerOption(
|
|
5219
|
+
options.commandRingCapacity,
|
|
5220
|
+
128,
|
|
5221
|
+
"commandRingCapacity",
|
|
5222
|
+
1
|
|
5223
|
+
);
|
|
5224
|
+
if (commandRingCapacity > 65536) {
|
|
5225
|
+
throw new RangeError("commandRingCapacity must be at most 65536");
|
|
5226
|
+
}
|
|
5227
|
+
const telemetryRingCapacity = requireIntegerOption(
|
|
5228
|
+
options.telemetryRingCapacity,
|
|
5229
|
+
128,
|
|
5230
|
+
"telemetryRingCapacity",
|
|
5231
|
+
1
|
|
5232
|
+
);
|
|
5233
|
+
const meterRingCapacity = requireIntegerOption(
|
|
5234
|
+
options.meterRingCapacity,
|
|
5235
|
+
128,
|
|
5236
|
+
"meterRingCapacity",
|
|
5237
|
+
1
|
|
5238
|
+
);
|
|
5239
|
+
const scopeRingCapacity = requireIntegerOption(
|
|
5240
|
+
options.scopeRingCapacity,
|
|
5241
|
+
64,
|
|
5242
|
+
"scopeRingCapacity",
|
|
5243
|
+
1
|
|
5244
|
+
);
|
|
5245
|
+
const scopeBands = resolveScopeBandCount(options.scopeBands);
|
|
5246
|
+
const clipPageRequestRingCapacity = requireIntegerOption(
|
|
5247
|
+
options.clipPageRequestRingCapacity,
|
|
5248
|
+
128,
|
|
5249
|
+
"clipPageRequestRingCapacity",
|
|
5250
|
+
1
|
|
5251
|
+
);
|
|
5252
|
+
const externalMidiRingCapacity = requireIntegerOption(
|
|
5253
|
+
options.externalMidiRingCapacity,
|
|
5254
|
+
256,
|
|
5255
|
+
"externalMidiRingCapacity",
|
|
5256
|
+
1
|
|
5257
|
+
);
|
|
5258
|
+
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(commandRingCapacity) : void 0;
|
|
5259
|
+
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(telemetryRingCapacity) : void 0;
|
|
5260
|
+
const meterRing = mode === "sab" ? createSonareMeterRingBuffer(meterRingCapacity) : void 0;
|
|
5261
|
+
const scopeIntervalFrames = requireIntegerOption(
|
|
5262
|
+
options.scopeIntervalFrames,
|
|
5263
|
+
0,
|
|
5264
|
+
"scopeIntervalFrames",
|
|
5265
|
+
0
|
|
5266
|
+
);
|
|
5267
|
+
const scopeRing = mode === "sab" && scopeIntervalFrames > 0 ? createSonareScopeRingBuffer(scopeRingCapacity, scopeBands) : void 0;
|
|
5268
|
+
const clipPageRequestRing = mode === "sab" ? createSonareClipPageRequestRingBuffer(clipPageRequestRingCapacity) : void 0;
|
|
5269
|
+
const externalMidiRing = mode === "sab" ? createSonareExternalMidiRingBuffer(externalMidiRingCapacity) : void 0;
|
|
5270
|
+
const channelCount = requireChannelCount(options.channelCount, 2);
|
|
3532
5271
|
const cueOutput = options.cueOutput === true;
|
|
3533
5272
|
const processorOptions = {
|
|
3534
5273
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
3535
5274
|
blockSize,
|
|
3536
5275
|
channelCount,
|
|
5276
|
+
meterIntervalFrames,
|
|
3537
5277
|
cueOutput,
|
|
3538
5278
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
3539
5279
|
commandRingCapacity: commandRing?.capacity,
|
|
@@ -3543,8 +5283,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3543
5283
|
meterRingCapacity: meterRing?.capacity,
|
|
3544
5284
|
scopeSharedBuffer: scopeRing?.sharedBuffer,
|
|
3545
5285
|
scopeRingCapacity: scopeRing?.capacity,
|
|
3546
|
-
scopeBands: scopeRing?.bands,
|
|
3547
|
-
scopeIntervalFrames:
|
|
5286
|
+
scopeBands: scopeIntervalFrames > 0 ? scopeRing?.bands ?? scopeBands : void 0,
|
|
5287
|
+
scopeIntervalFrames: scopeIntervalFrames > 0 ? scopeIntervalFrames : void 0,
|
|
3548
5288
|
clipPageRequestSharedBuffer: clipPageRequestRing?.sharedBuffer,
|
|
3549
5289
|
clipPageRequestRingCapacity: clipPageRequestRing?.capacity,
|
|
3550
5290
|
externalMidiSharedBuffer: externalMidiRing?.sharedBuffer,
|
|
@@ -3593,6 +5333,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3593
5333
|
play(sampleTime = -1) {
|
|
3594
5334
|
return this.sendCommand({ type: 2 /* TransportPlay */, sampleTime });
|
|
3595
5335
|
}
|
|
5336
|
+
/** Queues a reset of the master's integrated-loudness accumulator. */
|
|
5337
|
+
resetMasterLoudnessMeter(sampleTime = -1) {
|
|
5338
|
+
return this.sendCommand({ type: 28 /* ResetMasterLoudnessMeter */, sampleTime });
|
|
5339
|
+
}
|
|
3596
5340
|
stop(sampleTime = -1) {
|
|
3597
5341
|
return this.sendCommand({ type: 3 /* TransportStop */, sampleTime });
|
|
3598
5342
|
}
|
|
@@ -3805,6 +5549,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3805
5549
|
this.scopeListeners.clear();
|
|
3806
5550
|
this.midiOutListeners.clear();
|
|
3807
5551
|
this.clipPageRequestListeners.clear();
|
|
5552
|
+
this.syncErrorListeners.clear();
|
|
5553
|
+
this.node.port.onmessage = null;
|
|
3808
5554
|
}
|
|
3809
5555
|
emitTelemetry(telemetry) {
|
|
3810
5556
|
for (const listener of this.telemetryListeners) {
|
|
@@ -3944,6 +5690,9 @@ function resolveBusInsertAutomationId(ctx, busId, insertIndex, paramName) {
|
|
|
3944
5690
|
ctx.ensureBus(busId);
|
|
3945
5691
|
return ctx.offlineEngine.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
3946
5692
|
}
|
|
5693
|
+
function resolveInstrumentAutomationId(ctx, destinationId, paramName) {
|
|
5694
|
+
return ctx.offlineEngine.resolveInstrumentAutomationId(destinationId, paramName);
|
|
5695
|
+
}
|
|
3947
5696
|
function automationLaneCount(ctx) {
|
|
3948
5697
|
return ctx.offlineEngine.automationLaneCount();
|
|
3949
5698
|
}
|
|
@@ -3968,14 +5717,39 @@ function clearParameters(ctx) {
|
|
|
3968
5717
|
function trackIdFor(ctx, target) {
|
|
3969
5718
|
return ctx.trackLaneIds[ctx.ensureTrackLane(target)];
|
|
3970
5719
|
}
|
|
5720
|
+
function mergeStripJson(ctx, target, update) {
|
|
5721
|
+
const scene = JSON.parse(ctx.readStripJson(target) ?? emptyStripJson(target));
|
|
5722
|
+
update(target.kind === "bus" ? scene.buses[0] : scene.strips[0]);
|
|
5723
|
+
ctx.writeStripJson(target, JSON.stringify(scene));
|
|
5724
|
+
}
|
|
5725
|
+
function mergeEqBand(ctx, target, bandIndex, bandJson) {
|
|
5726
|
+
mergeStripJson(ctx, target, (entry) => {
|
|
5727
|
+
const eq = entry.eq ?? { enabled: true, bands: [] };
|
|
5728
|
+
const bands = eq.bands ?? [];
|
|
5729
|
+
while (bands.length < bandIndex) {
|
|
5730
|
+
bands.push({});
|
|
5731
|
+
}
|
|
5732
|
+
bands[bandIndex] = JSON.parse(bandJson);
|
|
5733
|
+
entry.eq = { ...eq, bands };
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
function setInsertParamByName(ctx, target, insertIndex, paramName, value, applyNative, message) {
|
|
5737
|
+
applyNative();
|
|
5738
|
+
const override = { target, insertIndex, paramName, value };
|
|
5739
|
+
ctx.insertParamOverrides.set(insertParamOverrideKey(override), override);
|
|
5740
|
+
ctx.postSync(message);
|
|
5741
|
+
}
|
|
3971
5742
|
function setTrackStripJson(ctx, trackId, sceneJson, trackStripJson) {
|
|
3972
|
-
|
|
5743
|
+
const target = { kind: "track", trackId };
|
|
5744
|
+
const result = replaceStripScene(ctx, target, trackStripJson.get(trackId), sceneJson);
|
|
3973
5745
|
trackStripJson.set(trackId, sceneJson);
|
|
5746
|
+
return result;
|
|
3974
5747
|
}
|
|
3975
5748
|
function setTrackStripEqBand(ctx, target, bandIndex, band) {
|
|
3976
5749
|
const trackId = trackIdFor(ctx, target);
|
|
3977
5750
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3978
5751
|
ctx.offlineEngine.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
5752
|
+
mergeEqBand(ctx, { kind: "track", trackId }, bandIndex, bandJson);
|
|
3979
5753
|
ctx.postSync({ type: "syncTrackStripEqBand", trackId, bandIndex, bandJson });
|
|
3980
5754
|
}
|
|
3981
5755
|
function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -3991,39 +5765,68 @@ function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOn
|
|
|
3991
5765
|
}
|
|
3992
5766
|
function setTrackStripInsertParamByName(ctx, target, insertIndex, paramName, value) {
|
|
3993
5767
|
const trackId = trackIdFor(ctx, target);
|
|
3994
|
-
|
|
3995
|
-
|
|
5768
|
+
setInsertParamByName(
|
|
5769
|
+
ctx,
|
|
5770
|
+
{ kind: "track", trackId },
|
|
5771
|
+
insertIndex,
|
|
5772
|
+
paramName,
|
|
5773
|
+
value,
|
|
5774
|
+
() => ctx.offlineEngine.restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value),
|
|
5775
|
+
{ type: "syncTrackStripInsertParamByName", trackId, insertIndex, paramName, value }
|
|
5776
|
+
);
|
|
3996
5777
|
}
|
|
3997
5778
|
function setTrackStripPan(ctx, target, pan) {
|
|
3998
5779
|
const trackId = trackIdFor(ctx, target);
|
|
3999
5780
|
ctx.offlineEngine.setTrackStripPan(trackId, pan);
|
|
5781
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5782
|
+
entry.pan = pan;
|
|
5783
|
+
});
|
|
4000
5784
|
ctx.postSync({ type: "syncTrackStripPan", trackId, pan });
|
|
4001
5785
|
}
|
|
4002
5786
|
function setTrackStripPanLaw(ctx, target, panLaw) {
|
|
4003
5787
|
const trackId = trackIdFor(ctx, target);
|
|
4004
5788
|
const code = panLawCode(panLaw);
|
|
4005
5789
|
ctx.offlineEngine.setTrackStripPanLaw(trackId, code);
|
|
5790
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5791
|
+
entry.panLaw = code;
|
|
5792
|
+
});
|
|
4006
5793
|
ctx.postSync({ type: "syncTrackStripPanLaw", trackId, panLaw: code });
|
|
4007
5794
|
}
|
|
4008
5795
|
function setTrackStripPanMode(ctx, target, panMode) {
|
|
4009
5796
|
const trackId = trackIdFor(ctx, target);
|
|
4010
5797
|
const code = panModeCode(panMode);
|
|
4011
5798
|
ctx.offlineEngine.setTrackStripPanMode(trackId, code);
|
|
5799
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5800
|
+
entry.panMode = code;
|
|
5801
|
+
});
|
|
4012
5802
|
ctx.postSync({ type: "syncTrackStripPanMode", trackId, panMode: code });
|
|
4013
5803
|
}
|
|
4014
5804
|
function setTrackStripDualPan(ctx, target, leftPan, rightPan) {
|
|
4015
5805
|
const trackId = trackIdFor(ctx, target);
|
|
4016
5806
|
ctx.offlineEngine.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
5807
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5808
|
+
entry.dualPanLeft = leftPan;
|
|
5809
|
+
entry.dualPanRight = rightPan;
|
|
5810
|
+
});
|
|
4017
5811
|
ctx.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
|
|
4018
5812
|
}
|
|
4019
5813
|
function setTrackStripChannelDelaySamples(ctx, target, delaySamples) {
|
|
4020
5814
|
const trackId = trackIdFor(ctx, target);
|
|
4021
5815
|
ctx.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
5816
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5817
|
+
entry.channelDelaySamples = delaySamples;
|
|
5818
|
+
});
|
|
4022
5819
|
ctx.postSync({ type: "syncTrackStripChannelDelaySamples", trackId, delaySamples });
|
|
4023
5820
|
}
|
|
5821
|
+
function cacheMasterStripScalar(ctx, field, value) {
|
|
5822
|
+
mergeStripJson(ctx, { kind: "master" }, (entry) => {
|
|
5823
|
+
entry[field] = value;
|
|
5824
|
+
});
|
|
5825
|
+
}
|
|
4024
5826
|
function setMasterStripEqBand(ctx, bandIndex, band) {
|
|
4025
5827
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
4026
5828
|
ctx.offlineEngine.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
5829
|
+
mergeEqBand(ctx, { kind: "master" }, bandIndex, bandJson);
|
|
4027
5830
|
ctx.postSync({ type: "syncMasterStripEqBand", bandIndex, bandJson });
|
|
4028
5831
|
}
|
|
4029
5832
|
function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -4031,12 +5834,67 @@ function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass)
|
|
|
4031
5834
|
ctx.postSync({ type: "syncMasterStripInsertBypassed", insertIndex, bypassed, resetOnBypass });
|
|
4032
5835
|
}
|
|
4033
5836
|
function setMasterStripInsertParamByName(ctx, insertIndex, paramName, value) {
|
|
4034
|
-
|
|
4035
|
-
|
|
5837
|
+
setInsertParamByName(
|
|
5838
|
+
ctx,
|
|
5839
|
+
{ kind: "master" },
|
|
5840
|
+
insertIndex,
|
|
5841
|
+
paramName,
|
|
5842
|
+
value,
|
|
5843
|
+
() => ctx.offlineEngine.restoreMasterStripInsertParamByName(insertIndex, paramName, value),
|
|
5844
|
+
{ type: "syncMasterStripInsertParamByName", insertIndex, paramName, value }
|
|
5845
|
+
);
|
|
4036
5846
|
}
|
|
4037
5847
|
function setBusStripInsertParamByName(ctx, busId, insertIndex, paramName, value) {
|
|
4038
|
-
|
|
4039
|
-
|
|
5848
|
+
setInsertParamByName(
|
|
5849
|
+
ctx,
|
|
5850
|
+
{ kind: "bus", busId },
|
|
5851
|
+
insertIndex,
|
|
5852
|
+
paramName,
|
|
5853
|
+
value,
|
|
5854
|
+
() => ctx.offlineEngine.restoreBusStripInsertParamByName(busId, insertIndex, paramName, value),
|
|
5855
|
+
{ type: "syncBusStripInsertParamByName", busId, insertIndex, paramName, value }
|
|
5856
|
+
);
|
|
5857
|
+
}
|
|
5858
|
+
function setBusStripInsertBypassed(ctx, busId, insertIndex, bypassed, resetOnBypass) {
|
|
5859
|
+
ctx.offlineEngine.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
5860
|
+
ctx.postSync({ type: "syncBusStripInsertBypassed", busId, insertIndex, bypassed, resetOnBypass });
|
|
5861
|
+
}
|
|
5862
|
+
function setBusStripEqBand(ctx, busId, bandIndex, band) {
|
|
5863
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
5864
|
+
ctx.offlineEngine.setBusStripEqBandJson(busId, bandIndex, bandJson);
|
|
5865
|
+
mergeEqBand(ctx, { kind: "bus", busId }, bandIndex, bandJson);
|
|
5866
|
+
ctx.postSync({ type: "syncBusStripEqBand", busId, bandIndex, bandJson });
|
|
5867
|
+
}
|
|
5868
|
+
function setBusStripPan(ctx, busId, pan) {
|
|
5869
|
+
ctx.offlineEngine.setBusStripPan(busId, pan);
|
|
5870
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5871
|
+
entry.pan = pan;
|
|
5872
|
+
});
|
|
5873
|
+
ctx.postSync({ type: "syncBusStripPan", busId, pan });
|
|
5874
|
+
}
|
|
5875
|
+
function setBusStripPanLaw(ctx, busId, panLaw) {
|
|
5876
|
+
const code = panLawCode(panLaw);
|
|
5877
|
+
ctx.offlineEngine.setBusStripPanLaw(busId, code);
|
|
5878
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5879
|
+
entry.panLaw = code;
|
|
5880
|
+
});
|
|
5881
|
+
ctx.postSync({ type: "syncBusStripPanLaw", busId, panLaw: code });
|
|
5882
|
+
}
|
|
5883
|
+
function setBusStripPanMode(ctx, busId, panMode) {
|
|
5884
|
+
const code = panModeCode(panMode);
|
|
5885
|
+
ctx.offlineEngine.setBusStripPanMode(busId, code);
|
|
5886
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5887
|
+
entry.panMode = code;
|
|
5888
|
+
});
|
|
5889
|
+
ctx.postSync({ type: "syncBusStripPanMode", busId, panMode: code });
|
|
5890
|
+
}
|
|
5891
|
+
function setBusStripDualPan(ctx, busId, leftPan, rightPan) {
|
|
5892
|
+
ctx.offlineEngine.setBusStripDualPan(busId, leftPan, rightPan);
|
|
5893
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5894
|
+
entry.dualPanLeft = leftPan;
|
|
5895
|
+
entry.dualPanRight = rightPan;
|
|
5896
|
+
});
|
|
5897
|
+
ctx.postSync({ type: "syncBusStripDualPan", busId, leftPan, rightPan });
|
|
4040
5898
|
}
|
|
4041
5899
|
function pushMidiNoteOn(ctx, trackId, group, channel, note, velocity, renderFrame) {
|
|
4042
5900
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
@@ -4077,9 +5935,55 @@ function pushMidiCc(ctx, trackId, group, channel, controller, value, renderFrame
|
|
|
4077
5935
|
renderFrame
|
|
4078
5936
|
});
|
|
4079
5937
|
}
|
|
5938
|
+
function pushMidiPitchBend(ctx, trackId, group, channel, bend14, renderFrame) {
|
|
5939
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5940
|
+
ctx.offlineEngine.pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame);
|
|
5941
|
+
ctx.postSync({
|
|
5942
|
+
type: "syncMidiPitchBend",
|
|
5943
|
+
destinationId,
|
|
5944
|
+
group,
|
|
5945
|
+
channel,
|
|
5946
|
+
data0: bend14,
|
|
5947
|
+
data1: 0,
|
|
5948
|
+
renderFrame
|
|
5949
|
+
});
|
|
5950
|
+
}
|
|
5951
|
+
function pushMidiChannelPressure(ctx, trackId, group, channel, pressure, renderFrame) {
|
|
5952
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5953
|
+
ctx.offlineEngine.pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame);
|
|
5954
|
+
ctx.postSync({
|
|
5955
|
+
type: "syncMidiChannelPressure",
|
|
5956
|
+
destinationId,
|
|
5957
|
+
group,
|
|
5958
|
+
channel,
|
|
5959
|
+
data0: pressure,
|
|
5960
|
+
data1: 0,
|
|
5961
|
+
renderFrame
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
function pushMidiPolyPressure(ctx, trackId, group, channel, note, pressure, renderFrame) {
|
|
5965
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5966
|
+
ctx.offlineEngine.pushMidiPolyPressure(
|
|
5967
|
+
destinationId,
|
|
5968
|
+
group,
|
|
5969
|
+
channel,
|
|
5970
|
+
note,
|
|
5971
|
+
pressure,
|
|
5972
|
+
renderFrame
|
|
5973
|
+
);
|
|
5974
|
+
ctx.postSync({
|
|
5975
|
+
type: "syncMidiPolyPressure",
|
|
5976
|
+
destinationId,
|
|
5977
|
+
group,
|
|
5978
|
+
channel,
|
|
5979
|
+
data0: note,
|
|
5980
|
+
data1: pressure,
|
|
5981
|
+
renderFrame
|
|
5982
|
+
});
|
|
5983
|
+
}
|
|
4080
5984
|
function pushMidiUmp(ctx, trackId, word0, renderFrame) {
|
|
4081
5985
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
4082
|
-
ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
|
|
5986
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, [word0], renderFrame);
|
|
4083
5987
|
ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
|
|
4084
5988
|
}
|
|
4085
5989
|
function setBuiltinInstrument(ctx, trackId, config) {
|
|
@@ -4205,16 +6109,21 @@ function transferableAudioBuffers(channels) {
|
|
|
4205
6109
|
var SonareEngine = class _SonareEngine {
|
|
4206
6110
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
4207
6111
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
6112
|
+
this.insertAutomationIdsByTarget = /* @__PURE__ */ new Map();
|
|
4208
6113
|
this.clips = /* @__PURE__ */ new Map();
|
|
4209
6114
|
this.midiClips = /* @__PURE__ */ new Map();
|
|
4210
6115
|
this.markers = /* @__PURE__ */ new Map();
|
|
4211
6116
|
this.trackLaneIds = [];
|
|
4212
6117
|
this.trackSends = /* @__PURE__ */ new Map();
|
|
4213
6118
|
this.trackOutputBus = /* @__PURE__ */ new Map();
|
|
6119
|
+
this.trackSourceChannelLayout = /* @__PURE__ */ new Map();
|
|
4214
6120
|
this.laneSidechains = /* @__PURE__ */ new Map();
|
|
6121
|
+
this.busSidechains = /* @__PURE__ */ new Map();
|
|
6122
|
+
this.masterSidechains = /* @__PURE__ */ new Map();
|
|
4215
6123
|
this.buses = [];
|
|
4216
6124
|
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
4217
6125
|
this.busStripJson = /* @__PURE__ */ new Map();
|
|
6126
|
+
this.insertParamOverrides = /* @__PURE__ */ new Map();
|
|
4218
6127
|
this.tempoBpm = 120;
|
|
4219
6128
|
this.timeSignature = { numerator: 4, denominator: 4 };
|
|
4220
6129
|
this.tempoSegments = [{ startPpq: 0, bpm: 120 }];
|
|
@@ -4309,6 +6218,11 @@ var SonareEngine = class _SonareEngine {
|
|
|
4309
6218
|
setTimeSignatureSegments(segments) {
|
|
4310
6219
|
setTimeSignatureSegments(this.tempoContext, segments);
|
|
4311
6220
|
}
|
|
6221
|
+
/**
|
|
6222
|
+
* A loop wrap, seek or stop sends note-offs plus CC64=0, CC121, CC123 and a
|
|
6223
|
+
* centred pitch bend on every channel played since the last reset, so
|
|
6224
|
+
* controller values set before a loop region are not restored at the wrap.
|
|
6225
|
+
*/
|
|
4312
6226
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
4313
6227
|
return setLoop(this.tempoContext, startPpq, endPpq, enabled);
|
|
4314
6228
|
}
|
|
@@ -4367,18 +6281,32 @@ var SonareEngine = class _SonareEngine {
|
|
|
4367
6281
|
* mixer lane first (like automationParamId) so the offline engine resolves the
|
|
4368
6282
|
* same strip selector the realtime engine uses.
|
|
4369
6283
|
*
|
|
6284
|
+
* The returned id uses the track's current positional lane selector. When
|
|
6285
|
+
* setTrackLanes successfully changes lane order or membership, the engine
|
|
6286
|
+
* remaps already queued and published track automation by track id, but it
|
|
6287
|
+
* cannot update a numeric id retained by the caller. Re-resolve every track
|
|
6288
|
+
* insert id after such a topology change before passing it to
|
|
6289
|
+
* setAutomationLane. Use setTrackStripInsertParamByName when the operation
|
|
6290
|
+
* needs a stable track identity. Master and bus insert ids are separate and
|
|
6291
|
+
* are not invalidated by track-lane changes.
|
|
6292
|
+
*
|
|
4370
6293
|
* @param target Track id (declares a mixer lane on first use).
|
|
4371
6294
|
* @param insertIndex Index into the strip's combined insert sequence.
|
|
4372
6295
|
* @param paramName Processor JSON-key parameter name.
|
|
4373
6296
|
* @returns Reserved insert-automation id, or -1 when strip/insert/key unknown.
|
|
4374
6297
|
*/
|
|
4375
6298
|
resolveTrackInsertAutomationId(target, insertIndex, paramName) {
|
|
4376
|
-
|
|
6299
|
+
const id = resolveTrackInsertAutomationId(
|
|
4377
6300
|
this.parameterContext,
|
|
4378
6301
|
target,
|
|
4379
6302
|
insertIndex,
|
|
4380
6303
|
paramName
|
|
4381
6304
|
);
|
|
6305
|
+
if (id >= 0) {
|
|
6306
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
6307
|
+
this.rememberInsertAutomationId({ kind: "track", trackId }, id);
|
|
6308
|
+
}
|
|
6309
|
+
return id;
|
|
4382
6310
|
}
|
|
4383
6311
|
/**
|
|
4384
6312
|
* Resolves a master-strip insert parameter to its reserved insert-automation
|
|
@@ -4389,7 +6317,15 @@ var SonareEngine = class _SonareEngine {
|
|
|
4389
6317
|
* @returns Reserved insert-automation id, or -1 when insert/key unknown.
|
|
4390
6318
|
*/
|
|
4391
6319
|
resolveMasterInsertAutomationId(insertIndex, paramName) {
|
|
4392
|
-
|
|
6320
|
+
const id = resolveMasterInsertAutomationId(
|
|
6321
|
+
this.parameterContext,
|
|
6322
|
+
insertIndex,
|
|
6323
|
+
paramName
|
|
6324
|
+
);
|
|
6325
|
+
if (id >= 0) {
|
|
6326
|
+
this.rememberInsertAutomationId({ kind: "master" }, id);
|
|
6327
|
+
}
|
|
6328
|
+
return id;
|
|
4393
6329
|
}
|
|
4394
6330
|
/**
|
|
4395
6331
|
* Resolves a bus-strip insert parameter to its reserved insert-automation id.
|
|
@@ -4402,12 +6338,29 @@ var SonareEngine = class _SonareEngine {
|
|
|
4402
6338
|
* @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
|
|
4403
6339
|
*/
|
|
4404
6340
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
4405
|
-
|
|
6341
|
+
const id = resolveBusInsertAutomationId(
|
|
4406
6342
|
this.parameterContext,
|
|
4407
6343
|
busId,
|
|
4408
6344
|
insertIndex,
|
|
4409
6345
|
paramName
|
|
4410
6346
|
);
|
|
6347
|
+
if (id >= 0) {
|
|
6348
|
+
this.rememberInsertAutomationId({ kind: "bus", busId }, id);
|
|
6349
|
+
}
|
|
6350
|
+
return id;
|
|
6351
|
+
}
|
|
6352
|
+
/**
|
|
6353
|
+
* Resolves a hosted instrument's continuous parameter to its reserved
|
|
6354
|
+
* instrument-automation id, so an instrument parameter follows a breakpoint
|
|
6355
|
+
* lane at audio-block precision like a strip insert. Bind the instrument
|
|
6356
|
+
* (`setSynthInstrument`) before resolving.
|
|
6357
|
+
*
|
|
6358
|
+
* @param destinationId MIDI destination the instrument is bound to.
|
|
6359
|
+
* @param paramName Instrument JSON-key parameter name (e.g. `cutoffHz`).
|
|
6360
|
+
* @returns Reserved instrument-automation id, or -1 when destination/key unknown.
|
|
6361
|
+
*/
|
|
6362
|
+
resolveInstrumentAutomationId(destinationId, paramName) {
|
|
6363
|
+
return resolveInstrumentAutomationId(this.parameterContext, destinationId, paramName);
|
|
4411
6364
|
}
|
|
4412
6365
|
/**
|
|
4413
6366
|
* Returns the number of automation lanes installed on the engine, including
|
|
@@ -4437,19 +6390,36 @@ var SonareEngine = class _SonareEngine {
|
|
|
4437
6390
|
return setTrackMonitorMode(this.parameterContext, target, mode, renderFrame);
|
|
4438
6391
|
}
|
|
4439
6392
|
setStripGain(target, db) {
|
|
4440
|
-
|
|
6393
|
+
const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
6394
|
+
if (sent && target === "master") {
|
|
6395
|
+
cacheMasterStripScalar(this.stripContext, "faderDb", db);
|
|
6396
|
+
}
|
|
6397
|
+
return sent;
|
|
4441
6398
|
}
|
|
4442
6399
|
setStripPan(target, pan) {
|
|
4443
|
-
|
|
6400
|
+
const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
|
|
6401
|
+
if (sent && target === "master") {
|
|
6402
|
+
cacheMasterStripScalar(this.stripContext, "pan", pan);
|
|
6403
|
+
}
|
|
6404
|
+
return sent;
|
|
6405
|
+
}
|
|
6406
|
+
/** Resets the master's integrated loudness accumulator in both engine mirrors. */
|
|
6407
|
+
resetMasterLoudnessMeter(renderFrame = -1) {
|
|
6408
|
+
this.offlineEngine.resetMasterLoudnessMeter(renderFrame);
|
|
6409
|
+
return this.sendMirroredCommand({
|
|
6410
|
+
type: 28 /* ResetMasterLoudnessMeter */,
|
|
6411
|
+
sampleTime: renderFrame
|
|
6412
|
+
});
|
|
4444
6413
|
}
|
|
4445
6414
|
/**
|
|
4446
6415
|
* Declares the mixer track lanes in an explicit order.
|
|
4447
6416
|
*
|
|
4448
|
-
*
|
|
4449
|
-
*
|
|
4450
|
-
*
|
|
4451
|
-
*
|
|
4452
|
-
* track's send list; entries without `sends` leave
|
|
6417
|
+
* A successful call may reorder existing track ids, remove track ids, or add
|
|
6418
|
+
* new ones. Existing queued and published track automation follows the track
|
|
6419
|
+
* id, while numeric track-insert automation ids retained by the caller do
|
|
6420
|
+
* not; re-resolve those ids after every topology change. Entries carrying
|
|
6421
|
+
* `sends` replace that track's send list; entries without `sends` leave
|
|
6422
|
+
* existing sends untouched.
|
|
4453
6423
|
*
|
|
4454
6424
|
* @param lanes Track ids or lane descriptors in the desired lane order.
|
|
4455
6425
|
*/
|
|
@@ -4477,14 +6447,35 @@ var SonareEngine = class _SonareEngine {
|
|
|
4477
6447
|
setTrackBuses(buses) {
|
|
4478
6448
|
setTrackBuses(this.mixerContext, buses);
|
|
4479
6449
|
}
|
|
6450
|
+
/**
|
|
6451
|
+
* Keys one insert of a bus strip from a track lane or another bus
|
|
6452
|
+
* (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
|
|
6453
|
+
*/
|
|
6454
|
+
setBusSidechain(busId, insertIndex, sourceKind, sourceId) {
|
|
6455
|
+
setBusSidechain(this.mixerContext, busId, insertIndex, sourceKind, sourceId);
|
|
6456
|
+
}
|
|
6457
|
+
/**
|
|
6458
|
+
* Keys one insert of the master strip from a track lane or a bus. Same
|
|
6459
|
+
* source rules as {@link setBusSidechain}.
|
|
6460
|
+
*/
|
|
6461
|
+
setMasterSidechain(insertIndex, sourceKind, sourceId) {
|
|
6462
|
+
setMasterSidechain(this.mixerContext, insertIndex, sourceKind, sourceId);
|
|
6463
|
+
}
|
|
4480
6464
|
setBusGain(busId, db) {
|
|
4481
6465
|
return setBusGain(this.mixerContext, busId, db);
|
|
4482
6466
|
}
|
|
4483
6467
|
setTrackStripJson(target, sceneJson) {
|
|
6468
|
+
decodedInsertSignature(sceneJson, { kind: "track", trackId: 0 });
|
|
4484
6469
|
const laneIndex = this.ensureTrackLane(target);
|
|
4485
6470
|
const trackId = this.trackLaneIds[laneIndex];
|
|
4486
|
-
|
|
4487
|
-
|
|
6471
|
+
const { insertBaseResets, constructionOverrides } = setTrackStripJson(
|
|
6472
|
+
this.stripContext,
|
|
6473
|
+
trackId,
|
|
6474
|
+
sceneJson,
|
|
6475
|
+
this.trackStripJson
|
|
6476
|
+
);
|
|
6477
|
+
pruneStripSidechains(this.mixerContext, { kind: "track", trackId }, sceneJson);
|
|
6478
|
+
this.syncMixer(insertBaseResets, constructionOverrides);
|
|
4488
6479
|
}
|
|
4489
6480
|
setTrackStripEqBand(target, bandIndex, band) {
|
|
4490
6481
|
setTrackStripEqBand(this.stripContext, target, bandIndex, band);
|
|
@@ -4541,9 +6532,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
4541
6532
|
setBusStripJson(this.mixerContext, busId, sceneJson);
|
|
4542
6533
|
}
|
|
4543
6534
|
setMasterStripJson(sceneJson) {
|
|
4544
|
-
this.
|
|
4545
|
-
this.masterStripJson = sceneJson;
|
|
4546
|
-
this.syncMixer();
|
|
6535
|
+
setMasterStripJson(this.mixerContext, sceneJson);
|
|
4547
6536
|
}
|
|
4548
6537
|
setMasterStripEqBand(bandIndex, band) {
|
|
4549
6538
|
setMasterStripEqBand(this.stripContext, bandIndex, band);
|
|
@@ -4558,6 +6547,47 @@ var SonareEngine = class _SonareEngine {
|
|
|
4558
6547
|
this.ensureBus(busId);
|
|
4559
6548
|
setBusStripInsertParamByName(this.stripContext, busId, insertIndex, paramName, value);
|
|
4560
6549
|
}
|
|
6550
|
+
/**
|
|
6551
|
+
* Bus-strip counterpart of {@link setStripInsertBypassed}. A bus is addressed
|
|
6552
|
+
* by its numeric id, which the `target` of the track/master form cannot
|
|
6553
|
+
* express, so it is its own method exactly like
|
|
6554
|
+
* {@link setBusStripInsertParamByName}. Bypassing keeps the insert's internal
|
|
6555
|
+
* state (reverb tail, compressor envelope) unless `resetOnBypass` is set;
|
|
6556
|
+
* re-posting the bus scene JSON, the only workaround before this existed,
|
|
6557
|
+
* rebuilds the chain and loses it.
|
|
6558
|
+
*/
|
|
6559
|
+
setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass = false) {
|
|
6560
|
+
this.ensureBus(busId);
|
|
6561
|
+
setBusStripInsertBypassed(
|
|
6562
|
+
this.stripContext,
|
|
6563
|
+
busId,
|
|
6564
|
+
insertIndex,
|
|
6565
|
+
bypassed,
|
|
6566
|
+
resetOnBypass
|
|
6567
|
+
);
|
|
6568
|
+
}
|
|
6569
|
+
/** Bus-strip counterpart of {@link setTrackStripEqBand}. */
|
|
6570
|
+
setBusStripEqBand(busId, bandIndex, band) {
|
|
6571
|
+
this.ensureBus(busId);
|
|
6572
|
+
setBusStripEqBand(this.stripContext, busId, bandIndex, band);
|
|
6573
|
+
}
|
|
6574
|
+
/** Bus-strip counterpart of {@link setTrackStripPan}; refused on a surround bus. */
|
|
6575
|
+
setBusStripPan(busId, pan) {
|
|
6576
|
+
this.ensureBus(busId);
|
|
6577
|
+
setBusStripPan(this.stripContext, busId, pan);
|
|
6578
|
+
}
|
|
6579
|
+
setBusStripPanLaw(busId, panLaw) {
|
|
6580
|
+
this.ensureBus(busId);
|
|
6581
|
+
setBusStripPanLaw(this.stripContext, busId, panLaw);
|
|
6582
|
+
}
|
|
6583
|
+
setBusStripPanMode(busId, panMode) {
|
|
6584
|
+
this.ensureBus(busId);
|
|
6585
|
+
setBusStripPanMode(this.stripContext, busId, panMode);
|
|
6586
|
+
}
|
|
6587
|
+
setBusStripDualPan(busId, leftPan, rightPan) {
|
|
6588
|
+
this.ensureBus(busId);
|
|
6589
|
+
setBusStripDualPan(this.stripContext, busId, leftPan, rightPan);
|
|
6590
|
+
}
|
|
4561
6591
|
setStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
4562
6592
|
if (target === "master") {
|
|
4563
6593
|
this.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
@@ -4638,6 +6668,49 @@ var SonareEngine = class _SonareEngine {
|
|
|
4638
6668
|
}
|
|
4639
6669
|
return { binding, provider: binding.provider };
|
|
4640
6670
|
}
|
|
6671
|
+
/**
|
|
6672
|
+
* Sets the clip-page look-ahead window in timeline frames on both the worklet
|
|
6673
|
+
* engine and this thread's offline engine.
|
|
6674
|
+
*
|
|
6675
|
+
* The audio thread reports the pages it is *about to* read that are not
|
|
6676
|
+
* resident yet, so the sliding-window streamer can service them before the
|
|
6677
|
+
* playhead reaches them. Without look-ahead a page miss is only reported
|
|
6678
|
+
* after the read already produced silence, which costs one block of silence
|
|
6679
|
+
* at every page boundary that was not primed.
|
|
6680
|
+
*
|
|
6681
|
+
* Defaults to half a second at the engine's sample rate. `0` disables it.
|
|
6682
|
+
* Safe to call during playback.
|
|
6683
|
+
*/
|
|
6684
|
+
setClipPagePrefetchFrames(frames) {
|
|
6685
|
+
if (this.destroyed) {
|
|
6686
|
+
throw new Error("SonareEngine is destroyed.");
|
|
6687
|
+
}
|
|
6688
|
+
if (!Number.isFinite(frames) || frames < 0) {
|
|
6689
|
+
throw new Error("clip page prefetch frames must be a finite value >= 0.");
|
|
6690
|
+
}
|
|
6691
|
+
this.offlineEngine.setClipPagePrefetchFrames(frames);
|
|
6692
|
+
this.postSync({ type: "syncClipPagePrefetchFrames", frames });
|
|
6693
|
+
}
|
|
6694
|
+
/**
|
|
6695
|
+
* Sets the number of concurrent time-stretch voices on both this thread's
|
|
6696
|
+
* offline engine and the live worklet engine. `voices` must be an integer
|
|
6697
|
+
* in `[0, 64]`; a non-integer, negative, or larger value throws and leaves
|
|
6698
|
+
* the capacity unchanged on both engines. Default is 8. Capacity 0 disables
|
|
6699
|
+
* time-stretch, so every warped clip plays resampled instead. A change
|
|
6700
|
+
* applied while the engine is running restarts the splice state of any clip
|
|
6701
|
+
* stretching through a voice at that moment.
|
|
6702
|
+
*/
|
|
6703
|
+
setWarpVoiceCapacity(voices) {
|
|
6704
|
+
if (this.destroyed) {
|
|
6705
|
+
throw new Error("SonareEngine is destroyed.");
|
|
6706
|
+
}
|
|
6707
|
+
this.offlineEngine.setWarpVoiceCapacity(voices);
|
|
6708
|
+
this.postSync({ type: "syncWarpVoiceCapacity", voices });
|
|
6709
|
+
}
|
|
6710
|
+
/** Reads the current time-stretch voice capacity (default 8). */
|
|
6711
|
+
warpVoiceCapacity() {
|
|
6712
|
+
return this.offlineEngine.warpVoiceCapacity();
|
|
6713
|
+
}
|
|
4641
6714
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
4642
6715
|
return addClip(this.clipContext, trackId, buffer, startPpq, opts);
|
|
4643
6716
|
}
|
|
@@ -4662,7 +6735,10 @@ var SonareEngine = class _SonareEngine {
|
|
|
4662
6735
|
/**
|
|
4663
6736
|
* Route a track's MIDI to the external output (drained via {@link onMidiOut})
|
|
4664
6737
|
* instead of an internal instrument, so the track plays an external device.
|
|
4665
|
-
* Pass `external=false` to restore internal-synth playback.
|
|
6738
|
+
* Pass `external=false` to restore internal-synth playback. The change takes
|
|
6739
|
+
* effect at the next processed block; switching a destination's route first
|
|
6740
|
+
* releases its notes and resets its controllers through the old route, and
|
|
6741
|
+
* drops its pending MIDI-FX events.
|
|
4666
6742
|
*/
|
|
4667
6743
|
setMidiDestinationExternal(trackId, external) {
|
|
4668
6744
|
setMidiDestinationExternal(this.stripContext, trackId, external);
|
|
@@ -4699,6 +6775,30 @@ var SonareEngine = class _SonareEngine {
|
|
|
4699
6775
|
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
4700
6776
|
pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
|
|
4701
6777
|
}
|
|
6778
|
+
pushMidiPitchBend(trackId, group, channel, bend14, renderFrame = -1) {
|
|
6779
|
+
pushMidiPitchBend(this.stripContext, trackId, group, channel, bend14, renderFrame);
|
|
6780
|
+
}
|
|
6781
|
+
pushMidiChannelPressure(trackId, group, channel, pressure, renderFrame = -1) {
|
|
6782
|
+
pushMidiChannelPressure(
|
|
6783
|
+
this.stripContext,
|
|
6784
|
+
trackId,
|
|
6785
|
+
group,
|
|
6786
|
+
channel,
|
|
6787
|
+
pressure,
|
|
6788
|
+
renderFrame
|
|
6789
|
+
);
|
|
6790
|
+
}
|
|
6791
|
+
pushMidiPolyPressure(trackId, group, channel, note, pressure, renderFrame = -1) {
|
|
6792
|
+
pushMidiPolyPressure(
|
|
6793
|
+
this.stripContext,
|
|
6794
|
+
trackId,
|
|
6795
|
+
group,
|
|
6796
|
+
channel,
|
|
6797
|
+
note,
|
|
6798
|
+
pressure,
|
|
6799
|
+
renderFrame
|
|
6800
|
+
);
|
|
6801
|
+
}
|
|
4702
6802
|
pushMidiUmp(trackId, word0, renderFrame = -1) {
|
|
4703
6803
|
pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
|
|
4704
6804
|
}
|
|
@@ -4752,6 +6852,39 @@ var SonareEngine = class _SonareEngine {
|
|
|
4752
6852
|
portTimeSamples
|
|
4753
6853
|
});
|
|
4754
6854
|
}
|
|
6855
|
+
pushMidiInputPitchBend(group, channel, bend14, portTimeSamples = 0) {
|
|
6856
|
+
this.offlineEngine.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
|
|
6857
|
+
this.postSync({
|
|
6858
|
+
type: "syncMidiInputPitchBend",
|
|
6859
|
+
group,
|
|
6860
|
+
channel,
|
|
6861
|
+
data0: bend14,
|
|
6862
|
+
data1: 0,
|
|
6863
|
+
portTimeSamples
|
|
6864
|
+
});
|
|
6865
|
+
}
|
|
6866
|
+
pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples = 0) {
|
|
6867
|
+
this.offlineEngine.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
|
|
6868
|
+
this.postSync({
|
|
6869
|
+
type: "syncMidiInputChannelPressure",
|
|
6870
|
+
group,
|
|
6871
|
+
channel,
|
|
6872
|
+
data0: pressure,
|
|
6873
|
+
data1: 0,
|
|
6874
|
+
portTimeSamples
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples = 0) {
|
|
6878
|
+
this.offlineEngine.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
|
|
6879
|
+
this.postSync({
|
|
6880
|
+
type: "syncMidiInputPolyPressure",
|
|
6881
|
+
group,
|
|
6882
|
+
channel,
|
|
6883
|
+
data0: note,
|
|
6884
|
+
data1: pressure,
|
|
6885
|
+
portTimeSamples
|
|
6886
|
+
});
|
|
6887
|
+
}
|
|
4755
6888
|
pushMidiPanic(renderFrame = -1) {
|
|
4756
6889
|
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
4757
6890
|
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
@@ -4949,8 +7082,32 @@ var SonareEngine = class _SonareEngine {
|
|
|
4949
7082
|
mixerLanes() {
|
|
4950
7083
|
return mixerLanes(this.mixerContext);
|
|
4951
7084
|
}
|
|
4952
|
-
syncMixer() {
|
|
4953
|
-
syncMixer(this.mixerContext);
|
|
7085
|
+
syncMixer(insertBaseResets = [], oneShotInsertParamOverrides = []) {
|
|
7086
|
+
syncMixer(this.mixerContext, false, insertBaseResets, oneShotInsertParamOverrides);
|
|
7087
|
+
}
|
|
7088
|
+
insertAutomationTargetKey(target) {
|
|
7089
|
+
return target.kind === "track" ? `track:${target.trackId}` : target.kind === "bus" ? `bus:${target.busId}` : "master";
|
|
7090
|
+
}
|
|
7091
|
+
rememberInsertAutomationId(target, id) {
|
|
7092
|
+
const key = this.insertAutomationTargetKey(target);
|
|
7093
|
+
const ids = this.insertAutomationIdsByTarget.get(key) ?? /* @__PURE__ */ new Set();
|
|
7094
|
+
ids.add(id);
|
|
7095
|
+
this.insertAutomationIdsByTarget.set(key, ids);
|
|
7096
|
+
}
|
|
7097
|
+
clearInsertAutomationLanes(target, forgetResolvedIds = false) {
|
|
7098
|
+
const key = this.insertAutomationTargetKey(target);
|
|
7099
|
+
const ids = this.insertAutomationIdsByTarget.get(key);
|
|
7100
|
+
if (!ids) {
|
|
7101
|
+
return;
|
|
7102
|
+
}
|
|
7103
|
+
for (const id of ids) {
|
|
7104
|
+
if (this.automationLanes.has(id)) {
|
|
7105
|
+
this.setAutomationLane(id, []);
|
|
7106
|
+
}
|
|
7107
|
+
}
|
|
7108
|
+
if (forgetResolvedIds) {
|
|
7109
|
+
this.insertAutomationIdsByTarget.delete(key);
|
|
7110
|
+
}
|
|
4954
7111
|
}
|
|
4955
7112
|
postInstrumentSync(message) {
|
|
4956
7113
|
if (this.destroyed) {
|
|
@@ -4978,21 +7135,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
4978
7135
|
if (this.destroyed) {
|
|
4979
7136
|
return;
|
|
4980
7137
|
}
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
this.realtimeNode.node.port.postMessage(message);
|
|
4986
|
-
}
|
|
4987
|
-
} finally {
|
|
4988
|
-
this.flushOfflineMirror();
|
|
7138
|
+
if (transfer && transfer.length > 0) {
|
|
7139
|
+
this.realtimeNode.node.port.postMessage(message, transfer);
|
|
7140
|
+
} else {
|
|
7141
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
4989
7142
|
}
|
|
4990
7143
|
}
|
|
4991
|
-
// The
|
|
4992
|
-
//
|
|
4993
|
-
// call this from the worklet/audio path: it is intentionally control-only.
|
|
7144
|
+
// The control-only mirror applies due commands immediately while preserving
|
|
7145
|
+
// scheduled ones for its next render block.
|
|
4994
7146
|
flushOfflineMirror() {
|
|
4995
|
-
this.offlineEngine.
|
|
7147
|
+
this.offlineEngine.applyCommandsDueNowPreservingFuture();
|
|
4996
7148
|
}
|
|
4997
7149
|
sendMirroredCommand(command) {
|
|
4998
7150
|
const accepted = this.realtimeNode.sendCommand(command);
|
|
@@ -5009,17 +7161,26 @@ var SonareEngine = class _SonareEngine {
|
|
|
5009
7161
|
trackLaneIds: this.trackLaneIds,
|
|
5010
7162
|
trackSends: this.trackSends,
|
|
5011
7163
|
trackOutputBus: this.trackOutputBus,
|
|
7164
|
+
trackSourceChannelLayout: this.trackSourceChannelLayout,
|
|
5012
7165
|
laneSidechains: this.laneSidechains,
|
|
7166
|
+
busSidechains: this.busSidechains,
|
|
7167
|
+
masterSidechains: this.masterSidechains,
|
|
5013
7168
|
buses: this.buses,
|
|
5014
7169
|
trackStripJson: this.trackStripJson,
|
|
5015
7170
|
busStripJson: this.busStripJson,
|
|
7171
|
+
insertParamOverrides: this.insertParamOverrides,
|
|
7172
|
+
flushOfflineMirror: () => this.flushOfflineMirror(),
|
|
5016
7173
|
postSync: (message) => this.postSync(message),
|
|
5017
7174
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
5018
7175
|
ensureBus: (busId) => this.ensureBus(busId),
|
|
5019
7176
|
mixerLanes: () => this.mixerLanes(),
|
|
5020
|
-
syncMixer: () => this.syncMixer(),
|
|
7177
|
+
syncMixer: (insertBaseResets, oneShotInsertParamOverrides) => this.syncMixer(insertBaseResets, oneShotInsertParamOverrides),
|
|
7178
|
+
clearInsertAutomationLanes: (target, forgetResolvedIds) => this.clearInsertAutomationLanes(target, forgetResolvedIds),
|
|
5021
7179
|
sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
|
|
5022
|
-
getMasterStripJson: () => this.masterStripJson
|
|
7180
|
+
getMasterStripJson: () => this.masterStripJson,
|
|
7181
|
+
cacheMasterStripJson: (sceneJson) => {
|
|
7182
|
+
this.masterStripJson = sceneJson;
|
|
7183
|
+
}
|
|
5023
7184
|
};
|
|
5024
7185
|
}
|
|
5025
7186
|
// Collaborator surface handed to the strip/pan/EQ/insert/MIDI free functions
|
|
@@ -5032,7 +7193,11 @@ var SonareEngine = class _SonareEngine {
|
|
|
5032
7193
|
postSync: (message) => this.postSync(message),
|
|
5033
7194
|
postInstrumentSync: (message) => this.postInstrumentSync(message),
|
|
5034
7195
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
5035
|
-
resolveTargetId: (target) => this.resolveTargetId(target)
|
|
7196
|
+
resolveTargetId: (target) => this.resolveTargetId(target),
|
|
7197
|
+
readStripJson: (target) => cachedStripJson(this.mixerContext, target),
|
|
7198
|
+
writeStripJson: (target, sceneJson) => cacheStripJson(this.mixerContext, target, sceneJson),
|
|
7199
|
+
insertParamOverrides: this.insertParamOverrides,
|
|
7200
|
+
clearInsertAutomationLanes: (target) => this.clearInsertAutomationLanes(target)
|
|
5036
7201
|
};
|
|
5037
7202
|
}
|
|
5038
7203
|
// Collaborator surface handed to the automation-lane free functions so they
|
|
@@ -5172,33 +7337,39 @@ var SonareEngine = class _SonareEngine {
|
|
|
5172
7337
|
return resolveTargetId(target);
|
|
5173
7338
|
}
|
|
5174
7339
|
ensureTrackLane(target) {
|
|
5175
|
-
|
|
5176
|
-
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
5177
|
-
throw new Error(`Invalid track id for mixer lane: ${String(target)}`);
|
|
5178
|
-
}
|
|
5179
|
-
const existing = this.trackLaneIds.indexOf(trackId);
|
|
5180
|
-
if (existing >= 0) {
|
|
5181
|
-
return existing;
|
|
5182
|
-
}
|
|
5183
|
-
this.trackLaneIds.push(trackId);
|
|
5184
|
-
this.syncMixer();
|
|
5185
|
-
return this.trackLaneIds.length - 1;
|
|
7340
|
+
return ensureTrackLane(this.mixerContext, target);
|
|
5186
7341
|
}
|
|
5187
7342
|
ensureBus(busId) {
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
throw new Error(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
7343
|
+
if (!Number.isInteger(busId) || busId <= 0) {
|
|
7344
|
+
throw new RangeError(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
5191
7345
|
}
|
|
5192
|
-
const existing = this.buses.findIndex((bus) => bus.busId ===
|
|
7346
|
+
const existing = this.buses.findIndex((bus) => bus.busId === busId);
|
|
5193
7347
|
if (existing >= 0) {
|
|
5194
7348
|
return existing;
|
|
5195
7349
|
}
|
|
5196
|
-
this.buses.push({ busId
|
|
7350
|
+
this.buses.push({ busId });
|
|
5197
7351
|
this.syncMixer();
|
|
5198
7352
|
return this.buses.length - 1;
|
|
5199
7353
|
}
|
|
5200
7354
|
};
|
|
5201
7355
|
|
|
7356
|
+
// src/worklet/audio_types.ts
|
|
7357
|
+
function copyPlanesToOutput(output, planes, frames) {
|
|
7358
|
+
const monoFanOut = planes.length === 1;
|
|
7359
|
+
for (let ch = 0; ch < output.length; ch++) {
|
|
7360
|
+
const target = output[ch];
|
|
7361
|
+
const source = monoFanOut ? planes[0] : planes[ch];
|
|
7362
|
+
let copied = 0;
|
|
7363
|
+
if (source) {
|
|
7364
|
+
copied = Math.min(target.length, frames, source.length);
|
|
7365
|
+
target.set(source.subarray(0, copied));
|
|
7366
|
+
}
|
|
7367
|
+
if (copied < target.length) {
|
|
7368
|
+
target.fill(0, copied);
|
|
7369
|
+
}
|
|
7370
|
+
}
|
|
7371
|
+
}
|
|
7372
|
+
|
|
5202
7373
|
// src/worklet/messages.ts
|
|
5203
7374
|
var DEFAULT_METRONOME_CONFIG = {
|
|
5204
7375
|
beatGain: 0.35,
|
|
@@ -5214,26 +7385,12 @@ function resolveMetronomeConfig(config) {
|
|
|
5214
7385
|
clickSeconds: config.clickSeconds ?? DEFAULT_METRONOME_CONFIG.clickSeconds
|
|
5215
7386
|
};
|
|
5216
7387
|
if (!Number.isFinite(resolved.beatGain) || resolved.beatGain < 0 || !Number.isFinite(resolved.accentGain) || resolved.accentGain < 0 || !Number.isInteger(resolved.clickSamples) || resolved.clickSamples < 0 || !Number.isFinite(resolved.clickSeconds) || resolved.clickSeconds < 0 || resolved.clickSamples > 384e3 || config.clickSeconds !== void 0 && (!Number.isFinite(config.clickSeconds) || config.clickSeconds < 0 || config.clickSeconds > 1)) {
|
|
5217
|
-
throw new RangeError("invalid metronome gains or click length");
|
|
5218
|
-
}
|
|
5219
|
-
return resolved;
|
|
5220
|
-
}
|
|
5221
|
-
|
|
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
|
-
}
|
|
7388
|
+
throw new RangeError("invalid metronome gains or click length");
|
|
5235
7389
|
}
|
|
7390
|
+
return resolved;
|
|
5236
7391
|
}
|
|
7392
|
+
|
|
7393
|
+
// src/worklet/engine-processor.ts
|
|
5237
7394
|
function captureTransferList(channels) {
|
|
5238
7395
|
const transfers = [];
|
|
5239
7396
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5273,9 +7430,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5273
7430
|
this.clipPageRequestOverflowReported = 0;
|
|
5274
7431
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
5275
7432
|
this.blockSize = options.blockSize ?? 128;
|
|
5276
|
-
this.channelCount =
|
|
7433
|
+
this.channelCount = requireChannelCount(options.channelCount, 2);
|
|
5277
7434
|
this.transport = transport;
|
|
5278
|
-
this.meterIntervalFrames =
|
|
7435
|
+
this.meterIntervalFrames = requireIntegerOption(
|
|
7436
|
+
options.meterIntervalFrames,
|
|
7437
|
+
2048,
|
|
7438
|
+
"meterIntervalFrames",
|
|
7439
|
+
0
|
|
7440
|
+
);
|
|
5279
7441
|
this.commandRing = options.commandSharedBuffer ? this.commandRingFromSharedBuffer(options.commandSharedBuffer, options.commandRingCapacity) : void 0;
|
|
5280
7442
|
this.telemetryRing = options.telemetrySharedBuffer ? this.telemetryRingFromSharedBuffer(
|
|
5281
7443
|
options.telemetrySharedBuffer,
|
|
@@ -5287,6 +7449,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5287
7449
|
options.scopeRingCapacity,
|
|
5288
7450
|
options.scopeBands
|
|
5289
7451
|
) : void 0;
|
|
7452
|
+
this.scopeIntervalFrames = requireIntegerOption(
|
|
7453
|
+
options.scopeIntervalFrames,
|
|
7454
|
+
this.scopeRing ? this.blockSize : 0,
|
|
7455
|
+
"scopeIntervalFrames",
|
|
7456
|
+
0
|
|
7457
|
+
);
|
|
7458
|
+
this.scopeBands = resolveScopeBandCount(options.scopeBands);
|
|
7459
|
+
this.scopeEnabled = this.scopeIntervalFrames > 0 && (this.scopeRing !== void 0 || typeof this.transport?.postMessage === "function");
|
|
5290
7460
|
this.clipPageRequestRing = options.clipPageRequestSharedBuffer ? clipPageRequestRingFromSharedBuffer(
|
|
5291
7461
|
options.clipPageRequestSharedBuffer,
|
|
5292
7462
|
options.clipPageRequestRingCapacity
|
|
@@ -5295,10 +7465,13 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5295
7465
|
options.externalMidiSharedBuffer,
|
|
5296
7466
|
options.externalMidiRingCapacity
|
|
5297
7467
|
) : void 0;
|
|
7468
|
+
if (this.commandRing && this.commandRing.capacity > 65536) {
|
|
7469
|
+
throw new RangeError("commandRingCapacity must be at most 65536");
|
|
7470
|
+
}
|
|
5298
7471
|
this.engine = new RealtimeEngine(
|
|
5299
7472
|
this.sampleRate,
|
|
5300
7473
|
this.blockSize,
|
|
5301
|
-
1024,
|
|
7474
|
+
Math.max(1024, this.commandRing?.capacity ?? 0),
|
|
5302
7475
|
1024,
|
|
5303
7476
|
this.channelCount
|
|
5304
7477
|
);
|
|
@@ -5315,9 +7488,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5315
7488
|
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
5316
7489
|
}
|
|
5317
7490
|
}
|
|
5318
|
-
if (this.
|
|
5319
|
-
|
|
5320
|
-
|
|
7491
|
+
if (this.scopeEnabled) {
|
|
7492
|
+
this.engine.configureScopeTelemetry(
|
|
7493
|
+
this.scopeIntervalFrames,
|
|
7494
|
+
Math.min(this.scopeRing?.bands ?? this.scopeBands, SONARE_SCOPE_MAX_BANDS)
|
|
7495
|
+
);
|
|
5321
7496
|
}
|
|
5322
7497
|
}
|
|
5323
7498
|
process(inputs, outputs) {
|
|
@@ -5480,6 +7655,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5480
7655
|
}
|
|
5481
7656
|
break;
|
|
5482
7657
|
}
|
|
7658
|
+
case "syncClipPagePrefetchFrames": {
|
|
7659
|
+
this.engine.setClipPagePrefetchFrames(message.frames);
|
|
7660
|
+
break;
|
|
7661
|
+
}
|
|
7662
|
+
case "syncWarpVoiceCapacity": {
|
|
7663
|
+
this.engine.setWarpVoiceCapacity(message.voices);
|
|
7664
|
+
break;
|
|
7665
|
+
}
|
|
5483
7666
|
case "syncClipPageCommit": {
|
|
5484
7667
|
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
5485
7668
|
const clip = message.clip ?? this.pendingPagedClips.get(message.clipId);
|
|
@@ -5538,7 +7721,38 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5538
7721
|
);
|
|
5539
7722
|
}
|
|
5540
7723
|
break;
|
|
5541
|
-
case "syncMixer":
|
|
7724
|
+
case "syncMixer": {
|
|
7725
|
+
if (message.sidechainDelta === true) {
|
|
7726
|
+
for (const binding of message.laneSidechains ?? []) {
|
|
7727
|
+
this.engine.setLaneSidechain(
|
|
7728
|
+
binding.trackId,
|
|
7729
|
+
binding.insertIndex,
|
|
7730
|
+
binding.sourceTrackId
|
|
7731
|
+
);
|
|
7732
|
+
}
|
|
7733
|
+
for (const binding of message.busSidechains ?? []) {
|
|
7734
|
+
this.engine.setBusSidechain(
|
|
7735
|
+
binding.busId,
|
|
7736
|
+
binding.insertIndex,
|
|
7737
|
+
binding.sourceKind,
|
|
7738
|
+
binding.sourceId
|
|
7739
|
+
);
|
|
7740
|
+
}
|
|
7741
|
+
for (const binding of message.masterSidechains ?? []) {
|
|
7742
|
+
this.engine.setMasterSidechain(
|
|
7743
|
+
binding.insertIndex,
|
|
7744
|
+
binding.sourceKind,
|
|
7745
|
+
binding.sourceId
|
|
7746
|
+
);
|
|
7747
|
+
}
|
|
7748
|
+
break;
|
|
7749
|
+
}
|
|
7750
|
+
if (this.commandRing) {
|
|
7751
|
+
const write = Atomics.load(this.commandRing.header, 0);
|
|
7752
|
+
const read = Atomics.load(this.commandRing.header, 1);
|
|
7753
|
+
this.drainCommands(Math.min(this.commandRing.capacity, Math.max(0, write - read)));
|
|
7754
|
+
}
|
|
7755
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
5542
7756
|
if (message.buses) {
|
|
5543
7757
|
this.engine.setTrackBuses(message.buses);
|
|
5544
7758
|
}
|
|
@@ -5552,10 +7766,62 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5552
7766
|
if (message.masterStripJson) {
|
|
5553
7767
|
this.engine.setMasterStripJson(message.masterStripJson);
|
|
5554
7768
|
}
|
|
7769
|
+
for (const target of message.insertBaseResets ?? []) {
|
|
7770
|
+
switch (target.kind) {
|
|
7771
|
+
case "track":
|
|
7772
|
+
this.engine.clearTrackInsertParameterBases(target.trackId);
|
|
7773
|
+
break;
|
|
7774
|
+
case "bus":
|
|
7775
|
+
this.engine.clearBusInsertParameterBases(target.busId);
|
|
7776
|
+
break;
|
|
7777
|
+
case "master":
|
|
7778
|
+
this.engine.clearMasterInsertParameterBases();
|
|
7779
|
+
break;
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
5555
7782
|
for (const binding of message.laneSidechains ?? []) {
|
|
5556
7783
|
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
5557
7784
|
}
|
|
7785
|
+
for (const binding of message.busSidechains ?? []) {
|
|
7786
|
+
this.engine.setBusSidechain(
|
|
7787
|
+
binding.busId,
|
|
7788
|
+
binding.insertIndex,
|
|
7789
|
+
binding.sourceKind,
|
|
7790
|
+
binding.sourceId
|
|
7791
|
+
);
|
|
7792
|
+
}
|
|
7793
|
+
for (const binding of message.masterSidechains ?? []) {
|
|
7794
|
+
this.engine.setMasterSidechain(binding.insertIndex, binding.sourceKind, binding.sourceId);
|
|
7795
|
+
}
|
|
7796
|
+
for (const override of message.insertParamOverrides ?? []) {
|
|
7797
|
+
switch (override.kind) {
|
|
7798
|
+
case "track":
|
|
7799
|
+
this.engine.restoreTrackStripInsertParamByName(
|
|
7800
|
+
override.trackId,
|
|
7801
|
+
override.insertIndex,
|
|
7802
|
+
override.paramName,
|
|
7803
|
+
override.value
|
|
7804
|
+
);
|
|
7805
|
+
break;
|
|
7806
|
+
case "bus":
|
|
7807
|
+
this.engine.restoreBusStripInsertParamByName(
|
|
7808
|
+
override.busId,
|
|
7809
|
+
override.insertIndex,
|
|
7810
|
+
override.paramName,
|
|
7811
|
+
override.value
|
|
7812
|
+
);
|
|
7813
|
+
break;
|
|
7814
|
+
case "master":
|
|
7815
|
+
this.engine.restoreMasterStripInsertParamByName(
|
|
7816
|
+
override.insertIndex,
|
|
7817
|
+
override.paramName,
|
|
7818
|
+
override.value
|
|
7819
|
+
);
|
|
7820
|
+
break;
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
5558
7823
|
break;
|
|
7824
|
+
}
|
|
5559
7825
|
case "syncCapture":
|
|
5560
7826
|
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
5561
7827
|
this.engine.setCaptureSource(message.source);
|
|
@@ -5568,6 +7834,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5568
7834
|
case "syncMasterStripEqBand":
|
|
5569
7835
|
this.engine.setMasterStripEqBandJson(message.bandIndex, message.bandJson);
|
|
5570
7836
|
break;
|
|
7837
|
+
case "syncBusStripEqBand":
|
|
7838
|
+
this.engine.setBusStripEqBandJson(message.busId, message.bandIndex, message.bandJson);
|
|
7839
|
+
break;
|
|
5571
7840
|
case "syncTrackStripInsertBypassed":
|
|
5572
7841
|
this.engine.setTrackStripInsertBypassed(
|
|
5573
7842
|
message.trackId,
|
|
@@ -5584,26 +7853,57 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5584
7853
|
);
|
|
5585
7854
|
break;
|
|
5586
7855
|
case "syncTrackStripInsertParamByName":
|
|
5587
|
-
this.engine.
|
|
7856
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7857
|
+
if (!this.engine.applyTrackStripInsertParamByNameNow(
|
|
5588
7858
|
message.trackId,
|
|
5589
7859
|
message.insertIndex,
|
|
5590
7860
|
message.paramName,
|
|
5591
7861
|
message.value
|
|
5592
|
-
)
|
|
7862
|
+
)) {
|
|
7863
|
+
this.engine.restoreTrackStripInsertParamByName(
|
|
7864
|
+
message.trackId,
|
|
7865
|
+
message.insertIndex,
|
|
7866
|
+
message.paramName,
|
|
7867
|
+
message.value
|
|
7868
|
+
);
|
|
7869
|
+
}
|
|
5593
7870
|
break;
|
|
5594
7871
|
case "syncMasterStripInsertParamByName":
|
|
5595
|
-
this.engine.
|
|
7872
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7873
|
+
if (!this.engine.applyMasterStripInsertParamByNameNow(
|
|
5596
7874
|
message.insertIndex,
|
|
5597
7875
|
message.paramName,
|
|
5598
7876
|
message.value
|
|
5599
|
-
)
|
|
7877
|
+
)) {
|
|
7878
|
+
this.engine.restoreMasterStripInsertParamByName(
|
|
7879
|
+
message.insertIndex,
|
|
7880
|
+
message.paramName,
|
|
7881
|
+
message.value
|
|
7882
|
+
);
|
|
7883
|
+
}
|
|
5600
7884
|
break;
|
|
5601
7885
|
case "syncBusStripInsertParamByName":
|
|
5602
|
-
this.engine.
|
|
7886
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7887
|
+
if (!this.engine.applyBusStripInsertParamByNameNow(
|
|
5603
7888
|
message.busId,
|
|
5604
7889
|
message.insertIndex,
|
|
5605
7890
|
message.paramName,
|
|
5606
7891
|
message.value
|
|
7892
|
+
)) {
|
|
7893
|
+
this.engine.restoreBusStripInsertParamByName(
|
|
7894
|
+
message.busId,
|
|
7895
|
+
message.insertIndex,
|
|
7896
|
+
message.paramName,
|
|
7897
|
+
message.value
|
|
7898
|
+
);
|
|
7899
|
+
}
|
|
7900
|
+
break;
|
|
7901
|
+
case "syncBusStripInsertBypassed":
|
|
7902
|
+
this.engine.setBusStripInsertBypassed(
|
|
7903
|
+
message.busId,
|
|
7904
|
+
message.insertIndex,
|
|
7905
|
+
message.bypassed,
|
|
7906
|
+
message.resetOnBypass
|
|
5607
7907
|
);
|
|
5608
7908
|
break;
|
|
5609
7909
|
case "syncTrackStripPan":
|
|
@@ -5618,6 +7918,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5618
7918
|
case "syncTrackStripDualPan":
|
|
5619
7919
|
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
5620
7920
|
break;
|
|
7921
|
+
case "syncBusStripPan":
|
|
7922
|
+
this.engine.setBusStripPan(message.busId, message.pan);
|
|
7923
|
+
break;
|
|
7924
|
+
case "syncBusStripPanLaw":
|
|
7925
|
+
this.engine.setBusStripPanLaw(message.busId, message.panLaw);
|
|
7926
|
+
break;
|
|
7927
|
+
case "syncBusStripPanMode":
|
|
7928
|
+
this.engine.setBusStripPanMode(message.busId, message.panMode);
|
|
7929
|
+
break;
|
|
7930
|
+
case "syncBusStripDualPan":
|
|
7931
|
+
this.engine.setBusStripDualPan(message.busId, message.leftPan, message.rightPan);
|
|
7932
|
+
break;
|
|
5621
7933
|
case "syncTrackStripChannelDelaySamples":
|
|
5622
7934
|
this.engine.setTrackStripChannelDelaySamples(message.trackId, message.delaySamples);
|
|
5623
7935
|
break;
|
|
@@ -5669,8 +7981,36 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5669
7981
|
message.renderFrame
|
|
5670
7982
|
);
|
|
5671
7983
|
break;
|
|
7984
|
+
case "syncMidiPitchBend":
|
|
7985
|
+
this.engine.pushMidiPitchBend(
|
|
7986
|
+
message.destinationId,
|
|
7987
|
+
message.group,
|
|
7988
|
+
message.channel,
|
|
7989
|
+
message.data0,
|
|
7990
|
+
message.renderFrame
|
|
7991
|
+
);
|
|
7992
|
+
break;
|
|
7993
|
+
case "syncMidiChannelPressure":
|
|
7994
|
+
this.engine.pushMidiChannelPressure(
|
|
7995
|
+
message.destinationId,
|
|
7996
|
+
message.group,
|
|
7997
|
+
message.channel,
|
|
7998
|
+
message.data0,
|
|
7999
|
+
message.renderFrame
|
|
8000
|
+
);
|
|
8001
|
+
break;
|
|
8002
|
+
case "syncMidiPolyPressure":
|
|
8003
|
+
this.engine.pushMidiPolyPressure(
|
|
8004
|
+
message.destinationId,
|
|
8005
|
+
message.group,
|
|
8006
|
+
message.channel,
|
|
8007
|
+
message.data0,
|
|
8008
|
+
message.data1,
|
|
8009
|
+
message.renderFrame
|
|
8010
|
+
);
|
|
8011
|
+
break;
|
|
5672
8012
|
case "syncMidiUmp":
|
|
5673
|
-
this.engine.pushMidiUmp(message.destinationId, message.word0, message.renderFrame);
|
|
8013
|
+
this.engine.pushMidiUmp(message.destinationId, [message.word0], message.renderFrame);
|
|
5674
8014
|
break;
|
|
5675
8015
|
case "syncMidiSysex":
|
|
5676
8016
|
this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
|
|
@@ -5723,6 +8063,31 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5723
8063
|
message.portTimeSamples
|
|
5724
8064
|
);
|
|
5725
8065
|
break;
|
|
8066
|
+
case "syncMidiInputPitchBend":
|
|
8067
|
+
this.engine.pushMidiInputPitchBend(
|
|
8068
|
+
message.group,
|
|
8069
|
+
message.channel,
|
|
8070
|
+
message.data0,
|
|
8071
|
+
message.portTimeSamples
|
|
8072
|
+
);
|
|
8073
|
+
break;
|
|
8074
|
+
case "syncMidiInputChannelPressure":
|
|
8075
|
+
this.engine.pushMidiInputChannelPressure(
|
|
8076
|
+
message.group,
|
|
8077
|
+
message.channel,
|
|
8078
|
+
message.data0,
|
|
8079
|
+
message.portTimeSamples
|
|
8080
|
+
);
|
|
8081
|
+
break;
|
|
8082
|
+
case "syncMidiInputPolyPressure":
|
|
8083
|
+
this.engine.pushMidiInputPolyPressure(
|
|
8084
|
+
message.group,
|
|
8085
|
+
message.channel,
|
|
8086
|
+
message.data0,
|
|
8087
|
+
message.data1,
|
|
8088
|
+
message.portTimeSamples
|
|
8089
|
+
);
|
|
8090
|
+
break;
|
|
5726
8091
|
}
|
|
5727
8092
|
}
|
|
5728
8093
|
receiveCaptureRequest(message) {
|
|
@@ -5803,11 +8168,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5803
8168
|
this.closed = true;
|
|
5804
8169
|
}
|
|
5805
8170
|
}
|
|
5806
|
-
drainCommands() {
|
|
8171
|
+
drainCommands(limit = 64) {
|
|
5807
8172
|
if (!this.commandRing) {
|
|
5808
8173
|
return;
|
|
5809
8174
|
}
|
|
5810
|
-
for (let i = 0; i <
|
|
8175
|
+
for (let i = 0; i < limit; i++) {
|
|
5811
8176
|
const command = popSonareEngineCommandRingBuffer(this.commandRing);
|
|
5812
8177
|
if (!command) {
|
|
5813
8178
|
return;
|
|
@@ -5909,6 +8274,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5909
8274
|
this.engine.setTrackMonitorMode(laneIndex, mode, sampleTime);
|
|
5910
8275
|
break;
|
|
5911
8276
|
}
|
|
8277
|
+
case 28 /* ResetMasterLoudnessMeter */:
|
|
8278
|
+
this.engine.resetMasterLoudnessMeter(sampleTime);
|
|
8279
|
+
break;
|
|
5912
8280
|
default:
|
|
5913
8281
|
this.publishTelemetryRecord({
|
|
5914
8282
|
type: 1 /* Error */,
|
|
@@ -5994,10 +8362,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5994
8362
|
if (meter.frame !== this.lastMeterFrame) {
|
|
5995
8363
|
this.lastMeterFrame = meter.frame;
|
|
5996
8364
|
}
|
|
5997
|
-
if (this.
|
|
5998
|
-
this.
|
|
8365
|
+
if (this.transport?.onMeter) {
|
|
8366
|
+
this.transport.onMeter(meter);
|
|
5999
8367
|
} else {
|
|
6000
|
-
this.transport?.onMeter?.(meter);
|
|
6001
8368
|
this.transport?.postMessage?.(meter);
|
|
6002
8369
|
}
|
|
6003
8370
|
}
|
|
@@ -6012,42 +8379,69 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
6012
8379
|
for (let field = 0; field < 11; field++) {
|
|
6013
8380
|
ring.records[offset + 3 + field] = this.engine.meterScratchValue(field);
|
|
6014
8381
|
}
|
|
8382
|
+
ring.records[offset + 14] = this.engine.meterScratchInputPeakDbL();
|
|
8383
|
+
ring.records[offset + 15] = this.engine.meterScratchInputPeakDbR();
|
|
6015
8384
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
6016
8385
|
}
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
ring.records[offset + 5] = meter.rmsDbL;
|
|
6030
|
-
ring.records[offset + 6] = meter.rmsDbR;
|
|
6031
|
-
ring.records[offset + 7] = meter.correlation;
|
|
6032
|
-
ring.records[offset + 8] = meter.truePeakDbL;
|
|
6033
|
-
ring.records[offset + 9] = meter.truePeakDbR;
|
|
6034
|
-
ring.records[offset + 10] = meter.momentaryLufs;
|
|
6035
|
-
ring.records[offset + 11] = meter.shortTermLufs;
|
|
6036
|
-
ring.records[offset + 12] = meter.integratedLufs;
|
|
6037
|
-
ring.records[offset + 13] = meter.gainReductionDb;
|
|
6038
|
-
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
6039
|
-
}
|
|
8386
|
+
// A snapshot-object ring writer used to live here as well. It was
|
|
8387
|
+
// unreachable: publishMeters returns after the writeMeterScratch loop
|
|
8388
|
+
// whenever a ring exists, so the ring is never fed from a materialised
|
|
8389
|
+
// snapshot. writeMeterScratch above is the only ring writer, and it keeps the
|
|
8390
|
+
// render callback allocation-free by reading the engine's scratch registers
|
|
8391
|
+
// directly.
|
|
8392
|
+
//
|
|
8393
|
+
// writeIndex is a free-running monotonic counter, so an overflow guard would
|
|
8394
|
+
// fire on essentially every write past the first `capacity` records and store
|
|
8395
|
+
// an ever-growing value, not a dropped-record count. Readers already detect
|
|
8396
|
+
// silent overrun via firstReadable = max(readIndex, writeIndex - capacity),
|
|
8397
|
+
// so header slot 3 is left at its initial 0.
|
|
6040
8398
|
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
6041
|
-
// the lock-free SAB scope ring
|
|
6042
|
-
//
|
|
8399
|
+
// the lock-free SAB scope ring or the bounded postMessage fallback. The SAB
|
|
8400
|
+
// path remains allocation-free; postMessage materialises only records that
|
|
8401
|
+
// are ready to publish.
|
|
6043
8402
|
publishScope() {
|
|
6044
|
-
|
|
6045
|
-
if (!ring) {
|
|
8403
|
+
if (!this.scopeEnabled) {
|
|
6046
8404
|
return;
|
|
6047
8405
|
}
|
|
8406
|
+
const ring = this.scopeRing;
|
|
8407
|
+
const transport = this.transport;
|
|
6048
8408
|
for (let count = 0; count < 64 && this.engine.popScopeTelemetryToScratch(); count++) {
|
|
6049
|
-
|
|
8409
|
+
if (ring) {
|
|
8410
|
+
this.writeScopeScratch(ring);
|
|
8411
|
+
} else {
|
|
8412
|
+
this.postScopeScratch(transport);
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
}
|
|
8416
|
+
postScopeScratch(transport) {
|
|
8417
|
+
if (!transport?.postMessage) {
|
|
8418
|
+
return;
|
|
8419
|
+
}
|
|
8420
|
+
const bandCount = Math.min(
|
|
8421
|
+
SONARE_SCOPE_MAX_BANDS,
|
|
8422
|
+
Math.max(0, Math.trunc(Number(this.engine.scopeScratchBandCount())))
|
|
8423
|
+
);
|
|
8424
|
+
const pointCount = Math.min(
|
|
8425
|
+
SONARE_SCOPE_MAX_POINTS,
|
|
8426
|
+
Math.max(0, Math.trunc(Number(this.engine.scopeScratchPointCount())))
|
|
8427
|
+
);
|
|
8428
|
+
const bands = new Float32Array(bandCount);
|
|
8429
|
+
for (let i = 0; i < bandCount; i++) {
|
|
8430
|
+
bands[i] = this.engine.scopeScratchBand(i);
|
|
8431
|
+
}
|
|
8432
|
+
const points = new Float32Array(pointCount * 2);
|
|
8433
|
+
for (let i = 0; i < pointCount; i++) {
|
|
8434
|
+
points[2 * i] = this.engine.scopeScratchPointLeft(i);
|
|
8435
|
+
points[2 * i + 1] = this.engine.scopeScratchPointRight(i);
|
|
6050
8436
|
}
|
|
8437
|
+
const scope = {
|
|
8438
|
+
type: "scope",
|
|
8439
|
+
targetId: this.engine.scopeScratchTargetId(),
|
|
8440
|
+
frame: Number(this.engine.scopeScratchRenderFrame()),
|
|
8441
|
+
bands,
|
|
8442
|
+
points
|
|
8443
|
+
};
|
|
8444
|
+
transport.postMessage(scope, [bands.buffer, points.buffer]);
|
|
6051
8445
|
}
|
|
6052
8446
|
writeScopeScratch(ring) {
|
|
6053
8447
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
@@ -6244,15 +8638,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6244
8638
|
}
|
|
6245
8639
|
return;
|
|
6246
8640
|
}
|
|
6247
|
-
|
|
6248
|
-
this.bridge.receiveCommand(event.data);
|
|
6249
|
-
} else if (isEngineSyncMessage(event.data)) {
|
|
6250
|
-
this.bridge.receiveSync(event.data);
|
|
6251
|
-
} else if (isEngineCaptureRequestMessage(event.data)) {
|
|
6252
|
-
this.bridge.receiveCaptureRequest(event.data);
|
|
6253
|
-
} else if (isEngineTransportRequestMessage(event.data)) {
|
|
6254
|
-
this.bridge.receiveTransportRequest(event.data);
|
|
6255
|
-
}
|
|
8641
|
+
this.routeMessage(event.data);
|
|
6256
8642
|
};
|
|
6257
8643
|
if (port?.addEventListener) {
|
|
6258
8644
|
port.addEventListener("message", onMessage);
|
|
@@ -6271,18 +8657,35 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6271
8657
|
}
|
|
6272
8658
|
return true;
|
|
6273
8659
|
}
|
|
8660
|
+
/**
|
|
8661
|
+
* Single dispatch point for the port, shared with the buffered replay so
|
|
8662
|
+
* the two cannot recognize different message sets.
|
|
8663
|
+
*/
|
|
8664
|
+
routeMessage(data) {
|
|
8665
|
+
const bridge = this.bridge;
|
|
8666
|
+
if (!bridge) {
|
|
8667
|
+
return;
|
|
8668
|
+
}
|
|
8669
|
+
if (isEngineCommandRecord(data)) {
|
|
8670
|
+
bridge.receiveCommand(data);
|
|
8671
|
+
} else if (isEngineSyncMessage(data)) {
|
|
8672
|
+
bridge.receiveSync(data);
|
|
8673
|
+
} else if (isEngineCaptureRequestMessage(data)) {
|
|
8674
|
+
bridge.receiveCaptureRequest(data);
|
|
8675
|
+
} else if (isEngineTransportRequestMessage(data)) {
|
|
8676
|
+
bridge.receiveTransportRequest(data);
|
|
8677
|
+
} else if (isRecord(data) && typeof data.type === "string" && data.type.startsWith("sync")) {
|
|
8678
|
+
this.port?.postMessage?.({
|
|
8679
|
+
type: "syncError",
|
|
8680
|
+
syncType: data.type,
|
|
8681
|
+
message: `Unrecognized worklet sync message: ${data.type}`
|
|
8682
|
+
});
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
6274
8685
|
replayPendingMessages() {
|
|
6275
8686
|
const messages = this.pendingMessages.splice(0);
|
|
6276
8687
|
for (const data of messages) {
|
|
6277
|
-
|
|
6278
|
-
this.bridge?.receiveCommand(data);
|
|
6279
|
-
} else if (isEngineSyncMessage(data)) {
|
|
6280
|
-
this.bridge?.receiveSync(data);
|
|
6281
|
-
} else if (isEngineCaptureRequestMessage(data)) {
|
|
6282
|
-
this.bridge?.receiveCaptureRequest(data);
|
|
6283
|
-
} else if (isEngineTransportRequestMessage(data)) {
|
|
6284
|
-
this.bridge?.receiveTransportRequest(data);
|
|
6285
|
-
}
|
|
8688
|
+
this.routeMessage(data);
|
|
6286
8689
|
}
|
|
6287
8690
|
}
|
|
6288
8691
|
async initializeEmbind(options, port) {
|
|
@@ -6335,15 +8738,50 @@ var SonareWorkletProcessor = class {
|
|
|
6335
8738
|
this.processedFrames = 0;
|
|
6336
8739
|
this.lastMeterFrame = 0;
|
|
6337
8740
|
this.lastSpectrumFrame = 0;
|
|
8741
|
+
/**
|
|
8742
|
+
* Reused meter record, so a publish writes fields instead of allocating one.
|
|
8743
|
+
*
|
|
8744
|
+
* `targetId` is always the master and the four LUFS / gain-reduction fields
|
|
8745
|
+
* are always unavailable here — the mixer worklet does not run the
|
|
8746
|
+
* K-weighting filters or the engine's pre-trim input tap — so finite floor
|
|
8747
|
+
* values are set once rather than per interval.
|
|
8748
|
+
*/
|
|
8749
|
+
this.meterScratch = {
|
|
8750
|
+
type: "meter",
|
|
8751
|
+
targetId: 0,
|
|
8752
|
+
frame: 0,
|
|
8753
|
+
peakDbL: 0,
|
|
8754
|
+
peakDbR: 0,
|
|
8755
|
+
rmsDbL: 0,
|
|
8756
|
+
rmsDbR: 0,
|
|
8757
|
+
correlation: 0,
|
|
8758
|
+
truePeakDbL: 0,
|
|
8759
|
+
truePeakDbR: 0,
|
|
8760
|
+
momentaryLufs: Number.NaN,
|
|
8761
|
+
shortTermLufs: Number.NaN,
|
|
8762
|
+
integratedLufs: Number.NaN,
|
|
8763
|
+
gainReductionDb: Number.NaN,
|
|
8764
|
+
inputPeakDbL: SONARE_FLOOR_DB,
|
|
8765
|
+
inputPeakDbR: SONARE_FLOOR_DB
|
|
8766
|
+
};
|
|
6338
8767
|
if (!options.sceneJson) {
|
|
6339
8768
|
throw new Error("sceneJson is required.");
|
|
6340
8769
|
}
|
|
6341
8770
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6342
8771
|
this.blockSize = options.blockSize ?? 128;
|
|
6343
|
-
this.meterIntervalFrames =
|
|
6344
|
-
|
|
8772
|
+
this.meterIntervalFrames = requireIntegerOption(
|
|
8773
|
+
options.meterIntervalFrames,
|
|
8774
|
+
2048,
|
|
8775
|
+
"meterIntervalFrames",
|
|
8776
|
+
0
|
|
8777
|
+
);
|
|
8778
|
+
this.spectrumIntervalFrames = requireIntegerOption(
|
|
8779
|
+
options.spectrumIntervalFrames,
|
|
8780
|
+
0,
|
|
8781
|
+
"spectrumIntervalFrames",
|
|
8782
|
+
0
|
|
8783
|
+
);
|
|
6345
8784
|
this.transport = transport;
|
|
6346
|
-
this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
|
|
6347
8785
|
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
6348
8786
|
this.spectrumRing = options.spectrumSharedBuffer ? spectrumRingFromSharedBuffer(
|
|
6349
8787
|
options.spectrumSharedBuffer,
|
|
@@ -6360,6 +8798,7 @@ var SonareWorkletProcessor = class {
|
|
|
6360
8798
|
throw new Error("stripCount must match the scene strip count.");
|
|
6361
8799
|
}
|
|
6362
8800
|
this.realtime = this.mixer.createRealtimeBuffer();
|
|
8801
|
+
this.mixer.configureMeter(this.meterIntervalFrames > 0, 4);
|
|
6363
8802
|
}
|
|
6364
8803
|
process(inputs, outputs) {
|
|
6365
8804
|
if (this.closed) {
|
|
@@ -6406,10 +8845,7 @@ var SonareWorkletProcessor = class {
|
|
|
6406
8845
|
}
|
|
6407
8846
|
}
|
|
6408
8847
|
this.processedFrames += usable;
|
|
6409
|
-
this.publishMeter(
|
|
6410
|
-
this.realtime.outLeft.subarray(0, usable),
|
|
6411
|
-
this.realtime.outRight.subarray(0, usable)
|
|
6412
|
-
);
|
|
8848
|
+
this.publishMeter();
|
|
6413
8849
|
this.publishSpectrum(
|
|
6414
8850
|
this.realtime.outLeft.subarray(0, usable),
|
|
6415
8851
|
this.realtime.outRight.subarray(0, usable)
|
|
@@ -6425,7 +8861,11 @@ var SonareWorkletProcessor = class {
|
|
|
6425
8861
|
return;
|
|
6426
8862
|
}
|
|
6427
8863
|
if (message.type === "setMeterInterval") {
|
|
6428
|
-
|
|
8864
|
+
const frames = Math.max(0, Math.floor(message.frames));
|
|
8865
|
+
if (frames > 0 !== this.meterIntervalFrames > 0) {
|
|
8866
|
+
this.mixer.configureMeter(frames > 0, 4);
|
|
8867
|
+
}
|
|
8868
|
+
this.meterIntervalFrames = frames;
|
|
6429
8869
|
return;
|
|
6430
8870
|
}
|
|
6431
8871
|
if (message.type === "scheduleInsertAutomation") {
|
|
@@ -6445,58 +8885,54 @@ var SonareWorkletProcessor = class {
|
|
|
6445
8885
|
this.closed = true;
|
|
6446
8886
|
}
|
|
6447
8887
|
}
|
|
6448
|
-
publishMeter(
|
|
6449
|
-
if (!this.transport || this.meterIntervalFrames <= 0) {
|
|
8888
|
+
publishMeter() {
|
|
8889
|
+
if (!this.transport && !this.meterRing || this.meterIntervalFrames <= 0) {
|
|
6450
8890
|
return;
|
|
6451
8891
|
}
|
|
6452
8892
|
if (this.processedFrames - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
6453
8893
|
return;
|
|
6454
8894
|
}
|
|
6455
8895
|
this.lastMeterFrame = this.processedFrames;
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
}
|
|
6472
|
-
sumL += l * l;
|
|
6473
|
-
sumR += r * r;
|
|
6474
|
-
sumLR += l * r;
|
|
8896
|
+
if (!this.mixer.latchMeterSnapshot()) {
|
|
8897
|
+
return;
|
|
8898
|
+
}
|
|
8899
|
+
if (this.meterRing) {
|
|
8900
|
+
const meter2 = this.meterScratch;
|
|
8901
|
+
meter2.frame = this.processedFrames;
|
|
8902
|
+
meter2.peakDbL = this.mixer.meterScratchValue(0);
|
|
8903
|
+
meter2.peakDbR = this.mixer.meterScratchValue(1);
|
|
8904
|
+
meter2.rmsDbL = this.mixer.meterScratchValue(2);
|
|
8905
|
+
meter2.rmsDbR = this.mixer.meterScratchValue(3);
|
|
8906
|
+
meter2.correlation = this.mixer.meterScratchValue(4);
|
|
8907
|
+
meter2.truePeakDbL = this.mixer.meterScratchValue(5);
|
|
8908
|
+
meter2.truePeakDbR = this.mixer.meterScratchValue(6);
|
|
8909
|
+
this.writeMeterRing(meter2);
|
|
8910
|
+
return;
|
|
6475
8911
|
}
|
|
6476
|
-
const rmsL = Math.sqrt(sumL / Math.max(1, left.length));
|
|
6477
|
-
const rmsR = Math.sqrt(sumR / Math.max(1, right.length));
|
|
6478
|
-
const denominator = Math.sqrt(sumL * sumR);
|
|
6479
8912
|
const meter = {
|
|
6480
8913
|
type: "meter",
|
|
6481
8914
|
targetId: 0,
|
|
6482
8915
|
frame: this.processedFrames,
|
|
6483
|
-
peakDbL:
|
|
6484
|
-
peakDbR:
|
|
6485
|
-
rmsDbL:
|
|
6486
|
-
rmsDbR:
|
|
6487
|
-
correlation:
|
|
6488
|
-
truePeakDbL:
|
|
6489
|
-
truePeakDbR:
|
|
8916
|
+
peakDbL: this.mixer.meterScratchValue(0),
|
|
8917
|
+
peakDbR: this.mixer.meterScratchValue(1),
|
|
8918
|
+
rmsDbL: this.mixer.meterScratchValue(2),
|
|
8919
|
+
rmsDbR: this.mixer.meterScratchValue(3),
|
|
8920
|
+
correlation: this.mixer.meterScratchValue(4),
|
|
8921
|
+
truePeakDbL: this.mixer.meterScratchValue(5),
|
|
8922
|
+
truePeakDbR: this.mixer.meterScratchValue(6),
|
|
8923
|
+
// Declared unavailable rather than floored: the mixer worklet does not run
|
|
8924
|
+
// the K-weighting filters, and a floor value would read as silence.
|
|
6490
8925
|
momentaryLufs: Number.NaN,
|
|
6491
8926
|
shortTermLufs: Number.NaN,
|
|
6492
8927
|
integratedLufs: Number.NaN,
|
|
6493
|
-
gainReductionDb: Number.NaN
|
|
8928
|
+
gainReductionDb: Number.NaN,
|
|
8929
|
+
inputPeakDbL: SONARE_FLOOR_DB,
|
|
8930
|
+
inputPeakDbR: SONARE_FLOOR_DB
|
|
6494
8931
|
};
|
|
6495
|
-
this.transport
|
|
6496
|
-
|
|
6497
|
-
this.writeMeterRing(meter);
|
|
8932
|
+
if (this.transport?.onMeter) {
|
|
8933
|
+
this.transport.onMeter(meter);
|
|
6498
8934
|
} else {
|
|
6499
|
-
this.transport
|
|
8935
|
+
this.transport?.postMessage?.(meter);
|
|
6500
8936
|
}
|
|
6501
8937
|
}
|
|
6502
8938
|
writeMeterRing(meter) {
|
|
@@ -6520,6 +8956,8 @@ var SonareWorkletProcessor = class {
|
|
|
6520
8956
|
ring.records[offset + 11] = meter.shortTermLufs;
|
|
6521
8957
|
ring.records[offset + 12] = meter.integratedLufs;
|
|
6522
8958
|
ring.records[offset + 13] = meter.gainReductionDb;
|
|
8959
|
+
ring.records[offset + 14] = meter.inputPeakDbL;
|
|
8960
|
+
ring.records[offset + 15] = meter.inputPeakDbR;
|
|
6523
8961
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
6524
8962
|
}
|
|
6525
8963
|
publishSpectrum(left, right) {
|
|
@@ -6540,8 +8978,11 @@ var SonareWorkletProcessor = class {
|
|
|
6540
8978
|
frame: this.processedFrames,
|
|
6541
8979
|
bands: new Float32Array(this.spectrumBands)
|
|
6542
8980
|
};
|
|
6543
|
-
this.transport?.onSpectrum
|
|
6544
|
-
|
|
8981
|
+
if (this.transport?.onSpectrum) {
|
|
8982
|
+
this.transport.onSpectrum(spectrum);
|
|
8983
|
+
} else {
|
|
8984
|
+
this.transport?.postMessage?.(spectrum);
|
|
8985
|
+
}
|
|
6545
8986
|
}
|
|
6546
8987
|
computeSpectrum(left, right) {
|
|
6547
8988
|
const n = Math.max(1, Math.min(left.length, right.length));
|
|
@@ -6569,7 +9010,7 @@ var SonareWorkletProcessor = class {
|
|
|
6569
9010
|
return;
|
|
6570
9011
|
}
|
|
6571
9012
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
6572
|
-
const offset = writeIndex % ring.capacity *
|
|
9013
|
+
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
6573
9014
|
ring.records[offset] = encodeFrameLo(frame);
|
|
6574
9015
|
ring.records[offset + 1] = encodeFrameHi(frame);
|
|
6575
9016
|
ring.records[offset + 2] = bands.length;
|
|
@@ -6609,13 +9050,232 @@ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
|
6609
9050
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
6610
9051
|
}
|
|
6611
9052
|
|
|
9053
|
+
// src/worklet/playback-processor.ts
|
|
9054
|
+
var MAX_INPUT_CHANNELS = 8;
|
|
9055
|
+
var SPEAKER_LAYOUT_CHANNELS = { stereo: 2, "5.1": 6, "7.1": 8 };
|
|
9056
|
+
function configObject(config) {
|
|
9057
|
+
if (config === void 0) {
|
|
9058
|
+
return {};
|
|
9059
|
+
}
|
|
9060
|
+
return typeof config === "string" ? JSON.parse(config) : config;
|
|
9061
|
+
}
|
|
9062
|
+
function outputChannelCount(config) {
|
|
9063
|
+
if (config.target?.kind !== "speakers") {
|
|
9064
|
+
return 2;
|
|
9065
|
+
}
|
|
9066
|
+
const count = SPEAKER_LAYOUT_CHANNELS[config.target.layout ?? ""];
|
|
9067
|
+
if (count === void 0) {
|
|
9068
|
+
throw new RangeError('target.layout must be "stereo", "5.1" or "7.1" for a speakers target');
|
|
9069
|
+
}
|
|
9070
|
+
return count;
|
|
9071
|
+
}
|
|
9072
|
+
var _SonarePlaybackWorkletProcessor = class _SonarePlaybackWorkletProcessor {
|
|
9073
|
+
constructor(options = {}, port) {
|
|
9074
|
+
this.inputPlanes = [];
|
|
9075
|
+
this.outputPlanes = [];
|
|
9076
|
+
this.unsupportedInputBlocks = 0;
|
|
9077
|
+
this.destroyed = false;
|
|
9078
|
+
this.port = port;
|
|
9079
|
+
this.maxBlockSize = requireIntegerOption(options.maxBlockSize, 128, "maxBlockSize", 1);
|
|
9080
|
+
const scopeRate = globalThis.sampleRate;
|
|
9081
|
+
const sampleRate = options.sampleRate ?? (typeof scopeRate === "number" ? scopeRate : 48e3);
|
|
9082
|
+
const hrtf = options.hrtf ? HrtfSet.fromBytes(new Uint8Array(options.hrtf)) : void 0;
|
|
9083
|
+
try {
|
|
9084
|
+
this.renderer = new PlaybackRenderer({
|
|
9085
|
+
config: options.config ?? {},
|
|
9086
|
+
hrtf,
|
|
9087
|
+
sampleRate,
|
|
9088
|
+
maxBlockSize: this.maxBlockSize
|
|
9089
|
+
});
|
|
9090
|
+
} finally {
|
|
9091
|
+
hrtf?.delete();
|
|
9092
|
+
}
|
|
9093
|
+
this.native = this.renderer.native;
|
|
9094
|
+
this.outputChannels = this.native.outputChannels();
|
|
9095
|
+
this.acquirePlanes();
|
|
9096
|
+
}
|
|
9097
|
+
/**
|
|
9098
|
+
* Handles a control-plane message. AudioWorklet port handlers run on the
|
|
9099
|
+
* rendering thread between `process()` calls, which is what makes `reset`
|
|
9100
|
+
* safe here. A refused message is answered with an `error` message.
|
|
9101
|
+
*/
|
|
9102
|
+
receiveMessage(message) {
|
|
9103
|
+
if (this.destroyed) {
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
try {
|
|
9107
|
+
switch (message.type) {
|
|
9108
|
+
case "config":
|
|
9109
|
+
this.renderer.setConfig(message.config);
|
|
9110
|
+
break;
|
|
9111
|
+
case "orientation":
|
|
9112
|
+
this.renderer.setHeadOrientation(message.yaw, message.pitch ?? 0, message.roll ?? 0);
|
|
9113
|
+
break;
|
|
9114
|
+
case "reset":
|
|
9115
|
+
this.renderer.reset();
|
|
9116
|
+
break;
|
|
9117
|
+
case "diagnostics":
|
|
9118
|
+
this.port?.postMessage?.({
|
|
9119
|
+
type: "diagnostics",
|
|
9120
|
+
diagnostics: this.diagnostics()
|
|
9121
|
+
});
|
|
9122
|
+
break;
|
|
9123
|
+
case "destroy":
|
|
9124
|
+
this.destroy();
|
|
9125
|
+
break;
|
|
9126
|
+
}
|
|
9127
|
+
} catch (error) {
|
|
9128
|
+
this.port?.postMessage?.({
|
|
9129
|
+
type: "error",
|
|
9130
|
+
request: message.type,
|
|
9131
|
+
message: error instanceof Error ? error.message : String(error)
|
|
9132
|
+
});
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
/** The renderer's diagnostics plus `unsupported_input_blocks`, as a plain object. */
|
|
9136
|
+
diagnostics() {
|
|
9137
|
+
return {
|
|
9138
|
+
...this.renderer.diagnostics(),
|
|
9139
|
+
unsupported_input_blocks: this.unsupportedInputBlocks
|
|
9140
|
+
};
|
|
9141
|
+
}
|
|
9142
|
+
process(inputs, outputs) {
|
|
9143
|
+
if (this.destroyed) {
|
|
9144
|
+
return false;
|
|
9145
|
+
}
|
|
9146
|
+
const output = outputs[0];
|
|
9147
|
+
const requested = output?.[0]?.length ?? 0;
|
|
9148
|
+
if (!output || requested === 0) {
|
|
9149
|
+
return true;
|
|
9150
|
+
}
|
|
9151
|
+
const frames = this.clampFrames(requested);
|
|
9152
|
+
if (this.inputPlanes[0]?.byteLength === 0 || this.outputPlanes[0]?.byteLength === 0) {
|
|
9153
|
+
this.acquirePlanes();
|
|
9154
|
+
}
|
|
9155
|
+
const input = inputs[0];
|
|
9156
|
+
const inChannels = input?.length ?? 0;
|
|
9157
|
+
let code;
|
|
9158
|
+
if (inChannels === 0) {
|
|
9159
|
+
code = this.native.processPreparedSilence(frames);
|
|
9160
|
+
} else if (inChannels > MAX_INPUT_CHANNELS) {
|
|
9161
|
+
this.unsupportedInputBlocks++;
|
|
9162
|
+
code = this.native.processPreparedSilence(frames);
|
|
9163
|
+
} else {
|
|
9164
|
+
for (let ch = 0; ch < inChannels; ch++) {
|
|
9165
|
+
const source = input[ch];
|
|
9166
|
+
const plane = this.inputPlanes[ch];
|
|
9167
|
+
const copied = Math.min(frames, source.length);
|
|
9168
|
+
plane.set(copied === source.length ? source : source.subarray(0, copied));
|
|
9169
|
+
if (copied < frames) {
|
|
9170
|
+
plane.fill(0, copied, frames);
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
code = this.native.processPrepared(inChannels, frames);
|
|
9174
|
+
if (code !== 0) {
|
|
9175
|
+
this.unsupportedInputBlocks++;
|
|
9176
|
+
code = this.native.processPreparedSilence(frames);
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
if (code === 0) {
|
|
9180
|
+
copyPlanesToOutput(output, this.outputPlanes, frames);
|
|
9181
|
+
} else {
|
|
9182
|
+
for (const channel of output) {
|
|
9183
|
+
channel.fill(0);
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
return true;
|
|
9187
|
+
}
|
|
9188
|
+
destroy() {
|
|
9189
|
+
if (this.destroyed) {
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
this.destroyed = true;
|
|
9193
|
+
this.renderer.delete();
|
|
9194
|
+
}
|
|
9195
|
+
acquirePlanes() {
|
|
9196
|
+
this.inputPlanes = [];
|
|
9197
|
+
for (let ch = 0; ch < MAX_INPUT_CHANNELS; ch++) {
|
|
9198
|
+
this.inputPlanes.push(this.native.inputPlane(ch));
|
|
9199
|
+
}
|
|
9200
|
+
this.outputPlanes = [];
|
|
9201
|
+
for (let ch = 0; ch < this.outputChannels; ch++) {
|
|
9202
|
+
this.outputPlanes.push(this.native.outputPlane(ch));
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
// Frames past the construction-time capacity are left silent rather than
|
|
9206
|
+
// reallocating on the audio thread.
|
|
9207
|
+
clampFrames(frames) {
|
|
9208
|
+
if (frames <= this.maxBlockSize) {
|
|
9209
|
+
return frames;
|
|
9210
|
+
}
|
|
9211
|
+
if (!_SonarePlaybackWorkletProcessor.warnedBlockOverflow) {
|
|
9212
|
+
_SonarePlaybackWorkletProcessor.warnedBlockOverflow = true;
|
|
9213
|
+
console.warn(
|
|
9214
|
+
`SonarePlaybackWorkletProcessor: requested ${frames} frames exceeds maxBlockSize ${this.maxBlockSize}; clamping.`
|
|
9215
|
+
);
|
|
9216
|
+
}
|
|
9217
|
+
return this.maxBlockSize;
|
|
9218
|
+
}
|
|
9219
|
+
};
|
|
9220
|
+
_SonarePlaybackWorkletProcessor.warnedBlockOverflow = false;
|
|
9221
|
+
var SonarePlaybackWorkletProcessor = _SonarePlaybackWorkletProcessor;
|
|
9222
|
+
function registerSonarePlaybackWorkletProcessor(name = "sonare-playback-processor") {
|
|
9223
|
+
const scope = globalThis;
|
|
9224
|
+
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
9225
|
+
throw new Error("AudioWorkletProcessor is not available in this context.");
|
|
9226
|
+
}
|
|
9227
|
+
const Base = scope.AudioWorkletProcessor;
|
|
9228
|
+
class RegisteredSonarePlaybackWorkletProcessor extends Base {
|
|
9229
|
+
constructor(options) {
|
|
9230
|
+
super();
|
|
9231
|
+
const port = this.port;
|
|
9232
|
+
this.bridge = new SonarePlaybackWorkletProcessor(options?.processorOptions ?? {}, port);
|
|
9233
|
+
const onMessage = (event) => {
|
|
9234
|
+
if (isPlaybackMessage(event.data)) {
|
|
9235
|
+
this.bridge.receiveMessage(event.data);
|
|
9236
|
+
}
|
|
9237
|
+
};
|
|
9238
|
+
if (port?.addEventListener) {
|
|
9239
|
+
port.addEventListener("message", onMessage);
|
|
9240
|
+
port.start?.();
|
|
9241
|
+
} else if (port) {
|
|
9242
|
+
port.onmessage = onMessage;
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
9245
|
+
process(inputs, outputs) {
|
|
9246
|
+
return this.bridge.process(inputs, outputs);
|
|
9247
|
+
}
|
|
9248
|
+
}
|
|
9249
|
+
scope.registerProcessor(name, RegisteredSonarePlaybackWorkletProcessor);
|
|
9250
|
+
}
|
|
9251
|
+
function createSonarePlaybackNode(context, options = {}) {
|
|
9252
|
+
const config = configObject(options.config);
|
|
9253
|
+
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
9254
|
+
const processorOptions = {
|
|
9255
|
+
config: JSON.stringify(config),
|
|
9256
|
+
hrtf: options.hrtf,
|
|
9257
|
+
maxBlockSize: options.maxBlockSize,
|
|
9258
|
+
sampleRate: options.sampleRate ?? context.sampleRate
|
|
9259
|
+
};
|
|
9260
|
+
return factory(context, options.processorName ?? "sonare-playback-processor", {
|
|
9261
|
+
numberOfInputs: 1,
|
|
9262
|
+
numberOfOutputs: 1,
|
|
9263
|
+
outputChannelCount: [outputChannelCount(config)],
|
|
9264
|
+
channelCountMode: "max",
|
|
9265
|
+
channelInterpretation: "discrete",
|
|
9266
|
+
processorOptions
|
|
9267
|
+
});
|
|
9268
|
+
}
|
|
9269
|
+
|
|
6612
9270
|
// src/worklet/voice-changer-processor.ts
|
|
6613
9271
|
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
6614
9272
|
constructor(options = {}) {
|
|
9273
|
+
// Reused so the mono fan-out never builds an array on the audio thread.
|
|
9274
|
+
this.monoPlane = [new Float32Array(0)];
|
|
6615
9275
|
this.destroyed = false;
|
|
6616
9276
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6617
9277
|
this.blockSize = options.blockSize ?? 128;
|
|
6618
|
-
this.channelCount =
|
|
9278
|
+
this.channelCount = requireChannelCount(options.channelCount, 1);
|
|
6619
9279
|
this.changer = new RealtimeVoiceChanger(options.preset ?? "neutral-monitor");
|
|
6620
9280
|
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
6621
9281
|
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
@@ -6668,7 +9328,8 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6668
9328
|
this.monoInput.fill(0, 0, frames2);
|
|
6669
9329
|
}
|
|
6670
9330
|
this.changer.processPreparedMono(frames2);
|
|
6671
|
-
|
|
9331
|
+
this.monoPlane[0] = this.monoOutput;
|
|
9332
|
+
copyPlanesToOutput(output, this.monoPlane, frames2);
|
|
6672
9333
|
return true;
|
|
6673
9334
|
}
|
|
6674
9335
|
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
@@ -6686,12 +9347,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6686
9347
|
}
|
|
6687
9348
|
}
|
|
6688
9349
|
this.changer.processPreparedPlanar(frames);
|
|
6689
|
-
|
|
6690
|
-
const src = this.planarChannels[ch];
|
|
6691
|
-
if (src) {
|
|
6692
|
-
output[ch].set(src.subarray(0, frames));
|
|
6693
|
-
}
|
|
6694
|
-
}
|
|
9350
|
+
copyPlanesToOutput(output, this.planarChannels, frames);
|
|
6695
9351
|
return true;
|
|
6696
9352
|
}
|
|
6697
9353
|
destroy() {
|
|
@@ -6801,6 +9457,7 @@ export {
|
|
|
6801
9457
|
SonareEngineCommandType,
|
|
6802
9458
|
SonareEngineTelemetryError,
|
|
6803
9459
|
SonareEngineTelemetryType,
|
|
9460
|
+
SonarePlaybackWorkletProcessor,
|
|
6804
9461
|
SonareRealtimeEngineNode,
|
|
6805
9462
|
SonareRealtimeEngineWorkletProcessor,
|
|
6806
9463
|
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
@@ -6812,6 +9469,7 @@ export {
|
|
|
6812
9469
|
createSonareEngineTelemetryRingBuffer,
|
|
6813
9470
|
createSonareExternalMidiRingBuffer,
|
|
6814
9471
|
createSonareMeterRingBuffer,
|
|
9472
|
+
createSonarePlaybackNode,
|
|
6815
9473
|
createSonareScopeRingBuffer,
|
|
6816
9474
|
createSonareSpectrumRingBuffer,
|
|
6817
9475
|
decodeFrame,
|
|
@@ -6829,6 +9487,7 @@ export {
|
|
|
6829
9487
|
readSonareMeterRingBuffer,
|
|
6830
9488
|
readSonareScopeRingBuffer,
|
|
6831
9489
|
readSonareSpectrumRingBuffer,
|
|
9490
|
+
registerSonarePlaybackWorkletProcessor,
|
|
6832
9491
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
6833
9492
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
6834
9493
|
registerSonareWorkletProcessor,
|