@djodjonx/x32-simulator 0.0.3 → 0.0.5
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 +9 -0
- package/README.md +22 -0
- package/dist/server.cjs +4 -5
- package/dist/server.d.cts +1 -10
- package/dist/server.d.mts +1 -10
- package/dist/server.mjs +5 -3
- 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,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as readline from 'node:readline';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { SimulationService } from '../../application/use-cases/SimulationService';
|
|
7
|
-
import { ConsoleLogger, LogLevel } from '../../infrastructure/services/ConsoleLogger';
|
|
8
|
-
import { UdpNetworkGateway } from '../../infrastructure/services/UdpNetworkGateway';
|
|
9
|
-
import { InMemoryStateRepository } from '../../infrastructure/repositories/InMemoryStateRepository';
|
|
10
|
-
import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
|
|
11
|
-
import { SchemaFactory } from '../../domain/services/SchemaFactory';
|
|
12
|
-
import { OscCodec } from '../../infrastructure/mappers/OscCodec';
|
|
13
|
-
|
|
14
|
-
// Basic .env loader
|
|
15
|
-
export const loadEnv = () => {
|
|
16
|
-
try {
|
|
17
|
-
const envPath = path.resolve(process.cwd(), '.env');
|
|
18
|
-
if (fs.existsSync(envPath)) {
|
|
19
|
-
const content = fs.readFileSync(envPath, 'utf8');
|
|
20
|
-
content.split('\n').forEach(line => {
|
|
21
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
22
|
-
if (match) {
|
|
23
|
-
const key = match[1].trim();
|
|
24
|
-
const value = match[2].trim();
|
|
25
|
-
if (!process.env[key]) {
|
|
26
|
-
process.env[key] = value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
-
console.warn('Failed to load .env file', message);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const parseArgs = (argv: string[]) => {
|
|
38
|
-
const args = argv.slice(2);
|
|
39
|
-
let cliPort: number | undefined;
|
|
40
|
-
let cliHost: string | undefined;
|
|
41
|
-
|
|
42
|
-
for (let i = 0; i < args.length; i++) {
|
|
43
|
-
const arg = args[i];
|
|
44
|
-
|
|
45
|
-
// Handle flags
|
|
46
|
-
if (arg === '--port' || arg === '-p') {
|
|
47
|
-
const next = args[i + 1];
|
|
48
|
-
if (next && /^\d+$/.test(next)) {
|
|
49
|
-
cliPort = parseInt(next, 10);
|
|
50
|
-
i++; // Skip next
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (arg === '--ip' || arg === '-h' || arg === '--host') {
|
|
55
|
-
const next = args[i + 1];
|
|
56
|
-
if (next) {
|
|
57
|
-
cliHost = next;
|
|
58
|
-
i++; // Skip next
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Handle positional arguments (simple heuristic)
|
|
64
|
-
// If it looks like an IP (x.x.x.x), treat as Host
|
|
65
|
-
if (!cliHost && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(arg)) {
|
|
66
|
-
cliHost = arg;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// If it looks like a port number (and we haven't set port yet), treat as Port
|
|
71
|
-
if (!cliPort && /^\d+$/.test(arg)) {
|
|
72
|
-
cliPort = parseInt(arg, 10);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const PORT = cliPort || parseInt(process.env.X32_PORT || '10023', 10);
|
|
78
|
-
const HOST = cliHost || process.env.X32_IP || '0.0.0.0';
|
|
79
|
-
|
|
80
|
-
return { PORT, HOST };
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export const bootstrap = async () => {
|
|
84
|
-
loadEnv();
|
|
85
|
-
const { PORT, HOST } = parseArgs(process.argv);
|
|
86
|
-
|
|
87
|
-
// 0. Configure Logging
|
|
88
|
-
const logger = ConsoleLogger.getInstance();
|
|
89
|
-
logger.setLevel(LogLevel.DEBUG);
|
|
90
|
-
|
|
91
|
-
// 1. Initialize Domain Services (Registry) & Mappers
|
|
92
|
-
const schemaFactory = new SchemaFactory();
|
|
93
|
-
const schemaRegistry = new SchemaRegistry(schemaFactory);
|
|
94
|
-
const oscCodec = new OscCodec(schemaRegistry);
|
|
95
|
-
|
|
96
|
-
// 2. Initialize Infrastructure
|
|
97
|
-
const gateway = new UdpNetworkGateway(logger, oscCodec);
|
|
98
|
-
const stateRepo = new InMemoryStateRepository(logger, schemaRegistry);
|
|
99
|
-
|
|
100
|
-
// 3. Initialize Application Service
|
|
101
|
-
const service = new SimulationService(
|
|
102
|
-
gateway,
|
|
103
|
-
logger,
|
|
104
|
-
stateRepo,
|
|
105
|
-
schemaRegistry,
|
|
106
|
-
PORT,
|
|
107
|
-
HOST
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
await service.start();
|
|
112
|
-
console.log(`
|
|
113
|
-
╔════════════════════════════════════════╗
|
|
114
|
-
║ 🚀 X32 SIMULATION SERVER ACTIVE ║
|
|
115
|
-
╚════════════════════════════════════════╝
|
|
116
|
-
• IP: ${HOST}
|
|
117
|
-
• Port: ${PORT}
|
|
118
|
-
• Protocol: UDP (Strict X32 Schema)
|
|
119
|
-
• Logging: ENHANCED (DEBUG)
|
|
120
|
-
|
|
121
|
-
👉 Type "exit" or "stop" to shut down.
|
|
122
|
-
👉 Type "reset" to reset faders to default.
|
|
123
|
-
`);
|
|
124
|
-
} catch (err: any) {
|
|
125
|
-
if (err.code === 'EADDRNOTAVAIL') {
|
|
126
|
-
console.error(`\n\x1b[31m❌ FAILED TO BIND TO IP: ${HOST}\x1b[0m`);
|
|
127
|
-
console.error(`\nThe IP address you requested does not exist on this machine.`);
|
|
128
|
-
console.error(`You likely need to create a network alias (Loopback) for it.\n`);
|
|
129
|
-
|
|
130
|
-
const isMac = process.platform === 'darwin';
|
|
131
|
-
const isLinux = process.platform === 'linux';
|
|
132
|
-
const isWin = process.platform === 'win32';
|
|
133
|
-
|
|
134
|
-
if (isMac || isLinux || isWin) {
|
|
135
|
-
console.error(`To fix this, try running the following command:\n`);
|
|
136
|
-
if (isMac) {
|
|
137
|
-
console.error(` \x1b[36msudo ifconfig lo0 alias ${HOST}\x1b[0m`);
|
|
138
|
-
} else if (isLinux) {
|
|
139
|
-
console.error(` \x1b[36msudo ip addr add ${HOST}/32 dev lo\x1b[0m`);
|
|
140
|
-
} else if (isWin) {
|
|
141
|
-
console.error(` \x1b[36mnetsh interface ip add address "Loopback" ${HOST} 255.255.255.255\x1b[0m`);
|
|
142
|
-
console.error(` (Note: Requires 'Microsoft Loopback Adapter' installed and named "Loopback")`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
console.error(`\nAlternatively, remove the IP argument to listen on all interfaces (0.0.0.0).`);
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
console.error("Failed to start server:", err);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 3. Setup CLI Input
|
|
154
|
-
const rl = readline.createInterface({
|
|
155
|
-
input: process.stdin,
|
|
156
|
-
output: process.stdout
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
rl.on('line', async (input) => {
|
|
160
|
-
const command = input.trim().toLowerCase();
|
|
161
|
-
|
|
162
|
-
switch (command) {
|
|
163
|
-
case 'exit':
|
|
164
|
-
case 'stop':
|
|
165
|
-
case 'quit':
|
|
166
|
-
console.log('Shutting down...');
|
|
167
|
-
await service.stop();
|
|
168
|
-
process.exit(0);
|
|
169
|
-
break;
|
|
170
|
-
|
|
171
|
-
case 'reset':
|
|
172
|
-
service.resetState();
|
|
173
|
-
console.log('✨ Console state reset to defaults.');
|
|
174
|
-
break;
|
|
175
|
-
|
|
176
|
-
default:
|
|
177
|
-
if (command) console.log(`Unknown command: "${command}"`);
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Handle Ctrl+C
|
|
183
|
-
process.on('SIGINT', async () => {
|
|
184
|
-
console.log('\n(SIGINT received)');
|
|
185
|
-
await service.stop();
|
|
186
|
-
process.exit(0);
|
|
187
|
-
});
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Check if main module
|
|
191
|
-
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
|
|
192
|
-
if (isMainModule) {
|
|
193
|
-
bootstrap();
|
|
194
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module x32-simulator
|
|
3
|
-
* @description
|
|
4
|
-
* The main entry point for the X32 Simulator library.
|
|
5
|
-
* This module exports all the necessary classes, interfaces, and types required to
|
|
6
|
-
* integrate the X32 simulator into your own Node.js applications or testing frameworks.
|
|
7
|
-
*
|
|
8
|
-
* It provides access to:
|
|
9
|
-
* - The core `SimulationService` for running the simulator.
|
|
10
|
-
* - Domain entities like `X32State` and `SubscriptionManager`.
|
|
11
|
-
* - Infrastructure components for networking and logging.
|
|
12
|
-
* - All OSC-related models and types.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* import { SimulationService, ConsoleLogger, InMemoryStateRepository, SchemaRegistry, SchemaFactory, UdpNetworkGateway, OscCodec } from '@djodjonx/x32-simulator';
|
|
17
|
-
*
|
|
18
|
-
* // 1. Setup Dependencies
|
|
19
|
-
* const logger = ConsoleLogger.getInstance();
|
|
20
|
-
* const schemaRegistry = new SchemaRegistry(new SchemaFactory());
|
|
21
|
-
* const codec = new OscCodec(schemaRegistry);
|
|
22
|
-
* const gateway = new UdpNetworkGateway(logger, codec);
|
|
23
|
-
* const repository = new InMemoryStateRepository(logger, schemaRegistry);
|
|
24
|
-
*
|
|
25
|
-
* // 2. Initialize Service
|
|
26
|
-
* const simulator = new SimulationService(
|
|
27
|
-
* gateway,
|
|
28
|
-
* logger,
|
|
29
|
-
* repository,
|
|
30
|
-
* schemaRegistry,
|
|
31
|
-
* 10023, // Port
|
|
32
|
-
* '127.0.0.1' // Host
|
|
33
|
-
* );
|
|
34
|
-
*
|
|
35
|
-
* // 3. Start
|
|
36
|
-
* await simulator.start();
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
// Application layer
|
|
41
|
-
/**
|
|
42
|
-
* The main service class that orchestrates the simulation logic.
|
|
43
|
-
*/
|
|
44
|
-
export { SimulationService } from '../../application/use-cases/SimulationService';
|
|
45
|
-
|
|
46
|
-
// Domain entities
|
|
47
|
-
/**
|
|
48
|
-
* Represents the complete state of the X32 console, including all parameters and faders.
|
|
49
|
-
*/
|
|
50
|
-
export { X32State } from '../../domain/entities/X32State';
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Manages client subscriptions to OSC updates (e.g. /xremote, /subscribe).
|
|
54
|
-
*/
|
|
55
|
-
export { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
|
|
56
|
-
|
|
57
|
-
// Domain models
|
|
58
|
-
/**
|
|
59
|
-
* Represents a parsed OSC message.
|
|
60
|
-
*/
|
|
61
|
-
export { OscMessage } from '../../domain/models/OscMessage';
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Utility class for parsing and manipulating X32 OSC address strings.
|
|
65
|
-
*/
|
|
66
|
-
export { X32Address } from '../../domain/models/X32Address';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Represents a single parameter node (fader, button, knob) in the X32 state.
|
|
70
|
-
*/
|
|
71
|
-
export { X32Node } from '../../domain/models/X32Node';
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Handles meter data generation and binary blob creation.
|
|
75
|
-
*/
|
|
76
|
-
export { MeterData } from '../../domain/models/MeterData';
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Configuration constant defining the number of meters per meter bank.
|
|
80
|
-
*/
|
|
81
|
-
export { METER_COUNTS } from '../../domain/models/MeterConfig';
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Exports all shared TypeScript types and interfaces used across the domain.
|
|
85
|
-
*/
|
|
86
|
-
export * from '../../domain/models/types';
|
|
87
|
-
|
|
88
|
-
// Domain ports (interfaces)
|
|
89
|
-
/**
|
|
90
|
-
* Interface for the network gateway (UDP communication).
|
|
91
|
-
*/
|
|
92
|
-
export type { INetworkGateway } from '../../domain/ports/INetworkGateway';
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Interface for the logger.
|
|
96
|
-
*/
|
|
97
|
-
export type { ILogger, LogData } from '../../domain/ports/ILogger';
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Enum for log categories.
|
|
101
|
-
*/
|
|
102
|
-
export { LogCategory } from '../../domain/ports/ILogger';
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Interface for the state repository (persistence/storage).
|
|
106
|
-
*/
|
|
107
|
-
export type { IStateRepository } from '../../domain/ports/IStateRepository';
|
|
108
|
-
|
|
109
|
-
// Infrastructure layer
|
|
110
|
-
/**
|
|
111
|
-
* In-memory implementation of the state repository.
|
|
112
|
-
*/
|
|
113
|
-
export { InMemoryStateRepository } from '../../infrastructure/repositories/InMemoryStateRepository';
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Console-based implementation of the logger.
|
|
117
|
-
*/
|
|
118
|
-
export { ConsoleLogger } from '../../infrastructure/services/ConsoleLogger';
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Node.js UDP implementation of the network gateway.
|
|
122
|
-
*/
|
|
123
|
-
export { UdpNetworkGateway } from '../../infrastructure/services/UdpNetworkGateway';
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* OSC Codec for encoding and decoding X32-specific OSC packets.
|
|
127
|
-
*/
|
|
128
|
-
export { OscCodec } from '../../infrastructure/mappers/OscCodec';
|
|
129
|
-
|
|
130
|
-
// Domain services
|
|
131
|
-
/**
|
|
132
|
-
* Factory service for creating the initial X32 state schema.
|
|
133
|
-
*/
|
|
134
|
-
export { SchemaFactory } from '../../domain/services/SchemaFactory';
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Registry service for looking up X32 nodes and definitions.
|
|
138
|
-
*/
|
|
139
|
-
export { SchemaRegistry } from '../../domain/services/SchemaRegistry';
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { BroadcastUpdatesUseCase } from '../../../src/application/use-cases/BroadcastUpdatesUseCase';
|
|
3
|
-
import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
|
|
4
|
-
import { X32State } from '../../../src/domain/entities/X32State';
|
|
5
|
-
import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
|
|
6
|
-
import { MeterService } from '../../../src/domain/services/MeterService';
|
|
7
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
8
|
-
import { X32Node } from '../../../src/domain/models/X32Node';
|
|
9
|
-
|
|
10
|
-
describe('BroadcastUpdatesUseCase', () => {
|
|
11
|
-
it('should broadcast single change to xremote', () => {
|
|
12
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
13
|
-
const manager = new SubscriptionManager(logger);
|
|
14
|
-
manager.addPathSubscriber({ address: '1.2.3.4', port: 1234 }, '/xremote');
|
|
15
|
-
|
|
16
|
-
const state = new X32State({});
|
|
17
|
-
const gateway = { send: vi.fn() } as unknown as INetworkGateway;
|
|
18
|
-
const meterService = new MeterService();
|
|
19
|
-
const registry = {} as unknown as SchemaRegistry;
|
|
20
|
-
|
|
21
|
-
const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
|
|
22
|
-
useCase.broadcastSingleChange('/ch/01/mix/on', 1);
|
|
23
|
-
|
|
24
|
-
expect(gateway.send).toHaveBeenCalledWith(
|
|
25
|
-
expect.objectContaining({ address: '1.2.3.4' }),
|
|
26
|
-
'/ch/01/mix/on',
|
|
27
|
-
[1]
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should execute batch updates', () => {
|
|
32
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
33
|
-
const manager = new SubscriptionManager(logger);
|
|
34
|
-
const client = { address: '1.2.3.4', port: 1234 };
|
|
35
|
-
|
|
36
|
-
// Add a batch subscriber
|
|
37
|
-
manager.addBatchSubscriber(client, '/batch/alias', ['/mix/fader'], 0, 1, 0);
|
|
38
|
-
|
|
39
|
-
const state = new X32State({ '/ch/01/mix/fader': new X32Node('f', 0.5) });
|
|
40
|
-
const gateway = { send: vi.fn() } as unknown as INetworkGateway;
|
|
41
|
-
const meterService = new MeterService();
|
|
42
|
-
const registry = {
|
|
43
|
-
getRootFromIndex: vi.fn().mockReturnValue('/ch/01'),
|
|
44
|
-
getNode: vi.fn().mockReturnValue(new X32Node('f', 0.0))
|
|
45
|
-
} as unknown as SchemaRegistry;
|
|
46
|
-
|
|
47
|
-
const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
|
|
48
|
-
useCase.execute();
|
|
49
|
-
|
|
50
|
-
expect(gateway.send).toHaveBeenCalledWith(
|
|
51
|
-
expect.objectContaining({ address: '1.2.3.4' }),
|
|
52
|
-
'/batch/alias',
|
|
53
|
-
[expect.any(Buffer)]
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should execute format updates', () => {
|
|
58
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
59
|
-
const manager = new SubscriptionManager(logger);
|
|
60
|
-
const client = { address: '1.2.3.4', port: 1234 };
|
|
61
|
-
|
|
62
|
-
// Add a format subscriber: alias, pattern, start, count, factor
|
|
63
|
-
manager.addFormatSubscriber(client, '/format/alias', '/ch/*/mix/fader', 1, 2, 1);
|
|
64
|
-
|
|
65
|
-
const state = new X32State({
|
|
66
|
-
'/ch/01/mix/fader': new X32Node('f', 0.5),
|
|
67
|
-
'/ch/02/mix/fader': new X32Node('f', 0.6)
|
|
68
|
-
});
|
|
69
|
-
const gateway = { send: vi.fn() } as unknown as INetworkGateway;
|
|
70
|
-
const meterService = new MeterService();
|
|
71
|
-
const registry = {
|
|
72
|
-
getNode: vi.fn().mockReturnValue(new X32Node('f', 0.0))
|
|
73
|
-
} as unknown as SchemaRegistry;
|
|
74
|
-
|
|
75
|
-
const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
|
|
76
|
-
useCase.execute();
|
|
77
|
-
|
|
78
|
-
expect(gateway.send).toHaveBeenCalledWith(
|
|
79
|
-
expect.objectContaining({ address: '1.2.3.4' }),
|
|
80
|
-
'/format/alias',
|
|
81
|
-
[expect.any(Buffer)]
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should broadcast meter updates', () => {
|
|
86
|
-
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
87
|
-
const manager = new SubscriptionManager(logger);
|
|
88
|
-
manager.addMeterSubscriber({ address: '1.2.3.4', port: 1234 }, '/meters/1');
|
|
89
|
-
|
|
90
|
-
const state = new X32State({});
|
|
91
|
-
const gateway = { send: vi.fn() } as unknown as INetworkGateway;
|
|
92
|
-
const meterService = new MeterService();
|
|
93
|
-
const registry = {} as unknown as SchemaRegistry;
|
|
94
|
-
|
|
95
|
-
const useCase = new BroadcastUpdatesUseCase(manager, state, gateway, logger as any, meterService, registry);
|
|
96
|
-
useCase.execute();
|
|
97
|
-
|
|
98
|
-
expect(gateway.send).toHaveBeenCalledWith(
|
|
99
|
-
expect.objectContaining({ address: '1.2.3.4' }),
|
|
100
|
-
'/meters/1',
|
|
101
|
-
[expect.any(Buffer)]
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { ManageSessionsUseCase } from '../../../src/application/use-cases/ManageSessionsUseCase';
|
|
3
|
-
import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
|
|
4
|
-
|
|
5
|
-
describe('ManageSessionsUseCase', () => {
|
|
6
|
-
it('should cleanup sessions', () => {
|
|
7
|
-
const manager = { cleanup: vi.fn() } as unknown as SubscriptionManager;
|
|
8
|
-
const useCase = new ManageSessionsUseCase(manager);
|
|
9
|
-
useCase.cleanup();
|
|
10
|
-
expect(manager.cleanup).toHaveBeenCalled();
|
|
11
|
-
});
|
|
12
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { ProcessPacketUseCase } from '../../../src/application/use-cases/ProcessPacketUseCase';
|
|
3
|
-
import { OscMessageHandler } from '../../../src/domain/services/OscMessageHandler';
|
|
4
|
-
import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
|
|
5
|
-
|
|
6
|
-
describe('ProcessPacketUseCase', () => {
|
|
7
|
-
it('should process message and send replies', () => {
|
|
8
|
-
const handler = {
|
|
9
|
-
handle: vi.fn().mockReturnValue([{ address: '/reply', args: [1] }])
|
|
10
|
-
} as unknown as OscMessageHandler;
|
|
11
|
-
|
|
12
|
-
const gateway = {
|
|
13
|
-
send: vi.fn()
|
|
14
|
-
} as unknown as INetworkGateway;
|
|
15
|
-
|
|
16
|
-
const useCase = new ProcessPacketUseCase(handler, gateway);
|
|
17
|
-
|
|
18
|
-
useCase.execute(
|
|
19
|
-
{ oscType: 'message', address: '/test', args: [123] },
|
|
20
|
-
{ address: '1.2.3.4', port: 1234 }
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
expect(handler.handle).toHaveBeenCalled();
|
|
24
|
-
expect(gateway.send).toHaveBeenCalledWith({ address: '1.2.3.4', port: 1234 }, '/reply', [1]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should process bundles', () => {
|
|
28
|
-
const handler = {
|
|
29
|
-
handle: vi.fn().mockReturnValue([])
|
|
30
|
-
} as unknown as OscMessageHandler;
|
|
31
|
-
const gateway = { send: vi.fn() } as unknown as INetworkGateway;
|
|
32
|
-
const useCase = new ProcessPacketUseCase(handler, gateway);
|
|
33
|
-
|
|
34
|
-
useCase.execute(
|
|
35
|
-
{
|
|
36
|
-
oscType: 'bundle',
|
|
37
|
-
address: '', // Bundles don't have address
|
|
38
|
-
args: [],
|
|
39
|
-
elements: [
|
|
40
|
-
{ oscType: 'message', address: '/msg1', args: [] },
|
|
41
|
-
{ oscType: 'message', address: '/msg2', args: [] }
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
|
-
{ address: '1.2.3.4', port: 1234 }
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
expect(handler.handle).toHaveBeenCalledTimes(2);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { SimulationService } from '../../../src/application/use-cases/SimulationService';
|
|
3
|
-
import { INetworkGateway } from '../../../src/domain/ports/INetworkGateway';
|
|
4
|
-
import { ILogger } from '../../../src/domain/ports/ILogger';
|
|
5
|
-
import { IStateRepository } from '../../../src/domain/ports/IStateRepository';
|
|
6
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
7
|
-
import { X32State } from '../../../src/domain/entities/X32State';
|
|
8
|
-
import * as os from 'os';
|
|
9
|
-
|
|
10
|
-
vi.mock('os', () => ({
|
|
11
|
-
networkInterfaces: vi.fn().mockReturnValue({
|
|
12
|
-
eth0: [{ family: 'IPv4', internal: false, address: '192.168.1.1' }]
|
|
13
|
-
})
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
describe('SimulationService', () => {
|
|
17
|
-
let service: SimulationService;
|
|
18
|
-
let gateway: INetworkGateway;
|
|
19
|
-
let logger: ILogger;
|
|
20
|
-
let stateRepo: IStateRepository;
|
|
21
|
-
let schemaRegistry: SchemaRegistry;
|
|
22
|
-
let state: X32State;
|
|
23
|
-
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
gateway = {
|
|
26
|
-
start: vi.fn().mockResolvedValue(undefined),
|
|
27
|
-
stop: vi.fn().mockResolvedValue(undefined),
|
|
28
|
-
send: vi.fn(),
|
|
29
|
-
onPacket: vi.fn()
|
|
30
|
-
};
|
|
31
|
-
logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
32
|
-
state = new X32State({});
|
|
33
|
-
stateRepo = {
|
|
34
|
-
getState: vi.fn().mockReturnValue(state),
|
|
35
|
-
reset: vi.fn()
|
|
36
|
-
};
|
|
37
|
-
schemaRegistry = {
|
|
38
|
-
getSchema: vi.fn().mockReturnValue({}),
|
|
39
|
-
getNode: vi.fn(),
|
|
40
|
-
getAllPaths: vi.fn().mockReturnValue([])
|
|
41
|
-
} as unknown as SchemaRegistry;
|
|
42
|
-
|
|
43
|
-
// Use 0.0.0.0 to trigger getLocalIp
|
|
44
|
-
service = new SimulationService(
|
|
45
|
-
gateway,
|
|
46
|
-
logger as any,
|
|
47
|
-
stateRepo,
|
|
48
|
-
schemaRegistry,
|
|
49
|
-
10023,
|
|
50
|
-
'0.0.0.0'
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should start the service and detect local IP', async () => {
|
|
55
|
-
await service.start();
|
|
56
|
-
expect(gateway.start).toHaveBeenCalledWith(10023, '0.0.0.0');
|
|
57
|
-
expect(os.networkInterfaces).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should stop the service', async () => {
|
|
61
|
-
await service.start();
|
|
62
|
-
await service.stop();
|
|
63
|
-
expect(gateway.stop).toHaveBeenCalled();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should handle state change events', () => {
|
|
67
|
-
// Trigger change on the state
|
|
68
|
-
state.emit('change', { address: '/test', value: 1 });
|
|
69
|
-
// Should log and broadcast (indirectly verified by coverage)
|
|
70
|
-
expect(logger.info).toHaveBeenCalled();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should reset state', () => {
|
|
74
|
-
service.resetState();
|
|
75
|
-
expect(stateRepo.reset).toHaveBeenCalled();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
|
|
3
|
-
import { ILogger } from '../../../src/domain/ports/ILogger';
|
|
4
|
-
|
|
5
|
-
describe('SubscriptionManager', () => {
|
|
6
|
-
const logger: ILogger = {
|
|
7
|
-
debug: vi.fn(),
|
|
8
|
-
info: vi.fn(),
|
|
9
|
-
warn: vi.fn(),
|
|
10
|
-
error: vi.fn()
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
it('should add a path subscriber', () => {
|
|
14
|
-
const manager = new SubscriptionManager(logger);
|
|
15
|
-
const client = { address: '127.0.0.1', port: 10000 };
|
|
16
|
-
|
|
17
|
-
manager.addPathSubscriber(client, '/ch/01/mix/fader');
|
|
18
|
-
const subs = manager.getSubscribers();
|
|
19
|
-
|
|
20
|
-
expect(subs).toHaveLength(1);
|
|
21
|
-
expect(subs[0].type).toBe('path');
|
|
22
|
-
expect(subs[0].path).toBe('/ch/01/mix/fader');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should remove subscriber', () => {
|
|
26
|
-
const manager = new SubscriptionManager(logger);
|
|
27
|
-
const client = { address: '127.0.0.1', port: 10000 };
|
|
28
|
-
|
|
29
|
-
manager.addPathSubscriber(client, '/test');
|
|
30
|
-
manager.removeSubscriber(client, '/test');
|
|
31
|
-
|
|
32
|
-
expect(manager.getSubscribers()).toHaveLength(0);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should cleanup expired subscribers', () => {
|
|
36
|
-
const manager = new SubscriptionManager(logger);
|
|
37
|
-
const client = { address: '127.0.0.1', port: 10000 };
|
|
38
|
-
|
|
39
|
-
manager.addPathSubscriber(client, '/test');
|
|
40
|
-
|
|
41
|
-
// Mock Date.now to fast forward
|
|
42
|
-
const originalNow = Date.now;
|
|
43
|
-
Date.now = vi.fn(() => originalNow() + 20000); // +20s
|
|
44
|
-
|
|
45
|
-
manager.cleanup();
|
|
46
|
-
expect(manager.getSubscribers()).toHaveLength(0);
|
|
47
|
-
|
|
48
|
-
Date.now = originalNow;
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { X32State } from '../../../src/domain/entities/X32State';
|
|
3
|
-
import { X32Node } from '../../../src/domain/models/X32Node';
|
|
4
|
-
|
|
5
|
-
describe('X32State', () => {
|
|
6
|
-
const schema = {
|
|
7
|
-
'/ch/01/mix/fader': new X32Node('f', 0.0),
|
|
8
|
-
'/ch/01/mix/on': new X32Node('i', 0),
|
|
9
|
-
'/ch/01/grp/mute': new X32Node('i', 0), // needed for mute group logic
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
it('should initialize with default values', () => {
|
|
13
|
-
const state = new X32State(schema);
|
|
14
|
-
expect(state.get('/ch/01/mix/fader')).toBe(0.0);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should update values and emit events', () => {
|
|
18
|
-
const state = new X32State(schema);
|
|
19
|
-
const spy = vi.fn();
|
|
20
|
-
state.on('change', spy);
|
|
21
|
-
|
|
22
|
-
state.set('/ch/01/mix/fader', 0.5);
|
|
23
|
-
expect(state.get('/ch/01/mix/fader')).toBe(0.5);
|
|
24
|
-
expect(spy).toHaveBeenCalledWith({ address: '/ch/01/mix/fader', value: 0.5 });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should reset to defaults', () => {
|
|
28
|
-
const state = new X32State(schema);
|
|
29
|
-
state.set('/ch/01/mix/fader', 0.8);
|
|
30
|
-
state.reset();
|
|
31
|
-
expect(state.get('/ch/01/mix/fader')).toBe(0.0);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Mute Group Logic Test
|
|
35
|
-
it('should handle mute group side effects', () => {
|
|
36
|
-
const state = new X32State(schema);
|
|
37
|
-
// Assign CH 01 to Mute Group 1 (Bit 0)
|
|
38
|
-
state.set('/ch/01/grp/mute', 1); // 1 << 0
|
|
39
|
-
|
|
40
|
-
const spy = vi.fn();
|
|
41
|
-
state.on('change', spy);
|
|
42
|
-
|
|
43
|
-
// Turn Mute Group 1 ON (1)
|
|
44
|
-
state.handleMuteGroupChange(1, 1);
|
|
45
|
-
|
|
46
|
-
// Expect CH 01 Mute to be 0 (Muted? Logic: isOn=1 -> target=0? Check code: isOn=1 -> target=0. Wait, 0 usually means OFF/Muted or ON/Active? In X32 'on' means passing audio. So Mute Group ON means 'on' parameter becomes 0)
|
|
47
|
-
// Code: const targetMute = isOn === 1 ? 0 : 1;
|
|
48
|
-
// So if MG is ON, channel.on = 0 (Muted). Correct.
|
|
49
|
-
expect(state.get('/ch/01/mix/on')).toBe(0);
|
|
50
|
-
expect(spy).toHaveBeenCalledWith({ address: '/ch/01/mix/on', value: 0 });
|
|
51
|
-
});
|
|
52
|
-
});
|