@9c5s/node-tcnet 0.5.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.
- package/LICENSE +7 -0
- package/README.MD +76 -0
- package/dist/index.d.mts +325 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +764 -0
- package/dist/index.mjs +764 -0
- package/package.json +59 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/tcnet.ts
|
|
9
|
+
import { createSocket } from "dgram";
|
|
10
|
+
|
|
11
|
+
// src/utils.ts
|
|
12
|
+
import { networkInterfaces } from "os";
|
|
13
|
+
function calculateBroadcastAddress(address, netmask) {
|
|
14
|
+
const addrParts = address.split(".").map(Number);
|
|
15
|
+
const maskParts = netmask.split(".").map(Number);
|
|
16
|
+
return addrParts.map((a, i) => a | ~maskParts[i] & 255).join(".");
|
|
17
|
+
}
|
|
18
|
+
function interfaceAddress(ifname) {
|
|
19
|
+
const interfaces = networkInterfaces();
|
|
20
|
+
const intf = interfaces[ifname];
|
|
21
|
+
if (!intf) {
|
|
22
|
+
throw new Error(`Interface ${ifname} does not exist`);
|
|
23
|
+
}
|
|
24
|
+
const address = intf.find((el) => el.family === "IPv4");
|
|
25
|
+
if (!address) {
|
|
26
|
+
throw new Error(`Interface ${ifname} does not have IPv4 address`);
|
|
27
|
+
}
|
|
28
|
+
return calculateBroadcastAddress(address.address, address.netmask);
|
|
29
|
+
}
|
|
30
|
+
var assert = (condition, message) => {
|
|
31
|
+
if (!condition) {
|
|
32
|
+
throw new Error(`Assertion failed: ${message}`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/network.ts
|
|
37
|
+
var TCNetMessageType = /* @__PURE__ */ ((TCNetMessageType2) => {
|
|
38
|
+
TCNetMessageType2[TCNetMessageType2["OptIn"] = 2] = "OptIn";
|
|
39
|
+
TCNetMessageType2[TCNetMessageType2["OptOut"] = 3] = "OptOut";
|
|
40
|
+
TCNetMessageType2[TCNetMessageType2["Status"] = 5] = "Status";
|
|
41
|
+
TCNetMessageType2[TCNetMessageType2["TimeSync"] = 10] = "TimeSync";
|
|
42
|
+
TCNetMessageType2[TCNetMessageType2["Error"] = 13] = "Error";
|
|
43
|
+
TCNetMessageType2[TCNetMessageType2["Request"] = 20] = "Request";
|
|
44
|
+
TCNetMessageType2[TCNetMessageType2["ApplicationData"] = 30] = "ApplicationData";
|
|
45
|
+
TCNetMessageType2[TCNetMessageType2["Control"] = 101] = "Control";
|
|
46
|
+
TCNetMessageType2[TCNetMessageType2["Text"] = 128] = "Text";
|
|
47
|
+
TCNetMessageType2[TCNetMessageType2["Keyboard"] = 132] = "Keyboard";
|
|
48
|
+
TCNetMessageType2[TCNetMessageType2["Data"] = 200] = "Data";
|
|
49
|
+
TCNetMessageType2[TCNetMessageType2["File"] = 204] = "File";
|
|
50
|
+
TCNetMessageType2[TCNetMessageType2["Time"] = 254] = "Time";
|
|
51
|
+
return TCNetMessageType2;
|
|
52
|
+
})(TCNetMessageType || {});
|
|
53
|
+
var TCNetDataPacketType = /* @__PURE__ */ ((TCNetDataPacketType2) => {
|
|
54
|
+
TCNetDataPacketType2[TCNetDataPacketType2["MetricsData"] = 2] = "MetricsData";
|
|
55
|
+
TCNetDataPacketType2[TCNetDataPacketType2["MetaData"] = 4] = "MetaData";
|
|
56
|
+
TCNetDataPacketType2[TCNetDataPacketType2["BeatGridData"] = 8] = "BeatGridData";
|
|
57
|
+
TCNetDataPacketType2[TCNetDataPacketType2["CUEData"] = 12] = "CUEData";
|
|
58
|
+
TCNetDataPacketType2[TCNetDataPacketType2["SmallWaveFormData"] = 16] = "SmallWaveFormData";
|
|
59
|
+
TCNetDataPacketType2[TCNetDataPacketType2["BigWaveFormData"] = 32] = "BigWaveFormData";
|
|
60
|
+
TCNetDataPacketType2[TCNetDataPacketType2["MixerData"] = 150] = "MixerData";
|
|
61
|
+
return TCNetDataPacketType2;
|
|
62
|
+
})(TCNetDataPacketType || {});
|
|
63
|
+
var NodeType = /* @__PURE__ */ ((NodeType2) => {
|
|
64
|
+
NodeType2[NodeType2["Auto"] = 1] = "Auto";
|
|
65
|
+
NodeType2[NodeType2["Master"] = 2] = "Master";
|
|
66
|
+
NodeType2[NodeType2["Slave"] = 4] = "Slave";
|
|
67
|
+
NodeType2[NodeType2["Repeater"] = 8] = "Repeater";
|
|
68
|
+
return NodeType2;
|
|
69
|
+
})(NodeType || {});
|
|
70
|
+
var TCNetPacket = class {
|
|
71
|
+
buffer;
|
|
72
|
+
header;
|
|
73
|
+
};
|
|
74
|
+
var TCNetManagementHeader = class _TCNetManagementHeader {
|
|
75
|
+
static MAJOR_VERSION = 3;
|
|
76
|
+
static MAGIC_HEADER = "TCN";
|
|
77
|
+
buffer;
|
|
78
|
+
nodeId;
|
|
79
|
+
minorVersion;
|
|
80
|
+
messageType;
|
|
81
|
+
nodeName;
|
|
82
|
+
seq;
|
|
83
|
+
nodeType;
|
|
84
|
+
nodeOptions;
|
|
85
|
+
timestamp;
|
|
86
|
+
constructor(buffer) {
|
|
87
|
+
this.buffer = buffer;
|
|
88
|
+
}
|
|
89
|
+
read() {
|
|
90
|
+
this.nodeId = this.buffer.readUInt16LE(0);
|
|
91
|
+
assert(this.buffer.readUInt8(2) == _TCNetManagementHeader.MAJOR_VERSION);
|
|
92
|
+
this.minorVersion = this.buffer.readUInt8(3);
|
|
93
|
+
assert(this.buffer.slice(4, 7).toString("ascii") == _TCNetManagementHeader.MAGIC_HEADER);
|
|
94
|
+
this.messageType = this.buffer.readUInt8(7);
|
|
95
|
+
this.nodeName = this.buffer.slice(8, 16).toString("ascii").replace(/\0.*$/g, "");
|
|
96
|
+
this.seq = this.buffer.readUInt8(16);
|
|
97
|
+
this.nodeType = this.buffer.readUInt8(17);
|
|
98
|
+
this.nodeOptions = this.buffer.readUInt16LE(18);
|
|
99
|
+
this.timestamp = this.buffer.readUInt32LE(20);
|
|
100
|
+
}
|
|
101
|
+
write() {
|
|
102
|
+
assert(Buffer.from(this.nodeName, "ascii").length <= 8);
|
|
103
|
+
this.buffer.writeUInt16LE(this.nodeId, 0);
|
|
104
|
+
this.buffer.writeUInt8(_TCNetManagementHeader.MAJOR_VERSION, 2);
|
|
105
|
+
this.buffer.writeUInt8(this.minorVersion, 3);
|
|
106
|
+
this.buffer.write(_TCNetManagementHeader.MAGIC_HEADER, 4, "ascii");
|
|
107
|
+
this.buffer.writeUInt8(this.messageType, 7);
|
|
108
|
+
this.buffer.write(this.nodeName.padEnd(8, "\0"), 8, "ascii");
|
|
109
|
+
this.buffer.writeUInt8(this.seq, 16);
|
|
110
|
+
this.buffer.writeUInt8(this.nodeType, 17);
|
|
111
|
+
this.buffer.writeUInt16LE(this.nodeOptions, 18);
|
|
112
|
+
this.buffer.writeUInt32LE(this.timestamp, 20);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var TCNetOptInPacket = class extends TCNetPacket {
|
|
116
|
+
nodeCount;
|
|
117
|
+
nodeListenerPort;
|
|
118
|
+
uptime;
|
|
119
|
+
vendorName;
|
|
120
|
+
appName;
|
|
121
|
+
majorVersion;
|
|
122
|
+
minorVersion;
|
|
123
|
+
bugVersion;
|
|
124
|
+
read() {
|
|
125
|
+
this.nodeCount = this.buffer.readUInt16LE(24);
|
|
126
|
+
this.nodeListenerPort = this.buffer.readUInt16LE(26);
|
|
127
|
+
this.uptime = this.buffer.readUInt16LE(28);
|
|
128
|
+
this.vendorName = this.buffer.slice(32, 48).toString("ascii").replace(/\0.*$/g, "");
|
|
129
|
+
this.appName = this.buffer.slice(48, 64).toString("ascii").replace(/\0.*$/g, "");
|
|
130
|
+
this.majorVersion = this.buffer.readUInt8(64);
|
|
131
|
+
this.minorVersion = this.buffer.readUInt8(65);
|
|
132
|
+
this.bugVersion = this.buffer.readUInt8(66);
|
|
133
|
+
}
|
|
134
|
+
write() {
|
|
135
|
+
assert(Buffer.from(this.vendorName, "ascii").length <= 16);
|
|
136
|
+
assert(Buffer.from(this.appName, "ascii").length <= 16);
|
|
137
|
+
this.buffer.writeUInt16LE(this.nodeCount, 24);
|
|
138
|
+
this.buffer.writeUInt16LE(this.nodeListenerPort, 26);
|
|
139
|
+
this.buffer.writeUInt16LE(this.uptime, 28);
|
|
140
|
+
this.buffer.write(this.vendorName.padEnd(16, "\0"), 32, "ascii");
|
|
141
|
+
this.buffer.write(this.appName.padEnd(16, "\0"), 48, "ascii");
|
|
142
|
+
this.buffer.writeUInt8(this.majorVersion, 64);
|
|
143
|
+
this.buffer.writeUInt8(this.minorVersion, 65);
|
|
144
|
+
this.buffer.writeUInt8(this.bugVersion, 66);
|
|
145
|
+
}
|
|
146
|
+
length() {
|
|
147
|
+
return 68;
|
|
148
|
+
}
|
|
149
|
+
type() {
|
|
150
|
+
return 2 /* OptIn */;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var TCNetOptOutPacket = class extends TCNetPacket {
|
|
154
|
+
nodeCount;
|
|
155
|
+
nodeListenerPort;
|
|
156
|
+
read() {
|
|
157
|
+
this.nodeCount = this.buffer.readUInt16LE(24);
|
|
158
|
+
this.nodeListenerPort = this.buffer.readUInt16LE(26);
|
|
159
|
+
}
|
|
160
|
+
write() {
|
|
161
|
+
this.buffer.writeUInt16LE(this.nodeCount, 24);
|
|
162
|
+
this.buffer.writeUInt16LE(this.nodeListenerPort, 26);
|
|
163
|
+
}
|
|
164
|
+
length() {
|
|
165
|
+
return 28;
|
|
166
|
+
}
|
|
167
|
+
type() {
|
|
168
|
+
return 3 /* OptOut */;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var TCNetLayerStatus = /* @__PURE__ */ ((TCNetLayerStatus2) => {
|
|
172
|
+
TCNetLayerStatus2[TCNetLayerStatus2["IDLE"] = 0] = "IDLE";
|
|
173
|
+
TCNetLayerStatus2[TCNetLayerStatus2["PLAYING"] = 3] = "PLAYING";
|
|
174
|
+
TCNetLayerStatus2[TCNetLayerStatus2["LOOPING"] = 4] = "LOOPING";
|
|
175
|
+
TCNetLayerStatus2[TCNetLayerStatus2["PAUSED"] = 5] = "PAUSED";
|
|
176
|
+
TCNetLayerStatus2[TCNetLayerStatus2["STOPPED"] = 6] = "STOPPED";
|
|
177
|
+
TCNetLayerStatus2[TCNetLayerStatus2["CUEDOWN"] = 7] = "CUEDOWN";
|
|
178
|
+
TCNetLayerStatus2[TCNetLayerStatus2["PLATTERDOWN"] = 8] = "PLATTERDOWN";
|
|
179
|
+
TCNetLayerStatus2[TCNetLayerStatus2["FFWD"] = 9] = "FFWD";
|
|
180
|
+
TCNetLayerStatus2[TCNetLayerStatus2["FFRV"] = 10] = "FFRV";
|
|
181
|
+
TCNetLayerStatus2[TCNetLayerStatus2["HOLD"] = 11] = "HOLD";
|
|
182
|
+
return TCNetLayerStatus2;
|
|
183
|
+
})(TCNetLayerStatus || {});
|
|
184
|
+
var TCNetStatusPacket = class extends TCNetPacket {
|
|
185
|
+
data = null;
|
|
186
|
+
layers = new Array(8);
|
|
187
|
+
read() {
|
|
188
|
+
this.data = {
|
|
189
|
+
nodeCount: this.buffer.readUInt16LE(24),
|
|
190
|
+
nodeListenerPort: this.buffer.readUInt16LE(26),
|
|
191
|
+
smpteMode: this.buffer.readUInt8(83),
|
|
192
|
+
autoMasterMode: this.buffer.readUInt8(84)
|
|
193
|
+
};
|
|
194
|
+
for (let n = 0; n < 8; n++) {
|
|
195
|
+
this.layers[n] = {
|
|
196
|
+
source: this.buffer.readUInt8(34 + n),
|
|
197
|
+
status: this.buffer.readUInt8(42 + n),
|
|
198
|
+
trackID: this.buffer.readUInt32LE(50 + n * 4),
|
|
199
|
+
name: this.buffer.slice(172 + n * 16, 172 + (n + 1) * 16).toString("ascii").replace(/\0.*$/g, "")
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
write() {
|
|
204
|
+
throw new Error("not supported!");
|
|
205
|
+
}
|
|
206
|
+
length() {
|
|
207
|
+
return 300;
|
|
208
|
+
}
|
|
209
|
+
type() {
|
|
210
|
+
return 5 /* Status */;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var TCNetRequestPacket = class extends TCNetPacket {
|
|
214
|
+
dataType;
|
|
215
|
+
layer;
|
|
216
|
+
read() {
|
|
217
|
+
this.dataType = this.buffer.readUInt8(24);
|
|
218
|
+
this.layer = this.buffer.readUInt8(25);
|
|
219
|
+
}
|
|
220
|
+
write() {
|
|
221
|
+
assert(0 <= this.dataType && this.dataType <= 255);
|
|
222
|
+
assert(0 <= this.layer && this.layer <= 255);
|
|
223
|
+
this.buffer.writeUInt8(this.dataType, 24);
|
|
224
|
+
this.buffer.writeUInt8(this.layer, 25);
|
|
225
|
+
}
|
|
226
|
+
length() {
|
|
227
|
+
return 26;
|
|
228
|
+
}
|
|
229
|
+
type() {
|
|
230
|
+
return 20 /* Request */;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
var TCNetTimecodeState = /* @__PURE__ */ ((TCNetTimecodeState2) => {
|
|
234
|
+
TCNetTimecodeState2[TCNetTimecodeState2["Stopped"] = 0] = "Stopped";
|
|
235
|
+
TCNetTimecodeState2[TCNetTimecodeState2["Running"] = 1] = "Running";
|
|
236
|
+
TCNetTimecodeState2[TCNetTimecodeState2["ForceReSync"] = 2] = "ForceReSync";
|
|
237
|
+
return TCNetTimecodeState2;
|
|
238
|
+
})(TCNetTimecodeState || {});
|
|
239
|
+
var TCNetTimecode = class {
|
|
240
|
+
mode;
|
|
241
|
+
state;
|
|
242
|
+
hours;
|
|
243
|
+
minutes;
|
|
244
|
+
seconds;
|
|
245
|
+
frames;
|
|
246
|
+
read(buffer, offset) {
|
|
247
|
+
this.mode = buffer.readUInt8(offset + 0);
|
|
248
|
+
this.state = buffer.readUInt8(offset + 1);
|
|
249
|
+
this.hours = buffer.readUInt8(offset + 2);
|
|
250
|
+
this.minutes = buffer.readUInt8(offset + 3);
|
|
251
|
+
this.seconds = buffer.readUInt8(offset + 4);
|
|
252
|
+
this.frames = buffer.readUInt8(offset + 5);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var TCNetTimePacket = class extends TCNetPacket {
|
|
256
|
+
_layers = new Array(8);
|
|
257
|
+
_generalSMPTEMode = 0;
|
|
258
|
+
read() {
|
|
259
|
+
for (let n = 0; n < 8; n++) {
|
|
260
|
+
this._layers[n] = {
|
|
261
|
+
currentTimeMillis: this.buffer.readUInt32LE(24 + n * 4),
|
|
262
|
+
totalTimeMillis: this.buffer.readUInt32LE(56 + n * 4),
|
|
263
|
+
beatMarker: this.buffer.readUInt8(88 + n),
|
|
264
|
+
state: this.buffer.readUInt8(96 + n),
|
|
265
|
+
onAir: this.buffer.length > 154 ? this.buffer.readUInt8(154 + n) : 255
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
this._generalSMPTEMode = this.buffer.readUInt8(105);
|
|
269
|
+
}
|
|
270
|
+
write() {
|
|
271
|
+
throw new Error("not supported!");
|
|
272
|
+
}
|
|
273
|
+
length() {
|
|
274
|
+
switch (this.buffer.length) {
|
|
275
|
+
case 154:
|
|
276
|
+
case 162:
|
|
277
|
+
return this.buffer.length;
|
|
278
|
+
default:
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
type() {
|
|
283
|
+
return 254 /* Time */;
|
|
284
|
+
}
|
|
285
|
+
get layers() {
|
|
286
|
+
return this._layers;
|
|
287
|
+
}
|
|
288
|
+
get generalSMPTEMode() {
|
|
289
|
+
return this._generalSMPTEMode;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var TCNetDataPacket = class extends TCNetPacket {
|
|
293
|
+
dataType;
|
|
294
|
+
/**
|
|
295
|
+
* 0-indexed layer ID (0-7)
|
|
296
|
+
*/
|
|
297
|
+
layer;
|
|
298
|
+
read() {
|
|
299
|
+
this.dataType = this.buffer.readUInt8(24);
|
|
300
|
+
this.layer = this.buffer.readUInt8(25) - 1;
|
|
301
|
+
}
|
|
302
|
+
write() {
|
|
303
|
+
assert(0 <= this.dataType && this.dataType <= 255);
|
|
304
|
+
assert(0 <= this.layer && this.layer <= 255);
|
|
305
|
+
this.buffer.writeUInt8(this.dataType, 24);
|
|
306
|
+
this.buffer.writeUInt8(this.layer, 25);
|
|
307
|
+
}
|
|
308
|
+
length() {
|
|
309
|
+
return -1;
|
|
310
|
+
}
|
|
311
|
+
type() {
|
|
312
|
+
return 200 /* Data */;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
var TCNetLayerSyncMaster = /* @__PURE__ */ ((TCNetLayerSyncMaster2) => {
|
|
316
|
+
TCNetLayerSyncMaster2[TCNetLayerSyncMaster2["Slave"] = 0] = "Slave";
|
|
317
|
+
TCNetLayerSyncMaster2[TCNetLayerSyncMaster2["Master"] = 1] = "Master";
|
|
318
|
+
return TCNetLayerSyncMaster2;
|
|
319
|
+
})(TCNetLayerSyncMaster || {});
|
|
320
|
+
var TCNetDataPacketMetrics = class extends TCNetDataPacket {
|
|
321
|
+
data = null;
|
|
322
|
+
read() {
|
|
323
|
+
this.data = {
|
|
324
|
+
state: this.buffer.readUInt8(27),
|
|
325
|
+
syncMaster: this.buffer.readUInt8(29),
|
|
326
|
+
beatMarker: this.buffer.readUInt8(31),
|
|
327
|
+
trackLength: this.buffer.readUInt32LE(32),
|
|
328
|
+
currentPosition: this.buffer.readUInt32LE(36),
|
|
329
|
+
speed: this.buffer.readUInt32LE(40),
|
|
330
|
+
beatNumber: this.buffer.readUInt32LE(57),
|
|
331
|
+
bpm: this.buffer.readUInt32LE(112),
|
|
332
|
+
pitchBend: this.buffer.readInt16LE(116),
|
|
333
|
+
trackID: this.buffer.readUInt32LE(118)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
write() {
|
|
337
|
+
throw new Error("not supported!");
|
|
338
|
+
}
|
|
339
|
+
length() {
|
|
340
|
+
return 122;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
var TCNetDataPacketMetadata = class extends TCNetDataPacket {
|
|
344
|
+
info = null;
|
|
345
|
+
read() {
|
|
346
|
+
if (this.header.minorVersion < 5) {
|
|
347
|
+
throw new Error("Unsupported packet version");
|
|
348
|
+
}
|
|
349
|
+
this.info = {
|
|
350
|
+
trackArtist: this.buffer.slice(29, 285).toString("utf16le").replace(/\0/g, ""),
|
|
351
|
+
trackTitle: this.buffer.slice(285, 541).toString("utf16le").replace(/\0/g, ""),
|
|
352
|
+
trackKey: this.buffer.readUInt16LE(541),
|
|
353
|
+
trackID: this.buffer.readUInt32LE(543)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
write() {
|
|
357
|
+
throw new Error("not supported!");
|
|
358
|
+
}
|
|
359
|
+
length() {
|
|
360
|
+
return 548;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
var TCNetPackets = {
|
|
364
|
+
[2 /* OptIn */]: TCNetOptInPacket,
|
|
365
|
+
[3 /* OptOut */]: TCNetOptOutPacket,
|
|
366
|
+
[5 /* Status */]: TCNetStatusPacket,
|
|
367
|
+
[10 /* TimeSync */]: null,
|
|
368
|
+
// not yet implemented
|
|
369
|
+
[13 /* Error */]: null,
|
|
370
|
+
// not yet implemented
|
|
371
|
+
[20 /* Request */]: TCNetRequestPacket,
|
|
372
|
+
[30 /* ApplicationData */]: null,
|
|
373
|
+
// not yet implemented
|
|
374
|
+
[101 /* Control */]: null,
|
|
375
|
+
// not yet implemented
|
|
376
|
+
[128 /* Text */]: null,
|
|
377
|
+
// not yet implemented
|
|
378
|
+
[132 /* Keyboard */]: null,
|
|
379
|
+
// not yet implemented
|
|
380
|
+
[200 /* Data */]: TCNetDataPacket,
|
|
381
|
+
[204 /* File */]: null,
|
|
382
|
+
// not yet implemented
|
|
383
|
+
[254 /* Time */]: TCNetTimePacket
|
|
384
|
+
};
|
|
385
|
+
var TCNetDataPackets = {
|
|
386
|
+
[2 /* MetricsData */]: TCNetDataPacketMetrics,
|
|
387
|
+
[4 /* MetaData */]: TCNetDataPacketMetadata,
|
|
388
|
+
[8 /* BeatGridData */]: null,
|
|
389
|
+
// not yet implemented
|
|
390
|
+
[12 /* CUEData */]: null,
|
|
391
|
+
// not yet implemented
|
|
392
|
+
[16 /* SmallWaveFormData */]: null,
|
|
393
|
+
// not yet implemented
|
|
394
|
+
[32 /* BigWaveFormData */]: null,
|
|
395
|
+
// not yet implemented
|
|
396
|
+
[150 /* MixerData */]: null
|
|
397
|
+
// not yet implemented
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/tcnet.ts
|
|
401
|
+
var EventEmitter = __require("events");
|
|
402
|
+
var TCNET_BROADCAST_PORT = 6e4;
|
|
403
|
+
var TCNET_TIMESTAMP_PORT = 60001;
|
|
404
|
+
var TCNetConfiguration = class {
|
|
405
|
+
logger = null;
|
|
406
|
+
unicastPort = 65023;
|
|
407
|
+
applicationCode = 65535;
|
|
408
|
+
nodeId = Math.floor(Math.random() * 65535);
|
|
409
|
+
nodeName = "TCNET.JS";
|
|
410
|
+
vendorName = "CHDXD1";
|
|
411
|
+
appName = "NODE-TCNET";
|
|
412
|
+
broadcastInterface = null;
|
|
413
|
+
broadcastAddress = "255.255.255.255";
|
|
414
|
+
broadcastListeningAddress = "";
|
|
415
|
+
requestTimeout = 2e3;
|
|
416
|
+
};
|
|
417
|
+
var closeSocket = (socket) => new Promise((resolve) => socket.close(() => resolve()));
|
|
418
|
+
var TCNetClient = class extends EventEmitter {
|
|
419
|
+
config;
|
|
420
|
+
broadcastSocket;
|
|
421
|
+
unicastSocket;
|
|
422
|
+
timestampSocket;
|
|
423
|
+
server;
|
|
424
|
+
seq = 0;
|
|
425
|
+
uptime = 0;
|
|
426
|
+
connected = false;
|
|
427
|
+
connectedHandler = null;
|
|
428
|
+
requests = /* @__PURE__ */ new Map();
|
|
429
|
+
announcementInterval;
|
|
430
|
+
/**
|
|
431
|
+
*
|
|
432
|
+
* @param config configuration for TCNet access
|
|
433
|
+
*/
|
|
434
|
+
constructor(config) {
|
|
435
|
+
super();
|
|
436
|
+
this.config = config || new TCNetConfiguration();
|
|
437
|
+
if (this.config.broadcastInterface && this.config.broadcastAddress == "255.255.255.255") {
|
|
438
|
+
this.config.broadcastAddress = interfaceAddress(this.config.broadcastInterface);
|
|
439
|
+
}
|
|
440
|
+
this.config.broadcastListeningAddress ||= "0.0.0.0";
|
|
441
|
+
}
|
|
442
|
+
get log() {
|
|
443
|
+
return this.config.logger;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Wrapper method to bind a socket with a Promise
|
|
447
|
+
* @param socket socket to bind
|
|
448
|
+
* @param port port to bind to
|
|
449
|
+
* @param address address to bind to
|
|
450
|
+
* @returns Promise which always resolves (no errors in callback)
|
|
451
|
+
*/
|
|
452
|
+
bindSocket(socket, port, address) {
|
|
453
|
+
return new Promise((resolve, reject) => {
|
|
454
|
+
socket.once("error", reject);
|
|
455
|
+
socket.bind(port, address, () => {
|
|
456
|
+
socket.removeListener("error", reject);
|
|
457
|
+
resolve();
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Connect to the TCNet networks
|
|
463
|
+
*/
|
|
464
|
+
async connect() {
|
|
465
|
+
this.broadcastSocket = createSocket({ type: "udp4", reuseAddr: true }, this.receiveBroadcast.bind(this));
|
|
466
|
+
await this.bindSocket(this.broadcastSocket, TCNET_BROADCAST_PORT, this.config.broadcastListeningAddress);
|
|
467
|
+
this.broadcastSocket.setBroadcast(true);
|
|
468
|
+
this.timestampSocket = createSocket({ type: "udp4", reuseAddr: true }, this.receiveTimestamp.bind(this));
|
|
469
|
+
await this.bindSocket(this.timestampSocket, TCNET_TIMESTAMP_PORT, this.config.broadcastListeningAddress);
|
|
470
|
+
this.timestampSocket.setBroadcast(true);
|
|
471
|
+
this.unicastSocket = createSocket({ type: "udp4", reuseAddr: false }, this.receiveUnicast.bind(this));
|
|
472
|
+
await this.bindSocket(this.unicastSocket, this.config.unicastPort, "0.0.0.0");
|
|
473
|
+
await this.announceApp();
|
|
474
|
+
this.announcementInterval = setInterval(this.announceApp.bind(this), 1e3);
|
|
475
|
+
await this.waitConnected();
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Disconnects from TCNet network
|
|
479
|
+
*/
|
|
480
|
+
disconnect() {
|
|
481
|
+
clearInterval(this.announcementInterval);
|
|
482
|
+
this.removeAllListeners();
|
|
483
|
+
this.connected = false;
|
|
484
|
+
return Promise.all([
|
|
485
|
+
closeSocket(this.broadcastSocket),
|
|
486
|
+
closeSocket(this.unicastSocket),
|
|
487
|
+
closeSocket(this.timestampSocket)
|
|
488
|
+
]).catch((err) => {
|
|
489
|
+
const error = new Error("Error disconnecting from TCNet");
|
|
490
|
+
error.cause = err instanceof Error ? err : new Error(String(err));
|
|
491
|
+
this.log?.error(error);
|
|
492
|
+
}).then(() => void 0);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Waiting for unicast from a master
|
|
496
|
+
*/
|
|
497
|
+
waitConnected() {
|
|
498
|
+
return new Promise((resolve, reject) => {
|
|
499
|
+
this.connectedHandler = resolve;
|
|
500
|
+
setTimeout(() => {
|
|
501
|
+
if (!this.connected) {
|
|
502
|
+
this.disconnect();
|
|
503
|
+
reject(new Error("Timeout connecting to network"));
|
|
504
|
+
}
|
|
505
|
+
}, this.config.requestTimeout);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Parse a packet from a ManagementHeader
|
|
510
|
+
* @param header the received management header
|
|
511
|
+
* @returns the parsed packet
|
|
512
|
+
*/
|
|
513
|
+
parsePacket(header) {
|
|
514
|
+
const packetClass = TCNetPackets[header.messageType];
|
|
515
|
+
if (packetClass !== null) {
|
|
516
|
+
const packet = new packetClass();
|
|
517
|
+
packet.buffer = header.buffer;
|
|
518
|
+
packet.header = header;
|
|
519
|
+
if (packet.length() !== -1 && packet.length() !== header.buffer.length) {
|
|
520
|
+
this.log?.debug(
|
|
521
|
+
`${TCNetMessageType[header.messageType]} packet has the wrong length (expected: ${packet.length()}, received: ${header.buffer.length})`
|
|
522
|
+
);
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
packet.read();
|
|
526
|
+
return packet;
|
|
527
|
+
} else {
|
|
528
|
+
this.log?.debug(`Unknown packet type: ${header.messageType} ${TCNetMessageType[header.messageType]}`);
|
|
529
|
+
}
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Callback method to receive datagrams on the broadcast socket
|
|
534
|
+
*
|
|
535
|
+
* @param msg datagram buffer
|
|
536
|
+
* @param rinfo remoteinfo
|
|
537
|
+
*/
|
|
538
|
+
receiveBroadcast(msg, rinfo) {
|
|
539
|
+
const mgmtHeader = new TCNetManagementHeader(msg);
|
|
540
|
+
mgmtHeader.read();
|
|
541
|
+
const packet = this.parsePacket(mgmtHeader);
|
|
542
|
+
if (packet) {
|
|
543
|
+
if (packet instanceof TCNetOptInPacket) {
|
|
544
|
+
if (mgmtHeader.nodeType == 2 /* Master */) {
|
|
545
|
+
this.server = rinfo;
|
|
546
|
+
this.server.port = packet.nodeListenerPort;
|
|
547
|
+
if (this.connectedHandler) {
|
|
548
|
+
this.connected = true;
|
|
549
|
+
this.announceApp().catch((err) => {
|
|
550
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
551
|
+
this.log?.debug(`Failed to announce on connect: ${error.message}`);
|
|
552
|
+
});
|
|
553
|
+
this.connectedHandler();
|
|
554
|
+
this.connectedHandler = null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (packet instanceof TCNetOptOutPacket) {
|
|
559
|
+
if (mgmtHeader.nodeType == 2 /* Master */) {
|
|
560
|
+
this.log?.debug("Received optout from current Master");
|
|
561
|
+
if (this.server?.address == rinfo.address && this.server?.port == packet.nodeListenerPort) {
|
|
562
|
+
this.server = null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (this.connected) {
|
|
567
|
+
this.emit("broadcast", packet);
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
this.log?.debug(`Unknown broadcast packet type: ${mgmtHeader.messageType}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Callback method to receive datagrams on the unicast socket
|
|
575
|
+
*
|
|
576
|
+
* @param msg datagram buffer
|
|
577
|
+
* @param rinfo remoteinfo
|
|
578
|
+
*/
|
|
579
|
+
receiveUnicast(msg, rinfo) {
|
|
580
|
+
const mgmtHeader = new TCNetManagementHeader(msg);
|
|
581
|
+
mgmtHeader.read();
|
|
582
|
+
const packet = this.parsePacket(mgmtHeader);
|
|
583
|
+
if (packet instanceof TCNetDataPacket) {
|
|
584
|
+
const dataPacketClass = TCNetDataPackets[packet.dataType];
|
|
585
|
+
if (dataPacketClass !== null) {
|
|
586
|
+
const dataPacket = new dataPacketClass();
|
|
587
|
+
dataPacket.buffer = msg;
|
|
588
|
+
dataPacket.header = mgmtHeader;
|
|
589
|
+
dataPacket.dataType = packet.dataType;
|
|
590
|
+
dataPacket.layer = packet.layer;
|
|
591
|
+
dataPacket.read();
|
|
592
|
+
if (this.connected) {
|
|
593
|
+
this.emit("data", dataPacket);
|
|
594
|
+
}
|
|
595
|
+
const key = `${dataPacket.dataType}-${dataPacket.layer}`;
|
|
596
|
+
const pendingRequest = this.requests.get(key);
|
|
597
|
+
if (pendingRequest) {
|
|
598
|
+
this.requests.delete(key);
|
|
599
|
+
clearTimeout(pendingRequest.timeout);
|
|
600
|
+
pendingRequest.resolve(dataPacket);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} else if (packet instanceof TCNetOptInPacket) {
|
|
604
|
+
if (mgmtHeader.nodeType == 2 /* Master */) {
|
|
605
|
+
this.server = rinfo;
|
|
606
|
+
this.server.port = packet.nodeListenerPort;
|
|
607
|
+
if (this.connectedHandler) {
|
|
608
|
+
this.connected = true;
|
|
609
|
+
this.connectedHandler();
|
|
610
|
+
this.connectedHandler = null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
if (this.connected) {
|
|
615
|
+
this.emit("broadcast", packet);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Callback method to receive datagrams on the timestamp socket
|
|
621
|
+
* @param msg datagram buffer
|
|
622
|
+
* @param rinfo remoteinfo
|
|
623
|
+
*/
|
|
624
|
+
receiveTimestamp(msg, _rinfo) {
|
|
625
|
+
const mgmtHeader = new TCNetManagementHeader(msg);
|
|
626
|
+
mgmtHeader.read();
|
|
627
|
+
if (mgmtHeader.messageType !== 254 /* Time */) {
|
|
628
|
+
this.log?.debug("Received non Time packet on Time port");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const packet = this.parsePacket(mgmtHeader);
|
|
632
|
+
this.emit("time", packet);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Fill headers of a packet
|
|
636
|
+
*
|
|
637
|
+
* @param packet Packet that needs header information
|
|
638
|
+
*/
|
|
639
|
+
fillHeader(packet) {
|
|
640
|
+
packet.header = new TCNetManagementHeader(packet.buffer);
|
|
641
|
+
packet.header.minorVersion = 5;
|
|
642
|
+
packet.header.nodeId = this.config.nodeId;
|
|
643
|
+
packet.header.messageType = packet.type();
|
|
644
|
+
packet.header.nodeName = this.config.nodeName;
|
|
645
|
+
packet.header.seq = this.seq = (this.seq + 1) % 255;
|
|
646
|
+
packet.header.nodeType = 4;
|
|
647
|
+
packet.header.nodeOptions = 0;
|
|
648
|
+
packet.header.timestamp = 0;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Generalized method to send packets to a given destination on a given socket
|
|
652
|
+
*
|
|
653
|
+
* @param packet Packet to send
|
|
654
|
+
* @param socket Socket to send on
|
|
655
|
+
* @param port Destination Port
|
|
656
|
+
* @param address Destination Address
|
|
657
|
+
*/
|
|
658
|
+
sendPacket(packet, socket, port, address) {
|
|
659
|
+
return new Promise((resolve, reject) => {
|
|
660
|
+
const buffer = Buffer.alloc(packet.length());
|
|
661
|
+
packet.buffer = buffer;
|
|
662
|
+
this.fillHeader(packet);
|
|
663
|
+
packet.header.write();
|
|
664
|
+
packet.write();
|
|
665
|
+
socket.send(buffer, port, address, (err) => {
|
|
666
|
+
if (err) reject(err);
|
|
667
|
+
resolve();
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Sends a packet to the discovered server
|
|
673
|
+
* @param packet Packet to send
|
|
674
|
+
*/
|
|
675
|
+
async sendServer(packet) {
|
|
676
|
+
if (this.server === null) {
|
|
677
|
+
throw new Error("Server not yet discovered");
|
|
678
|
+
}
|
|
679
|
+
await this.sendPacket(packet, this.broadcastSocket, this.server.port, this.server.address);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Called every second to announce our app on the network
|
|
683
|
+
*/
|
|
684
|
+
async announceApp() {
|
|
685
|
+
const optInPacket = new TCNetOptInPacket();
|
|
686
|
+
optInPacket.nodeCount = 0;
|
|
687
|
+
optInPacket.nodeListenerPort = this.config.unicastPort;
|
|
688
|
+
optInPacket.uptime = this.uptime++;
|
|
689
|
+
if (this.uptime >= 12 * 60 * 60) {
|
|
690
|
+
this.uptime = 0;
|
|
691
|
+
}
|
|
692
|
+
optInPacket.vendorName = this.config.vendorName;
|
|
693
|
+
optInPacket.appName = this.config.appName;
|
|
694
|
+
optInPacket.majorVersion = 1;
|
|
695
|
+
optInPacket.minorVersion = 1;
|
|
696
|
+
optInPacket.bugVersion = 1;
|
|
697
|
+
await this.broadcastPacket(optInPacket);
|
|
698
|
+
if (this.server) {
|
|
699
|
+
await this.sendServer(optInPacket);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Broadcasts a packet to the network
|
|
704
|
+
*
|
|
705
|
+
* @param packet packet to broadcast
|
|
706
|
+
*/
|
|
707
|
+
async broadcastPacket(packet) {
|
|
708
|
+
await this.sendPacket(packet, this.broadcastSocket, TCNET_BROADCAST_PORT, this.config.broadcastAddress);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Sends a request packet to the discovered server
|
|
712
|
+
*
|
|
713
|
+
* @param dataType requested data type
|
|
714
|
+
* @param layer requested layer
|
|
715
|
+
* @returns Promise to wait for answer on request
|
|
716
|
+
*/
|
|
717
|
+
requestData(dataType, layer) {
|
|
718
|
+
return new Promise((resolve, reject) => {
|
|
719
|
+
if (!Number.isInteger(layer) || layer < 0 || layer > 7) {
|
|
720
|
+
reject(new RangeError("layer must be an integer between 0 and 7"));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const request = new TCNetRequestPacket();
|
|
724
|
+
request.dataType = dataType;
|
|
725
|
+
request.layer = layer + 1;
|
|
726
|
+
const key = `${dataType}-${layer}`;
|
|
727
|
+
const timeout = setTimeout(() => {
|
|
728
|
+
if (this.requests.delete(key)) {
|
|
729
|
+
reject(new Error("Timeout while requesting data"));
|
|
730
|
+
}
|
|
731
|
+
}, this.config.requestTimeout);
|
|
732
|
+
this.requests.set(key, { resolve, timeout });
|
|
733
|
+
this.sendServer(request).catch((err) => {
|
|
734
|
+
if (this.requests.delete(key)) {
|
|
735
|
+
clearTimeout(timeout);
|
|
736
|
+
reject(err);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
export {
|
|
743
|
+
NodeType,
|
|
744
|
+
TCNetClient,
|
|
745
|
+
TCNetConfiguration,
|
|
746
|
+
TCNetDataPacket,
|
|
747
|
+
TCNetDataPacketMetadata,
|
|
748
|
+
TCNetDataPacketMetrics,
|
|
749
|
+
TCNetDataPacketType,
|
|
750
|
+
TCNetDataPackets,
|
|
751
|
+
TCNetLayerStatus,
|
|
752
|
+
TCNetLayerSyncMaster,
|
|
753
|
+
TCNetManagementHeader,
|
|
754
|
+
TCNetMessageType,
|
|
755
|
+
TCNetOptInPacket,
|
|
756
|
+
TCNetOptOutPacket,
|
|
757
|
+
TCNetPacket,
|
|
758
|
+
TCNetPackets,
|
|
759
|
+
TCNetRequestPacket,
|
|
760
|
+
TCNetStatusPacket,
|
|
761
|
+
TCNetTimePacket,
|
|
762
|
+
TCNetTimecode,
|
|
763
|
+
TCNetTimecodeState
|
|
764
|
+
};
|