diag(audio): say why the Listening device is silent

"I turned the sound back on and nothing comes out of the speakers", with
a log that reports success at every step: the network audio stream up,
664-byte packets arriving, the monitor started. The render goroutine's
error was thrown away — a device unplugged, renamed by Windows or unable
to open at 16 kHz fails exactly there, silently.

It is logged now, with the device id. Two once-only lines either side of
it say whether the decoded audio is reaching a running monitor or
arriving with nobody listening, which separates a device fault from the
speakers simply being switched off.
This commit is contained in:
2026-09-06 18:22:39 +02:00
parent 2808bead97
commit 0f082e1301
3 changed files with 37 additions and 5 deletions
+32 -3
View File
@@ -15,6 +15,12 @@ type Manager struct {
mu sync.Mutex
recStop chan struct{}
recDone chan recResult
// Said once each: "the audio is reaching the speakers" and "it is arriving
// with nobody listening". Both are answers to the same evening — sound
// switched on, nothing out of the speakers — and neither is worth a line per
// packet at fifty packets a second.
gotAudioOnce sync.Once
noSinkOnce sync.Once
// monGainPct scales what the RX monitor plays, 100 = as captured.
//
// The "From radio" slider used to reach only the QSO recorder, so an
@@ -224,6 +230,10 @@ func (m *Manager) StartMonitor(inputDev, outputDev string) error {
return m.startMonitor(inputDev, outputDev, true)
}
// Logf receives this package's diagnostic lines. Set to applog.Printf by the
// app; a no-op in tests and for anything that vendors the package alone.
var Logf = func(string, ...any) {}
// StartMonitorSink starts ONLY the render side (no USB capture) so an external
// producer — the network 50003 stream — can feed decoded RX PCM via
// PushMonitorAudio. Same output path as StartMonitor, minus the capture goroutine.
@@ -255,8 +265,17 @@ func (m *Manager) startMonitor(inputDev, outputDev string, capture bool) error {
}()
}
// Consumer: render the ring to the output device at the internal 16 kHz mono.
//
// The error was thrown away, and that is the whole of "I turned the sound on
// and nothing comes out": a Listening device that has been unplugged, renamed
// by Windows or cannot open at 16 kHz fails here, silently, while everything
// upstream reports success — the stream is up, the packets arrive, the
// monitor says it started. Said out loud, the operator knows to look at the
// device rather than at the radio.
go func() {
_ = renderStream(outputDev, sampleRate, channels, bitsPerSample, stop, ring)
if err := renderStream(outputDev, sampleRate, channels, bitsPerSample, stop, ring); err != nil {
Logf("audio: the Listening device could not be opened (%q): %v", outputDev, err)
}
}()
m.notify()
return nil
@@ -321,9 +340,19 @@ func (m *Manager) PushMonitorAudio(pcm []byte) {
m.mu.Lock()
ring := m.monRing
m.mu.Unlock()
if ring != nil {
ring.Push(pcm)
if ring == nil {
// Nothing is listening: the stream is feeding the recorder and the voice
// keyer only. Said ONCE, because the alternative — silence in the log for
// silence in the speakers — is what makes this take an evening to find.
m.noSinkOnce.Do(func() {
Logf("audio: network RX audio is arriving but no monitor is running — the speakers are off (Listening)")
})
return
}
m.gotAudioOnce.Do(func() {
Logf("audio: network RX audio reaching the Listening device (%d-byte chunks)", len(pcm))
})
ring.Push(pcm)
}
// ---- TX audio passthrough (Phase 3: live mic → rig over USB) --------------