Skip to content

FT8 TX: stop chopping leading Costas off every transmission#93

Merged
patrickrb merged 1 commit into
mainfrom
fix/tx-late-start-chops-costas
Jun 3, 2026
Merged

FT8 TX: stop chopping leading Costas off every transmission#93
patrickrb merged 1 commit into
mainfrom
fix/tx-late-start-chops-costas

Conversation

@patrickrb
Copy link
Copy Markdown
Owner

Summary

FT8TransmitSignal.java's lateStartSkipMs calculation treated every millisecond past the cycle boundary as lateness, so a perfectly on-time TX firing ~500–800 ms into the 15 s cycle (totally normal for UI thread → native generate → USB open latency) would chop 500–800 ms off the start of the audio buffer. That start is exactly where the leading Costas sync array lives (symbols 0–6, the first 1.12 s of the message). With a damaged leading Costas, receivers can't lock on, and the signal comes across as audible-but-undecodable.

Why

FT8 audio is 79 × 0.16 = 12.64 s; cycle is 15 s; we have 2.36 s of slack between the cycle boundary and the latest moment we can still fit the message inside the cycle. Compute msLate against that threshold (2.36 s), not against the cycle boundary, so on-time and mildly-late TXs send the full waveform with Costas intact. Genuinely late TXs (>2.4 s into the cycle) still clip leading audio to keep the message ending inside the 15 s window.

Test plan

  • Pixel 8 + FT-891 + C-Media USB CODEC: previously playLength was ~144,300 of 151,680 samples (~7,400 chopped, 616 ms gone from the front). After this change playLength == 151,680 on a normal-timing TX (no clipping).
  • Verify signal now decodes for other stations (PSKReporter spot, or HF buddy).
  • Sanity-check that a genuinely late TX (>2.4 s in — possible if the device wakes mid-cycle) still clips correctly so it doesn't overrun.

🤖 Generated with Claude Code

The lateStartSkipMs calculation treated every millisecond past the cycle
boundary as lateness:

    int msLate = (int) (UtcTimer.getSystemTime() % 15000);

But FT8 audio is supposed to start ~+0.5s into the 15-second cycle, and
TX actually fires ~500-800ms in (UI thread + native generate + USB
opens). So a *perfectly on-time* TX would compute msLate = 500-800ms
and chop that many milliseconds off the **start** of the audio buffer —
exactly where the leading Costas sync array lives (symbols 0-6, the
first 1.12 seconds of the message). Receivers couldn't lock on, and the
signal came across as audible-but-undecodable.

FT8 audio is 79 * 0.16 = 12.64s long; cycle is 15s; so we have
15 - 12.64 = 2.36s of slack between the cycle boundary and the latest
moment we can start without overrunning. Compute msLate against that
threshold, not against the cycle boundary, so on-time and mildly-late
TXs send the full waveform with Costas intact. Genuinely late TXs
(2.4s+ into cycle) still clip leading audio to keep the message inside
the cycle.

Verified locally on Pixel 8 + FT-891 + C-Media USB CODEC: previously
playLength was ~144,300 of 151,680 samples (~7,400 chopped, 616ms gone
from the front); after this change playLength == 151,680, i.e. zero
clipping on a normal-timing TX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@patrickrb patrickrb merged commit 31d5931 into main Jun 3, 2026
2 of 3 checks passed
@patrickrb patrickrb deleted the fix/tx-late-start-chops-costas branch June 3, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant