@djodjonx/x32-simulator 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +28 -0
  3. package/dist/{UdpNetworkGateway-BrroQ6-Q.mjs → SchemaRegistry-BRVgnyaA.mjs} +990 -2
  4. package/dist/{UdpNetworkGateway-Ccdd7Us5.cjs → SchemaRegistry-CfDtw84j.cjs} +1033 -3
  5. package/dist/index.cjs +160 -6
  6. package/dist/index.d.cts +61 -11
  7. package/dist/index.d.mts +61 -11
  8. package/dist/index.mjs +146 -2
  9. package/dist/server.cjs +8 -927
  10. package/dist/server.mjs +1 -920
  11. package/package.json +5 -1
  12. package/.commitlintrc.json +0 -3
  13. package/.github/workflows/publish.yml +0 -38
  14. package/.husky/commit-msg +0 -1
  15. package/.husky/pre-commit +0 -1
  16. package/.oxlintrc.json +0 -56
  17. package/INSTALL.md +0 -107
  18. package/docs/OSC-Communication.md +0 -184
  19. package/docs/X32-INTERNAL.md +0 -262
  20. package/docs/X32-OSC.pdf +0 -0
  21. package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
  22. package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
  23. package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
  24. package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
  25. package/src/application/use-cases/SimulationService.ts +0 -122
  26. package/src/domain/entities/SubscriptionManager.ts +0 -126
  27. package/src/domain/entities/X32State.ts +0 -78
  28. package/src/domain/models/MeterConfig.ts +0 -22
  29. package/src/domain/models/MeterData.ts +0 -59
  30. package/src/domain/models/OscMessage.ts +0 -93
  31. package/src/domain/models/X32Address.ts +0 -78
  32. package/src/domain/models/X32Node.ts +0 -43
  33. package/src/domain/models/types.ts +0 -96
  34. package/src/domain/ports/ILogger.ts +0 -27
  35. package/src/domain/ports/INetworkGateway.ts +0 -8
  36. package/src/domain/ports/IStateRepository.ts +0 -16
  37. package/src/domain/services/MeterService.ts +0 -46
  38. package/src/domain/services/OscMessageHandler.ts +0 -88
  39. package/src/domain/services/SchemaFactory.ts +0 -308
  40. package/src/domain/services/SchemaRegistry.ts +0 -67
  41. package/src/domain/services/StaticResponseService.ts +0 -52
  42. package/src/domain/services/strategies/BatchStrategy.ts +0 -74
  43. package/src/domain/services/strategies/MeterStrategy.ts +0 -45
  44. package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
  45. package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
  46. package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
  47. package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
  48. package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
  49. package/src/infrastructure/mappers/OscCodec.ts +0 -54
  50. package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -21
  51. package/src/infrastructure/services/ConsoleLogger.ts +0 -177
  52. package/src/infrastructure/services/UdpNetworkGateway.ts +0 -71
  53. package/src/presentation/cli/server.ts +0 -194
  54. package/src/presentation/library/library.ts +0 -9
  55. package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
  56. package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
  57. package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
  58. package/tests/application/use-cases/SimulationService.test.ts +0 -77
  59. package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
  60. package/tests/domain/entities/X32State.test.ts +0 -52
  61. package/tests/domain/models/MeterData.test.ts +0 -23
  62. package/tests/domain/models/OscMessage.test.ts +0 -38
  63. package/tests/domain/models/X32Address.test.ts +0 -30
  64. package/tests/domain/models/X32Node.test.ts +0 -30
  65. package/tests/domain/services/MeterService.test.ts +0 -27
  66. package/tests/domain/services/OscMessageHandler.test.ts +0 -51
  67. package/tests/domain/services/SchemaRegistry.test.ts +0 -47
  68. package/tests/domain/services/StaticResponseService.test.ts +0 -15
  69. package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
  70. package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
  71. package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
  72. package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
  73. package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
  74. package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
  75. package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
  76. package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
  77. package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
  78. package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
  79. package/tests/presentation/cli/server.test.ts +0 -178
  80. package/tests/presentation/library/library.test.ts +0 -13
  81. package/tsconfig.json +0 -21
  82. package/tsdown.config.ts +0 -15
  83. package/vitest.config.ts +0 -9
