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-java for examples of what you can do with this.
Beat-link is available through Maven Central, so to use it in your Maven project, all you need is to include the appropriate dependency.
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.
It also uses the Deep Symmetry projects electro (to synchronize with the Ableton Link timeline) and crate-digger (to download and parse rekordbox database files from players).
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
electro,
crate-digger,
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, and Remote Tea, so Maven is by far your easiest bet, because it will take care of all these libraries for you.
This is in no way a sanctioned implementation of the protocols. It should be clear, but:
Warning
Use at your own risk! For example, there are reports that the XDJ-RX (and XDJ-RX2) crash when BLT starts, so don’t use it with one on your network. As Pioneer themselves explain, the XDJ-RX does not actually implement the protocol:
“The LINK on the RX (and RX2) is ONLY for linking to rekordbox on your computer or a router with WiFi to connect rekordbox mobile. It can not exchange LINK data with other CDJs or DJMs.”
While these techniques appear to work for us so far, there are many gaps in our knowledge, and things could change at any time with new releases of hardware or even firmware updates from Pioneer.
Caution
You should also not expect to be able to run Beat Link, or any project like it, on the same machine that you are running rekordbox, because they will compete over access to network ports. Also, as noted above, the XDJ-RX and XDJ-RX2 do not support the full DJ Link protocol, and so will not work with software like this. And, of course, controllers which don't even have Link (Ethernet) jacks can't work either.
Tip
✅ Beat Link seems to work great with CDJ-2000 Nexus and nxs2 gear, and release 7.3.0 supports CDJ-3000 features. It works fairly well (with less information available) with older CDJ-2000s. It has also been reported to work with XDJ-1000 gear, and (starting with version 0.6.0) with the XDJ-XZ as well. If you can try it with anything else, please let us know what you learn in Beat Link Trigger's Zulip chat stream, or if you have worked out actionable details about something that could be improved, open an Issue or submit a pull request so we can all improve our understanding together.
🚧 We finally obtained the hardware, so can now truly support new features like those introduced with the CDJ-3000 and DJM-V10, including the fact that there can now be up to six devices online and on mixer and the new higher-resolution position packets.
If something isn’t working with your hardware, and you don’t yet know the details why, but are willing to learn a little and help figure it out, look at the dysentery project, which is where we are organizing the research tools and results which made programs like Beat Link possible.
Deep Symmetry’s projects are generously sponsored with hosting by Zulip, an open-source modern team chat app designed to keep both live and asynchronous conversations organized. Thanks to them, you can chat with our community, ask questions, get inspiration, and share your own ideas.
See the API Documentation for full details, but here is a nutshell guide:
The DeviceFinder
class allows you to find DJ Link devices on your network. To activate it:
import org.deepsymmetry.beatlink.DeviceFinder;
// ...
DeviceFinder.getInstance().start();
After a second, it should have heard from all the devices, and you can obtain the list of them by calling:
DeviceFinder.getInstance.getCurrentDevices();
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()
.
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() {
@Override
public void newBeat(Beat beat) {
// Your code here...
}
});
BeatFinder.getInstance().start();
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 theBeat
object toVirtualCdj.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
.)
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;
// ...
VirtualCdj.getInstance().start();
The Virtual player is normally created using an unused device number in the range 5–15 and the name
beat-link
, and announces its presence every second and a half. These values can be changed withsetDeviceNumber()
,setUseStandardPlayerNumber()
,setDeviceName()
, andsetAnnounceInterval()
.
As soon as it is running, you can pass any of the device announcements returned by
DeviceFinder.getCurrentDevices()
to 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 byBeatFinder
.
In addition to asking about individual devices, you can find out which
device is the current tempo master by calling
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
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
addUpdateListener()
.
In addition to learning about what is happening on other players, the
VirtualCdj
can ask them to do a limited number of things. You can
tell them to load a track from any media currently mounted in a player
by calling
sendLoadTrackCommand()
.
You can cause them to start playing (if they are currently at the cue
position), or stop playing and return to the cue position, by calling
sendFaderStartCommand()
.
You can tell players to turn Sync mode on or off by calling
sendSyncModeCommand()
,
and if there is no DJM on the network you can tell them that their
channels are on or off the air by sending
sendOnAirCommand()
.
(You can do that even with a DJM present, but your command will
quickly be overridden by the next one sent by the DJM.)
You can ask the VirtualCdj
to find out details about the media
mounted in a player's media slot by calling
sendMediaQuery()
.
In order to receive the response, you have to previously have
registered a
MediaDetailsListener
instance using
addMediaDetailsListener()
;
in general it's easier to just let the MetadataFinder
take care of
this for you as described below.
As of version 0.4.0, the VirtualCdj
can be configured to send status
packets of its own, so that it can take over the role of tempo master,
and even send beat packets to synchronize other CDJs with a musical
timeline of your choice. For this to work, it must be using a standard
player number in the range 1–4, configured before you start it as
described above.
The only way to be able to use the features that rely on sending status is to configure the
VirtualCdj
to use a device number in the range 1 to 4, like an actual CDJ, usingVirtualCdj.getInstance().setUseStandardPlayerNumber()
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.
As long as that is true, you can turn on this feature by calling
setSendingStatus()
.
Once it is sending status, you can adjust details about what it sends
by calling
setTempo()
,
setSynced()
,
setOnAir()
,
and
jumpToBeat()
.
To simulate playing a track, call
setPlaying()
.
During simulated playback, beat packets will sent at appropriate times
for the current tempo, and the beat number will advance appropriately.
You can find the current simulated playback time by calling
getPlaybackPosition()
.
As long as the VirtualCdj
is sending status packets, you can tell
players to become Tempo Master by calling
appointTempoMaster()
,
or have it become the Master itself by calling
becomeTempoMaster()
.
While acting as tempo master, any tempo changes you make by calling
setTempo()
will be followed by any players that are in Sync mode, and if the
VirtualCdj
is playing (and thus sending beats) you can call
adjustPlaybackPosition()
to keep the players' beat grids aligned with an external sync source
(this is how Beat Link Trigger aligns CDJs with Ableton Link).
In addition to responding to the above functions for setting its own Sync, On-Air, and Master states, the VirtualCdj respects commands sent to it by other players or a DJM instructing it to do these things. While it is in Sync mode, it will stay aligned with the tempo master like other players do.
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.
import org.deepsymmetry.beatlink.data.MetadataFinder;
// ...
MetadataFinder.getInstance().start();
⚠️ The only way to reliably retrieve metadata using theMetadataFinder
on its own is to configure theVirtualCdj
to use a device number in the range 1 to 4, like an actual CDJ, usingVirtualCdj.getInstance().setUseStandardPlayerNumber()
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 theVirtualCdj
using its default number of 5, but that means theMetadataFinder
will often be unable to talk to thedbserver
ports on the players.As of version 0.5.0, we finally have a good solution to this problem, which is to start up the
CrateDigger
class, which uses a completely separate mechanism for obtaining metadata, by downloading the entire rekordbox database export and track analysis files from the NFSv2 server that is also running in the players. The NFSv2 server is stateless, does not care what player number we are using, and can be used no matter how many players are on the network. So, in addition to the above code, also do this:
import org.deepsymmetry.beatlink.data.CrateDigger;
// ...
CrateDigger.getInstance().start();
Once the MetadataFinder
and CrateDigger
are 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.
You can use objects in the
org.deepsymmetry.beatlink.data
package 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:
While the MetadataFinder
is running, it also keeps track of which
players have media mounted in their slots. You can access that
information by calling
getMountedMediaSlots()
.
If you want to know immediately when slots mount and unmount, you can
register a
MountListener
using
addMountListener()
.
You can also find
useful information
about any mounted media database by calling
getMountedMediaDetails()
or
getMediaDetailsFor()
.
You can explore the hierarchy of menus served by a player for one of
its mounted media slots using the
MenuLoader
,
starting with
requestRootMenuFrom()
to determine what menus are actually available.
⚠️ The only way to get menus is by talking to thedbserver
on the players, so even if you are using theCrateDigger
as described above, you will only be able to do this reliably if you are using a real player number, which means you can have no more than three actual players on the network.Also note that in order to reliably send the correct message to obtain the root menu for a particular slot, the
MenuLoader
needs to know the type of database that is mounted in that slot, so theMetadataFinder
should be running and tracking media details for it to check.
If you want to know when a particular track has been loaded on a player,
independent of the specific rekordbox export media it was loaded from,
you can start the
SignatureFinder
,
and it will do most of the work for you, with the help of the
MetadataFinder
, WaveformFinder
, and BeatGridFinder
. Whenever a
rekordbox track is loaded and metadata for it has been obtained, it will
compute an SHA-1 hash of the track title, artist, duration, detailed
waveform and beat grid. The results boil down to a 40-character string
of hexadecimal digits which can be used to uniquely identify and
recognize that track (for example, using it as a key in a hash map to
find cues that should run when that track is playing).
You can access the signatures of all loaded rekordbox tracks by calling
getSignatures()
,
or look up the signature for a track on a specific player with
getLatestSignatureFor()
.
If you want to know immediately when signatures are available for loaded
tracks, you can register a SignatureListener
using addSignatureListener()
.
Here is the source for Example.java
, 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 {
VirtualCdj.getInstance().start();
} catch (java.net.SocketException e) {
System.err.println("Unable to start VirtualCdj: " + e);
}
VirtualCdj.getInstance().addMasterListener(new MasterListener() {
@Override
public void masterChanged(DeviceUpdate update) {
System.out.println("Master changed at " + new Date() + ": " + update);
}
@Override
public void tempoChanged(double tempo) {
System.out.println("Tempo changed at " + new Date() + ": " + tempo);
}
@Override
public void newBeat(Beat beat) {
System.out.println("Master player beat at " + new Date() + ": " + beat);
}
});
try {
Thread.sleep(60000);
} 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. You can also view Snapshot API Documentation to see what is available in the latest preview release.
Beat Link is, and will remain, completely free and open-source. If it has helped you, taught you something, or inspired you, please let us know and share some of your discoveries and how you are using it! If you'd like to financially support its ongoing development, you are welcome (but by no means obligated) to donate to offset the hundreds of hours of research, development, and writing that have already been invested. Or perhaps to facilitate future efforts, tools, toys, and time to explore.
using Liberapay, or
using PayPal
If enough people jump on board, we may even be able to get a newer CDJ to experiment with, although that's an unlikely stretch goal. :grinning:
If you have ideas, discoveries, or even code you’d like to share, that’s fantastic! Please take a look at the guidelines and get in touch!
Copyright © 2016–2023 Deep Symmetry, LLC
Distributed under the Eclipse Public License 2.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. A copy of the license can be found in LICENSE.md within this project.
Remote Tea, used for communicating with the NFSv2 servers on players, is licensed under the GNU Library General Public License, version 2.
The Kaitai Struct Java runtime, used for parsing rekordbox exports and media analysis files, is licensed under the MIT License.