From f599f7970222fcd52200a811a702a949455bbd9c Mon Sep 17 00:00:00 2001 From: Patrick Burns Date: Wed, 3 Jun 2026 11:15:24 -0500 Subject: [PATCH] docs(CLAUDE.md): add FT8 TX audio pipeline notes + fix package name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the two non-obvious gotchas that just took a day to diagnose: 1. lateStartSkipMs must threshold against (15s - 12.64s) = 2.36s of slack, not the cycle boundary. Wrong: chops the leading Costas array off every on-time transmission. (PR #93) 2. libusb iso OUT packet length must be (rate*ch*bps)/1000, NOT the endpoint's wMaxPacketSize. Wrong: device clocks samples ~4% fast and FT8 tones land off the WSJT-X 6.25Hz grid. AudioTrack path is unaffected because the kernel UAC driver does this math. (PR #94) Both failure modes are *silent* — the rig keys, audio is audible on the rig speaker, ALC looks right, but receivers see noise. Without this note in the project instructions, the next person (or the next Claude Code session) re-walks the entire diagnostic chain from debug.log → resampler audit → external WebSDR confirmation. Also fixes the stale package name throughout — applicationId moved from `com.bg7yoz.ft8cn` to `radio.ks3ckc.ft8af` at some point, but the adb pull / logcat / pidof examples in CLAUDE.md still pointed at the old name, so they would silently produce empty results. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9b83e527..03511b2d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,18 +24,60 @@ phone's serial (from `adb devices`). adb itself lives at ## Debug logs The app writes a structured event log to -`/sdcard/Android/data/com.bg7yoz.ft8cn/files/debug.log` via `fileLog()` in +`/sdcard/Android/data/radio.ks3ckc.ft8af/files/debug.log` via `fileLog()` in `ComposeMainActivity.kt` — CAT serial sends/recvs, USB attach events, autoConnect attempts, band/frequency changes, etc. This is usually the most useful source. Pull it with: ``` -adb -s pull /sdcard/Android/data/com.bg7yoz.ft8cn/files/debug.log /tmp/ +adb -s pull /sdcard/Android/data/radio.ks3ckc.ft8af/files/debug.log /tmp/ ``` For runtime detail not in `debug.log` (audio recording loop, system USB events, crashes), use `adb logcat`. Useful tags: `FT8SignalListener`, `MicRecorder`, `UsbAudioDevice`, `CableConnector`, `CableSerialPort`, `UsbHostManager`, -`UsbAlsaManager`. The app's `applicationId` is `com.bg7yoz.ft8cn` — pid-filter -with `--pid=$(adb shell pidof com.bg7yoz.ft8cn)` when you only want app-internal +`UsbAlsaManager`. The app's `applicationId` is `radio.ks3ckc.ft8af` — pid-filter +with `--pid=$(adb shell pidof radio.ks3ckc.ft8af)` when you only want app-internal lines. + +## FT8 TX audio pipeline + +How a `playFT8Signal` call becomes RF, with the gotchas that produce +audible-but-undecodable signals. Both of these were diagnosed the hard +way and re-introducing either makes TX silently broken — the rig keys, +audio is audible, ALC looks right, and zero spots appear on PSKReporter. + +**1. `libft8cn.so` generates the entire waveform.** `GenerateFT8.generateFt8(msg, +freq, 12000)` returns a mono 12.64-second `float[]` at 12 kHz containing the full +FT8 message. The Costas sync arrays at symbols 0-6, 36-42, 72-78 are +embedded by `synth_gfsk` in the native lib (the .so is a closed-source JNI +wrapper around kgoba `ft8_lib @ 6f528128`; see the user's memory entry +`libft8cn-native-origin.md`). The buffer is correct as generated — everything +downstream just has to *not break it*. + +**2. `lateStartSkipMs` clips leading audio, but only if we'd overrun the cycle.** +FT8 audio is 12.64 s; cycle is 15 s; slack is 2.36 s. `msLate` (in +`FT8TransmitSignal.java`) must be computed as +`max(0, time_into_cycle_ms - 2360)` — **not** `time_into_cycle_ms % +15000`. The latter treats every ms past the cycle boundary as lateness, +so a normal on-time TX firing ~500-800 ms into the cycle chops that many +ms off the **start** of the buffer — exactly where the leading Costas +array lives. Receivers see audio but can't sync. Tell from log: +`playLength < samples` when the TX started <2.4 s into the cycle. Fixed +in PR #93. + +**3. `libusb_set_iso_packet_lengths` must use the audio rate, not +`wMaxPacketSize`.** A USB Audio Class device plays back exactly the bytes +per frame the host hands it. For USB FS, that's +`(sampleRate * channels * bytesPerSample) / 1000` — e.g. 192 bytes/frame +at 48 kHz stereo 16-bit. The endpoint's `wMaxPacketSize` (~200 for +C-Media CM108-style chips) is the device's *max*, not the data rate. +Sending that much per frame makes the device clock samples ~4 % faster +than negotiated, shifting every FT8 tone up by the same ratio and +pushing the message off WSJT-X's 6.25 Hz grid. Tell from log: +`UsbAudioNative.nativeWrite` returns measurably faster than the audio +duration (12.14 s real time for 12.64 s of audio). Fixed in PR #94 in +`cpp/usb_audio_capture.cpp`. The Android-standard `AudioTrack` path is +unaffected because the kernel UAC driver does this math automatically; +the bug only bites the direct-libusb path used for car-dash kernels and +similar.