@libraz/libsonare 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +178 -0
- package/README.md +26 -203
- package/dist/_chain_config.d.ts +14 -0
- package/dist/_chain_config.d.ts.map +1 -0
- package/dist/_effects_common.d.ts +6 -0
- package/dist/_effects_common.d.ts.map +1 -0
- package/dist/_feature_validation.d.ts +8 -0
- package/dist/_feature_validation.d.ts.map +1 -0
- package/dist/_fft_options.d.ts +24 -0
- package/dist/_fft_options.d.ts.map +1 -0
- package/dist/align_take.d.ts +50 -0
- package/dist/align_take.d.ts.map +1 -0
- package/dist/analysis.d.ts +29 -5840
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +873 -722
- package/dist/analysis.js.map +1 -1
- package/dist/analysis_helpers.d.ts +9 -0
- package/dist/analysis_helpers.d.ts.map +1 -0
- package/dist/audio.d.ts +163 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/clip_page_streamer.d.ts +133 -0
- package/dist/clip_page_streamer.d.ts.map +1 -0
- package/dist/codes.d.ts +44 -0
- package/dist/codes.d.ts.map +1 -0
- package/dist/effects_mastering.d.ts +23 -0
- package/dist/effects_mastering.d.ts.map +1 -0
- package/dist/effects_note_ops.d.ts +477 -0
- package/dist/effects_note_ops.d.ts.map +1 -0
- package/dist/effects_percussive.d.ts +185 -0
- package/dist/effects_percussive.d.ts.map +1 -0
- package/dist/effects_separation.d.ts +65 -0
- package/dist/effects_separation.d.ts.map +1 -0
- package/dist/effects_spectral.d.ts +28 -0
- package/dist/effects_spectral.d.ts.map +1 -0
- package/dist/effects_timepitch.d.ts +129 -0
- package/dist/effects_timepitch.d.ts.map +1 -0
- package/dist/effects_voice_change.d.ts +53 -0
- package/dist/effects_voice_change.d.ts.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/feature_core.d.ts +341 -0
- package/dist/feature_core.d.ts.map +1 -0
- package/dist/feature_decompose.d.ts +278 -0
- package/dist/feature_decompose.d.ts.map +1 -0
- package/dist/feature_inverse.d.ts +128 -0
- package/dist/feature_inverse.d.ts.map +1 -0
- package/dist/feature_loudness.d.ts +66 -0
- package/dist/feature_loudness.d.ts.map +1 -0
- package/dist/feature_music.d.ts +307 -0
- package/dist/feature_music.d.ts.map +1 -0
- package/dist/feature_pitch.d.ts +108 -0
- package/dist/feature_pitch.d.ts.map +1 -0
- package/dist/feature_resample.d.ts +16 -0
- package/dist/feature_resample.d.ts.map +1 -0
- package/dist/feature_spectral.d.ts +137 -0
- package/dist/feature_spectral.d.ts.map +1 -0
- package/dist/feature_spectrogram.d.ts +198 -0
- package/dist/feature_spectrogram.d.ts.map +1 -0
- package/dist/features.d.ts +10 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/hrtf/default.shrf +0 -0
- package/dist/index.d.ts +74 -7431
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3758 -1362
- package/dist/index.js.map +1 -1
- package/dist/instrument_types.d.ts +517 -0
- package/dist/instrument_types.d.ts.map +1 -0
- package/dist/live_audio.d.ts +35 -0
- package/dist/live_audio.d.ts.map +1 -0
- package/dist/mastering_chain.d.ts +213 -0
- package/dist/mastering_chain.d.ts.map +1 -0
- package/dist/mastering_core.d.ts +457 -0
- package/dist/mastering_core.d.ts.map +1 -0
- package/dist/mastering_dynamics.d.ts +80 -0
- package/dist/mastering_dynamics.d.ts.map +1 -0
- package/dist/metering.d.ts +287 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/mixer.d.ts +464 -0
- package/dist/mixer.d.ts.map +1 -0
- package/dist/mixing_assistant.d.ts +62 -0
- package/dist/mixing_assistant.d.ts.map +1 -0
- package/dist/mixing_oneshot.d.ts +40 -0
- package/dist/mixing_oneshot.d.ts.map +1 -0
- package/dist/module_state.d.ts +15 -0
- package/dist/module_state.d.ts.map +1 -0
- package/dist/opfs_clip_pages.d.ts +28 -0
- package/dist/opfs_clip_pages.d.ts.map +1 -0
- package/dist/playback_renderer.d.ts +128 -0
- package/dist/playback_renderer.d.ts.map +1 -0
- package/dist/polyphony.d.ts +202 -0
- package/dist/polyphony.d.ts.map +1 -0
- package/dist/project.d.ts +8 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project_class.d.ts +562 -0
- package/dist/project_class.d.ts.map +1 -0
- package/dist/project_internal.d.ts +194 -0
- package/dist/project_internal.d.ts.map +1 -0
- package/dist/project_synth.d.ts +74 -0
- package/dist/project_synth.d.ts.map +1 -0
- package/dist/project_types.d.ts +652 -0
- package/dist/project_types.d.ts.map +1 -0
- package/dist/public_types.d.ts +185 -0
- package/dist/public_types.d.ts.map +1 -0
- package/dist/public_types_acoustic.d.ts +215 -0
- package/dist/public_types_acoustic.d.ts.map +1 -0
- package/dist/public_types_mastering.d.ts +510 -0
- package/dist/public_types_mastering.d.ts.map +1 -0
- package/dist/public_types_mixing.d.ts +436 -0
- package/dist/public_types_mixing.d.ts.map +1 -0
- package/dist/public_types_music.d.ts +619 -0
- package/dist/public_types_music.d.ts.map +1 -0
- package/dist/public_types_playback.d.ts +164 -0
- package/dist/public_types_playback.d.ts.map +1 -0
- package/dist/public_types_realtime.d.ts +174 -0
- package/dist/public_types_realtime.d.ts.map +1 -0
- package/dist/public_types_repair.d.ts +424 -0
- package/dist/public_types_repair.d.ts.map +1 -0
- package/dist/public_types_spectral.d.ts +697 -0
- package/dist/public_types_spectral.d.ts.map +1 -0
- package/dist/quick_analysis.d.ts +445 -0
- package/dist/quick_analysis.d.ts.map +1 -0
- package/dist/realtime_engine.d.ts +850 -0
- package/dist/realtime_engine.d.ts.map +1 -0
- package/dist/realtime_voice_changer.d.ts +158 -0
- package/dist/realtime_voice_changer.d.ts.map +1 -0
- package/dist/repair_dereverb.d.ts +187 -0
- package/dist/repair_dereverb.d.ts.map +1 -0
- package/dist/repair_impulsive.d.ts +186 -0
- package/dist/repair_impulsive.d.ts.map +1 -0
- package/dist/repair_noise.d.ts +239 -0
- package/dist/repair_noise.d.ts.map +1 -0
- package/dist/repair_trim.d.ts +123 -0
- package/dist/repair_trim.d.ts.map +1 -0
- package/dist/sample_bank.d.ts +84 -0
- package/dist/sample_bank.d.ts.map +1 -0
- package/dist/scale.d.ts +10 -0
- package/dist/scale.d.ts.map +1 -0
- package/dist/schemas/mixer-scene.schema.json +393 -0
- package/dist/schemas/playback-renderer-config.schema.json +392 -0
- package/dist/sonare-analysis.d.ts +8 -0
- package/dist/sonare-analysis.js +2 -2
- package/dist/sonare-analysis.wasm +0 -0
- package/dist/sonare.d.ts +3909 -0
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/stream_analyzer.d.ts +163 -0
- package/dist/stream_analyzer.d.ts.map +1 -0
- package/dist/stream_types.d.ts +214 -0
- package/dist/stream_types.d.ts.map +1 -0
- package/dist/streaming_mixing.d.ts +6 -0
- package/dist/streaming_mixing.d.ts.map +1 -0
- package/dist/streaming_processors.d.ts +335 -0
- package/dist/streaming_processors.d.ts.map +1 -0
- package/dist/transcribe.d.ts +77 -0
- package/dist/transcribe.d.ts.map +1 -0
- package/dist/validation.d.ts +140 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/web_midi.d.ts +77 -0
- package/dist/web_midi.d.ts.map +1 -0
- package/dist/worker.d.ts +5 -48
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +94 -41
- package/dist/worker.js.map +1 -1
- package/dist/worker_client.d.ts +96 -0
- package/dist/worker_client.d.ts.map +1 -0
- package/dist/worker_protocol.d.ts +43 -0
- package/dist/worker_protocol.d.ts.map +1 -0
- package/dist/worklet/audio_types.d.ts +21 -0
- package/dist/worklet/audio_types.d.ts.map +1 -0
- package/dist/worklet/engine-automation.d.ts +29 -0
- package/dist/worklet/engine-automation.d.ts.map +1 -0
- package/dist/worklet/engine-capture-facade.d.ts +35 -0
- package/dist/worklet/engine-capture-facade.d.ts.map +1 -0
- package/dist/worklet/engine-clips.d.ts +23 -0
- package/dist/worklet/engine-clips.d.ts.map +1 -0
- package/dist/worklet/engine-markers.d.ts +40 -0
- package/dist/worklet/engine-markers.d.ts.map +1 -0
- package/dist/worklet/engine-mixer-facade.d.ts +128 -0
- package/dist/worklet/engine-mixer-facade.d.ts.map +1 -0
- package/dist/worklet/engine-node.d.ts +81 -0
- package/dist/worklet/engine-node.d.ts.map +1 -0
- package/dist/worklet/engine-offline.d.ts +81 -0
- package/dist/worklet/engine-offline.d.ts.map +1 -0
- package/dist/worklet/engine-options.d.ts +12 -0
- package/dist/worklet/engine-options.d.ts.map +1 -0
- package/dist/worklet/engine-parameter-facade.d.ts +106 -0
- package/dist/worklet/engine-parameter-facade.d.ts.map +1 -0
- package/dist/worklet/engine-processor.d.ts +68 -0
- package/dist/worklet/engine-processor.d.ts.map +1 -0
- package/dist/worklet/engine-register.d.ts +2 -0
- package/dist/worklet/engine-register.d.ts.map +1 -0
- package/dist/worklet/engine-strips.d.ts +73 -0
- package/dist/worklet/engine-strips.d.ts.map +1 -0
- package/dist/worklet/engine-sync.d.ts +37 -0
- package/dist/worklet/engine-sync.d.ts.map +1 -0
- package/dist/worklet/engine-tempo-facade.d.ts +48 -0
- package/dist/worklet/engine-tempo-facade.d.ts.map +1 -0
- package/dist/worklet/engine.d.ts +397 -0
- package/dist/worklet/engine.d.ts.map +1 -0
- package/dist/worklet/guards.d.ts +47 -0
- package/dist/worklet/guards.d.ts.map +1 -0
- package/dist/worklet/messages.d.ts +710 -0
- package/dist/worklet/messages.d.ts.map +1 -0
- package/dist/worklet/mixer-processor.d.ts +46 -0
- package/dist/worklet/mixer-processor.d.ts.map +1 -0
- package/dist/worklet/playback-processor.d.ts +62 -0
- package/dist/worklet/playback-processor.d.ts.map +1 -0
- package/dist/worklet/protocol.d.ts +323 -0
- package/dist/worklet/protocol.d.ts.map +1 -0
- package/dist/worklet/voice-changer-processor.d.ts +41 -0
- package/dist/worklet/voice-changer-processor.d.ts.map +1 -0
- package/dist/worklet.d.ts +16 -2515
- package/dist/worklet.d.ts.map +1 -0
- package/dist/worklet.js +2763 -501
- package/dist/worklet.js.map +1 -1
- package/package.json +23 -12
- package/src/_effects_common.ts +17 -0
- package/src/_feature_validation.ts +34 -0
- package/src/_fft_options.ts +39 -0
- package/src/align_take.ts +64 -0
- package/src/analysis.ts +56 -3
- package/src/analysis_helpers.ts +7 -0
- package/src/audio.ts +106 -3
- package/src/codes.ts +39 -2
- package/src/effects_mastering.ts +97 -22
- package/src/effects_note_ops.ts +635 -0
- package/src/effects_percussive.ts +217 -0
- package/src/effects_separation.ts +150 -0
- package/src/effects_spectral.ts +60 -0
- package/src/effects_timepitch.ts +377 -0
- package/src/errors.ts +23 -1
- package/src/feature_core.ts +127 -2
- package/src/feature_decompose.ts +633 -0
- package/src/feature_inverse.ts +454 -0
- package/src/feature_loudness.ts +125 -0
- package/src/feature_music.ts +107 -14
- package/src/feature_pitch.ts +96 -1
- package/src/feature_spectral.ts +16 -611
- package/src/feature_spectrogram.ts +63 -450
- package/src/features.ts +36 -22
- package/src/index.ts +282 -30
- package/src/instrument_types.ts +645 -0
- package/src/live_audio.ts +27 -1
- package/src/mastering_chain.ts +184 -0
- package/src/mastering_core.ts +346 -32
- package/src/mastering_dynamics.ts +22 -11
- package/src/metering.ts +67 -24
- package/src/mixer.ts +212 -3
- package/src/mixing_assistant.ts +138 -0
- package/src/mixing_oneshot.ts +10 -5
- package/src/module_state.ts +24 -2
- package/src/playback_renderer.ts +252 -0
- package/src/polyphony.ts +279 -0
- package/src/project.ts +61 -24
- package/src/project_class.ts +450 -27
- package/src/project_internal.ts +149 -42
- package/src/project_synth.ts +67 -1
- package/src/project_types.ts +268 -270
- package/src/public_types.ts +122 -3
- package/src/public_types_acoustic.ts +112 -3
- package/src/public_types_mastering.ts +254 -73
- package/src/public_types_mixing.ts +363 -1
- package/src/public_types_music.ts +312 -2
- package/src/public_types_playback.ts +195 -0
- package/src/public_types_realtime.ts +39 -7
- package/src/public_types_repair.ts +446 -0
- package/src/public_types_spectral.ts +487 -1
- package/src/quick_analysis.ts +203 -26
- package/src/realtime_engine.ts +711 -28
- package/src/realtime_voice_changer.ts +55 -1
- package/src/repair_dereverb.ts +299 -0
- package/src/repair_impulsive.ts +395 -0
- package/src/repair_noise.ts +425 -0
- package/src/repair_trim.ts +226 -0
- package/src/sample_bank.ts +113 -0
- package/src/sonare.js.d.ts +1122 -30
- package/src/stream_analyzer.ts +36 -4
- package/src/stream_types.ts +37 -0
- package/src/streaming_mixing.ts +1 -1
- package/src/streaming_processors.ts +194 -10
- package/src/transcribe.ts +89 -0
- package/src/validation.ts +285 -11
- package/src/web_midi.ts +1 -6
- package/src/worker.ts +18 -2
- package/src/worklet/audio_types.ts +37 -0
- package/src/worklet/engine-mixer-facade.ts +444 -10
- package/src/worklet/engine-node.ts +78 -26
- package/src/worklet/engine-offline.ts +14 -8
- package/src/worklet/engine-parameter-facade.ts +21 -0
- package/src/worklet/engine-processor.ts +272 -83
- package/src/worklet/engine-register.ts +32 -18
- package/src/worklet/engine-strips.ts +280 -9
- package/src/worklet/engine-sync.ts +14 -6
- package/src/worklet/engine.ts +365 -31
- package/src/worklet/guards.ts +140 -44
- package/src/worklet/messages.ts +227 -2
- package/src/worklet/mixer-processor.ts +109 -47
- package/src/worklet/playback-processor.ts +300 -0
- package/src/worklet/protocol.ts +53 -3
- package/src/worklet/voice-changer-processor.ts +17 -11
- package/src/worklet.ts +17 -0
- package/src/effects_transform.ts +0 -718
- package/src/mastering_repair.ts +0 -273
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);
|
|
@@ -825,6 +1219,12 @@ var RealtimeEngine = class {
|
|
|
825
1219
|
* block / animation frame. `maxRecords` caps the number of output events
|
|
826
1220
|
* returned — the shared unit across every surface. Events past the cap stay
|
|
827
1221
|
* queued for the next call (lossless); call again to drain the rest.
|
|
1222
|
+
*
|
|
1223
|
+
* One queued record lowers to at most 4 MIDI 1.0 messages (a MIDI 2.0
|
|
1224
|
+
* registered or assignable controller becomes CC 101/100 or 99/98 plus Data
|
|
1225
|
+
* Entry 6/38), so a positive `maxRecords` below 4 could never consume a record
|
|
1226
|
+
* and is rejected with an `InvalidParameter` `SonareError` instead of
|
|
1227
|
+
* returning nothing forever.
|
|
828
1228
|
*/
|
|
829
1229
|
drainExternalMidi(maxRecords = 1024) {
|
|
830
1230
|
return this.native.drainExternalMidi(maxRecords);
|
|
@@ -837,7 +1237,7 @@ var RealtimeEngine = class {
|
|
|
837
1237
|
return this.native.externalMidiScratchDestinationId();
|
|
838
1238
|
}
|
|
839
1239
|
externalMidiScratchRenderFrame() {
|
|
840
|
-
return this.native.externalMidiScratchRenderFrame();
|
|
1240
|
+
return Number(this.native.externalMidiScratchRenderFrame());
|
|
841
1241
|
}
|
|
842
1242
|
externalMidiScratchByteWord() {
|
|
843
1243
|
return this.native.externalMidiScratchByteWord();
|
|
@@ -857,6 +1257,31 @@ var RealtimeEngine = class {
|
|
|
857
1257
|
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
858
1258
|
this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
859
1259
|
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Push a live MIDI pitch bend to the engine-owned MIDI input source.
|
|
1262
|
+
*
|
|
1263
|
+
* `bend14` is unsigned 14-bit with centre 8192 (0..16383) — the dimension is
|
|
1264
|
+
* not 7-bit, so a value past 16383 is refused rather than narrowed. The input
|
|
1265
|
+
* source must be enabled with {@link setMidiInputSource} first.
|
|
1266
|
+
*/
|
|
1267
|
+
pushMidiInputPitchBend(group, channel, bend14, portTimeSamples = 0) {
|
|
1268
|
+
this.native.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Push a live MIDI channel pressure to the engine-owned MIDI input source.
|
|
1272
|
+
* `pressure` is 7-bit (0..127). Under MPE this is the member channel's
|
|
1273
|
+
* per-note pressure.
|
|
1274
|
+
*/
|
|
1275
|
+
pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples = 0) {
|
|
1276
|
+
this.native.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Push a live MIDI polyphonic key pressure to the engine-owned MIDI input
|
|
1280
|
+
* source. `note` and `pressure` are 7-bit (0..127).
|
|
1281
|
+
*/
|
|
1282
|
+
pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples = 0) {
|
|
1283
|
+
this.native.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
|
|
1284
|
+
}
|
|
860
1285
|
pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
|
|
861
1286
|
this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
862
1287
|
}
|
|
@@ -872,9 +1297,53 @@ var RealtimeEngine = class {
|
|
|
872
1297
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
873
1298
|
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
874
1299
|
}
|
|
875
|
-
/**
|
|
876
|
-
|
|
877
|
-
|
|
1300
|
+
/**
|
|
1301
|
+
* Queue an immediate (live) MIDI pitch bend to a MIDI destination. `bend14`
|
|
1302
|
+
* is unsigned 14-bit with centre 8192 (0..16383); `renderFrame` is the frame
|
|
1303
|
+
* to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
|
|
1304
|
+
* `pushMidiPitchBend`.
|
|
1305
|
+
*/
|
|
1306
|
+
pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame = -1) {
|
|
1307
|
+
this.native.pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame);
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Queue an immediate (live) MIDI channel pressure to a MIDI destination.
|
|
1311
|
+
* `pressure` is 7-bit (0..127); `renderFrame` is the frame to fire at, or -1
|
|
1312
|
+
* for immediate. Mirrors the Node/Python/C-ABI `pushMidiChannelPressure`.
|
|
1313
|
+
*/
|
|
1314
|
+
pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame = -1) {
|
|
1315
|
+
this.native.pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame);
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Queue an immediate (live) MIDI polyphonic key pressure to a MIDI
|
|
1319
|
+
* destination. `note` and `pressure` are 7-bit (0..127); `renderFrame` is the
|
|
1320
|
+
* frame to fire at, or -1 for immediate. Mirrors the Node/Python/C-ABI
|
|
1321
|
+
* `pushMidiPolyPressure`.
|
|
1322
|
+
*/
|
|
1323
|
+
pushMidiPolyPressure(destinationId, group, channel, note, pressure, renderFrame = -1) {
|
|
1324
|
+
this.native.pushMidiPolyPressure(destinationId, group, channel, note, pressure, renderFrame);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Queue an immediate (live) raw UMP message to a MIDI destination. `words` is
|
|
1328
|
+
* 1 to 4 words, most significant first, and its length must match the message
|
|
1329
|
+
* type of `words[0]`. MIDI 2.0 channel-voice messages (MT 0x4) arrive at full
|
|
1330
|
+
* width; SysEx7 / data messages (MT 0x3 / 0x5) are refused, use
|
|
1331
|
+
* {@link pushMidiSysex}. Throws when the slot ring or command queue is full
|
|
1332
|
+
* (retry after a process block). `renderFrame` is the render-frame time to
|
|
1333
|
+
* apply, or -1 for immediate. A bare number is accepted as a one-word
|
|
1334
|
+
* message.
|
|
1335
|
+
*/
|
|
1336
|
+
pushMidiUmp(destinationId, words, renderFrame = -1) {
|
|
1337
|
+
const list = typeof words === "number" ? [words] : words;
|
|
1338
|
+
this.native.pushMidiUmp(destinationId, assertUmpWords("pushMidiUmp", list), renderFrame);
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Push one raw UMP message (1 to 4 words) to the engine-owned MIDI input
|
|
1342
|
+
* source. The message rules match {@link pushMidiUmp}. `portTimeSamples` is
|
|
1343
|
+
* the port timestamp in samples.
|
|
1344
|
+
*/
|
|
1345
|
+
pushMidiInputUmp(words, portTimeSamples = 0) {
|
|
1346
|
+
this.native.pushMidiInputUmp(assertUmpWords("pushMidiInputUmp", words), portTimeSamples);
|
|
878
1347
|
}
|
|
879
1348
|
/**
|
|
880
1349
|
* Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
|
|
@@ -921,10 +1390,18 @@ var RealtimeEngine = class {
|
|
|
921
1390
|
settleParameters() {
|
|
922
1391
|
this.native.settleParameters();
|
|
923
1392
|
}
|
|
1393
|
+
/** Snap only insert automation slots after structural replay. */
|
|
1394
|
+
settleInsertParameters() {
|
|
1395
|
+
this.native.settleInsertParameters();
|
|
1396
|
+
}
|
|
924
1397
|
/** Drains queued commands on an offline/control-only engine immediately. */
|
|
925
1398
|
flushControlCommands() {
|
|
926
1399
|
this.native.flushControlCommands();
|
|
927
1400
|
}
|
|
1401
|
+
/** Applies commands already due on a control-only mirror, retaining future commands. */
|
|
1402
|
+
applyCommandsDueNowPreservingFuture() {
|
|
1403
|
+
this.native.applyCommandsDueNowPreservingFuture();
|
|
1404
|
+
}
|
|
928
1405
|
seekPpq(ppq, renderFrame = -1) {
|
|
929
1406
|
this.native.seekPpq(ppq, renderFrame);
|
|
930
1407
|
}
|
|
@@ -1020,6 +1497,18 @@ var RealtimeEngine = class {
|
|
|
1020
1497
|
clipCount() {
|
|
1021
1498
|
return this.native.clipCount();
|
|
1022
1499
|
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Normalizes each send's pre/post tap point to the integer the native layer
|
|
1502
|
+
* reads (defaults to post-fader when omitted). Shared by track lanes and
|
|
1503
|
+
* buses, which carry the same send shape.
|
|
1504
|
+
*/
|
|
1505
|
+
static normalizeSends(sends) {
|
|
1506
|
+
return sends.map((send) => ({
|
|
1507
|
+
...send,
|
|
1508
|
+
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1509
|
+
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1510
|
+
}));
|
|
1511
|
+
}
|
|
1023
1512
|
setTrackLanes(lanes) {
|
|
1024
1513
|
this.native.setTrackLanes(
|
|
1025
1514
|
lanes.map((lane) => {
|
|
@@ -1029,14 +1518,7 @@ var RealtimeEngine = class {
|
|
|
1029
1518
|
if (!lane.sends) {
|
|
1030
1519
|
return lane;
|
|
1031
1520
|
}
|
|
1032
|
-
return {
|
|
1033
|
-
...lane,
|
|
1034
|
-
sends: lane.sends.map((send) => ({
|
|
1035
|
-
...send,
|
|
1036
|
-
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1037
|
-
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1038
|
-
}))
|
|
1039
|
-
};
|
|
1521
|
+
return { ...lane, sends: _RealtimeEngine.normalizeSends(lane.sends) };
|
|
1040
1522
|
})
|
|
1041
1523
|
);
|
|
1042
1524
|
}
|
|
@@ -1048,7 +1530,25 @@ var RealtimeEngine = class {
|
|
|
1048
1530
|
this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
1049
1531
|
}
|
|
1050
1532
|
setTrackBuses(buses) {
|
|
1051
|
-
this.native.setTrackBuses(
|
|
1533
|
+
this.native.setTrackBuses(
|
|
1534
|
+
Array.isArray(buses) ? buses.map(
|
|
1535
|
+
(bus) => bus.sends ? { ...bus, sends: _RealtimeEngine.normalizeSends(bus.sends) } : bus
|
|
1536
|
+
) : buses
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Keys one insert of a bus strip from a track lane or another bus
|
|
1541
|
+
* (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
|
|
1542
|
+
*/
|
|
1543
|
+
setBusSidechain(busId, insertIndex, sourceKind, sourceId) {
|
|
1544
|
+
this.native.setBusSidechain(busId, insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Keys one insert of the master strip from a track lane or a bus. Same
|
|
1548
|
+
* source rules as {@link setBusSidechain}.
|
|
1549
|
+
*/
|
|
1550
|
+
setMasterSidechain(insertIndex, sourceKind, sourceId) {
|
|
1551
|
+
this.native.setMasterSidechain(insertIndex, sidechainSourceKindCode(sourceKind), sourceId);
|
|
1052
1552
|
}
|
|
1053
1553
|
setBusStripJson(busId, sceneJson) {
|
|
1054
1554
|
try {
|
|
@@ -1081,6 +1581,17 @@ var RealtimeEngine = class {
|
|
|
1081
1581
|
setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1082
1582
|
this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
1083
1583
|
}
|
|
1584
|
+
/** Bus-strip counterpart of {@link setTrackStripEqBand}. */
|
|
1585
|
+
setBusStripEqBand(busId, bandIndex, band) {
|
|
1586
|
+
this.native.setBusStripEqBandJson(
|
|
1587
|
+
busId,
|
|
1588
|
+
bandIndex,
|
|
1589
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
setBusStripEqBandJson(busId, bandIndex, bandJson) {
|
|
1593
|
+
this.native.setBusStripEqBandJson(busId, bandIndex, bandJson);
|
|
1594
|
+
}
|
|
1084
1595
|
setMasterStripJson(sceneJson) {
|
|
1085
1596
|
try {
|
|
1086
1597
|
JSON.parse(sceneJson);
|
|
@@ -1104,22 +1615,59 @@ var RealtimeEngine = class {
|
|
|
1104
1615
|
}
|
|
1105
1616
|
/**
|
|
1106
1617
|
* Changes one track-strip insert parameter in realtime, addressed by the
|
|
1107
|
-
* processor's JSON-key parameter name
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1618
|
+
* processor's JSON-key parameter name — one of the entries
|
|
1619
|
+
* {@link masteringInsertParamInfo} reports with a non-null `id`; a
|
|
1620
|
+
* construction-only entry (`id` null) takes effect only when the insert is
|
|
1621
|
+
* built. Applied at the next block head via the engine command queue; safe
|
|
1622
|
+
* during playback. Throws if the track, insert, or name is unknown, the
|
|
1623
|
+
* param is not realtime-safe, or the command queue is full.
|
|
1111
1624
|
*/
|
|
1112
1625
|
setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1113
1626
|
this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1114
1627
|
}
|
|
1628
|
+
/** Apply a live insert edit on this engine's owning thread without draining its command queue. */
|
|
1629
|
+
applyTrackStripInsertParamByNameNow(trackId, insertIndex, paramName, value) {
|
|
1630
|
+
return this.native.applyTrackStripInsertParamByNameNow(trackId, insertIndex, paramName, value);
|
|
1631
|
+
}
|
|
1632
|
+
/** Restore a retained insert value exactly after a strip scene is replayed. */
|
|
1633
|
+
restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1634
|
+
this.native.restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1635
|
+
}
|
|
1115
1636
|
/** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1116
1637
|
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1117
1638
|
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1118
1639
|
}
|
|
1640
|
+
applyMasterStripInsertParamByNameNow(insertIndex, paramName, value) {
|
|
1641
|
+
return this.native.applyMasterStripInsertParamByNameNow(insertIndex, paramName, value);
|
|
1642
|
+
}
|
|
1643
|
+
restoreMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1644
|
+
this.native.restoreMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1645
|
+
}
|
|
1119
1646
|
/** Bus-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1120
1647
|
setBusStripInsertParamByName(busId, insertIndex, paramName, value) {
|
|
1121
1648
|
this.native.setBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
1122
1649
|
}
|
|
1650
|
+
applyBusStripInsertParamByNameNow(busId, insertIndex, paramName, value) {
|
|
1651
|
+
return this.native.applyBusStripInsertParamByNameNow(busId, insertIndex, paramName, value);
|
|
1652
|
+
}
|
|
1653
|
+
restoreBusStripInsertParamByName(busId, insertIndex, paramName, value) {
|
|
1654
|
+
this.native.restoreBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Forgets the remembered manual insert-parameter values of one track strip
|
|
1658
|
+
* and discards its queued insert edits. Call before {@link setTrackStripJson}
|
|
1659
|
+
* replaces the strip when its old values must not carry over; the setter
|
|
1660
|
+
* never does this itself, since a queued edit may already target the new chain.
|
|
1661
|
+
*/
|
|
1662
|
+
clearTrackInsertParameterBases(trackId) {
|
|
1663
|
+
this.native.clearTrackInsertParameterBases(trackId);
|
|
1664
|
+
}
|
|
1665
|
+
clearBusInsertParameterBases(busId) {
|
|
1666
|
+
this.native.clearBusInsertParameterBases(busId);
|
|
1667
|
+
}
|
|
1668
|
+
clearMasterInsertParameterBases() {
|
|
1669
|
+
this.native.clearMasterInsertParameterBases();
|
|
1670
|
+
}
|
|
1123
1671
|
/** Bus-strip counterpart of {@link setTrackStripInsertBypassed}. */
|
|
1124
1672
|
setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1125
1673
|
this.native.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
@@ -1149,6 +1697,41 @@ var RealtimeEngine = class {
|
|
|
1149
1697
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
1150
1698
|
return this.native.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
1151
1699
|
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Resolves a hosted instrument's continuous parameter (by its JSON-key name)
|
|
1702
|
+
* to the reserved automation id usable with `setAutomationLane` /
|
|
1703
|
+
* `setParameter`, so an instrument parameter is driven at audio-block
|
|
1704
|
+
* precision exactly like a strip insert. Returns `-1` when the destination
|
|
1705
|
+
* has no bound instrument, the instrument exposes no automatable parameters,
|
|
1706
|
+
* or the name is unknown.
|
|
1707
|
+
*
|
|
1708
|
+
* For the NativeSynth ({@link setSynthInstrument}) the names are the
|
|
1709
|
+
* continuous {@link SynthPatch} fields: `gain`, `busDrive`, `cutoffHz`,
|
|
1710
|
+
* `resonanceQ`, `drive`, `keyTrack`, `envToCutoffCents`, `velToCutoffCents`,
|
|
1711
|
+
* `ampAttackMs`, `ampDecayMs`, `ampSustain`, `ampReleaseMs`,
|
|
1712
|
+
* `filterAttackMs`, `filterDecayMs`, `filterSustain`, `filterReleaseMs`,
|
|
1713
|
+
* `lfoRateHz`, `lfoToPitchCents`, `lfo2RateHz`, `glideMs`, `bodyMix`,
|
|
1714
|
+
* `stereoSpread`, `detuneCents`, `driftCents`, `pitchOffsetCents`,
|
|
1715
|
+
* `hpCutoffHz`, `sampleHoldHz`, `bitDepth`.
|
|
1716
|
+
*
|
|
1717
|
+
* Structural fields (`preset`, `engineMode`, `waveform`, `filterModel`,
|
|
1718
|
+
* `unison`, `polyphony`, `body`, `modRoutings`) are not automatable and
|
|
1719
|
+
* return `-1`: they resize voice pools or swap DSP topology, which is not
|
|
1720
|
+
* audio-thread safe. Rebind the instrument with a new patch instead.
|
|
1721
|
+
*
|
|
1722
|
+
* `gain`, `busDrive`, `cutoffHz`, `resonanceQ`, `envToCutoffCents`,
|
|
1723
|
+
* `lfoToPitchCents` and `pitchOffsetCents` reach voices that are already
|
|
1724
|
+
* sounding from the next block; the rest are cached into per-voice state at
|
|
1725
|
+
* note-on and take effect from the next note, so a lane that moves one of
|
|
1726
|
+
* them under a held note looks inert until the next one speaks — that is the
|
|
1727
|
+
* behaviour, not a dropped write.
|
|
1728
|
+
*
|
|
1729
|
+
* The id survives an unbind/rebind of the same destination and applies
|
|
1730
|
+
* nothing while that destination is unbound.
|
|
1731
|
+
*/
|
|
1732
|
+
resolveInstrumentAutomationId(destinationId, paramName) {
|
|
1733
|
+
return this.native.resolveInstrumentAutomationId(destinationId, paramName);
|
|
1734
|
+
}
|
|
1152
1735
|
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
1153
1736
|
setTrackStripPan(trackId, pan) {
|
|
1154
1737
|
this.native.setTrackStripPan(trackId, pan);
|
|
@@ -1165,6 +1748,25 @@ var RealtimeEngine = class {
|
|
|
1165
1748
|
setTrackStripDualPan(trackId, leftPan, rightPan) {
|
|
1166
1749
|
this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
1167
1750
|
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Sets a bus strip's output pan position in realtime (glitch-free). Throws
|
|
1753
|
+
* for an unknown bus or one wider than stereo.
|
|
1754
|
+
*/
|
|
1755
|
+
setBusStripPan(busId, pan) {
|
|
1756
|
+
this.native.setBusStripPan(busId, pan);
|
|
1757
|
+
}
|
|
1758
|
+
/** Sets a bus strip's pan law in realtime. */
|
|
1759
|
+
setBusStripPanLaw(busId, panLaw) {
|
|
1760
|
+
this.native.setBusStripPanLaw(busId, panLawCode(panLaw));
|
|
1761
|
+
}
|
|
1762
|
+
/** Sets a bus strip's pan mode in realtime. */
|
|
1763
|
+
setBusStripPanMode(busId, panMode) {
|
|
1764
|
+
this.native.setBusStripPanMode(busId, panModeCode(panMode));
|
|
1765
|
+
}
|
|
1766
|
+
/** Sets a bus strip's dual-pan left/right positions in realtime. */
|
|
1767
|
+
setBusStripDualPan(busId, leftPan, rightPan) {
|
|
1768
|
+
this.native.setBusStripDualPan(busId, leftPan, rightPan);
|
|
1769
|
+
}
|
|
1168
1770
|
/**
|
|
1169
1771
|
* Sets a track lane strip's inter-channel alignment delay (whole samples).
|
|
1170
1772
|
* Adjusts strip latency, so PDC and reported graph latency are refreshed.
|
|
@@ -1205,16 +1807,62 @@ var RealtimeEngine = class {
|
|
|
1205
1807
|
clipPageRequestOverflowCount() {
|
|
1206
1808
|
return this.native.clipPageRequestOverflowCount();
|
|
1207
1809
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1810
|
+
/** Cumulative warp-stretch requests dropped because the native queue was full. */
|
|
1811
|
+
warpStretchOverflowCount() {
|
|
1812
|
+
return this.native.warpStretchOverflowCount();
|
|
1210
1813
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1814
|
+
/**
|
|
1815
|
+
* Sets the number of concurrent time-stretch voices. `voices` must be an
|
|
1816
|
+
* integer in `[0, 64]`; a non-integer, negative, or larger value throws and
|
|
1817
|
+
* leaves the capacity unchanged. Default is 8. Capacity 0 disables
|
|
1818
|
+
* time-stretch, so every warped clip plays resampled instead and none of
|
|
1819
|
+
* that counts toward {@link warpStretchOverflowCount}. A change applied
|
|
1820
|
+
* while the engine is running restarts the splice state of any clip
|
|
1821
|
+
* stretching through a voice at that moment. Control-thread only.
|
|
1822
|
+
*/
|
|
1823
|
+
setWarpVoiceCapacity(voices) {
|
|
1824
|
+
this.native.setWarpVoiceCapacity(voices);
|
|
1213
1825
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1826
|
+
/** Reads the current time-stretch voice capacity (default 8). */
|
|
1827
|
+
warpVoiceCapacity() {
|
|
1828
|
+
return this.native.warpVoiceCapacity();
|
|
1216
1829
|
}
|
|
1217
|
-
|
|
1830
|
+
/**
|
|
1831
|
+
* Sets the clip-page look-ahead window in timeline frames.
|
|
1832
|
+
*
|
|
1833
|
+
* The player reports the pages it is *about to* read that are not resident
|
|
1834
|
+
* yet, so a streaming host can service them before the audio thread reaches
|
|
1835
|
+
* them. Without look-ahead a page miss is only reported after the read
|
|
1836
|
+
* already produced silence, which costs one block of silence at every page
|
|
1837
|
+
* boundary the host has not primed — the reason a sliding-window streamer
|
|
1838
|
+
* cannot keep a live playhead fed from miss reports alone.
|
|
1839
|
+
*
|
|
1840
|
+
* Look-ahead requests drain through the same `popClipPageRequest` queue and
|
|
1841
|
+
* are queued *after* the block's genuine misses, so a host that keeps only
|
|
1842
|
+
* the newest request per clip (as {@link ClipPageStreamer} does) tracks the
|
|
1843
|
+
* look-ahead frontier.
|
|
1844
|
+
*
|
|
1845
|
+
* `prepare` defaults this to half a second at the engine's sample rate. `0`
|
|
1846
|
+
* disables the look-ahead. A clip whose pages are all resident produces no
|
|
1847
|
+
* requests at all, with or without look-ahead. Safe to call during playback.
|
|
1848
|
+
*/
|
|
1849
|
+
setClipPagePrefetchFrames(frames) {
|
|
1850
|
+
this.native.setClipPagePrefetchFrames(frames);
|
|
1851
|
+
}
|
|
1852
|
+
/** Current clip-page look-ahead window in timeline frames. */
|
|
1853
|
+
clipPagePrefetchFrames() {
|
|
1854
|
+
return this.native.clipPagePrefetchFrames();
|
|
1855
|
+
}
|
|
1856
|
+
setCaptureBuffer(numChannels, capacityFrames) {
|
|
1857
|
+
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
1858
|
+
}
|
|
1859
|
+
armCapture(armed = true) {
|
|
1860
|
+
this.native.armCapture(armed);
|
|
1861
|
+
}
|
|
1862
|
+
setCapturePunch(startSample, endSample, enabled = true) {
|
|
1863
|
+
this.native.setCapturePunch(startSample, endSample, enabled);
|
|
1864
|
+
}
|
|
1865
|
+
setCaptureSource(source) {
|
|
1218
1866
|
this.native.setCaptureSource(source);
|
|
1219
1867
|
}
|
|
1220
1868
|
/** Positive values delay capture relative to the punch window. */
|
|
@@ -1296,12 +1944,36 @@ var RealtimeEngine = class {
|
|
|
1296
1944
|
processWithMonitor(channels) {
|
|
1297
1945
|
return this.native.processWithMonitor(channels);
|
|
1298
1946
|
}
|
|
1299
|
-
renderOffline(
|
|
1300
|
-
|
|
1947
|
+
renderOffline(channelsOrRequest, blockSize = 128) {
|
|
1948
|
+
const request = normalizeRenderOfflineRequest(channelsOrRequest, blockSize);
|
|
1949
|
+
return this.native.renderOffline(request.channels, request.blockSize, request.finalize);
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* End a chunked offline render: release every note the sequencer still holds
|
|
1953
|
+
* and flush the PDC / alignment delay lines. Required after
|
|
1954
|
+
* `renderOffline({ finalize: false })`; the finalizing form does it itself.
|
|
1955
|
+
*
|
|
1956
|
+
* Skipping it leaves every note still sounding at the last chunk held. On an
|
|
1957
|
+
* engine-internal instrument the tail simply never releases; on a destination
|
|
1958
|
+
* marked external ({@link RealtimeEngine.setMidiDestinationExternal}) the
|
|
1959
|
+
* note-ons already left through the external MIDI queue, so the note-offs
|
|
1960
|
+
* emitted here are the only ones the receiving device will get and the notes
|
|
1961
|
+
* otherwise hang outside the engine.
|
|
1962
|
+
*/
|
|
1963
|
+
finishOfflineRender() {
|
|
1964
|
+
this.native.finishOfflineRender();
|
|
1301
1965
|
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Bounce the timeline to an interleaved buffer. `numChannels` above the
|
|
1968
|
+
* prepared channel count throws an `InvalidParameter` `SonareError`.
|
|
1969
|
+
*/
|
|
1302
1970
|
bounceOffline(options) {
|
|
1303
1971
|
return this.native.bounceOffline(options);
|
|
1304
1972
|
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Freeze the current graph to audio. `numChannels` above the prepared channel
|
|
1975
|
+
* count throws an `InvalidParameter` `SonareError`.
|
|
1976
|
+
*/
|
|
1305
1977
|
freezeOffline(options) {
|
|
1306
1978
|
return this.native.freezeOffline(options);
|
|
1307
1979
|
}
|
|
@@ -1395,9 +2067,18 @@ var RealtimeEngine = class {
|
|
|
1395
2067
|
scopeScratchPointRight(index) {
|
|
1396
2068
|
return this.native.scopeScratchPointRight(index);
|
|
1397
2069
|
}
|
|
2070
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1398
2071
|
destroy() {
|
|
2072
|
+
if (this.released) {
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
this.released = true;
|
|
1399
2076
|
this.native.delete();
|
|
1400
2077
|
}
|
|
2078
|
+
/** Alias for {@link destroy}, matching embind's own release method name. */
|
|
2079
|
+
delete() {
|
|
2080
|
+
this.destroy();
|
|
2081
|
+
}
|
|
1401
2082
|
};
|
|
1402
2083
|
var ClipPageProvider = class {
|
|
1403
2084
|
constructor(engine, id) {
|
|
@@ -1429,12 +2110,20 @@ var ClipPageProvider = class {
|
|
|
1429
2110
|
// src/mixer.ts
|
|
1430
2111
|
var Mixer = class _Mixer {
|
|
1431
2112
|
constructor(mixer, blockSize) {
|
|
2113
|
+
this.released = false;
|
|
1432
2114
|
this.mixer = mixer;
|
|
1433
2115
|
this.blockSize = blockSize;
|
|
1434
2116
|
}
|
|
1435
2117
|
/**
|
|
1436
2118
|
* Build a mixer from a scene JSON string.
|
|
1437
2119
|
*
|
|
2120
|
+
* A strip's meters are sized when the strip is built, so this is where their
|
|
2121
|
+
* configuration is chosen: an optional `metering` object on the strip
|
|
2122
|
+
* (`enabled` / `lufs` / `truePeak` / `truePeakOversample`) selects it, and
|
|
2123
|
+
* leaving it out keeps the full default (LUFS + true peak at 4x, about 1.4 MB
|
|
2124
|
+
* per strip at 48 kHz). `{"enabled": false}` drops both meters for a strip
|
|
2125
|
+
* whose snapshots are never read.
|
|
2126
|
+
*
|
|
1438
2127
|
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
1439
2128
|
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
1440
2129
|
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
@@ -1541,6 +2230,56 @@ var Mixer = class _Mixer {
|
|
|
1541
2230
|
}
|
|
1542
2231
|
};
|
|
1543
2232
|
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Turn the master-output meter on or off.
|
|
2235
|
+
*
|
|
2236
|
+
* While on, every {@link MixerRealtimeBuffer.process} call meters the stereo
|
|
2237
|
+
* master it just produced, so a caller reads {@link meterSnapshot} instead of
|
|
2238
|
+
* copying the output and measuring it again. `truePeakDb*` is an inter-sample
|
|
2239
|
+
* peak taken after oversampling (ITU-R BS.1770-4 Annex 2 requires at least
|
|
2240
|
+
* 4x), which is a different and higher quantity than the sample peak.
|
|
2241
|
+
*
|
|
2242
|
+
* Enabling resets the meter, so a reading never mixes in audio from a period
|
|
2243
|
+
* when metering was off.
|
|
2244
|
+
*
|
|
2245
|
+
* @param enabled - Whether to meter the master output.
|
|
2246
|
+
* @param truePeakOversample - 0 (= 4x) or a power of two in [1, 16].
|
|
2247
|
+
*/
|
|
2248
|
+
configureMeter(enabled, truePeakOversample = 4) {
|
|
2249
|
+
this.mixer.configureMeter(enabled, truePeakOversample);
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Latest master-output meter reading, describing the most recently metered
|
|
2253
|
+
* block. All dB fields are finite and floored at -120.
|
|
2254
|
+
*
|
|
2255
|
+
* @throws When the meter has never been enabled.
|
|
2256
|
+
*/
|
|
2257
|
+
meterSnapshot() {
|
|
2258
|
+
return this.mixer.meterSnapshot();
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Latch the latest meter reading into the mixer's internal scratch so
|
|
2262
|
+
* {@link meterScratchValue} can read it back one number at a time.
|
|
2263
|
+
*
|
|
2264
|
+
* This is the allocation-free form of {@link meterSnapshot}, for an audio
|
|
2265
|
+
* render callback that must not create a JS object per interval. It returns
|
|
2266
|
+
* `false` instead of throwing when the meter has never been enabled.
|
|
2267
|
+
*
|
|
2268
|
+
* @returns Whether a reading was latched.
|
|
2269
|
+
*/
|
|
2270
|
+
latchMeterSnapshot() {
|
|
2271
|
+
return this.mixer.latchMeterSnapshot();
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Read one field of the snapshot latched by {@link latchMeterSnapshot}.
|
|
2275
|
+
*
|
|
2276
|
+
* @param field - `0` peakDbL, `1` peakDbR, `2` rmsDbL, `3` rmsDbR,
|
|
2277
|
+
* `4` correlation, `5` truePeakDbL, `6` truePeakDbR. Any other index
|
|
2278
|
+
* reads `0`.
|
|
2279
|
+
*/
|
|
2280
|
+
meterScratchValue(field) {
|
|
2281
|
+
return this.mixer.meterScratchValue(field);
|
|
2282
|
+
}
|
|
1544
2283
|
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
1545
2284
|
stripCount() {
|
|
1546
2285
|
return this.mixer.stripCount();
|
|
@@ -1578,6 +2317,17 @@ var Mixer = class _Mixer {
|
|
|
1578
2317
|
const index = this.mixer.stripById(id);
|
|
1579
2318
|
return index < 0 ? null : index;
|
|
1580
2319
|
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Add a channel strip to the mixer topology. `metering` configures the strip's
|
|
2322
|
+
* pre/post taps; omitting it keeps the full default (LUFS + true peak at 4x,
|
|
2323
|
+
* about 1.4 MB per strip at 48 kHz). Marks the routing graph dirty; call
|
|
2324
|
+
* {@link compile} (or {@link processStereo}) to rebuild.
|
|
2325
|
+
*
|
|
2326
|
+
* @throws If the id is already taken, or `truePeakOversample` is outside `[0, 16]`
|
|
2327
|
+
*/
|
|
2328
|
+
addStrip(id, metering = {}) {
|
|
2329
|
+
this.mixer.addStrip(id, metering);
|
|
2330
|
+
}
|
|
1581
2331
|
/**
|
|
1582
2332
|
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
1583
2333
|
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
@@ -1643,6 +2393,30 @@ var Mixer = class _Mixer {
|
|
|
1643
2393
|
setWidth(stripIndex, width) {
|
|
1644
2394
|
this.mixer.setWidth(stripIndex, width);
|
|
1645
2395
|
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Snap the strip's input-trim, fader, pan and width smoothers to the values
|
|
2398
|
+
* already set on it, so the next processed block opens at those values
|
|
2399
|
+
* instead of gliding to them over the smoothing window (~5 ms).
|
|
2400
|
+
*
|
|
2401
|
+
* Call it after configuring a strip and before rendering a finite buffer: a
|
|
2402
|
+
* strip is smoothed for a live fader, and an offline render that does not
|
|
2403
|
+
* settle carries that glide as a level and image sweep across the head of
|
|
2404
|
+
* its output. Unlike a reset it clears nothing — automation, meters and
|
|
2405
|
+
* insert state are untouched.
|
|
2406
|
+
*
|
|
2407
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2408
|
+
*
|
|
2409
|
+
* @example
|
|
2410
|
+
* ```typescript
|
|
2411
|
+
* mixer.setFaderDb(0, -3);
|
|
2412
|
+
* mixer.setPan(0, 0.3);
|
|
2413
|
+
* mixer.settle(0);
|
|
2414
|
+
* const { left, right } = mixer.processStereo([dryLeft], [dryRight]);
|
|
2415
|
+
* ```
|
|
2416
|
+
*/
|
|
2417
|
+
settle(stripIndex) {
|
|
2418
|
+
this.mixer.settle(stripIndex);
|
|
2419
|
+
}
|
|
1646
2420
|
/** Set the strip's mute state. */
|
|
1647
2421
|
setMuted(stripIndex, muted) {
|
|
1648
2422
|
this.mixer.setMuted(stripIndex, muted);
|
|
@@ -1686,7 +2460,10 @@ var Mixer = class _Mixer {
|
|
|
1686
2460
|
}
|
|
1687
2461
|
/**
|
|
1688
2462
|
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
1689
|
-
*
|
|
2463
|
+
*
|
|
2464
|
+
* Applied when the engine's track mixer renders this strip's lane into a
|
|
2465
|
+
* destination with more than two channels. This stereo-only mixer's own
|
|
2466
|
+
* block entry points ignore it.
|
|
1690
2467
|
*/
|
|
1691
2468
|
setSurroundPan(stripIndex, pan) {
|
|
1692
2469
|
this.mixer.setSurroundPan(stripIndex, pan);
|
|
@@ -1752,6 +2529,59 @@ var Mixer = class _Mixer {
|
|
|
1752
2529
|
busMeter(busId) {
|
|
1753
2530
|
return this.mixer.busMeter(busId);
|
|
1754
2531
|
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Number of blocks in which the strip discarded recursive state because a
|
|
2534
|
+
* non-finite value had reached it.
|
|
2535
|
+
*
|
|
2536
|
+
* Advisory telemetry, and the only thing that separates a degraded strip
|
|
2537
|
+
* from a clean one. A discard returns the affected state to its
|
|
2538
|
+
* post-reset value, so the strip recovers in silence and the output stays
|
|
2539
|
+
* finite and in range while carrying samples unrelated to the input;
|
|
2540
|
+
* nothing else reports that this happened.
|
|
2541
|
+
*
|
|
2542
|
+
* The count covers the strip's own state, its EQ, every insert it owns and
|
|
2543
|
+
* both of its meters. None of those is separately addressable here, so a
|
|
2544
|
+
* discard inside one is observable only through this number -- and a
|
|
2545
|
+
* meter that loses its loudness window then reports the floor, which is
|
|
2546
|
+
* exactly what a genuinely silent strip reports, so nothing else
|
|
2547
|
+
* distinguishes the two.
|
|
2548
|
+
*
|
|
2549
|
+
* A meter's own discard lags by one block: it checks its loudness state at
|
|
2550
|
+
* the top of a block, before consuming that block's samples, so the block
|
|
2551
|
+
* that corrupts it is not the block the count moves on -- read this again
|
|
2552
|
+
* after one more block has processed. The EQ and inserts have no such lag;
|
|
2553
|
+
* they discard at the end of their own process, in the same block that
|
|
2554
|
+
* carried the poison.
|
|
2555
|
+
*
|
|
2556
|
+
* Cumulative since the strip was created and never cleared, so two
|
|
2557
|
+
* readings bracket a span of audio. The unit is one processed block, never
|
|
2558
|
+
* a channel, so a stereo block that discards on both channels adds one and
|
|
2559
|
+
* the number does not depend on a dimension the caller did not choose.
|
|
2560
|
+
*
|
|
2561
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2562
|
+
*/
|
|
2563
|
+
stripNonFiniteDiscardCount(stripIndex) {
|
|
2564
|
+
return this.mixer.stripNonFiniteDiscardCount(stripIndex);
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Number of blocks in which a bus discarded recursive state because a
|
|
2568
|
+
* non-finite value had reached it. Same contract as
|
|
2569
|
+
* {@link stripNonFiniteDiscardCount}, for a bus: covers every insert the
|
|
2570
|
+
* bus owns and its meter, neither separately addressable, so a discard
|
|
2571
|
+
* inside one is observable only here. Cumulative across graph recompiles
|
|
2572
|
+
* -- the count lives with the bus, not the compiled node, so an unrelated
|
|
2573
|
+
* edit elsewhere in the mixer does not reset it.
|
|
2574
|
+
*
|
|
2575
|
+
* A bus's DSP record is created by the first {@link compile}. Throws for a
|
|
2576
|
+
* bus that has been declared with {@link addBus} but never compiled --
|
|
2577
|
+
* reading zero there would read as clean, and it is not. Also throws for
|
|
2578
|
+
* an unknown bus id.
|
|
2579
|
+
*
|
|
2580
|
+
* @param busId - Bus id, as passed to {@link addBus} or declared in scene JSON
|
|
2581
|
+
*/
|
|
2582
|
+
busNonFiniteDiscardCount(busId) {
|
|
2583
|
+
return this.mixer.busNonFiniteDiscardCount(busId);
|
|
2584
|
+
}
|
|
1755
2585
|
/**
|
|
1756
2586
|
* Schedule sample-accurate fader automation on a strip.
|
|
1757
2587
|
*
|
|
@@ -1806,6 +2636,11 @@ var Mixer = class _Mixer {
|
|
|
1806
2636
|
/**
|
|
1807
2637
|
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
1808
2638
|
* (oldest to newest).
|
|
2639
|
+
*
|
|
2640
|
+
* `maxPoints` must be a finite non-negative integer; anything else throws an
|
|
2641
|
+
* `InvalidParameter` error. It is a request rather than an allocation size —
|
|
2642
|
+
* a value beyond the strip's goniometer ring simply returns every point the
|
|
2643
|
+
* ring holds.
|
|
1809
2644
|
*/
|
|
1810
2645
|
readGoniometerLatest(stripIndex, maxPoints) {
|
|
1811
2646
|
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
@@ -1841,8 +2676,12 @@ var Mixer = class _Mixer {
|
|
|
1841
2676
|
}
|
|
1842
2677
|
return this.mixer.drainTailStereo(numSamples);
|
|
1843
2678
|
}
|
|
1844
|
-
/** Release the underlying WASM object.
|
|
2679
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1845
2680
|
delete() {
|
|
2681
|
+
if (this.released) {
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
this.released = true;
|
|
1846
2685
|
this.mixer.delete();
|
|
1847
2686
|
}
|
|
1848
2687
|
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
@@ -1859,10 +2698,19 @@ var RealtimeVoiceChanger = class {
|
|
|
1859
2698
|
* explicit {@link prepare} lifecycle for callers that configure later.
|
|
1860
2699
|
*/
|
|
1861
2700
|
constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
|
|
2701
|
+
this.released = false;
|
|
1862
2702
|
const module2 = getSonareModule();
|
|
1863
2703
|
this.changer = module2.createRealtimeVoiceChanger(config);
|
|
1864
2704
|
if (sampleRate !== void 0) {
|
|
1865
|
-
|
|
2705
|
+
let prepared = false;
|
|
2706
|
+
try {
|
|
2707
|
+
this.changer.prepare(sampleRate, maxBlockSize, channels);
|
|
2708
|
+
prepared = true;
|
|
2709
|
+
} finally {
|
|
2710
|
+
if (!prepared) {
|
|
2711
|
+
this.changer.delete();
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
1866
2714
|
}
|
|
1867
2715
|
}
|
|
1868
2716
|
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
@@ -1888,6 +2736,34 @@ var RealtimeVoiceChanger = class {
|
|
|
1888
2736
|
latencySamples() {
|
|
1889
2737
|
return this.changer.latencySamples();
|
|
1890
2738
|
}
|
|
2739
|
+
/**
|
|
2740
|
+
* Channel-blocks in which the chain discarded its own state because a
|
|
2741
|
+
* non-finite value had reached it.
|
|
2742
|
+
*
|
|
2743
|
+
* Advisory telemetry, and the only thing that separates a degraded stream
|
|
2744
|
+
* from a clean one. Every stage of this chain leaves an in-domain finite
|
|
2745
|
+
* value where a non-finite one was — the input scrub and the
|
|
2746
|
+
* inter-sample-peak limiter substitute silence, the sample-domain limiter
|
|
2747
|
+
* folds an infinity onto its ceiling — so the output stays finite, in range
|
|
2748
|
+
* and free of any error while carrying samples unrelated to the input. A
|
|
2749
|
+
* non-zero count is what says the samples in between were not computed from
|
|
2750
|
+
* what you supplied.
|
|
2751
|
+
*
|
|
2752
|
+
* Monotonic for the lifetime of the instance. The unit is one processed
|
|
2753
|
+
* block, never a channel, so a stereo block that discards on both channels
|
|
2754
|
+
* adds one and the number does not depend on a dimension you did not choose.
|
|
2755
|
+
*
|
|
2756
|
+
* @example
|
|
2757
|
+
* ```ts
|
|
2758
|
+
* changer.processInterleaved(block, 2);
|
|
2759
|
+
* if (changer.nonFiniteDiscardCount() > 0) {
|
|
2760
|
+
* // the audio just produced is not a function of `block`
|
|
2761
|
+
* }
|
|
2762
|
+
* ```
|
|
2763
|
+
*/
|
|
2764
|
+
nonFiniteDiscardCount() {
|
|
2765
|
+
return this.changer.nonFiniteDiscardCount();
|
|
2766
|
+
}
|
|
1891
2767
|
/**
|
|
1892
2768
|
* Monotonically increases whenever {@link prepare} can replace the native
|
|
1893
2769
|
* scratch buffers. Cached WASM heap views must be reacquired after it changes.
|
|
@@ -2055,14 +2931,24 @@ var RealtimeVoiceChanger = class {
|
|
|
2055
2931
|
}
|
|
2056
2932
|
};
|
|
2057
2933
|
}
|
|
2934
|
+
/** Releases the native handle. Idempotent, as the Node facade is. */
|
|
2058
2935
|
delete() {
|
|
2936
|
+
if (this.released) {
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
this.released = true;
|
|
2059
2940
|
this.changer.delete();
|
|
2060
2941
|
}
|
|
2942
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
2943
|
+
destroy() {
|
|
2944
|
+
this.delete();
|
|
2945
|
+
}
|
|
2061
2946
|
};
|
|
2062
2947
|
|
|
2063
2948
|
// src/streaming_processors.ts
|
|
2064
2949
|
var StreamingMasteringChain = class {
|
|
2065
2950
|
constructor(config) {
|
|
2951
|
+
this.released = false;
|
|
2066
2952
|
const module2 = getSonareModule();
|
|
2067
2953
|
const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
|
|
2068
2954
|
this.chain = module2.createStreamingMasteringChain({
|
|
@@ -2121,10 +3007,75 @@ var StreamingMasteringChain = class {
|
|
|
2121
3007
|
stageNames() {
|
|
2122
3008
|
return this.chain.stageNames();
|
|
2123
3009
|
}
|
|
2124
|
-
/**
|
|
3010
|
+
/**
|
|
3011
|
+
* Samples a stage replaced with a finite in-domain one, keeping the output
|
|
3012
|
+
* finite and in range.
|
|
3013
|
+
*
|
|
3014
|
+
* A non-finite sample supplied by the caller is rejected before any stage
|
|
3015
|
+
* runs, so a replacement is always of a value a stage itself produced.
|
|
3016
|
+
*
|
|
3017
|
+
* Only the true-peak limiters replace anything, so with the maximizer's
|
|
3018
|
+
* limiter and the loudness stage both disabled a zero here means no stage was
|
|
3019
|
+
* able to replace anything rather than that nothing needed replacing.
|
|
3020
|
+
*
|
|
3021
|
+
* Cumulative over every block since {@link prepare}, and aggregated over the
|
|
3022
|
+
* stages and channels, so it identifies neither which block nor which stage.
|
|
3023
|
+
* Read it per block and compare against the previous reading to localize one.
|
|
3024
|
+
*
|
|
3025
|
+
* {@link prepare} rebuilds the stages and so clears it; {@link reset} does
|
|
3026
|
+
* not, because it drops processor state without rebuilding.
|
|
3027
|
+
*
|
|
3028
|
+
* @example
|
|
3029
|
+
* ```typescript
|
|
3030
|
+
* chain.processMono(block);
|
|
3031
|
+
* if (chain.nonFiniteSubstitutionCount() > previous) {
|
|
3032
|
+
* // the block just produced is not derived from `block` everywhere
|
|
3033
|
+
* }
|
|
3034
|
+
* ```
|
|
3035
|
+
*/
|
|
3036
|
+
nonFiniteSubstitutionCount() {
|
|
3037
|
+
return this.chain.nonFiniteSubstitutionCount();
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Processing calls in which a stage discarded its own recursive state
|
|
3041
|
+
* because a non-finite value had reached it.
|
|
3042
|
+
*
|
|
3043
|
+
* The companion to {@link nonFiniteSubstitutionCount}, and not the same
|
|
3044
|
+
* measurement -- a caller who assumes they are will read one and think
|
|
3045
|
+
* they have the other. That one counts SAMPLES a stage replaced and so
|
|
3046
|
+
* sums across stages; a discard is a whole stage returning to its
|
|
3047
|
+
* post-reset value and is counted once per call however many stages did
|
|
3048
|
+
* it. A stage may run more than once per call, which is why this is a
|
|
3049
|
+
* delta over the call and never a sum.
|
|
3050
|
+
*
|
|
3051
|
+
* Non-finite input is rejected before any stage runs, so what a stage
|
|
3052
|
+
* discards is always state it produced itself -- a finite sample large
|
|
3053
|
+
* enough to overflow inside a filter, most often. Unlike the substitution
|
|
3054
|
+
* count every stage can contribute, so a zero here means no stage
|
|
3055
|
+
* discarded rather than that none could.
|
|
3056
|
+
*
|
|
3057
|
+
* Both {@link processMono}/{@link processStereo} and
|
|
3058
|
+
* {@link flushMono}/{@link flushStereo} count, since a flush drives the
|
|
3059
|
+
* same stages. {@link prepare} rebuilds the stages and so clears it (as it
|
|
3060
|
+
* does {@link nonFiniteSubstitutionCount}, so the two counters on one
|
|
3061
|
+
* handle share an epoch); {@link reset} does not, because it drops
|
|
3062
|
+
* processor state without rebuilding.
|
|
3063
|
+
*/
|
|
3064
|
+
nonFiniteDiscardCount() {
|
|
3065
|
+
return this.chain.nonFiniteDiscardCount();
|
|
3066
|
+
}
|
|
3067
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
2125
3068
|
delete() {
|
|
3069
|
+
if (this.released) {
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
this.released = true;
|
|
2126
3073
|
this.chain.delete();
|
|
2127
3074
|
}
|
|
3075
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
3076
|
+
destroy() {
|
|
3077
|
+
this.delete();
|
|
3078
|
+
}
|
|
2128
3079
|
};
|
|
2129
3080
|
|
|
2130
3081
|
// src/index.ts
|
|
@@ -2175,11 +3126,11 @@ function buildTempoSync(tempoBpm, timeSignature, tempoSegments, timeSignatureSeg
|
|
|
2175
3126
|
};
|
|
2176
3127
|
}
|
|
2177
3128
|
function resolveTargetId(target) {
|
|
2178
|
-
|
|
2179
|
-
|
|
3129
|
+
const value = typeof target === "number" ? target : target.trim() === "" ? Number.NaN : Number(target);
|
|
3130
|
+
if (!Number.isInteger(value)) {
|
|
3131
|
+
throw new RangeError(`target id must be an integer, got ${JSON.stringify(target)}`);
|
|
2180
3132
|
}
|
|
2181
|
-
|
|
2182
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
3133
|
+
return value;
|
|
2183
3134
|
}
|
|
2184
3135
|
function resolveParamId(parameters, nodeId, param) {
|
|
2185
3136
|
if (typeof param === "number") {
|
|
@@ -2222,101 +3173,6 @@ function setAutomationLane(ctx, paramId, points) {
|
|
|
2222
3173
|
ctx.postSync({ type: "syncAutomation", paramId, points: sorted });
|
|
2223
3174
|
}
|
|
2224
3175
|
|
|
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
3176
|
// src/worklet/protocol.ts
|
|
2321
3177
|
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
2322
3178
|
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
@@ -2403,11 +3259,9 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
2403
3259
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MetronomeOverflow"] = 18] = "MetronomeOverflow";
|
|
2404
3260
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["InvalidCommand"] = 19] = "InvalidCommand";
|
|
2405
3261
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MaxChannelsExceeded"] = 20] = "MaxChannelsExceeded";
|
|
3262
|
+
SonareEngineTelemetryError2[SonareEngineTelemetryError2["ParameterBaseOverflow"] = 21] = "ParameterBaseOverflow";
|
|
2406
3263
|
return SonareEngineTelemetryError2;
|
|
2407
3264
|
})(SonareEngineTelemetryError || {});
|
|
2408
|
-
function toDb(value) {
|
|
2409
|
-
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
2410
|
-
}
|
|
2411
3265
|
function isRecord(value) {
|
|
2412
3266
|
return typeof value === "object" && value !== null;
|
|
2413
3267
|
}
|
|
@@ -2886,6 +3740,16 @@ function clipPageRequestRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
|
2886
3740
|
function recordOffset(index, capacity, recordBytes) {
|
|
2887
3741
|
return index % capacity * recordBytes;
|
|
2888
3742
|
}
|
|
3743
|
+
function isUint32Slot(value) {
|
|
3744
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3745
|
+
}
|
|
3746
|
+
function toUint32Slot(value, fallback, name) {
|
|
3747
|
+
const resolved = value ?? fallback;
|
|
3748
|
+
if (!isUint32Slot(resolved)) {
|
|
3749
|
+
throw new RangeError(`${name} must be an integer within [0, 4294967295]`);
|
|
3750
|
+
}
|
|
3751
|
+
return resolved;
|
|
3752
|
+
}
|
|
2889
3753
|
function toSafeInteger(value, fallback) {
|
|
2890
3754
|
const resolved = typeof value === "bigint" ? Number(value) : value;
|
|
2891
3755
|
if (resolved === void 0) {
|
|
@@ -2905,8 +3769,8 @@ function readInt64Words(view, offset) {
|
|
|
2905
3769
|
return view.getInt32(offset + 4, true) * 4294967296 + view.getUint32(offset, true);
|
|
2906
3770
|
}
|
|
2907
3771
|
function writeEngineCommandRecord(view, offset, command) {
|
|
2908
|
-
view.setUint32(offset, command.type, true);
|
|
2909
|
-
view.setUint32(offset + 4, command.targetId
|
|
3772
|
+
view.setUint32(offset, toUint32Slot(command.type, 0, "type"), true);
|
|
3773
|
+
view.setUint32(offset + 4, toUint32Slot(command.targetId, 0, "targetId"), true);
|
|
2910
3774
|
writeInt64Words(view, offset + 8, toSafeInteger(command.sampleTime, -1));
|
|
2911
3775
|
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
2912
3776
|
writeInt64Words(view, offset + 24, toSafeInteger(command.argInt, 0));
|
|
@@ -2975,47 +3839,341 @@ function magnitudeToDb(value) {
|
|
|
2975
3839
|
return value > 1e-12 ? 20 * Math.log10(value) : -120;
|
|
2976
3840
|
}
|
|
2977
3841
|
|
|
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");
|
|
3842
|
+
// src/worklet/guards.ts
|
|
3843
|
+
function isWorkletMessage(value) {
|
|
3844
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3845
|
+
return false;
|
|
2994
3846
|
}
|
|
2995
|
-
|
|
2996
|
-
return ctx.sendCommand({
|
|
2997
|
-
type: 13 /* ArmRecord */,
|
|
2998
|
-
targetId: 0,
|
|
2999
|
-
sampleTime: -1,
|
|
3000
|
-
argInt: enabled ? 1 : 0
|
|
3001
|
-
});
|
|
3847
|
+
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
3002
3848
|
}
|
|
3003
|
-
function
|
|
3004
|
-
|
|
3005
|
-
const outSample = ctx.offlineEngine.sampleAtPpq(outPpq);
|
|
3006
|
-
ctx.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
3007
|
-
return ctx.sendCommand({
|
|
3008
|
-
type: 14 /* Punch */,
|
|
3009
|
-
sampleTime: -1,
|
|
3010
|
-
argInt: inSample,
|
|
3011
|
-
argFloat: outSample
|
|
3012
|
-
});
|
|
3849
|
+
function isEngineCommandRecord(value) {
|
|
3850
|
+
return isRecord(value) && typeof value.type === "number";
|
|
3013
3851
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3852
|
+
var ENGINE_SYNC_MESSAGE_TYPES = {
|
|
3853
|
+
destroy: true,
|
|
3854
|
+
syncAutomation: true,
|
|
3855
|
+
syncBuiltinInstrument: true,
|
|
3856
|
+
syncBusStripDualPan: true,
|
|
3857
|
+
syncBusStripEqBand: true,
|
|
3858
|
+
syncBusStripInsertBypassed: true,
|
|
3859
|
+
syncBusStripInsertParamByName: true,
|
|
3860
|
+
syncBusStripPan: true,
|
|
3861
|
+
syncBusStripPanLaw: true,
|
|
3862
|
+
syncBusStripPanMode: true,
|
|
3863
|
+
syncCapture: true,
|
|
3864
|
+
syncClearMidiFx: true,
|
|
3865
|
+
syncClearMidiInputSource: true,
|
|
3866
|
+
syncClipPage: true,
|
|
3867
|
+
syncClipPageClear: true,
|
|
3868
|
+
syncClipPageCommit: true,
|
|
3869
|
+
syncClipPageDestroy: true,
|
|
3870
|
+
syncClipPagePrefetchFrames: true,
|
|
3871
|
+
syncClipPageProvider: true,
|
|
3872
|
+
syncClips: true,
|
|
3873
|
+
syncClipsDelta: true,
|
|
3874
|
+
syncExternalMidiClock: true,
|
|
3875
|
+
syncLoadSoundFont: true,
|
|
3876
|
+
syncMarkers: true,
|
|
3877
|
+
syncMasterStripEqBand: true,
|
|
3878
|
+
syncMasterStripInsertBypassed: true,
|
|
3879
|
+
syncMasterStripInsertParamByName: true,
|
|
3880
|
+
syncMetronome: true,
|
|
3881
|
+
syncMidiCc: true,
|
|
3882
|
+
syncMidiCcBinding: true,
|
|
3883
|
+
syncMidiChannelPressure: true,
|
|
3884
|
+
syncMidiClips: true,
|
|
3885
|
+
syncMidiDestinationExternal: true,
|
|
3886
|
+
syncMidiFx: true,
|
|
3887
|
+
syncMidiInputChannelPressure: true,
|
|
3888
|
+
syncMidiInputCc: true,
|
|
3889
|
+
syncMidiInputNoteOff: true,
|
|
3890
|
+
syncMidiInputNoteOn: true,
|
|
3891
|
+
syncMidiInputPitchBend: true,
|
|
3892
|
+
syncMidiInputPolyPressure: true,
|
|
3893
|
+
syncMidiInputSource: true,
|
|
3894
|
+
syncMidiNoteOff: true,
|
|
3895
|
+
syncMidiNoteOn: true,
|
|
3896
|
+
syncMidiPanic: true,
|
|
3897
|
+
syncMidiPitchBend: true,
|
|
3898
|
+
syncMidiPolyPressure: true,
|
|
3899
|
+
syncMidiSysex: true,
|
|
3900
|
+
syncMidiUmp: true,
|
|
3901
|
+
syncMixer: true,
|
|
3902
|
+
syncParameters: true,
|
|
3903
|
+
syncSf2Instrument: true,
|
|
3904
|
+
syncSynthInstrument: true,
|
|
3905
|
+
syncTempo: true,
|
|
3906
|
+
syncTrackStripChannelDelaySamples: true,
|
|
3907
|
+
syncTrackStripDualPan: true,
|
|
3908
|
+
syncTrackStripEqBand: true,
|
|
3909
|
+
syncTrackStripInsertBypassed: true,
|
|
3910
|
+
syncTrackStripInsertParamByName: true,
|
|
3911
|
+
syncTrackStripPan: true,
|
|
3912
|
+
syncTrackStripPanLaw: true,
|
|
3913
|
+
syncTrackStripPanMode: true,
|
|
3914
|
+
syncWarpVoiceCapacity: true
|
|
3915
|
+
};
|
|
3916
|
+
var engineSyncMessageTypes = new Set(Object.keys(ENGINE_SYNC_MESSAGE_TYPES));
|
|
3917
|
+
function isEngineSyncMessage(value) {
|
|
3918
|
+
return isRecord(value) && typeof value.type === "string" && engineSyncMessageTypes.has(value.type);
|
|
3016
3919
|
}
|
|
3017
|
-
function
|
|
3018
|
-
return
|
|
3920
|
+
function isEngineCaptureRequestMessage(value) {
|
|
3921
|
+
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
3922
|
+
}
|
|
3923
|
+
function isCaptureStatus(value) {
|
|
3924
|
+
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);
|
|
3925
|
+
}
|
|
3926
|
+
function isCaptureChannel(value) {
|
|
3927
|
+
return value instanceof Float32Array && typeof ArrayBuffer !== "undefined" && value.buffer instanceof ArrayBuffer;
|
|
3928
|
+
}
|
|
3929
|
+
function isCaptureChannels(value) {
|
|
3930
|
+
return Array.isArray(value) && value.every((channel) => isCaptureChannel(channel));
|
|
3931
|
+
}
|
|
3932
|
+
function hasOwn(value, key) {
|
|
3933
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
3934
|
+
}
|
|
3935
|
+
function engineCaptureResponseRequestId(value) {
|
|
3936
|
+
if (!isRecord(value) || value.type !== "captureResponse" || typeof value.requestId !== "number" || !Number.isSafeInteger(value.requestId)) {
|
|
3937
|
+
return void 0;
|
|
3938
|
+
}
|
|
3939
|
+
return value.requestId;
|
|
3940
|
+
}
|
|
3941
|
+
function isEngineCaptureResponseMessage(value) {
|
|
3942
|
+
const requestId = engineCaptureResponseRequestId(value);
|
|
3943
|
+
if (requestId === void 0 || !isRecord(value)) {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
if (value.ok === false) {
|
|
3947
|
+
return typeof value.error === "string" && !hasOwn(value, "status") && !hasOwn(value, "channels");
|
|
3948
|
+
}
|
|
3949
|
+
if (value.ok !== true || hasOwn(value, "error")) {
|
|
3950
|
+
return false;
|
|
3951
|
+
}
|
|
3952
|
+
const hasStatus = hasOwn(value, "status");
|
|
3953
|
+
const hasChannels = hasOwn(value, "channels");
|
|
3954
|
+
if (hasStatus && hasChannels) {
|
|
3955
|
+
return false;
|
|
3956
|
+
}
|
|
3957
|
+
if (hasStatus) {
|
|
3958
|
+
return isCaptureStatus(value.status);
|
|
3959
|
+
}
|
|
3960
|
+
if (hasChannels) {
|
|
3961
|
+
return isCaptureChannels(value.channels);
|
|
3962
|
+
}
|
|
3963
|
+
return true;
|
|
3964
|
+
}
|
|
3965
|
+
function isEngineCaptureResponseForOperation(response, op) {
|
|
3966
|
+
if (!response.ok) {
|
|
3967
|
+
return true;
|
|
3968
|
+
}
|
|
3969
|
+
switch (op) {
|
|
3970
|
+
case "status":
|
|
3971
|
+
return "status" in response;
|
|
3972
|
+
case "read":
|
|
3973
|
+
return "channels" in response;
|
|
3974
|
+
case "reset":
|
|
3975
|
+
return !("status" in response) && !("channels" in response);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
function isEngineTransportRequestMessage(value) {
|
|
3979
|
+
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
3980
|
+
}
|
|
3981
|
+
function isEngineTransportResponseMessage(value) {
|
|
3982
|
+
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
3983
|
+
}
|
|
3984
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
3985
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3986
|
+
return false;
|
|
3987
|
+
}
|
|
3988
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
3989
|
+
}
|
|
3990
|
+
var isOptionalNumber = (value) => value === void 0 || typeof value === "number";
|
|
3991
|
+
function isPlaybackMessage(value) {
|
|
3992
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3993
|
+
return false;
|
|
3994
|
+
}
|
|
3995
|
+
switch (value.type) {
|
|
3996
|
+
case "config":
|
|
3997
|
+
return typeof value.config === "string";
|
|
3998
|
+
case "orientation":
|
|
3999
|
+
return typeof value.yaw === "number" && isOptionalNumber(value.pitch) && isOptionalNumber(value.roll);
|
|
4000
|
+
case "reset":
|
|
4001
|
+
case "diagnostics":
|
|
4002
|
+
case "destroy":
|
|
4003
|
+
return true;
|
|
4004
|
+
default:
|
|
4005
|
+
return false;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
function isEngineTelemetryRecord(value) {
|
|
4009
|
+
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";
|
|
4010
|
+
}
|
|
4011
|
+
function isExternalMidiBatchMessage(value) {
|
|
4012
|
+
return isRecord(value) && value.type === "externalMidi" && Array.isArray(value.events);
|
|
4013
|
+
}
|
|
4014
|
+
function isClipPageRequestMessage(value) {
|
|
4015
|
+
return isRecord(value) && value.type === "clipPageRequest" && Array.isArray(value.requests) && value.requests.every(
|
|
4016
|
+
(request) => isRecord(request) && typeof request.clipId === "number" && typeof request.pageIndex === "number"
|
|
4017
|
+
);
|
|
4018
|
+
}
|
|
4019
|
+
function isMeterSnapshot(value) {
|
|
4020
|
+
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" && (typeof value.targetId === "number" || value.targetId === void 0);
|
|
4021
|
+
}
|
|
4022
|
+
function requireIntegerOption(value, fallback, name, minimum) {
|
|
4023
|
+
const resolved = value ?? fallback;
|
|
4024
|
+
if (!Number.isSafeInteger(resolved) || resolved < minimum) {
|
|
4025
|
+
throw new RangeError(`${name} must be an integer of at least ${minimum}`);
|
|
4026
|
+
}
|
|
4027
|
+
return resolved;
|
|
4028
|
+
}
|
|
4029
|
+
function requireInteger(value, fallback, name) {
|
|
4030
|
+
const resolved = value ?? fallback;
|
|
4031
|
+
if (!Number.isSafeInteger(resolved)) {
|
|
4032
|
+
throw new RangeError(`${name} must be an integer`);
|
|
4033
|
+
}
|
|
4034
|
+
return resolved;
|
|
4035
|
+
}
|
|
4036
|
+
function requireChannelCount(channelCount, fallback) {
|
|
4037
|
+
return requireIntegerOption(channelCount, fallback, "channelCount", 1);
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
// src/worklet/engine-offline.ts
|
|
4041
|
+
function buildCaptureConfig(options, defaultChannels) {
|
|
4042
|
+
return {
|
|
4043
|
+
// bufferFrames has no default; NaN makes the guard refuse an absent value.
|
|
4044
|
+
bufferFrames: requireIntegerOption(options.bufferFrames, Number.NaN, "bufferFrames", 1),
|
|
4045
|
+
channels: requireChannelCount(options.channels, defaultChannels),
|
|
4046
|
+
source: options.source ?? "output",
|
|
4047
|
+
recordOffsetSamples: requireInteger(options.recordOffsetSamples, 0, "recordOffsetSamples"),
|
|
4048
|
+
inputMonitor: {
|
|
4049
|
+
enabled: Boolean(options.inputMonitor?.enabled),
|
|
4050
|
+
gain: options.inputMonitor?.gain ?? 1
|
|
4051
|
+
}
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
function buildTransportFacade(ctx) {
|
|
4055
|
+
return {
|
|
4056
|
+
play: (sampleTime = -1) => {
|
|
4057
|
+
const ok = ctx.realtimeNode.play(sampleTime);
|
|
4058
|
+
if (ok) {
|
|
4059
|
+
ctx.setTransportPlaying(true);
|
|
4060
|
+
}
|
|
4061
|
+
return ok;
|
|
4062
|
+
},
|
|
4063
|
+
stop: (sampleTime = -1) => {
|
|
4064
|
+
const ok = ctx.realtimeNode.stop(sampleTime);
|
|
4065
|
+
if (ok) {
|
|
4066
|
+
ctx.setTransportPlaying(false);
|
|
4067
|
+
ctx.flushPendingInstrumentSync();
|
|
4068
|
+
}
|
|
4069
|
+
return ok;
|
|
4070
|
+
},
|
|
4071
|
+
seekPpq: (ppq, sampleTime = -1) => {
|
|
4072
|
+
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
4073
|
+
const ok = ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
4074
|
+
ctx.flushOfflineMirror();
|
|
4075
|
+
return ok;
|
|
4076
|
+
},
|
|
4077
|
+
seekSeconds: (seconds, sampleTime = -1) => {
|
|
4078
|
+
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
4079
|
+
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
4080
|
+
const ok = ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
4081
|
+
ctx.flushOfflineMirror();
|
|
4082
|
+
return ok;
|
|
4083
|
+
},
|
|
4084
|
+
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
4085
|
+
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
4086
|
+
setLoop: (startPpq, endPpq, enabled = true) => ctx.setLoop(startPpq, endPpq, enabled)
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function normalizeTrackLanes(existing, lanes) {
|
|
4090
|
+
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
4091
|
+
const ids = [];
|
|
4092
|
+
for (const entry of entries) {
|
|
4093
|
+
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
4094
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
4095
|
+
}
|
|
4096
|
+
ids.push(entry.trackId);
|
|
4097
|
+
}
|
|
4098
|
+
if (new Set(ids).size !== ids.length) {
|
|
4099
|
+
throw new Error("Duplicate track id in mixer lane list");
|
|
4100
|
+
}
|
|
4101
|
+
for (let index = 0; index < existing.length; index++) {
|
|
4102
|
+
if (ids[index] !== existing[index]) {
|
|
4103
|
+
throw new Error(
|
|
4104
|
+
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
return { entries, ids };
|
|
4109
|
+
}
|
|
4110
|
+
function resolveMarkerSet(markers, nextMarkerId) {
|
|
4111
|
+
const resolved = [];
|
|
4112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4113
|
+
let counter = nextMarkerId;
|
|
4114
|
+
for (const marker2 of markers) {
|
|
4115
|
+
if (!Number.isFinite(marker2.ppq) || marker2.ppq < 0) {
|
|
4116
|
+
throw new RangeError(`Invalid marker ppq: ${String(marker2.ppq)}`);
|
|
4117
|
+
}
|
|
4118
|
+
if (marker2.id !== void 0) {
|
|
4119
|
+
if (!Number.isInteger(marker2.id) || marker2.id <= 0) {
|
|
4120
|
+
throw new RangeError(`Invalid marker id: ${String(marker2.id)}`);
|
|
4121
|
+
}
|
|
4122
|
+
if (seen.has(marker2.id)) {
|
|
4123
|
+
throw new Error(`Duplicate marker id: ${marker2.id}`);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
const id = marker2.id ?? counter++;
|
|
4127
|
+
seen.add(id);
|
|
4128
|
+
if (id >= counter) {
|
|
4129
|
+
counter = id + 1;
|
|
4130
|
+
}
|
|
4131
|
+
resolved.push({ id, ppq: marker2.ppq, name: marker2.name ?? "" });
|
|
4132
|
+
}
|
|
4133
|
+
return { resolved, nextMarkerId: counter };
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
// src/worklet/engine-capture-facade.ts
|
|
4137
|
+
function configureCapture(ctx, options) {
|
|
4138
|
+
const config = buildCaptureConfig(options, ctx.offlineChannelCount);
|
|
4139
|
+
ctx.offlineEngine.setCaptureBuffer(config.channels, config.bufferFrames);
|
|
4140
|
+
ctx.offlineEngine.setCaptureSource(config.source);
|
|
4141
|
+
ctx.offlineEngine.setRecordOffsetSamples(config.recordOffsetSamples);
|
|
4142
|
+
ctx.offlineEngine.setInputMonitor(config.inputMonitor.enabled, config.inputMonitor.gain);
|
|
4143
|
+
ctx.setCaptureConfig(config);
|
|
4144
|
+
ctx.postSync({ type: "syncCapture", ...config });
|
|
4145
|
+
}
|
|
4146
|
+
function armRecord(ctx, trackId, enabled) {
|
|
4147
|
+
if (trackId !== 0) {
|
|
4148
|
+
throw new RangeError("Capture is global; armRecord only accepts trackId 0");
|
|
4149
|
+
}
|
|
4150
|
+
if (enabled && !ctx.getCaptureConfig()) {
|
|
4151
|
+
throw new Error("Capture buffer is not configured");
|
|
4152
|
+
}
|
|
4153
|
+
ctx.offlineEngine.armCapture(enabled);
|
|
4154
|
+
return ctx.sendCommand({
|
|
4155
|
+
type: 13 /* ArmRecord */,
|
|
4156
|
+
targetId: 0,
|
|
4157
|
+
sampleTime: -1,
|
|
4158
|
+
argInt: enabled ? 1 : 0
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
function punch(ctx, inPpq, outPpq) {
|
|
4162
|
+
const inSample = ctx.offlineEngine.sampleAtPpq(inPpq);
|
|
4163
|
+
const outSample = ctx.offlineEngine.sampleAtPpq(outPpq);
|
|
4164
|
+
ctx.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
4165
|
+
return ctx.sendCommand({
|
|
4166
|
+
type: 14 /* Punch */,
|
|
4167
|
+
sampleTime: -1,
|
|
4168
|
+
argInt: inSample,
|
|
4169
|
+
argFloat: outSample
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
function captureStatus(ctx) {
|
|
4173
|
+
return ctx.realtimeNode.requestCaptureStatus();
|
|
4174
|
+
}
|
|
4175
|
+
function capturedAudio(ctx) {
|
|
4176
|
+
return ctx.realtimeNode.requestCapturedAudio();
|
|
3019
4177
|
}
|
|
3020
4178
|
async function resetCapture(ctx) {
|
|
3021
4179
|
ctx.offlineEngine.resetCapture();
|
|
@@ -3167,16 +4325,191 @@ function syncMarkers(ctx) {
|
|
|
3167
4325
|
}
|
|
3168
4326
|
|
|
3169
4327
|
// src/worklet/engine-mixer-facade.ts
|
|
4328
|
+
function emptyStripJson(target) {
|
|
4329
|
+
switch (target.kind) {
|
|
4330
|
+
case "track":
|
|
4331
|
+
return `{"version":1,"strips":[{"id":"track-${target.trackId}"}],"buses":[],"connections":[]}`;
|
|
4332
|
+
case "bus":
|
|
4333
|
+
return `{"version":1,"strips":[],"buses":[{"id":"bus-${target.busId}"}],"connections":[]}`;
|
|
4334
|
+
case "master":
|
|
4335
|
+
return '{"version":1,"strips":[{"id":"master"}],"buses":[],"connections":[]}';
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
function pruneStripSidechains(ctx, target, sceneJson) {
|
|
4339
|
+
const scene = JSON.parse(sceneJson);
|
|
4340
|
+
const entry = target.kind === "bus" ? scene.buses?.[0] : scene.strips?.[0];
|
|
4341
|
+
const insertCount = Array.isArray(entry?.inserts) ? entry.inserts.length : 0;
|
|
4342
|
+
if (target.kind === "track") {
|
|
4343
|
+
for (const [key, binding] of ctx.laneSidechains) {
|
|
4344
|
+
if (binding.trackId === target.trackId && binding.insertIndex >= insertCount) {
|
|
4345
|
+
ctx.laneSidechains.delete(key);
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
} else if (target.kind === "bus") {
|
|
4349
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
4350
|
+
if (binding.busId === target.busId && binding.insertIndex >= insertCount) {
|
|
4351
|
+
ctx.busSidechains.delete(key);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
} else {
|
|
4355
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
4356
|
+
if (binding.insertIndex >= insertCount) {
|
|
4357
|
+
ctx.masterSidechains.delete(key);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4362
|
+
function hasInsertParamOverrides(overrides, target) {
|
|
4363
|
+
for (const override of overrides.values()) {
|
|
4364
|
+
if (sameStripTarget(override.target, target)) {
|
|
4365
|
+
return true;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
return false;
|
|
4369
|
+
}
|
|
4370
|
+
function applyFullStripJson(engine, target, sceneJson, resetInserts) {
|
|
4371
|
+
const apply = (json) => {
|
|
4372
|
+
switch (target.kind) {
|
|
4373
|
+
case "track":
|
|
4374
|
+
engine.setTrackStripJson(target.trackId, json);
|
|
4375
|
+
break;
|
|
4376
|
+
case "bus":
|
|
4377
|
+
engine.setBusStripJson(target.busId, json);
|
|
4378
|
+
break;
|
|
4379
|
+
case "master":
|
|
4380
|
+
engine.setMasterStripJson(json);
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
};
|
|
4384
|
+
engine.applyCommandsDueNowPreservingFuture();
|
|
4385
|
+
apply(sceneJson);
|
|
4386
|
+
if (resetInserts) {
|
|
4387
|
+
switch (target.kind) {
|
|
4388
|
+
case "track":
|
|
4389
|
+
engine.clearTrackInsertParameterBases(target.trackId);
|
|
4390
|
+
break;
|
|
4391
|
+
case "bus":
|
|
4392
|
+
engine.clearBusInsertParameterBases(target.busId);
|
|
4393
|
+
break;
|
|
4394
|
+
case "master":
|
|
4395
|
+
engine.clearMasterInsertParameterBases();
|
|
4396
|
+
break;
|
|
4397
|
+
}
|
|
4398
|
+
apply(emptyStripJson(target));
|
|
4399
|
+
apply(sceneJson);
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
function insertParamOverrideKey(override) {
|
|
4403
|
+
const targetId = override.target.kind === "track" ? override.target.trackId : override.target.kind === "bus" ? override.target.busId : "master";
|
|
4404
|
+
return JSON.stringify([override.target.kind, targetId, override.insertIndex, override.paramName]);
|
|
4405
|
+
}
|
|
4406
|
+
function clearInsertParamOverrides(overrides, target) {
|
|
4407
|
+
for (const [key, override] of overrides) {
|
|
4408
|
+
if (sameStripTarget(override.target, target)) {
|
|
4409
|
+
overrides.delete(key);
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
function sameStripTarget(left, right) {
|
|
4414
|
+
if (left.kind !== right.kind) {
|
|
4415
|
+
return false;
|
|
4416
|
+
}
|
|
4417
|
+
if (left.kind === "track" && right.kind === "track") {
|
|
4418
|
+
return left.trackId === right.trackId;
|
|
4419
|
+
}
|
|
4420
|
+
if (left.kind === "bus" && right.kind === "bus") {
|
|
4421
|
+
return left.busId === right.busId;
|
|
4422
|
+
}
|
|
4423
|
+
return left.kind === "master" && right.kind === "master";
|
|
4424
|
+
}
|
|
4425
|
+
function flattenInsertParamOverride(override) {
|
|
4426
|
+
switch (override.target.kind) {
|
|
4427
|
+
case "track":
|
|
4428
|
+
return {
|
|
4429
|
+
kind: "track",
|
|
4430
|
+
trackId: override.target.trackId,
|
|
4431
|
+
insertIndex: override.insertIndex,
|
|
4432
|
+
paramName: override.paramName,
|
|
4433
|
+
value: override.value
|
|
4434
|
+
};
|
|
4435
|
+
case "bus":
|
|
4436
|
+
return {
|
|
4437
|
+
kind: "bus",
|
|
4438
|
+
busId: override.target.busId,
|
|
4439
|
+
insertIndex: override.insertIndex,
|
|
4440
|
+
paramName: override.paramName,
|
|
4441
|
+
value: override.value
|
|
4442
|
+
};
|
|
4443
|
+
case "master":
|
|
4444
|
+
return {
|
|
4445
|
+
kind: "master",
|
|
4446
|
+
insertIndex: override.insertIndex,
|
|
4447
|
+
paramName: override.paramName,
|
|
4448
|
+
value: override.value
|
|
4449
|
+
};
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
function cachedStripJson(ctx, target) {
|
|
4453
|
+
switch (target.kind) {
|
|
4454
|
+
case "track":
|
|
4455
|
+
return ctx.trackStripJson.get(target.trackId);
|
|
4456
|
+
case "bus":
|
|
4457
|
+
return ctx.busStripJson.get(target.busId);
|
|
4458
|
+
case "master":
|
|
4459
|
+
return ctx.getMasterStripJson();
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
function cacheStripJson(ctx, target, sceneJson) {
|
|
4463
|
+
switch (target.kind) {
|
|
4464
|
+
case "track":
|
|
4465
|
+
ctx.trackStripJson.set(target.trackId, sceneJson);
|
|
4466
|
+
return;
|
|
4467
|
+
case "bus":
|
|
4468
|
+
ctx.busStripJson.set(target.busId, sceneJson);
|
|
4469
|
+
return;
|
|
4470
|
+
case "master":
|
|
4471
|
+
ctx.cacheMasterStripJson(sceneJson);
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
3170
4475
|
function mixerLanes(ctx) {
|
|
3171
4476
|
return buildMixerLanes(ctx.trackLaneIds, ctx.trackSends, ctx.trackOutputBus);
|
|
3172
4477
|
}
|
|
3173
|
-
function syncMixer(ctx) {
|
|
4478
|
+
function syncMixer(ctx, busesAlreadyApplied = false, forceInsertResets = []) {
|
|
3174
4479
|
const lanes = mixerLanes(ctx);
|
|
3175
4480
|
const buses = ctx.buses.map((bus) => ({ ...bus }));
|
|
3176
|
-
|
|
4481
|
+
if (!busesAlreadyApplied) {
|
|
4482
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
4483
|
+
}
|
|
3177
4484
|
if (lanes.length > 0) {
|
|
4485
|
+
ctx.offlineEngine.settleInsertParameters();
|
|
3178
4486
|
ctx.offlineEngine.setTrackLanes(lanes);
|
|
3179
4487
|
}
|
|
4488
|
+
if (forceInsertResets.length > 0) {
|
|
4489
|
+
for (const binding of ctx.laneSidechains.values()) {
|
|
4490
|
+
ctx.offlineEngine.setLaneSidechain(
|
|
4491
|
+
binding.trackId,
|
|
4492
|
+
binding.insertIndex,
|
|
4493
|
+
binding.sourceTrackId
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
for (const binding of ctx.busSidechains.values()) {
|
|
4497
|
+
ctx.offlineEngine.setBusSidechain(
|
|
4498
|
+
binding.busId,
|
|
4499
|
+
binding.insertIndex,
|
|
4500
|
+
binding.sourceKind,
|
|
4501
|
+
binding.sourceId
|
|
4502
|
+
);
|
|
4503
|
+
}
|
|
4504
|
+
for (const binding of ctx.masterSidechains.values()) {
|
|
4505
|
+
ctx.offlineEngine.setMasterSidechain(
|
|
4506
|
+
binding.insertIndex,
|
|
4507
|
+
binding.sourceKind,
|
|
4508
|
+
binding.sourceId
|
|
4509
|
+
);
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
replayInsertParamOverrides(ctx, lanes, buses);
|
|
3180
4513
|
const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
|
|
3181
4514
|
trackId,
|
|
3182
4515
|
sceneJson
|
|
@@ -3185,6 +4518,10 @@ function syncMixer(ctx) {
|
|
|
3185
4518
|
busId,
|
|
3186
4519
|
sceneJson
|
|
3187
4520
|
}));
|
|
4521
|
+
const insertParamOverrides = Array.from(
|
|
4522
|
+
ctx.insertParamOverrides.values(),
|
|
4523
|
+
flattenInsertParamOverride
|
|
4524
|
+
);
|
|
3188
4525
|
ctx.postSync({
|
|
3189
4526
|
type: "syncMixer",
|
|
3190
4527
|
lanes,
|
|
@@ -3192,9 +4529,49 @@ function syncMixer(ctx) {
|
|
|
3192
4529
|
trackStrips,
|
|
3193
4530
|
laneSidechains: Array.from(ctx.laneSidechains.values()),
|
|
3194
4531
|
busStrips,
|
|
3195
|
-
masterStripJson: ctx.getMasterStripJson()
|
|
4532
|
+
masterStripJson: ctx.getMasterStripJson(),
|
|
4533
|
+
busSidechains: Array.from(ctx.busSidechains.values()),
|
|
4534
|
+
masterSidechains: Array.from(ctx.masterSidechains.values()),
|
|
4535
|
+
...insertParamOverrides.length > 0 ? { insertParamOverrides } : {},
|
|
4536
|
+
...forceInsertResets.length > 0 ? { forceInsertResets } : {}
|
|
3196
4537
|
});
|
|
3197
4538
|
}
|
|
4539
|
+
function replayInsertParamOverrides(ctx, lanes = mixerLanes(ctx), buses = ctx.buses) {
|
|
4540
|
+
const activeTrackIds = new Set(lanes.map((lane) => lane.trackId));
|
|
4541
|
+
const activeBusIds = new Set(buses.map((bus) => bus.busId));
|
|
4542
|
+
for (const [key, override] of ctx.insertParamOverrides) {
|
|
4543
|
+
const active = override.target.kind === "track" ? activeTrackIds.has(override.target.trackId) : override.target.kind === "bus" ? activeBusIds.has(override.target.busId) : true;
|
|
4544
|
+
if (!active) {
|
|
4545
|
+
ctx.insertParamOverrides.delete(key);
|
|
4546
|
+
continue;
|
|
4547
|
+
}
|
|
4548
|
+
switch (override.target.kind) {
|
|
4549
|
+
case "track":
|
|
4550
|
+
ctx.offlineEngine.restoreTrackStripInsertParamByName(
|
|
4551
|
+
override.target.trackId,
|
|
4552
|
+
override.insertIndex,
|
|
4553
|
+
override.paramName,
|
|
4554
|
+
override.value
|
|
4555
|
+
);
|
|
4556
|
+
break;
|
|
4557
|
+
case "bus":
|
|
4558
|
+
ctx.offlineEngine.restoreBusStripInsertParamByName(
|
|
4559
|
+
override.target.busId,
|
|
4560
|
+
override.insertIndex,
|
|
4561
|
+
override.paramName,
|
|
4562
|
+
override.value
|
|
4563
|
+
);
|
|
4564
|
+
break;
|
|
4565
|
+
case "master":
|
|
4566
|
+
ctx.offlineEngine.restoreMasterStripInsertParamByName(
|
|
4567
|
+
override.insertIndex,
|
|
4568
|
+
override.paramName,
|
|
4569
|
+
override.value
|
|
4570
|
+
);
|
|
4571
|
+
break;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
3198
4575
|
function setTrackLanes(ctx, lanes) {
|
|
3199
4576
|
const { entries, ids } = normalizeTrackLanes(ctx.trackLaneIds, lanes);
|
|
3200
4577
|
for (const entry of entries) {
|
|
@@ -3234,143 +4611,116 @@ function setLaneSidechain(ctx, target, insertIndex, sourceTarget) {
|
|
|
3234
4611
|
const sourceIndex = ctx.ensureTrackLane(sourceTarget);
|
|
3235
4612
|
sourceTrackId = ctx.trackLaneIds[sourceIndex];
|
|
3236
4613
|
}
|
|
3237
|
-
if (sourceTrackId === 0) {
|
|
3238
|
-
ctx.laneSidechains.delete(key);
|
|
3239
|
-
} else {
|
|
3240
|
-
ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3241
|
-
}
|
|
3242
4614
|
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
laneSidechains
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
function setSends(ctx, target, sends) {
|
|
3250
|
-
const laneIndex = ctx.ensureTrackLane(target);
|
|
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) {
|
|
3269
|
-
ctx.ensureBus(busId);
|
|
3270
|
-
ctx.offlineEngine.setBusStripJson(busId, sceneJson);
|
|
3271
|
-
ctx.busStripJson.set(busId, sceneJson);
|
|
3272
|
-
ctx.syncMixer();
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
|
-
// src/worklet/guards.ts
|
|
3276
|
-
function isWorkletMessage(value) {
|
|
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;
|
|
3309
|
-
}
|
|
3310
|
-
return value.requestId;
|
|
3311
|
-
}
|
|
3312
|
-
function isEngineCaptureResponseMessage(value) {
|
|
3313
|
-
const requestId = engineCaptureResponseRequestId(value);
|
|
3314
|
-
if (requestId === void 0 || !isRecord(value)) {
|
|
3315
|
-
return false;
|
|
3316
|
-
}
|
|
3317
|
-
if (value.ok === false) {
|
|
3318
|
-
return typeof value.error === "string" && !hasOwn(value, "status") && !hasOwn(value, "channels");
|
|
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);
|
|
3333
|
-
}
|
|
3334
|
-
return true;
|
|
3335
|
-
}
|
|
3336
|
-
function isEngineCaptureResponseForOperation(response, op) {
|
|
3337
|
-
if (!response.ok) {
|
|
3338
|
-
return true;
|
|
3339
|
-
}
|
|
3340
|
-
switch (op) {
|
|
3341
|
-
case "status":
|
|
3342
|
-
return "status" in response;
|
|
3343
|
-
case "read":
|
|
3344
|
-
return "channels" in response;
|
|
3345
|
-
case "reset":
|
|
3346
|
-
return !("status" in response) && !("channels" in response);
|
|
4615
|
+
if (sourceTrackId === 0) {
|
|
4616
|
+
ctx.laneSidechains.delete(key);
|
|
4617
|
+
} else {
|
|
4618
|
+
ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3347
4619
|
}
|
|
4620
|
+
ctx.postSync({
|
|
4621
|
+
type: "syncMixer",
|
|
4622
|
+
sidechainDelta: true,
|
|
4623
|
+
lanes: ctx.mixerLanes(),
|
|
4624
|
+
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
4625
|
+
});
|
|
3348
4626
|
}
|
|
3349
|
-
function
|
|
3350
|
-
|
|
4627
|
+
function setBusSidechain(ctx, busId, insertIndex, kind, sourceId) {
|
|
4628
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
4629
|
+
ctx.ensureBus(busId);
|
|
4630
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
4631
|
+
ctx.offlineEngine.setBusSidechain(busId, insertIndex, sourceKind, sourceId);
|
|
4632
|
+
const key = `${busId}:${insertIndex}`;
|
|
4633
|
+
if (sourceId === 0) {
|
|
4634
|
+
ctx.busSidechains.delete(key);
|
|
4635
|
+
} else {
|
|
4636
|
+
ctx.busSidechains.set(key, { busId, insertIndex, sourceKind, sourceId });
|
|
4637
|
+
}
|
|
4638
|
+
ctx.postSync({
|
|
4639
|
+
type: "syncMixer",
|
|
4640
|
+
sidechainDelta: true,
|
|
4641
|
+
lanes: ctx.mixerLanes(),
|
|
4642
|
+
busSidechains: [{ busId, insertIndex, sourceKind, sourceId }]
|
|
4643
|
+
});
|
|
3351
4644
|
}
|
|
3352
|
-
function
|
|
3353
|
-
|
|
4645
|
+
function setMasterSidechain(ctx, insertIndex, kind, sourceId) {
|
|
4646
|
+
const sourceKind = sidechainSourceKindCode(kind);
|
|
4647
|
+
ensureSidechainSource(ctx, sourceKind, sourceId);
|
|
4648
|
+
ctx.offlineEngine.setMasterSidechain(insertIndex, sourceKind, sourceId);
|
|
4649
|
+
if (sourceId === 0) {
|
|
4650
|
+
ctx.masterSidechains.delete(insertIndex);
|
|
4651
|
+
} else {
|
|
4652
|
+
ctx.masterSidechains.set(insertIndex, { insertIndex, sourceKind, sourceId });
|
|
4653
|
+
}
|
|
4654
|
+
ctx.postSync({
|
|
4655
|
+
type: "syncMixer",
|
|
4656
|
+
sidechainDelta: true,
|
|
4657
|
+
lanes: ctx.mixerLanes(),
|
|
4658
|
+
masterSidechains: [{ insertIndex, sourceKind, sourceId }]
|
|
4659
|
+
});
|
|
3354
4660
|
}
|
|
3355
|
-
function
|
|
3356
|
-
if (
|
|
3357
|
-
return
|
|
4661
|
+
function ensureSidechainSource(ctx, sourceKind, sourceId) {
|
|
4662
|
+
if (sourceId === 0) {
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
if (sourceKind === 1) {
|
|
4666
|
+
ctx.ensureBus(sourceId);
|
|
4667
|
+
} else {
|
|
4668
|
+
ctx.ensureTrackLane(sourceId);
|
|
3358
4669
|
}
|
|
3359
|
-
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
3360
4670
|
}
|
|
3361
|
-
function
|
|
3362
|
-
|
|
4671
|
+
function setSends(ctx, target, sends) {
|
|
4672
|
+
const laneIndex = ctx.ensureTrackLane(target);
|
|
4673
|
+
const trackId = ctx.trackLaneIds[laneIndex];
|
|
4674
|
+
ctx.trackSends.set(
|
|
4675
|
+
trackId,
|
|
4676
|
+
sends.map((send) => ({ ...send }))
|
|
4677
|
+
);
|
|
4678
|
+
ctx.syncMixer();
|
|
3363
4679
|
}
|
|
3364
|
-
function
|
|
3365
|
-
|
|
4680
|
+
function setTrackBuses(ctx, buses) {
|
|
4681
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
4682
|
+
const retainedBusIds = new Set(buses.map((bus) => bus.busId));
|
|
4683
|
+
for (const bus of ctx.buses) {
|
|
4684
|
+
if (!retainedBusIds.has(bus.busId)) {
|
|
4685
|
+
ctx.clearInsertAutomationLanes({ kind: "bus", busId: bus.busId }, true);
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
4689
|
+
const activeBusIds = new Set(ctx.buses.map((bus) => bus.busId));
|
|
4690
|
+
for (const busId of ctx.busStripJson.keys()) {
|
|
4691
|
+
if (!activeBusIds.has(busId)) {
|
|
4692
|
+
ctx.busStripJson.delete(busId);
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
for (const [key, binding] of ctx.busSidechains) {
|
|
4696
|
+
if (!activeBusIds.has(binding.busId) || binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
|
|
4697
|
+
ctx.busSidechains.delete(key);
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
for (const [key, binding] of ctx.masterSidechains) {
|
|
4701
|
+
if (binding.sourceKind === 1 && !activeBusIds.has(binding.sourceId)) {
|
|
4702
|
+
ctx.masterSidechains.delete(key);
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
syncMixer(ctx, true);
|
|
3366
4706
|
}
|
|
3367
|
-
function
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
);
|
|
4707
|
+
function setBusGain(ctx, busId, db) {
|
|
4708
|
+
const busIndex = ctx.ensureBus(busId);
|
|
4709
|
+
ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
|
|
4710
|
+
return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
3371
4711
|
}
|
|
3372
|
-
function
|
|
3373
|
-
|
|
4712
|
+
function setBusStripJson(ctx, busId, sceneJson) {
|
|
4713
|
+
ctx.ensureBus(busId);
|
|
4714
|
+
const target = { kind: "bus", busId };
|
|
4715
|
+
const resetInserts = ctx.busStripJson.has(busId) && ctx.busStripJson.get(busId) !== sceneJson || hasInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
4716
|
+
applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
|
|
4717
|
+
if (resetInserts) {
|
|
4718
|
+
ctx.clearInsertAutomationLanes(target);
|
|
4719
|
+
}
|
|
4720
|
+
pruneStripSidechains(ctx, target, sceneJson);
|
|
4721
|
+
ctx.busStripJson.set(busId, sceneJson);
|
|
4722
|
+
clearInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
4723
|
+
ctx.syncMixer(resetInserts ? [target] : []);
|
|
3374
4724
|
}
|
|
3375
4725
|
|
|
3376
4726
|
// src/worklet/engine-node.ts
|
|
@@ -3387,15 +4737,17 @@ function isTrackMonitorMode(value) {
|
|
|
3387
4737
|
const mode = typeof value === "bigint" ? Number(value) : value;
|
|
3388
4738
|
return typeof mode === "number" && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
|
|
3389
4739
|
}
|
|
3390
|
-
function isTrackMonitorLaneIndex(value) {
|
|
3391
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3392
|
-
}
|
|
3393
4740
|
function isValidCommandRecord(command) {
|
|
3394
4741
|
const type = Number(command.type);
|
|
3395
4742
|
if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */) {
|
|
3396
4743
|
return false;
|
|
3397
4744
|
}
|
|
3398
|
-
return (
|
|
4745
|
+
return (
|
|
4746
|
+
// The record writer refuses a targetId outside the slot by throwing, and
|
|
4747
|
+
// this path answers false instead, so it must reject the same domain here.
|
|
4748
|
+
(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.
|
|
4749
|
+
(type !== 26 /* SetTrackMonitorMode */ || isUint32Slot(command.targetId) && isTrackMonitorMode(command.argInt))
|
|
4750
|
+
);
|
|
3399
4751
|
}
|
|
3400
4752
|
var AUDIO_WORKLET_RENDER_QUANTUM = 128;
|
|
3401
4753
|
function workletBlockSize(blockSize) {
|
|
@@ -3440,6 +4792,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3440
4792
|
this.resolveReady();
|
|
3441
4793
|
}
|
|
3442
4794
|
this.node.port.onmessage = (event) => {
|
|
4795
|
+
if (this.destroyed) {
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
3443
4798
|
const captureRequestId = engineCaptureResponseRequestId(event.data);
|
|
3444
4799
|
if (captureRequestId !== void 0) {
|
|
3445
4800
|
const pending = this.captureRequests.get(captureRequestId);
|
|
@@ -3521,14 +4876,59 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3521
4876
|
"SharedArrayBuffer mode requested but SharedArrayBuffer/Atomics are unavailable."
|
|
3522
4877
|
);
|
|
3523
4878
|
}
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
4879
|
+
const commandRingCapacity = requireIntegerOption(
|
|
4880
|
+
options.commandRingCapacity,
|
|
4881
|
+
128,
|
|
4882
|
+
"commandRingCapacity",
|
|
4883
|
+
1
|
|
4884
|
+
);
|
|
4885
|
+
if (commandRingCapacity > 65536) {
|
|
4886
|
+
throw new RangeError("commandRingCapacity must be at most 65536");
|
|
4887
|
+
}
|
|
4888
|
+
const telemetryRingCapacity = requireIntegerOption(
|
|
4889
|
+
options.telemetryRingCapacity,
|
|
4890
|
+
128,
|
|
4891
|
+
"telemetryRingCapacity",
|
|
4892
|
+
1
|
|
4893
|
+
);
|
|
4894
|
+
const meterRingCapacity = requireIntegerOption(
|
|
4895
|
+
options.meterRingCapacity,
|
|
4896
|
+
128,
|
|
4897
|
+
"meterRingCapacity",
|
|
4898
|
+
1
|
|
4899
|
+
);
|
|
4900
|
+
const scopeRingCapacity = requireIntegerOption(
|
|
4901
|
+
options.scopeRingCapacity,
|
|
4902
|
+
64,
|
|
4903
|
+
"scopeRingCapacity",
|
|
4904
|
+
1
|
|
4905
|
+
);
|
|
4906
|
+
const scopeBands = requireIntegerOption(options.scopeBands, 48, "scopeBands", 1);
|
|
4907
|
+
const clipPageRequestRingCapacity = requireIntegerOption(
|
|
4908
|
+
options.clipPageRequestRingCapacity,
|
|
4909
|
+
128,
|
|
4910
|
+
"clipPageRequestRingCapacity",
|
|
4911
|
+
1
|
|
4912
|
+
);
|
|
4913
|
+
const externalMidiRingCapacity = requireIntegerOption(
|
|
4914
|
+
options.externalMidiRingCapacity,
|
|
4915
|
+
256,
|
|
4916
|
+
"externalMidiRingCapacity",
|
|
4917
|
+
1
|
|
4918
|
+
);
|
|
4919
|
+
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(commandRingCapacity) : void 0;
|
|
4920
|
+
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(telemetryRingCapacity) : void 0;
|
|
4921
|
+
const meterRing = mode === "sab" ? createSonareMeterRingBuffer(meterRingCapacity) : void 0;
|
|
4922
|
+
const scopeIntervalFrames = requireIntegerOption(
|
|
4923
|
+
options.scopeIntervalFrames,
|
|
4924
|
+
0,
|
|
4925
|
+
"scopeIntervalFrames",
|
|
4926
|
+
0
|
|
4927
|
+
);
|
|
4928
|
+
const scopeRing = mode === "sab" && scopeIntervalFrames > 0 ? createSonareScopeRingBuffer(scopeRingCapacity, scopeBands) : void 0;
|
|
4929
|
+
const clipPageRequestRing = mode === "sab" ? createSonareClipPageRequestRingBuffer(clipPageRequestRingCapacity) : void 0;
|
|
4930
|
+
const externalMidiRing = mode === "sab" ? createSonareExternalMidiRingBuffer(externalMidiRingCapacity) : void 0;
|
|
4931
|
+
const channelCount = requireChannelCount(options.channelCount, 2);
|
|
3532
4932
|
const cueOutput = options.cueOutput === true;
|
|
3533
4933
|
const processorOptions = {
|
|
3534
4934
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
@@ -3805,6 +5205,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3805
5205
|
this.scopeListeners.clear();
|
|
3806
5206
|
this.midiOutListeners.clear();
|
|
3807
5207
|
this.clipPageRequestListeners.clear();
|
|
5208
|
+
this.syncErrorListeners.clear();
|
|
5209
|
+
this.node.port.onmessage = null;
|
|
3808
5210
|
}
|
|
3809
5211
|
emitTelemetry(telemetry) {
|
|
3810
5212
|
for (const listener of this.telemetryListeners) {
|
|
@@ -3944,6 +5346,9 @@ function resolveBusInsertAutomationId(ctx, busId, insertIndex, paramName) {
|
|
|
3944
5346
|
ctx.ensureBus(busId);
|
|
3945
5347
|
return ctx.offlineEngine.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
3946
5348
|
}
|
|
5349
|
+
function resolveInstrumentAutomationId(ctx, destinationId, paramName) {
|
|
5350
|
+
return ctx.offlineEngine.resolveInstrumentAutomationId(destinationId, paramName);
|
|
5351
|
+
}
|
|
3947
5352
|
function automationLaneCount(ctx) {
|
|
3948
5353
|
return ctx.offlineEngine.automationLaneCount();
|
|
3949
5354
|
}
|
|
@@ -3968,14 +5373,41 @@ function clearParameters(ctx) {
|
|
|
3968
5373
|
function trackIdFor(ctx, target) {
|
|
3969
5374
|
return ctx.trackLaneIds[ctx.ensureTrackLane(target)];
|
|
3970
5375
|
}
|
|
5376
|
+
function mergeStripJson(ctx, target, update) {
|
|
5377
|
+
const scene = JSON.parse(ctx.readStripJson(target) ?? emptyStripJson(target));
|
|
5378
|
+
update(target.kind === "bus" ? scene.buses[0] : scene.strips[0]);
|
|
5379
|
+
ctx.writeStripJson(target, JSON.stringify(scene));
|
|
5380
|
+
}
|
|
5381
|
+
function mergeEqBand(ctx, target, bandIndex, bandJson) {
|
|
5382
|
+
mergeStripJson(ctx, target, (entry) => {
|
|
5383
|
+
const eq = entry.eq ?? { enabled: true, bands: [] };
|
|
5384
|
+
const bands = eq.bands ?? [];
|
|
5385
|
+
while (bands.length < bandIndex) {
|
|
5386
|
+
bands.push({});
|
|
5387
|
+
}
|
|
5388
|
+
bands[bandIndex] = JSON.parse(bandJson);
|
|
5389
|
+
entry.eq = { ...eq, bands };
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
function setInsertParamByName(ctx, target, insertIndex, paramName, value, applyNative, message) {
|
|
5393
|
+
applyNative();
|
|
5394
|
+
const override = { target, insertIndex, paramName, value };
|
|
5395
|
+
ctx.insertParamOverrides.set(insertParamOverrideKey(override), override);
|
|
5396
|
+
ctx.postSync(message);
|
|
5397
|
+
}
|
|
3971
5398
|
function setTrackStripJson(ctx, trackId, sceneJson, trackStripJson) {
|
|
3972
|
-
|
|
5399
|
+
const target = { kind: "track", trackId };
|
|
5400
|
+
const resetInserts = trackStripJson.has(trackId) && trackStripJson.get(trackId) !== sceneJson || hasInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
5401
|
+
applyFullStripJson(ctx.offlineEngine, target, sceneJson, resetInserts);
|
|
5402
|
+
clearInsertParamOverrides(ctx.insertParamOverrides, target);
|
|
3973
5403
|
trackStripJson.set(trackId, sceneJson);
|
|
5404
|
+
return { resetInserts };
|
|
3974
5405
|
}
|
|
3975
5406
|
function setTrackStripEqBand(ctx, target, bandIndex, band) {
|
|
3976
5407
|
const trackId = trackIdFor(ctx, target);
|
|
3977
5408
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3978
5409
|
ctx.offlineEngine.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
5410
|
+
mergeEqBand(ctx, { kind: "track", trackId }, bandIndex, bandJson);
|
|
3979
5411
|
ctx.postSync({ type: "syncTrackStripEqBand", trackId, bandIndex, bandJson });
|
|
3980
5412
|
}
|
|
3981
5413
|
function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -3991,39 +5423,68 @@ function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOn
|
|
|
3991
5423
|
}
|
|
3992
5424
|
function setTrackStripInsertParamByName(ctx, target, insertIndex, paramName, value) {
|
|
3993
5425
|
const trackId = trackIdFor(ctx, target);
|
|
3994
|
-
|
|
3995
|
-
|
|
5426
|
+
setInsertParamByName(
|
|
5427
|
+
ctx,
|
|
5428
|
+
{ kind: "track", trackId },
|
|
5429
|
+
insertIndex,
|
|
5430
|
+
paramName,
|
|
5431
|
+
value,
|
|
5432
|
+
() => ctx.offlineEngine.restoreTrackStripInsertParamByName(trackId, insertIndex, paramName, value),
|
|
5433
|
+
{ type: "syncTrackStripInsertParamByName", trackId, insertIndex, paramName, value }
|
|
5434
|
+
);
|
|
3996
5435
|
}
|
|
3997
5436
|
function setTrackStripPan(ctx, target, pan) {
|
|
3998
5437
|
const trackId = trackIdFor(ctx, target);
|
|
3999
5438
|
ctx.offlineEngine.setTrackStripPan(trackId, pan);
|
|
5439
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5440
|
+
entry.pan = pan;
|
|
5441
|
+
});
|
|
4000
5442
|
ctx.postSync({ type: "syncTrackStripPan", trackId, pan });
|
|
4001
5443
|
}
|
|
4002
5444
|
function setTrackStripPanLaw(ctx, target, panLaw) {
|
|
4003
5445
|
const trackId = trackIdFor(ctx, target);
|
|
4004
5446
|
const code = panLawCode(panLaw);
|
|
4005
5447
|
ctx.offlineEngine.setTrackStripPanLaw(trackId, code);
|
|
5448
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5449
|
+
entry.panLaw = code;
|
|
5450
|
+
});
|
|
4006
5451
|
ctx.postSync({ type: "syncTrackStripPanLaw", trackId, panLaw: code });
|
|
4007
5452
|
}
|
|
4008
5453
|
function setTrackStripPanMode(ctx, target, panMode) {
|
|
4009
5454
|
const trackId = trackIdFor(ctx, target);
|
|
4010
5455
|
const code = panModeCode(panMode);
|
|
4011
5456
|
ctx.offlineEngine.setTrackStripPanMode(trackId, code);
|
|
5457
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5458
|
+
entry.panMode = code;
|
|
5459
|
+
});
|
|
4012
5460
|
ctx.postSync({ type: "syncTrackStripPanMode", trackId, panMode: code });
|
|
4013
5461
|
}
|
|
4014
5462
|
function setTrackStripDualPan(ctx, target, leftPan, rightPan) {
|
|
4015
5463
|
const trackId = trackIdFor(ctx, target);
|
|
4016
5464
|
ctx.offlineEngine.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
5465
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5466
|
+
entry.dualPanLeft = leftPan;
|
|
5467
|
+
entry.dualPanRight = rightPan;
|
|
5468
|
+
});
|
|
4017
5469
|
ctx.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
|
|
4018
5470
|
}
|
|
4019
5471
|
function setTrackStripChannelDelaySamples(ctx, target, delaySamples) {
|
|
4020
5472
|
const trackId = trackIdFor(ctx, target);
|
|
4021
5473
|
ctx.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
5474
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5475
|
+
entry.channelDelaySamples = delaySamples;
|
|
5476
|
+
});
|
|
4022
5477
|
ctx.postSync({ type: "syncTrackStripChannelDelaySamples", trackId, delaySamples });
|
|
4023
5478
|
}
|
|
5479
|
+
function cacheMasterStripScalar(ctx, field, value) {
|
|
5480
|
+
mergeStripJson(ctx, { kind: "master" }, (entry) => {
|
|
5481
|
+
entry[field] = value;
|
|
5482
|
+
});
|
|
5483
|
+
}
|
|
4024
5484
|
function setMasterStripEqBand(ctx, bandIndex, band) {
|
|
4025
5485
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
4026
5486
|
ctx.offlineEngine.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
5487
|
+
mergeEqBand(ctx, { kind: "master" }, bandIndex, bandJson);
|
|
4027
5488
|
ctx.postSync({ type: "syncMasterStripEqBand", bandIndex, bandJson });
|
|
4028
5489
|
}
|
|
4029
5490
|
function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -4031,12 +5492,67 @@ function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass)
|
|
|
4031
5492
|
ctx.postSync({ type: "syncMasterStripInsertBypassed", insertIndex, bypassed, resetOnBypass });
|
|
4032
5493
|
}
|
|
4033
5494
|
function setMasterStripInsertParamByName(ctx, insertIndex, paramName, value) {
|
|
4034
|
-
|
|
4035
|
-
|
|
5495
|
+
setInsertParamByName(
|
|
5496
|
+
ctx,
|
|
5497
|
+
{ kind: "master" },
|
|
5498
|
+
insertIndex,
|
|
5499
|
+
paramName,
|
|
5500
|
+
value,
|
|
5501
|
+
() => ctx.offlineEngine.restoreMasterStripInsertParamByName(insertIndex, paramName, value),
|
|
5502
|
+
{ type: "syncMasterStripInsertParamByName", insertIndex, paramName, value }
|
|
5503
|
+
);
|
|
4036
5504
|
}
|
|
4037
5505
|
function setBusStripInsertParamByName(ctx, busId, insertIndex, paramName, value) {
|
|
4038
|
-
|
|
4039
|
-
|
|
5506
|
+
setInsertParamByName(
|
|
5507
|
+
ctx,
|
|
5508
|
+
{ kind: "bus", busId },
|
|
5509
|
+
insertIndex,
|
|
5510
|
+
paramName,
|
|
5511
|
+
value,
|
|
5512
|
+
() => ctx.offlineEngine.restoreBusStripInsertParamByName(busId, insertIndex, paramName, value),
|
|
5513
|
+
{ type: "syncBusStripInsertParamByName", busId, insertIndex, paramName, value }
|
|
5514
|
+
);
|
|
5515
|
+
}
|
|
5516
|
+
function setBusStripInsertBypassed(ctx, busId, insertIndex, bypassed, resetOnBypass) {
|
|
5517
|
+
ctx.offlineEngine.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
5518
|
+
ctx.postSync({ type: "syncBusStripInsertBypassed", busId, insertIndex, bypassed, resetOnBypass });
|
|
5519
|
+
}
|
|
5520
|
+
function setBusStripEqBand(ctx, busId, bandIndex, band) {
|
|
5521
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
5522
|
+
ctx.offlineEngine.setBusStripEqBandJson(busId, bandIndex, bandJson);
|
|
5523
|
+
mergeEqBand(ctx, { kind: "bus", busId }, bandIndex, bandJson);
|
|
5524
|
+
ctx.postSync({ type: "syncBusStripEqBand", busId, bandIndex, bandJson });
|
|
5525
|
+
}
|
|
5526
|
+
function setBusStripPan(ctx, busId, pan) {
|
|
5527
|
+
ctx.offlineEngine.setBusStripPan(busId, pan);
|
|
5528
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5529
|
+
entry.pan = pan;
|
|
5530
|
+
});
|
|
5531
|
+
ctx.postSync({ type: "syncBusStripPan", busId, pan });
|
|
5532
|
+
}
|
|
5533
|
+
function setBusStripPanLaw(ctx, busId, panLaw) {
|
|
5534
|
+
const code = panLawCode(panLaw);
|
|
5535
|
+
ctx.offlineEngine.setBusStripPanLaw(busId, code);
|
|
5536
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5537
|
+
entry.panLaw = code;
|
|
5538
|
+
});
|
|
5539
|
+
ctx.postSync({ type: "syncBusStripPanLaw", busId, panLaw: code });
|
|
5540
|
+
}
|
|
5541
|
+
function setBusStripPanMode(ctx, busId, panMode) {
|
|
5542
|
+
const code = panModeCode(panMode);
|
|
5543
|
+
ctx.offlineEngine.setBusStripPanMode(busId, code);
|
|
5544
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5545
|
+
entry.panMode = code;
|
|
5546
|
+
});
|
|
5547
|
+
ctx.postSync({ type: "syncBusStripPanMode", busId, panMode: code });
|
|
5548
|
+
}
|
|
5549
|
+
function setBusStripDualPan(ctx, busId, leftPan, rightPan) {
|
|
5550
|
+
ctx.offlineEngine.setBusStripDualPan(busId, leftPan, rightPan);
|
|
5551
|
+
mergeStripJson(ctx, { kind: "bus", busId }, (entry) => {
|
|
5552
|
+
entry.dualPanLeft = leftPan;
|
|
5553
|
+
entry.dualPanRight = rightPan;
|
|
5554
|
+
});
|
|
5555
|
+
ctx.postSync({ type: "syncBusStripDualPan", busId, leftPan, rightPan });
|
|
4040
5556
|
}
|
|
4041
5557
|
function pushMidiNoteOn(ctx, trackId, group, channel, note, velocity, renderFrame) {
|
|
4042
5558
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
@@ -4077,9 +5593,55 @@ function pushMidiCc(ctx, trackId, group, channel, controller, value, renderFrame
|
|
|
4077
5593
|
renderFrame
|
|
4078
5594
|
});
|
|
4079
5595
|
}
|
|
5596
|
+
function pushMidiPitchBend(ctx, trackId, group, channel, bend14, renderFrame) {
|
|
5597
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5598
|
+
ctx.offlineEngine.pushMidiPitchBend(destinationId, group, channel, bend14, renderFrame);
|
|
5599
|
+
ctx.postSync({
|
|
5600
|
+
type: "syncMidiPitchBend",
|
|
5601
|
+
destinationId,
|
|
5602
|
+
group,
|
|
5603
|
+
channel,
|
|
5604
|
+
data0: bend14,
|
|
5605
|
+
data1: 0,
|
|
5606
|
+
renderFrame
|
|
5607
|
+
});
|
|
5608
|
+
}
|
|
5609
|
+
function pushMidiChannelPressure(ctx, trackId, group, channel, pressure, renderFrame) {
|
|
5610
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5611
|
+
ctx.offlineEngine.pushMidiChannelPressure(destinationId, group, channel, pressure, renderFrame);
|
|
5612
|
+
ctx.postSync({
|
|
5613
|
+
type: "syncMidiChannelPressure",
|
|
5614
|
+
destinationId,
|
|
5615
|
+
group,
|
|
5616
|
+
channel,
|
|
5617
|
+
data0: pressure,
|
|
5618
|
+
data1: 0,
|
|
5619
|
+
renderFrame
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
function pushMidiPolyPressure(ctx, trackId, group, channel, note, pressure, renderFrame) {
|
|
5623
|
+
const destinationId = ctx.resolveTargetId(trackId);
|
|
5624
|
+
ctx.offlineEngine.pushMidiPolyPressure(
|
|
5625
|
+
destinationId,
|
|
5626
|
+
group,
|
|
5627
|
+
channel,
|
|
5628
|
+
note,
|
|
5629
|
+
pressure,
|
|
5630
|
+
renderFrame
|
|
5631
|
+
);
|
|
5632
|
+
ctx.postSync({
|
|
5633
|
+
type: "syncMidiPolyPressure",
|
|
5634
|
+
destinationId,
|
|
5635
|
+
group,
|
|
5636
|
+
channel,
|
|
5637
|
+
data0: note,
|
|
5638
|
+
data1: pressure,
|
|
5639
|
+
renderFrame
|
|
5640
|
+
});
|
|
5641
|
+
}
|
|
4080
5642
|
function pushMidiUmp(ctx, trackId, word0, renderFrame) {
|
|
4081
5643
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
4082
|
-
ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
|
|
5644
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, [word0], renderFrame);
|
|
4083
5645
|
ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
|
|
4084
5646
|
}
|
|
4085
5647
|
function setBuiltinInstrument(ctx, trackId, config) {
|
|
@@ -4205,6 +5767,7 @@ function transferableAudioBuffers(channels) {
|
|
|
4205
5767
|
var SonareEngine = class _SonareEngine {
|
|
4206
5768
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
4207
5769
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
5770
|
+
this.insertAutomationIdsByTarget = /* @__PURE__ */ new Map();
|
|
4208
5771
|
this.clips = /* @__PURE__ */ new Map();
|
|
4209
5772
|
this.midiClips = /* @__PURE__ */ new Map();
|
|
4210
5773
|
this.markers = /* @__PURE__ */ new Map();
|
|
@@ -4212,9 +5775,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
4212
5775
|
this.trackSends = /* @__PURE__ */ new Map();
|
|
4213
5776
|
this.trackOutputBus = /* @__PURE__ */ new Map();
|
|
4214
5777
|
this.laneSidechains = /* @__PURE__ */ new Map();
|
|
5778
|
+
this.busSidechains = /* @__PURE__ */ new Map();
|
|
5779
|
+
this.masterSidechains = /* @__PURE__ */ new Map();
|
|
4215
5780
|
this.buses = [];
|
|
4216
5781
|
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
4217
5782
|
this.busStripJson = /* @__PURE__ */ new Map();
|
|
5783
|
+
this.insertParamOverrides = /* @__PURE__ */ new Map();
|
|
4218
5784
|
this.tempoBpm = 120;
|
|
4219
5785
|
this.timeSignature = { numerator: 4, denominator: 4 };
|
|
4220
5786
|
this.tempoSegments = [{ startPpq: 0, bpm: 120 }];
|
|
@@ -4373,12 +5939,17 @@ var SonareEngine = class _SonareEngine {
|
|
|
4373
5939
|
* @returns Reserved insert-automation id, or -1 when strip/insert/key unknown.
|
|
4374
5940
|
*/
|
|
4375
5941
|
resolveTrackInsertAutomationId(target, insertIndex, paramName) {
|
|
4376
|
-
|
|
5942
|
+
const id = resolveTrackInsertAutomationId(
|
|
4377
5943
|
this.parameterContext,
|
|
4378
5944
|
target,
|
|
4379
5945
|
insertIndex,
|
|
4380
5946
|
paramName
|
|
4381
5947
|
);
|
|
5948
|
+
if (id >= 0) {
|
|
5949
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
5950
|
+
this.rememberInsertAutomationId({ kind: "track", trackId }, id);
|
|
5951
|
+
}
|
|
5952
|
+
return id;
|
|
4382
5953
|
}
|
|
4383
5954
|
/**
|
|
4384
5955
|
* Resolves a master-strip insert parameter to its reserved insert-automation
|
|
@@ -4389,7 +5960,15 @@ var SonareEngine = class _SonareEngine {
|
|
|
4389
5960
|
* @returns Reserved insert-automation id, or -1 when insert/key unknown.
|
|
4390
5961
|
*/
|
|
4391
5962
|
resolveMasterInsertAutomationId(insertIndex, paramName) {
|
|
4392
|
-
|
|
5963
|
+
const id = resolveMasterInsertAutomationId(
|
|
5964
|
+
this.parameterContext,
|
|
5965
|
+
insertIndex,
|
|
5966
|
+
paramName
|
|
5967
|
+
);
|
|
5968
|
+
if (id >= 0) {
|
|
5969
|
+
this.rememberInsertAutomationId({ kind: "master" }, id);
|
|
5970
|
+
}
|
|
5971
|
+
return id;
|
|
4393
5972
|
}
|
|
4394
5973
|
/**
|
|
4395
5974
|
* Resolves a bus-strip insert parameter to its reserved insert-automation id.
|
|
@@ -4402,12 +5981,29 @@ var SonareEngine = class _SonareEngine {
|
|
|
4402
5981
|
* @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
|
|
4403
5982
|
*/
|
|
4404
5983
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
4405
|
-
|
|
5984
|
+
const id = resolveBusInsertAutomationId(
|
|
4406
5985
|
this.parameterContext,
|
|
4407
5986
|
busId,
|
|
4408
5987
|
insertIndex,
|
|
4409
5988
|
paramName
|
|
4410
5989
|
);
|
|
5990
|
+
if (id >= 0) {
|
|
5991
|
+
this.rememberInsertAutomationId({ kind: "bus", busId }, id);
|
|
5992
|
+
}
|
|
5993
|
+
return id;
|
|
5994
|
+
}
|
|
5995
|
+
/**
|
|
5996
|
+
* Resolves a hosted instrument's continuous parameter to its reserved
|
|
5997
|
+
* instrument-automation id, so an instrument parameter follows a breakpoint
|
|
5998
|
+
* lane at audio-block precision like a strip insert. Bind the instrument
|
|
5999
|
+
* (`setSynthInstrument`) before resolving.
|
|
6000
|
+
*
|
|
6001
|
+
* @param destinationId MIDI destination the instrument is bound to.
|
|
6002
|
+
* @param paramName Instrument JSON-key parameter name (e.g. `cutoffHz`).
|
|
6003
|
+
* @returns Reserved instrument-automation id, or -1 when destination/key unknown.
|
|
6004
|
+
*/
|
|
6005
|
+
resolveInstrumentAutomationId(destinationId, paramName) {
|
|
6006
|
+
return resolveInstrumentAutomationId(this.parameterContext, destinationId, paramName);
|
|
4411
6007
|
}
|
|
4412
6008
|
/**
|
|
4413
6009
|
* Returns the number of automation lanes installed on the engine, including
|
|
@@ -4437,10 +6033,18 @@ var SonareEngine = class _SonareEngine {
|
|
|
4437
6033
|
return setTrackMonitorMode(this.parameterContext, target, mode, renderFrame);
|
|
4438
6034
|
}
|
|
4439
6035
|
setStripGain(target, db) {
|
|
4440
|
-
|
|
6036
|
+
const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
6037
|
+
if (sent && target === "master") {
|
|
6038
|
+
cacheMasterStripScalar(this.stripContext, "faderDb", db);
|
|
6039
|
+
}
|
|
6040
|
+
return sent;
|
|
4441
6041
|
}
|
|
4442
6042
|
setStripPan(target, pan) {
|
|
4443
|
-
|
|
6043
|
+
const sent = this.sendSmoothedParam(this.stripParamId(target, ENGINE_MIXER_PARAM_PAN), pan);
|
|
6044
|
+
if (sent && target === "master") {
|
|
6045
|
+
cacheMasterStripScalar(this.stripContext, "pan", pan);
|
|
6046
|
+
}
|
|
6047
|
+
return sent;
|
|
4444
6048
|
}
|
|
4445
6049
|
/**
|
|
4446
6050
|
* Declares the mixer track lanes in an explicit order.
|
|
@@ -4477,14 +6081,37 @@ var SonareEngine = class _SonareEngine {
|
|
|
4477
6081
|
setTrackBuses(buses) {
|
|
4478
6082
|
setTrackBuses(this.mixerContext, buses);
|
|
4479
6083
|
}
|
|
6084
|
+
/**
|
|
6085
|
+
* Keys one insert of a bus strip from a track lane or another bus
|
|
6086
|
+
* (ducking/sidechainRouter inserts). `sourceId` 0 removes the binding.
|
|
6087
|
+
*/
|
|
6088
|
+
setBusSidechain(busId, insertIndex, sourceKind, sourceId) {
|
|
6089
|
+
setBusSidechain(this.mixerContext, busId, insertIndex, sourceKind, sourceId);
|
|
6090
|
+
}
|
|
6091
|
+
/**
|
|
6092
|
+
* Keys one insert of the master strip from a track lane or a bus. Same
|
|
6093
|
+
* source rules as {@link setBusSidechain}.
|
|
6094
|
+
*/
|
|
6095
|
+
setMasterSidechain(insertIndex, sourceKind, sourceId) {
|
|
6096
|
+
setMasterSidechain(this.mixerContext, insertIndex, sourceKind, sourceId);
|
|
6097
|
+
}
|
|
4480
6098
|
setBusGain(busId, db) {
|
|
4481
6099
|
return setBusGain(this.mixerContext, busId, db);
|
|
4482
6100
|
}
|
|
4483
6101
|
setTrackStripJson(target, sceneJson) {
|
|
4484
6102
|
const laneIndex = this.ensureTrackLane(target);
|
|
4485
6103
|
const trackId = this.trackLaneIds[laneIndex];
|
|
4486
|
-
|
|
4487
|
-
|
|
6104
|
+
const { resetInserts } = setTrackStripJson(
|
|
6105
|
+
this.stripContext,
|
|
6106
|
+
trackId,
|
|
6107
|
+
sceneJson,
|
|
6108
|
+
this.trackStripJson
|
|
6109
|
+
);
|
|
6110
|
+
if (resetInserts) {
|
|
6111
|
+
this.clearInsertAutomationLanes({ kind: "track", trackId });
|
|
6112
|
+
}
|
|
6113
|
+
pruneStripSidechains(this.mixerContext, { kind: "track", trackId }, sceneJson);
|
|
6114
|
+
this.syncMixer(resetInserts ? [{ kind: "track", trackId }] : []);
|
|
4488
6115
|
}
|
|
4489
6116
|
setTrackStripEqBand(target, bandIndex, band) {
|
|
4490
6117
|
setTrackStripEqBand(this.stripContext, target, bandIndex, band);
|
|
@@ -4541,9 +6168,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
4541
6168
|
setBusStripJson(this.mixerContext, busId, sceneJson);
|
|
4542
6169
|
}
|
|
4543
6170
|
setMasterStripJson(sceneJson) {
|
|
4544
|
-
|
|
6171
|
+
const target = { kind: "master" };
|
|
6172
|
+
const resetInserts = this.masterStripJson !== void 0 && this.masterStripJson !== sceneJson || hasInsertParamOverrides(this.insertParamOverrides, target);
|
|
6173
|
+
applyFullStripJson(this.offlineEngine, target, sceneJson, resetInserts);
|
|
6174
|
+
if (resetInserts) {
|
|
6175
|
+
this.clearInsertAutomationLanes(target);
|
|
6176
|
+
}
|
|
6177
|
+
pruneStripSidechains(this.mixerContext, target, sceneJson);
|
|
6178
|
+
clearInsertParamOverrides(this.insertParamOverrides, target);
|
|
4545
6179
|
this.masterStripJson = sceneJson;
|
|
4546
|
-
this.syncMixer();
|
|
6180
|
+
this.syncMixer(resetInserts ? [target] : []);
|
|
4547
6181
|
}
|
|
4548
6182
|
setMasterStripEqBand(bandIndex, band) {
|
|
4549
6183
|
setMasterStripEqBand(this.stripContext, bandIndex, band);
|
|
@@ -4558,6 +6192,47 @@ var SonareEngine = class _SonareEngine {
|
|
|
4558
6192
|
this.ensureBus(busId);
|
|
4559
6193
|
setBusStripInsertParamByName(this.stripContext, busId, insertIndex, paramName, value);
|
|
4560
6194
|
}
|
|
6195
|
+
/**
|
|
6196
|
+
* Bus-strip counterpart of {@link setStripInsertBypassed}. A bus is addressed
|
|
6197
|
+
* by its numeric id, which the `target` of the track/master form cannot
|
|
6198
|
+
* express, so it is its own method exactly like
|
|
6199
|
+
* {@link setBusStripInsertParamByName}. Bypassing keeps the insert's internal
|
|
6200
|
+
* state (reverb tail, compressor envelope) unless `resetOnBypass` is set;
|
|
6201
|
+
* re-posting the bus scene JSON, the only workaround before this existed,
|
|
6202
|
+
* rebuilds the chain and loses it.
|
|
6203
|
+
*/
|
|
6204
|
+
setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass = false) {
|
|
6205
|
+
this.ensureBus(busId);
|
|
6206
|
+
setBusStripInsertBypassed(
|
|
6207
|
+
this.stripContext,
|
|
6208
|
+
busId,
|
|
6209
|
+
insertIndex,
|
|
6210
|
+
bypassed,
|
|
6211
|
+
resetOnBypass
|
|
6212
|
+
);
|
|
6213
|
+
}
|
|
6214
|
+
/** Bus-strip counterpart of {@link setTrackStripEqBand}. */
|
|
6215
|
+
setBusStripEqBand(busId, bandIndex, band) {
|
|
6216
|
+
this.ensureBus(busId);
|
|
6217
|
+
setBusStripEqBand(this.stripContext, busId, bandIndex, band);
|
|
6218
|
+
}
|
|
6219
|
+
/** Bus-strip counterpart of {@link setTrackStripPan}; refused on a surround bus. */
|
|
6220
|
+
setBusStripPan(busId, pan) {
|
|
6221
|
+
this.ensureBus(busId);
|
|
6222
|
+
setBusStripPan(this.stripContext, busId, pan);
|
|
6223
|
+
}
|
|
6224
|
+
setBusStripPanLaw(busId, panLaw) {
|
|
6225
|
+
this.ensureBus(busId);
|
|
6226
|
+
setBusStripPanLaw(this.stripContext, busId, panLaw);
|
|
6227
|
+
}
|
|
6228
|
+
setBusStripPanMode(busId, panMode) {
|
|
6229
|
+
this.ensureBus(busId);
|
|
6230
|
+
setBusStripPanMode(this.stripContext, busId, panMode);
|
|
6231
|
+
}
|
|
6232
|
+
setBusStripDualPan(busId, leftPan, rightPan) {
|
|
6233
|
+
this.ensureBus(busId);
|
|
6234
|
+
setBusStripDualPan(this.stripContext, busId, leftPan, rightPan);
|
|
6235
|
+
}
|
|
4561
6236
|
setStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
4562
6237
|
if (target === "master") {
|
|
4563
6238
|
this.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
@@ -4638,6 +6313,49 @@ var SonareEngine = class _SonareEngine {
|
|
|
4638
6313
|
}
|
|
4639
6314
|
return { binding, provider: binding.provider };
|
|
4640
6315
|
}
|
|
6316
|
+
/**
|
|
6317
|
+
* Sets the clip-page look-ahead window in timeline frames on both the worklet
|
|
6318
|
+
* engine and this thread's offline engine.
|
|
6319
|
+
*
|
|
6320
|
+
* The audio thread reports the pages it is *about to* read that are not
|
|
6321
|
+
* resident yet, so the sliding-window streamer can service them before the
|
|
6322
|
+
* playhead reaches them. Without look-ahead a page miss is only reported
|
|
6323
|
+
* after the read already produced silence, which costs one block of silence
|
|
6324
|
+
* at every page boundary that was not primed.
|
|
6325
|
+
*
|
|
6326
|
+
* Defaults to half a second at the engine's sample rate. `0` disables it.
|
|
6327
|
+
* Safe to call during playback.
|
|
6328
|
+
*/
|
|
6329
|
+
setClipPagePrefetchFrames(frames) {
|
|
6330
|
+
if (this.destroyed) {
|
|
6331
|
+
throw new Error("SonareEngine is destroyed.");
|
|
6332
|
+
}
|
|
6333
|
+
if (!Number.isFinite(frames) || frames < 0) {
|
|
6334
|
+
throw new Error("clip page prefetch frames must be a finite value >= 0.");
|
|
6335
|
+
}
|
|
6336
|
+
this.offlineEngine.setClipPagePrefetchFrames(frames);
|
|
6337
|
+
this.postSync({ type: "syncClipPagePrefetchFrames", frames });
|
|
6338
|
+
}
|
|
6339
|
+
/**
|
|
6340
|
+
* Sets the number of concurrent time-stretch voices on both this thread's
|
|
6341
|
+
* offline engine and the live worklet engine. `voices` must be an integer
|
|
6342
|
+
* in `[0, 64]`; a non-integer, negative, or larger value throws and leaves
|
|
6343
|
+
* the capacity unchanged on both engines. Default is 8. Capacity 0 disables
|
|
6344
|
+
* time-stretch, so every warped clip plays resampled instead. A change
|
|
6345
|
+
* applied while the engine is running restarts the splice state of any clip
|
|
6346
|
+
* stretching through a voice at that moment.
|
|
6347
|
+
*/
|
|
6348
|
+
setWarpVoiceCapacity(voices) {
|
|
6349
|
+
if (this.destroyed) {
|
|
6350
|
+
throw new Error("SonareEngine is destroyed.");
|
|
6351
|
+
}
|
|
6352
|
+
this.offlineEngine.setWarpVoiceCapacity(voices);
|
|
6353
|
+
this.postSync({ type: "syncWarpVoiceCapacity", voices });
|
|
6354
|
+
}
|
|
6355
|
+
/** Reads the current time-stretch voice capacity (default 8). */
|
|
6356
|
+
warpVoiceCapacity() {
|
|
6357
|
+
return this.offlineEngine.warpVoiceCapacity();
|
|
6358
|
+
}
|
|
4641
6359
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
4642
6360
|
return addClip(this.clipContext, trackId, buffer, startPpq, opts);
|
|
4643
6361
|
}
|
|
@@ -4699,6 +6417,30 @@ var SonareEngine = class _SonareEngine {
|
|
|
4699
6417
|
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
4700
6418
|
pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
|
|
4701
6419
|
}
|
|
6420
|
+
pushMidiPitchBend(trackId, group, channel, bend14, renderFrame = -1) {
|
|
6421
|
+
pushMidiPitchBend(this.stripContext, trackId, group, channel, bend14, renderFrame);
|
|
6422
|
+
}
|
|
6423
|
+
pushMidiChannelPressure(trackId, group, channel, pressure, renderFrame = -1) {
|
|
6424
|
+
pushMidiChannelPressure(
|
|
6425
|
+
this.stripContext,
|
|
6426
|
+
trackId,
|
|
6427
|
+
group,
|
|
6428
|
+
channel,
|
|
6429
|
+
pressure,
|
|
6430
|
+
renderFrame
|
|
6431
|
+
);
|
|
6432
|
+
}
|
|
6433
|
+
pushMidiPolyPressure(trackId, group, channel, note, pressure, renderFrame = -1) {
|
|
6434
|
+
pushMidiPolyPressure(
|
|
6435
|
+
this.stripContext,
|
|
6436
|
+
trackId,
|
|
6437
|
+
group,
|
|
6438
|
+
channel,
|
|
6439
|
+
note,
|
|
6440
|
+
pressure,
|
|
6441
|
+
renderFrame
|
|
6442
|
+
);
|
|
6443
|
+
}
|
|
4702
6444
|
pushMidiUmp(trackId, word0, renderFrame = -1) {
|
|
4703
6445
|
pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
|
|
4704
6446
|
}
|
|
@@ -4752,6 +6494,39 @@ var SonareEngine = class _SonareEngine {
|
|
|
4752
6494
|
portTimeSamples
|
|
4753
6495
|
});
|
|
4754
6496
|
}
|
|
6497
|
+
pushMidiInputPitchBend(group, channel, bend14, portTimeSamples = 0) {
|
|
6498
|
+
this.offlineEngine.pushMidiInputPitchBend(group, channel, bend14, portTimeSamples);
|
|
6499
|
+
this.postSync({
|
|
6500
|
+
type: "syncMidiInputPitchBend",
|
|
6501
|
+
group,
|
|
6502
|
+
channel,
|
|
6503
|
+
data0: bend14,
|
|
6504
|
+
data1: 0,
|
|
6505
|
+
portTimeSamples
|
|
6506
|
+
});
|
|
6507
|
+
}
|
|
6508
|
+
pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples = 0) {
|
|
6509
|
+
this.offlineEngine.pushMidiInputChannelPressure(group, channel, pressure, portTimeSamples);
|
|
6510
|
+
this.postSync({
|
|
6511
|
+
type: "syncMidiInputChannelPressure",
|
|
6512
|
+
group,
|
|
6513
|
+
channel,
|
|
6514
|
+
data0: pressure,
|
|
6515
|
+
data1: 0,
|
|
6516
|
+
portTimeSamples
|
|
6517
|
+
});
|
|
6518
|
+
}
|
|
6519
|
+
pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples = 0) {
|
|
6520
|
+
this.offlineEngine.pushMidiInputPolyPressure(group, channel, note, pressure, portTimeSamples);
|
|
6521
|
+
this.postSync({
|
|
6522
|
+
type: "syncMidiInputPolyPressure",
|
|
6523
|
+
group,
|
|
6524
|
+
channel,
|
|
6525
|
+
data0: note,
|
|
6526
|
+
data1: pressure,
|
|
6527
|
+
portTimeSamples
|
|
6528
|
+
});
|
|
6529
|
+
}
|
|
4755
6530
|
pushMidiPanic(renderFrame = -1) {
|
|
4756
6531
|
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
4757
6532
|
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
@@ -4949,8 +6724,32 @@ var SonareEngine = class _SonareEngine {
|
|
|
4949
6724
|
mixerLanes() {
|
|
4950
6725
|
return mixerLanes(this.mixerContext);
|
|
4951
6726
|
}
|
|
4952
|
-
syncMixer() {
|
|
4953
|
-
syncMixer(this.mixerContext);
|
|
6727
|
+
syncMixer(forceInsertResets = []) {
|
|
6728
|
+
syncMixer(this.mixerContext, false, forceInsertResets);
|
|
6729
|
+
}
|
|
6730
|
+
insertAutomationTargetKey(target) {
|
|
6731
|
+
return target.kind === "track" ? `track:${target.trackId}` : target.kind === "bus" ? `bus:${target.busId}` : "master";
|
|
6732
|
+
}
|
|
6733
|
+
rememberInsertAutomationId(target, id) {
|
|
6734
|
+
const key = this.insertAutomationTargetKey(target);
|
|
6735
|
+
const ids = this.insertAutomationIdsByTarget.get(key) ?? /* @__PURE__ */ new Set();
|
|
6736
|
+
ids.add(id);
|
|
6737
|
+
this.insertAutomationIdsByTarget.set(key, ids);
|
|
6738
|
+
}
|
|
6739
|
+
clearInsertAutomationLanes(target, forgetResolvedIds = false) {
|
|
6740
|
+
const key = this.insertAutomationTargetKey(target);
|
|
6741
|
+
const ids = this.insertAutomationIdsByTarget.get(key);
|
|
6742
|
+
if (!ids) {
|
|
6743
|
+
return;
|
|
6744
|
+
}
|
|
6745
|
+
for (const id of ids) {
|
|
6746
|
+
if (this.automationLanes.has(id)) {
|
|
6747
|
+
this.setAutomationLane(id, []);
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
if (forgetResolvedIds) {
|
|
6751
|
+
this.insertAutomationIdsByTarget.delete(key);
|
|
6752
|
+
}
|
|
4954
6753
|
}
|
|
4955
6754
|
postInstrumentSync(message) {
|
|
4956
6755
|
if (this.destroyed) {
|
|
@@ -4975,24 +6774,19 @@ var SonareEngine = class _SonareEngine {
|
|
|
4975
6774
|
// Sync messages use a string `type` so the worklet's message handler routes
|
|
4976
6775
|
// them to receiveSync() (numeric `type` is reserved for SonareEngineCommandRecord).
|
|
4977
6776
|
postSync(message, transfer) {
|
|
4978
|
-
if (this.destroyed) {
|
|
4979
|
-
return;
|
|
4980
|
-
}
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
this.realtimeNode.node.port.postMessage(message);
|
|
4986
|
-
}
|
|
4987
|
-
} finally {
|
|
4988
|
-
this.flushOfflineMirror();
|
|
6777
|
+
if (this.destroyed) {
|
|
6778
|
+
return;
|
|
6779
|
+
}
|
|
6780
|
+
if (transfer && transfer.length > 0) {
|
|
6781
|
+
this.realtimeNode.node.port.postMessage(message, transfer);
|
|
6782
|
+
} else {
|
|
6783
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
4989
6784
|
}
|
|
4990
6785
|
}
|
|
4991
|
-
// The
|
|
4992
|
-
//
|
|
4993
|
-
// call this from the worklet/audio path: it is intentionally control-only.
|
|
6786
|
+
// The control-only mirror applies due commands immediately while preserving
|
|
6787
|
+
// scheduled ones for its next render block.
|
|
4994
6788
|
flushOfflineMirror() {
|
|
4995
|
-
this.offlineEngine.
|
|
6789
|
+
this.offlineEngine.applyCommandsDueNowPreservingFuture();
|
|
4996
6790
|
}
|
|
4997
6791
|
sendMirroredCommand(command) {
|
|
4998
6792
|
const accepted = this.realtimeNode.sendCommand(command);
|
|
@@ -5010,16 +6804,24 @@ var SonareEngine = class _SonareEngine {
|
|
|
5010
6804
|
trackSends: this.trackSends,
|
|
5011
6805
|
trackOutputBus: this.trackOutputBus,
|
|
5012
6806
|
laneSidechains: this.laneSidechains,
|
|
6807
|
+
busSidechains: this.busSidechains,
|
|
6808
|
+
masterSidechains: this.masterSidechains,
|
|
5013
6809
|
buses: this.buses,
|
|
5014
6810
|
trackStripJson: this.trackStripJson,
|
|
5015
6811
|
busStripJson: this.busStripJson,
|
|
6812
|
+
insertParamOverrides: this.insertParamOverrides,
|
|
6813
|
+
flushOfflineMirror: () => this.flushOfflineMirror(),
|
|
5016
6814
|
postSync: (message) => this.postSync(message),
|
|
5017
6815
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
5018
6816
|
ensureBus: (busId) => this.ensureBus(busId),
|
|
5019
6817
|
mixerLanes: () => this.mixerLanes(),
|
|
5020
|
-
syncMixer: () => this.syncMixer(),
|
|
6818
|
+
syncMixer: (forceInsertResets) => this.syncMixer(forceInsertResets),
|
|
6819
|
+
clearInsertAutomationLanes: (target, forgetResolvedIds) => this.clearInsertAutomationLanes(target, forgetResolvedIds),
|
|
5021
6820
|
sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
|
|
5022
|
-
getMasterStripJson: () => this.masterStripJson
|
|
6821
|
+
getMasterStripJson: () => this.masterStripJson,
|
|
6822
|
+
cacheMasterStripJson: (sceneJson) => {
|
|
6823
|
+
this.masterStripJson = sceneJson;
|
|
6824
|
+
}
|
|
5023
6825
|
};
|
|
5024
6826
|
}
|
|
5025
6827
|
// Collaborator surface handed to the strip/pan/EQ/insert/MIDI free functions
|
|
@@ -5032,7 +6834,10 @@ var SonareEngine = class _SonareEngine {
|
|
|
5032
6834
|
postSync: (message) => this.postSync(message),
|
|
5033
6835
|
postInstrumentSync: (message) => this.postInstrumentSync(message),
|
|
5034
6836
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
5035
|
-
resolveTargetId: (target) => this.resolveTargetId(target)
|
|
6837
|
+
resolveTargetId: (target) => this.resolveTargetId(target),
|
|
6838
|
+
readStripJson: (target) => cachedStripJson(this.mixerContext, target),
|
|
6839
|
+
writeStripJson: (target, sceneJson) => cacheStripJson(this.mixerContext, target, sceneJson),
|
|
6840
|
+
insertParamOverrides: this.insertParamOverrides
|
|
5036
6841
|
};
|
|
5037
6842
|
}
|
|
5038
6843
|
// Collaborator surface handed to the automation-lane free functions so they
|
|
@@ -5174,7 +6979,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
5174
6979
|
ensureTrackLane(target) {
|
|
5175
6980
|
const trackId = this.resolveTargetId(target);
|
|
5176
6981
|
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
5177
|
-
throw new
|
|
6982
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
|
|
5178
6983
|
}
|
|
5179
6984
|
const existing = this.trackLaneIds.indexOf(trackId);
|
|
5180
6985
|
if (existing >= 0) {
|
|
@@ -5185,20 +6990,36 @@ var SonareEngine = class _SonareEngine {
|
|
|
5185
6990
|
return this.trackLaneIds.length - 1;
|
|
5186
6991
|
}
|
|
5187
6992
|
ensureBus(busId) {
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
throw new Error(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
6993
|
+
if (!Number.isInteger(busId) || busId <= 0) {
|
|
6994
|
+
throw new RangeError(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
5191
6995
|
}
|
|
5192
|
-
const existing = this.buses.findIndex((bus) => bus.busId ===
|
|
6996
|
+
const existing = this.buses.findIndex((bus) => bus.busId === busId);
|
|
5193
6997
|
if (existing >= 0) {
|
|
5194
6998
|
return existing;
|
|
5195
6999
|
}
|
|
5196
|
-
this.buses.push({ busId
|
|
7000
|
+
this.buses.push({ busId });
|
|
5197
7001
|
this.syncMixer();
|
|
5198
7002
|
return this.buses.length - 1;
|
|
5199
7003
|
}
|
|
5200
7004
|
};
|
|
5201
7005
|
|
|
7006
|
+
// src/worklet/audio_types.ts
|
|
7007
|
+
function copyPlanesToOutput(output, planes, frames) {
|
|
7008
|
+
const monoFanOut = planes.length === 1;
|
|
7009
|
+
for (let ch = 0; ch < output.length; ch++) {
|
|
7010
|
+
const target = output[ch];
|
|
7011
|
+
const source = monoFanOut ? planes[0] : planes[ch];
|
|
7012
|
+
let copied = 0;
|
|
7013
|
+
if (source) {
|
|
7014
|
+
copied = Math.min(target.length, frames, source.length);
|
|
7015
|
+
target.set(source.subarray(0, copied));
|
|
7016
|
+
}
|
|
7017
|
+
if (copied < target.length) {
|
|
7018
|
+
target.fill(0, copied);
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
|
|
5202
7023
|
// src/worklet/messages.ts
|
|
5203
7024
|
var DEFAULT_METRONOME_CONFIG = {
|
|
5204
7025
|
beatGain: 0.35,
|
|
@@ -5220,20 +7041,6 @@ function resolveMetronomeConfig(config) {
|
|
|
5220
7041
|
}
|
|
5221
7042
|
|
|
5222
7043
|
// src/worklet/engine-processor.ts
|
|
5223
|
-
function copyPlanesToOutput(output, planes, frames) {
|
|
5224
|
-
for (let ch = 0; ch < output.length; ch++) {
|
|
5225
|
-
const target = output[ch];
|
|
5226
|
-
const source = planes[ch] ?? planes[0];
|
|
5227
|
-
if (source) {
|
|
5228
|
-
target.set(source.subarray(0, Math.min(target.length, frames)));
|
|
5229
|
-
if (target.length > frames) {
|
|
5230
|
-
target.fill(0, frames);
|
|
5231
|
-
}
|
|
5232
|
-
} else {
|
|
5233
|
-
target.fill(0);
|
|
5234
|
-
}
|
|
5235
|
-
}
|
|
5236
|
-
}
|
|
5237
7044
|
function captureTransferList(channels) {
|
|
5238
7045
|
const transfers = [];
|
|
5239
7046
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5273,9 +7080,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5273
7080
|
this.clipPageRequestOverflowReported = 0;
|
|
5274
7081
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
5275
7082
|
this.blockSize = options.blockSize ?? 128;
|
|
5276
|
-
this.channelCount =
|
|
7083
|
+
this.channelCount = requireChannelCount(options.channelCount, 2);
|
|
5277
7084
|
this.transport = transport;
|
|
5278
|
-
this.meterIntervalFrames =
|
|
7085
|
+
this.meterIntervalFrames = requireIntegerOption(
|
|
7086
|
+
options.meterIntervalFrames,
|
|
7087
|
+
2048,
|
|
7088
|
+
"meterIntervalFrames",
|
|
7089
|
+
0
|
|
7090
|
+
);
|
|
5279
7091
|
this.commandRing = options.commandSharedBuffer ? this.commandRingFromSharedBuffer(options.commandSharedBuffer, options.commandRingCapacity) : void 0;
|
|
5280
7092
|
this.telemetryRing = options.telemetrySharedBuffer ? this.telemetryRingFromSharedBuffer(
|
|
5281
7093
|
options.telemetrySharedBuffer,
|
|
@@ -5295,10 +7107,13 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5295
7107
|
options.externalMidiSharedBuffer,
|
|
5296
7108
|
options.externalMidiRingCapacity
|
|
5297
7109
|
) : void 0;
|
|
7110
|
+
if (this.commandRing && this.commandRing.capacity > 65536) {
|
|
7111
|
+
throw new RangeError("commandRingCapacity must be at most 65536");
|
|
7112
|
+
}
|
|
5298
7113
|
this.engine = new RealtimeEngine(
|
|
5299
7114
|
this.sampleRate,
|
|
5300
7115
|
this.blockSize,
|
|
5301
|
-
1024,
|
|
7116
|
+
Math.max(1024, this.commandRing?.capacity ?? 0),
|
|
5302
7117
|
1024,
|
|
5303
7118
|
this.channelCount
|
|
5304
7119
|
);
|
|
@@ -5316,7 +7131,12 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5316
7131
|
}
|
|
5317
7132
|
}
|
|
5318
7133
|
if (this.scopeRing) {
|
|
5319
|
-
const interval =
|
|
7134
|
+
const interval = requireIntegerOption(
|
|
7135
|
+
options.scopeIntervalFrames,
|
|
7136
|
+
this.blockSize,
|
|
7137
|
+
"scopeIntervalFrames",
|
|
7138
|
+
0
|
|
7139
|
+
);
|
|
5320
7140
|
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
5321
7141
|
}
|
|
5322
7142
|
}
|
|
@@ -5480,6 +7300,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5480
7300
|
}
|
|
5481
7301
|
break;
|
|
5482
7302
|
}
|
|
7303
|
+
case "syncClipPagePrefetchFrames": {
|
|
7304
|
+
this.engine.setClipPagePrefetchFrames(message.frames);
|
|
7305
|
+
break;
|
|
7306
|
+
}
|
|
7307
|
+
case "syncWarpVoiceCapacity": {
|
|
7308
|
+
this.engine.setWarpVoiceCapacity(message.voices);
|
|
7309
|
+
break;
|
|
7310
|
+
}
|
|
5483
7311
|
case "syncClipPageCommit": {
|
|
5484
7312
|
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
5485
7313
|
const clip = message.clip ?? this.pendingPagedClips.get(message.clipId);
|
|
@@ -5538,11 +7366,60 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5538
7366
|
);
|
|
5539
7367
|
}
|
|
5540
7368
|
break;
|
|
5541
|
-
case "syncMixer":
|
|
7369
|
+
case "syncMixer": {
|
|
7370
|
+
if (message.sidechainDelta === true) {
|
|
7371
|
+
for (const binding of message.laneSidechains ?? []) {
|
|
7372
|
+
this.engine.setLaneSidechain(
|
|
7373
|
+
binding.trackId,
|
|
7374
|
+
binding.insertIndex,
|
|
7375
|
+
binding.sourceTrackId
|
|
7376
|
+
);
|
|
7377
|
+
}
|
|
7378
|
+
for (const binding of message.busSidechains ?? []) {
|
|
7379
|
+
this.engine.setBusSidechain(
|
|
7380
|
+
binding.busId,
|
|
7381
|
+
binding.insertIndex,
|
|
7382
|
+
binding.sourceKind,
|
|
7383
|
+
binding.sourceId
|
|
7384
|
+
);
|
|
7385
|
+
}
|
|
7386
|
+
for (const binding of message.masterSidechains ?? []) {
|
|
7387
|
+
this.engine.setMasterSidechain(
|
|
7388
|
+
binding.insertIndex,
|
|
7389
|
+
binding.sourceKind,
|
|
7390
|
+
binding.sourceId
|
|
7391
|
+
);
|
|
7392
|
+
}
|
|
7393
|
+
break;
|
|
7394
|
+
}
|
|
7395
|
+
if (this.commandRing) {
|
|
7396
|
+
const write = Atomics.load(this.commandRing.header, 0);
|
|
7397
|
+
const read = Atomics.load(this.commandRing.header, 1);
|
|
7398
|
+
this.drainCommands(Math.min(this.commandRing.capacity, Math.max(0, write - read)));
|
|
7399
|
+
}
|
|
7400
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7401
|
+
this.engine.settleInsertParameters();
|
|
5542
7402
|
if (message.buses) {
|
|
5543
7403
|
this.engine.setTrackBuses(message.buses);
|
|
5544
7404
|
}
|
|
5545
7405
|
this.engine.setTrackLanes(message.lanes);
|
|
7406
|
+
for (const target of message.forceInsertResets ?? []) {
|
|
7407
|
+
const emptyJson = emptyStripJson(target);
|
|
7408
|
+
switch (target.kind) {
|
|
7409
|
+
case "track":
|
|
7410
|
+
this.engine.clearTrackInsertParameterBases(target.trackId);
|
|
7411
|
+
this.engine.setTrackStripJson(target.trackId, emptyJson);
|
|
7412
|
+
break;
|
|
7413
|
+
case "bus":
|
|
7414
|
+
this.engine.clearBusInsertParameterBases(target.busId);
|
|
7415
|
+
this.engine.setBusStripJson(target.busId, emptyJson);
|
|
7416
|
+
break;
|
|
7417
|
+
case "master":
|
|
7418
|
+
this.engine.clearMasterInsertParameterBases();
|
|
7419
|
+
this.engine.setMasterStripJson(emptyJson);
|
|
7420
|
+
break;
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
5546
7423
|
for (const strip of message.trackStrips ?? []) {
|
|
5547
7424
|
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
5548
7425
|
}
|
|
@@ -5555,7 +7432,46 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5555
7432
|
for (const binding of message.laneSidechains ?? []) {
|
|
5556
7433
|
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
5557
7434
|
}
|
|
7435
|
+
for (const binding of message.busSidechains ?? []) {
|
|
7436
|
+
this.engine.setBusSidechain(
|
|
7437
|
+
binding.busId,
|
|
7438
|
+
binding.insertIndex,
|
|
7439
|
+
binding.sourceKind,
|
|
7440
|
+
binding.sourceId
|
|
7441
|
+
);
|
|
7442
|
+
}
|
|
7443
|
+
for (const binding of message.masterSidechains ?? []) {
|
|
7444
|
+
this.engine.setMasterSidechain(binding.insertIndex, binding.sourceKind, binding.sourceId);
|
|
7445
|
+
}
|
|
7446
|
+
for (const override of message.insertParamOverrides ?? []) {
|
|
7447
|
+
switch (override.kind) {
|
|
7448
|
+
case "track":
|
|
7449
|
+
this.engine.restoreTrackStripInsertParamByName(
|
|
7450
|
+
override.trackId,
|
|
7451
|
+
override.insertIndex,
|
|
7452
|
+
override.paramName,
|
|
7453
|
+
override.value
|
|
7454
|
+
);
|
|
7455
|
+
break;
|
|
7456
|
+
case "bus":
|
|
7457
|
+
this.engine.restoreBusStripInsertParamByName(
|
|
7458
|
+
override.busId,
|
|
7459
|
+
override.insertIndex,
|
|
7460
|
+
override.paramName,
|
|
7461
|
+
override.value
|
|
7462
|
+
);
|
|
7463
|
+
break;
|
|
7464
|
+
case "master":
|
|
7465
|
+
this.engine.restoreMasterStripInsertParamByName(
|
|
7466
|
+
override.insertIndex,
|
|
7467
|
+
override.paramName,
|
|
7468
|
+
override.value
|
|
7469
|
+
);
|
|
7470
|
+
break;
|
|
7471
|
+
}
|
|
7472
|
+
}
|
|
5558
7473
|
break;
|
|
7474
|
+
}
|
|
5559
7475
|
case "syncCapture":
|
|
5560
7476
|
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
5561
7477
|
this.engine.setCaptureSource(message.source);
|
|
@@ -5568,6 +7484,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5568
7484
|
case "syncMasterStripEqBand":
|
|
5569
7485
|
this.engine.setMasterStripEqBandJson(message.bandIndex, message.bandJson);
|
|
5570
7486
|
break;
|
|
7487
|
+
case "syncBusStripEqBand":
|
|
7488
|
+
this.engine.setBusStripEqBandJson(message.busId, message.bandIndex, message.bandJson);
|
|
7489
|
+
break;
|
|
5571
7490
|
case "syncTrackStripInsertBypassed":
|
|
5572
7491
|
this.engine.setTrackStripInsertBypassed(
|
|
5573
7492
|
message.trackId,
|
|
@@ -5584,26 +7503,57 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5584
7503
|
);
|
|
5585
7504
|
break;
|
|
5586
7505
|
case "syncTrackStripInsertParamByName":
|
|
5587
|
-
this.engine.
|
|
7506
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7507
|
+
if (!this.engine.applyTrackStripInsertParamByNameNow(
|
|
5588
7508
|
message.trackId,
|
|
5589
7509
|
message.insertIndex,
|
|
5590
7510
|
message.paramName,
|
|
5591
7511
|
message.value
|
|
5592
|
-
)
|
|
7512
|
+
)) {
|
|
7513
|
+
this.engine.restoreTrackStripInsertParamByName(
|
|
7514
|
+
message.trackId,
|
|
7515
|
+
message.insertIndex,
|
|
7516
|
+
message.paramName,
|
|
7517
|
+
message.value
|
|
7518
|
+
);
|
|
7519
|
+
}
|
|
5593
7520
|
break;
|
|
5594
7521
|
case "syncMasterStripInsertParamByName":
|
|
5595
|
-
this.engine.
|
|
7522
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7523
|
+
if (!this.engine.applyMasterStripInsertParamByNameNow(
|
|
5596
7524
|
message.insertIndex,
|
|
5597
7525
|
message.paramName,
|
|
5598
7526
|
message.value
|
|
5599
|
-
)
|
|
7527
|
+
)) {
|
|
7528
|
+
this.engine.restoreMasterStripInsertParamByName(
|
|
7529
|
+
message.insertIndex,
|
|
7530
|
+
message.paramName,
|
|
7531
|
+
message.value
|
|
7532
|
+
);
|
|
7533
|
+
}
|
|
5600
7534
|
break;
|
|
5601
7535
|
case "syncBusStripInsertParamByName":
|
|
5602
|
-
this.engine.
|
|
7536
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7537
|
+
if (!this.engine.applyBusStripInsertParamByNameNow(
|
|
5603
7538
|
message.busId,
|
|
5604
7539
|
message.insertIndex,
|
|
5605
7540
|
message.paramName,
|
|
5606
7541
|
message.value
|
|
7542
|
+
)) {
|
|
7543
|
+
this.engine.restoreBusStripInsertParamByName(
|
|
7544
|
+
message.busId,
|
|
7545
|
+
message.insertIndex,
|
|
7546
|
+
message.paramName,
|
|
7547
|
+
message.value
|
|
7548
|
+
);
|
|
7549
|
+
}
|
|
7550
|
+
break;
|
|
7551
|
+
case "syncBusStripInsertBypassed":
|
|
7552
|
+
this.engine.setBusStripInsertBypassed(
|
|
7553
|
+
message.busId,
|
|
7554
|
+
message.insertIndex,
|
|
7555
|
+
message.bypassed,
|
|
7556
|
+
message.resetOnBypass
|
|
5607
7557
|
);
|
|
5608
7558
|
break;
|
|
5609
7559
|
case "syncTrackStripPan":
|
|
@@ -5618,6 +7568,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5618
7568
|
case "syncTrackStripDualPan":
|
|
5619
7569
|
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
5620
7570
|
break;
|
|
7571
|
+
case "syncBusStripPan":
|
|
7572
|
+
this.engine.setBusStripPan(message.busId, message.pan);
|
|
7573
|
+
break;
|
|
7574
|
+
case "syncBusStripPanLaw":
|
|
7575
|
+
this.engine.setBusStripPanLaw(message.busId, message.panLaw);
|
|
7576
|
+
break;
|
|
7577
|
+
case "syncBusStripPanMode":
|
|
7578
|
+
this.engine.setBusStripPanMode(message.busId, message.panMode);
|
|
7579
|
+
break;
|
|
7580
|
+
case "syncBusStripDualPan":
|
|
7581
|
+
this.engine.setBusStripDualPan(message.busId, message.leftPan, message.rightPan);
|
|
7582
|
+
break;
|
|
5621
7583
|
case "syncTrackStripChannelDelaySamples":
|
|
5622
7584
|
this.engine.setTrackStripChannelDelaySamples(message.trackId, message.delaySamples);
|
|
5623
7585
|
break;
|
|
@@ -5669,8 +7631,36 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5669
7631
|
message.renderFrame
|
|
5670
7632
|
);
|
|
5671
7633
|
break;
|
|
7634
|
+
case "syncMidiPitchBend":
|
|
7635
|
+
this.engine.pushMidiPitchBend(
|
|
7636
|
+
message.destinationId,
|
|
7637
|
+
message.group,
|
|
7638
|
+
message.channel,
|
|
7639
|
+
message.data0,
|
|
7640
|
+
message.renderFrame
|
|
7641
|
+
);
|
|
7642
|
+
break;
|
|
7643
|
+
case "syncMidiChannelPressure":
|
|
7644
|
+
this.engine.pushMidiChannelPressure(
|
|
7645
|
+
message.destinationId,
|
|
7646
|
+
message.group,
|
|
7647
|
+
message.channel,
|
|
7648
|
+
message.data0,
|
|
7649
|
+
message.renderFrame
|
|
7650
|
+
);
|
|
7651
|
+
break;
|
|
7652
|
+
case "syncMidiPolyPressure":
|
|
7653
|
+
this.engine.pushMidiPolyPressure(
|
|
7654
|
+
message.destinationId,
|
|
7655
|
+
message.group,
|
|
7656
|
+
message.channel,
|
|
7657
|
+
message.data0,
|
|
7658
|
+
message.data1,
|
|
7659
|
+
message.renderFrame
|
|
7660
|
+
);
|
|
7661
|
+
break;
|
|
5672
7662
|
case "syncMidiUmp":
|
|
5673
|
-
this.engine.pushMidiUmp(message.destinationId, message.word0, message.renderFrame);
|
|
7663
|
+
this.engine.pushMidiUmp(message.destinationId, [message.word0], message.renderFrame);
|
|
5674
7664
|
break;
|
|
5675
7665
|
case "syncMidiSysex":
|
|
5676
7666
|
this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
|
|
@@ -5723,6 +7713,31 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5723
7713
|
message.portTimeSamples
|
|
5724
7714
|
);
|
|
5725
7715
|
break;
|
|
7716
|
+
case "syncMidiInputPitchBend":
|
|
7717
|
+
this.engine.pushMidiInputPitchBend(
|
|
7718
|
+
message.group,
|
|
7719
|
+
message.channel,
|
|
7720
|
+
message.data0,
|
|
7721
|
+
message.portTimeSamples
|
|
7722
|
+
);
|
|
7723
|
+
break;
|
|
7724
|
+
case "syncMidiInputChannelPressure":
|
|
7725
|
+
this.engine.pushMidiInputChannelPressure(
|
|
7726
|
+
message.group,
|
|
7727
|
+
message.channel,
|
|
7728
|
+
message.data0,
|
|
7729
|
+
message.portTimeSamples
|
|
7730
|
+
);
|
|
7731
|
+
break;
|
|
7732
|
+
case "syncMidiInputPolyPressure":
|
|
7733
|
+
this.engine.pushMidiInputPolyPressure(
|
|
7734
|
+
message.group,
|
|
7735
|
+
message.channel,
|
|
7736
|
+
message.data0,
|
|
7737
|
+
message.data1,
|
|
7738
|
+
message.portTimeSamples
|
|
7739
|
+
);
|
|
7740
|
+
break;
|
|
5726
7741
|
}
|
|
5727
7742
|
}
|
|
5728
7743
|
receiveCaptureRequest(message) {
|
|
@@ -5803,11 +7818,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5803
7818
|
this.closed = true;
|
|
5804
7819
|
}
|
|
5805
7820
|
}
|
|
5806
|
-
drainCommands() {
|
|
7821
|
+
drainCommands(limit = 64) {
|
|
5807
7822
|
if (!this.commandRing) {
|
|
5808
7823
|
return;
|
|
5809
7824
|
}
|
|
5810
|
-
for (let i = 0; i <
|
|
7825
|
+
for (let i = 0; i < limit; i++) {
|
|
5811
7826
|
const command = popSonareEngineCommandRingBuffer(this.commandRing);
|
|
5812
7827
|
if (!command) {
|
|
5813
7828
|
return;
|
|
@@ -5994,10 +8009,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5994
8009
|
if (meter.frame !== this.lastMeterFrame) {
|
|
5995
8010
|
this.lastMeterFrame = meter.frame;
|
|
5996
8011
|
}
|
|
5997
|
-
if (this.
|
|
5998
|
-
this.
|
|
8012
|
+
if (this.transport?.onMeter) {
|
|
8013
|
+
this.transport.onMeter(meter);
|
|
5999
8014
|
} else {
|
|
6000
|
-
this.transport?.onMeter?.(meter);
|
|
6001
8015
|
this.transport?.postMessage?.(meter);
|
|
6002
8016
|
}
|
|
6003
8017
|
}
|
|
@@ -6014,29 +8028,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
6014
8028
|
}
|
|
6015
8029
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
6016
8030
|
}
|
|
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
|
-
}
|
|
8031
|
+
// A snapshot-object ring writer used to live here as well. It was
|
|
8032
|
+
// unreachable: publishMeters returns after the writeMeterScratch loop
|
|
8033
|
+
// whenever a ring exists, so the ring is never fed from a materialised
|
|
8034
|
+
// snapshot. writeMeterScratch above is the only ring writer, and it keeps the
|
|
8035
|
+
// render callback allocation-free by reading the engine's scratch registers
|
|
8036
|
+
// directly.
|
|
8037
|
+
//
|
|
8038
|
+
// writeIndex is a free-running monotonic counter, so an overflow guard would
|
|
8039
|
+
// fire on essentially every write past the first `capacity` records and store
|
|
8040
|
+
// an ever-growing value, not a dropped-record count. Readers already detect
|
|
8041
|
+
// silent overrun via firstReadable = max(readIndex, writeIndex - capacity),
|
|
8042
|
+
// so header slot 3 is left at its initial 0.
|
|
6040
8043
|
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
6041
8044
|
// the lock-free SAB scope ring. No allocation on the render path: records are
|
|
6042
8045
|
// written field-by-field into the ring.
|
|
@@ -6244,15 +8247,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6244
8247
|
}
|
|
6245
8248
|
return;
|
|
6246
8249
|
}
|
|
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
|
-
}
|
|
8250
|
+
this.routeMessage(event.data);
|
|
6256
8251
|
};
|
|
6257
8252
|
if (port?.addEventListener) {
|
|
6258
8253
|
port.addEventListener("message", onMessage);
|
|
@@ -6271,18 +8266,35 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6271
8266
|
}
|
|
6272
8267
|
return true;
|
|
6273
8268
|
}
|
|
8269
|
+
/**
|
|
8270
|
+
* Single dispatch point for the port, shared with the buffered replay so
|
|
8271
|
+
* the two cannot recognize different message sets.
|
|
8272
|
+
*/
|
|
8273
|
+
routeMessage(data) {
|
|
8274
|
+
const bridge = this.bridge;
|
|
8275
|
+
if (!bridge) {
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8278
|
+
if (isEngineCommandRecord(data)) {
|
|
8279
|
+
bridge.receiveCommand(data);
|
|
8280
|
+
} else if (isEngineSyncMessage(data)) {
|
|
8281
|
+
bridge.receiveSync(data);
|
|
8282
|
+
} else if (isEngineCaptureRequestMessage(data)) {
|
|
8283
|
+
bridge.receiveCaptureRequest(data);
|
|
8284
|
+
} else if (isEngineTransportRequestMessage(data)) {
|
|
8285
|
+
bridge.receiveTransportRequest(data);
|
|
8286
|
+
} else if (isRecord(data) && typeof data.type === "string" && data.type.startsWith("sync")) {
|
|
8287
|
+
this.port?.postMessage?.({
|
|
8288
|
+
type: "syncError",
|
|
8289
|
+
syncType: data.type,
|
|
8290
|
+
message: `Unrecognized worklet sync message: ${data.type}`
|
|
8291
|
+
});
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
6274
8294
|
replayPendingMessages() {
|
|
6275
8295
|
const messages = this.pendingMessages.splice(0);
|
|
6276
8296
|
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
|
-
}
|
|
8297
|
+
this.routeMessage(data);
|
|
6286
8298
|
}
|
|
6287
8299
|
}
|
|
6288
8300
|
async initializeEmbind(options, port) {
|
|
@@ -6335,15 +8347,48 @@ var SonareWorkletProcessor = class {
|
|
|
6335
8347
|
this.processedFrames = 0;
|
|
6336
8348
|
this.lastMeterFrame = 0;
|
|
6337
8349
|
this.lastSpectrumFrame = 0;
|
|
8350
|
+
/**
|
|
8351
|
+
* Reused meter record, so a publish writes fields instead of allocating one.
|
|
8352
|
+
*
|
|
8353
|
+
* `targetId` is always the master and the four LUFS / gain-reduction fields
|
|
8354
|
+
* are always unavailable here — the mixer worklet does not run the
|
|
8355
|
+
* K-weighting filters, and a floor value would read as silence — so both are
|
|
8356
|
+
* set once rather than per interval.
|
|
8357
|
+
*/
|
|
8358
|
+
this.meterScratch = {
|
|
8359
|
+
type: "meter",
|
|
8360
|
+
targetId: 0,
|
|
8361
|
+
frame: 0,
|
|
8362
|
+
peakDbL: 0,
|
|
8363
|
+
peakDbR: 0,
|
|
8364
|
+
rmsDbL: 0,
|
|
8365
|
+
rmsDbR: 0,
|
|
8366
|
+
correlation: 0,
|
|
8367
|
+
truePeakDbL: 0,
|
|
8368
|
+
truePeakDbR: 0,
|
|
8369
|
+
momentaryLufs: Number.NaN,
|
|
8370
|
+
shortTermLufs: Number.NaN,
|
|
8371
|
+
integratedLufs: Number.NaN,
|
|
8372
|
+
gainReductionDb: Number.NaN
|
|
8373
|
+
};
|
|
6338
8374
|
if (!options.sceneJson) {
|
|
6339
8375
|
throw new Error("sceneJson is required.");
|
|
6340
8376
|
}
|
|
6341
8377
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6342
8378
|
this.blockSize = options.blockSize ?? 128;
|
|
6343
|
-
this.meterIntervalFrames =
|
|
6344
|
-
|
|
8379
|
+
this.meterIntervalFrames = requireIntegerOption(
|
|
8380
|
+
options.meterIntervalFrames,
|
|
8381
|
+
2048,
|
|
8382
|
+
"meterIntervalFrames",
|
|
8383
|
+
0
|
|
8384
|
+
);
|
|
8385
|
+
this.spectrumIntervalFrames = requireIntegerOption(
|
|
8386
|
+
options.spectrumIntervalFrames,
|
|
8387
|
+
0,
|
|
8388
|
+
"spectrumIntervalFrames",
|
|
8389
|
+
0
|
|
8390
|
+
);
|
|
6345
8391
|
this.transport = transport;
|
|
6346
|
-
this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
|
|
6347
8392
|
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
6348
8393
|
this.spectrumRing = options.spectrumSharedBuffer ? spectrumRingFromSharedBuffer(
|
|
6349
8394
|
options.spectrumSharedBuffer,
|
|
@@ -6360,6 +8405,7 @@ var SonareWorkletProcessor = class {
|
|
|
6360
8405
|
throw new Error("stripCount must match the scene strip count.");
|
|
6361
8406
|
}
|
|
6362
8407
|
this.realtime = this.mixer.createRealtimeBuffer();
|
|
8408
|
+
this.mixer.configureMeter(this.meterIntervalFrames > 0, 4);
|
|
6363
8409
|
}
|
|
6364
8410
|
process(inputs, outputs) {
|
|
6365
8411
|
if (this.closed) {
|
|
@@ -6406,10 +8452,7 @@ var SonareWorkletProcessor = class {
|
|
|
6406
8452
|
}
|
|
6407
8453
|
}
|
|
6408
8454
|
this.processedFrames += usable;
|
|
6409
|
-
this.publishMeter(
|
|
6410
|
-
this.realtime.outLeft.subarray(0, usable),
|
|
6411
|
-
this.realtime.outRight.subarray(0, usable)
|
|
6412
|
-
);
|
|
8455
|
+
this.publishMeter();
|
|
6413
8456
|
this.publishSpectrum(
|
|
6414
8457
|
this.realtime.outLeft.subarray(0, usable),
|
|
6415
8458
|
this.realtime.outRight.subarray(0, usable)
|
|
@@ -6425,7 +8468,11 @@ var SonareWorkletProcessor = class {
|
|
|
6425
8468
|
return;
|
|
6426
8469
|
}
|
|
6427
8470
|
if (message.type === "setMeterInterval") {
|
|
6428
|
-
|
|
8471
|
+
const frames = Math.max(0, Math.floor(message.frames));
|
|
8472
|
+
if (frames > 0 !== this.meterIntervalFrames > 0) {
|
|
8473
|
+
this.mixer.configureMeter(frames > 0, 4);
|
|
8474
|
+
}
|
|
8475
|
+
this.meterIntervalFrames = frames;
|
|
6429
8476
|
return;
|
|
6430
8477
|
}
|
|
6431
8478
|
if (message.type === "scheduleInsertAutomation") {
|
|
@@ -6445,58 +8492,52 @@ var SonareWorkletProcessor = class {
|
|
|
6445
8492
|
this.closed = true;
|
|
6446
8493
|
}
|
|
6447
8494
|
}
|
|
6448
|
-
publishMeter(
|
|
6449
|
-
if (!this.transport || this.meterIntervalFrames <= 0) {
|
|
8495
|
+
publishMeter() {
|
|
8496
|
+
if (!this.transport && !this.meterRing || this.meterIntervalFrames <= 0) {
|
|
6450
8497
|
return;
|
|
6451
8498
|
}
|
|
6452
8499
|
if (this.processedFrames - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
6453
8500
|
return;
|
|
6454
8501
|
}
|
|
6455
8502
|
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;
|
|
8503
|
+
if (!this.mixer.latchMeterSnapshot()) {
|
|
8504
|
+
return;
|
|
8505
|
+
}
|
|
8506
|
+
if (this.meterRing) {
|
|
8507
|
+
const meter2 = this.meterScratch;
|
|
8508
|
+
meter2.frame = this.processedFrames;
|
|
8509
|
+
meter2.peakDbL = this.mixer.meterScratchValue(0);
|
|
8510
|
+
meter2.peakDbR = this.mixer.meterScratchValue(1);
|
|
8511
|
+
meter2.rmsDbL = this.mixer.meterScratchValue(2);
|
|
8512
|
+
meter2.rmsDbR = this.mixer.meterScratchValue(3);
|
|
8513
|
+
meter2.correlation = this.mixer.meterScratchValue(4);
|
|
8514
|
+
meter2.truePeakDbL = this.mixer.meterScratchValue(5);
|
|
8515
|
+
meter2.truePeakDbR = this.mixer.meterScratchValue(6);
|
|
8516
|
+
this.writeMeterRing(meter2);
|
|
8517
|
+
return;
|
|
6475
8518
|
}
|
|
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
8519
|
const meter = {
|
|
6480
8520
|
type: "meter",
|
|
6481
8521
|
targetId: 0,
|
|
6482
8522
|
frame: this.processedFrames,
|
|
6483
|
-
peakDbL:
|
|
6484
|
-
peakDbR:
|
|
6485
|
-
rmsDbL:
|
|
6486
|
-
rmsDbR:
|
|
6487
|
-
correlation:
|
|
6488
|
-
truePeakDbL:
|
|
6489
|
-
truePeakDbR:
|
|
8523
|
+
peakDbL: this.mixer.meterScratchValue(0),
|
|
8524
|
+
peakDbR: this.mixer.meterScratchValue(1),
|
|
8525
|
+
rmsDbL: this.mixer.meterScratchValue(2),
|
|
8526
|
+
rmsDbR: this.mixer.meterScratchValue(3),
|
|
8527
|
+
correlation: this.mixer.meterScratchValue(4),
|
|
8528
|
+
truePeakDbL: this.mixer.meterScratchValue(5),
|
|
8529
|
+
truePeakDbR: this.mixer.meterScratchValue(6),
|
|
8530
|
+
// Declared unavailable rather than floored: the mixer worklet does not run
|
|
8531
|
+
// the K-weighting filters, and a floor value would read as silence.
|
|
6490
8532
|
momentaryLufs: Number.NaN,
|
|
6491
8533
|
shortTermLufs: Number.NaN,
|
|
6492
8534
|
integratedLufs: Number.NaN,
|
|
6493
8535
|
gainReductionDb: Number.NaN
|
|
6494
8536
|
};
|
|
6495
|
-
this.transport
|
|
6496
|
-
|
|
6497
|
-
this.writeMeterRing(meter);
|
|
8537
|
+
if (this.transport?.onMeter) {
|
|
8538
|
+
this.transport.onMeter(meter);
|
|
6498
8539
|
} else {
|
|
6499
|
-
this.transport
|
|
8540
|
+
this.transport?.postMessage?.(meter);
|
|
6500
8541
|
}
|
|
6501
8542
|
}
|
|
6502
8543
|
writeMeterRing(meter) {
|
|
@@ -6540,8 +8581,11 @@ var SonareWorkletProcessor = class {
|
|
|
6540
8581
|
frame: this.processedFrames,
|
|
6541
8582
|
bands: new Float32Array(this.spectrumBands)
|
|
6542
8583
|
};
|
|
6543
|
-
this.transport?.onSpectrum
|
|
6544
|
-
|
|
8584
|
+
if (this.transport?.onSpectrum) {
|
|
8585
|
+
this.transport.onSpectrum(spectrum);
|
|
8586
|
+
} else {
|
|
8587
|
+
this.transport?.postMessage?.(spectrum);
|
|
8588
|
+
}
|
|
6545
8589
|
}
|
|
6546
8590
|
computeSpectrum(left, right) {
|
|
6547
8591
|
const n = Math.max(1, Math.min(left.length, right.length));
|
|
@@ -6609,13 +8653,232 @@ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
|
6609
8653
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
6610
8654
|
}
|
|
6611
8655
|
|
|
8656
|
+
// src/worklet/playback-processor.ts
|
|
8657
|
+
var MAX_INPUT_CHANNELS = 8;
|
|
8658
|
+
var SPEAKER_LAYOUT_CHANNELS = { stereo: 2, "5.1": 6, "7.1": 8 };
|
|
8659
|
+
function configObject(config) {
|
|
8660
|
+
if (config === void 0) {
|
|
8661
|
+
return {};
|
|
8662
|
+
}
|
|
8663
|
+
return typeof config === "string" ? JSON.parse(config) : config;
|
|
8664
|
+
}
|
|
8665
|
+
function outputChannelCount(config) {
|
|
8666
|
+
if (config.target?.kind !== "speakers") {
|
|
8667
|
+
return 2;
|
|
8668
|
+
}
|
|
8669
|
+
const count = SPEAKER_LAYOUT_CHANNELS[config.target.layout ?? ""];
|
|
8670
|
+
if (count === void 0) {
|
|
8671
|
+
throw new RangeError('target.layout must be "stereo", "5.1" or "7.1" for a speakers target');
|
|
8672
|
+
}
|
|
8673
|
+
return count;
|
|
8674
|
+
}
|
|
8675
|
+
var _SonarePlaybackWorkletProcessor = class _SonarePlaybackWorkletProcessor {
|
|
8676
|
+
constructor(options = {}, port) {
|
|
8677
|
+
this.inputPlanes = [];
|
|
8678
|
+
this.outputPlanes = [];
|
|
8679
|
+
this.unsupportedInputBlocks = 0;
|
|
8680
|
+
this.destroyed = false;
|
|
8681
|
+
this.port = port;
|
|
8682
|
+
this.maxBlockSize = requireIntegerOption(options.maxBlockSize, 128, "maxBlockSize", 1);
|
|
8683
|
+
const scopeRate = globalThis.sampleRate;
|
|
8684
|
+
const sampleRate = options.sampleRate ?? (typeof scopeRate === "number" ? scopeRate : 48e3);
|
|
8685
|
+
const hrtf = options.hrtf ? HrtfSet.fromBytes(new Uint8Array(options.hrtf)) : void 0;
|
|
8686
|
+
try {
|
|
8687
|
+
this.renderer = new PlaybackRenderer({
|
|
8688
|
+
config: options.config ?? {},
|
|
8689
|
+
hrtf,
|
|
8690
|
+
sampleRate,
|
|
8691
|
+
maxBlockSize: this.maxBlockSize
|
|
8692
|
+
});
|
|
8693
|
+
} finally {
|
|
8694
|
+
hrtf?.delete();
|
|
8695
|
+
}
|
|
8696
|
+
this.native = this.renderer.native;
|
|
8697
|
+
this.outputChannels = this.native.outputChannels();
|
|
8698
|
+
this.acquirePlanes();
|
|
8699
|
+
}
|
|
8700
|
+
/**
|
|
8701
|
+
* Handles a control-plane message. AudioWorklet port handlers run on the
|
|
8702
|
+
* rendering thread between `process()` calls, which is what makes `reset`
|
|
8703
|
+
* safe here. A refused message is answered with an `error` message.
|
|
8704
|
+
*/
|
|
8705
|
+
receiveMessage(message) {
|
|
8706
|
+
if (this.destroyed) {
|
|
8707
|
+
return;
|
|
8708
|
+
}
|
|
8709
|
+
try {
|
|
8710
|
+
switch (message.type) {
|
|
8711
|
+
case "config":
|
|
8712
|
+
this.renderer.setConfig(message.config);
|
|
8713
|
+
break;
|
|
8714
|
+
case "orientation":
|
|
8715
|
+
this.renderer.setHeadOrientation(message.yaw, message.pitch ?? 0, message.roll ?? 0);
|
|
8716
|
+
break;
|
|
8717
|
+
case "reset":
|
|
8718
|
+
this.renderer.reset();
|
|
8719
|
+
break;
|
|
8720
|
+
case "diagnostics":
|
|
8721
|
+
this.port?.postMessage?.({
|
|
8722
|
+
type: "diagnostics",
|
|
8723
|
+
diagnostics: this.diagnostics()
|
|
8724
|
+
});
|
|
8725
|
+
break;
|
|
8726
|
+
case "destroy":
|
|
8727
|
+
this.destroy();
|
|
8728
|
+
break;
|
|
8729
|
+
}
|
|
8730
|
+
} catch (error) {
|
|
8731
|
+
this.port?.postMessage?.({
|
|
8732
|
+
type: "error",
|
|
8733
|
+
request: message.type,
|
|
8734
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8735
|
+
});
|
|
8736
|
+
}
|
|
8737
|
+
}
|
|
8738
|
+
/** The renderer's diagnostics plus `unsupported_input_blocks`, as a plain object. */
|
|
8739
|
+
diagnostics() {
|
|
8740
|
+
return {
|
|
8741
|
+
...this.renderer.diagnostics(),
|
|
8742
|
+
unsupported_input_blocks: this.unsupportedInputBlocks
|
|
8743
|
+
};
|
|
8744
|
+
}
|
|
8745
|
+
process(inputs, outputs) {
|
|
8746
|
+
if (this.destroyed) {
|
|
8747
|
+
return false;
|
|
8748
|
+
}
|
|
8749
|
+
const output = outputs[0];
|
|
8750
|
+
const requested = output?.[0]?.length ?? 0;
|
|
8751
|
+
if (!output || requested === 0) {
|
|
8752
|
+
return true;
|
|
8753
|
+
}
|
|
8754
|
+
const frames = this.clampFrames(requested);
|
|
8755
|
+
if (this.inputPlanes[0]?.byteLength === 0 || this.outputPlanes[0]?.byteLength === 0) {
|
|
8756
|
+
this.acquirePlanes();
|
|
8757
|
+
}
|
|
8758
|
+
const input = inputs[0];
|
|
8759
|
+
const inChannels = input?.length ?? 0;
|
|
8760
|
+
let code;
|
|
8761
|
+
if (inChannels === 0) {
|
|
8762
|
+
code = this.native.processPreparedSilence(frames);
|
|
8763
|
+
} else if (inChannels > MAX_INPUT_CHANNELS) {
|
|
8764
|
+
this.unsupportedInputBlocks++;
|
|
8765
|
+
code = this.native.processPreparedSilence(frames);
|
|
8766
|
+
} else {
|
|
8767
|
+
for (let ch = 0; ch < inChannels; ch++) {
|
|
8768
|
+
const source = input[ch];
|
|
8769
|
+
const plane = this.inputPlanes[ch];
|
|
8770
|
+
const copied = Math.min(frames, source.length);
|
|
8771
|
+
plane.set(copied === source.length ? source : source.subarray(0, copied));
|
|
8772
|
+
if (copied < frames) {
|
|
8773
|
+
plane.fill(0, copied, frames);
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
code = this.native.processPrepared(inChannels, frames);
|
|
8777
|
+
if (code !== 0) {
|
|
8778
|
+
this.unsupportedInputBlocks++;
|
|
8779
|
+
code = this.native.processPreparedSilence(frames);
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
if (code === 0) {
|
|
8783
|
+
copyPlanesToOutput(output, this.outputPlanes, frames);
|
|
8784
|
+
} else {
|
|
8785
|
+
for (const channel of output) {
|
|
8786
|
+
channel.fill(0);
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
return true;
|
|
8790
|
+
}
|
|
8791
|
+
destroy() {
|
|
8792
|
+
if (this.destroyed) {
|
|
8793
|
+
return;
|
|
8794
|
+
}
|
|
8795
|
+
this.destroyed = true;
|
|
8796
|
+
this.renderer.delete();
|
|
8797
|
+
}
|
|
8798
|
+
acquirePlanes() {
|
|
8799
|
+
this.inputPlanes = [];
|
|
8800
|
+
for (let ch = 0; ch < MAX_INPUT_CHANNELS; ch++) {
|
|
8801
|
+
this.inputPlanes.push(this.native.inputPlane(ch));
|
|
8802
|
+
}
|
|
8803
|
+
this.outputPlanes = [];
|
|
8804
|
+
for (let ch = 0; ch < this.outputChannels; ch++) {
|
|
8805
|
+
this.outputPlanes.push(this.native.outputPlane(ch));
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
// Frames past the construction-time capacity are left silent rather than
|
|
8809
|
+
// reallocating on the audio thread.
|
|
8810
|
+
clampFrames(frames) {
|
|
8811
|
+
if (frames <= this.maxBlockSize) {
|
|
8812
|
+
return frames;
|
|
8813
|
+
}
|
|
8814
|
+
if (!_SonarePlaybackWorkletProcessor.warnedBlockOverflow) {
|
|
8815
|
+
_SonarePlaybackWorkletProcessor.warnedBlockOverflow = true;
|
|
8816
|
+
console.warn(
|
|
8817
|
+
`SonarePlaybackWorkletProcessor: requested ${frames} frames exceeds maxBlockSize ${this.maxBlockSize}; clamping.`
|
|
8818
|
+
);
|
|
8819
|
+
}
|
|
8820
|
+
return this.maxBlockSize;
|
|
8821
|
+
}
|
|
8822
|
+
};
|
|
8823
|
+
_SonarePlaybackWorkletProcessor.warnedBlockOverflow = false;
|
|
8824
|
+
var SonarePlaybackWorkletProcessor = _SonarePlaybackWorkletProcessor;
|
|
8825
|
+
function registerSonarePlaybackWorkletProcessor(name = "sonare-playback-processor") {
|
|
8826
|
+
const scope = globalThis;
|
|
8827
|
+
if (!scope.AudioWorkletProcessor || !scope.registerProcessor) {
|
|
8828
|
+
throw new Error("AudioWorkletProcessor is not available in this context.");
|
|
8829
|
+
}
|
|
8830
|
+
const Base = scope.AudioWorkletProcessor;
|
|
8831
|
+
class RegisteredSonarePlaybackWorkletProcessor extends Base {
|
|
8832
|
+
constructor(options) {
|
|
8833
|
+
super();
|
|
8834
|
+
const port = this.port;
|
|
8835
|
+
this.bridge = new SonarePlaybackWorkletProcessor(options?.processorOptions ?? {}, port);
|
|
8836
|
+
const onMessage = (event) => {
|
|
8837
|
+
if (isPlaybackMessage(event.data)) {
|
|
8838
|
+
this.bridge.receiveMessage(event.data);
|
|
8839
|
+
}
|
|
8840
|
+
};
|
|
8841
|
+
if (port?.addEventListener) {
|
|
8842
|
+
port.addEventListener("message", onMessage);
|
|
8843
|
+
port.start?.();
|
|
8844
|
+
} else if (port) {
|
|
8845
|
+
port.onmessage = onMessage;
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
process(inputs, outputs) {
|
|
8849
|
+
return this.bridge.process(inputs, outputs);
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
scope.registerProcessor(name, RegisteredSonarePlaybackWorkletProcessor);
|
|
8853
|
+
}
|
|
8854
|
+
function createSonarePlaybackNode(context, options = {}) {
|
|
8855
|
+
const config = configObject(options.config);
|
|
8856
|
+
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
8857
|
+
const processorOptions = {
|
|
8858
|
+
config: JSON.stringify(config),
|
|
8859
|
+
hrtf: options.hrtf,
|
|
8860
|
+
maxBlockSize: options.maxBlockSize,
|
|
8861
|
+
sampleRate: options.sampleRate ?? context.sampleRate
|
|
8862
|
+
};
|
|
8863
|
+
return factory(context, options.processorName ?? "sonare-playback-processor", {
|
|
8864
|
+
numberOfInputs: 1,
|
|
8865
|
+
numberOfOutputs: 1,
|
|
8866
|
+
outputChannelCount: [outputChannelCount(config)],
|
|
8867
|
+
channelCountMode: "max",
|
|
8868
|
+
channelInterpretation: "discrete",
|
|
8869
|
+
processorOptions
|
|
8870
|
+
});
|
|
8871
|
+
}
|
|
8872
|
+
|
|
6612
8873
|
// src/worklet/voice-changer-processor.ts
|
|
6613
8874
|
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
6614
8875
|
constructor(options = {}) {
|
|
8876
|
+
// Reused so the mono fan-out never builds an array on the audio thread.
|
|
8877
|
+
this.monoPlane = [new Float32Array(0)];
|
|
6615
8878
|
this.destroyed = false;
|
|
6616
8879
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6617
8880
|
this.blockSize = options.blockSize ?? 128;
|
|
6618
|
-
this.channelCount =
|
|
8881
|
+
this.channelCount = requireChannelCount(options.channelCount, 1);
|
|
6619
8882
|
this.changer = new RealtimeVoiceChanger(options.preset ?? "neutral-monitor");
|
|
6620
8883
|
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
6621
8884
|
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
@@ -6668,7 +8931,8 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6668
8931
|
this.monoInput.fill(0, 0, frames2);
|
|
6669
8932
|
}
|
|
6670
8933
|
this.changer.processPreparedMono(frames2);
|
|
6671
|
-
|
|
8934
|
+
this.monoPlane[0] = this.monoOutput;
|
|
8935
|
+
copyPlanesToOutput(output, this.monoPlane, frames2);
|
|
6672
8936
|
return true;
|
|
6673
8937
|
}
|
|
6674
8938
|
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
@@ -6686,12 +8950,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6686
8950
|
}
|
|
6687
8951
|
}
|
|
6688
8952
|
this.changer.processPreparedPlanar(frames);
|
|
6689
|
-
|
|
6690
|
-
const src = this.planarChannels[ch];
|
|
6691
|
-
if (src) {
|
|
6692
|
-
output[ch].set(src.subarray(0, frames));
|
|
6693
|
-
}
|
|
6694
|
-
}
|
|
8953
|
+
copyPlanesToOutput(output, this.planarChannels, frames);
|
|
6695
8954
|
return true;
|
|
6696
8955
|
}
|
|
6697
8956
|
destroy() {
|
|
@@ -6801,6 +9060,7 @@ export {
|
|
|
6801
9060
|
SonareEngineCommandType,
|
|
6802
9061
|
SonareEngineTelemetryError,
|
|
6803
9062
|
SonareEngineTelemetryType,
|
|
9063
|
+
SonarePlaybackWorkletProcessor,
|
|
6804
9064
|
SonareRealtimeEngineNode,
|
|
6805
9065
|
SonareRealtimeEngineWorkletProcessor,
|
|
6806
9066
|
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
@@ -6812,6 +9072,7 @@ export {
|
|
|
6812
9072
|
createSonareEngineTelemetryRingBuffer,
|
|
6813
9073
|
createSonareExternalMidiRingBuffer,
|
|
6814
9074
|
createSonareMeterRingBuffer,
|
|
9075
|
+
createSonarePlaybackNode,
|
|
6815
9076
|
createSonareScopeRingBuffer,
|
|
6816
9077
|
createSonareSpectrumRingBuffer,
|
|
6817
9078
|
decodeFrame,
|
|
@@ -6829,6 +9090,7 @@ export {
|
|
|
6829
9090
|
readSonareMeterRingBuffer,
|
|
6830
9091
|
readSonareScopeRingBuffer,
|
|
6831
9092
|
readSonareSpectrumRingBuffer,
|
|
9093
|
+
registerSonarePlaybackWorkletProcessor,
|
|
6832
9094
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
6833
9095
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
6834
9096
|
registerSonareWorkletProcessor,
|