feat: added full support in USB (local) & ethernet (local or remote) of audio for Icom
This commit is contained in:
@@ -96,6 +96,7 @@ const (
|
||||
keyCATIcomNetHost = "cat.icom.net.host" // Icom network remote: rig IP/hostname
|
||||
keyCATIcomNetUser = "cat.icom.net.user" // Icom network: Network User1 ID
|
||||
keyCATIcomNetPass = "cat.icom.net.pass" // Icom network: Network User1 password
|
||||
keyCATIcomNetAudio = "cat.icom.net.audio" // Icom network: stream RX audio on 50003 (experimental)
|
||||
keyCATTCIHost = "cat.tci.host" // TCI host (Expert Electronics SunSDR / ExpertSDR2)
|
||||
keyCATTCIPort = "cat.tci.port" // TCI WebSocket port (default 40001)
|
||||
keyCATTCISpots = "cat.tci.spots" // push cluster spots to the TCI panorama
|
||||
@@ -279,6 +280,7 @@ type CATSettings struct {
|
||||
IcomNetHost string `json:"icom_net_host"` // Icom network remote: rig IP/hostname (built-in LAN server)
|
||||
IcomNetUser string `json:"icom_net_user"` // Icom network Network User1 ID
|
||||
IcomNetPass string `json:"icom_net_pass"` // Icom network Network User1 password
|
||||
IcomNetAudio bool `json:"icom_net_audio"` // Icom network: stream RX audio (50003) — experimental, needs on-rig verification
|
||||
TCIHost string `json:"tci_host"` // TCI host (Expert Electronics SunSDR)
|
||||
TCIPort int `json:"tci_port"` // TCI WebSocket port (default 40001)
|
||||
TCISpots bool `json:"tci_spots"` // push cluster spots to the TCI panorama
|
||||
@@ -4321,7 +4323,7 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
if a.settings == nil {
|
||||
return CATSettings{Backend: "omnirig", OmniRigNum: 1, PollMs: 250}, fmt.Errorf("db not initialized")
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPollMs, keyCATDelayMs, keyCATDigitalDefault)
|
||||
m, err := a.settings.GetMany(a.ctx, keyCATEnabled, keyCATBackend, keyCATOmniRigNum, keyCATFlexHost, keyCATFlexPort, keyCATFlexSpots, keyCATIcomPort, keyCATIcomBaud, keyCATIcomAddr, keyCATIcomNetHost, keyCATIcomNetUser, keyCATIcomNetPass, keyCATIcomNetAudio, keyCATTCIHost, keyCATTCIPort, keyCATTCISpots, keyCATPollMs, keyCATDelayMs, keyCATDigitalDefault)
|
||||
if err != nil {
|
||||
return CATSettings{}, err
|
||||
}
|
||||
@@ -4338,6 +4340,7 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
||||
IcomNetHost: m[keyCATIcomNetHost],
|
||||
IcomNetUser: m[keyCATIcomNetUser],
|
||||
IcomNetPass: m[keyCATIcomNetPass],
|
||||
IcomNetAudio: m[keyCATIcomNetAudio] == "1",
|
||||
TCIHost: m[keyCATTCIHost],
|
||||
TCIPort: 40001,
|
||||
TCISpots: m[keyCATTCISpots] == "1",
|
||||
@@ -4413,6 +4416,10 @@ func (a *App) SaveCATSettings(s CATSettings) error {
|
||||
if s.TCISpots {
|
||||
tciSpots = "1"
|
||||
}
|
||||
icomNetAudio := "0"
|
||||
if s.IcomNetAudio {
|
||||
icomNetAudio = "1"
|
||||
}
|
||||
if s.DigitalDefault == "" {
|
||||
s.DigitalDefault = "FT8"
|
||||
}
|
||||
@@ -4429,6 +4436,7 @@ func (a *App) SaveCATSettings(s CATSettings) error {
|
||||
keyCATIcomNetHost: strings.TrimSpace(s.IcomNetHost),
|
||||
keyCATIcomNetUser: strings.TrimSpace(s.IcomNetUser),
|
||||
keyCATIcomNetPass: s.IcomNetPass,
|
||||
keyCATIcomNetAudio: icomNetAudio,
|
||||
keyCATTCIHost: strings.TrimSpace(s.TCIHost),
|
||||
keyCATTCIPort: strconv.Itoa(s.TCIPort),
|
||||
keyCATTCISpots: tciSpots,
|
||||
@@ -5877,6 +5885,72 @@ func (a *App) DVKStop() {
|
||||
}
|
||||
}
|
||||
|
||||
// AudioStartMonitor pipes live RX audio from the rig into your speakers so you
|
||||
// hear the radio inside OpsLog. Source = the "From radio" capture device (for a
|
||||
// USB-connected rig, its "USB Audio CODEC" input); sink = the "Listening"
|
||||
// device. This is the USB half of the audio feature; the network 50003 stream
|
||||
// will later Push into the same output path (see audio.Manager.PushMonitorAudio).
|
||||
func (a *App) AudioStartMonitor() error {
|
||||
if a.audioMgr == nil {
|
||||
return fmt.Errorf("audio not initialized")
|
||||
}
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
if strings.TrimSpace(cfg.FromRadio) == "" {
|
||||
return fmt.Errorf(`no "From radio" capture device set — pick the rig's USB Audio CODEC in Settings → Audio`)
|
||||
}
|
||||
applog.Printf("audio: RX monitor start (from=%q → listen=%q)", cfg.FromRadio, cfg.ListeningDevice)
|
||||
return a.audioMgr.StartMonitor(cfg.FromRadio, cfg.ListeningDevice)
|
||||
}
|
||||
|
||||
// AudioStopMonitor stops the RX monitor passthrough.
|
||||
func (a *App) AudioStopMonitor() {
|
||||
if a.audioMgr != nil {
|
||||
a.audioMgr.StopMonitor()
|
||||
applog.Printf("audio: RX monitor stopped")
|
||||
}
|
||||
}
|
||||
|
||||
// AudioMonitorActive reports whether the RX monitor is running (for the toggle).
|
||||
func (a *App) AudioMonitorActive() bool {
|
||||
return a.audioMgr != nil && a.audioMgr.MonitorActive()
|
||||
}
|
||||
|
||||
// AudioStartTX keys PTT and pipes your live mic into the rig ("To Radio" device)
|
||||
// so you can talk through the PC — the USB half of TX voice. PTT uses the
|
||||
// configured method (CAT/RTS/DTR); if keying fails the audio route isn't started.
|
||||
func (a *App) AudioStartTX() error {
|
||||
if a.audioMgr == nil {
|
||||
return fmt.Errorf("audio not initialized")
|
||||
}
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
if strings.TrimSpace(cfg.ToRadio) == "" {
|
||||
return fmt.Errorf(`no "To radio" device set — pick the rig's USB Audio CODEC output in Settings → Audio`)
|
||||
}
|
||||
if err := a.pttKey(cfg); err != nil { // key first — no point streaming to a rig that isn't transmitting
|
||||
return err
|
||||
}
|
||||
if err := a.audioMgr.StartTXAudio(cfg.RecordingDevice, cfg.ToRadio); err != nil {
|
||||
a.pttUnkey()
|
||||
return err
|
||||
}
|
||||
applog.Printf("audio: TX start (mic=%q → to-radio=%q, ptt=%q)", cfg.RecordingDevice, cfg.ToRadio, cfg.PTTMethod)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AudioStopTX stops the TX passthrough and unkeys PTT.
|
||||
func (a *App) AudioStopTX() {
|
||||
if a.audioMgr != nil {
|
||||
a.audioMgr.StopTXAudio()
|
||||
}
|
||||
a.pttUnkey()
|
||||
applog.Printf("audio: TX stopped")
|
||||
}
|
||||
|
||||
// AudioTXActive reports whether the TX passthrough is running (for the toggle).
|
||||
func (a *App) AudioTXActive() bool {
|
||||
return a.audioMgr != nil && a.audioMgr.TXAudioActive()
|
||||
}
|
||||
|
||||
// GetLogFilePath returns where the diagnostic log file lives so the user
|
||||
// can open it from the Settings UI. Empty when applog hasn't initialised.
|
||||
func (a *App) GetLogFilePath() string {
|
||||
@@ -8606,7 +8680,28 @@ func (a *App) reloadCAT() {
|
||||
case "icom-net":
|
||||
// Icom CI-V over the rig's built-in LAN server (remote, no RS-BA1 / Remote
|
||||
// Utility). Reuses the whole IcomController over the network transport.
|
||||
a.cat.Start(cat.NewIcomNet(s.IcomNetHost, s.IcomNetUser, s.IcomNetPass, s.IcomAddr, s.DigitalDefault))
|
||||
var audioSink func([]byte)
|
||||
if s.IcomNetAudio && a.audioMgr != nil {
|
||||
// Experimental: stream RX audio over 50003. Start the render-only monitor
|
||||
// sink (no USB capture) and feed each decoded payload into it, so remote
|
||||
// RX audio plays through the "Listening" device. Decoding is via the PCM
|
||||
// codec (Opus later). The 50003 payload OFFSET is still pending on-rig
|
||||
// verification (see icomaudio.go) — hence experimental + opt-in.
|
||||
acfg, _ := a.GetAudioSettings()
|
||||
a.audioMgr.StopMonitor() // clear any prior monitor/sink so a re-save restarts cleanly
|
||||
if err := a.audioMgr.StartMonitorSink(acfg.ListeningDevice); err != nil {
|
||||
applog.Printf("icom-net audio: cannot start output sink: %v", err)
|
||||
} else {
|
||||
codec := audio.NewPCM16Codec()
|
||||
audioSink = func(payload []byte) {
|
||||
if pcm, err := codec.Decode(payload); err == nil {
|
||||
a.audioMgr.PushMonitorAudio(pcm)
|
||||
}
|
||||
}
|
||||
applog.Printf("icom-net audio: RX audio streaming ENABLED (experimental) → %q", acfg.ListeningDevice)
|
||||
}
|
||||
}
|
||||
a.cat.Start(cat.NewIcomNet(s.IcomNetHost, s.IcomNetUser, s.IcomNetPass, s.IcomAddr, s.DigitalDefault, audioSink))
|
||||
case "tci":
|
||||
// Expert Electronics TCI (WebSocket) — SunSDR / ExpertSDR2, or any
|
||||
// TCI-compatible server.
|
||||
|
||||
Reference in New Issue
Block a user