@arcanewizards/tcnet 0.1.1

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.
@@ -0,0 +1,86 @@
1
+ import {
2
+ TCNetConfigurationError
3
+ } from "./chunk-VXAKTGOW.js";
4
+
5
+ // src/utils.ts
6
+ var differsByMoreThan = (a, b, diff) => {
7
+ return Math.abs(a - b) > diff;
8
+ };
9
+ var MAX_NODE_NAME_LENGTH = 8;
10
+ var MAX_VENDOR_NAME_LENGTH = 16;
11
+ var MAX_APPLICATION_NAME_LENGTH = 16;
12
+ var generateProtocolStrings = ({
13
+ nodeName,
14
+ vendorName,
15
+ appName
16
+ }) => {
17
+ const strings = {
18
+ nodeName: Buffer.from(
19
+ nodeName.padEnd(MAX_NODE_NAME_LENGTH, "\0"),
20
+ "ascii"
21
+ ),
22
+ vendorName: Buffer.from(
23
+ vendorName.padEnd(MAX_VENDOR_NAME_LENGTH, "\0"),
24
+ "ascii"
25
+ ),
26
+ appName: Buffer.from(
27
+ appName.padEnd(MAX_APPLICATION_NAME_LENGTH, "\0"),
28
+ "ascii"
29
+ )
30
+ };
31
+ if (strings.nodeName.length > MAX_NODE_NAME_LENGTH) {
32
+ throw new TCNetConfigurationError(
33
+ `Node name "${nodeName}" exceeds maximum length of ${MAX_NODE_NAME_LENGTH} ASCII characters`
34
+ );
35
+ }
36
+ if (strings.vendorName.length > MAX_VENDOR_NAME_LENGTH) {
37
+ throw new TCNetConfigurationError(
38
+ `Vendor name "${vendorName}" exceeds maximum length of ${MAX_VENDOR_NAME_LENGTH} ASCII characters`
39
+ );
40
+ }
41
+ if (strings.appName.length > MAX_APPLICATION_NAME_LENGTH) {
42
+ throw new TCNetConfigurationError(
43
+ `Application name "${appName}" exceeds maximum length of ${MAX_APPLICATION_NAME_LENGTH} ASCII characters`
44
+ );
45
+ }
46
+ return strings;
47
+ };
48
+ var generateApplicationVersion = (version) => {
49
+ const [major, minor, bug] = version.split(".").map((part) => parseInt(part));
50
+ if (major === void 0 || minor === void 0 || bug === void 0 || isNaN(major) || isNaN(minor) || isNaN(bug) || major < 0 || minor < 0 || bug < 0 || major > 255 || minor > 255 || bug > 255) {
51
+ throw new TCNetConfigurationError(
52
+ `Invalid application version "${version}". Must be in format "major.minor.bug" with each part between 0 and 255.`
53
+ );
54
+ }
55
+ const buffer = Buffer.alloc(3);
56
+ buffer.writeUInt8(major, 0);
57
+ buffer.writeUInt8(minor, 1);
58
+ buffer.writeUInt8(bug, 2);
59
+ return buffer;
60
+ };
61
+ var parseApplicationVersion = (buffer) => {
62
+ if (buffer.length !== 3) {
63
+ throw new TCNetConfigurationError(
64
+ `Invalid application version buffer length ${buffer.length}. Must be exactly 3 bytes.`
65
+ );
66
+ }
67
+ const major = buffer.readUInt8(0);
68
+ const minor = buffer.readUInt8(1);
69
+ const bug = buffer.readUInt8(2);
70
+ return `${major}.${minor}.${bug}`;
71
+ };
72
+ var getNodeDescription = (info) => {
73
+ return `${info.nodeName} (${info.appName} ${info.appVersion}) - ${info.host}:${info.nodeListenerPort} (${info.nodeType})`;
74
+ };
75
+ var calculateUniqueNodeId = (info) => {
76
+ return `${info.host}:${info.nodeId}`;
77
+ };
78
+
79
+ export {
80
+ differsByMoreThan,
81
+ generateProtocolStrings,
82
+ generateApplicationVersion,
83
+ parseApplicationVersion,
84
+ getNodeDescription,
85
+ calculateUniqueNodeId
86
+ };
@@ -0,0 +1,435 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+ var _chunkVYNI4G3Kcjs = require('./chunk-VYNI4G3K.cjs');
5
+
6
+ // src/protocol.ts
7
+ var TCNET_LAYER_COUNT = 8;
8
+ var TCNET_LAYER_STATE_IDS = Object.freeze({
9
+ IDLE: 0,
10
+ /**
11
+ * Not in spec, but seems to be what's observed.
12
+ */
13
+ LOADING: 2,
14
+ PLAYING: 3,
15
+ LOOPING: 4,
16
+ PAUSED: 5,
17
+ STOPPED: 6,
18
+ CUEDOWN: 7,
19
+ PLATTERDOWN: 8,
20
+ FFWD: 9,
21
+ FFRV: 10,
22
+ HOLD: 11,
23
+ /**
24
+ * Not in spec, but seems to be what's observed.
25
+ */
26
+ END: 17,
27
+ /**
28
+ * TODO: determine what this means, seems to be disconnected state?
29
+ */
30
+ UNKNOWN: 255
31
+ });
32
+ var TCNET_LAYER_STATES = Object.freeze(
33
+ Object.fromEntries(
34
+ Object.entries(TCNET_LAYER_STATE_IDS).map(([key, value]) => [
35
+ value,
36
+ key
37
+ ])
38
+ )
39
+ );
40
+ var getTcNetLayerState = (id) => {
41
+ const state = TCNET_LAYER_STATES[id];
42
+ if (!state) {
43
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet layer state: ${id}`);
44
+ }
45
+ return state;
46
+ };
47
+ var TCNET_MIXER_TYPE_IDS = Object.freeze({
48
+ STANDARD: 0,
49
+ EXTENDED: 2
50
+ });
51
+ var TCNET_MIXER_TYPES = Object.freeze(
52
+ Object.fromEntries(
53
+ Object.entries(TCNET_MIXER_TYPE_IDS).map(([key, value]) => [
54
+ value,
55
+ key
56
+ ])
57
+ )
58
+ );
59
+ var getTcNetMixerType = (id) => {
60
+ const type = TCNET_MIXER_TYPES[id];
61
+ if (!type) {
62
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet mixer type: ${id}`);
63
+ }
64
+ return type;
65
+ };
66
+ var TCNET_MESSAGE_TYPE_IDS = Object.freeze({
67
+ OPT_IN: 2,
68
+ OPT_OUT: 3,
69
+ STATUS: 5,
70
+ TIME_SYNC: 10,
71
+ ERROR: 13,
72
+ REQUEST: 20,
73
+ APPLICATION_SPECIFIC_DATA_1: 30,
74
+ CONTROL: 101,
75
+ TEXT_DATA: 128,
76
+ KEYBOARD_DATA: 132,
77
+ DATA: 200,
78
+ FILE: 204,
79
+ APPLICATION_SPECIFIC_DATA_2: 213,
80
+ TIME: 254
81
+ });
82
+ var TCNET_MESSAGE_TYPES = Object.freeze(
83
+ Object.fromEntries(
84
+ Object.entries(TCNET_MESSAGE_TYPE_IDS).map(([key, value]) => [value, key])
85
+ )
86
+ );
87
+ var getTcNetMessageType = (id) => {
88
+ const type = TCNET_MESSAGE_TYPES[id];
89
+ if (!type) {
90
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet message type: ${id}`);
91
+ }
92
+ return type;
93
+ };
94
+ var TCNET_DATA_PACKET_TYPE_IDS = Object.freeze({
95
+ METRICS_DATA: 2,
96
+ METADATA: 4,
97
+ BEAT_GRID_DATA: 8,
98
+ CUE_DATA: 12,
99
+ SMALL_WAVEFORM: 16,
100
+ BIG_WAVEFORM: 32,
101
+ MIXER_DATA: 150
102
+ });
103
+ var TCNET_DATA_PACKET_TYPES = Object.freeze(
104
+ Object.fromEntries(
105
+ Object.entries(TCNET_DATA_PACKET_TYPE_IDS).map(([key, value]) => [
106
+ value,
107
+ key
108
+ ])
109
+ )
110
+ );
111
+ var getTcNetDataPacketType = (id) => {
112
+ const type = TCNET_DATA_PACKET_TYPES[id];
113
+ if (!type) {
114
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet data packet type: ${id}`);
115
+ }
116
+ return type;
117
+ };
118
+ var TCNET_NODE_TYPE_IDS = Object.freeze({
119
+ AUTO: 1,
120
+ MASTER: 2,
121
+ SLAVE: 4,
122
+ REPEATER: 8
123
+ });
124
+ var TCNET_NODE_TYPES = Object.freeze(
125
+ Object.fromEntries(
126
+ Object.entries(TCNET_NODE_TYPE_IDS).map(([key, value]) => [value, key])
127
+ )
128
+ );
129
+ var getTcNetNodeType = (id) => {
130
+ const type = TCNET_NODE_TYPES[id];
131
+ if (!type) {
132
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet node type: ${id}`);
133
+ }
134
+ return type;
135
+ };
136
+ var TCNET_PORT_NODE_OPTION_FLAGS = Object.freeze({
137
+ NEED_AUTHENTICATION: 1,
138
+ /**
139
+ * Listens to TCNet Control Messages
140
+ */
141
+ SUPPORTS_TCNCM: 2,
142
+ /**
143
+ * Listens to TCNet Application Specific Data Packets
144
+ */
145
+ SUPPORTS_TCNASDP: 4,
146
+ /**
147
+ * Do not disturb/Sleeping. Node will request data itself if needed to avoid traffic
148
+ */
149
+ DO_NOT_DISTURB: 8
150
+ });
151
+ var parseTcNetPortNodeOptions = (flags) => {
152
+ return Object.fromEntries(
153
+ Object.entries(TCNET_PORT_NODE_OPTION_FLAGS).map(([key, flag]) => [
154
+ key,
155
+ (flags & flag) !== 0 ? true : void 0
156
+ ])
157
+ );
158
+ };
159
+ var generateTcNetPortNodeOptionsFlags = (options) => {
160
+ let flags = 0;
161
+ for (const [option, value] of Object.entries(options)) {
162
+ if (value) {
163
+ const flag = TCNET_PORT_NODE_OPTION_FLAGS[option];
164
+ flags |= flag;
165
+ }
166
+ }
167
+ return flags;
168
+ };
169
+ var TCNET_LAYER_TC_STATE_IDS = Object.freeze({
170
+ STOPPED: 0,
171
+ RUNNING: 1,
172
+ FORCE_RESYNC: 2
173
+ });
174
+ var TCNET_LAYER_TC_STATES = Object.freeze(
175
+ Object.fromEntries(
176
+ Object.entries(TCNET_LAYER_TC_STATE_IDS).map(([key, value]) => [
177
+ value,
178
+ key
179
+ ])
180
+ )
181
+ );
182
+ var getTcNetLayerTCState = (id) => {
183
+ const state = TCNET_LAYER_TC_STATES[id];
184
+ if (!state) {
185
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown TCNet layer timecode state: ${id}`);
186
+ }
187
+ return state;
188
+ };
189
+ var MAX_NODE_ID = 256 * 256 - 1;
190
+ var MGMT_HEADER_VERSION = 3;
191
+ var MGMT_HEADER_MINOR_VERSION = 5;
192
+ var MGMT_MAGIC_HEADER = "TCN";
193
+ var writeManagementHeader = (buffer, header, messageType) => {
194
+ buffer.writeUInt16LE(header.nodeId, 0);
195
+ buffer.writeUInt8(header.protocolVersionMajor, 2);
196
+ buffer.writeUInt8(header.protocolVersionMinor, 3);
197
+ buffer.write(MGMT_MAGIC_HEADER, 4, "ascii");
198
+ buffer.writeUInt8(TCNET_MESSAGE_TYPE_IDS[messageType], 7);
199
+ header.nodeName.copy(buffer, 8, 0, 8);
200
+ buffer.writeUInt8(header.seq, 16);
201
+ buffer.writeUInt8(TCNET_NODE_TYPE_IDS[header.nodeType], 17);
202
+ buffer.writeUInt16LE(
203
+ generateTcNetPortNodeOptionsFlags(header.nodeOptions),
204
+ 18
205
+ );
206
+ buffer.writeUInt32LE(header.timestamp, 20);
207
+ };
208
+ var parseManagementHeader = (buffer) => {
209
+ const magicHeader = buffer.toString("ascii", 4, 7);
210
+ if (magicHeader !== MGMT_MAGIC_HEADER) {
211
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(
212
+ `Invalid management header magic: ${magicHeader}`
213
+ );
214
+ }
215
+ return {
216
+ nodeId: buffer.readUInt16LE(0),
217
+ protocolVersionMajor: buffer.readUInt8(2),
218
+ protocolVersionMinor: buffer.readUInt8(3),
219
+ messageType: getTcNetMessageType(buffer.readUInt8(7)),
220
+ nodeName: buffer.subarray(8, 16),
221
+ seq: buffer.readUInt8(16),
222
+ nodeType: getTcNetNodeType(buffer.readUInt8(17)),
223
+ nodeOptions: parseTcNetPortNodeOptions(buffer.readUInt16LE(18)),
224
+ timestamp: buffer.readUInt32LE(20)
225
+ };
226
+ };
227
+ var writeOptInPacket = (data) => {
228
+ const buffer = Buffer.alloc(68);
229
+ writeManagementHeader(buffer, data.header, "OPT_IN");
230
+ buffer.writeUInt16LE(data.nodeCount, 24);
231
+ buffer.writeUInt16LE(data.nodeListenerPort, 26);
232
+ buffer.writeUInt16LE(data.uptime, 28);
233
+ data.vendorName.copy(buffer, 32, 0, 16);
234
+ data.applicationName.copy(buffer, 48, 0, 16);
235
+ data.applicationVersion.copy(buffer, 64, 0, 3);
236
+ return buffer;
237
+ };
238
+ var parseOptInPacket = (header, buffer) => {
239
+ return {
240
+ header,
241
+ type: "OPT_IN",
242
+ nodeCount: buffer.readUInt16LE(24),
243
+ nodeListenerPort: buffer.readUInt16LE(26),
244
+ uptime: buffer.readUInt16LE(28),
245
+ vendorName: buffer.subarray(32, 48),
246
+ applicationName: buffer.subarray(48, 64),
247
+ applicationVersion: buffer.subarray(64, 67)
248
+ };
249
+ };
250
+ var writeOptOutPacket = (data) => {
251
+ const buffer = Buffer.alloc(28);
252
+ writeManagementHeader(buffer, data.header, "OPT_OUT");
253
+ buffer.writeUInt16LE(data.nodeCount, 24);
254
+ buffer.writeUInt16LE(data.nodeListenerPort, 26);
255
+ return buffer;
256
+ };
257
+ var parseOptOutPacket = (header, buffer) => {
258
+ return {
259
+ header,
260
+ type: "OPT_OUT",
261
+ nodeCount: buffer.readUInt16LE(24),
262
+ nodeListenerPort: buffer.readUInt16LE(26)
263
+ };
264
+ };
265
+ var parseStatusPacket = (header, buffer) => {
266
+ const nodeCount = buffer.readUInt16LE(24);
267
+ const nodeListenerPort = buffer.readUInt16LE(26);
268
+ const smpteMode = buffer.readUInt8(83);
269
+ const autoMasterMode = buffer.readUInt8(84);
270
+ const layers = new Array(8);
271
+ for (let n = 0; n < 8; n++) {
272
+ layers[n] = {
273
+ source: buffer.readUInt8(34 + n),
274
+ status: getTcNetLayerState(buffer.readUInt8(42 + n)),
275
+ trackId: buffer.readUInt32LE(50 + n * 4),
276
+ name: buffer.slice(172 + n * 16, 172 + (n + 1) * 16).toString("ascii").replace(/\0/g, "")
277
+ };
278
+ }
279
+ return {
280
+ header,
281
+ type: "STATUS",
282
+ nodeCount,
283
+ nodeListenerPort,
284
+ smpteMode,
285
+ autoMasterMode,
286
+ layers
287
+ };
288
+ };
289
+ var writeRequestPacket = (data) => {
290
+ const buffer = Buffer.alloc(26);
291
+ writeManagementHeader(buffer, data.header, "REQUEST");
292
+ buffer.writeUInt8(TCNET_DATA_PACKET_TYPE_IDS[data.dataType], 24);
293
+ buffer.writeUInt8(data.layer, 25);
294
+ return buffer;
295
+ };
296
+ var parseApplicationSpecificData1Packet = (header, _buffer) => {
297
+ return {
298
+ header,
299
+ type: "APPLICATION_SPECIFIC_DATA_1"
300
+ };
301
+ };
302
+ var parseMetricsDataPacket = (header, buffer) => {
303
+ return {
304
+ header,
305
+ type: "DATA",
306
+ dataType: "METRICS_DATA",
307
+ layer: buffer.readUInt8(25),
308
+ state: getTcNetLayerState(buffer.readUInt8(27)),
309
+ syncMaster: buffer.readUInt8(29),
310
+ beatMarker: buffer.readUInt8(31),
311
+ trackLengthMillis: buffer.readUInt32LE(32),
312
+ currentPositionMillis: buffer.readUInt32LE(36),
313
+ speed: buffer.readUInt32LE(40),
314
+ beatNumber: buffer.readUInt32LE(57),
315
+ bpm: buffer.readUInt32LE(112),
316
+ pitchBend: buffer.readInt16LE(116),
317
+ trackId: buffer.readUInt32LE(118)
318
+ };
319
+ };
320
+ var parseMetadataDataPacket = (header, buffer) => {
321
+ const encoding = header.protocolVersionMajor >= 3 && header.protocolVersionMinor >= 5 ? "utf16le" : "utf-8";
322
+ return {
323
+ header,
324
+ type: "DATA",
325
+ dataType: "METADATA",
326
+ layer: buffer.readUInt8(25),
327
+ trackArtist: buffer.toString(encoding, 29, 285).replace(/\0/g, ""),
328
+ trackTitle: buffer.toString(encoding, 285, 541).replace(/\0/g, ""),
329
+ trackKey: buffer.readUInt16LE(541),
330
+ trackId: buffer.readUInt32LE(543)
331
+ };
332
+ };
333
+ var parseMixerDataPacket = (header, buffer) => {
334
+ return {
335
+ header,
336
+ type: "DATA",
337
+ dataType: "MIXER_DATA",
338
+ mixerId: buffer.readUInt8(25),
339
+ mixerType: getTcNetMixerType(buffer.readUInt8(26)),
340
+ mixerName: buffer.toString("ascii", 29, 45).replace(/\0/g, ""),
341
+ micEqHi: buffer.readUInt8(59),
342
+ micEqLow: buffer.readUInt8(60),
343
+ masterAudioLevel: buffer.readUInt8(61),
344
+ masterFaderLevel: buffer.readUInt8(62)
345
+ };
346
+ };
347
+ var parseDataPacket = (header, buffer) => {
348
+ const dataType = getTcNetDataPacketType(buffer.readUInt8(24));
349
+ if (dataType === "METRICS_DATA") {
350
+ return parseMetricsDataPacket(header, buffer);
351
+ } else if (dataType === "MIXER_DATA") {
352
+ return parseMixerDataPacket(header, buffer);
353
+ } else if (dataType === "METADATA") {
354
+ return parseMetadataDataPacket(header, buffer);
355
+ }
356
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetError)(`Library support for ${dataType} not implemented`);
357
+ };
358
+ var parseSMPTEFramerate = (framerate) => {
359
+ if (framerate === 24 || framerate === 25 || framerate === 29 || framerate === 30) {
360
+ return framerate;
361
+ }
362
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetProtocolError)(`Unknown SMPTE framerate: ${framerate}`);
363
+ };
364
+ var parseTimePacket = (header, buffer) => {
365
+ const layers = new Array(8);
366
+ for (let i = 0; i < 8; i++) {
367
+ const smpteMode = buffer.readUInt8(106 + i * 6);
368
+ layers[i] = {
369
+ currentTimeMillis: buffer.readUInt32LE(24 + i * 4),
370
+ totalTimeMillis: buffer.readUInt32LE(56 + i * 4),
371
+ beatMarker: buffer.readUInt8(88 + i),
372
+ state: getTcNetLayerState(buffer.readUInt8(96 + i)),
373
+ smpte: {
374
+ /**
375
+ * Spec says 0 here, but ShowKontrol seems to also be sending 1.
376
+ */
377
+ mode: smpteMode === 0 || smpteMode === 1 ? null : parseSMPTEFramerate(smpteMode),
378
+ state: getTcNetLayerTCState(buffer.readUInt8(107 + i * 6)),
379
+ hours: buffer.readUInt8(108 + i * 6),
380
+ minutes: buffer.readUInt8(109 + i * 6),
381
+ seconds: buffer.readUInt8(110 + i * 6),
382
+ frames: buffer.readUInt8(111 + i * 6)
383
+ },
384
+ onAir: buffer.length > 154 ? buffer.readUInt8(154 + i) : 255
385
+ };
386
+ }
387
+ return {
388
+ header,
389
+ type: "TIME",
390
+ layers,
391
+ generalSmpteFramerate: parseSMPTEFramerate(buffer.readUInt8(105))
392
+ };
393
+ };
394
+ var parsePacket = (buffer) => {
395
+ const header = parseManagementHeader(buffer);
396
+ switch (header.messageType) {
397
+ case "OPT_IN":
398
+ return parseOptInPacket(header, buffer);
399
+ case "OPT_OUT":
400
+ return parseOptOutPacket(header, buffer);
401
+ case "STATUS":
402
+ return parseStatusPacket(header, buffer);
403
+ case "DATA":
404
+ return parseDataPacket(header, buffer);
405
+ case "APPLICATION_SPECIFIC_DATA_1":
406
+ return parseApplicationSpecificData1Packet(header, buffer);
407
+ case "TIME":
408
+ return parseTimePacket(header, buffer);
409
+ default:
410
+ throw new (0, _chunkVYNI4G3Kcjs.TCNetError)(
411
+ `Library support for ${header.messageType} not implemented`
412
+ );
413
+ }
414
+ };
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+ exports.TCNET_LAYER_COUNT = TCNET_LAYER_COUNT; exports.getTcNetLayerState = getTcNetLayerState; exports.getTcNetMixerType = getTcNetMixerType; exports.getTcNetMessageType = getTcNetMessageType; exports.getTcNetDataPacketType = getTcNetDataPacketType; exports.getTcNetNodeType = getTcNetNodeType; exports.parseTcNetPortNodeOptions = parseTcNetPortNodeOptions; exports.generateTcNetPortNodeOptionsFlags = generateTcNetPortNodeOptionsFlags; exports.getTcNetLayerTCState = getTcNetLayerTCState; exports.MAX_NODE_ID = MAX_NODE_ID; exports.MGMT_HEADER_VERSION = MGMT_HEADER_VERSION; exports.MGMT_HEADER_MINOR_VERSION = MGMT_HEADER_MINOR_VERSION; exports.writeManagementHeader = writeManagementHeader; exports.parseManagementHeader = parseManagementHeader; exports.writeOptInPacket = writeOptInPacket; exports.writeOptOutPacket = writeOptOutPacket; exports.writeRequestPacket = writeRequestPacket; exports.parsePacket = parsePacket;