A modern (C++17 as my application limits me to 17 or older) library for interacting with Pioneer DJ equipment using the Pro DJ Link protocol. Monitor beats, sync to tempo masters, and control CDJs.
- Receive beat packets - Get beat/bar timing from CDJs and mixers
- Receive status packets - Monitor playback state, BPM, pitch, sync status
- Virtual CDJ mode - Act as a virtual player on the network
- Device discovery - Automatically detect all Pro DJ Link devices
- Tempo master tracking - Identify and follow the current tempo master
- Master handoff - Request and negotiate tempo master role
- Sync control - Send sync enable/disable commands to players
- Beat-synchronized callbacks
- Follow-master mode for automatic tempo alignment
- Custom virtual CDJ with configurable device ID, name, and MAC
- Multi-device tracking with lifecycle events (seen/updated/expired)
- Optional packet capture/replay for offline debugging
- Session metrics counters for monitoring
mkdir build && cd build
cmake ..
cmake --build .Listen for beats and status:
./prolink_listenerCreate a virtual CDJ:
./prolink_virtual_cdj 192.168.1.100 192.168.1.255 aa:bb:cc:dd:ee:ff 7 MyController 128Interactive virtual CDJ with full control:
./prolink_virtual_cdj_interactive 192.168.1.100 192.168.1.255 aa:bb:cc:dd:ee:ff 7 MyController 128Discover devices and test sync control:
./prolink_control_demo#include "prolink/prolink.h"
#include <iostream>
int main() {
prolink::Config config;
config.send_beats = false;
config.send_status = false;
config.send_announces = false;
prolink::Session session(config);
// Get notified on every beat
session.SetBeatCallback([](const prolink::BeatInfo& beat) {
std::cout << "Beat from " << beat.device_name
<< " @ " << (beat.bpm / 100.0) << " BPM"
<< " [" << +beat.beat_within_bar << "/4]"
<< std::endl;
});
// Monitor playback status changes
session.SetStatusCallback([](const prolink::StatusInfo& status) {
if (status.is_master) {
std::cout << status.device_name << " is tempo master" << std::endl;
}
});
session.Start();
std::cin.get(); // Wait for Enter
session.Stop();
return 0;
}#include "prolink/prolink.h"
int main() {
prolink::Config config;
config.device_name = "MyVisualizer";
config.device_number = 0x07; // Device ID
config.device_ip = "192.168.1.100";
config.broadcast_address = "192.168.1.255";
config.mac_address = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
config.tempo_bpm = 128.0;
config.playing = true;
config.master = true;
config.send_beats = true;
config.send_status = true;
config.send_announces = true;
prolink::Session session(config);
session.Start();
// Other CDJs will now see "MyVisualizer" on the network
// and receive beat packets at 128 BPM
std::cin.get();
session.Stop();
return 0;
}prolink::Config config;
config.device_ip = "192.168.1.100";
config.broadcast_address = "192.168.1.255";
config.mac_address = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
config.follow_master = true; // Automatically sync to current master
config.synced = true;
prolink::Session session(config);
session.Start();
// Your session will now automatically align its internal beat clock
// to match the current tempo master's BPM and beat phaseprolink::Session session(config);
bool success = session.Start(); // Returns false on error
session.Stop();session.SetBeatCallback([](const prolink::BeatInfo& beat) { /* ... */ });
session.SetStatusCallback([](const prolink::StatusInfo& status) { /* ... */ });
session.SetDeviceCallback([](const prolink::DeviceInfo& device) { /* ... */ });
session.SetDeviceEventCallback([](const prolink::DeviceEvent& event) { /* ... */ });session.SetTempo(128.5); // Set local tempo
session.SetPlaying(true); // Start/stop playback
session.SetMaster(true); // Become tempo master
session.SendSyncControl(target_device, prolink::SyncCommand::kEnableSync);
session.RequestMasterRole(); // Request master handoffauto devices = session.GetDevices(); // All discovered devices
auto master = session.GetTempoMaster(); // Current tempo master (optional)
std::string error = session.GetLastError(); // Last Start() error message
auto metrics = session.GetMetrics(); // Packet/error countersprolink::Config config;
// Identity
config.device_name = "prolink-cpp"; // Device name (max 20 chars)
config.device_number = 0x07; // Device ID (1-4 for players, 0x07+ for virtual)
config.device_type = 0x01; // 0x01=CDJ, 0x03=Mixer, 0x04=Rekordbox
// Network
config.bind_address = "0.0.0.0"; // Local bind address
config.broadcast_address = "192.168.1.255"; // Subnet broadcast (NOT 255.255.255.255!)
config.device_ip = "192.168.1.100"; // Our IP (required for announces)
config.mac_address = {0xaa, ...}; // MAC address (required for announces)
// Behavior
config.tempo_bpm = 120.0; // Initial tempo
config.playing = false; // Playback state
config.master = false; // Tempo master state
config.synced = false; // Sync state
config.follow_master = false; // Auto-align to tempo master
// Transmission
config.send_beats = true; // Send beat packets
config.send_status = true; // Send status packets
config.send_announces = true; // Send keep-alive packets
// Diagnostics
config.log_callback = [](const std::string& message) {
// Custom logger (optional)
};
config.capture_file = ""; // Capture incoming packets
config.replay_file = ""; // Replay packets from capture
// Timing
config.status_interval_ms = 200; // Status packet interval
config.announce_interval_ms = 1500; // Keep-alive interval
config.beats_per_bar = 4; // Time signatureImportant: Use your subnet's broadcast address (e.g., 192.168.1.255), not 255.255.255.255, for reliable operation.
cmake -DPROLINK_BUILD_TESTS=ON -DPROLINK_FETCH_GTEST=ON ..
cmake --build .ctest --output-on-failure
# or
./prolink_testsThis port is a work in progress and does not implement all aspects of the prolink protocol yet.
- Core beat/status packet parsing (CDJ-2000, XDJ, CDJ-3000)
- UDP send/receive on ports 50000-50002
- Virtual CDJ announcement and participation
- Beat clock with tempo/pitch tracking
- Follow-master mode (automatic tempo sync)
- Device discovery and lifecycle management
- Sync control packets (enable/disable sync)
- Master handoff protocol (request/response)
- Master role negotiation (M_h field handling)
- Thread-safe API with exception-safe callbacks
- Config validation with error reporting
- Currently: Linux and macOS only (POSIX sockets)
- Not implemented: Database queries, waveforms, artwork, NFS access
- dysentery Protocol Analysis
- Pro DJ Link Research - Protocol PDF
Contributions are welcome! This project is in active development.
- Report bugs - Open an issue with reproduction steps
- Suggest features - Describe your use case
- Submit PRs - Add tests, follow existing style
- Capture packets - Real hardware data for testing is valuable!
# Clone repository
git clone https://github.com/grantHarris/prolink-cpp.git
cd prolink-cpp
# Build with tests
mkdir build && cd build
cmake -DPROLINK_BUILD_TESTS=ON -DPROLINK_FETCH_GTEST=ON ..
cmake --build .
# Run tests
ctest --output-on-failureThis library has been tested with:
- CDJ-3000
- Issues: GitHub Issues
- Discussions: GitHub Discussions
This library would not be possible without the reverse-engineering work done by:
dysentery by Deep Symmetry
James Elliott (@brunchboy) and the Deep Symmetry team deserve credit for their meticulous documentation.
beat-link by Deep Symmetry
prolink-connect by Evan Purkhiser
- beat-link-trigger - Application for triggering events based on CDJ activity
- crate-digger - Rekordbox database analysis
- open-beat-control - Python library for Pro DJ Link
MIT License - see LICENSE file for details.
- GoogleTest - BSD 3-Clause License (testing only)
rekordbox™ and PRO DJ LINK™ are trademarks of AlphaTheta Corporation. This project or it's maintainers are not affiliated with, supported, or endorsed by AlphaTheta Corporation in any way shape or form.