@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.
- package/CHANGELOG.md +2 -0
- 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 -146
- 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 -72
- package/src/domain/models/X32Node.ts +0 -43
- package/src/domain/models/types.ts +0 -86
- 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 -37
- package/src/infrastructure/services/ConsoleLogger.ts +0 -177
- package/src/infrastructure/services/UdpNetworkGateway.ts +0 -100
- package/src/presentation/cli/server.ts +0 -194
- package/src/presentation/library/library.ts +0 -139
- 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,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
|
-
}
|