alphatheta-connect 0.19.2 → 0.21.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/lib/cli.js CHANGED
@@ -74,26 +74,30 @@ async function extractArtworkFromDevice(device, slot, filePath, logger = logger_
74
74
 
75
75
  Object.defineProperty(exports, "__esModule", ({ value: true }));
76
76
  exports.extractFromAiff = extractFromAiff;
77
- const id3_1 = __webpack_require__(/*! ./id3 */ "./src/artwork/parsers/id3.ts");
78
77
  const reader_1 = __webpack_require__(/*! ../reader */ "./src/artwork/reader.ts");
78
+ const id3_1 = __webpack_require__(/*! ./id3 */ "./src/artwork/parsers/id3.ts");
79
79
  async function extractFromAiff(reader) {
80
80
  const header = await reader.read(0, 12);
81
- if (header.length < 12 || header.toString('ascii', 0, 4) !== 'FORM')
81
+ if (header.length < 12 || header.toString('ascii', 0, 4) !== 'FORM') {
82
82
  return null;
83
+ }
83
84
  const formType = header.toString('ascii', 8, 12);
84
- if (formType !== 'AIFF' && formType !== 'AIFC')
85
+ if (formType !== 'AIFF' && formType !== 'AIFC') {
85
86
  return null;
87
+ }
86
88
  const formSize = header.readUInt32BE(4);
87
89
  const fileEnd = Math.min(8 + formSize, reader.size);
88
90
  let offset = 12;
89
91
  while (offset + 8 < fileEnd) {
90
92
  const chunkHeader = await reader.read(offset, 8);
91
- if (chunkHeader.length < 8)
93
+ if (chunkHeader.length < 8) {
92
94
  break;
95
+ }
93
96
  const chunkId = chunkHeader.toString('ascii', 0, 4);
94
97
  const chunkSize = chunkHeader.readUInt32BE(4);
95
- if (chunkSize <= 0 || offset + 8 + chunkSize > fileEnd)
98
+ if (chunkSize <= 0 || offset + 8 + chunkSize > fileEnd) {
96
99
  break;
100
+ }
97
101
  if (chunkId === 'ID3 ' || chunkId === 'id3 ') {
98
102
  const id3Data = await reader.read(offset + 8, chunkSize);
99
103
  const id3Reader = (0, reader_1.createBufferReader)(id3Data, 'mp3');
@@ -120,32 +124,37 @@ exports.extractFromFlac = extractFromFlac;
120
124
  const types_1 = __webpack_require__(/*! ../types */ "./src/artwork/types.ts");
121
125
  const utils_1 = __webpack_require__(/*! ./utils */ "./src/artwork/parsers/utils.ts");
122
126
  function parsePictureBlock(data) {
123
- if (data.length < 32)
127
+ if (data.length < 32) {
124
128
  return null;
129
+ }
125
130
  let offset = 0;
126
131
  const pictureType = data.readUInt32BE(offset);
127
132
  offset += 4;
128
133
  const mimeLength = data.readUInt32BE(offset);
129
134
  offset += 4;
130
- if (offset + mimeLength > data.length)
135
+ if (offset + mimeLength > data.length) {
131
136
  return null;
137
+ }
132
138
  const mimeType = data.toString('utf8', offset, offset + mimeLength);
133
139
  offset += mimeLength;
134
140
  const descLength = data.readUInt32BE(offset);
135
141
  offset += 4 + descLength;
136
- if (offset + 16 > data.length)
142
+ if (offset + 16 > data.length) {
137
143
  return null;
144
+ }
138
145
  const width = data.readUInt32BE(offset);
139
146
  offset += 4;
140
147
  const height = data.readUInt32BE(offset);
141
148
  offset += 4 + 8; // Skip depth and colors
142
149
  const imageLength = data.readUInt32BE(offset);
143
150
  offset += 4;
144
- if (offset + imageLength > data.length)
151
+ if (offset + imageLength > data.length) {
145
152
  return null;
153
+ }
146
154
  const imageData = data.subarray(offset, offset + imageLength);
147
- if (imageData.length === 0)
155
+ if (imageData.length === 0) {
148
156
  return null;
157
+ }
149
158
  return {
150
159
  data: imageData,
151
160
  mimeType: (0, utils_1.normalizeMimeType)(mimeType),
@@ -156,29 +165,34 @@ function parsePictureBlock(data) {
156
165
  }
157
166
  async function extractFromFlac(reader) {
158
167
  const signature = await reader.read(0, 4);
159
- if (signature.toString('ascii') !== 'fLaC')
168
+ if (signature.toString('ascii') !== 'fLaC') {
160
169
  return null;
170
+ }
161
171
  let offset = 4;
162
172
  let isLastBlock = false;
163
173
  let frontCover = null;
164
174
  let anyArtwork = null;
165
175
  while (!isLastBlock && offset < reader.size) {
166
176
  const blockHeader = await reader.read(offset, 4);
167
- if (blockHeader.length < 4)
177
+ if (blockHeader.length < 4) {
168
178
  break;
179
+ }
169
180
  isLastBlock = (blockHeader[0] & 0x80) !== 0;
170
181
  const blockType = blockHeader[0] & 0x7f;
171
182
  const blockLength = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
172
- if (blockLength <= 0 || offset + 4 + blockLength > reader.size)
183
+ if (blockLength <= 0 || offset + 4 + blockLength > reader.size) {
173
184
  break;
185
+ }
174
186
  if (blockType === 6 /* MetadataBlockType.PICTURE */) {
175
187
  const pictureData = await reader.read(offset + 4, blockLength);
176
188
  const artwork = parsePictureBlock(pictureData);
177
189
  if (artwork) {
178
- if (artwork.pictureType === types_1.PictureType.FrontCover)
190
+ if (artwork.pictureType === types_1.PictureType.FrontCover) {
179
191
  frontCover = artwork;
180
- else if (!anyArtwork)
192
+ }
193
+ else if (!anyArtwork) {
181
194
  anyArtwork = artwork;
195
+ }
182
196
  }
183
197
  }
184
198
  offset += 4 + blockLength;
@@ -212,14 +226,16 @@ function readNullTerminatedString(buffer, offset, encoding) {
212
226
  let end = offset;
213
227
  if (isUtf16) {
214
228
  while (end < buffer.length - 1) {
215
- if (buffer[end] === 0 && buffer[end + 1] === 0)
229
+ if (buffer[end] === 0 && buffer[end + 1] === 0) {
216
230
  break;
231
+ }
217
232
  end += 2;
218
233
  }
219
234
  }
220
235
  else {
221
- while (end < buffer.length && buffer[end] !== 0)
236
+ while (end < buffer.length && buffer[end] !== 0) {
222
237
  end++;
238
+ }
223
239
  }
224
240
  let value;
225
241
  if (encoding === 'utf16be') {
@@ -237,56 +253,70 @@ function readNullTerminatedString(buffer, offset, encoding) {
237
253
  }
238
254
  function getTextEncoding(encodingByte) {
239
255
  switch (encodingByte) {
240
- case 0: return 'latin1';
241
- case 1: return 'utf16le';
242
- case 2: return 'utf16be';
243
- case 3: return 'utf8';
244
- default: return 'latin1';
256
+ case 0:
257
+ return 'latin1';
258
+ case 1:
259
+ return 'utf16le';
260
+ case 2:
261
+ return 'utf16be';
262
+ case 3:
263
+ return 'utf8';
264
+ default:
265
+ return 'latin1';
245
266
  }
246
267
  }
247
268
  function parseApicFrame(data) {
248
- if (data.length < 4)
269
+ if (data.length < 4) {
249
270
  return null;
271
+ }
250
272
  let offset = 0;
251
273
  const encodingByte = data[offset++];
252
274
  let encoding = getTextEncoding(encodingByte);
253
275
  if (encodingByte === 1 && data.length > offset + 2) {
254
276
  const bom = data.readUInt16BE(offset);
255
- if (bom === 0xfeff)
277
+ if (bom === 0xfeff) {
256
278
  encoding = 'utf16be';
257
- else if (bom === 0xfffe)
279
+ }
280
+ else if (bom === 0xfffe) {
258
281
  encoding = 'utf16le';
282
+ }
259
283
  }
260
284
  const mimeResult = readNullTerminatedString(data, offset, 'latin1');
261
285
  const mimeType = mimeResult.value;
262
286
  offset += mimeResult.bytesConsumed;
263
- if (offset >= data.length)
287
+ if (offset >= data.length) {
264
288
  return null;
289
+ }
265
290
  const pictureType = data[offset++];
266
- if (offset >= data.length)
291
+ if (offset >= data.length) {
267
292
  return null;
293
+ }
268
294
  const descResult = readNullTerminatedString(data, offset, encoding);
269
295
  offset += descResult.bytesConsumed;
270
- if (offset >= data.length)
296
+ if (offset >= data.length) {
271
297
  return null;
298
+ }
272
299
  const imageData = data.subarray(offset);
273
- if (imageData.length === 0)
300
+ if (imageData.length === 0) {
274
301
  return null;
302
+ }
275
303
  const detectedType = (0, utils_1.detectImageType)(imageData);
276
304
  const finalMimeType = detectedType !== null && detectedType !== void 0 ? detectedType : (mimeType.includes('png') ? 'image/png' : 'image/jpeg');
277
305
  return { data: imageData, mimeType: finalMimeType, pictureType };
278
306
  }
279
307
  async function extractFromMp3(reader) {
280
308
  const header = await reader.read(0, 10);
281
- if (header.length < 10 || header.toString('ascii', 0, 3) !== 'ID3')
309
+ if (header.length < 10 || header.toString('ascii', 0, 3) !== 'ID3') {
282
310
  return null;
311
+ }
283
312
  const majorVersion = header[3];
284
313
  const flags = header[5];
285
314
  const tagSize = readSyncsafe(header, 6);
286
315
  let extendedHeaderSize = 0;
287
316
  if (flags & 0x40) {
288
317
  const extHeader = await reader.read(10, 4);
289
- extendedHeaderSize = majorVersion === 4 ? readSyncsafe(extHeader, 0) : extHeader.readUInt32BE(0);
318
+ extendedHeaderSize =
319
+ majorVersion === 4 ? readSyncsafe(extHeader, 0) : extHeader.readUInt32BE(0);
290
320
  }
291
321
  const tagData = await reader.read(10 + extendedHeaderSize, tagSize - extendedHeaderSize);
292
322
  let offset = 0;
@@ -294,29 +324,37 @@ async function extractFromMp3(reader) {
294
324
  let frontCover = null;
295
325
  let anyArtwork = null;
296
326
  while (offset < tagData.length - frameHeaderSize) {
297
- if (tagData[offset] === 0)
327
+ if (tagData[offset] === 0) {
298
328
  break;
329
+ }
299
330
  let frameId;
300
331
  let frameSize;
301
332
  if (majorVersion >= 3) {
302
333
  frameId = tagData.toString('ascii', offset, offset + 4);
303
- frameSize = majorVersion === 4 ? readSyncsafe(tagData, offset + 4) : tagData.readUInt32BE(offset + 4);
334
+ frameSize =
335
+ majorVersion === 4
336
+ ? readSyncsafe(tagData, offset + 4)
337
+ : tagData.readUInt32BE(offset + 4);
304
338
  }
305
339
  else {
306
340
  frameId = tagData.toString('ascii', offset, offset + 3);
307
- frameSize = (tagData[offset + 3] << 16) | (tagData[offset + 4] << 8) | tagData[offset + 5];
341
+ frameSize =
342
+ (tagData[offset + 3] << 16) | (tagData[offset + 4] << 8) | tagData[offset + 5];
308
343
  }
309
- if (frameSize <= 0 || frameSize > tagData.length - offset)
344
+ if (frameSize <= 0 || frameSize > tagData.length - offset) {
310
345
  break;
346
+ }
311
347
  const frameIdNormalized = majorVersion >= 3 ? frameId : frameId === 'PIC' ? 'APIC' : frameId;
312
348
  if (frameIdNormalized === 'APIC') {
313
349
  const frameData = tagData.subarray(offset + frameHeaderSize, offset + frameHeaderSize + frameSize);
314
350
  const artwork = parseApicFrame(frameData);
315
351
  if (artwork) {
316
- if (artwork.pictureType === types_1.PictureType.FrontCover)
352
+ if (artwork.pictureType === types_1.PictureType.FrontCover) {
317
353
  frontCover = artwork;
318
- else if (!anyArtwork)
354
+ }
355
+ else if (!anyArtwork) {
319
356
  anyArtwork = artwork;
357
+ }
320
358
  }
321
359
  }
322
360
  offset += frameHeaderSize + frameSize;
@@ -336,15 +374,15 @@ async function extractFromMp3(reader) {
336
374
  "use strict";
337
375
 
338
376
  Object.defineProperty(exports, "__esModule", ({ value: true }));
339
- exports.normalizeMimeType = exports.detectImageType = exports.extractFromAiff = exports.extractFromFlac = exports.extractFromMp4 = exports.extractFromMp3 = void 0;
377
+ exports.normalizeMimeType = exports.detectImageType = exports.extractFromMp4 = exports.extractFromMp3 = exports.extractFromFlac = exports.extractFromAiff = void 0;
378
+ var aiff_1 = __webpack_require__(/*! ./aiff */ "./src/artwork/parsers/aiff.ts");
379
+ Object.defineProperty(exports, "extractFromAiff", ({ enumerable: true, get: function () { return aiff_1.extractFromAiff; } }));
380
+ var flac_1 = __webpack_require__(/*! ./flac */ "./src/artwork/parsers/flac.ts");
381
+ Object.defineProperty(exports, "extractFromFlac", ({ enumerable: true, get: function () { return flac_1.extractFromFlac; } }));
340
382
  var id3_1 = __webpack_require__(/*! ./id3 */ "./src/artwork/parsers/id3.ts");
341
383
  Object.defineProperty(exports, "extractFromMp3", ({ enumerable: true, get: function () { return id3_1.extractFromMp3; } }));
342
384
  var mp4_1 = __webpack_require__(/*! ./mp4 */ "./src/artwork/parsers/mp4.ts");
343
385
  Object.defineProperty(exports, "extractFromMp4", ({ enumerable: true, get: function () { return mp4_1.extractFromMp4; } }));
344
- var flac_1 = __webpack_require__(/*! ./flac */ "./src/artwork/parsers/flac.ts");
345
- Object.defineProperty(exports, "extractFromFlac", ({ enumerable: true, get: function () { return flac_1.extractFromFlac; } }));
346
- var aiff_1 = __webpack_require__(/*! ./aiff */ "./src/artwork/parsers/aiff.ts");
347
- Object.defineProperty(exports, "extractFromAiff", ({ enumerable: true, get: function () { return aiff_1.extractFromAiff; } }));
348
386
  var utils_1 = __webpack_require__(/*! ./utils */ "./src/artwork/parsers/utils.ts");
349
387
  Object.defineProperty(exports, "detectImageType", ({ enumerable: true, get: function () { return utils_1.detectImageType; } }));
350
388
  Object.defineProperty(exports, "normalizeMimeType", ({ enumerable: true, get: function () { return utils_1.normalizeMimeType; } }));
@@ -365,30 +403,37 @@ exports.extractFromMp4 = extractFromMp4;
365
403
  const types_1 = __webpack_require__(/*! ../types */ "./src/artwork/types.ts");
366
404
  const utils_1 = __webpack_require__(/*! ./utils */ "./src/artwork/parsers/utils.ts");
367
405
  async function readAtomHeader(reader, offset) {
368
- if (offset + 8 > reader.size)
406
+ if (offset + 8 > reader.size) {
369
407
  return null;
408
+ }
370
409
  const header = await reader.read(offset, 8);
371
410
  const size = header.readUInt32BE(0);
372
411
  const type = header.toString('ascii', 4, 8);
373
412
  if (size === 1) {
374
- if (offset + 16 > reader.size)
413
+ if (offset + 16 > reader.size) {
375
414
  return null;
415
+ }
376
416
  const extHeader = await reader.read(offset + 8, 8);
377
417
  const extSize = Number(extHeader.readBigUInt64BE(0));
378
418
  return { size: extSize, type, headerSize: 16 };
379
419
  }
380
- if (size === 0)
420
+ if (size === 0) {
381
421
  return { size: reader.size - offset, type, headerSize: 8 };
422
+ }
382
423
  return { size, type, headerSize: 8 };
383
424
  }
384
425
  async function findAtom(reader, startOffset, endOffset, targetType) {
385
426
  let offset = startOffset;
386
427
  while (offset < endOffset) {
387
428
  const header = await readAtomHeader(reader, offset);
388
- if (!header || header.size <= 0)
429
+ if (!header || header.size <= 0) {
389
430
  break;
431
+ }
390
432
  if (header.type === targetType) {
391
- return { dataOffset: offset + header.headerSize, dataSize: header.size - header.headerSize };
433
+ return {
434
+ dataOffset: offset + header.headerSize,
435
+ dataSize: header.size - header.headerSize,
436
+ };
392
437
  }
393
438
  offset += header.size;
394
439
  }
@@ -399,35 +444,43 @@ async function findMoovAtom(reader) {
399
444
  }
400
445
  async function findCoverArtData(reader, moovOffset, moovSize) {
401
446
  const udta = await findAtom(reader, moovOffset, moovOffset + moovSize, 'udta');
402
- if (!udta)
447
+ if (!udta) {
403
448
  return null;
449
+ }
404
450
  const meta = await findAtom(reader, udta.dataOffset, udta.dataOffset + udta.dataSize, 'meta');
405
- if (!meta)
451
+ if (!meta) {
406
452
  return null;
453
+ }
407
454
  const metaDataStart = meta.dataOffset + 4;
408
455
  const metaDataEnd = meta.dataOffset + meta.dataSize;
409
456
  const ilst = await findAtom(reader, metaDataStart, metaDataEnd, 'ilst');
410
- if (!ilst)
457
+ if (!ilst) {
411
458
  return null;
459
+ }
412
460
  const covr = await findAtom(reader, ilst.dataOffset, ilst.dataOffset + ilst.dataSize, 'covr');
413
- if (!covr)
461
+ if (!covr) {
414
462
  return null;
463
+ }
415
464
  const data = await findAtom(reader, covr.dataOffset, covr.dataOffset + covr.dataSize, 'data');
416
- if (!data || data.dataSize < 8)
465
+ if (!data || data.dataSize < 8) {
417
466
  return null;
467
+ }
418
468
  return reader.read(data.dataOffset + 8, data.dataSize - 8);
419
469
  }
420
470
  async function extractFromMp4(reader) {
421
471
  var _a;
422
472
  const ftypHeader = await reader.read(0, 8);
423
- if (ftypHeader.length < 8 || ftypHeader.toString('ascii', 4, 8) !== 'ftyp')
473
+ if (ftypHeader.length < 8 || ftypHeader.toString('ascii', 4, 8) !== 'ftyp') {
424
474
  return null;
475
+ }
425
476
  const moov = await findMoovAtom(reader);
426
- if (!moov)
477
+ if (!moov) {
427
478
  return null;
479
+ }
428
480
  const imageData = await findCoverArtData(reader, moov.dataOffset, moov.dataSize);
429
- if (!imageData || imageData.length === 0)
481
+ if (!imageData || imageData.length === 0) {
430
482
  return null;
483
+ }
431
484
  const mimeType = (_a = (0, utils_1.detectImageType)(imageData)) !== null && _a !== void 0 ? _a : 'image/jpeg';
432
485
  return { data: imageData, mimeType, pictureType: types_1.PictureType.FrontCover };
433
486
  }
@@ -447,22 +500,28 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
447
500
  exports.detectImageType = detectImageType;
448
501
  exports.normalizeMimeType = normalizeMimeType;
449
502
  function detectImageType(data) {
450
- if (data.length < 4)
503
+ if (data.length < 4) {
451
504
  return null;
452
- if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff)
505
+ }
506
+ if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
453
507
  return 'image/jpeg';
454
- if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG')
508
+ }
509
+ if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
455
510
  return 'image/png';
456
- if (data.toString('ascii', 0, 3) === 'GIF')
511
+ }
512
+ if (data.toString('ascii', 0, 3) === 'GIF') {
457
513
  return 'image/gif';
514
+ }
458
515
  return null;
459
516
  }
460
517
  function normalizeMimeType(mimeType) {
461
518
  const lower = mimeType.toLowerCase();
462
- if (lower.includes('png'))
519
+ if (lower.includes('png')) {
463
520
  return 'image/png';
464
- if (lower.includes('gif'))
521
+ }
522
+ if (lower.includes('gif')) {
465
523
  return 'image/gif';
524
+ }
466
525
  return 'image/jpeg';
467
526
  }
468
527
 
@@ -750,19 +809,20 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
750
809
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
751
810
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
752
811
  };
753
- var _Control_hostDevice, _Control_beatSocket;
812
+ var _Control_hostDevice, _Control_beatSocket, _Control_correlationByte;
754
813
  Object.defineProperty(exports, "__esModule", ({ value: true }));
755
814
  exports.makePlaystatePacket = void 0;
756
815
  const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
757
816
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
758
817
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
759
818
  const udp_1 = __webpack_require__(/*! src/utils/udp */ "./src/utils/udp.ts");
819
+ const stagehand_1 = __webpack_require__(/*! ./stagehand */ "./src/control/stagehand.ts");
760
820
  const STATE_MAP = {
761
821
  [types_1.CDJStatus.PlayState.Cued]: 0x01,
762
822
  [types_1.CDJStatus.PlayState.Playing]: 0x00,
763
823
  };
764
824
  /**
765
- * Generates the packet used to control the playstate of CDJs
825
+ * Generates the packet used to control the playstate of CDJs in active connection mode
766
826
  */
767
827
  const makePlaystatePacket = ({ hostDevice, device, playState }) => Uint8Array.from([
768
828
  ...constants_1.PROLINK_HEADER,
@@ -783,21 +843,174 @@ class Control {
783
843
  * The socket used to send control packets
784
844
  */
785
845
  _Control_beatSocket.set(this, void 0);
846
+ /**
847
+ * Randomized correlation byte per session for Stagehand commands
848
+ */
849
+ _Control_correlationByte.set(this, void 0);
786
850
  __classPrivateFieldSet(this, _Control_beatSocket, beatSocket, "f");
787
851
  __classPrivateFieldSet(this, _Control_hostDevice, hostDevice, "f");
852
+ __classPrivateFieldSet(this, _Control_correlationByte, Math.floor(Math.random() * 256), "f");
788
853
  }
789
854
  /**
790
- * Start or stop a CDJ on the network
855
+ * Start or stop a CDJ on the network.
856
+ * Delegates automatically to Stagehand-specific transport control if connected in Stagehand mode.
791
857
  */
792
858
  async setPlayState(device, playState) {
859
+ if (__classPrivateFieldGet(this, _Control_hostDevice, "f").type === types_1.DeviceType.Stagehand) {
860
+ if (playState === types_1.CDJStatus.PlayState.Playing) {
861
+ await this.play(device);
862
+ }
863
+ else {
864
+ await this.pause(device);
865
+ }
866
+ return;
867
+ }
793
868
  const packet = (0, exports.makePlaystatePacket)({ hostDevice: __classPrivateFieldGet(this, _Control_hostDevice, "f"), device, playState });
794
869
  await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), packet, constants_1.BEAT_PORT, device.ip.address);
795
870
  }
871
+ /**
872
+ * Send Stagehand PLAY command (paired 0x0f and 0x14 packets)
873
+ */
874
+ async play(device) {
875
+ const p1 = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x0f, true, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
876
+ const p2 = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x14, true, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
877
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p1, constants_1.BEAT_PORT, device.ip.address);
878
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p2, constants_1.BEAT_PORT, device.ip.address);
879
+ }
880
+ /**
881
+ * Send Stagehand PAUSE command (paired 0x14 packet)
882
+ */
883
+ async pause(device) {
884
+ const p = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x14, false, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
885
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p, constants_1.BEAT_PORT, device.ip.address);
886
+ }
887
+ /**
888
+ * Send Stagehand SEEK forward command
889
+ * @param press - true to start seek, false to release
890
+ */
891
+ async seekForward(device, press) {
892
+ const p = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x1a, press, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
893
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p, constants_1.BEAT_PORT, device.ip.address);
894
+ }
895
+ /**
896
+ * Send Stagehand SEEK backward command
897
+ * @param press - true to start seek, false to release
898
+ */
899
+ async seekBackward(device, press) {
900
+ const p = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x1b, press, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
901
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p, constants_1.BEAT_PORT, device.ip.address);
902
+ }
903
+ /**
904
+ * Send Stagehand SKIP track command
905
+ * @param press - true to start skip, false to release
906
+ */
907
+ async skip(device, press) {
908
+ const p = (0, stagehand_1.makeStagehandTransportPacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), 0x18, press, __classPrivateFieldGet(this, _Control_correlationByte, "f"));
909
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p, constants_1.BEAT_PORT, device.ip.address);
910
+ }
911
+ /**
912
+ * Send Stagehand preference write command (0x6b packet) to port 50002
913
+ */
914
+ async setPreference(device, options) {
915
+ const p = (0, stagehand_1.makeStagehandPrefWritePacket)(__classPrivateFieldGet(this, _Control_hostDevice, "f"), options);
916
+ await (0, udp_1.udpSend)(__classPrivateFieldGet(this, _Control_beatSocket, "f"), p, constants_1.STATUS_PORT, device.ip.address);
917
+ }
796
918
  }
797
- _Control_hostDevice = new WeakMap(), _Control_beatSocket = new WeakMap();
919
+ _Control_hostDevice = new WeakMap(), _Control_beatSocket = new WeakMap(), _Control_correlationByte = new WeakMap();
798
920
  exports["default"] = Control;
799
921
 
800
922
 
923
+ /***/ },
924
+
925
+ /***/ "./src/control/stagehand.ts"
926
+ /*!**********************************!*\
927
+ !*** ./src/control/stagehand.ts ***!
928
+ \**********************************/
929
+ (__unused_webpack_module, exports, __webpack_require__) {
930
+
931
+ "use strict";
932
+
933
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
934
+ exports.makeStagehandTransportPacket = makeStagehandTransportPacket;
935
+ exports.makeStagehandPrefWritePacket = makeStagehandPrefWritePacket;
936
+ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
937
+ const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
938
+ /**
939
+ * Generates a Stagehand transport control packet (0x07, 56 bytes)
940
+ *
941
+ * @param hostDevice - The Stagehand device posing as sender
942
+ * @param op - The command opcode (e.g. 0x0f, 0x14, 0x18, 0x1a, 0x1b)
943
+ * @param press - Whether the action is press (true) or release (false)
944
+ * @param correlationByte - The randomized per-session correlation byte
945
+ */
946
+ function makeStagehandTransportPacket(hostDevice, op, press, correlationByte) {
947
+ const packet = new Uint8Array(56);
948
+ // 0-9: magic header
949
+ packet.set(constants_1.PROLINK_HEADER, 0);
950
+ // 10: opcode 0x07
951
+ packet[10] = 0x07;
952
+ // 11-30: device name
953
+ packet.set((0, utils_1.buildName)(hostDevice), 11);
954
+ // 31: 0x01
955
+ packet[31] = 0x01;
956
+ // 32: 0x03
957
+ packet[32] = 0x03;
958
+ // 33: per-session correlation byte
959
+ packet[33] = correlationByte;
960
+ // 34-35: remaining length 0x0030 (48 bytes)
961
+ packet[34] = 0x00;
962
+ packet[35] = 0x30;
963
+ // 40: Stagehand sub-id 0x3a
964
+ packet[40] = 0x3a;
965
+ // 44: command opcode
966
+ packet[44] = op;
967
+ // 46: press/release flag
968
+ packet[46] = press ? 0x01 : 0x00;
969
+ return packet;
970
+ }
971
+ /**
972
+ * Generates a Stagehand preference write packet (0x6b, 124 bytes)
973
+ *
974
+ * @param hostDevice - The Stagehand device posing as sender
975
+ * @param options - The preferences to write (onAir, quantize)
976
+ */
977
+ function makeStagehandPrefWritePacket(hostDevice, options) {
978
+ const packet = new Uint8Array(124);
979
+ // 0-9: magic header
980
+ packet.set(constants_1.PROLINK_HEADER, 0);
981
+ // 10: opcode 0x6b
982
+ packet[10] = 0x6b;
983
+ // 11-30: device name
984
+ const name = (0, utils_1.buildName)(hostDevice);
985
+ // Trailing byte 30 (which is offset 30, meaning index 19 of name) is set to 0x03
986
+ name[19] = 0x03;
987
+ packet.set(name, 11);
988
+ // 31: 0x01 (subscription-id-a constant)
989
+ packet[31] = 0x01;
990
+ // 32: 0x03 (constant)
991
+ packet[32] = 0x03;
992
+ // 33: Stagehand sub-id constant 0x3a
993
+ packet[33] = 0x3a;
994
+ // 34-35: body length 0x0050 (80 bytes)
995
+ packet[34] = 0x00;
996
+ packet[35] = 0x50;
997
+ // 36: transaction flag (0x01 = write)
998
+ packet[36] = 0x01;
999
+ // 44: on_air slot (0x80 = OFF, 0x81 = ON, 0x00 = untouched)
1000
+ if (options.onAir === 'on') {
1001
+ packet[44] = 0x81;
1002
+ }
1003
+ else if (options.onAir === 'off') {
1004
+ packet[44] = 0x80;
1005
+ }
1006
+ // 60: quantize slot (0x80 | enum_index, e.g. 0x81, 0x82 etc.)
1007
+ if (options.quantize !== undefined) {
1008
+ packet[60] = 0x80 | options.quantize;
1009
+ }
1010
+ return packet;
1011
+ }
1012
+
1013
+
801
1014
  /***/ },
802
1015
 
803
1016
  /***/ "./src/db/getArtworkFromFile.ts"
@@ -6577,14 +6790,15 @@ var _ProlinkNetwork_state, _ProlinkNetwork_announceSocket, _ProlinkNetwork_beatS
6577
6790
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6578
6791
  exports.ProlinkNetwork = void 0;
6579
6792
  exports.bringOnline = bringOnline;
6793
+ exports.bringOnlineStagehand = bringOnlineStagehand;
6580
6794
  const crypto_1 = __webpack_require__(/*! crypto */ "crypto");
6581
6795
  const dgram_1 = __importDefault(__webpack_require__(/*! dgram */ "dgram"));
6582
6796
  const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
6583
6797
  const control_1 = __importDefault(__webpack_require__(/*! src/control */ "./src/control/index.ts"));
6584
6798
  const db_1 = __importDefault(__webpack_require__(/*! src/db */ "./src/db/index.ts"));
6585
6799
  const devices_1 = __importDefault(__webpack_require__(/*! src/devices */ "./src/devices/index.ts"));
6586
- const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
6587
6800
  const localdb_1 = __importDefault(__webpack_require__(/*! src/localdb */ "./src/localdb/index.ts"));
6801
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
6588
6802
  const mixstatus_1 = __webpack_require__(/*! src/mixstatus */ "./src/mixstatus/index.ts");
6589
6803
  const remotedb_1 = __importDefault(__webpack_require__(/*! src/remotedb */ "./src/remotedb/index.ts"));
6590
6804
  const status_1 = __importDefault(__webpack_require__(/*! src/status */ "./src/status/index.ts"));
@@ -6639,6 +6853,17 @@ async function bringOnline(config) {
6639
6853
  });
6640
6854
  return network;
6641
6855
  }