@@ -1,71 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, OscReply, RemoteClient } from '../../models/types';
3
- import { X32State } from '../../entities/X32State';
4
- import { SchemaRegistry } from '../SchemaRegistry';
5
- import { ILogger, LogCategory } from '../../ports/ILogger';
6
-
7
- /**
8
- * General-purpose strategy for reading and writing console parameters.
9
- * acts as the fallback for any command that matches the schema.
10
- *
11
- * LOGIC:
12
- * - If no arguments: Treats as a QUERY (GET) and returns the current value.
13
- * - If arguments provided: Treats as an UPDATE (SET) and stores the new value.
14
- */
15
- export class StateAccessStrategy implements OscCommandStrategy {
16
- /**
17
- * Initializes the strategy.
18
- * @param state - Current mixer state.
19
- * @param logger - Logger instance.
20
- * @param schemaRegistry - Registry to validate addresses.
21
- */
22
- constructor(
23
- private state: X32State,
24
- private logger: ILogger,
25
- private schemaRegistry: SchemaRegistry
26
- ) {}
27
-
28
- /** @inheritdoc */
29
- public canHandle(address: string): boolean {
30
- // This is a catch-all for schema items.
31
- return this.schemaRegistry.has(address);
32
- }
33
-
34
- /** @inheritdoc */
35
- public execute(msg: OscMsg, _source: RemoteClient): OscReply[] {
36
- const addr = msg.address;
37
- const node = this.schemaRegistry.getNode(addr);
38
-
39
- // 1. GET (Query): If no args provided, client is asking for the value.
40
- if (msg.args.length === 0) {
41
- const val = this.state.get(addr);
42
- if (val !== undefined) {
43
- return [{ address: addr, args: [val] }];
44
- }
45
- return [];
46
- }
47
-
48
- // 2. SET (Update): If args provided, client is changing the value.
49
- const val = msg.args[0];
50
- if (node) {
51
- // Type Enforcement using new Node logic
52
- if (!node.validate(val)) {
53
- this.logger.warn(LogCategory.DISPATCH, `[TYPE ERR] ${addr} expected ${node.type}, got ${typeof val}`);
54
- return [];
55
- }
56
-
57
- this.state.set(addr, val as number | string);
58
- this.logger.debug(LogCategory.STATE, `[SET] ${addr} -> ${val}`);
59
-
60
- // Side Effects: Mute Groups
61
- if (addr.startsWith('/config/mute/')) {
62
- const groupIdx = parseInt(addr.split('/').pop()!, 10);
63
- if (!isNaN(groupIdx)) {
64
- this.state.handleMuteGroupChange(groupIdx, val as number);
65
- }
66
- }
67
- }
68
-
69
- return [];
70
- }
71
- }
@@ -1,42 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, OscReply, RemoteClient } from '../../models/types';
3
- import { StaticResponseService } from '../StaticResponseService';
4
-
5
- /**
6
- * Handles discovery and static information queries.
7
- * e.g., /status, /xinfo, /-prefs/...
8
- */
9
- export class StaticResponseStrategy implements OscCommandStrategy {
10
- constructor(
11
- private serverIp: string,
12
- private serverName: string,
13
- private serverModel: string,
14
- private staticResponseService: StaticResponseService
15
- ) {}
16
-
17
- canHandle(address: string): boolean {
18
- return !!this.staticResponseService.getResponse(address);
19
- }
20
-
21
- execute(msg: OscMsg, _source: RemoteClient): OscReply[] {
22
- const rawResponse = this.staticResponseService.getResponse(msg.address);
23
-
24
- if (!rawResponse) return [];
25
-
26
- // Replace template variables
27
- const args = rawResponse.map(arg => {
28
- if (typeof arg === 'string') {
29
- return arg
30
- .replace('{{ip}}', this.serverIp)
31
- .replace('{{name}}', this.serverName)
32
- .replace('{{model}}', this.serverModel);
33
- }
34
- return arg;
35
- });
36
-
37
- return [{
38
- address: msg.address,
39
- args
40
- }];
41
- }
42
- }
@@ -1,56 +0,0 @@
1
- import { OscCommandStrategy } from './OscCommandStrategy';
2
- import { OscMsg, RemoteClient, OscReply } from '../../models/types';
3
- import { SubscriptionManager } from '../../entities/SubscriptionManager';
4
- import { X32State } from '../../entities/X32State';
5
- import { ILogger, LogCategory } from '../../ports/ILogger';
6
-
7
- export class SubscriptionStrategy implements OscCommandStrategy {
8
- constructor(
9
- private subscriptionManager: SubscriptionManager,
10
- private state: X32State,
11
- private logger: ILogger
12
- ) {}
13
-
14
- canHandle(address: string): boolean {
15
- return ['/subscribe', '/renew', '/unsubscribe', '/xremote'].includes(address);
16
- }
17
-
18
- /**
19
- * Handles Session and Subscription lifecycle.
20
- *
21
- * ROUTES:
22
- * - /xremote: Firehose subscription (requests all console updates for 10s).
23
- * - /subscribe [path]: Requests updates for a specific node path for 10s.
24
- * - /renew [path]: Resets the 10s watchdog timer for a path.
25
- * - /unsubscribe [path]: Stops updates for a path.
26
- * @param msg - Parsed OSC message.
27
- * @param source - Source address and port of the packet.
28
- * @returns Returns the current value of the path upon subscription.
29
- */
30
- execute(msg: OscMsg, source: RemoteClient): OscReply[] {
31
- const addr = msg.address;
32
-
33
- if (addr === '/xremote') {
34
- this.subscriptionManager.addPathSubscriber(source, '/xremote');
35
- return [{ address: '/xremote', args: [] }];
36
- }
37
-
38
- if (addr === '/unsubscribe') {
39
- this.subscriptionManager.removeSubscriber(source, msg.args[0] as string);
40
- this.logger.debug(LogCategory.SUB, `[UNSUB] ${msg.args[0]}`);
41
- return [];
42
- }
43
-
44
- // Subscribe / Renew
45
- const target = msg.args[0] as string;
46
- this.subscriptionManager.addPathSubscriber(source, target);
47
- const val = this.state.get(target);
48
-
49
- if (val !== undefined) {
50
- this.logger.debug(LogCategory.SUB, `[SUB] ${target} -> ${val}`);
51
- return [{ address: target, args: [val] }];
52
- }
53
-
54
- return [];
55
- }
56
- }
@@ -1,54 +0,0 @@
1
- import { Message, decode, encode } from 'node-osc';
2
- import { OscPacket, OscArgument } from '../../domain/models/types';
3
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
4
-
5
- /**
6
- * Handles encoding and decoding of X32 OSC messages.
7
- */
8
- export class OscCodec {
9
-
10
- constructor(private schemaRegistry: SchemaRegistry) {}
11
-
12
- /**
13
- * Decodes a binary OSC message into a packet object.
14
- * @param msg - Raw UDP buffer.
15
- * @returns Decoded OscPacket.
16
- */
17
- public decode(msg: Buffer): OscPacket {
18
- return decode(msg) as OscPacket;
19
- }
20
-
21
- /**
22
- * Encodes an address and arguments into a binary OSC message.
23
- * @param address - OSC address pattern.
24
- * @param args - Array of arguments.
25
- * @returns Binary buffer.
26
- */
27
- public encode(address: string, args: OscArgument[]): Buffer {
28
- const msg = this.createMessage(address, args);
29
- return encode(msg);
30
- }
31
-
32
- /**
33
- * Creates a typed node-osc Message based on X32 schema.
34
- * @param address - Target address.
35
- * @param args - Untyped arguments.
36
- * @returns Typed Message.
37
- */
38
- private createMessage(address: string, args: OscArgument[]): Message {
39
- // Enforce types based on schema if possible
40
- const typedArgs = args.map((arg: OscArgument) => {
41
- if (typeof arg === 'object' && arg !== null && 'type' in arg && 'value' in arg) return arg;
42
- if (Buffer.isBuffer(arg)) return arg;
43
-
44
- const node = this.schemaRegistry.getNode(address);
45
- if (node) {
46
- if (node.type === 'f' && typeof arg === 'number') return { type: 'f', value: arg };
47
- if (node.type === 'i' && typeof arg === 'number') return { type: 'i', value: Math.round(arg) };
48
- if (node.type === 's' && typeof arg === 'string') return { type: 's', value: arg };
49
- }
50
- return arg;
51
- });
52
- return new Message(address, ...typedArgs);
53
- }
54
- }
@@ -1,21 +0,0 @@
1
- import { IStateRepository } from '../../domain/ports/IStateRepository';
2
- import { X32State } from '../../domain/entities/X32State';
3
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
4
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
5
-
6
- export class InMemoryStateRepository implements IStateRepository {
7
- private state: X32State;
8
-
9
- constructor(private logger: ILogger, schemaRegistry: SchemaRegistry) {
10
- this.state = new X32State(schemaRegistry.getSchema());
11
- }
12
-
13
- public getState(): X32State {
14
- return this.state;
15
- }
16
-
17
- public reset(): void {
18
- this.logger.info(LogCategory.SYSTEM, 'Resetting state to defaults');
19
- this.state.reset();
20
- }
21
- }
@@ -1,177 +0,0 @@
1
- import { ILogger, LogCategory, LogData } from '../../domain/ports/ILogger';
2
-
3
- /**
4
- * Log levels for the application.
5
- */
6
- export enum LogLevel {
7
- DEBUG = 0,
8
- INFO = 1,
9
- WARN = 2,
10
- ERROR = 3,
11
- NONE = 4
12
- }
13
-
14
- /**
15
- * Enhanced logger with category support and environment-based filtering.
16
- */
17
- export class ConsoleLogger implements ILogger {
18
- private static instance: ConsoleLogger;
19
- private level: LogLevel = LogLevel.DEBUG;
20
- private hiddenPatterns: string[] = [];
21
- private enabledCategories: Set<string> = new Set([
22
- LogCategory.SYSTEM,
23
- LogCategory.OSC_IN,
24
- LogCategory.OSC_OUT,
25
- LogCategory.DISPATCH,
26
- LogCategory.STATE,
27
- LogCategory.SUB,
28
- ]);
29
-
30
- constructor() {
31
- if (process.env.HIDDEN_LOG) {
32
- this.hiddenPatterns = process.env.HIDDEN_LOG.split(',').map(s => s.trim()).filter(s => s.length > 0);
33
- }
34
- }
35
-
36
- /**
37
- * Gets the singleton instance.
38
- * @returns ConsoleLogger instance.
39
- */
40
- public static getInstance(): ConsoleLogger {
41
- if (!ConsoleLogger.instance) {
42
- ConsoleLogger.instance = new ConsoleLogger();
43
- }
44
- return ConsoleLogger.instance;
45
- }
46
-
47
- /**
48
- * Sets the minimum log level.
49
- * @param level - LogLevel.
50
- */
51
- public setLevel(level: LogLevel) {
52
- this.level = level;
53
- }
54
-
55
- /**
56
- * Enables a category for output.
57
- * @param cat - Category name.
58
- */
59
- public enableCategory(cat: string) {
60
- this.enabledCategories.add(cat);
61
- }
62
-
63
- /**
64
- * Disables a category for output.
65
- * @param cat - Category name.
66
- */
67
- public disableCategory(cat: string) {
68
- this.enabledCategories.delete(cat);
69
- }
70
-
71
- /**
72
- * Logs a debug message.
73
- * @param category - Log category.
74
- * @param msg - Message string.
75
- * @param data - Optional metadata.
76
- */
77
- public debug(category: string, msg: string, data?: LogData) {
78
- if (this.shouldHide(msg, data)) return;
79
- if (this.level <= LogLevel.DEBUG && this.enabledCategories.has(category)) {
80
- console.log(this.format('DEBUG', category, msg, data));
81
- }
82
- }
83
-
84
- /**
85
- * Logs an info message.
86
- * @param category - Log category.
87
- * @param msg - Message string.
88
- * @param data - Optional metadata.
89
- */
90
- public info(category: string, msg: string, data?: LogData) {
91
- if (this.shouldHide(msg, data)) return;
92
- if (this.level <= LogLevel.INFO && this.enabledCategories.has(category)) {
93
- console.log(this.format('INFO ', category, msg, data));
94
- }
95
- }
96
-
97
- /**
98
- * Logs a warning message.
99
- * @param category - Log category.
100
- * @param msg - Message string.
101
- * @param data - Optional metadata.
102
- */
103
- public warn(category: string, msg: string, data?: LogData) {
104
- if (this.shouldHide(msg, data)) return;
105
- if (this.level <= LogLevel.WARN && this.enabledCategories.has(category)) {
106
- console.log(this.format('WARN ', category, msg, data));
107
- }
108
- }
109
-
110
- /**
111
- * Logs an error message.
112
- * @param category - Log category.
113
- * @param msg - Message string.
114
- * @param err - Optional error object.
115
- */
116
- public error(category: string, msg: string, err?: LogData) {
117
- if (this.shouldHide(msg, err)) return;
118
- if (this.level <= LogLevel.ERROR) {
119
- console.log(this.format('ERROR', category, msg, err));
120
- }
121
- }
122
-
123
- /**
124
- * Checks if a log should be hidden based on patterns.
125
- * @param msg - Message.
126
- * @param data - Metadata.
127
- * @returns True if log should be suppressed.
128
- */
129
- private shouldHide(msg: string, data?: LogData): boolean {
130
- if (this.hiddenPatterns.length === 0) return false;
131
- if (this.hiddenPatterns.some(p => msg.includes(p))) return true;
132
- if (data !== undefined) {
133
- const strData = Buffer.isBuffer(data) ? data.toString('hex') : (typeof data === 'object' ? JSON.stringify(data) : String(data));
134
- if (this.hiddenPatterns.some(p => strData.includes(p))) return true;
135
- }
136
- return false;
137
- }
138
-
139
- /**
140
- * Formats the log message.
141
- * @param level - Level label.
142
- * @param category - Category label.
143
- * @param msg - Message.
144
- * @param data - Metadata.
145
- * @returns Formatted string.
146
- */
147
- private format(level: string, category: string, msg: string, data?: LogData): string {
148
- const now = new Date();
149
- const time = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}.${now.getMilliseconds().toString().padStart(3,'0')}`;
150
-
151
- let out = `\x1b[90m[${time}]\x1b[0m `; // Gray timestamp
152
-
153
- switch(level) {
154
- case 'DEBUG': out += `\x1b[36m[DEBUG]\x1b[0m`; break;
155
- case 'INFO ': out += `\x1b[32m[INFO ]\x1b[0m`; break;
156
- case 'WARN ': out += `\x1b[33m[WARN ]\x1b[0m`; break;
157
- case 'ERROR': out += `\x1b[31m[ERROR]\x1b[0m`; break;
158
- }
159
-
160
- out += ` \x1b[35m[${category.padEnd(8)}]\x1b[0m ${msg}`;
161
-
162
- if (data !== undefined) {
163
- try {
164
- if (Buffer.isBuffer(data)) {
165
- out += ` \x1b[90m<Buffer ${data.length}b>\x1b[0m`;
166
- } else if (typeof data === 'object' && data !== null) {
167
- out += ` \x1b[90m${JSON.stringify(data)}\x1b[0m`;
168
- } else {
169
- out += ` \x1b[90m${data}\x1b[0m`;
170
- }
171
- } catch {
172
- out += ` [Circular/Error]`;
173
- }
174
- }
175
- return out;
176
- }
177
- }
@@ -1,71 +0,0 @@
1
- import * as dgram from 'node:dgram';
2
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
3
- import { OscPacket, RemoteClient } from '../../domain/models/types';
4
- import { OscCodec } from '../mappers/OscCodec';
5
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
6
-
7
- export class UdpNetworkGateway implements INetworkGateway {
8
- private socket: dgram.Socket;
9
- private isRunning = false;
10
- private packetCallback: ((packet: OscPacket, source: RemoteClient) => void) | null = null;
11
-
12
- constructor(private logger: ILogger, private codec: OscCodec) {
13
- this.socket = dgram.createSocket('udp4');
14
- }
15
-
16
- public onPacket(callback: (packet: OscPacket, source: RemoteClient) => void): void {
17
- this.packetCallback = callback;
18
- }
19
-
20
- public start(port: number, ip: string): Promise<void> {
21
- return new Promise((resolve, reject) => {
22
- this.socket.on('error', (err) => {
23
- this.logger.error(LogCategory.SYSTEM, 'Socket Error', err);
24
- reject(err);
25
- });
26
-
27
- this.socket.on('message', (msg, rinfo) => {
28
- try {
29
- this.logger.debug(LogCategory.OSC_IN, `Packet received ${msg.length}b`, { ip: rinfo.address });
30
- const decoded = this.codec.decode(msg);
31
- if (this.packetCallback) {
32
- this.packetCallback(decoded, { address: rinfo.address, port: rinfo.port });
33
- }
34
- } catch (err) {
35
- this.logger.error(LogCategory.OSC_IN, 'Decode Error', err instanceof Error ? err : new Error(String(err)));
36
- }
37
- });
38
-
39
- this.socket.bind(port, ip, () => {
40
- this.logger.info(LogCategory.SYSTEM, `Server bound to ${ip}:${port}`);
41
- this.isRunning = true;
42
- resolve();
43
- });
44
- });
45
- }
46
-
47
- public stop(): Promise<void> {
48
- return new Promise((resolve) => {
49
- if (!this.isRunning) return resolve();
50
- this.socket.close(() => {
51
- this.isRunning = false;
52
- this.logger.info(LogCategory.SYSTEM, 'Server stopped');
53
- resolve();
54
- });
55
- });
56
- }
57
-
58
- public send(target: RemoteClient, address: string, args: any[]): void {
59
- const buf = this.codec.encode(address, args);
60
- const cat = address.startsWith('/meters') ? LogCategory.METER : LogCategory.OSC_OUT;
61
-
62
- // Skip noisy meter logging if needed, or rely on logger level
63
- this.logger.debug(cat, `Sending ${address}`, { ip: target.address, args });
64
-
65
- this.socket.send(buf, target.port, target.address, (err) => {
66
- if (err) {
67
- this.logger.error(LogCategory.OSC_OUT, 'Send Error', err);
68
- }
69
- });
70
- }
71
- }
@@ -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
- }