@djodjonx/x32-simulator 0.0.1

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.
Files changed (86) hide show
  1. package/.commitlintrc.json +3 -0
  2. package/.github/workflows/publish.yml +38 -0
  3. package/.husky/commit-msg +1 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.oxlintrc.json +56 -0
  6. package/CHANGELOG.md +11 -0
  7. package/INSTALL.md +107 -0
  8. package/LICENSE +21 -0
  9. package/README.md +141 -0
  10. package/dist/UdpNetworkGateway-BrroQ6-Q.mjs +1189 -0
  11. package/dist/UdpNetworkGateway-Ccdd7Us5.cjs +1265 -0
  12. package/dist/index.cjs +7 -0
  13. package/dist/index.d.cts +207 -0
  14. package/dist/index.d.mts +207 -0
  15. package/dist/index.mjs +3 -0
  16. package/dist/server.cjs +1060 -0
  17. package/dist/server.d.cts +10 -0
  18. package/dist/server.d.mts +10 -0
  19. package/dist/server.mjs +1055 -0
  20. package/docs/OSC-Communication.md +184 -0
  21. package/docs/X32-INTERNAL.md +262 -0
  22. package/docs/X32-OSC.pdf +0 -0
  23. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  24. package/package.json +68 -0
  25. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +120 -0
  26. package/src/application/use-cases/ManageSessionsUseCase.ts +9 -0
  27. package/src/application/use-cases/ProcessPacketUseCase.ts +26 -0
  28. package/src/application/use-cases/SimulationService.ts +122 -0
  29. package/src/domain/entities/SubscriptionManager.ts +126 -0
  30. package/src/domain/entities/X32State.ts +78 -0
  31. package/src/domain/models/MeterConfig.ts +22 -0
  32. package/src/domain/models/MeterData.ts +59 -0
  33. package/src/domain/models/OscMessage.ts +93 -0
  34. package/src/domain/models/X32Address.ts +78 -0
  35. package/src/domain/models/X32Node.ts +43 -0
  36. package/src/domain/models/types.ts +96 -0
  37. package/src/domain/ports/ILogger.ts +27 -0
  38. package/src/domain/ports/INetworkGateway.ts +8 -0
  39. package/src/domain/ports/IStateRepository.ts +16 -0
  40. package/src/domain/services/MeterService.ts +46 -0
  41. package/src/domain/services/OscMessageHandler.ts +88 -0
  42. package/src/domain/services/SchemaFactory.ts +308 -0
  43. package/src/domain/services/SchemaRegistry.ts +67 -0
  44. package/src/domain/services/StaticResponseService.ts +52 -0
  45. package/src/domain/services/strategies/BatchStrategy.ts +74 -0
  46. package/src/domain/services/strategies/MeterStrategy.ts +45 -0
  47. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +36 -0
  48. package/src/domain/services/strategies/OscCommandStrategy.ts +22 -0
  49. package/src/domain/services/strategies/StateAccessStrategy.ts +71 -0
  50. package/src/domain/services/strategies/StaticResponseStrategy.ts +42 -0
  51. package/src/domain/services/strategies/SubscriptionStrategy.ts +56 -0
  52. package/src/infrastructure/mappers/OscCodec.ts +54 -0
  53. package/src/infrastructure/repositories/InMemoryStateRepository.ts +21 -0
  54. package/src/infrastructure/services/ConsoleLogger.ts +177 -0
  55. package/src/infrastructure/services/UdpNetworkGateway.ts +71 -0
  56. package/src/presentation/cli/server.ts +194 -0
  57. package/src/presentation/library/library.ts +9 -0
  58. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +104 -0
  59. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +12 -0
  60. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +49 -0
  61. package/tests/application/use-cases/SimulationService.test.ts +77 -0
  62. package/tests/domain/entities/SubscriptionManager.test.ts +50 -0
  63. package/tests/domain/entities/X32State.test.ts +52 -0
  64. package/tests/domain/models/MeterData.test.ts +23 -0
  65. package/tests/domain/models/OscMessage.test.ts +38 -0
  66. package/tests/domain/models/X32Address.test.ts +30 -0
  67. package/tests/domain/models/X32Node.test.ts +30 -0
  68. package/tests/domain/services/MeterService.test.ts +27 -0
  69. package/tests/domain/services/OscMessageHandler.test.ts +51 -0
  70. package/tests/domain/services/SchemaRegistry.test.ts +47 -0
  71. package/tests/domain/services/StaticResponseService.test.ts +15 -0
  72. package/tests/domain/services/strategies/BatchStrategy.test.ts +41 -0
  73. package/tests/domain/services/strategies/MeterStrategy.test.ts +19 -0
  74. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +22 -0
  75. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +49 -0
  76. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +15 -0
  77. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +45 -0
  78. package/tests/infrastructure/mappers/OscCodec.test.ts +41 -0
  79. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +29 -0
  80. package/tests/infrastructure/services/ConsoleLogger.test.ts +74 -0
  81. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +61 -0
  82. package/tests/presentation/cli/server.test.ts +178 -0
  83. package/tests/presentation/library/library.test.ts +13 -0
  84. package/tsconfig.json +21 -0
  85. package/tsdown.config.ts +15 -0
  86. package/vitest.config.ts +9 -0
