Java library for synchronizing and communicating with Pioneer DJ Link equipment
Clone or download
brunchboy Really do what I meant to in the last commit.
Derp... Implementing MediaDetailsListener as part of the public
interface of the class does not, obviously, prevent random code from
calling it! I plead having worked too hard with too little rest this
weekend. This commit gets to where I had been originally intending,
letting only the VirtualCDJ report media responses.
Latest commit aa9cfad Sep 17, 2018


A Java library for synchronizing with beats from Pioneer DJ Link equipment, and finding out details about the tracks that are playing.

See beat-link-trigger and beat-carabiner for examples of what you can do with this.


🚧 beat-link is being worked on very heavily at the moment to incorporate some amazing new discoveries. Until that settles down, there may be rapid releases and big changes between releases.

⚠️ Because beat-link is grew so much for release 0.3.0, it was time to split some classes apart, and even split it into multiple packages, so I also took the opportunity to make some basic changes to the API to position it for future growth and better reliability. This means version 0.3.0 is not API compatible with prior releases, and any code which compiled against an old release will require some rewriting.

:octocat: Since, as far as I know, I am still the only consumer of this API, this seemed like a good time to make these breaking changes.


Beat-link is available through Maven Central, so to use it in your Maven project, all you need is to include the appropriate dependency.

Maven Central

Click the maven central badge above to view the repository entry for beat-link. The proper format for including the latest release as a dependency in a variety of tools, including Leiningen if you are using beat-link from Clojure, can be found in the Dependency Information section.

Beat link uses slf4j to allow you to integrate it with whatever Java logging framework your project is using, so you will need to include the appropriate slf4j binding on your class path.

If you want to manually install beat-link, you can download the library from the releases page and put it on your project’s class path, along with the slf4j-api.jar and the slf4j binding to the logging framework you would like to use.

You will also need ConcurrentLinkedHashmap for maintaining album art caches, so Maven is your easiest bet.


See the API Documentation for full details, but here is a nutshell guide:

Finding Devices

The DeviceFinder class allows you to find DJ Link devices on your network. To activate it:

import org.deepsymmetry.beatlink.DeviceFinder;

// ...


After a second, it should have heard from all the devices, and you can obtain the list of them by calling:


This returns a list of DeviceAnnouncement objects describing the devices that were heard from. To find out immediately when a new device is noticed, or when an existing device disappears, you can use DeviceFinder.getInstance().addDeviceAnnouncementListener().

Responding to Beats

The BeatFinder class can notify you whenever any DJ Link devices on your network report the start of a new beat:

import org.deepsymmetry.beatlink.BeatFinder;

