Skip to content

Objects

Field offsets of the EP147 objects that answer "which deck" and "what is loaded". Unless a row says otherwise, released AZ-MODS code reads the offset on both builds, so it is the same on 1.30 and 2.00. Addresses are 1.30 values; the 2.00 value of every one is in addresses.csv.

Offsets are in bytes from the start of the object. u32, u64, f64 are little-endian.

Which deck an object belongs to

Take the deck from the constructor that receives it as an argument, then confirm it against the object's own field. Arrival order, slot position and "the first one found" are not identities: they are right on deck 1 and wrong on deck 2.

Object Deck comes from Confirm with
dj_player::DjPlayerUnit (the audio side of a deck; vtable 0x289f878) ctor 0x14a2560, whose second argument is the player id u32 at [unit + 0x1a0] + 0x1f18 = deck 0 to 3. Recorder units of the same base have +0x1a0 = 0.
gui::LoadState (vtable 0x2ddb878) ctor 0x1f9c498, deck in w4 (0 to 3) u32 at +0x47c = deck, u32 at +0x4a8 = 0x5e715e28
gui::BpmState (one per deck) ctor 0x1f5da20(self, int deck) low byte of the u64 at +0x08 = deck
waveform widget (WaveformWidgetBase) ctor 0x1b8f950, deck in its fourth argument u32 at +0x197f0 = deck
per-deck preview tile owner 0x1c1fad0, deck in its third argument, 1-based

gui::LoadState: what a deck has loaded

Offset Type Meaning
+0x000 vtable 0x2ddb878
+0x150 pointer its gui::LoadedTrackInfo (below)
+0x47c u32 deck, 0 to 3
+0x4a8 u32 0x5e715e28 on a constructed LoadState

gui::LoadedTrackInfo

Offset Type Meaning
+0x00 vtable 0x2dd9288: check it every time you read through the pointer
+0x08 16 bytes the loaded track's trackid::TrackID (all zero on an empty deck)
+0x38 juce::String title (a juce::String is one pointer to its UTF-8 text)
+0x40 juce::String artist

The fields change while a track loads; a reader on another thread can see one load's TrackID beside the next load's title. Read all three, then read the TrackID again and keep the result only if it did not change.

trackid::TrackID (16 bytes)

Offset Type Meaning
+0x00 8 bytes which library: 01 00 00 00 02 01 01 00 for a USB library; bytes 4 and 5 hold the media id
+0x08 u32 the track's content_id in that library's database
+0x0c u32 zero

Two decks holding the same song hold equal TrackIDs. Nothing in the 16 bytes names the USB stick, so a TrackID kept across a USB change can name a different song: keep them in memory, for the life of the process.

gui::BpmState

Offset Type Meaning
+0x08 u8 (low byte of a u64) deck, 0 to 3
+0xa0 f64 live, tempo-adjusted BPM (measured on 1.30)
+0xa8 f64 the same, x100 (measured on 1.30)
+0xb0 f64 the track's analyzed BPM

"No track" is DBL_MAX in the BPM fields, not zero. If you only need the BPM, use the core's deck_bpm(deck) instead of a hook.

From a deck to its key/tempo engine

KeyControlFGPR (vtable 0x27ba108) does the deck's key shift and time-stretch. It is built inside the DjPlayerUnit constructor, so it is reached from the unit by fixed pointers, each one vtable-checked:

Step Read Expect vtable
unit to PlayerBasisUnit [unit + 0x180] 0x289f798
to CascadedTimeStretchManager [pbu + 0x10] 0x27b8a50
to the KeyControlFGPR [mgr + 0x48] - 0xe0 (the pointer is to an interface inside it) 0x27ba108

If any step fails its check, the answer is "unknown", not another deck's engine.

When an offset differs between builds

2.00 is a recompile, and a few objects changed size. Example: MusicLibraryServer grew by 8 bytes, so its dispatcher pointer moved from +448 to +456. The address map cannot show that: it maps addresses, not layouts. To carry a field offset to another build, find that build's own instruction that reads the field (the ldr xN, [xM, #off] in the matching function), and prove the new offset with behavior before trusting it.