@@ -0,0 +1,1055 @@
1
+ #!/usr/bin/env node
2
+ import { a as SimulationService, i as InMemoryStateRepository, n as ConsoleLogger, o as STATIC_RESPONSES_DATA, r as LogLevel, t as UdpNetworkGateway } from "./UdpNetworkGateway-BrroQ6-Q.mjs";
3
+ import * as readline from "node:readline";
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { Buffer as Buffer$1 } from "node:buffer";
8
+
9
+ //#region src/domain/services/SchemaRegistry.ts
10
+ /**
11
+ * Service providing access to the X32 OSC Schema.
12
+ * Acts as the "Registry" of all available console parameters.
13
+ */
14
+ var SchemaRegistry = class {
15
+ _schema;
16
+ constructor(schemaFactory) {
17
+ this.schemaFactory = schemaFactory;
18
+ this._schema = this.schemaFactory.createSchema();
19
+ }
20
+ /**
21
+ * Retrieves the entire schema map.
22
+ * @returns The internal schema definition record.
23
+ */
24
+ getSchema() {
25
+ return this._schema;
26
+ }
27
+ /**
28
+ * Retrieves the node definition for a given path.
29
+ * @param path - The OSC path.
30
+ * @returns The X32Node definition or undefined if not found.
31
+ */
32
+ getNode(path$1) {
33
+ return this._schema[path$1];
34
+ }
35
+ /**
36
+ * Checks if a path exists in the schema.
37
+ * @param path - The path to check.
38
+ * @returns True if the path is registered.
39
+ */
40
+ has(path$1) {
41
+ return path$1 in this._schema;
42
+ }
43
+ /**
44
+ * Returns all paths in the schema.
45
+ * @returns Array of all registered OSC paths.
46
+ */
47
+ getAllPaths() {
48
+ return Object.keys(this._schema);
49
+ }
50
+ /**
51
+ * Maps an absolute X32 global index to its specific OSC root path.
52
+ * Used for batch subscriptions where clients request ranges of channels.
53
+ * @param index - The absolute integer index.
54
+ * @returns The root path string or null if index is out of bounds.
55
+ */
56
+ getRootFromIndex(index) {
57
+ if (index >= 0 && index <= 31) return `/ch/${(index + 1).toString().padStart(2, "0")}`;
58
+ if (index >= 32 && index <= 39) return `/auxin/${(index - 31).toString().padStart(2, "0")}`;
59
+ if (index >= 40 && index <= 47) return `/fxrtn/${(index - 39).toString().padStart(2, "0")}`;
60
+ if (index >= 48 && index <= 63) return `/bus/${(index - 47).toString().padStart(2, "0")}`;
61
+ if (index >= 64 && index <= 69) return `/mtx/${(index - 63).toString().padStart(2, "0")}`;
62
+ if (index === 70) return "/main/st";
63
+ if (index === 71) return "/main/m";
64
+ if (index >= 72 && index <= 79) return `/dca/${(index - 71).toString().padStart(2, "0")}`;
65
+ return null;
66
+ }
67
+ };
68
+
69
+ //#endregion
70
+ //#region src/domain/models/X32Node.ts
71
+ /**
72
+ * Metadata for a single state node in the X32 schema.
73
+ * Represents a "Knob" or "Variable" on the console.
74
+ */
75
+ var X32Node = class X32Node {
76
+ /** Value type (f=float, i=int, s=string). */
77
+ type;
78
+ /** Default value for reset. */
79
+ default;
80
+ /**
81
+ * Creates a new X32Node.
82
+ * @param type - The OSC data type ('f', 'i', 's').
83
+ * @param defaultValue - The default value.
84
+ */
85
+ constructor(type, defaultValue) {
86
+ this.type = type;
87
+ this.default = defaultValue;
88
+ }
89
+ /**
90
+ * Validates if a value is compatible with this node's type.
91
+ * @param value - The value to check.
92
+ * @returns True if valid.
93
+ */
94
+ validate(value) {
95
+ if (this.type === "f") return typeof value === "number";
96
+ if (this.type === "i") return typeof value === "number";
97
+ if (this.type === "s") return typeof value === "string";
98
+ return false;
99
+ }
100
+ /**
101
+ * Factory method to create from a plain object (for compatibility/migration).
102
+ * @param obj - Plain object.
103
+ * @param obj.type - OSC data type.
104
+ * @param obj.default - Default value.
105
+ * @returns A new X32Node instance.
106
+ */
107
+ static from(obj) {
108
+ return new X32Node(obj.type, obj.default);
109
+ }
110
+ };
111
+
112
+ //#endregion
113
+ //#region src/domain/services/SchemaFactory.ts
114
+ /**
115
+ * Factory service responsible for constructing the X32 OSC Schema.
116
+ * Encapsulates all the logic for generating channel strips, routing blocks, etc.
117
+ */
118
+ var SchemaFactory = class {
119
+ /**
120
+ * Builds the complete X32 OSC Schema.
121
+ * @returns The constructed schema map.
122
+ */
123
+ createSchema() {
124
+ const schema = {
125
+ ...this.generateNodes(32, "ch"),
126
+ ...this.generateNodes(16, "bus"),
127
+ ...this.generateNodes(8, "dca"),
128
+ ...this.generateNodes(8, "auxin"),
129
+ ...this.generateNodes(8, "fxrtn"),
130
+ ...this.generateNodes(6, "mtx"),
131
+ ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/fx/${i + 1}/type`, this.node("i", 0)])),
132
+ ...this.generateRange(8, "/fx", "/type", "i", 0),
133
+ ...this.generateRange(8, "/fx", "/source/l", "i", 0),
134
+ ...this.generateRange(8, "/fx", "/source/r", "i", 0),
135
+ ...Object.fromEntries(Array.from({ length: 8 }, (_, slot) => Object.entries(this.generateRange(64, `/fx/${slot + 1}/par`, "", "f", 0, 2, 1))).flat()),
136
+ ...this.generateRange(128, "/headamp", "/gain", "f", 0, 3, 0),
137
+ ...this.generateRange(128, "/headamp", "/phantom", "i", 0, 3, 0),
138
+ ...this.generateRange(6, "/config/mute", "", "i", 0),
139
+ ...this.generateRange(80, "/-stat/solosw", "", "i", 0),
140
+ "/-stat/selidx": this.node("i", 0),
141
+ "/-stat/sendsonfader": this.node("i", 0),
142
+ "/-stat/bussendbank": this.node("i", 0),
143
+ "/-stat/keysolo": this.node("i", 0),
144
+ "/-stat/screen/screen": this.node("i", 0),
145
+ "/-stat/screen/CHAN/page": this.node("i", 0),
146
+ "/-stat/screen/METER/page": this.node("i", 0),
147
+ "/-stat/screen/ROUTE/page": this.node("i", 0),
148
+ "/-stat/screen/SETUP/page": this.node("i", 0),
149
+ "/-stat/screen/LIBRARY/page": this.node("i", 0),
150
+ "/-stat/screen/FX/page": this.node("i", 0),
151
+ "/-stat/screen/MON/page": this.node("i", 0),
152
+ "/-stat/screen/USB/page": this.node("i", 0),
153
+ "/-stat/screen/SCENE/page": this.node("i", 0),
154
+ "/-stat/screen/ASSIGN/page": this.node("i", 0),
155
+ "/-stat/talk/A": this.node("i", 0),
156
+ "/-stat/talk/B": this.node("i", 0),
157
+ "/-stat/osc/on": this.node("i", 0),
158
+ "/-prefs/autosel": this.node("i", 1),
159
+ "/-action/setrtasrc": this.node("i", 0),
160
+ "/-action/playtrack": this.node("i", 0),
161
+ "/-action/goscene": this.node("i", 0),
162
+ "/-action/setscene": this.node("i", 0),
163
+ "/config/routing/AES50A/1-8": this.node("i", 0),
164
+ "/config/routing/AES50B/1-8": this.node("i", 0),
165
+ "/config/routing/CARD/1-8": this.node("i", 0),
166
+ "/config/routing/OUT/1-4": this.node("i", 0),
167
+ "/-prefs/invertmutes": this.node("i", 0),
168
+ ...this.generateChannelStrip("/main/st", 6, false),
169
+ ...this.generateChannelStrip("/main/m", 6, false),
170
+ ...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
171
+ ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
172
+ ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
173
+ ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/fxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
174
+ ...this.generateRange(32, "/config/userrout/in", "", "i", 0),
175
+ ...this.generateRange(48, "/config/userrout/out", "", "i", 0),
176
+ "/config/solo/level": this.node("f", 0),
177
+ "/config/solo/source": this.node("i", 0),
178
+ "/config/solo/sourcetrim": this.node("f", 0),
179
+ "/config/solo/exclusive": this.node("i", 0),
180
+ "/config/solo/dim": this.node("i", 0),
181
+ "/config/solo/dimpfl": this.node("i", 0),
182
+ "/config/solo/dimatt": this.node("f", 0),
183
+ "/config/solo/mono": this.node("i", 0),
184
+ "/config/solo/chmode": this.node("i", 0),
185
+ "/config/solo/busmode": this.node("i", 0),
186
+ "/config/solo/dcamode": this.node("i", 0),
187
+ "/config/solo/masterctrl": this.node("i", 0),
188
+ "/config/solo/delay": this.node("i", 0),
189
+ "/config/solo/delaytime": this.node("f", 0),
190
+ "/config/solo/followsel": this.node("i", 0),
191
+ "/config/solo/followsolo": this.node("i", 0),
192
+ "/config/talk/enable": this.node("i", 0),
193
+ "/config/talk/source": this.node("i", 0),
194
+ "/config/talk/A/level": this.node("f", 0),
195
+ "/config/talk/B/level": this.node("f", 0),
196
+ "/config/talk/A/dim": this.node("i", 0),
197
+ "/config/talk/B/dim": this.node("i", 0),
198
+ "/config/talk/A/latch": this.node("i", 0),
199
+ "/config/talk/B/latch": this.node("i", 0),
200
+ "/config/talk/A/destmap": this.node("i", 0),
201
+ "/config/talk/B/destmap": this.node("i", 0),
202
+ "/config/osc/on": this.node("i", 0),
203
+ "/config/osc/type": this.node("i", 0),
204
+ "/config/osc/fsel": this.node("i", 0),
205
+ "/config/osc/f1": this.node("f", .5),
206
+ "/config/osc/f2": this.node("f", .5),
207
+ "/config/osc/level": this.node("f", 0),
208
+ "/config/osc/dest": this.node("i", 0),
209
+ ...this.generateOutputs("main", 16),
210
+ ...this.generateOutputs("aux", 6),
211
+ ...this.generateOutputs("p16", 16),
212
+ ...this.generateOutputs("aes", 2),
213
+ ...this.generateOutputs("rec", 2)
214
+ };
215
+ Object.keys(STATIC_RESPONSES_DATA).forEach((key) => {
216
+ if (key.startsWith("/-") || key.startsWith("/stat")) schema[key] = this.node("i", 0);
217
+ });
218
+ [
219
+ "/config/routing/IN/1-8",
220
+ "/config/routing/IN/9-16",
221
+ "/config/routing/IN/17-24",
222
+ "/config/routing/IN/25-32",
223
+ "/config/routing/AUX/1-4",
224
+ "/config/routing/OUT/1-4",
225
+ "/config/routing/OUT/5-8",
226
+ "/config/routing/OUT/9-12",
227
+ "/config/routing/OUT/13-16",
228
+ "/config/routing/P16/1-8",
229
+ "/config/routing/P16/9-16",
230
+ "/config/routing/CARD/1-8",
231
+ "/config/routing/CARD/9-16",
232
+ "/config/routing/CARD/17-24",
233
+ "/config/routing/CARD/25-32",
234
+ "/config/routing/AES50A/1-8",
235
+ "/config/routing/AES50A/9-16",
236
+ "/config/routing/AES50A/17-24",
237
+ "/config/routing/AES50A/25-32",
238
+ "/config/routing/AES50A/33-40",
239
+ "/config/routing/AES50A/41-48",
240
+ "/config/routing/AES50B/1-8",
241
+ "/config/routing/AES50B/9-16",
242
+ "/config/routing/AES50B/17-24",
243
+ "/config/routing/AES50B/25-32",
244
+ "/config/routing/AES50B/33-40",
245
+ "/config/routing/AES50B/41-48",
246
+ "/config/routing/PLAY/1-8",
247
+ "/config/routing/PLAY/9-16",
248
+ "/config/routing/PLAY/17-24",
249
+ "/config/routing/PLAY/25-32"
250
+ ].forEach((path$1) => {
251
+ schema[path$1] = this.node("i", 0);
252
+ });
253
+ return schema;
254
+ }
255
+ node(type, def) {
256
+ return new X32Node(type, def);
257
+ }
258
+ generateChannelStrip(base, eqBands = 4, hasPreamp = false) {
259
+ const nodes = {};
260
+ nodes[`${base}/config/name`] = this.node("s", base.split("/").pop()?.toUpperCase() || "");
261
+ nodes[`${base}/config/icon`] = this.node("i", 1);
262
+ nodes[`${base}/config/color`] = this.node("i", 1);
263
+ nodes[`${base}/config/source`] = this.node("i", 1);
264
+ nodes[`${base}/mix/fader`] = this.node("f", .75);
265
+ nodes[`${base}/mix/on`] = this.node("i", 0);
266
+ nodes[`${base}/mix/pan`] = this.node("f", .5);
267
+ nodes[`${base}/mix/mono`] = this.node("i", 0);
268
+ nodes[`${base}/mix/mlevel`] = this.node("f", 0);
269
+ nodes[`${base}/mix/st`] = this.node("i", 1);
270
+ if (hasPreamp) {
271
+ nodes[`${base}/preamp/trim`] = this.node("f", .5);
272
+ nodes[`${base}/preamp/hpon`] = this.node("i", 0);
273
+ nodes[`${base}/preamp/hpf`] = this.node("f", 0);
274
+ nodes[`${base}/preamp/phantom`] = this.node("i", 0);
275
+ nodes[`${base}/preamp/rtnsw`] = this.node("i", 0);
276
+ nodes[`${base}/preamp/invert`] = this.node("i", 0);
277
+ }
278
+ nodes[`${base}/delay/on`] = this.node("i", 0);
279
+ nodes[`${base}/delay/time`] = this.node("f", 0);
280
+ nodes[`${base}/insert/on`] = this.node("i", 0);
281
+ nodes[`${base}/insert/pos`] = this.node("i", 0);
282
+ nodes[`${base}/insert/sel`] = this.node("i", 0);
283
+ nodes[`${base}/gate/on`] = this.node("i", 0);
284
+ nodes[`${base}/gate/mode`] = this.node("i", 0);
285
+ nodes[`${base}/gate/thr`] = this.node("f", 0);
286
+ nodes[`${base}/gate/range`] = this.node("f", 0);
287
+ nodes[`${base}/gate/attack`] = this.node("f", 0);
288
+ nodes[`${base}/gate/hold`] = this.node("f", 0);
289
+ nodes[`${base}/gate/release`] = this.node("f", 0);
290
+ nodes[`${base}/gate/keysrc`] = this.node("i", 0);
291
+ nodes[`${base}/gate/filter/on`] = this.node("i", 0);
292
+ nodes[`${base}/gate/filter/type`] = this.node("i", 0);
293
+ nodes[`${base}/gate/filter/f`] = this.node("f", .5);
294
+ nodes[`${base}/dyn/on`] = this.node("i", 0);
295
+ nodes[`${base}/dyn/mode`] = this.node("i", 0);
296
+ nodes[`${base}/dyn/pos`] = this.node("i", 0);
297
+ nodes[`${base}/dyn/det`] = this.node("i", 0);
298
+ nodes[`${base}/dyn/env`] = this.node("i", 0);
299
+ nodes[`${base}/dyn/thr`] = this.node("f", 0);
300
+ nodes[`${base}/dyn/ratio`] = this.node("i", 0);
301
+ nodes[`${base}/dyn/knee`] = this.node("f", 0);
302
+ nodes[`${base}/dyn/mgain`] = this.node("f", 0);
303
+ nodes[`${base}/dyn/attack`] = this.node("f", 0);
304
+ nodes[`${base}/dyn/hold`] = this.node("f", 0);
305
+ nodes[`${base}/dyn/release`] = this.node("f", 0);
306
+ nodes[`${base}/dyn/mix`] = this.node("f", 1);
307
+ nodes[`${base}/dyn/auto`] = this.node("i", 0);
308
+ nodes[`${base}/dyn/keysrc`] = this.node("i", 0);
309
+ nodes[`${base}/dyn/filter/on`] = this.node("i", 0);
310
+ nodes[`${base}/dyn/filter/type`] = this.node("i", 0);
311
+ nodes[`${base}/dyn/filter/f`] = this.node("f", .5);
312
+ nodes[`${base}/eq/on`] = this.node("i", 0);
313
+ nodes[`${base}/eq/mode`] = this.node("i", 0);
314
+ for (let b = 1; b <= eqBands; b++) {
315
+ nodes[`${base}/eq/${b}/type`] = this.node("i", 0);
316
+ nodes[`${base}/eq/${b}/f`] = this.node("f", .5);
317
+ nodes[`${base}/eq/${b}/g`] = this.node("f", .5);
318
+ nodes[`${base}/eq/${b}/q`] = this.node("f", .5);
319
+ }
320
+ nodes[`${base}/grp/dca`] = this.node("i", 0);
321
+ nodes[`${base}/grp/mute`] = this.node("i", 0);
322
+ return nodes;
323
+ }
324
+ generateNodes(count, prefix) {
325
+ const nodes = {};
326
+ const isChannelOrAux = prefix === "ch" || prefix === "auxin";
327
+ const eqBands = prefix === "bus" || prefix === "mtx" ? 6 : 4;
328
+ for (let i = 1; i <= count; i++) {
329
+ const padId = i.toString().padStart(2, "0");
330
+ [i.toString(), padId].forEach((id) => {
331
+ const base = `/${prefix}/${id}`;
332
+ if (prefix === "dca") {
333
+ nodes[`${base}/config/name`] = this.node("s", `DCA ${id}`);
334
+ nodes[`${base}/config/color`] = this.node("i", 1);
335
+ nodes[`${base}/fader`] = this.node("f", .75);
336
+ nodes[`${base}/on`] = this.node("i", 0);
337
+ } else {
338
+ Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
339
+ if (prefix === "ch" || prefix === "auxin" || prefix === "fxrtn" || prefix === "bus") for (let b = 1; b <= 16; b++) {
340
+ const busId = b.toString().padStart(2, "0");
341
+ nodes[`${base}/mix/${busId}/level`] = this.node("f", 0);
342
+ nodes[`${base}/mix/${busId}/on`] = this.node("i", 0);
343
+ nodes[`${base}/mix/${busId}/pan`] = this.node("f", .5);
344
+ nodes[`${base}/mix/${busId}/type`] = this.node("i", 0);
345
+ }
346
+ }
347
+ });
348
+ }
349
+ return nodes;
350
+ }
351
+ generateRange(count, prefix, suffix, type, def, pad = 2, start = 1) {
352
+ const nodes = {};
353
+ for (let i = start; i < start + count; i++) {
354
+ const id = i.toString().padStart(pad, "0");
355
+ nodes[`${prefix}/${i}${suffix}`] = this.node(type, def);
356
+ nodes[`${prefix}/${id}${suffix}`] = this.node(type, def);
357
+ }
358
+ return nodes;
359
+ }
360
+ generateOutputs(prefix, count) {
361
+ const nodes = {};
362
+ for (let i = 1; i <= count; i++) {
363
+ const base = `/outputs/${prefix}/${i.toString().padStart(2, "0")}`;
364
+ nodes[`${base}/src`] = this.node("i", 0);
365
+ nodes[`${base}/pos`] = this.node("i", 0);
366
+ nodes[`${base}/invert`] = this.node("i", 0);
367
+ nodes[`${base}/delay/on`] = this.node("i", 0);
368
+ nodes[`${base}/delay/time`] = this.node("f", 0);
369
+ if (prefix === "p16") {
370
+ nodes[`${base}/iQ/group`] = this.node("i", 0);
371
+ nodes[`${base}/iQ/model`] = this.node("i", 0);
372
+ nodes[`${base}/iQ/eq`] = this.node("i", 0);
373
+ nodes[`${base}/iQ/speaker`] = this.node("i", 0);
374
+ }
375
+ }
376
+ return nodes;
377
+ }
378
+ };
379
+
380
+ //#endregion
381
+ //#region node_modules/node-osc/lib/Message.mjs
382
+ const typeTags = {
383
+ s: "string",
384
+ f: "float",
385
+ i: "integer",
386
+ b: "blob",
387
+ m: "midi"
388
+ };
389
+ /**
390
+ * Represents a typed argument for an OSC message.
391
+ *
392
+ * @class
393
+ * @private
394
+ */
395
+ var Argument = class {
396
+ /**
397
+ * @param {string} type - The type of the argument (string, float, integer, blob, boolean).
398
+ * @param {*} value - The value of the argument.
399
+ */
400
+ constructor(type, value) {
401
+ this.type = type;
402
+ this.value = value;
403
+ }
404
+ };
405
+ /**
406
+ * Represents an OSC message with an address and arguments.
407
+ *
408
+ * OSC messages consist of an address pattern (string starting with '/')
409
+ * and zero or more arguments of various types.
410
+ *
411
+ * @class
412
+ *
413
+ * @example
414
+ * // Create a message with constructor arguments
415
+ * const msg = new Message('/test', 1, 2, 'hello');
416
+ *
417
+ * @example
418
+ * // Create a message and append arguments
419
+ * const msg = new Message('/test');
420
+ * msg.append(1);
421
+ * msg.append('hello');
422
+ * msg.append(3.14);
423
+ */
424
+ var Message = class {
425
+ /**
426
+ * Create an OSC Message.
427
+ *
428
+ * @param {string} address - The OSC address pattern (e.g., '/oscillator/frequency').
429
+ * @param {...*} args - Optional arguments to include in the message.
430
+ *
431
+ * @example
432
+ * const msg = new Message('/test');
433
+ *
434
+ * @example
435
+ * const msg = new Message('/test', 1, 2, 3);
436
+ *
437
+ * @example
438
+ * const msg = new Message('/synth', 'note', 60, 0.5);
439
+ */
440
+ constructor(address, ...args) {
441
+ this.oscType = "message";
442
+ this.address = address;
443
+ this.args = args;
444
+ }
445
+ /**
446
+ * Append an argument to the message.
447
+ *
448
+ * Automatically detects the type based on the JavaScript type:
449
+ * - Integers are encoded as OSC integers
450
+ * - Floats are encoded as OSC floats
451
+ * - Strings are encoded as OSC strings
452
+ * - Booleans are encoded as OSC booleans
453
+ * - Buffers are encoded as OSC blobs
454
+ * - Arrays are recursively appended
455
+ * - Objects with a 'type' property are used as-is
456
+ *
457
+ * @param {*} arg - The argument to append. Can be:
458
+ * - A primitive value (number, string, boolean)
459
+ * - A Buffer (encoded as blob)
460
+ * - An array of values (will be recursively appended)
461
+ * - An object with 'type' and 'value' properties for explicit type control
462
+ *
463
+ * @throws {Error} If the argument type cannot be encoded.
464
+ *
465
+ * @example
466
+ * const msg = new Message('/test');
467
+ * msg.append(42); // Integer
468
+ * msg.append(3.14); // Float
469
+ * msg.append('hello'); // String
470
+ * msg.append(true); // Boolean
471
+ *
472
+ * @example
473
+ * // Append multiple values at once
474
+ * msg.append([1, 2, 3]);
475
+ *
476
+ * @example
477
+ * // Explicitly specify type
478
+ * msg.append({ type: 'float', value: 42 });
479
+ * msg.append({ type: 'blob', value: Buffer.from('data') });
480
+ *
481
+ * @example
482
+ * // MIDI messages (4 bytes: port, status, data1, data2)
483
+ * msg.append({ type: 'midi', value: { port: 0, status: 144, data1: 60, data2: 127 } });
484
+ * msg.append({ type: 'm', value: Buffer.from([0, 144, 60, 127]) });
485
+ */
486
+ append(arg) {
487
+ let argOut;
488
+ switch (typeof arg) {
489
+ case "object":
490
+ if (Buffer.isBuffer(arg)) this.args.push(arg);
491
+ else if (arg instanceof Array) arg.forEach((a) => this.append(a));
492
+ else if (arg.type) {
493
+ if (typeTags[arg.type]) arg.type = typeTags[arg.type];
494
+ this.args.push(arg);
495
+ } else throw new Error(`don't know how to encode object ${arg}`);
496
+ break;
497
+ case "number":
498
+ if (Math.floor(arg) === arg) argOut = new Argument("integer", arg);
499
+ else argOut = new Argument("float", arg);
500
+ break;
501
+ case "string":
502
+ argOut = new Argument("string", arg);
503
+ break;
504
+ case "boolean":
505
+ argOut = new Argument("boolean", arg);
506
+ break;
507
+ default: throw new Error(`don't know how to encode ${arg}`);
508
+ }
509
+ if (argOut) this.args.push(argOut);
510
+ }
511
+ };
512
+ var Message_default = Message;
513
+
514
+ //#endregion
515
+ //#region node_modules/node-osc/lib/osc.mjs
516
+ function padString(str) {
517
+ const nullTerminated = str + "\0";
518
+ const padding = (4 - Buffer$1.byteLength(nullTerminated) % 4) % 4;
519
+ return nullTerminated + "\0".repeat(padding);
520
+ }
521
+ function readString(buffer, offset) {
522
+ let end = offset;
523
+ while (end < buffer.length && buffer[end] !== 0) end++;
524
+ if (end >= buffer.length) throw new Error("Malformed Packet: Missing null terminator for string");
525
+ return {
526
+ value: buffer.subarray(offset, end).toString("utf8"),
527
+ offset: offset + Math.ceil((end - offset + 1) / 4) * 4
528
+ };
529
+ }
530
+ function writeInt32(value) {
531
+ const buffer = Buffer$1.alloc(4);
532
+ buffer.writeInt32BE(value, 0);
533
+ return buffer;
534
+ }
535
+ function readInt32(buffer, offset) {
536
+ if (offset + 4 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for int32");
537
+ return {
538
+ value: buffer.readInt32BE(offset),
539
+ offset: offset + 4
540
+ };
541
+ }
542
+ function writeFloat32(value) {
543
+ const buffer = Buffer$1.alloc(4);
544
+ buffer.writeFloatBE(value, 0);
545
+ return buffer;
546
+ }
547
+ function readFloat32(buffer, offset) {
548
+ if (offset + 4 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for float32");
549
+ return {
550
+ value: buffer.readFloatBE(offset),
551
+ offset: offset + 4
552
+ };
553
+ }
554
+ function writeBlob(value) {
555
+ const length = value.length;
556
+ const lengthBuffer = writeInt32(length);
557
+ const padding = 4 - length % 4;
558
+ const paddingBuffer = Buffer$1.alloc(padding === 4 ? 0 : padding);
559
+ return Buffer$1.concat([
560
+ lengthBuffer,
561
+ value,
562
+ paddingBuffer
563
+ ]);
564
+ }
565
+ function readBlob(buffer, offset) {
566
+ const lengthResult = readInt32(buffer, offset);
567
+ const length = lengthResult.value;
568
+ if (length < 0) throw new Error("Malformed Packet: Invalid blob length");
569
+ if (lengthResult.offset + length > buffer.length) throw new Error("Malformed Packet: Not enough bytes for blob");
570
+ const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
571
+ const padding = 4 - length % 4;
572
+ const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
573
+ if (nextOffset > buffer.length) throw new Error("Malformed Packet: Not enough bytes for blob padding");
574
+ return {
575
+ value: data,
576
+ offset: nextOffset
577
+ };
578
+ }
579
+ function writeTimeTag(value) {
580
+ const buffer = Buffer$1.alloc(8);
581
+ if (value === 0 || value === null || value === void 0) {
582
+ buffer.writeUInt32BE(0, 0);
583
+ buffer.writeUInt32BE(1, 4);
584
+ } else if (typeof value === "number") {
585
+ const seconds = Math.floor(value);
586
+ const fraction = Math.floor((value - seconds) * 4294967296);
587
+ buffer.writeUInt32BE(seconds + 2208988800, 0);
588
+ buffer.writeUInt32BE(fraction, 4);
589
+ } else {
590
+ buffer.writeUInt32BE(0, 0);
591
+ buffer.writeUInt32BE(1, 4);
592
+ }
593
+ return buffer;
594
+ }
595
+ function readTimeTag(buffer, offset) {
596
+ if (offset + 8 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for timetag");
597
+ const seconds = buffer.readUInt32BE(offset);
598
+ const fraction = buffer.readUInt32BE(offset + 4);
599
+ let value;
600
+ if (seconds === 0 && fraction === 1) value = 0;
601
+ else value = seconds - 2208988800 + fraction / 4294967296;
602
+ return {
603
+ value,
604
+ offset: offset + 8
605
+ };
606
+ }
607
+ function writeMidi(value) {
608
+ const buffer = Buffer$1.alloc(4);
609
+ if (Buffer$1.isBuffer(value)) {
610
+ if (value.length !== 4) throw new Error("MIDI message must be exactly 4 bytes");
611
+ value.copy(buffer);
612
+ } else if (typeof value === "object" && value !== null) {
613
+ buffer.writeUInt8(value.port || 0, 0);
614
+ buffer.writeUInt8(value.status || 0, 1);
615
+ buffer.writeUInt8(value.data1 || 0, 2);
616
+ buffer.writeUInt8(value.data2 || 0, 3);
617
+ } else throw new Error("MIDI value must be a 4-byte Buffer or object with port, status, data1, data2 properties");
618
+ return buffer;
619
+ }
620
+ function readMidi(buffer, offset) {
621
+ if (offset + 4 > buffer.length) throw new Error("Not enough bytes for MIDI message");
622
+ return {
623
+ value: buffer.subarray(offset, offset + 4),
624
+ offset: offset + 4
625
+ };
626
+ }
627
+ function encodeArgument(arg) {
628
+ if (typeof arg === "object" && arg.type && arg.value !== void 0) switch (arg.type) {
629
+ case "i":
630
+ case "integer": return {
631
+ tag: "i",
632
+ data: writeInt32(arg.value)
633
+ };
634
+ case "f":
635
+ case "float": return {
636
+ tag: "f",
637
+ data: writeFloat32(arg.value)
638
+ };
639
+ case "s":
640
+ case "string": return {
641
+ tag: "s",
642
+ data: Buffer$1.from(padString(arg.value))
643
+ };
644
+ case "b":
645
+ case "blob": return {
646
+ tag: "b",
647
+ data: writeBlob(arg.value)
648
+ };
649
+ case "d":
650
+ case "double": return {
651
+ tag: "f",
652
+ data: writeFloat32(arg.value)
653
+ };
654
+ case "T": return {
655
+ tag: "T",
656
+ data: Buffer$1.alloc(0)
657
+ };
658
+ case "F": return {
659
+ tag: "F",
660
+ data: Buffer$1.alloc(0)
661
+ };
662
+ case "boolean": return arg.value ? {
663
+ tag: "T",
664
+ data: Buffer$1.alloc(0)
665
+ } : {
666
+ tag: "F",
667
+ data: Buffer$1.alloc(0)
668
+ };
669
+ case "m":
670
+ case "midi": return {
671
+ tag: "m",
672
+ data: writeMidi(arg.value)
673
+ };
674
+ default: throw new Error(`Unknown argument type: ${arg.type}`);
675
+ }
676
+ switch (typeof arg) {
677
+ case "number": if (Number.isInteger(arg)) return {
678
+ tag: "i",
679
+ data: writeInt32(arg)
680
+ };
681
+ else return {
682
+ tag: "f",
683
+ data: writeFloat32(arg)
684
+ };
685
+ case "string": return {
686
+ tag: "s",
687
+ data: Buffer$1.from(padString(arg))
688
+ };
689
+ case "boolean": return arg ? {
690
+ tag: "T",
691
+ data: Buffer$1.alloc(0)
692
+ } : {
693
+ tag: "F",
694
+ data: Buffer$1.alloc(0)
695
+ };
696
+ default:
697
+ if (Buffer$1.isBuffer(arg)) return {
698
+ tag: "b",
699
+ data: writeBlob(arg)
700
+ };
701
+ throw new Error(`Don't know how to encode argument: ${arg}`);
702
+ }
703
+ }
704
+ function decodeArgument(tag, buffer, offset) {
705
+ switch (tag) {
706
+ case "i": return readInt32(buffer, offset);
707
+ case "f": return readFloat32(buffer, offset);
708
+ case "s": return readString(buffer, offset);
709
+ case "b": return readBlob(buffer, offset);
710
+ case "T": return {
711
+ value: true,
712
+ offset
713
+ };
714
+ case "F": return {
715
+ value: false,
716
+ offset
717
+ };
718
+ case "N": return {
719
+ value: null,
720
+ offset
721
+ };
722
+ case "m": return readMidi(buffer, offset);
723
+ default: throw new Error(`I don't understand the argument code ${tag}`);
724
+ }
725
+ }
726
+ /**
727
+ * Encode an OSC message or bundle to a Buffer.
728
+ *
729
+ * This low-level function converts OSC messages and bundles into binary format
730
+ * for transmission or storage. Useful for sending OSC over custom transports
731
+ * (WebSocket, TCP, HTTP), storing to files, or implementing custom OSC routers.
732
+ *
733
+ * @param {Object} message - OSC message or bundle object with oscType property
734
+ * @returns {Buffer} The encoded OSC data ready for transmission
735
+ *
736
+ * @example
737
+ * // Encode a message
738
+ * import { Message, encode } from 'node-osc';
739
+ *
740
+ * const message = new Message('/oscillator/frequency', 440);
741
+ * const buffer = encode(message);
742
+ * console.log('Encoded bytes:', buffer.length);
743
+ *
744
+ * @example
745
+ * // Encode a bundle
746
+ * import { Bundle, encode } from 'node-osc';
747
+ *
748
+ * const bundle = new Bundle(['/one', 1], ['/two', 2]);
749
+ * const buffer = encode(bundle);
750
+ *
751
+ * @example
752
+ * // Send over WebSocket
753
+ * const buffer = encode(message);
754
+ * websocket.send(buffer);
755
+ */
756
+ function encode(message) {
757
+ if (message.oscType === "bundle") return encodeBundleToBuffer(message);
758
+ else return encodeMessageToBuffer(message);
759
+ }
760
+ function encodeMessageToBuffer(message) {
761
+ const address = padString(message.address);
762
+ const addressBuffer = Buffer$1.from(address);
763
+ const encodedArgs = message.args.map(encodeArgument);
764
+ const typeTags$1 = "," + encodedArgs.map((arg) => arg.tag).join("");
765
+ const typeTagsBuffer = Buffer$1.from(padString(typeTags$1));
766
+ const argumentBuffers = encodedArgs.map((arg) => arg.data);
767
+ return Buffer$1.concat([
768
+ addressBuffer,
769
+ typeTagsBuffer,
770
+ ...argumentBuffers
771
+ ]);
772
+ }
773
+ function encodeBundleToBuffer(bundle) {
774
+ const bundleString = padString("#bundle");
775
+ const bundleStringBuffer = Buffer$1.from(bundleString);
776
+ const timetagBuffer = writeTimeTag(bundle.timetag);
777
+ const elementBuffers = bundle.elements.map((element) => {
778
+ let elementBuffer;
779
+ if (element.oscType === "bundle") elementBuffer = encodeBundleToBuffer(element);
780
+ else elementBuffer = encodeMessageToBuffer(element);
781
+ const sizeBuffer = writeInt32(elementBuffer.length);
782
+ return Buffer$1.concat([sizeBuffer, elementBuffer]);
783
+ });
784
+ return Buffer$1.concat([
785
+ bundleStringBuffer,
786
+ timetagBuffer,
787
+ ...elementBuffers
788
+ ]);
789
+ }
790
+ /**
791
+ * Decode a Buffer containing OSC data into a message or bundle object.
792
+ *
793
+ * This low-level function parses binary OSC data back into JavaScript objects.
794
+ * Useful for receiving OSC over custom transports, reading from files,
795
+ * or implementing custom OSC routers.
796
+ *
797
+ * @param {Buffer} buffer - The Buffer containing OSC data
798
+ * @returns {Object} The decoded OSC message or bundle. Messages have
799
+ * {oscType: 'message', address: string, args: Array}, bundles have
800
+ * {oscType: 'bundle', timetag: number, elements: Array}
801
+ * @throws {Error} If the buffer contains malformed OSC data
802
+ *
803
+ * @example
804
+ * // Decode received data
805
+ * import { decode } from 'node-osc';
806
+ *
807
+ * const decoded = decode(buffer);
808
+ * if (decoded.oscType === 'message') {
809
+ * console.log('Address:', decoded.address);
810
+ * console.log('Arguments:', decoded.args);
811
+ * }
812
+ *
813
+ * @example
814
+ * // Round-trip encode/decode
815
+ * import { Message, encode, decode } from 'node-osc';
816
+ *
817
+ * const original = new Message('/test', 42, 'hello');
818
+ * const buffer = encode(original);
819
+ * const decoded = decode(buffer);
820
+ * console.log(decoded.address); // '/test'
821
+ */
822
+ function decode(buffer) {
823
+ if (buffer.length >= 8 && buffer.subarray(0, 8).toString() === "#bundle\0") return decodeBundleFromBuffer(buffer);
824
+ else return decodeMessageFromBuffer(buffer);
825
+ }
826
+ function decodeMessageFromBuffer(buffer) {
827
+ let offset = 0;
828
+ const addressResult = readString(buffer, offset);
829
+ const address = addressResult.value;
830
+ offset = addressResult.offset;
831
+ const typeTagsResult = readString(buffer, offset);
832
+ const typeTags$1 = typeTagsResult.value;
833
+ offset = typeTagsResult.offset;
834
+ if (!typeTags$1.startsWith(",")) throw new Error("Malformed Packet");
835
+ const tags = typeTags$1.slice(1);
836
+ const args = [];
837
+ for (const tag of tags) {
838
+ const argResult = decodeArgument(tag, buffer, offset);
839
+ args.push({ value: argResult.value });
840
+ offset = argResult.offset;
841
+ }
842
+ return {
843
+ oscType: "message",
844
+ address,
845
+ args
846
+ };
847
+ }
848
+ function decodeBundleFromBuffer(buffer) {
849
+ let offset = 8;
850
+ const timetagResult = readTimeTag(buffer, offset);
851
+ const timetag = timetagResult.value;
852
+ offset = timetagResult.offset;
853
+ const elements = [];
854
+ while (offset < buffer.length) {
855
+ const sizeResult = readInt32(buffer, offset);
856
+ const size = sizeResult.value;
857
+ offset = sizeResult.offset;
858
+ if (size <= 0 || offset + size > buffer.length) throw new Error("Malformed Packet");
859
+ const element = decode(buffer.subarray(offset, offset + size));
860
+ elements.push(element);
861
+ offset += size;
862
+ }
863
+ return {
864
+ oscType: "bundle",
865
+ timetag,
866
+ elements
867
+ };
868
+ }
869
+
870
+ //#endregion
871
+ //#region src/infrastructure/mappers/OscCodec.ts
872
+ /**
873
+ * Handles encoding and decoding of X32 OSC messages.
874
+ */
875
+ var OscCodec = class {
876
+ constructor(schemaRegistry) {
877
+ this.schemaRegistry = schemaRegistry;
878
+ }
879
+ /**
880
+ * Decodes a binary OSC message into a packet object.
881
+ * @param msg - Raw UDP buffer.
882
+ * @returns Decoded OscPacket.
883
+ */
884
+ decode(msg) {
885
+ return decode(msg);
886
+ }
887
+ /**
888
+ * Encodes an address and arguments into a binary OSC message.
889
+ * @param address - OSC address pattern.
890
+ * @param args - Array of arguments.
891
+ * @returns Binary buffer.
892
+ */
893
+ encode(address, args) {
894
+ return encode(this.createMessage(address, args));
895
+ }
896
+ /**
897
+ * Creates a typed node-osc Message based on X32 schema.
898
+ * @param address - Target address.
899
+ * @param args - Untyped arguments.
900
+ * @returns Typed Message.
901
+ */
902
+ createMessage(address, args) {
903
+ return new Message_default(address, ...args.map((arg) => {
904
+ if (typeof arg === "object" && arg !== null && "type" in arg && "value" in arg) return arg;
905
+ if (Buffer.isBuffer(arg)) return arg;
906
+ const node = this.schemaRegistry.getNode(address);
907
+ if (node) {
908
+ if (node.type === "f" && typeof arg === "number") return {
909
+ type: "f",
910
+ value: arg
911
+ };
912
+ if (node.type === "i" && typeof arg === "number") return {
913
+ type: "i",
914
+ value: Math.round(arg)
915
+ };
916
+ if (node.type === "s" && typeof arg === "string") return {
917
+ type: "s",
918
+ value: arg
919
+ };
920
+ }
921
+ return arg;
922
+ }));
923
+ }
924
+ };
925
+
926
+ //#endregion
927
+ //#region src/presentation/cli/server.ts
928
+ const loadEnv = () => {
929
+ try {
930
+ const envPath = path.resolve(process.cwd(), ".env");
931
+ if (fs.existsSync(envPath)) fs.readFileSync(envPath, "utf8").split("\n").forEach((line) => {
932
+ const match = line.match(/^([^=]+)=(.*)$/);
933
+ if (match) {
934
+ const key = match[1].trim();
935
+ const value = match[2].trim();
936
+ if (!process.env[key]) process.env[key] = value;
937
+ }
938
+ });
939
+ } catch (err) {
940
+ const message = err instanceof Error ? err.message : String(err);
941
+ console.warn("Failed to load .env file", message);
942
+ }
943
+ };
944
+ const parseArgs = (argv) => {
945
+ const args = argv.slice(2);
946
+ let cliPort;
947
+ let cliHost;
948
+ for (let i = 0; i < args.length; i++) {
949
+ const arg = args[i];
950
+ if (arg === "--port" || arg === "-p") {
951
+ const next = args[i + 1];
952
+ if (next && /^\d+$/.test(next)) {
953
+ cliPort = parseInt(next, 10);
954
+ i++;
955
+ continue;
956
+ }
957
+ }
958
+ if (arg === "--ip" || arg === "-h" || arg === "--host") {
959
+ const next = args[i + 1];
960
+ if (next) {
961
+ cliHost = next;
962
+ i++;
963
+ continue;
964
+ }
965
+ }
966
+ if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
967
+ cliHost = arg;
968
+ continue;
969
+ }
970
+ if (!cliPort && /^\d+$/.test(arg)) {
971
+ cliPort = parseInt(arg, 10);
972
+ continue;
973
+ }
974
+ }
975
+ return {
976
+ PORT: cliPort || parseInt(process.env.X32_PORT || "10023", 10),
977
+ HOST: cliHost || process.env.X32_IP || "0.0.0.0"
978
+ };
979
+ };
980
+ const bootstrap = async () => {
981
+ loadEnv();
982
+ const { PORT, HOST } = parseArgs(process.argv);
983
+ const logger = ConsoleLogger.getInstance();
984
+ logger.setLevel(LogLevel.DEBUG);
985
+ const schemaRegistry = new SchemaRegistry(new SchemaFactory());
986
+ const service = new SimulationService(new UdpNetworkGateway(logger, new OscCodec(schemaRegistry)), logger, new InMemoryStateRepository(logger, schemaRegistry), schemaRegistry, PORT, HOST);
987
+ try {
988
+ await service.start();
989
+ console.log(`
990
+ ╔════════════════════════════════════════╗
991
+ ║ 🚀 X32 SIMULATION SERVER ACTIVE ║
992
+ ╚════════════════════════════════════════╝
993
+ • IP: ${HOST}
994
+ • Port: ${PORT}
995
+ • Protocol: UDP (Strict X32 Schema)
996
+ • Logging: ENHANCED (DEBUG)
997
+
998
+ 👉 Type "exit" or "stop" to shut down.
999
+ 👉 Type "reset" to reset faders to default.
1000
+ `);
1001
+ } catch (err) {
1002
+ if (err.code === "EADDRNOTAVAIL") {
1003
+ console.error(`\n\x1b[31m❌ FAILED TO BIND TO IP: ${HOST}\x1b[0m`);
1004
+ console.error(`\nThe IP address you requested does not exist on this machine.`);
1005
+ console.error(`You likely need to create a network alias (Loopback) for it.\n`);
1006
+ const isMac = process.platform === "darwin";
1007
+ const isLinux = process.platform === "linux";
1008
+ const isWin = process.platform === "win32";
1009
+ if (isMac || isLinux || isWin) {
1010
+ console.error(`To fix this, try running the following command:\n`);
1011
+ if (isMac) console.error(` \x1b[36msudo ifconfig lo0 alias ${HOST}\x1b[0m`);
1012
+ else if (isLinux) console.error(` \x1b[36msudo ip addr add ${HOST}/32 dev lo\x1b[0m`);
1013
+ else if (isWin) {
1014
+ console.error(` \x1b[36mnetsh interface ip add address "Loopback" ${HOST} 255.255.255.255\x1b[0m`);
1015
+ console.error(` (Note: Requires 'Microsoft Loopback Adapter' installed and named "Loopback")`);
1016
+ }
1017
+ }
1018
+ console.error(`\nAlternatively, remove the IP argument to listen on all interfaces (0.0.0.0).`);
1019
+ process.exit(1);
1020
+ }
1021
+ console.error("Failed to start server:", err);
1022
+ process.exit(1);
1023
+ }
1024
+ readline.createInterface({
1025
+ input: process.stdin,
1026
+ output: process.stdout
1027
+ }).on("line", async (input) => {
1028
+ const command = input.trim().toLowerCase();
1029
+ switch (command) {
1030
+ case "exit":
1031
+ case "stop":
1032
+ case "quit":
1033
+ console.log("Shutting down...");
1034
+ await service.stop();
1035
+ process.exit(0);
1036
+ break;
1037
+ case "reset":
1038
+ service.resetState();
1039
+ console.log("✨ Console state reset to defaults.");
1040
+ break;
1041
+ default:
1042
+ if (command) console.log(`Unknown command: "${command}"`);
1043
+ break;
1044
+ }
1045
+ });
1046
+ process.on("SIGINT", async () => {
1047
+ console.log("\n(SIGINT received)");
1048
+ await service.stop();
1049
+ process.exit(0);
1050
+ });
1051
+ };
1052
+ if (process.argv[1] === fileURLToPath(import.meta.url)) bootstrap();
1053
+
1054
+ //#endregion
1055
+ export { bootstrap, loadEnv, parseArgs };