fix(halikey): pre-roll straight-key sending so dahs stay inside characters - #4
Open
tcpreplay-dev wants to merge 1 commit into
Open
tcpreplay-dev wants to merge 1 commit into
tcpreplay-dev wants to merge 1 commit into
Conversation
…cters
Every dah following a dit was rendering as a letter break — a hand-sent "V"
copied as "S T", confirmed by ear on real hardware. Cause is structural: an
element's duration isn't known until the key is released, so by the time the
command is sent the radio has finished the previous element and gone silent,
and that silence becomes the gap. On-air gap was therefore
your gap + (this element - previous element)
which cancels for dit-to-dit (hence "S" was always clean) but inflates every
dit-to-dah transition to roughly a letter space.
Fixed by setting KZL, which the K4 reference (Rev D5, resources/k4_cat_commands.json)
documents as "remote paddle key-down initial delay", 0-1000 ms. Measured: it delays
onset linearly and exactly (0 -> ~90ms baseline, 500 -> 608ms, 1000 -> ~1100ms).
Holding the radio's element queue non-empty is what stops it going idle between
elements. Confirmed by ear: broken without it, correct with it, including a
beginner's 500ms dah once the pre-roll covers it.
Required pre-roll exceeds one gap plus the longest element; 320ms failed where
560ms was needed and 600ms sufficed, so:
L = (1200 / min_wpm) * (1 + dah_dit_ratio) * 1.3, capped at KZL's 1000ms
expressed to the operator as speed bounds rather than milliseconds. Defaults
15 WPM and 3:1 give 416ms.
Notably the existing gap arithmetic was already correct — when the radio is busy
it resolves to the operator's true gap. Only busyUntil needed to account for the
pre-roll, since the radio applies its initial delay solely on a key-down after
idle. No client-side queue, timer or buffering was needed.
Also corrects two sites that sent KZL as 1200/wpm in a 2-digit field believing it
to be element length (cwcontroller.cpp, mainwindow.cpp). Their behaviour for the
iambic path is unchanged, but they no longer stomp the straight-key value, and
the comments now say what KZL actually is.
Off by default; UI to follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tcpreplay-dev
force-pushed
the
feature/straight-key-preroll
branch
from
August 25, 2026 21:34
e5d986a to
dc25d28
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #3. Fixes a defect confirmed by ear on real hardware: a hand-sent "V" was
copied as "S T" — every dah following a dit rendered as a letter break.
Cause
An element's duration isn't known until the key is released, so by the time the command is
sent the radio has finished the previous element and gone silent. That silence becomes the
gap:
Dit-to-dit cancels, which is why "S" was always clean and only the dah broke.
Fix
Set
KZL, documented in the K4 reference (Rev D5,resources/k4_cat_commands.json) as"remote paddle key-down initial delay", 0–1000 ms. Measured onset:
KZLLinear and exact. Holding the radio's element queue non-empty is what stops it going idle
between elements.
Required pre-roll exceeds one gap plus the longest element — 320 ms failed where 560 ms was
needed and 600 ms sufficed, hence the 1.3 margin:
Expressed to the operator as speed bounds rather than milliseconds (UI in the follow-up PR).
Notable
The existing gap arithmetic was already correct — when the radio is busy it resolves to
the operator's true gap. Only
busyUntilneeded to account for the pre-roll, since the radioapplies its initial delay solely on a key-down after idle. No client-side queue, timer or
buffering was required.
Also corrects two sites that sent
KZLas1200/wpmin a 2-digit field believing it to beelement length. Their behaviour for the iambic path is unchanged, but they no longer stomp
the straight-key value.
Off by default.
Testing
Verified on a K4 by ear: broken without the pre-roll, correct with it, including a
beginner's 500 ms dah once the pre-roll covers it.
IambicKeyerTestspasses.