@djodjonx/x32-simulator 0.0.2 → 0.0.3
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 +8 -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 +1 -1
- package/src/application/use-cases/SimulationService.ts +24 -0
- package/src/domain/entities/X32State.ts +1 -1
- package/src/domain/models/X32Address.ts +2 -8
- package/src/domain/models/types.ts +0 -10
- package/src/infrastructure/repositories/InMemoryStateRepository.ts +16 -0
- package/src/infrastructure/services/UdpNetworkGateway.ts +29 -0
- package/src/presentation/library/library.ts +131 -1
|
@@ -30,6 +30,7 @@ os = __toESM(os);
|
|
|
30
30
|
let node_events = require("node:events");
|
|
31
31
|
let node_dgram = require("node:dgram");
|
|
32
32
|
node_dgram = __toESM(node_dgram);
|
|
33
|
+
let node_buffer = require("node:buffer");
|
|
33
34
|
|
|
34
35
|
//#region src/application/use-cases/ProcessPacketUseCase.ts
|
|
35
36
|
var ProcessPacketUseCase = class {
|
|
@@ -861,6 +862,10 @@ function getLocalIp() {
|
|
|
861
862
|
for (const name of Object.keys(interfaces)) for (const iface of interfaces[name]) if (iface.family === "IPv4" && !iface.internal) return iface.address;
|
|
862
863
|
return "127.0.0.1";
|
|
863
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* The core service that manages the X32 simulation, including networking,
|
|
867
|
+
* state management, and OSC message handling.
|
|
868
|
+
*/
|
|
864
869
|
var SimulationService = class {
|
|
865
870
|
subscriptionManager;
|
|
866
871
|
messageHandler;
|
|
@@ -871,6 +876,17 @@ var SimulationService = class {
|
|
|
871
876
|
staticResponseService;
|
|
872
877
|
updateInterval = null;
|
|
873
878
|
cleanupInterval = null;
|
|
879
|
+
/**
|
|
880
|
+
* Creates a new SimulationService instance.
|
|
881
|
+
* @param gateway - The network gateway for OSC communication.
|
|
882
|
+
* @param logger - The logger service.
|
|
883
|
+
* @param stateRepo - The repository managing the mixer state.
|
|
884
|
+
* @param schemaRegistry - The registry for X32 OSC schema.
|
|
885
|
+
* @param port - UDP port to listen on (default 10023).
|
|
886
|
+
* @param ip - IP address to bind to (default '0.0.0.0').
|
|
887
|
+
* @param name - Reported console name.
|
|
888
|
+
* @param model - Reported console model.
|
|
889
|
+
*/
|
|
874
890
|
constructor(gateway, logger, stateRepo, schemaRegistry, port = 10023, ip = "0.0.0.0", name = "osc-server", model = "X32") {
|
|
875
891
|
this.gateway = gateway;
|
|
876
892
|
this.logger = logger;
|
|
@@ -893,6 +909,9 @@ var SimulationService = class {
|
|
|
893
909
|
});
|
|
894
910
|
this.gateway.onPacket((packet, source) => this.processPacket.execute(packet, source));
|
|
895
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* Starts the simulator server and internal loops.
|
|
914
|
+
*/
|
|
896
915
|
async start() {
|
|
897
916
|
await this.gateway.start(this.port, this.ip);
|
|
898
917
|
this.cleanupInterval = setInterval(() => {
|
|
@@ -902,11 +921,17 @@ var SimulationService = class {
|
|
|
902
921
|
this.broadcastUpdates.execute();
|
|
903
922
|
}, 100);
|
|
904
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Stops the simulator server and stops all internal loops.
|
|
926
|
+
*/
|
|
905
927
|
async stop() {
|
|
906
928
|
if (this.updateInterval) clearInterval(this.updateInterval);
|
|
907
929
|
if (this.cleanupInterval) clearInterval(this.cleanupInterval);
|
|
908
930
|
await this.gateway.stop();
|
|
909
931
|
}
|
|
932
|
+
/**
|
|
933
|
+
* Resets the mixer state to default values.
|
|
934
|
+
*/
|
|
910
935
|
resetState() {
|
|
911
936
|
this.stateRepo.reset();
|
|
912
937
|
}
|
|
@@ -916,7 +941,7 @@ var SimulationService = class {
|
|
|
916
941
|
//#region src/domain/entities/X32State.ts
|
|
917
942
|
/**
|
|
918
943
|
* Manages the internal "Digital Twin" state of the X32 console.
|
|
919
|
-
* acts as a single source of truth for all parameters.
|
|
944
|
+
* It acts as a single source of truth for all parameters.
|
|
920
945
|
* Emits 'change' events whenever a value is updated.
|
|
921
946
|
*/
|
|
922
947
|
var X32State = class extends node_events.EventEmitter {
|
|
@@ -983,17 +1008,76 @@ var X32State = class extends node_events.EventEmitter {
|
|
|
983
1008
|
}
|
|
984
1009
|
};
|
|
985
1010
|
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/domain/models/X32Node.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Metadata for a single state node in the X32 schema.
|
|
1015
|
+
* Represents a "Knob" or "Variable" on the console.
|
|
1016
|
+
*/
|
|
1017
|
+
var X32Node = class X32Node {
|
|
1018
|
+
/** Value type (f=float, i=int, s=string). */
|
|
1019
|
+
type;
|
|
1020
|
+
/** Default value for reset. */
|
|
1021
|
+
default;
|
|
1022
|
+
/**
|
|
1023
|
+
* Creates a new X32Node.
|
|
1024
|
+
* @param type - The OSC data type ('f', 'i', 's').
|
|
1025
|
+
* @param defaultValue - The default value.
|
|
1026
|
+
*/
|
|
1027
|
+
constructor(type, defaultValue) {
|
|
1028
|
+
this.type = type;
|
|
1029
|
+
this.default = defaultValue;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Validates if a value is compatible with this node's type.
|
|
1033
|
+
* @param value - The value to check.
|
|
1034
|
+
* @returns True if valid.
|
|
1035
|
+
*/
|
|
1036
|
+
validate(value) {
|
|
1037
|
+
if (this.type === "f") return typeof value === "number";
|
|
1038
|
+
if (this.type === "i") return typeof value === "number";
|
|
1039
|
+
if (this.type === "s") return typeof value === "string";
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Factory method to create from a plain object (for compatibility/migration).
|
|
1044
|
+
* @param obj - Plain object.
|
|
1045
|
+
* @param obj.type - OSC data type.
|
|
1046
|
+
* @param obj.default - Default value.
|
|
1047
|
+
* @returns A new X32Node instance.
|
|
1048
|
+
*/
|
|
1049
|
+
static from(obj) {
|
|
1050
|
+
return new X32Node(obj.type, obj.default);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
|
|
986
1054
|
//#endregion
|
|
987
1055
|
//#region src/infrastructure/repositories/InMemoryStateRepository.ts
|
|
1056
|
+
/**
|
|
1057
|
+
* In-memory implementation of the state repository.
|
|
1058
|
+
* Stores the mixer state in volatile memory during the simulator's execution.
|
|
1059
|
+
*/
|
|
988
1060
|
var InMemoryStateRepository = class {
|
|
989
1061
|
state;
|
|
1062
|
+
/**
|
|
1063
|
+
* Creates a new InMemoryStateRepository.
|
|
1064
|
+
* @param logger - Logger service.
|
|
1065
|
+
* @param schemaRegistry - Registry providing the initial state schema.
|
|
1066
|
+
*/
|
|
990
1067
|
constructor(logger, schemaRegistry) {
|
|
991
1068
|
this.logger = logger;
|
|
992
1069
|
this.state = new X32State(schemaRegistry.getSchema());
|
|
993
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Returns the current mixer state instance.
|
|
1073
|
+
* @returns The X32State entity.
|
|
1074
|
+
*/
|
|
994
1075
|
getState() {
|
|
995
1076
|
return this.state;
|
|
996
1077
|
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Resets the entire state to its default values.
|
|
1080
|
+
*/
|
|
997
1081
|
reset() {
|
|
998
1082
|
this.logger.info(LogCategory.SYSTEM, "Resetting state to defaults");
|
|
999
1083
|
this.state.reset();
|
|
@@ -1154,18 +1238,37 @@ var ConsoleLogger = class ConsoleLogger {
|
|
|
1154
1238
|
|
|
1155
1239
|
//#endregion
|
|
1156
1240
|
//#region src/infrastructure/services/UdpNetworkGateway.ts
|
|
1241
|
+
/**
|
|
1242
|
+
* Node.js UDP implementation of the network gateway.
|
|
1243
|
+
* Handles the low-level socket communication and OSC packet routing.
|
|
1244
|
+
*/
|
|
1157
1245
|
var UdpNetworkGateway = class {
|
|
1158
1246
|
socket;
|
|
1159
1247
|
isRunning = false;
|
|
1160
1248
|
packetCallback = null;
|
|
1249
|
+
/**
|
|
1250
|
+
* Creates a new UdpNetworkGateway.
|
|
1251
|
+
* @param logger - Logger service.
|
|
1252
|
+
* @param codec - OSC codec for encoding/decoding.
|
|
1253
|
+
*/
|
|
1161
1254
|
constructor(logger, codec) {
|
|
1162
1255
|
this.logger = logger;
|
|
1163
1256
|
this.codec = codec;
|
|
1164
1257
|
this.socket = node_dgram.createSocket("udp4");
|
|
1165
1258
|
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Registers a callback to be executed whenever a new OSC packet arrives.
|
|
1261
|
+
* @param callback - Function called with decoded packet and source info.
|
|
1262
|
+
*/
|
|
1166
1263
|
onPacket(callback) {
|
|
1167
1264
|
this.packetCallback = callback;
|
|
1168
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Starts the UDP server on the specified port and IP.
|
|
1268
|
+
* @param port - UDP port.
|
|
1269
|
+
* @param ip - IP address to bind to.
|
|
1270
|
+
* @returns Promise resolving when the socket is bound.
|
|
1271
|
+
*/
|
|
1169
1272
|
start(port, ip) {
|
|
1170
1273
|
return new Promise((resolve, reject) => {
|
|
1171
1274
|
this.socket.on("error", (err) => {
|
|
@@ -1191,6 +1294,10 @@ var UdpNetworkGateway = class {
|
|
|
1191
1294
|
});
|
|
1192
1295
|
});
|
|
1193
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Shuts down the UDP server.
|
|
1299
|
+
* @returns Promise resolving when the socket is closed.
|
|
1300
|
+
*/
|
|
1194
1301
|
stop() {
|
|
1195
1302
|
return new Promise((resolve) => {
|
|
1196
1303
|
if (!this.isRunning) return resolve();
|
|
@@ -1201,6 +1308,12 @@ var UdpNetworkGateway = class {
|
|
|
1201
1308
|
});
|
|
1202
1309
|
});
|
|
1203
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Sends an OSC message to a specific remote client.
|
|
1313
|
+
* @param target - Target client info (IP/port).
|
|
1314
|
+
* @param address - OSC address pattern.
|
|
1315
|
+
* @param args - Array of arguments.
|
|
1316
|
+
*/
|
|
1204
1317
|
send(target, address, args) {
|
|
1205
1318
|
const buf = this.codec.encode(address, args);
|
|
1206
1319
|
const cat = address.startsWith("/meters") ? LogCategory.METER : LogCategory.OSC_OUT;
|
|
@@ -1214,6 +1327,881 @@ var UdpNetworkGateway = class {
|
|
|
1214
1327
|
}
|
|
1215
1328
|
};
|
|
1216
1329
|
|
|
1330
|
+
//#endregion
|
|
1331
|
+
//#region node_modules/node-osc/lib/Message.mjs
|
|
1332
|
+
const typeTags = {
|
|
1333
|
+
s: "string",
|
|
1334
|
+
f: "float",
|
|
1335
|
+
i: "integer",
|
|
1336
|
+
b: "blob",
|
|
1337
|
+
m: "midi"
|
|
1338
|
+
};
|
|
1339
|
+
/**
|
|
1340
|
+
* Represents a typed argument for an OSC message.
|
|
1341
|
+
*
|
|
1342
|
+
* @class
|
|
1343
|
+
* @private
|
|
1344
|
+
*/
|
|
1345
|
+
var Argument = class {
|
|
1346
|
+
/**
|
|
1347
|
+
* @param {string} type - The type of the argument (string, float, integer, blob, boolean).
|
|
1348
|
+
* @param {*} value - The value of the argument.
|
|
1349
|
+
*/
|
|
1350
|
+
constructor(type, value) {
|
|
1351
|
+
this.type = type;
|
|
1352
|
+
this.value = value;
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
/**
|
|
1356
|
+
* Represents an OSC message with an address and arguments.
|
|
1357
|
+
*
|
|
1358
|
+
* OSC messages consist of an address pattern (string starting with '/')
|
|
1359
|
+
* and zero or more arguments of various types.
|
|
1360
|
+
*
|
|
1361
|
+
* @class
|
|
1362
|
+
*
|
|
1363
|
+
* @example
|
|
1364
|
+
* // Create a message with constructor arguments
|
|
1365
|
+
* const msg = new Message('/test', 1, 2, 'hello');
|
|
1366
|
+
*
|
|
1367
|
+
* @example
|
|
1368
|
+
* // Create a message and append arguments
|
|
1369
|
+
* const msg = new Message('/test');
|
|
1370
|
+
* msg.append(1);
|
|
1371
|
+
* msg.append('hello');
|
|
1372
|
+
* msg.append(3.14);
|
|
1373
|
+
*/
|
|
1374
|
+
var Message = class {
|
|
1375
|
+
/**
|
|
1376
|
+
* Create an OSC Message.
|
|
1377
|
+
*
|
|
1378
|
+
* @param {string} address - The OSC address pattern (e.g., '/oscillator/frequency').
|
|
1379
|
+
* @param {...*} args - Optional arguments to include in the message.
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* const msg = new Message('/test');
|
|
1383
|
+
*
|
|
1384
|
+
* @example
|
|
1385
|
+
* const msg = new Message('/test', 1, 2, 3);
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* const msg = new Message('/synth', 'note', 60, 0.5);
|
|
1389
|
+
*/
|
|
1390
|
+
constructor(address, ...args) {
|
|
1391
|
+
this.oscType = "message";
|
|
1392
|
+
this.address = address;
|
|
1393
|
+
this.args = args;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Append an argument to the message.
|
|
1397
|
+
*
|
|
1398
|
+
* Automatically detects the type based on the JavaScript type:
|
|
1399
|
+
* - Integers are encoded as OSC integers
|
|
1400
|
+
* - Floats are encoded as OSC floats
|
|
1401
|
+
* - Strings are encoded as OSC strings
|
|
1402
|
+
* - Booleans are encoded as OSC booleans
|
|
1403
|
+
* - Buffers are encoded as OSC blobs
|
|
1404
|
+
* - Arrays are recursively appended
|
|
1405
|
+
* - Objects with a 'type' property are used as-is
|
|
1406
|
+
*
|
|
1407
|
+
* @param {*} arg - The argument to append. Can be:
|
|
1408
|
+
* - A primitive value (number, string, boolean)
|
|
1409
|
+
* - A Buffer (encoded as blob)
|
|
1410
|
+
* - An array of values (will be recursively appended)
|
|
1411
|
+
* - An object with 'type' and 'value' properties for explicit type control
|
|
1412
|
+
*
|
|
1413
|
+
* @throws {Error} If the argument type cannot be encoded.
|
|
1414
|
+
*
|
|
1415
|
+
* @example
|
|
1416
|
+
* const msg = new Message('/test');
|
|
1417
|
+
* msg.append(42); // Integer
|
|
1418
|
+
* msg.append(3.14); // Float
|
|
1419
|
+
* msg.append('hello'); // String
|
|
1420
|
+
* msg.append(true); // Boolean
|
|
1421
|
+
*
|
|
1422
|
+
* @example
|
|
1423
|
+
* // Append multiple values at once
|
|
1424
|
+
* msg.append([1, 2, 3]);
|
|
1425
|
+
*
|
|
1426
|
+
* @example
|
|
1427
|
+
* // Explicitly specify type
|
|
1428
|
+
* msg.append({ type: 'float', value: 42 });
|
|
1429
|
+
* msg.append({ type: 'blob', value: Buffer.from('data') });
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* // MIDI messages (4 bytes: port, status, data1, data2)
|
|
1433
|
+
* msg.append({ type: 'midi', value: { port: 0, status: 144, data1: 60, data2: 127 } });
|
|
1434
|
+
* msg.append({ type: 'm', value: Buffer.from([0, 144, 60, 127]) });
|
|
1435
|
+
*/
|
|
1436
|
+
append(arg) {
|
|
1437
|
+
let argOut;
|
|
1438
|
+
switch (typeof arg) {
|
|
1439
|
+
case "object":
|
|
1440
|
+
if (Buffer.isBuffer(arg)) this.args.push(arg);
|
|
1441
|
+
else if (arg instanceof Array) arg.forEach((a) => this.append(a));
|
|
1442
|
+
else if (arg.type) {
|
|
1443
|
+
if (typeTags[arg.type]) arg.type = typeTags[arg.type];
|
|
1444
|
+
this.args.push(arg);
|
|
1445
|
+
} else throw new Error(`don't know how to encode object ${arg}`);
|
|
1446
|
+
break;
|
|
1447
|
+
case "number":
|
|
1448
|
+
if (Math.floor(arg) === arg) argOut = new Argument("integer", arg);
|
|
1449
|
+
else argOut = new Argument("float", arg);
|
|
1450
|
+
break;
|
|
1451
|
+
case "string":
|
|
1452
|
+
argOut = new Argument("string", arg);
|
|
1453
|
+
break;
|
|
1454
|
+
case "boolean":
|
|
1455
|
+
argOut = new Argument("boolean", arg);
|
|
1456
|
+
break;
|
|
1457
|
+
default: throw new Error(`don't know how to encode ${arg}`);
|
|
1458
|
+
}
|
|
1459
|
+
if (argOut) this.args.push(argOut);
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
var Message_default = Message;
|
|
1463
|
+
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region node_modules/node-osc/lib/osc.mjs
|
|
1466
|
+
function padString(str) {
|
|
1467
|
+
const nullTerminated = str + "\0";
|
|
1468
|
+
const padding = (4 - node_buffer.Buffer.byteLength(nullTerminated) % 4) % 4;
|
|
1469
|
+
return nullTerminated + "\0".repeat(padding);
|
|
1470
|
+
}
|
|
1471
|
+
function readString(buffer, offset) {
|
|
1472
|
+
let end = offset;
|
|
1473
|
+
while (end < buffer.length && buffer[end] !== 0) end++;
|
|
1474
|
+
if (end >= buffer.length) throw new Error("Malformed Packet: Missing null terminator for string");
|
|
1475
|
+
return {
|
|
1476
|
+
value: buffer.subarray(offset, end).toString("utf8"),
|
|
1477
|
+
offset: offset + Math.ceil((end - offset + 1) / 4) * 4
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function writeInt32(value) {
|
|
1481
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
1482
|
+
buffer.writeInt32BE(value, 0);
|
|
1483
|
+
return buffer;
|
|
1484
|
+
}
|
|
1485
|
+
function readInt32(buffer, offset) {
|
|
1486
|
+
if (offset + 4 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for int32");
|
|
1487
|
+
return {
|
|
1488
|
+
value: buffer.readInt32BE(offset),
|
|
1489
|
+
offset: offset + 4
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function writeFloat32(value) {
|
|
1493
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
1494
|
+
buffer.writeFloatBE(value, 0);
|
|
1495
|
+
return buffer;
|
|
1496
|
+
}
|
|
1497
|
+
function readFloat32(buffer, offset) {
|
|
1498
|
+
if (offset + 4 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for float32");
|
|
1499
|
+
return {
|
|
1500
|
+
value: buffer.readFloatBE(offset),
|
|
1501
|
+
offset: offset + 4
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function writeBlob(value) {
|
|
1505
|
+
const length = value.length;
|
|
1506
|
+
const lengthBuffer = writeInt32(length);
|
|
1507
|
+
const padding = 4 - length % 4;
|
|
1508
|
+
const paddingBuffer = node_buffer.Buffer.alloc(padding === 4 ? 0 : padding);
|
|
1509
|
+
return node_buffer.Buffer.concat([
|
|
1510
|
+
lengthBuffer,
|
|
1511
|
+
value,
|
|
1512
|
+
paddingBuffer
|
|
1513
|
+
]);
|
|
1514
|
+
}
|
|
1515
|
+
function readBlob(buffer, offset) {
|
|
1516
|
+
const lengthResult = readInt32(buffer, offset);
|
|
1517
|
+
const length = lengthResult.value;
|
|
1518
|
+
if (length < 0) throw new Error("Malformed Packet: Invalid blob length");
|
|
1519
|
+
if (lengthResult.offset + length > buffer.length) throw new Error("Malformed Packet: Not enough bytes for blob");
|
|
1520
|
+
const data = buffer.subarray(lengthResult.offset, lengthResult.offset + length);
|
|
1521
|
+
const padding = 4 - length % 4;
|
|
1522
|
+
const nextOffset = lengthResult.offset + length + (padding === 4 ? 0 : padding);
|
|
1523
|
+
if (nextOffset > buffer.length) throw new Error("Malformed Packet: Not enough bytes for blob padding");
|
|
1524
|
+
return {
|
|
1525
|
+
value: data,
|
|
1526
|
+
offset: nextOffset
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
function writeTimeTag(value) {
|
|
1530
|
+
const buffer = node_buffer.Buffer.alloc(8);
|
|
1531
|
+
if (value === 0 || value === null || value === void 0) {
|
|
1532
|
+
buffer.writeUInt32BE(0, 0);
|
|
1533
|
+
buffer.writeUInt32BE(1, 4);
|
|
1534
|
+
} else if (typeof value === "number") {
|
|
1535
|
+
const seconds = Math.floor(value);
|
|
1536
|
+
const fraction = Math.floor((value - seconds) * 4294967296);
|
|
1537
|
+
buffer.writeUInt32BE(seconds + 2208988800, 0);
|
|
1538
|
+
buffer.writeUInt32BE(fraction, 4);
|
|
1539
|
+
} else {
|
|
1540
|
+
buffer.writeUInt32BE(0, 0);
|
|
1541
|
+
buffer.writeUInt32BE(1, 4);
|
|
1542
|
+
}
|
|
1543
|
+
return buffer;
|
|
1544
|
+
}
|
|
1545
|
+
function readTimeTag(buffer, offset) {
|
|
1546
|
+
if (offset + 8 > buffer.length) throw new Error("Malformed Packet: Not enough bytes for timetag");
|
|
1547
|
+
const seconds = buffer.readUInt32BE(offset);
|
|
1548
|
+
const fraction = buffer.readUInt32BE(offset + 4);
|
|
1549
|
+
let value;
|
|
1550
|
+
if (seconds === 0 && fraction === 1) value = 0;
|
|
1551
|
+
else value = seconds - 2208988800 + fraction / 4294967296;
|
|
1552
|
+
return {
|
|
1553
|
+
value,
|
|
1554
|
+
offset: offset + 8
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
function writeMidi(value) {
|
|
1558
|
+
const buffer = node_buffer.Buffer.alloc(4);
|
|
1559
|
+
if (node_buffer.Buffer.isBuffer(value)) {
|
|
1560
|
+
if (value.length !== 4) throw new Error("MIDI message must be exactly 4 bytes");
|
|
1561
|
+
value.copy(buffer);
|
|
1562
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1563
|
+
buffer.writeUInt8(value.port || 0, 0);
|
|
1564
|
+
buffer.writeUInt8(value.status || 0, 1);
|
|
1565
|
+
buffer.writeUInt8(value.data1 || 0, 2);
|
|
1566
|
+
buffer.writeUInt8(value.data2 || 0, 3);
|
|
1567
|
+
} else throw new Error("MIDI value must be a 4-byte Buffer or object with port, status, data1, data2 properties");
|
|
1568
|
+
return buffer;
|
|
1569
|
+
}
|
|
1570
|
+
function readMidi(buffer, offset) {
|
|
1571
|
+
if (offset + 4 > buffer.length) throw new Error("Not enough bytes for MIDI message");
|
|
1572
|
+
return {
|
|
1573
|
+
value: buffer.subarray(offset, offset + 4),
|
|
1574
|
+
offset: offset + 4
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
function encodeArgument(arg) {
|
|
1578
|
+
if (typeof arg === "object" && arg.type && arg.value !== void 0) switch (arg.type) {
|
|
1579
|
+
case "i":
|
|
1580
|
+
case "integer": return {
|
|
1581
|
+
tag: "i",
|
|
1582
|
+
data: writeInt32(arg.value)
|
|
1583
|
+
};
|
|
1584
|
+
case "f":
|
|
1585
|
+
case "float": return {
|
|
1586
|
+
tag: "f",
|
|
1587
|
+
data: writeFloat32(arg.value)
|
|
1588
|
+
};
|
|
1589
|
+
case "s":
|
|
1590
|
+
case "string": return {
|
|
1591
|
+
tag: "s",
|
|
1592
|
+
data: node_buffer.Buffer.from(padString(arg.value))
|
|
1593
|
+
};
|
|
1594
|
+
case "b":
|
|
1595
|
+
case "blob": return {
|
|
1596
|
+
tag: "b",
|
|
1597
|
+
data: writeBlob(arg.value)
|
|
1598
|
+
};
|
|
1599
|
+
case "d":
|
|
1600
|
+
case "double": return {
|
|
1601
|
+
tag: "f",
|
|
1602
|
+
data: writeFloat32(arg.value)
|
|
1603
|
+
};
|
|
1604
|
+
case "T": return {
|
|
1605
|
+
tag: "T",
|
|
1606
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1607
|
+
};
|
|
1608
|
+
case "F": return {
|
|
1609
|
+
tag: "F",
|
|
1610
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1611
|
+
};
|
|
1612
|
+
case "boolean": return arg.value ? {
|
|
1613
|
+
tag: "T",
|
|
1614
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1615
|
+
} : {
|
|
1616
|
+
tag: "F",
|
|
1617
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1618
|
+
};
|
|
1619
|
+
case "m":
|
|
1620
|
+
case "midi": return {
|
|
1621
|
+
tag: "m",
|
|
1622
|
+
data: writeMidi(arg.value)
|
|
1623
|
+
};
|
|
1624
|
+
default: throw new Error(`Unknown argument type: ${arg.type}`);
|
|
1625
|
+
}
|
|
1626
|
+
switch (typeof arg) {
|
|
1627
|
+
case "number": if (Number.isInteger(arg)) return {
|
|
1628
|
+
tag: "i",
|
|
1629
|
+
data: writeInt32(arg)
|
|
1630
|
+
};
|
|
1631
|
+
else return {
|
|
1632
|
+
tag: "f",
|
|
1633
|
+
data: writeFloat32(arg)
|
|
1634
|
+
};
|
|
1635
|
+
case "string": return {
|
|
1636
|
+
tag: "s",
|
|
1637
|
+
data: node_buffer.Buffer.from(padString(arg))
|
|
1638
|
+
};
|
|
1639
|
+
case "boolean": return arg ? {
|
|
1640
|
+
tag: "T",
|
|
1641
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1642
|
+
} : {
|
|
1643
|
+
tag: "F",
|
|
1644
|
+
data: node_buffer.Buffer.alloc(0)
|
|
1645
|
+
};
|
|
1646
|
+
default:
|
|
1647
|
+
if (node_buffer.Buffer.isBuffer(arg)) return {
|
|
1648
|
+
tag: "b",
|
|
1649
|
+
data: writeBlob(arg)
|
|
1650
|
+
};
|
|
1651
|
+
throw new Error(`Don't know how to encode argument: ${arg}`);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
function decodeArgument(tag, buffer, offset) {
|
|
1655
|
+
switch (tag) {
|
|
1656
|
+
case "i": return readInt32(buffer, offset);
|
|
1657
|
+
case "f": return readFloat32(buffer, offset);
|
|
1658
|
+
case "s": return readString(buffer, offset);
|
|
1659
|
+
case "b": return readBlob(buffer, offset);
|
|
1660
|
+
case "T": return {
|
|
1661
|
+
value: true,
|
|
1662
|
+
offset
|
|
1663
|
+
};
|
|
1664
|
+
case "F": return {
|
|
1665
|
+
value: false,
|
|
1666
|
+
offset
|
|
1667
|
+
};
|
|
1668
|
+
case "N": return {
|
|
1669
|
+
value: null,
|
|
1670
|
+
offset
|
|
1671
|
+
};
|
|
1672
|
+
case "m": return readMidi(buffer, offset);
|
|
1673
|
+
default: throw new Error(`I don't understand the argument code ${tag}`);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Encode an OSC message or bundle to a Buffer.
|
|
1678
|
+
*
|
|
1679
|
+
* This low-level function converts OSC messages and bundles into binary format
|
|
1680
|
+
* for transmission or storage. Useful for sending OSC over custom transports
|
|
1681
|
+
* (WebSocket, TCP, HTTP), storing to files, or implementing custom OSC routers.
|
|
1682
|
+
*
|
|
1683
|
+
* @param {Object} message - OSC message or bundle object with oscType property
|
|
1684
|
+
* @returns {Buffer} The encoded OSC data ready for transmission
|
|
1685
|
+
*
|
|
1686
|
+
* @example
|
|
1687
|
+
* // Encode a message
|
|
1688
|
+
* import { Message, encode } from 'node-osc';
|
|
1689
|
+
*
|
|
1690
|
+
* const message = new Message('/oscillator/frequency', 440);
|
|
1691
|
+
* const buffer = encode(message);
|
|
1692
|
+
* console.log('Encoded bytes:', buffer.length);
|
|
1693
|
+
*
|
|
1694
|
+
* @example
|
|
1695
|
+
* // Encode a bundle
|
|
1696
|
+
* import { Bundle, encode } from 'node-osc';
|
|
1697
|
+
*
|
|
1698
|
+
* const bundle = new Bundle(['/one', 1], ['/two', 2]);
|
|
1699
|
+
* const buffer = encode(bundle);
|
|
1700
|
+
*
|
|
1701
|
+
* @example
|
|
1702
|
+
* // Send over WebSocket
|
|
1703
|
+
* const buffer = encode(message);
|
|
1704
|
+
* websocket.send(buffer);
|
|
1705
|
+
*/
|
|
1706
|
+
function encode(message) {
|
|
1707
|
+
if (message.oscType === "bundle") return encodeBundleToBuffer(message);
|
|
1708
|
+
else return encodeMessageToBuffer(message);
|
|
1709
|
+
}
|
|
1710
|
+
function encodeMessageToBuffer(message) {
|
|
1711
|
+
const address = padString(message.address);
|
|
1712
|
+
const addressBuffer = node_buffer.Buffer.from(address);
|
|
1713
|
+
const encodedArgs = message.args.map(encodeArgument);
|
|
1714
|
+
const typeTags$1 = "," + encodedArgs.map((arg) => arg.tag).join("");
|
|
1715
|
+
const typeTagsBuffer = node_buffer.Buffer.from(padString(typeTags$1));
|
|
1716
|
+
const argumentBuffers = encodedArgs.map((arg) => arg.data);
|
|
1717
|
+
return node_buffer.Buffer.concat([
|
|
1718
|
+
addressBuffer,
|
|
1719
|
+
typeTagsBuffer,
|
|
1720
|
+
...argumentBuffers
|
|
1721
|
+
]);
|
|
1722
|
+
}
|
|
1723
|
+
function encodeBundleToBuffer(bundle) {
|
|
1724
|
+
const bundleString = padString("#bundle");
|
|
1725
|
+
const bundleStringBuffer = node_buffer.Buffer.from(bundleString);
|
|
1726
|
+
const timetagBuffer = writeTimeTag(bundle.timetag);
|
|
1727
|
+
const elementBuffers = bundle.elements.map((element) => {
|
|
1728
|
+
let elementBuffer;
|
|
1729
|
+
if (element.oscType === "bundle") elementBuffer = encodeBundleToBuffer(element);
|
|
1730
|
+
else elementBuffer = encodeMessageToBuffer(element);
|
|
1731
|
+
const sizeBuffer = writeInt32(elementBuffer.length);
|
|
1732
|
+
return node_buffer.Buffer.concat([sizeBuffer, elementBuffer]);
|
|
1733
|
+
});
|
|
1734
|
+
return node_buffer.Buffer.concat([
|
|
1735
|
+
bundleStringBuffer,
|
|
1736
|
+
timetagBuffer,
|
|
1737
|
+
...elementBuffers
|
|
1738
|
+
]);
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Decode a Buffer containing OSC data into a message or bundle object.
|
|
1742
|
+
*
|
|
1743
|
+
* This low-level function parses binary OSC data back into JavaScript objects.
|
|
1744
|
+
* Useful for receiving OSC over custom transports, reading from files,
|
|
1745
|
+
* or implementing custom OSC routers.
|
|
1746
|
+
*
|
|
1747
|
+
* @param {Buffer} buffer - The Buffer containing OSC data
|
|
1748
|
+
* @returns {Object} The decoded OSC message or bundle. Messages have
|
|
1749
|
+
* {oscType: 'message', address: string, args: Array}, bundles have
|
|
1750
|
+
* {oscType: 'bundle', timetag: number, elements: Array}
|
|
1751
|
+
* @throws {Error} If the buffer contains malformed OSC data
|
|
1752
|
+
*
|
|
1753
|
+
* @example
|
|
1754
|
+
* // Decode received data
|
|
1755
|
+
* import { decode } from 'node-osc';
|
|
1756
|
+
*
|
|
1757
|
+
* const decoded = decode(buffer);
|
|
1758
|
+
* if (decoded.oscType === 'message') {
|
|
1759
|
+
* console.log('Address:', decoded.address);
|
|
1760
|
+
* console.log('Arguments:', decoded.args);
|
|
1761
|
+
* }
|
|
1762
|
+
*
|
|
1763
|
+
* @example
|
|
1764
|
+
* // Round-trip encode/decode
|
|
1765
|
+
* import { Message, encode, decode } from 'node-osc';
|
|
1766
|
+
*
|
|
1767
|
+
* const original = new Message('/test', 42, 'hello');
|
|
1768
|
+
* const buffer = encode(original);
|
|
1769
|
+
* const decoded = decode(buffer);
|
|
1770
|
+
* console.log(decoded.address); // '/test'
|
|
1771
|
+
*/
|
|
1772
|
+
function decode(buffer) {
|
|
1773
|
+
if (buffer.length >= 8 && buffer.subarray(0, 8).toString() === "#bundle\0") return decodeBundleFromBuffer(buffer);
|
|
1774
|
+
else return decodeMessageFromBuffer(buffer);
|
|
1775
|
+
}
|
|
1776
|
+
function decodeMessageFromBuffer(buffer) {
|
|
1777
|
+
let offset = 0;
|
|
1778
|
+
const addressResult = readString(buffer, offset);
|
|
1779
|
+
const address = addressResult.value;
|
|
1780
|
+
offset = addressResult.offset;
|
|
1781
|
+
const typeTagsResult = readString(buffer, offset);
|
|
1782
|
+
const typeTags$1 = typeTagsResult.value;
|
|
1783
|
+
offset = typeTagsResult.offset;
|
|
1784
|
+
if (!typeTags$1.startsWith(",")) throw new Error("Malformed Packet");
|
|
1785
|
+
const tags = typeTags$1.slice(1);
|
|
1786
|
+
const args = [];
|
|
1787
|
+
for (const tag of tags) {
|
|
1788
|
+
const argResult = decodeArgument(tag, buffer, offset);
|
|
1789
|
+
args.push({ value: argResult.value });
|
|
1790
|
+
offset = argResult.offset;
|
|
1791
|
+
}
|
|
1792
|
+
return {
|
|
1793
|
+
oscType: "message",
|
|
1794
|
+
address,
|
|
1795
|
+
args
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
function decodeBundleFromBuffer(buffer) {
|
|
1799
|
+
let offset = 8;
|
|
1800
|
+
const timetagResult = readTimeTag(buffer, offset);
|
|
1801
|
+
const timetag = timetagResult.value;
|
|
1802
|
+
offset = timetagResult.offset;
|
|
1803
|
+
const elements = [];
|
|
1804
|
+
while (offset < buffer.length) {
|
|
1805
|
+
const sizeResult = readInt32(buffer, offset);
|
|
1806
|
+
const size = sizeResult.value;
|
|
1807
|
+
offset = sizeResult.offset;
|
|
1808
|
+
if (size <= 0 || offset + size > buffer.length) throw new Error("Malformed Packet");
|
|
1809
|
+
const element = decode(buffer.subarray(offset, offset + size));
|
|
1810
|
+
elements.push(element);
|
|
1811
|
+
offset += size;
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
oscType: "bundle",
|
|
1815
|
+
timetag,
|
|
1816
|
+
elements
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
//#endregion
|
|
1821
|
+
//#region src/infrastructure/mappers/OscCodec.ts
|
|
1822
|
+
/**
|
|
1823
|
+
* Handles encoding and decoding of X32 OSC messages.
|
|
1824
|
+
*/
|
|
1825
|
+
var OscCodec = class {
|
|
1826
|
+
constructor(schemaRegistry) {
|
|
1827
|
+
this.schemaRegistry = schemaRegistry;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Decodes a binary OSC message into a packet object.
|
|
1831
|
+
* @param msg - Raw UDP buffer.
|
|
1832
|
+
* @returns Decoded OscPacket.
|
|
1833
|
+
*/
|
|
1834
|
+
decode(msg) {
|
|
1835
|
+
return decode(msg);
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Encodes an address and arguments into a binary OSC message.
|
|
1839
|
+
* @param address - OSC address pattern.
|
|
1840
|
+
* @param args - Array of arguments.
|
|
1841
|
+
* @returns Binary buffer.
|
|
1842
|
+
*/
|
|
1843
|
+
encode(address, args) {
|
|
1844
|
+
return encode(this.createMessage(address, args));
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Creates a typed node-osc Message based on X32 schema.
|
|
1848
|
+
* @param address - Target address.
|
|
1849
|
+
* @param args - Untyped arguments.
|
|
1850
|
+
* @returns Typed Message.
|
|
1851
|
+
*/
|
|
1852
|
+
createMessage(address, args) {
|
|
1853
|
+
return new Message_default(address, ...args.map((arg) => {
|
|
1854
|
+
if (typeof arg === "object" && arg !== null && "type" in arg && "value" in arg) return arg;
|
|
1855
|
+
if (Buffer.isBuffer(arg)) return arg;
|
|
1856
|
+
const node = this.schemaRegistry.getNode(address);
|
|
1857
|
+
if (node) {
|
|
1858
|
+
if (node.type === "f" && typeof arg === "number") return {
|
|
1859
|
+
type: "f",
|
|
1860
|
+
value: arg
|
|
1861
|
+
};
|
|
1862
|
+
if (node.type === "i" && typeof arg === "number") return {
|
|
1863
|
+
type: "i",
|
|
1864
|
+
value: Math.round(arg)
|
|
1865
|
+
};
|
|
1866
|
+
if (node.type === "s" && typeof arg === "string") return {
|
|
1867
|
+
type: "s",
|
|
1868
|
+
value: arg
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
return arg;
|
|
1872
|
+
}));
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
//#endregion
|
|
1877
|
+
//#region src/domain/services/SchemaFactory.ts
|
|
1878
|
+
/**
|
|
1879
|
+
* Factory service responsible for constructing the X32 OSC Schema.
|
|
1880
|
+
* Encapsulates all the logic for generating channel strips, routing blocks, etc.
|
|
1881
|
+
*/
|
|
1882
|
+
var SchemaFactory = class {
|
|
1883
|
+
/**
|
|
1884
|
+
* Builds the complete X32 OSC Schema.
|
|
1885
|
+
* @returns The constructed schema map.
|
|
1886
|
+
*/
|
|
1887
|
+
createSchema() {
|
|
1888
|
+
const schema = {
|
|
1889
|
+
...this.generateNodes(32, "ch"),
|
|
1890
|
+
...this.generateNodes(16, "bus"),
|
|
1891
|
+
...this.generateNodes(8, "dca"),
|
|
1892
|
+
...this.generateNodes(8, "auxin"),
|
|
1893
|
+
...this.generateNodes(8, "fxrtn"),
|
|
1894
|
+
...this.generateNodes(6, "mtx"),
|
|
1895
|
+
...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/fx/${i + 1}/type`, this.node("i", 0)])),
|
|
1896
|
+
...this.generateRange(8, "/fx", "/type", "i", 0),
|
|
1897
|
+
...this.generateRange(8, "/fx", "/source/l", "i", 0),
|
|
1898
|
+
...this.generateRange(8, "/fx", "/source/r", "i", 0),
|
|
1899
|
+
...Object.fromEntries(Array.from({ length: 8 }, (_, slot) => Object.entries(this.generateRange(64, `/fx/${slot + 1}/par`, "", "f", 0, 2, 1))).flat()),
|
|
1900
|
+
...this.generateRange(128, "/headamp", "/gain", "f", 0, 3, 0),
|
|
1901
|
+
...this.generateRange(128, "/headamp", "/phantom", "i", 0, 3, 0),
|
|
1902
|
+
...this.generateRange(6, "/config/mute", "", "i", 0),
|
|
1903
|
+
...this.generateRange(80, "/-stat/solosw", "", "i", 0),
|
|
1904
|
+
"/-stat/selidx": this.node("i", 0),
|
|
1905
|
+
"/-stat/sendsonfader": this.node("i", 0),
|
|
1906
|
+
"/-stat/bussendbank": this.node("i", 0),
|
|
1907
|
+
"/-stat/keysolo": this.node("i", 0),
|
|
1908
|
+
"/-stat/screen/screen": this.node("i", 0),
|
|
1909
|
+
"/-stat/screen/CHAN/page": this.node("i", 0),
|
|
1910
|
+
"/-stat/screen/METER/page": this.node("i", 0),
|
|
1911
|
+
"/-stat/screen/ROUTE/page": this.node("i", 0),
|
|
1912
|
+
"/-stat/screen/SETUP/page": this.node("i", 0),
|
|
1913
|
+
"/-stat/screen/LIBRARY/page": this.node("i", 0),
|
|
1914
|
+
"/-stat/screen/FX/page": this.node("i", 0),
|
|
1915
|
+
"/-stat/screen/MON/page": this.node("i", 0),
|
|
1916
|
+
"/-stat/screen/USB/page": this.node("i", 0),
|
|
1917
|
+
"/-stat/screen/SCENE/page": this.node("i", 0),
|
|
1918
|
+
"/-stat/screen/ASSIGN/page": this.node("i", 0),
|
|
1919
|
+
"/-stat/talk/A": this.node("i", 0),
|
|
1920
|
+
"/-stat/talk/B": this.node("i", 0),
|
|
1921
|
+
"/-stat/osc/on": this.node("i", 0),
|
|
1922
|
+
"/-prefs/autosel": this.node("i", 1),
|
|
1923
|
+
"/-action/setrtasrc": this.node("i", 0),
|
|
1924
|
+
"/-action/playtrack": this.node("i", 0),
|
|
1925
|
+
"/-action/goscene": this.node("i", 0),
|
|
1926
|
+
"/-action/setscene": this.node("i", 0),
|
|
1927
|
+
"/config/routing/AES50A/1-8": this.node("i", 0),
|
|
1928
|
+
"/config/routing/AES50B/1-8": this.node("i", 0),
|
|
1929
|
+
"/config/routing/CARD/1-8": this.node("i", 0),
|
|
1930
|
+
"/config/routing/OUT/1-4": this.node("i", 0),
|
|
1931
|
+
"/-prefs/invertmutes": this.node("i", 0),
|
|
1932
|
+
...this.generateChannelStrip("/main/st", 6, false),
|
|
1933
|
+
...this.generateChannelStrip("/main/m", 6, false),
|
|
1934
|
+
...Object.fromEntries(Array.from({ length: 16 }, (_, i) => [`/config/chlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
|
|
1935
|
+
...Object.fromEntries(Array.from({ length: 8 }, (_, i) => [`/config/buslink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
|
|
1936
|
+
...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/auxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
|
|
1937
|
+
...Object.fromEntries(Array.from({ length: 4 }, (_, i) => [`/config/fxlink/${i * 2 + 1}-${i * 2 + 2}`, this.node("i", 0)])),
|
|
1938
|
+
...this.generateRange(32, "/config/userrout/in", "", "i", 0),
|
|
1939
|
+
...this.generateRange(48, "/config/userrout/out", "", "i", 0),
|
|
1940
|
+
"/config/solo/level": this.node("f", 0),
|
|
1941
|
+
"/config/solo/source": this.node("i", 0),
|
|
1942
|
+
"/config/solo/sourcetrim": this.node("f", 0),
|
|
1943
|
+
"/config/solo/exclusive": this.node("i", 0),
|
|
1944
|
+
"/config/solo/dim": this.node("i", 0),
|
|
1945
|
+
"/config/solo/dimpfl": this.node("i", 0),
|
|
1946
|
+
"/config/solo/dimatt": this.node("f", 0),
|
|
1947
|
+
"/config/solo/mono": this.node("i", 0),
|
|
1948
|
+
"/config/solo/chmode": this.node("i", 0),
|
|
1949
|
+
"/config/solo/busmode": this.node("i", 0),
|
|
1950
|
+
"/config/solo/dcamode": this.node("i", 0),
|
|
1951
|
+
"/config/solo/masterctrl": this.node("i", 0),
|
|
1952
|
+
"/config/solo/delay": this.node("i", 0),
|
|
1953
|
+
"/config/solo/delaytime": this.node("f", 0),
|
|
1954
|
+
"/config/solo/followsel": this.node("i", 0),
|
|
1955
|
+
"/config/solo/followsolo": this.node("i", 0),
|
|
1956
|
+
"/config/talk/enable": this.node("i", 0),
|
|
1957
|
+
"/config/talk/source": this.node("i", 0),
|
|
1958
|
+
"/config/talk/A/level": this.node("f", 0),
|
|
1959
|
+
"/config/talk/B/level": this.node("f", 0),
|
|
1960
|
+
"/config/talk/A/dim": this.node("i", 0),
|
|
1961
|
+
"/config/talk/B/dim": this.node("i", 0),
|
|
1962
|
+
"/config/talk/A/latch": this.node("i", 0),
|
|
1963
|
+
"/config/talk/B/latch": this.node("i", 0),
|
|
1964
|
+
"/config/talk/A/destmap": this.node("i", 0),
|
|
1965
|
+
"/config/talk/B/destmap": this.node("i", 0),
|
|
1966
|
+
"/config/osc/on": this.node("i", 0),
|
|
1967
|
+
"/config/osc/type": this.node("i", 0),
|
|
1968
|
+
"/config/osc/fsel": this.node("i", 0),
|
|
1969
|
+
"/config/osc/f1": this.node("f", .5),
|
|
1970
|
+
"/config/osc/f2": this.node("f", .5),
|
|
1971
|
+
"/config/osc/level": this.node("f", 0),
|
|
1972
|
+
"/config/osc/dest": this.node("i", 0),
|
|
1973
|
+
...this.generateOutputs("main", 16),
|
|
1974
|
+
...this.generateOutputs("aux", 6),
|
|
1975
|
+
...this.generateOutputs("p16", 16),
|
|
1976
|
+
...this.generateOutputs("aes", 2),
|
|
1977
|
+
...this.generateOutputs("rec", 2)
|
|
1978
|
+
};
|
|
1979
|
+
Object.keys(STATIC_RESPONSES_DATA).forEach((key) => {
|
|
1980
|
+
if (key.startsWith("/-") || key.startsWith("/stat")) schema[key] = this.node("i", 0);
|
|
1981
|
+
});
|
|
1982
|
+
[
|
|
1983
|
+
"/config/routing/IN/1-8",
|
|
1984
|
+
"/config/routing/IN/9-16",
|
|
1985
|
+
"/config/routing/IN/17-24",
|
|
1986
|
+
"/config/routing/IN/25-32",
|
|
1987
|
+
"/config/routing/AUX/1-4",
|
|
1988
|
+
"/config/routing/OUT/1-4",
|
|
1989
|
+
"/config/routing/OUT/5-8",
|
|
1990
|
+
"/config/routing/OUT/9-12",
|
|
1991
|
+
"/config/routing/OUT/13-16",
|
|
1992
|
+
"/config/routing/P16/1-8",
|
|
1993
|
+
"/config/routing/P16/9-16",
|
|
1994
|
+
"/config/routing/CARD/1-8",
|
|
1995
|
+
"/config/routing/CARD/9-16",
|
|
1996
|
+
"/config/routing/CARD/17-24",
|
|
1997
|
+
"/config/routing/CARD/25-32",
|
|
1998
|
+
"/config/routing/AES50A/1-8",
|
|
1999
|
+
"/config/routing/AES50A/9-16",
|
|
2000
|
+
"/config/routing/AES50A/17-24",
|
|
2001
|
+
"/config/routing/AES50A/25-32",
|
|
2002
|
+
"/config/routing/AES50A/33-40",
|
|
2003
|
+
"/config/routing/AES50A/41-48",
|
|
2004
|
+
"/config/routing/AES50B/1-8",
|
|
2005
|
+
"/config/routing/AES50B/9-16",
|
|
2006
|
+
"/config/routing/AES50B/17-24",
|
|
2007
|
+
"/config/routing/AES50B/25-32",
|
|
2008
|
+
"/config/routing/AES50B/33-40",
|
|
2009
|
+
"/config/routing/AES50B/41-48",
|
|
2010
|
+
"/config/routing/PLAY/1-8",
|
|
2011
|
+
"/config/routing/PLAY/9-16",
|
|
2012
|
+
"/config/routing/PLAY/17-24",
|
|
2013
|
+
"/config/routing/PLAY/25-32"
|
|
2014
|
+
].forEach((path) => {
|
|
2015
|
+
schema[path] = this.node("i", 0);
|
|
2016
|
+
});
|
|
2017
|
+
return schema;
|
|
2018
|
+
}
|
|
2019
|
+
node(type, def) {
|
|
2020
|
+
return new X32Node(type, def);
|
|
2021
|
+
}
|
|
2022
|
+
generateChannelStrip(base, eqBands = 4, hasPreamp = false) {
|
|
2023
|
+
const nodes = {};
|
|
2024
|
+
nodes[`${base}/config/name`] = this.node("s", base.split("/").pop()?.toUpperCase() || "");
|
|
2025
|
+
nodes[`${base}/config/icon`] = this.node("i", 1);
|
|
2026
|
+
nodes[`${base}/config/color`] = this.node("i", 1);
|
|
2027
|
+
nodes[`${base}/config/source`] = this.node("i", 1);
|
|
2028
|
+
nodes[`${base}/mix/fader`] = this.node("f", .75);
|
|
2029
|
+
nodes[`${base}/mix/on`] = this.node("i", 0);
|
|
2030
|
+
nodes[`${base}/mix/pan`] = this.node("f", .5);
|
|
2031
|
+
nodes[`${base}/mix/mono`] = this.node("i", 0);
|
|
2032
|
+
nodes[`${base}/mix/mlevel`] = this.node("f", 0);
|
|
2033
|
+
nodes[`${base}/mix/st`] = this.node("i", 1);
|
|
2034
|
+
if (hasPreamp) {
|
|
2035
|
+
nodes[`${base}/preamp/trim`] = this.node("f", .5);
|
|
2036
|
+
nodes[`${base}/preamp/hpon`] = this.node("i", 0);
|
|
2037
|
+
nodes[`${base}/preamp/hpf`] = this.node("f", 0);
|
|
2038
|
+
nodes[`${base}/preamp/phantom`] = this.node("i", 0);
|
|
2039
|
+
nodes[`${base}/preamp/rtnsw`] = this.node("i", 0);
|
|
2040
|
+
nodes[`${base}/preamp/invert`] = this.node("i", 0);
|
|
2041
|
+
}
|
|
2042
|
+
nodes[`${base}/delay/on`] = this.node("i", 0);
|
|
2043
|
+
nodes[`${base}/delay/time`] = this.node("f", 0);
|
|
2044
|
+
nodes[`${base}/insert/on`] = this.node("i", 0);
|
|
2045
|
+
nodes[`${base}/insert/pos`] = this.node("i", 0);
|
|
2046
|
+
nodes[`${base}/insert/sel`] = this.node("i", 0);
|
|
2047
|
+
nodes[`${base}/gate/on`] = this.node("i", 0);
|
|
2048
|
+
nodes[`${base}/gate/mode`] = this.node("i", 0);
|
|
2049
|
+
nodes[`${base}/gate/thr`] = this.node("f", 0);
|
|
2050
|
+
nodes[`${base}/gate/range`] = this.node("f", 0);
|
|
2051
|
+
nodes[`${base}/gate/attack`] = this.node("f", 0);
|
|
2052
|
+
nodes[`${base}/gate/hold`] = this.node("f", 0);
|
|
2053
|
+
nodes[`${base}/gate/release`] = this.node("f", 0);
|
|
2054
|
+
nodes[`${base}/gate/keysrc`] = this.node("i", 0);
|
|
2055
|
+
nodes[`${base}/gate/filter/on`] = this.node("i", 0);
|
|
2056
|
+
nodes[`${base}/gate/filter/type`] = this.node("i", 0);
|
|
2057
|
+
nodes[`${base}/gate/filter/f`] = this.node("f", .5);
|
|
2058
|
+
nodes[`${base}/dyn/on`] = this.node("i", 0);
|
|
2059
|
+
nodes[`${base}/dyn/mode`] = this.node("i", 0);
|
|
2060
|
+
nodes[`${base}/dyn/pos`] = this.node("i", 0);
|
|
2061
|
+
nodes[`${base}/dyn/det`] = this.node("i", 0);
|
|
2062
|
+
nodes[`${base}/dyn/env`] = this.node("i", 0);
|
|
2063
|
+
nodes[`${base}/dyn/thr`] = this.node("f", 0);
|
|
2064
|
+
nodes[`${base}/dyn/ratio`] = this.node("i", 0);
|
|
2065
|
+
nodes[`${base}/dyn/knee`] = this.node("f", 0);
|
|
2066
|
+
nodes[`${base}/dyn/mgain`] = this.node("f", 0);
|
|
2067
|
+
nodes[`${base}/dyn/attack`] = this.node("f", 0);
|
|
2068
|
+
nodes[`${base}/dyn/hold`] = this.node("f", 0);
|
|
2069
|
+
nodes[`${base}/dyn/release`] = this.node("f", 0);
|
|
2070
|
+
nodes[`${base}/dyn/mix`] = this.node("f", 1);
|
|
2071
|
+
nodes[`${base}/dyn/auto`] = this.node("i", 0);
|
|
2072
|
+
nodes[`${base}/dyn/keysrc`] = this.node("i", 0);
|
|
2073
|
+
nodes[`${base}/dyn/filter/on`] = this.node("i", 0);
|
|
2074
|
+
nodes[`${base}/dyn/filter/type`] = this.node("i", 0);
|
|
2075
|
+
nodes[`${base}/dyn/filter/f`] = this.node("f", .5);
|
|
2076
|
+
nodes[`${base}/eq/on`] = this.node("i", 0);
|
|
2077
|
+
nodes[`${base}/eq/mode`] = this.node("i", 0);
|
|
2078
|
+
for (let b = 1; b <= eqBands; b++) {
|
|
2079
|
+
nodes[`${base}/eq/${b}/type`] = this.node("i", 0);
|
|
2080
|
+
nodes[`${base}/eq/${b}/f`] = this.node("f", .5);
|
|
2081
|
+
nodes[`${base}/eq/${b}/g`] = this.node("f", .5);
|
|
2082
|
+
nodes[`${base}/eq/${b}/q`] = this.node("f", .5);
|
|
2083
|
+
}
|
|
2084
|
+
nodes[`${base}/grp/dca`] = this.node("i", 0);
|
|
2085
|
+
nodes[`${base}/grp/mute`] = this.node("i", 0);
|
|
2086
|
+
return nodes;
|
|
2087
|
+
}
|
|
2088
|
+
generateNodes(count, prefix) {
|
|
2089
|
+
const nodes = {};
|
|
2090
|
+
const isChannelOrAux = prefix === "ch" || prefix === "auxin";
|
|
2091
|
+
const eqBands = prefix === "bus" || prefix === "mtx" ? 6 : 4;
|
|
2092
|
+
for (let i = 1; i <= count; i++) {
|
|
2093
|
+
const padId = i.toString().padStart(2, "0");
|
|
2094
|
+
[i.toString(), padId].forEach((id) => {
|
|
2095
|
+
const base = `/${prefix}/${id}`;
|
|
2096
|
+
if (prefix === "dca") {
|
|
2097
|
+
nodes[`${base}/config/name`] = this.node("s", `DCA ${id}`);
|
|
2098
|
+
nodes[`${base}/config/color`] = this.node("i", 1);
|
|
2099
|
+
nodes[`${base}/fader`] = this.node("f", .75);
|
|
2100
|
+
nodes[`${base}/on`] = this.node("i", 0);
|
|
2101
|
+
} else {
|
|
2102
|
+
Object.assign(nodes, this.generateChannelStrip(base, eqBands, isChannelOrAux));
|
|
2103
|
+
if (prefix === "ch" || prefix === "auxin" || prefix === "fxrtn" || prefix === "bus") for (let b = 1; b <= 16; b++) {
|
|
2104
|
+
const busId = b.toString().padStart(2, "0");
|
|
2105
|
+
nodes[`${base}/mix/${busId}/level`] = this.node("f", 0);
|
|
2106
|
+
nodes[`${base}/mix/${busId}/on`] = this.node("i", 0);
|
|
2107
|
+
nodes[`${base}/mix/${busId}/pan`] = this.node("f", .5);
|
|
2108
|
+
nodes[`${base}/mix/${busId}/type`] = this.node("i", 0);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
return nodes;
|
|
2114
|
+
}
|
|
2115
|
+
generateRange(count, prefix, suffix, type, def, pad = 2, start = 1) {
|
|
2116
|
+
const nodes = {};
|
|
2117
|
+
for (let i = start; i < start + count; i++) {
|
|
2118
|
+
const id = i.toString().padStart(pad, "0");
|
|
2119
|
+
nodes[`${prefix}/${i}${suffix}`] = this.node(type, def);
|
|
2120
|
+
nodes[`${prefix}/${id}${suffix}`] = this.node(type, def);
|
|
2121
|
+
}
|
|
2122
|
+
return nodes;
|
|
2123
|
+
}
|
|
2124
|
+
generateOutputs(prefix, count) {
|
|
2125
|
+
const nodes = {};
|
|
2126
|
+
for (let i = 1; i <= count; i++) {
|
|
2127
|
+
const base = `/outputs/${prefix}/${i.toString().padStart(2, "0")}`;
|
|
2128
|
+
nodes[`${base}/src`] = this.node("i", 0);
|
|
2129
|
+
nodes[`${base}/pos`] = this.node("i", 0);
|
|
2130
|
+
nodes[`${base}/invert`] = this.node("i", 0);
|
|
2131
|
+
nodes[`${base}/delay/on`] = this.node("i", 0);
|
|
2132
|
+
nodes[`${base}/delay/time`] = this.node("f", 0);
|
|
2133
|
+
if (prefix === "p16") {
|
|
2134
|
+
nodes[`${base}/iQ/group`] = this.node("i", 0);
|
|
2135
|
+
nodes[`${base}/iQ/model`] = this.node("i", 0);
|
|
2136
|
+
nodes[`${base}/iQ/eq`] = this.node("i", 0);
|
|
2137
|
+
nodes[`${base}/iQ/speaker`] = this.node("i", 0);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
return nodes;
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
//#endregion
|
|
2145
|
+
//#region src/domain/services/SchemaRegistry.ts
|
|
2146
|
+
/**
|
|
2147
|
+
* Service providing access to the X32 OSC Schema.
|
|
2148
|
+
* Acts as the "Registry" of all available console parameters.
|
|
2149
|
+
*/
|
|
2150
|
+
var SchemaRegistry = class {
|
|
2151
|
+
_schema;
|
|
2152
|
+
constructor(schemaFactory) {
|
|
2153
|
+
this.schemaFactory = schemaFactory;
|
|
2154
|
+
this._schema = this.schemaFactory.createSchema();
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Retrieves the entire schema map.
|
|
2158
|
+
* @returns The internal schema definition record.
|
|
2159
|
+
*/
|
|
2160
|
+
getSchema() {
|
|
2161
|
+
return this._schema;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Retrieves the node definition for a given path.
|
|
2165
|
+
* @param path - The OSC path.
|
|
2166
|
+
* @returns The X32Node definition or undefined if not found.
|
|
2167
|
+
*/
|
|
2168
|
+
getNode(path) {
|
|
2169
|
+
return this._schema[path];
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Checks if a path exists in the schema.
|
|
2173
|
+
* @param path - The path to check.
|
|
2174
|
+
* @returns True if the path is registered.
|
|
2175
|
+
*/
|
|
2176
|
+
has(path) {
|
|
2177
|
+
return path in this._schema;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Returns all paths in the schema.
|
|
2181
|
+
* @returns Array of all registered OSC paths.
|
|
2182
|
+
*/
|
|
2183
|
+
getAllPaths() {
|
|
2184
|
+
return Object.keys(this._schema);
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Maps an absolute X32 global index to its specific OSC root path.
|
|
2188
|
+
* Used for batch subscriptions where clients request ranges of channels.
|
|
2189
|
+
* @param index - The absolute integer index.
|
|
2190
|
+
* @returns The root path string or null if index is out of bounds.
|
|
2191
|
+
*/
|
|
2192
|
+
getRootFromIndex(index) {
|
|
2193
|
+
if (index >= 0 && index <= 31) return `/ch/${(index + 1).toString().padStart(2, "0")}`;
|
|
2194
|
+
if (index >= 32 && index <= 39) return `/auxin/${(index - 31).toString().padStart(2, "0")}`;
|
|
2195
|
+
if (index >= 40 && index <= 47) return `/fxrtn/${(index - 39).toString().padStart(2, "0")}`;
|
|
2196
|
+
if (index >= 48 && index <= 63) return `/bus/${(index - 47).toString().padStart(2, "0")}`;
|
|
2197
|
+
if (index >= 64 && index <= 69) return `/mtx/${(index - 63).toString().padStart(2, "0")}`;
|
|
2198
|
+
if (index === 70) return "/main/st";
|
|
2199
|
+
if (index === 71) return "/main/m";
|
|
2200
|
+
if (index >= 72 && index <= 79) return `/dca/${(index - 71).toString().padStart(2, "0")}`;
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
|
|
1217
2205
|
//#endregion
|
|
1218
2206
|
Object.defineProperty(exports, 'ConsoleLogger', {
|
|
1219
2207
|
enumerable: true,
|
|
@@ -1239,10 +2227,34 @@ Object.defineProperty(exports, 'LogLevel', {
|
|
|
1239
2227
|
return LogLevel;
|
|
1240
2228
|
}
|
|
1241
2229
|
});
|
|
1242
|
-
Object.defineProperty(exports, '
|
|
2230
|
+
Object.defineProperty(exports, 'METER_COUNTS', {
|
|
2231
|
+
enumerable: true,
|
|
2232
|
+
get: function () {
|
|
2233
|
+
return METER_COUNTS;
|
|
2234
|
+
}
|
|
2235
|
+
});
|
|
2236
|
+
Object.defineProperty(exports, 'MeterData', {
|
|
2237
|
+
enumerable: true,
|
|
2238
|
+
get: function () {
|
|
2239
|
+
return MeterData;
|
|
2240
|
+
}
|
|
2241
|
+
});
|
|
2242
|
+
Object.defineProperty(exports, 'OscCodec', {
|
|
2243
|
+
enumerable: true,
|
|
2244
|
+
get: function () {
|
|
2245
|
+
return OscCodec;
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
Object.defineProperty(exports, 'SchemaFactory', {
|
|
2249
|
+
enumerable: true,
|
|
2250
|
+
get: function () {
|
|
2251
|
+
return SchemaFactory;
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
Object.defineProperty(exports, 'SchemaRegistry', {
|
|
1243
2255
|
enumerable: true,
|
|
1244
2256
|
get: function () {
|
|
1245
|
-
return
|
|
2257
|
+
return SchemaRegistry;
|
|
1246
2258
|
}
|
|
1247
2259
|
});
|
|
1248
2260
|
Object.defineProperty(exports, 'SimulationService', {
|
|
@@ -1251,12 +2263,30 @@ Object.defineProperty(exports, 'SimulationService', {
|
|
|
1251
2263
|
return SimulationService;
|
|
1252
2264
|
}
|
|
1253
2265
|
});
|
|
2266
|
+
Object.defineProperty(exports, 'SubscriptionManager', {
|
|
2267
|
+
enumerable: true,
|
|
2268
|
+
get: function () {
|
|
2269
|
+
return SubscriptionManager;
|
|
2270
|
+
}
|
|
2271
|
+
});
|
|
1254
2272
|
Object.defineProperty(exports, 'UdpNetworkGateway', {
|
|
1255
2273
|
enumerable: true,
|
|
1256
2274
|
get: function () {
|
|
1257
2275
|
return UdpNetworkGateway;
|
|
1258
2276
|
}
|
|
1259
2277
|
});
|
|
2278
|
+
Object.defineProperty(exports, 'X32Node', {
|
|
2279
|
+
enumerable: true,
|
|
2280
|
+
get: function () {
|
|
2281
|
+
return X32Node;
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
Object.defineProperty(exports, 'X32State', {
|
|
2285
|
+
enumerable: true,
|
|
2286
|
+
get: function () {
|
|
2287
|
+
return X32State;
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
1260
2290
|
Object.defineProperty(exports, '__toESM', {
|
|
1261
2291
|
enumerable: true,
|
|
1262
2292
|
get: function () {
|