6856
+ /**
6857
+ * Brings the Prolink network online using the Pioneer Stagehand connection method.
6858
+ *
6859
+ * This connects to the network as a non-player Stagehand device using its abbreviated handshake.
6860
+ */
6861
+ async function bringOnlineStagehand(config) {
6862
+ return bringOnline({
6863
+ ...config,
6864
+ connectMethod: 'stagehand',
6865
+ });
6866
+ }
6642
6867
  class ProlinkNetwork {
6643
6868
  /**
6644
6869
  * @internal
@@ -6688,6 +6913,7 @@ class ProlinkNetwork {
6688
6913
  * Defaults the Virtual CDJ ID to 7.
6689
6914
  */
6690
6915
  async autoconfigFromPeers() {
6916
+ var _a;
6691
6917
  const tx = Telemetry.startTransaction({ name: 'autoConfigure' });
6692
6918
  // wait for first device to appear on the network
6693
6919
  const firstDevice = await new Promise(resolve => __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f").once('connected', resolve));
@@ -6703,7 +6929,10 @@ class ProlinkNetwork {
6703
6929
  tx.finish();
6704
6930
  throw new Error('Unable to determine network interface');
6705
6931
  }
6706
- __classPrivateFieldSet(this, _ProlinkNetwork_config, { ...__classPrivateFieldGet(this, _ProlinkNetwork_config, "f"), vcdjId: constants_1.DEFAULT_VCDJ_ID, iface }, "f");
6932
+ const defaultId = ((_a = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f")) === null || _a === void 0 ? void 0 : _a.connectMethod) === 'stagehand'
6933
+ ? (0, virtualcdj_1.generateStagehandDeviceId)()
6934
+ : constants_1.DEFAULT_VCDJ_ID;
6935
+ __classPrivateFieldSet(this, _ProlinkNetwork_config, { ...__classPrivateFieldGet(this, _ProlinkNetwork_config, "f"), vcdjId: defaultId, iface }, "f");
6707
6936
  tx.finish();
6708
6937
  }
6709
6938
  /**
@@ -6713,23 +6942,34 @@ class ProlinkNetwork {
6713
6942
  * or manual configuration). This will then initialize all the network services.
6714
6943
  */
6715
6944
  connect() {
6716
- var _a, _b, _c;
6945
+ var _a, _b, _c, _d, _e, _f;
6717
6946
  if (__classPrivateFieldGet(this, _ProlinkNetwork_config, "f") === null) {
6718
6947
  throw new Error(connectErrorHelp);
6719
6948
  }
6720
6949
  const tx = Telemetry.startTransaction({ name: 'connect' });
6721
- // Create VCDJ for the interface's broadcast address
6722
- const vcdj = (0, virtualcdj_1.getVirtualCDJ)(__classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjId, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName);
6723
- // Update device manager to filter out our VCDJ name
6724
- if (__classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName !== undefined) {
6725
- __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f").reconfigure({ vcdjName: __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName });
6950
+ const connectMethod = (_a = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").connectMethod) !== null && _a !== void 0 ? _a : 'active';
6951
+ const vcdjId = (_b = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjId) !== null && _b !== void 0 ? _b : (connectMethod === 'stagehand' ? (0, virtualcdj_1.generateStagehandDeviceId)() : constants_1.DEFAULT_VCDJ_ID);
6952
+ const vcdjName = (_c = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName) !== null && _c !== void 0 ? _c : (connectMethod === 'stagehand' ? 'Stagehand' : undefined);
6953
+ // Create VCDJ or Stagehand device
6954
+ const vcdj = connectMethod === 'stagehand'
6955
+ ? (0, virtualcdj_1.getVirtualStagehand)(__classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, vcdjId, vcdjName)
6956
+ : (0, virtualcdj_1.getVirtualCDJ)(__classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, vcdjId, vcdjName);
6957
+ // Update device manager to filter out our device name
6958
+ if (vcdjName !== undefined) {
6959
+ __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f").reconfigure({ vcdjName });
6726
6960
  }
6727
6961
  // Start announcing
6728
- const announcer = new virtualcdj_1.Announcer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, (_a = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").fullStartup) !== null && _a !== void 0 ? _a : false, (_b = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").announceToStagehand) !== null && _b !== void 0 ? _b : false, __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
6962
+ let announcer;
6963
+ if (connectMethod === 'stagehand') {
6964
+ announcer = new virtualcdj_1.StagehandAnnouncer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
6965
+ }
6966
+ else {
6967
+ announcer = new virtualcdj_1.Announcer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, (_d = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").fullStartup) !== null && _d !== void 0 ? _d : false, (_e = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").announceToStagehand) !== null && _e !== void 0 ? _e : false, __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
6968
+ }
6729
6969
  announcer.start();
6730
6970
  // Create remote and local databases
6731
6971
  const remotedb = new remotedb_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), vcdj);
6732
- const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (_c = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").databasePreference) !== null && _c !== void 0 ? _c : 'auto');
6972
+ const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (_f = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").databasePreference) !== null && _f !== void 0 ? _f : 'auto');
6733
6973
  // Create unified database
6734
6974
  const database = new db_1.default(localdb, remotedb, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"));
6735
6975
  // Create controller service
@@ -8459,11 +8699,11 @@ exports["default"] = RemoteDatabase;
8459
8699
 
8460
8700
  Object.defineProperty(exports, "__esModule", ({ value: true }));
8461
8701
  exports.Message = void 0;
8462
- const telemetry_1 = __webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts");
8463
8702
  const constants_1 = __webpack_require__(/*! src/remotedb/constants */ "./src/remotedb/constants.ts");
8464
8703
  const fields_1 = __webpack_require__(/*! src/remotedb/fields */ "./src/remotedb/fields.ts");
8465
8704
  const response_1 = __webpack_require__(/*! src/remotedb/message/response */ "./src/remotedb/message/response.ts");
8466
8705
  const types_1 = __webpack_require__(/*! src/remotedb/message/types */ "./src/remotedb/message/types.ts");
8706
+ const telemetry_1 = __webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts");
8467
8707
  /**
8468
8708
  * Argument types are used in argument list fields. This is essentially
8469
8709
  * duplicating the field type, but has different values for whatever reason.
@@ -9538,11 +9778,22 @@ class StatusEmitter {
9538
9778
  * Lock used to avoid media slot query races
9539
9779
  */
9540
9780
  _StatusEmitter_mediaSlotQueryLock.set(this, new async_mutex_1.Mutex());
9541
- // Bind public event emitter interface
9781
+ // Bind public event emitter interface. Use explicit generic signatures keyed
9782
+ // on StatusEvents rather than `Emitter['on']`: extracting the indexed `on`
9783
+ // type out of strict-event-emitter-types degrades to its unique-symbol
9784
+ // compatibility overload under newer TypeScript, so consumers calling
9785
+ // `.on('status', …)` would fail to typecheck. The runtime is unchanged.
9542
9786
  this.on = __classPrivateFieldGet(this, _StatusEmitter_emitter, "f").addListener.bind(__classPrivateFieldGet(this, _StatusEmitter_emitter, "f"));
9543
9787
  this.off = __classPrivateFieldGet(this, _StatusEmitter_emitter, "f").removeListener.bind(__classPrivateFieldGet(this, _StatusEmitter_emitter, "f"));
9544
9788
  this.once = __classPrivateFieldGet(this, _StatusEmitter_emitter, "f").once.bind(__classPrivateFieldGet(this, _StatusEmitter_emitter, "f"));
9545
9789
  _StatusEmitter_handleStatus.set(this, (message) => {
9790
+ // Stagehand mixer state (type 0x39)
9791
+ if (message.length >= 11 && message[10] === 0x39) {
9792
+ const mixerState = (0, utils_1.mixerStateFromPacket)(message);
9793
+ if (mixerState !== undefined) {
9794
+ return __classPrivateFieldGet(this, _StatusEmitter_emitter, "f").emit('mixerState', mixerState);
9795
+ }
9796
+ }
9546
9797
  const status = (0, utils_1.statusFromPacket)(message);
9547
9798
  if (status !== undefined) {
9548
9799
  return __classPrivateFieldGet(this, _StatusEmitter_emitter, "f").emit('status', status);
@@ -9666,6 +9917,14 @@ class PositionEmitter {
9666
9917
  this.off = __classPrivateFieldGet(this, _PositionEmitter_emitter, "f").removeListener.bind(__classPrivateFieldGet(this, _PositionEmitter_emitter, "f"));
9667
9918
  this.once = __classPrivateFieldGet(this, _PositionEmitter_emitter, "f").once.bind(__classPrivateFieldGet(this, _PositionEmitter_emitter, "f"));
9668
9919
  _PositionEmitter_handlePosition.set(this, (message) => {
9920
+ // Stagehand VU meter (type 0x58)
9921
+ if (message.length >= 11 && message[10] === 0x58) {
9922
+ const vu = (0, utils_1.vuFromPacket)(message);
9923
+ if (vu !== undefined) {
9924
+ __classPrivateFieldGet(this, _PositionEmitter_emitter, "f").emit('vu', vu);
9925
+ return;
9926
+ }
9927
+ }
9669
9928
  const position = (0, utils_1.positionFromPacket)(message);
9670
9929
  if (position !== undefined) {
9671
9930
  __classPrivateFieldGet(this, _PositionEmitter_emitter, "f").emit('position', position);
@@ -9734,6 +9993,8 @@ exports.statusFromPacket = statusFromPacket;
9734
9993
  exports.mediaSlotFromPacket = mediaSlotFromPacket;
9735
9994
  exports.positionFromPacket = positionFromPacket;
9736
9995
  exports.onAirFromPacket = onAirFromPacket;
9996
+ exports.mixerStateFromPacket = mixerStateFromPacket;
9997
+ exports.vuFromPacket = vuFromPacket;
9737
9998
  const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
9738
9999
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
9739
10000
  const MAX_INT32 = Math.pow(2, 32) - 1;
@@ -9916,6 +10177,83 @@ function onAirFromPacket(packet) {
9916
10177
  }
9917
10178
  return undefined;
9918
10179
  }
10180
+ /**
10181
+ * Parse unicast mixer state packet from DJM-A9 (packet type 0x39 on port 50002).
10182
+ */
10183
+ function mixerStateFromPacket(packet) {
10184
+ if (packet.indexOf(constants_1.PROLINK_HEADER) !== 0) {
10185
+ return undefined;
10186
+ }
10187
+ // Verify packet type 0x39
10188
+ if (packet[10] !== 0x39) {
10189
+ return undefined;
10190
+ }
10191
+ if (packet.length < 266) {
10192
+ return undefined;
10193
+ }
10194
+ const deviceName = packet.slice(11, 30).toString().replace(/\0/g, '').trim();
10195
+ const crossfader = packet[180];
10196
+ const channels = {};
10197
+ for (let ch = 1; ch <= 4; ch++) {
10198
+ const offset = 36 + (ch - 1) * 24;
10199
+ let crossfaderAssign = 'thru';
10200
+ const assignVal = packet[offset + 12];
10201
+ if (assignVal === 0x01) {
10202
+ crossfaderAssign = 'A';
10203
+ }
10204
+ else if (assignVal === 0x02) {
10205
+ crossfaderAssign = 'B';
10206
+ }
10207
+ channels[ch] = {
10208
+ trim: packet[offset + 1],
10209
+ eqHi: packet[offset + 3],
10210
+ eqMid: packet[offset + 4],
10211
+ eqLow: packet[offset + 6],
10212
+ colorFx: packet[offset + 7],
10213
+ fader: packet[offset + 11],
10214
+ crossfaderAssign,
10215
+ };
10216
+ }
10217
+ return {
10218
+ deviceId: 33, // Mixers are typically device 33 on ProDJLink
10219
+ deviceName,
10220
+ channels,
10221
+ crossfader,
10222
+ };
10223
+ }
10224
+ /**
10225
+ * Parse unicast VU meter packet from DJM-A9 (packet type 0x58 on port 50001).
10226
+ * Contains 15 sample-tuples (16-bit BE left/right levels) per channel.
10227
+ */
10228
+ function vuFromPacket(packet) {
10229
+ if (packet.indexOf(constants_1.PROLINK_HEADER) !== 0) {
10230
+ return undefined;
10231
+ }
10232
+ // Verify packet type 0x58
10233
+ if (packet[10] !== 0x58) {
10234
+ return undefined;
10235
+ }
10236
+ if (packet.length < 584) {
10237
+ return undefined;
10238
+ }
10239
+ const channels = {};
10240
+ for (let ch = 1; ch <= 4; ch++) {
10241
+ const chOffset = 44 + (ch - 1) * 60; // 15 frames * 4 bytes per frame = 60 bytes
10242
+ const frames = [];
10243
+ for (let i = 0; i < 15; i++) {
10244
+ const frameOffset = chOffset + i * 4;
10245
+ frames.push({
10246
+ left: packet.readUInt16BE(frameOffset),
10247
+ right: packet.readUInt16BE(frameOffset + 2),
10248
+ });
10249
+ }
10250
+ channels[ch] = frames;
10251
+ }
10252
+ return {
10253
+ deviceId: 33, // Mixers are typically device 33 on ProDJLink
10254
+ channels,
10255
+ };
10256
+ }
9919
10257
 
9920
10258
 
9921
10259
  /***/ },
@@ -9972,6 +10310,7 @@ var DeviceType;
9972
10310
  DeviceType[DeviceType["CDJ"] = 1] = "CDJ";
9973
10311
  DeviceType[DeviceType["Mixer"] = 3] = "Mixer";
9974
10312
  DeviceType[DeviceType["Rekordbox"] = 4] = "Rekordbox";
10313
+ DeviceType[DeviceType["Stagehand"] = 5] = "Stagehand";
9975
10314
  })(DeviceType || (exports.DeviceType = DeviceType = {}));
9976
10315
  var MediaColor;
9977
10316
  (function (MediaColor) {
@@ -10012,9 +10351,6 @@ var TrackType;
10012
10351
  TrackType[TrackType["AudioCD"] = 5] = "AudioCD";
10013
10352
  TrackType[TrackType["Streaming"] = 6] = "Streaming";
10014
10353
  })(TrackType || (exports.TrackType = TrackType = {}));
10015
- /**
10016
- * Re-export cue types from onelibrary-connect
10017
- */
10018
10354
  var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
10019
10355
  Object.defineProperty(exports, "CueColor", ({ enumerable: true, get: function () { return onelibrary_connect_1.CueColor; } }));
10020
10356
  Object.defineProperty(exports, "HotcueButton", ({ enumerable: true, get: function () { return onelibrary_connect_1.HotcueButton; } }));
@@ -10471,6 +10807,9 @@ var __importStar = (this && this.__importStar) || (function () {
10471
10807
  return result;
10472
10808
  };
10473
10809
  })();
10810
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10811
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
10812
+ };
10474
10813
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
10475
10814
  if (kind === "m") throw new TypeError("Private method is not writable");
10476
10815
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
@@ -10963,6 +11302,271 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
10963
11302
  // Then schedule regular keep-alives
10964
11303
  __classPrivateFieldSet(this, _Announcer_intervalHandle, setInterval(sendKeepAlive, constants_1.ANNOUNCE_INTERVAL), "f");
10965
11304
  };
11305
+ __exportStar(__webpack_require__(/*! ./stagehand */ "./src/virtualcdj/stagehand.ts"), exports);
11306
+
11307
+
11308
+ /***/ },
11309
+
11310
+ /***/ "./src/virtualcdj/stagehand.ts"
11311
+ /*!*************************************!*\
11312
+ !*** ./src/virtualcdj/stagehand.ts ***!
11313
+ \*************************************/
11314
+ (__unused_webpack_module, exports, __webpack_require__) {
11315
+
11316
+ "use strict";
11317
+
11318
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11319
+ if (k2 === undefined) k2 = k;
11320
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11321
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11322
+ desc = { enumerable: true, get: function() { return m[k]; } };
11323
+ }
11324
+ Object.defineProperty(o, k2, desc);
11325
+ }) : (function(o, m, k, k2) {
11326
+ if (k2 === undefined) k2 = k;
11327
+ o[k2] = m[k];
11328
+ }));
11329
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
11330
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11331
+ }) : function(o, v) {
11332
+ o["default"] = v;
11333
+ });
11334
+ var __importStar = (this && this.__importStar) || (function () {
11335
+ var ownKeys = function(o) {
11336
+ ownKeys = Object.getOwnPropertyNames || function (o) {
11337
+ var ar = [];
11338
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
11339
+ return ar;
11340
+ };
11341
+ return ownKeys(o);
11342
+ };
11343
+ return function (mod) {
11344
+ if (mod && mod.__esModule) return mod;
11345
+ var result = {};
11346
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
11347
+ __setModuleDefault(result, mod);
11348
+ return result;
11349
+ };
11350
+ })();
11351
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
11352
+ if (kind === "m") throw new TypeError("Private method is not writable");
11353
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
11354
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
11355
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11356
+ };
11357
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
11358
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
11359
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11360
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11361
+ };
11362
+ var _StagehandAnnouncer_instances, _StagehandAnnouncer_announceSocket, _StagehandAnnouncer_vcdj, _StagehandAnnouncer_intervalHandle, _StagehandAnnouncer_currentStage, _StagehandAnnouncer_stageCounter, _StagehandAnnouncer_iface, _StagehandAnnouncer_logger, _StagehandAnnouncer_onStartupComplete, _StagehandAnnouncer_mac, _StagehandAnnouncer_startStartup, _StagehandAnnouncer_sendPacket, _StagehandAnnouncer_sendStagePackets, _StagehandAnnouncer_advanceStage, _StagehandAnnouncer_startKeepAlive;
11363
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
11364
+ exports.StagehandAnnouncer = exports.getVirtualStagehand = exports.StagehandStartupStage = exports.STAGEHAND_KEEP_ALIVE_INTERVAL = exports.STAGEHAND_STARTUP_INTERVAL = void 0;
11365
+ exports.generateStagehandMac = generateStagehandMac;
11366
+ exports.generateStagehandDeviceId = generateStagehandDeviceId;
11367
+ exports.makeStagehand0aPacket = makeStagehand0aPacket;
11368
+ exports.makeStagehand02Packet = makeStagehand02Packet;
11369
+ exports.makeStagehand06Packet = makeStagehand06Packet;
11370
+ const ip = __importStar(__webpack_require__(/*! ip-address */ "ip-address"));
11371
+ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
11372
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
11373
+ const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
11374
+ const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
11375
+ exports.STAGEHAND_STARTUP_INTERVAL = 305;
11376
+ exports.STAGEHAND_KEEP_ALIVE_INTERVAL = 2000;
11377
+ var StagehandStartupStage;
11378
+ (function (StagehandStartupStage) {
11379
+ StagehandStartupStage[StagehandStartupStage["InitialAnnounce"] = 10] = "InitialAnnounce";
11380
+ StagehandStartupStage[StagehandStartupStage["SecondStageClaim"] = 2] = "SecondStageClaim";
11381
+ StagehandStartupStage[StagehandStartupStage["KeepAlive"] = 6] = "KeepAlive";
11382
+ })(StagehandStartupStage || (exports.StagehandStartupStage = StagehandStartupStage = {}));
11383
+ /**
11384
+ * Generates a randomized MAC address with the AlphaTheta OUI (c8:3d:fc).
11385
+ */
11386
+ function generateStagehandMac() {
11387
+ const mac = new Uint8Array(6);
11388
+ mac[0] = 0xc8;
11389
+ mac[1] = 0x3d;
11390
+ mac[2] = 0xfc;
11391
+ mac[3] = Math.floor(Math.random() * 256);
11392
+ mac[4] = Math.floor(Math.random() * 256);
11393
+ mac[5] = Math.floor(Math.random() * 256);
11394
+ return mac;
11395
+ }
11396
+ /**
11397
+ * Generates a random Stagehand device ID in the observed range of 141 to 211.
11398
+ */
11399
+ function generateStagehandDeviceId() {
11400
+ return Math.floor(Math.random() * (211 - 141 + 1)) + 141;
11401
+ }
11402
+ /**
11403
+ * Constructs a virtual Stagehand Device.
11404
+ *
11405
+ * @param iface - The network interface to use
11406
+ * @param id - The device ID to use (defaults to random Stagehand ID)
11407
+ * @param name - The device name (defaults to 'Stagehand')
11408
+ * @param macAddr - The optional randomized MAC address
11409
+ */
11410
+ const getVirtualStagehand = (iface, id = generateStagehandDeviceId(), name = 'Stagehand', macAddr = generateStagehandMac()) => ({
11411
+ id,
11412
+ name,
11413
+ type: types_1.DeviceType.Stagehand,
11414
+ ip: new ip.Address4(iface.address),
11415
+ macAddr,
11416
+ });
11417
+ exports.getVirtualStagehand = getVirtualStagehand;
11418
+ /**
11419
+ * Build Stagehand stage 0x0a packet: Initial announcement.
11420
+ * Sent 3 times at 305ms intervals.
11421
+ */
11422
+ function makeStagehand0aPacket(device) {
11423
+ const parts = [
11424
+ ...constants_1.PROLINK_HEADER, // 10 bytes
11425
+ ...[0x0a, 0x00], // 2 bytes packet type (0x0a)
11426
+ ...(0, utils_1.buildName)(device), // 20 bytes device name
11427
+ ...[0x01, 0x03], // 2 bytes protocol / structure bytes
11428
+ ...[0x00, 0x25], // 2 bytes packet length (37)
11429
+ ...[types_1.DeviceType.Stagehand], // 1 byte device type (0x05)
11430
+ ];
11431
+ return Uint8Array.from(parts);
11432
+ }
11433
+ /**
11434
+ * Build Stagehand stage 0x02 packet: Second-stage device number claim.
11435
+ * Sent 3 times at 305ms intervals with counter N (1, 2, 3).
11436
+ */
11437
+ function makeStagehand02Packet(device, mac, counter) {
11438
+ const parts = [
11439
+ ...constants_1.PROLINK_HEADER, // 10 bytes
11440
+ ...[0x02, 0x00], // 2 bytes packet type (0x02)
11441
+ ...(0, utils_1.buildName)(device), // 20 bytes device name
11442
+ ...[0x01, 0x03], // 2 bytes protocol / structure bytes
11443
+ ...[0x00, 0x32], // 2 bytes packet length (50)
11444
+ ...device.ip.toArray(), // 4 bytes IP
11445
+ ...mac, // 6 bytes identifier
11446
+ ...[0x3a], // 1 byte constant
11447
+ ...[counter], // 1 byte counter
11448
+ ...[types_1.DeviceType.Stagehand], // 1 byte device type (0x05)
11449
+ ...[0x01], // 1 byte constant
11450
+ ];
11451
+ return Uint8Array.from(parts);
11452
+ }
11453
+ /**
11454
+ * Build Stagehand stage 0x06 packet: Keep-alive.
11455
+ * Sent every 2.0s after startup complete.
11456
+ */
11457
+ function makeStagehand06Packet(device, mac) {
11458
+ const parts = [
11459
+ ...constants_1.PROLINK_HEADER, // 10 bytes
11460
+ ...[0x06, 0x00], // 2 bytes packet type (0x06)
11461
+ ...(0, utils_1.buildName)(device), // 20 bytes device name
11462
+ ...[0x01, 0x03], // 2 bytes protocol / structure bytes
11463
+ ...[0x00, 0x36], // 2 bytes packet length (54)
11464
+ ...[device.id], // 1 byte device number
11465
+ ...[0x01], // 1 byte constant
11466
+ ...mac, // 6 bytes identifier
11467
+ ...device.ip.toArray(), // 4 bytes IP
11468
+ ...[0x01, 0x00, 0x00, 0x00], // 4 bytes constant
11469
+ ...[types_1.DeviceType.Stagehand], // 1 byte device-type (0x05)
11470
+ ...[0x20], // 1 byte trailing byte
11471
+ ];
11472
+ return Uint8Array.from(parts);
11473
+ }
11474
+ class StagehandAnnouncer {
11475
+ constructor(vcdj, announceSocket, deviceManager, iface, logger = logger_1.noopLogger) {
11476
+ _StagehandAnnouncer_instances.add(this);
11477
+ _StagehandAnnouncer_announceSocket.set(this, void 0);
11478
+ _StagehandAnnouncer_vcdj.set(this, void 0);
11479
+ _StagehandAnnouncer_intervalHandle.set(this, void 0);
11480
+ _StagehandAnnouncer_currentStage.set(this, StagehandStartupStage.InitialAnnounce);
11481
+ _StagehandAnnouncer_stageCounter.set(this, 0);
11482
+ _StagehandAnnouncer_iface.set(this, void 0);
11483
+ _StagehandAnnouncer_logger.set(this, void 0);
11484
+ _StagehandAnnouncer_onStartupComplete.set(this, void 0);
11485
+ _StagehandAnnouncer_mac.set(this, void 0);
11486
+ __classPrivateFieldSet(this, _StagehandAnnouncer_vcdj, vcdj, "f");
11487
+ __classPrivateFieldSet(this, _StagehandAnnouncer_announceSocket, announceSocket, "f");
11488
+ __classPrivateFieldSet(this, _StagehandAnnouncer_iface, iface, "f");
11489
+ __classPrivateFieldSet(this, _StagehandAnnouncer_logger, logger, "f");
11490
+ __classPrivateFieldSet(this, _StagehandAnnouncer_mac, vcdj.macAddr, "f");
11491
+ }
11492
+ get ready() {
11493
+ if (__classPrivateFieldGet(this, _StagehandAnnouncer_currentStage, "f") === StagehandStartupStage.KeepAlive) {
11494
+ return Promise.resolve();
11495
+ }
11496
+ return new Promise(resolve => {
11497
+ __classPrivateFieldSet(this, _StagehandAnnouncer_onStartupComplete, resolve, "f");
11498
+ });
11499
+ }
11500
+ start() {
11501
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").info(`Starting Stagehand announcer: device name "${__classPrivateFieldGet(this, _StagehandAnnouncer_vcdj, "f").name}", ID ${__classPrivateFieldGet(this, _StagehandAnnouncer_vcdj, "f").id}`);
11502
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_startStartup).call(this);
11503
+ }
11504
+ stop() {
11505
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").info('Stopping Stagehand announcer');
11506
+ if (__classPrivateFieldGet(this, _StagehandAnnouncer_intervalHandle, "f") !== undefined) {
11507
+ clearInterval(__classPrivateFieldGet(this, _StagehandAnnouncer_intervalHandle, "f"));
11508
+ clearTimeout(__classPrivateFieldGet(this, _StagehandAnnouncer_intervalHandle, "f"));
11509
+ __classPrivateFieldSet(this, _StagehandAnnouncer_intervalHandle, undefined, "f");
11510
+ }
11511
+ }
11512
+ }
11513
+ exports.StagehandAnnouncer = StagehandAnnouncer;
11514
+ _StagehandAnnouncer_announceSocket = new WeakMap(), _StagehandAnnouncer_vcdj = new WeakMap(), _StagehandAnnouncer_intervalHandle = new WeakMap(), _StagehandAnnouncer_currentStage = new WeakMap(), _StagehandAnnouncer_stageCounter = new WeakMap(), _StagehandAnnouncer_iface = new WeakMap(), _StagehandAnnouncer_logger = new WeakMap(), _StagehandAnnouncer_onStartupComplete = new WeakMap(), _StagehandAnnouncer_mac = new WeakMap(), _StagehandAnnouncer_instances = new WeakSet(), _StagehandAnnouncer_startStartup = function _StagehandAnnouncer_startStartup() {
11515
+ __classPrivateFieldSet(this, _StagehandAnnouncer_currentStage, StagehandStartupStage.InitialAnnounce, "f");
11516
+ __classPrivateFieldSet(this, _StagehandAnnouncer_stageCounter, 0, "f");
11517
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_sendStagePackets).call(this);
11518
+ }, _StagehandAnnouncer_sendPacket = function _StagehandAnnouncer_sendPacket(packet) {
11519
+ const broadcastAddr = (0, utils_1.getBroadcastAddress)(__classPrivateFieldGet(this, _StagehandAnnouncer_iface, "f"));
11520
+ __classPrivateFieldGet(this, _StagehandAnnouncer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
11521
+ }, _StagehandAnnouncer_sendStagePackets = function _StagehandAnnouncer_sendStagePackets() {
11522
+ var _a;
11523
+ var _b;
11524
+ __classPrivateFieldSet(this, _StagehandAnnouncer_stageCounter, (_b = __classPrivateFieldGet(this, _StagehandAnnouncer_stageCounter, "f"), _b++, _b), "f");
11525
+ let packet;
11526
+ switch (__classPrivateFieldGet(this, _StagehandAnnouncer_currentStage, "f")) {
11527
+ case StagehandStartupStage.InitialAnnounce:
11528
+ packet = makeStagehand0aPacket(__classPrivateFieldGet(this, _StagehandAnnouncer_vcdj, "f"));
11529
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").debug(`Stagehand sending stage 0x0a (announce) packet ${__classPrivateFieldGet(this, _StagehandAnnouncer_stageCounter, "f")}/3`);
11530
+ break;
11531
+ case StagehandStartupStage.SecondStageClaim:
11532
+ packet = makeStagehand02Packet(__classPrivateFieldGet(this, _StagehandAnnouncer_vcdj, "f"), __classPrivateFieldGet(this, _StagehandAnnouncer_mac, "f"), __classPrivateFieldGet(this, _StagehandAnnouncer_stageCounter, "f"));
11533
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").debug(`Stagehand sending stage 0x02 (claim) packet ${__classPrivateFieldGet(this, _StagehandAnnouncer_stageCounter, "f")}/3`);
11534
+ break;
11535
+ case StagehandStartupStage.KeepAlive:
11536
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").info('Stagehand join sequence complete, transitioning to keep-alive');
11537
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_startKeepAlive).call(this);
11538
+ (_a = __classPrivateFieldGet(this, _StagehandAnnouncer_onStartupComplete, "f")) === null || _a === void 0 ? void 0 : _a.call(this);
11539
+ __classPrivateFieldSet(this, _StagehandAnnouncer_onStartupComplete, undefined, "f");
11540
+ return;
11541
+ }
11542
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_sendPacket).call(this, packet);
11543
+ if (__classPrivateFieldGet(this, _StagehandAnnouncer_stageCounter, "f") >= 3) {
11544
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_advanceStage).call(this);
11545
+ }
11546
+ __classPrivateFieldSet(this, _StagehandAnnouncer_intervalHandle, setTimeout(() => __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_sendStagePackets).call(this), exports.STAGEHAND_STARTUP_INTERVAL), "f");
11547
+ }, _StagehandAnnouncer_advanceStage = function _StagehandAnnouncer_advanceStage() {
11548
+ __classPrivateFieldSet(this, _StagehandAnnouncer_stageCounter, 0, "f");
11549
+ switch (__classPrivateFieldGet(this, _StagehandAnnouncer_currentStage, "f")) {
11550
+ case StagehandStartupStage.InitialAnnounce:
11551
+ __classPrivateFieldSet(this, _StagehandAnnouncer_currentStage, StagehandStartupStage.SecondStageClaim, "f");
11552
+ break;
11553
+ case StagehandStartupStage.SecondStageClaim:
11554
+ __classPrivateFieldSet(this, _StagehandAnnouncer_currentStage, StagehandStartupStage.KeepAlive, "f");
11555
+ break;
11556
+ }
11557
+ }, _StagehandAnnouncer_startKeepAlive = function _StagehandAnnouncer_startKeepAlive() {
11558
+ if (__classPrivateFieldGet(this, _StagehandAnnouncer_intervalHandle, "f")) {
11559
+ clearTimeout(__classPrivateFieldGet(this, _StagehandAnnouncer_intervalHandle, "f"));
11560
+ __classPrivateFieldSet(this, _StagehandAnnouncer_intervalHandle, undefined, "f");
11561
+ }
11562
+ const sendKeepAlive = () => {
11563
+ const packet = makeStagehand06Packet(__classPrivateFieldGet(this, _StagehandAnnouncer_vcdj, "f"), __classPrivateFieldGet(this, _StagehandAnnouncer_mac, "f"));
11564
+ __classPrivateFieldGet(this, _StagehandAnnouncer_logger, "f").debug('Stagehand sending keep-alive packet');
11565
+ __classPrivateFieldGet(this, _StagehandAnnouncer_instances, "m", _StagehandAnnouncer_sendPacket).call(this, packet);
11566
+ };
11567
+ sendKeepAlive();
11568
+ __classPrivateFieldSet(this, _StagehandAnnouncer_intervalHandle, setInterval(sendKeepAlive, exports.STAGEHAND_KEEP_ALIVE_INTERVAL), "f");
11569
+ };
10966
11570
 
10967
11571
 
10968
11572
  /***/ },