@djodjonx/x32-simulator 0.0.6 → 0.0.7

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.
@@ -1,32 +1,8 @@
1
- import * as os from "os";
2
1
  import { EventEmitter } from "node:events";
2
+ import * as os from "os";
3
3
  import * as dgram from "node:dgram";
4
4
  import { Buffer as Buffer$1 } from "node:buffer";
5
5
 
6
- //#region src/application/use-cases/ProcessPacketUseCase.ts
7
- var ProcessPacketUseCase = class {
8
- constructor(messageHandler, gateway) {
9
- this.messageHandler = messageHandler;
10
- this.gateway = gateway;
11
- }
12
- execute(packet, rinfo) {
13
- if (packet.oscType === "bundle") packet.elements?.forEach((el) => this.execute(el, rinfo));
14
- else if (packet.oscType === "message") {
15
- const args = packet.args.map((arg) => {
16
- if (typeof arg === "object" && arg !== null && "value" in arg) return arg.value;
17
- return arg;
18
- });
19
- this.messageHandler.handle({
20
- address: packet.address,
21
- args
22
- }, rinfo).forEach((reply) => {
23
- this.gateway.send(rinfo, reply.address, reply.args);
24
- });
25
- }
26
- }
27
- };
28
-
29
- //#endregion
30
6
  //#region src/domain/ports/ILogger.ts
31
7
  /**
32
8
  * Standard log categories for the domain.
@@ -42,6 +18,35 @@ let LogCategory = /* @__PURE__ */ function(LogCategory$1) {
42
18
  return LogCategory$1;
43
19
  }({});
44
20
 
21
+ //#endregion
22
+ //#region src/application/use-cases/ProcessPacketUseCase.ts
23
+ var ProcessPacketUseCase = class {
24
+ constructor(messageHandler, gateway, logger) {
25
+ this.messageHandler = messageHandler;
26
+ this.gateway = gateway;
27
+ this.logger = logger;
28
+ }
29
+ execute(packet, rinfo) {
30
+ try {
31
+ if (packet.oscType === "bundle") packet.elements?.forEach((el) => this.execute(el, rinfo));
32
+ else if (packet.oscType === "message") {
33
+ const args = packet.args.map((arg) => {
34
+ if (typeof arg === "object" && arg !== null && "value" in arg) return arg.value;
35
+ return arg;
36
+ });
37
+ this.messageHandler.handle({
38
+ address: packet.address,
39
+ args
40
+ }, rinfo).forEach((reply) => {
41
+ this.gateway.send(rinfo, reply.address, reply.args);
42
+ });
43
+ }
44
+ } catch (err) {
45
+ this.logger?.error(LogCategory.DISPATCH, `Error processing packet from ${rinfo.address}:${rinfo.port}`, err instanceof Error ? err : new Error(String(err)));
46
+ }
47
+ }
48
+ };
49
+
45
50
  //#endregion
46
51
  //#region src/application/use-cases/BroadcastUpdatesUseCase.ts
47
52
  var BroadcastUpdatesUseCase = class {
@@ -159,16 +164,134 @@ var ManageSessionsUseCase = class {
159
164
  }
160
165
  };
161
166
 
167
+ //#endregion
168
+ //#region src/domain/entities/X32State.ts
169
+ /**
170
+ * Manages the internal "Digital Twin" state of the X32 console.
171
+ * It acts as a single source of truth for all parameters.
172
+ * Emits 'change' events whenever a value is updated.
173
+ * Uses an injectable repository for storage (memory or persistent).
174
+ */
175
+ var X32State = class extends EventEmitter {
176
+ defaultState = /* @__PURE__ */ new Map();
177
+ /**
178
+ * Initializes the state with default values from the schema.
179
+ * @param schema - The schema definition map.
180
+ * @param repository - The storage repository (injectable).
181
+ */
182
+ constructor(schema, repository) {
183
+ super();
184
+ this.repository = repository;
185
+ for (const [addr, def] of Object.entries(schema)) this.defaultState.set(addr, def.default);
186
+ }
187
+ /**
188
+ * Initializes the state - loads from repository or resets to defaults.
189
+ */
190
+ async initialize() {
191
+ const stored = await this.repository.load();
192
+ if (Object.keys(stored).length === 0) this.reset();
193
+ }
194
+ /**
195
+ * Resets all state parameters to their default values defined in the schema.
196
+ */
197
+ reset() {
198
+ const defaults = {};
199
+ this.defaultState.forEach((val, key) => {
200
+ defaults[key] = val;
201
+ });
202
+ this.repository.reset(defaults);
203
+ }
204
+ /**
205
+ * Retrieves the value of a specific OSC node.
206
+ * @param address - The full OSC address path.
207
+ * @returns The stored value (number or string) or undefined if not found.
208
+ */
209
+ get(address) {
210
+ return this.repository.get(address);
211
+ }
212
+ /**
213
+ * Retrieves all state entries as a plain object.
214
+ * @returns A record of all OSC addresses and their current values.
215
+ */
216
+ getAll() {
217
+ return this.repository.getAll();
218
+ }
219
+ /**
220
+ * Updates the value of a specific OSC node and notifies subscribers.
221
+ * @param address - The full OSC address path.
222
+ * @param value - The new value to store.
223
+ */
224
+ set(address, value) {
225
+ this.repository.set(address, value);
226
+ this.emit("change", {
227
+ address,
228
+ value
229
+ });
230
+ }
231
+ /**
232
+ * Flushes pending changes to persistent storage.
233
+ */
234
+ async flush() {
235
+ await this.repository.flush();
236
+ }
237
+ /**
238
+ * Specialized logic to handle X32 Mute Groups side-effects.
239
+ * When a mute group is toggled, it iterates through all channels
240
+ * assigned to that group and updates their individual mute status.
241
+ * @param groupIdx - The index of the mute group (1-6).
242
+ * @param isOn - The new state of the group master switch (0 or 1).
243
+ */
244
+ handleMuteGroupChange(groupIdx, isOn) {
245
+ for (let i = 1; i <= 32; i++) {
246
+ const ch = i.toString().padStart(2, "0");
247
+ const grpVal = this.get(`/ch/${ch}/grp/mute`);
248
+ if (typeof grpVal === "number") {
249
+ if ((grpVal & 1 << groupIdx - 1) !== 0) {
250
+ const targetMute = isOn === 1 ? 0 : 1;
251
+ const muteAddr = `/ch/${ch}/mix/on`;
252
+ this.set(muteAddr, targetMute);
253
+ }
254
+ }
255
+ }
256
+ }
257
+ };
258
+
259
+ //#endregion
260
+ //#region src/domain/models/Config.ts
261
+ /**
262
+ * Default configuration values for the simulator.
263
+ */
264
+ const DEFAULT_CONFIG = {
265
+ SUBSCRIPTION_TTL_MS: 1e4,
266
+ CLEANUP_INTERVAL_MS: 5e3,
267
+ BROADCAST_INTERVAL_MS: 100,
268
+ DEFAULT_PORT: 10023,
269
+ DEFAULT_HOST: "0.0.0.0",
270
+ DEFAULT_NAME: "osc-server",
271
+ DEFAULT_MODEL: "X32"
272
+ };
273
+ /**
274
+ * Merges user-provided configuration with defaults.
275
+ * @param partial - Partial configuration to merge.
276
+ * @returns Complete configuration with defaults applied.
277
+ */
278
+ function mergeConfig(partial) {
279
+ return {
280
+ ...DEFAULT_CONFIG,
281
+ ...partial
282
+ };
283
+ }
284
+
162
285
  //#endregion
163
286
  //#region src/domain/entities/SubscriptionManager.ts
164
287
  /**
165
288
  * Manages OSC client subscriptions and their lifecycle.
166
289
  */
