feat(tci): name the transmit audio source when keying, per the protocol

The specification settles what a night of experiments could only guess at.
TRX takes an optional THIRD argument naming the signal source — tci, mic1,
mic2, micPC, ecoder2 — and TCI 2.0 says it plainly: 'The signal for
transmitting is always taken from the microphone selected in the
ExpertSDR3. If a third-party software connected via TCI wants to transmit
its audio signal, you must specify the third argument - TCI.'

Without it the radio sends no chrono at all, whatever the mode. That is
what the SSB attempts ran into, and what was misread here as 'digital
modes only' — the mode was never the rule, the missing argument was.

So OpsLog says it, following the 'To radio' device: 'tci' when the voice
keyer owns the transmission, nothing at all otherwise, which leaves the
operator's own microphone alone for every other PTT. Nobody has to find
that setting in ExpertSDR3 and set it again for every mode, which is how
it is remembered there.

Two more things from the same document. A chrono with no audio ready is
now answered with silence rather than left unanswered — the vendor calls
that preferable. And the receive stream declares float32 and two channels
instead of trusting the defaults: they are the documented defaults, but a
default is something another program sharing this radio can have changed,
and a stream in an unexpected format is heard as noise rather than as a
mistake.
This commit is contained in:
2026-08-26 00:21:32 +02:00
parent 5388f76733
commit fdc2378191
8 changed files with 148 additions and 309 deletions
+26
View File
@@ -45,6 +45,31 @@ func (a *App) tciTXPlayer(pcm []byte, rate, ch, bits int, stop <-chan struct{})
return player.PlayTXAudio(pcm, rate, ch, bits, stop)
}
// applyTCITXSource tells the radio where to take its transmit audio from,
// following the "To radio" device.
//
// This is the setting an operator would otherwise have to find in ExpertSDR3
// and set again for every mode, because it is remembered per mode there. It is
// the third argument of TRX, so OpsLog can simply say it each time it keys —
// and a radio told nothing keeps the operator's microphone, which is what
// every PTT that is not a voice message should do.
func (a *App) applyTCITXSource() {
if a.cat == nil {
return
}
cfg, _ := a.GetAudioSettings()
src := ""
if cfg.ToRadio == audio.NetworkDeviceID {
src = "tci"
}
_ = a.cat.TCIAudioDo(func(t cat.TCIAudioController) error {
if s, ok := t.(interface{ SetTXAudioSource(string) }); ok {
s.SetTXAudioSource(src)
}
return nil
})
}
// installTCITXPlayer offers the radio as an audio output, or withdraws it.
//
// Withdrawing matters as much as offering: a radio that has gone away must stop
@@ -56,5 +81,6 @@ func (a *App) installTCITXPlayer(on bool) {
return
}
audio.SetNetworkPlayer(a.tciTXPlayer)
a.applyTCITXSource()
applog.Printf("tci: the radio is available as an audio output — no virtual cable needed for the voice keyer")
}