Skip to content

Releases: fiverecords/SuperTimecodeConverter

Super Timecode Converter V1.8.2

29 Mar 09:22
c91b649

Choose a tag to compare

Bug Fix: Tracks Without Artist Metadata

Fixed: tracks with no artist tag were blocked from multiple TrackMap features. The root cause was that artist was treated as a required field throughout the codebase, but many tracks (sound effects, jingles, DJ tools, untagged files) legitimately have no artist.

Symptoms fixed:

  • Learn button rejected tracks without artist (form validation required both artist and title)
  • TrackMap lookup skipped tracks without artist — offset, triggers, and cue points never activated
  • BPM multiplier saved via double-click but offset didn't fire on next load
  • Waveform/artwork disk cache skipped save and load for tracks without artist
  • Cue point auto-populate from rekordbox didn't fire without artist
  • ANLZ disk cache (beat grid, cues, phrases, detail waveform) skipped without artist
  • Status text didn't show "MAP: ..." or "NO MAP" without artist

Fixes applied (21 guards across 8 files):

  • AppSettings.h: hasValidKey() now only requires title
  • TrackMapEditor.h: form validation and Learn button work without artist; dbserver enrichment protected against empty overwrite
  • TimecodeEngine.h: lookupTrackInMap(), refreshTrackMapLookup(), getActiveTrackInfo() enrichment, deferred metadata enrichment, status text (ProDJLink + StageLinQ paths)
  • ProDJLinkView.h: TrackMap lookup, BPM save, waveform cache save/load, artwork cache save/load, cue auto-populate, ANLZ save, metadata enrichment (2 paths)
  • StageLinQView.h: TrackMap lookup
  • DbServerClient.h: disk cache key generation (4 paths)
  • MainComponent.cpp: BPM multiplier button state, CuePoint editor artwork lookup, CuePoint playhead matching, BPM mult save to TrackMap

Bug Fix: Metadata Overwrite With Empty Values

Fixed: when a track has no ID3 tags, the CDJ provides a fallback title like "Track #3". The dbserver metadata query returns empty artist and title for untagged tracks. Previously, this empty result unconditionally overwrote the CDJ's fallback title, leaving the track with no title at all — breaking waveform display, TrackMap lookup, and status text.

Now, dbserver results only overwrite artist/title when the returned value is non-empty. This preserves the CDJ's fallback title and any partial metadata.

5 overwrite paths fixed:

  • TimecodeEngine.h: track change enrichment, deferred "Track #" enrichment, getActiveTrackInfo()
  • ProDJLinkView.h: timer metadata update, fallback by trackId
  • TrackMapEditor.h: Learn dbserver enrichment

Bug Fix: TrackMap Duration Mismatch

Fixed: TrackMap entries created from the PDL View (BPM double-click) or Learn could fail to match during engine lookup because of a duration key mismatch.

The TrackMap key includes an optional duration suffix (e.g. |intro solu|300). The PDL View saves entries with the CDJ-reported duration, but the engine's cachedTrackDurationSec may still be 0 or stale when the lookup runs (duration resolves asynchronously via dbserver). The existing fallback only tried find(artist, title, 0), which generates key |intro solu — this doesn't match |intro solu|300.

Added findIgnoringDuration() to TrackMap: searches by artist|title prefix regardless of duration suffix. Used as a third-level fallback in all 8 lookup sites (engine, PDL View, StageLinQ View, MainComponent).


Bug Fix: Cue Points Not Populated From BPM Double-Click

Fixed: creating a new TrackMap entry via BPM multiplier double-click (in the PDL View or engine panel) did not auto-populate rekordbox cue points, even though creating via Learn did.

The auto-populate code in the PDL View timer only runs once per track — when cue data first arrives from dbserver (detailCuesFed flag). By the time the user double-clicks BPM to create the entry, that moment has already passed. Now, when creating a new entry from BPM double-click, the cached rekordbox cues are populated immediately from getCachedMetadataByTrackId().


PDL View Responsive Layout

Redesigned the per-deck layout in the Pro DJ Link View to scale properly at any window size.

Before: All sections used proportional height allocation. At small window sizes, every section received too little space simultaneously — text became microscopic and lower info rows disappeared.

After: Priority-based layout with fixed minimum sizes:

  • Info section uses fixed 14px line heights — text never shrinks below readable size
  • Bottom chrome (map row, engine row, BPM multiplier) uses fixed 16px rows that hide progressively on very small decks
  • Waveforms get whatever space remains after info and chrome
  • Detail waveform hides first when space is tight, freeing room for preview waveform and timecode
  • Beat indicator and track time rows appear only when there's room

Modified Files

File Change Summary
TimecodeEngine.h Artist guards removed (4); dbserver empty overwrite protection (2); status text artist requirement removed (2); lookupTrackInMap() duration fallback with findIgnoringDuration()
ProDJLinkView.h Artist guards removed (6); dbserver empty overwrite protection (2); TrackMap "Track #" filter removed; priority-based deck layout; duration fallback in all lookups; cue auto-populate on BPM entry creation
TrackMapEditor.h Form validation: only title required; Learn "Track #" filter removed; dbserver enrichment protected
AppSettings.h hasValidKey() only requires title; findIgnoringDuration() method added to TrackMap
StageLinQView.h Artist guard removed (TrackMap lookup); duration fallback added
DbServerClient.h Artist guards removed (4 disk cache paths)
MainComponent.cpp Artist guards removed (4); audio settings layout order fixed; duration fallback in BPM mult lookups; cue auto-populate on BPM entry creation
Main.cpp Version 1.8.2
SuperTimecodeConverter.iss Version 1.8.2

Super Timecode Converter V1.8.1

28 Mar 21:28
04e7778

Choose a tag to compare

Real-Time Audio BPM Detection

STC can now detect BPM in real time from any audio input, filling a gap for non-DJ input sources (MTC, LTC, Art-Net, System Time) where no CDJ or Denon hardware provides BPM data.

The detected tempo is forwarded to all BPM outputs -- MIDI Clock, OSC BPM, and Ableton Link -- exactly as CDJ/Denon BPM is forwarded in Pro DJ Link and StageLinQ modes.

How it works:

Audio BPM uses the BTT library (Beat-and-Tempo-Tracking by Michael Krzyzaniak, MIT License) to perform spectral flux onset detection, autocorrelation-based tempo estimation, and beat prediction -- all in real time from the audio callback. It processes mono audio and produces a BPM estimate that converges within a few seconds of hearing music.

Configuration:

  • Enable "AUDIO BPM" in the engine panel (visible when input is MTC, LTC, Art-Net, or System Time)
  • Select an audio input device and channel from the audio settings controls
  • When the engine input is LTC, Audio BPM shows its own separate device/channel/gain controls (since LTC owns the shared audio settings section)
  • When the engine input is any other non-DJ source, Audio BPM shares the AUDIO SETTINGS section (driver filter, sample rate, buffer size, device, channel, gain, meter)
  • The detected BPM appears with a live beat LED indicator and colour-coded confidence display

BPM Display:

  • Green: confidence > 60% -- tempo locked, reading reliable
  • Orange: confidence 30-60% -- actively tracking, adjusting
  • Dim orange: confidence 15-30% -- detecting but uncertain
  • Grey "--- BPM": no detection (silence or below confidence threshold)
  • Beat LED flashes at the detected BPM rate

BPM Smoothing:

A "BPM SMOOTHING" slider (0-100%) controls how quickly the BPM reading reacts to tempo changes:

  • 0% = fast tracking (reacts immediately, may fluctuate)
  • 50% = balanced (default)
  • 100% = very stable (slow to react but rock-solid reading)

Smoothing operates at two levels: an EMA (exponential moving average) on the output, and BTT's internal Gaussian tempo histogram parameters (decay and width). Both are tuned simultaneously from the single slider.

BPM Output Routing:

When Audio BPM is active, BPM output toggles (MIDI Clock, OSC Tempo, Ableton Link) appear inline within the AUDIO BPM section -- no empty section headers cluttering the UI.

Typical use case: A DJ plays from vinyl, CDJ without Pro DJ Link, or a MIDI controller. The venue's audio feed goes into an audio interface. STC receives LTC timecode on one channel for show cue synchronization, and analyses the audio on another channel (or the same interface) for BPM. Resolume, lighting desks, and other tools receive both timecode and tempo.

Design decisions:

  • Audio BPM runs on its own AudioDeviceManager, independent of the LTC input device.
  • When a DJ source (Pro DJ Link / StageLinQ) is active, audio BPM is not available -- protocol BPM is always more precise.
  • BTT is tuned for DJ/electronic music: tempo centre 128 BPM, range 60-200 BPM.
  • A confidence threshold (15%) prevents spurious values during silence or speech.
  • Device in-use markers show which engines are using which audio devices for BPM (same pattern as LTC markers).

OSC Command Template (GMA3 Support)

The OSC BPM forward now supports a command template for consoles that receive BPM as a string command rather than a float value.

A new "CMD" field appears below the OSC address when OSC Tempo is enabled. When the CMD field is empty (default), STC sends a standard OSC float -- compatible with Resolume and most OSC receivers. When CMD contains text, STC sends it as an OSC string argument with %BPM% replaced by the current BPM value.

GrandMA3 example:

  • Address: /gma3/cmd
  • CMD: Master 3.x at %BPM%

STC sends: /gma3/cmd with string argument "Master 3.x at 128.5"

Resolume (default, no change needed):

  • Address: /composition/tempocontroller/tempo
  • CMD: (empty)

STC sends: /composition/tempocontroller/tempo with float argument 128.5

The template works with all BPM sources (Pro DJ Link, StageLinQ, Audio BPM).


Ableton Link Exclusivity

Ableton Link is now exclusive per engine. Only one engine can have Link active at a time.

  • If Engine 1 has Link active and you try to enable it on Engine 2, the toggle reverts and shows "Link active on Engine 1" in red.
  • When viewing an engine without Link, the status label shows "Active on [Engine X]" in grey if another engine owns it.
  • Link is no longer propagated via global settings -- each engine's Link state is independent.
  • Settings restore guards ensure only the first engine with linkEnabled=true activates Link (protects against legacy settings from pre-v1.8.1 where Link was propagated to all engines).

PDL View Responsive Layout

Redesigned the per-deck layout in the Pro DJ Link View to scale properly at any window size.

