@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.
@@ -25,37 +25,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
 
27
27
  //#endregion
28
+ let node_events = require("node:events");
28
29
  let os = require("os");
29
30
  os = __toESM(os);
30
- let node_events = require("node:events");
31
31
  let node_dgram = require("node:dgram");
32
32
  node_dgram = __toESM(node_dgram);
33
33
  let node_buffer = require("node:buffer");
34
34
 
35
- //#region src/application/use-cases/ProcessPacketUseCase.ts
36
- var ProcessPacketUseCase = class {
37
- constructor(messageHandler, gateway) {
38
- this.messageHandler = messageHandler;
39
- this.gateway = gateway;
40
- }
41
- execute(packet, rinfo) {
42
- if (packet.oscType === "bundle") packet.elements?.forEach((el) => this.execute(el, rinfo));
43
- else if (packet.oscType === "message") {
44
- const args = packet.args.map((arg) => {
45
- if (typeof arg === "object" && arg !== null && "value" in arg) return arg.value;
46
- return arg;
47
- });
48
- this.messageHandler.handle({
49
- address: packet.address,
50
- args
51
- }, rinfo).forEach((reply) => {
52
- this.gateway.send(rinfo, reply.address, reply.args);
53
- });
54
- }
55
- }
56
- };
57
-
58
- //#endregion
59
35
  //#region src/domain/ports/ILogger.ts
60
36
  /**
61
37
  * Standard log categories for the domain.
@@ -71,6 +47,35 @@ let LogCategory = /* @__PURE__ */ function(LogCategory$1) {
71
47
  return LogCategory$1;
72
48
  }({});
73
49
 
50
+ //#endregion
51
+ //#region src/application/use-cases/ProcessPacketUseCase.ts
52
+ var ProcessPacketUseCase = class {
53
+ constructor(messageHandler, gateway, logger) {
54
+ this.messageHandler = messageHandler;
55
+ this.gateway = gateway;
56
+ this.logger = logger;
57
+ }
58
+ execute(packet, rinfo) {
59
+ try {
60
+ if (packet.oscType === "bundle") packet.elements?.forEach((el) => this.execute(el, rinfo));
61
+ else if (packet.oscType === "message") {
62
+ const args = packet.args.map((arg) => {
63
+ if (typeof arg === "object" && arg !== null && "value" in arg) return arg.value;
64
+ return arg;
65
+ });
66
+ this.messageHandler.handle({
67
+ address: packet.address,
68
+ args
69
+ }, rinfo).forEach((reply) => {
70
+ this.gateway.send(rinfo, reply.address, reply.args);
71
+ });
72
+ }
73
+ } catch (err) {
74
+ this.logger?.error(LogCategory.DISPATCH, `Error processing packet from ${rinfo.address}:${rinfo.port}`, err instanceof Error ? err : new Error(String(err)));
75
+ }
76
+ }
77
+ };
78
+
74
79
  //#endregion
75
80
  //#region src/application/use-cases/BroadcastUpdatesUseCase.ts
76
81
  var BroadcastUpdatesUseCase = class {
@@ -188,16 +193,134 @@ var ManageSessionsUseCase = class {
188
193
  }
189
194
  };
190
195
 
196
+ //#endregion
197
+ //#region src/domain/entities/X32State.ts
198
+ /**
199
+ * Manages the internal "Digital Twin" state of the X32 console.
200
+ * It acts as a single source of truth for all parameters.
201
+ * Emits 'change' events whenever a value is updated.
202
+ * Uses an injectable repository for storage (memory or persistent).
203
+ */
204
+ var X32State = class extends node_events.EventEmitter {
205
+ defaultState = /* @__PURE__ */ new Map();
206
+ /**
207
+ * Initializes the state with default values from the schema.
208
+ * @param schema - The schema definition map.
209
+ * @param repository - The storage repository (injectable).
210
+ */
211
+ constructor(schema, repository) {
212
+ super();
213
+ this.repository = repository;
214
+ for (const [addr, def] of Object.entries(schema)) this.defaultState.set(addr, def.default);
215
+ }
216
+ /**
217
+ * Initializes the state - loads from repository or resets to defaults.
218
+ */
219
+ async initialize() {
220
+ const stored = await this.repository.load();
221
+ if (Object.keys(stored).length === 0) this.reset();
222
+ }
223
+ /**
224
+ * Resets all state parameters to their default values defined in the schema.
225
+ */
226
+ reset() {
227
+ const defaults = {};
228
+ this.defaultState.forEach((val, key) => {
229
+ defaults[key] = val;
230
+ });
231
+ this.repository.reset(defaults);
232
+ }
233
+ /**
234
+ * Retrieves the value of a specific OSC node.
235
+ * @param address - The full OSC address path.
236
+ * @returns The stored value (number or string) or undefined if not found.
237
+ */
238
+ get(address) {
239
+ return this.repository.get(address);
240
+ }
241
+ /**
242
+ * Retrieves all state entries as a plain object.
243
+ * @returns A record of all OSC addresses and their current values.
244
+ */
245
+ getAll() {
246
+ return this.repository.getAll();
247
+ }
248
+ /**
249
+ * Updates the value of a specific OSC node and notifies subscribers.
250
+ * @param address - The full OSC address path.
251
+ * @param value - The new value to store.
252
+ */
253
+ set(address, value) {
254
+ this.repository.set(address, value);
255
+ this.emit("change", {
256
+ address,
257
+ value
258
+ });
259
+ }
260
+ /**
261
+ * Flushes pending changes to persistent storage.
262
+ */
263
+ async flush() {
264
+ await this.repository.flush();
265
+ }
266
+ /**
267
+ * Specialized logic to handle X32 Mute Groups side-effects.
268
+ * When a mute group is toggled, it iterates through all channels
269
+ * assigned to that group and updates their individual mute status.
270
+ * @param groupIdx - The index of the mute group (1-6).
271
+ * @param isOn - The new state of the group master switch (0 or 1).
272
+ */
273
+ handleMuteGroupChange(groupIdx, isOn) {
274
+ for (let i = 1; i <= 32; i++) {
275
+ const ch = i.toString().padStart(2, "0");
276
+ const grpVal = this.get(`/ch/${ch}/grp/mute`);
277
+ if (typeof grpVal === "number") {
278
+ if ((grpVal & 1 << groupIdx - 1) !== 0) {
279
+ const targetMute = isOn === 1 ? 0 : 1;
280
+ const muteAddr = `/ch/${ch}/mix/on`;
281
+ this.set(muteAddr, targetMute);
282
+ }
283
+ }
284
+ }
285
+ }
286
+ };
287
+
288
+ //#endregion
289
+ //#region src/domain/models/Config.ts
290
+ /**
291
+ * Default configuration values for the simulator.
292
+ */
293
+ const DEFAULT_CONFIG = {
294
+ SUBSCRIPTION_TTL_MS: 1e4,
295
+ CLEANUP_INTERVAL_MS: 5e3,
296
+ BROADCAST_INTERVAL_MS: 100,
297
+ DEFAULT_PORT: 10023,
298
+ DEFAULT_HOST: "0.0.0.0",
299
+ DEFAULT_NAME: "osc-server",
300
+ DEFAULT_MODEL: "X32"
301
+ };
302
+ /**
303
+ * Merges user-provided configuration with defaults.
304
+ * @param partial - Partial configuration to merge.
305
+ * @returns Complete configuration with defaults applied.
306
+ */
307
+ function mergeConfig(partial) {
308
+ return {
309
+ ...DEFAULT_CONFIG,
310
+ ...partial
311
+ };
312
+ }
313
+
191
314
  //#endregion
192
315
  //#region src/domain/entities/SubscriptionManager.ts
193
316
  /**
194
317
  * Manages OSC client subscriptions and their lifecycle.
195
318
  */
