fix(icom): create the audio manager before the CAT link comes up

reloadCAT builds the network audio sink gated on audioMgr, and startup created
the manager AFTER the first reloadCAT: every fresh launch connected with audio
silently off however the option was set, and the operator had to
untick/save/retick it to hear anything. The manager is idle-until-used, so
creating it earlier costs nothing.
This commit is contained in:
2026-08-29 16:47:42 +02:00
parent 490941f506
commit 1fba7d2d57
2 changed files with 47 additions and 40 deletions
+43 -38
View File
@@ -1252,6 +1252,49 @@ func (a *App) startup(ctx context.Context) {
go a.applyRelayAuto(s.FreqHz, s.Band)
}
})
// Digital Voice Keyer + QSO recorder (WASAPI). Idle until used. Created
// BEFORE the CAT link comes up, and it matters: a network Icom with RX
// audio enabled builds its audio sink inside reloadCAT, gated on audioMgr —
// when the manager was created later, every fresh launch connected with
// audio silently off, and the operator had to untick/save/retick the option
// to hear anything.
a.audioMgr = audio.NewManager(func() {
st := a.dvkStatus()
// When a voice message finishes (or is stopped), drop the PTT we keyed
// for it — but tag the release with the current key generation so it
// can't cut a transmission a newer message already started.
if !st.Playing {
a.pttMu.Lock()
keyed := a.dvkPttKeyed
gen := a.pttGen
if keyed {
a.dvkPttKeyed = false
}
a.pttMu.Unlock()
if keyed {
go a.dvkUnkeyPTT(gen)
}
// And give the microphone back, if we took it (see raiseFlexDVKDax).
// Off the callback's goroutine: this talks to the radio, and the
// audio manager is reporting a state change, not waiting on us.
go a.lowerFlexDVKDax()
}
if a.ctx != nil {
wruntime.EventsEmit(a.ctx, "audio:status", st)
}
})
a.qsoRec = audio.NewRecorder()
if a.audioMgr != nil {
// A running monitor picks the new level up immediately: the operator is
// listening while they move the slider, and asking them to stop and
// restart it to hear the change is how a working control gets reported
// as broken.
if cfg, err := a.GetAudioSettings(); err == nil {
a.audioMgr.SetMonitorGain(cfg.FromGain)
}
}
a.startQSORecorderIfEnabled()
a.reloadCAT()
// The QSO logbook lives where the ACTIVE PROFILE points it: the local SQLite
@@ -1479,44 +1522,6 @@ func (a *App) startup(ctx context.Context) {
})
a.solar.Start()
// Digital Voice Keyer + QSO recorder (WASAPI). Idle until used.
a.audioMgr = audio.NewManager(func() {
st := a.dvkStatus()
// When a voice message finishes (or is stopped), drop the PTT we keyed
// for it — but tag the release with the current key generation so it
// can't cut a transmission a newer message already started.
if !st.Playing {
a.pttMu.Lock()
keyed := a.dvkPttKeyed
gen := a.pttGen
if keyed {
a.dvkPttKeyed = false
}
a.pttMu.Unlock()
if keyed {
go a.dvkUnkeyPTT(gen)
}
// And give the microphone back, if we took it (see raiseFlexDVKDax).
// Off the callback's goroutine: this talks to the radio, and the
// audio manager is reporting a state change, not waiting on us.
go a.lowerFlexDVKDax()
}
if a.ctx != nil {
wruntime.EventsEmit(a.ctx, "audio:status", st)
}
})
a.qsoRec = audio.NewRecorder()
if a.audioMgr != nil {
// A running monitor picks the new level up immediately: the operator is
// listening while they move the slider, and asking them to stop and
// restart it to hear the change is how a working control gets reported
// as broken.
if cfg, err := a.GetAudioSettings(); err == nil {
a.audioMgr.SetMonitorGain(cfg.FromGain)
}
}
a.startQSORecorderIfEnabled()
// NET Control store (global JSON, shared across logbooks).
if ns, err := netctl.Open(filepath.Join(a.dataDir, "nets.json")); err != nil {
applog.Printf("netctl: open failed: %v", err)
+4 -2
View File
@@ -17,7 +17,8 @@
"QSO recorder: starting a fresh manual take resets the counter for good — it used to flash 0 and jump back to the previous takes elapsed time.",
"MY_RIG follows the radio actually connected: the entry form now asks the active radio first, before the per-band default — an IC-7760 on the air no longer logs as the Flex the band plan names.",
"QSO recorder: a manual take on a network Icom records the network RX stream, not the “From radio” sound card (which captured another radios DAX on a mixed station).",
"Icom network audio: duplicate and late-retransmitted packets no longer reach the recorder — recordings came out longer than the QSO, slowed and stuttering, while the speakers played fine."
"Icom network audio: duplicate and late-retransmitted packets no longer reach the recorder — recordings came out longer than the QSO, slowed and stuttering, while the speakers played fine.",
"Icom network audio: it now starts with the app — launching OpsLog connected with audio silently off, and the option had to be unticked and re-saved to hear anything."
],
"fr": [
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à laffichage de la radio (il lisait environ deux points S trop bas).",
@@ -34,7 +35,8 @@
"Enregistreur de QSO : démarrer une nouvelle prise manuelle remet le compteur à zéro pour de bon — il affichait 0 puis resautait au temps de la prise précédente.",
"MY_RIG suit la radio réellement connectée : le formulaire interroge dabord la radio active, avant le défaut par bande — un IC-7760 à lantenne ne se logue plus comme le Flex prévu par le plan de bande.",
"Enregistreur de QSO : une prise manuelle sur un Icom réseau enregistre le flux RX réseau, pas la carte son « From Radio » (qui capturait le DAX dune autre radio sur une station mixte).",
"Audio réseau Icom : les paquets dupliqués ou retransmis en retard natteignent plus lenregistreur — les enregistrements sortaient plus longs que le QSO, ralentis et hachés, alors que les haut-parleurs jouaient bien."
"Audio réseau Icom : les paquets dupliqués ou retransmis en retard natteignent plus lenregistreur — les enregistrements sortaient plus longs que le QSO, ralentis et hachés, alors que les haut-parleurs jouaient bien.",
"Audio réseau Icom : il démarre maintenant avec lapplication — au lancement, la connexion se faisait audio coupé, et il fallait décocher/recocher loption pour entendre quelque chose."
]
},
{