@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.
- package/CHANGELOG.md +10 -0
- package/README.md +28 -0
- package/dist/{UdpNetworkGateway-BrroQ6-Q.mjs → SchemaRegistry-BRVgnyaA.mjs} +990 -2
- package/dist/{UdpNetworkGateway-Ccdd7Us5.cjs → SchemaRegistry-CfDtw84j.cjs} +1033 -3
- package/dist/index.cjs +160 -6
- package/dist/index.d.cts +61 -11
- package/dist/index.d.mts +61 -11
- package/dist/index.mjs +146 -2
- package/dist/server.cjs +8 -927
- package/dist/server.mjs +1 -920
- package/package.json +5 -1
- package/.commitlintrc.json +0 -3
- package/.github/workflows/publish.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.oxlintrc.json +0 -56
- package/INSTALL.md +0 -107
- package/docs/OSC-Communication.md +0 -184
- package/docs/X32-INTERNAL.md +0 -262
- package/docs/X32-OSC.pdf +0 -0
- package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
- package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
- package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
- package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
- package/src/application/use-cases/SimulationService.ts +0 -122
- package/src/domain/entities/SubscriptionManager.ts +0 -126
- package/src/domain/entities/X32State.ts +0 -78
- package/src/domain/models/MeterConfig.ts +0 -22
- package/src/domain/models/MeterData.ts +0 -59
- package/src/domain/models/OscMessage.ts +0 -93
- package/src/domain/models/X32Address.ts +0 -78
- package/src/domain/models/X32Node.ts +0 -43
- package/src/domain/models/types.ts +0 -96
- package/src/domain/ports/ILogger.ts +0 -27
- package/src/domain/ports/INetworkGateway.ts +0 -8
- package/src/domain/ports/IStateRepository.ts +0 -16
- package/src/domain/services/MeterService.ts +0 -46
- package/src/domain/services/OscMessageHandler.ts +0 -88
- package/src/domain/services/SchemaFactory.ts +0 -308
- package/src/domain/services/SchemaRegistry.ts +0 -67
- package/src/domain/services/StaticResponseService.ts +0 -52
- package/src/domain/services/strategies/BatchStrategy.ts +0 -74
- package/src/domain/services/strategies/MeterStrategy.ts +0 -45
- package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
- package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
- package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
- package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
- package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
- package/src/infrastructure/mappers/OscCodec.ts +0 -54
- package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -21
- package/src/infrastructure/services/ConsoleLogger.ts +0 -177
- package/src/infrastructure/services/UdpNetworkGateway.ts +0 -71
- package/src/presentation/cli/server.ts +0 -194
- package/src/presentation/library/library.ts +0 -9
- package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
- package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
- package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
- package/tests/application/use-cases/SimulationService.test.ts +0 -77
- package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
- package/tests/domain/entities/X32State.test.ts +0 -52
- package/tests/domain/models/MeterData.test.ts +0 -23
- package/tests/domain/models/OscMessage.test.ts +0 -38
- package/tests/domain/models/X32Address.test.ts +0 -30
- package/tests/domain/models/X32Node.test.ts +0 -30
- package/tests/domain/services/MeterService.test.ts +0 -27
- package/tests/domain/services/OscMessageHandler.test.ts +0 -51
- package/tests/domain/services/SchemaRegistry.test.ts +0 -47
- package/tests/domain/services/StaticResponseService.test.ts +0 -15
- package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
- package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
- package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
- package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
- package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
- package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
- package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
- package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
- package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
- package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
- package/tests/presentation/cli/server.test.ts +0 -178
- package/tests/presentation/library/library.test.ts +0 -13
- package/tsconfig.json +0 -21
- package/tsdown.config.ts +0 -15
- 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
|
-
}
|