@djodjonx/x32-simulator 0.0.2 → 0.0.4

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 (83) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +28 -0
  3. package/dist/{UdpNetworkGateway-BrroQ6-Q.mjs → SchemaRegistry-BRVgnyaA.mjs} +990 -2
  4. package/dist/{UdpNetworkGateway-Ccdd7Us5.cjs → SchemaRegistry-CfDtw84j.cjs} +1033 -3
  5. package/dist/index.cjs +160 -6
  6. package/dist/index.d.cts +61 -11
  7. package/dist/index.d.mts +61 -11
  8. package/dist/index.mjs +146 -2
  9. package/dist/server.cjs +8 -927
  10. package/dist/server.mjs +1 -920
  11. package/package.json +5 -1
  12. package/.commitlintrc.json +0 -3
  13. package/.github/workflows/publish.yml +0 -38
  14. package/.husky/commit-msg +0 -1
  15. package/.husky/pre-commit +0 -1
  16. package/.oxlintrc.json +0 -56
  17. package/INSTALL.md +0 -107
  18. package/docs/OSC-Communication.md +0 -184
  19. package/docs/X32-INTERNAL.md +0 -262
  20. package/docs/X32-OSC.pdf +0 -0
  21. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  22. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
  23. package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
  24. package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
  25. package/src/application/use-cases/SimulationService.ts +0 -122
  26. package/src/domain/entities/SubscriptionManager.ts +0 -126
  27. package/src/domain/entities/X32State.ts +0 -78
  28. package/src/domain/models/MeterConfig.ts +0 -22
  29. package/src/domain/models/MeterData.ts +0 -59
  30. package/src/domain/models/OscMessage.ts +0 -93
  31. package/src/domain/models/X32Address.ts +0 -78
  32. package/src/domain/models/X32Node.ts +0 -43
  33. package/src/domain/models/types.ts +0 -96
  34. package/src/domain/ports/ILogger.ts +0 -27
  35. package/src/domain/ports/INetworkGateway.ts +0 -8
  36. package/src/domain/ports/IStateRepository.ts +0 -16
  37. package/src/domain/services/MeterService.ts +0 -46
  38. package/src/domain/services/OscMessageHandler.ts +0 -88
  39. package/src/domain/services/SchemaFactory.ts +0 -308
  40. package/src/domain/services/SchemaRegistry.ts +0 -67
  41. package/src/domain/services/StaticResponseService.ts +0 -52
  42. package/src/domain/services/strategies/BatchStrategy.ts +0 -74
  43. package/src/domain/services/strategies/MeterStrategy.ts +0 -45
  44. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
  45. package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
  46. package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
  47. package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
  48. package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
  49. package/src/infrastructure/mappers/OscCodec.ts +0 -54
  50. package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -21
  51. package/src/infrastructure/services/ConsoleLogger.ts +0 -177
  52. package/src/infrastructure/services/UdpNetworkGateway.ts +0 -71
  53. package/src/presentation/cli/server.ts +0 -194
  54. package/src/presentation/library/library.ts +0 -9
  55. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
  56. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
  57. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
  58. package/tests/application/use-cases/SimulationService.test.ts +0 -77
  59. package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
  60. package/tests/domain/entities/X32State.test.ts +0 -52
  61. package/tests/domain/models/MeterData.test.ts +0 -23
  62. package/tests/domain/models/OscMessage.test.ts +0 -38
  63. package/tests/domain/models/X32Address.test.ts +0 -30
  64. package/tests/domain/models/X32Node.test.ts +0 -30
  65. package/tests/domain/services/MeterService.test.ts +0 -27
  66. package/tests/domain/services/OscMessageHandler.test.ts +0 -51
  67. package/tests/domain/services/SchemaRegistry.test.ts +0 -47
  68. package/tests/domain/services/StaticResponseService.test.ts +0 -15
  69. package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
  70. package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
  71. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
  72. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
  73. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
  74. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
  75. package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
  76. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
  77. package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
  78. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
  79. package/tests/presentation/cli/server.test.ts +0 -178
  80. package/tests/presentation/library/library.test.ts +0 -13
  81. package/tsconfig.json +0 -21
  82. package/tsdown.config.ts +0 -15
  83. package/vitest.config.ts +0 -9
