diff --git a/internal/cat/tci_tx_probe.go b/internal/cat/tci_tx_probe.go index 3dd6b56..a376991 100644 --- a/internal/cat/tci_tx_probe.go +++ b/internal/cat/tci_tx_probe.go @@ -8,11 +8,13 @@ package cat // 1. The radio asks for audio only when the transmission is the CLIENT'S. With // the operator keying the microphone it sent 282 receive frames and nothing // else, over six seconds. -// 2. It asks only in a DIGITAL mode. Keyed from here in SSB: nothing, four -// times over. The same button in DIGU: chrono frames immediately. In SSB the -// modulator is wired to the microphone and no amount of network audio will -// reach it — which is also the honest answer to "why can I hear myself but -// not the tone". +// 2. It asks only when its TRANSMIT AUDIO SOURCE is TCI rather than the +// microphone. This first read as "digital modes only" — SSB produced +// nothing four times over, DIGU answered at once — but the mode was a +// coincidence: ExpertSDR3 keeps that source setting per mode, and it was on +// the microphone in SSB. Which is why nothing is refused on the strength of +// the mode: the radio is asked, and it answers by asking or by staying +// quiet. // 3. The chrono is a REQUEST, not a clock to follow. It carries no payload — // the message itself is the ask — and it names the size it wants in the // header's length field: 2048 samples, two channels interleaved, arriving @@ -118,9 +120,12 @@ func (t *TCI) setTXFeed(fn func(samples int) []byte) { // ProbeTXStream keys the radio, answers its chrono requests with a tone for the // given number of seconds, unkeys, and reports what happened. // -// INTO A DUMMY LOAD, AND IN A DIGITAL MODE. In SSB the radio takes the -// microphone and this produces nothing — which is a property of the radio, not -// a fault here, so it is said rather than worked around. +// INTO A DUMMY LOAD. Confirmed on a SunSDR: 80 W out of a 1 kHz tone at 70% of +// full scale into 80% drive. +// +// If the radio's transmit audio source is the microphone rather than TCI it +// will not ask for anything, and this stops within a fifth of a second and says +// which setting to change. func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { if seconds <= 0 { seconds = 5 @@ -144,10 +149,15 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { return fmt.Errorf("the radio refuses transmitting (tx_enable is false)") } if !tciDigitalMode(mode) { - // Refused rather than attempted. A pass in SSB keys the transmitter, - // produces nothing, and teaches nobody anything — and it is still a - // transmission. - return fmt.Errorf("the radio is in %s: transmit audio over TCI only reaches the modulator in a digital mode (DIGU, DIGL, or an FT8/data mode) — switch mode and try again", mode) + // A NOTE, not a refusal. + // + // The first experiments said "digital modes only": SSB produced nothing + // four times over, DIGU answered at once. That was a real observation + // and the wrong rule. ExpertSDR3 has a TRANSMIT AUDIO SOURCE — the + // microphone or TCI — and it was simply set to the microphone; the mode + // had nothing to do with it. Refusing SSB would have blocked the one + // thing a voice keyer exists for. + debugLog.Printf("TCI: TX PROBE — mode is %s, not a digital mode. That is fine IF ExpertSDR3's transmit audio source is set to TCI rather than the microphone; if it is not, the radio will not ask for audio and this stops straight away", mode) } t.audio.mu.Lock() @@ -206,7 +216,32 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { } }() - time.Sleep(time.Duration(seconds) * time.Second) + // Wait for the radio to ask, and give up quickly if it does not. + // + // The radio declares what it wants by requesting audio — 47 times a second + // when it wants any at all. So there is no need to decide in advance whether + // this mode or that setting will work: key, listen for one request, and if + // none comes in a fifth of a second, stop. That is a quarter of a second of + // carrier instead of five, and an answer that names the setting to change. + deadline := time.Now().Add(200 * time.Millisecond) + for time.Now().Before(deadline) { + t.audio.mu.Lock() + asked := t.audio.txSent > 0 + t.audio.mu.Unlock() + if asked { + break + } + time.Sleep(10 * time.Millisecond) + } + t.audio.mu.Lock() + started := t.audio.txSent + t.audio.mu.Unlock() + if started == 0 { + debugLog.Printf("TCI: TX PROBE — the radio never asked for audio; set ExpertSDR3's transmit audio source to TCI (it is on the microphone)") + return fmt.Errorf("the radio did not ask for any audio — set ExpertSDR3's transmit audio source to TCI instead of the microphone, then try again") + } + + time.Sleep(time.Duration(seconds)*time.Second - 200*time.Millisecond) t.audio.mu.Lock() sent, short := t.audio.txSent, t.audio.txShort