Skip to content

Super Timecode Converter V1.8.3

Choose a tag to compare

@fiverecords fiverecords released this 01 Apr 20:21
· 12 commits to main since this release
94cd27b

CDJ-2000NXS2 Support

Added support for CDJ-2000NXS2 and other older Pioneer players that don't send Absolute Position packets.

STC was designed and tested exclusively on CDJ-3000, which sends precise playhead position (absolute position packets at ~30Hz). The CDJ-2000NXS2 does not send these packets — it only provides beat count and BPM in status packets at ~5Hz. This caused two problems:

  1. Timecode stuttering at 5Hz instead of smooth output — visible jerky movement in lighting/video systems
  2. Metadata showing "Track #" instead of real artist/title — dbserver queries failed because the NXS2 rejects player number 5

Smooth Timecode From Beat-Derived Position

The CDJ-2000NXS2 sends status packets at ~5Hz containing beat count and BPM, but no millisecond-precision playhead. STC already derived a playhead position from these (beatCount × 60000 / BPM), but the interpolation between packets was capped at 50ms — a limit designed for CDJ-3000's 30Hz updates. With 200ms gaps between NXS2 packets, the timecode froze for 150ms out of every 200ms cycle, causing visible stuttering.

Five fixes applied:

  • Adaptive interpolation window: CDJ-3000 (absolute position, ~30Hz) keeps the tight 50ms cap, scaled with playback speed. NXS2 and older players (beat-derived, ~5Hz) get a fixed 250ms real-time window — not speed-scaled, because status packets arrive at 5Hz regardless of playback speed. Speed-scaling would starve the interpolation at low speeds (e.g. 0.5× → 125ms cap vs 200ms gap).

  • Beat packet position advancement: Beat packets (type 0x28, port 50001) arrive at the exact moment of each beat — roughly every 500ms at 120 BPM. For NXS2 players, STC now uses these as additional position anchors: each beat packet increments the beat count and recalculates the playhead. Combined with 5Hz status packets, this reduces the effective update rate gap from ~200ms to ~80-150ms. The next status packet from the CDJ corrects the beat count to its authoritative value, making this self-correcting.

  • Stale status packet guard: Beat packets and status packets are processed in the same network thread, with beat packets drained first. A status packet generated before a beat transition can carry a stale beat count that would revert the position backward. Now, when beat-derived mode is active, the status handler only accepts a beat count if it's equal to or greater than the stored value — preventing backward jumps from stale packets. A tolerance of 4+ beats allows real backward jumps (seek, scratch, cue jump) to pass through: at 120 BPM with 5Hz status, beat packets advance at most ~3 beats between status updates, so staleness never exceeds 3 beats.

  • Beat grid precision lookup: Following the approach used by beat-link (Deep Symmetry), the rough beat-derived position (beatCount × 60000 / BPM) is replaced with the exact millisecond position from the rekordbox beat grid when available. This handles variable-BPM tracks and non-zero first-beat offsets that the simple formula misses. The beat grid is already fetched for each track via dbserver/NFS (PQTZ tag from ANLZ data). The interpolation system was adapted for grid-corrected positions: new-packet detection uses timestamps for NXS2 (not position comparison), and between beats the snap anchor advances at each 5Hz status packet to prevent the 250ms interpolation cap from stalling mid-beat.

  • Pause deceleration interpolation: When the DJ pauses, playState changes to PAUSED instantly but the motor decelerates over 4-5 seconds (actualSpeed ramps to 0). The interpolation between packets was gated on isPlayerPlaying, which became false immediately — so during the entire deceleration ramp, timecode only updated at 5Hz (packet rate) instead of 60Hz (timer rate). CDJ-3000 was unaffected because abspos packets at 30Hz compensated. Now the interpolation also runs when actualSpeed is above the dead zone, regardless of play state.

Technical detail: The existing PLL (used for LTC pitch calculation) required no changes — it already handles both absolute position and beat-derived inputs via its driveVelocity fallback path. The actualSpeed field (status packet offset 152) is present in NXS2 packets and drives the interpolation velocity.

Metadata Queries for NXS2

