@djodjonx/x32-simulator 0.0.5 → 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({
@@ -374,8 +497,7 @@ var NodeDiscoveryStrategy = class {
374
497
  return address === "/node";
375
498
  }
376
499
  execute(msg, _source) {
377
- const queryPath = msg.args[0];
378
- if (!queryPath) return [];
500
+ const queryPath = msg.args[0] || "/";
379
501
  const children = /* @__PURE__ */ new Set();
380
502
  const prefix = queryPath.endsWith("/") ? queryPath : `${queryPath}/`;
381
503
  for (const key of this.schemaRegistry.getAllPaths()) if (key.startsWith(prefix)) {
@@ -392,8 +514,9 @@ var NodeDiscoveryStrategy = class {
392
514
  //#endregion
393
515
  //#region src/domain/services/strategies/StaticResponseStrategy.ts
394
516
  /**
395
- * Handles discovery and static information queries.
396
- * 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.
397
520
  */
398
521
  var StaticResponseStrategy = class {
399
522
  constructor(serverIp, serverName, serverModel, staticResponseService) {
@@ -402,7 +525,8 @@ var StaticResponseStrategy = class {
402
525
  this.serverModel = serverModel;
403
526
  this.staticResponseService = staticResponseService;
404
527
  }
405
- canHandle(address) {
528
+ canHandle(address, args) {
529
+ if (args && args.length > 0) return false;
406
530
  return !!this.staticResponseService.getResponse(address);
407
531
  }
408
532
  execute(msg, _source) {
@@ -573,6 +697,7 @@ var MeterStrategy = class {
573
697
  * LOGIC:
574
698
  * - If no arguments: Treats as a QUERY (GET) and returns the current value.
575
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.
576
701
  */
577
702
  var StateAccessStrategy = class {
578
703
  /**
@@ -580,43 +705,685 @@ var StateAccessStrategy = class {
580
705
  * @param state - Current mixer state.
581
706
  * @param logger - Logger instance.
582
707
  * @param schemaRegistry - Registry to validate addresses.
708
+ * @param permissive - If true, store unknown addresses too (default: true).
583
709
  */
584
- constructor(state, logger, schemaRegistry) {
710
+ constructor(state, logger, schemaRegistry, permissive = true) {
585
711
  this.state = state;
586
712
  this.logger = logger;
587
713
  this.schemaRegistry = schemaRegistry;
714
+ this.permissive = permissive;
588
715
  }
589
716
  /** @inheritdoc */
590
717
  canHandle(address) {
591
- return this.schemaRegistry.has(address);
718
+ if (this.schemaRegistry.has(address)) return true;
719
+ return this.permissive && address.startsWith("/");
592
720
  }
593
721
  /** @inheritdoc */
594
722
  execute(msg, _source) {
595
723
  const addr = msg.address;
596
724
  const node = this.schemaRegistry.getNode(addr);
725
+ const isKnown = !!node;
597
726
  if (msg.args.length === 0) {
598
727
  const val$1 = this.state.get(addr);
599
728
  if (val$1 !== void 0) return [{
600
729
  address: addr,
601
730
  args: [val$1]
602
731
  }];
732
+ if (!isKnown) this.logger.debug(LogCategory.STATE, `[GET] Unknown address queried: ${addr}`);
603
733
  return [];
604
734
  }
605
735
  const val = msg.args[0];
606
- if (node) {
736
+ if (isKnown) {
607
737
  if (!node.validate(val)) {
608
738
  this.logger.warn(LogCategory.DISPATCH, `[TYPE ERR] ${addr} expected ${node.type}, got ${typeof val}`);
609
739
  return [];
610
740
  }
611
- this.state.set(addr, val);
612
- this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
613
- if (addr.startsWith("/config/mute/")) {
614
- const groupIdx = parseInt(addr.split("/").pop(), 10);
615
- if (!isNaN(groupIdx)) this.state.handleMuteGroupChange(groupIdx, val);
616
- }
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}`);
752
+ }
753
+ return [];
754
+ }
755
+ };
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}`);
617
1300
  }
618
1301
  return [];
619
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
+ }
620
1387
  };
621
1388
 
622
1389
  //#endregion
@@ -648,10 +1415,12 @@ var OscMessageHandler = class {
648
1415
  this.staticResponseService = staticResponseService;
649
1416
  this.strategies = [
650
1417
  new NodeDiscoveryStrategy(this.schemaRegistry),
651
- new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService),
652
1418
  new SubscriptionStrategy(subscriptionManager, state, logger),
653
1419
  new BatchStrategy(subscriptionManager, logger),
654
1420
  new MeterStrategy(subscriptionManager, state, this.meterService),
1421
+ new ShowDumpStrategy(state, logger),
1422
+ new SceneDataStrategy(state, logger),
1423
+ new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService),
655
1424
  new StateAccessStrategy(state, logger, this.schemaRegistry)
656
1425
  ];
657
1426
  }
@@ -662,19 +1431,41 @@ var OscMessageHandler = class {
662
1431
  * @returns Array of replies generated by the strategy.
663
1432
  */
664
1433
  handle(msg, source) {
665
- 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
+ }
666
1454
  if (!addr.startsWith("/meters")) this.logger.debug(LogCategory.DISPATCH, `Handling`, {
667
1455
  addr,
668
- args: msg.args
1456
+ args
669
1457
  });
670
- for (const strategy of this.strategies) if (strategy.canHandle(addr)) {
671
- 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);
672
1463
  if (replies.length > 0) this.logger.debug(LogCategory.DISPATCH, `Strategy ${strategy.constructor.name} replied`, { count: replies.length });
673
1464
  return replies;
674
1465
  }
675
1466
  this.logger.warn(LogCategory.DISPATCH, `Unknown Command`, {
676
1467
  addr,
677
- args: msg.args,
1468
+ args,
678
1469
  ip: source.address
679
1470
  });
680
1471
  return [];
@@ -788,6 +1579,8 @@ var MeterService = class {
788
1579
  //#region src/domain/services/StaticResponseService.ts
789
1580
  /**
790
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.
791
1584
  */
792
1585
  const STATIC_RESPONSES_DATA = {
793
1586
  "/xinfo": [
@@ -806,34 +1599,7 @@ const STATIC_RESPONSES_DATA = {
806
1599
  "active",
807
1600
  "{{ip}}",
808
1601
  "{{name}}"
809
- ],
810
- "/-prefs/invertmutes": [0],
811
- "/-prefs/fine": [0],
812
- "/-prefs/bright": [50],
813
- "/-prefs/contrast": [50],
814
- "/-prefs/led_bright": [50],
815
- "/-prefs/lcd_bright": [50],
816
- "/-stat/userpar/1/value": [0],
817
- "/-stat/userpar/2/value": [0],
818
- "/-stat/tape/state": [0],
819
- "/-stat/aes50/A": [0],
820
- "/-stat/aes50/B": [0],
821
- "/-stat/solo": [0],
822
- "/-stat/rtasource": [0],
823
- "/-urec/errorcode": [0],
824
- "/-prefs/rta/gain": [0],
825
- "/-prefs/rta/autogain": [0],
826
- "/-prefs/hardmute": [0],
827
- "/-prefs/dcamute": [0],
828
- "/config/mono/mode": [0],
829
- "/config/amixenable/X": [0],
830
- "/config/amixenable/Y": [0],
831
- "/-show/prepos/current": [0],
832
- "/-show/showfile/current": [""],
833
- "/-action/setrtasrc": [0],
834
- "/-action/playtrack": [0],
835
- "/-action/goscene": [0],
836
- "/-action/setscene": [0]
1602
+ ]
837
1603
  };
838
1604
  /**
839
1605
  * Service providing static system responses.
@@ -863,47 +1629,51 @@ function getLocalIp() {
863
1629
  return "127.0.0.1";
864
1630
  }
865
1631
  /**
866
- * The core service that manages the X32 simulation, including networking,
1632
+ * The core service that manages the X32 simulation, including networking,
867
1633
  * state management, and OSC message handling.
868
1634
  */
869
1635
  var SimulationService = class {
870
1636
  subscriptionManager;
871
1637
  messageHandler;
1638
+ state;
872
1639
  processPacket;
873
1640
  broadcastUpdates;
874
1641
  manageSessions;
875
1642
  meterService;
876
1643
  staticResponseService;
1644
+ config;
877
1645
  updateInterval = null;
878
1646
  cleanupInterval = null;
879
1647
  /**
880
1648
  * Creates a new SimulationService instance.
881
1649
  * @param gateway - The network gateway for OSC communication.
882
1650
  * @param logger - The logger service.
883
- * @param stateRepo - The repository managing the mixer state.
1651
+ * @param stateRepository - The repository for state persistence.
884
1652
  * @param schemaRegistry - The registry for X32 OSC schema.
885
- * @param port - UDP port to listen on (default 10023).
886
- * @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).
887
1655
  * @param name - Reported console name.
888
1656
  * @param model - Reported console model.
1657
+ * @param configOverrides - Optional configuration overrides.
889
1658
  */
890
- 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) {
891
1660
  this.gateway = gateway;
892
1661
  this.logger = logger;
893
- this.stateRepo = stateRepo;
1662
+ this.stateRepository = stateRepository;
894
1663
  this.schemaRegistry = schemaRegistry;
895
1664
  this.port = port;
896
1665
  this.ip = ip;
897
- const state = this.stateRepo.getState();
898
- 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);
899
1669
  this.meterService = new MeterService();
900
1670
  this.staticResponseService = new StaticResponseService();
901
1671
  const reportedIp = this.ip === "0.0.0.0" ? getLocalIp() : this.ip;
902
- this.messageHandler = new OscMessageHandler(state, this.subscriptionManager, logger, reportedIp, name, model, this.meterService, this.schemaRegistry, this.staticResponseService);
903
- this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway);
904
- 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);
905
1675
  this.manageSessions = new ManageSessionsUseCase(this.subscriptionManager);
906
- state.on("change", (evt) => {
1676
+ this.state.on("change", (evt) => {
907
1677
  this.logger.info(LogCategory.STATE, `State Changed`, evt);
908
1678
  this.broadcastUpdates.broadcastSingleChange(evt.address, evt.value);
909
1679
  });
@@ -913,13 +1683,14 @@ var SimulationService = class {
913
1683
  * Starts the simulator server and internal loops.
914
1684
  */
915
1685
  async start() {
1686
+ await this.state.initialize();
916
1687
  await this.gateway.start(this.port, this.ip);
917
1688
  this.cleanupInterval = setInterval(() => {
918
1689
  this.manageSessions.cleanup();
919
- }, 5e3);
1690
+ }, this.config.CLEANUP_INTERVAL_MS);
920
1691
  this.updateInterval = setInterval(() => {
921
1692
  this.broadcastUpdates.execute();
922
- }, 100);
1693
+ }, this.config.BROADCAST_INTERVAL_MS);
923
1694
  }
924
1695
  /**
925
1696
  * Stops the simulator server and stops all internal loops.
@@ -927,84 +1698,20 @@ var SimulationService = class {
927
1698
  async stop() {
928
1699
  if (this.updateInterval) clearInterval(this.updateInterval);
929
1700
  if (this.cleanupInterval) clearInterval(this.cleanupInterval);
1701
+ await this.state.flush();
930
1702
  await this.gateway.stop();
931
1703
  }
932
1704
  /**
933
1705
  * Resets the mixer state to default values.
934
1706
  */
935
1707
  resetState() {
936
- this.stateRepo.reset();
937
- }
938
- };
939
-
940
- //#endregion
941
- //#region src/domain/entities/X32State.ts
942
- /**
943
- * Manages the internal "Digital Twin" state of the X32 console.
944
- * It acts as a single source of truth for all parameters.
945
- * Emits 'change' events whenever a value is updated.
946
- */
947
- var X32State = class extends node_events.EventEmitter {
948
- /** Map storing all OSC paths and their current values. */
949
- state = /* @__PURE__ */ new Map();
950
- defaultState = /* @__PURE__ */ new Map();
951
- /**
952
- * Initializes the state with default values from the schema.
953
- * @param schema - The schema definition map.
954
- */
955
- constructor(schema) {
956
- super();
957
- for (const [addr, def] of Object.entries(schema)) this.defaultState.set(addr, def.default);
958
- this.reset();
959
- }
960
- /**
961
- * Resets all state parameters to their default values defined in the schema.
962
- */
963
- reset() {
964
- this.state.clear();
965
- this.defaultState.forEach((val, key) => {
966
- this.state.set(key, val);
967
- });
1708
+ this.state.reset();
968
1709
  }
969
1710
  /**
970
- * Retrieves the value of a specific OSC node.
971
- * @param address - The full OSC address path.
972
- * @returns The stored value (number or string) or undefined if not found.
1711
+ * Forces a save of the current state to storage.
973
1712
  */
974
- get(address) {
975
- return this.state.get(address);
976
- }
977
- /**
978
- * Updates the value of a specific OSC node and notifies subscribers.
979
- * @param address - The full OSC address path.
980
- * @param value - The new value to store.
981
- */
982
- set(address, value) {
983
- this.state.set(address, value);
984
- this.emit("change", {
985
- address,
986
- value
987
- });
988
- }
989
- /**
990
- * Specialized logic to handle X32 Mute Groups side-effects.
991
- * When a mute group is toggled, it iterates through all channels
992
- * assigned to that group and updates their individual mute status.
993
- * @param groupIdx - The index of the mute group (1-6).
994
- * @param isOn - The new state of the group master switch (0 or 1).
995
- */
996
- handleMuteGroupChange(groupIdx, isOn) {
997
- for (let i = 1; i <= 32; i++) {
998
- const ch = i.toString().padStart(2, "0");
999
- const grpVal = this.get(`/ch/${ch}/grp/mute`);
1000
- if (typeof grpVal === "number") {
1001
- if ((grpVal & 1 << groupIdx - 1) !== 0) {
1002
- const targetMute = isOn === 1 ? 0 : 1;
1003
- const muteAddr = `/ch/${ch}/mix/on`;
1004
- this.set(muteAddr, targetMute);
1005
- }
1006
- }
1007
- }
1713
+ async saveState() {
1714
+ await this.state.flush();
1008
1715
  }
1009
1716
  };
1010
1717
 
@@ -1019,69 +1726,106 @@ var X32Node = class X32Node {
1019
1726
  type;
1020
1727
  /** Default value for reset. */
1021
1728
  default;
1729
+ /** Optional validation constraints. */
1730
+ options;
1022
1731
  /**
1023
1732
  * Creates a new X32Node.
1024
1733
  * @param type - The OSC data type ('f', 'i', 's').
1025
1734
  * @param defaultValue - The default value.
1735
+ * @param options - Optional validation constraints.
1026
1736
  */
1027
- constructor(type, defaultValue) {
1737
+ constructor(type, defaultValue, options) {
1028
1738
  this.type = type;
1029
1739
  this.default = defaultValue;
1740
+ this.options = options;
1030
1741
  }
1031
1742
  /**
1032
1743
  * Validates if a value is compatible with this node's type.
1033
1744
  * @param value - The value to check.
1745
+ * @param strict - If true, also validates against min/max constraints.
1034
1746
  * @returns True if valid.
1035
1747
  */
1036
- validate(value) {
1037
- if (this.type === "f") return typeof value === "number";
1038
- if (this.type === "i") return typeof value === "number";
1039
- if (this.type === "s") return typeof value === "string";
1040
- 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;
1041
1758
  }
1042
1759
  /**
1043
1760
  * Factory method to create from a plain object (for compatibility/migration).
1044
1761
  * @param obj - Plain object.
1045
1762
  * @param obj.type - OSC data type.
1046
1763
  * @param obj.default - Default value.
1764
+ * @param obj.options - Optional validation constraints.
1047
1765
  * @returns A new X32Node instance.
1048
1766
  */
1049
1767
  static from(obj) {
1050
- return new X32Node(obj.type, obj.default);
1768
+ return new X32Node(obj.type, obj.default, obj.options);
1051
1769
  }
1052
1770
  };
1053
1771
 
1054
1772
  //#endregion
1055
1773
  //#region src/infrastructure/repositories/InMemoryStateRepository.ts
1056
1774
  /**
1057
- * In-memory implementation of the state repository.
1058
- * 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.
1059
1778
  */
1060
1779
  var InMemoryStateRepository = class {
1061
- state;
1780
+ state = /* @__PURE__ */ new Map();
1062
1781
  /**
1063
- * Creates a new InMemoryStateRepository.
1064
- * @param logger - Logger service.
1065
- * @param schemaRegistry - Registry providing the initial state schema.
1782
+ * Loads state from memory.
1783
+ * For in-memory storage, this returns current state.
1066
1784
  */
1067
- constructor(logger, schemaRegistry) {
1068
- this.logger = logger;
1069
- this.state = new X32State(schemaRegistry.getSchema());
1785
+ async load() {
1786
+ return this.getAll();
1070
1787
  }
1071
1788
  /**
1072
- * Returns the current mixer state instance.
1073
- * @returns The X32State entity.
1789
+ * Saves state to memory.
1790
+ * For in-memory storage, this replaces current state.
1074
1791
  */
1075
- getState() {
1076
- 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);
1077
1795
  }
1078
1796
  /**
1079
- * Resets the entire state to its default values.
1797
+ * Gets a single value from memory.
1080
1798
  */
1081
- reset() {
1082
- this.logger.info(LogCategory.SYSTEM, "Resetting state to defaults");
1083
- 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);
1084
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);
1824
+ }
1825
+ /**
1826
+ * No-op for in-memory storage.
1827
+ */
1828
+ async flush() {}
1085
1829
  };
1086
1830
 
1087
1831
  //#endregion
@@ -1900,41 +2644,215 @@ var SchemaFactory = class {
1900
2644
  ...this.generateRange(128, "/headamp", "/gain", "f", 0, 3, 0),
1901
2645
  ...this.generateRange(128, "/headamp", "/phantom", "i", 0, 3, 0),
1902
2646
  ...this.generateRange(6, "/config/mute", "", "i", 0),
1903
- ...this.generateRange(80, "/-stat/solosw", "", "i", 0),
2647
+ ...this.generateRange(80, "/-stat/solosw", "", "i", 0, 2, 1),
1904
2648
  "/-stat/selidx": this.node("i", 0),
2649
+ "/-stat/chfaderbank": this.node("i", 0),
2650
+ "/-stat/grpfaderbank": this.node("i", 0),
1905
2651
  "/-stat/sendsonfader": this.node("i", 0),
1906
2652
  "/-stat/bussendbank": this.node("i", 0),
2653
+ "/-stat/eqband": this.node("i", 0),
1907
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),
1908
2669
  "/-stat/screen/screen": this.node("i", 0),
2670
+ "/-stat/screen/mutegrp": this.node("i", 0),
2671
+ "/-stat/screen/utils": this.node("i", 0),
1909
2672
  "/-stat/screen/CHAN/page": this.node("i", 0),
1910
2673
  "/-stat/screen/METER/page": this.node("i", 0),
1911
2674
  "/-stat/screen/ROUTE/page": this.node("i", 0),
1912
2675
  "/-stat/screen/SETUP/page": this.node("i", 0),
2676
+ "/-stat/screen/LIB/page": this.node("i", 0),
1913
2677
  "/-stat/screen/LIBRARY/page": this.node("i", 0),
1914
2678
  "/-stat/screen/FX/page": this.node("i", 0),
1915
2679
  "/-stat/screen/MON/page": this.node("i", 0),
1916
2680
  "/-stat/screen/USB/page": this.node("i", 0),
1917
2681
  "/-stat/screen/SCENE/page": this.node("i", 0),
1918
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),
1919
2695
  "/-stat/talk/A": this.node("i", 0),
1920
2696
  "/-stat/talk/B": this.node("i", 0),
1921
2697
  "/-stat/osc/on": this.node("i", 0),
1922
- "/-prefs/autosel": this.node("i", 1),
1923
- "/-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),
1924
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),
1925
2714
  "/-action/goscene": this.node("i", 0),
1926
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),
1927
2832
  "/config/routing/AES50A/1-8": this.node("i", 0),
1928
2833
  "/config/routing/AES50B/1-8": this.node("i", 0),
1929
2834
  "/config/routing/CARD/1-8": this.node("i", 0),
1930
2835
  "/config/routing/OUT/1-4": this.node("i", 0),
1931
- "/-prefs/invertmutes": this.node("i", 0),
1932
2836
  ...this.generateChannelStrip("/main/st", 6, false),
1933
2837
  ...this.generateChannelStrip("/main/m", 6, false),
1934
2838
  ...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1935
2839
  ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1936
2840
  ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
1937
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),
1938
2856
  ...this.generateRange(32, "/config/userrout/in", "", "i", 0),
1939
2857
  ...this.generateRange(48, "/config/userrout/out", "", "i", 0),
1940
2858
  "/config/solo/level": this.node("f", 0),
@@ -1970,6 +2888,7 @@ var SchemaFactory = class {
1970
2888
  "/config/osc/f2": this.node("f", .5),
1971
2889
  "/config/osc/level": this.node("f", 0),
1972
2890
  "/config/osc/dest": this.node("i", 0),
2891
+ ...this.generateDP48Config(),
1973
2892
  ...this.generateOutputs("main", 16),
1974
2893
  ...this.generateOutputs("aux", 6),
1975
2894
  ...this.generateOutputs("p16", 16),
@@ -1977,7 +2896,11 @@ var SchemaFactory = class {
1977
2896
  ...this.generateOutputs("rec", 2)
1978
2897
  };
1979
2898
  Object.keys(STATIC_RESPONSES_DATA).forEach((key) => {
1980
- if (key.startsWith("/-") || key.startsWith("/stat")) schema[key] = this.node("i", 0);
2899
+ if (key.startsWith("/-")) {
2900
+ const val = STATIC_RESPONSES_DATA[key][0];
2901
+ const type = typeof val === "string" ? "s" : Number.isInteger(val) ? "i" : "f";
2902
+ schema[key] = this.node(type, val);
2903
+ }
1981
2904
  });
1982
2905
  [
1983
2906
  "/config/routing/IN/1-8",
@@ -2088,6 +3011,7 @@ var SchemaFactory = class {
2088
3011
  generateNodes(count, prefix) {
2089
3012
  const nodes = {};
2090
3013
  const isChannelOrAux = prefix === "ch" || prefix === "auxin";
3014
+ const hasMtxPreamp = prefix === "mtx";
2091
3015
  const eqBands = prefix === "bus" || prefix === "mtx" ? 6 : 4;
2092
3016
  for (let i = 1; i <= count; i++) {
2093
3017
  const padId = i.toString().padStart(2, "0");
@@ -2099,7 +3023,7 @@ var SchemaFactory = class {
2099
3023
  nodes[`${base}/fader`] = this.node("f", .75);
2100
3024
  nodes[`${base}/on`] = this.node("i", 0);
2101
3025
  } else {
2102
- Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
3026
+ Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux || hasMtxPreamp));
2103
3027
  if (prefix === "ch" || prefix === "auxin" || prefix === "fxrtn" || prefix === "bus") for (let b = 1; b <= 16; b++) {
2104
3028
  const busId = b.toString().padStart(2, "0");
2105
3029
  nodes[`${base}/mix/${busId}/level`] = this.node("f", 0);
@@ -2107,6 +3031,11 @@ var SchemaFactory = class {
2107
3031
  nodes[`${base}/mix/${busId}/pan`] = this.node("f", .5);
2108
3032
  nodes[`${base}/mix/${busId}/type`] = this.node("i", 0);
2109
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
+ }
2110
3039
  }
2111
3040
  });
2112
3041
  }
@@ -2139,6 +3068,159 @@ var SchemaFactory = class {
2139
3068
  }
2140
3069
  return nodes;
2141
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
+ }
2142
3224
  };
2143
3225
 
2144
3226
  //#endregion
@@ -2209,6 +3291,12 @@ Object.defineProperty(exports, 'ConsoleLogger', {
2209
3291
  return ConsoleLogger;
2210
3292
  }
2211
3293
  });
3294
+ Object.defineProperty(exports, 'DEFAULT_CONFIG', {
3295
+ enumerable: true,
3296
+ get: function () {
3297
+ return DEFAULT_CONFIG;
3298
+ }
3299
+ });
2212
3300
  Object.defineProperty(exports, 'InMemoryStateRepository', {
2213
3301
  enumerable: true,
2214
3302
  get: function () {
@@ -2292,4 +3380,10 @@ Object.defineProperty(exports, '__toESM', {
2292
3380
  get: function () {
2293
3381
  return __toESM;
2294
3382
  }
3383
+ });
3384
+ Object.defineProperty(exports, 'mergeConfig', {
3385
+ enumerable: true,
3386
+ get: function () {
3387
+ return mergeConfig;
3388
+ }
2295
3389
  });