feat(icom): phase 5 — the voice keyer plays to the radio over the LAN

The audio session opens its TX side alongside RX (txenable in the conninfo,
16 kHz both ways), the stream gains a paced sender — 320 samples every 20 ms,
the frame mirroring what the rig itself sends on this socket, IDs swapped —
and the network Icom takes the same NetworkPlayer slot a TCI radio does: pick
'Radio (network audio)' as the To-radio device and the voice keyer needs no
cable and no virtual sound card. PTT brackets the message through the same
code as every other audio path.
This commit is contained in:
2026-08-29 19:34:05 +02:00
parent 6345710de5
commit 13434ca36c
7 changed files with 149 additions and 7 deletions
+5 -2
View File
@@ -8440,7 +8440,7 @@ func (a *App) ListAudioInputDevices() ([]audio.Device, error) {
return devs, err
}
if a.tciAudioAvailable() {
devs = append([]audio.Device{{ID: audio.NetworkDeviceID, Name: "Radio (TCI network audio)"}}, devs...)
devs = append([]audio.Device{{ID: audio.NetworkDeviceID, Name: "Radio (network audio)"}}, devs...)
}
return devs, nil
}
@@ -8467,7 +8467,7 @@ func (a *App) ListAudioOutputDevices() ([]audio.Device, error) {
return devs, err
}
if audio.NetworkPlayerReady() {
devs = append([]audio.Device{{ID: audio.NetworkDeviceID, Name: "Radio (TCI network audio)"}}, devs...)
devs = append([]audio.Device{{ID: audio.NetworkDeviceID, Name: "Radio (network audio)"}}, devs...)
}
return devs, nil
}
@@ -15625,6 +15625,9 @@ func (a *App) reloadCAT() {
}
}
a.cat.Start(cat.NewIcomNet(s.IcomNetHost, s.IcomNetUser, s.IcomNetPass, s.IcomAddr, s.DigitalDefault, audioSink))
// With the audio session open the radio can also TAKE audio: offer it to
// the voice keyer, the way a TCI radio is offered.
a.installIcomTXPlayer(audioSink != nil)
case "tci":
// Expert Electronics TCI (WebSocket) — SunSDR / ExpertSDR2, or any
// TCI-compatible server. The receive audio rides the same socket, so