@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,9 +0,0 @@
|
|
|
1
|
-
export { SimulationService } from '../../application/use-cases/SimulationService';
|
|
2
|
-
export type { INetworkGateway } from '../../domain/ports/INetworkGateway';
|
|
3
|
-
export type { ILogger, LogData } from '../../domain/ports/ILogger';
|
|
4
|
-
export { LogCategory } from '../../domain/ports/ILogger';
|
|
5
|
-
export type { IStateRepository } from '../../domain/ports/IStateRepository';
|
|
6
|
-
export { InMemoryStateRepository } from '../../infrastructure/repositories/InMemoryStateRepository';
|
|
7
|
-
export { ConsoleLogger } from '../../infrastructure/services/ConsoleLogger';
|
|
8
|
-
export { UdpNetworkGateway } from '../../infrastructure/services/UdpNetworkGateway';
|
|
9
|
-
export * from '../../domain/models/types';
|
|
@@ -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
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { MeterData } from '../../../src/domain/models/MeterData';
|
|
3
|
-
|
|
4
|
-
describe('MeterData', () => {
|
|
5
|
-
it('should create and store values', () => {
|
|
6
|
-
const data = new MeterData('/meters/1', [0.1, 0.2]);
|
|
7
|
-
expect(data.path).toBe('/meters/1');
|
|
8
|
-
expect(data.values).toEqual([0.1, 0.2]);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should encode to binary blob', () => {
|
|
12
|
-
const data = new MeterData('/meters/1', [0.5]);
|
|
13
|
-
const blob = data.toBlob();
|
|
14
|
-
|
|
15
|
-
// Size = 4 (total size) + 4 (count) + 4 (float) = 12 bytes body + header?
|
|
16
|
-
// Implementation: 4 byte size + 4 byte count + floats
|
|
17
|
-
// totalSize = 4 + 4 + (1 * 4) = 12
|
|
18
|
-
|
|
19
|
-
expect(blob.readInt32BE(0)).toBe(12); // Header Size
|
|
20
|
-
expect(blob.readInt32LE(4)).toBe(1); // Count
|
|
21
|
-
expect(blob.readFloatLE(8)).toBeCloseTo(0.5); // Value
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { OscMessage } from '../../../src/domain/models/OscMessage';
|
|
3
|
-
|
|
4
|
-
describe('OscMessage', () => {
|
|
5
|
-
it('should create an instance correctly', () => {
|
|
6
|
-
const msg = new OscMessage('/test', [1, 'string', 0.5]);
|
|
7
|
-
expect(msg.address).toBe('/test');
|
|
8
|
-
expect(msg.args).toEqual([1, 'string', 0.5]);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should validate address prefix', () => {
|
|
12
|
-
const msg = new OscMessage('/ch/01/mix', []);
|
|
13
|
-
expect(msg.startsWith('/ch')).toBe(true);
|
|
14
|
-
expect(msg.startsWith('/bus')).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should retrieve typed arguments safely', () => {
|
|
18
|
-
const msg = new OscMessage('/test', [123, 'hello']);
|
|
19
|
-
expect(msg.getArgAsNumber(0)).toBe(123);
|
|
20
|
-
expect(msg.getArgAsString(1)).toBe('hello');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should throw when retrieving wrong type', () => {
|
|
24
|
-
const msg = new OscMessage('/test', [123]);
|
|
25
|
-
expect(() => msg.getArgAsString(0)).toThrow();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should create from packet', () => {
|
|
29
|
-
const packet = {
|
|
30
|
-
oscType: 'message' as const,
|
|
31
|
-
address: '/foo',
|
|
32
|
-
args: [{ type: 'i', value: 10 }, 20]
|
|
33
|
-
};
|
|
34
|
-
const msg = OscMessage.fromPacket(packet);
|
|
35
|
-
expect(msg.address).toBe('/foo');
|
|
36
|
-
expect(msg.args).toEqual([10, 20]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { X32Address } from '../../../src/domain/models/X32Address';
|
|
3
|
-
|
|
4
|
-
describe('X32Address', () => {
|
|
5
|
-
it('should parse path correctly', () => {
|
|
6
|
-
const addr = new X32Address('/ch/01/mix/fader');
|
|
7
|
-
expect(addr.path).toBe('/ch/01/mix/fader');
|
|
8
|
-
expect(addr.root).toBe('ch');
|
|
9
|
-
expect(addr.index).toBe('01');
|
|
10
|
-
expect(addr.suffix).toBe('/mix/fader');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('should handle short paths', () => {
|
|
14
|
-
const addr = new X32Address('/status');
|
|
15
|
-
expect(addr.root).toBe('status');
|
|
16
|
-
expect(addr.index).toBeUndefined();
|
|
17
|
-
expect(addr.suffix).toBe('');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should match category', () => {
|
|
21
|
-
const addr = new X32Address('/ch/01');
|
|
22
|
-
expect(addr.isCategory('ch')).toBe(true);
|
|
23
|
-
expect(addr.isCategory('bus')).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should match regex pattern', () => {
|
|
27
|
-
const addr = new X32Address('/ch/01/mix/on');
|
|
28
|
-
expect(addr.matches(/^\/ch\/\d+\/mix\/on$/)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { X32Node } from '../../../src/domain/models/X32Node';
|
|
3
|
-
|
|
4
|
-
describe('X32Node', () => {
|
|
5
|
-
it('should validate float types', () => {
|
|
6
|
-
const node = new X32Node('f', 0.0);
|
|
7
|
-
expect(node.validate(0.5)).toBe(true);
|
|
8
|
-
expect(node.validate(1)).toBe(true);
|
|
9
|
-
expect(node.validate('0.5')).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('should validate int types', () => {
|
|
13
|
-
const node = new X32Node('i', 0);
|
|
14
|
-
expect(node.validate(1)).toBe(true);
|
|
15
|
-
expect(node.validate(1.5)).toBe(true); // JS numbers are floats, simulation logic accepts it
|
|
16
|
-
expect(node.validate('1')).toBe(false);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should validate string types', () => {
|
|
20
|
-
const node = new X32Node('s', '');
|
|
21
|
-
expect(node.validate('text')).toBe(true);
|
|
22
|
-
expect(node.validate(123)).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should create from factory', () => {
|
|
26
|
-
const node = X32Node.from({ type: 'i', default: 1 });
|
|
27
|
-
expect(node.type).toBe('i');
|
|
28
|
-
expect(node.default).toBe(1);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { MeterService } from '../../../src/domain/services/MeterService';
|
|
3
|
-
import { X32State } from '../../../src/domain/entities/X32State';
|
|
4
|
-
import { X32Node } from '../../../src/domain/models/X32Node';
|
|
5
|
-
|
|
6
|
-
describe('MeterService', () => {
|
|
7
|
-
it('should generate meter data with noise floor', () => {
|
|
8
|
-
const service = new MeterService();
|
|
9
|
-
// /meters/0 has 70 meters
|
|
10
|
-
const data = service.generateMeterData('/meters/0');
|
|
11
|
-
expect(data.path).toBe('/meters/0');
|
|
12
|
-
expect(data.values).toHaveLength(70);
|
|
13
|
-
// Expect small random values
|
|
14
|
-
expect(data.values[0]).toBeGreaterThanOrEqual(0);
|
|
15
|
-
expect(data.values[0]).toBeLessThan(0.06);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should simulate fader signal', () => {
|
|
19
|
-
const service = new MeterService();
|
|
20
|
-
const state = new X32State({ '/ch/01/mix/fader': new X32Node('f', 0.0) });
|
|
21
|
-
state.set('/ch/01/mix/fader', 0.8);
|
|
22
|
-
|
|
23
|
-
const data = service.generateMeterData('/meters/1', state);
|
|
24
|
-
// Meter 1 corresponds to CH 01
|
|
25
|
-
expect(data.values[0]).toBeGreaterThan(0.5); // Should reflect fader level
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { OscMessageHandler } from '../../../src/domain/services/OscMessageHandler';
|
|
3
|
-
import { X32State } from '../../../src/domain/entities/X32State';
|
|
4
|
-
import { SubscriptionManager } from '../../../src/domain/entities/SubscriptionManager';
|
|
5
|
-
import { MeterService } from '../../../src/domain/services/MeterService';
|
|
6
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
7
|
-
import { StaticResponseService } from '../../../src/domain/services/StaticResponseService';
|
|
8
|
-
import { ILogger } from '../../../src/domain/ports/ILogger';
|
|
9
|
-
|
|
10
|
-
describe('OscMessageHandler', () => {
|
|
11
|
-
let handler: OscMessageHandler;
|
|
12
|
-
let state: X32State;
|
|
13
|
-
let subManager: SubscriptionManager;
|
|
14
|
-
let logger: ILogger;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
state = new X32State({});
|
|
18
|
-
logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
19
|
-
subManager = new SubscriptionManager(logger);
|
|
20
|
-
const meterService = new MeterService();
|
|
21
|
-
const schemaRegistry = {
|
|
22
|
-
has: vi.fn().mockReturnValue(false),
|
|
23
|
-
getNode: vi.fn(),
|
|
24
|
-
getAllPaths: vi.fn().mockReturnValue([])
|
|
25
|
-
} as unknown as SchemaRegistry;
|
|
26
|
-
const staticResponseService = new StaticResponseService();
|
|
27
|
-
|
|
28
|
-
handler = new OscMessageHandler(
|
|
29
|
-
state,
|
|
30
|
-
subManager,
|
|
31
|
-
logger,
|
|
32
|
-
'127.0.0.1',
|
|
33
|
-
'Mixer',
|
|
34
|
-
'X32',
|
|
35
|
-
meterService,
|
|
36
|
-
schemaRegistry,
|
|
37
|
-
staticResponseService
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should dispatch /status to StaticResponseStrategy', () => {
|
|
42
|
-
const replies = handler.handle({ address: '/status', args: [] }, { address: '1.2.3.4', port: 1234 });
|
|
43
|
-
expect(replies).toHaveLength(1);
|
|
44
|
-
expect(replies[0].address).toBe('/status');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should warn on unknown command', () => {
|
|
48
|
-
handler.handle({ address: '/unknown', args: [] }, { address: '1.2.3.4', port: 1234 });
|
|
49
|
-
expect(logger.warn).toHaveBeenCalled();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { SchemaRegistry } from '../../../src/domain/services/SchemaRegistry';
|
|
3
|
-
import { SchemaFactory } from '../../../src/domain/services/SchemaFactory';
|
|
4
|
-
|
|
5
|
-
describe('SchemaRegistry', () => {
|
|
6
|
-
const factory = new SchemaFactory();
|
|
7
|
-
const registry = new SchemaRegistry(factory);
|
|
8
|
-
|
|
9
|
-
it('should load full schema', () => {
|
|
10
|
-
const schema = registry.getSchema();
|
|
11
|
-
expect(Object.keys(schema).length).toBeGreaterThan(1000);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should find specific nodes', () => {
|
|
15
|
-
const node = registry.getNode('/ch/01/mix/fader');
|
|
16
|
-
expect(node).toBeDefined();
|
|
17
|
-
expect(node?.type).toBe('f');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should map index to root', () => {
|
|
21
|
-
expect(registry.getRootFromIndex(0)).toBe('/ch/01');
|
|
22
|
-
expect(registry.getRootFromIndex(31)).toBe('/ch/32');
|
|
23
|
-
expect(registry.getRootFromIndex(32)).toBe('/auxin/01');
|
|
24
|
-
expect(registry.getRootFromIndex(39)).toBe('/auxin/08');
|
|
25
|
-
expect(registry.getRootFromIndex(40)).toBe('/fxrtn/01');
|
|
26
|
-
expect(registry.getRootFromIndex(47)).toBe('/fxrtn/08');
|
|
27
|
-
expect(registry.getRootFromIndex(48)).toBe('/bus/01');
|
|
28
|
-
expect(registry.getRootFromIndex(63)).toBe('/bus/16');
|
|
29
|
-
expect(registry.getRootFromIndex(64)).toBe('/mtx/01');
|
|
30
|
-
expect(registry.getRootFromIndex(69)).toBe('/mtx/06');
|
|
31
|
-
expect(registry.getRootFromIndex(70)).toBe('/main/st');
|
|
32
|
-
expect(registry.getRootFromIndex(71)).toBe('/main/m');
|
|
33
|
-
expect(registry.getRootFromIndex(72)).toBe('/dca/01');
|
|
34
|
-
expect(registry.getRootFromIndex(79)).toBe('/dca/08');
|
|
35
|
-
expect(registry.getRootFromIndex(80)).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should check if path exists', () => {
|
|
39
|
-
expect(registry.has('/ch/01/mix/fader')).toBe(true);
|
|
40
|
-
expect(registry.has('/non/existent')).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should return all paths', () => {
|
|
44
|
-
const paths = registry.getAllPaths();
|
|
45
|
-
expect(paths).toContain('/ch/01/mix/fader');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { StaticResponseService } from '../../../src/domain/services/StaticResponseService';
|
|
3
|
-
|
|
4
|
-
describe('StaticResponseService', () => {
|
|
5
|
-
it('should return static data', () => {
|
|
6
|
-
const service = new StaticResponseService();
|
|
7
|
-
const resp = service.getResponse('/status');
|
|
8
|
-
expect(resp).toEqual(['active', '{{ip}}', '{{name}}']);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should return undefined for unknown path', () => {
|
|
12
|
-
const service = new StaticResponseService();
|
|
13
|
-
expect(service.getResponse('/unknown')).toBeUndefined();
|
|
14
|
-
});
|
|
15
|
-
});
|