@libraz/libsonare 1.7.1 → 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 -170
- 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 -5837
- 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 -7329
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3816 -1359
- 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 -2142
- package/dist/worklet.d.ts.map +1 -0
- package/dist/worklet.js +2889 -466
- 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 +285 -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 +64 -24
- package/src/project_class.ts +502 -29
- package/src/project_internal.ts +151 -42
- package/src/project_synth.ts +67 -1
- package/src/project_types.ts +302 -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 +750 -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 +1125 -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 +212 -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 +85 -28
- package/src/worklet/engine-offline.ts +14 -8
- package/src/worklet/engine-parameter-facade.ts +21 -0
- package/src/worklet/engine-processor.ts +307 -71
- 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 +239 -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 +22 -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) {
|
|
@@ -579,6 +602,10 @@ var AUTOMATION_CURVE_VALUES = {
|
|
|
579
602
|
hold: 2,
|
|
580
603
|
"s-curve": 3
|
|
581
604
|
};
|
|
605
|
+
var PROJECT_AUTOMATION_CURVE_VALUES = {
|
|
606
|
+
...AUTOMATION_CURVE_VALUES,
|
|
607
|
+
scurve: 3
|
|
608
|
+
};
|
|
582
609
|
var PAN_LAW_VALUES = {
|
|
583
610
|
const3db: 0,
|
|
584
611
|
"const-3db": 0,
|
|
@@ -604,6 +631,7 @@ var PAN_MODE_VALUES = {
|
|
|
604
631
|
};
|
|
605
632
|
var METER_TAP_VALUES = { preFader: 0, postFader: 1 };
|
|
606
633
|
var SEND_TIMING_VALUES = { postFader: 0, preFader: 1 };
|
|
634
|
+
var SIDECHAIN_SOURCE_KIND_VALUES = { track: 0, bus: 1 };
|
|
607
635
|
var TRACK_MONITOR_MODE_VALUES = { off: 0, pfl: 1, afl: 2 };
|
|
608
636
|
function automationCurveCode(curve) {
|
|
609
637
|
return resolveEnumOrdinal(curve, AUTOMATION_CURVE_VALUES, "automation curve");
|
|
@@ -622,12 +650,276 @@ function meterTapCode(tap) {
|
|
|
622
650
|
function sendTimingCode(timing) {
|
|
623
651
|
return resolveEnumOrdinal(timing, SEND_TIMING_VALUES, "send timing");
|
|
624
652
|
}
|
|
653
|
+
function sidechainSourceKindCode(kind) {
|
|
654
|
+
return resolveEnumOrdinal(kind, SIDECHAIN_SOURCE_KIND_VALUES, "sidechain source kind");
|
|
655
|
+
}
|
|
625
656
|
function trackMonitorModeCode(mode) {
|
|
626
657
|
return resolveEnumOrdinal(mode, TRACK_MONITOR_MODE_VALUES, "track monitor mode");
|
|
627
658
|
}
|
|
628
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
|
+
|
|
629
896
|
// src/realtime_engine.ts
|
|
630
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
|
+
}
|
|
631
923
|
function engineCapabilities() {
|
|
632
924
|
const abiVersion = getSonareModule().engineAbiVersion();
|
|
633
925
|
const sharedArrayBuffer = typeof globalThis.SharedArrayBuffer === "function";
|
|
@@ -643,8 +935,9 @@ function engineCapabilities() {
|
|
|
643
935
|
mode: sharedArrayBuffer && atomics ? "sab" : "postMessage"
|
|
644
936
|
};
|
|
645
937
|
}
|
|
646
|
-
var RealtimeEngine = class {
|
|
938
|
+
var RealtimeEngine = class _RealtimeEngine {
|
|
647
939
|
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
940
|
+
this.released = false;
|
|
648
941
|
const module2 = getSonareModule();
|
|
649
942
|
const capabilities = engineCapabilities();
|
|
650
943
|
if (!capabilities.abiCompatible) {
|
|
@@ -660,6 +953,15 @@ var RealtimeEngine = class {
|
|
|
660
953
|
maxChannels
|
|
661
954
|
);
|
|
662
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
|
+
*/
|
|
663
965
|
prepare(sampleRate, maxBlockSize, commandCapacity = 1024, telemetryCapacity = 1024, maxChannels = 64) {
|
|
664
966
|
this.native.prepareWithChannels(
|
|
665
967
|
sampleRate,
|
|
@@ -706,9 +1008,13 @@ var RealtimeEngine = class {
|
|
|
706
1008
|
* scheduled MIDI clips routed to that destination render through the synth.
|
|
707
1009
|
* Unknown preset names throw. An object patch's `destinationId` is a JS
|
|
708
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.
|
|
709
1015
|
*/
|
|
710
1016
|
setSynthInstrument(patch = {}, destinationId = (typeof patch === "object" ? patch.destinationId : void 0) ?? 0) {
|
|
711
|
-
this.native.setSynthInstrument(destinationId, patch);
|
|
1017
|
+
this.native.setSynthInstrument(destinationId, normalizeSynthInstrument(patch));
|
|
712
1018
|
}
|
|
713
1019
|
/**
|
|
714
1020
|
* Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
|
|
@@ -761,6 +1067,113 @@ var RealtimeEngine = class {
|
|
|
761
1067
|
midiCcBindingCount() {
|
|
762
1068
|
return this.native.midiCcBindingCount();
|
|
763
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
|
+
}
|
|
764
1177
|
/** Install/replace a live non-destructive MIDI-FX insert for one destination. */
|
|
765
1178
|
setMidiFx(destinationId, configJson) {
|
|
766
1179
|
this.native.setMidiFx(destinationId, configJson);
|
|
@@ -806,6 +1219,12 @@ var RealtimeEngine = class {
|
|
|
806
1219
|
* block / animation frame. `maxRecords` caps the number of output events
|
|
807
1220
|
* returned — the shared unit across every surface. Events past the cap stay
|
|
808
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.
|
|
809
1228
|
*/
|
|
810
1229
|
drainExternalMidi(maxRecords = 1024) {
|
|
811
1230
|
return this.native.drainExternalMidi(maxRecords);
|
|
@@ -818,7 +1237,7 @@ var RealtimeEngine = class {
|
|
|
818
1237
|
return this.native.externalMidiScratchDestinationId();
|
|
819
1238
|
}
|
|
820
1239
|
externalMidiScratchRenderFrame() {
|
|
821
|
-
return this.native.externalMidiScratchRenderFrame();
|
|
1240
|
+
return Number(this.native.externalMidiScratchRenderFrame());
|
|
822
1241
|
}
|
|
823
1242
|
externalMidiScratchByteWord() {
|
|
824
1243
|
return this.native.externalMidiScratchByteWord();
|
|
@@ -838,6 +1257,31 @@ var RealtimeEngine = class {
|
|
|
838
1257
|
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
839
1258
|
this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
840
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
|
+
}
|
|
841
1285
|
pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
|
|
842
1286
|
this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
843
1287
|
}
|
|
@@ -853,9 +1297,53 @@ var RealtimeEngine = class {
|
|
|
853
1297
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
854
1298
|
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
855
1299
|
}
|
|
856
|
-
/**
|
|
857
|
-
|
|
858
|
-
|
|
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);
|
|
859
1347
|
}
|
|
860
1348
|
/**
|
|
861
1349
|
* Queue an immediate (live) MIDI SysEx frame to a MIDI destination. `data` is
|
|
@@ -902,10 +1390,18 @@ var RealtimeEngine = class {
|
|
|
902
1390
|
settleParameters() {
|
|
903
1391
|
this.native.settleParameters();
|
|
904
1392
|
}
|
|
1393
|
+
/** Snap only insert automation slots after structural replay. */
|
|
1394
|
+
settleInsertParameters() {
|
|
1395
|
+
this.native.settleInsertParameters();
|
|
1396
|
+
}
|
|
905
1397
|
/** Drains queued commands on an offline/control-only engine immediately. */
|
|
906
1398
|
flushControlCommands() {
|
|
907
1399
|
this.native.flushControlCommands();
|
|
908
1400
|
}
|
|
1401
|
+
/** Applies commands already due on a control-only mirror, retaining future commands. */
|
|
1402
|
+
applyCommandsDueNowPreservingFuture() {
|
|
1403
|
+
this.native.applyCommandsDueNowPreservingFuture();
|
|
1404
|
+
}
|
|
909
1405
|
seekPpq(ppq, renderFrame = -1) {
|
|
910
1406
|
this.native.seekPpq(ppq, renderFrame);
|
|
911
1407
|
}
|
|
@@ -1001,6 +1497,18 @@ var RealtimeEngine = class {
|
|
|
1001
1497
|
clipCount() {
|
|
1002
1498
|
return this.native.clipCount();
|
|
1003
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
|
+
}
|
|
1004
1512
|
setTrackLanes(lanes) {
|
|
1005
1513
|
this.native.setTrackLanes(
|
|
1006
1514
|
lanes.map((lane) => {
|
|
@@ -1010,14 +1518,7 @@ var RealtimeEngine = class {
|
|
|
1010
1518
|
if (!lane.sends) {
|
|
1011
1519
|
return lane;
|
|
1012
1520
|
}
|
|
1013
|
-
return {
|
|
1014
|
-
...lane,
|
|
1015
|
-
sends: lane.sends.map((send) => ({
|
|
1016
|
-
...send,
|
|
1017
|
-
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1018
|
-
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1019
|
-
}))
|
|
1020
|
-
};
|
|
1521
|
+
return { ...lane, sends: _RealtimeEngine.normalizeSends(lane.sends) };
|
|
1021
1522
|
})
|
|
1022
1523
|
);
|
|
1023
1524
|
}
|
|
@@ -1029,7 +1530,25 @@ var RealtimeEngine = class {
|
|
|
1029
1530
|
this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
1030
1531
|
}
|
|
1031
1532
|
setTrackBuses(buses) {
|
|
1032
|
-
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);
|
|
1033
1552
|
}
|
|
1034
1553
|
setBusStripJson(busId, sceneJson) {
|
|
1035
1554
|
try {
|
|
@@ -1062,6 +1581,17 @@ var RealtimeEngine = class {
|
|
|
1062
1581
|
setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1063
1582
|
this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
1064
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
|
+
}
|
|
1065
1595
|
setMasterStripJson(sceneJson) {
|
|
1066
1596
|
try {
|
|
1067
1597
|
JSON.parse(sceneJson);
|
|
@@ -1085,22 +1615,59 @@ var RealtimeEngine = class {
|
|
|
1085
1615
|
}
|
|
1086
1616
|
/**
|
|
1087
1617
|
* Changes one track-strip insert parameter in realtime, addressed by the
|
|
1088
|
-
* processor's JSON-key parameter name
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
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.
|
|
1092
1624
|
*/
|
|
1093
1625
|
setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1094
1626
|
this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1095
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
|
+
}
|
|
1096
1636
|
/** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1097
1637
|
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1098
1638
|
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1099
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
|
+
}
|
|
1100
1646
|
/** Bus-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1101
1647
|
setBusStripInsertParamByName(busId, insertIndex, paramName, value) {
|
|
1102
1648
|
this.native.setBusStripInsertParamByName(busId, insertIndex, paramName, value);
|
|
1103
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
|
+
}
|
|
1104
1671
|
/** Bus-strip counterpart of {@link setTrackStripInsertBypassed}. */
|
|
1105
1672
|
setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1106
1673
|
this.native.setBusStripInsertBypassed(busId, insertIndex, bypassed, resetOnBypass);
|
|
@@ -1111,6 +1678,15 @@ var RealtimeEngine = class {
|
|
|
1111
1678
|
* Returns `-1` when the track, insert, or name is unknown. (The Python binding
|
|
1112
1679
|
* raises a `SonareError` for an unknown id where Node/WASM return the `-1`
|
|
1113
1680
|
* sentinel.)
|
|
1681
|
+
*
|
|
1682
|
+
* This trio is how a mastering processor gets time-varying automation: the
|
|
1683
|
+
* `eq.*`, `dynamics.*`, `saturation.*`, `spectral.*`, `stereo.*`,
|
|
1684
|
+
* `maximizer.*` and `multiband.*` processors are all available as strip
|
|
1685
|
+
* inserts, so placing one on a strip and resolving its parameter here drives
|
|
1686
|
+
* it at audio-block precision, live and offline alike. The whole-signal
|
|
1687
|
+
* stages of the offline mastering chain (`repair.*`, `loudness`, and the
|
|
1688
|
+
* match stages) have no insert form and no automation id: they buffer the
|
|
1689
|
+
* entire signal by construction and do not run on the realtime path.
|
|
1114
1690
|
*/
|
|
1115
1691
|
resolveTrackInsertAutomationId(trackId, insertIndex, paramName) {
|
|
1116
1692
|
return this.native.resolveTrackInsertAutomationId(trackId, insertIndex, paramName);
|
|
@@ -1121,6 +1697,41 @@ var RealtimeEngine = class {
|
|
|
1121
1697
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
1122
1698
|
return this.native.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
1123
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
|
+
}
|
|
1124
1735
|
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
1125
1736
|
setTrackStripPan(trackId, pan) {
|
|
1126
1737
|
this.native.setTrackStripPan(trackId, pan);
|
|
@@ -1137,6 +1748,25 @@ var RealtimeEngine = class {
|
|
|
1137
1748
|
setTrackStripDualPan(trackId, leftPan, rightPan) {
|
|
1138
1749
|
this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
1139
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
|
+
}
|
|
1140
1770
|
/**
|
|
1141
1771
|
* Sets a track lane strip's inter-channel alignment delay (whole samples).
|
|
1142
1772
|
* Adjusts strip latency, so PDC and reported graph latency are refreshed.
|
|
@@ -1177,6 +1807,52 @@ var RealtimeEngine = class {
|
|
|
1177
1807
|
clipPageRequestOverflowCount() {
|
|
1178
1808
|
return this.native.clipPageRequestOverflowCount();
|
|
1179
1809
|
}
|
|
1810
|
+
/** Cumulative warp-stretch requests dropped because the native queue was full. */
|
|
1811
|
+
warpStretchOverflowCount() {
|
|
1812
|
+
return this.native.warpStretchOverflowCount();
|
|
1813
|
+
}
|
|
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);
|
|
1825
|
+
}
|
|
1826
|
+
/** Reads the current time-stretch voice capacity (default 8). */
|
|
1827
|
+
warpVoiceCapacity() {
|
|
1828
|
+
return this.native.warpVoiceCapacity();
|
|
1829
|
+
}
|
|
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
|
+
}
|
|
1180
1856
|
setCaptureBuffer(numChannels, capacityFrames) {
|
|
1181
1857
|
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
1182
1858
|
}
|
|
@@ -1238,15 +1914,66 @@ var RealtimeEngine = class {
|
|
|
1238
1914
|
processPrepared(numFrames) {
|
|
1239
1915
|
this.native.processPrepared(numFrames);
|
|
1240
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Allocates the cue-bus counterpart of {@link prepareChannels}. Needed only
|
|
1919
|
+
* when PFL/AFL monitoring must reach a separate output: `processPrepared`
|
|
1920
|
+
* folds the cue bus into the program output, while
|
|
1921
|
+
* {@link processPreparedWithMonitor} keeps the two apart. Call once, off the
|
|
1922
|
+
* audio thread, with at least as many channels as `prepareChannels` got.
|
|
1923
|
+
*/
|
|
1924
|
+
prepareMonitorChannels(numChannels, maxFrames) {
|
|
1925
|
+
this.native.prepareMonitorChannels(numChannels, maxFrames);
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Returns a Float32Array view onto the persistent cue-bus scratch for one
|
|
1929
|
+
* channel (valid for up to `numFrames`). Read it after
|
|
1930
|
+
* {@link processPreparedWithMonitor}. Re-acquire after WASM memory growth.
|
|
1931
|
+
*/
|
|
1932
|
+
getMonitorChannelBuffer(channel, numFrames) {
|
|
1933
|
+
return this.native.getMonitorChannelBuffer(channel, numFrames);
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Runs the engine in place over the prepared scratch, writing the cue bus to
|
|
1937
|
+
* the monitor scratch instead of folding it into the program output.
|
|
1938
|
+
* Allocation-free: safe on the AudioWorklet render thread after
|
|
1939
|
+
* `prepareChannels` and `prepareMonitorChannels`.
|
|
1940
|
+
*/
|
|
1941
|
+
processPreparedWithMonitor(numFrames) {
|
|
1942
|
+
this.native.processPreparedWithMonitor(numFrames);
|
|
1943
|
+
}
|
|
1241
1944
|
processWithMonitor(channels) {
|
|
1242
1945
|
return this.native.processWithMonitor(channels);
|
|
1243
1946
|
}
|
|
1244
|
-
renderOffline(
|
|
1245
|
-
|
|
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();
|
|
1246
1965
|
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Bounce the timeline to an interleaved buffer. `numChannels` above the
|
|
1968
|
+
* prepared channel count throws an `InvalidParameter` `SonareError`.
|
|
1969
|
+
*/
|
|
1247
1970
|
bounceOffline(options) {
|
|
1248
1971
|
return this.native.bounceOffline(options);
|
|
1249
1972
|
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Freeze the current graph to audio. `numChannels` above the prepared channel
|
|
1975
|
+
* count throws an `InvalidParameter` `SonareError`.
|
|
1976
|
+
*/
|
|
1250
1977
|
freezeOffline(options) {
|
|
1251
1978
|
return this.native.freezeOffline(options);
|
|
1252
1979
|
}
|
|
@@ -1340,9 +2067,18 @@ var RealtimeEngine = class {
|
|
|
1340
2067
|
scopeScratchPointRight(index) {
|
|
1341
2068
|
return this.native.scopeScratchPointRight(index);
|
|
1342
2069
|
}
|
|
2070
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1343
2071
|
destroy() {
|
|
2072
|
+
if (this.released) {
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
this.released = true;
|
|
1344
2076
|
this.native.delete();
|
|
1345
2077
|
}
|
|
2078
|
+
/** Alias for {@link destroy}, matching embind's own release method name. */
|
|
2079
|
+
delete() {
|
|
2080
|
+
this.destroy();
|
|
2081
|
+
}
|
|
1346
2082
|
};
|
|
1347
2083
|
var ClipPageProvider = class {
|
|
1348
2084
|
constructor(engine, id) {
|
|
@@ -1374,12 +2110,20 @@ var ClipPageProvider = class {
|
|
|
1374
2110
|
// src/mixer.ts
|
|
1375
2111
|
var Mixer = class _Mixer {
|
|
1376
2112
|
constructor(mixer, blockSize) {
|
|
2113
|
+
this.released = false;
|
|
1377
2114
|
this.mixer = mixer;
|
|
1378
2115
|
this.blockSize = blockSize;
|
|
1379
2116
|
}
|
|
1380
2117
|
/**
|
|
1381
2118
|
* Build a mixer from a scene JSON string.
|
|
1382
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
|
+
*
|
|
1383
2127
|
* @param json - Scene JSON (strips, buses, sends, connections, inserts)
|
|
1384
2128
|
* @param sampleRate - Sample rate in Hz (default: 48000)
|
|
1385
2129
|
* @param blockSize - Maximum block size per {@link processStereo} call (default: 512)
|
|
@@ -1486,15 +2230,65 @@ var Mixer = class _Mixer {
|
|
|
1486
2230
|
}
|
|
1487
2231
|
};
|
|
1488
2232
|
}
|
|
1489
|
-
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
1490
|
-
stripCount() {
|
|
1491
|
-
return this.mixer.stripCount();
|
|
1492
|
-
}
|
|
1493
2233
|
/**
|
|
1494
|
-
*
|
|
2234
|
+
* Turn the master-output meter on or off.
|
|
1495
2235
|
*
|
|
1496
|
-
*
|
|
1497
|
-
*
|
|
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
|
+
}
|
|
2283
|
+
/** Number of strips in the mixer (e.g. strips loaded from the scene). */
|
|
2284
|
+
stripCount() {
|
|
2285
|
+
return this.mixer.stripCount();
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Schedule sample-accurate insert-parameter automation on a strip's insert.
|
|
2289
|
+
*
|
|
2290
|
+
* @param stripIndex - Strip index in `[0, stripCount())`
|
|
2291
|
+
* @param insertIndex - Index into the strip's combined insert sequence
|
|
1498
2292
|
* (`[pre-inserts... post-inserts...]`)
|
|
1499
2293
|
* @param paramId - Processor-specific parameter id
|
|
1500
2294
|
* @param samplePos - Absolute samples from the start of processing (the mixer
|
|
@@ -1523,6 +2317,17 @@ var Mixer = class _Mixer {
|
|
|
1523
2317
|
const index = this.mixer.stripById(id);
|
|
1524
2318
|
return index < 0 ? null : index;
|
|
1525
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
|
+
}
|
|
1526
2331
|
/**
|
|
1527
2332
|
* Add a bus to the mixer topology. `role` is one of `'master'`, `'aux'`, or
|
|
1528
2333
|
* `'submix'` (defaults to `'aux'`). Marks the routing graph dirty; call
|
|
@@ -1588,6 +2393,30 @@ var Mixer = class _Mixer {
|
|
|
1588
2393
|
setWidth(stripIndex, width) {
|
|
1589
2394
|
this.mixer.setWidth(stripIndex, width);
|
|
1590
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
|
+
}
|
|
1591
2420
|
/** Set the strip's mute state. */
|
|
1592
2421
|
setMuted(stripIndex, muted) {
|
|
1593
2422
|
this.mixer.setMuted(stripIndex, muted);
|
|
@@ -1631,7 +2460,10 @@ var Mixer = class _Mixer {
|
|
|
1631
2460
|
}
|
|
1632
2461
|
/**
|
|
1633
2462
|
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
1634
|
-
*
|
|
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.
|
|
1635
2467
|
*/
|
|
1636
2468
|
setSurroundPan(stripIndex, pan) {
|
|
1637
2469
|
this.mixer.setSurroundPan(stripIndex, pan);
|
|
@@ -1697,6 +2529,59 @@ var Mixer = class _Mixer {
|
|
|
1697
2529
|
busMeter(busId) {
|
|
1698
2530
|
return this.mixer.busMeter(busId);
|
|
1699
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
|
+
}
|
|
1700
2585
|
/**
|
|
1701
2586
|
* Schedule sample-accurate fader automation on a strip.
|
|
1702
2587
|
*
|
|
@@ -1751,6 +2636,11 @@ var Mixer = class _Mixer {
|
|
|
1751
2636
|
/**
|
|
1752
2637
|
* Read up to `maxPoints` of a strip's most recent goniometer samples
|
|
1753
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.
|
|
1754
2644
|
*/
|
|
1755
2645
|
readGoniometerLatest(stripIndex, maxPoints) {
|
|
1756
2646
|
return this.mixer.readGoniometerLatest(stripIndex, maxPoints);
|
|
@@ -1786,8 +2676,12 @@ var Mixer = class _Mixer {
|
|
|
1786
2676
|
}
|
|
1787
2677
|
return this.mixer.drainTailStereo(numSamples);
|
|
1788
2678
|
}
|
|
1789
|
-
/** Release the underlying WASM object.
|
|
2679
|
+
/** Release the underlying WASM object. Idempotent, as the Node facade is. */
|
|
1790
2680
|
delete() {
|
|
2681
|
+
if (this.released) {
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
this.released = true;
|
|
1791
2685
|
this.mixer.delete();
|
|
1792
2686
|
}
|
|
1793
2687
|
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
@@ -1804,10 +2698,19 @@ var RealtimeVoiceChanger = class {
|
|
|
1804
2698
|
* explicit {@link prepare} lifecycle for callers that configure later.
|
|
1805
2699
|
*/
|
|
1806
2700
|
constructor(config = "neutral-monitor", sampleRate, maxBlockSize = 128, channels = 1) {
|
|
2701
|
+
this.released = false;
|
|
1807
2702
|
const module2 = getSonareModule();
|
|
1808
2703
|
this.changer = module2.createRealtimeVoiceChanger(config);
|
|
1809
2704
|
if (sampleRate !== void 0) {
|
|
1810
|
-
|
|
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
|
+
}
|
|
1811
2714
|
}
|
|
1812
2715
|
}
|
|
1813
2716
|
prepare(sampleRate, maxBlockSize = 128, channels = 1) {
|
|
@@ -1833,6 +2736,34 @@ var RealtimeVoiceChanger = class {
|
|
|
1833
2736
|
latencySamples() {
|
|
1834
2737
|
return this.changer.latencySamples();
|
|
1835
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
|
+
}
|
|
1836
2767
|
/**
|
|
1837
2768
|
* Monotonically increases whenever {@link prepare} can replace the native
|
|
1838
2769
|
* scratch buffers. Cached WASM heap views must be reacquired after it changes.
|
|
@@ -2000,9 +2931,151 @@ var RealtimeVoiceChanger = class {
|
|
|
2000
2931
|
}
|
|
2001
2932
|
};
|
|
2002
2933
|
}
|
|
2934
|
+
/** Releases the native handle. Idempotent, as the Node facade is. */
|
|
2003
2935
|
delete() {
|
|
2936
|
+
if (this.released) {
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
this.released = true;
|
|
2004
2940
|
this.changer.delete();
|
|
2005
2941
|
}
|
|
2942
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
2943
|
+
destroy() {
|
|
2944
|
+
this.delete();
|
|
2945
|
+
}
|
|
2946
|
+
};
|
|
2947
|
+
|
|
2948
|
+
// src/streaming_processors.ts
|
|
2949
|
+
var StreamingMasteringChain = class {
|
|
2950
|
+
constructor(config) {
|
|
2951
|
+
this.released = false;
|
|
2952
|
+
const module2 = getSonareModule();
|
|
2953
|
+
const { loudnessStaticGainDb, loudnessStaticGainPeakDb, ...chainConfig } = config;
|
|
2954
|
+
this.chain = module2.createStreamingMasteringChain({
|
|
2955
|
+
__flatParams: flattenChainConfig(chainConfig),
|
|
2956
|
+
loudnessStaticGainDb,
|
|
2957
|
+
loudnessStaticGainPeakDb
|
|
2958
|
+
});
|
|
2959
|
+
}
|
|
2960
|
+
/**
|
|
2961
|
+
* Initialize processors for the given sample rate and block layout.
|
|
2962
|
+
*
|
|
2963
|
+
* @param sampleRate - Sample rate in Hz
|
|
2964
|
+
* @param maxBlockSize - Maximum block size per process call
|
|
2965
|
+
* @param numChannels - 1 (mono) or 2 (stereo)
|
|
2966
|
+
*/
|
|
2967
|
+
prepare(sampleRate, maxBlockSize, numChannels) {
|
|
2968
|
+
this.chain.prepare(sampleRate, maxBlockSize, numChannels);
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Process one mono block, returning the processed samples (same length).
|
|
2972
|
+
*/
|
|
2973
|
+
processMono(samples) {
|
|
2974
|
+
return this.chain.processMono(samples);
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Process one stereo block, returning the processed channels.
|
|
2978
|
+
*/
|
|
2979
|
+
processStereo(left, right) {
|
|
2980
|
+
if (left.length !== right.length) {
|
|
2981
|
+
throw new Error("Stereo channel lengths must match.");
|
|
2982
|
+
}
|
|
2983
|
+
return this.chain.processStereo(left, right);
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Emit delayed audio and finite processor tails after the final mono block.
|
|
2987
|
+
* Call until this returns an empty array. The initial `latencySamples()`
|
|
2988
|
+
* samples of the concatenated stream are delayed and should be discarded for
|
|
2989
|
+
* time-aligned output.
|
|
2990
|
+
*/
|
|
2991
|
+
flushMono() {
|
|
2992
|
+
return this.chain.flushMono();
|
|
2993
|
+
}
|
|
2994
|
+
/** Stereo counterpart of {@link flushMono}. */
|
|
2995
|
+
flushStereo() {
|
|
2996
|
+
return this.chain.flushStereo();
|
|
2997
|
+
}
|
|
2998
|
+
/** Reset all processor state without rebuilding. */
|
|
2999
|
+
reset() {
|
|
3000
|
+
this.chain.reset();
|
|
3001
|
+
}
|
|
3002
|
+
/** Total reported latency in samples across all active processors. */
|
|
3003
|
+
latencySamples() {
|
|
3004
|
+
return this.chain.latencySamples();
|
|
3005
|
+
}
|
|
3006
|
+
/** Ordered stage names that will run (e.g. `"eq.tilt"`). */
|
|
3007
|
+
stageNames() {
|
|
3008
|
+
return this.chain.stageNames();
|
|
3009
|
+
}
|
|
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. */
|
|
3068
|
+
delete() {
|
|
3069
|
+
if (this.released) {
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
this.released = true;
|
|
3073
|
+
this.chain.delete();
|
|
3074
|
+
}
|
|
3075
|
+
/** Alias for {@link delete}, provided for cross-binding (Node) compatibility. */
|
|
3076
|
+
destroy() {
|
|
3077
|
+
this.delete();
|
|
3078
|
+
}
|
|
2006
3079
|
};
|
|
2007
3080
|
|
|
2008
3081
|
// src/index.ts
|
|
@@ -2053,11 +3126,11 @@ function buildTempoSync(tempoBpm, timeSignature, tempoSegments, timeSignatureSeg
|
|
|
2053
3126
|
};
|
|
2054
3127
|
}
|
|
2055
3128
|
function resolveTargetId(target) {
|
|
2056
|
-
|
|
2057
|
-
|
|
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)}`);
|
|
2058
3132
|
}
|
|
2059
|
-
|
|
2060
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
3133
|
+
return value;
|
|
2061
3134
|
}
|
|
2062
3135
|
function resolveParamId(parameters, nodeId, param) {
|
|
2063
3136
|
if (typeof param === "number") {
|
|
@@ -2100,101 +3173,6 @@ function setAutomationLane(ctx, paramId, points) {
|
|
|
2100
3173
|
ctx.postSync({ type: "syncAutomation", paramId, points: sorted });
|
|
2101
3174
|
}
|
|
2102
3175
|
|
|
2103
|
-
// src/worklet/engine-offline.ts
|
|
2104
|
-
function buildCaptureConfig(options, defaultChannels) {
|
|
2105
|
-
return {
|
|
2106
|
-
bufferFrames: Math.trunc(options.bufferFrames),
|
|
2107
|
-
channels: Math.trunc(options.channels ?? defaultChannels),
|
|
2108
|
-
source: options.source ?? "output",
|
|
2109
|
-
recordOffsetSamples: Math.trunc(options.recordOffsetSamples ?? 0),
|
|
2110
|
-
inputMonitor: {
|
|
2111
|
-
enabled: Boolean(options.inputMonitor?.enabled),
|
|
2112
|
-
gain: options.inputMonitor?.gain ?? 1
|
|
2113
|
-
}
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
function buildTransportFacade(ctx) {
|
|
2117
|
-
return {
|
|
2118
|
-
play: (sampleTime = -1) => {
|
|
2119
|
-
const ok = ctx.realtimeNode.play(sampleTime);
|
|
2120
|
-
if (ok) {
|
|
2121
|
-
ctx.setTransportPlaying(true);
|
|
2122
|
-
}
|
|
2123
|
-
return ok;
|
|
2124
|
-
},
|
|
2125
|
-
stop: (sampleTime = -1) => {
|
|
2126
|
-
const ok = ctx.realtimeNode.stop(sampleTime);
|
|
2127
|
-
if (ok) {
|
|
2128
|
-
ctx.setTransportPlaying(false);
|
|
2129
|
-
ctx.flushPendingInstrumentSync();
|
|
2130
|
-
}
|
|
2131
|
-
return ok;
|
|
2132
|
-
},
|
|
2133
|
-
seekPpq: (ppq, sampleTime = -1) => {
|
|
2134
|
-
ctx.offlineEngine.seekPpq(ppq, sampleTime);
|
|
2135
|
-
const ok = ctx.realtimeNode.seekPpq(ppq, sampleTime);
|
|
2136
|
-
ctx.flushOfflineMirror();
|
|
2137
|
-
return ok;
|
|
2138
|
-
},
|
|
2139
|
-
seekSeconds: (seconds, sampleTime = -1) => {
|
|
2140
|
-
const timelineSample = Math.max(0, Math.round(seconds * ctx.sampleRate));
|
|
2141
|
-
ctx.offlineEngine.seekSample(timelineSample, sampleTime);
|
|
2142
|
-
const ok = ctx.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
2143
|
-
ctx.flushOfflineMirror();
|
|
2144
|
-
return ok;
|
|
2145
|
-
},
|
|
2146
|
-
setTempo: (bpm) => ctx.setTempo(bpm),
|
|
2147
|
-
setTempoSegments: (segments) => ctx.setTempoSegments(segments),
|
|
2148
|
-
setLoop: (startPpq, endPpq, enabled = true) => ctx.setLoop(startPpq, endPpq, enabled)
|
|
2149
|
-
};
|
|
2150
|
-
}
|
|
2151
|
-
function normalizeTrackLanes(existing, lanes) {
|
|
2152
|
-
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
2153
|
-
const ids = [];
|
|
2154
|
-
for (const entry of entries) {
|
|
2155
|
-
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
2156
|
-
throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
2157
|
-
}
|
|
2158
|
-
ids.push(entry.trackId);
|
|
2159
|
-
}
|
|
2160
|
-
if (new Set(ids).size !== ids.length) {
|
|
2161
|
-
throw new Error("Duplicate track id in mixer lane list");
|
|
2162
|
-
}
|
|
2163
|
-
for (let index = 0; index < existing.length; index++) {
|
|
2164
|
-
if (ids[index] !== existing[index]) {
|
|
2165
|
-
throw new Error(
|
|
2166
|
-
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
2167
|
-
);
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
return { entries, ids };
|
|
2171
|
-
}
|
|
2172
|
-
function resolveMarkerSet(markers, nextMarkerId) {
|
|
2173
|
-
const resolved = [];
|
|
2174
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2175
|
-
let counter = nextMarkerId;
|
|
2176
|
-
for (const marker2 of markers) {
|
|
2177
|
-
if (!Number.isFinite(marker2.ppq)) {
|
|
2178
|
-
throw new Error(`Invalid marker ppq: ${String(marker2.ppq)}`);
|
|
2179
|
-
}
|
|
2180
|
-
if (marker2.id !== void 0) {
|
|
2181
|
-
if (!Number.isInteger(marker2.id) || marker2.id <= 0) {
|
|
2182
|
-
throw new Error(`Invalid marker id: ${String(marker2.id)}`);
|
|
2183
|
-
}
|
|
2184
|
-
if (seen.has(marker2.id)) {
|
|
2185
|
-
throw new Error(`Duplicate marker id: ${marker2.id}`);
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
const id = marker2.id ?? counter++;
|
|
2189
|
-
seen.add(id);
|
|
2190
|
-
if (id >= counter) {
|
|
2191
|
-
counter = id + 1;
|
|
2192
|
-
}
|
|
2193
|
-
resolved.push({ id, ppq: marker2.ppq, name: marker2.name ?? "" });
|
|
2194
|
-
}
|
|
2195
|
-
return { resolved, nextMarkerId: counter };
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
3176
|
// src/worklet/protocol.ts
|
|
2199
3177
|
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
2200
3178
|
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
@@ -2281,11 +3259,9 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
2281
3259
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MetronomeOverflow"] = 18] = "MetronomeOverflow";
|
|
2282
3260
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["InvalidCommand"] = 19] = "InvalidCommand";
|
|
2283
3261
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["MaxChannelsExceeded"] = 20] = "MaxChannelsExceeded";
|
|
3262
|
+
SonareEngineTelemetryError2[SonareEngineTelemetryError2["ParameterBaseOverflow"] = 21] = "ParameterBaseOverflow";
|
|
2284
3263
|
return SonareEngineTelemetryError2;
|
|
2285
3264
|
})(SonareEngineTelemetryError || {});
|
|
2286
|
-
function toDb(value) {
|
|
2287
|
-
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
2288
|
-
}
|
|
2289
3265
|
function isRecord(value) {
|
|
2290
3266
|
return typeof value === "object" && value !== null;
|
|
2291
3267
|
}
|
|
@@ -2764,6 +3740,16 @@ function clipPageRequestRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
|
2764
3740
|
function recordOffset(index, capacity, recordBytes) {
|
|
2765
3741
|
return index % capacity * recordBytes;
|
|
2766
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
|
+
}
|
|
2767
3753
|
function toSafeInteger(value, fallback) {
|
|
2768
3754
|
const resolved = typeof value === "bigint" ? Number(value) : value;
|
|
2769
3755
|
if (resolved === void 0) {
|
|
@@ -2783,8 +3769,8 @@ function readInt64Words(view, offset) {
|
|
|
2783
3769
|
return view.getInt32(offset + 4, true) * 4294967296 + view.getUint32(offset, true);
|
|
2784
3770
|
}
|
|
2785
3771
|
function writeEngineCommandRecord(view, offset, command) {
|
|
2786
|
-
view.setUint32(offset, command.type, true);
|
|
2787
|
-
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);
|
|
2788
3774
|
writeInt64Words(view, offset + 8, toSafeInteger(command.sampleTime, -1));
|
|
2789
3775
|
view.setFloat64(offset + 16, command.argFloat ?? 0, true);
|
|
2790
3776
|
writeInt64Words(view, offset + 24, toSafeInteger(command.argInt, 0));
|
|
@@ -2853,43 +3839,337 @@ function magnitudeToDb(value) {
|
|
|
2853
3839
|
return value > 1e-12 ? 20 * Math.log10(value) : -120;
|
|
2854
3840
|
}
|
|
2855
3841
|
|
|
2856
|
-
// src/worklet/
|
|
2857
|
-
function
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
ctx.offlineEngine.setCaptureSource(config.source);
|
|
2861
|
-
ctx.offlineEngine.setRecordOffsetSamples(config.recordOffsetSamples);
|
|
2862
|
-
ctx.offlineEngine.setInputMonitor(config.inputMonitor.enabled, config.inputMonitor.gain);
|
|
2863
|
-
ctx.setCaptureConfig(config);
|
|
2864
|
-
ctx.postSync({ type: "syncCapture", ...config });
|
|
2865
|
-
}
|
|
2866
|
-
function armRecord(ctx, trackId, enabled) {
|
|
2867
|
-
if (trackId !== 0) {
|
|
2868
|
-
throw new RangeError("Capture is global; armRecord only accepts trackId 0");
|
|
2869
|
-
}
|
|
2870
|
-
if (enabled && !ctx.getCaptureConfig()) {
|
|
2871
|
-
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;
|
|
2872
3846
|
}
|
|
2873
|
-
|
|
2874
|
-
return ctx.sendCommand({
|
|
2875
|
-
type: 13 /* ArmRecord */,
|
|
2876
|
-
targetId: 0,
|
|
2877
|
-
sampleTime: -1,
|
|
2878
|
-
argInt: enabled ? 1 : 0
|
|
2879
|
-
});
|
|
3847
|
+
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
2880
3848
|
}
|
|
2881
|
-
function
|
|
2882
|
-
|
|
2883
|
-
const outSample = ctx.offlineEngine.sampleAtPpq(outPpq);
|
|
2884
|
-
ctx.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
2885
|
-
return ctx.sendCommand({
|
|
2886
|
-
type: 14 /* Punch */,
|
|
2887
|
-
sampleTime: -1,
|
|
2888
|
-
argInt: inSample,
|
|
2889
|
-
argFloat: outSample
|
|
2890
|
-
});
|
|
3849
|
+
function isEngineCommandRecord(value) {
|
|
3850
|
+
return isRecord(value) && typeof value.type === "number";
|
|
2891
3851
|
}
|
|
2892
|
-
|
|
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);
|
|
3919
|
+
}
|
|
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) {
|
|
2893
4173
|
return ctx.realtimeNode.requestCaptureStatus();
|
|
2894
4174
|
}
|
|
2895
4175
|
function capturedAudio(ctx) {
|
|
@@ -3045,16 +4325,191 @@ function syncMarkers(ctx) {
|
|
|
3045
4325
|
}
|
|
3046
4326
|
|
|
3047
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
|
+
}
|
|
3048
4475
|
function mixerLanes(ctx) {
|
|
3049
4476
|
return buildMixerLanes(ctx.trackLaneIds, ctx.trackSends, ctx.trackOutputBus);
|
|
3050
4477
|
}
|
|
3051
|
-
function syncMixer(ctx) {
|
|
4478
|
+
function syncMixer(ctx, busesAlreadyApplied = false, forceInsertResets = []) {
|
|
3052
4479
|
const lanes = mixerLanes(ctx);
|
|
3053
4480
|
const buses = ctx.buses.map((bus) => ({ ...bus }));
|
|
3054
|
-
|
|
4481
|
+
if (!busesAlreadyApplied) {
|
|
4482
|
+
ctx.offlineEngine.setTrackBuses(buses);
|
|
4483
|
+
}
|
|
3055
4484
|
if (lanes.length > 0) {
|
|
4485
|
+
ctx.offlineEngine.settleInsertParameters();
|
|
3056
4486
|
ctx.offlineEngine.setTrackLanes(lanes);
|
|
3057
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);
|
|
3058
4513
|
const trackStrips = Array.from(ctx.trackStripJson, ([trackId, sceneJson]) => ({
|
|
3059
4514
|
trackId,
|
|
3060
4515
|
sceneJson
|
|
@@ -3063,6 +4518,10 @@ function syncMixer(ctx) {
|
|
|
3063
4518
|
busId,
|
|
3064
4519
|
sceneJson
|
|
3065
4520
|
}));
|
|
4521
|
+
const insertParamOverrides = Array.from(
|
|
4522
|
+
ctx.insertParamOverrides.values(),
|
|
4523
|
+
flattenInsertParamOverride
|
|
4524
|
+
);
|
|
3066
4525
|
ctx.postSync({
|
|
3067
4526
|
type: "syncMixer",
|
|
3068
4527
|
lanes,
|
|
@@ -3070,9 +4529,49 @@ function syncMixer(ctx) {
|
|
|
3070
4529
|
trackStrips,
|
|
3071
4530
|
laneSidechains: Array.from(ctx.laneSidechains.values()),
|
|
3072
4531
|
busStrips,
|
|
3073
|
-
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 } : {}
|
|
3074
4537
|
});
|
|
3075
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
|
+
}
|
|
3076
4575
|
function setTrackLanes(ctx, lanes) {
|
|
3077
4576
|
const { entries, ids } = normalizeTrackLanes(ctx.trackLaneIds, lanes);
|
|
3078
4577
|
for (const entry of entries) {
|
|
@@ -3112,143 +4611,116 @@ function setLaneSidechain(ctx, target, insertIndex, sourceTarget) {
|
|
|
3112
4611
|
const sourceIndex = ctx.ensureTrackLane(sourceTarget);
|
|
3113
4612
|
sourceTrackId = ctx.trackLaneIds[sourceIndex];
|
|
3114
4613
|
}
|
|
4614
|
+
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3115
4615
|
if (sourceTrackId === 0) {
|
|
3116
4616
|
ctx.laneSidechains.delete(key);
|
|
3117
4617
|
} else {
|
|
3118
4618
|
ctx.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3119
4619
|
}
|
|
3120
|
-
ctx.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3121
4620
|
ctx.postSync({
|
|
3122
4621
|
type: "syncMixer",
|
|
4622
|
+
sidechainDelta: true,
|
|
3123
4623
|
lanes: ctx.mixerLanes(),
|
|
3124
|
-
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
3125
|
-
});
|
|
3126
|
-
}
|
|
3127
|
-
function
|
|
3128
|
-
const
|
|
3129
|
-
|
|
3130
|
-
ctx
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
)
|
|
3134
|
-
|
|
3135
|
-
}
|
|
3136
|
-
|
|
3137
|
-
ctx.buses.splice(0, ctx.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
3138
|
-
ctx.syncMixer();
|
|
3139
|
-
}
|
|
3140
|
-
function setBusGain(ctx, busId, db) {
|
|
3141
|
-
const busIndex = ctx.ensureBus(busId);
|
|
3142
|
-
ctx.buses[busIndex] = { ...ctx.buses[busIndex], busId, gainDb: db };
|
|
3143
|
-
ctx.offlineEngine.setTrackBuses(ctx.buses);
|
|
3144
|
-
return ctx.sendSmoothedParam(engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB), db);
|
|
3145
|
-
}
|
|
3146
|
-
function setBusStripJson(ctx, busId, sceneJson) {
|
|
3147
|
-
ctx.ensureBus(busId);
|
|
3148
|
-
ctx.offlineEngine.setBusStripJson(busId, sceneJson);
|
|
3149
|
-
ctx.busStripJson.set(busId, sceneJson);
|
|
3150
|
-
ctx.syncMixer();
|
|
3151
|
-
}
|
|
3152
|
-
|
|
3153
|
-
// src/worklet/guards.ts
|
|
3154
|
-
function isWorkletMessage(value) {
|
|
3155
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3156
|
-
return false;
|
|
3157
|
-
}
|
|
3158
|
-
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
3159
|
-
}
|
|
3160
|
-
function isEngineCommandRecord(value) {
|
|
3161
|
-
return isRecord(value) && typeof value.type === "number";
|
|
3162
|
-
}
|
|
3163
|
-
function isEngineSyncMessage(value) {
|
|
3164
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
3165
|
-
return false;
|
|
3166
|
-
}
|
|
3167
|
-
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";
|
|
3168
|
-
}
|
|
3169
|
-
function isEngineCaptureRequestMessage(value) {
|
|
3170
|
-
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
3171
|
-
}
|
|
3172
|
-
function isCaptureStatus(value) {
|
|
3173
|
-
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);
|
|
3174
|
-
}
|
|
3175
|
-
function isCaptureChannel(value) {
|
|
3176
|
-
return value instanceof Float32Array && typeof ArrayBuffer !== "undefined" && value.buffer instanceof ArrayBuffer;
|
|
3177
|
-
}
|
|
3178
|
-
function isCaptureChannels(value) {
|
|
3179
|
-
return Array.isArray(value) && value.every((channel) => isCaptureChannel(channel));
|
|
3180
|
-
}
|
|
3181
|
-
function hasOwn(value, key) {
|
|
3182
|
-
return Object.prototype.hasOwnProperty.call(value, key);
|
|
3183
|
-
}
|
|
3184
|
-
function engineCaptureResponseRequestId(value) {
|
|
3185
|
-
if (!isRecord(value) || value.type !== "captureResponse" || typeof value.requestId !== "number" || !Number.isSafeInteger(value.requestId)) {
|
|
3186
|
-
return void 0;
|
|
3187
|
-
}
|
|
3188
|
-
return value.requestId;
|
|
3189
|
-
}
|
|
3190
|
-
function isEngineCaptureResponseMessage(value) {
|
|
3191
|
-
const requestId = engineCaptureResponseRequestId(value);
|
|
3192
|
-
if (requestId === void 0 || !isRecord(value)) {
|
|
3193
|
-
return false;
|
|
3194
|
-
}
|
|
3195
|
-
if (value.ok === false) {
|
|
3196
|
-
return typeof value.error === "string" && !hasOwn(value, "status") && !hasOwn(value, "channels");
|
|
3197
|
-
}
|
|
3198
|
-
if (value.ok !== true || hasOwn(value, "error")) {
|
|
3199
|
-
return false;
|
|
3200
|
-
}
|
|
3201
|
-
const hasStatus = hasOwn(value, "status");
|
|
3202
|
-
const hasChannels = hasOwn(value, "channels");
|
|
3203
|
-
if (hasStatus && hasChannels) {
|
|
3204
|
-
return false;
|
|
3205
|
-
}
|
|
3206
|
-
if (hasStatus) {
|
|
3207
|
-
return isCaptureStatus(value.status);
|
|
4624
|
+
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
4625
|
+
});
|
|
4626
|
+
}
|
|
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 });
|
|
3208
4637
|
}
|
|
3209
|
-
|
|
3210
|
-
|
|
4638
|
+
ctx.postSync({
|
|
4639
|
+
type: "syncMixer",
|
|
4640
|
+
sidechainDelta: true,
|
|
4641
|
+
lanes: ctx.mixerLanes(),
|
|
4642
|
+
busSidechains: [{ busId, insertIndex, sourceKind, sourceId }]
|
|
4643
|
+
});
|
|
4644
|
+
}
|
|
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 });
|
|
3211
4653
|
}
|
|
3212
|
-
|
|
4654
|
+
ctx.postSync({
|
|
4655
|
+
type: "syncMixer",
|
|
4656
|
+
sidechainDelta: true,
|
|
4657
|
+
lanes: ctx.mixerLanes(),
|
|
4658
|
+
masterSidechains: [{ insertIndex, sourceKind, sourceId }]
|
|
4659
|
+
});
|
|
3213
4660
|
}
|
|
3214
|
-
function
|
|
3215
|
-
if (
|
|
3216
|
-
return
|
|
4661
|
+
function ensureSidechainSource(ctx, sourceKind, sourceId) {
|
|
4662
|
+
if (sourceId === 0) {
|
|
4663
|
+
return;
|
|
3217
4664
|
}
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
return "channels" in response;
|
|
3223
|
-
case "reset":
|
|
3224
|
-
return !("status" in response) && !("channels" in response);
|
|
4665
|
+
if (sourceKind === 1) {
|
|
4666
|
+
ctx.ensureBus(sourceId);
|
|
4667
|
+
} else {
|
|
4668
|
+
ctx.ensureTrackLane(sourceId);
|
|
3225
4669
|
}
|
|
3226
4670
|
}
|
|
3227
|
-
function
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
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();
|
|
3232
4679
|
}
|
|
3233
|
-
function
|
|
3234
|
-
|
|
3235
|
-
|
|
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
|
+
}
|
|
3236
4687
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
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);
|
|
3244
4706
|
}
|
|
3245
|
-
function
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
);
|
|
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);
|
|
3249
4711
|
}
|
|
3250
|
-
function
|
|
3251
|
-
|
|
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] : []);
|
|
3252
4724
|
}
|
|
3253
4725
|
|
|
3254
4726
|
// src/worklet/engine-node.ts
|
|
@@ -3265,15 +4737,17 @@ function isTrackMonitorMode(value) {
|
|
|
3265
4737
|
const mode = typeof value === "bigint" ? Number(value) : value;
|
|
3266
4738
|
return typeof mode === "number" && Number.isSafeInteger(mode) && mode >= 0 && mode <= 2;
|
|
3267
4739
|
}
|
|
3268
|
-
function isTrackMonitorLaneIndex(value) {
|
|
3269
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= 4294967295;
|
|
3270
|
-
}
|
|
3271
4740
|
function isValidCommandRecord(command) {
|
|
3272
4741
|
const type = Number(command.type);
|
|
3273
4742
|
if (!Number.isSafeInteger(type) || type < 0 /* SetParam */ || type > 17 /* SeekMarker */ && type !== 26 /* SetTrackMonitorMode */) {
|
|
3274
4743
|
return false;
|
|
3275
4744
|
}
|
|
3276
|
-
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
|
+
);
|
|
3277
4751
|
}
|
|
3278
4752
|
var AUDIO_WORKLET_RENDER_QUANTUM = 128;
|
|
3279
4753
|
function workletBlockSize(blockSize) {
|
|
@@ -3318,6 +4792,9 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3318
4792
|
this.resolveReady();
|
|
3319
4793
|
}
|
|
3320
4794
|
this.node.port.onmessage = (event) => {
|
|
4795
|
+
if (this.destroyed) {
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
3321
4798
|
const captureRequestId = engineCaptureResponseRequestId(event.data);
|
|
3322
4799
|
if (captureRequestId !== void 0) {
|
|
3323
4800
|
const pending = this.captureRequests.get(captureRequestId);
|
|
@@ -3399,18 +4876,65 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3399
4876
|
"SharedArrayBuffer mode requested but SharedArrayBuffer/Atomics are unavailable."
|
|
3400
4877
|
);
|
|
3401
4878
|
}
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
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);
|
|
4932
|
+
const cueOutput = options.cueOutput === true;
|
|
3410
4933
|
const processorOptions = {
|
|
3411
4934
|
sampleRate: options.sampleRate ?? context.sampleRate,
|
|
3412
4935
|
blockSize,
|
|
3413
4936
|
channelCount,
|
|
4937
|
+
cueOutput,
|
|
3414
4938
|
commandSharedBuffer: commandRing?.sharedBuffer,
|
|
3415
4939
|
commandRingCapacity: commandRing?.capacity,
|
|
3416
4940
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
@@ -3432,8 +4956,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3432
4956
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
3433
4957
|
const node = factory(context, processorName, {
|
|
3434
4958
|
numberOfInputs: 1,
|
|
3435
|
-
|
|
3436
|
-
|
|
4959
|
+
// The cue bus needs its own output; a single-output node keeps the
|
|
4960
|
+
// historical mix where process() folds the cue into the program.
|
|
4961
|
+
numberOfOutputs: cueOutput ? 2 : 1,
|
|
4962
|
+
outputChannelCount: cueOutput ? [channelCount, channelCount] : [channelCount],
|
|
3437
4963
|
processorOptions
|
|
3438
4964
|
});
|
|
3439
4965
|
return new _SonareRealtimeEngineNode(
|
|
@@ -3446,6 +4972,7 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3446
4972
|
audioWorklet,
|
|
3447
4973
|
clipPageRequestsRealtimeSafe: mode === "sab",
|
|
3448
4974
|
externalMidiRealtimeSafe: mode === "sab",
|
|
4975
|
+
cueOutput,
|
|
3449
4976
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
3450
4977
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
3451
4978
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
@@ -3678,6 +5205,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
3678
5205
|
this.scopeListeners.clear();
|
|
3679
5206
|
this.midiOutListeners.clear();
|
|
3680
5207
|
this.clipPageRequestListeners.clear();
|
|
5208
|
+
this.syncErrorListeners.clear();
|
|
5209
|
+
this.node.port.onmessage = null;
|
|
3681
5210
|
}
|
|
3682
5211
|
emitTelemetry(telemetry) {
|
|
3683
5212
|
for (const listener of this.telemetryListeners) {
|
|
@@ -3817,6 +5346,9 @@ function resolveBusInsertAutomationId(ctx, busId, insertIndex, paramName) {
|
|
|
3817
5346
|
ctx.ensureBus(busId);
|
|
3818
5347
|
return ctx.offlineEngine.resolveBusInsertAutomationId(busId, insertIndex, paramName);
|
|
3819
5348
|
}
|
|
5349
|
+
function resolveInstrumentAutomationId(ctx, destinationId, paramName) {
|
|
5350
|
+
return ctx.offlineEngine.resolveInstrumentAutomationId(destinationId, paramName);
|
|
5351
|
+
}
|
|
3820
5352
|
function automationLaneCount(ctx) {
|
|
3821
5353
|
return ctx.offlineEngine.automationLaneCount();
|
|
3822
5354
|
}
|
|
@@ -3841,14 +5373,41 @@ function clearParameters(ctx) {
|
|
|
3841
5373
|
function trackIdFor(ctx, target) {
|
|
3842
5374
|
return ctx.trackLaneIds[ctx.ensureTrackLane(target)];
|
|
3843
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
|
+
}
|
|
3844
5398
|
function setTrackStripJson(ctx, trackId, sceneJson, trackStripJson) {
|
|
3845
|
-
|
|
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);
|
|
3846
5403
|
trackStripJson.set(trackId, sceneJson);
|
|
5404
|
+
return { resetInserts };
|
|
3847
5405
|
}
|
|
3848
5406
|
function setTrackStripEqBand(ctx, target, bandIndex, band) {
|
|
3849
5407
|
const trackId = trackIdFor(ctx, target);
|
|
3850
5408
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3851
5409
|
ctx.offlineEngine.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
5410
|
+
mergeEqBand(ctx, { kind: "track", trackId }, bandIndex, bandJson);
|
|
3852
5411
|
ctx.postSync({ type: "syncTrackStripEqBand", trackId, bandIndex, bandJson });
|
|
3853
5412
|
}
|
|
3854
5413
|
function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -3864,39 +5423,68 @@ function setTrackStripInsertBypassed(ctx, target, insertIndex, bypassed, resetOn
|
|
|
3864
5423
|
}
|
|
3865
5424
|
function setTrackStripInsertParamByName(ctx, target, insertIndex, paramName, value) {
|
|
3866
5425
|
const trackId = trackIdFor(ctx, target);
|
|
3867
|
-
|
|
3868
|
-
|
|
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
|
+
);
|
|
3869
5435
|
}
|
|
3870
5436
|
function setTrackStripPan(ctx, target, pan) {
|
|
3871
5437
|
const trackId = trackIdFor(ctx, target);
|
|
3872
5438
|
ctx.offlineEngine.setTrackStripPan(trackId, pan);
|
|
5439
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5440
|
+
entry.pan = pan;
|
|
5441
|
+
});
|
|
3873
5442
|
ctx.postSync({ type: "syncTrackStripPan", trackId, pan });
|
|
3874
5443
|
}
|
|
3875
5444
|
function setTrackStripPanLaw(ctx, target, panLaw) {
|
|
3876
5445
|
const trackId = trackIdFor(ctx, target);
|
|
3877
5446
|
const code = panLawCode(panLaw);
|
|
3878
5447
|
ctx.offlineEngine.setTrackStripPanLaw(trackId, code);
|
|
5448
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5449
|
+
entry.panLaw = code;
|
|
5450
|
+
});
|
|
3879
5451
|
ctx.postSync({ type: "syncTrackStripPanLaw", trackId, panLaw: code });
|
|
3880
5452
|
}
|
|
3881
5453
|
function setTrackStripPanMode(ctx, target, panMode) {
|
|
3882
5454
|
const trackId = trackIdFor(ctx, target);
|
|
3883
5455
|
const code = panModeCode(panMode);
|
|
3884
5456
|
ctx.offlineEngine.setTrackStripPanMode(trackId, code);
|
|
5457
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5458
|
+
entry.panMode = code;
|
|
5459
|
+
});
|
|
3885
5460
|
ctx.postSync({ type: "syncTrackStripPanMode", trackId, panMode: code });
|
|
3886
5461
|
}
|
|
3887
5462
|
function setTrackStripDualPan(ctx, target, leftPan, rightPan) {
|
|
3888
5463
|
const trackId = trackIdFor(ctx, target);
|
|
3889
5464
|
ctx.offlineEngine.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
5465
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5466
|
+
entry.dualPanLeft = leftPan;
|
|
5467
|
+
entry.dualPanRight = rightPan;
|
|
5468
|
+
});
|
|
3890
5469
|
ctx.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
|
|
3891
5470
|
}
|
|
3892
5471
|
function setTrackStripChannelDelaySamples(ctx, target, delaySamples) {
|
|
3893
5472
|
const trackId = trackIdFor(ctx, target);
|
|
3894
5473
|
ctx.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
5474
|
+
mergeStripJson(ctx, { kind: "track", trackId }, (entry) => {
|
|
5475
|
+
entry.channelDelaySamples = delaySamples;
|
|
5476
|
+
});
|
|
3895
5477
|
ctx.postSync({ type: "syncTrackStripChannelDelaySamples", trackId, delaySamples });
|
|
3896
5478
|
}
|
|
5479
|
+
function cacheMasterStripScalar(ctx, field, value) {
|
|
5480
|
+
mergeStripJson(ctx, { kind: "master" }, (entry) => {
|
|
5481
|
+
entry[field] = value;
|
|
5482
|
+
});
|
|
5483
|
+
}
|
|
3897
5484
|
function setMasterStripEqBand(ctx, bandIndex, band) {
|
|
3898
5485
|
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3899
5486
|
ctx.offlineEngine.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
5487
|
+
mergeEqBand(ctx, { kind: "master" }, bandIndex, bandJson);
|
|
3900
5488
|
ctx.postSync({ type: "syncMasterStripEqBand", bandIndex, bandJson });
|
|
3901
5489
|
}
|
|
3902
5490
|
function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass) {
|
|
@@ -3904,12 +5492,67 @@ function setMasterStripInsertBypassed(ctx, insertIndex, bypassed, resetOnBypass)
|
|
|
3904
5492
|
ctx.postSync({ type: "syncMasterStripInsertBypassed", insertIndex, bypassed, resetOnBypass });
|
|
3905
5493
|
}
|
|
3906
5494
|
function setMasterStripInsertParamByName(ctx, insertIndex, paramName, value) {
|
|
3907
|
-
|
|
3908
|
-
|
|
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
|
+
);
|
|
3909
5504
|
}
|
|
3910
5505
|
function setBusStripInsertParamByName(ctx, busId, insertIndex, paramName, value) {
|
|
3911
|
-
|
|
3912
|
-
|
|
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 });
|
|
3913
5556
|
}
|
|
3914
5557
|
function pushMidiNoteOn(ctx, trackId, group, channel, note, velocity, renderFrame) {
|
|
3915
5558
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
@@ -3950,9 +5593,55 @@ function pushMidiCc(ctx, trackId, group, channel, controller, value, renderFrame
|
|
|
3950
5593
|
renderFrame
|
|
3951
5594
|
});
|
|
3952
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
|
+
}
|
|
3953
5642
|
function pushMidiUmp(ctx, trackId, word0, renderFrame) {
|
|
3954
5643
|
const destinationId = ctx.resolveTargetId(trackId);
|
|
3955
|
-
ctx.offlineEngine.pushMidiUmp(destinationId, word0, renderFrame);
|
|
5644
|
+
ctx.offlineEngine.pushMidiUmp(destinationId, [word0], renderFrame);
|
|
3956
5645
|
ctx.postSync({ type: "syncMidiUmp", destinationId, word0, renderFrame });
|
|
3957
5646
|
}
|
|
3958
5647
|
function setBuiltinInstrument(ctx, trackId, config) {
|
|
@@ -4078,6 +5767,7 @@ function transferableAudioBuffers(channels) {
|
|
|
4078
5767
|
var SonareEngine = class _SonareEngine {
|
|
4079
5768
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
4080
5769
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
5770
|
+
this.insertAutomationIdsByTarget = /* @__PURE__ */ new Map();
|
|
4081
5771
|
this.clips = /* @__PURE__ */ new Map();
|
|
4082
5772
|
this.midiClips = /* @__PURE__ */ new Map();
|
|
4083
5773
|
this.markers = /* @__PURE__ */ new Map();
|
|
@@ -4085,9 +5775,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
4085
5775
|
this.trackSends = /* @__PURE__ */ new Map();
|
|
4086
5776
|
this.trackOutputBus = /* @__PURE__ */ new Map();
|
|
4087
5777
|
this.laneSidechains = /* @__PURE__ */ new Map();
|
|
5778
|
+
this.busSidechains = /* @__PURE__ */ new Map();
|
|
5779
|
+
this.masterSidechains = /* @__PURE__ */ new Map();
|
|
4088
5780
|
this.buses = [];
|
|
4089
5781
|
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
4090
5782
|
this.busStripJson = /* @__PURE__ */ new Map();
|
|
5783
|
+
this.insertParamOverrides = /* @__PURE__ */ new Map();
|
|
4091
5784
|
this.tempoBpm = 120;
|
|
4092
5785
|
this.timeSignature = { numerator: 4, denominator: 4 };
|
|
4093
5786
|
this.tempoSegments = [{ startPpq: 0, bpm: 120 }];
|
|
@@ -4246,12 +5939,17 @@ var SonareEngine = class _SonareEngine {
|
|
|
4246
5939
|
* @returns Reserved insert-automation id, or -1 when strip/insert/key unknown.
|
|
4247
5940
|
*/
|
|
4248
5941
|
resolveTrackInsertAutomationId(target, insertIndex, paramName) {
|
|
4249
|
-
|
|
5942
|
+
const id = resolveTrackInsertAutomationId(
|
|
4250
5943
|
this.parameterContext,
|
|
4251
5944
|
target,
|
|
4252
5945
|
insertIndex,
|
|
4253
5946
|
paramName
|
|
4254
5947
|
);
|
|
5948
|
+
if (id >= 0) {
|
|
5949
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
5950
|
+
this.rememberInsertAutomationId({ kind: "track", trackId }, id);
|
|
5951
|
+
}
|
|
5952
|
+
return id;
|
|
4255
5953
|
}
|
|
4256
5954
|
/**
|
|
4257
5955
|
* Resolves a master-strip insert parameter to its reserved insert-automation
|
|
@@ -4262,7 +5960,15 @@ var SonareEngine = class _SonareEngine {
|
|
|
4262
5960
|
* @returns Reserved insert-automation id, or -1 when insert/key unknown.
|
|
4263
5961
|
*/
|
|
4264
5962
|
resolveMasterInsertAutomationId(insertIndex, paramName) {
|
|
4265
|
-
|
|
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;
|
|
4266
5972
|
}
|
|
4267
5973
|
/**
|
|
4268
5974
|
* Resolves a bus-strip insert parameter to its reserved insert-automation id.
|
|
@@ -4275,12 +5981,29 @@ var SonareEngine = class _SonareEngine {
|
|
|
4275
5981
|
* @returns Reserved insert-automation id, or -1 when bus/insert/key unknown.
|
|
4276
5982
|
*/
|
|
4277
5983
|
resolveBusInsertAutomationId(busId, insertIndex, paramName) {
|
|
4278
|
-
|
|
5984
|
+
const id = resolveBusInsertAutomationId(
|
|
4279
5985
|
this.parameterContext,
|
|
4280
5986
|
busId,
|
|
4281
5987
|
insertIndex,
|
|
4282
5988
|
paramName
|
|
4283
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);
|
|
4284
6007
|
}
|
|
4285
6008
|
/**
|
|
4286
6009
|
* Returns the number of automation lanes installed on the engine, including
|
|
@@ -4310,10 +6033,18 @@ var SonareEngine = class _SonareEngine {
|
|
|
4310
6033
|
return setTrackMonitorMode(this.parameterContext, target, mode, renderFrame);
|
|
4311
6034
|
}
|
|
4312
6035
|
setStripGain(target, db) {
|
|
4313
|
-
|
|
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;
|
|
4314
6041
|
}
|
|
4315
6042
|
setStripPan(target, pan) {
|
|
4316
|
-
|
|
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;
|
|
4317
6048
|
}
|
|
4318
6049
|
/**
|
|
4319
6050
|
* Declares the mixer track lanes in an explicit order.
|
|
@@ -4350,14 +6081,37 @@ var SonareEngine = class _SonareEngine {
|
|
|
4350
6081
|
setTrackBuses(buses) {
|
|
4351
6082
|
setTrackBuses(this.mixerContext, buses);
|
|
4352
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
|
+
}
|
|
4353
6098
|
setBusGain(busId, db) {
|
|
4354
6099
|
return setBusGain(this.mixerContext, busId, db);
|
|
4355
6100
|
}
|
|
4356
6101
|
setTrackStripJson(target, sceneJson) {
|
|
4357
6102
|
const laneIndex = this.ensureTrackLane(target);
|
|
4358
6103
|
const trackId = this.trackLaneIds[laneIndex];
|
|
4359
|
-
|
|
4360
|
-
|
|
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 }] : []);
|
|
4361
6115
|
}
|
|
4362
6116
|
setTrackStripEqBand(target, bandIndex, band) {
|
|
4363
6117
|
setTrackStripEqBand(this.stripContext, target, bandIndex, band);
|
|
@@ -4414,9 +6168,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
4414
6168
|
setBusStripJson(this.mixerContext, busId, sceneJson);
|
|
4415
6169
|
}
|
|
4416
6170
|
setMasterStripJson(sceneJson) {
|
|
4417
|
-
|
|
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);
|
|
4418
6179
|
this.masterStripJson = sceneJson;
|
|
4419
|
-
this.syncMixer();
|
|
6180
|
+
this.syncMixer(resetInserts ? [target] : []);
|
|
4420
6181
|
}
|
|
4421
6182
|
setMasterStripEqBand(bandIndex, band) {
|
|
4422
6183
|
setMasterStripEqBand(this.stripContext, bandIndex, band);
|
|
@@ -4431,6 +6192,47 @@ var SonareEngine = class _SonareEngine {
|
|
|
4431
6192
|
this.ensureBus(busId);
|
|
4432
6193
|
setBusStripInsertParamByName(this.stripContext, busId, insertIndex, paramName, value);
|
|
4433
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
|
+
}
|
|
4434
6236
|
setStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
4435
6237
|
if (target === "master") {
|
|
4436
6238
|
this.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
@@ -4511,6 +6313,49 @@ var SonareEngine = class _SonareEngine {
|
|
|
4511
6313
|
}
|
|
4512
6314
|
return { binding, provider: binding.provider };
|
|
4513
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
|
+
}
|
|
4514
6359
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
4515
6360
|
return addClip(this.clipContext, trackId, buffer, startPpq, opts);
|
|
4516
6361
|
}
|
|
@@ -4572,6 +6417,30 @@ var SonareEngine = class _SonareEngine {
|
|
|
4572
6417
|
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
4573
6418
|
pushMidiCc(this.stripContext, trackId, group, channel, controller, value, renderFrame);
|
|
4574
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
|
+
}
|
|
4575
6444
|
pushMidiUmp(trackId, word0, renderFrame = -1) {
|
|
4576
6445
|
pushMidiUmp(this.stripContext, trackId, word0, renderFrame);
|
|
4577
6446
|
}
|
|
@@ -4625,6 +6494,39 @@ var SonareEngine = class _SonareEngine {
|
|
|
4625
6494
|
portTimeSamples
|
|
4626
6495
|
});
|
|
4627
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
|
+
}
|
|
4628
6530
|
pushMidiPanic(renderFrame = -1) {
|
|
4629
6531
|
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
4630
6532
|
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
@@ -4822,8 +6724,32 @@ var SonareEngine = class _SonareEngine {
|
|
|
4822
6724
|
mixerLanes() {
|
|
4823
6725
|
return mixerLanes(this.mixerContext);
|
|
4824
6726
|
}
|
|
4825
|
-
syncMixer() {
|
|
4826
|
-
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
|
+
}
|
|
4827
6753
|
}
|
|
4828
6754
|
postInstrumentSync(message) {
|
|
4829
6755
|
if (this.destroyed) {
|
|
@@ -4851,21 +6777,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
4851
6777
|
if (this.destroyed) {
|
|
4852
6778
|
return;
|
|
4853
6779
|
}
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
this.realtimeNode.node.port.postMessage(message);
|
|
4859
|
-
}
|
|
4860
|
-
} finally {
|
|
4861
|
-
this.flushOfflineMirror();
|
|
6780
|
+
if (transfer && transfer.length > 0) {
|
|
6781
|
+
this.realtimeNode.node.port.postMessage(message, transfer);
|
|
6782
|
+
} else {
|
|
6783
|
+
this.realtimeNode.node.port.postMessage(message);
|
|
4862
6784
|
}
|
|
4863
6785
|
}
|
|
4864
|
-
// The
|
|
4865
|
-
//
|
|
4866
|
-
// 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.
|
|
4867
6788
|
flushOfflineMirror() {
|
|
4868
|
-
this.offlineEngine.
|
|
6789
|
+
this.offlineEngine.applyCommandsDueNowPreservingFuture();
|
|
4869
6790
|
}
|
|
4870
6791
|
sendMirroredCommand(command) {
|
|
4871
6792
|
const accepted = this.realtimeNode.sendCommand(command);
|
|
@@ -4883,16 +6804,24 @@ var SonareEngine = class _SonareEngine {
|
|
|
4883
6804
|
trackSends: this.trackSends,
|
|
4884
6805
|
trackOutputBus: this.trackOutputBus,
|
|
4885
6806
|
laneSidechains: this.laneSidechains,
|
|
6807
|
+
busSidechains: this.busSidechains,
|
|
6808
|
+
masterSidechains: this.masterSidechains,
|
|
4886
6809
|
buses: this.buses,
|
|
4887
6810
|
trackStripJson: this.trackStripJson,
|
|
4888
6811
|
busStripJson: this.busStripJson,
|
|
6812
|
+
insertParamOverrides: this.insertParamOverrides,
|
|
6813
|
+
flushOfflineMirror: () => this.flushOfflineMirror(),
|
|
4889
6814
|
postSync: (message) => this.postSync(message),
|
|
4890
6815
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
4891
6816
|
ensureBus: (busId) => this.ensureBus(busId),
|
|
4892
6817
|
mixerLanes: () => this.mixerLanes(),
|
|
4893
|
-
syncMixer: () => this.syncMixer(),
|
|
6818
|
+
syncMixer: (forceInsertResets) => this.syncMixer(forceInsertResets),
|
|
6819
|
+
clearInsertAutomationLanes: (target, forgetResolvedIds) => this.clearInsertAutomationLanes(target, forgetResolvedIds),
|
|
4894
6820
|
sendSmoothedParam: (paramId, value) => this.sendSmoothedParam(paramId, value),
|
|
4895
|
-
getMasterStripJson: () => this.masterStripJson
|
|
6821
|
+
getMasterStripJson: () => this.masterStripJson,
|
|
6822
|
+
cacheMasterStripJson: (sceneJson) => {
|
|
6823
|
+
this.masterStripJson = sceneJson;
|
|
6824
|
+
}
|
|
4896
6825
|
};
|
|
4897
6826
|
}
|
|
4898
6827
|
// Collaborator surface handed to the strip/pan/EQ/insert/MIDI free functions
|
|
@@ -4905,7 +6834,10 @@ var SonareEngine = class _SonareEngine {
|
|
|
4905
6834
|
postSync: (message) => this.postSync(message),
|
|
4906
6835
|
postInstrumentSync: (message) => this.postInstrumentSync(message),
|
|
4907
6836
|
ensureTrackLane: (target) => this.ensureTrackLane(target),
|
|
4908
|
-
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
|
|
4909
6841
|
};
|
|
4910
6842
|
}
|
|
4911
6843
|
// Collaborator surface handed to the automation-lane free functions so they
|
|
@@ -5047,7 +6979,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
5047
6979
|
ensureTrackLane(target) {
|
|
5048
6980
|
const trackId = this.resolveTargetId(target);
|
|
5049
6981
|
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
5050
|
-
throw new
|
|
6982
|
+
throw new RangeError(`Invalid track id for mixer lane: ${String(target)}`);
|
|
5051
6983
|
}
|
|
5052
6984
|
const existing = this.trackLaneIds.indexOf(trackId);
|
|
5053
6985
|
if (existing >= 0) {
|
|
@@ -5058,20 +6990,36 @@ var SonareEngine = class _SonareEngine {
|
|
|
5058
6990
|
return this.trackLaneIds.length - 1;
|
|
5059
6991
|
}
|
|
5060
6992
|
ensureBus(busId) {
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
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)}`);
|
|
5064
6995
|
}
|
|
5065
|
-
const existing = this.buses.findIndex((bus) => bus.busId ===
|
|
6996
|
+
const existing = this.buses.findIndex((bus) => bus.busId === busId);
|
|
5066
6997
|
if (existing >= 0) {
|
|
5067
6998
|
return existing;
|
|
5068
6999
|
}
|
|
5069
|
-
this.buses.push({ busId
|
|
7000
|
+
this.buses.push({ busId });
|
|
5070
7001
|
this.syncMixer();
|
|
5071
7002
|
return this.buses.length - 1;
|
|
5072
7003
|
}
|
|
5073
7004
|
};
|
|
5074
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
|
+
|
|
5075
7023
|
// src/worklet/messages.ts
|
|
5076
7024
|
var DEFAULT_METRONOME_CONFIG = {
|
|
5077
7025
|
beatGain: 0.35,
|
|
@@ -5115,6 +7063,10 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5115
7063
|
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
5116
7064
|
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
5117
7065
|
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
7066
|
+
// Cue-bus plane, allocated only when the host asked for a separate PFL/AFL
|
|
7067
|
+
// output. Empty otherwise, so a single-output host pays no heap and keeps the
|
|
7068
|
+
// historical behaviour where process() folds the cue into the program mix.
|
|
7069
|
+
this.monitorBuffers = [];
|
|
5118
7070
|
this.liveClips = /* @__PURE__ */ new Map();
|
|
5119
7071
|
this.pagedClipProviders = /* @__PURE__ */ new Map();
|
|
5120
7072
|
this.pagedClipPageFrames = /* @__PURE__ */ new Map();
|
|
@@ -5128,9 +7080,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5128
7080
|
this.clipPageRequestOverflowReported = 0;
|
|
5129
7081
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
5130
7082
|
this.blockSize = options.blockSize ?? 128;
|
|
5131
|
-
this.channelCount =
|
|
7083
|
+
this.channelCount = requireChannelCount(options.channelCount, 2);
|
|
5132
7084
|
this.transport = transport;
|
|
5133
|
-
this.meterIntervalFrames =
|
|
7085
|
+
this.meterIntervalFrames = requireIntegerOption(
|
|
7086
|
+
options.meterIntervalFrames,
|
|
7087
|
+
2048,
|
|
7088
|
+
"meterIntervalFrames",
|
|
7089
|
+
0
|
|
7090
|
+
);
|
|
5134
7091
|
this.commandRing = options.commandSharedBuffer ? this.commandRingFromSharedBuffer(options.commandSharedBuffer, options.commandRingCapacity) : void 0;
|
|
5135
7092
|
this.telemetryRing = options.telemetrySharedBuffer ? this.telemetryRingFromSharedBuffer(
|
|
5136
7093
|
options.telemetrySharedBuffer,
|
|
@@ -5150,10 +7107,13 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5150
7107
|
options.externalMidiSharedBuffer,
|
|
5151
7108
|
options.externalMidiRingCapacity
|
|
5152
7109
|
) : void 0;
|
|
7110
|
+
if (this.commandRing && this.commandRing.capacity > 65536) {
|
|
7111
|
+
throw new RangeError("commandRingCapacity must be at most 65536");
|
|
7112
|
+
}
|
|
5153
7113
|
this.engine = new RealtimeEngine(
|
|
5154
7114
|
this.sampleRate,
|
|
5155
7115
|
this.blockSize,
|
|
5156
|
-
1024,
|
|
7116
|
+
Math.max(1024, this.commandRing?.capacity ?? 0),
|
|
5157
7117
|
1024,
|
|
5158
7118
|
this.channelCount
|
|
5159
7119
|
);
|
|
@@ -5162,8 +7122,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5162
7122
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5163
7123
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
5164
7124
|
}
|
|
7125
|
+
this.cueOutput = options.cueOutput === true;
|
|
7126
|
+
if (this.cueOutput) {
|
|
7127
|
+
this.engine.prepareMonitorChannels(this.channelCount, this.blockSize);
|
|
7128
|
+
this.monitorBuffers = new Array(this.channelCount);
|
|
7129
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
7130
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
7131
|
+
}
|
|
7132
|
+
}
|
|
5165
7133
|
if (this.scopeRing) {
|
|
5166
|
-
const interval =
|
|
7134
|
+
const interval = requireIntegerOption(
|
|
7135
|
+
options.scopeIntervalFrames,
|
|
7136
|
+
this.blockSize,
|
|
7137
|
+
"scopeIntervalFrames",
|
|
7138
|
+
0
|
|
7139
|
+
);
|
|
5167
7140
|
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
5168
7141
|
}
|
|
5169
7142
|
}
|
|
@@ -5198,6 +7171,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5198
7171
|
if ((this.channelBuffers[0]?.byteLength ?? 0) === 0) {
|
|
5199
7172
|
this.reacquireChannelBuffers();
|
|
5200
7173
|
}
|
|
7174
|
+
if (this.cueOutput && (this.monitorBuffers[0]?.byteLength ?? 0) === 0) {
|
|
7175
|
+
this.reacquireMonitorBuffers();
|
|
7176
|
+
}
|
|
5201
7177
|
const input = inputs[0];
|
|
5202
7178
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
5203
7179
|
const dst = this.channelBuffers[ch];
|
|
@@ -5208,17 +7184,16 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5208
7184
|
dst.fill(0, 0, usableFrames);
|
|
5209
7185
|
}
|
|
5210
7186
|
}
|
|
5211
|
-
this.
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
target.fill(0);
|
|
7187
|
+
if (this.cueOutput) {
|
|
7188
|
+
this.engine.processPreparedWithMonitor(usableFrames);
|
|
7189
|
+
} else {
|
|
7190
|
+
this.engine.processPrepared(usableFrames);
|
|
7191
|
+
}
|
|
7192
|
+
copyPlanesToOutput(output, this.channelBuffers, usableFrames);
|
|
7193
|
+
if (this.cueOutput) {
|
|
7194
|
+
const cue = outputs[1];
|
|
7195
|
+
if (cue) {
|
|
7196
|
+
copyPlanesToOutput(cue, this.monitorBuffers, usableFrames);
|
|
5222
7197
|
}
|
|
5223
7198
|
}
|
|
5224
7199
|
this.publishClipPageRequests();
|
|
@@ -5233,6 +7208,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5233
7208
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
5234
7209
|
}
|
|
5235
7210
|
}
|
|
7211
|
+
reacquireMonitorBuffers() {
|
|
7212
|
+
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
7213
|
+
this.monitorBuffers[ch] = this.engine.getMonitorChannelBuffer(ch, this.blockSize);
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
5236
7216
|
receiveCommand(command) {
|
|
5237
7217
|
if (!this.closed) {
|
|
5238
7218
|
this.safeApplyCommand(command);
|
|
@@ -5320,6 +7300,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5320
7300
|
}
|
|
5321
7301
|
break;
|
|
5322
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
|
+
}
|
|
5323
7311
|
case "syncClipPageCommit": {
|
|
5324
7312
|
const providerId = this.pagedClipProviders.get(message.clipId);
|
|
5325
7313
|
const clip = message.clip ?? this.pendingPagedClips.get(message.clipId);
|
|
@@ -5378,11 +7366,60 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5378
7366
|
);
|
|
5379
7367
|
}
|
|
5380
7368
|
break;
|
|
5381
|
-
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();
|
|
5382
7402
|
if (message.buses) {
|
|
5383
7403
|
this.engine.setTrackBuses(message.buses);
|
|
5384
7404
|
}
|
|
5385
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
|
+
}
|
|
5386
7423
|
for (const strip of message.trackStrips ?? []) {
|
|
5387
7424
|
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
5388
7425
|
}
|
|
@@ -5395,7 +7432,46 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5395
7432
|
for (const binding of message.laneSidechains ?? []) {
|
|
5396
7433
|
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
5397
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
|
+
}
|
|
5398
7473
|
break;
|
|
7474
|
+
}
|
|
5399
7475
|
case "syncCapture":
|
|
5400
7476
|
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
5401
7477
|
this.engine.setCaptureSource(message.source);
|
|
@@ -5408,6 +7484,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5408
7484
|
case "syncMasterStripEqBand":
|
|
5409
7485
|
this.engine.setMasterStripEqBandJson(message.bandIndex, message.bandJson);
|
|
5410
7486
|
break;
|
|
7487
|
+
case "syncBusStripEqBand":
|
|
7488
|
+
this.engine.setBusStripEqBandJson(message.busId, message.bandIndex, message.bandJson);
|
|
7489
|
+
break;
|
|
5411
7490
|
case "syncTrackStripInsertBypassed":
|
|
5412
7491
|
this.engine.setTrackStripInsertBypassed(
|
|
5413
7492
|
message.trackId,
|
|
@@ -5424,26 +7503,57 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5424
7503
|
);
|
|
5425
7504
|
break;
|
|
5426
7505
|
case "syncTrackStripInsertParamByName":
|
|
5427
|
-
this.engine.
|
|
7506
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7507
|
+
if (!this.engine.applyTrackStripInsertParamByNameNow(
|
|
5428
7508
|
message.trackId,
|
|
5429
7509
|
message.insertIndex,
|
|
5430
7510
|
message.paramName,
|
|
5431
7511
|
message.value
|
|
5432
|
-
)
|
|
7512
|
+
)) {
|
|
7513
|
+
this.engine.restoreTrackStripInsertParamByName(
|
|
7514
|
+
message.trackId,
|
|
7515
|
+
message.insertIndex,
|
|
7516
|
+
message.paramName,
|
|
7517
|
+
message.value
|
|
7518
|
+
);
|
|
7519
|
+
}
|
|
5433
7520
|
break;
|
|
5434
7521
|
case "syncMasterStripInsertParamByName":
|
|
5435
|
-
this.engine.
|
|
7522
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7523
|
+
if (!this.engine.applyMasterStripInsertParamByNameNow(
|
|
5436
7524
|
message.insertIndex,
|
|
5437
7525
|
message.paramName,
|
|
5438
7526
|
message.value
|
|
5439
|
-
)
|
|
7527
|
+
)) {
|
|
7528
|
+
this.engine.restoreMasterStripInsertParamByName(
|
|
7529
|
+
message.insertIndex,
|
|
7530
|
+
message.paramName,
|
|
7531
|
+
message.value
|
|
7532
|
+
);
|
|
7533
|
+
}
|
|
5440
7534
|
break;
|
|
5441
7535
|
case "syncBusStripInsertParamByName":
|
|
5442
|
-
this.engine.
|
|
7536
|
+
this.engine.applyCommandsDueNowPreservingFuture();
|
|
7537
|
+
if (!this.engine.applyBusStripInsertParamByNameNow(
|
|
5443
7538
|
message.busId,
|
|
5444
7539
|
message.insertIndex,
|
|
5445
7540
|
message.paramName,
|
|
5446
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
|
|
5447
7557
|
);
|
|
5448
7558
|
break;
|
|
5449
7559
|
case "syncTrackStripPan":
|
|
@@ -5458,6 +7568,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5458
7568
|
case "syncTrackStripDualPan":
|
|
5459
7569
|
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
5460
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;
|
|
5461
7583
|
case "syncTrackStripChannelDelaySamples":
|
|
5462
7584
|
this.engine.setTrackStripChannelDelaySamples(message.trackId, message.delaySamples);
|
|
5463
7585
|
break;
|
|
@@ -5509,8 +7631,36 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5509
7631
|
message.renderFrame
|
|
5510
7632
|
);
|
|
5511
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;
|
|
5512
7662
|
case "syncMidiUmp":
|
|
5513
|
-
this.engine.pushMidiUmp(message.destinationId, message.word0, message.renderFrame);
|
|
7663
|
+
this.engine.pushMidiUmp(message.destinationId, [message.word0], message.renderFrame);
|
|
5514
7664
|
break;
|
|
5515
7665
|
case "syncMidiSysex":
|
|
5516
7666
|
this.engine.pushMidiSysex(message.destinationId, message.data, message.renderFrame);
|
|
@@ -5563,6 +7713,31 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5563
7713
|
message.portTimeSamples
|
|
5564
7714
|
);
|
|
5565
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;
|
|
5566
7741
|
}
|
|
5567
7742
|
}
|
|
5568
7743
|
receiveCaptureRequest(message) {
|
|
@@ -5643,11 +7818,11 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5643
7818
|
this.closed = true;
|
|
5644
7819
|
}
|
|
5645
7820
|
}
|
|
5646
|
-
drainCommands() {
|
|
7821
|
+
drainCommands(limit = 64) {
|
|
5647
7822
|
if (!this.commandRing) {
|
|
5648
7823
|
return;
|
|
5649
7824
|
}
|
|
5650
|
-
for (let i = 0; i <
|
|
7825
|
+
for (let i = 0; i < limit; i++) {
|
|
5651
7826
|
const command = popSonareEngineCommandRingBuffer(this.commandRing);
|
|
5652
7827
|
if (!command) {
|
|
5653
7828
|
return;
|
|
@@ -5834,10 +8009,9 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5834
8009
|
if (meter.frame !== this.lastMeterFrame) {
|
|
5835
8010
|
this.lastMeterFrame = meter.frame;
|
|
5836
8011
|
}
|
|
5837
|
-
if (this.
|
|
5838
|
-
this.
|
|
8012
|
+
if (this.transport?.onMeter) {
|
|
8013
|
+
this.transport.onMeter(meter);
|
|
5839
8014
|
} else {
|
|
5840
|
-
this.transport?.onMeter?.(meter);
|
|
5841
8015
|
this.transport?.postMessage?.(meter);
|
|
5842
8016
|
}
|
|
5843
8017
|
}
|
|
@@ -5854,29 +8028,18 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
5854
8028
|
}
|
|
5855
8029
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
5856
8030
|
}
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
ring.records[offset + 5] = meter.rmsDbL;
|
|
5870
|
-
ring.records[offset + 6] = meter.rmsDbR;
|
|
5871
|
-
ring.records[offset + 7] = meter.correlation;
|
|
5872
|
-
ring.records[offset + 8] = meter.truePeakDbL;
|
|
5873
|
-
ring.records[offset + 9] = meter.truePeakDbR;
|
|
5874
|
-
ring.records[offset + 10] = meter.momentaryLufs;
|
|
5875
|
-
ring.records[offset + 11] = meter.shortTermLufs;
|
|
5876
|
-
ring.records[offset + 12] = meter.integratedLufs;
|
|
5877
|
-
ring.records[offset + 13] = meter.gainReductionDb;
|
|
5878
|
-
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
5879
|
-
}
|
|
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.
|
|
5880
8043
|
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
5881
8044
|
// the lock-free SAB scope ring. No allocation on the render path: records are
|
|
5882
8045
|
// written field-by-field into the ring.
|
|
@@ -6084,15 +8247,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6084
8247
|
}
|
|
6085
8248
|
return;
|
|
6086
8249
|
}
|
|
6087
|
-
|
|
6088
|
-
this.bridge.receiveCommand(event.data);
|
|
6089
|
-
} else if (isEngineSyncMessage(event.data)) {
|
|
6090
|
-
this.bridge.receiveSync(event.data);
|
|
6091
|
-
} else if (isEngineCaptureRequestMessage(event.data)) {
|
|
6092
|
-
this.bridge.receiveCaptureRequest(event.data);
|
|
6093
|
-
} else if (isEngineTransportRequestMessage(event.data)) {
|
|
6094
|
-
this.bridge.receiveTransportRequest(event.data);
|
|
6095
|
-
}
|
|
8250
|
+
this.routeMessage(event.data);
|
|
6096
8251
|
};
|
|
6097
8252
|
if (port?.addEventListener) {
|
|
6098
8253
|
port.addEventListener("message", onMessage);
|
|
@@ -6111,18 +8266,35 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
6111
8266
|
}
|
|
6112
8267
|
return true;
|
|
6113
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
|
+
}
|
|
6114
8294
|
replayPendingMessages() {
|
|
6115
8295
|
const messages = this.pendingMessages.splice(0);
|
|
6116
8296
|
for (const data of messages) {
|
|
6117
|
-
|
|
6118
|
-
this.bridge?.receiveCommand(data);
|
|
6119
|
-
} else if (isEngineSyncMessage(data)) {
|
|
6120
|
-
this.bridge?.receiveSync(data);
|
|
6121
|
-
} else if (isEngineCaptureRequestMessage(data)) {
|
|
6122
|
-
this.bridge?.receiveCaptureRequest(data);
|
|
6123
|
-
} else if (isEngineTransportRequestMessage(data)) {
|
|
6124
|
-
this.bridge?.receiveTransportRequest(data);
|
|
6125
|
-
}
|
|
8297
|
+
this.routeMessage(data);
|
|
6126
8298
|
}
|
|
6127
8299
|
}
|
|
6128
8300
|
async initializeEmbind(options, port) {
|
|
@@ -6175,15 +8347,48 @@ var SonareWorkletProcessor = class {
|
|
|
6175
8347
|
this.processedFrames = 0;
|
|
6176
8348
|
this.lastMeterFrame = 0;
|
|
6177
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
|
+
};
|
|
6178
8374
|
if (!options.sceneJson) {
|
|
6179
8375
|
throw new Error("sceneJson is required.");
|
|
6180
8376
|
}
|
|
6181
8377
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6182
8378
|
this.blockSize = options.blockSize ?? 128;
|
|
6183
|
-
this.meterIntervalFrames =
|
|
6184
|
-
|
|
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
|
+
);
|
|
6185
8391
|
this.transport = transport;
|
|
6186
|
-
this.meterIntervalFrames = Math.max(0, Math.floor(options.meterIntervalFrames ?? 2048));
|
|
6187
8392
|
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
6188
8393
|
this.spectrumRing = options.spectrumSharedBuffer ? spectrumRingFromSharedBuffer(
|
|
6189
8394
|
options.spectrumSharedBuffer,
|
|
@@ -6200,6 +8405,7 @@ var SonareWorkletProcessor = class {
|
|
|
6200
8405
|
throw new Error("stripCount must match the scene strip count.");
|
|
6201
8406
|
}
|
|
6202
8407
|
this.realtime = this.mixer.createRealtimeBuffer();
|
|
8408
|
+
this.mixer.configureMeter(this.meterIntervalFrames > 0, 4);
|
|
6203
8409
|
}
|
|
6204
8410
|
process(inputs, outputs) {
|
|
6205
8411
|
if (this.closed) {
|
|
@@ -6246,10 +8452,7 @@ var SonareWorkletProcessor = class {
|
|
|
6246
8452
|
}
|
|
6247
8453
|
}
|
|
6248
8454
|
this.processedFrames += usable;
|
|
6249
|
-
this.publishMeter(
|
|
6250
|
-
this.realtime.outLeft.subarray(0, usable),
|
|
6251
|
-
this.realtime.outRight.subarray(0, usable)
|
|
6252
|
-
);
|
|
8455
|
+
this.publishMeter();
|
|
6253
8456
|
this.publishSpectrum(
|
|
6254
8457
|
this.realtime.outLeft.subarray(0, usable),
|
|
6255
8458
|
this.realtime.outRight.subarray(0, usable)
|
|
@@ -6265,7 +8468,11 @@ var SonareWorkletProcessor = class {
|
|
|
6265
8468
|
return;
|
|
6266
8469
|
}
|
|
6267
8470
|
if (message.type === "setMeterInterval") {
|
|
6268
|
-
|
|
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;
|
|
6269
8476
|
return;
|
|
6270
8477
|
}
|
|
6271
8478
|
if (message.type === "scheduleInsertAutomation") {
|
|
@@ -6285,58 +8492,52 @@ var SonareWorkletProcessor = class {
|
|
|
6285
8492
|
this.closed = true;
|
|
6286
8493
|
}
|
|
6287
8494
|
}
|
|
6288
|
-
publishMeter(
|
|
6289
|
-
if (!this.transport || this.meterIntervalFrames <= 0) {
|
|
8495
|
+
publishMeter() {
|
|
8496
|
+
if (!this.transport && !this.meterRing || this.meterIntervalFrames <= 0) {
|
|
6290
8497
|
return;
|
|
6291
8498
|
}
|
|
6292
8499
|
if (this.processedFrames - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
6293
8500
|
return;
|
|
6294
8501
|
}
|
|
6295
8502
|
this.lastMeterFrame = this.processedFrames;
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
}
|
|
6312
|
-
sumL += l * l;
|
|
6313
|
-
sumR += r * r;
|
|
6314
|
-
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;
|
|
6315
8518
|
}
|
|
6316
|
-
const rmsL = Math.sqrt(sumL / Math.max(1, left.length));
|
|
6317
|
-
const rmsR = Math.sqrt(sumR / Math.max(1, right.length));
|
|
6318
|
-
const denominator = Math.sqrt(sumL * sumR);
|
|
6319
8519
|
const meter = {
|
|
6320
8520
|
type: "meter",
|
|
6321
8521
|
targetId: 0,
|
|
6322
8522
|
frame: this.processedFrames,
|
|
6323
|
-
peakDbL:
|
|
6324
|
-
peakDbR:
|
|
6325
|
-
rmsDbL:
|
|
6326
|
-
rmsDbR:
|
|
6327
|
-
correlation:
|
|
6328
|
-
truePeakDbL:
|
|
6329
|
-
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.
|
|
6330
8532
|
momentaryLufs: Number.NaN,
|
|
6331
8533
|
shortTermLufs: Number.NaN,
|
|
6332
8534
|
integratedLufs: Number.NaN,
|
|
6333
8535
|
gainReductionDb: Number.NaN
|
|
6334
8536
|
};
|
|
6335
|
-
this.transport
|
|
6336
|
-
|
|
6337
|
-
this.writeMeterRing(meter);
|
|
8537
|
+
if (this.transport?.onMeter) {
|
|
8538
|
+
this.transport.onMeter(meter);
|
|
6338
8539
|
} else {
|
|
6339
|
-
this.transport
|
|
8540
|
+
this.transport?.postMessage?.(meter);
|
|
6340
8541
|
}
|
|
6341
8542
|
}
|
|
6342
8543
|
writeMeterRing(meter) {
|
|
@@ -6380,8 +8581,11 @@ var SonareWorkletProcessor = class {
|
|
|
6380
8581
|
frame: this.processedFrames,
|
|
6381
8582
|
bands: new Float32Array(this.spectrumBands)
|
|
6382
8583
|
};
|
|
6383
|
-
this.transport?.onSpectrum
|
|
6384
|
-
|
|
8584
|
+
if (this.transport?.onSpectrum) {
|
|
8585
|
+
this.transport.onSpectrum(spectrum);
|
|
8586
|
+
} else {
|
|
8587
|
+
this.transport?.postMessage?.(spectrum);
|
|
8588
|
+
}
|
|
6385
8589
|
}
|
|
6386
8590
|
computeSpectrum(left, right) {
|
|
6387
8591
|
const n = Math.max(1, Math.min(left.length, right.length));
|
|
@@ -6449,13 +8653,232 @@ function registerSonareWorkletProcessor(name = "sonare-worklet-processor") {
|
|
|
6449
8653
|
scope.registerProcessor(name, RegisteredSonareWorkletProcessor);
|
|
6450
8654
|
}
|
|
6451
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
|
+
|
|
6452
8873
|
// src/worklet/voice-changer-processor.ts
|
|
6453
8874
|
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
6454
8875
|
constructor(options = {}) {
|
|
8876
|
+
// Reused so the mono fan-out never builds an array on the audio thread.
|
|
8877
|
+
this.monoPlane = [new Float32Array(0)];
|
|
6455
8878
|
this.destroyed = false;
|
|
6456
8879
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
6457
8880
|
this.blockSize = options.blockSize ?? 128;
|
|
6458
|
-
this.channelCount =
|
|
8881
|
+
this.channelCount = requireChannelCount(options.channelCount, 1);
|
|
6459
8882
|
this.changer = new RealtimeVoiceChanger(options.preset ?? "neutral-monitor");
|
|
6460
8883
|
this.changer.prepare(this.sampleRate, this.blockSize, this.channelCount);
|
|
6461
8884
|
this.monoInput = this.changer.getMonoInputBuffer(this.blockSize);
|
|
@@ -6508,7 +8931,8 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6508
8931
|
this.monoInput.fill(0, 0, frames2);
|
|
6509
8932
|
}
|
|
6510
8933
|
this.changer.processPreparedMono(frames2);
|
|
6511
|
-
|
|
8934
|
+
this.monoPlane[0] = this.monoOutput;
|
|
8935
|
+
copyPlanesToOutput(output, this.monoPlane, frames2);
|
|
6512
8936
|
return true;
|
|
6513
8937
|
}
|
|
6514
8938
|
const frames = this.ensureInterleavedCapacity(requestedFrames, requestedChannels);
|
|
@@ -6526,12 +8950,7 @@ var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChan
|
|
|
6526
8950
|
}
|
|
6527
8951
|
}
|
|
6528
8952
|
this.changer.processPreparedPlanar(frames);
|
|
6529
|
-
|
|
6530
|
-
const src = this.planarChannels[ch];
|
|
6531
|
-
if (src) {
|
|
6532
|
-
output[ch].set(src.subarray(0, frames));
|
|
6533
|
-
}
|
|
6534
|
-
}
|
|
8953
|
+
copyPlanesToOutput(output, this.planarChannels, frames);
|
|
6535
8954
|
return true;
|
|
6536
8955
|
}
|
|
6537
8956
|
destroy() {
|
|
@@ -6641,16 +9060,19 @@ export {
|
|
|
6641
9060
|
SonareEngineCommandType,
|
|
6642
9061
|
SonareEngineTelemetryError,
|
|
6643
9062
|
SonareEngineTelemetryType,
|
|
9063
|
+
SonarePlaybackWorkletProcessor,
|
|
6644
9064
|
SonareRealtimeEngineNode,
|
|
6645
9065
|
SonareRealtimeEngineWorkletProcessor,
|
|
6646
9066
|
SonareRealtimeVoiceChangerWorkletProcessor,
|
|
6647
9067
|
SonareWorkletProcessor,
|
|
9068
|
+
StreamingMasteringChain,
|
|
6648
9069
|
attachOpfsClipStream,
|
|
6649
9070
|
createSonareClipPageRequestRingBuffer,
|
|
6650
9071
|
createSonareEngineCommandRingBuffer,
|
|
6651
9072
|
createSonareEngineTelemetryRingBuffer,
|
|
6652
9073
|
createSonareExternalMidiRingBuffer,
|
|
6653
9074
|
createSonareMeterRingBuffer,
|
|
9075
|
+
createSonarePlaybackNode,
|
|
6654
9076
|
createSonareScopeRingBuffer,
|
|
6655
9077
|
createSonareSpectrumRingBuffer,
|
|
6656
9078
|
decodeFrame,
|
|
@@ -6668,6 +9090,7 @@ export {
|
|
|
6668
9090
|
readSonareMeterRingBuffer,
|
|
6669
9091
|
readSonareScopeRingBuffer,
|
|
6670
9092
|
readSonareSpectrumRingBuffer,
|
|
9093
|
+
registerSonarePlaybackWorkletProcessor,
|
|
6671
9094
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
6672
9095
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
6673
9096
|
registerSonareWorkletProcessor,
|