Before: All sections (info, preview waveform, detail waveform, timecode) used proportional height allocation (flexH * 0.28f, etc.). At small window sizes, every section received too little space simultaneously -- text became microscopic and lower rows (beat indicator, track time) disappeared completely even with space available.

After: Priority-based layout with fixed minimum sizes:

  • Info section uses fixed 14px line heights -- text never shrinks below readable size
  • Bottom chrome (map row, engine row, BPM multiplier) uses fixed 16px rows that hide progressively on very small decks (< 120-160px)
  • Waveforms get whatever space remains after info + chrome
  • Detail waveform hides first when space drops below 80px, freeing room for preview waveform and timecode
  • Beat indicator and track time rows appear only when there's room, instead of always taking space

The result: at normal sizes everything looks the same, but shrinking the window now degrades gracefully -- detail waveform disappears first, then bottom chrome, while text stays readable throughout.


Bug Fix: Tracks Without Artist

Fixed: tracks with no artist metadata were blocked from Learn, TrackMap lookup, waveform/artwork caching, cue point auto-populate, and BPM multiplier persistence.

The root cause was hasValidKey() requiring both artist and title. Since the TrackMap key is artist|title[|duration], an empty artist produces a valid key like |my track title|300 -- unique and functional.

18 guards fixed across 7 files:

  • AppSettings.h: hasValidKey() now only requires title
  • TrackMapEditor.h: Learn button works without artist
  • TimecodeEngine.h: lookupTrackInMap() and refreshTrackMapLookup()
  • ProDJLinkView.h: BPM save, TrackMap lookup, waveform cache, artwork cache, cue point auto-populate, ANLZ save (6 fixes)
  • StageLinQView.h: TrackMap lookup during playback
  • DbServerClient.h: disk cache key generation for ANLZ/waveform (4 fixes)
  • MainComponent.cpp: BPM multiplier button state, CuePoint editor artwork lookup, CuePoint playhead matching, BPM mult save to TrackMap (4 fixes)

BTT Library Integration

The BTT library (ANSI C, zero external dependencies, MIT License) is integrated as a single-file amalgamation, following the same pattern as sqlite3:

  • btt_build.cpp -- the only file added to the Projucer (C++ wrapper with MSVC warning suppression and macro isolation)
  • btt_amalgamation.inc -- all BTT source files concatenated with extern "C" linkage (not added to Projucer)
  • BTT.h -- standalone public API header
  • BTT_LICENSE -- MIT license (Michael Krzyzaniak, 2021)

MSVC compatibility: VLA replacement with fixed arrays, M_PI define, random() -> rand(), explicit casts for calloc/enum/void*, #undef real/imag to neutralize <complex> macros from JUCE PCH.


New Files

File Description
BTT.h Standalone BTT public API header (no subfolder dependency)
btt_build.cpp C++ wrapper -- only BTT file added to Projucer
btt_amalgamation.inc Full BTT library amalgamation (included by btt_build.cpp)
BTT_LICENSE MIT license (Michael Krzyzaniak, 2021)
AudioBpmInput.h Audio BPM detection class with EMA smoothing

Modified Files

File Change Summary
TimecodeEngine.h Audio BPM member + start/stop/getters; sendBpmOsc() helper with template support; oscFwdBpmCmd member; setOscForward() 3-param signature; tick forwarding for non-DJ sources; Link not force-disabled when audio BPM active
TriggerOutput.h Added sendOscString() method for OSC command template
AppSettings.h New fields: audioBpmEnabled, audioBpmDevice, audioBpmType, audioBpmChannel, audioBpmSmoothing, audioBpmGain, oscBpmCmd; hasValidKey() fix
MainComponent.h New widgets: BeatLed class, lblBpmValue, ledBeat, sldBpmSmoothing, sldBpmInputGain, edOscFwdBpmCmd/lblOscFwdBpmCmd; findLinkOwnerOtherThan() method; beat flash timing state
MainComponent.cpp Full Audio BPM UI (toggle, BPM display, beat LED, smoothing, gain, device combos); shared vs separate audio settings for LTC mode; inline BPM output layout; OSC command template UI; Link exclusivity; device in-use markers for Audio BPM; artist bug fixes (4); settings save/load/restore for all new fields
ProDJLinkView.h Priority-based deck layout (fixed line heights, progressive hiding, detail waveform collapses first); artist bug fixes (6)
StageLinQView.h Artist bug fix: TrackMap lookup
DbServerClient.h Artist bug fixes (4): disk cache key generation
TrackMapEditor.h Artist bug fix: Learn button
Main.cpp Version 1.8.1
SuperTimecodeConverter.iss Version 1.8.1

Super Timecode Converter V1.8.0

28 Mar 12:24
08d9b43

Choose a tag to compare

CDJ-Style Waveform Display with Rekordbox Integration

v1.8.0 brings a major upgrade to the PDL View: a scrolling high-resolution waveform per deck, fed with rekordbox analysis data downloaded directly from the CDJ. The DJ's hot cues, beat grid, and song structure phrases appear in real time -- giving the lighting/video operator the same visual context the DJ sees on the CDJ screen.


Detail Waveform

A scrolling waveform rendered below the preview waveform in the PDL View. Shows 150 entries/second waveform data centred on the current playback position, matching the CDJ-3000 detail view.

Visual elements:

  • Beat grid: full-height vertical lines at every beat. Downbeats are brighter than intermediate beats, so bar structure is visible at a glance.
  • Song structure phrases: a coloured strip above the waveform showing Intro, Verse, Chorus, Bridge, Outro sections from rekordbox phrase analysis. Colour coding follows the rekordbox mood classification (High/Mid/Low energy).
  • Rekordbox cue markers: hot cues appear as coloured triangles with the DJ's letter assignment and comment label (e.g. "A INTRO", "C DROP"). Memory points show as red diamonds. Loops show as orange triangles with a semi-transparent overlay between start and end.
  • Active loop overlay: when the DJ activates a loop on the CDJ-3000 (stored or dynamic), the loop region is highlighted on the detail waveform.
  • Playhead cursor: fixed centre line with a triangle marker. The waveform scrolls underneath.
  • Zoom: +/- buttons in the waveform area and mouse scroll wheel. Range 1-32x, independent per deck.

Supports CDJ-3000 3-band format (PWV7, three frequency bands) and NXS2 colour format (PWV5). CDJ-3000 data is preferred when available; NXS2 format is used as fallback.


Rekordbox Data Download

STC downloads rekordbox analysis data directly from the CDJ via the dbserver protocol. Four new queries retrieve the data from the player's USB/SD memory:

  • Cue list (PCO2/PCOB): hot cues, memory points, and loops. Tries the extended NXS2 format first (PCO2 from .EXT file) -- which includes DJ-assigned colours and text comments -- then falls back to the standard format (PCOB) for older exports.
  • Beat grid (PQTZ): position and tempo for every beat in the track.
  • Detail waveform (PWV7/PWV5): high-resolution waveform data at 150 entries per second.
  • Song structure (PSSI): phrase analysis with mood classification from rekordbox 6+.

When the dbserver connection fails (common on CDJ-3000 for certain ANLZ tags), STC falls back to downloading the raw .EXT and .DAT files directly via NFS from the CDJ's USB/SD filesystem -- the same files rekordbox writes during export.

All downloaded data is cached to disk so it loads instantly when the same track is played again, even across sessions. Cache files are stored alongside the existing waveform and artwork cache in the app data directory.


Preview Waveform Enhancements

The existing preview waveform (the full-track overview) now shows additional context:

  • Beat grid lines: downbeat-only full-height lines, subtle enough to avoid clutter at the small size.
  • Rekordbox cue markers: coloured triangles above the waveform matching the DJ's colour assignments.
  • Stored loop overlays: semi-transparent coloured region between loop start and end.
  • Minute markers: white tick marks below the waveform at each minute.

These elements appear in both the PDL View decks and the engine mini-preview waveforms.


Auto-Populate TrackMap Cue Points from Rekordbox

When rekordbox cue data arrives for a track that has a TrackMap entry with no manually-configured cue points, the hot cues, memory points, and loops are automatically imported as CuePoints. Names are composed from the hot cue letter and DJ comment (e.g. "A INTRO", "C DROP"). Memory points get "MEM", loops get "LOOP".

Existing manual cue points are never overwritten -- this only fills empty entries. Auto-population is blocked during Show Lock, since the TrackMap is configuration.


Beat Grid PLL Micro-Correction

Between CDJ absolute position packets (~30Hz), the PLL interpolates forward at constant velocity. Over a few hundred milliseconds, small timing drift accumulates. When a rekordbox beat grid is available, the PLL applies a gentle 3% nudge toward the nearest beat position each tick. This only activates within 15ms of a beat boundary to avoid correction during inter-beat travel.

The result: LTC and MTC output stays tighter to the musical grid without fighting the CDJ position correction. Noticeable improvement in phase accuracy when syncing video to beat-aligned cue points.

The beat grid is cleared automatically on track change and on all engine reset paths.


Reverse Play Detection

Detects reverse playback on CDJ-3000 by comparing consecutive absolute position values. When the position decreases by more than 10ms while playing, the isReverse flag is set. Backward jumps caused by loop resets are suppressed (loop end → loop start is not reverse play).

Displayed as a red REV badge in the PDL View deck header, alongside the existing MST and AIR badges.


Track Time and Cue Count

Two new data points in the PDL View deck header:

  • Track time: elapsed or remaining time per deck, shown as MM:SS / MM:SS. Click the time display to toggle between elapsed and remaining per deck independently. Remaining time shows with a minus sign and amber colour.
  • Cue count: number of rekordbox cues shown in the BPM/Key metadata line (e.g. "138.0 BPM Am | 8 cues"). Updates when the cue list arrives from the dbserver, even if it arrives after the initial metadata load.

CDJ-3000 Dynamic Loops

Loop start/end positions are now parsed from CDJ-3000 status packets (bytes 0x1B6-0x1C1). This detects both stored loops from rekordbox and dynamic loops created during live performance. The active loop is highlighted on the detail waveform in real time.


Timecode Offset Stability Fix

Fixed a timecode offset accumulation bug that caused the displayed timecode to fluctuate when a CDJ was paused or stopped on a track with a TrackMap offset.

