Repository navigation
Conversation
iOS TX kept silent on air — the rig's Data-VOX never keyed and TUNE was silent at the radio too, even though RX decoded fine and the output route was the USB interface at full volume. The waveform (full-scale GFSK) and session routing were both correct; the audio simply never left the engine. Root cause: RX and TX share one AVAudioEngine that AudioCaptureService starts for *input* capture only. The first thing to touch mainMixerNode is the TX path's `engine.connect(playerNode, to: mainMixerNode)` in ensureConnected(). Forming that mixer -> output link on an already-running engine leaves the new output chain un-pulled by the render thread: the node keys without error but no samples reach the hardware. play() only (re)started the engine when it was stopped, and the capture engine is always running, so the freshly-formed output connection was never brought up rendering. (PR #836 fixed the earlier crash on this same path; this is the silent-TX state it left behind.) Fix: ensureConnected() now reports whether it actually (re)connected, and play() cycles the engine (stop -> start) once after a fresh connect so the whole graph — player -> mixer -> output — comes up rendering. Gated on an actual (re)connect via the pure, unit-tested TxConnectionState.mustCycleEngineAfterConnect(didConnect:), so steady-state transmit slots don't stutter the RX capture tap. This also covers the post-config-change reconnect (DigiRig replug), which returns through the same path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #921 +/- ##
============================================
- Coverage 41.51% 41.50% -0.01%
Complexity 252 252
============================================
Files 280 280
Lines 33993 33992 -1
Branches 4034 4063 +29
============================================
- Hits 14112 14110 -2
+ Misses 19592 19591 -1
- Partials 289 291 +2
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
handleConfigChange, the engine-restart failure path and the keying exception path each repeated the same invalidate-and-drop-converter sequence; route all three through invalidateConnectionLocked(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1Claude
Fix commit: b0f95d5 (a pure refactor with no behavior change). Nothing ran locally, since there's no Swift toolchain on this Windows machine. Verification is the PR's |
Problem
On iOS, TX never keyed the radio: the FT-891's Data-VOX stayed quiet and TUNE was silent at the radio too, even though RX decoded perfectly and the output route was the USB (DigiRig) interface at full volume. So this wasn't routing, levels, or the sequencer — the TX audio simply never left the audio engine.
Root cause
RX and TX share a single
AVAudioEnginethatAudioCaptureServicestarts for input capture only. The first code to ever touchmainMixerNodeis the TX path'sengine.connect(playerNode, to: mainMixerNode)inTxPlayerService.ensureConnected(). Forming thatmixer → outputlink on an already-running engine leaves the new output chain un-pulled by the render thread — the player node keys without error, but no samples reach the hardware.play()only (re)started the engine when it was stopped, and the capture engine is always running, so the freshly-formed output connection was never kicked into rendering.This is the silent-TX state left behind by PR #836 (which fixed the earlier crash on this same connect-after-config-change path).
Fix
ensureConnected()now reports whether it actually (re)connected the node.play()cycles the engine (stop()→start()) once so the whole graph —player → mixer → output— comes up rendering.TxConnectionState.mustCycleEngineAfterConnect(didConnect:), so steady-state transmit slots don't stutter the RX capture tap every cycle.Testing
TxConnectionStateTestsextended with the cycle-after-connect decision (cycle on fresh connect, not on steady state). FullFT8AFTestssuite passes on the iPhone 16 simulator (Xcode 26.6):🤖 Generated with Claude Code