Python control for the Pioneer DDJ-FLX4, with a MIDI map that states where every value came from.
pip install pyflx4On Linux with a recent Python, python-rtmidi may have no prebuilt wheel and will
build from source, which needs the ALSA headers:
sudo apt-get install libasound2-devmacOS and Windows install from wheels with nothing extra.
from pyflx4 import DDJFLX4, DDJFLX4Tools
dj = DDJFLX4Tools(DDJFLX4())
dj.play(deck=1)
dj.set_eq(deck=1, low=-1.0) # kill the bass on deck 1
dj.set_crossfader(0.5)
dj.trigger_hot_cue(deck=2, pad=1)The FLX4's four deck knobs sit at CC 0x04, 0x07, 0x0B, 0x0F — adjacent
values in one run. A map shifted by a single position still looks entirely
plausible and still works, in the sense that turning things produces MIDI. It
just moves the wrong control: trim adjusts the highs, "EQ low" sweeps the filter.
Nothing errors. You find out by ear, mid-set.
Every value in pyflx4.mapping was cross-checked against the
Pioneer-DDJ-FLX4 mapping shipped by Mixxx, the most widely exercised
FLX4 map in existence, and the ones that matter are asserted in the test suite
rather than merely documented.
Where a value could not be cross-checked, the library says so:
>>> from pyflx4 import provenance, unverified
>>> provenance("CC", "EQ_LOW")
<Source.MIXXX: 'cross-checked against the Mixxx DDJ-FLX4 mapping'>
>>> unverified()
('CC.HEADPHONES_VOL', 'CC.MASTER_VOLUME')Two values are unverified, and they are named. That is the whole list.
- Deck 2's pads are on channel 9, not 8. Deck 1 is 7. The obvious guess is wrong.
- Pads send a different note range per mode, not a mode flag: hot cue
0x00, pad FX0x10, beat jump0x20, sampler0x30. Listening only at0x00means your hot cues stop working the moment the user switches pad mode. - The controller never sends note-off. Release is note-on with velocity 0 —
there is not one
0x8nmessage in its vocabulary. Decoders waiting for note-off latch a held button forever. - Every analog control is a 14-bit pair, LSB exactly
0x20above MSB. Reading the MSB alone gives you a control that works but jumps in 128 steps. - Shift is not a modifier. The firmware sends a different note when shift is held, so there is no shift state to track.
- The tempo fader is on the deck channel, not the mixer channel where the rest of the faders live.
DDJFLX4 is raw MIDI: connect, send notes and CCs, receive input, drive LEDs.
DDJFLX4Tools sits on top and speaks in DJ terms — play, set_eq, set_loop,
trigger_hot_cue — returning a plain dict from each call, so it drops into a CLI,
an HTTP handler, or a tool-calling agent without the library caring which.
Both MIDI ports are injectable, so the whole control surface can be driven with no hardware attached:
controller = DDJFLX4(midi_out=fake_out, midi_in=fake_in)That is how this library's own tests assert the exact bytes each call emits.
from pyflx4 import CC, NOTE works without python-rtmidi installed — the
controller classes are imported lazily, and even touching DDJFLX4 does not load
it until you actually open a port. The map is useful on its own, for generating a
mapping for other software or for checking your own values against these — and it
stays usable on a machine where the MIDI bindings will not build.
flx4py is another Python FLX4 library, and covers LED animation and an event system more thoroughly than this one does. Check its EQ and trim CCs against your hardware before relying on them; at the time of writing they sit one control away from the values in the Mixxx mapping.
Mixxx itself is the reference if you want the complete picture, including every shifted note. It is GPL-2.0 and written as a JavaScript mapping, so it is a source to read rather than one to depend on from Python.
0.1.0. 40 tests, no hardware required to run them. Extracted from a working
system, so the paths exercised in anger — transport, EQ, crossfader, loops, hot
cues, pad modes — are the solid ones. The API is not settled; feedback before
1.0 is welcome.
See CONTRIBUTING.md to build on it and SECURITY.md to report a vulnerability.
MIT