196
319
  var SubscriptionManager = class {
197
- logger;
198
320
  subscribers = [];
199
- constructor(logger) {
321
+ constructor(logger, subscriptionTtlMs = DEFAULT_CONFIG.SUBSCRIPTION_TTL_MS) {
200
322
  this.logger = logger;
323
+ this.subscriptionTtlMs = subscriptionTtlMs;
201
324
  }
202
325
  /**
203
326
  * Cleans up expired subscriptions.
@@ -235,7 +358,7 @@ var SubscriptionManager = class {
235
358
  */
236
359
  addPathSubscriber(rinfo, path) {
237
360
  const key = `${rinfo.address}:${rinfo.port}:${path}`;
238
- const expires = Date.now() + 1e4;
361
+ const expires = Date.now() + this.subscriptionTtlMs;
239
362
  const existing = this.subscribers.find((s) => s.type === "path" && `${s.address}:${s.port}:${s.path}` === key);
240
363
  if (existing) {
241
364
  existing.expires = expires;
@@ -268,7 +391,7 @@ var SubscriptionManager = class {
268
391
  * @param args - Command arguments.
269
392
  */
270
393
  addBatchSubscriber(rinfo, alias, paths, start, count, factor, args) {
271
- const expires = Date.now() + 1e4;
394
+ const expires = Date.now() + this.subscriptionTtlMs;
272
395
  this.subscribers = this.subscribers.filter((s) => !(s.type === "batch" && s.alias === alias && s.address === rinfo.address));
273
396
  this.subscribers.push({
274
397
  type: "batch",
@@ -300,7 +423,7 @@ var SubscriptionManager = class {
300
423
  * @param factor - Frequency factor.
301
424
  */
302
425
  addFormatSubscriber(rinfo, alias, pattern, start, count, factor) {
303
- const expires = Date.now() + 1e4;
426
+ const expires = Date.now() + this.subscriptionTtlMs;
304
427
  this.subscribers = this.subscribers.filter((s) => !(s.type === "format" && s.alias === alias && s.address === rinfo.address));
305
428
  this.subscribers.push({
306
429
  type: "format",
@@ -326,7 +449,7 @@ var SubscriptionManager = class {
326
449
  * @param meterPath - Target meter path.
327
450
  */
328
451
  addMeterSubscriber(rinfo, meterPath) {
329
- const expires = Date.now() + 1e4;
452
+ const expires = Date.now() + this.subscriptionTtlMs;
330
453
  const existing = this.subscribers.find((s) => s.type === "meter" && s.meterPath === meterPath && s.address === rinfo.address);
331
454
  this.subscribers = this.subscribers.filter((s) => !(s.type === "meter" && s.meterPath === meterPath && s.address === rinfo.address));
332
455
  this.subscribers.push({
@@ -391,8 +514,9 @@ var NodeDiscoveryStrategy = class {
391
514
  //#endregion
392
515
  //#region src/domain/services/strategies/StaticResponseStrategy.ts
393
516
  /**
394
- * Handles discovery and static information queries.
395
- * e.g., /status, /xinfo, /-prefs/...
517
+ * Handles discovery and static information queries (GET only).
518
+ * e.g., /status, /xinfo
519
+ * Note: Only handles queries without arguments. SET commands pass through to StateAccessStrategy.
396
520
  */
397
521
  var StaticResponseStrategy = class {
398
522
  constructor(serverIp, serverName, serverModel, staticResponseService) {
@@ -401,7 +525,8 @@ var StaticResponseStrategy = class {
401
525
  this.serverModel = serverModel;
402
526
  this.staticResponseService = staticResponseService;
403
527
  }
404
- canHandle(address) {
528
+ canHandle(address, args) {
529
+ if (args && args.length > 0) return false;
405
530
  return !!this.staticResponseService.getResponse(address);
406
531
  }
407
532
  execute(msg, _source) {
@@ -572,6 +697,7 @@ var MeterStrategy = class {
572
697
  * LOGIC:
573
698
  * - If no arguments: Treats as a QUERY (GET) and returns the current value.
574
699
  * - If arguments provided: Treats as an UPDATE (SET) and stores the new value.
700
+ * - Unknown addresses are also stored in permissive mode for compatibility.
575
701
  */
576
702
  var StateAccessStrategy = class {
577
703
  /**
@@ -579,45 +705,687 @@ var StateAccessStrategy = class {
579
705
  * @param state - Current mixer state.
580
706
  * @param logger - Logger instance.
581
707
  * @param schemaRegistry - Registry to validate addresses.
708
+ * @param permissive - If true, store unknown addresses too (default: true).
582
709
  */
583
- constructor(state, logger, schemaRegistry) {
710
+ constructor(state, logger, schemaRegistry, permissive = true) {
584
711
  this.state = state;
585
712
  this.logger = logger;
586
713
  this.schemaRegistry = schemaRegistry;
714
+ this.permissive = permissive;
587
715
  }
588
716
  /** @inheritdoc */
589
717
  canHandle(address) {
590
- return this.schemaRegistry.has(address);
718
+ if (this.schemaRegistry.has(address)) return true;
719
+ return this.permissive && address.startsWith("/");
591
720
  }
592
721
  /** @inheritdoc */
593
722
  execute(msg, _source) {
594
723
  const addr = msg.address;
595
724
  const node = this.schemaRegistry.getNode(addr);
725
+ const isKnown = !!node;
596
726
  if (msg.args.length === 0) {
597
727
  const val$1 = this.state.get(addr);
598
728
  if (val$1 !== void 0) return [{
599
729
  address: addr,
600
730
  args: [val$1]
601
731
  }];
732
+ if (!isKnown) this.logger.debug(LogCategory.STATE, `[GET] Unknown address queried: ${addr}`);
602
733
  return [];
603
734
  }
604
735
  const val = msg.args[0];
605
- if (node) {
736
+ if (isKnown) {
606
737
  if (!node.validate(val)) {
607
738
  this.logger.warn(LogCategory.DISPATCH, `[TYPE ERR] ${addr} expected ${node.type}, got ${typeof val}`);
608
739
  return [];
609
740
  }
610
- this.state.set(addr, val);
611
- this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
612
- if (addr.startsWith("/config/mute/")) {
613
- const groupIdx = parseInt(addr.split("/").pop(), 10);
614
- if (!isNaN(groupIdx)) this.state.handleMuteGroupChange(groupIdx, val);
615
- }
741
+ } else this.logger.warn(LogCategory.STATE, `[UNKNOWN] Storing unknown address: ${addr} = ${val}`);
742
+ this.state.set(addr, val);
743
+ this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
744
+ if (addr.startsWith("/config/mute/")) {
745
+ const groupIdx = parseInt(addr.split("/").pop(), 10);
746
+ if (!isNaN(groupIdx)) this.state.handleMuteGroupChange(groupIdx, val);
747
+ }
748
+ if (addr === "/-action/undopt" && val === 1) {
749
+ const timeStr = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
750
+ this.state.set("/-undo/time", timeStr);
751
+ this.logger.debug(LogCategory.STATE, `[UNDO] Checkpoint created at ${timeStr}`);
616
752
  }
617
753
  return [];
618
754
  }
619
755
  };
620
756
 
757
+ //#endregion
758
+ //#region src/domain/services/strategies/ShowDumpStrategy.ts
759
+ /**
760
+ * Handles the /showdump command which requests a full state dump.
761
+ * Used by X32-Edit during synchronization to retrieve all console parameters.
762
+ */
763
+ var ShowDumpStrategy = class {
764
+ constructor(state, logger) {
765
+ this.state = state;
766
+ this.logger = logger;
767
+ }
768
+ /** @inheritdoc */
769
+ canHandle(address) {
770
+ return address === "/showdump";
771
+ }
772
+ /** @inheritdoc */
773
+ execute(_msg, _source) {
774
+ this.logger.debug(LogCategory.DISPATCH, "[SHOWDUMP] Full state dump requested");
775
+ const replies = [];
776
+ const allState = this.state.getAll();
777
+ for (const [address, value] of Object.entries(allState)) replies.push({
778
+ address,
779
+ args: [value]
780
+ });
781
+ this.logger.debug(LogCategory.DISPATCH, `[SHOWDUMP] Sending ${replies.length} state entries`);
782
+ return replies;
783
+ }
784
+ };
785
+
786
+ //#endregion
787
+ //#region src/domain/services/strategies/SceneDataStrategy.ts
788
+ /**
789
+ * Maps scene file format (batch values) to individual OSC addresses.
790
+ * The X32 scene format sends multiple values for a parent address.
791
+ * Example: /ch/01/mix ON -0.2 ON -100 OFF -oo
792
+ * 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
793
+ */
794
+ const SCENE_MAPPINGS = {
795
+ "headamp": ["gain", "phantom"],
796
+ "config": [
797
+ "name",
798
+ "icon",
799
+ "color",
800
+ "source"
801
+ ],
802
+ "delay": ["on", "time"],
803
+ "preamp": [
804
+ "trim",
805
+ "invert",
806
+ "hpon",
807
+ "hpslope",
808
+ "hpf"
809
+ ],
810
+ "gate": [
811
+ "on",
812
+ "mode",
813
+ "thr",
814
+ "range",
815
+ "attack",
816
+ "hold",
817
+ "release",
818
+ "keysrc"
819
+ ],
820
+ "gate/filter": [
821
+ "on",
822
+ "type",
823
+ "f"
824
+ ],
825
+ "dyn": [
826
+ "on",
827
+ "mode",
828
+ "det",
829
+ "env",
830
+ "thr",
831
+ "ratio",
832
+ "knee",
833
+ "mgain",
834
+ "attack",
835
+ "hold",
836
+ "release",
837
+ "pos",
838
+ "keysrc",
839
+ "mix",
840
+ "auto"
841
+ ],
842
+ "dyn/filter": [
843
+ "on",
844
+ "type",
845
+ "f"
846
+ ],
847
+ "insert": [
848
+ "on",
849
+ "pos",
850
+ "sel"
851
+ ],
852
+ "eq": ["on"],
853
+ "eq/1": [
854
+ "type",
855
+ "f",
856
+ "g",
857
+ "q"
858
+ ],
859
+ "eq/2": [
860
+ "type",
861
+ "f",
862
+ "g",
863
+ "q"
864
+ ],
865
+ "eq/3": [
866
+ "type",
867
+ "f",
868
+ "g",
869
+ "q"
870
+ ],
871
+ "eq/4": [
872
+ "type",
873
+ "f",
874
+ "g",
875
+ "q"
876
+ ],
877
+ "eq/5": [
878
+ "type",
879
+ "f",
880
+ "g",
881
+ "q"
882
+ ],
883
+ "eq/6": [
884
+ "type",
885
+ "f",
886
+ "g",
887
+ "q"
888
+ ],
889
+ "mix": [
890
+ "on",
891
+ "fader",
892
+ "st",
893
+ "pan",
894
+ "mono",
895
+ "mlevel"
896
+ ],
897
+ "mix/01": [
898
+ "on",
899
+ "level",
900
+ "pan",
901
+ "type",
902
+ "panFollow"
903
+ ],
904
+ "mix/02": ["on", "level"],
905
+ "mix/03": [
906
+ "on",
907
+ "level",
908
+ "pan",
909
+ "type",
910
+ "panFollow"
911
+ ],
912
+ "mix/04": ["on", "level"],
913
+ "mix/05": [
914
+ "on",
915
+ "level",
916
+ "pan",
917
+ "type",
918
+ "panFollow"
919
+ ],
920
+ "mix/06": ["on", "level"],
921
+ "mix/07": [
922
+ "on",
923
+ "level",
924
+ "pan",
925
+ "type",
926
+ "panFollow"
927
+ ],
928
+ "mix/08": ["on", "level"],
929
+ "mix/09": [
930
+ "on",
931
+ "level",
932
+ "pan",
933
+ "type",
934
+ "panFollow"
935
+ ],
936
+ "mix/10": ["on", "level"],
937
+ "mix/11": [
938
+ "on",
939
+ "level",
940
+ "pan",
941
+ "type",
942
+ "panFollow"
943
+ ],
944
+ "mix/12": ["on", "level"],
945
+ "mix/13": [
946
+ "on",
947
+ "level",
948
+ "pan",
949
+ "type",
950
+ "panFollow"
951
+ ],
952
+ "mix/14": ["on", "level"],
953
+ "mix/15": [
954
+ "on",
955
+ "level",
956
+ "pan",
957
+ "type",
958
+ "panFollow"
959
+ ],
960
+ "mix/16": ["on", "level"],
961
+ "grp": ["dca", "mute"],
962
+ "automix": ["group", "weight"],
963
+ "chlink": [
964
+ "1-2",
965
+ "3-4",
966
+ "5-6",
967
+ "7-8",
968
+ "9-10",
969
+ "11-12",
970
+ "13-14",
971
+ "15-16",
972
+ "17-18",
973
+ "19-20",
974
+ "21-22",
975
+ "23-24",
976
+ "25-26",
977
+ "27-28",
978
+ "29-30",
979
+ "31-32"
980
+ ],
981
+ "auxlink": [
982
+ "1-2",
983
+ "3-4",
984
+ "5-6",
985
+ "7-8"
986
+ ],
987
+ "fxlink": [
988
+ "1-2",
989
+ "3-4",
990
+ "5-6",
991
+ "7-8"
992
+ ],
993
+ "buslink": [
994
+ "1-2",
995
+ "3-4",
996
+ "5-6",
997
+ "7-8",
998
+ "9-10",
999
+ "11-12",
1000
+ "13-14",
1001
+ "15-16"
1002
+ ],
1003
+ "mtxlink": [
1004
+ "1-2",
1005
+ "3-4",
1006
+ "5-6"
1007
+ ],
1008
+ "mute": [
1009
+ "1",
1010
+ "2",
1011
+ "3",
1012
+ "4",
1013
+ "5",
1014
+ "6"
1015
+ ],
1016
+ "linkcfg": [
1017
+ "hadly",
1018
+ "eq",
1019
+ "dyn",
1020
+ "fdrmute"
1021
+ ],
1022
+ "mono": ["mode", "link"],
1023
+ "solo": [
1024
+ "level",
1025
+ "source",
1026
+ "sourcetrim",
1027
+ "chmode",
1028
+ "busmode",
1029
+ "dcamode",
1030
+ "exclusive",
1031
+ "followsel",
1032
+ "followsolo",
1033
+ "dimatt",
1034
+ "dim",
1035
+ "mono",
1036
+ "delay",
1037
+ "delaytime",
1038
+ "masterctrl",
1039
+ "mute",
1040
+ "dimmute"
1041
+ ],
1042
+ "talk": ["enable", "source"],
1043
+ "talk/A": [
1044
+ "level",
1045
+ "dim",
1046
+ "latch",
1047
+ "destmap"
1048
+ ],
1049
+ "talk/B": [
1050
+ "level",
1051
+ "dim",
1052
+ "latch",
1053
+ "destmap"
1054
+ ],
1055
+ "osc": [
1056
+ "level",
1057
+ "f1",
1058
+ "f2",
1059
+ "type",
1060
+ "dest"
1061
+ ],
1062
+ "tape": [
1063
+ "gainL",
1064
+ "gainR",
1065
+ "autoplay"
1066
+ ],
1067
+ "amixenable": ["X", "Y"],
1068
+ "dp48": [
1069
+ "broadcast",
1070
+ "bundles",
1071
+ "assign"
1072
+ ],
1073
+ "dp48/assign": Array.from({ length: 48 }, (_, i) => String(i + 1)),
1074
+ "dp48/link": Array.from({ length: 24 }, (_, i) => String(i + 1)),
1075
+ "dp48/grpname": Array.from({ length: 12 }, (_, i) => String(i + 1)),
1076
+ "userrout/out": Array.from({ length: 48 }, (_, i) => String(i + 1)),
1077
+ "userrout/in": Array.from({ length: 32 }, (_, i) => String(i + 1)),
1078
+ "routing": ["routswitch"],
1079
+ "routing/IN": [
1080
+ "1-8",
1081
+ "9-16",
1082
+ "17-24",
1083
+ "25-32",
1084
+ "AUX"
1085
+ ],
1086
+ "routing/AES50A": [
1087
+ "1-8",
1088
+ "9-16",
1089
+ "17-24",
1090
+ "25-32",
1091
+ "33-40",
1092
+ "41-48"
1093
+ ],
1094
+ "routing/AES50B": [
1095
+ "1-8",
1096
+ "9-16",
1097
+ "17-24",
1098
+ "25-32",
1099
+ "33-40",
1100
+ "41-48"
1101
+ ],
1102
+ "routing/CARD": [
1103
+ "1-8",
1104
+ "9-16",
1105
+ "17-24",
1106
+ "25-32"
1107
+ ],
1108
+ "routing/OUT": [
1109
+ "1-4",
1110
+ "5-8",
1111
+ "9-12",
1112
+ "13-16"
1113
+ ],
1114
+ "routing/PLAY": [
1115
+ "1-8",
1116
+ "9-16",
1117
+ "17-24",
1118
+ "25-32",
1119
+ "AUX"
1120
+ ],
1121
+ "userctrl/A": ["color"],
1122
+ "userctrl/A/enc": [
1123
+ "1",
1124
+ "2",
1125
+ "3",
1126
+ "4"
1127
+ ],
1128
+ "userctrl/A/btn": [
1129
+ "1",
1130
+ "2",
1131
+ "3",
1132
+ "4",
1133
+ "5",
1134
+ "6",
1135
+ "7",
1136
+ "8"
1137
+ ],
1138
+ "userctrl/B": ["color"],
1139
+ "userctrl/B/enc": [
1140
+ "1",
1141
+ "2",
1142
+ "3",
1143
+ "4"
1144
+ ],
1145
+ "userctrl/B/btn": [
1146
+ "1",
1147
+ "2",
1148
+ "3",
1149
+ "4",
1150
+ "5",
1151
+ "6",
1152
+ "7",
1153
+ "8"
1154
+ ],
1155
+ "userctrl/C": ["color"],
1156
+ "userctrl/C/enc": [
1157
+ "1",
1158
+ "2",
1159
+ "3",
1160
+ "4"
1161
+ ],
1162
+ "userctrl/C/btn": [
1163
+ "1",
1164
+ "2",
1165
+ "3",
1166
+ "4",
1167
+ "5",
1168
+ "6",
1169
+ "7",
1170
+ "8",
1171
+ "9",
1172
+ "10",
1173
+ "11",
1174
+ "12"
1175
+ ],
1176
+ "dca": ["on", "fader"],
1177
+ "bus/config": [
1178
+ "name",
1179
+ "icon",
1180
+ "color"
1181
+ ],
1182
+ "bus/mix": [
1183
+ "on",
1184
+ "fader",
1185
+ "st",
1186
+ "pan",
1187
+ "mono",
1188
+ "mlevel"
1189
+ ],
1190
+ "main/st/config": [
1191
+ "name",
1192
+ "icon",
1193
+ "color"
1194
+ ],
1195
+ "main/st/mix": [
1196
+ "on",
1197
+ "fader",
1198
+ "st",
1199
+ "pan"
1200
+ ],
1201
+ "main/m/config": [
1202
+ "name",
1203
+ "icon",
1204
+ "color"
1205
+ ],
1206
+ "main/m/mix": ["on", "fader"],
1207
+ "fx/source": ["inL", "inR"],
1208
+ "fx": ["type"],
1209
+ "fx/par": Array.from({ length: 64 }, (_, i) => String(i + 1)),
1210
+ "outputs/main": [
1211
+ "src",
1212
+ "pos",
1213
+ "invert"
1214
+ ],
1215
+ "outputs/main/delay": ["on", "time"],
1216
+ "outputs/aux": [
1217
+ "src",
1218
+ "pos",
1219
+ "invert"
1220
+ ],
1221
+ "outputs/p16": [
1222
+ "src",
1223
+ "pos",
1224
+ "invert"
1225
+ ],
1226
+ "outputs/p16/iQ": [
1227
+ "model",
1228
+ "eqset",
1229
+ "sound"
1230
+ ],
1231
+ "outputs/aes": [
1232
+ "src",
1233
+ "pos",
1234
+ "invert"
1235
+ ],
1236
+ "outputs/rec": [
1237
+ "src",
1238
+ "pos",
1239
+ "invert"
1240
+ ]
1241
+ };
1242
+ /**
1243
+ * Converts scene file values to proper OSC types.
1244
+ */
1245
+ function parseSceneValue(val) {
1246
+ if (val === "ON") return 1;
1247
+ if (val === "OFF") return 0;
1248
+ if (val === "-oo" || val === "-inf") return -90;
1249
+ if (val === "+oo" || val === "+inf") return 90;
1250
+ if (val.startsWith("%")) return parseInt(val.slice(1), 2);
1251
+ const freqMatch = val.match(/^(\d+)k(\d+)$/);
1252
+ if (freqMatch) return parseFloat(freqMatch[1] + "." + freqMatch[2]) * 1e3;
1253
+ if (val.startsWith("+") || val.startsWith("-")) {
1254
+ const num$1 = parseFloat(val);
1255
+ if (!isNaN(num$1)) return num$1;
1256
+ }
1257
+ const num = parseFloat(val);
1258
+ if (!isNaN(num)) return num;
1259
+ const int = parseInt(val, 10);
1260
+ if (!isNaN(int)) return int;
1261
+ return val.replace(/^"|"$/g, "");
1262
+ }
1263
+ /**
1264
+ * Strategy to handle X32 scene file format (batch multi-value commands).
1265
+ * Expands parent addresses with multiple values into individual parameter sets.
1266
+ */
1267
+ var SceneDataStrategy = class {
1268
+ constructor(state, logger) {
1269
+ this.state = state;
1270
+ this.logger = logger;
1271
+ }
1272
+ canHandle(address, args) {
1273
+ if (!args || args.length === 0) return false;
1274
+ if (args.length === 1) {
1275
+ if (address.match(/^\/fx\/\d$/)) return true;
1276
+ return false;
1277
+ }
1278
+ if (args.length === 2) {
1279
+ if (address.match(/^\/dca\/\d$/)) return true;
1280
+ }
1281
+ return this.findMapping(address) !== null;
1282
+ }
1283
+ execute(msg, _source) {
1284
+ const { address, args } = msg;
1285
+ const mapping = this.findMapping(address);
1286
+ if (!mapping) return [];
1287
+ const { basePath, fields } = mapping;
1288
+ this.logger.debug(LogCategory.STATE, `[SCENE] Expanding batch command`, {
1289
+ address,
1290
+ argCount: args.length,
1291
+ fields: fields.length
1292
+ });
1293
+ for (let i = 0; i < args.length && i < fields.length; i++) {
1294
+ const fieldName = fields[i];
1295
+ const rawValue = args[i];
1296
+ const value = typeof rawValue === "string" ? parseSceneValue(rawValue) : rawValue;
1297
+ const fullPath = `${basePath}/${fieldName}`;
1298
+ this.state.set(fullPath, value);
1299
+ this.logger.debug(LogCategory.STATE, `[SCENE] Set ${fullPath} = ${value}`);
1300
+ }
1301
+ return [];
1302
+ }
1303
+ findMapping(address) {
1304
+ if (address.match(/^\/headamp\/\d{3}$/) && SCENE_MAPPINGS["headamp"]) return {
1305
+ basePath: address,
1306
+ fields: SCENE_MAPPINGS["headamp"]
1307
+ };
1308
+ if (address.match(/^\/fx\/\d\/source$/) && SCENE_MAPPINGS["fx/source"]) return {
1309
+ basePath: address,
1310
+ fields: SCENE_MAPPINGS["fx/source"]
1311
+ };
1312
+ if (address.match(/^\/fx\/\d\/par$/) && SCENE_MAPPINGS["fx/par"]) return {
1313
+ basePath: address,
1314
+ fields: SCENE_MAPPINGS["fx/par"]
1315
+ };
1316
+ if (address.match(/^\/fx\/\d$/) && SCENE_MAPPINGS["fx"]) return {
1317
+ basePath: address,
1318
+ fields: SCENE_MAPPINGS["fx"]
1319
+ };
1320
+ if (address.match(/^\/dca\/\d$/) && SCENE_MAPPINGS["dca"]) return {
1321
+ basePath: address,
1322
+ fields: SCENE_MAPPINGS["dca"]
1323
+ };
1324
+ if (address.match(/^\/dca\/\d\/config$/)) return {
1325
+ basePath: address,
1326
+ fields: [
1327
+ "name",
1328
+ "icon",
1329
+ "color"
1330
+ ]
1331
+ };
1332
+ if (address.match(/^\/outputs\/main\/\d{2}\/delay$/) && SCENE_MAPPINGS["outputs/main/delay"]) return {
1333
+ basePath: address,
1334
+ fields: SCENE_MAPPINGS["outputs/main/delay"]
1335
+ };
1336
+ if (address.match(/^\/outputs\/p16\/\d{2}\/iQ$/) && SCENE_MAPPINGS["outputs/p16/iQ"]) return {
1337
+ basePath: address,
1338
+ fields: SCENE_MAPPINGS["outputs/p16/iQ"]
1339
+ };
1340
+ if (address.match(/^\/outputs\/main\/\d{2}$/) && SCENE_MAPPINGS["outputs/main"]) return {
1341
+ basePath: address,
1342
+ fields: SCENE_MAPPINGS["outputs/main"]
1343
+ };
1344
+ if (address.match(/^\/outputs\/aux\/\d{2}$/) && SCENE_MAPPINGS["outputs/aux"]) return {
1345
+ basePath: address,
1346
+ fields: SCENE_MAPPINGS["outputs/aux"]
1347
+ };
1348
+ if (address.match(/^\/outputs\/p16\/\d{2}$/) && SCENE_MAPPINGS["outputs/p16"]) return {
1349
+ basePath: address,
1350
+ fields: SCENE_MAPPINGS["outputs/p16"]
1351
+ };
1352
+ if (address.match(/^\/outputs\/aes\/\d{2}$/) && SCENE_MAPPINGS["outputs/aes"]) return {
1353
+ basePath: address,
1354
+ fields: SCENE_MAPPINGS["outputs/aes"]
1355
+ };
1356
+ if (address.match(/^\/outputs\/rec\/\d{2}$/) && SCENE_MAPPINGS["outputs/rec"]) return {
1357
+ basePath: address,
1358
+ fields: SCENE_MAPPINGS["outputs/rec"]
1359
+ };
1360
+ if (address.startsWith("/config/")) {
1361
+ const suffix = address.replace("/config/", "");
1362
+ if (SCENE_MAPPINGS[suffix]) return {
1363
+ basePath: address,
1364
+ fields: SCENE_MAPPINGS[suffix]
1365
+ };
1366
+ }
1367
+ for (const [pattern, fields] of Object.entries(SCENE_MAPPINGS)) {
1368
+ if (address.endsWith("/" + pattern)) return {
1369
+ basePath: address,
1370
+ fields
1371
+ };
1372
+ if ((/* @__PURE__ */ new RegExp(`/${pattern.replace(/\//g, "/")}$`)).test(address)) return {
1373
+ basePath: address,
1374
+ fields
1375
+ };
1376
+ }
1377
+ const numberedPathMatch = address.match(/^\/\w+\/\d+\/(.+)$/);
1378
+ if (numberedPathMatch) {
1379
+ const suffix = numberedPathMatch[1];
1380
+ if (SCENE_MAPPINGS[suffix]) return {
1381
+ basePath: address,
1382
+ fields: SCENE_MAPPINGS[suffix]
1383
+ };
1384
+ }
1385
+ return null;
1386
+ }
1387
+ };
1388
+
621
1389
  //#endregion
622
1390
  //#region src/domain/services/OscMessageHandler.ts
623
1391
  /**
@@ -650,8 +1418,10 @@ var OscMessageHandler = class {
650
1418
  new SubscriptionStrategy(subscriptionManager, state, logger),
651
1419
  new BatchStrategy(subscriptionManager, logger),
652
1420
  new MeterStrategy(subscriptionManager, state, this.meterService),
653
- new StateAccessStrategy(state, logger, this.schemaRegistry),
654
- new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService)
1421
+ new ShowDumpStrategy(state, logger),
1422
+ new SceneDataStrategy(state, logger),
1423
+ new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService),
1424
+ new StateAccessStrategy(state, logger, this.schemaRegistry)
655
1425
  ];
656
1426
  }
657
1427
  /**
@@ -661,19 +1431,41 @@ var OscMessageHandler = class {
661
1431
  * @returns Array of replies generated by the strategy.
662
1432
  */
663
1433
  handle(msg, source) {
664
- const addr = msg.address;
1434
+ let addr = msg.address;
1435
+ let args = msg.args;
1436
+ if (addr === "/" && args.length === 1 && typeof args[0] === "string") {
1437
+ const parts = args[0].trim().match(/(?:[^\s"]+|"[^"]*")+/g) || [];
1438
+ if (parts.length > 0 && parts[0]) {
1439
+ let innerAddr = parts[0];
1440
+ if (!innerAddr.startsWith("/")) innerAddr = "/" + innerAddr;
1441
+ const innerArgs = parts.slice(1).map((arg) => {
1442
+ let val = arg.replace(/^"|"$/g, "");
1443
+ if (/^-?\d+$/.test(val)) return parseInt(val, 10);
1444
+ if (/^-?\d+\.\d+$/.test(val)) return parseFloat(val);
1445
+ return val;
1446
+ });
1447
+ this.logger.debug(LogCategory.DISPATCH, `Unwrapped X32-Edit command: ${innerAddr}`, { innerArgs });
1448
+ return this.handle({
1449
+ address: innerAddr,
1450
+ args: innerArgs
1451
+ }, source);
1452
+ }
1453
+ }
665
1454
  if (!addr.startsWith("/meters")) this.logger.debug(LogCategory.DISPATCH, `Handling`, {
666
1455
  addr,
667
- args: msg.args
1456
+ args
668
1457
  });
669
- for (const strategy of this.strategies) if (strategy.canHandle(addr)) {
670
- const replies = strategy.execute(msg, source);
1458
+ for (const strategy of this.strategies) if (strategy.canHandle(addr, args)) {
1459
+ const replies = strategy.execute({
1460
+ address: addr,
1461
+ args
1462
+ }, source);
671
1463
  if (replies.length > 0) this.logger.debug(LogCategory.DISPATCH, `Strategy ${strategy.constructor.name} replied`, { count: replies.length });
672
1464
  return replies;
673
1465
  }
674
1466
  this.logger.warn(LogCategory.DISPATCH, `Unknown Command`, {
675
1467
  addr,
676
- args: msg.args,
1468
+ args,
677
1469
  ip: source.address
678
1470
  });
679
1471
  return [];
@@ -787,6 +1579,8 @@ var MeterService = class {
787
1579
  //#region src/domain/services/StaticResponseService.ts
788
1580
  /**
789
1581
  * Static OSC responses for X32 Discovery and System status.
1582
+ * These are read-only informational endpoints that return system info.
1583
+ * All other parameters are handled by StateAccessStrategy from the schema.
790
1584
  */
791
1585
  const STATIC_RESPONSES_DATA = {
792
1586
  "/xinfo": [
@@ -805,34 +1599,7 @@ const STATIC_RESPONSES_DATA = {
805
1599
  "active",
806
1600
  "{{ip}}",
807
1601
  "{{name}}"
808
- ],
809
- "/-prefs/invertmutes": [0],
810
- "/-prefs/fine": [0],
811
- "/-prefs/bright": [50],
812
- "/-prefs/contrast": [50],
813
- "/-prefs/led_bright": [50],
814
- "/-prefs/lcd_bright": [50],
815
- "/-stat/userpar/1/value": [0],
816
- "/-stat/userpar/2/value": [0],
817
- "/-stat/tape/state": [0],
818
- "/-stat/aes50/A": [0],
819
- "/-stat/aes50/B": [0],
820
- "/-stat/solo": [0],
821
- "/-stat/rtasource": [0],
822
- "/-urec/errorcode": [0],
823
- "/-prefs/rta/gain": [0],
824
- "/-prefs/rta/autogain": [0],
825
- "/-prefs/hardmute": [0],
826
- "/-prefs/dcamute": [0],
827
- "/config/mono/mode": [0],
828
- "/config/amixenable/X": [0],
829
- "/config/amixenable/Y": [0],
830
- "/-show/prepos/current": [0],
831
- "/-show/showfile/current": [""],
832
- "/-action/setrtasrc": [0],
833
- "/-action/playtrack": [0],
834
- "/-action/goscene": [0],
835
- "/-action/setscene": [0]
1602
+ ]
836
1603
  };
837
1604
  /**
838
1605
  * Service providing static system responses.
@@ -862,47 +1629,51 @@ function getLocalIp() {
862
1629
  return "127.0.0.1";
863
1630
  }
864
1631
  /**
865
- * The core service that manages the X32 simulation, including networking,
1632
+ * The core service that manages the X32 simulation, including networking,
866
1633
  * state management, and OSC message handling.
867
1634
  */
868
1635
  var SimulationService = class {
869
1636
  subscriptionManager;
870
1637
  messageHandler;
1638
+ state;
871
1639
  processPacket;
872
1640
  broadcastUpdates;
873
1641
  manageSessions;
874
1642
  meterService;
875
1643
  staticResponseService;
1644
+ config;
876
1645
  updateInterval = null;
877
1646
  cleanupInterval = null;
878
1647
  /**
879
1648
  * Creates a new SimulationService instance.
880
1649
  * @param gateway - The network gateway for OSC communication.
881
1650
  * @param logger - The logger service.
882
- * @param stateRepo - The repository managing the mixer state.
1651
+ * @param stateRepository - The repository for state persistence.
883
1652
  * @param schemaRegistry - The registry for X32 OSC schema.
884
- * @param port - UDP port to listen on (default 10023).
885
- * @param ip - IP address to bind to (default '0.0.0.0').
1653
+ * @param port - UDP port to listen on (default from config).
1654
+ * @param ip - IP address to bind to (default from config).
886
1655
  * @param name - Reported console name.
887
1656
  * @param model - Reported console model.
1657
+ * @param configOverrides - Optional configuration overrides.
888
1658
  */
889
- constructor(gateway, logger, stateRepo, schemaRegistry, port = 10023, ip = "0.0.0.0", name = "osc-server", model = "X32") {
1659
+ 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) {
890
1660
  this.gateway = gateway;
891
1661
  this.logger = logger;
892
- this.stateRepo = stateRepo;
1662
+ this.stateRepository = stateRepository;
893
1663
  this.schemaRegistry = schemaRegistry;
894
1664
  this.port = port;
895
1665
  this.ip = ip;
896
- const state = this.stateRepo.getState();
897
- this.subscriptionManager = new SubscriptionManager(logger);
1666
+ this.config = mergeConfig(configOverrides);
1667
+ this.state = new X32State(schemaRegistry.getSchema(), stateRepository);
1668
+ this.subscriptionManager = new SubscriptionManager(logger, this.config.SUBSCRIPTION_TTL_MS);
898
1669
  this.meterService = new MeterService();
899
1670
  this.staticResponseService = new StaticResponseService();
900
1671
  const reportedIp = this.ip === "0.0.0.0" ? getLocalIp() : this.ip;
901
- this.messageHandler = new OscMessageHandler(state, this.subscriptionManager, logger, reportedIp, name, model, this.meterService, this.schemaRegistry, this.staticResponseService);
902
- this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway);
903
- this.broadcastUpdates = new BroadcastUpdatesUseCase(this.subscriptionManager, state, gateway, logger, this.meterService, this.schemaRegistry);
1672
+ this.messageHandler = new OscMessageHandler(this.state, this.subscriptionManager, logger, reportedIp, name, model, this.meterService, this.schemaRegistry, this.staticResponseService);
1673
+ this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway, logger);
1674
+ this.broadcastUpdates = new BroadcastUpdatesUseCase(this.subscriptionManager, this.state, gateway, logger, this.meterService, this.schemaRegistry);
904
1675
  this.manageSessions = new ManageSessionsUseCase(this.subscriptionManager);
905
- state.on("change", (evt) => {
1676
+ this.state.on("change", (evt) => {
906
1677
  this.logger.info(LogCategory.STATE, `State Changed`, evt);
907
1678
  this.broadcastUpdates.broadcastSingleChange(evt.address, evt.value);
908
1679
  });
@@ -912,13 +1683,14 @@ var SimulationService = class {
912
1683
  * Starts the simulator server and internal loops.
913
1684
  */
914
1685
  async start() {
1686
+ await this.state.initialize();
915
1687
  await this.gateway.start(this.port, this.ip);
916
1688
  this.cleanupInterval = setInterval(() => {
917
1689
  this.manageSessions.cleanup();
918
- }, 5e3);
1690
+ }, this.config.CLEANUP_INTERVAL_MS);
919
1691
  this.updateInterval = setInterval(() => {
920
1692
  this.broadcastUpdates.execute();
921
- }, 100);
1693
+ }, this.config.BROADCAST_INTERVAL_MS);
922
1694
  }
923
1695
  /**
924
1696
  * Stops the simulator server and stops all internal loops.
@@ -926,84 +1698,20 @@ var SimulationService = class {
926
1698
  async stop() {
927
1699
  if (this.updateInterval) clearInterval(this.updateInterval);
928
1700
  if (this.cleanupInterval) clearInterval(this.cleanupInterval);
1701
+ await this.state.flush();
929
1702
  await this.gateway.stop();
930
1703
  }
931
1704
  /**
932
1705
  * Resets the mixer state to default values.
933
1706
  */
934
1707
  resetState() {
935
- this.stateRepo.reset();
936
- }
937
- };
938
-
939
- //#endregion
940
- //#region src/domain/entities/X32State.ts
941
- /**
942
- * Manages the internal "Digital Twin" state of the X32 console.
943
- * It acts as a single source of truth for all parameters.
944
- * Emits 'change' events whenever a value is updated.
945
- */
946
- var X32State = class extends node_events.EventEmitter {
947
- /** Map storing all OSC paths and their current values. */
948
- state = /* @__PURE__ */ new Map();
949
- defaultState = /* @__PURE__ */ new Map();
950
- /**
951
- * Initializes the state with default values from the schema.
952
- * @param schema - The schema definition map.
953
- */
954
- constructor(schema) {
955
- super();
956
- for (const [addr, def] of Object.entries(schema)) this.defaultState.set(addr, def.default);
957
- this.reset();
958
- }
959
- /**
960
- * Resets all state parameters to their default values defined in the schema.
961
- */
962
- reset() {
963
- this.state.clear();
964
- this.defaultState.forEach((val, key) => {
965
- this.state.set(key, val);
966
- });
967
- }
968
- /**
969
- * Retrieves the value of a specific OSC node.
970
- * @param address - The full OSC address path.
971
- * @returns The stored value (number or string) or undefined if not found.
972
- */
973
- get(address) {
974
- return this.state.get(address);
1708
+ this.state.reset();
975
1709
  }
976
1710
  /**
977
- * Updates the value of a specific OSC node and notifies subscribers.
978
- * @param address - The full OSC address path.
979
- * @param value - The new value to store.
1711
+ * Forces a save of the current state to storage.
980
1712
  */
981
- set(address, value) {
982
- this.state.set(address, value);
983
- this.emit("change", {
984
- address,
985
- value
986
- });
987
- }
988
- /**
989
- * Specialized logic to handle X32 Mute Groups side-effects.
990
- * When a mute group is toggled, it iterates through all channels
991
- * assigned to that group and updates their individual mute status.
992
- * @param groupIdx - The index of the mute group (1-6).
993
- * @param isOn - The new state of the group master switch (0 or 1).
994
- */
995
- handleMuteGroupChange(groupIdx, isOn) {
996
- for (let i = 1; i <= 32; i++) {
997
- const ch = i.toString().padStart(2, "0");
998
- const grpVal = this.get(`/ch/${ch}/grp/mute`);
999
- if (typeof grpVal === "number") {
1000
- if ((grpVal & 1 << groupIdx - 1) !== 0) {
1001
- const targetMute = isOn === 1 ? 0 : 1;
1002
- const muteAddr = `/ch/${ch}/mix/on`;
1003
- this.set(muteAddr, targetMute);
1004
- }
1005
- }
1006
- }
1713
+ async saveState() {
1714
+ await this.state.flush();
1007
1715
  }
1008
1716
  };
1009
1717
 
@@ -1018,69 +1726,106 @@ var X32Node = class X32Node {
1018
1726
  type;
1019
1727
  /** Default value for reset. */
1020
1728
  default;
1729
+ /** Optional validation constraints. */
1730
+ options;
1021
1731
  /**
1022
1732
  * Creates a new X32Node.
1023
1733
  * @param type - The OSC data type ('f', 'i', 's').
1024
1734
  * @param defaultValue - The default value.
1735
+ * @param options - Optional validation constraints.
1025
1736
  */
1026
- constructor(type, defaultValue) {
1737
+ constructor(type, defaultValue, options) {
1027
1738
  this.type = type;
1028
1739
  this.default = defaultValue;
1740
+ this.options = options;
1029
1741
  }
1030
1742
  /**
1031
1743
  * Validates if a value is compatible with this node's type.
1032
1744
  * @param value - The value to check.
1745
+ * @param strict - If true, also validates against min/max constraints.
1033
1746
  * @returns True if valid.
1034
1747
  */
1035
- validate(value) {
1036
- if (this.type === "f") return typeof value === "number";
1037
- if (this.type === "i") return typeof value === "number";
1038
- if (this.type === "s") return typeof value === "string";
1039
- return false;
1748
+ validate(value, strict = false) {
1749
+ if (this.type === "f" && typeof value !== "number") return false;
1750
+ if (this.type === "i" && typeof value !== "number") return false;
1751
+ if (this.type === "s" && typeof value !== "string") return false;
1752
+ if (strict && this.options && typeof value === "number") {
1753
+ if (this.options.min !== void 0 && value < this.options.min) return false;
1754
+ if (this.options.max !== void 0 && value > this.options.max) return false;
1755
+ if (this.type === "i" && this.options.strictInteger && !Number.isInteger(value)) return false;
1756
+ }
1757
+ return true;
1040
1758
  }
1041
1759
  /**
1042
1760
  * Factory method to create from a plain object (for compatibility/migration).
1043
1761
  * @param obj - Plain object.
1044
1762
  * @param obj.type - OSC data type.
1045
1763
  * @param obj.default - Default value.
1764
+ * @param obj.options - Optional validation constraints.
1046
1765
  * @returns A new X32Node instance.
1047
1766
  */
1048
1767
  static from(obj) {
1049
- return new X32Node(obj.type, obj.default);
1768
+ return new X32Node(obj.type, obj.default, obj.options);
1050
1769
  }
1051
1770
  };
1052
1771
 
1053
1772
  //#endregion
1054
1773
  //#region src/infrastructure/repositories/InMemoryStateRepository.ts
1055
1774
  /**
1056
- * In-memory implementation of the state repository.
1057
- * Stores the mixer state in volatile memory during the simulator's execution.
1775
+ * In-memory state repository.
1776
+ * Stores state in a Map, no persistence across restarts.
1777
+ * This is the default and fastest option.
1058
1778
  */
1059
1779
  var InMemoryStateRepository = class {
1060
- state;
1780
+ state = /* @__PURE__ */ new Map();
1061
1781
  /**
1062
- * Creates a new InMemoryStateRepository.
1063
- * @param logger - Logger service.
1064
- * @param schemaRegistry - Registry providing the initial state schema.
1782
+ * Loads state from memory.
1783
+ * For in-memory storage, this returns current state.
1065
1784
  */
1066
- constructor(logger, schemaRegistry) {
1067
- this.logger = logger;
1068
- this.state = new X32State(schemaRegistry.getSchema());
1785
+ async load() {
1786
+ return this.getAll();
1069
1787
  }
1070
1788
  /**
1071
- * Returns the current mixer state instance.
1072
- * @returns The X32State entity.
1789
+ * Saves state to memory.
1790
+ * For in-memory storage, this replaces current state.
1073
1791
  */
1074
- getState() {
1075
- return this.state;
1792
+ async save(state) {
1793
+ this.state.clear();
1794
+ for (const [key, value] of Object.entries(state)) this.state.set(key, value);
1076
1795
  }
1077
1796
  /**
1078
- * Resets the entire state to its default values.
1797
+ * Gets a single value from memory.
1079
1798
  */
1080
- reset() {
1081
- this.logger.info(LogCategory.SYSTEM, "Resetting state to defaults");
1082
- this.state.reset();
1799
+ get(address) {
1800
+ return this.state.get(address);
1801
+ }
1802
+ /**
1803
+ * Sets a single value in memory.
1804
+ */
1805
+ set(address, value) {
1806
+ this.state.set(address, value);
1807
+ }
1808
+ /**
1809
+ * Gets all entries from memory.
1810
+ */
1811
+ getAll() {
1812
+ const result = {};
1813
+ this.state.forEach((value, key) => {
1814
+ result[key] = value;
1815
+ });
1816
+ return result;
1817
+ }
1818
+ /**
1819
+ * Resets state to provided defaults.
1820
+ */
1821
+ reset(defaults) {
1822
+ this.state.clear();
1823
+ for (const [key, value] of Object.entries(defaults)) this.state.set(key, value);
1083
1824
  }
1825
+ /**
1826
+ * No-op for in-memory storage.
1827
+ */
1828
+ async flush() {}
1084
1829
  };
1085
1830
 
1086
1831
  //#endregion
@@ -1899,41 +2644,215 @@ var SchemaFactory = class {
1899
2644
  ...this.generateRange(128, "/headamp", "/gain", "f", 0, 3, 0),
1900
2645
  ...this.generateRange(128, "/headamp", "/phantom", "i", 0, 3, 0),
1901
2646
  ...this.generateRange(6, "/config/mute", "", "i", 0),
1902
- ...this.generateRange(80, "/-stat/solosw", "", "i", 0),
2647
+ ...this.generateRange(80, "/-stat/solosw", "", "i", 0, 2, 1),
1903
2648
  "/-stat/selidx": this.node("i", 0),
2649
+ "/-stat/chfaderbank": this.node("i", 0),
2650
+ "/-stat/grpfaderbank": this.node("i", 0),
1904
2651
  "/-stat/sendsonfader": this.node("i", 0),
1905
2652
  "/-stat/bussendbank": this.node("i", 0),
2653
+ "/-stat/eqband": this.node("i", 0),
1906
2654
  "/-stat/keysolo": this.node("i", 0),
2655
+ "/-stat/userbank": this.node("i", 0),
2656
+ "/-stat/autosave": this.node("i", 0),
2657
+ "/-stat/lock": this.node("i", 0),
2658
+ "/-stat/usbmounted": this.node("i", 0),
2659
+ "/-stat/remote": this.node("i", 0),
2660
+ "/-stat/rtamodeeq": this.node("i", 0),
2661
+ "/-stat/rtamodegeq": this.node("i", 0),
2662
+ "/-stat/rtaeqpre": this.node("i", 0),
2663
+ "/-stat/rtageqpost": this.node("i", 0),
2664
+ "/-stat/rtasource": this.node("i", 0),
2665
+ "/-stat/xcardtype": this.node("i", 0),
2666
+ "/-stat/xcardsync": this.node("i", 0),
2667
+ "/-stat/geqonfdr": this.node("i", 0),
2668
+ "/-stat/geqpos": this.node("i", 0),
1907
2669
  "/-stat/screen/screen": this.node("i", 0),
2670
+ "/-stat/screen/mutegrp": this.node("i", 0),
2671
+ "/-stat/screen/utils": this.node("i", 0),
1908
2672
  "/-stat/screen/CHAN/page": this.node("i", 0),
1909
2673
  "/-stat/screen/METER/page": this.node("i", 0),
1910
2674
  "/-stat/screen/ROUTE/page": this.node("i", 0),
1911
2675
  "/-stat/screen/SETUP/page": this.node("i", 0),
2676
+ "/-stat/screen/LIB/page": this.node("i", 0),
1912
2677
  "/-stat/screen/LIBRARY/page": this.node("i", 0),
1913
2678
  "/-stat/screen/FX/page": this.node("i", 0),
1914
2679
  "/-stat/screen/MON/page": this.node("i", 0),
1915
2680
  "/-stat/screen/USB/page": this.node("i", 0),
1916
2681
  "/-stat/screen/SCENE/page": this.node("i", 0),
1917
2682
  "/-stat/screen/ASSIGN/page": this.node("i", 0),
2683
+ "/-stat/aes50/state": this.node("i", 0),
2684
+ "/-stat/aes50/A": this.node("i", 0),
2685
+ "/-stat/aes50/B": this.node("i", 0),
2686
+ "/-stat/aes50/stats/A": this.node("i", 0),
2687
+ "/-stat/aes50/stats/B": this.node("i", 0),
2688
+ "/-stat/tape/state": this.node("i", 0),
2689
+ "/-stat/tape/file": this.node("s", ""),
2690
+ "/-stat/tape/etime": this.node("i", 0),
2691
+ "/-stat/tape/rtime": this.node("i", 0),
2692
+ "/-stat/urec/state": this.node("i", 0),
2693
+ "/-stat/urec/etime": this.node("i", 0),
2694
+ "/-stat/urec/rtime": this.node("i", 0),
1918
2695
  "/-stat/talk/A": this.node("i", 0),
1919
2696
  "/-stat/talk/B": this.node("i", 0),
1920
2697
  "/-stat/osc/on": this.node("i", 0),
1921
- "/-prefs/autosel": this.node("i", 1),
1922
- "/-action/setrtasrc": this.node("i", 0),
2698
+ "/-action/setip": this.node("i", 0),
2699
+ "/-action/setclock": this.node("i", 0),
2700
+ "/-action/initall": this.node("i", 0),
2701
+ "/-action/initlib": this.node("i", 0),
2702
+ "/-action/initshow": this.node("i", 0),
2703
+ "/-action/savestate": this.node("i", 0),
2704
+ "/-action/undopt": this.node("i", 0),
2705
+ "/-action/doundo": this.node("i", 0),
1923
2706
  "/-action/playtrack": this.node("i", 0),
2707
+ "/-action/newscreen": this.node("i", 0),
2708
+ "/-action/clearsolo": this.node("i", 0),
2709
+ "/-action/setprebus": this.node("i", 0),
2710
+ "/-action/setsrate": this.node("i", 0),
2711
+ "/-action/setrtasrc": this.node("i", 0),
2712
+ "/-action/recselect": this.node("i", 0),
2713
+ "/-action/gocue": this.node("i", 0),
1924
2714
  "/-action/goscene": this.node("i", 0),
1925
2715
  "/-action/setscene": this.node("i", 0),
2716
+ "/-action/gosnippet": this.node("i", 0),
2717
+ "/-action/selsession": this.node("i", 0),
2718
+ "/-action/delsession": this.node("i", 0),
2719
+ "/-action/selmarker": this.node("i", 0),
2720
+ "/-action/delmarker": this.node("i", 0),
2721
+ "/-action/savemarker": this.node("i", 0),
2722
+ "/-action/addmarker": this.node("i", 0),
2723
+ "/-action/selposition": this.node("i", 0),
2724
+ "/-action/clearalert": this.node("i", 0),
2725
+ "/-action/formatcard": this.node("i", 0),
2726
+ "/-undo/time": this.node("s", ""),
2727
+ "/-show": this.node("i", 0),
2728
+ "/-show/prepos": this.node("i", 0),
2729
+ "/-show/prepos/current": this.node("i", 0),
2730
+ "/-show/showfile": this.node("s", ""),
2731
+ "/-show/showfile/inputs": this.node("i", 0),
2732
+ "/-show/showfile/mxsends": this.node("i", 0),
2733
+ "/-show/showfile/mxbuses": this.node("i", 0),
2734
+ "/-show/showfile/console": this.node("i", 0),
2735
+ "/-show/showfile/chan16": this.node("i", 0),
2736
+ "/-show/showfile/chan32": this.node("i", 0),
2737
+ "/-show/showfile/return": this.node("i", 0),
2738
+ "/-show/showfile/buses": this.node("i", 0),
2739
+ "/-show/showfile/lrmtxdca": this.node("i", 0),
2740
+ "/-show/showfile/effects": this.node("i", 0),
2741
+ ...this.generateShowfileCues(),
2742
+ ...this.generateShowfileScenes(),
2743
+ ...this.generateShowfileSnippets(),
2744
+ "/-usb/path": this.node("s", ""),
2745
+ "/-usb/title": this.node("s", ""),
2746
+ "/-usb/dir": this.node("i", 0),
2747
+ "/-usb/dirpos": this.node("i", 0),
2748
+ "/-usb/maxpos": this.node("i", 0),
2749
+ ...this.generateUSBDir(),
2750
+ "/-urec/sessionmax": this.node("i", 0),
2751
+ "/-urec/markermax": this.node("i", 0),
2752
+ "/-urec/sessionlen": this.node("i", 0),
2753
+ "/-urec/sessionpos": this.node("i", 0),
2754
+ "/-urec/markerpos": this.node("i", 0),
2755
+ "/-urec/batterystate": this.node("i", 0),
2756
+ "/-urec/srate": this.node("i", 0),
2757
+ "/-urec/tracks": this.node("i", 0),
2758
+ "/-urec/sessionspan": this.node("i", 0),
2759
+ "/-urec/sessionoffs": this.node("i", 0),
2760
+ "/-urec/sd1state": this.node("i", 0),
2761
+ "/-urec/sd2state": this.node("i", 0),
2762
+ "/-urec/sd1info": this.node("s", ""),
2763
+ "/-urec/sd2info": this.node("s", ""),
2764
+ "/-urec/errormessage": this.node("s", ""),
2765
+ "/-urec/errorcode": this.node("i", 0),
2766
+ ...this.generateURecSessions(),
2767
+ ...this.generateLibraries(),
2768
+ "/-insert": this.node("i", 0),
2769
+ "/-prefs/style": this.node("i", 0),
2770
+ "/-prefs/bright": this.node("i", 50),
2771
+ "/-prefs/lcdcont": this.node("i", 50),
2772
+ "/-prefs/ledbright": this.node("i", 50),
2773
+ "/-prefs/lamp": this.node("i", 50),
2774
+ "/-prefs/lampon": this.node("i", 0),
2775
+ "/-prefs/clockrate": this.node("i", 0),
2776
+ "/-prefs/clocksource": this.node("i", 0),
2777
+ "/-prefs/confirm_general": this.node("i", 1),
2778
+ "/-prefs/confirm_overwrite": this.node("i", 1),
2779
+ "/-prefs/confirm_sceneload": this.node("i", 1),
2780
+ "/-prefs/viewrtn": this.node("i", 0),
2781
+ "/-prefs/selfollowsbank": this.node("i", 0),
2782
+ "/-prefs/scene_advance": this.node("i", 0),
2783
+ "/-prefs/safe_masterlevels": this.node("i", 0),
2784
+ "/-prefs/haflags": this.node("i", 0),
2785
+ "/-prefs/autosel": this.node("i", 1),
2786
+ "/-prefs/show_control": this.node("i", 0),
2787
+ "/-prefs/clockmode": this.node("i", 0),
2788
+ "/-prefs/hardmute": this.node("i", 0),
2789
+ "/-prefs/dcamute": this.node("i", 0),
2790
+ "/-prefs/invertmutes": this.node("i", 0),
2791
+ "/-prefs/name": this.node("s", "X32-Simulator"),
2792
+ "/-prefs/rec_control": this.node("i", 0),
2793
+ "/-prefs/fastFaders": this.node("i", 0),
2794
+ "/-prefs/ip": this.node("s", ""),
2795
+ "/-prefs/ip/dhcp": this.node("i", 0),
2796
+ "/-prefs/ip/addr": this.node("s", "192.168.1.1"),
2797
+ "/-prefs/ip/mask": this.node("s", "255.255.255.0"),
2798
+ "/-prefs/ip/gateway": this.node("s", "192.168.1.1"),
2799
+ "/-prefs/remote": this.node("i", 0),
2800
+ "/-prefs/remote/enable": this.node("i", 0),
2801
+ "/-prefs/remote/protocol": this.node("i", 0),
2802
+ "/-prefs/remote/port": this.node("i", 10023),
2803
+ "/-prefs/remote/ioenable": this.node("i", 0),
2804
+ "/-prefs/card": this.node("i", 0),
2805
+ "/-prefs/card/UFifc": this.node("i", 0),
2806
+ "/-prefs/card/UFmode": this.node("i", 0),
2807
+ "/-prefs/card/USBmode": this.node("i", 0),
2808
+ "/-prefs/card/ADATwc": this.node("i", 0),
2809
+ "/-prefs/card/ADATsync": this.node("i", 0),
2810
+ "/-prefs/card/MADImode": this.node("i", 0),
2811
+ "/-prefs/card/MADIin": this.node("i", 0),
2812
+ "/-prefs/card/MADIout": this.node("i", 0),
2813
+ "/-prefs/card/MADIsrc": this.node("i", 0),
2814
+ "/-prefs/card/URECtracks": this.node("i", 0),
2815
+ "/-prefs/card/URECplayb": this.node("i", 0),
2816
+ "/-prefs/card/URECrout": this.node("i", 0),
2817
+ "/-prefs/card/URECsdsel": this.node("i", 0),
2818
+ "/-prefs/rta": this.node("i", 0),
2819
+ "/-prefs/rta/visibility": this.node("s", "70%"),
2820
+ "/-prefs/rta/gain": this.node("f", 0),
2821
+ "/-prefs/rta/autogain": this.node("i", 0),
2822
+ "/-prefs/rta/source": this.node("i", 0),
2823
+ "/-prefs/rta/pos": this.node("i", 0),
2824
+ "/-prefs/rta/mode": this.node("i", 0),
2825
+ "/-prefs/rta/option": this.node("i", 0),
2826
+ "/-prefs/rta/det": this.node("i", 0),
2827
+ "/-prefs/rta/decay": this.node("i", 0),
2828
+ "/-prefs/rta/peakhold": this.node("i", 0),
2829
+ ...this.generatePrefsIQ(),
2830
+ "/-prefs/key/layout": this.node("i", 0),
2831
+ ...this.generateRange(100, "/-prefs/key", "", "i", 0, 2, 0),
1926
2832
  "/config/routing/AES50A/1-8": this.node("i", 0),
1927
2833
  "/config/routing/AES50B/1-8": this.node("i", 0),
1928
2834
  "/config/routing/CARD/1-8": this.node("i", 0),
1929
2835
  "/config/routing/OUT/1-4": this.node("i", 0),
1930
- "/-prefs/invertmutes": this.node("i", 0),
1931
2836
  ...this.generateChannelStrip("/main/st", 6, false),
1932
2837
  ...this.generateChannelStrip("/main/m", 6, false),
1933
2838
  ...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1934
2839
  ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1935
2840
  ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1936
2841
  ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/fxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
2842
+ ...Object.fromEntries(Array.from({ length: 3 }, (_, i) => [`/config/mtxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
2843
+ "/config/linkcfg/hadly": this.node("i", 0),
2844
+ "/config/linkcfg/eq": this.node("i", 0),
2845
+ "/config/linkcfg/dyn": this.node("i", 0),
2846
+ "/config/linkcfg/fdrmute": this.node("i", 0),
2847
+ "/config/mono/mode": this.node("i", 0),
2848
+ "/config/mono/link": this.node("i", 0),
2849
+ "/config/tape/autoplay": this.node("i", 0),
2850
+ "/config/tape/gainL": this.node("f", .5),
2851
+ "/config/tape/gainR": this.node("f", .5),
2852
+ "/config/amixenable/X": this.node("i", 0),
2853
+ "/config/amixenable/Y": this.node("i", 0),
2854
+ ...this.generateUserCtrl(),
2855
+ "/config/routing/routswitch": this.node("i", 0),
1937
2856
  ...this.generateRange(32, "/config/userrout/in", "", "i", 0),
1938
2857
  ...this.generateRange(48, "/config/userrout/out", "", "i", 0),
1939
2858
  "/config/solo/level": this.node("f", 0),
@@ -1969,6 +2888,7 @@ var SchemaFactory = class {
1969
2888
  "/config/osc/f2": this.node("f", .5),
1970
2889
  "/config/osc/level": this.node("f", 0),
1971
2890
  "/config/osc/dest": this.node("i", 0),
2891
+ ...this.generateDP48Config(),
1972
2892
  ...this.generateOutputs("main", 16),
1973
2893
  ...this.generateOutputs("aux", 6),
1974
2894
  ...this.generateOutputs("p16", 16),
@@ -1976,7 +2896,7 @@ var SchemaFactory = class {
1976
2896
  ...this.generateOutputs("rec", 2)
1977
2897
  };
1978
2898
  Object.keys(STATIC_RESPONSES_DATA).forEach((key) => {
1979
- if (key.startsWith("/-") || key.startsWith("/stat")) {
2899
+ if (key.startsWith("/-")) {
1980
2900
  const val = STATIC_RESPONSES_DATA[key][0];
1981
2901
  const type = typeof val === "string" ? "s" : Number.isInteger(val) ? "i" : "f";
1982
2902
  schema[key] = this.node(type, val);
@@ -2091,6 +3011,7 @@ var SchemaFactory = class {
2091
3011
  generateNodes(count, prefix) {
2092
3012
  const nodes = {};
2093
3013
  const isChannelOrAux = prefix === "ch" || prefix === "auxin";
3014
+ const hasMtxPreamp = prefix === "mtx";
2094
3015
  const eqBands = prefix === "bus" || prefix === "mtx" ? 6 : 4;
2095
3016
  for (let i = 1; i <= count; i++) {
2096
3017
  const padId = i.toString().padStart(2, "0");
@@ -2102,7 +3023,7 @@ var SchemaFactory = class {
2102
3023
  nodes[`${base}/fader`] = this.node("f", .75);
2103
3024
  nodes[`${base}/on`] = this.node("i", 0);
2104
3025
  } else {
2105
- Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
3026
+ Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux || hasMtxPreamp));
2106
3027
  if (prefix === "ch" || prefix === "auxin" || prefix === "fxrtn" || prefix === "bus") for (let b = 1; b <= 16; b++) {
2107
3028
  const busId = b.toString().padStart(2, "0");
2108
3029
  nodes[`${base}/mix/${busId}/level`] = this.node("f", 0);
@@ -2110,6 +3031,11 @@ var SchemaFactory = class {
2110
3031
  nodes[`${base}/mix/${busId}/pan`] = this.node("f", .5);
2111
3032
  nodes[`${base}/mix/${busId}/type`] = this.node("i", 0);
2112
3033
  }
3034
+ if (prefix === "ch") {
3035
+ nodes[`${base}/automix`] = this.node("i", 0);
3036
+ nodes[`${base}/automix/group`] = this.node("i", 0);
3037
+ nodes[`${base}/automix/weight`] = this.node("f", 0);
3038
+ }
2113
3039
  }
2114
3040
  });
2115
3041
  }
@@ -2142,6 +3068,159 @@ var SchemaFactory = class {
2142
3068
  }
2143
3069
  return nodes;
2144
3070
  }
3071
+ /**
3072
+ * Generates showfile cue entries (100 cues: 000-099)
3073
+ */
3074
+ generateShowfileCues() {
3075
+ const nodes = {};
3076
+ for (let i = 0; i < 100; i++) {
3077
+ const base = `/-show/showfile/cue/${i.toString().padStart(3, "0")}`;
3078
+ nodes[base] = this.node("s", "");
3079
+ nodes[`${base}/numb`] = this.node("i", 0);
3080
+ nodes[`${base}/name`] = this.node("s", "");
3081
+ nodes[`${base}/skip`] = this.node("i", 0);
3082
+ nodes[`${base}/scene`] = this.node("i", 0);
3083
+ nodes[`${base}/bit`] = this.node("i", 0);
3084
+ nodes[`${base}/miditype`] = this.node("i", 0);
3085
+ nodes[`${base}/midichan`] = this.node("i", 0);
3086
+ nodes[`${base}/midipara1`] = this.node("i", 0);
3087
+ nodes[`${base}/midipara2`] = this.node("i", 0);
3088
+ }
3089
+ return nodes;
3090
+ }
3091
+ /**
3092
+ * Generates showfile scene entries (100 scenes: 000-099)
3093
+ */
3094
+ generateShowfileScenes() {
3095
+ const nodes = {};
3096
+ for (let i = 0; i < 100; i++) {
3097
+ const base = `/-show/showfile/scene/${i.toString().padStart(3, "0")}`;
3098
+ nodes[base] = this.node("s", "");
3099
+ nodes[`${base}/name`] = this.node("s", "");
3100
+ nodes[`${base}/notes`] = this.node("s", "");
3101
+ nodes[`${base}/safes`] = this.node("i", 0);
3102
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3103
+ }
3104
+ return nodes;
3105
+ }
3106
+ /**
3107
+ * Generates showfile snippet entries (100 snippets: 000-099)
3108
+ */
3109
+ generateShowfileSnippets() {
3110
+ const nodes = {};
3111
+ for (let i = 0; i < 100; i++) {
3112
+ const base = `/-show/showfile/snippet/${i.toString().padStart(3, "0")}`;
3113
+ nodes[base] = this.node("s", "");
3114
+ nodes[`${base}/name`] = this.node("s", "");
3115
+ nodes[`${base}/eventtyp`] = this.node("i", 0);
3116
+ nodes[`${base}/channels`] = this.node("i", 0);
3117
+ nodes[`${base}/auxbuses`] = this.node("i", 0);
3118
+ nodes[`${base}/maingrps`] = this.node("i", 0);
3119
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3120
+ }
3121
+ return nodes;
3122
+ }
3123
+ /**
3124
+ * Generates preferences iQ entries (16 entries: 01-16)
3125
+ */
3126
+ generatePrefsIQ() {
3127
+ const nodes = {};
3128
+ for (let i = 1; i <= 16; i++) {
3129
+ const base = `/-prefs/iQ/${i.toString().padStart(2, "0")}`;
3130
+ nodes[base] = this.node("i", 0);
3131
+ nodes[`${base}/iQmodel`] = this.node("i", 0);
3132
+ nodes[`${base}/iQeqset`] = this.node("i", 0);
3133
+ nodes[`${base}/iQsound`] = this.node("i", 0);
3134
+ }
3135
+ return nodes;
3136
+ }
3137
+ /**
3138
+ * Generates DP48 Personal Monitor config entries (48 channels)
3139
+ */
3140
+ generateDP48Config() {
3141
+ const nodes = {};
3142
+ for (let i = 1; i <= 48; i++) {
3143
+ const id = i.toString().padStart(2, "0");
3144
+ nodes[`/config/dp48/link/${id}`] = this.node("i", 0);
3145
+ }
3146
+ nodes["/config/dp48/assign"] = this.node("i", 0);
3147
+ nodes["/config/dp48/grpname/01"] = this.node("s", "");
3148
+ nodes["/config/dp48/grpname/02"] = this.node("s", "");
3149
+ nodes["/config/dp48/grpname/03"] = this.node("s", "");
3150
+ nodes["/config/dp48/grpname/04"] = this.node("s", "");
3151
+ nodes["/config/dp48/grpname/05"] = this.node("s", "");
3152
+ nodes["/config/dp48/grpname/06"] = this.node("s", "");
3153
+ nodes["/config/dp48/grpname/07"] = this.node("s", "");
3154
+ nodes["/config/dp48/grpname/08"] = this.node("s", "");
3155
+ nodes["/config/dp48/grpname/09"] = this.node("s", "");
3156
+ nodes["/config/dp48/grpname/10"] = this.node("s", "");
3157
+ nodes["/config/dp48/grpname/11"] = this.node("s", "");
3158
+ nodes["/config/dp48/grpname/12"] = this.node("s", "");
3159
+ return nodes;
3160
+ }
3161
+ /**
3162
+ * Generates USB directory entries (1000 entries: 000-999)
3163
+ */
3164
+ generateUSBDir() {
3165
+ const nodes = {};
3166
+ for (let i = 0; i < 1e3; i++) {
3167
+ const id = i.toString().padStart(3, "0");
3168
+ nodes[`/-usb/dir/${id}`] = this.node("s", "");
3169
+ nodes[`/-usb/dir/${id}/name`] = this.node("s", "");
3170
+ }
3171
+ return nodes;
3172
+ }
3173
+ /**
3174
+ * Generates USB recording session and marker entries
3175
+ */
3176
+ generateURecSessions() {
3177
+ const nodes = {};
3178
+ for (let i = 1; i <= 100; i++) {
3179
+ const id = i.toString().padStart(3, "0");
3180
+ nodes[`/-urec/session/${id}/name`] = this.node("s", "");
3181
+ nodes[`/-urec/marker/${id}/time`] = this.node("i", 0);
3182
+ }
3183
+ return nodes;
3184
+ }
3185
+ /**
3186
+ * Generates library entries (ch, fx, r, mon - 100 each)
3187
+ */
3188
+ generateLibraries() {
3189
+ const nodes = {};
3190
+ for (const lib of [
3191
+ "ch",
3192
+ "fx",
3193
+ "r",
3194
+ "mon"
3195
+ ]) {
3196
+ nodes[`/-libs/${lib}`] = this.node("i", 0);
3197
+ for (let i = 1; i <= 100; i++) {
3198
+ const base = `/-libs/${lib}/${i.toString().padStart(3, "0")}`;
3199
+ nodes[base] = this.node("s", "");
3200
+ nodes[`${base}/pos`] = this.node("i", 0);
3201
+ nodes[`${base}/name`] = this.node("s", "");
3202
+ nodes[`${base}/flags`] = this.node("i", 0);
3203
+ nodes[`${base}/hasdata`] = this.node("i", 0);
3204
+ }
3205
+ }
3206
+ return nodes;
3207
+ }
3208
+ /**
3209
+ * Generates user control entries (A, B, C with enc and btn)
3210
+ */
3211
+ generateUserCtrl() {
3212
+ const nodes = {};
3213
+ for (const bank of [
3214
+ "A",
3215
+ "B",
3216
+ "C"
3217
+ ]) {
3218
+ nodes[`/config/userctrl/${bank}`] = this.node("i", 0);
3219
+ for (let i = 1; i <= 4; i++) nodes[`/config/userctrl/${bank}/enc/${i}`] = this.node("i", 0);
3220
+ for (let i = 1; i <= 12; i++) nodes[`/config/userctrl/${bank}/btn/${i}`] = this.node("i", 0);
3221
+ }
3222
+ return nodes;
3223
+ }
2145
3224
  };
2146
3225
 
2147
3226
  //#endregion
@@ -2212,6 +3291,12 @@ Object.defineProperty(exports, 'ConsoleLogger', {
2212
3291
  return ConsoleLogger;
2213
3292
  }
2214
3293
  });
3294
+ Object.defineProperty(exports, 'DEFAULT_CONFIG', {
3295
+ enumerable: true,
3296
+ get: function () {
3297
+ return DEFAULT_CONFIG;
3298
+ }
3299
+ });
2215
3300
  Object.defineProperty(exports, 'InMemoryStateRepository', {
2216
3301
  enumerable: true,
2217
3302
  get: function () {
@@ -2295,4 +3380,10 @@ Object.defineProperty(exports, '__toESM', {
2295
3380
  get: function () {
2296
3381
  return __toESM;
2297
3382
  }
3383
+ });
3384
+ Object.defineProperty(exports, 'mergeConfig', {
3385
+ enumerable: true,
3386
+ get: function () {
3387
+ return mergeConfig;
3388
+ }
2298
3389
  });