@libraz/libsonare 1.3.2 → 1.4.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/README.md +45 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +403 -70
- package/dist/index.js.map +1 -1
- package/dist/sonare-rt-module.js +2 -2
- package/dist/sonare-rt.js +2 -2
- package/dist/sonare-rt.wasm +0 -0
- package/dist/sonare.js +2 -2
- package/dist/sonare.wasm +0 -0
- package/dist/worklet.d.ts +907 -144
- package/dist/worklet.js +1803 -207
- package/dist/worklet.js.map +1 -1
- package/package.json +1 -1
- package/src/codes.ts +6 -1
- package/src/effects_mastering.ts +103 -1
- package/src/feature_music.ts +18 -4
- package/src/feature_spectral.ts +7 -1
- package/src/index.ts +27 -1
- package/src/mixer.ts +9 -0
- package/src/module_state.ts +82 -17
- package/src/opfs_clip_pages.ts +43 -9
- package/src/project.ts +74 -0
- package/src/public_types.ts +52 -0
- package/src/realtime_engine.ts +313 -109
- package/src/sonare.js.d.ts +140 -0
- package/src/stream_types.ts +7 -0
- package/src/validation.ts +7 -0
- package/src/web_midi.ts +15 -11
- package/src/worklet/audio_types.ts +2 -0
- package/src/worklet/guards.ts +146 -0
- package/src/worklet/messages.ts +461 -0
- package/src/worklet/protocol.ts +767 -0
- package/src/worklet.ts +1659 -888
package/dist/worklet.js
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
// src/codes.ts
|
|
2
|
+
function automationCurveCode(curve) {
|
|
3
|
+
switch (curve) {
|
|
4
|
+
case "linear":
|
|
5
|
+
return 0;
|
|
6
|
+
case "exponential":
|
|
7
|
+
return 1;
|
|
8
|
+
case "hold":
|
|
9
|
+
return 2;
|
|
10
|
+
case "s-curve":
|
|
11
|
+
return 3;
|
|
12
|
+
default:
|
|
13
|
+
throw new Error(`Invalid automation curve: ${curve}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function panLawCode(panLaw) {
|
|
17
|
+
if (typeof panLaw === "number") {
|
|
18
|
+
return panLaw;
|
|
19
|
+
}
|
|
20
|
+
switch (panLaw) {
|
|
21
|
+
case "const4.5dB":
|
|
22
|
+
return 1;
|
|
23
|
+
case "const6dB":
|
|
24
|
+
return 2;
|
|
25
|
+
case "linear0dB":
|
|
26
|
+
return 3;
|
|
27
|
+
default:
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function panModeCode(panMode) {
|
|
32
|
+
if (typeof panMode === "number") {
|
|
33
|
+
return panMode;
|
|
34
|
+
}
|
|
35
|
+
switch (panMode) {
|
|
36
|
+
case "stereoPan":
|
|
37
|
+
case "stereo-pan":
|
|
38
|
+
return 1;
|
|
39
|
+
case "dualPan":
|
|
40
|
+
case "dual-pan":
|
|
41
|
+
return 2;
|
|
42
|
+
default:
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function meterTapCode(tap) {
|
|
47
|
+
return tap === "preFader" || tap === 0 ? 0 : 1;
|
|
48
|
+
}
|
|
49
|
+
function sendTimingCode(timing) {
|
|
50
|
+
if (typeof timing === "number") {
|
|
51
|
+
return timing;
|
|
52
|
+
}
|
|
53
|
+
return timing === "preFader" ? 1 : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
// src/errors.ts
|
|
2
57
|
var SonareError = class extends Error {
|
|
3
58
|
constructor(code, codeName, message) {
|
|
@@ -39,6 +94,7 @@ function makeSonareError(raw, thrown) {
|
|
|
39
94
|
}
|
|
40
95
|
function wrapModuleErrors(raw) {
|
|
41
96
|
const cache = /* @__PURE__ */ new Map();
|
|
97
|
+
const objectCache = /* @__PURE__ */ new WeakMap();
|
|
42
98
|
const convert = (error) => {
|
|
43
99
|
const ptr = nativeExceptionPtr(error);
|
|
44
100
|
if (ptr !== null) {
|
|
@@ -46,6 +102,83 @@ function wrapModuleErrors(raw) {
|
|
|
46
102
|
}
|
|
47
103
|
throw error;
|
|
48
104
|
};
|
|
105
|
+
const wrapNativeObject = (value) => {
|
|
106
|
+
if (value === null || typeof value !== "object") {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer || value instanceof Promise) {
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
const objectValue = value;
|
|
113
|
+
const cached = objectCache.get(objectValue);
|
|
114
|
+
if (cached) {
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
118
|
+
const wrapped = new Proxy(objectValue, {
|
|
119
|
+
get(target, prop, receiver) {
|
|
120
|
+
const member = Reflect.get(target, prop, receiver);
|
|
121
|
+
if (typeof member !== "function") {
|
|
122
|
+
return member;
|
|
123
|
+
}
|
|
124
|
+
const cachedMethod = methodCache.get(prop);
|
|
125
|
+
if (cachedMethod) {
|
|
126
|
+
return cachedMethod;
|
|
127
|
+
}
|
|
128
|
+
const method = member;
|
|
129
|
+
const wrappedMethod = (...args) => {
|
|
130
|
+
try {
|
|
131
|
+
return wrapNativeObject(Reflect.apply(method, target, args));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return convert(error);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
methodCache.set(prop, wrappedMethod);
|
|
137
|
+
return wrappedMethod;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
objectCache.set(objectValue, wrapped);
|
|
141
|
+
return wrapped;
|
|
142
|
+
};
|
|
143
|
+
const wrapFunction = (value) => {
|
|
144
|
+
const fnCache = /* @__PURE__ */ new Map();
|
|
145
|
+
return new Proxy(value, {
|
|
146
|
+
get(target, prop, receiver) {
|
|
147
|
+
const member = Reflect.get(target, prop, receiver);
|
|
148
|
+
if (typeof member !== "function") {
|
|
149
|
+
return member;
|
|
150
|
+
}
|
|
151
|
+
const cachedMember = fnCache.get(prop);
|
|
152
|
+
if (cachedMember) {
|
|
153
|
+
return cachedMember;
|
|
154
|
+
}
|
|
155
|
+
const fn = member;
|
|
156
|
+
const wrappedMember = (...args) => {
|
|
157
|
+
try {
|
|
158
|
+
return wrapNativeObject(Reflect.apply(fn, target, args));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return convert(error);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
fnCache.set(prop, wrappedMember);
|
|
164
|
+
return wrappedMember;
|
|
165
|
+
},
|
|
166
|
+
apply(t, thisArg, args) {
|
|
167
|
+
try {
|
|
168
|
+
return wrapNativeObject(Reflect.apply(t, thisArg, args));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return convert(error);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
construct(t, args, newTarget) {
|
|
174
|
+
try {
|
|
175
|
+
return wrapNativeObject(Reflect.construct(t, args, newTarget));
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return convert(error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
};
|
|
49
182
|
return new Proxy(raw, {
|
|
50
183
|
get(target, prop, receiver) {
|
|
51
184
|
const value = Reflect.get(target, prop, receiver);
|
|
@@ -56,23 +189,7 @@ function wrapModuleErrors(raw) {
|
|
|
56
189
|
if (cached) {
|
|
57
190
|
return cached;
|
|
58
191
|
}
|
|
59
|
-
const
|
|
60
|
-
const wrapped = new Proxy(fn, {
|
|
61
|
-
apply(t, thisArg, args) {
|
|
62
|
-
try {
|
|
63
|
-
return Reflect.apply(t, thisArg, args);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
return convert(error);
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
construct(t, args, newTarget) {
|
|
69
|
-
try {
|
|
70
|
-
return Reflect.construct(t, args, newTarget);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
return convert(error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
});
|
|
192
|
+
const wrapped = wrapFunction(value);
|
|
76
193
|
cache.set(prop, wrapped);
|
|
77
194
|
return wrapped;
|
|
78
195
|
}
|
|
@@ -88,58 +205,6 @@ function getSonareModule() {
|
|
|
88
205
|
return wrappedModule;
|
|
89
206
|
}
|
|
90
207
|
|
|
91
|
-
// src/codes.ts
|
|
92
|
-
function automationCurveCode(curve) {
|
|
93
|
-
switch (curve) {
|
|
94
|
-
case "linear":
|
|
95
|
-
return 0;
|
|
96
|
-
case "exponential":
|
|
97
|
-
return 1;
|
|
98
|
-
case "hold":
|
|
99
|
-
return 2;
|
|
100
|
-
case "s-curve":
|
|
101
|
-
return 3;
|
|
102
|
-
default:
|
|
103
|
-
throw new Error(`Invalid automation curve: ${curve}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function panLawCode(panLaw) {
|
|
107
|
-
if (typeof panLaw === "number") {
|
|
108
|
-
return panLaw;
|
|
109
|
-
}
|
|
110
|
-
switch (panLaw) {
|
|
111
|
-
case "const4.5dB":
|
|
112
|
-
return 1;
|
|
113
|
-
case "const6dB":
|
|
114
|
-
return 2;
|
|
115
|
-
case "linear0dB":
|
|
116
|
-
return 3;
|
|
117
|
-
default:
|
|
118
|
-
return 0;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function panModeCode(panMode) {
|
|
122
|
-
if (typeof panMode === "number") {
|
|
123
|
-
return panMode;
|
|
124
|
-
}
|
|
125
|
-
switch (panMode) {
|
|
126
|
-
case "stereoPan":
|
|
127
|
-
case "stereo-pan":
|
|
128
|
-
return 1;
|
|
129
|
-
case "dualPan":
|
|
130
|
-
case "dual-pan":
|
|
131
|
-
return 2;
|
|
132
|
-
default:
|
|
133
|
-
return 0;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function meterTapCode(tap) {
|
|
137
|
-
return tap === "preFader" || tap === 0 ? 0 : 1;
|
|
138
|
-
}
|
|
139
|
-
function sendTimingCode(timing) {
|
|
140
|
-
return timing === "preFader" || timing === 0 ? 0 : 1;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
208
|
// src/mixer.ts
|
|
144
209
|
var Mixer = class _Mixer {
|
|
145
210
|
constructor(mixer) {
|
|
@@ -390,6 +455,13 @@ var Mixer = class _Mixer {
|
|
|
390
455
|
setDualPan(stripIndex, leftPan, rightPan) {
|
|
391
456
|
this.mixer.setDualPan(stripIndex, leftPan, rightPan);
|
|
392
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Set the strip's surround pan position, used when it feeds a >2-channel bus.
|
|
460
|
+
* Stored on the scene; inert until the surround DSP path applies it.
|
|
461
|
+
*/
|
|
462
|
+
setSurroundPan(stripIndex, pan) {
|
|
463
|
+
this.mixer.setSurroundPan(stripIndex, pan);
|
|
464
|
+
}
|
|
393
465
|
/**
|
|
394
466
|
* Add a send to a strip after construction.
|
|
395
467
|
*
|
|
@@ -732,9 +804,6 @@ function engineCapabilities() {
|
|
|
732
804
|
};
|
|
733
805
|
}
|
|
734
806
|
var RealtimeEngine = class {
|
|
735
|
-
nativeExt() {
|
|
736
|
-
return this.native;
|
|
737
|
-
}
|
|
738
807
|
constructor(sampleRate = 48e3, maxBlockSize = 128, commandCapacity = 1024, telemetryCapacity = 1024) {
|
|
739
808
|
const module2 = getSonareModule();
|
|
740
809
|
const capabilities = engineCapabilities();
|
|
@@ -761,8 +830,14 @@ var RealtimeEngine = class {
|
|
|
761
830
|
setParameterSmoothed(paramId, value, renderFrame = -1) {
|
|
762
831
|
this.native.setParameterSmoothed(paramId, value, renderFrame);
|
|
763
832
|
}
|
|
833
|
+
setSoloMute(laneIndex, solo, mute, renderFrame = -1) {
|
|
834
|
+
this.native.setSoloMute(laneIndex, solo, mute, renderFrame);
|
|
835
|
+
}
|
|
836
|
+
setMidiClips(clips) {
|
|
837
|
+
this.native.setMidiClips(clips);
|
|
838
|
+
}
|
|
764
839
|
setBuiltinInstrument(config = {}, destinationId = config.destinationId ?? 0) {
|
|
765
|
-
this.
|
|
840
|
+
this.native.setBuiltinInstrument(destinationId, config);
|
|
766
841
|
}
|
|
767
842
|
/**
|
|
768
843
|
* Bind the patch-driven NativeSynth to a realtime MIDI destination. `patch`
|
|
@@ -774,7 +849,7 @@ var RealtimeEngine = class {
|
|
|
774
849
|
* binding convenience, not part of the NativeSynth patch itself.
|
|
775
850
|
*/
|
|
776
851
|
setSynthInstrument(patch = {}, destinationId = (typeof patch === "object" ? patch.destinationId : void 0) ?? 0) {
|
|
777
|
-
this.
|
|
852
|
+
this.native.setSynthInstrument(destinationId, patch);
|
|
778
853
|
}
|
|
779
854
|
/**
|
|
780
855
|
* Load (parse) SoundFont 2 bytes into the engine so SF2 instruments can be
|
|
@@ -783,7 +858,7 @@ var RealtimeEngine = class {
|
|
|
783
858
|
* not referenced afterwards. Replaces any previously loaded SoundFont.
|
|
784
859
|
*/
|
|
785
860
|
loadSoundFont(data) {
|
|
786
|
-
this.
|
|
861
|
+
this.native.loadSoundFont(data);
|
|
787
862
|
}
|
|
788
863
|
/**
|
|
789
864
|
* Bind a GS-compatible SoundFont player to a realtime MIDI destination, fed
|
|
@@ -795,13 +870,13 @@ var RealtimeEngine = class {
|
|
|
795
870
|
* synthesizer GM fallback bank (the data-free floor).
|
|
796
871
|
*/
|
|
797
872
|
setSf2Instrument(config = {}, destinationId = config.destinationId ?? 0) {
|
|
798
|
-
this.
|
|
873
|
+
this.native.setSf2Instrument(destinationId, config);
|
|
799
874
|
}
|
|
800
875
|
clearMidiInstrument(destinationId = 0) {
|
|
801
|
-
this.
|
|
876
|
+
this.native.clearMidiInstrument(destinationId);
|
|
802
877
|
}
|
|
803
878
|
midiInstrumentCount() {
|
|
804
|
-
return this.
|
|
879
|
+
return this.native.midiInstrumentCount();
|
|
805
880
|
}
|
|
806
881
|
/**
|
|
807
882
|
* Bind a live MIDI CC to an engine automation parameter. The MIDI event still
|
|
@@ -809,7 +884,7 @@ var RealtimeEngine = class {
|
|
|
809
884
|
* mapped into [minValue, maxValue] for `paramId`.
|
|
810
885
|
*/
|
|
811
886
|
bindMidiCc(channel, controller, paramId, options = {}) {
|
|
812
|
-
this.
|
|
887
|
+
this.native.bindMidiCc(
|
|
813
888
|
channel,
|
|
814
889
|
controller,
|
|
815
890
|
paramId,
|
|
@@ -818,42 +893,42 @@ var RealtimeEngine = class {
|
|
|
818
893
|
);
|
|
819
894
|
}
|
|
820
895
|
clearMidiCcBindings() {
|
|
821
|
-
this.
|
|
896
|
+
this.native.clearMidiCcBindings();
|
|
822
897
|
}
|
|
823
898
|
midiCcBindingCount() {
|
|
824
|
-
return this.
|
|
899
|
+
return this.native.midiCcBindingCount();
|
|
825
900
|
}
|
|
826
901
|
/** Install/replace a live non-destructive MIDI-FX insert for one destination. */
|
|
827
902
|
setMidiFx(destinationId, configJson) {
|
|
828
|
-
this.
|
|
903
|
+
this.native.setMidiFx(destinationId, configJson);
|
|
829
904
|
}
|
|
830
905
|
clearMidiFx(destinationId = 0) {
|
|
831
|
-
this.
|
|
906
|
+
this.native.clearMidiFx(destinationId);
|
|
832
907
|
}
|
|
833
908
|
/** Enable the engine-owned live MIDI input source for a destination. */
|
|
834
909
|
setMidiInputSource(destinationId = 0) {
|
|
835
|
-
this.
|
|
910
|
+
this.native.setMidiInputSource(destinationId);
|
|
836
911
|
}
|
|
837
912
|
clearMidiInputSource() {
|
|
838
|
-
this.
|
|
913
|
+
this.native.clearMidiInputSource();
|
|
839
914
|
}
|
|
840
915
|
midiInputPendingCount() {
|
|
841
|
-
return this.
|
|
916
|
+
return this.native.midiInputPendingCount();
|
|
842
917
|
}
|
|
843
918
|
pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples = 0) {
|
|
844
|
-
this.
|
|
919
|
+
this.native.pushMidiInputNoteOn(group, channel, note, velocity, portTimeSamples);
|
|
845
920
|
}
|
|
846
921
|
pushMidiInputNoteOff(group, channel, note, velocity = 0, portTimeSamples = 0) {
|
|
847
|
-
this.
|
|
922
|
+
this.native.pushMidiInputNoteOff(group, channel, note, velocity, portTimeSamples);
|
|
848
923
|
}
|
|
849
924
|
pushMidiInputCc(group, channel, controller, value, portTimeSamples = 0) {
|
|
850
|
-
this.
|
|
925
|
+
this.native.pushMidiInputCc(group, channel, controller, value, portTimeSamples);
|
|
851
926
|
}
|
|
852
927
|
pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame = -1) {
|
|
853
|
-
this.
|
|
928
|
+
this.native.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
854
929
|
}
|
|
855
930
|
pushMidiNoteOff(destinationId, group, channel, note, velocity = 0, renderFrame = -1) {
|
|
856
|
-
this.
|
|
931
|
+
this.native.pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
|
|
857
932
|
}
|
|
858
933
|
/**
|
|
859
934
|
* Queue an immediate (live) MIDI control change to a MIDI destination
|
|
@@ -862,21 +937,21 @@ var RealtimeEngine = class {
|
|
|
862
937
|
* immediate. Mirrors the Node/Python/C-ABI `pushMidiCc`.
|
|
863
938
|
*/
|
|
864
939
|
pushMidiCc(destinationId, group, channel, controller, value, renderFrame = -1) {
|
|
865
|
-
this.
|
|
940
|
+
this.native.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
866
941
|
}
|
|
867
942
|
/**
|
|
868
943
|
* Queue a MIDI panic (all-notes-off) releasing every sounding note at
|
|
869
944
|
* `renderFrame` (-1 = immediate). Mirrors the C-ABI `pushMidiPanic`.
|
|
870
945
|
*/
|
|
871
946
|
pushMidiPanic(renderFrame = -1) {
|
|
872
|
-
this.
|
|
947
|
+
this.native.pushMidiPanic(renderFrame);
|
|
873
948
|
}
|
|
874
949
|
/**
|
|
875
950
|
* Remove all registered parameters (and their automation lanes). Control-thread
|
|
876
951
|
* only; not realtime-safe. Mirrors the C-ABI `clearParameters`.
|
|
877
952
|
*/
|
|
878
953
|
clearParameters() {
|
|
879
|
-
this.
|
|
954
|
+
this.native.clearParameters();
|
|
880
955
|
}
|
|
881
956
|
/** Read back the current transport state snapshot. */
|
|
882
957
|
getTransportState() {
|
|
@@ -891,15 +966,33 @@ var RealtimeEngine = class {
|
|
|
891
966
|
seekSample(timelineSample, renderFrame = -1) {
|
|
892
967
|
this.native.seekSample(timelineSample, renderFrame);
|
|
893
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Snaps every in-flight parameter ramp (engine-level smoothed params, mixer
|
|
971
|
+
* lane fader/pan/gate, bus gains) to its target value. Offline renders call
|
|
972
|
+
* this after a priming process() block so the first audible block renders at
|
|
973
|
+
* settled values instead of ramping in from defaults.
|
|
974
|
+
*/
|
|
975
|
+
settleParameters() {
|
|
976
|
+
this.native.settleParameters();
|
|
977
|
+
}
|
|
894
978
|
seekPpq(ppq, renderFrame = -1) {
|
|
895
979
|
this.native.seekPpq(ppq, renderFrame);
|
|
896
980
|
}
|
|
897
981
|
setTempo(bpm) {
|
|
898
982
|
this.native.setTempo(bpm);
|
|
899
983
|
}
|
|
984
|
+
setTempoSegments(segments) {
|
|
985
|
+
this.native.setTempoSegments([...segments]);
|
|
986
|
+
}
|
|
900
987
|
setTimeSignature(numerator, denominator) {
|
|
901
988
|
this.native.setTimeSignature(numerator, denominator);
|
|
902
989
|
}
|
|
990
|
+
setTimeSignatureSegments(segments) {
|
|
991
|
+
this.native.setTimeSignatureSegments([...segments]);
|
|
992
|
+
}
|
|
993
|
+
sampleAtPpq(ppq) {
|
|
994
|
+
return Number(this.native.sampleAtPpq(ppq));
|
|
995
|
+
}
|
|
903
996
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
904
997
|
this.native.setLoop(startPpq, endPpq, enabled);
|
|
905
998
|
}
|
|
@@ -968,21 +1061,140 @@ var RealtimeEngine = class {
|
|
|
968
1061
|
clipCount() {
|
|
969
1062
|
return this.native.clipCount();
|
|
970
1063
|
}
|
|
1064
|
+
setTrackLanes(lanes) {
|
|
1065
|
+
this.native.setTrackLanes(
|
|
1066
|
+
lanes.map((lane) => {
|
|
1067
|
+
if (typeof lane === "number") {
|
|
1068
|
+
return { trackId: lane };
|
|
1069
|
+
}
|
|
1070
|
+
if (!lane.sends) {
|
|
1071
|
+
return lane;
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
...lane,
|
|
1075
|
+
sends: lane.sends.map((send) => ({
|
|
1076
|
+
...send,
|
|
1077
|
+
// Post-fader (0) is the default for an omitted sendTiming.
|
|
1078
|
+
sendTiming: send.sendTiming === void 0 ? 0 : sendTimingCode(send.sendTiming)
|
|
1079
|
+
}))
|
|
1080
|
+
};
|
|
1081
|
+
})
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Keys one insert of a lane strip from another lane's post-strip audio
|
|
1086
|
+
* (ducking/sidechainRouter inserts). sourceTrackId 0 removes the binding.
|
|
1087
|
+
*/
|
|
1088
|
+
setLaneSidechain(trackId, insertIndex, sourceTrackId) {
|
|
1089
|
+
this.native.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
1090
|
+
}
|
|
1091
|
+
setTrackBuses(buses) {
|
|
1092
|
+
this.native.setTrackBuses(buses);
|
|
1093
|
+
}
|
|
1094
|
+
setBusStripJson(busId, sceneJson) {
|
|
1095
|
+
try {
|
|
1096
|
+
JSON.parse(sceneJson);
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
const message = error instanceof Error ? error.message : "invalid bus strip JSON";
|
|
1099
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1100
|
+
}
|
|
1101
|
+
this.native.setBusStripJson(busId, sceneJson);
|
|
1102
|
+
}
|
|
1103
|
+
setTrackStripJson(trackId, sceneJson) {
|
|
1104
|
+
try {
|
|
1105
|
+
JSON.parse(sceneJson);
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
const message = error instanceof Error ? error.message : "invalid track strip JSON";
|
|
1108
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1109
|
+
}
|
|
1110
|
+
this.native.setTrackStripJson(trackId, sceneJson);
|
|
1111
|
+
}
|
|
1112
|
+
setTrackStripEqBand(trackId, bandIndex, band) {
|
|
1113
|
+
this.native.setTrackStripEqBandJson(
|
|
1114
|
+
trackId,
|
|
1115
|
+
bandIndex,
|
|
1116
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
setTrackStripEqBandJson(trackId, bandIndex, bandJson) {
|
|
1120
|
+
this.native.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
1121
|
+
}
|
|
1122
|
+
setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass = false) {
|
|
1123
|
+
this.native.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
1124
|
+
}
|
|
1125
|
+
setMasterStripJson(sceneJson) {
|
|
1126
|
+
try {
|
|
1127
|
+
JSON.parse(sceneJson);
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
const message = error instanceof Error ? error.message : "invalid master strip JSON";
|
|
1130
|
+
throw new SonareError(2 /* InvalidFormat */, "InvalidFormat", message);
|
|
1131
|
+
}
|
|
1132
|
+
this.native.setMasterStripJson(sceneJson);
|
|
1133
|
+
}
|
|
1134
|
+
setMasterStripEqBand(bandIndex, band) {
|
|
1135
|
+
this.native.setMasterStripEqBandJson(
|
|
1136
|
+
bandIndex,
|
|
1137
|
+
typeof band === "string" ? band : JSON.stringify(band)
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
setMasterStripEqBandJson(bandIndex, bandJson) {
|
|
1141
|
+
this.native.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
1142
|
+
}
|
|
1143
|
+
setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
|
|
1144
|
+
this.native.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Changes one track-strip insert parameter in realtime, addressed by the
|
|
1148
|
+
* processor's JSON-key parameter name (see {@link masteringInsertParamInfo}).
|
|
1149
|
+
* Applied at the next block head via the engine command queue; safe during
|
|
1150
|
+
* playback. Throws if the track, insert, or name is unknown, the param is not
|
|
1151
|
+
* realtime-safe, or the command queue is full.
|
|
1152
|
+
*/
|
|
1153
|
+
setTrackStripInsertParamByName(trackId, insertIndex, paramName, value) {
|
|
1154
|
+
this.native.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
1155
|
+
}
|
|
1156
|
+
/** Master-strip counterpart of {@link setTrackStripInsertParamByName}. */
|
|
1157
|
+
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
1158
|
+
this.native.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
1159
|
+
}
|
|
1160
|
+
/** Sets a track lane strip's pan position in realtime (glitch-free). */
|
|
1161
|
+
setTrackStripPan(trackId, pan) {
|
|
1162
|
+
this.native.setTrackStripPan(trackId, pan);
|
|
1163
|
+
}
|
|
1164
|
+
/** Sets a track lane strip's pan law in realtime. */
|
|
1165
|
+
setTrackStripPanLaw(trackId, panLaw) {
|
|
1166
|
+
this.native.setTrackStripPanLaw(trackId, panLawCode(panLaw));
|
|
1167
|
+
}
|
|
1168
|
+
/** Sets a track lane strip's pan mode in realtime. */
|
|
1169
|
+
setTrackStripPanMode(trackId, panMode) {
|
|
1170
|
+
this.native.setTrackStripPanMode(trackId, panModeCode(panMode));
|
|
1171
|
+
}
|
|
1172
|
+
/** Sets a track lane strip's dual-pan left/right positions in realtime. */
|
|
1173
|
+
setTrackStripDualPan(trackId, leftPan, rightPan) {
|
|
1174
|
+
this.native.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Sets a track lane strip's inter-channel alignment delay (whole samples).
|
|
1178
|
+
* Adjusts strip latency, so PDC and reported graph latency are refreshed.
|
|
1179
|
+
*/
|
|
1180
|
+
setTrackStripChannelDelaySamples(trackId, delaySamples) {
|
|
1181
|
+
this.native.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
1182
|
+
}
|
|
971
1183
|
createClipPageProvider(numChannels, numSamples, pageFrames) {
|
|
972
|
-
const id = this.
|
|
1184
|
+
const id = this.native.createClipPageProvider(numChannels, numSamples, pageFrames);
|
|
973
1185
|
return new ClipPageProvider(this, id);
|
|
974
1186
|
}
|
|
975
1187
|
supplyClipPage(providerId, pageIndex, channels) {
|
|
976
|
-
this.
|
|
1188
|
+
this.native.supplyClipPage(providerId, pageIndex, channels);
|
|
977
1189
|
}
|
|
978
1190
|
clearClipPage(providerId, pageIndex) {
|
|
979
|
-
this.
|
|
1191
|
+
this.native.clearClipPage(providerId, pageIndex);
|
|
980
1192
|
}
|
|
981
1193
|
destroyClipPageProvider(providerId) {
|
|
982
|
-
this.
|
|
1194
|
+
this.native.destroyClipPageProvider(providerId);
|
|
983
1195
|
}
|
|
984
1196
|
popClipPageRequest() {
|
|
985
|
-
return this.
|
|
1197
|
+
return this.native.popClipPageRequest();
|
|
986
1198
|
}
|
|
987
1199
|
setCaptureBuffer(numChannels, capacityFrames) {
|
|
988
1200
|
this.native.setCaptureBuffer(numChannels, capacityFrames);
|
|
@@ -1057,6 +1269,30 @@ var RealtimeEngine = class {
|
|
|
1057
1269
|
drainMeterTelemetry(maxRecords = 1024) {
|
|
1058
1270
|
return this.native.drainMeterTelemetry(maxRecords);
|
|
1059
1271
|
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Drains pending meter telemetry as per-plane (wide) records for a surround
|
|
1274
|
+
* target. Use this for a surround mix target; {@link drainMeterTelemetry}
|
|
1275
|
+
* stays the stereo fast path. The two share one queue — call only one per
|
|
1276
|
+
* target. The live AudioWorklet path owns the queue via the stereo drain, so
|
|
1277
|
+
* this wide drain is for an offline (non-worklet) engine instance; per-plane
|
|
1278
|
+
* surround meters are not delivered over the live worklet meter ring.
|
|
1279
|
+
*/
|
|
1280
|
+
drainMeterTelemetryWide(maxRecords = 1024) {
|
|
1281
|
+
return this.native.drainMeterTelemetryWide(maxRecords);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Enables per-target spectrum + vectorscope capture. @param intervalFrames is
|
|
1285
|
+
* the minimum render-frame gap between snapshots (0 disables). @param bandCount
|
|
1286
|
+
* is the FFT band resolution (1..64); changing it re-prepares the tap. Returns
|
|
1287
|
+
* the band count actually applied.
|
|
1288
|
+
*/
|
|
1289
|
+
configureScopeTelemetry(intervalFrames, bandCount) {
|
|
1290
|
+
return this.native.configureScopeTelemetry(intervalFrames, bandCount);
|
|
1291
|
+
}
|
|
1292
|
+
/** Drains pending spectrum + vectorscope snapshots (per mix target). */
|
|
1293
|
+
drainScopeTelemetry(maxRecords = 1024) {
|
|
1294
|
+
return this.native.drainScopeTelemetry(maxRecords);
|
|
1295
|
+
}
|
|
1060
1296
|
destroy() {
|
|
1061
1297
|
this.native.delete();
|
|
1062
1298
|
}
|
|
@@ -1100,7 +1336,7 @@ async function init(options) {
|
|
|
1100
1336
|
}
|
|
1101
1337
|
initPromise = (async () => {
|
|
1102
1338
|
try {
|
|
1103
|
-
const createModule = (await import("./sonare.js")).default;
|
|
1339
|
+
const createModule = options?.moduleFactory ?? (await import("./sonare.js")).default;
|
|
1104
1340
|
module = await createModule(options);
|
|
1105
1341
|
setSonareModule(module);
|
|
1106
1342
|
} catch (error) {
|
|
@@ -1114,10 +1350,24 @@ function isInitialized() {
|
|
|
1114
1350
|
return module !== null;
|
|
1115
1351
|
}
|
|
1116
1352
|
|
|
1117
|
-
// src/worklet.ts
|
|
1353
|
+
// src/worklet/protocol.ts
|
|
1354
|
+
var ENGINE_MIXER_TARGET_BASE = 1297612800;
|
|
1355
|
+
var ENGINE_MIXER_PARAM_FADER_DB = 1;
|
|
1356
|
+
var ENGINE_MIXER_PARAM_PAN = 2;
|
|
1357
|
+
function engineMixerLaneTarget(laneIndex, paramKind) {
|
|
1358
|
+
return ENGINE_MIXER_TARGET_BASE | (laneIndex & 255) << 8 | paramKind & 255;
|
|
1359
|
+
}
|
|
1360
|
+
function engineMixerBusTarget(busIndex, paramKind) {
|
|
1361
|
+
return ENGINE_MIXER_TARGET_BASE | (254 - busIndex & 255) << 8 | paramKind & 255;
|
|
1362
|
+
}
|
|
1363
|
+
function engineMixerMasterTarget(paramKind) {
|
|
1364
|
+
return ENGINE_MIXER_TARGET_BASE | 255 << 8 | paramKind & 255;
|
|
1365
|
+
}
|
|
1118
1366
|
var SONARE_METER_RING_HEADER_INTS = 4;
|
|
1119
|
-
var SONARE_METER_RING_RECORD_FLOATS =
|
|
1367
|
+
var SONARE_METER_RING_RECORD_FLOATS = 14;
|
|
1120
1368
|
var SONARE_SPECTRUM_RING_HEADER_INTS = 5;
|
|
1369
|
+
var SONARE_SCOPE_RING_HEADER_INTS = 6;
|
|
1370
|
+
var SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS = 5;
|
|
1121
1371
|
var SONARE_FRAME_LANE_BASE = 16777216;
|
|
1122
1372
|
function encodeFrameLo(frame) {
|
|
1123
1373
|
const f = Math.max(0, Math.floor(frame));
|
|
@@ -1176,51 +1426,12 @@ var SonareEngineTelemetryError = /* @__PURE__ */ ((SonareEngineTelemetryError2)
|
|
|
1176
1426
|
SonareEngineTelemetryError2[SonareEngineTelemetryError2["SmoothedParameterCapacity"] = 13] = "SmoothedParameterCapacity";
|
|
1177
1427
|
return SonareEngineTelemetryError2;
|
|
1178
1428
|
})(SonareEngineTelemetryError || {});
|
|
1179
|
-
var DEFAULT_METRONOME_CONFIG = {
|
|
1180
|
-
beatGain: 0.35,
|
|
1181
|
-
accentGain: 0.7,
|
|
1182
|
-
clickSamples: 96
|
|
1183
|
-
};
|
|
1184
|
-
function resolveMetronomeConfig(config) {
|
|
1185
|
-
return {
|
|
1186
|
-
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
1187
|
-
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
1188
|
-
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
1429
|
function toDb(value) {
|
|
1192
1430
|
return value > 0 ? 20 * Math.log10(value) : Number.NEGATIVE_INFINITY;
|
|
1193
1431
|
}
|
|
1194
1432
|
function isRecord(value) {
|
|
1195
1433
|
return typeof value === "object" && value !== null;
|
|
1196
1434
|
}
|
|
1197
|
-
function isWorkletMessage(value) {
|
|
1198
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1199
|
-
return false;
|
|
1200
|
-
}
|
|
1201
|
-
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
1202
|
-
}
|
|
1203
|
-
function isEngineCommandRecord(value) {
|
|
1204
|
-
return isRecord(value) && typeof value.type === "number";
|
|
1205
|
-
}
|
|
1206
|
-
function isEngineSyncMessage(value) {
|
|
1207
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1208
|
-
return false;
|
|
1209
|
-
}
|
|
1210
|
-
return value.type === "syncClips" || value.type === "syncMarkers" || value.type === "syncMetronome" || value.type === "syncAutomation";
|
|
1211
|
-
}
|
|
1212
|
-
function isRealtimeVoiceChangerMessage(value) {
|
|
1213
|
-
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1214
|
-
return false;
|
|
1215
|
-
}
|
|
1216
|
-
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
1217
|
-
}
|
|
1218
|
-
function isEngineTelemetryRecord(value) {
|
|
1219
|
-
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";
|
|
1220
|
-
}
|
|
1221
|
-
function isMeterSnapshot(value) {
|
|
1222
|
-
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";
|
|
1223
|
-
}
|
|
1224
1435
|
function sonareMeterRingBufferByteLength(capacity) {
|
|
1225
1436
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1226
1437
|
return SONARE_METER_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_METER_RING_RECORD_FLOATS * Float32Array.BYTES_PER_ELEMENT;
|
|
@@ -1237,19 +1448,27 @@ function createSonareMeterRingBuffer(capacity = 128) {
|
|
|
1237
1448
|
}
|
|
1238
1449
|
function readSonareMeterRingBuffer(ring, readIndex = 0) {
|
|
1239
1450
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1451
|
+
const recordFloats = Atomics.load(ring.header, 2) || SONARE_METER_RING_RECORD_FLOATS;
|
|
1240
1452
|
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
1241
1453
|
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
1242
1454
|
const meters = [];
|
|
1243
1455
|
for (let index = firstReadable; index < writeIndex; index++) {
|
|
1244
|
-
const offset = index % ring.capacity *
|
|
1456
|
+
const offset = index % ring.capacity * recordFloats;
|
|
1245
1457
|
meters.push({
|
|
1246
1458
|
type: "meter",
|
|
1247
|
-
frame: decodeFrame(ring.records[offset], ring.records[offset +
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1459
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 1]),
|
|
1460
|
+
targetId: ring.records[offset + 2],
|
|
1461
|
+
peakDbL: ring.records[offset + 3],
|
|
1462
|
+
peakDbR: ring.records[offset + 4],
|
|
1463
|
+
rmsDbL: ring.records[offset + 5],
|
|
1464
|
+
rmsDbR: ring.records[offset + 6],
|
|
1465
|
+
correlation: ring.records[offset + 7],
|
|
1466
|
+
truePeakDbL: ring.records[offset + 8],
|
|
1467
|
+
truePeakDbR: ring.records[offset + 9],
|
|
1468
|
+
momentaryLufs: ring.records[offset + 10],
|
|
1469
|
+
shortTermLufs: ring.records[offset + 11],
|
|
1470
|
+
integratedLufs: ring.records[offset + 12],
|
|
1471
|
+
gainReductionDb: ring.records[offset + 13]
|
|
1253
1472
|
});
|
|
1254
1473
|
}
|
|
1255
1474
|
return { nextReadIndex: writeIndex, meters };
|
|
@@ -1298,29 +1517,125 @@ function readSonareSpectrumRingBuffer(ring, readIndex = 0) {
|
|
|
1298
1517
|
}
|
|
1299
1518
|
return { nextReadIndex: writeIndex, spectra };
|
|
1300
1519
|
}
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_COMMAND_RECORD_BYTES;
|
|
1520
|
+
function sonareScopeRingRecordFloats(bands, maxPoints) {
|
|
1521
|
+
return SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands + 2 * maxPoints;
|
|
1304
1522
|
}
|
|
1305
|
-
function
|
|
1523
|
+
function sonareScopeRingBufferByteLength(capacity, bands = 48, maxPoints = 32) {
|
|
1306
1524
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1307
|
-
|
|
1525
|
+
const clampedBands = Math.max(1, Math.floor(bands));
|
|
1526
|
+
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
1527
|
+
return SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * sonareScopeRingRecordFloats(clampedBands, clampedPoints) * Float32Array.BYTES_PER_ELEMENT;
|
|
1308
1528
|
}
|
|
1309
|
-
function
|
|
1529
|
+
function createSonareScopeRingBuffer(capacity = 64, bands = 48, maxPoints = 32) {
|
|
1310
1530
|
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1531
|
+
const clampedBands = Math.max(1, Math.floor(bands));
|
|
1532
|
+
const clampedPoints = Math.max(0, Math.floor(maxPoints));
|
|
1311
1533
|
const sharedBuffer = new SharedArrayBuffer(
|
|
1312
|
-
|
|
1534
|
+
sonareScopeRingBufferByteLength(clampedCapacity, clampedBands, clampedPoints)
|
|
1313
1535
|
);
|
|
1314
|
-
const ring =
|
|
1536
|
+
const ring = scopeRingFromSharedBuffer(
|
|
1315
1537
|
sharedBuffer,
|
|
1316
|
-
|
|
1317
|
-
|
|
1538
|
+
clampedCapacity,
|
|
1539
|
+
clampedBands,
|
|
1540
|
+
clampedPoints
|
|
1318
1541
|
);
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1542
|
+
Atomics.store(ring.header, 0, 0);
|
|
1543
|
+
Atomics.store(ring.header, 1, clampedCapacity);
|
|
1544
|
+
Atomics.store(ring.header, 2, ring.recordFloats);
|
|
1545
|
+
Atomics.store(ring.header, 3, clampedBands);
|
|
1546
|
+
Atomics.store(ring.header, 4, clampedPoints);
|
|
1547
|
+
Atomics.store(ring.header, 5, 0);
|
|
1548
|
+
return {
|
|
1549
|
+
sharedBuffer,
|
|
1550
|
+
header: ring.header,
|
|
1551
|
+
records: ring.records,
|
|
1552
|
+
capacity: ring.capacity,
|
|
1553
|
+
bands: ring.bands,
|
|
1554
|
+
maxPoints: ring.maxPoints
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
function readSonareScopeRingBuffer(ring, readIndex = 0) {
|
|
1558
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
1559
|
+
const bands = Atomics.load(ring.header, 3) || ring.bands;
|
|
1560
|
+
const maxPoints = Atomics.load(ring.header, 4);
|
|
1561
|
+
const recordFloats = Atomics.load(ring.header, 2) || sonareScopeRingRecordFloats(bands, maxPoints);
|
|
1562
|
+
const nextReadIndex = Math.max(0, Math.min(readIndex, writeIndex));
|
|
1563
|
+
const firstReadable = Math.max(nextReadIndex, writeIndex - ring.capacity);
|
|
1564
|
+
const scopes = [];
|
|
1565
|
+
for (let index = firstReadable; index < writeIndex; index++) {
|
|
1566
|
+
const offset = index % ring.capacity * recordFloats;
|
|
1567
|
+
const bandCount = Math.min(bands, Math.max(0, ring.records[offset + 3]));
|
|
1568
|
+
const pointCount = Math.min(maxPoints, Math.max(0, ring.records[offset + 4]));
|
|
1569
|
+
const bandsView = new Float32Array(bandCount);
|
|
1570
|
+
bandsView.set(
|
|
1571
|
+
ring.records.subarray(
|
|
1572
|
+
offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS,
|
|
1573
|
+
offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bandCount
|
|
1574
|
+
)
|
|
1575
|
+
);
|
|
1576
|
+
const pointsBase = offset + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS + bands;
|
|
1577
|
+
const pointsView = new Float32Array(pointCount * 2);
|
|
1578
|
+
pointsView.set(ring.records.subarray(pointsBase, pointsBase + pointCount * 2));
|
|
1579
|
+
scopes.push({
|
|
1580
|
+
type: "scope",
|
|
1581
|
+
frame: decodeFrame(ring.records[offset], ring.records[offset + 1]),
|
|
1582
|
+
targetId: ring.records[offset + 2],
|
|
1583
|
+
bands: bandsView,
|
|
1584
|
+
points: pointsView
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return { nextReadIndex: writeIndex, scopes };
|
|
1588
|
+
}
|
|
1589
|
+
function scopeRingFromSharedBuffer(sharedBuffer, fallbackCapacity, fallbackBands, fallbackMaxPoints) {
|
|
1590
|
+
const headerBytes = SONARE_SCOPE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT;
|
|
1591
|
+
const header = new Int32Array(sharedBuffer, 0, SONARE_SCOPE_RING_HEADER_INTS);
|
|
1592
|
+
const existingCapacity = Atomics.load(header, 1);
|
|
1593
|
+
const existingBands = Atomics.load(header, 3);
|
|
1594
|
+
const existingMaxPoints = Atomics.load(header, 4);
|
|
1595
|
+
const capacity = Math.max(1, Math.floor(existingCapacity || fallbackCapacity || 1));
|
|
1596
|
+
const bands = Math.max(1, Math.floor(existingBands || fallbackBands || 48));
|
|
1597
|
+
const maxPoints = Math.max(0, Math.floor(existingMaxPoints || (fallbackMaxPoints ?? 32)));
|
|
1598
|
+
const recordFloats = sonareScopeRingRecordFloats(bands, maxPoints);
|
|
1599
|
+
const minBytes = sonareScopeRingBufferByteLength(capacity, bands, maxPoints);
|
|
1600
|
+
if (sharedBuffer.byteLength < minBytes) {
|
|
1601
|
+
throw new Error("scopeSharedBuffer is too small for the requested ring capacity.");
|
|
1602
|
+
}
|
|
1603
|
+
Atomics.store(header, 1, capacity);
|
|
1604
|
+
Atomics.store(header, 2, recordFloats);
|
|
1605
|
+
Atomics.store(header, 3, bands);
|
|
1606
|
+
Atomics.store(header, 4, maxPoints);
|
|
1607
|
+
return {
|
|
1608
|
+
header,
|
|
1609
|
+
records: new Float32Array(sharedBuffer, headerBytes, capacity * recordFloats),
|
|
1610
|
+
capacity,
|
|
1611
|
+
bands,
|
|
1612
|
+
maxPoints,
|
|
1613
|
+
recordFloats
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
function sonareEngineCommandRingBufferByteLength(capacity) {
|
|
1617
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1618
|
+
return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_COMMAND_RECORD_BYTES;
|
|
1619
|
+
}
|
|
1620
|
+
function sonareEngineTelemetryRingBufferByteLength(capacity) {
|
|
1621
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1622
|
+
return SONARE_ENGINE_RING_HEADER_INTS * Int32Array.BYTES_PER_ELEMENT + clampedCapacity * SONARE_ENGINE_TELEMETRY_RECORD_BYTES;
|
|
1623
|
+
}
|
|
1624
|
+
function createSonareEngineCommandRingBuffer(capacity = 128) {
|
|
1625
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1626
|
+
const sharedBuffer = new SharedArrayBuffer(
|
|
1627
|
+
sonareEngineCommandRingBufferByteLength(clampedCapacity)
|
|
1628
|
+
);
|
|
1629
|
+
const ring = engineRingFromSharedBuffer(
|
|
1630
|
+
sharedBuffer,
|
|
1631
|
+
SONARE_ENGINE_COMMAND_RECORD_BYTES,
|
|
1632
|
+
clampedCapacity
|
|
1633
|
+
);
|
|
1634
|
+
return { sharedBuffer, header: ring.header, view: ring.view, capacity: ring.capacity };
|
|
1635
|
+
}
|
|
1636
|
+
function createSonareEngineTelemetryRingBuffer(capacity = 128) {
|
|
1637
|
+
const clampedCapacity = Math.max(1, Math.floor(capacity));
|
|
1638
|
+
const sharedBuffer = new SharedArrayBuffer(
|
|
1324
1639
|
sonareEngineTelemetryRingBufferByteLength(clampedCapacity)
|
|
1325
1640
|
);
|
|
1326
1641
|
const ring = engineRingFromSharedBuffer(
|
|
@@ -1509,17 +1824,81 @@ function telemetryFromEngine(telemetry) {
|
|
|
1509
1824
|
function meterFromEngine(meter) {
|
|
1510
1825
|
return {
|
|
1511
1826
|
type: "meter",
|
|
1827
|
+
targetId: meter.targetId,
|
|
1512
1828
|
frame: meter.renderFrame,
|
|
1513
1829
|
peakDbL: meter.peakDbL,
|
|
1514
1830
|
peakDbR: meter.peakDbR,
|
|
1515
1831
|
rmsDbL: meter.rmsDbL,
|
|
1516
1832
|
rmsDbR: meter.rmsDbR,
|
|
1517
|
-
correlation: meter.correlation
|
|
1833
|
+
correlation: meter.correlation,
|
|
1834
|
+
truePeakDbL: meter.truePeakDbL,
|
|
1835
|
+
truePeakDbR: meter.truePeakDbR,
|
|
1836
|
+
momentaryLufs: meter.momentaryLufs,
|
|
1837
|
+
shortTermLufs: meter.shortTermLufs,
|
|
1838
|
+
integratedLufs: meter.integratedLufs,
|
|
1839
|
+
gainReductionDb: meter.gainReductionDb
|
|
1518
1840
|
};
|
|
1519
1841
|
}
|
|
1520
1842
|
function magnitudeToDb(value) {
|
|
1521
1843
|
return value > 1e-12 ? 20 * Math.log10(value) : -120;
|
|
1522
1844
|
}
|
|
1845
|
+
|
|
1846
|
+
// src/worklet/guards.ts
|
|
1847
|
+
function isWorkletMessage(value) {
|
|
1848
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
return value.type === "scheduleInsertAutomation" || value.type === "setMeterInterval" || value.type === "destroy";
|
|
1852
|
+
}
|
|
1853
|
+
function isEngineCommandRecord(value) {
|
|
1854
|
+
return isRecord(value) && typeof value.type === "number";
|
|
1855
|
+
}
|
|
1856
|
+
function isEngineSyncMessage(value) {
|
|
1857
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1858
|
+
return false;
|
|
1859
|
+
}
|
|
1860
|
+
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 === "syncTrackStripInsertParamByName" || value.type === "syncMasterStripInsertParamByName" || 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 === "syncMidiNoteOn" || value.type === "syncMidiNoteOff" || value.type === "syncMidiCc" || value.type === "syncMidiPanic";
|
|
1861
|
+
}
|
|
1862
|
+
function isEngineCaptureRequestMessage(value) {
|
|
1863
|
+
return isRecord(value) && value.type === "captureRequest" && typeof value.requestId === "number" && (value.op === "status" || value.op === "read" || value.op === "reset");
|
|
1864
|
+
}
|
|
1865
|
+
function isEngineCaptureResponseMessage(value) {
|
|
1866
|
+
return isRecord(value) && value.type === "captureResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1867
|
+
}
|
|
1868
|
+
function isEngineTransportRequestMessage(value) {
|
|
1869
|
+
return isRecord(value) && value.type === "transportRequest" && typeof value.requestId === "number" && value.op === "state";
|
|
1870
|
+
}
|
|
1871
|
+
function isEngineTransportResponseMessage(value) {
|
|
1872
|
+
return isRecord(value) && value.type === "transportResponse" && typeof value.requestId === "number" && typeof value.ok === "boolean";
|
|
1873
|
+
}
|
|
1874
|
+
function isRealtimeVoiceChangerMessage(value) {
|
|
1875
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
return value.type === "setConfig" || value.type === "reset" || value.type === "destroy";
|
|
1879
|
+
}
|
|
1880
|
+
function isEngineTelemetryRecord(value) {
|
|
1881
|
+
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";
|
|
1882
|
+
}
|
|
1883
|
+
function isMeterSnapshot(value) {
|
|
1884
|
+
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);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/worklet/messages.ts
|
|
1888
|
+
var DEFAULT_METRONOME_CONFIG = {
|
|
1889
|
+
beatGain: 0.35,
|
|
1890
|
+
accentGain: 0.7,
|
|
1891
|
+
clickSamples: 96
|
|
1892
|
+
};
|
|
1893
|
+
function resolveMetronomeConfig(config) {
|
|
1894
|
+
return {
|
|
1895
|
+
beatGain: config.beatGain ?? DEFAULT_METRONOME_CONFIG.beatGain,
|
|
1896
|
+
accentGain: config.accentGain ?? DEFAULT_METRONOME_CONFIG.accentGain,
|
|
1897
|
+
clickSamples: config.clickSamples ?? DEFAULT_METRONOME_CONFIG.clickSamples
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
// src/worklet.ts
|
|
1523
1902
|
var SonareWorkletProcessor = class {
|
|
1524
1903
|
constructor(options, transport) {
|
|
1525
1904
|
this.closed = false;
|
|
@@ -1669,12 +2048,19 @@ var SonareWorkletProcessor = class {
|
|
|
1669
2048
|
const denominator = Math.sqrt(sumL * sumR);
|
|
1670
2049
|
const meter = {
|
|
1671
2050
|
type: "meter",
|
|
2051
|
+
targetId: 0,
|
|
1672
2052
|
frame: this.processedFrames,
|
|
1673
2053
|
peakDbL: toDb(peakL),
|
|
1674
2054
|
peakDbR: toDb(peakR),
|
|
1675
2055
|
rmsDbL: toDb(rmsL),
|
|
1676
2056
|
rmsDbR: toDb(rmsR),
|
|
1677
|
-
correlation: denominator > 0 ? sumLR / denominator : 0
|
|
2057
|
+
correlation: denominator > 0 ? sumLR / denominator : 0,
|
|
2058
|
+
truePeakDbL: toDb(peakL),
|
|
2059
|
+
truePeakDbR: toDb(peakR),
|
|
2060
|
+
momentaryLufs: Number.NaN,
|
|
2061
|
+
shortTermLufs: Number.NaN,
|
|
2062
|
+
integratedLufs: Number.NaN,
|
|
2063
|
+
gainReductionDb: Number.NaN
|
|
1678
2064
|
};
|
|
1679
2065
|
this.transport.onMeter?.(meter);
|
|
1680
2066
|
if (this.meterRing) {
|
|
@@ -1691,12 +2077,19 @@ var SonareWorkletProcessor = class {
|
|
|
1691
2077
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
1692
2078
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
1693
2079
|
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
1694
|
-
ring.records[offset + 1] = meter.
|
|
1695
|
-
ring.records[offset + 2] = meter.
|
|
1696
|
-
ring.records[offset + 3] = meter.
|
|
1697
|
-
ring.records[offset + 4] = meter.
|
|
1698
|
-
ring.records[offset + 5] = meter.
|
|
1699
|
-
ring.records[offset + 6] =
|
|
2080
|
+
ring.records[offset + 1] = encodeFrameHi(meter.frame);
|
|
2081
|
+
ring.records[offset + 2] = meter.targetId;
|
|
2082
|
+
ring.records[offset + 3] = meter.peakDbL;
|
|
2083
|
+
ring.records[offset + 4] = meter.peakDbR;
|
|
2084
|
+
ring.records[offset + 5] = meter.rmsDbL;
|
|
2085
|
+
ring.records[offset + 6] = meter.rmsDbR;
|
|
2086
|
+
ring.records[offset + 7] = meter.correlation;
|
|
2087
|
+
ring.records[offset + 8] = meter.truePeakDbL;
|
|
2088
|
+
ring.records[offset + 9] = meter.truePeakDbR;
|
|
2089
|
+
ring.records[offset + 10] = meter.momentaryLufs;
|
|
2090
|
+
ring.records[offset + 11] = meter.shortTermLufs;
|
|
2091
|
+
ring.records[offset + 12] = meter.integratedLufs;
|
|
2092
|
+
ring.records[offset + 13] = meter.gainReductionDb;
|
|
1700
2093
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
1701
2094
|
}
|
|
1702
2095
|
publishSpectrum(left, right) {
|
|
@@ -1761,6 +2154,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1761
2154
|
// Latest metronome gains/click length pushed via 'syncMetronome'. The
|
|
1762
2155
|
// SetMetronome command only toggles enabled state; the config arrives here.
|
|
1763
2156
|
this.metronomeConfig = { ...DEFAULT_METRONOME_CONFIG };
|
|
2157
|
+
this.liveClips = /* @__PURE__ */ new Map();
|
|
1764
2158
|
this.sampleRate = options.sampleRate ?? 48e3;
|
|
1765
2159
|
this.blockSize = options.blockSize ?? 128;
|
|
1766
2160
|
this.channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
@@ -1778,12 +2172,21 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1778
2172
|
options.telemetryRingCapacity
|
|
1779
2173
|
) : void 0;
|
|
1780
2174
|
this.meterRing = options.meterSharedBuffer ? meterRingFromSharedBuffer(options.meterSharedBuffer, options.meterRingCapacity) : void 0;
|
|
2175
|
+
this.scopeRing = options.scopeSharedBuffer ? scopeRingFromSharedBuffer(
|
|
2176
|
+
options.scopeSharedBuffer,
|
|
2177
|
+
options.scopeRingCapacity,
|
|
2178
|
+
options.scopeBands
|
|
2179
|
+
) : void 0;
|
|
1781
2180
|
this.engine = new RealtimeEngine(this.sampleRate, this.blockSize);
|
|
1782
2181
|
this.engine.prepareChannels(this.channelCount, this.blockSize);
|
|
1783
2182
|
this.channelBuffers = new Array(this.channelCount);
|
|
1784
2183
|
for (let ch = 0; ch < this.channelCount; ch++) {
|
|
1785
2184
|
this.channelBuffers[ch] = this.engine.getChannelBuffer(ch, this.blockSize);
|
|
1786
2185
|
}
|
|
2186
|
+
if (this.scopeRing) {
|
|
2187
|
+
const interval = Math.max(1, Math.floor(options.scopeIntervalFrames ?? this.blockSize));
|
|
2188
|
+
this.engine.configureScopeTelemetry(interval, this.scopeRing.bands);
|
|
2189
|
+
}
|
|
1787
2190
|
}
|
|
1788
2191
|
process(inputs, outputs) {
|
|
1789
2192
|
if (this.closed) {
|
|
@@ -1841,6 +2244,7 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1841
2244
|
}
|
|
1842
2245
|
this.publishTelemetry();
|
|
1843
2246
|
this.publishMeters();
|
|
2247
|
+
this.publishScope();
|
|
1844
2248
|
return true;
|
|
1845
2249
|
}
|
|
1846
2250
|
reacquireChannelBuffers() {
|
|
@@ -1864,8 +2268,28 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1864
2268
|
}
|
|
1865
2269
|
switch (message.type) {
|
|
1866
2270
|
case "syncClips":
|
|
2271
|
+
this.liveClips.clear();
|
|
2272
|
+
for (const clip of message.clips) {
|
|
2273
|
+
if (clip.id !== void 0) {
|
|
2274
|
+
this.liveClips.set(clip.id, clip);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
1867
2277
|
this.engine.setClips(message.clips);
|
|
1868
2278
|
break;
|
|
2279
|
+
case "syncClipsDelta":
|
|
2280
|
+
for (const clipId of message.removeIds) {
|
|
2281
|
+
this.liveClips.delete(clipId);
|
|
2282
|
+
}
|
|
2283
|
+
for (const clip of message.upserts) {
|
|
2284
|
+
if (clip.id !== void 0) {
|
|
2285
|
+
this.liveClips.set(clip.id, clip);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
this.engine.setClips(Array.from(this.liveClips.values()));
|
|
2289
|
+
break;
|
|
2290
|
+
case "syncMidiClips":
|
|
2291
|
+
this.engine.setMidiClips(message.clips);
|
|
2292
|
+
break;
|
|
1869
2293
|
case "syncMarkers":
|
|
1870
2294
|
this.engine.setMarkers(message.markers);
|
|
1871
2295
|
break;
|
|
@@ -1876,6 +2300,217 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1876
2300
|
case "syncAutomation":
|
|
1877
2301
|
this.engine.setAutomationLane(message.paramId, message.points);
|
|
1878
2302
|
break;
|
|
2303
|
+
case "syncTempo":
|
|
2304
|
+
if (message.tempoSegments) {
|
|
2305
|
+
this.engine.setTempoSegments(message.tempoSegments);
|
|
2306
|
+
} else {
|
|
2307
|
+
this.engine.setTempo(message.bpm);
|
|
2308
|
+
}
|
|
2309
|
+
if (message.timeSignatureSegments) {
|
|
2310
|
+
this.engine.setTimeSignatureSegments(message.timeSignatureSegments);
|
|
2311
|
+
} else {
|
|
2312
|
+
this.engine.setTimeSignature(
|
|
2313
|
+
message.timeSignature.numerator,
|
|
2314
|
+
message.timeSignature.denominator
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
break;
|
|
2318
|
+
case "syncMixer":
|
|
2319
|
+
if (message.buses) {
|
|
2320
|
+
this.engine.setTrackBuses(message.buses);
|
|
2321
|
+
}
|
|
2322
|
+
this.engine.setTrackLanes(message.lanes);
|
|
2323
|
+
for (const strip of message.trackStrips ?? []) {
|
|
2324
|
+
this.engine.setTrackStripJson(strip.trackId, strip.sceneJson);
|
|
2325
|
+
}
|
|
2326
|
+
for (const strip of message.busStrips ?? []) {
|
|
2327
|
+
this.engine.setBusStripJson(strip.busId, strip.sceneJson);
|
|
2328
|
+
}
|
|
2329
|
+
if (message.masterStripJson) {
|
|
2330
|
+
this.engine.setMasterStripJson(message.masterStripJson);
|
|
2331
|
+
}
|
|
2332
|
+
for (const binding of message.laneSidechains ?? []) {
|
|
2333
|
+
this.engine.setLaneSidechain(binding.trackId, binding.insertIndex, binding.sourceTrackId);
|
|
2334
|
+
}
|
|
2335
|
+
break;
|
|
2336
|
+
case "syncCapture":
|
|
2337
|
+
this.engine.setCaptureBuffer(message.channels, message.bufferFrames);
|
|
2338
|
+
this.engine.setCaptureSource(message.source);
|
|
2339
|
+
this.engine.setRecordOffsetSamples(message.recordOffsetSamples);
|
|
2340
|
+
this.engine.setInputMonitor(message.inputMonitor.enabled, message.inputMonitor.gain);
|
|
2341
|
+
break;
|
|
2342
|
+
case "syncTrackStripEqBand":
|
|
2343
|
+
this.engine.setTrackStripEqBandJson(message.trackId, message.bandIndex, message.bandJson);
|
|
2344
|
+
break;
|
|
2345
|
+
case "syncMasterStripEqBand":
|
|
2346
|
+
this.engine.setMasterStripEqBandJson(message.bandIndex, message.bandJson);
|
|
2347
|
+
break;
|
|
2348
|
+
case "syncTrackStripInsertBypassed":
|
|
2349
|
+
this.engine.setTrackStripInsertBypassed(
|
|
2350
|
+
message.trackId,
|
|
2351
|
+
message.insertIndex,
|
|
2352
|
+
message.bypassed,
|
|
2353
|
+
message.resetOnBypass
|
|
2354
|
+
);
|
|
2355
|
+
break;
|
|
2356
|
+
case "syncMasterStripInsertBypassed":
|
|
2357
|
+
this.engine.setMasterStripInsertBypassed(
|
|
2358
|
+
message.insertIndex,
|
|
2359
|
+
message.bypassed,
|
|
2360
|
+
message.resetOnBypass
|
|
2361
|
+
);
|
|
2362
|
+
break;
|
|
2363
|
+
case "syncTrackStripInsertParamByName":
|
|
2364
|
+
this.engine.setTrackStripInsertParamByName(
|
|
2365
|
+
message.trackId,
|
|
2366
|
+
message.insertIndex,
|
|
2367
|
+
message.paramName,
|
|
2368
|
+
message.value
|
|
2369
|
+
);
|
|
2370
|
+
break;
|
|
2371
|
+
case "syncMasterStripInsertParamByName":
|
|
2372
|
+
this.engine.setMasterStripInsertParamByName(
|
|
2373
|
+
message.insertIndex,
|
|
2374
|
+
message.paramName,
|
|
2375
|
+
message.value
|
|
2376
|
+
);
|
|
2377
|
+
break;
|
|
2378
|
+
case "syncTrackStripPan":
|
|
2379
|
+
this.engine.setTrackStripPan(message.trackId, message.pan);
|
|
2380
|
+
break;
|
|
2381
|
+
case "syncTrackStripPanLaw":
|
|
2382
|
+
this.engine.setTrackStripPanLaw(message.trackId, message.panLaw);
|
|
2383
|
+
break;
|
|
2384
|
+
case "syncTrackStripPanMode":
|
|
2385
|
+
this.engine.setTrackStripPanMode(message.trackId, message.panMode);
|
|
2386
|
+
break;
|
|
2387
|
+
case "syncTrackStripDualPan":
|
|
2388
|
+
this.engine.setTrackStripDualPan(message.trackId, message.leftPan, message.rightPan);
|
|
2389
|
+
break;
|
|
2390
|
+
case "syncTrackStripChannelDelaySamples":
|
|
2391
|
+
this.engine.setTrackStripChannelDelaySamples(message.trackId, message.delaySamples);
|
|
2392
|
+
break;
|
|
2393
|
+
case "syncBuiltinInstrument":
|
|
2394
|
+
this.engine.setBuiltinInstrument(message.config, message.destinationId);
|
|
2395
|
+
break;
|
|
2396
|
+
case "syncSynthInstrument":
|
|
2397
|
+
this.engine.setSynthInstrument(message.patch, message.destinationId);
|
|
2398
|
+
break;
|
|
2399
|
+
case "syncLoadSoundFont":
|
|
2400
|
+
this.engine.loadSoundFont(message.data);
|
|
2401
|
+
break;
|
|
2402
|
+
case "syncSf2Instrument":
|
|
2403
|
+
this.engine.setSf2Instrument(message.config, message.destinationId);
|
|
2404
|
+
break;
|
|
2405
|
+
case "syncMidiNoteOn":
|
|
2406
|
+
this.engine.pushMidiNoteOn(
|
|
2407
|
+
message.destinationId,
|
|
2408
|
+
message.group,
|
|
2409
|
+
message.channel,
|
|
2410
|
+
message.note,
|
|
2411
|
+
message.velocity,
|
|
2412
|
+
message.renderFrame
|
|
2413
|
+
);
|
|
2414
|
+
break;
|
|
2415
|
+
case "syncMidiNoteOff":
|
|
2416
|
+
this.engine.pushMidiNoteOff(
|
|
2417
|
+
message.destinationId,
|
|
2418
|
+
message.group,
|
|
2419
|
+
message.channel,
|
|
2420
|
+
message.note,
|
|
2421
|
+
message.velocity,
|
|
2422
|
+
message.renderFrame
|
|
2423
|
+
);
|
|
2424
|
+
break;
|
|
2425
|
+
case "syncMidiCc":
|
|
2426
|
+
this.engine.pushMidiCc(
|
|
2427
|
+
message.destinationId,
|
|
2428
|
+
message.group,
|
|
2429
|
+
message.channel,
|
|
2430
|
+
message.controller,
|
|
2431
|
+
message.value,
|
|
2432
|
+
message.renderFrame
|
|
2433
|
+
);
|
|
2434
|
+
break;
|
|
2435
|
+
case "syncMidiPanic":
|
|
2436
|
+
this.engine.pushMidiPanic(message.renderFrame);
|
|
2437
|
+
break;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
receiveCaptureRequest(message) {
|
|
2441
|
+
if (this.closed) {
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
if (message.op === "status") {
|
|
2446
|
+
const status = this.engine.captureStatus();
|
|
2447
|
+
this.transport?.postMessage?.({
|
|
2448
|
+
type: "captureResponse",
|
|
2449
|
+
requestId: message.requestId,
|
|
2450
|
+
ok: true,
|
|
2451
|
+
status: {
|
|
2452
|
+
capturedFrames: status.capturedFrames,
|
|
2453
|
+
overflowCount: status.overflowCount,
|
|
2454
|
+
armed: status.armed,
|
|
2455
|
+
punchEnabled: status.punchEnabled,
|
|
2456
|
+
source: status.source,
|
|
2457
|
+
recordOffsetSamples: status.recordOffsetSamples
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
if (message.op === "read") {
|
|
2463
|
+
const captured = this.engine.capturedAudio();
|
|
2464
|
+
const channels = [];
|
|
2465
|
+
for (let ch = 0; ch < captured.length; ch++) {
|
|
2466
|
+
const source = captured[ch];
|
|
2467
|
+
const copy = [];
|
|
2468
|
+
for (let i = 0; i < source.length; i++) {
|
|
2469
|
+
copy.push(Number(source[i]));
|
|
2470
|
+
}
|
|
2471
|
+
channels.push(copy);
|
|
2472
|
+
}
|
|
2473
|
+
this.transport?.postMessage?.({
|
|
2474
|
+
type: "captureResponse",
|
|
2475
|
+
requestId: message.requestId,
|
|
2476
|
+
ok: true,
|
|
2477
|
+
channels
|
|
2478
|
+
});
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
this.engine.resetCapture();
|
|
2482
|
+
this.transport?.postMessage?.({
|
|
2483
|
+
type: "captureResponse",
|
|
2484
|
+
requestId: message.requestId,
|
|
2485
|
+
ok: true
|
|
2486
|
+
});
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
this.transport?.postMessage?.({
|
|
2489
|
+
type: "captureResponse",
|
|
2490
|
+
requestId: message.requestId,
|
|
2491
|
+
ok: false,
|
|
2492
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
receiveTransportRequest(message) {
|
|
2497
|
+
if (this.closed) {
|
|
2498
|
+
return;
|
|
2499
|
+
}
|
|
2500
|
+
try {
|
|
2501
|
+
this.transport?.postMessage?.({
|
|
2502
|
+
type: "transportResponse",
|
|
2503
|
+
requestId: message.requestId,
|
|
2504
|
+
ok: true,
|
|
2505
|
+
state: this.engine.getTransportState()
|
|
2506
|
+
});
|
|
2507
|
+
} catch (error) {
|
|
2508
|
+
this.transport?.postMessage?.({
|
|
2509
|
+
type: "transportResponse",
|
|
2510
|
+
requestId: message.requestId,
|
|
2511
|
+
ok: false,
|
|
2512
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2513
|
+
});
|
|
1879
2514
|
}
|
|
1880
2515
|
}
|
|
1881
2516
|
destroy() {
|
|
@@ -1956,6 +2591,14 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1956
2591
|
case 17 /* SeekMarker */:
|
|
1957
2592
|
this.engine.seekMarker(Math.trunc(Number(command.targetId ?? 0)), sampleTime);
|
|
1958
2593
|
break;
|
|
2594
|
+
case 10 /* SetSoloMute */:
|
|
2595
|
+
this.engine.setSoloMute(
|
|
2596
|
+
Math.trunc(Number(command.targetId ?? 0)),
|
|
2597
|
+
Boolean((Number(command.argInt ?? 0) & 2) !== 0),
|
|
2598
|
+
Boolean((Number(command.argInt ?? 0) & 1) !== 0),
|
|
2599
|
+
sampleTime
|
|
2600
|
+
);
|
|
2601
|
+
break;
|
|
1959
2602
|
default:
|
|
1960
2603
|
this.publishTelemetryRecord({
|
|
1961
2604
|
type: 1 /* Error */,
|
|
@@ -1981,16 +2624,29 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
1981
2624
|
}
|
|
1982
2625
|
this.transport?.postMessage?.(record);
|
|
1983
2626
|
}
|
|
2627
|
+
// Drains the engine meter telemetry queue into the stereo meter ring / transport.
|
|
2628
|
+
//
|
|
2629
|
+
// Shared-queue contract: `drainMeterTelemetry` and `drainMeterTelemetryWide`
|
|
2630
|
+
// pop the SAME single-consumer telemetry queue, so exactly ONE of them may run
|
|
2631
|
+
// per engine. The live worklet path owns the queue via the stereo drain below;
|
|
2632
|
+
// the worklet meter ring (SONARE_METER_RING_RECORD_FLOATS) is a fixed stereo
|
|
2633
|
+
// layout carrying planes 0/1 plus the correlation/LUFS summary. Per-plane
|
|
2634
|
+
// surround meters are NOT delivered over the live worklet ring — a host that
|
|
2635
|
+
// needs them must use the offline `drainMeterTelemetryWide()` API on a
|
|
2636
|
+
// non-worklet engine instance (do not also call it on a worklet-driven engine,
|
|
2637
|
+
// or the two drains will starve each other).
|
|
1984
2638
|
publishMeters() {
|
|
1985
2639
|
if (this.meterIntervalFrames <= 0 || !this.transport && !this.meterRing) {
|
|
1986
2640
|
return;
|
|
1987
2641
|
}
|
|
1988
2642
|
for (const item of this.engine.drainMeterTelemetry(64)) {
|
|
1989
2643
|
const meter = meterFromEngine(item);
|
|
1990
|
-
if (meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
2644
|
+
if (meter.frame !== this.lastMeterFrame && meter.frame - this.lastMeterFrame < this.meterIntervalFrames) {
|
|
1991
2645
|
continue;
|
|
1992
2646
|
}
|
|
1993
|
-
this.lastMeterFrame
|
|
2647
|
+
if (meter.frame !== this.lastMeterFrame) {
|
|
2648
|
+
this.lastMeterFrame = meter.frame;
|
|
2649
|
+
}
|
|
1994
2650
|
if (this.meterRing) {
|
|
1995
2651
|
this.writeMeterRing(meter);
|
|
1996
2652
|
} else {
|
|
@@ -2007,12 +2663,54 @@ var _SonareRealtimeEngineWorkletProcessor = class _SonareRealtimeEngineWorkletPr
|
|
|
2007
2663
|
const writeIndex = Atomics.load(ring.header, 0);
|
|
2008
2664
|
const offset = writeIndex % ring.capacity * SONARE_METER_RING_RECORD_FLOATS;
|
|
2009
2665
|
ring.records[offset] = encodeFrameLo(meter.frame);
|
|
2010
|
-
ring.records[offset + 1] = meter.
|
|
2011
|
-
ring.records[offset + 2] = meter.
|
|
2012
|
-
ring.records[offset + 3] = meter.
|
|
2013
|
-
ring.records[offset + 4] = meter.
|
|
2014
|
-
ring.records[offset + 5] = meter.
|
|
2015
|
-
ring.records[offset + 6] =
|
|
2666
|
+
ring.records[offset + 1] = encodeFrameHi(meter.frame);
|
|
2667
|
+
ring.records[offset + 2] = meter.targetId;
|
|
2668
|
+
ring.records[offset + 3] = meter.peakDbL;
|
|
2669
|
+
ring.records[offset + 4] = meter.peakDbR;
|
|
2670
|
+
ring.records[offset + 5] = meter.rmsDbL;
|
|
2671
|
+
ring.records[offset + 6] = meter.rmsDbR;
|
|
2672
|
+
ring.records[offset + 7] = meter.correlation;
|
|
2673
|
+
ring.records[offset + 8] = meter.truePeakDbL;
|
|
2674
|
+
ring.records[offset + 9] = meter.truePeakDbR;
|
|
2675
|
+
ring.records[offset + 10] = meter.momentaryLufs;
|
|
2676
|
+
ring.records[offset + 11] = meter.shortTermLufs;
|
|
2677
|
+
ring.records[offset + 12] = meter.integratedLufs;
|
|
2678
|
+
ring.records[offset + 13] = meter.gainReductionDb;
|
|
2679
|
+
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2680
|
+
}
|
|
2681
|
+
// Drains the engine's scope producer (FFT spectrum + goniometer points) into
|
|
2682
|
+
// the lock-free SAB scope ring. Only the embind runtime publishes scope
|
|
2683
|
+
// telemetry; the sonare-rt runtime owns its own transport. No allocation on
|
|
2684
|
+
// the render path: records are written field-by-field into the ring.
|
|
2685
|
+
publishScope() {
|
|
2686
|
+
const ring = this.scopeRing;
|
|
2687
|
+
if (!ring) {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
for (const item of this.engine.drainScopeTelemetry(64)) {
|
|
2691
|
+
this.writeScopeRing(ring, item);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
writeScopeRing(ring, record) {
|
|
2695
|
+
const writeIndex = Atomics.load(ring.header, 0);
|
|
2696
|
+
const base = writeIndex % ring.capacity * ring.recordFloats;
|
|
2697
|
+
ring.records[base] = encodeFrameLo(record.renderFrame);
|
|
2698
|
+
ring.records[base + 1] = encodeFrameHi(record.renderFrame);
|
|
2699
|
+
ring.records[base + 2] = record.targetId;
|
|
2700
|
+
const bandCount = Math.min(ring.bands, record.bands.length);
|
|
2701
|
+
ring.records[base + 3] = bandCount;
|
|
2702
|
+
const pointCount = Math.min(ring.maxPoints, record.points.length);
|
|
2703
|
+
ring.records[base + 4] = pointCount;
|
|
2704
|
+
const bandsBase = base + SONARE_SCOPE_RING_RECORD_PREFIX_FLOATS;
|
|
2705
|
+
for (let i = 0; i < bandCount; i++) {
|
|
2706
|
+
ring.records[bandsBase + i] = record.bands[i];
|
|
2707
|
+
}
|
|
2708
|
+
const pointsBase = bandsBase + ring.bands;
|
|
2709
|
+
for (let i = 0; i < pointCount; i++) {
|
|
2710
|
+
const point = record.points[i];
|
|
2711
|
+
ring.records[pointsBase + 2 * i] = point.left;
|
|
2712
|
+
ring.records[pointsBase + 2 * i + 1] = point.right;
|
|
2713
|
+
}
|
|
2016
2714
|
Atomics.store(ring.header, 0, writeIndex + 1);
|
|
2017
2715
|
}
|
|
2018
2716
|
commandRingFromSharedBuffer(sharedBuffer, fallbackCapacity) {
|
|
@@ -2144,9 +2842,28 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2144
2842
|
this.metronomeConfig.clickSamples
|
|
2145
2843
|
);
|
|
2146
2844
|
break;
|
|
2845
|
+
case "syncTempo":
|
|
2846
|
+
this.module._sonare_rt_engine_set_tempo(this.engine, message.bpm);
|
|
2847
|
+
break;
|
|
2147
2848
|
case "syncClips":
|
|
2849
|
+
case "syncClipsDelta":
|
|
2850
|
+
case "syncMidiClips":
|
|
2148
2851
|
case "syncMarkers":
|
|
2149
2852
|
case "syncAutomation":
|
|
2853
|
+
case "syncMixer":
|
|
2854
|
+
case "syncCapture":
|
|
2855
|
+
case "syncTrackStripEqBand":
|
|
2856
|
+
case "syncMasterStripEqBand":
|
|
2857
|
+
case "syncTrackStripInsertBypassed":
|
|
2858
|
+
case "syncMasterStripInsertBypassed":
|
|
2859
|
+
case "syncBuiltinInstrument":
|
|
2860
|
+
case "syncSynthInstrument":
|
|
2861
|
+
case "syncSf2Instrument":
|
|
2862
|
+
case "syncLoadSoundFont":
|
|
2863
|
+
case "syncMidiNoteOn":
|
|
2864
|
+
case "syncMidiNoteOff":
|
|
2865
|
+
case "syncMidiCc":
|
|
2866
|
+
case "syncMidiPanic":
|
|
2150
2867
|
if (this.telemetryRing) {
|
|
2151
2868
|
writeSonareEngineTelemetryRingBuffer(this.telemetryRing, {
|
|
2152
2869
|
type: 1 /* Error */,
|
|
@@ -2161,6 +2878,28 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2161
2878
|
break;
|
|
2162
2879
|
}
|
|
2163
2880
|
}
|
|
2881
|
+
receiveCaptureRequest(message, port) {
|
|
2882
|
+
if (this.closed) {
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
port?.postMessage?.({
|
|
2886
|
+
type: "captureResponse",
|
|
2887
|
+
requestId: message.requestId,
|
|
2888
|
+
ok: false,
|
|
2889
|
+
error: "Capture read-back is not supported by the sonare-rt runtime."
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
receiveTransportRequest(message, port) {
|
|
2893
|
+
if (this.closed) {
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
port?.postMessage?.({
|
|
2897
|
+
type: "transportResponse",
|
|
2898
|
+
requestId: message.requestId,
|
|
2899
|
+
ok: false,
|
|
2900
|
+
error: "Transport state read-back is not supported by the sonare-rt runtime."
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2164
2903
|
destroy() {
|
|
2165
2904
|
if (this.closed) {
|
|
2166
2905
|
return;
|
|
@@ -2333,26 +3072,53 @@ var SonareRtRealtimeEngineRuntime = class {
|
|
|
2333
3072
|
}
|
|
2334
3073
|
};
|
|
2335
3074
|
var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
2336
|
-
constructor(node, capabilities, commandRing, telemetryRing, meterRing) {
|
|
3075
|
+
constructor(node, capabilities, commandRing, telemetryRing, meterRing, scopeRing) {
|
|
2337
3076
|
this.telemetryReadIndex = 0;
|
|
2338
3077
|
this.meterReadIndex = 0;
|
|
3078
|
+
this.scopeReadIndex = 0;
|
|
2339
3079
|
this.telemetryListeners = /* @__PURE__ */ new Set();
|
|
2340
3080
|
this.meterListeners = /* @__PURE__ */ new Set();
|
|
3081
|
+
this.scopeListeners = /* @__PURE__ */ new Set();
|
|
3082
|
+
this.captureRequestId = 1;
|
|
3083
|
+
this.captureRequests = /* @__PURE__ */ new Map();
|
|
3084
|
+
this.transportRequestId = 1;
|
|
3085
|
+
this.transportRequests = /* @__PURE__ */ new Map();
|
|
2341
3086
|
this.destroyed = false;
|
|
2342
3087
|
this.node = node;
|
|
2343
3088
|
this.capabilities = capabilities;
|
|
2344
3089
|
this.commandRing = commandRing;
|
|
2345
3090
|
this.telemetryRing = telemetryRing;
|
|
2346
3091
|
this.meterRing = meterRing;
|
|
3092
|
+
this.scopeRing = scopeRing;
|
|
2347
3093
|
this.ready = new Promise((resolve, reject) => {
|
|
2348
3094
|
this.resolveReady = resolve;
|
|
2349
3095
|
this.rejectReady = reject;
|
|
2350
3096
|
});
|
|
2351
|
-
if (capabilities.
|
|
3097
|
+
if (!capabilities.readyMessage) {
|
|
2352
3098
|
this.resolveReady();
|
|
2353
3099
|
}
|
|
2354
3100
|
this.node.port.onmessage = (event) => {
|
|
2355
|
-
if (
|
|
3101
|
+
if (isEngineCaptureResponseMessage(event.data)) {
|
|
3102
|
+
const pending = this.captureRequests.get(event.data.requestId);
|
|
3103
|
+
if (pending) {
|
|
3104
|
+
this.captureRequests.delete(event.data.requestId);
|
|
3105
|
+
if (event.data.ok) {
|
|
3106
|
+
pending.resolve(event.data);
|
|
3107
|
+
} else {
|
|
3108
|
+
pending.reject(new Error(event.data.error ?? "Capture request failed"));
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
} else if (isEngineTransportResponseMessage(event.data)) {
|
|
3112
|
+
const pending = this.transportRequests.get(event.data.requestId);
|
|
3113
|
+
if (pending) {
|
|
3114
|
+
this.transportRequests.delete(event.data.requestId);
|
|
3115
|
+
if (event.data.ok) {
|
|
3116
|
+
pending.resolve(event.data);
|
|
3117
|
+
} else {
|
|
3118
|
+
pending.reject(new Error(event.data.error ?? "Transport request failed"));
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
} else if (isEngineTelemetryRecord(event.data)) {
|
|
2356
3122
|
this.emitTelemetry(event.data);
|
|
2357
3123
|
} else if (isMeterSnapshot(event.data)) {
|
|
2358
3124
|
this.emitMeter(event.data);
|
|
@@ -2393,6 +3159,8 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2393
3159
|
const commandRing = mode === "sab" ? createSonareEngineCommandRingBuffer(options.commandRingCapacity ?? 128) : void 0;
|
|
2394
3160
|
const telemetryRing = mode === "sab" ? createSonareEngineTelemetryRingBuffer(options.telemetryRingCapacity ?? 128) : void 0;
|
|
2395
3161
|
const meterRing = mode === "sab" && runtimeTarget === "embind" ? createSonareMeterRingBuffer(options.meterRingCapacity ?? 128) : void 0;
|
|
3162
|
+
const scopeIntervalFrames = Math.max(0, Math.floor(options.scopeIntervalFrames ?? 0));
|
|
3163
|
+
const scopeRing = mode === "sab" && runtimeTarget === "embind" && scopeIntervalFrames > 0 ? createSonareScopeRingBuffer(options.scopeRingCapacity ?? 64, options.scopeBands ?? 48) : void 0;
|
|
2396
3164
|
const channelCount = Math.max(1, Math.floor(options.channelCount ?? 2));
|
|
2397
3165
|
const processorOptions = {
|
|
2398
3166
|
runtimeTarget,
|
|
@@ -2406,7 +3174,14 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2406
3174
|
telemetrySharedBuffer: telemetryRing?.sharedBuffer,
|
|
2407
3175
|
telemetryRingCapacity: telemetryRing?.capacity,
|
|
2408
3176
|
meterSharedBuffer: meterRing?.sharedBuffer,
|
|
2409
|
-
meterRingCapacity: meterRing?.capacity
|
|
3177
|
+
meterRingCapacity: meterRing?.capacity,
|
|
3178
|
+
scopeSharedBuffer: scopeRing?.sharedBuffer,
|
|
3179
|
+
scopeRingCapacity: scopeRing?.capacity,
|
|
3180
|
+
scopeBands: scopeRing?.bands,
|
|
3181
|
+
scopeIntervalFrames: scopeRing ? scopeIntervalFrames : void 0,
|
|
3182
|
+
wasmBinary: options.wasmBinary,
|
|
3183
|
+
initialSyncMessages: options.initialSyncMessages,
|
|
3184
|
+
initialCommands: options.initialCommands
|
|
2410
3185
|
};
|
|
2411
3186
|
const factory = options.nodeFactory ?? ((ctx, name, nodeOptions) => new AudioWorkletNode(ctx, name, nodeOptions));
|
|
2412
3187
|
const node = factory(context, processorName, {
|
|
@@ -2426,11 +3201,13 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2426
3201
|
engineAbiVersion: detectedCapabilities?.engineAbiVersion,
|
|
2427
3202
|
expectedEngineAbiVersion: detectedCapabilities?.expectedEngineAbiVersion,
|
|
2428
3203
|
abiCompatible: detectedCapabilities?.abiCompatible,
|
|
2429
|
-
degradedReason
|
|
3204
|
+
degradedReason,
|
|
3205
|
+
readyMessage: runtimeTarget === "sonare-rt" || runtimeTarget === "embind" && moduleUrl !== void 0 && !options.nodeFactory
|
|
2430
3206
|
},
|
|
2431
3207
|
commandRing,
|
|
2432
3208
|
telemetryRing,
|
|
2433
|
-
meterRing
|
|
3209
|
+
meterRing,
|
|
3210
|
+
scopeRing
|
|
2434
3211
|
);
|
|
2435
3212
|
}
|
|
2436
3213
|
play(sampleTime = -1) {
|
|
@@ -2463,6 +3240,32 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2463
3240
|
this.node.port.postMessage(command);
|
|
2464
3241
|
return true;
|
|
2465
3242
|
}
|
|
3243
|
+
requestCaptureStatus() {
|
|
3244
|
+
return this.sendCaptureRequest("status").then((response) => {
|
|
3245
|
+
if (!response.status) {
|
|
3246
|
+
throw new Error("Capture status response is missing status.");
|
|
3247
|
+
}
|
|
3248
|
+
return response.status;
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
requestCapturedAudio() {
|
|
3252
|
+
return this.sendCaptureRequest("read").then(
|
|
3253
|
+
(response) => (response.channels ?? []).map(
|
|
3254
|
+
(channel) => channel instanceof Float32Array ? channel : new Float32Array(channel)
|
|
3255
|
+
)
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
requestCaptureReset() {
|
|
3259
|
+
return this.sendCaptureRequest("reset").then(() => void 0);
|
|
3260
|
+
}
|
|
3261
|
+
requestTransportState() {
|
|
3262
|
+
return this.sendTransportRequest().then((response) => {
|
|
3263
|
+
if (!response.state) {
|
|
3264
|
+
throw new Error("Transport state response is missing state.");
|
|
3265
|
+
}
|
|
3266
|
+
return response.state;
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
2466
3269
|
pollTelemetry() {
|
|
2467
3270
|
if (!this.telemetryRing) {
|
|
2468
3271
|
return [];
|
|
@@ -2488,6 +3291,20 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2488
3291
|
}
|
|
2489
3292
|
return read.meters;
|
|
2490
3293
|
}
|
|
3294
|
+
// Drains scope telemetry (FFT spectrum + goniometer points) published into the
|
|
3295
|
+
// SAB scope ring and forwards each record to onScope listeners. A no-op unless
|
|
3296
|
+
// the node was created with scopeIntervalFrames > 0 (embind SAB mode).
|
|
3297
|
+
pollScope() {
|
|
3298
|
+
if (!this.scopeRing) {
|
|
3299
|
+
return [];
|
|
3300
|
+
}
|
|
3301
|
+
const read = readSonareScopeRingBuffer(this.scopeRing, this.scopeReadIndex);
|
|
3302
|
+
this.scopeReadIndex = read.nextReadIndex;
|
|
3303
|
+
for (const scope of read.scopes) {
|
|
3304
|
+
this.emitScope(scope);
|
|
3305
|
+
}
|
|
3306
|
+
return read.scopes;
|
|
3307
|
+
}
|
|
2491
3308
|
onTelemetry(callback) {
|
|
2492
3309
|
this.telemetryListeners.add(callback);
|
|
2493
3310
|
return () => {
|
|
@@ -2500,6 +3317,12 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2500
3317
|
this.meterListeners.delete(callback);
|
|
2501
3318
|
};
|
|
2502
3319
|
}
|
|
3320
|
+
onScope(callback) {
|
|
3321
|
+
this.scopeListeners.add(callback);
|
|
3322
|
+
return () => {
|
|
3323
|
+
this.scopeListeners.delete(callback);
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
2503
3326
|
destroy() {
|
|
2504
3327
|
if (this.destroyed) {
|
|
2505
3328
|
return;
|
|
@@ -2507,8 +3330,17 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2507
3330
|
this.destroyed = true;
|
|
2508
3331
|
this.node.port.postMessage({ type: 3 /* TransportStop */, sampleTime: -1 });
|
|
2509
3332
|
this.node.disconnect();
|
|
3333
|
+
for (const pending of this.captureRequests.values()) {
|
|
3334
|
+
pending.reject(new Error("Realtime engine node is destroyed."));
|
|
3335
|
+
}
|
|
3336
|
+
this.captureRequests.clear();
|
|
3337
|
+
for (const pending of this.transportRequests.values()) {
|
|
3338
|
+
pending.reject(new Error("Realtime engine node is destroyed."));
|
|
3339
|
+
}
|
|
3340
|
+
this.transportRequests.clear();
|
|
2510
3341
|
this.telemetryListeners.clear();
|
|
2511
3342
|
this.meterListeners.clear();
|
|
3343
|
+
this.scopeListeners.clear();
|
|
2512
3344
|
}
|
|
2513
3345
|
emitTelemetry(telemetry) {
|
|
2514
3346
|
for (const listener of this.telemetryListeners) {
|
|
@@ -2520,14 +3352,57 @@ var SonareRealtimeEngineNode = class _SonareRealtimeEngineNode {
|
|
|
2520
3352
|
listener(meter);
|
|
2521
3353
|
}
|
|
2522
3354
|
}
|
|
3355
|
+
emitScope(scope) {
|
|
3356
|
+
for (const listener of this.scopeListeners) {
|
|
3357
|
+
listener(scope);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
sendCaptureRequest(op) {
|
|
3361
|
+
if (this.destroyed) {
|
|
3362
|
+
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
3363
|
+
}
|
|
3364
|
+
const requestId = this.captureRequestId++;
|
|
3365
|
+
const promise = new Promise((resolve, reject) => {
|
|
3366
|
+
this.captureRequests.set(requestId, { resolve, reject });
|
|
3367
|
+
});
|
|
3368
|
+
this.node.port.postMessage({ type: "captureRequest", requestId, op });
|
|
3369
|
+
return promise;
|
|
3370
|
+
}
|
|
3371
|
+
sendTransportRequest() {
|
|
3372
|
+
if (this.destroyed) {
|
|
3373
|
+
return Promise.reject(new Error("Realtime engine node is destroyed."));
|
|
3374
|
+
}
|
|
3375
|
+
const requestId = this.transportRequestId++;
|
|
3376
|
+
const promise = new Promise((resolve, reject) => {
|
|
3377
|
+
this.transportRequests.set(requestId, { resolve, reject });
|
|
3378
|
+
});
|
|
3379
|
+
this.node.port.postMessage({ type: "transportRequest", requestId, op: "state" });
|
|
3380
|
+
return promise;
|
|
3381
|
+
}
|
|
2523
3382
|
};
|
|
2524
3383
|
var SonareEngine = class _SonareEngine {
|
|
2525
3384
|
constructor(context, realtimeNode, offlineEngine, sampleRate, offlineBlockSize, offlineChannelCount) {
|
|
2526
3385
|
this.automationLanes = /* @__PURE__ */ new Map();
|
|
2527
3386
|
this.clips = /* @__PURE__ */ new Map();
|
|
3387
|
+
this.midiClips = /* @__PURE__ */ new Map();
|
|
2528
3388
|
this.markers = /* @__PURE__ */ new Map();
|
|
3389
|
+
this.trackLaneIds = [];
|
|
3390
|
+
this.trackSends = /* @__PURE__ */ new Map();
|
|
3391
|
+
this.trackOutputBus = /* @__PURE__ */ new Map();
|
|
3392
|
+
this.laneSidechains = /* @__PURE__ */ new Map();
|
|
3393
|
+
this.buses = [];
|
|
3394
|
+
this.trackStripJson = /* @__PURE__ */ new Map();
|
|
3395
|
+
this.busStripJson = /* @__PURE__ */ new Map();
|
|
3396
|
+
this.tempoBpm = 120;
|
|
3397
|
+
this.timeSignature = { numerator: 4, denominator: 4 };
|
|
3398
|
+
this.tempoSegments = [{ startPpq: 0, bpm: 120 }];
|
|
3399
|
+
this.timeSignatureSegments = [
|
|
3400
|
+
{ startPpq: 0, numerator: 4, denominator: 4 }
|
|
3401
|
+
];
|
|
2529
3402
|
this.nextClipId = 1;
|
|
2530
3403
|
this.nextMarkerId = 1;
|
|
3404
|
+
this.transportPlaying = false;
|
|
3405
|
+
this.pendingInstrumentSync = [];
|
|
2531
3406
|
this.destroyed = false;
|
|
2532
3407
|
this.context = context;
|
|
2533
3408
|
this.realtimeNode = realtimeNode;
|
|
@@ -2538,8 +3413,21 @@ var SonareEngine = class _SonareEngine {
|
|
|
2538
3413
|
this.offlineBlockSize = offlineBlockSize;
|
|
2539
3414
|
this.offlineChannelCount = offlineChannelCount;
|
|
2540
3415
|
this.transport = {
|
|
2541
|
-
play: (sampleTime = -1) =>
|
|
2542
|
-
|
|
3416
|
+
play: (sampleTime = -1) => {
|
|
3417
|
+
const ok = this.realtimeNode.play(sampleTime);
|
|
3418
|
+
if (ok) {
|
|
3419
|
+
this.transportPlaying = true;
|
|
3420
|
+
}
|
|
3421
|
+
return ok;
|
|
3422
|
+
},
|
|
3423
|
+
stop: (sampleTime = -1) => {
|
|
3424
|
+
const ok = this.realtimeNode.stop(sampleTime);
|
|
3425
|
+
if (ok) {
|
|
3426
|
+
this.transportPlaying = false;
|
|
3427
|
+
this.flushPendingInstrumentSync();
|
|
3428
|
+
}
|
|
3429
|
+
return ok;
|
|
3430
|
+
},
|
|
2543
3431
|
seekPpq: (ppq, sampleTime = -1) => {
|
|
2544
3432
|
this.offlineEngine.seekPpq(ppq, sampleTime);
|
|
2545
3433
|
return this.realtimeNode.seekPpq(ppq, sampleTime);
|
|
@@ -2550,6 +3438,7 @@ var SonareEngine = class _SonareEngine {
|
|
|
2550
3438
|
return this.realtimeNode.seekSample(timelineSample, sampleTime);
|
|
2551
3439
|
},
|
|
2552
3440
|
setTempo: (bpm) => this.setTempo(bpm),
|
|
3441
|
+
setTempoSegments: (segments) => this.setTempoSegments(segments),
|
|
2553
3442
|
setLoop: (startPpq, endPpq, enabled = true) => this.setLoop(startPpq, endPpq, enabled)
|
|
2554
3443
|
};
|
|
2555
3444
|
}
|
|
@@ -2584,13 +3473,37 @@ var SonareEngine = class _SonareEngine {
|
|
|
2584
3473
|
await this.context.resume?.();
|
|
2585
3474
|
}
|
|
2586
3475
|
setTempo(bpm) {
|
|
3476
|
+
this.tempoBpm = bpm;
|
|
3477
|
+
this.tempoSegments = [{ startPpq: 0, bpm }];
|
|
2587
3478
|
this.offlineEngine.setTempo(bpm);
|
|
3479
|
+
this.postTempoSync();
|
|
2588
3480
|
this.realtimeNode.sendCommand({
|
|
2589
3481
|
type: 6 /* SetTempoMap */,
|
|
2590
3482
|
sampleTime: -1,
|
|
2591
3483
|
argFloat: bpm
|
|
2592
3484
|
});
|
|
2593
3485
|
}
|
|
3486
|
+
setTempoSegments(segments) {
|
|
3487
|
+
this.tempoSegments = segments.map((segment) => ({ ...segment }));
|
|
3488
|
+
this.tempoBpm = this.tempoSegments[0]?.bpm ?? this.tempoBpm;
|
|
3489
|
+
this.offlineEngine.setTempoSegments(this.tempoSegments);
|
|
3490
|
+
this.postTempoSync();
|
|
3491
|
+
}
|
|
3492
|
+
setTimeSignature(numerator, denominator) {
|
|
3493
|
+
this.timeSignature = { numerator, denominator };
|
|
3494
|
+
this.timeSignatureSegments = [{ startPpq: 0, numerator, denominator }];
|
|
3495
|
+
this.offlineEngine.setTimeSignature(numerator, denominator);
|
|
3496
|
+
this.postTempoSync();
|
|
3497
|
+
}
|
|
3498
|
+
setTimeSignatureSegments(segments) {
|
|
3499
|
+
this.timeSignatureSegments = segments.map((segment) => ({ ...segment }));
|
|
3500
|
+
const first = this.timeSignatureSegments[0];
|
|
3501
|
+
if (first) {
|
|
3502
|
+
this.timeSignature = { numerator: first.numerator, denominator: first.denominator };
|
|
3503
|
+
}
|
|
3504
|
+
this.offlineEngine.setTimeSignatureSegments(this.timeSignatureSegments);
|
|
3505
|
+
this.postTempoSync();
|
|
3506
|
+
}
|
|
2594
3507
|
setLoop(startPpq, endPpq, enabled = true) {
|
|
2595
3508
|
this.offlineEngine.setLoop(startPpq, endPpq, enabled);
|
|
2596
3509
|
return this.realtimeNode.sendCommand({
|
|
@@ -2601,6 +3514,17 @@ var SonareEngine = class _SonareEngine {
|
|
|
2601
3514
|
argInt: Math.round(endPpq * 1e6)
|
|
2602
3515
|
});
|
|
2603
3516
|
}
|
|
3517
|
+
countInEndSample(startSample, bars) {
|
|
3518
|
+
return this.offlineEngine.countInEndSample(startSample, bars);
|
|
3519
|
+
}
|
|
3520
|
+
async getTransportState() {
|
|
3521
|
+
const state = await this.realtimeNode.requestTransportState();
|
|
3522
|
+
this.latestTransportState = state;
|
|
3523
|
+
return state;
|
|
3524
|
+
}
|
|
3525
|
+
cachedTransportState() {
|
|
3526
|
+
return this.latestTransportState;
|
|
3527
|
+
}
|
|
2604
3528
|
setParam(nodeId, param, value) {
|
|
2605
3529
|
const paramId = this.resolveParamId(nodeId, param);
|
|
2606
3530
|
this.offlineEngine.setParameter(paramId, value);
|
|
@@ -2623,6 +3547,64 @@ var SonareEngine = class _SonareEngine {
|
|
|
2623
3547
|
addAutomationPoint(laneId, ppq, value, curve = "linear") {
|
|
2624
3548
|
this.scheduleParam("", laneId, ppq, value, curve);
|
|
2625
3549
|
}
|
|
3550
|
+
/**
|
|
3551
|
+
* Replaces the automation lane for `paramId` with the given breakpoints.
|
|
3552
|
+
*
|
|
3553
|
+
* Unlike scheduleParam (which appends a single point), this sets the whole
|
|
3554
|
+
* lane at once; an empty array clears the lane. The points are defensively
|
|
3555
|
+
* copied and sorted by ppq before being mirrored to the offline engine and
|
|
3556
|
+
* the live worklet engine.
|
|
3557
|
+
*
|
|
3558
|
+
* @param paramId Automation target id (registered parameter or a reserved
|
|
3559
|
+
* engine mixer target from automationParamId/busAutomationParamId).
|
|
3560
|
+
* @param points Lane breakpoints; order does not matter.
|
|
3561
|
+
*/
|
|
3562
|
+
setAutomationLane(paramId, points) {
|
|
3563
|
+
const sorted = points.map((point) => ({ ...point })).sort((a, b) => a.ppq - b.ppq);
|
|
3564
|
+
if (sorted.length === 0) {
|
|
3565
|
+
this.automationLanes.delete(paramId);
|
|
3566
|
+
} else {
|
|
3567
|
+
this.automationLanes.set(paramId, sorted);
|
|
3568
|
+
}
|
|
3569
|
+
this.offlineEngine.setAutomationLane(paramId, sorted);
|
|
3570
|
+
this.postSync({ type: "syncAutomation", paramId, points: sorted });
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Returns the automation target id for a mixer strip parameter.
|
|
3574
|
+
*
|
|
3575
|
+
* The id addresses the engine's reserved mixer namespace, so it can be fed
|
|
3576
|
+
* straight to setAutomationLane to automate a fader or pan without
|
|
3577
|
+
* registering a parameter.
|
|
3578
|
+
*
|
|
3579
|
+
* @param target Track id (declares a mixer lane on first use) or 'master'.
|
|
3580
|
+
* @param kind Strip parameter to address.
|
|
3581
|
+
* @returns Reserved engine parameter id for the strip parameter.
|
|
3582
|
+
*/
|
|
3583
|
+
automationParamId(target, kind) {
|
|
3584
|
+
const paramKind = kind === "pan" ? ENGINE_MIXER_PARAM_PAN : ENGINE_MIXER_PARAM_FADER_DB;
|
|
3585
|
+
if (target === "master") {
|
|
3586
|
+
return engineMixerMasterTarget(paramKind);
|
|
3587
|
+
}
|
|
3588
|
+
return engineMixerLaneTarget(this.ensureTrackLane(target), paramKind);
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* Returns the automation target id for a bus fader.
|
|
3592
|
+
*
|
|
3593
|
+
* @param busId Bus id (declares the mixer bus on first use).
|
|
3594
|
+
* @returns Reserved engine parameter id for the bus fader gain (dB).
|
|
3595
|
+
*/
|
|
3596
|
+
busAutomationParamId(busId) {
|
|
3597
|
+
return engineMixerBusTarget(this.ensureBus(busId), ENGINE_MIXER_PARAM_FADER_DB);
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Returns the number of automation lanes installed on the engine, including
|
|
3601
|
+
* lanes whose breakpoint list is currently empty.
|
|
3602
|
+
*
|
|
3603
|
+
* @returns Engine-side automation lane count.
|
|
3604
|
+
*/
|
|
3605
|
+
automationLaneCount() {
|
|
3606
|
+
return this.offlineEngine.automationLaneCount();
|
|
3607
|
+
}
|
|
2626
3608
|
listParameters() {
|
|
2627
3609
|
const parameters = [];
|
|
2628
3610
|
for (let index = 0; index < this.offlineEngine.parameterCount(); index++) {
|
|
@@ -2631,12 +3613,300 @@ var SonareEngine = class _SonareEngine {
|
|
|
2631
3613
|
return parameters;
|
|
2632
3614
|
}
|
|
2633
3615
|
setSoloMute(target, solo, mute) {
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
3616
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3617
|
+
this.offlineEngine.setSoloMute(laneIndex, solo, mute);
|
|
3618
|
+
return this.realtimeNode.sendCommand({
|
|
3619
|
+
type: 10 /* SetSoloMute */,
|
|
3620
|
+
targetId: laneIndex,
|
|
3621
|
+
sampleTime: -1,
|
|
3622
|
+
argInt: (mute ? 1 : 0) | (solo ? 2 : 0)
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
setStripGain(target, db) {
|
|
3626
|
+
if (target === "master") {
|
|
3627
|
+
const paramId2 = engineMixerMasterTarget(ENGINE_MIXER_PARAM_FADER_DB);
|
|
3628
|
+
this.offlineEngine.setParameter(paramId2, db);
|
|
3629
|
+
return this.realtimeNode.sendCommand({
|
|
3630
|
+
type: 1 /* SetParamSmoothed */,
|
|
3631
|
+
targetId: paramId2,
|
|
3632
|
+
sampleTime: -1,
|
|
3633
|
+
argFloat: db
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3637
|
+
const paramId = engineMixerLaneTarget(laneIndex, ENGINE_MIXER_PARAM_FADER_DB);
|
|
3638
|
+
this.offlineEngine.setParameter(paramId, db);
|
|
3639
|
+
return this.realtimeNode.sendCommand({
|
|
3640
|
+
type: 1 /* SetParamSmoothed */,
|
|
3641
|
+
targetId: paramId,
|
|
3642
|
+
sampleTime: -1,
|
|
3643
|
+
argFloat: db
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
setStripPan(target, pan) {
|
|
3647
|
+
if (target === "master") {
|
|
3648
|
+
const paramId2 = engineMixerMasterTarget(ENGINE_MIXER_PARAM_PAN);
|
|
3649
|
+
this.offlineEngine.setParameter(paramId2, pan);
|
|
3650
|
+
return this.realtimeNode.sendCommand({
|
|
3651
|
+
type: 1 /* SetParamSmoothed */,
|
|
3652
|
+
targetId: paramId2,
|
|
3653
|
+
sampleTime: -1,
|
|
3654
|
+
argFloat: pan
|
|
3655
|
+
});
|
|
3656
|
+
}
|
|
3657
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3658
|
+
const paramId = engineMixerLaneTarget(laneIndex, ENGINE_MIXER_PARAM_PAN);
|
|
3659
|
+
this.offlineEngine.setParameter(paramId, pan);
|
|
3660
|
+
return this.realtimeNode.sendCommand({
|
|
3661
|
+
type: 1 /* SetParamSmoothed */,
|
|
3662
|
+
targetId: paramId,
|
|
3663
|
+
sampleTime: -1,
|
|
3664
|
+
argFloat: pan
|
|
3665
|
+
});
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Declares the mixer track lanes in an explicit order.
|
|
3669
|
+
*
|
|
3670
|
+
* Lane indices are append-only: once a track id occupies a lane, its index
|
|
3671
|
+
* stays fixed for the engine's lifetime. The given list must therefore start
|
|
3672
|
+
* with the already-declared lane ids in their current order and may only
|
|
3673
|
+
* append new track ids after them. Entries carrying `sends` replace that
|
|
3674
|
+
* track's send list; entries without `sends` leave existing sends untouched.
|
|
3675
|
+
*
|
|
3676
|
+
* @param lanes Track ids or lane descriptors in the desired lane order.
|
|
3677
|
+
*/
|
|
3678
|
+
setTrackLanes(lanes) {
|
|
3679
|
+
const entries = lanes.map((lane) => typeof lane === "number" ? { trackId: lane } : lane);
|
|
3680
|
+
const ids = [];
|
|
3681
|
+
for (const entry of entries) {
|
|
3682
|
+
if (!Number.isInteger(entry.trackId) || entry.trackId <= 0) {
|
|
3683
|
+
throw new Error(`Invalid track id for mixer lane: ${String(entry.trackId)}`);
|
|
3684
|
+
}
|
|
3685
|
+
ids.push(entry.trackId);
|
|
3686
|
+
}
|
|
3687
|
+
if (new Set(ids).size !== ids.length) {
|
|
3688
|
+
throw new Error("Duplicate track id in mixer lane list");
|
|
3689
|
+
}
|
|
3690
|
+
for (let index = 0; index < this.trackLaneIds.length; index++) {
|
|
3691
|
+
if (ids[index] !== this.trackLaneIds[index]) {
|
|
3692
|
+
throw new Error(
|
|
3693
|
+
"Mixer lanes are append-only: keep existing lanes in order and only append new track ids"
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
for (const entry of entries) {
|
|
3698
|
+
if (entry.sends) {
|
|
3699
|
+
this.trackSends.set(
|
|
3700
|
+
entry.trackId,
|
|
3701
|
+
entry.sends.map((send) => ({ ...send }))
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3704
|
+
if (entry.outputBusId !== void 0) {
|
|
3705
|
+
if (entry.outputBusId === 0) {
|
|
3706
|
+
this.trackOutputBus.delete(entry.trackId);
|
|
3707
|
+
} else {
|
|
3708
|
+
this.trackOutputBus.set(entry.trackId, entry.outputBusId);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
this.trackLaneIds.splice(0, this.trackLaneIds.length, ...ids);
|
|
3713
|
+
this.syncMixer();
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Routes a track lane's post-fader output into a declared bus instead of
|
|
3717
|
+
* the master mix (group/folder routing); busId 0 restores the master mix.
|
|
3718
|
+
*/
|
|
3719
|
+
setTrackOutputBus(target, busId) {
|
|
3720
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3721
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3722
|
+
if (busId === 0) {
|
|
3723
|
+
this.trackOutputBus.delete(trackId);
|
|
3724
|
+
} else {
|
|
3725
|
+
this.trackOutputBus.set(trackId, busId);
|
|
3726
|
+
}
|
|
3727
|
+
this.syncMixer();
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3730
|
+
* Keys one insert of a lane strip from another lane's post-strip pre-fader
|
|
3731
|
+
* audio (ducking/sidechainRouter inserts). sourceTarget null removes the
|
|
3732
|
+
* binding.
|
|
3733
|
+
*/
|
|
3734
|
+
setLaneSidechain(target, insertIndex, sourceTarget) {
|
|
3735
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3736
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3737
|
+
const key = `${trackId}:${insertIndex}`;
|
|
3738
|
+
let sourceTrackId = 0;
|
|
3739
|
+
if (sourceTarget !== null) {
|
|
3740
|
+
const sourceIndex = this.ensureTrackLane(sourceTarget);
|
|
3741
|
+
sourceTrackId = this.trackLaneIds[sourceIndex];
|
|
3742
|
+
}
|
|
3743
|
+
if (sourceTrackId === 0) {
|
|
3744
|
+
this.laneSidechains.delete(key);
|
|
3745
|
+
} else {
|
|
3746
|
+
this.laneSidechains.set(key, { trackId, insertIndex, sourceTrackId });
|
|
3747
|
+
}
|
|
3748
|
+
this.offlineEngine.setLaneSidechain(trackId, insertIndex, sourceTrackId);
|
|
3749
|
+
this.postSync({
|
|
3750
|
+
type: "syncMixer",
|
|
3751
|
+
lanes: this.mixerLanes(),
|
|
3752
|
+
laneSidechains: [{ trackId, insertIndex, sourceTrackId }]
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3755
|
+
setSends(target, sends) {
|
|
3756
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3757
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3758
|
+
this.trackSends.set(
|
|
3759
|
+
trackId,
|
|
3760
|
+
sends.map((send) => ({ ...send }))
|
|
2639
3761
|
);
|
|
3762
|
+
this.syncMixer();
|
|
3763
|
+
}
|
|
3764
|
+
setTrackBuses(buses) {
|
|
3765
|
+
this.buses.splice(0, this.buses.length, ...buses.map((bus) => ({ ...bus })));
|
|
3766
|
+
this.syncMixer();
|
|
3767
|
+
}
|
|
3768
|
+
setBusGain(busId, db) {
|
|
3769
|
+
const busIndex = this.ensureBus(busId);
|
|
3770
|
+
this.buses[busIndex] = { ...this.buses[busIndex], busId, gainDb: db };
|
|
3771
|
+
this.offlineEngine.setTrackBuses(this.buses);
|
|
3772
|
+
const paramId = engineMixerBusTarget(busIndex, ENGINE_MIXER_PARAM_FADER_DB);
|
|
3773
|
+
this.offlineEngine.setParameter(paramId, db);
|
|
3774
|
+
return this.realtimeNode.sendCommand({
|
|
3775
|
+
type: 1 /* SetParamSmoothed */,
|
|
3776
|
+
targetId: paramId,
|
|
3777
|
+
sampleTime: -1,
|
|
3778
|
+
argFloat: db
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
setTrackStripJson(target, sceneJson) {
|
|
3782
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3783
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3784
|
+
this.offlineEngine.setTrackStripJson(trackId, sceneJson);
|
|
3785
|
+
this.trackStripJson.set(trackId, sceneJson);
|
|
3786
|
+
this.syncMixer();
|
|
3787
|
+
}
|
|
3788
|
+
setTrackStripEqBand(target, bandIndex, band) {
|
|
3789
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3790
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3791
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3792
|
+
this.offlineEngine.setTrackStripEqBandJson(trackId, bandIndex, bandJson);
|
|
3793
|
+
this.postSync({ type: "syncTrackStripEqBand", trackId, bandIndex, bandJson });
|
|
3794
|
+
}
|
|
3795
|
+
setTrackStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass = false) {
|
|
3796
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3797
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3798
|
+
this.offlineEngine.setTrackStripInsertBypassed(trackId, insertIndex, bypassed, resetOnBypass);
|
|
3799
|
+
this.postSync({
|
|
3800
|
+
type: "syncTrackStripInsertBypassed",
|
|
3801
|
+
trackId,
|
|
3802
|
+
insertIndex,
|
|
3803
|
+
bypassed,
|
|
3804
|
+
resetOnBypass
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
setTrackStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
3808
|
+
const laneIndex = this.ensureTrackLane(target);
|
|
3809
|
+
const trackId = this.trackLaneIds[laneIndex];
|
|
3810
|
+
this.offlineEngine.setTrackStripInsertParamByName(trackId, insertIndex, paramName, value);
|
|
3811
|
+
this.postSync({
|
|
3812
|
+
type: "syncTrackStripInsertParamByName",
|
|
3813
|
+
trackId,
|
|
3814
|
+
insertIndex,
|
|
3815
|
+
paramName,
|
|
3816
|
+
value
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
setTrackStripPan(target, pan) {
|
|
3820
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3821
|
+
this.offlineEngine.setTrackStripPan(trackId, pan);
|
|
3822
|
+
this.postSync({ type: "syncTrackStripPan", trackId, pan });
|
|
3823
|
+
}
|
|
3824
|
+
setTrackStripPanLaw(target, panLaw) {
|
|
3825
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3826
|
+
const code = panLawCode(panLaw);
|
|
3827
|
+
this.offlineEngine.setTrackStripPanLaw(trackId, code);
|
|
3828
|
+
this.postSync({ type: "syncTrackStripPanLaw", trackId, panLaw: code });
|
|
3829
|
+
}
|
|
3830
|
+
setTrackStripPanMode(target, panMode) {
|
|
3831
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3832
|
+
const code = panModeCode(panMode);
|
|
3833
|
+
this.offlineEngine.setTrackStripPanMode(trackId, code);
|
|
3834
|
+
this.postSync({ type: "syncTrackStripPanMode", trackId, panMode: code });
|
|
3835
|
+
}
|
|
3836
|
+
setTrackStripDualPan(target, leftPan, rightPan) {
|
|
3837
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3838
|
+
this.offlineEngine.setTrackStripDualPan(trackId, leftPan, rightPan);
|
|
3839
|
+
this.postSync({ type: "syncTrackStripDualPan", trackId, leftPan, rightPan });
|
|
3840
|
+
}
|
|
3841
|
+
setTrackStripChannelDelaySamples(target, delaySamples) {
|
|
3842
|
+
const trackId = this.trackLaneIds[this.ensureTrackLane(target)];
|
|
3843
|
+
this.offlineEngine.setTrackStripChannelDelaySamples(trackId, delaySamples);
|
|
3844
|
+
this.postSync({ type: "syncTrackStripChannelDelaySamples", trackId, delaySamples });
|
|
3845
|
+
}
|
|
3846
|
+
setStripEq(target, bandIndex, band) {
|
|
3847
|
+
if (target === "master") {
|
|
3848
|
+
this.setMasterStripEqBand(bandIndex, band);
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
this.setTrackStripEqBand(target, bandIndex, band);
|
|
3852
|
+
}
|
|
3853
|
+
setStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass = false) {
|
|
3854
|
+
if (target === "master") {
|
|
3855
|
+
this.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
this.setTrackStripInsertBypassed(target, insertIndex, bypassed, resetOnBypass);
|
|
3859
|
+
}
|
|
3860
|
+
setStripInserts(target, sceneJson) {
|
|
3861
|
+
if (target === "master") {
|
|
3862
|
+
this.setMasterStripJson(sceneJson);
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
this.setTrackStripJson(target, sceneJson);
|
|
3866
|
+
}
|
|
3867
|
+
setBusStripJson(busId, sceneJson) {
|
|
3868
|
+
this.ensureBus(busId);
|
|
3869
|
+
this.offlineEngine.setBusStripJson(busId, sceneJson);
|
|
3870
|
+
this.busStripJson.set(busId, sceneJson);
|
|
3871
|
+
this.syncMixer();
|
|
3872
|
+
}
|
|
3873
|
+
setMasterStripJson(sceneJson) {
|
|
3874
|
+
this.offlineEngine.setMasterStripJson(sceneJson);
|
|
3875
|
+
this.masterStripJson = sceneJson;
|
|
3876
|
+
this.syncMixer();
|
|
3877
|
+
}
|
|
3878
|
+
setMasterStripEqBand(bandIndex, band) {
|
|
3879
|
+
const bandJson = typeof band === "string" ? band : JSON.stringify(band);
|
|
3880
|
+
this.offlineEngine.setMasterStripEqBandJson(bandIndex, bandJson);
|
|
3881
|
+
this.postSync({ type: "syncMasterStripEqBand", bandIndex, bandJson });
|
|
3882
|
+
}
|
|
3883
|
+
setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass = false) {
|
|
3884
|
+
this.offlineEngine.setMasterStripInsertBypassed(insertIndex, bypassed, resetOnBypass);
|
|
3885
|
+
this.postSync({
|
|
3886
|
+
type: "syncMasterStripInsertBypassed",
|
|
3887
|
+
insertIndex,
|
|
3888
|
+
bypassed,
|
|
3889
|
+
resetOnBypass
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
setMasterStripInsertParamByName(insertIndex, paramName, value) {
|
|
3893
|
+
this.offlineEngine.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
3894
|
+
this.postSync({
|
|
3895
|
+
type: "syncMasterStripInsertParamByName",
|
|
3896
|
+
insertIndex,
|
|
3897
|
+
paramName,
|
|
3898
|
+
value
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3901
|
+
setStripInsertParamByName(target, insertIndex, paramName, value) {
|
|
3902
|
+
if (target === "master") {
|
|
3903
|
+
this.setMasterStripInsertParamByName(insertIndex, paramName, value);
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
this.setTrackStripInsertParamByName(target, insertIndex, paramName, value);
|
|
3907
|
+
}
|
|
3908
|
+
setMasterChain(sceneJson) {
|
|
3909
|
+
this.setMasterStripJson(sceneJson);
|
|
2640
3910
|
}
|
|
2641
3911
|
addClip(trackId, buffer, startPpq, opts = {}) {
|
|
2642
3912
|
const id = opts.id ?? this.nextClipId++;
|
|
@@ -2644,18 +3914,108 @@ var SonareEngine = class _SonareEngine {
|
|
|
2644
3914
|
...opts,
|
|
2645
3915
|
id,
|
|
2646
3916
|
channels: buffer,
|
|
2647
|
-
startPpq
|
|
3917
|
+
startPpq,
|
|
3918
|
+
trackId: this.resolveTargetId(trackId)
|
|
2648
3919
|
};
|
|
3920
|
+
this.ensureTrackLane(trackId);
|
|
2649
3921
|
this.clips.set(id, clip);
|
|
2650
|
-
this.
|
|
2651
|
-
void trackId;
|
|
3922
|
+
this.syncClipsDelta([clip], []);
|
|
2652
3923
|
return id;
|
|
2653
3924
|
}
|
|
2654
3925
|
removeClip(clipId) {
|
|
2655
3926
|
this.clips.delete(clipId);
|
|
2656
|
-
this.
|
|
3927
|
+
this.syncClipsDelta([], [clipId]);
|
|
3928
|
+
}
|
|
3929
|
+
setMidiClips(clips) {
|
|
3930
|
+
this.midiClips.clear();
|
|
3931
|
+
for (const clip of clips) {
|
|
3932
|
+
const id = clip.id ?? this.nextClipId++;
|
|
3933
|
+
this.midiClips.set(id, { ...clip, id, events: clip.events.map((event) => ({ ...event })) });
|
|
3934
|
+
}
|
|
3935
|
+
this.syncMidiClips();
|
|
3936
|
+
}
|
|
3937
|
+
setBuiltinInstrument(trackId, config = {}) {
|
|
3938
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3939
|
+
this.offlineEngine.setBuiltinInstrument(config, destinationId);
|
|
3940
|
+
this.postInstrumentSync({ type: "syncBuiltinInstrument", destinationId, config });
|
|
3941
|
+
}
|
|
3942
|
+
setSynthInstrument(trackId, patch = {}) {
|
|
3943
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3944
|
+
this.offlineEngine.setSynthInstrument(patch, destinationId);
|
|
3945
|
+
this.postInstrumentSync({ type: "syncSynthInstrument", destinationId, patch });
|
|
3946
|
+
}
|
|
3947
|
+
loadSoundFont(data) {
|
|
3948
|
+
this.offlineEngine.loadSoundFont(data);
|
|
3949
|
+
this.postInstrumentSync({ type: "syncLoadSoundFont", data });
|
|
3950
|
+
}
|
|
3951
|
+
setSf2Instrument(trackId, config = {}) {
|
|
3952
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3953
|
+
this.offlineEngine.setSf2Instrument(config, destinationId);
|
|
3954
|
+
this.postInstrumentSync({ type: "syncSf2Instrument", destinationId, config });
|
|
3955
|
+
}
|
|
3956
|
+
pushMidiNoteOn(trackId, group, channel, note, velocity, renderFrame = -1) {
|
|
3957
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3958
|
+
this.offlineEngine.pushMidiNoteOn(destinationId, group, channel, note, velocity, renderFrame);
|
|
3959
|
+
this.postSync({
|
|
3960
|
+
type: "syncMidiNoteOn",
|
|
3961
|
+
destinationId,
|
|
3962
|
+
group,
|
|
3963
|
+
channel,
|
|
3964
|
+
note,
|
|
3965
|
+
velocity,
|
|
3966
|
+
renderFrame
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3969
|
+
pushMidiNoteOff(trackId, group, channel, note, velocity = 0, renderFrame = -1) {
|
|
3970
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3971
|
+
this.offlineEngine.pushMidiNoteOff(destinationId, group, channel, note, velocity, renderFrame);
|
|
3972
|
+
this.postSync({
|
|
3973
|
+
type: "syncMidiNoteOff",
|
|
3974
|
+
destinationId,
|
|
3975
|
+
group,
|
|
3976
|
+
channel,
|
|
3977
|
+
note,
|
|
3978
|
+
velocity,
|
|
3979
|
+
renderFrame
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
pushMidiCc(trackId, group, channel, controller, value, renderFrame = -1) {
|
|
3983
|
+
const destinationId = this.resolveTargetId(trackId);
|
|
3984
|
+
this.offlineEngine.pushMidiCc(destinationId, group, channel, controller, value, renderFrame);
|
|
3985
|
+
this.postSync({
|
|
3986
|
+
type: "syncMidiCc",
|
|
3987
|
+
destinationId,
|
|
3988
|
+
group,
|
|
3989
|
+
channel,
|
|
3990
|
+
controller,
|
|
3991
|
+
value,
|
|
3992
|
+
renderFrame
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
pushMidiPanic(renderFrame = -1) {
|
|
3996
|
+
this.offlineEngine.pushMidiPanic(renderFrame);
|
|
3997
|
+
this.postSync({ type: "syncMidiPanic", renderFrame });
|
|
3998
|
+
}
|
|
3999
|
+
configureCapture(options) {
|
|
4000
|
+
const bufferFrames = Math.trunc(options.bufferFrames);
|
|
4001
|
+
const channels = Math.trunc(options.channels ?? this.offlineChannelCount);
|
|
4002
|
+
const source = options.source ?? "output";
|
|
4003
|
+
const recordOffsetSamples = Math.trunc(options.recordOffsetSamples ?? 0);
|
|
4004
|
+
const inputMonitor = {
|
|
4005
|
+
enabled: Boolean(options.inputMonitor?.enabled),
|
|
4006
|
+
gain: options.inputMonitor?.gain ?? 1
|
|
4007
|
+
};
|
|
4008
|
+
this.offlineEngine.setCaptureBuffer(channels, bufferFrames);
|
|
4009
|
+
this.offlineEngine.setCaptureSource(source);
|
|
4010
|
+
this.offlineEngine.setRecordOffsetSamples(recordOffsetSamples);
|
|
4011
|
+
this.offlineEngine.setInputMonitor(inputMonitor.enabled, inputMonitor.gain);
|
|
4012
|
+
this.captureConfig = { bufferFrames, channels, source, recordOffsetSamples, inputMonitor };
|
|
4013
|
+
this.postSync({ type: "syncCapture", ...this.captureConfig });
|
|
2657
4014
|
}
|
|
2658
4015
|
armRecord(trackId, enabled) {
|
|
4016
|
+
if (enabled && !this.captureConfig) {
|
|
4017
|
+
throw new Error("Capture buffer is not configured");
|
|
4018
|
+
}
|
|
2659
4019
|
this.offlineEngine.armCapture(enabled);
|
|
2660
4020
|
return this.realtimeNode.sendCommand({
|
|
2661
4021
|
type: 13 /* ArmRecord */,
|
|
@@ -2665,8 +4025,8 @@ var SonareEngine = class _SonareEngine {
|
|
|
2665
4025
|
});
|
|
2666
4026
|
}
|
|
2667
4027
|
punch(inPpq, outPpq) {
|
|
2668
|
-
const inSample = this.
|
|
2669
|
-
const outSample = this.
|
|
4028
|
+
const inSample = this.offlineEngine.sampleAtPpq(inPpq);
|
|
4029
|
+
const outSample = this.offlineEngine.sampleAtPpq(outPpq);
|
|
2670
4030
|
this.offlineEngine.setCapturePunch(inSample, outSample, true);
|
|
2671
4031
|
return this.realtimeNode.sendCommand({
|
|
2672
4032
|
type: 14 /* Punch */,
|
|
@@ -2675,6 +4035,16 @@ var SonareEngine = class _SonareEngine {
|
|
|
2675
4035
|
argFloat: outSample
|
|
2676
4036
|
});
|
|
2677
4037
|
}
|
|
4038
|
+
captureStatus() {
|
|
4039
|
+
return this.realtimeNode.requestCaptureStatus();
|
|
4040
|
+
}
|
|
4041
|
+
capturedAudio() {
|
|
4042
|
+
return this.realtimeNode.requestCapturedAudio();
|
|
4043
|
+
}
|
|
4044
|
+
async resetCapture() {
|
|
4045
|
+
this.offlineEngine.resetCapture();
|
|
4046
|
+
await this.realtimeNode.requestCaptureReset();
|
|
4047
|
+
}
|
|
2678
4048
|
setMetronome(opts) {
|
|
2679
4049
|
this.offlineEngine.setMetronome(opts);
|
|
2680
4050
|
this.postSync({ type: "syncMetronome", config: opts });
|
|
@@ -2690,6 +4060,55 @@ var SonareEngine = class _SonareEngine {
|
|
|
2690
4060
|
this.syncMarkers();
|
|
2691
4061
|
return id;
|
|
2692
4062
|
}
|
|
4063
|
+
/**
|
|
4064
|
+
* Replaces the whole marker set in one call.
|
|
4065
|
+
*
|
|
4066
|
+
* Entries without an `id` are assigned fresh ids; entries carrying an `id`
|
|
4067
|
+
* keep it (ids must be positive and unique within the list). Returns the
|
|
4068
|
+
* resolved markers in the order given, so a caller can map its own marker
|
|
4069
|
+
* identities to the engine ids used by `seekMarker`/`setLoopFromMarkers`.
|
|
4070
|
+
*
|
|
4071
|
+
* @param markers The full marker list (an empty list clears all markers).
|
|
4072
|
+
* @returns The markers with their resolved engine ids.
|
|
4073
|
+
*/
|
|
4074
|
+
setMarkers(markers) {
|
|
4075
|
+
const resolved = [];
|
|
4076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4077
|
+
for (const marker of markers) {
|
|
4078
|
+
if (!Number.isFinite(marker.ppq)) {
|
|
4079
|
+
throw new Error(`Invalid marker ppq: ${String(marker.ppq)}`);
|
|
4080
|
+
}
|
|
4081
|
+
if (marker.id !== void 0) {
|
|
4082
|
+
if (!Number.isInteger(marker.id) || marker.id <= 0) {
|
|
4083
|
+
throw new Error(`Invalid marker id: ${String(marker.id)}`);
|
|
4084
|
+
}
|
|
4085
|
+
if (seen.has(marker.id)) {
|
|
4086
|
+
throw new Error(`Duplicate marker id: ${marker.id}`);
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
const id = marker.id ?? this.nextMarkerId++;
|
|
4090
|
+
seen.add(id);
|
|
4091
|
+
if (id >= this.nextMarkerId) {
|
|
4092
|
+
this.nextMarkerId = id + 1;
|
|
4093
|
+
}
|
|
4094
|
+
resolved.push({ id, ppq: marker.ppq, name: marker.name ?? "" });
|
|
4095
|
+
}
|
|
4096
|
+
this.markers.clear();
|
|
4097
|
+
for (const marker of resolved) {
|
|
4098
|
+
this.markers.set(marker.id, marker);
|
|
4099
|
+
}
|
|
4100
|
+
this.syncMarkers();
|
|
4101
|
+
return resolved.map((marker) => ({ ...marker }));
|
|
4102
|
+
}
|
|
4103
|
+
markerCount() {
|
|
4104
|
+
return this.offlineEngine.markerCount();
|
|
4105
|
+
}
|
|
4106
|
+
markerByIndex(index) {
|
|
4107
|
+
return this.offlineEngine.markerByIndex(index);
|
|
4108
|
+
}
|
|
4109
|
+
marker(markerId) {
|
|
4110
|
+
return this.offlineEngine.marker(markerId);
|
|
4111
|
+
}
|
|
2693
4112
|
seekMarker(markerId) {
|
|
2694
4113
|
this.offlineEngine.seekMarker(markerId);
|
|
2695
4114
|
return this.realtimeNode.sendCommand({
|
|
@@ -2698,6 +4117,12 @@ var SonareEngine = class _SonareEngine {
|
|
|
2698
4117
|
sampleTime: -1
|
|
2699
4118
|
});
|
|
2700
4119
|
}
|
|
4120
|
+
setLoopFromMarkers(startMarkerId, endMarkerId) {
|
|
4121
|
+
this.offlineEngine.setLoopFromMarkers(startMarkerId, endMarkerId);
|
|
4122
|
+
const start = this.offlineEngine.marker(startMarkerId);
|
|
4123
|
+
const end = this.offlineEngine.marker(endMarkerId);
|
|
4124
|
+
return this.setLoop(start.ppq, end.ppq, true);
|
|
4125
|
+
}
|
|
2701
4126
|
async renderOffline(totalFrames) {
|
|
2702
4127
|
const frames = Math.max(0, Math.floor(totalFrames));
|
|
2703
4128
|
const inputs = [];
|
|
@@ -2709,6 +4134,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
2709
4134
|
onMeter(callback) {
|
|
2710
4135
|
return this.realtimeNode.onMeter(callback);
|
|
2711
4136
|
}
|
|
4137
|
+
onScope(callback) {
|
|
4138
|
+
return this.realtimeNode.onScope(callback);
|
|
4139
|
+
}
|
|
2712
4140
|
onTelemetry(callback) {
|
|
2713
4141
|
return this.realtimeNode.onTelemetry(callback);
|
|
2714
4142
|
}
|
|
@@ -2718,6 +4146,9 @@ var SonareEngine = class _SonareEngine {
|
|
|
2718
4146
|
pollMeters() {
|
|
2719
4147
|
return this.realtimeNode.pollMeters();
|
|
2720
4148
|
}
|
|
4149
|
+
pollScope() {
|
|
4150
|
+
return this.realtimeNode.pollScope();
|
|
4151
|
+
}
|
|
2721
4152
|
destroy() {
|
|
2722
4153
|
if (this.destroyed) {
|
|
2723
4154
|
return;
|
|
@@ -2728,16 +4159,89 @@ var SonareEngine = class _SonareEngine {
|
|
|
2728
4159
|
this.realtimeNode.destroy();
|
|
2729
4160
|
this.offlineEngine.destroy();
|
|
2730
4161
|
}
|
|
2731
|
-
|
|
4162
|
+
syncClipsDelta(upserts, removeIds) {
|
|
2732
4163
|
const clips = Array.from(this.clips.values());
|
|
2733
4164
|
this.offlineEngine.setClips(clips);
|
|
2734
|
-
this.postSync({
|
|
4165
|
+
this.postSync({
|
|
4166
|
+
type: "syncClipsDelta",
|
|
4167
|
+
upserts,
|
|
4168
|
+
removeIds
|
|
4169
|
+
});
|
|
4170
|
+
}
|
|
4171
|
+
syncMidiClips() {
|
|
4172
|
+
const clips = Array.from(this.midiClips.values());
|
|
4173
|
+
this.offlineEngine.setMidiClips(clips);
|
|
4174
|
+
this.postSync({ type: "syncMidiClips", clips });
|
|
4175
|
+
}
|
|
4176
|
+
mixerLanes() {
|
|
4177
|
+
return this.trackLaneIds.map((trackId) => {
|
|
4178
|
+
const sends = this.trackSends.get(trackId);
|
|
4179
|
+
const outputBusId = this.trackOutputBus.get(trackId);
|
|
4180
|
+
return {
|
|
4181
|
+
trackId,
|
|
4182
|
+
...sends && sends.length > 0 ? { sends: sends.map((send) => ({ ...send })) } : {},
|
|
4183
|
+
...outputBusId !== void 0 ? { outputBusId } : {}
|
|
4184
|
+
};
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
syncMixer() {
|
|
4188
|
+
const lanes = this.mixerLanes();
|
|
4189
|
+
const buses = this.buses.map((bus) => ({ ...bus }));
|
|
4190
|
+
this.offlineEngine.setTrackBuses(buses);
|
|
4191
|
+
if (lanes.length > 0) {
|
|
4192
|
+
this.offlineEngine.setTrackLanes(lanes);
|
|
4193
|
+
}
|
|
4194
|
+
const trackStrips = Array.from(this.trackStripJson, ([trackId, sceneJson]) => ({
|
|
4195
|
+
trackId,
|
|
4196
|
+
sceneJson
|
|
4197
|
+
}));
|
|
4198
|
+
const busStrips = Array.from(this.busStripJson, ([busId, sceneJson]) => ({
|
|
4199
|
+
busId,
|
|
4200
|
+
sceneJson
|
|
4201
|
+
}));
|
|
4202
|
+
this.postSync({
|
|
4203
|
+
type: "syncMixer",
|
|
4204
|
+
lanes,
|
|
4205
|
+
buses,
|
|
4206
|
+
trackStrips,
|
|
4207
|
+
laneSidechains: Array.from(this.laneSidechains.values()),
|
|
4208
|
+
busStrips,
|
|
4209
|
+
masterStripJson: this.masterStripJson
|
|
4210
|
+
});
|
|
2735
4211
|
}
|
|
2736
4212
|
syncMarkers() {
|
|
2737
4213
|
const markers = Array.from(this.markers.values()).sort((a, b) => a.ppq - b.ppq);
|
|
2738
4214
|
this.offlineEngine.setMarkers(markers);
|
|
2739
4215
|
this.postSync({ type: "syncMarkers", markers });
|
|
2740
4216
|
}
|
|
4217
|
+
postInstrumentSync(message) {
|
|
4218
|
+
if (this.destroyed) {
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
if (this.transportPlaying) {
|
|
4222
|
+
this.pendingInstrumentSync.push(message);
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
this.postSync(message);
|
|
4226
|
+
}
|
|
4227
|
+
flushPendingInstrumentSync() {
|
|
4228
|
+
if (this.destroyed || this.pendingInstrumentSync.length === 0) {
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
const pending = this.pendingInstrumentSync.splice(0);
|
|
4232
|
+
for (const message of pending) {
|
|
4233
|
+
this.postSync(message);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
postTempoSync() {
|
|
4237
|
+
this.postSync({
|
|
4238
|
+
type: "syncTempo",
|
|
4239
|
+
bpm: this.tempoBpm,
|
|
4240
|
+
timeSignature: { ...this.timeSignature },
|
|
4241
|
+
tempoSegments: this.tempoSegments.map((segment) => ({ ...segment })),
|
|
4242
|
+
timeSignatureSegments: this.timeSignatureSegments.map((segment) => ({ ...segment }))
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
2741
4245
|
// Posts an out-of-band control-sync message to the worklet engine processor.
|
|
2742
4246
|
// Sync messages use a string `type` so the worklet's message handler routes
|
|
2743
4247
|
// them to receiveSync() (numeric `type` is reserved for SonareEngineCommandRecord).
|
|
@@ -2764,15 +4268,38 @@ var SonareEngine = class _SonareEngine {
|
|
|
2764
4268
|
const parsed = Number.parseInt(target, 10);
|
|
2765
4269
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
2766
4270
|
}
|
|
4271
|
+
ensureTrackLane(target) {
|
|
4272
|
+
const trackId = this.resolveTargetId(target);
|
|
4273
|
+
if (!Number.isInteger(trackId) || trackId <= 0) {
|
|
4274
|
+
throw new Error(`Invalid track id for mixer lane: ${String(target)}`);
|
|
4275
|
+
}
|
|
4276
|
+
const existing = this.trackLaneIds.indexOf(trackId);
|
|
4277
|
+
if (existing >= 0) {
|
|
4278
|
+
return existing;
|
|
4279
|
+
}
|
|
4280
|
+
this.trackLaneIds.push(trackId);
|
|
4281
|
+
this.syncMixer();
|
|
4282
|
+
return this.trackLaneIds.length - 1;
|
|
4283
|
+
}
|
|
4284
|
+
ensureBus(busId) {
|
|
4285
|
+
const resolved = Math.trunc(busId);
|
|
4286
|
+
if (!Number.isInteger(resolved) || resolved <= 0) {
|
|
4287
|
+
throw new Error(`Invalid bus id for mixer bus: ${String(busId)}`);
|
|
4288
|
+
}
|
|
4289
|
+
const existing = this.buses.findIndex((bus) => bus.busId === resolved);
|
|
4290
|
+
if (existing >= 0) {
|
|
4291
|
+
return existing;
|
|
4292
|
+
}
|
|
4293
|
+
this.buses.push({ busId: resolved });
|
|
4294
|
+
this.syncMixer();
|
|
4295
|
+
return this.buses.length - 1;
|
|
4296
|
+
}
|
|
2767
4297
|
curveCode(curve) {
|
|
2768
4298
|
if (typeof curve === "number") {
|
|
2769
4299
|
return curve;
|
|
2770
4300
|
}
|
|
2771
4301
|
return curve === "exponential" ? 1 : 0;
|
|
2772
4302
|
}
|
|
2773
|
-
ppqToApproxSample(ppq) {
|
|
2774
|
-
return Math.max(0, Math.round(ppq * 60 / 120 * this.sampleRate));
|
|
2775
|
-
}
|
|
2776
4303
|
};
|
|
2777
4304
|
var _SonareRealtimeVoiceChangerWorkletProcessor = class _SonareRealtimeVoiceChangerWorkletProcessor {
|
|
2778
4305
|
constructor(options = {}) {
|
|
@@ -2993,23 +4520,33 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
2993
4520
|
class RegisteredSonareRealtimeEngineWorkletProcessor extends Base {
|
|
2994
4521
|
constructor(options) {
|
|
2995
4522
|
super();
|
|
4523
|
+
this.pendingMessages = [];
|
|
2996
4524
|
const port = this.port;
|
|
2997
4525
|
const processorOptions = options?.processorOptions ?? {};
|
|
2998
4526
|
if (processorOptions.runtimeTarget === "sonare-rt") {
|
|
2999
4527
|
void this.initializeSonareRt(processorOptions, port);
|
|
3000
4528
|
} else {
|
|
3001
|
-
this.
|
|
3002
|
-
postMessage: (message) => port?.postMessage?.(message),
|
|
3003
|
-
onMeter: (meter) => port?.postMessage?.(meter)
|
|
3004
|
-
});
|
|
4529
|
+
void this.initializeEmbind(processorOptions, port);
|
|
3005
4530
|
}
|
|
3006
4531
|
const onMessage = (event) => {
|
|
4532
|
+
if (!this.bridge && !this.rtBridge) {
|
|
4533
|
+
if (this.pendingMessages.length < 1024) {
|
|
4534
|
+
this.pendingMessages.push(event.data);
|
|
4535
|
+
}
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
3007
4538
|
if (isEngineCommandRecord(event.data)) {
|
|
3008
4539
|
this.bridge?.receiveCommand(event.data);
|
|
3009
4540
|
this.rtBridge?.receiveCommand(event.data);
|
|
3010
4541
|
} else if (isEngineSyncMessage(event.data)) {
|
|
3011
4542
|
this.bridge?.receiveSync(event.data);
|
|
3012
4543
|
this.rtBridge?.receiveSync(event.data);
|
|
4544
|
+
} else if (isEngineCaptureRequestMessage(event.data)) {
|
|
4545
|
+
this.bridge?.receiveCaptureRequest(event.data);
|
|
4546
|
+
this.rtBridge?.receiveCaptureRequest(event.data, port);
|
|
4547
|
+
} else if (isEngineTransportRequestMessage(event.data)) {
|
|
4548
|
+
this.bridge?.receiveTransportRequest(event.data);
|
|
4549
|
+
this.rtBridge?.receiveTransportRequest(event.data, port);
|
|
3013
4550
|
}
|
|
3014
4551
|
};
|
|
3015
4552
|
if (port?.addEventListener) {
|
|
@@ -3032,6 +4569,60 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
3032
4569
|
}
|
|
3033
4570
|
return true;
|
|
3034
4571
|
}
|
|
4572
|
+
replayPendingMessages(port) {
|
|
4573
|
+
const messages = this.pendingMessages.splice(0);
|
|
4574
|
+
for (const data of messages) {
|
|
4575
|
+
if (isEngineCommandRecord(data)) {
|
|
4576
|
+
this.bridge?.receiveCommand(data);
|
|
4577
|
+
this.rtBridge?.receiveCommand(data);
|
|
4578
|
+
} else if (isEngineSyncMessage(data)) {
|
|
4579
|
+
this.bridge?.receiveSync(data);
|
|
4580
|
+
this.rtBridge?.receiveSync(data);
|
|
4581
|
+
} else if (isEngineCaptureRequestMessage(data)) {
|
|
4582
|
+
this.bridge?.receiveCaptureRequest(data);
|
|
4583
|
+
this.rtBridge?.receiveCaptureRequest(data, port);
|
|
4584
|
+
} else if (isEngineTransportRequestMessage(data)) {
|
|
4585
|
+
this.bridge?.receiveTransportRequest(data);
|
|
4586
|
+
this.rtBridge?.receiveTransportRequest(data, port);
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
async initializeEmbind(options, port) {
|
|
4591
|
+
try {
|
|
4592
|
+
const initPromise2 = globalThis.SonareEmbindInitPromise;
|
|
4593
|
+
if (initPromise2) {
|
|
4594
|
+
await initPromise2;
|
|
4595
|
+
}
|
|
4596
|
+
if (!isInitialized()) {
|
|
4597
|
+
const moduleFactory = globalThis.SonareEmbindModuleFactory;
|
|
4598
|
+
if (!moduleFactory) {
|
|
4599
|
+
throw new Error("embind realtime engine module is not initialized.");
|
|
4600
|
+
}
|
|
4601
|
+
await init({
|
|
4602
|
+
locateFile: (path) => path,
|
|
4603
|
+
wasmBinary: options.wasmBinary,
|
|
4604
|
+
moduleFactory
|
|
4605
|
+
});
|
|
4606
|
+
}
|
|
4607
|
+
this.bridge = new SonareRealtimeEngineWorkletProcessor(options, {
|
|
4608
|
+
postMessage: (message) => port?.postMessage?.(message),
|
|
4609
|
+
onMeter: (meter) => port?.postMessage?.(meter)
|
|
4610
|
+
});
|
|
4611
|
+
for (const message of options.initialSyncMessages ?? []) {
|
|
4612
|
+
this.bridge.receiveSync(message);
|
|
4613
|
+
}
|
|
4614
|
+
for (const command of options.initialCommands ?? []) {
|
|
4615
|
+
this.bridge.receiveCommand(command);
|
|
4616
|
+
}
|
|
4617
|
+
this.replayPendingMessages(port);
|
|
4618
|
+
port?.postMessage?.({ type: "ready", runtimeTarget: "embind" });
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
port?.postMessage?.({
|
|
4621
|
+
type: "error",
|
|
4622
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
3035
4626
|
async initializeSonareRt(options, port) {
|
|
3036
4627
|
try {
|
|
3037
4628
|
if (!options.rtModuleUrl) {
|
|
@@ -3057,6 +4648,7 @@ function registerSonareRealtimeEngineWorkletProcessor(name = "sonare-realtime-en
|
|
|
3057
4648
|
telemetrySharedBuffer: options.telemetrySharedBuffer,
|
|
3058
4649
|
telemetryRingCapacity: options.telemetryRingCapacity
|
|
3059
4650
|
});
|
|
4651
|
+
this.replayPendingMessages(port);
|
|
3060
4652
|
port?.postMessage?.({ type: "ready", runtimeTarget: "sonare-rt" });
|
|
3061
4653
|
} catch (error) {
|
|
3062
4654
|
port?.postMessage?.({
|
|
@@ -3074,6 +4666,7 @@ export {
|
|
|
3074
4666
|
SONARE_ENGINE_TELEMETRY_RECORD_BYTES,
|
|
3075
4667
|
SONARE_METER_RING_HEADER_INTS,
|
|
3076
4668
|
SONARE_METER_RING_RECORD_FLOATS,
|
|
4669
|
+
SONARE_SCOPE_RING_HEADER_INTS,
|
|
3077
4670
|
SONARE_SPECTRUM_RING_HEADER_INTS,
|
|
3078
4671
|
SonareEngine,
|
|
3079
4672
|
SonareEngineCommandType,
|
|
@@ -3087,6 +4680,7 @@ export {
|
|
|
3087
4680
|
createSonareEngineCommandRingBuffer,
|
|
3088
4681
|
createSonareEngineTelemetryRingBuffer,
|
|
3089
4682
|
createSonareMeterRingBuffer,
|
|
4683
|
+
createSonareScopeRingBuffer,
|
|
3090
4684
|
createSonareSpectrumRingBuffer,
|
|
3091
4685
|
decodeFrame,
|
|
3092
4686
|
encodeFrameHi,
|
|
@@ -3097,6 +4691,7 @@ export {
|
|
|
3097
4691
|
pushSonareEngineCommandRingBuffer,
|
|
3098
4692
|
readSonareEngineTelemetryRingBuffer,
|
|
3099
4693
|
readSonareMeterRingBuffer,
|
|
4694
|
+
readSonareScopeRingBuffer,
|
|
3100
4695
|
readSonareSpectrumRingBuffer,
|
|
3101
4696
|
registerSonareRealtimeEngineWorkletProcessor,
|
|
3102
4697
|
registerSonareRealtimeVoiceChangerWorkletProcessor,
|
|
@@ -3104,6 +4699,7 @@ export {
|
|
|
3104
4699
|
sonareEngineCommandRingBufferByteLength,
|
|
3105
4700
|
sonareEngineTelemetryRingBufferByteLength,
|
|
3106
4701
|
sonareMeterRingBufferByteLength,
|
|
4702
|
+
sonareScopeRingBufferByteLength,
|
|
3107
4703
|
sonareSpectrumRingBufferByteLength,
|
|
3108
4704
|
writeSonareEngineTelemetryRingBuffer
|
|
3109
4705
|
};
|