@9c5s/node-tcnet 0.5.1 → 0.6.0

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/dist/index.mjs CHANGED
@@ -360,6 +360,163 @@ var TCNetDataPacketMetadata = class extends TCNetDataPacket {
360
360
  return 548;
361
361
  }
362
362
  };
363
+ var TCNetDataPacketCUE = class extends TCNetDataPacket {
364
+ data = null;
365
+ read() {
366
+ const loopInTime = this.buffer.readUInt32LE(42);
367
+ const loopOutTime = this.buffer.readUInt32LE(46);
368
+ const cues = [];
369
+ const cueStart = 50;
370
+ for (let i = 0; i < 18; i++) {
371
+ const offset = cueStart + i * 22;
372
+ if (offset + 22 > this.buffer.length) break;
373
+ const type = this.buffer.readUInt8(offset);
374
+ if (type === 0) continue;
375
+ cues.push({
376
+ index: i + 1,
377
+ type,
378
+ inTime: this.buffer.readUInt32LE(offset + 2),
379
+ outTime: this.buffer.readUInt32LE(offset + 6),
380
+ color: {
381
+ r: this.buffer.readUInt8(offset + 11),
382
+ g: this.buffer.readUInt8(offset + 12),
383
+ b: this.buffer.readUInt8(offset + 13)
384
+ }
385
+ });
386
+ }
387
+ this.data = { loopInTime, loopOutTime, cues };
388
+ }
389
+ write() {
390
+ throw new Error("not supported!");
391
+ }
392
+ length() {
393
+ return 436;
394
+ }
395
+ };
396
+ function parseWaveformBars(source, dataStart, maxBytes) {
397
+ const bars = [];
398
+ const end = maxBytes !== void 0 ? Math.min(dataStart + maxBytes, source.length) : source.length;
399
+ const safeEnd = dataStart + (end - dataStart & ~1);
400
+ for (let i = dataStart; i < safeEnd; i += 2) {
401
+ bars.push({
402
+ level: source.readUInt8(i),
403
+ color: source.readUInt8(i + 1)
404
+ });
405
+ }
406
+ return bars;
407
+ }
408
+ var TCNetDataPacketSmallWaveForm = class extends TCNetDataPacket {
409
+ data = null;
410
+ read() {
411
+ this.data = { bars: parseWaveformBars(this.buffer, 42, 2400) };
412
+ }
413
+ write() {
414
+ throw new Error("not supported!");
415
+ }
416
+ length() {
417
+ return 2442;
418
+ }
419
+ };
420
+ var TCNetDataPacketMixer = class extends TCNetDataPacket {
421
+ data = null;
422
+ read() {
423
+ if (this.buffer.length < 259) {
424
+ return;
425
+ }
426
+ const parseChannel = (offset) => ({
427
+ sourceSelect: this.buffer.readUInt8(offset),
428
+ audioLevel: this.buffer.readUInt8(offset + 1),
429
+ faderLevel: this.buffer.readUInt8(offset + 2),
430
+ trimLevel: this.buffer.readUInt8(offset + 3),
431
+ compLevel: this.buffer.readUInt8(offset + 4),
432
+ eqHi: this.buffer.readUInt8(offset + 5),
433
+ eqHiMid: this.buffer.readUInt8(offset + 6),
434
+ eqLowMid: this.buffer.readUInt8(offset + 7),
435
+ eqLow: this.buffer.readUInt8(offset + 8),
436
+ filterColor: this.buffer.readUInt8(offset + 9),
437
+ send: this.buffer.readUInt8(offset + 10),
438
+ cueA: this.buffer.readUInt8(offset + 11),
439
+ cueB: this.buffer.readUInt8(offset + 12),
440
+ crossfaderAssign: this.buffer.readUInt8(offset + 13)
441
+ });
442
+ this.data = {
443
+ mixerId: this.buffer.readUInt8(25),
444
+ mixerType: this.buffer.readUInt8(26),
445
+ mixerName: this.buffer.slice(29, 45).toString("ascii").replace(/\0.*$/g, ""),
446
+ masterAudioLevel: this.buffer.readUInt8(61),
447
+ masterFaderLevel: this.buffer.readUInt8(62),
448
+ masterFilter: this.buffer.readUInt8(69),
449
+ masterIsolatorOn: this.buffer.readUInt8(74) === 1,
450
+ masterIsolatorHi: this.buffer.readUInt8(75),
451
+ masterIsolatorMid: this.buffer.readUInt8(76),
452
+ masterIsolatorLow: this.buffer.readUInt8(77),
453
+ filterHpf: this.buffer.readUInt8(79),
454
+ filterLpf: this.buffer.readUInt8(80),
455
+ filterResonance: this.buffer.readUInt8(81),
456
+ crossFader: this.buffer.readUInt8(99),
457
+ crossFaderCurve: this.buffer.readUInt8(98),
458
+ channelFaderCurve: this.buffer.readUInt8(97),
459
+ beatFxOn: this.buffer.readUInt8(100) === 1,
460
+ beatFxSelect: this.buffer.readUInt8(103),
461
+ beatFxLevelDepth: this.buffer.readUInt8(101),
462
+ beatFxChannelSelect: this.buffer.readUInt8(102),
463
+ headphonesALevel: this.buffer.readUInt8(108),
464
+ headphonesBLevel: this.buffer.readUInt8(110),
465
+ boothLevel: this.buffer.readUInt8(112),
466
+ channels: [125, 149, 173, 197, 221, 245].map(parseChannel)
467
+ };
468
+ }
469
+ write() {
470
+ throw new Error("not supported!");
471
+ }
472
+ length() {
473
+ return 270;
474
+ }
475
+ };
476
+ var TCNetDataPacketBeatGrid = class extends TCNetDataPacket {
477
+ data = null;
478
+ read() {
479
+ this.readFromOffset(42);
480
+ }
481
+ // アセンブル済みバッファからのパース用
482
+ readAssembled(assembled) {
483
+ this.readFromOffset(0, assembled);
484
+ }
485
+ readFromOffset(dataStart, buf) {
486
+ const source = buf ?? this.buffer;
487
+ const entries = [];
488
+ for (let offset = dataStart; offset + 8 <= source.length; offset += 8) {
489
+ const beatNumber = source.readUInt16LE(offset);
490
+ const beatType = source.readUInt8(offset + 2);
491
+ const timestampMs = source.readUInt32LE(offset + 4);
492
+ if (beatNumber === 0 && timestampMs === 0) continue;
493
+ entries.push({ beatNumber, beatType, timestampMs });
494
+ }
495
+ this.data = { entries };
496
+ }
497
+ write() {
498
+ throw new Error("not supported!");
499
+ }
500
+ length() {
501
+ return 2442;
502
+ }
503
+ };
504
+ var TCNetDataPacketBigWaveForm = class extends TCNetDataPacket {
505
+ data = null;
506
+ read() {
507
+ this.data = { bars: parseWaveformBars(this.buffer, 42) };
508
+ }
509
+ // アセンブル済みバッファからのパース用
510
+ readAssembled(assembled) {
511
+ this.data = { bars: parseWaveformBars(assembled, 0) };
512
+ }
513
+ write() {
514
+ throw new Error("not supported!");
515
+ }
516
+ length() {
517
+ return -1;
518
+ }
519
+ };
363
520
  var TCNetPackets = {
364
521
  [2 /* OptIn */]: TCNetOptInPacket,
365
522
  [3 /* OptOut */]: TCNetOptOutPacket,
@@ -385,22 +542,48 @@ var TCNetPackets = {
385
542
  var TCNetDataPackets = {
386
543
  [2 /* MetricsData */]: TCNetDataPacketMetrics,
387
544
  [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
545
+ [8 /* BeatGridData */]: TCNetDataPacketBeatGrid,
546
+ [12 /* CUEData */]: TCNetDataPacketCUE,
547
+ [16 /* SmallWaveFormData */]: TCNetDataPacketSmallWaveForm,
548
+ [32 /* BigWaveFormData */]: TCNetDataPacketBigWaveForm,
549
+ [150 /* MixerData */]: TCNetDataPacketMixer
550
+ };
551
+
552
+ // src/multi-packet.ts
553
+ var MultiPacketAssembler = class {
554
+ packets = /* @__PURE__ */ new Map();
555
+ totalPackets = 0;
556
+ // パケットを追加し、全パケットが揃ったら true を返す
557
+ add(buffer) {
558
+ if (buffer.length < 42) return false;
559
+ const newTotalPackets = buffer.readUInt32LE(30);
560
+ if (newTotalPackets === 0) return false;
561
+ if (this.totalPackets > 0 && newTotalPackets !== this.totalPackets) return false;
562
+ this.totalPackets = newTotalPackets;
563
+ const packetNo = buffer.readUInt32LE(34);
564
+ const clusterSize = buffer.readUInt32LE(38);
565
+ const dataStart = 42;
566
+ if (dataStart + clusterSize > buffer.length) return false;
567
+ this.packets.set(packetNo, Buffer.from(buffer.slice(dataStart, dataStart + clusterSize)));
568
+ return this.packets.size >= this.totalPackets;
569
+ }
570
+ // packetNo 順にソートしてデータを結合する
571
+ assemble() {
572
+ const sorted = [...this.packets.entries()].sort((a, b) => a[0] - b[0]);
573
+ return Buffer.concat(sorted.map(([, buf]) => buf));
574
+ }
575
+ // 状態をリセットする
576
+ reset() {
577
+ this.packets.clear();
578
+ this.totalPackets = 0;
579
+ }
398
580
  };
399
581
 
400
582
  // src/tcnet.ts
401
583
  var EventEmitter = __require("events");
402
584
  var TCNET_BROADCAST_PORT = 6e4;
403
585
  var TCNET_TIMESTAMP_PORT = 60001;
586
+ var MULTI_PACKET_TYPES = /* @__PURE__ */ new Set([32 /* BigWaveFormData */, 8 /* BeatGridData */]);
404
587
  var TCNetConfiguration = class {
405
588
  logger = null;
406
589
  unicastPort = 65023;
@@ -589,15 +772,44 @@ var TCNetClient = class extends EventEmitter {
589
772
  dataPacket.dataType = packet.dataType;
590
773
  dataPacket.layer = packet.layer;
591
774
  dataPacket.read();
592
- if (this.connected) {
593
- this.emit("data", dataPacket);
594
- }
595
775
  const key = `${dataPacket.dataType}-${dataPacket.layer}`;
596
776
  const pendingRequest = this.requests.get(key);
597
- if (pendingRequest) {
598
- this.requests.delete(key);
599
- clearTimeout(pendingRequest.timeout);
600
- pendingRequest.resolve(dataPacket);
777
+ if (pendingRequest && pendingRequest.assembler) {
778
+ const complete = pendingRequest.assembler.add(msg);
779
+ if (complete) {
780
+ const assembled = pendingRequest.assembler.assemble();
781
+ const finalPacket = new dataPacketClass();
782
+ finalPacket.buffer = msg;
783
+ finalPacket.header = mgmtHeader;
784
+ finalPacket.dataType = dataPacket.dataType;
785
+ finalPacket.layer = dataPacket.layer;
786
+ if ("readAssembled" in finalPacket && typeof finalPacket.readAssembled === "function") {
787
+ finalPacket.readAssembled(assembled);
788
+ }
789
+ this.requests.delete(key);
790
+ clearTimeout(pendingRequest.timeout);
791
+ if (this.connected) {
792
+ this.emit("data", finalPacket);
793
+ }
794
+ pendingRequest.resolve(finalPacket);
795
+ } else {
796
+ clearTimeout(pendingRequest.timeout);
797
+ pendingRequest.timeout = setTimeout(() => {
798
+ if (this.requests.delete(key)) {
799
+ pendingRequest.assembler?.reset();
800
+ pendingRequest.reject(new Error("Timeout while requesting data"));
801
+ }
802
+ }, this.config.requestTimeout);
803
+ }
804
+ } else {
805
+ if (this.connected) {
806
+ this.emit("data", dataPacket);
807
+ }
808
+ if (pendingRequest) {
809
+ this.requests.delete(key);
810
+ clearTimeout(pendingRequest.timeout);
811
+ pendingRequest.resolve(dataPacket);
812
+ }
601
813
  }
602
814
  }
603
815
  } else if (packet instanceof TCNetOptInPacket) {
@@ -725,11 +937,18 @@ var TCNetClient = class extends EventEmitter {
725
937
  request.layer = layer + 1;
726
938
  const key = `${dataType}-${layer}`;
727
939
  const timeout = setTimeout(() => {
728
- if (this.requests.delete(key)) {
940
+ const req = this.requests.get(key);
941
+ if (req && this.requests.delete(key)) {
942
+ req.assembler?.reset();
729
943
  reject(new Error("Timeout while requesting data"));
730
944
  }
731
945
  }, this.config.requestTimeout);
732
- this.requests.set(key, { resolve, timeout });
946
+ this.requests.set(key, {
947
+ resolve,
948
+ reject,
949
+ timeout,
950
+ assembler: MULTI_PACKET_TYPES.has(dataType) ? new MultiPacketAssembler() : void 0
951
+ });
733
952
  this.sendServer(request).catch((err) => {
734
953
  if (this.requests.delete(key)) {
735
954
  clearTimeout(timeout);
@@ -740,12 +959,18 @@ var TCNetClient = class extends EventEmitter {
740
959
  }
741
960
  };
742
961
  export {
962
+ MultiPacketAssembler,
743
963
  NodeType,
744
964
  TCNetClient,
745
965
  TCNetConfiguration,
746
966
  TCNetDataPacket,
967
+ TCNetDataPacketBeatGrid,
968
+ TCNetDataPacketBigWaveForm,
969
+ TCNetDataPacketCUE,
747
970
  TCNetDataPacketMetadata,
748
971
  TCNetDataPacketMetrics,
972
+ TCNetDataPacketMixer,
973
+ TCNetDataPacketSmallWaveForm,
749
974
  TCNetDataPacketType,
750
975
  TCNetDataPackets,
751
976
  TCNetLayerStatus,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@9c5s/node-tcnet",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Implements the TC-Supply TCNet protocol used by ShowKontrol and ProDJLink Bridge app from Pioneer",
5
5
  "exports": {
6
6
  ".": {
@@ -19,15 +19,17 @@
19
19
  "/dist"
20
20
  ],
21
21
  "scripts": {
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
22
24
  "example": "node --import tsx examples",
23
25
  "build": "rm -rf dist && tsup && check-export-map",
24
26
  "watch": "tsc -b -w",
25
27
  "changeset": "changeset",
26
28
  "clean": "tsc -b --clean",
27
29
  "lint": "eslint . --ext ts --ignore-pattern '**/*.d.ts'",
28
- "format:fix": "prettier --write \"./**/*.{ts,html,css,json,jsonc}\"",
29
- "format:check": "prettier --check \"./**/*.{ts,html,css,json,jsonc}\"",
30
- "format-pre-commit": "pretty-quick --staged --pattern '*/**/*.{ts,html,css,json,jsonc}'",
30
+ "format:fix": "prettier --write \"./**/*.{ts,js,mjs,json,jsonc}\"",
31
+ "format:check": "prettier --check \"./**/*.{ts,js,mjs,json,jsonc}\"",
32
+ "format-pre-commit": "pretty-quick --staged --pattern '*/**/*.{ts,js,mjs,json,jsonc}'",
31
33
  "mdlint": "markdownlint-cli2 \"docs/wiki/**/*.md\" \"*.{md,MD}\" \"!CHANGELOG.md\"",
32
34
  "mdlint:fix": "markdownlint-cli2 --fix \"docs/wiki/**/*.md\" \"*.{md,MD}\" \"!CHANGELOG.md\"",
33
35
  "textlint": "textlint \"docs/wiki/**/*.md\" \"README.MD\"",
@@ -35,9 +37,10 @@
35
37
  },
36
38
  "devDependencies": {
37
39
  "@changesets/cli": "^2.27.9",
40
+ "@changesets/parse": "^0.4.3",
38
41
  "@commitlint/cli": "^20.5.0",
39
42
  "@commitlint/config-conventional": "^20.5.0",
40
- "@types/node": "^15.12.5",
43
+ "@types/node": "^25.5.0",
41
44
  "@typescript-eslint/eslint-plugin": "^4.22.1",
42
45
  "@typescript-eslint/parser": "^4.22.1",
43
46
  "check-export-map": "^1.3.1",
@@ -50,7 +53,8 @@
50
53
  "textlint-rule-preset-ja-technical-writing": "^12.0.2",
51
54
  "tsup": "^8.3.5",
52
55
  "tsx": "^4.19.2",
53
- "typescript": "^4.2.4"
56
+ "typescript": "^4.2.4",
57
+ "vitest": "^4.1.0"
54
58
  },
55
59
  "publishConfig": {
56
60
  "access": "public",