alphatheta-connect 0.21.10 → 0.23.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/lib/cli.js CHANGED
@@ -10795,6 +10795,155 @@ function udpClose(conn) {
10795
10795
  }
10796
10796
 
10797
10797
 
10798
+ /***/ },
10799
+
10800
+ /***/ "./src/virtualcdj/device-id.ts"
10801
+ /*!*************************************!*\
10802
+ !*** ./src/virtualcdj/device-id.ts ***!
10803
+ \*************************************/
10804
+ (__unused_webpack_module, exports) {
10805
+
10806
+ "use strict";
10807
+
10808
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
10809
+ exports.DEFAULT_MIXER_PLAYER_CEILING = exports.MIXER_PLAYER_CEILING = exports.MAX_DEVICE_ID = exports.REMOTEDB_MAX_DEVICE_ID = void 0;
10810
+ exports.playerNumberCeiling = playerNumberCeiling;
10811
+ exports.pickAvailableDeviceId = pickAvailableDeviceId;
10812
+ /**
10813
+ * The highest device ID a CDJ will answer remotedb queries from.
10814
+ *
10815
+ * CDJs ignore metadata requests from devices numbered above this, which is why
10816
+ * the 1-6 range is worth preferring even though real players live there too.
10817
+ */
10818
+ exports.REMOTEDB_MAX_DEVICE_ID = 6;
10819
+ /**
10820
+ * The highest device ID the prolink protocol allows.
10821
+ */
10822
+ exports.MAX_DEVICE_ID = 32;
10823
+ /**
10824
+ * `DeviceType.Mixer`, inlined so this module stays free of runtime imports —
10825
+ * it is re-exported into consumers' test environments, where pulling in the
10826
+ * whole type module (and its ip-address dependency) is dead weight.
10827
+ */
10828
+ const MIXER_DEVICE_TYPE = 0x03;
10829
+ /**
10830
+ * How many player numbers each mixer can hand out.
10831
+ *
10832
+ * Players take their number from the mixer channel they are plugged into, so
10833
+ * an N-channel mixer only ever produces players 1..N — and N+1 is the lowest
10834
+ * number that no player on that rig can claim.
10835
+ *
10836
+ * These are physical channel counts, deliberately NOT the `channelCount` in
10837
+ * the app's own capability table, which caps the DJM-V10 at 4 for its mixer
10838
+ * signal model. Capping here would put us on player 5 of a 6-channel rig,
10839
+ * which is the collision this table exists to avoid.
10840
+ */
10841
+ exports.MIXER_PLAYER_CEILING = {
10842
+ // 6-channel flagships — no player number in 1-6 is safe on these
10843
+ 'DJM-V10': 6,
10844
+ 'DJM-V10-LF': 6,
10845
+ // 4-channel
10846
+ 'DJM-A9': 4,
10847
+ 'DJM-900NXS2': 4,
10848
+ 'DJM-900NXS': 4,
10849
+ 'DJM-900SRT': 4,
10850
+ 'DJM-850': 4,
10851
+ 'DJM-800': 4,
10852
+ 'DJM-750MK2': 4,
10853
+ 'DJM-750': 4,
10854
+ 'DJM-TOUR1': 4,
10855
+ 'DJM-2000NXS': 4,
10856
+ 'DJM-2000': 4,
10857
+ EUPHONIA: 4,
10858
+ // 3-channel (2026)
10859
+ 'DJM-V5': 3,
10860
+ // 2-channel
10861
+ 'DJM-450': 2,
10862
+ 'DJM-350': 2,
10863
+ 'DJM-250MK2': 2,
10864
+ // All-in-ones: their built-in players occupy numbers too, so the ceiling is
10865
+ // however many decks the unit announces rather than its mixer channel count
10866
+ 'XDJ-XZ': 4,
10867
+ 'XDJ-AZ': 4,
10868
+ 'XDJ-RX3': 2,
10869
+ 'XDJ-RX2': 2,
10870
+ 'XDJ-RX': 2,
10871
+ 'XDJ-AN': 2,
10872
+ 'OPUS-QUAD': 4,
10873
+ };
10874
+ /**
10875
+ * What to assume about a mixer whose model we do not recognise.
10876
+ *
10877
+ * Four is both the most common layout and the choice that keeps remotedb
10878
+ * metadata working: assuming six would push us to player 7 on every mixer
10879
+ * released after this table was written, silently losing metadata for
10880
+ * unanalyzed and streaming tracks. A six-channel unit we failed to recognise
10881
+ * may later claim the number we took, which the announcer's conflict handling
10882
+ * resolves by moving us.
10883
+ */
10884
+ exports.DEFAULT_MIXER_PLAYER_CEILING = 4;
10885
+ /**
10886
+ * The highest player number the discovered gear can hand out, or undefined
10887
+ * when nothing on the network implies one.
10888
+ *
10889
+ * Undefined means we have no mixer to reason from — players may have been
10890
+ * numbered by hand anywhere in 1-6, and there is no better guess available.
10891
+ */
10892
+ function playerNumberCeiling(devices) {
10893
+ let ceiling;
10894
+ for (const device of devices) {
10895
+ const known = exports.MIXER_PLAYER_CEILING[device.name.trim().toUpperCase()];
10896
+ const value = known !== null && known !== void 0 ? known : (device.type === MIXER_DEVICE_TYPE ? exports.DEFAULT_MIXER_PLAYER_CEILING : undefined);
10897
+ if (value !== undefined && (ceiling === undefined || value > ceiling)) {
10898
+ ceiling = value;
10899
+ }
10900
+ }
10901
+ return ceiling;
10902
+ }
10903
+ const descending = (from, to) => {
10904
+ const ids = [];
10905
+ for (let id = from; id >= to; id--) {
10906
+ ids.push(id);
10907
+ }
10908
+ return ids;
10909
+ };
10910
+ const ascending = (from, to) => {
10911
+ const ids = [];
10912
+ for (let id = from; id <= to; id++) {
10913
+ ids.push(id);
10914
+ }
10915
+ return ids;
10916
+ };
10917
+ /**
10918
+ * Pick a device ID that no device on the network is currently using.
10919
+ *
10920
+ * Taking an ID a live player already holds knocks that player off the network
10921
+ * mid-set, so every caller that chooses its own ID should route through here
10922
+ * rather than hardcoding one.
10923
+ *
10924
+ * Returns null when every ID from 1 to 32 is occupied, in which case there is
10925
+ * no safe ID and the caller must not join the network.
10926
+ */
10927
+ function pickAvailableDeviceId(usedIds, { preferRemoteDb = false, playerCeiling } = {}) {
10928
+ var _a;
10929
+ const used = new Set(usedIds);
10930
+ const aboveThePlayers = playerCeiling === undefined
10931
+ ? descending(exports.REMOTEDB_MAX_DEVICE_ID, 1)
10932
+ : ascending(playerCeiling + 1, exports.REMOTEDB_MAX_DEVICE_ID);
10933
+ const outsideRemoteDb = ascending(exports.REMOTEDB_MAX_DEVICE_ID + 1, exports.MAX_DEVICE_ID);
10934
+ // Numbers a player on this rig could be assigned. Last resort: better to
10935
+ // contend for a slot than not to join at all, and the announcer moves us if
10936
+ // the player that owns it shows up.
10937
+ const amongThePlayers = playerCeiling === undefined
10938
+ ? []
10939
+ : descending(Math.min(playerCeiling, exports.REMOTEDB_MAX_DEVICE_ID), 1);
10940
+ const search = preferRemoteDb
10941
+ ? [...aboveThePlayers, ...outsideRemoteDb, ...amongThePlayers]
10942
+ : [...outsideRemoteDb, ...aboveThePlayers, ...amongThePlayers];
10943
+ return (_a = search.find(id => !used.has(id))) !== null && _a !== void 0 ? _a : null;
10944
+ }
10945
+
10946
+
10798
10947
  /***/ },
