@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,120 +0,0 @@
1
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
2
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
3
- import { X32State } from '../../domain/entities/X32State';
4
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
5
- import { MeterService } from '../../domain/services/MeterService';
6
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
7
-
8
- export class BroadcastUpdatesUseCase {
9
- constructor(
10
- private subscriptionManager: SubscriptionManager,
11
- private state: X32State,
12
- private gateway: INetworkGateway,
13
- private logger: ILogger,
14
- private meterService: MeterService,
15
- private schemaRegistry: SchemaRegistry
16
- ) {}
17
-
18
- public execute(): void {
19
- this.subscriptionManager.getSubscribers().forEach(sub => {
20
- if (sub.type === 'path' || !sub.alias) {
21
- if (sub.type === 'meter' && sub.meterPath) {
22
- const meterData = this.meterService.generateMeterData(sub.meterPath, this.state);
23
- this.gateway.send({ address: sub.address, port: sub.port }, sub.meterPath, [meterData.toBlob()]);
24
- }
25
- return;
26
- }
27
-
28
- const rinfo = { address: sub.address, port: sub.port };
29
-
30
- if (sub.type === 'batch' && sub.paths) {
31
- if (sub.paths.length === 1 && sub.paths[0].startsWith('/meters')) {
32
- const meterData = this.meterService.generateMeterData(sub.paths[0], this.state);
33
- this.gateway.send(rinfo, sub.alias, [meterData.toBlob()]);
34
- return;
35
- }
36
-
37
- let stride: number;
38
- const dataSize = sub.paths.length * 4;
39
- const factor = sub.factor || 0;
40
- const count = sub.count || 0;
41
-
42
- if (factor > 0) {
43
- stride = Math.max(factor * 4, dataSize);
44
- } else {
45
- stride = (sub.paths.length === 3) ? 16 : dataSize;
46
- }
47
-
48
- const blobSize = count * stride;
49
- if (isNaN(blobSize) || blobSize < 0) {
50
- this.logger.error(LogCategory.SYSTEM, `Invalid blob size`, { alias: sub.alias, blobSize, count, stride, factor, dataSize });
51
- return;
52
- }
53
-
54
- const blob = Buffer.alloc(blobSize);
55
-
56
- for (let i = 0; i < count; i++) {
57
- const root = this.schemaRegistry.getRootFromIndex(sub.start! + i);
58
- let currentOffset = i * stride;
59
-
60
- if (!root) { continue; }
61
-
62
- sub.paths.forEach((s, pIdx) => {
63
- if (pIdx * 4 >= stride) return;
64
-
65
- if (s.startsWith('/meters')) {
66
- blob.writeFloatLE(0.0, currentOffset);
67
- } else {
68
- const target = `${root}${s}`;
69
- const node = this.schemaRegistry.getNode(target);
70
- let val = this.state.get(target);
71
- if (val === undefined) val = node ? node.default : 0;
72
-
73
- if (typeof val === 'number') {
74
- if (node && node.type === 'f') {
75
- blob.writeFloatLE(val, currentOffset);
76
- }
77
- else blob.writeInt32LE(val, currentOffset);
78
- } else blob.writeInt32LE(0, currentOffset);
79
- }
80
- currentOffset += 4;
81
- });
82
- }
83
- this.gateway.send(rinfo, sub.alias, [blob]);
84
- } else if (sub.type === 'format' && sub.pattern) {
85
- const stride = sub.factor ? sub.factor * 4 : 4;
86
- const start = sub.start || 0;
87
- const count = sub.count || 0;
88
-
89
- const totalSize = (start + count) * stride;
90
- if (isNaN(totalSize) || totalSize < 0) return;
91
-
92
- const blob = Buffer.alloc(totalSize);
93
- let offset = start * stride;
94
-
95
- for (let i = start; i < start + count; i++) {
96
- const id = i.toString().padStart(2, '0');
97
- const target = sub.pattern!.replace(/\*\*?/, id);
98
- let val = this.state.get(target);
99
- if (val === undefined) val = 0;
100
- if (typeof val === 'number') {
101
- const node = this.schemaRegistry.getNode(target);
102
- if (node && node.type === 'f') blob.writeFloatLE(val, offset);
103
- else blob.writeInt32LE(val, offset);
104
- } else blob.writeInt32LE(0, offset);
105
- offset += stride;
106
- }
107
- this.gateway.send(rinfo, sub.alias, [blob]);
108
- }
109
- });
110
- }
111
-
112
- public broadcastSingleChange(path: string, value: number | string): void {
113
- this.subscriptionManager.getSubscribers().forEach(sub => {
114
- const match = path === sub.path || sub.path === '/xremote' || (sub.path && sub.path.includes('*') && path.startsWith(sub.path.split('*')[0]));
115
- if (match) {
116
- this.gateway.send({ address: sub.address, port: sub.port }, path, [value]);
117
- }
118
- });
119
- }
120
- }
@@ -1,9 +0,0 @@
1
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
2
-
3
- export class ManageSessionsUseCase {
4
- constructor(private subscriptionManager: SubscriptionManager) {}
5
-
6
- public cleanup(): void {
7
- this.subscriptionManager.cleanup();
8
- }
9
- }
@@ -1,26 +0,0 @@
1
- import { OscPacket, OscArgument, RemoteClient } from '../../domain/models/types';
2
- import { OscMessageHandler } from '../../domain/services/OscMessageHandler';
3
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
4
-
5
- export class ProcessPacketUseCase {
6
- constructor(
7
- private messageHandler: OscMessageHandler,
8
- private gateway: INetworkGateway
9
- ) {}
10
-
11
- public execute(packet: OscPacket, rinfo: RemoteClient): void {
12
- if (packet.oscType === 'bundle') {
13
- packet.elements?.forEach((el: OscPacket) => this.execute(el, rinfo));
14
- } else if (packet.oscType === 'message') {
15
- const args = packet.args.map((arg: OscArgument) => {
16
- if (typeof arg === 'object' && arg !== null && 'value' in arg) return arg.value;
17
- return arg;
18
- });
19
-
20
- const replies = this.messageHandler.handle({ address: packet.address, args }, rinfo);
21
- replies.forEach(reply => {
22
- this.gateway.send(rinfo, reply.address, reply.args);
23
- });
24
- }
25
- }
26
- }
@@ -1,122 +0,0 @@
1
- import { ProcessPacketUseCase } from './ProcessPacketUseCase';
2
- import { BroadcastUpdatesUseCase } from './BroadcastUpdatesUseCase';
3
- import { ManageSessionsUseCase } from './ManageSessionsUseCase';
4
- import { INetworkGateway } from '../../domain/ports/INetworkGateway';
5
- import { ILogger, LogCategory } from '../../domain/ports/ILogger';
6
- import { IStateRepository } from '../../domain/ports/IStateRepository';
7
- import { SubscriptionManager } from '../../domain/entities/SubscriptionManager';
8
- import { OscMessageHandler } from '../../domain/services/OscMessageHandler';
9
- import { MeterService } from '../../domain/services/MeterService';
10
- import { SchemaRegistry } from '../../domain/services/SchemaRegistry';
11
- import { StaticResponseService } from '../../domain/services/StaticResponseService';
12
- import * as os from 'os';
13
-
14
- /**
15
- * Gets the first non-internal IPv4 address found on this machine.
16
- * @returns Detected IP or localhost.
17
- */
18
- function getLocalIp(): string {
19
- const interfaces = os.networkInterfaces();
20
- for (const name of Object.keys(interfaces)) {
21
- for (const iface of interfaces[name]!) {
22
- if (iface.family === 'IPv4' && !iface.internal) {
23
- return iface.address;
24
- }
25
- }
26
- }
27
- return '127.0.0.1';
28
- }
29
-
30
- export class SimulationService {
31
- private subscriptionManager: SubscriptionManager;
32
- private messageHandler: OscMessageHandler;
33
-
34
- // Use Cases
35
- private processPacket: ProcessPacketUseCase;
36
- private broadcastUpdates: BroadcastUpdatesUseCase;
37
- private manageSessions: ManageSessionsUseCase;
38
-
39
- // Services
40
- private meterService: MeterService;
41
- private staticResponseService: StaticResponseService;
42
-
43
- private updateInterval: NodeJS.Timeout | null = null;
44
- private cleanupInterval: NodeJS.Timeout | null = null;
45
-
46
- constructor(
47
- private gateway: INetworkGateway,
48
- private logger: ILogger,
49
- private stateRepo: IStateRepository,
50
- private schemaRegistry: SchemaRegistry,
51
- private port: number = 10023,
52
- private ip: string = '0.0.0.0',
53
- name: string = 'osc-server',
54
- model: string = 'X32'
55
- ) {
56
- // Initialize Domain Entities
57
- const state = this.stateRepo.getState();
58
- this.subscriptionManager = new SubscriptionManager(logger);
59
-
60
- // Initialize Services
61
- this.meterService = new MeterService();
62
- this.staticResponseService = new StaticResponseService();
63
-
64
- const reportedIp = (this.ip === '0.0.0.0') ? getLocalIp() : this.ip;
65
- this.messageHandler = new OscMessageHandler(
66
- state,
67
- this.subscriptionManager,
68
- logger,
69
- reportedIp,
70
- name,
71
- model,
72
- this.meterService,
73
- this.schemaRegistry,
74
- this.staticResponseService
75
- );
76
-
77
- // Initialize Use Cases
78
- this.processPacket = new ProcessPacketUseCase(this.messageHandler, gateway);
79
- this.broadcastUpdates = new BroadcastUpdatesUseCase(
80
- this.subscriptionManager,
81
- state,
82
- gateway,
83
- logger,
84
- this.meterService,
85
- this.schemaRegistry
86
- );
87
- this.manageSessions = new ManageSessionsUseCase(this.subscriptionManager);
88
-
89
- // Bind State Changes to Broadcast
90
- state.on('change', (evt: { address: string, value: number | string }) => {
91
- this.logger.info(LogCategory.STATE, `State Changed`, evt);
92
- this.broadcastUpdates.broadcastSingleChange(evt.address, evt.value);
93
- });
94
-
95
- // Bind Gateway Packet Handling
96
- this.gateway.onPacket((packet, source) => this.processPacket.execute(packet, source));
97
- }
98
-
99
- public async start(): Promise<void> {
100
- await this.gateway.start(this.port, this.ip);
101
-
102
- // Cleanup expired subscribers every 5s
103
- this.cleanupInterval = setInterval(() => {
104
- this.manageSessions.cleanup();
105
- }, 5000);
106
-
107
- // Send updates every 100ms
108
- this.updateInterval = setInterval(() => {
109
- this.broadcastUpdates.execute();
110
- }, 100);
111
- }
112
-
113
- public async stop(): Promise<void> {
114
- if (this.updateInterval) clearInterval(this.updateInterval);
115
- if (this.cleanupInterval) clearInterval(this.cleanupInterval);
116
- await this.gateway.stop();
117
- }
118
-
119
- public resetState() {
120
- this.stateRepo.reset();
121
- }
122
- }
@@ -1,126 +0,0 @@
1
- import { Subscriber, RemoteClient } from '../models/types';
2
- import { ILogger, LogCategory } from '../ports/ILogger';
3
-
4
- /**
5
- * Manages OSC client subscriptions and their lifecycle.
6
- */
7
- export class SubscriptionManager {
8
- private logger: ILogger;
9
- private subscribers: Subscriber[] = [];
10
-
11
- constructor(logger: ILogger) {
12
- this.logger = logger;
13
- }
14
-
15
- /**
16
- * Cleans up expired subscriptions.
17
- * Standard X32 subscriptions last 10 seconds.
18
- */
19
- public cleanup() {
20
- const now = Date.now();
21
- const initialCount = this.subscribers.length;
22
- this.subscribers = this.subscribers.filter(s => {
23
- const active = s.expires > now;
24
- if (!active) {
25
- this.logger.debug(LogCategory.SUB, `Expired subscriber`, { type: s.type, ip: s.address, port: s.port, path: 'path' in s ? s.path : s.alias });
26
- }
27
- return active;
28
- });
29
- if (this.subscribers.length !== initialCount) {
30
- this.logger.info(LogCategory.SUB, `Cleanup finished`, { removed: initialCount - this.subscribers.length, remaining: this.subscribers.length });
31
- }
32
- }
33
-
34
- /**
35
- * Gets all active subscribers.
36
- * @returns Array of subscribers.
37
- */
38
- public getSubscribers(): Subscriber[] {
39
- return this.subscribers;
40
- }
41
-
42
- /**
43
- * Adds or renews a path-based subscription (e.g. /subscribe or /xremote).
44
- * @param rinfo - Client remote info.
45
- * @param path - Subscription path.
46
- */
47
- public addPathSubscriber(rinfo: RemoteClient, path: string) {
48
- const key = `${rinfo.address}:${rinfo.port}:${path}`;
49
- const expires = Date.now() + 10000;
50
- const existing = this.subscribers.find(s => s.type === 'path' && `${s.address}:${s.port}:${s.path}` === key);
51
-
52
- if (existing) {
53
- existing.expires = expires;
54
- this.logger.debug(LogCategory.SUB, `Renewed subscription`, { ip: rinfo.address, path });
55
- } else {
56
- this.subscribers.push({ type: 'path', address: rinfo.address, port: rinfo.port, path, expires });
57
- this.logger.info(LogCategory.SUB, `New subscription`, { ip: rinfo.address, path });
58
- }
59
- }
60
-
61
- /**
62
- * Adds or renews a batch subscription.
63
- * @param rinfo - Client remote info.
64
- * @param alias - Response alias.
65
- * @param paths - Target paths.
66
- * @param start - Start index.
67
- * @param count - Count.
68
- * @param factor - Frequency factor.
69
- * @param args - Command arguments.
70
- */
71
- public addBatchSubscriber(rinfo: RemoteClient, alias: string, paths: string[], start: number, count: number, factor?: number, args?: number[]) {
72
- const expires = Date.now() + 10000;
73
- this.subscribers = this.subscribers.filter(s => !(s.type === 'batch' && s.alias === alias && s.address === rinfo.address));
74
-
75
- this.subscribers.push({ type: 'batch', address: rinfo.address, port: rinfo.port, alias, paths, start, count, factor, expires, args });
76
- this.logger.info(LogCategory.SUB, `Batch subscription`, { alias, count: paths.length, factor, ip: rinfo.address, args });
77
- }
78
-
79
- /**
80
- * Adds or renews a format subscription.
81
- * @param rinfo - Client remote info.
82
- * @param alias - Response alias.
83
- * @param pattern - Path pattern.
84
- * @param start - Start index.
85
- * @param count - Count.
86
- * @param factor - Frequency factor.
87
- */
88
- public addFormatSubscriber(rinfo: RemoteClient, alias: string, pattern: string, start: number, count: number, factor?: number) {
89
- const expires = Date.now() + 10000;
90
- this.subscribers = this.subscribers.filter(s => !(s.type === 'format' && s.alias === alias && s.address === rinfo.address));
91
- this.subscribers.push({ type: 'format', address: rinfo.address, port: rinfo.port, alias, pattern, start, count, factor, expires });
92
- this.logger.info(LogCategory.SUB, `Format subscription`, { alias, pattern, factor, ip: rinfo.address });
93
- }
94
-
95
- /**
96
- * Adds or renews a high-frequency meter subscription.
97
- * @param rinfo - Client remote info.
98
- * @param meterPath - Target meter path.
99
- */
100
- public addMeterSubscriber(rinfo: RemoteClient, meterPath: string) {
101
- const expires = Date.now() + 10000;
102
- const existing = this.subscribers.find(s => s.type === 'meter' && s.meterPath === meterPath && s.address === rinfo.address);
103
-
104
- this.subscribers = this.subscribers.filter(s => !(s.type === 'meter' && s.meterPath === meterPath && s.address === rinfo.address));
105
- this.subscribers.push({ type: 'meter', address: rinfo.address, port: rinfo.port, meterPath, expires });
106
-
107
- if (!existing) {
108
- this.logger.info(LogCategory.SUB, `Meter subscription`, { path: meterPath, ip: rinfo.address });
109
- } else {
110
- this.logger.debug(LogCategory.SUB, `Meter renewal`, { path: meterPath, ip: rinfo.address });
111
- }
112
- }
113
-
114
- /**
115
- * Removes a subscription.
116
- * @param rinfo - Client remote info.
117
- * @param path - Subscription path.
118
- */
119
- public removeSubscriber(rinfo: RemoteClient, path: string) {
120
- const initial = this.subscribers.length;
121
- this.subscribers = this.subscribers.filter(s => !(s.address === rinfo.address && s.port === rinfo.port && s.path === path));
122
- if (this.subscribers.length < initial) {
123
- this.logger.info(LogCategory.SUB, `Unsubscribed`, { path, ip: rinfo.address });
124
- }
125
- }
126
- }
@@ -1,78 +0,0 @@
1
- import { EventEmitter } from 'node:events';
2
- import { X32Node } from '../models/X32Node';
3
-
4
- /**
5
- * Manages the internal "Digital Twin" state of the X32 console.
6
- * acts as a single source of truth for all parameters.
7
- * Emits 'change' events whenever a value is updated.
8
- */
9
- export class X32State extends EventEmitter {
10
- /** Map storing all OSC paths and their current values. */
11
- private state = new Map<string, number | string>();
12
- private readonly defaultState: Map<string, number | string> = new Map();
13
-
14
- /**
15
- * Initializes the state with default values from the schema.
16
- * @param schema - The schema definition map.
17
- */
18
- constructor(schema: Record<string, X32Node>) {
19
- super();
20
- // Pre-calculate default state
21
- for (const [addr, def] of Object.entries(schema)) {
22
- this.defaultState.set(addr, def.default);
23
- }
24
- this.reset();
25
- }
26
-
27
- /**
28
- * Resets all state parameters to their default values defined in the schema.
29
- */
30
- public reset() {
31
- this.state.clear();
32
- this.defaultState.forEach((val, key) => {
33
- this.state.set(key, val);
34
- });
35
- }
36
-
37
- /**
38
- * Retrieves the value of a specific OSC node.
39
- * @param address - The full OSC address path.
40
- * @returns The stored value (number or string) or undefined if not found.
41
- */
42
- public get(address: string): number | string | undefined {
43
- return this.state.get(address);
44
- }
45
-
46
- /**
47
- * Updates the value of a specific OSC node and notifies subscribers.
48
- * @param address - The full OSC address path.
49
- * @param value - The new value to store.
50
- */
51
- public set(address: string, value: number | string) {
52
- this.state.set(address, value);
53
- this.emit('change', { address, value });
54
- }
55
-
56
- /**
57
- * Specialized logic to handle X32 Mute Groups side-effects.
58
- * When a mute group is toggled, it iterates through all channels
59
- * assigned to that group and updates their individual mute status.
60
- * @param groupIdx - The index of the mute group (1-6).
61
- * @param isOn - The new state of the group master switch (0 or 1).
62
- */
63
- public handleMuteGroupChange(groupIdx: number, isOn: number) {
64
- for(let i=1; i<=32; i++) {
65
- const ch = i.toString().padStart(2, '0');
66
- const grpVal = this.get(`/ch/${ch}/grp/mute`);
67
-
68
- if (typeof grpVal === 'number') {
69
- if ((grpVal & (1 << (groupIdx - 1))) !== 0) {
70
- const targetMute = isOn === 1 ? 0 : 1;
71
- const muteAddr = `/ch/${ch}/mix/on`;
72
- // This set calls emit('change') which will trigger broadcast
73
- this.set(muteAddr, targetMute);
74
- }
75
- }
76
- }
77
- }
78
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Configuration for meter counts per path.
3
- */
4
- export const METER_COUNTS: Record<string, number> = {
5
- '/meters/0': 70,
6
- '/meters/1': 96,
7
- '/meters/2': 49,
8
- '/meters/3': 22,
9
- '/meters/4': 82,
10
- '/meters/5': 27,
11
- '/meters/6': 4,
12
- '/meters/7': 16,
13
- '/meters/8': 6,
14
- '/meters/9': 32,
15
- '/meters/10': 32,
16
- '/meters/11': 5,
17
- '/meters/12': 4,
18
- '/meters/13': 48,
19
- '/meters/14': 80,
20
- '/meters/15': 50,
21
- '/meters/16': 48,
22
- };
@@ -1,59 +0,0 @@
1
- /**
2
- * Represents a set of meter values for a specific meter path.
3
- * Handles the storage of float values and serialization to X32-specific binary blobs.
4
- */
5
- export class MeterData {
6
- private readonly _path: string;
7
- private readonly _values: number[];
8
-
9
- // Cache for the binary buffer to avoid re-allocation if values haven't changed (optional optimization,
10
- // but here we mainly cache the structure or just generate on demand).
11
- // Given the previous util had a static cache, we can keep it simple here.
12
-
13
- /**
14
- * Creates a new MeterData instance.
15
- * @param path - The meter OSC path (e.g., "/meters/1").
16
- * @param values - The array of float values (0.0 - 1.0 or similar).
17
- */
18
- constructor(path: string, values: number[]) {
19
- this._path = path;
20
- this._values = values;
21
- }
22
-
23
- /**
24
- * Gets the meter path.
25
- */
26
- get path(): string {
27
- return this._path;
28
- }
29
-
30
- /**
31
- * Gets the values.
32
- */
33
- get values(): number[] {
34
- return [...this._values];
35
- }
36
-
37
- /**
38
- * Generates an optimized binary blob for X32 meters (mixed endianness).
39
- * Structure: [Size (Int32BE)] [Count (Int32LE)] [Float1 (LE)] [Float2 (LE)] ...
40
- * @returns Buffer containing the OSC blob.
41
- */
42
- toBlob(): Buffer {
43
- const count = this._values.length;
44
- const totalSize = 4 + 4 + (count * 4);
45
-
46
- const blob = Buffer.alloc(totalSize);
47
-
48
- // Header: Size (BE) + Count (LE)
49
- blob.writeInt32BE(totalSize, 0);
50
- blob.writeInt32LE(count, 4);
51
-
52
- // Body: Floats (LE)
53
- for (let i = 0; i < count; i++) {
54
- blob.writeFloatLE(this._values[i], 8 + (i * 4));
55
- }
56
-
57
- return blob;
58
- }
59
- }
@@ -1,93 +0,0 @@
1
- import { OscArgumentValue, OscPacket } from './types';
2
-
3
- /**
4
- * Represents a parsed OSC message ready for handling.
5
- * Encapsulates the address and arguments, providing helper methods for validation and extraction.
6
- */
7
- export class OscMessage {
8
- private readonly _address: string;
9
- private readonly _args: OscArgumentValue[];
10
-
11
- /**
12
- * Creates a new OscMessage instance.
13
- * @param address - The OSC address string.
14
- * @param args - The list of arguments.
15
- */
16
- constructor(address: string, args: OscArgumentValue[]) {
17
- this._address = address;
18
- this._args = args;
19
- }
20
-
21
- /**
22
- * Gets the OSC address.
23
- * @returns The address string.
24
- */
25
- get address(): string {
26
- return this._address;
27
- }
28
-
29
- /**
30
- * Gets the message arguments.
31
- * @returns Copy of the arguments array.
32
- */
33
- get args(): OscArgumentValue[] {
34
- return [...this._args]; // Return copy to preserve immutability
35
- }
36
-
37
- /**
38
- * Checks if the address starts with the given prefix.
39
- * @param prefix - The prefix to check.
40
- * @returns True if it starts with the prefix.
41
- */
42
- startsWith(prefix: string): boolean {
43
- return this._address.startsWith(prefix);
44
- }
45
-
46
- /**
47
- * Gets an argument at a specific index, safely.
48
- * @param index - The index of the argument.
49
- * @returns The argument value or undefined.
50
- */
51
- getArg(index: number): OscArgumentValue | undefined {
52
- return this._args[index];
53
- }
54
-
55
- /**
56
- * Gets an argument as a number, or throws if missing/invalid.
57
- * @param index - The index.
58
- * @returns The number value.
59
- */
60
- getArgAsNumber(index: number): number {
61
- const val = this._args[index];
62
- if (typeof val !== 'number') {
63
- throw new Error(`Argument at index ${index} is not a number.`);
64
- }
65
- return val;
66
- }
67
-
68
- /**
69
- * Gets an argument as a string, or throws if missing/invalid.
70
- * @param index - The index.
71
- * @returns The string value.
72
- */
73
- getArgAsString(index: number): string {
74
- const val = this._args[index];
75
- if (typeof val !== 'string') {
76
- throw new Error(`Argument at index ${index} is not a string.`);
77
- }
78
- return val;
79
- }
80
-
81
- /**
82
- * Factory method to create an OscMessage from a raw packet.
83
- * @param packet - The raw OSC packet.
84
- * @returns A new OscMessage instance.
85
- */
86
- static fromPacket(packet: OscPacket): OscMessage {
87
- const values = packet.args.map(arg =>
88
- (typeof arg === 'object' && arg !== null && 'value' in arg) ? arg.value : arg
89
- ) as OscArgumentValue[];
90
-
91
- return new OscMessage(packet.address, values);
92
- }
93
- }