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:
@@ -1188,6 +1188,7 @@ func (a *App) startup(ctx context.Context) {
|
||||
audio.AlertSink = func(format string, args ...any) { a.toast(fmt.Sprintf(format, args...)) }
|
||||
extsvc.LogSink = applog.Printf // log raw QRZ (and other) service responses for diagnosis
|
||||
lookup.LogSink = applog.Printf // which call was queried, and why a portable lookup fell back
|
||||
audio.Logf = applog.Printf // why the Listening device stayed silent, above all
|
||||
db.LogSink = applog.Printf // which schema migrations ran, and how long they took
|
||||
// Version and executable FIRST, before anything else can fail. Diagnosing a
|
||||
// report means knowing which build produced the log, and that was previously
|
||||
|
||||
+4
-2
@@ -8,7 +8,8 @@
|
||||
"Icom over the network: a rig left in standby no longer sits in a dial-and-drop loop. The clock that bounds “the control link answers but no CI-V comes back” belongs to a session and was never cleared when a new one opened, so every fresh session started already past its grace — torn down at once, redialled twenty seconds later, and torn down again for as long as the radio was asleep. Silent since connect is now read as what it is: a rig in standby, with the session kept so it can be woken.",
|
||||
"The Icom console appears whenever the configured radio is an Icom, not only once the rig is talking — the console is where the power-ON button lives, so it used to be missing at the one moment it was needed. The console’s configured backend also follows a radio switched from the status bar, instead of waiting for a trip through Settings and a Save that changed nothing.",
|
||||
"Icom over the network: when CI-V goes quiet while the experimental RX audio stream is still delivering, the log now says so and names the switch to try. The two share the rig’s session, and the shape in the field is exactly that — hundreds of audio packets arriving, not one CI-V reply, the watchdog tearing the session down, and the whole thing starting again. The silence report also lists the last eight CI-V commands sent: a rig that answers at connect and then never again has usually been sent something it does not like, and a count of unanswered commands never said which one.",
|
||||
"Club Log uploads are no longer refused as “not configured”. The check added for services with no credentials demanded a Club Log API key, which nobody has ever set — OpsLog carries its own application key — so an operator whose live upload had worked for months was turned away when sending QSOs by hand. Each service’s requirements now live beside the uploader that enforces them, and the message names the fields that are actually missing."
|
||||
"Club Log uploads are no longer refused as “not configured”. The check added for services with no credentials demanded a Club Log API key, which nobody has ever set — OpsLog carries its own application key — so an operator whose live upload had worked for months was turned away when sending QSOs by hand. Each service’s requirements now live beside the uploader that enforces them, and the message names the fields that are actually missing.",
|
||||
"Audio: the Listening device now says when it cannot be opened. A device unplugged, renamed by Windows or unable to run at 16 kHz failed silently while everything upstream reported success — the stream up, the packets arriving, the monitor started — which is the whole of “I turned the sound on and nothing comes out”. The log also says, once, whether the network RX audio is reaching the speakers or arriving with nobody listening."
|
||||
],
|
||||
"fr": [
|
||||
"Carte FTx : l’indicatif, le locator et le report réapparaissent au survol. Le cercle invisible qui capte les clics est au-dessus du point, donc il capte aussi le survol — et l’étiquette n’était liée qu’au point du dessous, ce qui rendait la carte muette dès que les stations sont devenues cliquables.",
|
||||
@@ -16,7 +17,8 @@
|
||||
"Icom en réseau : un poste laissé en veille ne tourne plus en boucle connexion/déconnexion. L’horloge qui borne « la liaison de contrôle répond mais aucun CI-V ne revient » appartient à une session et n’était jamais remise à zéro à l’ouverture de la suivante : chaque nouvelle session démarrait déjà au-delà de son délai de grâce — coupée aussitôt, rappelée vingt secondes plus tard, recoupée, aussi longtemps que la radio dormait. « Silencieux depuis la connexion » se lit désormais pour ce que c’est : un poste en veille, dont on garde la session pour pouvoir le réveiller.",
|
||||
"La console Icom s’affiche dès que la radio configurée est un Icom, et pas seulement quand le poste parle — c’est là que se trouve le bouton d’allumage, il manquait donc au seul moment où il servait. Le backend configuré suit aussi un changement de radio fait depuis la barre d’état, au lieu d’attendre un passage dans les réglages et un « Enregistrer » qui ne changeait rien.",
|
||||
"Icom en réseau : quand le CI-V devient muet alors que le flux audio expérimental continue d’arriver, le journal le dit et nomme l’option à essayer. Les deux partagent la session du poste, et c’est exactement la forme observée en vrai — des centaines de paquets audio, pas une réponse CI-V, le chien de garde qui coupe la session, et tout qui recommence. Le rapport de silence liste aussi les huit dernières commandes CI-V envoyées : un poste qui répond à la connexion puis plus jamais s’est en général vu envoyer quelque chose qu’il n’aime pas, et un compteur de commandes sans réponse n’a jamais dit laquelle.",
|
||||
"Les envois vers Club Log ne sont plus refusés comme « non configuré ». Le contrôle ajouté pour les services sans identifiants réclamait une clé API Club Log que personne n’a jamais saisie — OpsLog embarque la sienne — et un opérateur dont l’envoi automatique fonctionnait depuis des mois se voyait éconduit au moment d’envoyer des QSO à la main. Les exigences de chaque service vivent désormais à côté du code qui les applique, et le message nomme les champs réellement manquants."
|
||||
"Les envois vers Club Log ne sont plus refusés comme « non configuré ». Le contrôle ajouté pour les services sans identifiants réclamait une clé API Club Log que personne n’a jamais saisie — OpsLog embarque la sienne — et un opérateur dont l’envoi automatique fonctionnait depuis des mois se voyait éconduit au moment d’envoyer des QSO à la main. Les exigences de chaque service vivent désormais à côté du code qui les applique, et le message nomme les champs réellement manquants.",
|
||||
"Audio : le périphérique d’écoute signale désormais quand il ne peut pas s’ouvrir. Un périphérique débranché, renommé par Windows ou incapable de fonctionner en 16 kHz échouait en silence pendant que tout en amont annonçait le succès — flux ouvert, paquets reçus, moniteur démarré — ce qui est exactement le « j’ai remis le son et rien ne sort ». Le journal dit aussi, une fois, si l’audio réseau atteint les haut-parleurs ou arrive sans que personne n’écoute."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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) --------------
|
||||
|
||||
Reference in New Issue
Block a user