@andyfarthing/prolink-connect
Version:
Fork of prolink-connect with modern build system and fixes
1,716 lines (1,693 loc) • 63.6 kB
text/typescript
import { Address4 } from 'ip-address';
import StrictEventEmitter from 'strict-event-emitter-types';
import { EventEmitter } from 'events';
import { Socket } from 'dgram';
import { NetworkInterfaceInfoIPv4 } from 'os';
import { Mutex } from 'async-mutex';
import PromiseSocket from 'promise-socket';
import { Socket as Socket$1 } from 'net';
import { PromiseReadable } from 'promise-readable';
/**
* Status flag bitmasks
*/
declare enum StatusFlag {
OnAir = 8,
Sync = 16,
Master = 32,
Playing = 64
}
/**
* Play state flags
*/
declare enum PlayState {
Empty = 0,
Loading = 2,
Playing = 3,
Looping = 4,
Paused = 5,
Cued = 6,
Cuing = 7,
PlatterHeld = 8,
Searching = 9,
SpunDown = 14,
Ended = 17
}
/**
* Represents various details about the current state of the CDJ.
*/
interface State {
/**
* The device reporting this status.
*/
deviceId: number;
/**
* The ID of the track loaded on the device.
*
* 0 When no track is loaded.
*/
trackId: number;
/**
* The device ID the track is loaded from.
*
* For example if you have two CDJs and you've loaded a track over the 'LINK',
* this will be the ID of the player with the USB media device connected to it.
*/
trackDeviceId: DeviceID;
/**
* The MediaSlot the track is loaded from. For example a SD card or USB device.
*/
trackSlot: MediaSlot;
/**
* The TrackType of the track, for example a CD or Rekordbox analyzed track.
*/
trackType: TrackType;
/**
* The current play state of the CDJ.
*/
playState: PlayState;
/**
* Whether the CDJ is currently reporting itself as 'on-air'.
*
* This is indicated by the red ring around the platter on the CDJ Nexus models.
* A DJM mixer must be ont he network for the CDJ to report this as true.
*/
isOnAir: boolean;
/**
* Whether the CDJ is synced.
*/
isSync: boolean;
/**
* Whether the CDJ is the master player.
*/
isMaster: boolean;
/**
* Whether the CDJ is in an emergency state (emergecy loop / emergency mode
* on newer players)
*/
isEmergencyMode: boolean;
/**
* The BPM of the loaded track. null if no track is loaded or the BPM is unknown.
*/
trackBPM: number | null;
/**
* The "effective" pitch of the plyaer. This is reported anytime the jogwheel is
* nudged, the CDJ spins down by pausing with the vinyl stop knob not at 0, or
* by holding the platter.
*/
effectivePitch: number;
/**
* The current slider pitch
*/
sliderPitch: number;
/**
* The current beat within the measure. 1-4. 0 when no track is loaded.
*/
beatInMeasure: number;
/**
* Number of beats remaining until the next cue point is reached. Null if there
* is no next cue point
*/
beatsUntilCue: number | null;
/**
* The beat 'timestamp' of the track. Can be used to compute absolute track time
* given the slider pitch.
*/
beat: number | null;
/**
* A counter that increments for every status packet sent.
*/
packetNum: number;
}
type types_PlayState = PlayState;
declare const types_PlayState: typeof PlayState;
type types_State = State;
type types_StatusFlag = StatusFlag;
declare const types_StatusFlag: typeof StatusFlag;
declare namespace types {
export { types_PlayState as PlayState, type types_State as State, types_StatusFlag as StatusFlag };
}
/**
* Table names available
*/
declare enum Table {
Artist = "artist",
Album = "album",
Genre = "genre",
Color = "color",
Label = "label",
Key = "key",
Artwork = "artwork",
Playlist = "playlist",
PlaylistEntry = "playlist_entry",
Track = "track"
}
/**
* Object Relation Mapper as an abstraction ontop of a local database
* connection.
*
* May be used to populate a metadata database and query objects.
*/
declare class MetadataORM {
#private;
constructor();
close(): void;
/**
* Insert a entity object into the database.
*/
insertEntity(table: Table, object: Record<string, any>): void;
/**
* Locate a track by ID in the database
*/
findTrack(id: number): Track;
/**
* Query for a list of {folders, playlists, tracks} given a playlist ID. If
* no ID is provided the root list is queried.
*
* Note that when tracks are returned there will be no folders or playslists.
* But the API here is simpler to assume there could be.
*
* Tracks are returned in the order they are placed on the playlist.
*/
findPlaylist(playlistId?: number): {
folders: Playlist[];
playlists: Playlist[];
trackEntries: PlaylistEntry<EntityFK.WithFKs>[];
};
}
/**
* Details about the current state of the hydtration task
*/
interface HydrationProgress {
/**
* The specific table that progress is being reported for
*/
table: string;
/**
* The total progress steps for this table
*/
total: number;
/**
* The completed number of progress steps
*/
complete: number;
}
interface MixstatusConfig {
/**
* Selects the mixstatus reporting mode
*/
mode: MixstatusMode;
/**
* Specifies the duration in seconds that no tracks must be on air. This can
* be thought of as how long 'air silence' is reasonable in a set before a
* separate one is considered have begun.
*
* @default 30 (half a minute)
*/
timeBetweenSets: number;
/**
* Indicates if the status objects reported should have their on-air flag
* read. Setting this to false will degrade the functionality of the processor
* such that it will not consider the value of isOnAir and always assume CDJs
* are live.
*
* @default true
*/
useOnAirStatus: boolean;
/**
* Configures how many beats a track may not be live or playing for it to
* still be considered active.
*
* @default 8 (two bars)
*/
allowedInterruptBeats: number;
/**
* Configures how many beats the track must consecutively be playing for
* (since the beat it was cued at) until the track is considered to be
* active.
*
* Used for MixstatusMode.SmartTiming
*
* @default 128 (2 phrases)
*/
beatsUntilReported: number;
}
/**
* The interface the mix status event emitter should follow
*/
interface MixstatusEvents {
/**
* Fired when a track is considered to be on-air and is being heard by the
* audience
*/
nowPlaying: (state: State) => void;
/**
* Fired when a track has stopped and is completely offair
*/
stopped: (opt: {
deviceId: DeviceID;
}) => void;
/**
* Fired when a DJ set first starts
*/
setStarted: () => void;
/**
* Fired when tracks have been stopped
*/
setEnded: () => void;
}
type Emitter$3 = StrictEventEmitter<EventEmitter, MixstatusEvents>;
/**
* MixstatusProcessor is a configurable processor which when fed device state
* will attempt to accurately determine events that happen within the DJ set.
*
* The following events are fired:
*
* - nowPlaying: The track is considered playing and on air to the audience.
* - stopped: The track was stopped / paused / went off-air.
*
* Additionally the following non-track status are reported:
*
* - setStarted: The first track has begun playing.
* - setEnded: The TimeBetweenSets has passed since any tracks were live.
*
* See Config for configuration options.
*
* Config options may be changed after the processor has been created and is
* actively receiving state updates.
*/
declare class MixstatusProcessor {
#private;
constructor(config?: Partial<MixstatusConfig>);
/**
* Update the configuration
*/
configure(config?: Partial<MixstatusConfig>): void;
on: Emitter$3['on'];
off: Emitter$3['off'];
once: Emitter$3['once'];
/**
* Feed a CDJStatus state object to the mix state processor
*/
handleState(state: State): void;
/**
* Manually reports the track that has been playing the longest which has not
* yet been reported as live.
*/
triggerNextTrack(): void;
}
interface Options$5 {
hostDevice: Device;
device: Device;
playState: PlayState.Cued | PlayState.Playing;
}
declare class Control {
#private;
constructor(beatSocket: Socket, hostDevice: Device);
/**
* Start or stop a CDJ on the network
*/
setPlayState(device: Device, playState: Options$5['playState']): Promise<void>;
}
interface Config {
/**
* Time in milliseconds after which a device is considered to have
* disconnected if it has not broadcast an announcement.
*
* @default 10000 ms
*/
deviceTimeout?: number;
}
/**
* The configuration object that may be passed to reconfigure the manager
*/
type ConfigEditable = Omit<Config, 'announceSocket'>;
/**
* The interface the device manager event emitter should follow
*/
interface DeviceEvents {
/**
* Fired when a new device becomes available on the network
*/
connected: (device: Device) => void;
/**
* Fired when a device has not announced itself on the network for the
* specified timeout.
*/
disconnected: (device: Device) => void;
/**
* Fired every time the device announces itself on the network
*/
announced: (device: Device) => void;
}
type Emitter$2 = StrictEventEmitter<EventEmitter, DeviceEvents>;
/**
* The device manager is responsible for tracking devices that appear on the
* prolink network, providing an API to react to devices livecycle events as
* they connect and disconnect form the network.
*/
declare class DeviceManager {
#private;
constructor(announceSocket: Socket, config?: Config);
on: Emitter$2['on'];
off: Emitter$2['off'];
once: Emitter$2['once'];
/**
* Get active devices on the network.
*/
get devices(): Map<number, Device>;
/**
* Waits for a specific device ID to appear on the network, with a
* configurable timeout, in which case it will resolve with null.
*/
getDeviceEnsured(id: DeviceID, timeout?: number): Promise<Device | null>;
reconfigure(config: ConfigEditable): void;
}
interface FetchProgress {
read: number;
total: number;
}
interface Options$4 {
/**
* The device asking for media info
*/
hostDevice: Device;
/**
* The target device. This is the device we'll be querying for details of
* it's media slot.
*/
device: Device;
/**
* The specific slot
*/
slot: MediaSlot;
}
/**
* Get information about the media connected to the specified slot on the
* device.
*/
declare const makeMediaSlotRequest: ({ hostDevice, device, slot }: Options$4) => Uint8Array<ArrayBuffer>;
interface StatusEvents {
/**
* Fired each time the CDJ reports its status
*/
status: (status: State) => void;
/**
* Fired when the CDJ reports its media slot status
*/
mediaSlot: (info: MediaSlotInfo) => void;
}
type Emitter$1 = StrictEventEmitter<EventEmitter, StatusEvents>;
type MediaSlotOptions = Parameters<typeof makeMediaSlotRequest>[0];
/**
* The status emitter will report every time a device status is received
*/
declare class StatusEmitter {
#private;
/**
* @param statusSocket A UDP socket to receive CDJ status packets on
*/
constructor(statusSocket: Socket);
on: Emitter$1['on'];
off: Emitter$1['off'];
once: Emitter$1['once'];
/**
* Retrieve media slot status information.
*/
queryMediaSlot(options: MediaSlotOptions): Promise<MediaSlotInfo>;
}
/**
* Rekordbox databases will only exist within these two slots
*/
type DatabaseSlot = MediaSlot.USB | MediaSlot.SD;
interface CommonProgressOpts {
/**
* The device progress is being reported for
*/
device: Device;
/**
* The media slot progress is being reported for
*/
slot: MediaSlot;
}
type DownloadProgressOpts = CommonProgressOpts & {
/**
* The current progress of the fetch
*/
progress: FetchProgress;
};
type HydrationProgressOpts = CommonProgressOpts & {
/**
* The current progress of the database hydration
*/
progress: HydrationProgress;
};
type HydrationDoneOpts = CommonProgressOpts;
/**
* Events that may be triggered by the LocalDatabase emitter
*/
interface DatabaseEvents {
/**
* Triggered when we are fetching a database from a CDJ
*/
fetchProgress: (opts: DownloadProgressOpts) => void;
/**
* Triggered when we are hydrating a rekordbox database into the in-memory
* sqlite database.
*/
hydrationProgress: (opts: HydrationProgressOpts) => void;
/**
* Triggered when the database has been fully hydrated.
*
* There is a period of time between hydrationProgress reporting 100% copletion,
* and the database being flushed, so it may be useful to wait for this event
* before considering the database to be fully hydrated.
*/
hydrationDone: (opts: HydrationDoneOpts) => void;
}
type Emitter = StrictEventEmitter<EventEmitter, DatabaseEvents>;
/**
* The local database is responsible for syncing the remote rekordbox databases
* of media slots on a device into in-memory sqlite databases.
*
* This service will attempt to ensure the in-memory databases for each media
* device that is connected to a CDJ is locally kept in sync. Fetching the
* database for any media slot of it's not already cached.
*/
declare class LocalDatabase {
#private;
constructor(hostDevice: Device, deviceManager: DeviceManager, statusEmitter: StatusEmitter);
on: Emitter['on'];
off: Emitter['off'];
once: Emitter['once'];
/**
* Disconnects the local database connection for the specified device
*/
disconnectForDevice(device: Device): void;
/**
* Gets the sqlite ORM service for to a database hydrated with the media
* metadata for the provided device slot.
*
* If the database has not already been hydrated this will first hydrate the
* database, which may take some time depending on the size of the database.
*
* @returns null if no rekordbox media present
*/
get(deviceId: DeviceID, slot: DatabaseSlot): Promise<MetadataORM | null>;
/**
* Preload the databases for all connected devices.
*/
preload(): Promise<void>;
}
/**
* Used for control messages with the remote database
*/
declare enum ControlRequest {
Introduce = 0,
Disconnect = 256,
RenderMenu = 12288
}
/**
* Used to setup renders for specific Menus
*/
declare enum MenuRequest {
MenuRoot = 4096,
MenuGenre = 4097,
MenuArtist = 4098,
MenuAlbum = 4099,
MenuTrack = 4100,
MenuBPM = 4102,
MenuRating = 4103,
MenuYear = 4104,
MenuLabel = 4106,
MenuColor = 4109,
MenuTime = 4112,
MenuBitrate = 4113,
MenuHistory = 4114,
MenuFilename = 4115,
MenuKey = 4116,
MenuOriginalArtist = 4866,
MenuRemixer = 5634,
MenuPlaylist = 4357,
MenuArtistsOfGenre = 4353,
MenuAlbumsOfArtist = 4354,
MenuTracksOfAlbum = 4355,
MenuTracksOfRating = 4359,
MenuYearsOfDecade = 4360,
MenuArtistsOfLabel = 4362,
MenuTracksOfColor = 4365,
MenuTracksOfTime = 4368,
MenuTracksOfHistory = 4370,
MenuDistancesOfKey = 4372,
MenuAlbumsOfOriginalArtist = 5122,
MenuAlbumsOfRemixer = 5890,
MenuAlbumsOfGenreAndArtist = 4609,
MenuTracksOfArtistAndAlbum = 4610,
MenuTracksOfBPMPercentRange = 4614,
MenuTracksOfDecadeAndYear = 4616,
MenuAlbumsOfLabelAndArtist = 4618,
MenuTracksNearKey = 4628,
MenuTracksOfOriginalArtistAndAlbum = 5378,
MenuTracksOfRemixerAndAlbum = 6146,
MenuTracksOfGenreArtistAndAlbum = 4865,
MenuTracksOfLabelArtistAndAlbum = 4874,
MenuSearch = 4864,
MenuFolder = 8198
}
/**
* Request message types used to obtain specfiic track information
*/
declare enum DataRequest {
GetMetadata = 8194,
GetArtwork = 8195,
GetWaveformPreview = 8196,
GetTrackInfo = 8450,
GetGenericMetadata = 8706,
GetCueAndLoops = 8452,
GetBeatGrid = 8708,
GetWaveformDetailed = 10500,
GetAdvCueAndLoops = 11012,
GetWaveformHD = 11268
}
/**
* Response message types for messages sent back by the server.
*/
declare enum Response {
Success = 16384,
Error = 16387,
Artwork = 16386,
MenuItem = 16641,
MenuHeader = 16385,
MenuFooter = 16897,
BeatGrid = 17922,
CueAndLoop = 18178,
WaveformPreview = 17410,
WaveformDetailed = 18946,
AdvCueAndLoops = 19970,
WaveformHD = 20226
}
/**
* All Known message types. These are used for both request and response messages.
*/
type MessageType = ControlRequest | MenuRequest | DataRequest | Response;
declare const MessageType: {
readonly [x: number]: string;
readonly Success: Response.Success;
readonly Error: Response.Error;
readonly Artwork: Response.Artwork;
readonly MenuItem: Response.MenuItem;
readonly MenuHeader: Response.MenuHeader;
readonly MenuFooter: Response.MenuFooter;
readonly BeatGrid: Response.BeatGrid;
readonly CueAndLoop: Response.CueAndLoop;
readonly WaveformPreview: Response.WaveformPreview;
readonly WaveformDetailed: Response.WaveformDetailed;
readonly AdvCueAndLoops: Response.AdvCueAndLoops;
readonly WaveformHD: Response.WaveformHD;
readonly GetMetadata: DataRequest.GetMetadata;
readonly GetArtwork: DataRequest.GetArtwork;
readonly GetWaveformPreview: DataRequest.GetWaveformPreview;
readonly GetTrackInfo: DataRequest.GetTrackInfo;
readonly GetGenericMetadata: DataRequest.GetGenericMetadata;
readonly GetCueAndLoops: DataRequest.GetCueAndLoops;
readonly GetBeatGrid: DataRequest.GetBeatGrid;
readonly GetWaveformDetailed: DataRequest.GetWaveformDetailed;
readonly GetAdvCueAndLoops: DataRequest.GetAdvCueAndLoops;
readonly GetWaveformHD: DataRequest.GetWaveformHD;
readonly MenuRoot: MenuRequest.MenuRoot;
readonly MenuGenre: MenuRequest.MenuGenre;
readonly MenuArtist: MenuRequest.MenuArtist;
readonly MenuAlbum: MenuRequest.MenuAlbum;
readonly MenuTrack: MenuRequest.MenuTrack;
readonly MenuBPM: MenuRequest.MenuBPM;
readonly MenuRating: MenuRequest.MenuRating;
readonly MenuYear: MenuRequest.MenuYear;
readonly MenuLabel: MenuRequest.MenuLabel;
readonly MenuColor: MenuRequest.MenuColor;
readonly MenuTime: MenuRequest.MenuTime;
readonly MenuBitrate: MenuRequest.MenuBitrate;
readonly MenuHistory: MenuRequest.MenuHistory;
readonly MenuFilename: MenuRequest.MenuFilename;
readonly MenuKey: MenuRequest.MenuKey;
readonly MenuOriginalArtist: MenuRequest.MenuOriginalArtist;
readonly MenuRemixer: MenuRequest.MenuRemixer;
readonly MenuPlaylist: MenuRequest.MenuPlaylist;
readonly MenuArtistsOfGenre: MenuRequest.MenuArtistsOfGenre;
readonly MenuAlbumsOfArtist: MenuRequest.MenuAlbumsOfArtist;
readonly MenuTracksOfAlbum: MenuRequest.MenuTracksOfAlbum;
readonly MenuTracksOfRating: MenuRequest.MenuTracksOfRating;
readonly MenuYearsOfDecade: MenuRequest.MenuYearsOfDecade;
readonly MenuArtistsOfLabel: MenuRequest.MenuArtistsOfLabel;
readonly MenuTracksOfColor: MenuRequest.MenuTracksOfColor;
readonly MenuTracksOfTime: MenuRequest.MenuTracksOfTime;
readonly MenuTracksOfHistory: MenuRequest.MenuTracksOfHistory;
readonly MenuDistancesOfKey: MenuRequest.MenuDistancesOfKey;
readonly MenuAlbumsOfOriginalArtist: MenuRequest.MenuAlbumsOfOriginalArtist;
readonly MenuAlbumsOfRemixer: MenuRequest.MenuAlbumsOfRemixer;
readonly MenuAlbumsOfGenreAndArtist: MenuRequest.MenuAlbumsOfGenreAndArtist;
readonly MenuTracksOfArtistAndAlbum: MenuRequest.MenuTracksOfArtistAndAlbum;
readonly MenuTracksOfBPMPercentRange: MenuRequest.MenuTracksOfBPMPercentRange;
readonly MenuTracksOfDecadeAndYear: MenuRequest.MenuTracksOfDecadeAndYear;
readonly MenuAlbumsOfLabelAndArtist: MenuRequest.MenuAlbumsOfLabelAndArtist;
readonly MenuTracksNearKey: MenuRequest.MenuTracksNearKey;
readonly MenuTracksOfOriginalArtistAndAlbum: MenuRequest.MenuTracksOfOriginalArtistAndAlbum;
readonly MenuTracksOfRemixerAndAlbum: MenuRequest.MenuTracksOfRemixerAndAlbum;
readonly MenuTracksOfGenreArtistAndAlbum: MenuRequest.MenuTracksOfGenreArtistAndAlbum;
readonly MenuTracksOfLabelArtistAndAlbum: MenuRequest.MenuTracksOfLabelArtistAndAlbum;
readonly MenuSearch: MenuRequest.MenuSearch;
readonly MenuFolder: MenuRequest.MenuFolder;
readonly Introduce: ControlRequest.Introduce;
readonly Disconnect: ControlRequest.Disconnect;
readonly RenderMenu: ControlRequest.RenderMenu;
};
/**
* Field type is a leading byte that indicates what the field is.
*/
declare enum FieldType {
UInt8 = 15,
UInt16 = 16,
UInt32 = 17,
Binary = 20,
String = 38
}
declare abstract class BaseField {
['constructor']: typeof BaseField;
/**
* The raw field data
*/
data: Buffer;
/**
* Corce the field into a buffer. This differs from reading the data
* property in that it will include the field type header.
*/
abstract buffer: Buffer;
/**
* Declares the type of field this class represents
*/
static type: FieldType;
/**
* The number of bytes to read for this field. If the field is not a fixed size,
* set this to a function which will receive the UInt32 value just after
* reading the field type, returning the next number of bytes to read.
*/
static bytesToRead: number | ((reportedLength: number) => number);
}
type NumberField<T extends number = number> = BaseField & {
/**
* The fields number value
*/
value: T;
};
type StringField<T extends string = string> = BaseField & {
/**
* The fields decoded string value
*/
value: T;
};
type BinaryField = BaseField & {
/**
* The binary value encapsulated in the field
*/
value: Buffer;
};
type Field = NumberField | StringField | BinaryField;
/**
* Item types associated to the MenuItem message type.
*/
declare enum ItemType {
Path = 0,
Folder = 1,
AlbumTitle = 2,
Disc = 3,
TrackTitle = 4,
Genre = 6,
Artist = 7,
Playlist = 8,
Rating = 10,
Duration = 11,
Tempo = 13,
Label = 14,
Key = 15,
BitRate = 16,
Year = 17,
Comment = 35,
HistoryPlaylist = 36,
OriginalArtist = 40,
Remixer = 41,
DateAdded = 46,
Unknown01 = 47,
Unknown02 = 42,
ColorNone = 19,
ColorPink = 20,
ColorRed = 21,
ColorOrange = 22,
ColorYellow = 23,
ColorGreen = 24,
ColorAqua = 25,
ColorBlue = 26,
ColorPurple = 27,
MenuGenre = 128,
MenuArtist = 129,
MenuAlbum = 130,
MenuTrack = 131,
MenuPlaylist = 132,
MenuBPM = 133,
MenuRating = 134,
MenuYear = 135,
MenuRemixer = 136,
MenuLabel = 137,
MenuOriginal = 138,
MenuKey = 139,
MenuColor = 142,
MenuFolder = 144,
MenuSearch = 145,
MenuTime = 146,
MenuBit = 147,
MenuFilename = 148,
MenuHistory = 149,
MenuAll = 160,
TrackTitleAlbum = 516,
TrackTitleGenre = 1540,
TrackTitleArtist = 1796,
TrackTitleRating = 2564,
TrackTitleTime = 2820,
TrackTitleBPM = 3332,
TrackTitleLabel = 3588,
TrackTitleKey = 3844,
TrackTitleBitRate = 4100,
TrackTitleColor = 6660,
TrackTitleComment = 8964,
TrackTitleOriginalArtist = 10244,
TrackTitleRemixer = 10500,
TrackTitleDJPlayCount = 10756,
MenuTrackTitleDateAdded = 11780
}
/**
* All items have 12 arguments of these types
*/
type ItemArgs = [
NumberField,
NumberField,
NumberField,
StringField,
NumberField,
StringField,
NumberField<ItemType>,
NumberField,
NumberField,
NumberField,
NumberField,
NumberField
];
/**
* Convert a message item argument lists to a structured intermediate object
* for more clear access.
*/
declare const makeItemData: (args: ItemArgs) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
type ItemData = ReturnType<typeof makeItemData>;
/**
* Maps item types to structured objects
*/
declare const transformItem: {
0: (a: ItemData) => {
path: string;
};
4: (a: ItemData) => {
id: number;
title: string;
artworkId: number;
};
2: (a: ItemData) => {
id: number;
name: string;
};
7: (a: ItemData) => {
id: number;
name: string;
};
6: (a: ItemData) => {
id: number;
name: string;
};
14: (a: ItemData) => {
id: number;
name: string;
};
15: (a: ItemData) => {
id: number;
name: string;
};
40: (a: ItemData) => {
id: number;
name: string;
};
41: (a: ItemData) => {
id: number;
name: string;
};
16: (a: ItemData) => {
bitrate: number;
};
35: (a: ItemData) => {
comment: string;
};
17: (a: ItemData) => {
year: number;
};
10: (a: ItemData) => {
rating: number;
};
13: (a: ItemData) => {
bpm: number;
};
11: (a: ItemData) => {
duration: number;
};
47: (_: ItemData) => null;
42: (_: ItemData) => null;
19: (a: ItemData) => {
id: number;
name: string;
};
20: (a: ItemData) => {
id: number;
name: string;
};
21: (a: ItemData) => {
id: number;
name: string;
};
22: (a: ItemData) => {
id: number;
name: string;
};
23: (a: ItemData) => {
id: number;
name: string;
};
24: (a: ItemData) => {
id: number;
name: string;
};
25: (a: ItemData) => {
id: number;
name: string;
};
26: (a: ItemData) => {
id: number;
name: string;
};
27: (a: ItemData) => {
id: number;
name: string;
};
1: (a: ItemData) => {
id: number;
name: string;
};
8: (a: ItemData) => {
id: number;
name: string;
};
3: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
36: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
46: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
128: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
129: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
130: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
131: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
132: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
133: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
134: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
135: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
136: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
137: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
138: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
139: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
142: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
144: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
145: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
146: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
147: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
148: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
149: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
160: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
516: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
1540: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
1796: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
2564: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
2820: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
3332: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
3588: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
3844: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
4100: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
6660: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
8964: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
10244: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
10500: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
10756: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
11780: (a: ItemData) => {
parentId: number;
mainId: number;
label1: string;
label2: string;
type: ItemType;
artworkId: number;
};
};
/**
* Represents a generic Item, specialized to a specific item by providing a
* ItemType to the template.
*/
type Item<T extends ItemType> = ReturnType<(typeof transformItem)[T]> & {
type: T;
};
/**
* Maps ItemTypes to Items
*/
type Items = {
[T in keyof typeof transformItem]: Item<T>;
};
declare const responseTransform: {
readonly 16384: (args: Field[]) => {
itemsAvailable: number;
};
readonly 16387: (_args: Field[]) => null;
readonly 16385: (_args: Field[]) => null;
readonly 16897: (_args: Field[]) => null;
readonly 16641: (args: Field[]) => Items[ItemType];
readonly 16386: (args: Field[]) => Buffer;
readonly 17922: (args: Field[]) => BeatGrid;
readonly 18178: (args: Field[]) => CueAndLoop[];
readonly 17410: (args: Field[]) => WaveformPreview;
readonly 18946: (args: Field[]) => WaveformDetailed;
readonly 20226: (args: Field[]) => WaveformHD;
readonly 19970: (args: Field[]) => CueAndLoop[];
};
interface Options$3<T extends MessageType> {
transactionId?: number;
type: T;
args: Field[];
}
type ResponseType<T> = T extends Response ? T : never;
type Data<T> = ReturnType<(typeof responseTransform)[ResponseType<T>]>;
/**
* Representation of a set of fields sequenced into a known message format.
*/
declare class Message<T extends MessageType = MessageType> {
/**
* Read a single mesasge via a readable stream
*/
static fromStream<T extends Response>(stream: PromiseReadable<any>, expect: T): Promise<Message<T>>;
/**
* The transaction ID is used to associate responses to their requests.
*/
transactionId?: number;
readonly type: T;
readonly args: Field[];
constructor({ transactionId, type, args }: Options$3<T>);
/**
* The byte serialization of the message
*/
get buffer(): Buffer<ArrayBuffer>;
/**
* The JS representation of the message.
*
* Currently only supports representing response messages.
*/
get data(): Data<T>;
}
/**
* This module contains logic for each type of query to understand what
* arguments are required, and how to transform the resulting Items into
* something useful.
*/
interface HandlerOpts<A extends Record<string, unknown> = Record<string, unknown>> {
conn: Connection;
lookupDescriptor: LookupDescriptor;
args: A;
}
type TrackQueryOpts = HandlerOpts<{
/**
* The ID of the track to query for
*/
trackId: number;
}>;
/**
* Lookup track metadata from rekordbox and coerce it into a Track entity
*/
declare function getMetadata(opts: TrackQueryOpts): Promise<Track<EntityFK.WithRelations>>;
/**
* Lookup generic metadata for an unanalyzed track
*/
declare function getGenericMetadata(opts: TrackQueryOpts): Promise<Track<EntityFK.WithRelations>>;
/**
* Lookup the artwork image given the artworkId obtained from a track
*/
declare function getArtwork(opts: HandlerOpts<{
artworkId: number;
}>): Promise<Buffer<ArrayBufferLike>>;
/**
* Lookup the beatgrid for the specified trackId
*/
declare function getBeatgrid(opts: TrackQueryOpts): Promise<BeatGrid>;
/**
* Lookup the waveform preview for the specified trackId
*/
declare function getWaveformPreview(opts: TrackQueryOpts): Promise<WaveformPreview>;
/**
* Lookup the detailed waveform for the specified trackId
*/
declare function getWaveformDetailed(opts: TrackQueryOpts): Promise<WaveformDetailed>;
/**
* Lookup the HD (nexus2) waveform for the specified trackId
*/
declare function getWaveformHD(opts: TrackQueryOpts): Promise<WaveformHD>;
/**
* Lookup the [hot]cue points and [hot]loops for a track
*/
declare function getCueAndLoops(opts: TrackQueryOpts): Promise<CueAndLoop[]>;
/**
* Lookup the "advanced" (nexus2) [hot]cue points and [hot]loops for a track
*/
declare function getCueAndLoopsAdv(opts: TrackQueryOpts): Promise<CueAndLoop[]>;
/**
* Lookup the track information, currently just returns the track path
*/
declare function getTrackInfo(opts: TrackQueryOpts): Promise<string>;
type PlaylistQueryOpts = HandlerOpts<{
/**
* The ID of the playlist to query for. May be left blank to query the root
* playlist folder.
*/
id?: number;
/**
* When querying for a playlist folder this must be true.
*/
isFolderRequest: boolean;
}>;
/**
* Lookup playlist entries
*/
declare function getPlaylist(opts: PlaylistQueryOpts): Promise<{
folders: Playlist[];
playlists: Playlist[];
trackEntries: Item<ItemType.TrackTitle>[];
}>;
declare const queryHandlers: {
8194: typeof getMetadata;
8195: typeof getArtwork;
8196: typeof getWaveformPreview;
8450: typeof getTrackInfo;
8706: typeof getGenericMetadata;
8452: typeof getCueAndLoops;
8708: typeof getBeatgrid;
10500: typeof getWaveformDetailed;
11012: typeof getCueAndLoopsAdv;
11268: typeof getWaveformHD;
4357: typeof getPlaylist;
};
type Handler<T extends Query> = (typeof queryHandlers)[T];
type HandlerArgs<T extends Query> = Parameters<Handler<T>>[0]['args'];
type HandlerReturn<T extends Query> = ReturnType<Handler<T>>;
type Await<T> = T extends PromiseLike<infer U> ? U : T;
/**
* Menu target specifies where a menu should be "rendered" This differs based
* on the request being made.
*/
declare enum MenuTarget {
Main = 1
}
/**
* Used to specify where to lookup data when making queries
*/
interface QueryDescriptor {
menuTarget: MenuTarget;
trackSlot: MediaSlot;
trackType: TrackType;
}
/**
* Used internally when making queries.
*/
type LookupDescriptor = QueryDescriptor & {
targetDevice: Device;
hostDevice: Device;
};
/**
* Used to specify the query type that is being made
*/
type Query = keyof typeof queryHandlers;
declare const Query: {
readonly [x: number]: string;
readonly GetMetadata: DataRequest.GetMetadata;
readonly GetArtwork: DataRequest.GetArtwork;
readonly GetWaveformPreview: DataRequest.GetWaveformPreview;
readonly GetTrackInfo: DataRequest.GetTrackInfo;
readonly GetGenericMetadata: DataRequest.GetGenericMetadata;
readonly GetCueAndLoops: DataRequest.GetCueAndLoops;
readonly GetBeatGrid: DataRequest.GetBeatGrid;
readonly GetWaveformDetailed: DataRequest.GetWaveformDetailed;
readonly GetAdvCueAndLoops: DataRequest.GetAdvCueAndLoops;
readonly GetWaveformHD: DataRequest.GetWaveformHD;
readonly MenuRoot: MenuRequest.MenuRoot;
readonly MenuGenre: MenuRequest.MenuGenre;
readonly MenuArtist: MenuRequest.MenuArtist;
readonly MenuAlbum: MenuRequest.MenuAlbum;
readonly MenuTrack: MenuRequest.MenuTrack;
readonly MenuBPM: MenuRequest.MenuBPM;
readonly MenuRating: MenuRequest.MenuRating;
readonly MenuYear: MenuRequest.MenuYear;
readonly MenuLabel: MenuRequest.MenuLabel;
readonly MenuColor: MenuRequest.MenuColor;
readonly MenuTime: MenuRequest.MenuTime;
readonly MenuBitrate: MenuRequest.MenuBitrate;
readonly MenuHistory: MenuRequest.MenuHistory;
readonly MenuFilename: MenuRequest.MenuFilename;
readonly MenuKey: MenuRequest.MenuKey;
readonly MenuOriginalArtist: MenuRequest.MenuOriginalArtist;
readonly MenuRemixer: MenuRequest.MenuRemixer;
readonly MenuPlaylist: MenuRequest.MenuPlaylist;
readonly MenuArtistsOfGenre: MenuRequest.MenuArtistsOfGenre;
readonly MenuAlbumsOfArtist: MenuRequest.MenuAlbumsOfArtist;
readonly MenuTracksOfAlbum: MenuRequest.MenuTracksOfAlbum;
readonly MenuTracksOfRating: MenuRequest.MenuTracksOfRating;
readonly MenuYearsOfDecade: MenuRequest.MenuYearsOfDecade;
readonly MenuArtistsOfLabel: MenuRequest.MenuArtistsOfLabel;
readonly MenuTracksOfColor: MenuRequest.MenuTracksOfColor;
readonly MenuTracksOfTime: MenuRequest.MenuTracksOfTime;
readonly MenuTracksOfHistory: MenuRequest.MenuTracksOfHistory;
readonly MenuDistancesOfKey: MenuRequest.MenuDistancesOfKey;
readonly MenuAlbumsOfOriginalArtist: MenuRequest.MenuAlbumsOfOriginalArtist;
readonly MenuAlbumsOfRemixer: MenuRequest.MenuAlbumsOfRemixer;
readonly MenuAlbumsOfGenreAndArtist: MenuRequest.MenuAlbumsOfGenreAndArtist;
readonly MenuTracksOfArtistAndAlbum: MenuRequest.MenuTracksOfArtistAndAlbum;
readonly MenuTracksOfBPMPercentRange: MenuRequest.MenuTracksOfBPMPercentRange;
readonly MenuTracksOfDecadeAndYear: MenuRequest.MenuTracksOfDecadeAndYear;
readonly MenuAlbumsOfLabelAndArtist: MenuRequest.MenuAlbumsOfLabelAndArtist;
readonly MenuTracksNearKey: MenuRequest.MenuTracksNearKey;
readonly MenuTracksOfOriginalArtistAndAlbum: MenuRequest.MenuTracksOfOriginalArtistAndAlbum;
readonly MenuTracksOfRemixerAndAlbum: MenuRequest.MenuTracksOfRemixerAndAlbum;
readonly MenuTracksOfGenreArtistAndAlbum: MenuRequest.MenuTracksOfGenreArtistAndAlbum;
readonly MenuTracksOfLabelArtistAndAlbum: MenuRequest.MenuTracksOfLabelArtistAndAlbum;
readonly MenuSearch: MenuRequest.MenuSearch;
readonly MenuFolder: MenuRequest.MenuFolder;
readonly Introduce: ControlRequest.Introduce;
readonly Disconnect: ControlRequest.Disconnect;
readonly RenderMenu: ControlRequest.RenderMenu;
};
/**
* Options used to make a remotedb query
*/
interface QueryOpts<T extends Query> {
queryDescriptor: QueryDescriptor;
/**
* The query type to make
*/
query: T;
/**
* Arguments to pass to the query. These are query specific
*/
args: HandlerArgs<T>;
}
/**
* Manages a connection to a single device
*/
declare class Connection {
#private;
device: Device;
constructor(device: Device, socket: PromiseSocket<Socket$1>);
writeMessage(message: Message): Promise<void>;
readMessage<T extends Response>(expect: T): Promise<Message<T>>;
close(): void;
}
declare class QueryInterface {
#private;
constructor(conn: Connection, lock: Mutex, hostDevice: Device);
/**
* Make a query to the remote database connection.
*/
query<T extends Query>(opts: QueryOpts<T>): Promise<Await<HandlerReturn<T>>>;
}
/**
* Service that maintains remote database connections with devices on the network.
*/
declare class RemoteDatabase {
#private;
constructor(deviceManager: DeviceManager, hostDevice: Device);
/**
* Open a connection to the specified device for querying
*/
connectToDevice: (device: Device) => Promise<void>;
/**
* Disconnect from the specified device
*/
disconnectFromDevice: (device: Device) => Promise<void>;
/**
* Gets the remote database query interface for the given device.
*
* If we have not already established a connection with the specified device,
* we will attempt to first connect.
*
* @returns null if the device does not export a remote database service
*/
get(deviceId: DeviceID): Promise<QueryInterface | null>;
}
interface Options$2 {
/**
* The device to query the track artwork off of
*/
deviceId: DeviceID;
/**
* The media slot the track is present in
*/
trackSlot: MediaSlot;
/**
* The type of track we are querying artwork for
*/
trackType: TrackType;
/**
* The track to lookup artwork for
*/
track: Track;
}
interface Options$1 {
/**
* The device to query the track metadata from
*/
deviceId: DeviceID;
/**
* The media slot the track is present in
*/
trackSlot: MediaSlot;
/**
* The type of track we are querying for
*/
trackType: TrackType;
/**
* The track id to retrieve metadata for
*/
trackId: number;
}
interface Options {
/**
* The playlist or folder to query the entries of. This may be left as
* undefined to retrieve the root playlist.
*/
playlist?: Playlist;
/**
* The device to query the track metadata from
*/
deviceId: DeviceID;
/**
* The media slot the track is present in
*/
mediaSlot: MediaSlot;
}
/**
* A Database is the central service used to query devices on the prolink
* network for information from their databases.
*/
declare class Database {
#private;
constructor(hostDevice: Device, local: LocalDatabase, remote: RemoteDatabase, deviceManager: DeviceManager);
/**
* Reports weather or not the CDJs can be communicated to over the remote
* database protocol. This is important when trying to query for unanalyzed or
* compact disc tracks.
*/
get cdjSupportsRemotedb(): boolean;
/**
* Retrieve metadata for a track on a specific device slot.
*/
getMetadata(opts: Options$1): Promise<Track | null>;
/**
* Retrieves the artwork for a track on a specific device slot.
*/
getArtwork(opts: Options$2): Promise<Buffer<ArrayBufferLike> | null>;
/**
* Retrieves the waveforms for a track on a specific device slot.
*/
getWaveforms(opts: Options$2): Promise<Waveforms | null>;
/**
* Retrieve folders, playlists, and tracks within the playlist tree. The id
* may be left undefined to query the root of the playlist tree.
*
* NOTE: You will never receive a track list and playlists or folders at the
* same time. But the API is simpler to combine the lookup for these.
*/
getPlaylist(opts: Options): Promise<PlaylistContents | null>;
}
interface NetworkConfig {
/**
* The network interface to listen for devices on the network over
*/
iface: NetworkInterfaceInfoIPv4;
/**
* The ID of the virtual CDJ to pose as.
*
* IMPORTANT:
*
* You will likely want to configure this to be > 6, however it is important to
* note, if you choose an ID within the 1-6 range, no other CDJ may exist on the
* network using that ID. you CAN NOT have 6 CDJs if you're using one of their slots.
*
* However, should you want to make metadata queries to a unanalized media
* device connected to the CDJ, or metadata queries for CD disc data, you MUST
* use a ID within the 1-6 range, as the CDJs will not respond to metadata
* requests outside of the range of 1-6
*
* Note that rekordbox analyzed media connected to the CDJ is accessed out of
* band of the networks remote database protocol, and is not limited by this
* restriction.
*/
vcdjId: number;
}
interface ConstructOpts {
config?: NetworkConfig;
announceSocket: Socket;
beatSocket: Socket;
statusSocket: Socket;
deviceManager: DeviceManager;
statusEmitter: StatusEmitter;
}
/**
* Services that are not accessible until connected
*/
type ConnectedServices = 'statusEmitter' | 'control' | 'db' | 'localdb' | 'remotedb' | 'mixstatus';
type ConnectedProlinkNetwork = ProlinkNetwork & {
[P in ConnectedServices]: NonNullable<ProlinkNetwork[P]>;
} & {
state: NetworkState.Connected;
isConfigured: true;
};
/**
* Brings the Prolink network online.
*
* This is the primary entrypoint for connecting to the prolink network.
*/
declare function bringOnline(config?: NetworkConfig): Promise<ProlinkNetwork>;
declare class ProlinkNetwork {
#private;
/**
* @internal
*/
constructor({ config, announceSocket, beatSocket, statusSocket, deviceManager, statusEmitter, }: ConstructOpts);
/**
* Configure / reconfigure the network with an explicit configuration.
*
* You may need to disconnect and re-connect the network after making a
* networking configuration change.
*/
configure(config: NetworkConfig): void;
/**
* Wait for another device to show up on the network to determine which network
* interface to listen on.
*
* Defaults the Virtual CDJ ID to 7.
*/
autoconfigFromPeers(): Promise<void>;
/**
* Connect to the network.
*
* The network must first have been configured (either with autoconfigFromPeers
* or manual configuration). This will then initialize all the network services.
*/
connect(): void;
/**
* Disconnect from the network
*/
disconnect(): Promise<[unknown, unknown, unknown]>;
/**
* Get the current NetworkState of the network.
*
* When the network is Online you may use the deviceManager to list and react to
* devices on the nettwork
*
* Once the network is Connected you may use the statusEmitter to listen for
* player status events, query the media databases of devices using the db
* service (or speci