UNPKG

@andyfarthing/prolink-connect

Version:

Fork of prolink-connect with modern build system and fixes

1,716 lines (1,693 loc) 63.6 kB
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