@djodjonx/x32-simulator 0.0.3 → 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 (74) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/package.json +5 -1
  3. package/.commitlintrc.json +0 -3
  4. package/.github/workflows/publish.yml +0 -38
  5. package/.husky/commit-msg +0 -1
  6. package/.husky/pre-commit +0 -1
  7. package/.oxlintrc.json +0 -56
  8. package/INSTALL.md +0 -107
  9. package/docs/OSC-Communication.md +0 -184
  10. package/docs/X32-INTERNAL.md +0 -262
  11. package/docs/X32-OSC.pdf +0 -0
  12. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  13. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
  14. package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
  15. package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
  16. package/src/application/use-cases/SimulationService.ts +0 -146
  17. package/src/domain/entities/SubscriptionManager.ts +0 -126
  18. package/src/domain/entities/X32State.ts +0 -78
  19. package/src/domain/models/MeterConfig.ts +0 -22
  20. package/src/domain/models/MeterData.ts +0 -59
  21. package/src/domain/models/OscMessage.ts +0 -93
  22. package/src/domain/models/X32Address.ts +0 -72
  23. package/src/domain/models/X32Node.ts +0 -43
  24. package/src/domain/models/types.ts +0 -86
  25. package/src/domain/ports/ILogger.ts +0 -27
  26. package/src/domain/ports/INetworkGateway.ts +0 -8
  27. package/src/domain/ports/IStateRepository.ts +0 -16
  28. package/src/domain/services/MeterService.ts +0 -46
  29. package/src/domain/services/OscMessageHandler.ts +0 -88
  30. package/src/domain/services/SchemaFactory.ts +0 -308
  31. package/src/domain/services/SchemaRegistry.ts +0 -67
  32. package/src/domain/services/StaticResponseService.ts +0 -52
  33. package/src/domain/services/strategies/BatchStrategy.ts +0 -74
  34. package/src/domain/services/strategies/MeterStrategy.ts +0 -45
  35. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
  36. package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
  37. package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
  38. package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
  39. package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
  40. package/src/infrastructure/mappers/OscCodec.ts +0 -54
  41. package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -37
  42. package/src/infrastructure/services/ConsoleLogger.ts +0 -177
  43. package/src/infrastructure/services/UdpNetworkGateway.ts +0 -100
  44. package/src/presentation/cli/server.ts +0 -194
  45. package/src/presentation/library/library.ts +0 -139
  46. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
  47. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
  48. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
  49. package/tests/application/use-cases/SimulationService.test.ts +0 -77
  50. package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
  51. package/tests/domain/entities/X32State.test.ts +0 -52
  52. package/tests/domain/models/MeterData.test.ts +0 -23
  53. package/tests/domain/models/OscMessage.test.ts +0 -38
  54. package/tests/domain/models/X32Address.test.ts +0 -30
  55. package/tests/domain/models/X32Node.test.ts +0 -30
  56. package/tests/domain/services/MeterService.test.ts +0 -27
  57. package/tests/domain/services/OscMessageHandler.test.ts +0 -51
  58. package/tests/domain/services/SchemaRegistry.test.ts +0 -47
  59. package/tests/domain/services/StaticResponseService.test.ts +0 -15
  60. package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
  61. package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
  62. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
  63. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
  64. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
  65. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
  66. package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
  67. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
  68. package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
  69. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
  70. package/tests/presentation/cli/server.test.ts +0 -178
  71. package/tests/presentation/library/library.test.ts +0 -13
  72. package/tsconfig.json +0 -21
  73. package/tsdown.config.ts +0 -15
  74. package/vitest.config.ts +0 -9
