prolink-cpp
January 4, 2026 · View on GitHub
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.
Features
Core Functionality
- 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
Supported Operations
- 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
Quick Start
Build
mkdir build && cd build
cmake ..
cmake --build .
Run Examples
Listen for beats and status:
./prolink_listener
Create a virtual CDJ:
./prolink_virtual_cdj 192.168.1.100 192.168.1.255 aa:bb:cc:dd:ee:ff 7 MyController 128
Interactive 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 128
Discover devices and test sync control:
./prolink_control_demo
Usage
Listen for Beats
#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;
}
Create a Virtual CDJ
#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;
}
Follow the Tempo Master
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 phase
API Overview
Session Management
prolink::Session session(config);
bool success = session.Start(); // Returns false on error
session.Stop();
Callbacks
session.SetBeatCallback([](const prolink::BeatInfo& beat) { /* ... */ });
session.SetStatusCallback([](const prolink::StatusInfo& status) { /* ... */ });
session.SetDeviceCallback([](const prolink::DeviceInfo& device) { /* ... */ });
session.SetDeviceEventCallback([](const prolink::DeviceEvent& event) { /* ... */ });
Control
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 handoff
Query
auto 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 counters
Configuration
prolink::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 signature
Important: Use your subnet's broadcast address (e.g., 192.168.1.255), not 255.255.255.255, for reliable operation.
Testing
Build with Tests
cmake -DPROLINK_BUILD_TESTS=ON -DPROLINK_FETCH_GTEST=ON ..
cmake --build .
Run Tests
ctest --output-on-failure
# or
./prolink_tests
Implementation Status
This port is a work in progress and does not implement all aspects of the prolink protocol yet.
Implemented
- 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
Known Issues & Limitations
Platform Support
- Currently: Linux and macOS only (POSIX sockets)
Protocol Coverage
- Not implemented: Database queries, waveforms, artwork, NFS access
External Resources
- dysentery Protocol Analysis
- Pro DJ Link Research - Protocol PDF
Contributing
Contributions are welcome! This project is in active development.
How to Contribute
- 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!
Development Setup
# 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-failure
Production Use
This library has been tested with:
- CDJ-3000
Community & Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Credits & Acknowledgments
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
Related Projects
- 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
License
MIT License - see LICENSE file for details.
Third-Party Licenses
- GoogleTest - BSD 3-Clause License (testing only)
Notice
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.