The CDJ-2000NXS2 dbserver rejects queries from player numbers outside the 1-4 range. STC identifies itself as player 5 on the Pro DJ Link network (chosen specifically to avoid conflicting with real player slots 1-4). The CDJ-3000 accepts queries from player 5; the NXS2 does not.

Fix: When the source player doesn't have absolute position (indicating NXS2 or older hardware), STC now impersonates a discovered player number 1-4 for dbserver queries instead of using player 5. The player to impersonate is chosen automatically: the first discovered player 1-4 that isn't the query target. CDJ-3000 queries continue using player 5 unchanged.

This fix is applied in both the engine metadata path (TimecodeEngine) and the PDL View metadata path (ProDJLinkView), ensuring artist, title, artwork, waveform, and cue point data resolve correctly from NXS2 players.

Limitation: If the NXS2 is the only player on the network (no other player 1-4 to impersonate), the query uses the NXS2's own player number as a last resort. This may or may not work depending on the CDJ firmware — a Wireshark capture from a real NXS2 setup would confirm.

The dbserver TCP connection now tracks the player identity used during context setup. If a subsequent query requires a different identity (e.g. network topology changed), the connection is closed and re-established with the new identity. This prevents a protocol mismatch where the context says "player 2" but queries carry "player 3" in the DMST argument.

Mixed Network Support

CDJ-3000 and CDJ-2000NXS2 can run simultaneously on the same network. Each player is handled independently:

  • CDJ-3000 players: absolute position at ~30Hz, tight interpolation, player 5 for dbserver
  • NXS2 players: beat-derived position with beat packet advancement, wide interpolation, player 1-4 for dbserver
  • The per-player hasAbsolutePosition flag (already present in the architecture) drives all path selection automatically

Status Packet Parsing

No changes needed — NXS2 status packets (0x11c bytes) share the same field offsets as CDJ-3000 for all fields STC uses (play state, flags, BPM, actual speed, beat count). The existing length checks (if (len > N)) naturally handle both packet sizes. CDJ-3000-only fields (key shift, dynamic loop, media presence) are at offsets beyond 0x11c and are already gated by length checks.


Testing Notes

These changes have been tested on real CDJ-2000NXS2 hardware by a community member, on both macOS and Windows 10, including mixed networks with CDJ-3000.

CDJ-3000 is not affected — all NXS2-specific changes are gated behind hasAbsolutePosition checks. CDJ-3000 players continue using the absolute position path with no behavioral change.


Bug Fix: Artwork Disk Cache Key Mismatch on NXS2

The artwork disk cache in ProDJLinkView used ds.trackLenSec (from abspos protocol packets) as part of the cache key. CDJ-3000 provides this via absolute position packets before artwork arrives, so the key always matched. NXS2 has no abspos packets, leaving trackLenSec at 0 — the artwork was saved under a key without duration (e.g. muse|hysteria) while the TrackMap entry and CuePointEditor use a key with duration (e.g. muse|hysteria|227). This caused artwork to not appear in the CuePoint Editor for tracks that had only played on an NXS2.

Fix: ds.trackLenSec is now updated directly from meta.durationSeconds when dbserver metadata resolves in ProDJLinkView, in both the primary and fallback metadata paths. The artwork save runs later in the same timer tick and sees the correct duration. CDJ-3000 is unaffected — the update is redundant since abspos already provides the value.


rekordbox XML Import

The TrackMap Import button now accepts rekordbox XML exports (.xml) in addition to STC's native JSON format. This allows pre-populating the TrackMap with your entire rekordbox library — artist, title, and duration are imported for each track, enabling instant TrackMap matching without needing to Learn each track individually.

How to use: In rekordbox, go to File → Export Collection in xml format. In STC, open the TrackMap Editor and click Import. Select the exported .xml file. A preview dialog shows all tracks found — select which ones to import.

What's imported: Artist, title, and duration. Timecode offsets, triggers, and cue points are left at defaults — configure them per track as needed, or let cue points auto-populate from rekordbox data when the track plays on a CDJ.