What happened: STC's tick loop runs at 60Hz. When the CDJ is playing, each tick generates a fresh timecode value from the CDJ's absolute position and then applies the TrackMap offset. But when the CDJ is paused and no new position packet arrives, the previous tick's timecode value (which already had the offset applied) was carried forward to the next tick -- where the offset was applied a second time. On the third tick, a third time. The result was a timecode that jumped by one offset per tick (60 times per second), visibly climbing through the hours before snapping back on the next CDJ packet.

The fix: each tick now always initialises currentTimecode from the raw CDJ playhead before applying any offset. The offset is applied exactly once, regardless of whether a new packet arrived. Same fix applied to both Pro DJ Link and StageLinQ paths.


TCNet Output Fix for Non-DJ Sources

Fixed TCNet output sending the wrong playhead position when using MTC, LTC, Art-Net, or System Time as input while CDJs were connected to the network.

What happened: the internal getSmoothedPlayheadMs() function did not check which input source was active before falling through to the Pro DJ Link code path. When CDJs were discovered on the network (even if no engine was using Pro DJ Link as input), the function returned the CDJ's playhead instead of 0. TCNet uses playheadMs when it's non-zero, bypassing the timecode-to-milliseconds conversion. The result: Resolume received the CDJ's position instead of the engine's timecode -- but only when CDJs were present on the network. Without CDJs, playheadMs was 0 and TCNet correctly used the timecode conversion.

The fix: getSmoothedPlayheadMs() and getSmoothedPlayPositionRatio() now return 0 immediately when the active input is not Pro DJ Link or StageLinQ. TCNet falls through to tcToMs(tc, fps) and sends the correct timecode-derived position.


macOS Beat Counter Fix

Fixed the beat-in-bar indicator (1-4) not updating on macOS while everything else (timecode, waveform, mixer data) worked correctly.

What happened: CDJ beat packets (type 0x28) are broadcast on port 50001. On macOS, sockets bound to a specific IP address do not receive broadcast packets -- only unicast. The beat socket was bound to the interface IP (which works on Windows), so beat packets were silently dropped. Absolute position packets (type 0x0b, unicast) arrived normally because they are sent directly to STC's IP, which is why timecode worked fine.

The fix: the beat socket now uses the same platform-specific binding as the keepalive socket -- INADDR_ANY on macOS (required for broadcast reception), interface IP on Windows (required for correct NIC selection on multi-adapter systems).


Crash Fix: Switching from MTC Input to Another Source

Fixed a crash when switching from MTC input to Pro DJ Link (or any other source) while MTC was actively receiving. The crash occurred inside JUCE 8's internal BytestreamToUMPDispatcher -- the new Universal MIDI Packet conversion layer.

What happened: MtcInput::stop() called midiInput->stop() then immediately set midiInput = nullptr, destroying the MidiInput object. On macOS, CoreMIDI's MIDIPortDisconnectSource does not guarantee that an in-flight callback has returned before it completes. If the platform MIDI thread was mid-callback inside the UMP dispatcher chain when the object was destroyed, the dispatcher accessed freed memory.

The fix: stop() now sets isRunningFlag = false first, then moves the device to a retirement list without calling midiInput->stop() or destroying it. The callb...

Read more

Super Timecode Converter v1.7.1

24 Mar 17:25
83594ac

Choose a tag to compare

Focus: TCNet Refinement, Input Switch Reliability, BPM Forward Robustness

Stability and usability fixes discovered during the first field deployment of v1.7.0 with multiple CDJs, plus TCNet output offset support.


TCNet Output Offset

Per-engine millisecond offset for the TCNet output, independent of the frame-based offsets used by MTC/Art-Net/LTC. Range: -1000 to +1000 ms. Visible in the output panel when TCNet OUT is enabled. Double-click to reset to zero.

This compensates for latency between STC and Resolume (or other TCNet receivers) without affecting the timecode outputs. Useful when video and timecode destinations have different processing delays.

Settings: Per-engine tcnetOutputOffsetMs in settings.json. Clamped to -1000/+1000 on load.


TCNet Jog Anti-Jitter

During jog/scratch on a CDJ, the hardware's absolute position oscillates by several milliseconds due to mechanical jog wheel resolution. At 60Hz TCNet output, these sub-frame reversals cause visible frame vibration in Resolume.

Fix: Deadband filter in TCNetOutput::setLayerFromEngine. During playback, positions pass through directly (monotonic advance, no filtering needed). When paused or jogging, the position only updates when cumulative movement exceeds 33ms (~1 video frame at 30fps). The jog wheel's mechanical jitter is typically 5-10ms -- well below the threshold -- so Resolume sees a stable position. Sustained jog movement exceeds 33ms immediately and updates without delay.


