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