10799
10948
 
10800
10949
  /***/ "./src/virtualcdj/index.ts"
@@ -10852,7 +11001,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10852
11001
  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");
10853
11002
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
10854
11003
  };
10855
- var _Announcer_instances, _Announcer_announceSocket, _Announcer_deviceManager, _Announcer_vcdj, _Announcer_intervalHandle, _Announcer_fullStartup, _Announcer_announceToStagehand, _Announcer_currentStage, _Announcer_stageCounter, _Announcer_conflictListener, _Announcer_iface, _Announcer_logger, _Announcer_onStartupComplete, _Announcer_startFullStartup, _Announcer_handleConflict, _Announcer_sendPacket, _Announcer_sendStagePackets, _Announcer_advanceStage, _Announcer_startKeepAlive;
11004
+ var _Announcer_instances, _Announcer_announceSocket, _Announcer_deviceManager, _Announcer_vcdj, _Announcer_intervalHandle, _Announcer_fullStartup, _Announcer_announceToStagehand, _Announcer_currentStage, _Announcer_stageCounter, _Announcer_conflictListener, _Announcer_iface, _Announcer_logger, _Announcer_onStartupComplete, _Announcer_armConflictListener, _Announcer_startFullStartup, _Announcer_handleConflict, _Announcer_clearTimer, _Announcer_sendPacket, _Announcer_sendStagePackets, _Announcer_advanceStage, _Announcer_startKeepAlive;
10856
11005
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10857
11006
  exports.Announcer = exports.getVirtualCDJ = void 0;
10858
11007
  exports.makeStatusPacket = makeStatusPacket;