Fixes

  1. Track not detected after input switch-back (TimecodeEngine.h): Switching an engine from Pro DJ Link to another source (MTC, LTC, etc.) and back left cachedTrackId=0 permanently. The engine's startProDJLinkInput synchronized lastSeenTrackVersion with the CDJ's current value, so the tick loop's "track changed" check (pdlTrackVer != lastSeenTrackVersion) never fired. The track, its metadata, TrackMap offset, cue points, and triggers were all lost until the DJ loaded a different track or STC was restarted. Fix: lastSeenTrackVersion stays at 0 after resetProDJLinkCache, forcing the first tick to rediscover the current track. Same fix applied to StageLinQ path.

  2. Ableton Link stuck at 120 BPM on enable (LinkBridge.h, TimecodeEngine.h): Two issues combined. First, LinkBridge::setEnabled(true) did not reset lastCommittedBpm, so re-enabling Link after a disable left the session at the SDK default (120 BPM) because the hysteresis check (abs(bpm - lastCommittedBpm) < 0.1) suppressed the "identical" commit. Fix: setEnabled(true) now resets lastCommittedBpm=0 to force the first setTempo through. Second, getMasterBPM() returns 0 when no CDJ has the master flag active (both paused, startup transient, no DJM present). The Ableton Link, MIDI Clock, and OSC BPM Forward paths in the ProDJLink tick all used getMasterBPM() without fallback, so BPM was never sent until a CDJ became master. Fix: all three paths now fall back to getBPM(ep) (the assigned player's BPM) when master BPM is unavailable, matching the pattern StageLinQ already had. The same fallback added to setMidiClockEnabled() initial BPM.

  3. Waveform not loading for Link Export tracks (MainComponent.cpp): When a CDJ loads a track via Link Export from another CDJ's USB, the DbServerClient caches the metadata under the source player's IP (the CDJ with the USB). The engine panel's waveform lookup used getPlayerIP(pdlPlayer) -- the deck's own IP -- which missed the cache entry. The waveform stayed empty until the track was reloaded or STC restarted. Fix: lookup now uses getLoadedPlayer() to resolve the source player's IP, matching the pattern used by TimecodeEngine::requestDbMetadata. Artwork was unaffected because getCachedArtwork searches by artwork ID without IP.

  4. AnyDesk port conflict documented (README.md): AnyDesk remote desktop software uses UDP ports in the 50000-60001 range, overlapping with both Pro DJ Link (50000-50002) and TCNet (60000-60001). When AnyDesk's background service is running, it can intercept abspos packets, causing Pro DJ Link timecode to stutter at ~5Hz instead of 67Hz. The Known Issues section now documents both Pro DJ Link and TCNet impact with workaround.


Modified files

File Description
AppSettings.h tcnetOutputOffsetMs field (per-engine, -1000 to +1000), save/load in both multi-engine and legacy paths
TimecodeEngine.h tcnetOutputOffsetMs member + getter/setter with clamp, lastSeenTrackVersion not synced on start (forces track rediscovery), getMasterBPM() fallback to getBPM(ep) in MIDI Clock / Ableton Link / OSC BPM Forward / setMidiClockEnabled()
TCNetOutput.h setLayerFromEngine offset parameter (int64 arithmetic), jog anti-jitter deadband (33ms) with committedMs / wasPlaying per-layer state
LinkBridge.h setEnabled(true) resets lastCommittedBpm=0 to force immediate tempo commit
MainComponent.h sldTcnetOffset + lblTcnetOffset UI members
MainComponent.cpp TCNet offset slider (setup, sync, visibility, layout, save, load, feeding), waveform cache lookup uses source player IP for Link Export
Main.cpp Version bump 1.7.0 -> 1.7.1
README.md Version bump, TCNet offset in Synchronization section, AnyDesk port conflict expanded to cover Pro DJ Link
SuperTimecodeConverter.iss Inno Setup installer script (new)

Super Timecode Converter v1.7.0

23 Mar 22:57
1285573

Choose a tag to compare

Cue Points -- Timed Triggers Within Tracks

v1.7.0 introduces cue points -- per-track triggers that fire at specific playhead positions during playback. While track-change triggers (introduced in v1.5) fire once when a track is loaded, cue points fire at precise moments within the track. This enables show automation synchronized to the DJ's performance: lighting cues at the drop, pyro at the breakdown, video transitions at the intro.

How it works

  1. Map a track in the Track Map (Learn from CDJ/Denon, or manual entry)
  2. Click the Cues column for that track to open the Cue Point Editor
  3. Play the track on a CDJ or Denon deck
  4. Press Capture at each key moment -- STC reads the current playhead position and creates a cue there
  5. Configure each cue's trigger: MIDI Note, MIDI CC, OSC, Art-Net DMX (any combination)
  6. During the show, when the playhead crosses each cue position, the triggers fire automatically

Technical details

Firing logic (TimecodeEngine):

  • Cue points are loaded from the Track Map on track change and armed for the current track
  • The engine tick (60Hz) compares the CDJ/Denon playhead position against armed cue points
  • Forward scan with early-exit (cues are sorted by position)
  • Seek detection: backward seek resets cues ahead of the new position; forward seek (>500ms jump) marks skipped cues as already fired
  • Same dispatch path as track-change triggers: MIDI Note+CC via TriggerOutput, OSC via OscSender, Art-Net DMX via ArtnetOutput

Persistence:

  • Cue points are stored in trackmap.json as part of each TrackMapEntry
  • Each cue point stores: positionMs, name, MIDI (channel/note/vel/CC/val), OSC (address/args), ArtNet (channel/value)

Live editing:

  • Cue points added or modified while a track is playing take effect immediately
  • refreshTrackMapLookup reloads armed cues with playhead-aware state preservation (cues behind current position stay fired, cues ahead are armed)

UI (CuePointEditor):

  • Table view with columns: #, Position (MM:SS.mmm), Name, MIDI, OSC, DMX
  • Multi-selection support (Ctrl+click, Shift+click) -- Delete button removes all selected cues at once
  • Detail panel for editing all fields of a selected cue (shown only with single selection)
  • Selected cue highlighted white on the waveform strip for visual identification
  • Capture button reads live playhead from the active engine, or edit cursor if set
  • Edit cursor (cyan) clears automatically on any playhead movement (play, jog, scrub) or click outside the waveform
  • Add button for manual entry, Delete button for removal
  • Maximize button for full-screen editing on large waveforms
  • Window position persisted across sessions
  • Opened from the Track Map Editor by clicking the Cues column or the "Cue Editor" button in the edit form
  • Blocked during Show Lock (cue editor auto-closes when Show Lock is activated)

Integration with Track Map Editor:

  • New "Cues" column shows cue point count per track ("+" when empty)
  • Click the count to open the Cue Point Editor for that track
  • "Cue Editor" button in the edit form (disabled when adding a new track)

Duration-Based Track Identification

Track Map entries now include track duration as part of the identification key. This distinguishes different versions of the same track -- a 3:45 radio edit and a 5:30 extended mix with the same artist and title are treated as separate entries, each with their own timecode offset, triggers, and cue points.

  • Key format: artist|title|durationSec (when duration is known)
  • Learn automatically captures duration from CDJ/Denon metadata
  • Learn works with both Pioneer and Denon: ProDJLink via player selector, StageLinQ via the active engine's current track
  • Deferred duration pickup: if duration isn't available at track-change time (NXS2 via dbserver, or StageLinQ where TrackLength arrives after SongName), the engine polls each tick until duration resolves, then re-lookups the TrackMap and arms cue points

Cue Point Editor with Waveform Display

The Cue Point Editor includes a waveform strip showing the track's color preview with four cursor layers:

  • Playhead cursor (red) -- real-time position from CDJ/Denon at ~30Hz
  • Edit cursor (cyan) -- placed by clicking/dragging on the waveform, used for precise cue placement. Clears automatically when the playhead moves (jog, scrub, play) or on click outside the waveform.
  • Cue markers (yellow) -- vertical lines showing all saved cue point positions
  • Selected cue (white, thicker) -- the cue currently selected in the table, for visual identification
  • Album artwork displayed in the header (live from CDJ/Denon, or from cache when offline)
  • Capture uses the edit cursor position when set, or the live playhead when not
  • Duration sourced from dbserver metadata (Pioneer), engine state (StageLinQ), or TrackMap entry (offline editing)
  • StageLinQ tracks show waveform preview and album artwork from the Engine Library database (via FileTransfer + SQLite), same as Pioneer tracks via dbserver.
  • Waveform cache: waveform data is automatically saved to disk (waveform_cache/ in app data directory) the first time a track's waveform is loaded from a CDJ or Denon deck. The cue editor loads cached waveforms when the hardware is not connected, enabling offline cue programming. Files are ~3-5KB each (MD5-hashed filenames, binary format with 12-byte header + raw RGB data).
  • Artwork cache: album artwork is saved as PNG alongside the waveform cache, enabling offline display in the cue editor header. Same MD5 key scheme, .art.png extension.

Accessible from the Track Map Editor via the "Cues" column (click) or the "Cue Editor" button in the edit form.

StageLinQ XF-A/XF-B Crossfader Auto-Follow (Preliminary)

XF-A/XF-B mode now works with Denon StageLinQ hardware in addition to Pioneer DJM. The engine derives on-air status from fader position + crossfader position + channel assignment, and uses the same sticky resolution logic as ProDJLink (stays on current deck while on-air, switches when on-air is lost).

  • Channel assignment values assumed: 0=THRU, 1=A, 2=B (same convention as Pioneer DJM)
  • On-air derived from: fader > 2% AND not cut by crossfader on opposite side
  • Status text shows "XF-A D2" / "XF-B D3" badges
  • Awaiting confirmation with real Denon hardware -- if assignment values differ from Pioneer, a simple mapping adjustment is all that's needed

TCNet Output -- Full TCNet Server for Resolume Arena

STC now includes a complete TCNet server that replaces the PRO DJ LINK Bridge for video and lighting control. No intermediate hardware or bridge software required -- STC communicates directly with Resolume Arena, ChamSys, Avolites, madMapper, and any other TCNet-compatible system.

Key advantages over LTC/MTC for video

60fps video playback. Traditional LTC/MTC limits Resolume to the timecode frame rate (24-30fps). TCNet sends playhead position in milliseconds at 60Hz, giving Resolume enough data to interpolate and play video clips at full 60fps -- even when the timecode source is 24fps. This is a fundamental improvement: the 30fps ceiling that LTC imposed on Resolume clips no longer applies when using TCNet.

Automatic clip triggering. Resolume assigns clips by track name received over TCNet. When a CDJ loads a track, Resolume can automatically trigger the matching video clip without any manual MIDI mapping. For non-DJ sources (MTC, LTC, Art-Net, System Time), the engine name becomes the track title -- name your engine "INTRO" or "LASER SHOW" and Resolume maps clips to it.

Track metadata and artwork. Artist name, track title, and album artwork appear in Resolume's deck display. CDJ and Denon tracks show real metadata from the player; non-DJ sources show the input type as artist and the engine name as title.

Play/pause transport sync. Resolume follows the source's transport state in real time. Press pause on the CDJ and the clip pauses in Resolume. Press play and it resumes. No trigger configuration needed.

Fader-controlled opacity. When a DJM or Denon mixer is connected, the channel fader position (0-255) is sent as OnAir data in the Time packet. Resolume can use this to control clip opacity -- pull down the fader and the video fades out. Without a mixer, the fader defaults to 255 (fully visible).

Protocol implementation

Packet format verified byte-for-byte against a PRO DJ LINK Bridge v1.1 Wireshark capture and cross-referenced with the TCNet Link Specification V3.5.1B.

Broadcast (5 sockets):

  • OptIn (68B) + Status (300B) on port 60000 at 1Hz -- node keepalive and layer state
  • Time (162B) on port 60001 at 60Hz -- playhead position in ms, beat marker, layer state, fader on-air per layer
  • OptOut (28B) on port 60000 on shutdown

Unicast server (slave discovery + Request/Response):

  • Listen on port 60000 for slave OptIn broadcasts (automatic discovery of Resolume, ChamSys, etc.)
  • Listen on NodeListenerPort (65023) for Request packets from slaves
  • Respond with Metadata (548B, UTF-32LE artist + title) and Metrics (122B, state/position/speed/BPM/duration)
  • Stream Metrics unicast at 30Hz to all discovered slaves -- this is what Resolume uses for play/pause and track loaded status
  • Album artwork (type 204, JPEG in 4800-byte chunks) sent on track change and on request. CDJ/Denon tracks use cached album art; non-DJ sources show the STC logo.
  • Application handshake with Error response (matches Bridge behavior)
  • Unicast OptIn to each slave every 1 second
  • Slave expiry after 10 seconds without keepalive

Per-layer data:

  • Playhead position in milliseconds (sub-frame precision from CDJ/Denon; TC-to-ms conversion for other sources)
  • Track duration (real duration from CDJ/Denon; 23:59:59 for timecode sources, allowing full SMPTE offset range)
  • Layer sta...
Read more

Super Timecode Converter v1.6

21 Mar 20:16
b3d3bf0

Choose a tag to compare

Denon StageLinQ Integration

v1.6.0 adds native support for Denon Engine OS hardware via the StageLinQ protocol. STC now connects to Denon DJ equipment (SC5000, SC6000, Prime 4/2/Go, X1800, X1850) alongside Pioneer CDJ/DJM hardware, with the same timecode generation, track mapping, mixer forwarding, and show control capabilities.

Important: The Denon StageLinQ implementation is preliminary. It has been built entirely from open-source protocol references (chrisle/StageLinq, icedream/go-stagelinq, Jaxc/PyStageLinQ) and verified byte-by-byte against their source code, but has not yet been tested with real Denon hardware. The protocol frames, service handshake, StateMap subscriptions, BeatInfo parsing, and FileTransfer database download all match the reference implementations exactly, but real-world device behavior may differ.

If you have access to Denon Prime hardware and would like to help test, please try it and report your results on the GitHub issues page. Debug builds include logging that will help diagnose any issues. Bug reports with Wireshark captures are especially valuable.


Protocol implementation (StageLinQInput.h, 2401 lines)

Complete StageLinQ protocol in C++17/JUCE, based on three MIT-licensed open-source references: chrisle/StageLinq (TypeScript), icedream/go-stagelinq (Go), and Jaxc/PyStageLinQ (Python).

  • Discovery: UDP broadcast on port 51337 with "airD" magic, per-interface broadcast for Windows compatibility, periodic re-announcement (1s), exit notification on shutdown
  • Service negotiation: TCP connect to discovered devices, service request/response for StateMap, BeatInfo and FileTransfer ports, reference keepalives (250ms)
  • StateMap: 45/45 deck paths (100% coverage of all 3 references) + 26/26 global paths + 10 mixer paths, "smaa" framed TCP with UTF-16BE paths and JSON values
  • BeatInfo: Real-time binary stream with per-deck beat position, total beats, BPM, and timeline
  • Multi-device: Automatic deck offset from /Client/Preferences/Player -- SC6000 Player 2 maps to STC Decks 3-4

StateMap deck paths (45 per deck):
Play, PlayState, PlayStatePath, CurrentBPM, Speed, SpeedState, SpeedNeutral, SpeedRange, SpeedOffsetUp, SpeedOffsetDown, SyncMode, ExternalMixerVolume, ExternalScratchWheelTouch, Pads/View, Track/ArtistName, Track/SongName, Track/TrackName, Track/TrackLength, Track/SongLoaded, Track/SongAnalyzed, Track/CurrentBPM, Track/CurrentKeyIndex, Track/KeyLock, Track/CuePosition, Track/SampleRate, Track/TrackNetworkPath, Track/TrackUri, Track/TrackData, Track/TrackBytes, Track/TrackWasPlayed, Track/Bleep, Track/SoundSwitchGuid, Track/PlayPauseLEDState, Track/CurrentLoopInPosition, Track/CurrentLoopOutPosition, Track/CurrentLoopSizeInBeats, Track/LoopEnableState, Track/Loop/QuickLoop1-8, DeckIsMaster

StateMap global paths (26):
DeckCount, Master/MasterTempo, Sync/Network/MasterStatus, LayerA, LayerB, Player, PlayerJogColorA/B, SyncMode, PlayerColor1-4 with A/B variants (12), CurrentDevice, HasSDCardConnected, HasUsbDeviceConnected, ActiveDeck, ViewLayerB

StateMap mixer paths (10):
CH1-4 fader positions, CrossfaderPosition, ChannelAssignment1-4, NumberOfChannels


Database client (StageLinQDbClient.h, 1373 lines)

FileTransfer protocol client + SQLite database reader for Denon Engine Library metadata, artwork, waveform, and performance data.

  • FileTransfer protocol: "fltx" framed TCP, source enumeration, file stat, chunked download with transfer IDs
  • Database download: Database2/m.db (v2) with fallback to m.db (v1), matching chrisle/StageLinq behavior
  • SQLite amalgamation: sqlite3.h + sqlite3.c (v3.47.2, public domain) compiled directly into the project
  • Track query: By path (local tracks) or by URI (streaming tracks from Beatsource/Tidal/SoundCloud), matching chrisle DbConnection.ts
  • Schema-aware: v2 albumArtId vs v1 idAlbumArt column names, automatic fallback
  • NULL-safe: All sqlite3_column_text calls wrapped in null check (Engine DJ databases frequently have NULL metadata fields)
  • Network path parsing: net://uuid/source/Engine Library/Music/path format, identical algorithm to chrisle/StageLinq trackPath.ts

BLOB decoders (from libdjinterop format documentation):

BLOB Compression Content
overviewWaveFormData zlib 3-band waveform (low/mid/high), reordered to Pioneer display format at decode time
quickCues zlib Cue points with label, sample offset, ARGB color + main cue
loops raw (not compressed) Saved loops with label, start/end sample offsets, ARGB color
beatData zlib Beat grid markers with sample offset, beat number
AlbumArt.albumArt raw JPEG/PNG image data
Track.key integer 0-23 or text Musical key (converted via musicalKeyToString)

StageLinQ View (StageLinQView.h, 961 lines)

External 30Hz deck view window with full Denon visualization.

Per-deck panel (4 decks):

  • Artwork from Engine Library database
  • 3-band waveform (overview) with playhead cursor
  • Cue markers and saved loop regions (from database BLOBs)
  • Live active loop overlay (from StateMap, green, real-time)
  • Track info: artist, title, musical key (live from StateMap, updates with key shift)
  • Key lock badge [KL]
  • BPM with effective multiplier
  • Pitch percentage
  • Beat-in-bar indicator (4 dots)
  • Status badges: LOOP (with beat size), REV (bleep/reverse), JOG (jog wheel touch)
  • SMPTE timecode display
  • TrackMap offset timecode
  • Channel fader bar
  • Engine assignment and TrackMap status

Mixer section:

  • Crossfader visualization with A/B labels and thumb indicator

Layout:

  • Toggle between 4x1 (horizontal row, for docking below Resolume etc.) and 2x2 grid
  • Window bounds and layout state persisted in AppSettings

Mixer forward (OSC / MIDI / ArtNet)

StageLinQ mixer data is forwarded to external systems via the same OSC/MIDI/ArtNet buttons as Pioneer.

  • MixerMap Denon mode: Separate editable mixer map (slq_mixermap.json) with CH1-4 faders + crossfader
  • OSC: Configurable addresses (default /mixer/ch1..4, /mixer/crossfader), 0.0-1.0 float
  • MIDI CC: Configurable CC numbers (default CC 1-5), 0-127
  • MIDI Note: Configurable note numbers for grandMA2/MA3 executor faders
  • ArtNet DMX: Configurable DMX channels (default 1-5), 0-255
  • Dedup: Values only sent on change, DMX re-send at 100ms for node timeout prevention
  • Editor: MixerMap editor opens Pioneer or Denon map based on active input, DJM model filter hidden for Denon

Inline display (MainComponent)

When StageLinQ is active, the main display shows:

  • Track info (artist -- title) in Denon green accent
  • BPM with multiplier, pitch percentage, device model
  • Artwork from Engine Library database
  • 3-band waveform with playhead cursor (from database)
  • Mixer fader bars (Unicode block characters, same style as Pioneer DJM display)
  • Player combo: DECK 1-4 (no PLAYER 5-6 or XF-A/XF-B which are Pioneer-specific)
  • MixerMap button in Denon green (switches to Pioneer blue when ProDJLink is active)

TimecodeEngine integration

  • InputSource::StageLinQ with complete tick loop: PLL, interpolation, track change detection, TrackMap lookup, MIDI Clock, Ableton Link, OSC BPM forward, mixer forward
  • forwardStageLinQMixer() reads from Denon MixerMap entries
  • DMX buffer reset on input switch (prevents stale Pioneer data leaking into StageLinQ session)
  • Dedup arrays for both Pioneer (lastSentMixer[128]) and StageLinQ (lastSentSlqMixer[5])

Other changes

  • Tab centering: Engine tabs centered relative to full window width
  • Unknown path logger: Wrapped in #if JUCE_DEBUG for zero overhead in Release builds
  • MixerMap dual-mode: MixerMapMode::Pioneer / MixerMapMode::Denon with separate defaults and persistence files
  • Restore path fix: Engine restore loop now wires all shared pointers (StageLinQ, DbServer, SlqMixerMap were missing)

Network robustness fixes (pre-hardware audit)

Eleven fixes to StageLinQ network handling, identified by cross-referencing chrisle/StageLinq v3 changelog, go-stagelinq, PyStageLinQ protocol docs, and MarByteBeep discussion threads against our implementation. All are pure logic improvements that do not require hardware to verify. Fixes 6-7 add clean protocol shutdown and protocol constraint documentation. Fixes 8-9 address connection lifecycle and debugging instrumentation. Fixes 10-11 correct subscription ordering and error handling.

  1. Discovery EXIT stops active connections (StageLinQInput.h): When a Denon device broadcasts DISCOVERER_EXIT_, we now stop the active DeviceConnectionThread for that IP before erasing the device entry. Previously, the thread would keep running on dead sockets until I/O error, and if the device re-announced quickly, two threads could run for the same IP producing duplicate data.

  2. Reconnect kills stale threads (StageLinQInput.h): Before launching a new DeviceConnectionThread in manageConnections(), we now scan for and signal any existing thread for the same IP to exit. Prevents duplicate threads when a device does rapid EXIT + re-announce faster than thread teardown.

  3. StateMap/BeatInfo buffer resync (StageLinQInput.h): StateMap resync now scans forward for smaa magic bytes instead of blind 4-byte skip (which could burn CPU on a corrupted stream). BeatInfo resync discards the entire buffer instead of byte-skipping (beat data is real-time, frame loss is harmless, and BeatInfo has no magic bytes for re-alignment).

  4. FileTransfer Disconnect handling (StageLinQDbClient.h): The FileTransfer disconnect response (message ID 0x9) now closes the FT socket and resets the pointer, per chrisle changelog: "Hand...

Read more

Super Timecode Converter V1.5.3

20 Mar 08:33
a278ad2

Choose a tag to compare

Focus: Multi-Engine Stability & Live Performance Hardening

This release is entirely focused on stability and efficiency when running multiple engines simultaneously in a live environment. No new features -- just a more robust, leaner runtime for demanding show setups.


Fixes

DJM Fader Data Not Working on Windows (Critical)

Fixed a regression where the DJM-900NXS2 stopped sending mixer fader data (0x39 packets) to STC on Windows, while macOS continued to work. Two related issues, both involving multi-NIC configurations:

Issue 1 -- Keepalive socket bound to INADDR_ANY on Windows: The keepalive socket (port 50000) was the only socket not bound to the selected interface IP. On Windows systems with two NICs on the same subnet, the OS routing table could send the 54B bridge keepalive broadcast out the wrong interface. The DJM sees the broadcast arriving from a MAC address that doesn't match the one in the protocol payload, and rejects the bridge identity.

Fix: keepaliveSock now binds to bindIp on Windows (like beat, status, and bridge sockets). On macOS, it remains bound to INADDR_ANY because macOS does not deliver broadcast packets to sockets bound to a specific IP -- binding to bindIp would break device discovery entirely.

Issue 2 -- MAC address lookup by adapter name: The getMacAddress() function on Windows matched network adapters by FriendlyName using substring matching. If the adapter name changed -- due to a Windows update, driver update, user renaming, or regional language settings -- the match failed silently and STC used a fallback MAC address (02:00:00:00:00:05). The DJM firmware compares the MAC in the protocol payload against the Ethernet frame source MAC; when they don't match, it rejects the bridge.

Fix: getMacAddress() on Windows now matches primarily by IP address (which STC knows exactly from the bind). Name-based matching is kept as a fallback only.

DJM-A9 Support (Feature)

Full DJM-A9 support, tested and confirmed on real hardware. STC now auto-detects the connected DJM model and adapts the interface accordingly.

The Mixer Map editor includes a three-way model selector (DJM-900NXS2 / DJM-A9 / DJM-V10) that shows only the parameters available on your mixer. The ProDJLinkView mixer panel renders dual CUE A/B buttons when an A9 or V10 is connected. Beat FX names in the GUI match the A9's effect list (including Triplet Filter, Triplet Roll, and Mobius).

A9-specific features supported: Dual CUE A/B, Master CUE B, Headphone B, Booth EQ, Multi I/O (with A9 routing options: CH1-4, MIC, XF-A, XF-B, Master), Bluetooth and USB input sources.

Note: The A9 mic effects section (Echo, Pitch, Megaphone, Reverb) is processed internally by the mixer and is not available for forwarding via the Pro DJ Link protocol.

OpenGL Rendering Removed (Critical -- Windows)

Removed juce::OpenGLContext from both the main window and the ProDJLinkView window. When attached, JUCE's OpenGL renderer calls paint() for all child components on the GL thread, while timerCallback() writes data on the message thread. Every juce::String read in paint() -- artist, title, playState, sourceName, label text, etc. -- is reference-counted, and a concurrent read during write corrupts the refcount, leading to a crash or heap corruption. This affects TimecodeDisplay, ProDJLinkView, all juce::Label instances, and any component that reads String data in paint().

JUCE's OpenGL renderer paints into software images on the CPU and only uses the GPU for the final texture upload and composite. With the waveform image cache, HiDPI deck image cache, and targeted dirty-rect repainting already in place, the performance difference is negligible. Windows DWM already hardware-accelerates the native GDI composite path.

For a live performance application, eliminating the entire class of GL-thread data races is worth more than the marginal compositing speedup.

Requires: The juce_opengl module can remain enabled in Projucer -- it is simply no longer used at runtime.

LTC Encoder Thread Safety (Critical)

Fixed a data race between the audio thread and the message thread in the LTC encoder. The needNewFrame and encoderSeeded flags were plain bool variables written by reseed() on the message thread and read/written by the audio callback at sample rate. On x86 this was benign by coincidence, but on ARM (Apple Silicon) it could produce a corrupted LTC frame after pause/resume transitions.

Fix: Both flags are now std::atomic<bool> with explicit load()/store() operations in all code paths: reseed(), resetEncoder(), packFrame(), and the audio callback.

MIDI Clock Timer Thread Safety (Critical)

Fixed a data race on the midiOut pointer inside the MIDI Clock timer. The pointer was a plain juce::MidiOutput* read at 1000Hz by the HighResolutionTimer thread and written by the message thread via start(), stop(), and updateOutput(). This is undefined behavior in C++ and could crash on Apple Silicon when the pointer write is not atomically visible to the timer thread.

Fix: The pointer is now std::atomic<juce::MidiOutput*>. The timer callback loads it once per invocation via atomic::load(), and all message-thread writes use atomic::store().

Status Text Allocation Reduction (Performance)

Each engine built ~25 juce::String status text values every tick via concatenation, regardless of whether the engine was currently displayed in the UI. With 8 engines at 60Hz, this produced ~12,000 heap allocations per second purely for strings that were never read.

Fix: A new statusTextVisible flag on each engine is set to true only for the currently selected engine. All inputStatusText assignments in tick() are gated behind this flag. Background engines skip all string construction while continuing to process timecode and drive outputs normally.

Lightweight Metadata Cache Lookup (Performance)

getActiveTrackInfo() is called every frame (60Hz) and was copying a full TrackMetadata struct from the DbServerClient cache, including the color waveform std::vector<uint8_t> (~3600 bytes per track). This triggered a heap allocation and memcpy every frame for data that was never used in this code path.

Fix: New getCachedMetadataLightById() method returns a lightweight struct with only text fields and IDs (artist, title, key, BPM, artworkId, duration). All three call sites in TimecodeEngine that only need text metadata now use the light lookup. The full getCachedMetadataByTrackId() remains available for code paths that need waveform data.

TrackMap Double Lookup in PDL View (Performance)

The ProDJLinkView timerCallback() called TrackMap::find() twice per deck per frame with identical arguments -- once for the timecode offset and again for the BPM multiplier. At 30Hz with 4 decks, this was 240 redundant string lookups per second.

Fix: The first find() result is cached in a local tmEntry pointer and reused for both offset and BPM multiplier extraction.

TrackMap Save Without callAsync (Robustness)

The auto-fill TrackMap save used juce::MessageManager::callAsync() capturing a raw TrackMap* pointer. If the application closed between the async post and execution, the pointer would dangle. Since tick() already runs on the message thread (timerCallback), the save is now called directly.

Clean Shutdown and Engine Removal Pointer Nullification (Robustness)

During shutdown and engine removal, engines now have their shared pointers (TrackMap, MixerMap, ProDJLinkInput, DbServerClient) explicitly set to nullptr before output protocols are stopped and before engine objects are destroyed. Previously, removeEngine() stopped protocols but left shared pointers intact, which could cause stale access if a HighResolutionTimer callback fired during the destruction sequence. Both ~MainComponent() and removeEngine() now follow the same cleanup pattern: disconnect shared state first, stop MIDI clock, then stop all I/O.

Zero-Allocation OSC Float Sending (Performance)

The mixer forwarding hot path (sendOscFloat) went through sendFloat() which converted the float to a juce::String, then send() tokenized it back with StringArray::fromTokens and parsed the float again with getFloatValue(). Each call produced ~10 heap allocations for a 40-byte UDP packet. With mixer forwarding active and a DJ moving faders, this generated 100-200 unnecessary allocations per second.

Fix: New OscSender::sendFloatDirect() builds the OSC packet in a 256-byte stack buffer with zero heap allocations: address string padded to 4-byte boundary, type tag ",f", float as big-endian IEEE 754. A single SpinLock acquisition protects the socket write. TriggerOutput::sendOscFloat() now calls sendFloatDirect() directly, also eliminating a redundant double-lock (isConnected() + sendFloat() previously acquired the lock twice).

Waveform Loading Loop Fix (Bug)

After a track change, if the waveform was not yet in the DbServerClient cache (normal -- waveform queries are async and arrive 1-3 seconds after metadata), the main engine panel entered a tight loop that executed every frame at 60Hz: clear waveform, set displayedWaveformTrackId = 0, take the cache SpinLock, copy the full TrackMetadata struct including waveform vector, find no waveform, repeat. The retry else if path was dead code because the first if condition always matched when displayedWaveformTrackId was 0.

Fix: displayedWaveformTrackId is now set to the track ID immediately on track change (marking "attempted"), so the clear only happens once. The retry path checks !waveformDisplay.hasWaveformData() to detect when the async waveform arrives. Eliminates ~60 redundant cache lookups per second during the 1-3 second wa...

Read more

Super Timecode Converter v1.5.2

17 Mar 08:31
077cc34

Choose a tag to compare

New Features

DJM-V10 Full Protocol Support

All DJM-V10 mixer parameters have been mapped and confirmed from real Wireshark captures. The V10 uses the same 248-byte mixer packet (type 0x39) and 524-byte VU packet (type 0x58) as the DJM-900NXS2, with global offsets at identical absolute positions.

Per-channel: 5 new parameters in byte positions that are always zero on the 900NXS2:

  • Compressor (+2) -- V10 per-channel compressor knob
  • EQ Low Mid (+5) -- V10 4-band EQ (the existing "Mid" becomes "Hi Mid")
  • Send (+8) -- V10 per-channel send knob for the built-in effects bus
  • CUE B (+10) -- V10 dual-cue headphone system
  • Input Source (+0) -- extended enum: BUILT-IN, EXT1, EXT2, MULTI I/O, COMBO

CH5 and CH6 blocks confirmed at 0x084 and 0x09c.

Global: 20+ new parameters (all V10-only, zero on 900NXS2):

  • Master CUE B (0x0ba)
  • Isolator On/Hi/Mid/Lo (0x0bb-0x0be)
  • Booth EQ Hi/Lo (0x0c0-0x0c1)
  • Headphones B Cue Link/Mixing/Level (0x0c5, 0x0e6-0x0e7)
  • Headphones A Pre EQ (0x0e5)
  • Filter LPF/HPF/Resonance (0x0d8-0x0da)
  • Send Ext1/Ext2 On/Off (0x0dc-0x0dd)
  • Master Mix On/Size/Time/Tone/Level (0x0de-0x0e2)
  • Multi I/O Select/Level (0x0ce-0x0cf)

Repurposed offsets (different meaning on V10 vs 900NXS2):

  • 0x0ca: FX Assign -- V10 uses linear enum (0-5=CH1-CH6, 6=Mic, 7=Master) vs 900NXS2's non-linear enum
  • 0x0c9: Beat FX Select -- same 0-13 range but different effect order (Shimmer replaces Slip Roll)
  • 0x0ce/0x0cf: Multi I/O Select/Level on V10 (was Beat FX Assign mirror / Send Return on 900NXS2)
  • 0x0db: Send Built-IN Select on V10 (was Color FX Select on 900NXS2)
  • 0x0e2: Master Mix Level on V10 (was Color FX Param on 900NXS2)

VU meters (type 0x58, 524 bytes): Master L/R remain at 0x11c/0x158. CH5/CH6 appended at 0x194/0x1d0.

What adapts automatically:

  • PDL View mixer panel: 4 or 6 channel strips, V10-specific controls (compressor "K", 4-band EQ "H/h/l/L", send "S", dual CUE A/B buttons)
  • Beat FX names: V10 effect name table (Shimmer, reordered Helix/Reverb/Filter/Trans/Pitch)
  • MixerMap: 123 entries (V10) vs 76 entries (900NXS2), with DJM model toggle in the editor
  • VU meters: 8-block layout (6 channels + master L/R) on V10, 6-block on 900NXS2

CDJ-3000X Support

The CDJ-3000X uses the same Pro DJ Link protocol as the CDJ-3000. All features -- absolute position tracking, track metadata, waveforms, artwork, play state detection -- work identically. No code changes were needed.

Mixer Map Export / Import

The Mixer Map editor now has Export and Import buttons for saving and loading parameter mappings as JSON files. Useful for sharing configurations between machines, keeping backups, or pre-configuring mappings for different DJM models without having the mixer connected.

The DJM model toggle (DJM-900NXS2 / DJM-V10) controls which parameters are visible. V10-specific parameters are hidden when in 900NXS2 mode. The toggle auto-detects the connected mixer but can be switched manually.

Configuration Backup & Restore

Two new buttons in the title bar -- Backup and Restore -- let you export and import the entire STC configuration as a single JSON file. The backup includes all engine settings, Track Map entries, and Mixer Map mappings. Useful for migrating to a new machine, keeping a safety copy before a show, or sharing a known-good setup between systems.

  • Backup saves the current settings.json, trackmap.json, and mixermap.json into one portable stc_backup.json file
  • Restore loads a backup file, replaces all three config files, reloads settings and mixer maps, and prompts for a restart to fully apply engine and audio device configuration

Platform Notes

macOS: Disable the Firewall for Pro DJ Link

The macOS application firewall inspects every incoming UDP packet before delivering it to the application. With Pro DJ Link's high packet rate (CDJ status, beat data, DJM mixer and VU data -- hundreds of packets per second), the firewall adds enough latency and CPU overhead to cause timecode fluctuation, missed beats, and sluggish waveform updates.

Fix: Go to System Settings > Network > Firewall and either turn it off or click Options... and add Super Timecode Converter to the allowed list. On Windows this is not an issue -- Windows Firewall prompts once on first launch and does not intercept subsequent packets.

macOS: Unsigned Application

STC is open-source software and is not signed with an Apple Developer certificate. On first launch, macOS Gatekeeper will block the application. Right-click the app and select Open, then click Open in the confirmation dialog. Alternatively: System Settings > Privacy & Security > Security > Open Anyway. This is a one-time step.


GPU-Accelerated Rendering (Windows)

On Windows, STC now uses hardware-accelerated rendering via OpenGL. This offloads image compositing from GDI to the GPU, reducing message-thread load from repaint().

On macOS, OpenGL is intentionally disabled. JUCE's OpenGL renderer still performs all painting into software images on the CPU, then uploads them as textures through Apple's deprecated OpenGL-to-Metal translation layer -- adding overhead rather than reducing it. macOS CoreGraphics already uses Metal internally for compositing, so native rendering without OpenGL is faster. The HiDPI deck image cache and waveform image cache minimize per-frame paint work on both platforms.

Requires: The juce_opengl module must be enabled in Projucer (used on Windows builds only).

Waveform Rendering Improvements

Waveform display performance and visual quality significantly improved in both the PDL View and the engine mini-player:

  • Cached waveform image: The waveform bars (1200 entries per track) are rendered once into a juce::Image and only regenerated when the track data changes or the component resizes. Each frame blits the cached image and draws only the lightweight playhead cursor overlay on top.
  • Live cursor in PDL View: The waveform cursor now updates every frame at the full 30Hz timer rate, independent of the deck panel's static content updates.
  • Correct bar width at all sizes: Waveform bars scale proportionally when the window is enlarged or in fullscreen. Previously bars were always 1px wide, leaving visible dark gaps at larger sizes.
  • Native text rendering in PDL View: Deck panels are painted directly into the screen Graphics context, preserving subpixel font hinting and HiDPI scaling.

PDL View Improvements

  • Timecode auto-scale: The timecode font scales to fit both the available height and width (down to 8pt minimum), so the full HH:MM:SS.FF string is never truncated regardless of window size.
  • Empty deck cleanup: Decks without a connected player no longer show a timecode overlay.
  • Crossfader A/B labels repositioned to the left and right edges of the fader bar, vertically centred with the thumb.
  • Stale FPS badge removed: The per-deck FPS indicator showed a hardcoded default and never reflected the engine's actual output frame rate.

Fixes

macOS Timecode Fluctuation (Socket Binding)

Fixed the root cause of timecode fluctuation on macOS, traced to SO_REUSEPORT on the UDP sockets and binding to INADDR_ANY.

Root cause: JUCE's setEnablePortReuse(true) enables SO_REUSEPORT on macOS, which distributes incoming UDP packets among ALL sockets bound to the same port. If a previous STC instance left a zombie socket, or other Pro DJ Link software had a socket on port 50001/50002, the kernel split packets between them -- causing subset delivery and out-of-order stale packets, producing the characteristic forward/backward timecode jumps.

Fix: Removed setEnablePortReuse() from beatSock (50001) and statusSock (50002). Both sockets now bind to the specific interface IP instead of INADDR_ANY. keepaliveSock (50000) retains SO_REUSEPORT for coexistence with other Pro DJ Link software.

Smooth Timecode Interpolation

The CDJ-3000 sends position updates at ~30Hz, but the UI runs at 60Hz. Previously, the display froze for ~33ms between packets then jumped. STC now interpolates between packets using the CDJ's actual playback speed, producing smooth 60Hz timecode movement. Interpolation is clamped to 50ms x speed to prevent overshoot on delayed packets.

Direct CDJ Display

The timecode display and waveform cursor now read directly from the CDJ's absolute playhead instead of the PLL. Wireshark analysis confirmed the raw position data is clean -- monotonically increasing at ~30Hz with zero reversals. The PLL remains active for its original purpose (LTC bit-rate scaling) but is no longer in the display path.

ProDJLink Protocol Cleanup

Aligned STC's protocol behavior with the official Pioneer Bridge based on Wireshark analysis:

  • Removed unicast keepalive to DJM -- the real Bridge only broadcasts; the extra unicast was unnecessary.
  • Subscribe from dedicated port only -- the Bridge sends 0x57 subscribe from a single ephemeral port, not from the beat port.
  • Non-blocking socket reads -- switched from 1ms timeout (which on macOS could block 200-400ms) to instant poll with a single Thread::sleep(1) per loop.
  • Subscribe without sleep -- replaced the 400ms blocking burst with a single subscribe; the 2-second re-subscribe timer handles retries.

Mixer Status Fits Panel Width

The mini DJM mixer display in the engine panel was too wide on Windows (Consolas 9pt renders wider than Menlo on macOS). Fixed by reducing fader bars from 4 to 3 blocks, single spacing between channels, and capping the model name to 7 characters.


Untested Hardware

DJM-A9: Likely shares protocol characteristics with both the 900NXS2 and V10. If you have a DJM-A9 and can prov...

Read more

Super Timecode Converter v1.5.1

14 Mar 00:21
f655e16

Choose a tag to compare

Crossfader Auto-Follow (XF-A / XF-B)

New player mode for engines: XF-A and XF-B. Instead of monitoring a fixed player (1-6), the engine automatically follows whichever physical player is assigned to crossfader side A or B on the DJM.

This solves the core problem with show control timecode: software like Resolume can only receive 2 LTC streams simultaneously. With two engines (one set to XF-A, one to XF-B), each outputs the timecode of whichever deck is currently live on that side of the crossfader. When the DJ loads a new track on a different player but assigns it to the same crossfader side, the engine seamlessly switches to follow the new player.

Sticky selection: When multiple players share the same crossfader side, the engine stays on the current player as long as it has the on-air flag. Only when it loses on-air does the engine switch to another player on the same side. This prevents unnecessary switching during brief fader movements.

Status display: In XF mode, the input status shows XF-A P2 CDJ-3000 or XF-B P1 CDJ-3000 so you always know which physical player is being followed. If no player is assigned to the engine's crossfader side, it shows XF-A: NO PLAYER ON SIDE.

Available in the player selector combo alongside PLAYER 1-6.


Track Identification by Artist + Title

The Track Map now identifies tracks by artist + title instead of the CDJ's internal database row ID. The previous approach used the rekordbox USB/SD row ID, which is a sequential number local to each media device -- the same ID could map to completely different tracks on different USB sticks, causing false matches and misfired triggers.

With artist + title as the key, tracks are identified universally regardless of which USB/SD they're loaded from. The Track Map editor no longer shows or asks for a Track ID field. Existing trackmap.json files with trackId-only entries are migrated automatically (entries that had artist+title already work directly; entries with only a trackId generate a "Track #NNN" placeholder title that can be edited).

The CDJ row ID is still used internally for dbserver metadata queries and waveform/artwork cache -- it's just no longer the Track Map lookup key.


Crossfader Assign Badge in PDL View

Each deck in the PDL View now shows a small A or B badge next to the player number when the DJM has that channel assigned to a crossfader side. The badge uses cyan for side A and amber for side B. When the channel is set to THRU (no crossfader routing), no badge is shown.


Shared MIDI Output

MTC output and MIDI triggers/clock/mixer forward now share a single MIDI port when configured on the same device. Previously, each feature opened its own handle to the MIDI device -- on Windows, where exclusive port access is enforced, this meant the second one would silently fail. Now STC detects when both target the same port and automatically shares the connection.

This works transparently in all scenarios:

  • MTC already running, trigger enabled on same port: TriggerOutput borrows MTC's handle -- no double-open.
  • Trigger already running, MTC enabled on same port: TriggerOutput releases its own handle, MTC opens, then TriggerOutput borrows it.
  • MTC disabled while sharing: TriggerOutput re-opens its own handle automatically.
  • Different ports for MTC and triggers: Each opens independently, no change from previous behavior.

MIDI Clock, CC forwarding (mixer faders), and Note triggers all route through the shared handle when active.


Art-Net DMX Interface Selector

New ART-NET DMX INTERFACE combo in the Art-Net DMX section. Previously, Art-Net mixer forward and track triggers always broadcast on "All Interfaces" or inherited the timecode output's interface -- there was no way to select a specific NIC for DMX traffic independently.

The combo appears automatically when you enable ARTNET MIXER FWD or ARTNET Trigger, and lists the same network interfaces as the Art-Net output combo. Selection is persisted per-engine in settings.


BPM Multiplier

New per-track BPM scaling for MIDI Clock, Ableton Link, and OSC BPM forwarding. Scale the outgoing BPM independently of the CDJ's actual tempo -- useful when the lighting or video system expects a different tempo range than the DJ is playing (half-time sets, double-time drum & bass, etc.).

5 multiplier values: /4 (quarter), /2 (half), 1x (passthrough), x2 (double), x4 (quad).

Two interaction modes:

  • Single click on any multiplier button applies a temporary session override. It stays active until you load a different track or click another value. Great for quick adjustments mid-set.
  • Double click saves the multiplier to the Track Map for that track. Next time the track is loaded on any engine, the multiplier is applied automatically. Double click on 1x to clear a saved multiplier.

Visual feedback:

  • The active multiplier is highlighted in blue.
  • A saved Track Map value is always shown in gold text, even when a temporary override is active on a different value -- so you always know what's saved vs. what's temporary.
  • The effective (multiplied) BPM is displayed next to the original BPM in both the engine panel and PDL View: 128.0 BPM -> 256.0 (x2).

Available in both the engine panel (BPM MULT row) and the PDL View (per-deck buttons).


Non-Modal Editor Windows

The Track Map Editor and Mixer Map Editor windows no longer block interaction with the main window. You can now keep them open while adjusting engines, switching tabs, or monitoring playback.

  • Both windows remain open when switching between engines -- they are shared resources, not tied to a single engine.
  • Windows close automatically when no engine is using Pro DJ Link as its input source.
  • The Track Map Editor updates in real time when tracks are loaded, multipliers are saved, or metadata arrives from the CDJ.

Track Map Editor Cleanup

Simplified the Track Map entry form for clarity and reduced clutter.

Track ID removed: The Track ID field has been completely removed from the editor. Tracks are now identified solely by artist + title (see above). The table no longer shows a Track ID column.

Trigger section separated: All trigger-related fields (MIDI, OSC, Art-Net DMX, BPM Multiplier) are now grouped under a "TRIGGERS ON TRACK LOAD" header, visually separated from the track data fields (Artist, Title, Offset, Notes). Makes it immediately clear which fields define what the track is vs. what fires when it loads.

FPS per-track removed: The per-entry FPS selector has been removed. The offset timecode is always interpreted using the engine's output frame rate. Existing frameRate values in saved JSON are silently ignored on load.

MIDI Program Change removed: The PC# trigger field has been removed. Note On and CC cover all real-world show control use cases. Existing PC values in saved JSON are silently ignored on load.


UI Improvements

  • MIDI/OSC/Art-Net panel layout: Device/destination selectors (MIDI device combo, OSC IP:Port, Art-Net DMX interface) now appear after the feature toggle buttons, consistent across all three sections. Previously MIDI and OSC had the selector first, which was inconsistent with Art-Net.
  • Position source indicator removed: The [ABS]/[BEAT] tag in the input status text has been removed. It was a diagnostic indicator that never changed during normal operation (CDJ-3000 always shows ABS, NXS2 always shows BEAT).

Bug Fixes

  • OSC Mixer Forward not connecting on app restart. The OSC socket was not opened on startup when only OSC Mixer Forward was enabled (without OSC triggers or BPM forward).
  • MIDI Mixer Forward not opening device on app restart. Same issue -- the MIDI device was not opened when only MIDI Mixer Forward was enabled.
  • Beat indicator flickering back and forth. The beat-in-bar display (1-4) could briefly show the previous beat before snapping to the current one. Caused by status packets (~5Hz) overwriting beatInBar with a stale value after a beat packet had already updated it. Fixed by making beat packets (type 0x28) the sole authoritative source.
  • Stale track data when switching player. Changing the player selector without the new player being online left the previous player's track title, artwork, waveform, and BPM on screen. Now a full cache and UI reset is performed on player change.
  • Art-Net DMX track trigger could access out-of-range buffer. The track-change trigger path for Art-Net DMX did not validate the DMX channel before writing to the buffer. Now bounds-checked to [1,512] consistent with the mixer forward path.
  • RUNNING/STOPPED indicator not centred. The status dot and label above the timecode counter used fixed pixel offsets instead of measuring the actual text width. Now the dot + text are measured and centred as a unit over the counter.

Super Timecode Converter V1.5

10 Mar 19:46
2a704ea

Choose a tag to compare

Native Pro DJ Link Integration

v1.5 introduces a native Pro DJ Link implementation, turning STC into a direct bridge between Pioneer DJ hardware and professional show control systems — no additional software required.


Pro DJ Link Input

STC now connects directly to Pioneer CDJ and DJM hardware on the network as a Virtual CDJ.

Tested hardware:

  • CDJ-3000 + DJM-900NXS2

Expected compatible (not yet verified):

  • CDJ-2000NXS2, XDJ series, and other Pro DJ Link players
  • DJM-V10 and other Pro DJ Link mixers

Player features:

  • Automatic player discovery via Pro DJ Link keepalive protocol (port 50000)
  • Absolute position tracking from CDJ-3000 (30Hz, millisecond precision, type 0x0b)
  • Beat-derived position for NXS2 and older models (beatCount × 60000/BPM from status packets)
  • Play state detection: Playing, Paused, Cued, Looping, Seeking, End of Track
  • BPM, pitch fader, actual playback speed (including motor ramp)
  • On-air status from both CDJ flags and DJM channel reports
  • Master player detection
  • Per-player monitoring: any engine can follow any of 6 players independently

Playhead PLL (Phase-Locked Loop):

  • Smooth timecode generation from CDJ data — no frame skipping or jitter
  • Driven by actual motor speed (offset 152), which includes acceleration ramps, jog wheel, and pitch fader changes
  • Gentle position correction (25% per packet, converges in ~100ms on CDJ-3000)
  • Hard reset on seek/hot cue/track load (>500ms position jump)
  • dp/dt velocity fallback for NXS2 models that don't report actualSpeed

End of track / Pause handling:

  • Pause: actualSpeed ramps to zero naturally over ~4-5s — PLL follows the deceleration, outputs stop smoothly
  • End of track: CDJ freezes actualSpeed at last playing value — STC detects playState 0x11 and freezes timecode to prevent flicker

DJM Mixer Integration

STC receives real-time mixer parameter data from DJM hardware on the Pro DJ Link network.

Mixer data (58 parameters):

  • Per-channel (CH1-CH4): fader, trim, EQ high/mid/low, color/FX knob, CUE button, input source, crossfader assign
  • Master: crossfader, master fader, master CUE, fader curve, crossfader curve, booth level
  • Headphones: cue link, mixing knob, level
  • Beat FX: selector, level, on/off, assign, frequency band buttons, send/return
  • Color FX: selector, parameter knob, assign
  • Mic: EQ high, EQ low

VU meters:

  • 6-channel peak metering: CH1-CH4 (mono) + Master L/R (stereo)
  • 15 segments per channel matching the physical DJM meter strips
  • Displayed as segmented VU bars alongside each fader in the PDL View

On-air status:

  • Per-channel on-air flags from DJM broadcast and unicast reports
  • Combined with CDJ status flags for reliable on-air detection

Mixer Map — Configurable Output Routing

New MixerMap system maps every DJM parameter to OSC, MIDI CC, MIDI Note, and Art-Net DMX independently.

58 parameters mapped:

  • 9 per channel × 4 channels = 36 (fader, trim, EQ hi/mid/lo, color, CUE, input source, XF assign)
  • 22 global parameters (crossfader, master, booth, headphones, beat FX, color FX, mic EQ)

Output protocols:

  • OSC: normalized float 0.0–1.0, configurable address per parameter
  • MIDI CC: 0–127, configurable CC number and channel per parameter
  • MIDI Note: 0–127 velocity (for grandMA2/MA3 executor faders), configurable note number per parameter
  • Art-Net DMX: 0–255 raw value, configurable DMX channel per parameter, configurable universe

Deduplication: values only sent when changed (per-parameter last-sent comparison).

Art-Net DMX timeout compliance: persistent buffer re-sent at 10Hz even when no values change, preventing receiver blackout on nodes with 2-3s DMX timeout.

Editor: table UI with editable OSC addresses, MIDI CC, MIDI Note, and DMX channel per row. Enable/disable toggle per parameter. Reset to defaults. Enable/disable all.


Track Map — Enhanced Triggers

Track change triggers now support independent MIDI Note, MIDI CC, Program Change, OSC, and Art-Net DMX outputs simultaneously per track.

What changed from v1.4:

  • The old mutually-exclusive MIDI type selector (None / Note On / PC / CC) is replaced with independent fields — any combination can fire simultaneously on the same track change
  • New Art-Net DMX trigger: send a specific value to a specific DMX channel on track change (persistent buffer — values stay until overwritten by another track's trigger)
  • Configurable Art-Net trigger universe (separate from mixer universe)
  • Existing TrackMap files from v1.4 are automatically migrated to the new trigger format on first load

Per-track trigger options:

  • MIDI Note On (+ immediate Note Off): note number + velocity
  • MIDI CC: controller number + value
  • MIDI Program Change: program number
  • OSC: address + typed arguments with variable expansion ({trackId}, {artist}, {title}, {offset})
  • Art-Net DMX: channel + value

DbServer Client — Track Metadata

Background TCP client queries CDJ internal databases (port 12523) for track metadata.

  • Artist, title, album, genre, key, BPM, duration, rating, date added
  • Album artwork (JPEG, cached per artwork ID)
  • Color preview waveform: ThreeBand (CDJ-3000) and ColorNxs2 formats
  • LRU cache with automatic eviction (64 metadata entries, 32 artwork entries)
  • Async request queue with semaphore-based wakeup (no busy-loop)

PDL View — Network Visualization

External window showing the full Pro DJ Link network state at 30Hz.

4-deck display (2×2 grid):

  • Album artwork from DbServer cache
  • Color waveform with playhead cursor
  • Track info: artist, title, key
  • BPM, pitch %, play state, position source (ABS / BEAT)
  • Raw timecode + TrackMap offset timecode
  • Engine assignment indicators
  • On-air / master / beat-in-bar indicators

Mixer strip:

  • Channel faders CH1-CH4 with per-channel VU meters (segmented, green→yellow→red)
  • Crossfader with A/B position indicator
  • Master fader with stereo VU (L+R)
  • DJM model label

Ableton Link Bridge

BPM from the selected CDJ player is published to an Ableton Link session on the local network.

  • Any Link-enabled peer (Resolume, Ableton Live, Traktor, etc.) syncs automatically
  • Hysteresis: only commits when BPM changes by ≥0.1 to avoid flooding the session
  • Peer count displayed in UI
  • Enable/disable per engine

Other Changes

MIDI Clock output: 24ppqn clock driven by CDJ BPM, with fractional accumulator for drift-free timing. Start/Stop messages on enable/disable.

OSC BPM forward: sends current BPM as float to configurable OSC address (default: Resolume's /composition/tempocontroller/tempo). Dedup threshold: 0.05 BPM.

Frame precision fix: wallClockToTimecode epsilon (+1e-9) in both NDF and DF paths prevents floating-point truncation at frame boundaries (e.g. 0.9999 → 0 instead of 1).


Compatibility

  • Windows 10/11 (VS2022, x64)
  • macOS 12+ (Xcode, arm64/x86_64)
  • JUCE 8.0.6, C++17

Tested hardware: CDJ-3000 + DJM-900NXS2. Other Pro DJ Link compatible hardware (CDJ-2000NXS2, XDJ series, DJM-V10, etc.) should work but has not been verified yet. Please report any issues on GitHub.

License

MIT License — © 2026 Fiverecords

Disclaimer

This project is not affiliated with, endorsed by, or associated with AlphaTheta Corporation or Pioneer DJ. PRO DJ LINK™ is a trademark of AlphaTheta Corporation. The Pro DJ Link protocol implementation is based on independent community research, particularly the DJ Link Ecosystem Analysis by Deep Symmetry. Use at your own risk — behaviour may change with future firmware updates or on untested hardware.