@@ -1,308 +0,0 @@
1
- import { X32Node } from '../models/X32Node';
2
- import { STATIC_RESPONSES_DATA } from './StaticResponseService';
3
-
4
- /**
5
- * Factory service responsible for constructing the X32 OSC Schema.
6
- * Encapsulates all the logic for generating channel strips, routing blocks, etc.
7
- */
8
- export class SchemaFactory {
9
-
10
- /**
11
- * Builds the complete X32 OSC Schema.
12
- * @returns The constructed schema map.
13
- */
14
- public createSchema(): Record<string, X32Node> {
15
- const schema: Record<string, X32Node> = {
16
- ...this.generateNodes(32, 'ch'), ...this.generateNodes(16, 'bus'),
17
- ...this.generateNodes(8, 'dca'), ...this.generateNodes(8, 'auxin'),
18
- ...this.generateNodes(8, 'fxrtn'), ...this.generateNodes(6, 'mtx'),
19
- ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/fx/${i + 1}/type`, this.node('i', 0)])),
20
- ...this.generateRange(8, '/fx', '/type', 'i', 0),
21
- ...this.generateRange(8, '/fx', '/source/l', 'i', 0),
22
- ...this.generateRange(8, '/fx', '/source/r', 'i', 0),
23
- ...Object.fromEntries(
24
- Array.from({ length: 8 }, (_, slot) =>
25
- Object.entries(this.generateRange(64, `/fx/${slot + 1}/par`, '', 'f', 0.0, 2, 1))
26
- ).flat()
27
- ),
28
- ...this.generateRange(128, '/headamp', '/gain', 'f', 0.0, 3, 0),
29
- ...this.generateRange(128, '/headamp', '/phantom', 'i', 0, 3, 0),
30
- ...this.generateRange(6, '/config/mute', '', 'i', 0),
31
- ...this.generateRange(80, '/-stat/solosw', '', 'i', 0),
32
-
33
- // Global Status
34
- '/-stat/selidx': this.node('i', 0),
35
- '/-stat/sendsonfader': this.node('i', 0),
36
- '/-stat/bussendbank': this.node('i', 0),
37
- '/-stat/keysolo': this.node('i', 0),
38
-
39
- // Screens
40
- '/-stat/screen/screen': this.node('i', 0),
41
- '/-stat/screen/CHAN/page': this.node('i', 0),
42
- '/-stat/screen/METER/page': this.node('i', 0),
43
- '/-stat/screen/ROUTE/page': this.node('i', 0),
44
- '/-stat/screen/SETUP/page': this.node('i', 0),
45
- '/-stat/screen/LIBRARY/page': this.node('i', 0),
46
- '/-stat/screen/FX/page': this.node('i', 0),
47
- '/-stat/screen/MON/page': this.node('i', 0),
48
- '/-stat/screen/USB/page': this.node('i', 0),
49
- '/-stat/screen/SCENE/page': this.node('i', 0),
50
- '/-stat/screen/ASSIGN/page': this.node('i', 0),
51
-
52
- // Telemetry
53
- '/-stat/talk/A': this.node('i', 0),
54
- '/-stat/talk/B': this.node('i', 0),
55
- '/-stat/osc/on': this.node('i', 0),
56
- '/-prefs/autosel': this.node('i', 1),
57
-
58
- // Actions
59
- '/-action/setrtasrc': this.node('i', 0),
60
- '/-action/playtrack': this.node('i', 0),
61
- '/-action/goscene': this.node('i', 0),
62
- '/-action/setscene': this.node('i', 0),
63
- '/config/routing/AES50A/1-8': this.node('i', 0),
64
- '/config/routing/AES50B/1-8': this.node('i', 0),
65
- '/config/routing/CARD/1-8': this.node('i', 0),
66
- '/config/routing/OUT/1-4': this.node('i', 0),
67
- '/-prefs/invertmutes': this.node('i', 0),
68
-
69
- ...this.generateChannelStrip('/main/st', 6, false),
70
- ...this.generateChannelStrip('/main/m', 6, false),
71
-
72
- // Links
73
- ...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node('i', 0)])),
74
- ...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node('i', 0)])),
75
- ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node('i', 0)])),
76
- ...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/fxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node('i', 0)])),
77
-
78
- ...this.generateRange(32, '/config/userrout/in', '', 'i', 0),
79
- ...this.generateRange(48, '/config/userrout/out', '', 'i', 0),
80
-
81
- // Solo Config
82
- '/config/solo/level': this.node('f', 0.0),
83
- '/config/solo/source': this.node('i', 0),
84
- '/config/solo/sourcetrim': this.node('f', 0.0),
85
- '/config/solo/exclusive': this.node('i', 0),
86
- '/config/solo/dim': this.node('i', 0),
87
- '/config/solo/dimpfl': this.node('i', 0),
88
- '/config/solo/dimatt': this.node('f', 0.0),
89
- '/config/solo/mono': this.node('i', 0),
90
- '/config/solo/chmode': this.node('i', 0),
91
- '/config/solo/busmode': this.node('i', 0),
92
- '/config/solo/dcamode': this.node('i', 0),
93
- '/config/solo/masterctrl': this.node('i', 0),
94
- '/config/solo/delay': this.node('i', 0),
95
- '/config/solo/delaytime': this.node('f', 0.0),
96
- '/config/solo/followsel': this.node('i', 0),
97
- '/config/solo/followsolo': this.node('i', 0),
98
-
99
- // Talkback Config
100
- '/config/talk/enable': this.node('i', 0),
101
- '/config/talk/source': this.node('i', 0),
102
- '/config/talk/A/level': this.node('f', 0.0),
103
- '/config/talk/B/level': this.node('f', 0.0),
104
- '/config/talk/A/dim': this.node('i', 0),
105
- '/config/talk/B/dim': this.node('i', 0),
106
- '/config/talk/A/latch': this.node('i', 0),
107
- '/config/talk/B/latch': this.node('i', 0),
108
- '/config/talk/A/destmap': this.node('i', 0),
109
- '/config/talk/B/destmap': this.node('i', 0),
110
-
111
- // Oscillator
112
- '/config/osc/on': this.node('i', 0),
113
- '/config/osc/type': this.node('i', 0),
114
- '/config/osc/fsel': this.node('i', 0),
115
- '/config/osc/f1': this.node('f', 0.5),
116
- '/config/osc/f2': this.node('f', 0.5),
117
- '/config/osc/level': this.node('f', 0.0),
118
- '/config/osc/dest': this.node('i', 0),
119
-
120
- // Outputs
121
- ...this.generateOutputs('main', 16),
122
- ...this.generateOutputs('aux', 6),
123
- ...this.generateOutputs('p16', 16),
124
- ...this.generateOutputs('aes', 2),
125
- ...this.generateOutputs('rec', 2)
126
- };
127
-
128
- // Static Responses Integration
129
- Object.keys(STATIC_RESPONSES_DATA).forEach(key => {
130
- if (key.startsWith('/-') || key.startsWith('/stat')) {
131
- schema[key] = this.node('i', 0);
132
- }
133
- });
134
-
135
- // Routing Blocks
136
- const routingBlocks = [
137
- '/config/routing/IN/1-8', '/config/routing/IN/9-16', '/config/routing/IN/17-24', '/config/routing/IN/25-32',
138
- '/config/routing/AUX/1-4',
139
- '/config/routing/OUT/1-4', '/config/routing/OUT/5-8', '/config/routing/OUT/9-12', '/config/routing/OUT/13-16',
140
- '/config/routing/P16/1-8', '/config/routing/P16/9-16',
141
- '/config/routing/CARD/1-8', '/config/routing/CARD/9-16', '/config/routing/CARD/17-24', '/config/routing/CARD/25-32',
142
- '/config/routing/AES50A/1-8', '/config/routing/AES50A/9-16', '/config/routing/AES50A/17-24', '/config/routing/AES50A/25-32', '/config/routing/AES50A/33-40', '/config/routing/AES50A/41-48',
143
- '/config/routing/AES50B/1-8', '/config/routing/AES50B/9-16', '/config/routing/AES50B/17-24', '/config/routing/AES50B/25-32', '/config/routing/AES50B/33-40', '/config/routing/AES50B/41-48',
144
- '/config/routing/PLAY/1-8', '/config/routing/PLAY/9-16', '/config/routing/PLAY/17-24', '/config/routing/PLAY/25-32'
145
- ];
146
-
147
- routingBlocks.forEach(path => {
148
- schema[path] = this.node('i', 0);
149
- });
150
-
151
- return schema;
152
- }
153
-
154
- private node(type: 'f' | 'i' | 's', def: number | string): X32Node {
155
- return new X32Node(type, def);
156
- }
157
-
158
- private generateChannelStrip(base: string, eqBands: number = 4, hasPreamp: boolean = false): Record<string, X32Node> {
159
- const nodes: Record<string, X32Node> = {};
160
-
161
- // Config
162
- nodes[`${base}/config/name`] = this.node('s', base.split('/').pop()?.toUpperCase() || '');
163
- nodes[`${base}/config/icon`] = this.node('i', 1);
164
- nodes[`${base}/config/color`] = this.node('i', 1);
165
- nodes[`${base}/config/source`] = this.node('i', 1);
166
-
167
- // Mix
168
- nodes[`${base}/mix/fader`] = this.node('f', 0.75);
169
- nodes[`${base}/mix/on`] = this.node('i', 0);
170
- nodes[`${base}/mix/pan`] = this.node('f', 0.5);
171
- nodes[`${base}/mix/mono`] = this.node('i', 0);
172
- nodes[`${base}/mix/mlevel`] = this.node('f', 0.0);
173
- nodes[`${base}/mix/st`] = this.node('i', 1);
174
-
175
- if (hasPreamp) {
176
- nodes[`${base}/preamp/trim`] = this.node('f', 0.5);
177
- nodes[`${base}/preamp/hpon`] = this.node('i', 0);
178
- nodes[`${base}/preamp/hpf`] = this.node('f', 0.0);
179
- nodes[`${base}/preamp/phantom`] = this.node('i', 0);
180
- nodes[`${base}/preamp/rtnsw`] = this.node('i', 0);
181
- nodes[`${base}/preamp/invert`] = this.node('i', 0);
182
- }
183
-
184
- // Delay
185
- nodes[`${base}/delay/on`] = this.node('i', 0);
186
- nodes[`${base}/delay/time`] = this.node('f', 0.0);
187
-
188
- // Insert
189
- nodes[`${base}/insert/on`] = this.node('i', 0);
190
- nodes[`${base}/insert/pos`] = this.node('i', 0);
191
- nodes[`${base}/insert/sel`] = this.node('i', 0);
192
-
193
- // Gate
194
- nodes[`${base}/gate/on`] = this.node('i', 0);
195
- nodes[`${base}/gate/mode`] = this.node('i', 0);
196
- nodes[`${base}/gate/thr`] = this.node('f', 0.0);
197
- nodes[`${base}/gate/range`] = this.node('f', 0.0);
198
- nodes[`${base}/gate/attack`] = this.node('f', 0.0);
199
- nodes[`${base}/gate/hold`] = this.node('f', 0.0);
200
- nodes[`${base}/gate/release`] = this.node('f', 0.0);
201
- nodes[`${base}/gate/keysrc`] = this.node('i', 0);
202
- nodes[`${base}/gate/filter/on`] = this.node('i', 0);
203
- nodes[`${base}/gate/filter/type`] = this.node('i', 0);
204
- nodes[`${base}/gate/filter/f`] = this.node('f', 0.5);
205
-
206
- // Dynamics
207
- nodes[`${base}/dyn/on`] = this.node('i', 0);
208
- nodes[`${base}/dyn/mode`] = this.node('i', 0);
209
- nodes[`${base}/dyn/pos`] = this.node('i', 0);
210
- nodes[`${base}/dyn/det`] = this.node('i', 0);
211
- nodes[`${base}/dyn/env`] = this.node('i', 0);
212
- nodes[`${base}/dyn/thr`] = this.node('f', 0.0);
213
- nodes[`${base}/dyn/ratio`] = this.node('i', 0);
214
- nodes[`${base}/dyn/knee`] = this.node('f', 0.0);
215
- nodes[`${base}/dyn/mgain`] = this.node('f', 0.0);
216
- nodes[`${base}/dyn/attack`] = this.node('f', 0.0);
217
- nodes[`${base}/dyn/hold`] = this.node('f', 0.0);
218
- nodes[`${base}/dyn/release`] = this.node('f', 0.0);
219
- nodes[`${base}/dyn/mix`] = this.node('f', 1.0);
220
- nodes[`${base}/dyn/auto`] = this.node('i', 0);
221
- nodes[`${base}/dyn/keysrc`] = this.node('i', 0);
222
- nodes[`${base}/dyn/filter/on`] = this.node('i', 0);
223
- nodes[`${base}/dyn/filter/type`] = this.node('i', 0);
224
- nodes[`${base}/dyn/filter/f`] = this.node('f', 0.5);
225
-
226
- // EQ
227
- nodes[`${base}/eq/on`] = this.node('i', 0);
228
- nodes[`${base}/eq/mode`] = this.node('i', 0);
229
- for (let b = 1; b <= eqBands; b++) {
230
- nodes[`${base}/eq/${b}/type`] = this.node('i', 0);
231
- nodes[`${base}/eq/${b}/f`] = this.node('f', 0.5);
232
- nodes[`${base}/eq/${b}/g`] = this.node('f', 0.5);
233
- nodes[`${base}/eq/${b}/q`] = this.node('f', 0.5);
234
- }
235
-
236
- // Groups
237
- nodes[`${base}/grp/dca`] = this.node('i', 0);
238
- nodes[`${base}/grp/mute`] = this.node('i', 0);
239
-
240
- return nodes;
241
- }
242
-
243
- private generateNodes(count: number, prefix: string): Record<string, X32Node> {
244
- const nodes: Record<string, X32Node> = {};
245
- const isChannelOrAux = prefix === 'ch' || prefix === 'auxin';
246
- const eqBands = (prefix === 'bus' || prefix === 'mtx') ? 6 : 4;
247
-
248
- for (let i = 1; i <= count; i++) {
249
- const padId = i.toString().padStart(2, '0');
250
- const ids = [i.toString(), padId];
251
-
252
- ids.forEach(id => {
253
- const base = `/${prefix}/${id}`;
254
-
255
- if (prefix === 'dca') {
256
- nodes[`${base}/config/name`] = this.node('s', `DCA ${id}`);
257
- nodes[`${base}/config/color`] = this.node('i', 1);
258
- nodes[`${base}/fader`] = this.node('f', 0.75);
259
- nodes[`${base}/on`] = this.node('i', 0);
260
- } else {
261
- Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
262
-
263
- if (prefix === 'ch' || prefix === 'auxin' || prefix === 'fxrtn' || prefix === 'bus') {
264
- for (let b = 1; b <= 16; b++) {
265
- const busId = b.toString().padStart(2, '0');
266
- nodes[`${base}/mix/${busId}/level`] = this.node('f', 0.0);
267
- nodes[`${base}/mix/${busId}/on`] = this.node('i', 0);
268
- nodes[`${base}/mix/${busId}/pan`] = this.node('f', 0.5);
269
- nodes[`${base}/mix/${busId}/type`] = this.node('i', 0);
270
- }
271
- }
272
- }
273
- });
274
- }
275
- return nodes;
276
- }
277
-
278
- private generateRange(count: number, prefix: string, suffix: string, type: 'f'|'i'|'s', def: number|string, pad: number = 2, start: number = 1): Record<string, X32Node> {
279
- const nodes: Record<string, X32Node> = {};
280
- for (let i = start; i < start + count; i++) {
281
- const id = i.toString().padStart(pad, '0');
282
- nodes[`${prefix}/${i}${suffix}`] = this.node(type, def);
283
- nodes[`${prefix}/${id}${suffix}`] = this.node(type, def);
284
- }
285
- return nodes;
286
- }
287
-
288
- private generateOutputs(prefix: string, count: number): Record<string, X32Node> {
289
- const nodes: Record<string, X32Node> = {};
290
- for (let i = 1; i <= count; i++) {
291
- const id = i.toString().padStart(2, '0');
292
- const base = `/outputs/${prefix}/${id}`;
293
- nodes[`${base}/src`] = this.node('i', 0);
294
- nodes[`${base}/pos`] = this.node('i', 0);
295
- nodes[`${base}/invert`] = this.node('i', 0);
296
- nodes[`${base}/delay/on`] = this.node('i', 0);
297
- nodes[`${base}/delay/time`] = this.node('f', 0.0);
298
-
299
- if (prefix === 'p16') {
300
- nodes[`${base}/iQ/group`] = this.node('i', 0);
301
- nodes[`${base}/iQ/model`] = this.node('i', 0);
302
- nodes[`${base}/iQ/eq`] = this.node('i', 0);
303
- nodes[`${base}/iQ/speaker`] = this.node('i', 0);
304
- }
305
- }
306
- return nodes;
307
- }
308
- }
@@ -1,67 +0,0 @@
1
- import { X32Node } from '../models/X32Node';
2
- import { SchemaFactory } from './SchemaFactory';
3
-
4
- /**
5
- * Service providing access to the X32 OSC Schema.
6
- * Acts as the "Registry" of all available console parameters.
7
- */
8
- export class SchemaRegistry {
9
- private readonly _schema: Record<string, X32Node>;
10
-
11
- constructor(private schemaFactory: SchemaFactory) {
12
- this._schema = this.schemaFactory.createSchema();
13
- }
14
-
15
-
16
- /**
17
- * Retrieves the entire schema map.
18
- * @returns The internal schema definition record.
19
- */
20
- public getSchema(): Record<string, X32Node> {
21
- return this._schema;
22
- }
23
-
24
- /**
25
- * Retrieves the node definition for a given path.
26
- * @param path - The OSC path.
27
- * @returns The X32Node definition or undefined if not found.
28
- */
29
- public getNode(path: string): X32Node | undefined {
30
- return this._schema[path];
31
- }
32
-
33
- /**
34
- * Checks if a path exists in the schema.
35
- * @param path - The path to check.
36
- * @returns True if the path is registered.
37
- */
38
- public has(path: string): boolean {
39
- return path in this._schema;
40
- }
41
-
42
- /**
43
- * Returns all paths in the schema.
44
- * @returns Array of all registered OSC paths.
45
- */
46
- public getAllPaths(): string[] {
47
- return Object.keys(this._schema);
48
- }
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
- public getRootFromIndex(index: number): string | null {
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
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Static OSC responses for X32 Discovery and System status.
3
- */
4
- export const STATIC_RESPONSES_DATA: Record<string, (number | string)[]> = {
5
- "/xinfo": ["{{ip}}", "{{name}}", "{{model}}", "4.06"],
6
- "/info": ["V2.05", "{{name}}", "{{model}}", "4.06"],
7
- "/status": ["active", "{{ip}}", "{{name}}"],
8
- "/-prefs/invertmutes": [0],
9
- "/-prefs/fine": [0],
10
- "/-prefs/bright": [50],
11
- "/-prefs/contrast": [50],
12
- "/-prefs/led_bright": [50],
13
- "/-prefs/lcd_bright": [50],
14
- "/-stat/userpar/1/value": [0],
15
- "/-stat/userpar/2/value": [0],
16
- "/-stat/tape/state": [0],
17
- "/-stat/aes50/A": [0],
18
- "/-stat/aes50/B": [0],
19
- "/-stat/solo": [0],
20
- "/-stat/rtasource": [0],
21
- "/-urec/errorcode": [0],
22
- "/-prefs/rta/gain": [0.0],
23
- "/-prefs/rta/autogain": [0],
24
- "/-prefs/hardmute": [0],
25
- "/-prefs/dcamute": [0],
26
- "/config/mono/mode": [0],
27
- "/config/amixenable/X": [0],
28
- "/config/amixenable/Y": [0],
29
- "/-show/prepos/current": [0],
30
- "/-show/showfile/current": [""],
31
- "/-action/setrtasrc": [0],
32
- "/-action/playtrack": [0],
33
- "/-action/goscene": [0],
34
- "/-action/setscene": [0]
35
- };
36
-
37
- /**
38
- * Service providing static system responses.
39
- * Mimics the fixed firmware responses of the X32.
40
- */
41
- export class StaticResponseService {
42
- private readonly responses = STATIC_RESPONSES_DATA;
43
-
44
- /**
45
- * Retrieves a static response for a given path.
46
- * @param path - The OSC path.
47
- * @returns The response arguments or undefined.
48
- */
49
- public getResponse(path: string): (number | string)[] | undefined {
50
- return this.responses[path];
51
- }
52
- }
@@ -1,74 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, RemoteClient, OscReply } from '../../models/types';
3
- import { SubscriptionManager } from '../../entities/SubscriptionManager';
4
- import { ILogger, LogCategory } from '../../ports/ILogger';
5
-
6
- /**
7
- * Handles bulk data subscription requests from advanced clients like X32-Edit or Mixing Station.
8
- *
9
- * SUPPORTED COMMANDS:
10
- * - /formatsubscribe: Requests a range of numbered nodes (e.g. /ch/01-32/mix/fader).
11
- * - /batchsubscribe: Aggregates multiple parameters into a single binary blob response.
12
- */
13
- export class BatchStrategy implements OscCommandStrategy {
14
- /**
15
- * Initializes the strategy.
16
- * @param subscriptionManager - Session manager.
17
- * @param logger - Logger instance.
18
- */
19
- constructor(
20
- private subscriptionManager: SubscriptionManager,
21
- private logger: ILogger
22
- ) {}
23
-
24
- /** @inheritdoc */
25
- public canHandle(address: string): boolean {
26
- return ['/batchsubscribe', '/formatsubscribe'].includes(address);
27
- }
28
-
29
- /** @inheritdoc */
30
- public execute(msg: OscMsg, source: RemoteClient): OscReply[] {
31
- const addr = msg.address;
32
-
33
- if (addr === '/formatsubscribe') {
34
- const alias = msg.args[0] as string;
35
- const pattern = msg.args[1] as string;
36
- const start = msg.args[2] as number;
37
- const end = msg.args[3] as number;
38
- const factor = msg.args[4] as number;
39
- const count = end - start + 1;
40
-
41
- this.subscriptionManager.addFormatSubscriber(source, alias, pattern, start, count, factor);
42
- this.logger.debug(LogCategory.SUB, `[FORMAT] ${alias} factor: ${factor}`);
43
- return [];
44
- }
45
-
46
- if (addr === '/batchsubscribe') {
47
- const alias = msg.args[0] as string;
48
-
49
- const firstIntIndex = msg.args.findIndex(a => typeof a === 'number');
50
- if (firstIntIndex === -1) return [];
51
-
52
- const paths = msg.args.slice(1, firstIntIndex) as string[];
53
-
54
- // Collect all integer args
55
- const intArgs = msg.args.slice(firstIntIndex) as number[];
56
-
57
- // The last int is the factor (frequency)
58
- const factor = intArgs.pop();
59
-
60
- // Remaining ints are arguments for the meter command (e.g. 3 0 for /meters/5)
61
- const cmdArgs = intArgs;
62
-
63
- // Batch subscribe is for ONE periodic update containing the blob
64
- const count = 1;
65
- const start = 0; // Not used for this type
66
-
67
- this.subscriptionManager.addBatchSubscriber(source, alias, paths, start, count, factor, cmdArgs);
68
- this.logger.debug(LogCategory.SUB, `[BATCH] ${alias} paths: ${JSON.stringify(paths)} args: ${JSON.stringify(cmdArgs)} factor: ${factor}`);
69
- return [];
70
- }
71
-
72
- return [];
73
- }
74
- }
@@ -1,45 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, RemoteClient, OscReply } from '../../models/types';
3
- import { SubscriptionManager } from '../../entities/SubscriptionManager';
4
- import { MeterService } from '../MeterService';
5
- import { X32State } from '../../entities/X32State';
6
-
7
- /**
8
- * Handles high-frequency metering data requests.
9
- * Meters are sent as binary blobs to optimize network bandwidth.
10
- * Supports both direct address requests (/meters/1) and argument-based requests (/meters ,s "/meters/1").
11
- */
12
- export class MeterStrategy implements OscCommandStrategy {
13
- /**
14
- * Initializes the strategy.
15
- * @param subscriptionManager - Session manager.
16
- * @param state - Current mixer state.
17
- * @param meterService - Service to generate meter data.
18
- */
19
- constructor(
20
- private subscriptionManager: SubscriptionManager,
21
- private state: X32State,
22
- private meterService: MeterService
23
- ) {}
24
-
25
- /** @inheritdoc */
26
- public canHandle(address: string): boolean {
27
- return address.startsWith('/meters');
28
- }
29
-
30
- /** @inheritdoc */
31
- public execute(msg: OscMsg, source: RemoteClient): OscReply[] {
32
- let meterPath = msg.address;
33
-
34
- // X32-Edit often sends: /meters ,s "/meters/1"
35
- if (msg.address === '/meters' && typeof msg.args[0] === 'string' && msg.args[0].startsWith('/meters')) {
36
- meterPath = msg.args[0];
37
- }
38
-
39
- this.subscriptionManager.addMeterSubscriber(source, meterPath);
40
- const meterData = this.meterService.generateMeterData(meterPath, this.state);
41
- const blob = meterData.toBlob();
42
-
43
- return [{ address: meterPath, args: [blob] }];
44
- }
45
- }
@@ -1,36 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, OscReply, RemoteClient } from '../../models/types';
3
- import { SchemaRegistry } from '../SchemaRegistry';
4
-
5
- /**
6
- * Handles the /node command for tree traversal/discovery.
7
- * Returns a list of child nodes for a given path.
8
- */
9
- export class NodeDiscoveryStrategy implements OscCommandStrategy {
10
- constructor(private schemaRegistry: SchemaRegistry) {}
11
-
12
- canHandle(address: string): boolean {
13
- return address === '/node';
14
- }
15
-
16
- execute(msg: OscMsg, _source: RemoteClient): OscReply[] {
17
- const queryPath = msg.args[0] as string;
18
- if (!queryPath) return [];
19
-
20
- const children = new Set<string>();
21
- const prefix = queryPath.endsWith('/') ? queryPath : `${queryPath}/`;
22
-
23
- for (const key of this.schemaRegistry.getAllPaths()) {
24
- if (key.startsWith(prefix)) {
25
- const rest = key.slice(prefix.length);
26
- const segment = rest.split('/')[0];
27
- if (segment) children.add(segment);
28
- }
29
- }
30
-
31
- return [{
32
- address: '/node',
33
- args: [queryPath, ...children]
34
- }];
35
- }
36
- }
@@ -1,22 +0,0 @@
1
- import { OscMsg, RemoteClient, OscReply } from '../../models/types';
2
-
3
- /**
4
- * Interface for all OSC command handlers.
5
- * implementing the Strategy pattern for modular message processing.
6
- */
7
- export interface OscCommandStrategy {
8
- /**
9
- * Determines if this strategy can handle the given address.
10
- * @param address - OSC address pattern.
11
- * @returns True if the strategy matches.
12
- */
13
- canHandle(address: string): boolean;
14
-
15
- /**
16
- * Processes the message and generates appropriate replies.
17
- * @param msg - The input message.
18
- * @param source - Client remote info (address and port).
19
- * @returns Array of responses to send back.
20
- */
21
- execute(msg: OscMsg, source: RemoteClient): OscReply[];
22
- }