@@ -1,88 +0,0 @@
1
- import { OscMsg, RemoteClient, OscReply } from '../models/types';
2
- import { X32State } from '../entities/X32State';
3
- import { SubscriptionManager } from '../entities/SubscriptionManager';
4
- import { OscCommandStrategy } from './strategies/OscCommandStrategy';
5
- import { NodeDiscoveryStrategy } from './strategies/NodeDiscoveryStrategy';
6
- import { StaticResponseStrategy } from './strategies/StaticResponseStrategy';
7
- import { SubscriptionStrategy } from './strategies/SubscriptionStrategy';
8
- import { BatchStrategy } from './strategies/BatchStrategy';
9
- import { MeterStrategy } from './strategies/MeterStrategy';
10
- import { StateAccessStrategy } from './strategies/StateAccessStrategy';
11
- import { ILogger, LogCategory } from '../ports/ILogger';
12
- import { MeterService } from './MeterService';
13
- import { SchemaRegistry } from './SchemaRegistry';
14
- import { StaticResponseService } from './StaticResponseService';
15
-
16
- /**
17
- * The central dispatcher for incoming OSC messages.
18
- * Uses a Chain of Responsibility (Strategy pattern) to delegate handling.
19
- * Order of strategies is critical for correct prioritization.
20
- */
21
- export class OscMessageHandler {
22
- /** List of active command strategies. */
23
- private strategies: OscCommandStrategy[];
24
-
25
- /**
26
- * Initializes the handler with all available strategies.
27
- * @param state - Global mixer state.
28
- * @param subscriptionManager - Active client sessions.
29
- * @param logger - Logger instance.
30
- * @param serverIp - Host IP reported in handshakes.
31
- * @param serverName - Console name reported in handshakes.
32
- * @param serverModel - Console model reported in handshakes.
33
- * @param meterService - Service for metering.
34
- * @param schemaRegistry - Service for schema validation.
35
- * @param staticResponseService - Service for static responses.
36
- */
37
- constructor(
38
- state: X32State,
39
- subscriptionManager: SubscriptionManager,
40
- private logger: ILogger,
41
- serverIp: string,
42
- serverName: string,
43
- serverModel: string,
44
- private meterService: MeterService,
45
- private schemaRegistry: SchemaRegistry,
46
- private staticResponseService: StaticResponseService
47
- ) {
48
- // Order matters! Specific strategies (Discovery, Handshake) first, generic (State Access) last.
49
- this.strategies = [
50
- new NodeDiscoveryStrategy(this.schemaRegistry),
51
- new StaticResponseStrategy(serverIp, serverName, serverModel, this.staticResponseService),
52
- new SubscriptionStrategy(subscriptionManager, state, logger),
53
- new BatchStrategy(subscriptionManager, logger),
54
- new MeterStrategy(subscriptionManager, state, this.meterService),
55
- new StateAccessStrategy(state, logger, this.schemaRegistry)
56
- ];
57
- }
58
-
59
- /**
60
- * Dispatches an incoming message to the first matching strategy.
61
- * @param msg - Parsed OSC message.
62
- * @param source - Source address and port of the packet.
63
- * @returns Array of replies generated by the strategy.
64
- */
65
- public handle(msg: OscMsg, source: RemoteClient): OscReply[] {
66
- const addr = msg.address;
67
-
68
- // Skip noisy subscriptions in high-level logging
69
- if (!addr.startsWith('/meters')) {
70
- this.logger.debug(LogCategory.DISPATCH, `Handling`, { addr, args: msg.args });
71
- }
72
-
73
- // Delegate to first matching Strategy
74
- for (const strategy of this.strategies) {
75
- if (strategy.canHandle(addr)) {
76
- const replies = strategy.execute(msg, source);
77
- if (replies.length > 0) {
78
- this.logger.debug(LogCategory.DISPATCH, `Strategy ${strategy.constructor.name} replied`, { count: replies.length });
79
- }
80
- return replies;
81
- }
82
- }
83
-
84
- // Mimic physical hardware: ignore unknown commands
85
- this.logger.warn(LogCategory.DISPATCH, `Unknown Command`, { addr, args: msg.args, ip: source.address });
86
- return [];
87
- }
88
- }
@@ -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
- }