What's not imported: Artwork, waveforms, and beat grids are not in the rekordbox XML format. These are fetched automatically from the CDJ via dbserver/NFS when the track plays for the first time, as usual.

Deduplication: The same track may appear multiple times in the XML (e.g. from the local library and a USB export). Duplicates with the same artist + title + duration are automatically filtered — only the first occurrence is kept.


TrackMap Editor Improvements

  • Clear All button: removes all tracks from the Track Map in one click, with confirmation dialog.
  • Enter/Return to save: pressing Enter in any form field saves the entry (same as clicking the Save button). No longer necessary to reach for the Save button after typing an offset.

Faster Metadata Pipeline

Reduced the dbserver socket read timeout from 3000ms to 1000ms. Wireshark captures confirm all CDJ responses arrive in under 10ms — the 3-second timeout caused unnecessary blocking between sequential dbserver queries (beat grid, detail waveform, cue list, song structure). With 4 queries per track, worst-case metadata completion drops from ~12 seconds to ~4 seconds when switching tracks rapidly.


Bug Fix: MTC and Art-Net Timecode Flicker

Interpolation between CDJ packets (both CDJ-3000 abspos at 30Hz and NXS2 beat-derived at 5Hz) can overshoot the real position by a fraction of a frame. When the next packet arrives, the timecode snaps back by 1 frame. The LTC encoder was already immune because it maintains its own running timecode via auto-increment and only resyncs on jumps of more than 1 frame.

MTC and Art-Net lacked this protection. In MTC, the backward frame was captured at QF0 and transmitted over the entire 8-QF cycle (~66ms at 30fps), causing the receiver to resync visibly. In Art-Net, the backward frame was sent directly in an ArtTimeCode packet.

Fix: Both MTC and Art-Net encoders now use the same auto-increment architecture as LTC:

  • MtcOutput: At QF0, auto-increments cycleTimecode by 2 frames (one QF cycle = 2 frame durations). Only resyncs from pendingTimecode if the difference exceeds 2 frames. mtcSeeded flag resets on start, resume, and forceResync().
  • ArtnetOutput: Auto-increments encoderTc by 1 frame per send. Only resyncs from timecodeToSend if the difference exceeds 1 frame. artnetSeeded flag resets on start, resume, and forceResync().

This eliminates flicker on all player types and speeds. Seek, hot cue, and track change trigger immediate resync as before. LTC and TCNet are unaffected — LTC already had auto-increment, TCNet sends raw frames handled natively by receivers like Resolume.


Modified Files

File Change Summary
ProDJLinkInput.h Beat packet position advancement for NXS2 (handleBeatPacket); stale status packet guard with backward-jump tolerance (handleStatusPacket); suggestDbPlayerNumber() helper; getBeatCount() getter
TimecodeEngine.h Beat grid precision lookup for NXS2 (beatCount → grid ms); timestamp-based new-packet detection with snap advancement (pdlLastAbsPosTs); adaptive interpolation cap (50ms×speed for abs, fixed 250ms for beat-derived); pause deceleration interpolation (actualSpeed > deadzone gate); NXS2-aware dbserver player number in requestDbMetadata(); NXS2 beat boundary overshoot guard
ProDJLinkView.h NXS2-aware dbserver player number in metadata request path; trackLenSec update from dbserver metadata (fixes artwork disk cache key mismatch on NXS2)
DbServerClient.h contextPlayer tracking in PlayerConnection — forces reconnect on identity mismatch; lastFailTime reset on intentional close; kReadTimeoutMs reduced from 3000 to 1000
MtcOutput.h Auto-increment encoder with cycleTimecode and mtcSeeded — prevents 1-frame backward jitter from causing full QF cycle of wrong data
ArtnetOutput.h Auto-increment encoder with encoderTc and artnetSeeded — prevents 1-frame backward jitter in ArtTimeCode packets
AppSettings.h TrackMap::parseRekordboxXml() — rekordbox DJ_PLAYLISTS XML parser
TrackMapEditor.h Import accepts .json and .xml; rekordbox XML parser; Clear All button; Enter/Return to save form
Main.cpp Version 1.8.3
README.md Version 1.8.3