Releases: fiverecords/SuperTimecodeConverter
Super Timecode Converter V1.9.9
StageLinQ: catalogue-heartbeat handling
A community contributor with SC5000 + X1800 hardware shared a Wireshark capture and a Python reference implementation that they had been working on independently. Cross-checking that material against STC produced two findings worth shipping.
The first is purely cosmetic. SC5000 firmware revisions in the field periodically re-broadcast their available StateMap path catalogue using opcode 0x07D2 -- the same opcode STC sends in the other direction to subscribe -- with no JSON value, just the path string and a 4-byte minimum-interval field. Until now, debug builds logged each of these as StageLinQ: Unknown smaa subtype 0x7d2, once per path every few seconds, which made debug captures noisier than they needed to be. STC now recognises the device-direction 0x07D2 as a periodic catalogue announcement and silently consumes it, the same way it has always silently consumed the 0x07D1 subscription acknowledgements. There is no behavioural change in release builds; this only affects log output.
The second finding is the BeatInfo handshake itself: the eight-byte payload STC sends to start a BeatInfo stream is byte-identical to what the contributor's Python implementation sends, including the explicit absence of a token. The contributor reports having confirmed via packet capture that adding a payload to that frame (e.g. an attempt to authenticate the BeatInfo connection by appending the StageLinQ token) is enough to make the device silently refuse to stream. This is now documented in a comment block above buildBeatInfoStart() so the property is preserved if the function is ever revisited.
StageLinQ: documented BeatInfo silence conditions
Independent of the protocol bytes, the same contributor surfaced four hardware-side conditions that can silence the BeatInfo stream from a Denon player even when everything on the network and protocol layers is correct:
- The deck has no track loaded, or is paused / not playing.
- The physical LINK button on the player is on.
- Decks are linked from Engine DJ (same effect as the physical LINK button).
- The player was already running when the consumer started listening; some firmware revisions only emit BeatInfo if a consumer was listening at the time the player booted, and a power-cycle of the player restores the stream.
These have been added in two places. A comment block above buildBeatInfoStart() in StageLinQInput.h lists them so future maintainers see them at the same spot they would land if debugging "BeatInfo connects but no packets ever arrive". A new troubleshooting entry under "Known Issues & Platform Notes" in the README ("StageLinQ: Playhead Does Not Advance") walks an end user through the same four checks in the order they should try them.
These are not fixes -- they are recorded prerequisites that the protocol does not advertise. The motivation is that the failure mode (StageLinQ view shows BPM and track name, but the playhead stays at zero) was not previously documented anywhere, and the four conditions had been collected only across community testing.
Generator: stop SMPTE at end of file, and resume audio with one click after a natural end
Two related bugs in the Generator's audio playback transport, reported together. Both stem from the fact that the generator's wall-clock SMPTE tick and the JUCE audio transport that drives the loaded file were keeping their state independently, with no path for one to learn that the other had reached a terminal condition.
The first bug: when a loaded audio file played to its end, the audio transport auto-stopped at EOF (which JUCE does internally), but the generator tick had no end-of-file check, so genCurrentMs kept advancing every tick and the SMPTE clock kept running past the end of the file as if nothing had happened. The only existing auto-stop was the user-defined Stop TC, which most users do not set when they are using the player as a transport. The tick now reads the loaded file's length and, if not looping and no Stop TC has fired first, clamps genCurrentMs to the end-of-file mark and stops the generator. A user-defined Stop TC still takes precedence, so the previous behaviour for that case is unchanged.
The second bug: after the audio transport had auto-stopped at EOF, clicking earlier on the waveform timeline moved the SMPTE position correctly but no audio resumed -- the user had to press Stop and then Play to get sound back, which restarted from the beginning and lost the position they had clicked. The intended behaviour, modelled after consumer media players, is that a click on the timeline after a natural end is interpreted as "play from here", in a single action.
The engine now distinguishes a natural end (the audio file ran out) from a user-initiated stop. When the EOF auto-stop fires, an internal genEndedAtEof marker is set in addition to transitioning to Stopped. The next call to setGeneratorPosition (which is what the waveform click handler calls) consumes that marker: if it is set, the seek transitions the engine straight to Playing and re-arms the audio player so the transport restarts at the seeked position. If the marker is not set (cold start, manual Stop, post-Pause scrub), the seek behaves as it always did and transitions to Paused, requiring a separate Play to start audio. The marker is cleared by every other explicit user transition -- Play, Stop, file change, input source change -- so it never survives a context switch and never causes spurious auto-play.
A second, smaller change in seekSeconds() itself adds defence in depth for the case where the audio transport has auto-stopped at EOF but the generator tick has not yet detected it (e.g. the tick is briefly delayed): if the player intent is "should be playing" (shouldPlay set, not user-paused) and the device is open, the seek now also re-engages the transport. start() is a no-op if the transport is already playing, so the in-flight seek-while-playing case is unaffected.
Files changed
Modified
StageLinQInput.h--kSmaaSubscribeconstant comment expanded to describe both directions of the opcode; the StateMap parser's "unknown subtype" debug log now also excludes0x07D2from the device side, treating it as a periodic catalogue announcement; a comment block abovebuildBeatInfoStart()records the eight-byte handshake constraint and the four field-reported BeatInfo silence conditions.TimecodeEngine.h-- generator tick now auto-stops the engine when the loaded audio file has played past its end (only when not looping, only when a user-defined Stop TC has not already fired). When the EOF auto-stop fires, an internalgenEndedAtEofmarker is set;setGeneratorPositionconsumes that marker so a click on the waveform timeline immediately after a natural end transitions toPlayingand resumes audio at the seeked position. The marker is cleared by every other explicit user transition (Play / Stop / file change / input source change) so it never survives a context switch.GeneratorAudioPlayer.h--seekSeconds()now re-engages the audio transport after the seek when the caller intent is "playing" (shouldPlayset, not user-paused, device open), so a seek that lands while the transport had auto-stopped at EOF resumes playback rather than leaving the file silent.README.md-- new troubleshooting entry "StageLinQ: Playhead Does Not Advance" under "Known Issues & Platform Notes", listing the four field-reported conditions that can silence the BeatInfo stream from a Denon player. CMake snippet bumped to1.9.9.BACKLOG.md-- removed the "StageLinQ real beat grid and detailed waveform" item, which had shipped in v1.9.8.Main.cpp-- version bump to1.9.9.
Super Timecode Converter V1.9.8
Generator presets: cue points for MIDI / OSC / Art-Net triggers
Generator presets now support cue points -- the same kind of mid-track triggers the TrackMap has had for a while, but anchored to the absolute SMPTE timecode generated by the preset rather than to a CDJ playhead.
A cue at 01:00:30:00 fires once when the generated TC reaches that mark. The trigger payload is identical to a TrackMap cue:
- MIDI -- Note On (note + velocity) and / or CC (controller + value), on a configurable channel.
- OSC -- address + typed args (
i:42 s:hello f:3.14syntax, same as TrackMap). - Art-Net DMX -- one-shot value on a single channel, sent via the configured trigger universe.
A Cues... button has been added to the preset editor, enabled when a preset is selected. It opens a dedicated cue-point editor window: list of cues + a detail panel for editing position / name / triggers, identical in feel to the TrackMap cue editor.
When the preset has an audio file attached, the cue editor also shows a mirror-style waveform strip above the table (matching the look of the Generator's main waveform view). Click anywhere on the strip to set a candidate cue position, or click and drag to fine-tune; the time display in the detail panel updates live. Pressing Add creates a new cue at the cursor position. Existing cues appear as numbered vertical markers on the strip; the selected cue is highlighted in amber. While the engine is playing the preset, a bright red playback cursor follows the audio in real time so you can place cues by ear -- start playback, listen, and click where each trigger should fire.
Why TC and not "ms from audio start"? It works for both modes the Generator has -- pure TC generation (no audio) and TC + audio playback. In both cases the engine clock is the absolute generated TC, so the cue position is unambiguous regardless of whether there's audio attached.
Dispatch semantics are different from the TrackMap path on purpose. A cue fires every time the generated TC advances across its position in play (strict crossing: previous tick TC < cue position <= current tick TC):
- Seek backward and play forward -- cues are crossed again and fire each time. This is the rehearsal-friendly behaviour the Generator's transport-mode UX wants, vs. the TrackMap path where each cue fires at most once per track because a CDJ scratch / jog should not re-trigger.
- Seek to exactly a cue position and play -- the cue is NOT fired, because the seek snaps the dispatch cursor to the destination and the next tick advances past the cue without crossing into it.
- Stop and play again -- transport returns to Start TC, the dispatch cursor follows, and cues inside the range fire afresh on the next play pass.
- Backward jumps (seek-back, stop, etc.) just update the cursor without firing anything.
Cues are persisted in the preset JSON and round-trip through Import / Export.
ProDJLink: new MASTER auto-follow mode for the player selector
The PLAYER dropdown for a ProDJLink-fed engine now offers a MASTER option alongside the existing fixed players (1-6) and crossfader-based modes (XF-A / XF-B). When MASTER is selected, the engine automatically follows whichever CDJ currently holds the DJM master flag -- so the DJ decides which deck drives timecode by tapping MASTER on the CDJ, the same way they'd hand off beat sync.
The behaviour is sticky in the same way XF mode is: STC stays on the current master while it remains master, switches when another CDJ takes master, and holds the previous selection across the brief drop the master flag often has during a track load (rather than flickering through "no source"). The status text reads MASTER P3 (where 3 is whichever player currently holds master), or MASTER: NO MASTER PLAYER if no CDJ is reporting master at all.
This is intended for shows where any of several decks can carry the timecoded track and the DJ wants explicit control over which one drives it. It complements XF-A/XF-B (follow the crossfader) and the fixed player choice (lock to one deck).
(StageLinQ engines don't yet expose a MASTER option in their selector -- the underlying isMaster state is already parsed from the protocol, so adding it is straightforward; let us know if it's useful for your workflow and it'll go into the next release.)
DJM-A9 detection on Windows
The DJM-A9 was being silently ignored by the PDL view on Windows: the bridge would announce itself, the A9 would never respond to subscribes, and no fader / VU / channel data would arrive. Other DJM models (V10, 900NXS2) accepted the Windows announcement and worked normally, which is why this regressed without anyone noticing for several versions. The A9's firmware is stricter and rejects identities that don't match the canonical bridge byte-for-byte.
The Windows path now matches macOS, which had been working correctly all along. Three bytes in the bridge keepalive / claim packets are corrected, and both platforms now share a single code path with no #ifdef for these fields. All DJM models continue to work; the A9 now appears in the PDL view and starts delivering fader / VU / channel-on-air data normally a few seconds after detection.
On-Air flag no longer flickers when the CDJ has "On Air Display" disabled
When a DJ disables the "On Air Display" option in the CDJ-3000's UTILITY menu, the CDJ stops setting its own on-air bit in the status packets it broadcasts -- but the channel is still genuinely on-air at the mixer, and the mixer continues to report it as such. STC was reading both sources into the same flag, and the two writers raced packet-by-packet, producing visible flicker on the player tile.
The mixer is now treated as the authoritative source. Whenever STC has heard from the DJM in the last 5 seconds (either via the 0x03 broadcast or the 0x29 unicast bridge channel), the CDJ's self-reported on-air bit is ignored. If the DJM disappears, the CDJ status takes over again as a fallback. Net effect: the flag tracks what's actually happening at the mixer regardless of how the CDJ has its display setting configured.
Crash on shutdown fixed
Closing the application could occasionally crash inside std::atomic<>::load() somewhere in the standard library. The two contributing causes:
- The
MainComponentdestructor was callingengines.clear()without first stopping per-engine resources that had their own threads or audio callbacks (Generator audio, Audio BPM analyser, Trigger MIDI / OSC). Each engine's destructor then had to tear those down on the fly, which left a small window where a callback or worker thread could read atomics whose owning engine was already partly destroyed. ~GeneratorAudioPlayer()relied onremoveAudioCallback()waiting for any in-flight audio callback to return. JUCE does that on most backends, but a handful of Windows audio drivers have historically delivered one final callback after the unregister returns, which on shutdown would land on already-destroyed atomics.
The MainComponent destructor now stops Generator audio, Audio BPM, and the Trigger output explicitly for each engine before engines.clear() runs. The audio player's destructor latches a shuttingDown flag before doing anything else, and all three audio callbacks (audioDeviceIOCallbackWithContext, audioDeviceAboutToStart, audioDeviceStopped) check that flag first and return immediately if set, so a stray late callback can no longer touch other atomics or locks.
DbServer parser regression fixed (the 1.9.7 PDL view crash)
In v1.9.7 (regression introduced in v1.9.6's dbserver hardening), opening anything that triggered a database response with blob data -- artwork JPEG, ANLZ waveform tags -- crashed inside juce::MemoryBlock::operator=. After the parser moved a blob into its result and cached the raw argument for "last argument seen" tracking, the cached copy ended up with data == nullptr but size > 0, which the next memcpy faithfully tried to read.
The parser now copies only the scalar fields (type / numericValue / ok) into the last-argument cache instead of the whole Argument struct, so the moved-from blob can never be read again. PDL view, waveform fetch, and any other path that pulls blob data from the CDJ database is stable.
TCNet now serves real waveform and beat grid data for StageLinQ engines
Previously, layers fed by a StageLinQ input had to fall back to a BPM-synthesised beat grid and an empty waveform when consumers (ChamSys MagicQ, Resolume, etc.) requested those datatypes via TCNet -- only ProDJLink layers got the real metadata. The StageLinQ database parser already had access to the same information; it just wasn't wired through to the TCNet output.
This release closes that gap. StageLinQ engines now feed:
- Overview waveform (datatype 16, Small Waveform Data) -- pulled from the Engine DJ database's
overviewWaveFormDataBLOB. The decoder already produces Pioneer ThreeBand order (mid / high / low, 3 bytes per entry), so the existing TCNet packer accepts it directly without any format adapter. - Real beat grid (datatype 8, Beat Grid Data) -- expanded from the database's sparse downbeat markers (which give a sample offset every 4 beats in 4/4) to the per-beat list TCNet expects. Each marker's region is divided by
numBeatsto derive the per-beat sample stride; the last marker's stride is held for any beats following it. Sample offsets are converted to milliseconds using the track's stored sample rate. Downbeats (every 4th beat in a bar) are flagged correctly via the existingsetLayerBeatGridheuristic.
Cache key reuses the same artist|title|durationSec format as the ProDJLink path. The key commit is gated on actually having data: if the StageLinQ database query is still in flight when a new track shows up (the query is asynchronous and runs ...
Super Timecode Converter V1.9.7
Audio playback bundled with Generator presets
The Generator can now play an audio file synchronised with the generated timecode. In show contexts where a CDJ is not available but the timecode still needs to drive a song, operators can run STC alone instead of pairing it with a separate audio player and trying to keep the two in sync manually.
Per-preset audio configuration
Each Generator preset carries two new fields alongside its existing Start TC / Stop TC:
- Audio file -- absolute path to a WAV / AIFF / FLAC / OGG / MP3 file. Empty means "no audio for this preset".
- Loop -- when checked, the audio file restarts from its own beginning each time it reaches its end. The generated timecode keeps advancing toward Stop TC regardless. When unchecked, the audio simply ends and the generator continues silently to Stop TC.
The Generator Preset Editor was extended with a new "Audio" column in the table (showing the file basename plus a "(loop)" marker when applicable), a path field, a ... Browse button and an X Clear button. Imported / exported preset JSON files include the audio fields. The audio file association is global to the preset: any engine that activates the preset loads the same file.
Per-engine audio playback device, sample rate, buffer and volume
Each engine has its own audio device for the playback, independent from the LTC output so that the LTC bitstream and the programme audio do not have to share an interface. The Generator panel now exposes:
- AUDIO PLAYBACK toggle and device combo.
- AUDIO CHANNEL -- output routing (Stereo on ch 1+2, or mono mix to a specific channel).
- FILE CHANNELS -- which channel(s) of the audio FILE to use: Stereo (L+R), Left only, or Right only. This handles the industry-standard practice of carrying programme audio on the left channel and LTC on the right; selecting "Left only" silences the LTC side and centres the audio in the stereo image. The output routing remains independent.
- SAMPLE RATE and BUFFER SIZE -- per-engine overrides for the audio playback device. "Default" inherits the global preferred values (the existing top-level combos); explicit values are useful for cards that don't share a clock between LTC and music outputs.
- VOLUME -- linear 0..1.5 slider (1.0 = unity, headroom available for low-level recordings). Applied via the JUCE transport gain so a volume change does not invalidate the audio reader's read-ahead buffer.
All these settings persist per engine across sessions. Switching the active engine reflects the new engine's values in the panel immediately.
Sync model and transport
The Generator's wall-clock tick remains authoritative for the timecode. Transport state changes:
- Play -- audio resumes from the current position. When coming from Stop, the audio is seeked to
(genCurrentMs - genStartMs) / 1000.0first; when resuming from Pause, no seek is performed (the read-ahead buffer is preserved, so resume is instant). - Pause -- triggered by a fast logical-pause flag rather than
transport.stop(). The flag is checked by the audio callback before each block, so the message thread returns in nanoseconds. Without this, stopping aBufferingAudioSourcemid-MP3-frame could briefly freeze the UI. - Stop (manual or auto-stop at Stop TC) -- audio is silenced and rewound to the start of the file. Resuming from Stop reseeks.
Loop behaviour: when the audio file ends and Loop is on, the file restarts from 0 while the timecode continues toward Stop TC. With Loop off, the audio simply ends silently. No drift correction is applied between the wall clock and the audio device clock; over a typical track length the divergence is in the microseconds.
Asynchronous file loading
Switching presets while another track is loaded triggers a file change. With MP3 in particular, AudioTransportSource::setSource() synchronises with the file reader's background thread, which can be in the middle of decoding a frame; doing the load on the message thread used to freeze the UI for tens to hundreds of milliseconds (especially right after a Pause, when the read-ahead buffer is still being filled).
The load now runs on a dedicated LoaderThread inside the player. The UI returns instantly; the new file becomes active when the thread finishes attaching it. Multiple rapid requests (e.g. holding NEXT) are coalesced -- only the most recent file is processed.
A shouldPlay intent flag bridges the gap between asynchronous load and the user's intent: if play() is called while a load is still pending, the loader thread starts the transport itself once the source is attached, so GO works regardless of timing.
Show Lock
Configuration controls (toggle, device / channel / SR / buffer combos, file-channels selector, the preset editor itself) are blocked during Show Lock. Transport buttons (Play / Pause / Stop), GO, navigation arrows and the volume slider stay operational, since they are needed mid-show.
MP3 support
MP3 decoding is enabled in this build via the JUCE_USE_MP3AUDIOFORMAT Projucer flag. When a load fails the player records a specific reason (UNSUPPORTED FORMAT: <ext>, FILE NOT FOUND, EMPTY OR CORRUPT FILE) which is surfaced both in the Generator panel's status label and in the waveform view's empty-state text, so configuration mistakes are diagnosable without a debugger.
OSC
The existing /stc/N/gen/preset command (which loads and plays a named preset) now also pushes that preset's audio file into the engine's audio player, so a QLab cue or Companion button can switch tracks and timecode together.
Waveform display window
The Generator audio playback gets a visual companion: a waveform view of the loaded file with a moving playhead cursor and click-to-seek. Two presentations of the same component:
- Mini waveform -- a 48-pixel-tall overview embedded in the Generator panel, visible when AUDIO PLAYBACK is on. Non-interactive; clicking it opens the floating window. The cursor moves in real time as the generator advances.
- Floating waveform window -- opened with the new
WAVEFORMbutton (or by clicking the mini view). Shows the filename andcurrent / totalfile time, a large absolute-TC display in amber (28pt), the full-width waveform with file-time markers above (0:00 / 1:00 / 2:00…) and absolute-TC markers below (HH:MM:SS:FF, computed from each engine's Start TC and active FPS), transport buttons, preset navigation, an EDIT button to open the preset list, and a volume slider. The window is non-modal so MainComponent stays interactive while it is open. Default size 900x280; size and position are persisted insettings.genWaveformBoundsand restored on next launch (with the centre clamped to a still-existing display so the window cannot reappear off-screen if a monitor was removed).
Stereo rendering
Stereo files are drawn DAW-style (Ableton / Logic / Reaper): a single horizontal track sharing one centreline, with the left channel emerging upward and the right channel downward. Implementation: each channel is drawn into the full waveform area so both bipolar shapes share the same vertical centre, then a half-height clip region restricts each channel to its side. Mono and >2-channel files draw as a single bipolar shape over the full area.
Click-to-seek and hover preview
Clicking anywhere on the waveform in the floating window seeks both the timecode and the audio playback to that position. Dragging acts as continuous seek (scrubbing).
While the cursor is over the waveform, a semi-transparent grey hover line shows where a click would land, with a tooltip displaying the M:SS HH:MM:SS:FF of that point (file time and absolute TC simultaneously). The tooltip auto-flips to the left side of the cursor near the right edge so it never gets clipped.
The seek behaviour is state-aware:
- Playing -- audio continues playing from the new position.
- Paused -- audio stays at the new position, paused.
- Stopped -- transitions to Paused, preserving the seeked position so the next
PLAYpress resumes from there instead of resetting to Start TC.
Seek is blocked while Show Lock is engaged; the transport buttons themselves remain operational under Show Lock.
In-window preset navigation
The floating window includes < (prev) and > (next) buttons that route through the same combo as the panel arrows, so behaviour (load new preset's TC range; refresh waveform when idle; do not interrupt audio when playing) is identical regardless of which UI surface is used. An EDIT button (right-aligned to differentiate it from transport) opens the preset editor without having to leave the window. EDIT is blocked under Show Lock.
Volume
The volume slider in the floating window is bidirectionally synced with the panel slider: moving either updates both, and both are stored in the same per-engine setting. The window's slider auto-hides when the window is too narrow to keep transport readable.
Multi-engine
Both the mini view and the floating window always reflect the currently selected engine. Switching tabs rebinds them to the new engine's audio player without reopening the window. Removing the displayed engine clears the views before the engine is destroyed and the views rebind to the new active engine afterwards.
Behaviour notes
- Hot-unplug. When the audio interface is removed mid-show, JUCE's
AudioDeviceManagerreports a callback failure; STC logs and continues without audio. The generated timecode is never affected by audio errors. - No time-stretching. The audio plays at its native rate. The Generator does not have a tempo multiplier in the playback path; if the audio file's tempo does not match the desired BPM the file must be pre-prepared.
- Path portability. The audio file path stored in a preset is ...
Super Timecode Converter V1.9.6
TCNet output: deep compatibility work for ChamSys MagicQ
This release is almost entirely focused on bringing STC's TCNet output to feature parity with the official PRO DJ LINK Bridge. ChamSys MagicQ in particular requires a very specific set of behaviours that go beyond what the published TCNet spec describes; STC now matches those observed behaviours so MagicQ tracks the beat, displays the waveform, and correctly handles source changes without entering an error state.
Beat tracker now updates in MagicQ
Previously MagicQ's beat tracker would not move when STC was the TCNet master. The cause was that MagicQ requires a Beat Grid Data response (datatype 8) before it trusts the node and starts displaying the beat -- without that response it keeps polling indefinitely.
STC now responds to Beat Grid requests with synthesised beat positions covering the full track duration. When the source is a CDJ with rekordbox-analysed audio, the real beat grid from the analysis (with exact timestamps and downbeat positions) is sent. When the source is the Generator or another non-DJ input, the grid is synthesised from the current BPM with downbeats at every fourth beat (4/4 time assumed). Multi-packet output is supported so beat grids long enough to cover the full default 2-hour duration fit within the protocol's per-packet limits.
Waveform display in MagicQ
Small Waveform Data (datatype 16) is now served in response to MagicQ's request. When the loaded track has an analysed waveform on the CDJ (preview or detail), STC reads it from the dbserver cache and converts it on the fly to the PWV5 format MagicQ expects:
- CDJ-3000 ThreeBand (3 bytes per entry): the three frequency-band heights are scaled from 5-bit (0-31) to 8-bit (0-255) and the dominant band determines the colour code.
- NXS2 ColorNxs2 (6 bytes per entry): the magnitude bands feed the height; the RGB colour is packed into a single byte.
If no analysed waveform is available for the loaded track, STC sends an empty (flat) waveform with valid headers so MagicQ still considers the node compliant. Resampling to the 1200-entry target preserves visual proportions regardless of the source resolution.
Time Sync handler
STC now responds to TCNet Time Sync requests (message type 10) from receivers. When MagicQ initiates a sync round (STEP=1), STC echoes back with STEP+1, our local microsecond timestamp, and the initiator's original timestamp in the Remote Timestamp field so it can compute round-trip delay.
Source change without disrupting the receiver
Previously, switching a TCNet-enabled engine from one source to another (Pro DJ Link to Generator, for example) would cause MagicQ to enter an error state, refusing to update any field until the console was restarted.
Two changes fix this. First, each layer now keeps its identity once it has been activated even when the source disappears. The layer state transitions to idle but Metrics keep flowing, matching the way the Bridge keeps emitting Metrics for a CDJ that has just been disconnected. Second, the Track ID is now distinct per source type, so when the user switches sources MagicQ sees the change as a new track and refreshes its cached Beat Grid and Waveform instead of holding onto stale data from the previous source.
Track Length / default duration
The "no track" duration sent for sourceless engines (Generator, MTC, Art-Net, LTC) is now 2 hours. Previous releases used various values up to 24 hours; empirical testing with MagicQ established that:
- 24h, 23:59:59, 6h, 4h, 3h and 2h30 are all rejected as "invalid duration"
- 2h00 and 1h are accepted
The hard limit appears to be somewhere between 2h00 and 2h30. STC settles on 2h to give offset-heavy shows more headroom than the previous 1h while staying inside the validated range.
TCNet Metrics Packet field corrections
Several Metrics fields had values outside the range the Bridge actually sends, which made consumers reject the packet as inconsistent:
- Speed (bytes 40-43) was 32768 (out of the spec's 0-20000 range). Fixed to 1048576 (2^20 fixed-point representing 100% playback) which matches the values observed from the real Bridge.
- Pitch Bend (bytes 116-117) was 32768 (treated as +100% pitch by some consoles). Now 0 for neutral playback, matching Bridge behaviour.
- Byte 45 is now set to the SMPTE framerate (30) -- the Bridge fills this byte that the spec marks as reserved.
Layer State enum values per spec
The Layer State byte in the Time Packet (96-103) and Status Packet (42-49) is now sent as the spec-defined enum value (0=IDLE, 3=PLAYING, 5=PAUSED, etc.) without bit-field modification. This restores Resolume's ability to distinguish play from pause, which had been broken when an additional master flag bit was being OR-ed in.
Sync Master byte
The Sync Master byte (Metrics byte 29) now matches the Layer ID, satisfying a coherence check that MagicQ performs on each Metrics packet. Without this match the receiver treats the node as untrusted and ignores the beat marker.
Other changes
- Beat Number (Metrics bytes 57-60) is now sent (was always 0).
- Total Time per layer in Time Packet (bytes 56-87) is now sent (was always 0). MagicQ uses this to bound the playhead position.
- Per-layer Time Code block (bytes 106-153) is now populated with HH:MM:SS:FF and the TC State byte (0=Stopped, 1=Running). Without this, receivers saw "timecode stopped at 00:00:00:00" and ignored the advancing timecode.
- Byte 104 in Time Packet is now correctly left at 0 (was previously 1; spec marks it RESERVED).
- Beat derivation for non-DJ sources: when STC has a BPM but no native beat counter (Generator with audio BPM analyser, MTC, Art-Net, LTC), beat marker and beat number are now derived from the playhead position assuming 4/4 time. The CDJ's native beat counter is still used when available.
- Generator artist name: TCNet metadata now reports "Generator" (not "System Time") as the artist for engines whose input source is the internal Generator.
- Status Packet: layer source / status / track ID slots are filled for any layer that has ever been active, providing a stable picture of the node's state to broadcast receivers.
Compatibility
- ChamSys MagicQ: beat tracker, waveform display, and source-change handling all working.
- Resolume Arena/Avenue: continues to work as before; the Metrics Speed and Pitch Bend changes match what Resolume already expected.
- Other TCNet-aware consoles: should benefit from the spec-compliant enum values and properly populated Time Packet fields. Field reports welcome.
Known limitations
- Big Waveform Data (datatype 32) is not yet implemented; STC responds with an Error packet to such requests. MagicQ falls back to the Small Waveform we send.
- Cue Data (datatype 12) is not yet implemented; same behaviour as above.
- StageLinQ: real beat grid and waveform feeds from the StageLinQ database are not wired up yet; engines using StageLinQ get the BPM-synthesised fallback. ChamSys still gets a working beat display.
CDJ-2000NXS2 fixes
Three separate issues affecting NXS2 operation were identified from community Wireshark captures and are addressed in this release. Each is rooted in NXS2 firmware behaviour that differs from CDJ-3000.
Crash after extended use with NXS2
STC could terminate after a period of NXS2 operation, typically between 10 and 30 minutes in. Root cause: the NXS2 dbserver occasionally delivers a stale response whose transaction ID does not match the current request -- an internal session-state artefact in the firmware. STC's existing safeguard correctly rejects the stale response and closes the connection for a clean reopen, but the waveform-format fallback path (which tries a second format when the first fails) did not re-verify the connection before sending its follow-up request. On NXS2 that sequence hit a freshly-closed socket and the resulting access violation was terminating the process.
Fixed by verifying connection health at two points: before any fallback is dispatched inside the waveform and detail-waveform wrappers, and defensively at the entry of each single-format query. The existing abrupt-close response to a firmware hiccup is preserved -- the crash path is simply no longer exposed to it.
LTC running while NXS2 is paused (CDJ mode)
On NXS2 in CDJ mode (as opposed to Vinyl mode), pressing pause clears the player's PLAYING status flag immediately but freezes the reported motor-speed field at the pitch-fader value -- for example 1.0025x for a +0.25% fader setting. CDJ-3000 behaves differently: it applies a 4-5 second deceleration ramp even in CDJ mode, so its reported speed decays naturally. STC's source-active gate was reading motor speed alone, which correctly closes on a CDJ-3000 pause but never closed on an NXS2 CDJ-mode pause. LTC therefore continued to stream while the disc was paused.
The source-active gate is now model-aware. CDJ-3000 continues to use the speed-based gate it has always used -- its deceleration ramp must be allowed to play out, and a flag-based gate would truncate the ramp abruptly. Non-3000 players additionally require the PLAYING status flag to be set. On NXS2 in Vinyl mode the flag clears after speed has already dropped, so the additional gate is a no-op; in CDJ mode it correctly closes the gate the moment pause is pressed.
Dbserver parser hardened
The dbserver parser now handles a specific edge case in which a binary-blob argument is advertised with a zero-length indicator immediately before it. The protocol documentation defines this as a valid empty blob that carries no bytes on the wire, but our parser previously attempted to read a type byte for it anyway, consuming one byte of the next argument and desynchronising the rest of the message. This is a defensive harde...
Super Timecode Converter V1.9.5
dbserver robustness for CDJ-2000NXS2
Packet captures provided by trickofdjequip with CDJ-2000NXS2 hardware revealed several robustness issues in how STC talked to the CDJ's dbserver. The changes below address each one, backed by the captures.
1. Fix teardown sometimes emitting RST instead of a clean close
The polite dbserver teardown added in v1.9.4 was not always producing a clean close. In v1.9.4 captures, roughly 4 out of 10 close sequences ended with the Windows kernel emitting an RST immediately after the teardown bytes (same millisecond), instead of a clean shutdown exchange.
Cause: JUCE's StreamingSocket::close() calls closesocket() directly, and on Windows this can produce an RST if data is still unacked in the TCP send buffer. The teardown bytes were still in flight when we closed.
The fix is to wait for the CDJ's own close to arrive before closing our side. After writing the teardown we now call waitUntilReady(true, 200) -- the CDJ typically responds within 2 ms in the captures -- drain any pending bytes, and only then close. Result: clean shutdown on every close sequence.
A companion closeAbrupt() variant (no teardown) is used in cases where writing more bytes would be counterproductive: when the CDJ has already rejected our setup, when the player has left the network (nowhere to send teardown), and after a read failure (firmware already failed to respond; don't pile more bytes on top).
2. Shorter idle timeout on dbserver connections (30s → 1s)
Previously STC kept dbserver connections open for 30 seconds after the last query. In practice this meant that during a set a single connection could stay open for 2-5 minutes continuously while the DJ cycled through tracks.
Captures show a firmware state on NXS2 where, after a minute or so of active queries, the dbserver stops returning payload responses while the TCP socket remains alive -- the CDJ keeps sending bare ACKs but no data. The correlation between long-lived sessions and entry into this state is strong in the captures. Closing more quickly should reduce exposure.
1 second after the last query is now the threshold. The overhead of reconnecting between track loads is negligible (~2 ms on a LAN) thanks to the port cache added in v1.9.4, and in normal operation queries come in tight bursts followed by long periods of no activity -- the new timeout rarely fires mid-burst.
3. Close the connection after a read failure
Previously, if a dbserver read timed out, STC left the connection open and retried on the next query. One of the captures showed the direct consequence of this: 40+ seconds of STC sending queries once a second to a dbserver that was replying only with empty TCP ACKs. lastActivityTime was being re-armed by each failed attempt, so the idle closer never fired either.
STC now explicitly closes the socket inline after any failed dbserver read, so the next query opens a fresh connection. Combined with the shorter idle timeout, this ensures STC will not hammer a silent dbserver beyond a single failed query.
4. Validate transaction ID on responses
Previously STC accepted any dbserver response as valid regardless of its transaction ID. The protocol carries a per-request transaction ID precisely so the client can verify that a response matches the request just sent. If the CDJ's firmware ever got into a state where responses were returned out of order (answer to query N delivered when we expected answer to query N+1), we would silently attach the wrong metadata to the wrong track.
STC now verifies the transaction ID on every response it reads. On mismatch the response is rejected as if the read had failed, which with the other changes in this release means the connection is closed and the next query opens fresh.
5. Defensive cap on menu batch size
When rendering a metadata response, STC previously requested all items reported by the CDJ in a single batch. Single-track metadata is typically 14-15 items so this is normally fine, but if the firmware ever reports a bogus count we want to bound the damage rather than enter a huge read loop. A cap of 64 items is now applied defensively; in normal operation this never kicks in.
TCNet BPM for non-DJ sources
Previously TCNet output only carried BPM when the engine's input was ProDJLink or StageLinQ. For other inputs (MTC, Art-Net, LTC, Generator, HippoNet) the BPM field was always zero. Resolume ignores this field, so it was harmless for the typical use case.
ChamSys lighting consoles can read BPM from TCNet, so STC now falls back to the engine's audio BPM analyser (BTT) when the input is not a DJ source and the audio analyser is running on that engine.
The DJ path is unchanged -- a CDJ that reports BPM 0 (paused, no track loaded) is not papered over with the audio analyser. Only non-DJ sources trigger the fallback.
To use: enable the audio BPM analyser on the engine (configure an audio input and channel), and configure TCNet output as usual.
Files Changed
DbServerClient.h- Teardown waits for CDJ's close before closing our side; drains pending bytes
closeAbrupt()variant (no teardown) for use after errors, on network loss, and after setup rejectionreadMessageOrInvalidate()helper: closes the connection if the read fails or the txId is unexpected- Idle timeout lowered 30s → 1s
- Defensive cap on metadata item count at 64
MainComponent.cpp-- Audio BPM fallback for TCNet output on non-DJ sourcesMain.cpp-- Version bump to 1.9.5README.md-- Version bump
Super Timecode Converter V1.9.4
dbserver connection handling
Two small changes to how STC manages its TCP connections to the CDJ dbserver ports (1051 on NXS2, 1052 on CDJ-3000).
1. Cache discovered db port per player
The dbserver port for each CDJ is now cached after the first successful discovery on port 12523. Previously every reconnect repeated that discovery handshake before opening the real socket. The port doesn't change for a given CDJ, so this was wasted TCP traffic. The cache is cleared if a player disconnects via invalidatePlayer.
2. Polite dbserver teardown on close
When STC closes its connection to a CDJ's dbserver, it now sends the protocol-level teardown message (magic TxID 0xfffffffe, type 0x0100, zero arguments) before closing the TCP socket. The CDJ then closes the connection from its side.
Files Changed
DbServerClient.h— Cached db port per player IP; polite dbserver teardown before socket closeMain.cpp— Version bump to 1.9.4README.md— Version bump
Super Timecode Converter v1.9.3
New Feature — On-Air Gate
A new ON-AIR ONLY toggle in the output panel makes the engine only produce active timecode when the current CDJ is flagged on-air by the DJM mixer. This lets the DJ load, cue and preview tracks on a deck without triggering downstream timecode until they bring the deck in.
The on-air flag is computed by the DJM itself from the full mixer state (channel fader, cross-fader, EQ kill, mute), transmitted to each CDJ, and is what lights up the ON AIR indicator on the CDJ display. Using it here means STC follows exactly what the DJ sees — no threshold or channel mapping needed.
Only available when the input source is Pro DJ Link.
New Feature — Sortable Track Map Columns
The Track Map Editor now has a sortable # column showing each track's position in the imported playlist order (or "--" for tracks not in any playlist). Plus Artist, Title, and Offset columns are clickable to sort — just like Finder / Explorer. Click a column header to sort ascending; click again to sort descending.
The default view sorts by playlist position ascending, so when you import a playlist the tracks appear in setlist order. To return to that view after sorting by any other column, click the # header.
Artist sort uses Title as tiebreak. BPM, Trig, Cues and Notes remain non-sortable.
Bug Fix — Frame rate reset to 30 fps on every restart when using ProDJLink / StageLinQ
When using ProDJLink or StageLinQ as the input source, the configured frame rate (e.g. 25 fps) was silently overwritten with 30 fps every time STC started up, or when settings were restored from a backup.
The cause was a line in startProDJLinkInput / startStageLinQInput that set currentFps = outputFps on every start. Since outputFps defaults to 30 fps and is only updated when the user explicitly enables FPS Conversion, the engine's current frame rate was being reset to 30 every time the input source was started — which happens automatically at boot.
The offending line has been removed. currentFps now stays at the value loaded from settings (or the user's button selection).
Bug Fix — Playlist import failed when folder names contain spaces
When importing a rekordbox XML playlist located inside a folder whose name contained a space, the import returned no tracks. The path parser used juce::StringArray::addTokens which treats its second argument as a set of separator characters, not as a substring — so " / " was interpreted as "separate on space OR slash", splitting "My Folder / Saturday" into four tokens instead of two.
Replaced the character-based split with a proper substring search using indexOf(" / "). Playlists in folders with spaces in their names now import correctly.
Bug Fix — Race condition in Pro DJ Link player invalidation
Fixed a latent race condition between the ProDJLink thread and the DbServerClient worker thread. When a CDJ disappeared from the network, the onPlayerLost callback (running on the ProDJLink thread) would close TCP sockets directly while the DbServerClient worker thread could be in the middle of a socket->write() using those same sockets — a classic use-after-free that could cause crashes when players drop off the network during active metadata queries.
Invalidation is now deferred: the ProDJLink thread queues the player IP in a lock-protected list, and the DbServerClient worker thread processes the queue between TCP queries (at the start of each worker loop iteration, max ~500ms latency). Since connection close and query execution now happen on the same thread, there is no race.
This was a rare bug — it required a CDJ to disconnect at the exact moment of an active TCP query — but when it did happen, it could crash STC. Most users would never have hit it, but it's the kind of bug that manifests during long shows with unstable network hardware.
Bug Fix — Safer shutdown thread ordering
Reordered shutdown sequences (application exit, input source switching, and auto-stop of unused shared inputs) so the packet receiver threads (ProDJLink, StageLinQ) are joined before their corresponding database clients are stopped or their callbacks are cleared. Prevents a std::function race that could fire when a player disconnect coincided with a shutdown.
Bug Fix — Track Map import preserves existing entries
When importing a rekordbox XML collection, the "Import Tracks" flow no longer overwrites existing Track Map entries. Previously, if a user manually selected a duplicate track in the Import Preview dialog, the import would reset that track's cues, triggers, offsets, and notes. Now existing entries are preserved — the import only adds new tracks. The summary count reflects only the actual additions.
This aligns with the "Apply Playlist Order" behavior introduced in v1.9.2: imports never destroy user configuration.
Files Changed
TimecodeEngine.h— On-air gate state +isOnAirGateOpen()helper + hook intosourceActive; removedcurrentFps = outputFpsoverwrite instartProDJLinkInput/startStageLinQInputAppSettings.h— On-air gate serialization; playlist path split fix (indexOf instead of addTokens)DbServerClient.h— Deferred invalidation viapendingInvalidateIPsqueueMainComponent.h/MainComponent.cpp— On-air gate toggle in output panel; reordered shutdown sequencesTrackMapEditor.h— Sortable Artist/Title/Offset columns; Import Preview preserves existing entries; corrected summary countMain.cpp— Version bump to 1.9.3README.md— Version bump
Super Timecode Converter v1.9.2
Track Map — Playlist import from rekordbox XML
When importing a rekordbox XML file that contains playlists, STC now shows a playlist picker dialog with two options:
Apply Playlist Order
Reorders the Track Map to match the playlist sequence. Existing tracks keep all their configuration (cue points, triggers, timecode offsets, notes). Tracks from the playlist that don't exist in the Track Map are added as new entries. Tracks not in the playlist remain in the Track Map but appear after the playlist tracks (sorted alphabetically).
This is the main use case: the DJ prepares a setlist playlist in rekordbox, the technician imports it into STC to reorder the Track Map before the show — all pre-programmed cues and triggers stay intact.
Import Tracks
Opens the Import Preview dialog where you can select which individual tracks to import from the entire XML collection. Existing tracks are deselected by default to prevent accidental overwrites.
Import Preview improvements
- Tracks that already exist in the Track Map are now deselected by default — prevents accidentally overwriting existing cues and triggers
- New Select New button — quickly re-selects only new tracks after using Select None
- Offset column hidden for rekordbox XML imports (rekordbox doesn't export timecode offsets). The column still appears when importing from STC's own JSON format.
Technical details
- Track order is stored as a
sortOrderfield per entry (persisted intrackmap.json, default 0) - Editing a track (offset, triggers, cues) preserves its playlist position — no reordering on save
- Matching uses artist + title with duration-tolerant fallback (XML duration may differ from CDJ by ±1 second)
- Nested playlist folders are supported (shown as "Folder / Playlist" in the dropdown)
- Fully backward-compatible: existing trackmaps load and display identically without any migration
Super Timecode Converter v1.9.1
Pro DJ Link — Critical stability fixes for CDJ-2000NXS2
Three bugs discovered from a full-show Wireshark capture (37 minutes, 2× CDJ-2000NXS2) that together caused metadata failure and CDJ freezing. These fixes also improve stability on CDJ-3000 and all other Pro DJ Link hardware.
Fixed: Double notify causing CDJ subscriber accumulation
The bridge notify packet (0x55) was sent from two sockets simultaneously — statusSock (port 50002) and bridgeSock (ephemeral port). CDJs register each source port as a separate subscriber and send status updates to both, doubling the return traffic (10–13 pps instead of the normal 5 pps). When STC's internal socket changed port during the session, ghost subscribers accumulated — the CDJ never cleans up old subscribers, so traffic increased to 3× normal over time.
Impact: Both CDJ-2000NXS2 players froze for 22 seconds during a live show at the 15-minute mark. The accumulated status traffic overwhelmed the CDJ network stack.
Fix: Notify now sends from a single socket (bridgeSock with fallback to statusSock), matching Pioneer Bridge's own behavior. The subscribe packet (0x57) already used this pattern correctly.
Fixed: Port 65535 accepted as valid database port
When STC queries a CDJ-2000NXS2 for its database server port via the port discovery service (TCP 12523), the CDJ sometimes returns 0xFFFF (65535) meaning "no service available". STC's validation (port > 65535) had an off-by-one error that accepted 65535 as valid, then attempted TCP connections to port 65535 — which always fail.
Impact: Metadata took 15 minutes to load. STC wasted 5 connection attempts on an invalid port before falling back to a working path.
Fix: Validation changed to port >= 65535.
Fixed: Zombie database connections blocking CDJ metadata
Database connections (DbServerClient) had no idle timeout. A connection established at startup could remain idle for 10+ minutes, exchanging TCP keepalive probes every 10 seconds without transferring any data. CDJ-2000NXS2 has limited NFS connection slots — the zombie connection occupied a slot, preventing actual metadata requests from going through.
Impact: Metadata was blocked for the first 10 minutes of the session.
Fix: Added 30-second idle timeout (kIdleTimeoutMs). Connections with no activity for 30 seconds are automatically closed, freeing CDJ resources.
Network — Shared input cleanup
StageLinQ and Pro DJ Link shared inputs now automatically stop when no engine uses them as input source. Previously, if an engine was configured for StageLinQ and later switched to Pro DJ Link, the StageLinQ thread continued broadcasting discovery packets (1 pps on UDP 51337) for the entire session. Same for Pro DJ Link keepalives when all engines switched away.
Impact: Unnecessary broadcast/multicast traffic on the CDJ network is eliminated.
HippoNet Input — Temporarily disabled
The HippoNet input (Hippotizer integration) is temporarily hidden from the UI pending hardware validation with a real Hippotizer system. All code is preserved and will be re-enabled in a future release.
Settings that had HippoNet as input source will automatically fall back to Generator on launch.
Protocol improvements developed for this release (will take effect when re-enabled):
- Variable block size support (18-byte and 26-byte TC blocks)
- Peer announcement broadcast on UDP port 6092
- HippoEngineHost connection detection for correct subscribe routing
Bug Fix — Linux compilation
- Fixed ambiguous
int64_tcast fromjuce::varinAppSettings.h— changed tojuce::int64for GCC compatibility
Pro DJ Link — Connection hardening
Additional robustness improvements to the database connection layer, addressing patterns observed in long-running sessions with CDJ-2000NXS2 hardware.
Port discovery retry
The dbserver port discovery query (TCP 12523) now retries up to 3 times with 1-second delays. Players may not be ready to respond immediately after booting or loading media — a single failed attempt previously caused metadata to be unavailable until the next track load.
Player disappearance cleanup
When a CDJ disappears from the network (10 seconds without keepalive), STC now immediately closes all TCP connections and clears cached metadata for that player. Previously, stale connections could persist indefinitely, holding CDJ resources and causing issues when the player reconnected.
TCP_NODELAY on database connections
Database server connections now set TCP_NODELAY to ensure each protocol message is sent as a single TCP segment. Some players fail to parse messages that arrive split across multiple packets due to Nagle's algorithm.
Files Changed
ProDJLinkInput.h— Single-socket notify;onPlayerLostcallback on GC timeoutDbServerClient.h— Port ≥65535 validation; idle timeout (30s); port discovery retry (3×); TCP_NODELAY; includes for socket optionsMainComponent.cpp— Shared input auto-stop; HippoNet input hidden;onPlayerLost→invalidatePlayerwiring; layout skip for hidden buttonsHippotizerInput.h— Protocol improvements (hidden, pending validation)AppSettings.h— Linux compilation fixMain.cpp— Version 1.9.1README.md— Updated
Super Timecode Converter v1.9.0
New: Timecode Generator with Presets & OSC Control (replaces System Time)
The "System Time" input source has been replaced by a full Timecode Generator with two modes, a preset system, and OSC remote control.
Clock Mode (default)
- Reads the system wall clock — identical to the old "System Time" behavior
- Always active, no transport controls needed
- Useful for scheduled programming tied to real-world time
Transport Mode
- PLAY / PAUSE / STOP transport buttons with color-coded state (green = playing, amber = paused)
- Start TC — configurable starting timecode (HH:MM:SS:FF). Play begins from this position; Stop resets to it.
- Stop TC — optional ending timecode. Generator auto-stops when reached. Set to 00:00:00:00 for freerun.
- Previous / Next navigation buttons to step through presets
- Timecode values are automatically normalized (e.g. typing 00:70:00:00 becomes 01:10:00:00)
Generator Presets
- Named timecode presets with Start TC and Stop TC, stored in
generator_presets.json - Preset selector — dropdown to pick a preset, fills Start/Stop TC fields on selection
- GO button — loads the selected preset and starts playback immediately
- Preset Editor — table-based editor window with Add, Save, Delete, Clear All
- Import/Export — presets can be saved to and loaded from external JSON files
- OSC command reference — built-in help panel (OSC ? button) with examples for QLab, Resolume, Companion
- Editor window saves/restores its position and size across sessions (multi-monitor aware)
- Presets included in configuration backup/restore
OSC Remote Control
- OSC IN toggle with configurable network interface and port (default 9800)
- Receives standard OSC 1.0 messages over UDP
- All commands require an engine number (no ambiguous default):
/stc/1/gen/play/stc/1/gen/pause/stc/1/gen/stop/stc/1/gen/start "HH:MM:SS:FF"— set start timecode/stc/1/gen/stoptime "HH:MM:SS:FF"— set stop timecode/stc/1/gen/clock 0|1— switch transport/clock mode/stc/1/gen/preset "NAME"— activate preset by name and play
- Replace
1with engine number 1–8 to target a specific engine - OSC input is global (not per-engine) — one listener controls any engine
- Show Lock blocks configuration but not transport/GO commands
- Status label shows LISTENING/BIND FAILED feedback
Backward Compatibility
- Settings files with "SystemTime" load correctly
- Default behavior unchanged: clock mode enabled by default, OSC input disabled
New: HippoNet Timecode Input (Green Hippo Hippotizer)
Added HippoNet as a new timecode input source for Green Hippo Hippotizer media servers.
- Protocol: UDP port 6091, proprietary 42-byte packets at ~82Hz. Magic header
c6 5e e5 00, milliseconds since midnight as uint32 LE. - Multi-layer support: Hippotizer can send multiple timecodes in a single packet (variable packet sizes: 42/60/78/96 bytes). STC parses all layers and presents TC 1 / TC 2 channel selector — matching Hippotizer's TC layer numbering.
- Auto-Discovery: listens on UDP port 9009 for Hippotizer broadcast announcements. Displays machine name and firmware version in status.
- Network interface selector with fallback to all interfaces.
- Protocol implementation based on Wireshark capture analysis of Hippotizer v4.8.4.23374 firmware, validated with Hippotizer PLAY (free version).
New: Next Cue Countdown
Live countdown to the next upcoming cue point below the waveform: ▶ NEXT: DROP in 1:05. Color changes amber → orange → red as the cue approaches. Works with both ProDJLink and StageLinQ.
New: StageLinQ Cue Point Parity
- Quick cues from Denon Engine database displayed as colored triangles on waveform
- TrackMap auto-populated from Engine database quick cues (up to 8, A-H labels)
- Track duration now set on StageLinQ waveform (fixes missing minute markers)
Bug Fixes
- CDJ-2000NXS2 single-CDJ metadata:
suggestDbPlayerNumber()now picks highest free number (4→1) instead of self-referencing - Output toggle Show Lock bypass: all output toggles now blocked during Show Lock
- Layout artifacts on input/output switching:
resized()now called after all visibility changes across all input button handlers, output toggles, and collapse buttons. Explicitrepaint()on both panels for section separator rendering. - Timecode input normalization: Start/Stop TC fields, Generator Preset Editor, and Cue Point Editor all normalize out-of-range values on confirm (e.g. 00:70:00:00 → 01:10:00:00)
Files Changed
OscInputServer.h— NEW — UDP OSC 1.0 listener with int/float/string parsing, network interface selectionGeneratorPresetEditor.h— NEW — Table editor for generator presets with Import/Export and OSC referenceHippotizerInput.h— NEW — HippoNet UDP receiver (port 6091), multi-layer parsing, auto-discovery (port 9009)TimecodeEngine.h— Generator state machine, HippoNet input integration, NextCueInfoMainComponent.h— Generator UI, OSC Input UI, HippoNet UI (TC channel selector), next cue label, preset editor windowMainComponent.cpp— Generator + presets + OSC dispatch, HippoNet multi-layer UI, Next Cue Countdown, StageLinQ cue parity, Show Lock guards, layout fixesAppSettings.h— GeneratorPreset/Map with import/export, generator settings, OSC settings, HippoNet settings, backup/restoreProDJLinkInput.h— Fixed suggestDbPlayerNumber() fallbackCuePointEditor.h— Position normalization after editMain.cpp— Version bump to 1.9.0README.md— Updated for Generator, HippoNet input, Denon cue parity, Next Cue Countdown