@@ -10862,6 +11011,7 @@ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts
10862
11011
  const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
10863
11012
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
10864
11013
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
11014
+ const device_id_1 = __webpack_require__(/*! src/virtualcdj/device-id */ "./src/virtualcdj/device-id.ts");
10865
11015
  /**
10866
11016
  * Constructs a virtual CDJ Device.
10867
11017
  *
@@ -11183,6 +11333,7 @@ class Announcer {
11183
11333
  });
11184
11334
  }
11185
11335
  start() {
11336
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_armConflictListener).call(this);
11186
11337
  if (__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
11187
11338
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_startFullStartup).call(this);
11188
11339
  }
@@ -11191,11 +11342,7 @@ class Announcer {
11191
11342
  }
11192
11343
  }
11193
11344
  stop() {
11194
- if (__classPrivateFieldGet(this, _Announcer_intervalHandle, "f") !== undefined) {
11195
- clearInterval(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11196
- clearTimeout(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11197
- __classPrivateFieldSet(this, _Announcer_intervalHandle, undefined, "f");
11198
- }
11345
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_clearTimer).call(this);
11199
11346
  // Remove conflict listener if active
11200
11347
  if (__classPrivateFieldGet(this, _Announcer_conflictListener, "f")) {
11201
11348
  __classPrivateFieldGet(this, _Announcer_announceSocket, "f").off('message', __classPrivateFieldGet(this, _Announcer_conflictListener, "f"));
@@ -11204,10 +11351,10 @@ class Announcer {
11204
11351
  }
11205
11352
  }
11206
11353
  exports.Announcer = Announcer;
11207
- _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMap(), _Announcer_vcdj = new WeakMap(), _Announcer_intervalHandle = new WeakMap(), _Announcer_fullStartup = new WeakMap(), _Announcer_announceToStagehand = new WeakMap(), _Announcer_currentStage = new WeakMap(), _Announcer_stageCounter = new WeakMap(), _Announcer_conflictListener = new WeakMap(), _Announcer_iface = new WeakMap(), _Announcer_logger = new WeakMap(), _Announcer_onStartupComplete = new WeakMap(), _Announcer_instances = new WeakSet(), _Announcer_startFullStartup = function _Announcer_startFullStartup() {
11208
- __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11209
- __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11210
- // Set up conflict detection listener
11354
+ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMap(), _Announcer_vcdj = new WeakMap(), _Announcer_intervalHandle = new WeakMap(), _Announcer_fullStartup = new WeakMap(), _Announcer_announceToStagehand = new WeakMap(), _Announcer_currentStage = new WeakMap(), _Announcer_stageCounter = new WeakMap(), _Announcer_conflictListener = new WeakMap(), _Announcer_iface = new WeakMap(), _Announcer_logger = new WeakMap(), _Announcer_onStartupComplete = new WeakMap(), _Announcer_instances = new WeakSet(), _Announcer_armConflictListener = function _Announcer_armConflictListener() {
11355
+ if (__classPrivateFieldGet(this, _Announcer_conflictListener, "f")) {
11356
+ return;
11357
+ }
11211
11358
  __classPrivateFieldSet(this, _Announcer_conflictListener, (msg) => {
11212
11359
  const conflictDeviceId = parseConflictPacket(msg);
11213
11360
  if (conflictDeviceId === __classPrivateFieldGet(this, _Announcer_vcdj, "f").id) {
@@ -11216,43 +11363,52 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11216
11363
  }
11217
11364
  }, "f");
11218
11365
  __classPrivateFieldGet(this, _Announcer_announceSocket, "f").on('message', __classPrivateFieldGet(this, _Announcer_conflictListener, "f"));
11366
+ }, _Announcer_startFullStartup = function _Announcer_startFullStartup() {
11367
+ __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11368
+ __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11219
11369
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendStagePackets).call(this);
11220
11370
  }, _Announcer_handleConflict = function _Announcer_handleConflict() {
11221
- // Stop current startup
11222
- if (__classPrivateFieldGet(this, _Announcer_intervalHandle, "f")) {
11223
- clearTimeout(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11224
- __classPrivateFieldSet(this, _Announcer_intervalHandle, undefined, "f");
11225
- }
11226
- // Find an available device ID
11227
- const usedIds = new Set([...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()].map(d => d.id));
11228
- let newId = null;
11229
- // Try IDs from 7-32 first (recommended range), then 1-6 if needed
11230
- for (let id = 7; id <= 32; id++) {
11231
- if (!usedIds.has(id)) {
11232
- newId = id;
11233
- break;
11234
- }
11235
- }
11236
- if (newId === null) {
11237
- for (let id = 1; id <= 6; id++) {
11238
- if (!usedIds.has(id)) {
11239
- newId = id;
11240
- break;
11241
- }
11242
- }
11243
- }
11371
+ // Stop announcing under the contested ID
11372
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_clearTimer).call(this);
11373
+ // Stay in whichever range we were already in. Dropping out of 1-6 would
11374
+ // silently lose remotedb metadata for unanalyzed and streaming tracks, so
11375
+ // a conflict on player 5 looks for another free player number first.
11376
+ //
11377
+ // The mixer decides which of those numbers are actually safe: a player can
11378
+ // only be assigned a number its mixer has a channel for, so anything above
11379
+ // that is out of reach of the rig that just contested us.
11380
+ const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()];
11381
+ const newId = (0, device_id_1.pickAvailableDeviceId)(devices.map(d => d.id), {
11382
+ preferRemoteDb: __classPrivateFieldGet(this, _Announcer_vcdj, "f").id <= device_id_1.REMOTEDB_MAX_DEVICE_ID,
11383
+ playerCeiling: (0, device_id_1.playerNumberCeiling)(devices),
11384
+ });
11244
11385
  if (newId === null) {
11245
11386
  __classPrivateFieldGet(this, _Announcer_logger, "f").error('No available device IDs. All 32 slots are occupied.');
11246
11387
  this.stop();
11247
11388
  return;
11248
11389
  }
11249
11390
  __classPrivateFieldGet(this, _Announcer_logger, "f").info(`Switching to device ID ${newId}`);
11250
- // Update virtual CDJ with new ID
11251
- __classPrivateFieldSet(this, _Announcer_vcdj, (0, exports.getVirtualCDJ)(__classPrivateFieldGet(this, _Announcer_iface, "f"), newId), "f");
11252
- // Restart startup sequence
11253
- __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11254
- __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11255
- __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendStagePackets).call(this);
11391
+ // Mutate in place rather than building a replacement device. The remotedb,
11392
+ // localdb and control services were all handed this same object when the
11393
+ // network connected and read `.id` when they build packets, so swapping
11394
+ // the reference here would leave us announcing as one ID and querying as
11395
+ // another.
11396
+ __classPrivateFieldGet(this, _Announcer_vcdj, "f").id = newId;
11397
+ // Re-announce under the new ID
11398
+ if (__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
11399
+ __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11400
+ __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11401
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendStagePackets).call(this);
11402
+ }
11403
+ else {
11404
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_startKeepAlive).call(this);
11405
+ }
11406
+ }, _Announcer_clearTimer = function _Announcer_clearTimer() {
11407
+ if (__classPrivateFieldGet(this, _Announcer_intervalHandle, "f") !== undefined) {
11408
+ clearTimeout(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11409
+ clearInterval(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11410
+ __classPrivateFieldSet(this, _Announcer_intervalHandle, undefined, "f");
11411
+ }
11256
11412
  }, _Announcer_sendPacket = function _Announcer_sendPacket(packet) {
11257
11413
  const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()].filter(d => __classPrivateFieldGet(this, _Announcer_announceToStagehand, "f") || !d.name.toLowerCase().includes('stagehand'));
11258
11414
  devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
@@ -11312,15 +11468,7 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11312
11468
  break;
11313
11469
  }
11314
11470
  }, _Announcer_startKeepAlive = function _Announcer_startKeepAlive() {
11315
- if (__classPrivateFieldGet(this, _Announcer_intervalHandle, "f")) {
11316
- clearTimeout(__classPrivateFieldGet(this, _Announcer_intervalHandle, "f"));
11317
- __classPrivateFieldSet(this, _Announcer_intervalHandle, undefined, "f");
11318
- }
11319
- // Remove conflict listener once we're in keep-alive mode
11320
- if (__classPrivateFieldGet(this, _Announcer_conflictListener, "f")) {
11321
- __classPrivateFieldGet(this, _Announcer_announceSocket, "f").off('message', __classPrivateFieldGet(this, _Announcer_conflictListener, "f"));
11322
- __classPrivateFieldSet(this, _Announcer_conflictListener, undefined, "f");
11323
- }
11471
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_clearTimer).call(this);
11324
11472
  const sendKeepAlive = () => {
11325
11473
  const peerCount = __classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.size + 1; // +1 for ourselves
11326
11474
  const packet = __classPrivateFieldGet(this, _Announcer_fullStartup, "f")
@@ -11333,6 +11481,7 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11333
11481
  // Then schedule regular keep-alives
11334
11482
  __classPrivateFieldSet(this, _Announcer_intervalHandle, setInterval(sendKeepAlive, constants_1.ANNOUNCE_INTERVAL), "f");
11335
11483
  };
11484
+ __exportStar(__webpack_require__(/*! ./device-id */ "./src/virtualcdj/device-id.ts"), exports);
11336
11485
  __exportStar(__webpack_require__(/*! ./stagehand */ "./src/virtualcdj/stagehand.ts"), exports);
11337
11486
 
11338
11487