// ...

  BeatFinder.getInstance().addBeatListener(new BeatListener() {
      public void newBeat(Beat beat) {
         // Your code here...


The Beat object you receive will contain useful information about the state of the device (such as tempo) at the time of the beat.

To fully understand how to respond to the beats, you will want to start VirtualCdj as described in the next section, so it can tell you important details about the states of all the players, such as which one is the current tempo master. Once that is running, you can pass the Beat object to VirtualCdj.getLatestStatusFor(beat) and get back the most recent status update received from the device reporting the beat.

With just the Beat object you can call getBpm() to learn the track BPM when the beat occurred, getPitch() to learn the current pitch (speed) of the player at that moment, and getEffectiveTempo() to find the combined effect of pitch and track BPM—the beats per minute currently being played. You can also call getBeatWithinBar() to see where this beat falls within a measure of music.

If the VirtualCdj is active, you can also call the Beat object’s isTempoMaster() method to find out if it was sent by the device that is currently in control of the master tempo. (If VirtualCdj was not started, this method will throw an IllegalStateException.)

Getting Device Details

To find some kinds of information, like which device is the tempo master, how many beats of a track have been played, or how many beats there are until the next cue point in a track, and any detailed information about the tracks themselves, you need to have beat-link create a virtual player on the network. This causes the other players to send detailed status updates directly to beat-link, so it can interpret and keep track of this information for you.

import org.deepsymmetry.beatlink.VirtualCdj;

// ...


The Virtual player is normally created using device number 5 and the name beat-link, and announces its presence every second and a half. These values can be changed with setDeviceNumber(), setDeviceName(), and setAnnounceInterval().

As soon as it is running, you can pass any of the device announcements returned by DeviceFinder.getCurrentDevices() to VirtualCdj.getLatestStatusFor(device) and get back the most recent status update received from that device. The return value will either be a MixerStatus or a CdjStatus, containing a great deal of useful information.

As described above, you can do the same thing with the Beat objects returned by BeatFinder.

In addition to asking about individual devices, you can find out which device is the current tempo master by calling VirtualCdj.getTempoMaster(), and learn the current master tempo by calling VirtualCdj.getMasterTempo().

If you want to be notified when either of these values change, or whenever the player that is the current tempo master starts a new beat, you can use VirtualCdj.addMasterListener().

If you are building an interface that wants to display as much detail as possible, you can request every device status update as soon as it is received, using VirtualCdj.addUpdateListener().

Getting Track Metadata

If you want to be able to retrieve details about loaded tracks, such as the title, artist, genre, length (in seconds), key, and even artwork images, start the MetadataFinder, which will also start the VirtualCdj if it is not already running.


// ...


The safest way to successfully retrieve metadata is to configure the VirtualCdj to use a device number in the range 1 to 4, like an actual CDJ, using VirtualCdj.getInstance().setDeviceNumber() as described above. You can only do that if you are using fewer than 4 CDJs, because you need to use a number that is not being used by any actual CDJ. If you are using 4 actual CDJs, you will need to leave the VirtualCdj using its default number of 5, but that means the MetadataFinder will need to "borrow" one of the actual CDJ device numbers when it is requesting metadata. If three of the CDJs have loaded tracks from a media slot on the fourth, then there will be no device numbers available for use, and the metadata request will not even be attempted. Even if they have not, there is no way for beat-link to know if the DJ is using Link Info in a way that causes the metadata request to fail, or worse, causes one of the CDJs to get confused and stop working quite right. So if you want to work with metadata, to be safe, reserve a physical player number from 1 to 4 for the exclusive use of beat-link, or have all the CDJs load tracks from rekordbox, rather than from each other.

Alternately, you can tell the MetadataFinder to create a cache file of the metadata from a media slot when you have a convenient moment, and then have it use that cache file during a busy show when the players would have a hard time responding to queries.

Once the MetadataFinder is running, you can access all the metadata for currently-loaded tracks by calling MetadataFinder.getInstance().getLoadedTracks(), which returns a Map from deck references (player numbers and hot cue numbers) to TrackMetadata objects describing the track currently loaded in that player slot. You can also call MetadataFinder.getLatestMetadataFor(int player) to find the metadata for the track loaded in the playback deck of the specified player. See the TrackMetadata API documentation for all the details it provides.

With the MetadataFinder running, you can also start the ArtFinder, and use its can call its getLoadedArt() or getLatestArtFor() methods to get the artwork images associated with the tracks, if there are any.

Getting Other Track Information

As of version 0.3.0, much more of the database protocol has been understood and implemented, so you can use objects in the namespace to get beat grids and track waveform data, and even to create Swing UI components to display the preview and detailed waveform, reflecting the current playback position.

To illustrate the kind of interface that you can now put together from the elements offered by Beat Link, here is the Player Status window from Beat Link Trigger:

Player Status window

An Example

Here is the source for, a small class that demonstrates how to watch for changes related to the tempo master (and also shows that printing DeviceUpdate objects can be a useful diagnostic aid):

import java.util.Date;
import org.deepsymmetry.beatlink.*;

public class Example {

    public static void main(String[] args) {
        try {
        } catch ( e) {
            System.err.println("Unable to start VirtualCdj: " + e);

        VirtualCdj.getInstance().addMasterListener(new MasterListener() {
                        public void masterChanged(DeviceUpdate update) {
                            System.out.println("Master changed at " + new Date() + ": " + update);

                        public void tempoChanged(double tempo) {
                            System.out.println("Tempo changed at " + new Date() + ": " + tempo);

                        public void newBeat(Beat beat) {
                            System.out.println("Master player beat at " + new Date() + ": " + beat);

        try {
        } catch (InterruptedException e) {
            System.out.println("Interrupted, exiting.");

Compiling and running this class (with the beat-link and slf4j jars on the class path) watches and reports on tempo master activity for one minute, producing output like this:

> java -cp .:beat-link.jar:slf4j-api-1.7.21.jar:slf4j-simple-1.7.21.jar Example
Master changed at Sun May 08 20:49:23 CDT 2016: CDJ status: Device 2,
 name: DJ-2000nexus, busy? true, pitch: +0.00%, track: 5, track BPM:
 128.0, effective BPM: 128.0, beat: 55, beat within bar: 3,
 cue: --.-, Playing? true, Master? true, Synced? true, On-Air? true
Tempo changed at Sun May 08 20:49:23 CDT 2016: 128.0
Tempo changed at Sun May 08 20:49:47 CDT 2016: 127.9359130859375
    [... lines omitted ...]
Tempo changed at Sun May 08 20:49:51 CDT 2016: 124.991943359375
Tempo changed at Sun May 08 20:49:51 CDT 2016: 124.927978515625
Master changed at Sun May 08 20:49:55 CDT 2016: CDJ status: Device 3,
 name: DJ-2000nexus, busy? true, pitch: -0.85%, track: 4, track BPM:
 126.0, effective BPM: 124.9, beat: 25, beat within bar: 1,
 cue: 31.4, Playing? true, Master? true, Synced? true, On-Air? true


This project is being developed with the help of dysentery. Check that out for details of the packets and protocol, and for ways you can help figure out more.


Deep Symmetry

Copyright © 2016–2018 Deep Symmetry, LLC

Distributed under the Eclipse Public License 1.0. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.