167
290
  var SubscriptionManager = class {
168
- logger;
169
291
  subscribers = [];
170
- constructor(logger) {
292
+ constructor(logger, subscriptionTtlMs = DEFAULT_CONFIG.SUBSCRIPTION_TTL_MS) {
171
293
  this.logger = logger;
294
+ this.subscriptionTtlMs = subscriptionTtlMs;
172
295
  }
173
296
  /**
174
297
  * Cleans up expired subscriptions.
@@ -206,7 +329,7 @@ var SubscriptionManager = class {
206
329
  */
207
330
  addPathSubscriber(rinfo, path) {
208
331
  const key = `${rinfo.address}:${rinfo.port}:${path}`;
209
- const expires = Date.now() + 1e4;
332
+ const expires = Date.now() + this.subscriptionTtlMs;
210
333
  const existing = this.subscribers.find((s) => s.type === "path" && `${s.address}:${s.port}:${s.path}` === key);
211
334
  if (existing) {
212
335
  existing.expires = expires;
@@ -239,7 +362,7 @@ var SubscriptionManager = class {
239
362
  * @param args - Command arguments.
240
363
  */
241
364
  addBatchSubscriber(rinfo, alias, paths, start, count, factor, args) {
242
- const expires = Date.now() + 1e4;
365
+ const expires = Date.now() + this.subscriptionTtlMs;
243
366
  this.subscribers = this.subscribers.filter((s) => !(s.type === "batch" && s.alias === alias && s.address === rinfo.address));
244
367
  this.subscribers.push({
245
368
  type: "batch",
@@ -271,7 +394,7 @@ var SubscriptionManager = class {
271
394
  * @param factor - Frequency factor.
272
395
  */
273
396
  addFormatSubscriber(rinfo, alias, pattern, start, count, factor) {
274
- const expires = Date.now() + 1e4;
397
+ const expires = Date.now() + this.subscriptionTtlMs;
275
398
  this.subscribers = this.subscribers.filter((s) => !(s.type === "format" && s.alias === alias && s.address === rinfo.address));
276
399
  this.subscribers.push({
277
400
  type: "format",
@@ -297,7 +420,7 @@ var SubscriptionManager = class {
297
420
  * @param meterPath - Target meter path.
298
421
  */
299
422
  addMeterSubscriber(rinfo, meterPath) {
300
- const expires = Date.now() + 1e4;
423
+ const expires = Date.now() + this.subscriptionTtlMs;
301
424
  const existing = this.subscribers.find((s) => s.type === "meter" && s.meterPath === meterPath && s.address === rinfo.address);
302
425
  this.subscribers = this.subscribers.filter((s) => !(s.type === "meter" && s.meterPath === meterPath && s.address === rinfo.address));
303
426
  this.subscribers.push({
@@ -362,8 +485,9 @@ var NodeDiscoveryStrategy = class {
362
485
  //#endregion
363
486
  //#region src/domain/services/strategies/StaticResponseStrategy.ts
364
487
  /**
365
- * Handles discovery and static information queries.
366
- * e.g., /status, /xinfo, /-prefs/...
488
+ * Handles discovery and static information queries (GET only).
489
+ * e.g., /status, /xinfo
490
+ * Note: Only handles queries without arguments. SET commands pass through to StateAccessStrategy.
367
491
  */
368
492
  var StaticResponseStrategy = class {
369
493
  constructor(serverIp, serverName, serverModel, staticResponseService) {
@@ -372,7 +496,8 @@ var StaticResponseStrategy = class {
372
496
  this.serverModel = serverModel;
373
497
  this.staticResponseService = staticResponseService;
374
498
  }
375
- canHandle(address) {
499
+ canHandle(address, args) {
500
+ if (args && args.length > 0) return false;
376
501
  return !!this.staticResponseService.getResponse(address);
377
502
  }
378
503
  execute(msg, _source) {
@@ -543,6 +668,7 @@ var MeterStrategy = class {
543
668
  * LOGIC:
544
669
  * - If no arguments: Treats as a QUERY (GET) and returns the current value.
545
670
  * - If arguments provided: Treats as an UPDATE (SET) and stores the new value.
671
+ * - Unknown addresses are also stored in permissive mode for compatibility.
546
672
  */
547
673
  var StateAccessStrategy = class {
548
674
  /**
@@ -550,43 +676,685 @@ var StateAccessStrategy = class {
550
676
  * @param state - Current mixer state.
551
677
  * @param logger - Logger instance.
552
678
  * @param schemaRegistry - Registry to validate addresses.
679
+ * @param permissive - If true, store unknown addresses too (default: true).
553
680
  */
554
- constructor(state, logger, schemaRegistry) {
681
+ constructor(state, logger, schemaRegistry, permissive = true) {
555
682
  this.state = state;
556
683
  this.logger = logger;
557
684
  this.schemaRegistry = schemaRegistry;
685
+ this.permissive = permissive;
558
686
  }
559
687
  /** @inheritdoc */
560
688
  canHandle(address) {
561
- return this.schemaRegistry.has(address);
689
+ if (this.schemaRegistry.has(address)) return true;
690
+ return this.permissive && address.startsWith("/");
562
691
  }
563
692
  /** @inheritdoc */
564
693
  execute(msg, _source) {
565
694
  const addr = msg.address;
566
695
  const node = this.schemaRegistry.getNode(addr);
696
+ const isKnown = !!node;
567
697
  if (msg.args.length === 0) {
568
698
  const val$1 = this.state.get(addr);
569
699
  if (val$1 !== void 0) return [{
570
700
  address: addr,
571
701
  args: [val$1]
572
702
  }];
703
+ if (!isKnown) this.logger.debug(LogCategory.STATE, `[GET] Unknown address queried: ${addr}`);
573
704
  return [];
574
705
  }
575
706
  const val = msg.args[0];
576
- if (node) {
707
+ if (isKnown) {
577
708
  if (!node.validate(val)) {
578
709
  this.logger.warn(LogCategory.DISPATCH, `[TYPE ERR] ${addr} expected ${node.type}, got ${typeof val}`);
579
710
  return [];
580
711
  }
581
- this.state.set(addr, val);
582
- this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
583
- if (addr.startsWith("/config/mute/")) {
584
- const groupIdx = parseInt(addr.split("/").pop(), 10);
585
- if (!isNaN(groupIdx)) this.state.handleMuteGroupChange(groupIdx, val);
586
- }
712
+ } else this.logger.warn(LogCategory.STATE, `[UNKNOWN] Storing unknown address: ${addr} = ${val}`);
713
+ this.state.set(addr, val);
714
+ this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
715
+ if (addr.startsWith("/config/mute/")) {
716
+ const groupIdx = parseInt(addr.split("/").pop(), 10);
717
+ if (!isNaN(groupIdx)) this.state.handleMuteGroupChange(groupIdx, val);
718
+ }
719
+ if (addr === "/-action/undopt" && val === 1) {
720
+ const timeStr = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
721
+ this.state.set("/-undo/time", timeStr);
722
+ this.logger.debug(LogCategory.STATE, `[UNDO] Checkpoint created at ${timeStr}`);
723
+ }
724
+ return [];
725
+ }
726
+ };
727
+
728
+ //#endregion
729
+ //#region src/domain/services/strategies/ShowDumpStrategy.ts
730
+ /**
731
+ * Handles the /showdump command which requests a full state dump.
732
+ * Used by X32-Edit during synchronization to retrieve all console parameters.
733
+ */
734
+ var ShowDumpStrategy = class {
735
+ constructor(state, logger) {
736
+ this.state = state;
737
+ this.logger = logger;
738
+ }
739
+ /** @inheritdoc */
740
+ canHandle(address) {
741
+ return address === "/showdump";
742
+ }
743
+ /** @inheritdoc */
744
+ execute(_msg, _source) {
745
+ this.logger.debug(LogCategory.DISPATCH, "[SHOWDUMP] Full state dump requested");
746
+ const replies = [];
747
+ const allState = this.state.getAll();
748
+ for (const [address, value] of Object.entries(allState)) replies.push({
749
+ address,
750
+ args: [value]
751
+ });
752
+ this.logger.debug(LogCategory.DISPATCH, `[SHOWDUMP] Sending ${replies.length} state entries`);
753
+ return replies;
754
+ }
755
+ };
756
+
757
+ //#endregion
758
+ //#region src/domain/services/strategies/SceneDataStrategy.ts
759
+ /**
760
+ * Maps scene file format (batch values) to individual OSC addresses.
761
+ * The X32 scene format sends multiple values for a parent address.
762
+ * Example: /ch/01/mix ON -0.2 ON -100 OFF -oo
763
+ * Maps to: /ch/01/mix/on, /ch/01/mix/fader, /ch/01/mix/st, /ch/01/mix/pan, /ch/01/mix/mono, /ch/01/mix/mlevel
764
+ */
765
+ const SCENE_MAPPINGS = {
766
+ "headamp": ["gain", "phantom"],
767
+ "config": [
768
+ "name",
769
+ "icon",
770
+ "color",
771
+ "source"
772
+ ],
773
+ "delay": ["on", "time"],
774
+ "preamp": [
775
+ "trim",
776
+ "invert",
777
+ "hpon",
778
+ "hpslope",
779
+ "hpf"
780
+ ],
781
+ "gate": [
782
+ "on",
783
+ "mode",
784
+ "thr",
785
+ "range",
786
+ "attack",
787
+ "hold",
788
+ "release",
789
+ "keysrc"
790
+ ],
791
+ "gate/filter": [
792
+ "on",
793
+ "type",
794
+ "f"
795
+ ],
796
+ "dyn": [
797
+ "on",
798
+ "mode",
799
+ "det",
800
+ "env",
801
+ "thr",
802
+ "ratio",
803
+ "knee",
804
+ "mgain",
805
+ "attack",
806
+ "hold",
807
+ "release",
808
+ "pos",
809
+ "keysrc",
810
+ "mix",
811
+ "auto"
812
+ ],
813
+ "dyn/filter": [
814
+ "on",
815
+ "type",
816
+ "f"
817
+ ],
818
+ "insert": [
819
+ "on",
820
+ "pos",
821
+ "sel"
822
+ ],
823
+ "eq": ["on"],
824
+ "eq/1": [
825
+ "type",
826
+ "f",
827
+ "g",
828
+ "q"
829
+ ],
830
+ "eq/2": [
831
+ "type",
832
+ "f",
833
+ "g",
834
+ "q"
835
+ ],
836
+ "eq/3": [
837
+ "type",
838
+ "f",
839
+ "g",
840
+ "q"
841
+ ],
842
+ "eq/4": [
843
+ "type",
844
+ "f",
845
+ "g",
846
+ "q"
847
+ ],
848
+ "eq/5": [
849
+ "type",
850
+ "f",
851
+ "g",
852
+ "q"
853
+ ],
854
+ "eq/6": [
855
+ "type",
856
+ "f",
857
+ "g",
858
+ "q"
859
+ ],
860
+ "mix": [
861
+ "on",
862
+ "fader",
863
+ "st",
864
+ "pan",
865
+ "mono",
866
+ "mlevel"
867
+ ],
868
+ "mix/01": [
869
+ "on",
870
+ "level",
871
+ "pan",
872
+ "type",
873
+ "panFollow"
874
+ ],
875
+ "mix/02": ["on", "level"],
876
+ "mix/03": [
877
+ "on",
878
+ "level",
879
+ "pan",
880
+ "type",
881
+ "panFollow"
882
+ ],
883
+ "mix/04": ["on", "level"],
884
+ "mix/05": [
885
+ "on",
886
+ "level",
887
+ "pan",
888
+ "type",
889
+ "panFollow"
890
+ ],
891
+ "mix/06": ["on", "level"],
892
+ "mix/07": [
893
+ "on",
894
+ "level",
895
+ "pan",
896
+ "type",
897
+ "panFollow"
898
+ ],
899
+ "mix/08": ["on", "level"],
900
+ "mix/09": [
901
+ "on",
902
+ "level",
903
+ "pan",
904
+ "type",
905
+ "panFollow"
906
+ ],
907
+ "mix/10": ["on", "level"],
908
+ "mix/11": [
909
+ "on",
910
+ "level",
911
+ "pan",
912
+ "type",
913
+ "panFollow"
914
+ ],
915
+ "mix/12": ["on", "level"],
916
+ "mix/13": [
917
+ "on",
918
+ "level",
919
+ "pan",
920
+ "type",
921
+ "panFollow"
922
+ ],
923
+ "mix/14": ["on", "level"],
924
+ "mix/15": [
925
+ "on",
926
+ "level",
927
+ "pan",
928
+ "type",
929
+ "panFollow"
930
+ ],
931
+ "mix/16": ["on", "level"],
932
+ "grp": ["dca", "mute"],
933
+ "automix": ["group", "weight"],
934
+ "chlink": [
935
+ "1-2",
936
+ "3-4",
937
+ "5-6",
938
+ "7-8",
939
+ "9-10",
940
+ "11-12",
941
+ "13-14",
942
+ "15-16",
943
+ "17-18",
944
+ "19-20",
945
+ "21-22",
946
+ "23-24",
947
+ "25-26",
948
+ "27-28",
949
+ "29-30",
950
+ "31-32"
951
+ ],
952
+ "auxlink": [
953
+ "1-2",
954
+ "3-4",
955
+ "5-6",
956
+ "7-8"
957
+ ],
958
+ "fxlink": [
959
+ "1-2",
960
+ "3-4",
961
+ "5-6",
962
+ "7-8"
963
+ ],
964
+ "buslink": [
965
+ "1-2",
966
+ "3-4",
967
+ "5-6",
968
+ "7-8",
969
+ "9-10",
970
+ "11-12",
971
+ "13-14",
972
+ "15-16"
973
+ ],
974
+ "mtxlink": [
975
+ "1-2",
976
+ "3-4",
977
+ "5-6"
978
+ ],
979
+ "mute": [
980
+ "1",
981
+ "2",
982
+ "3",
983
+ "4",
984
+ "5",
985
+ "6"
986
+ ],
987
+ "linkcfg": [
988
+ "hadly",
989
+ "eq",
990
+ "dyn",
991
+ "fdrmute"
992
+ ],
993
+ "mono": ["mode", "link"],
994
+ "solo": [
995
+ "level",
996
+ "source",
997
+ "sourcetrim",
998
+ "chmode",
999
+ "busmode",
1000
+ "dcamode",
1001
+ "exclusive",
1002
+ "followsel",
1003
+ "followsolo",
1004
+ "dimatt",
1005
+ "dim",
1006
+ "mono",
1007
+ "delay",
1008
+ "delaytime",
1009
+ "masterctrl",
1010
+ "mute",
1011
+ "dimmute"
1012
+ ],
1013
+ "talk": ["enable", "source"],
1014
+ "talk/A": [
1015
+ "level",
1016
+ "dim",
1017
+ "latch",
1018
+ "destmap"
1019
+ ],
1020
+ "talk/B": [
1021
+ "level",
1022
+ "dim",
1023
+ "latch",
1024
+ "destmap"
1025
+ ],
1026
+ "osc": [
1027
+ "level",
1028
+ "f1",
1029
+ "f2",
1030
+ "type",
1031
+ "dest"
1032
+ ],
1033
+ "tape": [
1034
+ "gainL",
1035
+ "gainR",
1036
+ "autoplay"
1037
+ ],
1038
+ "amixenable": ["X", "Y"],
1039
+ "dp48": [
1040
+ "broadcast",
1041
+ "bundles",
1042
+ "assign"
1043
+ ],
1044
+ "dp48/assign": Array.from({ length: 48 }, (_, i) => String(i + 1)),
1045
+ "dp48/link": Array.from({ length: 24 }, (_, i) => String(i + 1)),
1046
+ "dp48/grpname": Array.from({ length: 12 }, (_, i) => String(i + 1)),
1047
+ "userrout/out": Array.from({ length: 48 }, (_, i) => String(i + 1)),
1048
+ "userrout/in": Array.from({ length: 32 }, (_, i) => String(i + 1)),
1049
+ "routing": ["routswitch"],
1050
+ "routing/IN": [
1051
+ "1-8",
1052
+ "9-16",
1053
+ "17-24",
1054
+ "25-32",
1055
+ "AUX"
1056
+ ],
1057
+ "routing/AES50A": [
1058
+ "1-8",
1059
+ "9-16",
1060
+ "17-24",
1061
+ "25-32",
1062
+ "33-40",
1063
+ "41-48"
1064
+ ],
1065
+ "routing/AES50B": [
1066
+ "1-8",
1067
+ "9-16",
1068
+ "17-24",
1069
+ "25-32",
1070
+ "33-40",
1071
+ "41-48"
1072
+ ],
1073
+ "routing/CARD": [
1074
+ "1-8",
1075
+ "9-16",
1076
+ "17-24",
1077
+ "25-32"
1078
+ ],
1079
+ "routing/OUT": [
1080
+ "1-4",
1081
+ "5-8",
1082
+ "9-12",
1083
+ "13-16"
1084
+ ],
1085
+ "routing/PLAY": [
1086
+ "1-8",
1087
+ "9-16",
1088
+ "17-24",
1089
+ "25-32",
1090
+ "AUX"
1091
+ ],
1092
+ "userctrl/A": ["color"],
1093
+ "userctrl/A/enc": [
1094
+ "1",
1095
+ "2",
1096
+ "3",
1097
+ "4"
1098
+ ],
1099
+ "userctrl/A/btn": [
1100
+ "1",
1101
+ "2",
1102
+ "3",
1103
+ "4",
1104
+ "5",
1105
+ "6",
1106
+ "7",
1107
+ "8"
1108
+ ],
1109
+ "userctrl/B": ["color"],
1110
+ "userctrl/B/enc": [
1111
+ "1",
1112
+ "2",
1113
+ "3",
1114
+ "4"
1115
+ ],
1116
+ "userctrl/B/btn": [
1117
+ "1",
1118
+ "2",
1119
+ "3",
1120
+ "4",
1121
+ "5",
1122
+ "6",
1123
+ "7",
1124
+ "8"
1125
+ ],
1126
+ "userctrl/C": ["color"],
1127
+ "userctrl/C/enc": [
1128
+ "1",
1129
+ "2",
1130
+ "3",
1131
+ "4"
1132
+ ],
1133
+ "userctrl/C/btn": [
1134
+ "1",
1135
+ "2",
1136
+ "3",
1137
+ "4",
1138
+ "5",
1139
+ "6",
1140
+ "7",
1141
+ "8",
1142
+ "9",
1143
+ "10",
1144
+ "11",
1145
+ "12"
1146
+ ],
1147
+ "dca": ["on", "fader"],
1148
+ "bus/config": [
1149
+ "name",
1150
+ "icon",
1151
+ "color"
1152
+ ],
1153
+ "bus/mix": [
1154
+ "on",
1155
+ "fader",
1156
+ "st",
1157
+ "pan",
1158
+ "mono",
1159
+ "mlevel"
1160
+ ],
1161
+ "main/st/config": [
1162
+ "name",
1163
+ "icon",
1164
+ "color"
1165
+ ],
1166
+ "main/st/mix": [
1167
+ "on",
1168
+ "fader",
1169
+ "st",
1170
+ "pan"
1171
+ ],
1172
+ "main/m/config": [
1173
+ "name",
1174
+ "icon",
1175
+ "color"
1176
+ ],
1177
+ "main/m/mix": ["on", "fader"],
1178
+ "fx/source": ["inL", "inR"],
1179
+ "fx": ["type"],
1180
+ "fx/par": Array.from({ length: 64 }, (_, i) => String(i + 1)),
1181
+ "outputs/main": [
1182
+ "src",
1183
+ "pos",
1184
+ "invert"
1185
+ ],
1186
+ "outputs/main/delay": ["on", "time"],
1187
+ "outputs/aux": [
1188
+ "src",
1189
+ "pos",
1190
+ "invert"
1191
+ ],
1192
+ "outputs/p16": [
1193
+ "src",
1194
+ "pos",
1195
+ "invert"
1196
+ ],
1197
+ "outputs/p16/iQ": [
1198
+ "model",
1199
+ "eqset",
1200
+ "sound"
1201
+ ],
1202
+ "outputs/aes": [
1203
+ "src",
1204
+ "pos",
1205
+ "invert"
1206
+ ],
1207
+ "outputs/rec": [
1208
+ "src",
1209
+ "pos",
1210
+ "invert"
1211
+ ]
1212
+ };
1213
+ /**
1214
+ * Converts scene file values to proper OSC types.
1215
+ */
1216
+ function parseSceneValue(val) {
1217
+ if (val === "ON") return 1;
1218
+ if (val === "OFF") return 0;
1219
+ if (val === "-oo" || val === "-inf") return -90;
1220
+ if (val === "+oo" || val === "+inf") return 90;
1221
+ if (val.startsWith("%")) return parseInt(val.slice(1), 2);
1222
+ const freqMatch = val.match(/^(\d+)k(\d+)$/);
1223
+ if (freqMatch) return parseFloat(freqMatch[1] + "." + freqMatch[2]) * 1e3;
1224
+ if (val.startsWith("+") || val.startsWith("-")) {
1225
+ const num$1 = parseFloat(val);
1226
+ if (!isNaN(num$1)) return num$1;
1227
+ }
1228
+ const num = parseFloat(val);
1229
+ if (!isNaN(num)) return num;
1230
+ const int = parseInt(val, 10);
1231
+ if (!isNaN(int)) return int;
1232
+ return val.replace(/^"|"$/g, "");
1233
+ }
1234
+ /**
1235
+ * Strategy to handle X32 scene file format (batch multi-value commands).
1236
+ * Expands parent addresses with multiple values into individual parameter sets.
1237
+ */
1238
+ var SceneDataStrategy = class {
1239
+ constructor(state, logger) {
1240
+ this.state = state;
1241
+ this.logger = logger;
1242
+ }
1243
+ canHandle(address, args) {
1244
+ if (!args || args.length === 0) return false;
1245
+ if (args.length === 1) {
1246
+ if (address.match(/^\/fx\/\d$/)) return true;
1247
+ return false;
1248
+ }
1249
+ if (args.length === 2) {
1250
+ if (address.match(/^\/dca\/\d$/)) return true;
1251
+ }
1252
+ return this.findMapping(address) !== null;
1253
+ }
1254
+ execute(msg, _source) {
1255
+ const { address, args } = msg;
1256
+ const mapping = this.findMapping(address);
1257
+ if (!mapping) return [];
1258
+ const { basePath, fields } = mapping;
1259
+ this.logger.debug(LogCategory.STATE, `[SCENE] Expanding batch command`, {
1260
+ address,
1261
+ argCount: args.length,
1262
+ fields: fields.length
1263
+ });
1264
+ for (let i = 0; i < args.length && i < fields.length; i++) {
1265
+ const fieldName = fields[i];
1266
+ const rawValue = args[i];
1267
+ const value = typeof rawValue === "string" ? parseSceneValue(rawValue) : rawValue;
1268
+ const fullPath = `${basePath}/${fieldName}`;
1269
+ this.state.set(fullPath, value);
1270
+ this.logger.debug(LogCategory.STATE, `[SCENE] Set ${fullPath} = ${value}`);
587
1271
  }
588
1272
  return [];
589
1273
  }
1274
+ findMapping(address) {
1275
+ if (address.match(/^\/headamp\/\d{3}$/) && SCENE_MAPPINGS["headamp"]) return {
1276
+ basePath: address,
1277
+ fields: SCENE_MAPPINGS["headamp"]
1278
+ };
1279
+ if (address.match(/^\/fx\/\d\/source$/) && SCENE_MAPPINGS["fx/source"]) return {
1280
+ basePath: address,
1281
+ fields: SCENE_MAPPINGS["fx/source"]
1282
+ };
1283
+ if (address.match(/^\/fx\/\d\/par$/) && SCENE_MAPPINGS["fx/par"]) return {
1284
+ basePath: address,
1285
+ fields: SCENE_MAPPINGS["fx/par"]
1286
+ };
1287
+ if (address.match(/^\/fx\/\d$/) && SCENE_MAPPINGS["fx"]) return {
1288
+ basePath: address,
1289
+ fields: SCENE_MAPPINGS["fx"]
1290
+ };
1291
+ if (address.match(/^\/dca\/\d$/) && SCENE_MAPPINGS["dca"]) return {
1292
+ basePath: address,
1293
+ fields: SCENE_MAPPINGS["dca"]
1294
+ };
1295
+ if (address.match(/^\/dca\/\d\/config$/)) return {
1296
+ basePath: address,
1297
+ fields: [
1298
+ "name",
1299
+ "icon",
1300
+ "color"
1301
+ ]
1302
+ };
1303
+ if (address.match(/^\/outputs\/main\/\d{2}\/delay$/) && SCENE_MAPPINGS["outputs/main/delay"]) return {
1304
+ basePath: address,
1305
+ fields: SCENE_MAPPINGS["outputs/main/delay"]
1306
+ };
1307
+ if (address.match(/^\/outputs\/p16\/\d{2}\/iQ$/) && SCENE_MAPPINGS["outputs/p16/iQ"]) return {
1308
+ basePath: address,
1309
+ fields: SCENE_MAPPINGS["outputs/p16/iQ"]
1310
+ };
1311
+ if (address.match(/^\/outputs\/main\/\d{2}$/) && SCENE_MAPPINGS["outputs/main"]) return {
1312
+ basePath: address,
1313
+ fields: SCENE_MAPPINGS["outputs/main"]
1314
+ };
1315
+ if (address.match(/^\/outputs\/aux\/\d{2}$/) && SCENE_MAPPINGS["outputs/aux"]) return {
1316
+ basePath: address,
1317
+ fields: SCENE_MAPPINGS["outputs/aux"]
1318
+ };
1319
+ if (address.match(/^\/outputs\/p16\/\d{2}$/) && SCENE_MAPPINGS["outputs/p16"]) return {
1320
+ basePath: address,
1321
+ fields: SCENE_MAPPINGS["outputs/p16"]
1322
+ };
1323
+ if (address.match(/^\/outputs\/aes\/\d{2}$/) && SCENE_MAPPINGS["outputs/aes"]) return {
1324
+ basePath: address,
1325
+ fields: SCENE_MAPPINGS["outputs/aes"]
1326
+ };
1327
+ if (address.match(/^\/outputs\/rec\/\d{2}$/) && SCENE_MAPPINGS["outputs/rec"]) return {
1328
+ basePath: address,
1329
+ fields: SCENE_MAPPINGS["outputs/rec"]
1330
+ };
1331
+ if (address.startsWith("/config/")) {
1332
+ const suffix = address.replace("/config/", "");
1333
+ if (SCENE_MAPPINGS[suffix]) return {
1334
+ basePath: address,
1335
+ fields: SCENE_MAPPINGS[suffix]
1336
+ };
1337
+ }
1338
+ for (const [pattern, fields] of Object.entries(SCENE_MAPPINGS)) {
1339
+ if (address.endsWith("/" + pattern)) return {
1340
+ basePath: address,
1341
+ fields
1342
+ };
1343
+ if ((/* @__PURE__ */ new RegExp(`/${pattern.replace(/\//g, "/")}$`)).test(address)) return {
1344
+ basePath: address,
1345
+ fields
1346
+ };
1347
+ }
1348
+ const numberedPathMatch = address.match(/^\/\w+\/\d+\/(.+)$/);
1349
+ if (numberedPathMatch) {
1350
+ const suffix = numberedPathMatch[1];
1351
+ if (SCENE_MAPPINGS[suffix]) return {
1352
+ basePath: address,
1353
+ fields: SCENE_MAPPINGS[suffix]
1354
+ };
1355
+ }
1356
+ return null;
1357
+ }
590
1358
  };
591
1359
 
592
1360
  //#endregion
@@ -621,8 +1389,10 @@ var OscMessageHandler = class {
621
1389
  new SubscriptionStrategy(subscriptionManager, state, logger),
622
1390
  new BatchStrategy(subscriptionManager, logger),
623
1391
  new MeterStrategy(subscriptionManager, state, this.meterService),
624
- new StateAccessStrategy(state, logger, this.schemaRegistry),
625
- new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService)
1392
+ new ShowDumpStrategy(state, logger),
1393
+ new SceneDataStrategy(state, logger),
1394
+ new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService),
1395
+ new StateAccessStrategy(state, logger, this.schemaRegistry)
626
1396
  ];
627
1397
  }
628
1398
  /**
@@ -632,19 +1402,41 @@ var OscMessageHandler = class {
632
1402
  * @returns Array of replies generated by the strategy.
633
1403
  */
634
1404
  handle(msg, source) {
635
- const addr = msg.address;
1405
+ let addr = msg.address;
1406
+ let args = msg.args;
1407
+ if (addr === "/" && args.length === 1 && typeof args[0] === "string") {
1408
+ const parts = args[0].trim().match(/(?:[^\s"]+|"[^"]*")+/g) || [];
1409
+ if (parts.length > 0 && parts[0]) {
1410
+ let innerAddr = parts[0];
1411
+ if (!innerAddr.startsWith("/")) innerAddr = "/" + innerAddr;
1412
+ const innerArgs = parts.slice(1).map((arg) => {
1413
+ let val = arg.replace(/^"|"$/g, "");
1414
+ if (/^-?\d+$/.test(val)) return parseInt(val, 10);
1415
+ if (/^-?\d+\.\d+$/.test(val)) return parseFloat(val);
1416
+ return val;
1417
+ });
1418
+ this.logger.debug(LogCategory.DISPATCH, `Unwrapped X32-Edit command: ${innerAddr}`, { innerArgs });
1419
+ return this.handle({
1420
+ address: innerAddr,
1421
+ args: innerArgs
1422
+ }, source);
1423
+ }
1424
+ }
636
1425
  if (!addr.startsWith("/meters")) this.logger.debug(LogCategory.DISPATCH, `Handling`, {
637
1426
  addr,
638
- args: msg.args
1427
+ args
639
1428
  });
640
- for (const strategy of this.strategies) if (strategy.canHandle(addr)) {
641
- const replies = strategy.execute(msg, source);
1429
+ for (const strategy of this.strategies) if (strategy.canHandle(addr, args)) {
1430
+ const replies = strategy.execute({
1431
+ address: addr,
1432
+ args
1433
+ }, source);
642
1434
  if (replies.length > 0) this.logger.debug(LogCategory.DISPATCH, `Strategy ${strategy.constructor.name} replied`, { count: replies.length });
643
1435
  return replies;
644
1436
  }
645
1437
  this.logger.warn(LogCategory.DISPATCH, `Unknown Command`, {
646
1438
  addr,
647
- args: msg.args,
1439
+ args,
648
1440
  ip: source.address
649
1441
  });
650
1442
  return [];
@@ -758,6 +1550,8 @@ var MeterService = class {
758
1550
  //#region src/domain/services/StaticResponseService.ts
759
1551
  /**
760
1552
  * Static OSC responses for X32 Discovery and System status.
1553
+ * These are read-only informational endpoints that return system info.
1554
+ * All other parameters are handled by StateAccessStrategy from the schema.
761
1555
  */
762
1556
  const STATIC_RESPONSES_DATA = {
763
1557
  "/xinfo": [
@@ -776,34 +1570,7 @@ const STATIC_RESPONSES_DATA = {
776
1570
  "active",
777
1571
  "{{ip}}",
778
1572
  "{{name}}"
779
- ],
780
- "/-prefs/invertmutes": [0],
781
- "/-prefs/fine": [0],
782
- "/-prefs/bright": [50],
783
- "/-prefs/contrast": [50],
784
- "/-prefs/led_bright": [50],
785
- "/-prefs/lcd_bright": [50],
786
- "/-stat/userpar/1/value": [0],
787
- "/-stat/userpar/2/value": [0],
788
- "/-stat/tape/state": [0],
789
- "/-stat/aes50/A": [0],
790
- "/-stat/aes50/B": [0],
791
- "/-stat/solo": [0],
792
- "/-stat/rtasource": [0],
793
- "/-urec/errorcode": [0],
794
- "/-prefs/rta/gain": [0],
795
- "/-prefs/rta/autogain": [0],
796
- "/-prefs/hardmute": [0],
797
- "/-prefs/dcamute": [0],
798
- "/config/mono/mode": [0],
799
- "/config/amixenable/X": [0],
800
- "/config/amixenable/Y": [0],
801
- "/-show/prepos/current": [0],
802
- "/-show/showfile/current": [""],
803
- "/-action/setrtasrc": [0],
804
- "/-action/playtrack": [0],
805
- "/-action/goscene": [0],
806
- "/-action/setscene": [0]
1573
+ ]
807
1574
  };
808
1575
  /**
809
1576
  * Service providing static system responses.
@@ -833,47 +1600,51 @@ function getLocalIp() {
833
1600
  return "127.0.0.1";
834
1601
  }
835
1602
  /**
836
- * The core service that manages the X32 simulation, including networking,
1603
+ * The core service that manages the X32 simulation, including networking,
837
1604
  * state management, and OSC message handling.
838
1605
  */
839
1606
  var SimulationService = class {
840
1607
  subscriptionManager;
841
1608
  messageHandler;
1609
+ state;
842
1610
  processPacket;
843
1611
  broadcastUpdates;
844
1612
  manageSessions;
845
1613
  meterService;
846
1614
  staticResponseService;
1615
+ config;
847
1616
  updateInterval = null;
848
1617
  cleanupInterval = null;
849
1618
  /**
850
1619
  * Creates a new SimulationService instance.
851
1620
  * @param gateway - The network gateway for OSC communication.
852
1621
  * @param logger - The logger service.
853
- * @param stateRepo - The repository managing the mixer state.
1622
+ * @param stateRepository - The repository for state persistence.
854
1623
  * @param schemaRegistry - The registry for X32 OSC schema.
855
- * @param port - UDP port to listen on (default 10023).
856
- * @param ip - IP address to bind to (default '0.0.0.0').
1624
+ * @param port - UDP port to listen on (default from config).
1625
+ * @param ip - IP address to bind to (default from config).
857
1626
  * @param name - Reported console name.
858
1627
  * @param model - Reported console model.
1628
+ * @param configOverrides - Optional configuration overrides.
859
1629
  */
860
- constructor(gateway, logger, stateRepo, schemaRegistry, port = 10023, ip = "0.0.0.0", name = "osc-server", model = "X32") {
1630
+ constructor(gateway, logger, stateRepository, schemaRegistry, port = DEFAULT_CONFIG.DEFAULT_PORT, ip = DEFAULT_CONFIG.DEFAULT_HOST, name = DEFAULT_CONFIG.DEFAULT_NAME, model = DEFAULT_CONFIG.DEFAULT_MODEL, configOverrides) {
861
1631
  this.gateway = gateway;
862
1632
  this.logger = logger;
863
- this.stateRepo = stateRepo;
1633
+ this.stateRepository = stateRepository;
864
1634
  this.schemaRegistry = schemaRegistry;
865
1635
  this.port = port;
866
1636
  this.ip = ip;
867
- const state = this.stateRepo.getState();
868
- this.subscriptionManager = new SubscriptionManager(logger);
1637
+ this.config = mergeConfig(configOverrides);
1638
+ this.state = new X32State(schemaRegistry.getSchema(), stateRepository);
1639
+ this.subscriptionManager = new SubscriptionManager(logger, this.config.SUBSCRIPTION_TTL_MS);
869
1640
  this.meterService = new MeterService();
870
1641
  this.staticResponseService = new StaticResponseService();
871
1642
  const reportedIp = this.ip === "0.0.0.0" ? getLocalIp() : this.ip;
872
- this.messageHandler = new OscMessageHandler(state, this.subscriptionManager, logger, reportedIp, name, model, this.meterService, this.schemaRegistry, this.staticResponseService);
873
- this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway);
874
- this.broadcastUpdates = new BroadcastUpdatesUseCase(this.subscriptionManager, state, gateway, logger, this.meterService, this.schemaRegistry);
1643
+ this.messageHandler = new OscMessageHandler(this.state, this.subscriptionManager, logger, reportedIp, name, model, this.meterService, this.schemaRegistry, this.staticResponseService);
1644
+ this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway, logger);
1645
+ this.broadcastUpdates = new BroadcastUpdatesUseCase(this.subscriptionManager, this.state, gateway, logger, this.meterService, this.schemaRegistry);
875
1646
  this.manageSessions = new ManageSessionsUseCase(this.subscriptionManager);
876
- state.on("change", (evt) => {
1647
+ this.state.on("change", (evt) => {
877
1648
  this.logger.info(LogCategory.STATE, `State Changed`, evt);
878
1649
  this.broadcastUpdates.broadcastSingleChange(evt.address, evt.value);
879
1650
  });
@@ -883,13 +1654,14 @@ var SimulationService = class {
883
1654
  * Starts the simulator server and internal loops.
884
1655
  */
885
1656
  async start() {
1657
+ await this.state.initialize();
886
1658
  await this.gateway.start(this.port, this.ip);
887
1659
  this.cleanupInterval = setInterval(() => {
888
1660
  this.manageSessions.cleanup();
889
- }, 5e3);
1661
+ }, this.config.CLEANUP_INTERVAL_MS);
890
1662
  this.updateInterval = setInterval(() => {
891
1663
  this.broadcastUpdates.execute();
892
- }, 100);
1664
+ }, this.config.BROADCAST_INTERVAL_MS);
893
1665
  }
894
1666
  /**
895
1667
  * Stops the simulator server and stops all internal loops.
@@ -897,84 +1669,20 @@ var SimulationService = class {
897
1669
  async stop() {
898
1670
  if (this.updateInterval) clearInterval(this.updateInterval);
899
1671
  if (this.cleanupInterval) clearInterval(this.cleanupInterval);
1672
+ await this.state.flush();
900
1673
  await this.gateway.stop();
901
1674
  }
902
1675
  /**
903
1676
  * Resets the mixer state to default values.
904
1677
  */
905
1678
  resetState() {
906
- this.stateRepo.reset();
907
- }
908
- };
909
-
910
- //#endregion
911
- //#region src/domain/entities/X32State.ts
912
- /**
913
- * Manages the internal "Digital Twin" state of the X32 console.
914
- * It acts as a single source of truth for all parameters.
915
- * Emits 'change' events whenever a value is updated.
916
- */
917
- var X32State = class extends EventEmitter {
918
- /** Map storing all OSC paths and their current values. */
919
- state = /* @__PURE__ */ new Map();
920
- defaultState = /* @__PURE__ */ new Map();
921
- /**
922
- * Initializes the state with default values from the schema.
923
- * @param schema - The schema definition map.
924
- */
925
- constructor(schema) {
926
- super();
927
- for (const [addr, def] of Object.entries(schema)) this.defaultState.set(addr, def.default);
928
- this.reset();
929
- }
930
- /**
931
- * Resets all state parameters to their default values defined in the schema.
932
- */
933
- reset() {
934
- this.state.clear();
935
- this.defaultState.forEach((val, key) => {
936
- this.state.set(key, val);
937
- });
938
- }
939
- /**
940
- * Retrieves the value of a specific OSC node.
941
- * @param address - The full OSC address path.
942
- * @returns The stored value (number or string) or undefined if not found.
943
- */
944
- get(address) {
945
- return this.state.get(address);
946
- }
947
- /**
948
- * Updates the value of a specific OSC node and notifies subscribers.
949
- * @param address - The full OSC address path.
950
- * @param value - The new value to store.
951
- */
952
- set(address, value) {
953
- this.state.set(address, value);
954
- this.emit("change", {
955
- address,
956
- value
957
- });
1679
+ this.state.reset();
958
1680
  }
959
1681
  /**
960
- * Specialized logic to handle X32 Mute Groups side-effects.
961
- * When a mute group is toggled, it iterates through all channels
962
- * assigned to that group and updates their individual mute status.
963
- * @param groupIdx - The index of the mute group (1-6).
964
- * @param isOn - The new state of the group master switch (0 or 1).
1682
+ * Forces a save of the current state to storage.
965
1683
  */
966
- handleMuteGroupChange(groupIdx, isOn) {
967
- for (let i = 1; i <= 32; i++) {
968
- const ch = i.toString().padStart(2, "0");
969
- const grpVal = this.get(`/ch/${ch}/grp/mute`);
970
- if (typeof grpVal === "number") {
971
- if ((grpVal & 1 << groupIdx - 1) !== 0) {
972
- const targetMute = isOn === 1 ? 0 : 1;
973
- const muteAddr = `/ch/${ch}/mix/on`;
974
- this.set(muteAddr, targetMute);
975
- }
976
- }
977
- }
1684
+ async saveState() {
1685
+ await this.state.flush();
978
1686
  }
979
1687
  };
980
1688
 
@@ -989,69 +1697,106 @@ var X32Node = class X32Node {
989
1697
  type;
990
1698
  /** Default value for reset. */
991
1699
  default;
1700
+ /** Optional validation constraints. */
1701
+ options;
992
1702
  /**
993
1703
  * Creates a new X32Node.
994
1704
  * @param type - The OSC data type ('f', 'i', 's').
995
1705
  * @param defaultValue - The default value.
1706
+ * @param options - Optional validation constraints.
996
1707
  */
997
- constructor(type, defaultValue) {
1708
+ constructor(type, defaultValue, options) {
998
1709
  this.type = type;
999
1710
  this.default = defaultValue;
1711
+ this.options = options;
1000
1712
  }
1001
1713
  /**
1002
1714
  * Validates if a value is compatible with this node's type.
1003
1715
  * @param value - The value to check.
1716
+ * @param strict - If true, also validates against min/max constraints.
1004
1717
  * @returns True if valid.
1005
1718
  */
1006
- validate(value) {
1007
- if (this.type === "f") return typeof value === "number";
1008
- if (this.type === "i") return typeof value === "number";
1009
- if (this.type === "s") return typeof value === "string";
1010
- return false;
1719
+ validate(value, strict = false) {
1720
+ if (this.type === "f" && typeof value !== "number") return false;
1721
+ if (this.type === "i" && typeof value !== "number") return false;
1722
+ if (this.type === "s" && typeof value !== "string") return false;
1723
+ if (strict && this.options && typeof value === "number") {
1724
+ if (this.options.min !== void 0 && value < this.options.min) return false;
1725
+ if (this.options.max !== void 0 && value > this.options.max) return false;
1726
+ if (this.type === "i" && this.options.strictInteger && !Number.isInteger(value)) return false;
1727
+ }
1728
+ return true;
1011
1729
  }
1012
1730
  /**
1013
1731
  * Factory method to create from a plain object (for compatibility/migration).
1014
1732
  * @param obj - Plain object.
1015
1733
  * @param obj.type - OSC data type.
1016
1734
  * @param obj.default - Default value.
1735
+ * @param obj.options - Optional validation constraints.
1017
1736
  * @returns A new X32Node instance.
1018
1737
  */
1019
1738
  static from(obj) {
1020
- return new X32Node(obj.type, obj.default);
1739
+ return new X32Node(obj.type, obj.default, obj.options);
1021
1740
  }
1022
1741
  };
1023
1742
 
1024
1743
  //#endregion
1025
1744
  //#region src/infrastructure/repositories/InMemoryStateRepository.ts
1026
1745
  /**
1027
- * In-memory implementation of the state repository.
1028
- * Stores the mixer state in volatile memory during the simulator's execution.
1746
+ * In-memory state repository.
1747
+ * Stores state in a Map, no persistence across restarts.
1748
+ * This is the default and fastest option.
1029
1749
  */
1030
1750
  var InMemoryStateRepository = class {
1031
- state;
1751
+ state = /* @__PURE__ */ new Map();
1032
1752
  /**
1033
- * Creates a new InMemoryStateRepository.
1034
- * @param logger - Logger service.
1035
- * @param schemaRegistry - Registry providing the initial state schema.
1753
+ * Loads state from memory.
1754
+ * For in-memory storage, this returns current state.
1036
1755
  */
1037
- constructor(logger, schemaRegistry) {
1038
- this.logger = logger;
1039
- this.state = new X32State(schemaRegistry.getSchema());
1756
+ async load() {
1757
+ return this.getAll();
1040
1758
  }
1041
1759
  /**
1042
- * Returns the current mixer state instance.
1043
- * @returns The X32State entity.
1760
+ * Saves state to memory.
1761
+ * For in-memory storage, this replaces current state.
1044
1762
  */
1045
- getState() {
1046
- return this.state;
1763
+ async save(state) {
1764
+ this.state.clear();
1765
+ for (const [key, value] of Object.entries(state)) this.state.set(key, value);
1047
1766
  }
1048
1767
  /**
1049
- * Resets the entire state to its default values.
1768
+ * Gets a single value from memory.
1050
1769
  */
1051
- reset() {
1052
- this.logger.info(LogCategory.SYSTEM, "Resetting state to defaults");
1053
- this.state.reset();
1770
+ get(address) {
1771
+ return this.state.get(address);
1054
1772
  }
1773
+ /**
1774
+ * Sets a single value in memory.
1775
+ */
1776
+ set(address, value) {
1777
+ this.state.set(address, value);
1778
+ }
1779
+ /**
1780
+ * Gets all entries from memory.
1781
+ */
1782
+ getAll() {
1783
+ const result = {};
1784
+ this.state.forEach((value, key) => {
1785
+ result[key] = value;
1786
+ });
1787
+ return result;
1788
+ }
1789
+ /**
1790
+ * Resets state to provided defaults.
1791
+ */
1792
+ reset(defaults) {
1793
+ this.state.clear();
1794
+ for (const [key, value] of Object.entries(defaults)) this.state.set(key, value);
1795
+ }
1796
+ /**
1797
+ * No-op for in-memory storage.
1798
+ */
1799
+ async flush() {}
1055
1800
  };
1056
1801
 
1057
1802
  //#endregion
@@ -1870,41 +2615,215 @@ var SchemaFactory = class {
1870
2615
  ...this.generateRange(128, "/headamp", "/gain", "f", 0, 3, 0),
1871
2616
  ...this.generateRange(128, "/headamp", "/phantom", "i", 0, 3, 0),
1872
2617
  ...this.generateRange(6, "/config/mute", "", "i", 0),
1873
- ...this.generateRange(80, "/-stat/solosw", "", "i", 0),
2618
+ ...this.generateRange(80, "/-stat/solosw", "", "i", 0, 2, 1),
1874
2619
  "/-stat/selidx": this.node("i", 0),
2620
+ "/-stat/chfaderbank": this.node("i", 0),
2621
+ "/-stat/grpfaderbank": this.node("i", 0),
1875
2622
  "/-stat/sendsonfader": this.node("i", 0),
1876
2623
  "/-stat/bussendbank": this.node("i", 0),
2624
+ "/-stat/eqband": this.node("i", 0),
1877
2625
  "/-stat/keysolo": this.node("i", 0),
2626
+ "/-stat/userbank": this.node("i", 0),
2627
+ "/-stat/autosave": this.node("i", 0),
2628
+ "/-stat/lock": this.node("i", 0),
2629
+ "/-stat/usbmounted": this.node("i", 0),
2630
+ "/-stat/remote": this.node("i", 0),
2631
+ "/-stat/rtamodeeq": this.node("i", 0),
2632
+ "/-stat/rtamodegeq": this.node("i", 0),
2633
+ "/-stat/rtaeqpre": this.node("i", 0),
2634
+ "/-stat/rtageqpost": this.node("i", 0),
2635
+ "/-stat/rtasource": this.node("i", 0),
2636
+ "/-stat/xcardtype": this.node("i", 0),
2637
+ "/-stat/xcardsync": this.node("i", 0),
2638
+ "/-stat/geqonfdr": this.node("i", 0),
2639
+ "/-stat/geqpos": this.node("i", 0),
1878
2640
  "/-stat/screen/screen": this.node("i", 0),
2641
+ "/-stat/screen/mutegrp": this.node("i", 0),
2642
+ "/-stat/screen/utils": this.node("i", 0),
1879
2643
  "/-stat/screen/CHAN/page": this.node("i", 0),
1880
2644
  "/-stat/screen/METER/page": this.node("i", 0),
1881
2645
  "/-stat/screen/ROUTE/page": this.node("i", 0),
1882
2646
  "/-stat/screen/SETUP/page": this.node("i", 0),
2647
+ "/-stat/screen/LIB/page": this.node("i", 0),
1883
2648
  "/-stat/screen/LIBRARY/page": this.node("i", 0),
1884
2649
  "/-stat/screen/FX/page": this.node("i", 0),
1885
2650
  "/-stat/screen/MON/page": this.node("i", 0),
1886
2651
  "/-stat/screen/USB/page": this.node("i", 0),
1887
2652
  "/-stat/screen/SCENE/page": this.node("i", 0),
1888
2653
  "/-stat/screen/ASSIGN/page": this.node("i", 0),
2654
+ "/-stat/aes50/state": this.node("i", 0),
2655
+ "/-stat/aes50/A": this.node("i", 0),
2656
+ "/-stat/aes50/B": this.node("i", 0),
2657
+ "/-stat/aes50/stats/A": this.node("i", 0),
2658
+ "/-stat/aes50/stats/B": this.node("i", 0),
2659
+ "/-stat/tape/state": this.node("i", 0),
2660
+ "/-stat/tape/file": this.node("s", ""),
2661
+ "/-stat/tape/etime": this.node("i", 0),
2662
+ "/-stat/tape/rtime": this.node("i", 0),
2663
+ "/-stat/urec/state": this.node("i", 0),
2664
+ "/-stat/urec/etime": this.node("i", 0),
2665
+ "/-stat/urec/rtime": this.node("i", 0),
1889
2666
  "/-stat/talk/A": this.node("i", 0),
1890
2667
  "/-stat/talk/B": this.node("i", 0),
1891
2668
  "/-stat/osc/on": this.node("i", 0),
1892
- "/-prefs/autosel": this.node("i", 1),
1893
- "/-action/setrtasrc": this.node("i", 0),
2669
+ "/-action/setip": this.node("i", 0),
2670
+ "/-action/setclock": this.node("i", 0),
2671
+ "/-action/initall": this.node("i", 0),
2672
+ "/-action/initlib": this.node("i", 0),
2673
+ "/-action/initshow": this.node("i", 0),
2674
+ "/-action/savestate": this.node("i", 0),
2675
+ "/-action/undopt": this.node("i", 0),
2676
+ "/-action/doundo": this.node("i", 0),
1894
2677
  "/-action/playtrack": this.node("i", 0),
2678
+ "/-action/newscreen": this.node("i", 0),
2679
+ "/-action/clearsolo": this.node("i", 0),
2680
+ "/-action/setprebus": this.node("i", 0),
2681
+ "/-action/setsrate": this.node("i", 0),
2682
+ "/-action/setrtasrc": this.node("i", 0),
2683
+ "/-action/recselect": this.node("i", 0),
2684
+ "/-action/gocue": this.node("i", 0),
1895
2685
  "/-action/goscene": this.node("i", 0),
1896
2686
  "/-action/setscene": this.node("i", 0),
2687
+ "/-action/gosnippet": this.node("i", 0),
2688
+ "/-action/selsession": this.node("i", 0),
2689
+ "/-action/delsession": this.node("i", 0),
2690
+ "/-action/selmarker": this.node("i", 0),
2691
+ "/-action/delmarker": this.node("i", 0),
2692
+ "/-action/savemarker": this.node("i", 0),
2693
+ "/-action/addmarker": this.node("i", 0),
2694
+ "/-action/selposition": this.node("i", 0),
2695
+ "/-action/clearalert": this.node("i", 0),
2696
+ "/-action/formatcard": this.node("i", 0),
2697
+ "/-undo/time": this.node("s", ""),
2698
+ "/-show": this.node("i", 0),
2699
+ "/-show/prepos": this.node("i", 0),
2700
+ "/-show/prepos/current": this.node("i", 0),
2701
+ "/-show/showfile": this.node("s", ""),
2702
+ "/-show/showfile/inputs": this.node("i", 0),
2703
+ "/-show/showfile/mxsends": this.node("i", 0),
2704
+ "/-show/showfile/mxbuses": this.node("i", 0),
2705
+ "/-show/showfile/console": this.node("i", 0),
2706
+ "/-show/showfile/chan16": this.node("i", 0),
2707
+ "/-show/showfile/chan32": this.node("i", 0),
2708
+ "/-show/showfile/return": this.node("i", 0),
2709
+ "/-show/showfile/buses": this.node("i", 0),
2710
+ "/-show/showfile/lrmtxdca": this.node("i", 0),
2711
+ "/-show/showfile/effects": this.node("i", 0),
2712
+ ...this.generateShowfileCues(),
2713
+ ...this.generateShowfileScenes(),
2714
+ ...this.generateShowfileSnippets(),
2715
+ "/-usb/path": this.node("s", ""),
2716
+ "/-usb/title": this.node("s", ""),
2717
+ "/-usb/dir": this.node("i", 0),
2718
+ "/-usb/dirpos": this.node("i", 0),
2719
+ "/-usb/maxpos": this.node("i", 0),
2720
+ ...this.generateUSBDir(),
2721
+ "/-urec/sessionmax": this.node("i", 0),
2722
+ "/-urec/markermax": this.node("i", 0),
2723
+ "/-urec/sessionlen": this.node("i", 0),
2724
+ "/-urec/sessionpos": this.node("i", 0),
2725
+ "/-urec/markerpos": this.node("i", 0),
2726
+ "/-urec/batterystate": this.node("i", 0),
2727
+ "/-urec/srate": this.node("i", 0),
2728
+ "/-urec/tracks": this.node("i", 0),
2729
+ "/-urec/sessionspan": this.node("i", 0),
2730
+ "/-urec/sessionoffs": this.node("i", 0),
2731
+ "/-urec/sd1state": this.node("i", 0),
2732
+ "/-urec/sd2state": this.node("i", 0),
2733
+ "/-urec/sd1info": this.node("s", ""),
2734
+ "/-urec/sd2info": this.node("s", ""),
2735
+ "/-urec/errormessage": this.node("s", ""),
2736
+ "/-urec/errorcode": this.node("i", 0),
2737
+ ...this.generateURecSessions(),
2738
+ ...this.generateLibraries(),
2739
+ "/-insert": this.node("i", 0),
2740
+ "/-prefs/style": this.node("i", 0),
2741
+ "/-prefs/bright": this.node("i", 50),
2742
+ "/-prefs/lcdcont": this.node("i", 50),
2743
+ "/-prefs/ledbright": this.node("i", 50),
2744
+ "/-prefs/lamp": this.node("i", 50),
2745
+ "/-prefs/lampon": this.node("i", 0),
2746
+ "/-prefs/clockrate": this.node("i", 0),
2747
+ "/-prefs/clocksource": this.node("i", 0),
2748
+ "/-prefs/confirm_general": this.node("i", 1),
2749
+ "/-prefs/confirm_overwrite": this.node("i", 1),
2750
+ "/-prefs/confirm_sceneload": this.node("i", 1),
2751
+ "/-prefs/viewrtn": this.node("i", 0),
2752
+ "/-prefs/selfollowsbank": this.node("i", 0),
2753
+ "/-prefs/scene_advance": this.node("i", 0),
2754
+ "/-prefs/safe_masterlevels": this.node("i", 0),
2755
+ "/-prefs/haflags": this.node("i", 0),
2756
+ "/-prefs/autosel": this.node("i", 1),
2757
+ "/-prefs/show_control": this.node("i", 0),
2758
+ "/-prefs/clockmode": this.node("i", 0),
2759
+ "/-prefs/hardmute": this.node("i", 0),
2760
+ "/-prefs/dcamute": this.node("i", 0),
2761
+ "/-prefs/invertmutes": this.node("i", 0),
2762
+ "/-prefs/name": this.node("s", "X32-Simulator"),
2763
+ "/-prefs/rec_control": this.node("i", 0),
2764
+ "/-prefs/fastFaders": this.node("i", 0),
2765
+ "/-prefs/ip": this.node("s", ""),
2766
+ "/-prefs/ip/dhcp": this.node("i", 0),
2767
+ "/-prefs/ip/addr": this.node("s", "192.168.1.1"),
2768
+ "/-prefs/ip/mask": this.node("s", "255.255.255.0"),
2769
+ "/-prefs/ip/gateway": this.node("s", "192.168.1.1"),
2770
+ "/-prefs/remote": this.node("i", 0),
2771
+ "/-prefs/remote/enable": this.node("i", 0),
2772
+ "/-prefs/remote/protocol": this.node("i", 0),
2773
+ "/-prefs/remote/port": this.node("i", 10023),
2774
+ "/-prefs/remote/ioenable": this.node("i", 0),
2775
+ "/-prefs/card": this.node("i", 0),
2776
+ "/-prefs/card/UFifc": this.node("i", 0),
2777
+ "/-prefs/card/UFmode": this.node("i", 0),
2778
+ "/-prefs/card/USBmode": this.node("i", 0),
2779
+ "/-prefs/card/ADATwc": this.node("i", 0),
2780
+ "/-prefs/card/ADATsync": this.node("i", 0),
2781
+ "/-prefs/card/MADImode": this.node("i", 0),
2782
+ "/-prefs/card/MADIin": this.node("i", 0),
2783
+ "/-prefs/card/MADIout": this.node("i", 0),
2784
+ "/-prefs/card/MADIsrc": this.node("i", 0),
2785
+ "/-prefs/card/URECtracks": this.node("i", 0),
2786
+ "/-prefs/card/URECplayb": this.node("i", 0),
2787
+ "/-prefs/card/URECrout": this.node("i", 0),
2788
+ "/-prefs/card/URECsdsel": this.node("i", 0),
2789
+ "/-prefs/rta": this.node("i", 0),
2790
+ "/-prefs/rta/visibility": this.node("s", "70%"),
2791
+ "/-prefs/rta/gain": this.node("f", 0),
2792
+ "/-prefs/rta/autogain": this.node("i", 0),
2793
+ "/-prefs/rta/source": this.node("i", 0),
2794
+ "/-prefs/rta/pos": this.node("i", 0),
2795
+ "/-prefs/rta/mode": this.node("i", 0),
2796
+ "/-prefs/rta/option": this.node("i", 0),
2797
+ "/-prefs/rta/det": this.node("i", 0),
2798
+ "/-prefs/rta/decay": this.node("i", 0),
2799
+ "/-prefs/rta/peakhold": this.node("i", 0),
2800
+ ...this.generatePrefsIQ(),
2801
+ "/-prefs/key/layout": this.node("i", 0),
2802
+ ...this.generateRange(100, "/-prefs/key", "", "i", 0, 2, 0),
1897
2803
  "/config/routing/AES50A/1-8": this.node("i", 0),
1898
2804
  "/config/routing/AES50B/1-8": this.node("i", 0),
1899
2805
  "/config/routing/CARD/1-8": this.node("i", 0),
1900
2806
  "/config/routing/OUT/1-4": this.node("i", 0),
1901
- "/-prefs/invertmutes": this.node("i", 0),
1902
2807
  ...this.generateChannelStrip("/main/st", 6, false),
1903
2808
  ...this.generateChannelStrip("/main/m", 6, false),
1904
2809
  ...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1905
2810
  ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1906
2811
  ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1907
2812
  ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/fxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
2813
+ ...Object.fromEntries(Array.from({ length: 3 }, (_, i) => [`/config/mtxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
2814
+ "/config/linkcfg/hadly": this.node("i", 0),
2815
+ "/config/linkcfg/eq": this.node("i", 0),
2816
+ "/config/linkcfg/dyn": this.node("i", 0),
2817
+ "/config/linkcfg/fdrmute": this.node("i", 0),
2818
+ "/config/mono/mode": this.node("i", 0),
2819
+ "/config/mono/link": this.node("i", 0),
2820
+ "/config/tape/autoplay": this.node("i", 0),
2821
+ "/config/tape/gainL": this.node("f", .5),
2822
+ "/config/tape/gainR": this.node("f", .5),
2823
+ "/config/amixenable/X": this.node("i", 0),
2824
+ "/config/amixenable/Y": this.node("i", 0),
2825
+ ...this.generateUserCtrl(),
2826
+ "/config/routing/routswitch": this.node("i", 0),
1908
2827
  ...this.generateRange(32, "/config/userrout/in", "", "i", 0),
1909
2828
  ...this.generateRange(48, "/config/userrout/out", "", "i", 0),
1910
2829
  "/config/solo/level": this.node("f", 0),
@@ -1940,6 +2859,7 @@ var SchemaFactory = class {
1940
2859
  "/config/osc/f2": this.node("f", .5),
1941
2860
  "/config/osc/level": this.node("f", 0),
1942
2861
  "/config/osc/dest": this.node("i", 0),
2862
+ ...this.generateDP48Config(),
1943
2863
  ...this.generateOutputs("main", 16),
1944
2864
  ...this.generateOutputs("aux", 6),
1945
2865
  ...this.generateOutputs("p16", 16),
@@ -1947,7 +2867,7 @@ var SchemaFactory = class {
1947
2867
  ...this.generateOutputs("rec", 2)
1948
2868
  };
1949
2869
  Object.keys(STATIC_RESPONSES_DATA).forEach((key) => {
1950
- if (key.startsWith("/-") || key.startsWith("/stat")) {
2870
+ if (key.startsWith("/-")) {
1951
2871
  const val = STATIC_RESPONSES_DATA[key][0];
1952
2872
  const type = typeof val === "string" ? "s" : Number.isInteger(val) ? "i" : "f";
1953
2873
  schema[key] = this.node(type, val);
@@ -2062,6 +2982,7 @@ var SchemaFactory = class {
2062
2982
  generateNodes(count, prefix) {
2063
2983
  const nodes = {};
2064
2984
  const isChannelOrAux = prefix === "ch" || prefix === "auxin";
2985
+ const hasMtxPreamp = prefix === "mtx";
2065
2986
  const eqBands = prefix === "bus" || prefix === "mtx" ? 6 : 4;
2066
2987
  for (let i = 1; i <= count; i++) {
2067
2988
  const padId = i.toString().padStart(2, "0");
@@ -2073,7 +2994,7 @@ var SchemaFactory = class {
2073
2994
  nodes[`${base}/fader`] = this.node("f", .75);
2074
2995
  nodes[`${base}/on`] = this.node("i", 0);
2075
2996
  } else {
2076
- Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
2997
+ Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux || hasMtxPreamp));
2077
2998
  if (prefix === "ch" || prefix === "auxin" || prefix === "fxrtn" || prefix === "bus") for (let b = 1; b <= 16; b++) {
2078
2999
  const busId = b.toString().padStart(2, "0");
2079
3000
  nodes[`${base}/mix/${busId}/level`] = this.node("f", 0);
@@ -2081,6 +3002,11 @@ var SchemaFactory = class {
2081
3002
  nodes[`${base}/mix/${busId}/pan`] = this.node("f", .5);
2082
3003
  nodes[`${base}/mix/${busId}/type`] = this.node("i", 0);
2083
3004
  }
3005
+ if (prefix === "ch") {
3006
+ nodes[`${base}/automix`] = this.node("i", 0);
3007
+ nodes[`${base}/automix/group`] = this.node("i", 0);
3008
+ nodes[`${base}/automix/weight`] = this.node("f", 0);
3009
+ }
2084
3010
  }
2085
3011
  });
2086
3012
  }
@@ -2113,6 +3039,159 @@ var SchemaFactory = class {
2113
3039
  }
2114
3040
  return nodes;
2115
3041
  }
3042
+ /**
3043
+ * Generates showfile cue entries (100 cues: 000-099)
3044
+ */
3045
+ generateShowfileCues() {
3046
+ const nodes = {};
3047
+ for (let i = 0; i < 100; i++) {
3048
+ const base = `/-show/showfile/cue/${i.toString().padStart(3, "0")}`;
3049
+ nodes[base] = this.node("s", "");
3050
+ nodes[`${base}/numb`] = this.node("i", 0);
3051
+ nodes[`${base}/name`] = this.node("s", "");
3052
+ nodes[`${base}/skip`] = this.node("i", 0);
3053
+ nodes[`${base}/scene`] = this.node("i", 0);
3054
+ nodes[`${base}/bit`] = this.node("i", 0);
3055
+ nodes[`${base}/miditype`] = this.node("i", 0);
3056
+ nodes[`${base}/midichan`] = this.node("i", 0);
3057
+ nodes[`${base}/midipara1`] = this.node("i", 0);
3058
+ nodes[`${base}/midipara2`] = this.node("i", 0);
3059
+ }
3060
+ return nodes;
3061
+ }
3062
+ /**
3063
+ * Generates showfile scene entries (100 scenes: 000-099)
3064
+ */
3065
+ generateShowfileScenes() {
3066
+ const nodes = {};
3067
+ for (let i = 0; i < 100; i++) {
3068
+ const base = `/-show/showfile/scene/${i.toString().padStart(3, "0")}`;
3069
+ nodes[base] = this.node("s", "");
3070
+ nodes[`${base}/name`] = this.node("s", "");
3071
+ nodes[`${base}/notes`] = this.node("s", "");
3072
+ nodes[`${base}/safes`] = this.node("i", 0);
3073
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3074
+ }
3075
+ return nodes;
3076
+ }
3077
+ /**
3078
+ * Generates showfile snippet entries (100 snippets: 000-099)
3079
+ */
3080
+ generateShowfileSnippets() {
3081
+ const nodes = {};
3082
+ for (let i = 0; i < 100; i++) {
3083
+ const base = `/-show/showfile/snippet/${i.toString().padStart(3, "0")}`;
3084
+ nodes[base] = this.node("s", "");
3085
+ nodes[`${base}/name`] = this.node("s", "");
3086
+ nodes[`${base}/eventtyp`] = this.node("i", 0);
3087
+ nodes[`${base}/channels`] = this.node("i", 0);
3088
+ nodes[`${base}/auxbuses`] = this.node("i", 0);
3089
+ nodes[`${base}/maingrps`] = this.node("i", 0);
3090
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3091
+ }
3092
+ return nodes;
3093
+ }
3094
+ /**
3095
+ * Generates preferences iQ entries (16 entries: 01-16)
3096
+ */
3097
+ generatePrefsIQ() {
3098
+ const nodes = {};
3099
+ for (let i = 1; i <= 16; i++) {
3100
+ const base = `/-prefs/iQ/${i.toString().padStart(2, "0")}`;
3101
+ nodes[base] = this.node("i", 0);
3102
+ nodes[`${base}/iQmodel`] = this.node("i", 0);
3103
+ nodes[`${base}/iQeqset`] = this.node("i", 0);
3104
+ nodes[`${base}/iQsound`] = this.node("i", 0);
3105
+ }
3106
+ return nodes;
3107
+ }
3108
+ /**
3109
+ * Generates DP48 Personal Monitor config entries (48 channels)
3110
+ */
3111
+ generateDP48Config() {
3112
+ const nodes = {};
3113
+ for (let i = 1; i <= 48; i++) {
3114
+ const id = i.toString().padStart(2, "0");
3115
+ nodes[`/config/dp48/link/${id}`] = this.node("i", 0);
3116
+ }
3117
+ nodes["/config/dp48/assign"] = this.node("i", 0);
3118
+ nodes["/config/dp48/grpname/01"] = this.node("s", "");
3119
+ nodes["/config/dp48/grpname/02"] = this.node("s", "");
3120
+ nodes["/config/dp48/grpname/03"] = this.node("s", "");
3121
+ nodes["/config/dp48/grpname/04"] = this.node("s", "");
3122
+ nodes["/config/dp48/grpname/05"] = this.node("s", "");
3123
+ nodes["/config/dp48/grpname/06"] = this.node("s", "");
3124
+ nodes["/config/dp48/grpname/07"] = this.node("s", "");
3125
+ nodes["/config/dp48/grpname/08"] = this.node("s", "");
3126
+ nodes["/config/dp48/grpname/09"] = this.node("s", "");
3127
+ nodes["/config/dp48/grpname/10"] = this.node("s", "");
3128
+ nodes["/config/dp48/grpname/11"] = this.node("s", "");
3129
+ nodes["/config/dp48/grpname/12"] = this.node("s", "");
3130
+ return nodes;
3131
+ }
3132
+ /**
3133
+ * Generates USB directory entries (1000 entries: 000-999)
3134
+ */
3135
+ generateUSBDir() {
3136
+ const nodes = {};
3137
+ for (let i = 0; i < 1e3; i++) {
3138
+ const id = i.toString().padStart(3, "0");
3139
+ nodes[`/-usb/dir/${id}`] = this.node("s", "");
3140
+ nodes[`/-usb/dir/${id}/name`] = this.node("s", "");
3141
+ }
3142
+ return nodes;
3143
+ }
3144
+ /**
3145
+ * Generates USB recording session and marker entries
3146
+ */
3147
+ generateURecSessions() {
3148
+ const nodes = {};
3149
+ for (let i = 1; i <= 100; i++) {
3150
+ const id = i.toString().padStart(3, "0");
3151
+ nodes[`/-urec/session/${id}/name`] = this.node("s", "");
3152
+ nodes[`/-urec/marker/${id}/time`] = this.node("i", 0);
3153
+ }
3154
+ return nodes;
3155
+ }
3156
+ /**
3157
+ * Generates library entries (ch, fx, r, mon - 100 each)
3158
+ */
3159
+ generateLibraries() {
3160
+ const nodes = {};
3161
+ for (const lib of [
3162
+ "ch",
3163
+ "fx",
3164
+ "r",
3165
+ "mon"
3166
+ ]) {
3167
+ nodes[`/-libs/${lib}`] = this.node("i", 0);
3168
+ for (let i = 1; i <= 100; i++) {
3169
+ const base = `/-libs/${lib}/${i.toString().padStart(3, "0")}`;
3170
+ nodes[base] = this.node("s", "");
3171
+ nodes[`${base}/pos`] = this.node("i", 0);
3172
+ nodes[`${base}/name`] = this.node("s", "");
3173
+ nodes[`${base}/flags`] = this.node("i", 0);
3174
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3175
+ }
3176
+ }
3177
+ return nodes;
3178
+ }
3179
+ /**
3180
+ * Generates user control entries (A, B, C with enc and btn)
3181
+ */
3182
+ generateUserCtrl() {
3183
+ const nodes = {};
3184
+ for (const bank of [
3185
+ "A",
3186
+ "B",
3187
+ "C"
3188
+ ]) {
3189
+ nodes[`/config/userctrl/${bank}`] = this.node("i", 0);
3190
+ for (let i = 1; i <= 4; i++) nodes[`/config/userctrl/${bank}/enc/${i}`] = this.node("i", 0);
3191
+ for (let i = 1; i <= 12; i++) nodes[`/config/userctrl/${bank}/btn/${i}`] = this.node("i", 0);
3192
+ }
3193
+ return nodes;
3194
+ }
2116
3195
  };
2117
3196
 
2118
3197
  //#endregion
@@ -2177,4 +3256,4 @@ var SchemaRegistry = class {
2177
3256
  };
2178
3257
 
2179
3258
  //#endregion
2180
- export { ConsoleLogger as a, X32Node as c, METER_COUNTS as d, MeterData as f, UdpNetworkGateway as i, X32State as l, LogCategory as m, SchemaFactory as n, LogLevel as o, SubscriptionManager as p, OscCodec as r, InMemoryStateRepository as s, SchemaRegistry as t, SimulationService as u };
3259
+ export { ConsoleLogger as a, X32Node as c, MeterData as d, SubscriptionManager as f, LogCategory as g, X32State as h, UdpNetworkGateway as i, SimulationService as l, mergeConfig as m, SchemaFactory as n, LogLevel as o, DEFAULT_CONFIG as p, OscCodec as r, InMemoryStateRepository as s, SchemaRegistry as t, METER_COUNTS as u };