@libraz/libsonare 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +277 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +333 -55
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +1 -1
- package/dist/sonare-rt.js +1 -1
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +1 -1
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +397 -9
- package/dist/worklet.js +1259 -87
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/effects_mastering.ts +16 -0
- package/src/errors.ts +44 -0
- package/src/index.ts +15 -1
- package/src/mixer.ts +11 -0
- package/src/module_state.ts +180 -4
- package/src/opfs_clip_pages.ts +43 -9
- package/src/realtime_engine.ts +174 -109
- package/src/sonare.js.d.ts +65 -0
- package/src/web_midi.ts +15 -11
- package/src/worklet.ts +1402 -66
package/dist/worklet.js
CHANGED
|
@@ -1,13 +1,153 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var SonareError = class extends Error {
|
|
3
|
+
constructor(code, codeName, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "SonareError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.codeName = codeName;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
1
11
|
// src/module_state.ts
|
|
2
|
-
var
|
|
12
|
+
var wrappedModule = null;
|
|
13
|
+
function nativeExceptionPtr(error) {
|
|
14
|
+
if (typeof error === "number") {
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
if (error !== null && typeof error === "object") {
|
|
18
|
+
const ptr = error.excPtr;
|
|
19
|
+
if (typeof ptr === "number") {
|
|
20
|
+
return ptr;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function makeSonareError(raw, thrown) {
|
|
26
|
+
let code = 99 /* Unknown */;
|
|
27
|
+
let codeName = "Unknown";
|
|
28
|
+
let message = `libsonare native exception (${thrown})`;
|
|
29
|
+
try {
|
|
30
|
+
const info = raw.sonareExceptionInfo?.(thrown);
|
|
31
|
+
if (info) {
|
|
32
|
+
code = info.code ?? code;
|
|
33
|
+
codeName = info.codeName ?? codeName;
|
|
34
|
+
message = info.message || message;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
return new SonareError(code, codeName, message);
|
|
39
|
+
}
|
|
40
|
+
function wrapModuleErrors(raw) {
|
|
41
|
+
const cache = /* @__PURE__ */ new Map();
|
|
42
|
+
const objectCache = /* @__PURE__ */ new WeakMap();
|
|
43
|
+
const convert = (error) => {
|
|
44
|
+
const ptr = nativeExceptionPtr(error);
|
|
45
|
+
if (ptr !== null) {
|
|
46
|
+
throw makeSonareError(raw, ptr);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
};
|
|
50
|
+
const wrapNativeObject = (value) => {
|
|
51
|
+
if (value === null || typeof value !== "object") {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
const objectValue = value;
|
|
58
|
+
const cached = objectCache.get(objectValue);
|
|
59
|
+
if (cached) {
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
63
|
+
const wrapped = new Proxy(objectValue, {
|
|
64
|
+
get(target, prop, receiver) {
|
|
65
|
+
const member = Reflect.get(target, prop, receiver);
|
|
66
|
+
if (typeof member !== "function") {
|
|
67
|
+
return member;
|
|
68
|
+
}
|
|
69
|
+
const cachedMethod = methodCache.get(prop);
|
|
70
|
+
if (cachedMethod) {
|
|
71
|
+
return cachedMethod;
|
|
72
|
+
}
|
|
73
|
+
const method = member;
|
|
74
|
+
const wrappedMethod = (...args) => {
|
|
75
|
+
try {
|
|
76
|
+
return wrapNativeObject(Reflect.apply(method, target, args));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return convert(error);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
methodCache.set(prop, wrappedMethod);
|
|
82
|
+
return wrappedMethod;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
objectCache.set(objectValue, wrapped);
|
|
86
|
+
return wrapped;
|
|
87
|
+
};
|
|
88
|
+
const wrapFunction = (value) => {
|
|
89
|
+
const fnCache = /* @__PURE__ */ new Map();
|
|
90
|
+
return new Proxy(value, {
|
|
91
|
+
get(target, prop, receiver) {
|
|
92
|
+
const member = Reflect.get(target, prop, receiver);
|
|
93
|
+
if (typeof member !== "function") {
|
|
94
|
+
return member;
|
|
95
|
+
}
|
|
96
|
+
const cachedMember = fnCache.get(prop);
|
|
97
|
+
if (cachedMember) {
|
|
98
|
+
return cachedMember;
|
|
99
|
+
}
|
|
100
|
+
const fn = member;
|
|
101
|
+
const wrappedMember = (...args) => {
|
|
102
|
+
try {
|
|
103
|
+
return wrapNativeObject(Reflect.apply(fn, target, args));
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return convert(error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
fnCache.set(prop, wrappedMember);
|
|
109
|
+
return wrappedMember;
|
|
110
|
+
},
|
|
111
|
+
apply(t, thisArg, args) {
|
|
112
|
+
try {
|
|
113
|
+
return wrapNativeObject(Reflect.apply(t, thisArg, args));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return convert(error);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
construct(t, args, newTarget) {
|
|
119
|
+
try {
|
|
120
|
+
return wrapNativeObject(Reflect.construct(t, args, newTarget));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return convert(error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
return new Proxy(raw, {
|
|
128
|
+
get(target, prop, receiver) {
|
|
129
|
+
const value = Reflect.get(target, prop, receiver);
|
|
130
|
+
if (typeof value !== "function") {
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
const cached = cache.get(prop);
|
|
134
|
+
if (cached) {
|
|
135
|
+
return cached;
|
|
136
|
+
}
|
|
137
|
+
const wrapped = wrapFunction(value);
|
|
138
|
+
cache.set(prop, wrapped);
|
|
139
|
+
return wrapped;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
3
143
|
function setSonareModule(module2) {
|
|
4
|
-
|
|
144
|
+
wrappedModule = wrapModuleErrors(module2);
|
|
5
145
|
}
|
|
6
146
|
function getSonareModule() {
|
|
7
|
-
if (!
|
|
147
|
+
if (!wrappedModule) {
|
|
8
148
|
throw new Error("Module not initialized. Call init() first.");
|
|
9
149
|
}
|
|
10
|
-
return
|
|
150
|
+
return wrappedModule;
|
|
11
151
|
}
|
|
12
152
|
|
|
13
153
|
// src/codes.ts
|
|
@@ -82,6 +222,16 @@ var Mixer = class _Mixer {
|
|
|
82
222
|
compile() {
|
|
83
223
|
this.mixer.compile();
|
|
84
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Non-fatal warnings captured when this mixer was built from scene JSON: one
|
|
227
|
+
* entry per channel-strip insert that was handed param keys it does not read
|
|
228
|
+
* (a likely typo, or a key meant for a different processor). The scene still
|
|
229
|
+
* loaded; these keys simply took no effect. Empty when every key was consumed.
|
|
230
|
+
* Use {@link masteringInsertParamNames} to discover the keys an insert accepts.
|
|
231
|
+
*/
|
|
232
|
+
sceneWarnings() {
|
|
233
|
+
return this.mixer.sceneWarnings();
|
|
234
|
+
}
|
|
85
235
|
/**
|
|
86
236
|
* Mix one block of per-strip stereo audio into the stereo master.
|
|
87
237
|
*
|
|
@@ -644,9 +794,6 @@ function engineCapabilities() {
|
|
|
644
794
|
};
|
|
645
795
|
}
|
|
646
796
|
var RealtimeEngine = class {
|
|
647
|
-
nativeExt() {
|
|
648
|
-
return this.native;
|
|
649
|
-
}
|
|
650
797
|
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
|
|
651
798
|
const module2 = getSonareModule();
|
|
652
799
|
const capabilities = engineCapabilities();
|
|
@@ -673,8 +820,14 @@ var RealtimeEngine = class {
|
|
|
673
820
|
setParameterSmoothed(paramId, value, renderFrame = -1) {
|
|
674
821
|
this.native.setParameterSmoothed(paramId, value, renderFrame);
|
|
675
822
|
}
|
|
823
|
+
setSoloMute(laneIndex, solo, mute, renderFrame = -1) {
|
|
824
|
+
this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
|
|
825
|
+
}
|
|
826
|
+
setMidiClips(clips) {
|
|
827
|
+
this.native.setMidiClips(clips);
|
|
828
|
+
}
|
|
676
829
|
setBuiltinInstrument(config = {}, destinationId = config.destinationId ?? 0) {
|
|
677
|
-
this.
|
|
830
|
+
this.native.setBuiltinInstrument(destinationId, config);
|
|
678
831
|
}
|
|
679
832
|
/**
|
|
680
833
|
* Bind the patch-driven NativeSynth to a realtime MIDI destination. `patch`
|
|
@@ -686,7 +839,7 @@ var RealtimeEngine = class {
|
|
|
686
839
|
* binding convenience, not part of the NativeSynth patch itself.
|
|
687
840
|
*/
|
|
688
841
|
setSynthInstrument(patch = {}, destinationId = (typeof patch === "object" ? patch.destinationId : void 0) ?? 0) {
|
|
689
|
-
this.
|
|
842
|
+
this.native.setSynthInstrument(destinationId, patch);
|
|
690
843
|
}
|
|
691
844
|
/**
|
|
692
845
|
* Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
|
|
@@ -695,7 +848,7 @@ var RealtimeEngine = class {
|
|
|
695
848
|
* not referenced afterwards. Replaces any previously loaded SoundFont.
|
|
696
849
|
*/
|
|
697
850
|
loadSoundFont(data) {
|
|
698
|
-
this.
|
|
851
|
+
this.native.loadSoundFont(data);
|
|
699
852
|
}
|
|
700
853
|
/**
|
|
701
854
|
* Bind a GS-compatible SoundFont player to a realtime MIDI destination, fed
|
|
@@ -707,13 +860,13 @@ var RealtimeEngine = class {
|
|
|
707
860
|
* synthesizer GM fallback bank (the data-free floor).
|
|
708
861
|
*/
|
|
709
862
|
setSf2Instrument(config = {}, destinationId = config.destinationId ?? 0) {
|
|
710
|
-
this.
|
|
863
|
+
this.native.setSf2Instrument(destinationId, config);
|
|
711
864
|
}
|
|
712
865
|
clearMidiInstrument(destinationId = 0) {
|
|
713
|
-
this.
|
|
866
|
+
this.native.clearMidiInstrument(destinationId);
|
|
714
867
|
}
|
|
715
868
|
midiInstrumentCount() {
|
|
716
|
-
return this.
|
|
869
|
+
return this.native.midiInstrumentCount();
|
|
717
870
|
}
|
|
718
871
|
/**
|
|
719
872
|
* Bind a live MIDI CC to an engine automation parameter. The MIDI event still
|
|
@@ -721,7 +874,7 @@ var RealtimeEngine = class {
|
|
|
721
874
|
* mapped into [minValue, maxValue] for `paramId`.
|
|
722
875
|
*/
|
|
723
876
|
bindMidiCc(channel, controller, paramId, options = {}) {
|
|
724
|
-
this.
|
|
877
|
+
this.native.bindMidiCc(
|
|
725
878
|
channel,
|
|
726
879
|
controller,
|
|
727
880
|
paramId,
|
|
@@ -730,42 +883,42 @@ var RealtimeEngine = class {
|
|
|
730
883
|
);
|
|
731
884
|
}
|
|
732
885
|
clearMidiCcBindings() {
|
|
733
|
-
this.
|
|
886
|
+
this.native.clearMidiCcBindings();
|
|
734
887
|
}
|
|
735
888
|
midiCcBindingCount() {
|
|
736
|
-
return this.
|
|
889
|
+
return this.native.midiCcBindingCount();
|
|
737
890
|
}
|
|
738
891
|
/** Install/replace a live non-destructive MIDI-FX insert for one destination. */
|
|
739
892
|
setMidiFx(destinationId, configJson) {
|
|
740
|
-
this.
|
|
893
|
+
this.native.setMidiFx(destinationId, configJson);
|
|
741
894
|
}
|
|
742
895
|
clearMidiFx(destinationId = 0) {
|
|
743
|
-
this.
|
|
896
|
+
this.native.clearMidiFx(destinationId);
|
|
744
897
|
}
|
|
745
898
|
/** Enable the engine-owned live MIDI input source for a destination. */
|
|
746
899
|
setMidiInputSource(destinationId = 0) {
|
|
747
|
-
this.
|
|
900
|
+
this.native.setMidiInputSource(destinationId);
|
|
748
901
|
}
|
|
749
902
|
clearMidiInputSource() {
|
|
750
|
-
this.
|
|
903
|
+
this.native.clearMidiInputSource();
|
|
751
904
|
}
|
|
752
905
|
midiInputPendingCount() {
|
|
753
|
-
return this.
|
|
906
|
+
return this.native.midiInputPendingCount();
|
|
754
907
|
}
|
|
755
908
|
pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
|
|
756
|
-
this.
|
|
909
|
+
this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
|
|
757
910
|
}
|
|
758
911
|
pushMidiInputNoteOff(group, channel, note, velocity = 0, portTimeSamples = 0) {
|
|
759
|
-
this.
|
|
912
|
+
this.native.pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
|
|
760
913
|
}
|
|
761
914
|
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
762
|
-
this.
|
|
915
|
+
this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
763
916
|
}
|
|
764
917
|
pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
|
|
765
|
-
this.
|
|
918
|
+
this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
766
919
|
}
|
|
767
920
|
pushMidiNoteOff(destinationId, group, channel, note, velocity = 0, renderFrame = -1) {
|
|
768
|
-
this.
|
|
921
|
+
this.native.pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
|
|
769
922
|
}
|
|
770
923
|
/**
|
|
771
924
|
* Queue an immediate (live) MIDI control change to a MIDI destination
|
|
@@ -774,21 +927,21 @@ var RealtimeEngine = class {
|
|
|
774
927
|
* immediate. Mirrors the Node/Python/C-ABI `pushMidiCc`.
|
|
775
928
|
*/
|
|
776
929
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
777
|
-
this.
|
|
930
|
+
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
778
931
|
}
|
|
779
932
|
/**
|
|
780
933
|
* Queue a MIDI panic (all-notes-off) releasing every sounding note at
|
|
781
934
|
* `renderFrame` (-1 = immediate). Mirrors the C-ABI `pushMidiPanic`.
|
|
782
935
|
*/
|
|
783
936
|
pushMidiPanic(renderFrame = -1) {
|
|
784
|
-
this.
|
|
937
|
+
this.native.pushMidiPanic(renderFrame);
|
|
785
938
|
}
|
|
786
939
|
/**
|
|
787
940
|
* Remove all registered parameters (and their automation lanes). Control-thread
|
|
788
941
|
* only; not realtime-safe. Mirrors the C-ABI `clearParameters`.
|
|
789
942
|
*/
|
|
790
943
|
clearParameters() {
|
|
791
|
-
this.
|
|
944
|
+
this.native.clearParameters();
|
|
792
945
|
}
|
|
793
946
|
/** Read back the current transport state snapshot. */
|
|
794
947
|
getTransportState() {
|
|
@@ -809,9 +962,18 @@ var RealtimeEngine = class {
|
|
|
809
962
|
setTempo(bpm) {
|
|
810
963
|
this.native.setTempo(bpm);
|
|
811
964
|
}
|
|
965
|
+
setTempoSegments(segments) {
|
|
966
|
+
this.native.setTempoSegments([...segments]);
|
|
967
|
+
}
|
|
812
968
|
setTimeSignature(numerator, denominator) {
|
|
813
969
|
this.native.setTimeSignature(numerator, denominator);
|
|
814
970
|
}
|
|
971
|
+
setTimeSignatureSegments(segments) {
|
|
972
|
+
this.native.setTimeSignatureSegments([...segments]);
|
|
973
|
+
}
|
|
974
|
+
sampleAtPpq(ppq) {
|
|
975
|
+
return Number(this.native.sampleAtPpq(ppq));
|
|
976
|
+
}
|
|
815
977
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
816
978
|
this.native.setLoop(startPpq, endPpq, enabled);
|
|
817
979
|
}
|
|
@@ -880,21 +1042,81 @@ var RealtimeEngine = class {
|
|
|
880
1042
|
clipCount() {
|
|
881
1043
|
return this.native.clipCount();
|
|
882
1044
|
}
|
|
1045
|
+
setTrackLanes(lanes) {
|
|
1046
|
+
this.native.setTrackLanes(
|
|
1047
|
+
lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane)
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
setTrackBuses(buses) {
|
|
1051
|
+
this.native.setTrackBuses(buses);
|
|
1052
|
+
}
|
|
1053
|
+
setBusStripJson(busId, sceneJson) {
|
|
1054
|
+
try {
|
|
1055
|
+
JSON.parse(sceneJson);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
const message = error instanceof Error ? error.message : "invalid bus strip JSON";
|
|
1058
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1059
|
+
}
|
|
1060
|
+
this.native.setBusStripJson(busId, sceneJson);
|
|
1061
|
+
}
|
|
1062
|
+
setTrackStripJson(trackId, sceneJson) {
|
|
1063
|
+
try {
|
|
1064
|
+
JSON.parse(sceneJson);
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
const message = error instanceof Error ? error.message : "invalid track strip JSON";
|
|
1067
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1068
|
+
}
|
|
1069
|
+
this.native.setTrackStripJson(trackId, sceneJson);
|
|
1070
|
+
}
|
|
1071
|
+
setTrackStripEqBand(trackId, bandIndex, band) {
|
|
1072
|
+
this.native.setTrackStripEqBandJson(
|
|
1073
|
+
trackId,
|
|
1074
|
+
bandIndex,
|
|
1075
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
setTrackStripEqBandJson(trackId, bandIndex, bandJson) {
|
|
1079
|
+
this.native.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
1080
|
+
}
|
|
1081
|
+
setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1082
|
+
this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
1083
|
+
}
|
|
1084
|
+
setMasterStripJson(sceneJson) {
|
|
1085
|
+
try {
|
|
1086
|
+
JSON.parse(sceneJson);
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
const message = error instanceof Error ? error.message : "invalid master strip JSON";
|
|
1089
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1090
|
+
}
|
|
1091
|
+
this.native.setMasterStripJson(sceneJson);
|
|
1092
|
+
}
|
|
1093
|
+
setMasterStripEqBand(bandIndex, band) {
|
|
1094
|
+
this.native.setMasterStripEqBandJson(
|
|
1095
|
+
bandIndex,
|
|
1096
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
setMasterStripEqBandJson(bandIndex, bandJson) {
|
|
1100
|
+
this.native.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
1101
|
+
}
|
|
1102
|
+
setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
|
|
1103
|
+
this.native.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
1104
|
+
}
|
|
883
1105
|
createClipPageProvider(numChannels, numSamples, pageFrames) {
|
|
884
|
-
const id = this.
|
|
1106
|
+
const id = this.native.createClipPageProvider(numChannels, numSamples, pageFrames);
|
|
885
1107
|
return new ClipPageProvider(this, id);
|
|
886
1108
|
}
|
|
887
1109
|
supplyClipPage(providerId, pageIndex, channels) {
|
|
888
|
-
this.
|
|
1110
|
+
this.native.supplyClipPage(providerId, pageIndex, channels);
|
|
889
1111
|
}
|
|
890
1112
|
clearClipPage(providerId, pageIndex) {
|
|
891
|
-
this.
|
|
1113
|
+
this.native.clearClipPage(providerId, pageIndex);
|
|
892
1114
|
}
|
|
893
1115
|
destroyClipPageProvider(providerId) {
|
|
894
|
-
this.
|
|
1116
|
+
this.native.destroyClipPageProvider(providerId);
|
|
895
1117
|
}
|
|
896
1118
|
popClipPageRequest() {
|
|
897
|
-
return this.
|
|
1119
|
+
return this.native.popClipPageRequest();
|
|
898
1120
|
}
|
|
899
1121
|
setCaptureBuffer(numChannels, capacityFrames) {
|
|
900
1122
|
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
@@ -1012,7 +1234,7 @@ async function init(options) {
|
|
|
1012
1234
|
}
|
|
1013
1235
|
initPromise = (async () => {
|
|
1014
1236
|
try {
|
|
1015
|
-
const createModule = (await import("./sonare.js")).default;
|
|
1237
|
+
const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
|
|
1016
1238
|
module = await createModule(options);
|
|
1017
1239
|
setSonareModule(module);
|
|
1018
1240
|
} catch (error) {
|
|
@@ -1027,8 +1249,20 @@ function isInitialized() {
|
|
|
1027
1249
|
}
|
|
1028
1250
|
|
|
1029
1251
|
// src/worklet.ts
|
|
1252
|
+
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
1253
|
+
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
1254
|
+
var ENGINE_MIXER_PARAM_PAN = 2;
|
|
1255
|
+
function engineMixerLaneTarget(laneIndex, paramKind) {
|
|
1256
|
+
return ENGINE_MIXER_TARGET_BASE | (laneIndex & 255) << 8 | paramKind & 255;
|
|
1257
|
+
}
|
|
1258
|
+
function engineMixerBusTarget(busIndex, paramKind) {
|
|
1259
|
+
return ENGINE_MIXER_TARGET_BASE | (254 - busIndex & 255) << 8 | paramKind & 255;
|
|
1260
|
+
}
|
|
1261
|
+
function engineMixerMasterTarget(paramKind) {
|
|
1262
|
+
return ENGINE_MIXER_TARGET_BASE | 255 << 8 | paramKind & 255;
|
|
1263
|
+
}
|
|
1030
1264
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
1031
|
-
var SONARE_METER_RING_RECORD_FLOATS =
|
|
1265
|
+
var SONARE_METER_RING_RECORD_FLOATS = 14;
|
|
1032
1266
|
var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
|
|
1033
1267
|
var SONARE_FRAME_LANE_BASE = 16777216;
|
|
1034
1268
|
function encodeFrameLo(frame) {
|
|
@@ -1119,7 +1353,19 @@ function isEngineSyncMessage(value) {
|
|
|
1119
1353
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1120
1354
|
return false;
|
|
1121
1355
|
}
|
|
1122
|
-
return value.type === "syncClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation";
|
|
1356
|
+
return value.type === "syncClips" || value.type === "syncClipsDelta" || 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 === "syncBuiltinInstrument" || value.type === "syncSynthInstrument" || value.type === "syncSf2Instrument" || value.type === "syncLoadSoundFont" || value.type === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiPanic";
|
|
1357
|
+
}
|
|
1358
|
+
function isEngineCaptureRequestMessage(value) {
|
|
1359
|
+
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
1360
|
+
}
|
|
1361
|
+
function isEngineCaptureResponseMessage(value) {
|
|
1362
|
+
return isRecord(value) && value.type === "captureResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1363
|
+
}
|
|
1364
|
+
function isEngineTransportRequestMessage(value) {
|
|
1365
|
+
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
1366
|
+
}
|
|
1367
|
+
function isEngineTransportResponseMessage(value) {
|
|
1368
|
+
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1123
1369
|
}
|
|
1124
1370
|
function isRealtimeVoiceChangerMessage(value) {
|
|
1125
1371
|
if (!isRecord(value) || typeof value.type !== "string") {
|
|
@@ -1131,7 +1377,7 @@ function isEngineTelemetryRecord(value) {
|
|
|
1131
1377
|
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";
|
|
1132
1378
|
}
|
|
1133
1379
|
function isMeterSnapshot(value) {
|
|
1134
|
-
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";
|
|
1380
|
+
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);
|
|
1135
1381
|
}
|
|
1136
1382
|
function sonareMeterRingBufferByteLength(capacity) {
|
|
1137
1383
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
@@ -1149,19 +1395,27 @@ function createSonareMeterRingBuffer(capacity = 128) {
|
|
|
1149
1395
|
}
|
|
1150
1396
|
function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
1151
1397
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1398
|
+
const recordFloats = Atomics.load(ring.header, 2) || SONARE_METER_RING_RECORD_FLOATS;
|
|
1152
1399
|
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
1153
1400
|
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
1154
1401
|
const meters = [];
|
|
1155
1402
|
for (let index = firstReadable; index < writeIndex; index++) {
|
|
1156
|
-
const offset = index % ring.capacity *
|
|
1403
|
+
const offset = index % ring.capacity * recordFloats;
|
|
1157
1404
|
meters.push({
|
|
1158
1405
|
type: "meter",
|
|
1159
|
-
frame: decodeFrame(ring.records[offset], ring.records[offset +
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1406
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 1]),
|
|
1407
|
+
targetId: ring.records[offset + 2],
|
|
1408
|
+
peakDbL: ring.records[offset + 3],
|
|
1409
|
+
peakDbR: ring.records[offset + 4],
|
|
1410
|
+
rmsDbL: ring.records[offset + 5],
|
|
1411
|
+
rmsDbR: ring.records[offset + 6],
|
|
1412
|
+
correlation: ring.records[offset + 7],
|
|
1413
|
+
truePeakDbL: ring.records[offset + 8],
|
|
1414
|
+
truePeakDbR: ring.records[offset + 9],
|
|
1415
|
+
momentaryLufs: ring.records[offset + 10],
|
|
1416
|
+
shortTermLufs: ring.records[offset + 11],
|
|
1417
|
+
integratedLufs: ring.records[offset + 12],
|
|
1418
|
+
gainReductionDb: ring.records[offset + 13]
|
|
1165
1419
|
});
|
|
1166
1420
|
}
|
|
1167
1421
|
return { nextReadIndex: writeIndex, meters };
|
|
@@ -1421,12 +1675,19 @@ function telemetryFromEngine(telemetry) {
|
|
|
1421
1675
|
function meterFromEngine(meter) {
|
|
1422
1676
|
return {
|
|
1423
1677
|
type: "meter",
|
|
1678
|
+
targetId: meter.targetId,
|
|
1424
1679
|
frame: meter.renderFrame,
|
|
1425
1680
|
peakDbL: meter.peakDbL,
|
|
1426
1681
|
peakDbR: meter.peakDbR,
|
|
1427
1682
|
rmsDbL: meter.rmsDbL,
|
|
1428
1683
|
rmsDbR: meter.rmsDbR,
|
|
1429
|
-
correlation: meter.correlation
|
|
1684
|
+
correlation: meter.correlation,
|
|
1685
|
+
truePeakDbL: meter.truePeakDbL,
|
|
1686
|
+
truePeakDbR: meter.truePeakDbR,
|
|
1687
|
+
momentaryLufs: meter.momentaryLufs,
|
|
1688
|
+
shortTermLufs: meter.shortTermLufs,
|
|
1689
|
+
integratedLufs: meter.integratedLufs,
|
|
1690
|
+
gainReductionDb: meter.gainReductionDb
|
|
1430
1691
|
};
|
|
1431
1692
|
}
|
|
1432
1693
|
function magnitudeToDb(value) {
|
|
@@ -1581,12 +1842,19 @@ var SonareWorkletProcessor = class {
|
|
|
1581
1842
|
const denominator = Math.sqrt(sumL * sumR);
|
|
1582
1843
|
const meter = {
|
|
1583
1844
|
type: "meter",
|
|
1845
|
+
targetId: 0,
|
|
1584
1846
|
frame: this.processedFrames,
|
|
1585
1847
|
peakDbL: toDb(peakL),
|
|
1586
1848
|
peakDbR: toDb(peakR),
|
|
1587
1849
|
rmsDbL: toDb(rmsL),
|
|
1588
1850
|
rmsDbR: toDb(rmsR),
|
|
1589
|
-
correlation: denominator > 0 ? sumLR / denominator : 0
|
|
1851
|
+
correlation: denominator > 0 ? sumLR / denominator : 0,
|
|
1852
|
+
truePeakDbL: toDb(peakL),
|
|
1853
|
+
truePeakDbR: toDb(peakR),
|
|
1854
|
+
momentaryLufs: Number.NaN,
|
|
1855
|
+
shortTermLufs: Number.NaN,
|
|
1856
|
+
integratedLufs: Number.NaN,
|
|
1857
|
+
gainReductionDb: Number.NaN
|
|
1590
1858
|
};
|
|
1591
1859
|
this.transport.onMeter?.(meter);
|
|
1592
1860
|
if (this.meterRing) {
|
|
@@ -1603,12 +1871,19 @@ var SonareWorkletProcessor = class {
|
|
|
1603
1871
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1604
1872
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1605
1873
|
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1606
|
-
ring.records[offset + 1] = meter.
|
|
1607
|
-
ring.records[offset + 2] = meter.
|
|
1608
|
-
ring.records[offset + 3] = meter.
|
|
1609
|
-
ring.records[offset + 4] = meter.
|
|
1610
|
-
ring.records[offset + 5] = meter.
|
|
1611
|
-
ring.records[offset + 6] =
|
|
1874
|
+
ring.records[offset + 1] = encodeFrameHi(meter.frame);
|
|
1875
|
+
ring.records[offset + 2] = meter.targetId;
|
|
1876
|
+
ring.records[offset + 3] = meter.peakDbL;
|
|
1877
|
+
ring.records[offset + 4] = meter.peakDbR;
|
|
1878
|
+
ring.records[offset + 5] = meter.rmsDbL;
|
|
1879
|
+
ring.records[offset + 6] = meter.rmsDbR;
|
|
1880
|
+
ring.records[offset + 7] = meter.correlation;
|
|
1881
|
+
ring.records[offset + 8] = meter.truePeakDbL;
|
|
1882
|
+
ring.records[offset + 9] = meter.truePeakDbR;
|
|
1883
|
+
ring.records[offset + 10] = meter.momentaryLufs;
|
|
1884
|
+
ring.records[offset + 11] = meter.shortTermLufs;
|
|
1885
|
+
ring.records[offset + 12] = meter.integratedLufs;
|
|
1886
|
+
ring.records[offset + 13] = meter.gainReductionDb;
|
|
1612
1887
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1613
1888
|
}
|
|
1614
1889
|
publishSpectrum(left, right) {
|
|
@@ -1673,6 +1948,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1673
1948
|
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
1674
1949
|
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
1675
1950
|
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
1951
|
+
this.liveClips = /* @__PURE__ */ new Map();
|
|
1676
1952
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
1677
1953
|
this.blockSize = options.blockSize ?? 128;
|
|
1678
1954
|
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
@@ -1776,8 +2052,28 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1776
2052
|
}
|
|
1777
2053
|
switch (message.type) {
|
|
1778
2054
|
case "syncClips":
|
|
2055
|
+
this.liveClips.clear();
|
|
2056
|
+
for (const clip of message.clips) {
|
|
2057
|
+
if (clip.id !== void 0) {
|
|
2058
|
+
this.liveClips.set(clip.id, clip);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
1779
2061
|
this.engine.setClips(message.clips);
|
|
1780
2062
|
break;
|
|
2063
|
+
case "syncClipsDelta":
|
|
2064
|
+
for (const clipId of message.removeIds) {
|
|
2065
|
+
this.liveClips.delete(clipId);
|
|
2066
|
+
}
|
|
2067
|
+
for (const clip of message.upserts) {
|
|
2068
|
+
if (clip.id !== void 0) {
|
|
2069
|
+
this.liveClips.set(clip.id, clip);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
this.engine.setClips(Array.from(this.liveClips.values()));
|
|
2073
|
+
break;
|
|
2074
|
+
case "syncMidiClips":
|
|
2075
|
+
this.engine.setMidiClips(message.clips);
|
|
2076
|
+
break;
|
|
1781
2077
|
case "syncMarkers":
|
|
1782
2078
|
this.engine.setMarkers(message.markers);
|
|
1783
2079
|
break;
|
|
@@ -1788,6 +2084,184 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1788
2084
|
case "syncAutomation":
|
|
1789
2085
|
this.engine.setAutomationLane(message.paramId, message.points);
|
|
1790
2086
|
break;
|
|
2087
|
+
case "syncTempo":
|
|
2088
|
+
if (message.tempoSegments) {
|
|
2089
|
+
this.engine.setTempoSegments(message.tempoSegments);
|
|
2090
|
+
} else {
|
|
2091
|
+
this.engine.setTempo(message.bpm);
|
|
2092
|
+
}
|
|
2093
|
+
if (message.timeSignatureSegments) {
|
|
2094
|
+
this.engine.setTimeSignatureSegments(message.timeSignatureSegments);
|
|
2095
|
+
} else {
|
|
2096
|
+
this.engine.setTimeSignature(
|
|
2097
|
+
message.timeSignature.numerator,
|
|
2098
|
+
message.timeSignature.denominator
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
break;
|
|
2102
|
+
case "syncMixer":
|
|
2103
|
+
if (message.buses) {
|
|
2104
|
+
this.engine.setTrackBuses(message.buses);
|
|
2105
|
+
}
|
|
2106
|
+
this.engine.setTrackLanes(message.lanes);
|
|
2107
|
+
for (const strip of message.trackStrips ?? []) {
|
|
2108
|
+
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
2109
|
+
}
|
|
2110
|
+
for (const strip of message.busStrips ?? []) {
|
|
2111
|
+
this.engine.setBusStripJson(strip.busId, strip.sceneJson);
|
|
2112
|
+
}
|
|
2113
|
+
if (message.masterStripJson) {
|
|
2114
|
+
this.engine.setMasterStripJson(message.masterStripJson);
|
|
2115
|
+
}
|
|
2116
|
+
break;
|
|
2117
|
+
case "syncCapture":
|
|
2118
|
+
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
2119
|
+
this.engine.setCaptureSource(message.source);
|
|
2120
|
+
this.engine.setRecordOffsetSamples(message.recordOffsetSamples);
|
|
2121
|
+
this.engine.setInputMonitor(message.inputMonitor.enabled, message.inputMonitor.gain);
|
|
2122
|
+
break;
|
|
2123
|
+
case "syncTrackStripEqBand":
|
|
2124
|
+
this.engine.setTrackStripEqBandJson(message.trackId, message.bandIndex, message.bandJson);
|
|
2125
|
+
break;
|
|
2126
|
+
case "syncMasterStripEqBand":
|
|
2127
|
+
this.engine.setMasterStripEqBandJson(message.bandIndex, message.bandJson);
|
|
2128
|
+
break;
|
|
2129
|
+
case "syncTrackStripInsertBypassed":
|
|
2130
|
+
this.engine.setTrackStripInsertBypassed(
|
|
2131
|
+
message.trackId,
|
|
2132
|
+
message.insertIndex,
|
|
2133
|
+
message.bypassed,
|
|
2134
|
+
message.resetOnBypass
|
|
2135
|
+
);
|
|
2136
|
+
break;
|
|
2137
|
+
case "syncMasterStripInsertBypassed":
|
|
2138
|
+
this.engine.setMasterStripInsertBypassed(
|
|
2139
|
+
message.insertIndex,
|
|
2140
|
+
message.bypassed,
|
|
2141
|
+
message.resetOnBypass
|
|
2142
|
+
);
|
|
2143
|
+
break;
|
|
2144
|
+
case "syncBuiltinInstrument":
|
|
2145
|
+
this.engine.setBuiltinInstrument(message.config, message.destinationId);
|
|
2146
|
+
break;
|
|
2147
|
+
case "syncSynthInstrument":
|
|
2148
|
+
this.engine.setSynthInstrument(message.patch, message.destinationId);
|
|
2149
|
+
break;
|
|
2150
|
+
case "syncLoadSoundFont":
|
|
2151
|
+
this.engine.loadSoundFont(message.data);
|
|
2152
|
+
break;
|
|
2153
|
+
case "syncSf2Instrument":
|
|
2154
|
+
this.engine.setSf2Instrument(message.config, message.destinationId);
|
|
2155
|
+
break;
|
|
2156
|
+
case "syncMidiNoteOn":
|
|
2157
|
+
this.engine.pushMidiNoteOn(
|
|
2158
|
+
message.destinationId,
|
|
2159
|
+
message.group,
|
|
2160
|
+
message.channel,
|
|
2161
|
+
message.note,
|
|
2162
|
+
message.velocity,
|
|
2163
|
+
message.renderFrame
|
|
2164
|
+
);
|
|
2165
|
+
break;
|
|
2166
|
+
case "syncMidiNoteOff":
|
|
2167
|
+
this.engine.pushMidiNoteOff(
|
|
2168
|
+
message.destinationId,
|
|
2169
|
+
message.group,
|
|
2170
|
+
message.channel,
|
|
2171
|
+
message.note,
|
|
2172
|
+
message.velocity,
|
|
2173
|
+
message.renderFrame
|
|
2174
|
+
);
|
|
2175
|
+
break;
|
|
2176
|
+
case "syncMidiCc":
|
|
2177
|
+
this.engine.pushMidiCc(
|
|
2178
|
+
message.destinationId,
|
|
2179
|
+
message.group,
|
|
2180
|
+
message.channel,
|
|
2181
|
+
message.controller,
|
|
2182
|
+
message.value,
|
|
2183
|
+
message.renderFrame
|
|
2184
|
+
);
|
|
2185
|
+
break;
|
|
2186
|
+
case "syncMidiPanic":
|
|
2187
|
+
this.engine.pushMidiPanic(message.renderFrame);
|
|
2188
|
+
break;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
receiveCaptureRequest(message) {
|
|
2192
|
+
if (this.closed) {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
try {
|
|
2196
|
+
if (message.op === "status") {
|
|
2197
|
+
const status = this.engine.captureStatus();
|
|
2198
|
+
this.transport?.postMessage?.({
|
|
2199
|
+
type: "captureResponse",
|
|
2200
|
+
requestId: message.requestId,
|
|
2201
|
+
ok: true,
|
|
2202
|
+
status: {
|
|
2203
|
+
capturedFrames: status.capturedFrames,
|
|
2204
|
+
overflowCount: status.overflowCount,
|
|
2205
|
+
armed: status.armed,
|
|
2206
|
+
punchEnabled: status.punchEnabled,
|
|
2207
|
+
source: status.source,
|
|
2208
|
+
recordOffsetSamples: status.recordOffsetSamples
|
|
2209
|
+
}
|
|
2210
|
+
});
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (message.op === "read") {
|
|
2214
|
+
const captured = this.engine.capturedAudio();
|
|
2215
|
+
const channels = [];
|
|
2216
|
+
for (let ch = 0; ch < captured.length; ch++) {
|
|
2217
|
+
const source = captured[ch];
|
|
2218
|
+
const copy = [];
|
|
2219
|
+
for (let i = 0; i < source.length; i++) {
|
|
2220
|
+
copy.push(Number(source[i]));
|
|
2221
|
+
}
|
|
2222
|
+
channels.push(copy);
|
|
2223
|
+
}
|
|
2224
|
+
this.transport?.postMessage?.({
|
|
2225
|
+
type: "captureResponse",
|
|
2226
|
+
requestId: message.requestId,
|
|
2227
|
+
ok: true,
|
|
2228
|
+
channels
|
|
2229
|
+
});
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
this.engine.resetCapture();
|
|
2233
|
+
this.transport?.postMessage?.({
|
|
2234
|
+
type: "captureResponse",
|
|
2235
|
+
requestId: message.requestId,
|
|
2236
|
+
ok: true
|
|
2237
|
+
});
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
this.transport?.postMessage?.({
|
|
2240
|
+
type: "captureResponse",
|
|
2241
|
+
requestId: message.requestId,
|
|
2242
|
+
ok: false,
|
|
2243
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
receiveTransportRequest(message) {
|
|
2248
|
+
if (this.closed) {
|
|
2249
|
+
return;
|
|
2250
|
+
}
|
|
2251
|
+
try {
|
|
2252
|
+
this.transport?.postMessage?.({
|
|
2253
|
+
type: "transportResponse",
|
|
2254
|
+
requestId: message.requestId,
|
|
2255
|
+
ok: true,
|
|
2256
|
+
state: this.engine.getTransportState()
|
|
2257
|
+
});
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
this.transport?.postMessage?.({
|
|
2260
|
+
type: "transportResponse",
|
|
2261
|
+
requestId: message.requestId,
|
|
2262
|
+
ok: false,
|
|
2263
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2264
|
+
});
|
|
1791
2265
|
}
|
|
1792
2266
|
}
|
|
1793
2267
|
destroy() {
|
|
@@ -1868,6 +2342,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1868
2342
|
case 17 /* SeekMarker */:
|
|
1869
2343
|
this.engine.seekMarker(Math.trunc(Number(command.targetId ?? 0)), sampleTime);
|
|
1870
2344
|
break;
|
|
2345
|
+
case 10 /* SetSoloMute */:
|
|
2346
|
+
this.engine.setSoloMute(
|
|
2347
|
+
Math.trunc(Number(command.targetId ?? 0)),
|
|
2348
|
+
Boolean((Number(command.argInt ?? 0) & 2) !== 0),
|
|
2349
|
+
Boolean((Number(command.argInt ?? 0) & 1) !== 0),
|
|
2350
|
+
sampleTime
|
|
2351
|
+
);
|
|
2352
|
+
break;
|
|
1871
2353
|
default:
|
|
1872
2354
|
this.publishTelemetryRecord({
|
|
1873
2355
|
type: 1 /* Error */,
|
|
@@ -1899,10 +2381,12 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1899
2381
|
}
|
|
1900
2382
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
1901
2383
|
const meter = meterFromEngine(item);
|
|
1902
|
-
if (meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
2384
|
+
if (meter.frame !== this.lastMeterFrame && meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
1903
2385
|
continue;
|
|
1904
2386
|
}
|
|
1905
|
-
this.lastMeterFrame
|
|
2387
|
+
if (meter.frame !== this.lastMeterFrame) {
|
|
2388
|
+
this.lastMeterFrame = meter.frame;
|
|
2389
|
+
}
|
|
1906
2390
|
if (this.meterRing) {
|
|
1907
2391
|
this.writeMeterRing(meter);
|
|
1908
2392
|
} else {
|
|
@@ -1919,12 +2403,19 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1919
2403
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1920
2404
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1921
2405
|
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1922
|
-
ring.records[offset + 1] = meter.
|
|
1923
|
-
ring.records[offset + 2] = meter.
|
|
1924
|
-
ring.records[offset + 3] = meter.
|
|
1925
|
-
ring.records[offset + 4] = meter.
|
|
1926
|
-
ring.records[offset + 5] = meter.
|
|
1927
|
-
ring.records[offset + 6] =
|
|
2406
|
+
ring.records[offset + 1] = encodeFrameHi(meter.frame);
|
|
2407
|
+
ring.records[offset + 2] = meter.targetId;
|
|
2408
|
+
ring.records[offset + 3] = meter.peakDbL;
|
|
2409
|
+
ring.records[offset + 4] = meter.peakDbR;
|
|
2410
|
+
ring.records[offset + 5] = meter.rmsDbL;
|
|
2411
|
+
ring.records[offset + 6] = meter.rmsDbR;
|
|
2412
|
+
ring.records[offset + 7] = meter.correlation;
|
|
2413
|
+
ring.records[offset + 8] = meter.truePeakDbL;
|
|
2414
|
+
ring.records[offset + 9] = meter.truePeakDbR;
|
|
2415
|
+
ring.records[offset + 10] = meter.momentaryLufs;
|
|
2416
|
+
ring.records[offset + 11] = meter.shortTermLufs;
|
|
2417
|
+
ring.records[offset + 12] = meter.integratedLufs;
|
|
2418
|
+
ring.records[offset + 13] = meter.gainReductionDb;
|
|
1928
2419
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1929
2420
|
}
|
|
1930
2421
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
@@ -2056,9 +2547,28 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2056
2547
|
this.metronomeConfig.clickSamples
|
|
2057
2548
|
);
|
|
2058
2549
|
break;
|
|
2550
|
+
case "syncTempo":
|
|
2551
|
+
this.module._sonare_rt_engine_set_tempo(this.engine, message.bpm);
|
|
2552
|
+
break;
|
|
2059
2553
|
case "syncClips":
|
|
2554
|
+
case "syncClipsDelta":
|
|
2555
|
+
case "syncMidiClips":
|
|
2060
2556
|
case "syncMarkers":
|
|
2061
2557
|
case "syncAutomation":
|
|
2558
|
+
case "syncMixer":
|
|
2559
|
+
case "syncCapture":
|
|
2560
|
+
case "syncTrackStripEqBand":
|
|
2561
|
+
case "syncMasterStripEqBand":
|
|
2562
|
+
case "syncTrackStripInsertBypassed":
|
|
2563
|
+
case "syncMasterStripInsertBypassed":
|
|
2564
|
+
case "syncBuiltinInstrument":
|
|
2565
|
+
case "syncSynthInstrument":
|
|
2566
|
+
case "syncSf2Instrument":
|
|
2567
|
+
case "syncLoadSoundFont":
|
|
2568
|
+
case "syncMidiNoteOn":
|
|
2569
|
+
case "syncMidiNoteOff":
|
|
2570
|
+
case "syncMidiCc":
|
|
2571
|
+
case "syncMidiPanic":
|
|
2062
2572
|
if (this.telemetryRing) {
|
|
2063
2573
|
writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
|
|
2064
2574
|
type: 1 /* Error */,
|
|
@@ -2073,6 +2583,28 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2073
2583
|
break;
|
|
2074
2584
|
}
|
|
2075
2585
|
}
|
|
2586
|
+
receiveCaptureRequest(message, port) {
|
|
2587
|
+
if (this.closed) {
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
port?.postMessage?.({
|
|
2591
|
+
type: "captureResponse",
|
|
2592
|
+
requestId: message.requestId,
|
|
2593
|
+
ok: false,
|
|
2594
|
+
error: "Capture read-back is not supported by the sonare-rt runtime."
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
receiveTransportRequest(message, port) {
|
|
2598
|
+
if (this.closed) {
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
port?.postMessage?.({
|
|
2602
|
+
type: "transportResponse",
|
|
2603
|
+
requestId: message.requestId,
|
|
2604
|
+
ok: false,
|
|
2605
|
+
error: "Transport state read-back is not supported by the sonare-rt runtime."
|
|
2606
|
+
});
|
|
2607
|
+
}
|
|
2076
2608
|
destroy() {
|
|
2077
2609
|
if (this.closed) {
|
|
2078
2610
|
return;
|
|
@@ -2250,6 +2782,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2250
2782
|
this.meterReadIndex = 0;
|
|
2251
2783
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
2252
2784
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
2785
|
+
this.captureRequestId = 1;
|
|
2786
|
+
this.captureRequests = /* @__PURE__ */ new Map();
|
|
2787
|
+
this.transportRequestId = 1;
|
|
2788
|
+
this.transportRequests = /* @__PURE__ */ new Map();
|
|
2253
2789
|
this.destroyed = false;
|
|
2254
2790
|
this.node = node;
|
|
2255
2791
|
this.capabilities = capabilities;
|
|
@@ -2260,11 +2796,31 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2260
2796
|
this.resolveReady = resolve;
|
|
2261
2797
|
this.rejectReady = reject;
|
|
2262
2798
|
});
|
|
2263
|
-
if (capabilities.
|
|
2799
|
+
if (!capabilities.readyMessage) {
|
|
2264
2800
|
this.resolveReady();
|
|
2265
2801
|
}
|
|
2266
2802
|
this.node.port.onmessage = (event) => {
|
|
2267
|
-
if (
|
|
2803
|
+
if (isEngineCaptureResponseMessage(event.data)) {
|
|
2804
|
+
const pending = this.captureRequests.get(event.data.requestId);
|
|
2805
|
+
if (pending) {
|
|
2806
|
+
this.captureRequests.delete(event.data.requestId);
|
|
2807
|
+
if (event.data.ok) {
|
|
2808
|
+
pending.resolve(event.data);
|
|
2809
|
+
} else {
|
|
2810
|
+
pending.reject(new Error(event.data.error ?? "Capture request failed"));
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
} else if (isEngineTransportResponseMessage(event.data)) {
|
|
2814
|
+
const pending = this.transportRequests.get(event.data.requestId);
|
|
2815
|
+
if (pending) {
|
|
2816
|
+
this.transportRequests.delete(event.data.requestId);
|
|
2817
|
+
if (event.data.ok) {
|
|
2818
|
+
pending.resolve(event.data);
|
|
2819
|
+
} else {
|
|
2820
|
+
pending.reject(new Error(event.data.error ?? "Transport request failed"));
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
} else if (isEngineTelemetryRecord(event.data)) {
|
|
2268
2824
|
this.emitTelemetry(event.data);
|
|
2269
2825
|
} else if (isMeterSnapshot(event.data)) {
|
|
2270
2826
|
this.emitMeter(event.data);
|
|
@@ -2318,7 +2874,10 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2318
2874
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
2319
2875
|
telemetryRingCapacity: telemetryRing?.capacity,
|
|
2320
2876
|
meterSharedBuffer: meterRing?.sharedBuffer,
|
|
2321
|
-
meterRingCapacity: meterRing?.capacity
|
|
2877
|
+
meterRingCapacity: meterRing?.capacity,
|
|
2878
|
+
wasmBinary: options.wasmBinary,
|
|
2879
|
+
initialSyncMessages: options.initialSyncMessages,
|
|
2880
|
+
initialCommands: options.initialCommands
|
|
2322
2881
|
};
|
|
2323
2882
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
2324
2883
|
const node = factory(context, processorName, {
|
|
@@ -2338,7 +2897,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2338
2897
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
2339
2898
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
2340
2899
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
2341
|
-
degradedReason
|
|
2900
|
+
degradedReason,
|
|
2901
|
+
readyMessage: runtimeTarget === "sonare-rt" || runtimeTarget === "embind" && moduleUrl !== void 0 && !options.nodeFactory
|
|
2342
2902
|
},
|
|
2343
2903
|
commandRing,
|
|
2344
2904
|
telemetryRing,
|
|
@@ -2375,6 +2935,32 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2375
2935
|
this.node.port.postMessage(command);
|
|
2376
2936
|
return true;
|
|
2377
2937
|
}
|
|
2938
|
+
requestCaptureStatus() {
|
|
2939
|
+
return this.sendCaptureRequest("status").then((response) => {
|
|
2940
|
+
if (!response.status) {
|
|
2941
|
+
throw new Error("Capture status response is missing status.");
|
|
2942
|
+
}
|
|
2943
|
+
return response.status;
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
requestCapturedAudio() {
|
|
2947
|
+
return this.sendCaptureRequest("read").then(
|
|
2948
|
+
(response) => (response.channels ?? []).map(
|
|
2949
|
+
(channel) => channel instanceof Float32Array ? channel : new Float32Array(channel)
|
|
2950
|
+
)
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
requestCaptureReset() {
|
|
2954
|
+
return this.sendCaptureRequest("reset").then(() => void 0);
|
|
2955
|
+
}
|
|
2956
|
+
requestTransportState() {
|
|
2957
|
+
return this.sendTransportRequest().then((response) => {
|
|
2958
|
+
if (!response.state) {
|
|
2959
|
+
throw new Error("Transport state response is missing state.");
|
|
2960
|
+
}
|
|
2961
|
+
return response.state;
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2378
2964
|
pollTelemetry() {
|
|
2379
2965
|
if (!this.telemetryRing) {
|
|
2380
2966
|
return [];
|
|
@@ -2419,6 +3005,14 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2419
3005
|
this.destroyed = true;
|
|
2420
3006
|
this.node.port.postMessage({ type: 3 /* TransportStop */, sampleTime: -1 });
|
|
2421
3007
|
this.node.disconnect();
|
|
3008
|
+
for (const pending of this.captureRequests.values()) {
|
|
3009
|
+
pending.reject(new Error("Realtime engine node is destroyed."));
|
|
3010
|
+
}
|
|
3011
|
+
this.captureRequests.clear();
|
|
3012
|
+
for (const pending of this.transportRequests.values()) {
|
|
3013
|
+
pending.reject(new Error("Realtime engine node is destroyed."));
|
|
3014
|
+
}
|
|
3015
|
+
this.transportRequests.clear();
|
|
2422
3016
|
this.telemetryListeners.clear();
|
|
2423
3017
|
this.meterListeners.clear();
|
|
2424
3018
|
}
|
|
@@ -2432,14 +3026,50 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2432
3026
|
listener(meter);
|
|
2433
3027
|
}
|
|
2434
3028
|
}
|
|
3029
|
+
sendCaptureRequest(op) {
|
|
3030
|
+
if (this.destroyed) {
|
|
3031
|
+
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
3032
|
+
}
|
|
3033
|
+
const requestId = this.captureRequestId++;
|
|
3034
|
+
const promise = new Promise((resolve, reject) => {
|
|
3035
|
+
this.captureRequests.set(requestId, { resolve, reject });
|
|
3036
|
+
});
|
|
3037
|
+
this.node.port.postMessage({ type: "captureRequest", requestId, op });
|
|
3038
|
+
return promise;
|
|
3039
|
+
}
|
|
3040
|
+
sendTransportRequest() {
|
|
3041
|
+
if (this.destroyed) {
|
|
3042
|
+
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
3043
|
+
}
|
|
3044
|
+
const requestId = this.transportRequestId++;
|
|
3045
|
+
const promise = new Promise((resolve, reject) => {
|
|
3046
|
+
this.transportRequests.set(requestId, { resolve, reject });
|
|
3047
|
+
});
|
|
3048
|
+
this.node.port.postMessage({ type: "transportRequest", requestId, op: "state" });
|
|
3049
|
+
return promise;
|
|
3050
|
+
}
|
|
2435
3051
|
};
|
|
2436
3052
|
var SonareEngine = class _SonareEngine {
|
|
2437
3053
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
2438
3054
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
2439
3055
|
this.clips = /* @__PURE__ */ new Map();
|
|
3056
|
+
this.midiClips = /* @__PURE__ */ new Map();
|
|
2440
3057
|
this.markers = /* @__PURE__ */ new Map();
|
|
3058
|
+
this.trackLaneIds = [];
|
|
3059
|
+
this.trackSends = /* @__PURE__ */ new Map();
|
|
3060
|
+
this.buses = [];
|
|
3061
|
+
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
3062
|
+
this.busStripJson = /* @__PURE__ */ new Map();
|
|
3063
|
+
this.tempoBpm = 120;
|
|
3064
|
+
this.timeSignature = { numerator: 4, denominator: 4 };
|
|
3065
|
+
this.tempoSegments = [{ startPpq: 0, bpm: 120 }];
|
|
3066
|
+
this.timeSignatureSegments = [
|
|
3067
|
+
{ startPpq: 0, numerator: 4, denominator: 4 }
|
|
3068
|
+
];
|
|
2441
3069
|
this.nextClipId = 1;
|
|
2442
3070
|
this.nextMarkerId = 1;
|
|
3071
|
+
this.transportPlaying = false;
|
|
3072
|
+
this.pendingInstrumentSync = [];
|
|
2443
3073
|
this.destroyed = false;
|
|
2444
3074
|
this.context = context;
|
|
2445
3075
|
this.realtimeNode = realtimeNode;
|
|
@@ -2450,8 +3080,21 @@ var SonareEngine = class _SonareEngine {
|
|
|
2450
3080
|
this.offlineBlockSize = offlineBlockSize;
|
|
2451
3081
|
this.offlineChannelCount = offlineChannelCount;
|
|
2452
3082
|
this.transport = {
|
|
2453
|
-
play: (sampleTime = -1) =>
|
|
2454
|
-
|
|
3083
|
+
play: (sampleTime = -1) => {
|
|
3084
|
+
const ok = this.realtimeNode.play(sampleTime);
|
|
3085
|
+
if (ok) {
|
|
3086
|
+
this.transportPlaying = true;
|
|
3087
|
+
}
|
|
3088
|
+
return ok;
|
|
3089
|
+
},
|
|
3090
|
+
stop: (sampleTime = -1) => {
|
|
3091
|
+
const ok = this.realtimeNode.stop(sampleTime);
|
|
3092
|
+
if (ok) {
|
|
3093
|
+
this.transportPlaying = false;
|
|
3094
|
+
this.flushPendingInstrumentSync();
|
|
3095
|
+
}
|
|
3096
|
+
return ok;
|
|
3097
|
+
},
|
|
2455
3098
|
seekPpq: (ppq, sampleTime = -1) => {
|
|
2456
3099
|
this.offlineEngine.seekPpq(ppq, sampleTime);
|
|
2457
3100
|
return this.realtimeNode.seekPpq(ppq, sampleTime);
|
|
@@ -2462,6 +3105,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
2462
3105
|
return this.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
2463
3106
|
},
|
|
2464
3107
|
setTempo: (bpm) => this.setTempo(bpm),
|
|
3108
|
+
setTempoSegments: (segments) => this.setTempoSegments(segments),
|
|
2465
3109
|
setLoop: (startPpq, endPpq, enabled = true) => this.setLoop(startPpq, endPpq, enabled)
|
|
2466
3110
|
};
|
|
2467
3111
|
}
|
|
@@ -2496,13 +3140,37 @@ var SonareEngine = class _SonareEngine {
|
|
|
2496
3140
|
await this.context.resume?.();
|
|
2497
3141
|
}
|
|
2498
3142
|
setTempo(bpm) {
|
|
3143
|
+
this.tempoBpm = bpm;
|
|
3144
|
+
this.tempoSegments = [{ startPpq: 0, bpm }];
|
|
2499
3145
|
this.offlineEngine.setTempo(bpm);
|
|
3146
|
+
this.postTempoSync();
|
|
2500
3147
|
this.realtimeNode.sendCommand({
|
|
2501
3148
|
type: 6 /* SetTempoMap */,
|
|
2502
3149
|
sampleTime: -1,
|
|
2503
3150
|
argFloat: bpm
|
|
2504
3151
|
});
|
|
2505
3152
|
}
|
|
3153
|
+
setTempoSegments(segments) {
|
|
3154
|
+
this.tempoSegments = segments.map((segment) => ({ ...segment }));
|
|
3155
|
+
this.tempoBpm = this.tempoSegments[0]?.bpm ?? this.tempoBpm;
|
|
3156
|
+
this.offlineEngine.setTempoSegments(this.tempoSegments);
|
|
3157
|
+
this.postTempoSync();
|
|
3158
|
+
}
|
|
3159
|
+
setTimeSignature(numerator, denominator) {
|
|
3160
|
+
this.timeSignature = { numerator, denominator };
|
|
3161
|
+
this.timeSignatureSegments = [{ startPpq: 0, numerator, denominator }];
|
|
3162
|
+
this.offlineEngine.setTimeSignature(numerator, denominator);
|
|
3163
|
+
this.postTempoSync();
|
|
3164
|
+
}
|
|
3165
|
+
setTimeSignatureSegments(segments) {
|
|
3166
|
+
this.timeSignatureSegments = segments.map((segment) => ({ ...segment }));
|
|
3167
|
+
const first = this.timeSignatureSegments[0];
|
|
3168
|
+
if (first) {
|
|
3169
|
+
this.timeSignature = { numerator: first.numerator, denominator: first.denominator };
|
|
3170
|
+
}
|
|
3171
|
+
this.offlineEngine.setTimeSignatureSegments(this.timeSignatureSegments);
|
|
3172
|
+
this.postTempoSync();
|
|
3173
|
+
}
|
|
2506
3174
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
2507
3175
|
this.offlineEngine.setLoop(startPpq, endPpq, enabled);
|
|
2508
3176
|
return this.realtimeNode.sendCommand({
|
|
@@ -2513,6 +3181,17 @@ var SonareEngine = class _SonareEngine {
|
|
|
2513
3181
|
argInt: Math.round(endPpq * 1e6)
|
|
2514
3182
|
});
|
|
2515
3183
|
}
|
|
3184
|
+
countInEndSample(startSample, bars) {
|
|
3185
|
+
return this.offlineEngine.countInEndSample(startSample, bars);
|
|
3186
|
+
}
|
|
3187
|
+
async getTransportState() {
|
|
3188
|
+
const state = await this.realtimeNode.requestTransportState();
|
|
3189
|
+
this.latestTransportState = state;
|
|
3190
|
+
return state;
|
|
3191
|
+
}
|
|
3192
|
+
cachedTransportState() {
|
|
3193
|
+
return this.latestTransportState;
|
|
3194
|
+
}
|
|
2516
3195
|
setParam(nodeId, param, value) {
|
|
2517
3196
|
const paramId = this.resolveParamId(nodeId, param);
|
|
2518
3197
|
this.offlineEngine.setParameter(paramId, value);
|
|
@@ -2543,12 +3222,198 @@ var SonareEngine = class _SonareEngine {
|
|
|
2543
3222
|
return parameters;
|
|
2544
3223
|
}
|
|
2545
3224
|
setSoloMute(target, solo, mute) {
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
3225
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3226
|
+
this.offlineEngine.setSoloMute(laneIndex, solo, mute);
|
|
3227
|
+
return this.realtimeNode.sendCommand({
|
|
3228
|
+
type: 10 /* SetSoloMute */,
|
|
3229
|
+
targetId: laneIndex,
|
|
3230
|
+
sampleTime: -1,
|
|
3231
|
+
argInt: (mute ? 1 : 0) | (solo ? 2 : 0)
|
|
3232
|
+
});
|
|
3233
|
+
}
|
|
3234
|
+
setStripGain(target, db) {
|
|
3235
|
+
if (target === "master") {
|
|
3236
|
+
const paramId2 = engineMixerMasterTarget(ENGINE_MIXER_PARAM_FADER_DB);
|
|
3237
|
+
this.offlineEngine.setParameter(paramId2, db);
|
|
3238
|
+
return this.realtimeNode.sendCommand({
|
|
3239
|
+
type: 1 /* SetParamSmoothed */,
|
|
3240
|
+
targetId: paramId2,
|
|
3241
|
+
sampleTime: -1,
|
|
3242
|
+
argFloat: db
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3246
|
+
const paramId = engineMixerLaneTarget(laneIndex, ENGINE_MIXER_PARAM_FADER_DB);
|
|
3247
|
+
this.offlineEngine.setParameter(paramId, db);
|
|
3248
|
+
return this.realtimeNode.sendCommand({
|
|
3249
|
+
type: 1 /* SetParamSmoothed */,
|
|
3250
|
+
targetId: paramId,
|
|
3251
|
+
sampleTime: -1,
|
|
3252
|
+
argFloat: db
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
setStripPan(target, pan) {
|
|
3256
|
+
if (target === "master") {
|
|
3257
|
+
const paramId2 = engineMixerMasterTarget(ENGINE_MIXER_PARAM_PAN);
|
|
3258
|
+
this.offlineEngine.setParameter(paramId2, pan);
|
|
3259
|
+
return this.realtimeNode.sendCommand({
|
|
3260
|
+
type: 1 /* SetParamSmoothed */,
|
|
3261
|
+
targetId: paramId2,
|
|
3262
|
+
sampleTime: -1,
|
|
3263
|
+
argFloat: pan
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3267
|
+
const paramId = engineMixerLaneTarget(laneIndex, ENGINE_MIXER_PARAM_PAN);
|
|
3268
|
+
this.offlineEngine.setParameter(paramId, pan);
|
|
3269
|
+
return this.realtimeNode.sendCommand({
|
|
3270
|
+
type: 1 /* SetParamSmoothed */,
|
|
3271
|
+
targetId: paramId,
|
|
3272
|
+
sampleTime: -1,
|
|
3273
|
+
argFloat: pan
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Declares the mixer track lanes in an explicit order.
|
|
3278
|
+
*
|
|
3279
|
+
* Lane indices are append-only: once a track id occupies a lane, its index
|
|
3280
|
+
* stays fixed for the engine's lifetime. The given list must therefore start
|
|
3281
|
+
* with the already-declared lane ids in their current order and may only
|
|
3282
|
+
* append new track ids after them. Entries carrying `sends` replace that
|
|
3283
|
+
* track's send list; entries without `sends` leave existing sends untouched.
|
|
3284
|
+
*
|
|
3285
|
+
* @param lanes Track ids or lane descriptors in the desired lane order.
|
|
3286
|
+
*/
|
|
3287
|
+
setTrackLanes(lanes) {
|
|
3288
|
+
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
3289
|
+
const ids = [];
|
|
3290
|
+
for (const entry of entries) {
|
|
3291
|
+
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
3292
|
+
throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
3293
|
+
}
|
|
3294
|
+
ids.push(entry.trackId);
|
|
3295
|
+
}
|
|
3296
|
+
if (new Set(ids).size !== ids.length) {
|
|
3297
|
+
throw new Error("Duplicate track id in mixer lane list");
|
|
3298
|
+
}
|
|
3299
|
+
for (let index = 0; index < this.trackLaneIds.length; index++) {
|
|
3300
|
+
if (ids[index] !== this.trackLaneIds[index]) {
|
|
3301
|
+
throw new Error(
|
|
3302
|
+
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
for (const entry of entries) {
|
|
3307
|
+
if (entry.sends) {
|
|
3308
|
+
this.trackSends.set(
|
|
3309
|
+
entry.trackId,
|
|
3310
|
+
entry.sends.map((send) => ({ ...send }))
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
this.trackLaneIds.splice(0, this.trackLaneIds.length, ...ids);
|
|
3315
|
+
this.syncMixer();
|
|
3316
|
+
}
|
|
3317
|
+
setSends(target, sends) {
|
|
3318
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3319
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3320
|
+
this.trackSends.set(
|
|
3321
|
+
trackId,
|
|
3322
|
+
sends.map((send) => ({ ...send }))
|
|
2551
3323
|
);
|
|
3324
|
+
this.syncMixer();
|
|
3325
|
+
}
|
|
3326
|
+
setTrackBuses(buses) {
|
|
3327
|
+
this.buses.splice(0, this.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
3328
|
+
this.syncMixer();
|
|
3329
|
+
}
|
|
3330
|
+
setBusGain(busId, db) {
|
|
3331
|
+
const busIndex = this.ensureBus(busId);
|
|
3332
|
+
this.buses[busIndex] = { ...this.buses[busIndex], busId, gainDb: db };
|
|
3333
|
+
this.offlineEngine.setTrackBuses(this.buses);
|
|
3334
|
+
const paramId = engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB);
|
|
3335
|
+
this.offlineEngine.setParameter(paramId, db);
|
|
3336
|
+
return this.realtimeNode.sendCommand({
|
|
3337
|
+
type: 1 /* SetParamSmoothed */,
|
|
3338
|
+
targetId: paramId,
|
|
3339
|
+
sampleTime: -1,
|
|
3340
|
+
argFloat: db
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
setTrackStripJson(target, sceneJson) {
|
|
3344
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3345
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3346
|
+
this.offlineEngine.setTrackStripJson(trackId, sceneJson);
|
|
3347
|
+
this.trackStripJson.set(trackId, sceneJson);
|
|
3348
|
+
this.syncMixer();
|
|
3349
|
+
}
|
|
3350
|
+
setTrackStripEqBand(target, bandIndex, band) {
|
|
3351
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3352
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3353
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3354
|
+
this.offlineEngine.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
3355
|
+
this.postSync({ type: "syncTrackStripEqBand", trackId, bandIndex, bandJson });
|
|
3356
|
+
}
|
|
3357
|
+
setTrackStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass = false) {
|
|
3358
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3359
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3360
|
+
this.offlineEngine.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
3361
|
+
this.postSync({
|
|
3362
|
+
type: "syncTrackStripInsertBypassed",
|
|
3363
|
+
trackId,
|
|
3364
|
+
insertIndex,
|
|
3365
|
+
bypassed,
|
|
3366
|
+
resetOnBypass
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
setStripEq(target, bandIndex, band) {
|
|
3370
|
+
if (target === "master") {
|
|
3371
|
+
this.setMasterStripEqBand(bandIndex, band);
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
this.setTrackStripEqBand(target, bandIndex, band);
|
|
3375
|
+
}
|
|
3376
|
+
setStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass = false) {
|
|
3377
|
+
if (target === "master") {
|
|
3378
|
+
this.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
this.setTrackStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass);
|
|
3382
|
+
}
|
|
3383
|
+
setStripInserts(target, sceneJson) {
|
|
3384
|
+
if (target === "master") {
|
|
3385
|
+
this.setMasterStripJson(sceneJson);
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
this.setTrackStripJson(target, sceneJson);
|
|
3389
|
+
}
|
|
3390
|
+
setBusStripJson(busId, sceneJson) {
|
|
3391
|
+
this.ensureBus(busId);
|
|
3392
|
+
this.offlineEngine.setBusStripJson(busId, sceneJson);
|
|
3393
|
+
this.busStripJson.set(busId, sceneJson);
|
|
3394
|
+
this.syncMixer();
|
|
3395
|
+
}
|
|
3396
|
+
setMasterStripJson(sceneJson) {
|
|
3397
|
+
this.offlineEngine.setMasterStripJson(sceneJson);
|
|
3398
|
+
this.masterStripJson = sceneJson;
|
|
3399
|
+
this.syncMixer();
|
|
3400
|
+
}
|
|
3401
|
+
setMasterStripEqBand(bandIndex, band) {
|
|
3402
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3403
|
+
this.offlineEngine.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
3404
|
+
this.postSync({ type: "syncMasterStripEqBand", bandIndex, bandJson });
|
|
3405
|
+
}
|
|
3406
|
+
setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
|
|
3407
|
+
this.offlineEngine.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
3408
|
+
this.postSync({
|
|
3409
|
+
type: "syncMasterStripInsertBypassed",
|
|
3410
|
+
insertIndex,
|
|
3411
|
+
bypassed,
|
|
3412
|
+
resetOnBypass
|
|
3413
|
+
});
|
|
3414
|
+
}
|
|
3415
|
+
setMasterChain(sceneJson) {
|
|
3416
|
+
this.setMasterStripJson(sceneJson);
|
|
2552
3417
|
}
|
|
2553
3418
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
2554
3419
|
const id = opts.id ?? this.nextClipId++;
|
|
@@ -2556,18 +3421,108 @@ var SonareEngine = class _SonareEngine {
|
|
|
2556
3421
|
...opts,
|
|
2557
3422
|
id,
|
|
2558
3423
|
channels: buffer,
|
|
2559
|
-
startPpq
|
|
3424
|
+
startPpq,
|
|
3425
|
+
trackId: this.resolveTargetId(trackId)
|
|
2560
3426
|
};
|
|
3427
|
+
this.ensureTrackLane(trackId);
|
|
2561
3428
|
this.clips.set(id, clip);
|
|
2562
|
-
this.
|
|
2563
|
-
void trackId;
|
|
3429
|
+
this.syncClipsDelta([clip], []);
|
|
2564
3430
|
return id;
|
|
2565
3431
|
}
|
|
2566
3432
|
removeClip(clipId) {
|
|
2567
3433
|
this.clips.delete(clipId);
|
|
2568
|
-
this.
|
|
3434
|
+
this.syncClipsDelta([], [clipId]);
|
|
3435
|
+
}
|
|
3436
|
+
setMidiClips(clips) {
|
|
3437
|
+
this.midiClips.clear();
|
|
3438
|
+
for (const clip of clips) {
|
|
3439
|
+
const id = clip.id ?? this.nextClipId++;
|
|
3440
|
+
this.midiClips.set(id, { ...clip, id, events: clip.events.map((event) => ({ ...event })) });
|
|
3441
|
+
}
|
|
3442
|
+
this.syncMidiClips();
|
|
3443
|
+
}
|
|
3444
|
+
setBuiltinInstrument(trackId, config = {}) {
|
|
3445
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3446
|
+
this.offlineEngine.setBuiltinInstrument(config, destinationId);
|
|
3447
|
+
this.postInstrumentSync({ type: "syncBuiltinInstrument", destinationId, config });
|
|
3448
|
+
}
|
|
3449
|
+
setSynthInstrument(trackId, patch = {}) {
|
|
3450
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3451
|
+
this.offlineEngine.setSynthInstrument(patch, destinationId);
|
|
3452
|
+
this.postInstrumentSync({ type: "syncSynthInstrument", destinationId, patch });
|
|
3453
|
+
}
|
|
3454
|
+
loadSoundFont(data) {
|
|
3455
|
+
this.offlineEngine.loadSoundFont(data);
|
|
3456
|
+
this.postInstrumentSync({ type: "syncLoadSoundFont", data });
|
|
3457
|
+
}
|
|
3458
|
+
setSf2Instrument(trackId, config = {}) {
|
|
3459
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3460
|
+
this.offlineEngine.setSf2Instrument(config, destinationId);
|
|
3461
|
+
this.postInstrumentSync({ type: "syncSf2Instrument", destinationId, config });
|
|
3462
|
+
}
|
|
3463
|
+
pushMidiNoteOn(trackId, group, channel, note, velocity, renderFrame = -1) {
|
|
3464
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3465
|
+
this.offlineEngine.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
3466
|
+
this.postSync({
|
|
3467
|
+
type: "syncMidiNoteOn",
|
|
3468
|
+
destinationId,
|
|
3469
|
+
group,
|
|
3470
|
+
channel,
|
|
3471
|
+
note,
|
|
3472
|
+
velocity,
|
|
3473
|
+
renderFrame
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
pushMidiNoteOff(trackId, group, channel, note, velocity = 0, renderFrame = -1) {
|
|
3477
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3478
|
+
this.offlineEngine.pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
|
|
3479
|
+
this.postSync({
|
|
3480
|
+
type: "syncMidiNoteOff",
|
|
3481
|
+
destinationId,
|
|
3482
|
+
group,
|
|
3483
|
+
channel,
|
|
3484
|
+
note,
|
|
3485
|
+
velocity,
|
|
3486
|
+
renderFrame
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
3490
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3491
|
+
this.offlineEngine.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
3492
|
+
this.postSync({
|
|
3493
|
+
type: "syncMidiCc",
|
|
3494
|
+
destinationId,
|
|
3495
|
+
group,
|
|
3496
|
+
channel,
|
|
3497
|
+
controller,
|
|
3498
|
+
value,
|
|
3499
|
+
renderFrame
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
pushMidiPanic(renderFrame = -1) {
|
|
3503
|
+
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
3504
|
+
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
3505
|
+
}
|
|
3506
|
+
configureCapture(options) {
|
|
3507
|
+
const bufferFrames = Math.trunc(options.bufferFrames);
|
|
3508
|
+
const channels = Math.trunc(options.channels ?? this.offlineChannelCount);
|
|
3509
|
+
const source = options.source ?? "output";
|
|
3510
|
+
const recordOffsetSamples = Math.trunc(options.recordOffsetSamples ?? 0);
|
|
3511
|
+
const inputMonitor = {
|
|
3512
|
+
enabled: Boolean(options.inputMonitor?.enabled),
|
|
3513
|
+
gain: options.inputMonitor?.gain ?? 1
|
|
3514
|
+
};
|
|
3515
|
+
this.offlineEngine.setCaptureBuffer(channels, bufferFrames);
|
|
3516
|
+
this.offlineEngine.setCaptureSource(source);
|
|
3517
|
+
this.offlineEngine.setRecordOffsetSamples(recordOffsetSamples);
|
|
3518
|
+
this.offlineEngine.setInputMonitor(inputMonitor.enabled, inputMonitor.gain);
|
|
3519
|
+
this.captureConfig = { bufferFrames, channels, source, recordOffsetSamples, inputMonitor };
|
|
3520
|
+
this.postSync({ type: "syncCapture", ...this.captureConfig });
|
|
2569
3521
|
}
|
|
2570
3522
|
armRecord(trackId, enabled) {
|
|
3523
|
+
if (enabled && !this.captureConfig) {
|
|
3524
|
+
throw new Error("Capture buffer is not configured");
|
|
3525
|
+
}
|
|
2571
3526
|
this.offlineEngine.armCapture(enabled);
|
|
2572
3527
|
return this.realtimeNode.sendCommand({
|
|
2573
3528
|
type: 13 /* ArmRecord */,
|
|
@@ -2577,8 +3532,8 @@ var SonareEngine = class _SonareEngine {
|
|
|
2577
3532
|
});
|
|
2578
3533
|
}
|
|
2579
3534
|
punch(inPpq, outPpq) {
|
|
2580
|
-
const inSample = this.
|
|
2581
|
-
const outSample = this.
|
|
3535
|
+
const inSample = this.offlineEngine.sampleAtPpq(inPpq);
|
|
3536
|
+
const outSample = this.offlineEngine.sampleAtPpq(outPpq);
|
|
2582
3537
|
this.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
2583
3538
|
return this.realtimeNode.sendCommand({
|
|
2584
3539
|
type: 14 /* Punch */,
|
|
@@ -2587,6 +3542,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
2587
3542
|
argFloat: outSample
|
|
2588
3543
|
});
|
|
2589
3544
|
}
|
|
3545
|
+
captureStatus() {
|
|
3546
|
+
return this.realtimeNode.requestCaptureStatus();
|
|
3547
|
+
}
|
|
3548
|
+
capturedAudio() {
|
|
3549
|
+
return this.realtimeNode.requestCapturedAudio();
|
|
3550
|
+
}
|
|
3551
|
+
async resetCapture() {
|
|
3552
|
+
this.offlineEngine.resetCapture();
|
|
3553
|
+
await this.realtimeNode.requestCaptureReset();
|
|
3554
|
+
}
|
|
2590
3555
|
setMetronome(opts) {
|
|
2591
3556
|
this.offlineEngine.setMetronome(opts);
|
|
2592
3557
|
this.postSync({ type: "syncMetronome", config: opts });
|
|
@@ -2602,6 +3567,55 @@ var SonareEngine = class _SonareEngine {
|
|
|
2602
3567
|
this.syncMarkers();
|
|
2603
3568
|
return id;
|
|
2604
3569
|
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Replaces the whole marker set in one call.
|
|
3572
|
+
*
|
|
3573
|
+
* Entries without an `id` are assigned fresh ids; entries carrying an `id`
|
|
3574
|
+
* keep it (ids must be positive and unique within the list). Returns the
|
|
3575
|
+
* resolved markers in the order given, so a caller can map its own marker
|
|
3576
|
+
* identities to the engine ids used by `seekMarker`/`setLoopFromMarkers`.
|
|
3577
|
+
*
|
|
3578
|
+
* @param markers The full marker list (an empty list clears all markers).
|
|
3579
|
+
* @returns The markers with their resolved engine ids.
|
|
3580
|
+
*/
|
|
3581
|
+
setMarkers(markers) {
|
|
3582
|
+
const resolved = [];
|
|
3583
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3584
|
+
for (const marker of markers) {
|
|
3585
|
+
if (!Number.isFinite(marker.ppq)) {
|
|
3586
|
+
throw new Error(`Invalid marker ppq: ${String(marker.ppq)}`);
|
|
3587
|
+
}
|
|
3588
|
+
if (marker.id !== void 0) {
|
|
3589
|
+
if (!Number.isInteger(marker.id) || marker.id <= 0) {
|
|
3590
|
+
throw new Error(`Invalid marker id: ${String(marker.id)}`);
|
|
3591
|
+
}
|
|
3592
|
+
if (seen.has(marker.id)) {
|
|
3593
|
+
throw new Error(`Duplicate marker id: ${marker.id}`);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
const id = marker.id ?? this.nextMarkerId++;
|
|
3597
|
+
seen.add(id);
|
|
3598
|
+
if (id >= this.nextMarkerId) {
|
|
3599
|
+
this.nextMarkerId = id + 1;
|
|
3600
|
+
}
|
|
3601
|
+
resolved.push({ id, ppq: marker.ppq, name: marker.name ?? "" });
|
|
3602
|
+
}
|
|
3603
|
+
this.markers.clear();
|
|
3604
|
+
for (const marker of resolved) {
|
|
3605
|
+
this.markers.set(marker.id, marker);
|
|
3606
|
+
}
|
|
3607
|
+
this.syncMarkers();
|
|
3608
|
+
return resolved.map((marker) => ({ ...marker }));
|
|
3609
|
+
}
|
|
3610
|
+
markerCount() {
|
|
3611
|
+
return this.offlineEngine.markerCount();
|
|
3612
|
+
}
|
|
3613
|
+
markerByIndex(index) {
|
|
3614
|
+
return this.offlineEngine.markerByIndex(index);
|
|
3615
|
+
}
|
|
3616
|
+
marker(markerId) {
|
|
3617
|
+
return this.offlineEngine.marker(markerId);
|
|
3618
|
+
}
|
|
2605
3619
|
seekMarker(markerId) {
|
|
2606
3620
|
this.offlineEngine.seekMarker(markerId);
|
|
2607
3621
|
return this.realtimeNode.sendCommand({
|
|
@@ -2610,6 +3624,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
2610
3624
|
sampleTime: -1
|
|
2611
3625
|
});
|
|
2612
3626
|
}
|
|
3627
|
+
setLoopFromMarkers(startMarkerId, endMarkerId) {
|
|
3628
|
+
this.offlineEngine.setLoopFromMarkers(startMarkerId, endMarkerId);
|
|
3629
|
+
const start = this.offlineEngine.marker(startMarkerId);
|
|
3630
|
+
const end = this.offlineEngine.marker(endMarkerId);
|
|
3631
|
+
return this.setLoop(start.ppq, end.ppq, true);
|
|
3632
|
+
}
|
|
2613
3633
|
async renderOffline(totalFrames) {
|
|
2614
3634
|
const frames = Math.max(0, Math.floor(totalFrames));
|
|
2615
3635
|
const inputs = [];
|
|
@@ -2640,16 +3660,80 @@ var SonareEngine = class _SonareEngine {
|
|
|
2640
3660
|
this.realtimeNode.destroy();
|
|
2641
3661
|
this.offlineEngine.destroy();
|
|
2642
3662
|
}
|
|
2643
|
-
|
|
3663
|
+
syncClipsDelta(upserts, removeIds) {
|
|
2644
3664
|
const clips = Array.from(this.clips.values());
|
|
2645
3665
|
this.offlineEngine.setClips(clips);
|
|
2646
|
-
this.postSync({
|
|
3666
|
+
this.postSync({
|
|
3667
|
+
type: "syncClipsDelta",
|
|
3668
|
+
upserts,
|
|
3669
|
+
removeIds
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
syncMidiClips() {
|
|
3673
|
+
const clips = Array.from(this.midiClips.values());
|
|
3674
|
+
this.offlineEngine.setMidiClips(clips);
|
|
3675
|
+
this.postSync({ type: "syncMidiClips", clips });
|
|
3676
|
+
}
|
|
3677
|
+
syncMixer() {
|
|
3678
|
+
const lanes = this.trackLaneIds.map((trackId) => {
|
|
3679
|
+
const sends = this.trackSends.get(trackId);
|
|
3680
|
+
return sends && sends.length > 0 ? { trackId, sends: sends.map((send) => ({ ...send })) } : { trackId };
|
|
3681
|
+
});
|
|
3682
|
+
const buses = this.buses.map((bus) => ({ ...bus }));
|
|
3683
|
+
this.offlineEngine.setTrackBuses(buses);
|
|
3684
|
+
if (lanes.length > 0) {
|
|
3685
|
+
this.offlineEngine.setTrackLanes(lanes);
|
|
3686
|
+
}
|
|
3687
|
+
const trackStrips = Array.from(this.trackStripJson, ([trackId, sceneJson]) => ({
|
|
3688
|
+
trackId,
|
|
3689
|
+
sceneJson
|
|
3690
|
+
}));
|
|
3691
|
+
const busStrips = Array.from(this.busStripJson, ([busId, sceneJson]) => ({
|
|
3692
|
+
busId,
|
|
3693
|
+
sceneJson
|
|
3694
|
+
}));
|
|
3695
|
+
this.postSync({
|
|
3696
|
+
type: "syncMixer",
|
|
3697
|
+
lanes,
|
|
3698
|
+
buses,
|
|
3699
|
+
trackStrips,
|
|
3700
|
+
busStrips,
|
|
3701
|
+
masterStripJson: this.masterStripJson
|
|
3702
|
+
});
|
|
2647
3703
|
}
|
|
2648
3704
|
syncMarkers() {
|
|
2649
3705
|
const markers = Array.from(this.markers.values()).sort((a, b) => a.ppq - b.ppq);
|
|
2650
3706
|
this.offlineEngine.setMarkers(markers);
|
|
2651
3707
|
this.postSync({ type: "syncMarkers", markers });
|
|
2652
3708
|
}
|
|
3709
|
+
postInstrumentSync(message) {
|
|
3710
|
+
if (this.destroyed) {
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
if (this.transportPlaying) {
|
|
3714
|
+
this.pendingInstrumentSync.push(message);
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
this.postSync(message);
|
|
3718
|
+
}
|
|
3719
|
+
flushPendingInstrumentSync() {
|
|
3720
|
+
if (this.destroyed || this.pendingInstrumentSync.length === 0) {
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
const pending = this.pendingInstrumentSync.splice(0);
|
|
3724
|
+
for (const message of pending) {
|
|
3725
|
+
this.postSync(message);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
postTempoSync() {
|
|
3729
|
+
this.postSync({
|
|
3730
|
+
type: "syncTempo",
|
|
3731
|
+
bpm: this.tempoBpm,
|
|
3732
|
+
timeSignature: { ...this.timeSignature },
|
|
3733
|
+
tempoSegments: this.tempoSegments.map((segment) => ({ ...segment })),
|
|
3734
|
+
timeSignatureSegments: this.timeSignatureSegments.map((segment) => ({ ...segment }))
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
2653
3737
|
// Posts an out-of-band control-sync message to the worklet engine processor.
|
|
2654
3738
|
// Sync messages use a string `type` so the worklet's message handler routes
|
|
2655
3739
|
// them to receiveSync() (numeric `type` is reserved for SonareEngineCommandRecord).
|
|
@@ -2676,15 +3760,38 @@ var SonareEngine = class _SonareEngine {
|
|
|
2676
3760
|
const parsed = Number.parseInt(target, 10);
|
|
2677
3761
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
2678
3762
|
}
|
|
3763
|
+
ensureTrackLane(target) {
|
|
3764
|
+
const trackId = this.resolveTargetId(target);
|
|
3765
|
+
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
3766
|
+
throw new Error(`Invalid track id for mixer lane: ${String(target)}`);
|
|
3767
|
+
}
|
|
3768
|
+
const existing = this.trackLaneIds.indexOf(trackId);
|
|
3769
|
+
if (existing >= 0) {
|
|
3770
|
+
return existing;
|
|
3771
|
+
}
|
|
3772
|
+
this.trackLaneIds.push(trackId);
|
|
3773
|
+
this.syncMixer();
|
|
3774
|
+
return this.trackLaneIds.length - 1;
|
|
3775
|
+
}
|
|
3776
|
+
ensureBus(busId) {
|
|
3777
|
+
const resolved = Math.trunc(busId);
|
|
3778
|
+
if (!Number.isInteger(resolved) || resolved <= 0) {
|
|
3779
|
+
throw new Error(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
3780
|
+
}
|
|
3781
|
+
const existing = this.buses.findIndex((bus) => bus.busId === resolved);
|
|
3782
|
+
if (existing >= 0) {
|
|
3783
|
+
return existing;
|
|
3784
|
+
}
|
|
3785
|
+
this.buses.push({ busId: resolved });
|
|
3786
|
+
this.syncMixer();
|
|
3787
|
+
return this.buses.length - 1;
|
|
3788
|
+
}
|
|
2679
3789
|
curveCode(curve) {
|
|
2680
3790
|
if (typeof curve === "number") {
|
|
2681
3791
|
return curve;
|
|
2682
3792
|
}
|
|
2683
3793
|
return curve === "exponential" ? 1 : 0;
|
|
2684
3794
|
}
|
|
2685
|
-
ppqToApproxSample(ppq) {
|
|
2686
|
-
return Math.max(0, Math.round(ppq * 60 / 120 * this.sampleRate));
|
|
2687
|
-
}
|
|
2688
3795
|
};
|
|
2689
3796
|
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
2690
3797
|
constructor(options = {}) {
|
|
@@ -2905,23 +4012,33 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2905
4012
|
class RegisteredSonareRealtimeEngineWorkletProcessor extends Base {
|
|
2906
4013
|
constructor(options) {
|
|
2907
4014
|
super();
|
|
4015
|
+
this.pendingMessages = [];
|
|
2908
4016
|
const port = this.port;
|
|
2909
4017
|
const processorOptions = options?.processorOptions ?? {};
|
|
2910
4018
|
if (processorOptions.runtimeTarget === "sonare-rt") {
|
|
2911
4019
|
void this.initializeSonareRt(processorOptions, port);
|
|
2912
4020
|
} else {
|
|
2913
|
-
this.
|
|
2914
|
-
postMessage: (message) => port?.postMessage?.(message),
|
|
2915
|
-
onMeter: (meter) => port?.postMessage?.(meter)
|
|
2916
|
-
});
|
|
4021
|
+
void this.initializeEmbind(processorOptions, port);
|
|
2917
4022
|
}
|
|
2918
4023
|
const onMessage = (event) => {
|
|
4024
|
+
if (!this.bridge && !this.rtBridge) {
|
|
4025
|
+
if (this.pendingMessages.length < 1024) {
|
|
4026
|
+
this.pendingMessages.push(event.data);
|
|
4027
|
+
}
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
2919
4030
|
if (isEngineCommandRecord(event.data)) {
|
|
2920
4031
|
this.bridge?.receiveCommand(event.data);
|
|
2921
4032
|
this.rtBridge?.receiveCommand(event.data);
|
|
2922
4033
|
} else if (isEngineSyncMessage(event.data)) {
|
|
2923
4034
|
this.bridge?.receiveSync(event.data);
|
|
2924
4035
|
this.rtBridge?.receiveSync(event.data);
|
|
4036
|
+
} else if (isEngineCaptureRequestMessage(event.data)) {
|
|
4037
|
+
this.bridge?.receiveCaptureRequest(event.data);
|
|
4038
|
+
this.rtBridge?.receiveCaptureRequest(event.data, port);
|
|
4039
|
+
} else if (isEngineTransportRequestMessage(event.data)) {
|
|
4040
|
+
this.bridge?.receiveTransportRequest(event.data);
|
|
4041
|
+
this.rtBridge?.receiveTransportRequest(event.data, port);
|
|
2925
4042
|
}
|
|
2926
4043
|
};
|
|
2927
4044
|
if (port?.addEventListener) {
|
|
@@ -2944,6 +4061,60 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2944
4061
|
}
|
|
2945
4062
|
return true;
|
|
2946
4063
|
}
|
|
4064
|
+
replayPendingMessages(port) {
|
|
4065
|
+
const messages = this.pendingMessages.splice(0);
|
|
4066
|
+
for (const data of messages) {
|
|
4067
|
+
if (isEngineCommandRecord(data)) {
|
|
4068
|
+
this.bridge?.receiveCommand(data);
|
|
4069
|
+
this.rtBridge?.receiveCommand(data);
|
|
4070
|
+
} else if (isEngineSyncMessage(data)) {
|
|
4071
|
+
this.bridge?.receiveSync(data);
|
|
4072
|
+
this.rtBridge?.receiveSync(data);
|
|
4073
|
+
} else if (isEngineCaptureRequestMessage(data)) {
|
|
4074
|
+
this.bridge?.receiveCaptureRequest(data);
|
|
4075
|
+
this.rtBridge?.receiveCaptureRequest(data, port);
|
|
4076
|
+
} else if (isEngineTransportRequestMessage(data)) {
|
|
4077
|
+
this.bridge?.receiveTransportRequest(data);
|
|
4078
|
+
this.rtBridge?.receiveTransportRequest(data, port);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
async initializeEmbind(options, port) {
|
|
4083
|
+
try {
|
|
4084
|
+
const initPromise2 = globalThis.SonareEmbindInitPromise;
|
|
4085
|
+
if (initPromise2) {
|
|
4086
|
+
await initPromise2;
|
|
4087
|
+
}
|
|
4088
|
+
if (!isInitialized()) {
|
|
4089
|
+
const moduleFactory = globalThis.SonareEmbindModuleFactory;
|
|
4090
|
+
if (!moduleFactory) {
|
|
4091
|
+
throw new Error("embind realtime engine module is not initialized.");
|
|
4092
|
+
}
|
|
4093
|
+
await init({
|
|
4094
|
+
locateFile: (path) => path,
|
|
4095
|
+
wasmBinary: options.wasmBinary,
|
|
4096
|
+
moduleFactory
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
this.bridge = new SonareRealtimeEngineWorkletProcessor(options, {
|
|
4100
|
+
postMessage: (message) => port?.postMessage?.(message),
|
|
4101
|
+
onMeter: (meter) => port?.postMessage?.(meter)
|
|
4102
|
+
});
|
|
4103
|
+
for (const message of options.initialSyncMessages ?? []) {
|
|
4104
|
+
this.bridge.receiveSync(message);
|
|
4105
|
+
}
|
|
4106
|
+
for (const command of options.initialCommands ?? []) {
|
|
4107
|
+
this.bridge.receiveCommand(command);
|
|
4108
|
+
}
|
|
4109
|
+
this.replayPendingMessages(port);
|
|
4110
|
+
port?.postMessage?.({ type: "ready", runtimeTarget: "embind" });
|
|
4111
|
+
} catch (error) {
|
|
4112
|
+
port?.postMessage?.({
|
|
4113
|
+
type: "error",
|
|
4114
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
2947
4118
|
async initializeSonareRt(options, port) {
|
|
2948
4119
|
try {
|
|
2949
4120
|
if (!options.rtModuleUrl) {
|
|
@@ -2969,6 +4140,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2969
4140
|
telemetrySharedBuffer: options.telemetrySharedBuffer,
|
|
2970
4141
|
telemetryRingCapacity: options.telemetryRingCapacity
|
|
2971
4142
|
});
|
|
4143
|
+
this.replayPendingMessages(port);
|
|
2972
4144
|
port?.postMessage?.({ type: "ready", runtimeTarget: "sonare-rt" });
|
|
2973
4145
|
} catch (error) {
|
|
2974
4146
|
port?.postMessage?.({
|