@djodjonx/x32-simulator 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +28 -0
- package/dist/{UdpNetworkGateway-BrroQ6-Q.mjs → SchemaRegistry-BRVgnyaA.mjs} +990 -2
- package/dist/{UdpNetworkGateway-Ccdd7Us5.cjs → SchemaRegistry-CfDtw84j.cjs} +1033 -3
- package/dist/index.cjs +160 -6
- package/dist/index.d.cts +61 -11
- package/dist/index.d.mts +61 -11
- package/dist/index.mjs +146 -2
- package/dist/server.cjs +8 -927
- package/dist/server.mjs +1 -920
- package/package.json +5 -1
- package/.commitlintrc.json +0 -3
- package/.github/workflows/publish.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.oxlintrc.json +0 -56
- package/INSTALL.md +0 -107
- package/docs/OSC-Communication.md +0 -184
- package/docs/X32-INTERNAL.md +0 -262
- package/docs/X32-OSC.pdf +0 -0
- package/docs/behringer-x32-x32-osc-remote-protocol-en-44463.pdf +0 -0
- package/src/application/use-cases/BroadcastUpdatesUseCase.ts +0 -120
- package/src/application/use-cases/ManageSessionsUseCase.ts +0 -9
- package/src/application/use-cases/ProcessPacketUseCase.ts +0 -26
- package/src/application/use-cases/SimulationService.ts +0 -122
- package/src/domain/entities/SubscriptionManager.ts +0 -126
- package/src/domain/entities/X32State.ts +0 -78
- package/src/domain/models/MeterConfig.ts +0 -22
- package/src/domain/models/MeterData.ts +0 -59
- package/src/domain/models/OscMessage.ts +0 -93
- package/src/domain/models/X32Address.ts +0 -78
- package/src/domain/models/X32Node.ts +0 -43
- package/src/domain/models/types.ts +0 -96
- package/src/domain/ports/ILogger.ts +0 -27
- package/src/domain/ports/INetworkGateway.ts +0 -8
- package/src/domain/ports/IStateRepository.ts +0 -16
- package/src/domain/services/MeterService.ts +0 -46
- package/src/domain/services/OscMessageHandler.ts +0 -88
- package/src/domain/services/SchemaFactory.ts +0 -308
- package/src/domain/services/SchemaRegistry.ts +0 -67
- package/src/domain/services/StaticResponseService.ts +0 -52
- package/src/domain/services/strategies/BatchStrategy.ts +0 -74
- package/src/domain/services/strategies/MeterStrategy.ts +0 -45
- package/src/domain/services/strategies/NodeDiscoveryStrategy.ts +0 -36
- package/src/domain/services/strategies/OscCommandStrategy.ts +0 -22
- package/src/domain/services/strategies/StateAccessStrategy.ts +0 -71
- package/src/domain/services/strategies/StaticResponseStrategy.ts +0 -42
- package/src/domain/services/strategies/SubscriptionStrategy.ts +0 -56
- package/src/infrastructure/mappers/OscCodec.ts +0 -54
- package/src/infrastructure/repositories/InMemoryStateRepository.ts +0 -21
- package/src/infrastructure/services/ConsoleLogger.ts +0 -177
- package/src/infrastructure/services/UdpNetworkGateway.ts +0 -71
- package/src/presentation/cli/server.ts +0 -194
- package/src/presentation/library/library.ts +0 -9
- package/tests/application/use-cases/BroadcastUpdatesUseCase.test.ts +0 -104
- package/tests/application/use-cases/ManageSessionsUseCase.test.ts +0 -12
- package/tests/application/use-cases/ProcessPacketUseCase.test.ts +0 -49
- package/tests/application/use-cases/SimulationService.test.ts +0 -77
- package/tests/domain/entities/SubscriptionManager.test.ts +0 -50
- package/tests/domain/entities/X32State.test.ts +0 -52
- package/tests/domain/models/MeterData.test.ts +0 -23
- package/tests/domain/models/OscMessage.test.ts +0 -38
- package/tests/domain/models/X32Address.test.ts +0 -30
- package/tests/domain/models/X32Node.test.ts +0 -30
- package/tests/domain/services/MeterService.test.ts +0 -27
- package/tests/domain/services/OscMessageHandler.test.ts +0 -51
- package/tests/domain/services/SchemaRegistry.test.ts +0 -47
- package/tests/domain/services/StaticResponseService.test.ts +0 -15
- package/tests/domain/services/strategies/BatchStrategy.test.ts +0 -41
- package/tests/domain/services/strategies/MeterStrategy.test.ts +0 -19
- package/tests/domain/services/strategies/NodeDiscoveryStrategy.test.ts +0 -22
- package/tests/domain/services/strategies/StateAccessStrategy.test.ts +0 -49
- package/tests/domain/services/strategies/StaticResponseStrategy.test.ts +0 -15
- package/tests/domain/services/strategies/SubscriptionStrategy.test.ts +0 -45
- package/tests/infrastructure/mappers/OscCodec.test.ts +0 -41
- package/tests/infrastructure/repositories/InMemoryStateRepository.test.ts +0 -29
- package/tests/infrastructure/services/ConsoleLogger.test.ts +0 -74
- package/tests/infrastructure/services/UdpNetworkGateway.test.ts +0 -61
- package/tests/presentation/cli/server.test.ts +0 -178
- package/tests/presentation/library/library.test.ts +0 -13
- package/tsconfig.json +0 -21
- package/tsdown.config.ts +0 -15
- package/vitest.config.ts +0 -9
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents an X32 OSC Address path.
|
|
3
|
-
* parsing logic and component extraction.
|
|
4
|
-
*/
|
|
5
|
-
export class X32Address {
|
|
6
|
-
private readonly _path: string;
|
|
7
|
-
private readonly _parts: string[];
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Creates a new X32Address.
|
|
11
|
-
* @param path - The full OSC path (e.g., "/ch/01/mix/fader").
|
|
12
|
-
*/
|
|
13
|
-
constructor(path: string) {
|
|
14
|
-
this._path = path;
|
|
15
|
-
// Split by '/' and remove empty strings from leading slash
|
|
16
|
-
this._parts = path.split('/').filter(p => p.length > 0);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Gets the full string representation of the path.
|
|
21
|
-
* @returns The full OSC path string.
|
|
22
|
-
*/
|
|
23
|
-
get path(): string {
|
|
24
|
-
return this._path;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Gets the full string representation of the path.
|
|
29
|
-
return this._path;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Gets the root category (e.g., "ch", "bus", "config").
|
|
34
|
-
* @returns The first segment of the path.
|
|
35
|
-
*/
|
|
36
|
-
get root(): string | undefined {
|
|
37
|
-
return this._parts[0];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Gets the index/ID part if present (e.g., "01" from "/ch/01").
|
|
42
|
-
* @returns The second segment of the path.
|
|
43
|
-
*/
|
|
44
|
-
get index(): string | undefined {
|
|
45
|
-
return this._parts[1];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Gets the suffix (everything after the ID).
|
|
50
|
-
* e.g., for "/ch/01/mix/fader", returns "/mix/fader".
|
|
51
|
-
* @returns The remaining path after the index.
|
|
52
|
-
*/
|
|
53
|
-
get suffix(): string {
|
|
54
|
-
if (this._parts.length < 3) return '';
|
|
55
|
-
return '/' + this._parts.slice(2).join('/');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Checks if this address belongs to a specific category.
|
|
60
|
-
* @param category - The category to check (e.g., "meters", "ch").
|
|
61
|
-
* @returns True if it matches the category.
|
|
62
|
-
*/
|
|
63
|
-
isCategory(category: string): boolean {
|
|
64
|
-
return this.root === category;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Checks if the address matches a specific pattern.
|
|
69
|
-
* @param pattern - The regex or string pattern.
|
|
70
|
-
* @returns True if it matches the pattern.
|
|
71
|
-
*/
|
|
72
|
-
matches(pattern: RegExp | string): boolean {
|
|
73
|
-
if (typeof pattern === 'string') {
|
|
74
|
-
return this._path === pattern;
|
|
75
|
-
}
|
|
76
|
-
return pattern.test(this._path);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Metadata for a single state node in the X32 schema.
|
|
3
|
-
* Represents a "Knob" or "Variable" on the console.
|
|
4
|
-
*/
|
|
5
|
-
export class X32Node {
|
|
6
|
-
/** Value type (f=float, i=int, s=string). */
|
|
7
|
-
readonly type: 'f' | 'i' | 's';
|
|
8
|
-
/** Default value for reset. */
|
|
9
|
-
readonly default: number | string;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new X32Node.
|
|
13
|
-
* @param type - The OSC data type ('f', 'i', 's').
|
|
14
|
-
* @param defaultValue - The default value.
|
|
15
|
-
*/
|
|
16
|
-
constructor(type: 'f' | 'i' | 's', defaultValue: number | string) {
|
|
17
|
-
this.type = type;
|
|
18
|
-
this.default = defaultValue;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validates if a value is compatible with this node's type.
|
|
23
|
-
* @param value - The value to check.
|
|
24
|
-
* @returns True if valid.
|
|
25
|
-
*/
|
|
26
|
-
validate(value: any): boolean {
|
|
27
|
-
if (this.type === 'f') return typeof value === 'number'; // && value >= 0.0 && value <= 1.0 (optional strict check)
|
|
28
|
-
if (this.type === 'i') return typeof value === 'number'; // && Number.isInteger(value)
|
|
29
|
-
if (this.type === 's') return typeof value === 'string';
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Factory method to create from a plain object (for compatibility/migration).
|
|
35
|
-
* @param obj - Plain object.
|
|
36
|
-
* @param obj.type - OSC data type.
|
|
37
|
-
* @param obj.default - Default value.
|
|
38
|
-
* @returns A new X32Node instance.
|
|
39
|
-
*/
|
|
40
|
-
static from(obj: { type: 'f'|'i'|'s', default: number|string }): X32Node {
|
|
41
|
-
return new X32Node(obj.type, obj.default);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents a basic OSC argument value.
|
|
3
|
-
*/
|
|
4
|
-
export type OscArgumentValue = Buffer | boolean | number | string;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Represents an OSC argument, which can be a primitive value or a typed object.
|
|
8
|
-
*/
|
|
9
|
-
export type OscArgument = OscArgumentValue | { type: string; value: OscArgumentValue };
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Represents a decoded OSC packet (bundle or message) as returned by the codec.
|
|
13
|
-
*/
|
|
14
|
-
export interface OscPacket {
|
|
15
|
-
/** The type of OSC packet. */
|
|
16
|
-
oscType: 'bundle' | 'message';
|
|
17
|
-
/** The address pattern. */
|
|
18
|
-
address: string;
|
|
19
|
-
/** The arguments of the message or elements of the bundle. */
|
|
20
|
-
args: OscArgument[];
|
|
21
|
-
/** Elements if this is a bundle. */
|
|
22
|
-
elements?: OscPacket[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Represents a remote OSC client's connection info.
|
|
27
|
-
*/
|
|
28
|
-
export interface RemoteClient {
|
|
29
|
-
/** Target IP address. */
|
|
30
|
-
address: string;
|
|
31
|
-
/** Target port. */
|
|
32
|
-
port: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Represents a parsed OSC message ready for handling.
|
|
37
|
-
* Arguments are already unwrapped from their {type, value} containers.
|
|
38
|
-
*/
|
|
39
|
-
export interface OscMsg {
|
|
40
|
-
/** The OSC address. */
|
|
41
|
-
address: string;
|
|
42
|
-
/** The message arguments. */
|
|
43
|
-
args: OscArgumentValue[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Metadata for a single state node.
|
|
48
|
-
*/
|
|
49
|
-
export interface X32Node {
|
|
50
|
-
/** Value type (float, int, or string). */
|
|
51
|
-
type: 'f' | 'i' | 's';
|
|
52
|
-
/** Default value for reset. */
|
|
53
|
-
default: number | string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Represents an active subscription from a client.
|
|
58
|
-
*/
|
|
59
|
-
export interface Subscriber {
|
|
60
|
-
/** Target IP address. */
|
|
61
|
-
address: string;
|
|
62
|
-
/** Target port. */
|
|
63
|
-
port: number;
|
|
64
|
-
/** Expiry timestamp. */
|
|
65
|
-
expires: number;
|
|
66
|
-
/** Subscription type. */
|
|
67
|
-
type: 'batch' | 'format' | 'meter' | 'path';
|
|
68
|
-
/** Optional exact path for 'path' type. */
|
|
69
|
-
path?: string;
|
|
70
|
-
/** Target meter path for 'meter' type. */
|
|
71
|
-
meterPath?: string;
|
|
72
|
-
/** Alias for batch/format responses. */
|
|
73
|
-
alias?: string;
|
|
74
|
-
/** Glob/Wildcard pattern for 'format' type. */
|
|
75
|
-
pattern?: string;
|
|
76
|
-
/** List of paths for 'batch' type. */
|
|
77
|
-
paths?: string[];
|
|
78
|
-
/** Start index for ranges. */
|
|
79
|
-
start?: number;
|
|
80
|
-
/** Number of items in range. */
|
|
81
|
-
count?: number;
|
|
82
|
-
/** Frequency reduction factor. */
|
|
83
|
-
factor?: number;
|
|
84
|
-
/** Parameter arguments for the command. */
|
|
85
|
-
args?: number[];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Structure representing a reply to be sent back to an OSC client.
|
|
90
|
-
*/
|
|
91
|
-
export interface OscReply {
|
|
92
|
-
/** The target OSC address. */
|
|
93
|
-
address: string;
|
|
94
|
-
/** The response arguments. */
|
|
95
|
-
args: OscArgument[];
|
|
96
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents data that can be logged.
|
|
3
|
-
*/
|
|
4
|
-
export type LogData = Buffer | Error | LogData[] | boolean | number | string | { [key: string]: LogData } | null | undefined;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Standard log categories for the domain.
|
|
8
|
-
*/
|
|
9
|
-
export enum LogCategory {
|
|
10
|
-
SYSTEM = 'SYSTEM',
|
|
11
|
-
OSC_IN = 'OSC_IN',
|
|
12
|
-
OSC_OUT = 'OSC_OUT',
|
|
13
|
-
DISPATCH = 'DISPATCH',
|
|
14
|
-
STATE = 'STATE',
|
|
15
|
-
SUB = 'SUB',
|
|
16
|
-
METER = 'METER'
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Interface for the logging port.
|
|
21
|
-
*/
|
|
22
|
-
export interface ILogger {
|
|
23
|
-
debug(category: string, msg: string, data?: LogData): void;
|
|
24
|
-
info(category: string, msg: string, data?: LogData): void;
|
|
25
|
-
warn(category: string, msg: string, data?: LogData): void;
|
|
26
|
-
error(category: string, msg: string, err?: LogData): void;
|
|
27
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { OscPacket, RemoteClient } from '../models/types';
|
|
2
|
-
|
|
3
|
-
export interface INetworkGateway {
|
|
4
|
-
start(port: number, ip: string): Promise<void>;
|
|
5
|
-
stop(): Promise<void>;
|
|
6
|
-
send(target: RemoteClient, address: string, args: any[]): void;
|
|
7
|
-
onPacket(callback: (packet: OscPacket, source: RemoteClient) => void): void;
|
|
8
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { X32State } from '../entities/X32State';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Interface for accessing the mixer state.
|
|
5
|
-
*/
|
|
6
|
-
export interface IStateRepository {
|
|
7
|
-
/**
|
|
8
|
-
* Retrieves the single instance of the mixer state (Singleton in this context).
|
|
9
|
-
*/
|
|
10
|
-
getState(): X32State;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resets the state to defaults.
|
|
14
|
-
*/
|
|
15
|
-
reset(): void;
|
|
16
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { X32State } from '../entities/X32State';
|
|
2
|
-
import { MeterData } from '../models/MeterData';
|
|
3
|
-
import { METER_COUNTS } from '../models/MeterConfig';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Service responsible for generating meter values and data.
|
|
7
|
-
* Mimics the physics/audio engine of the X32.
|
|
8
|
-
*/
|
|
9
|
-
export class MeterService {
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Generates a MeterData object for a given path.
|
|
13
|
-
* @param path - The meter OSC path (e.g., "/meters/1").
|
|
14
|
-
* @param state - The current X32 state (for correlating faders to meters).
|
|
15
|
-
* @returns MeterData object containing the values.
|
|
16
|
-
*/
|
|
17
|
-
public generateMeterData(path: string, state?: X32State): MeterData {
|
|
18
|
-
const count = METER_COUNTS[path];
|
|
19
|
-
if (count === undefined) {
|
|
20
|
-
return new MeterData(path, []);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const values: number[] = [];
|
|
24
|
-
for (let i = 0; i < count; i++) {
|
|
25
|
-
let val = Math.random() * 0.05; // Noise floor
|
|
26
|
-
|
|
27
|
-
// Simulation logic: Link meters 1-32 to Channel Faders 1-32
|
|
28
|
-
if (state && path === '/meters/1') {
|
|
29
|
-
if (i < 32) {
|
|
30
|
-
const ch = (i + 1).toString().padStart(2, '0');
|
|
31
|
-
// We should use X32Address logic here ideally, but string construction is fast
|
|
32
|
-
const faderPath = `/ch/${ch}/mix/fader`;
|
|
33
|
-
const fader = state.get(faderPath);
|
|
34
|
-
|
|
35
|
-
if (typeof fader === 'number') {
|
|
36
|
-
// If fader is up, show signal (simulated with random variation)
|
|
37
|
-
val = fader > 0.01 ? fader * (0.9 + Math.random() * 0.1) : 0;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
values.push(val);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return new MeterData(path, values);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -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
|
-
}
|