feat(tci): read the radio's declared format, and record a test WAV

The SunSDR announces its own stream at connect —
audio_stream_sample_type:float32 and audio_stream_channels:2 — and both
were being logged as unhandled while the code worked the format out from
frame arithmetic. The declaration is better evidence and arrives before
the first frame; the arithmetic stays as the check on it. The channel
count now drives the mix-down instead of an assumed stereo.

Adds a ten-second test recording, written as a WAV beside the QSO
recordings. Counting frames proves a socket is delivering bytes; it says
nothing about whether those bytes are the receiver's audio, at the right
rate, in the right order. A stream decoded with the width wrong or the
samples misaligned counts exactly as well as a correct one and sounds
like a fan — so the test is a file the operator can play, the same way
the CW decoder was settled on the air rather than on a spectrogram.

The file is written at the rate the RADIO reported, not a constant: a
recording at the wrong rate plays at the wrong speed, which is the one
fault that would be blamed on the decoding.
This commit is contained in:
2026-08-25 08:25:29 +02:00
parent 9b8168370f
commit 01a23ccb77
7 changed files with 247 additions and 6 deletions
+2 -2
View File
@@ -494,7 +494,7 @@ const en: Dict = {
'aud.noneDefault': '— none / system default —', 'aud.defaultTag': '(default)',
'aud.fromRadioShort': 'From Radio', 'aud.toRadioShort': 'To Radio', 'aud.explainFrom': '= what you receive (used by the QSO recorder).', 'aud.explainTo': '= where voice-keyer messages are transmitted.',
'aud.monitorTitle': "Hear the rig's RX audio (From Radio) through your Listening device", 'aud.listenRadio': '▶ Listen to radio', 'aud.stopListening': '■ Stop listening',
'aud.monitorOn': 'RX monitor running — From Radio → Listening device.', 'aud.tciTitle': 'SunSDR receive audio over TCI (experimental)', 'aud.tciStart': 'Open the stream', 'aud.tciStop': 'Close the stream', 'aud.tciSilent': 'silent', 'aud.tciHint': 'Takes the receive audio straight from the radio over TCI, with no virtual audio cable and no second sound card. Receive only for now — the voice keyer still uses the devices above. Requires the CAT backend to be TCI.', 'aud.monitorHint': 'Live-monitor the rig here (USB codec now; network audio later).',
'aud.monitorOn': 'RX monitor running — From Radio → Listening device.', 'aud.tciTitle': 'SunSDR receive audio over TCI (experimental)', 'aud.tciStart': 'Open the stream', 'aud.tciStop': 'Close the stream', 'aud.tciRec': 'Record 10 s to listen', 'aud.tciRecBusy': 'Recording…', 'aud.tciSilent': 'silent', 'aud.tciHint': 'Takes the receive audio straight from the radio over TCI, with no virtual audio cable and no second sound card. Receive only for now — the voice keyer still uses the devices above. Requires the CAT backend to be TCI.', 'aud.monitorHint': 'Live-monitor the rig here (USB codec now; network audio later).',
'aud.txTitle': 'Key PTT and pipe your live mic into the rig (To Radio device)', 'aud.talkRadio': '🎙 Talk to radio (TX)', 'aud.stopTalk': '■ Stop talking (TX)',
'aud.txOn': 'TRANSMITTING — mic → To Radio, PTT keyed. Click to stop.', 'aud.txHint': 'Live mic → rig with PTT (USB now; network TX later).',
'aud.recorder': 'QSO recorder', 'aud.recordEvery': 'Record every QSO to an audio file (From Radio + your mic)', 'aud.recFolder': 'Recordings folder', 'aud.browse': 'Browse…',
@@ -962,7 +962,7 @@ const fr: Dict = {
'aud.noneDefault': '— aucun / défaut système —', 'aud.defaultTag': '(défaut)',
'aud.fromRadioShort': 'Depuis la radio', 'aud.toRadioShort': 'Vers la radio', 'aud.explainFrom': "= ce que vous recevez (utilisé par l'enregistreur de QSO).", 'aud.explainTo': '= où sont émis les messages du manipulateur vocal.',
'aud.monitorTitle': "Écouter l'audio RX du poste (Depuis la radio) sur votre périphérique d'écoute", 'aud.listenRadio': '▶ Écouter la radio', 'aud.stopListening': "■ Arrêter l'écoute",
'aud.monitorOn': "Écoute RX active — Depuis la radio → périphérique d'écoute.", 'aud.tciTitle': 'Audio de réception SunSDR par TCI (expérimental)', 'aud.tciStart': 'Ouvrir le flux', 'aud.tciStop': 'Fermer le flux', 'aud.tciSilent': 'silence', 'aud.tciHint': "Prend l'audio de réception directement sur la radio via TCI, sans câble audio virtuel ni seconde carte son. Réception seulement pour l'instant — le manipulateur vocal utilise toujours les périphériques ci-dessus. Nécessite le CAT réglé sur TCI.", 'aud.monitorHint': "Écoute directe du poste (codec USB pour l'instant ; audio réseau plus tard).",
'aud.monitorOn': "Écoute RX active — Depuis la radio → périphérique d'écoute.", 'aud.tciTitle': 'Audio de réception SunSDR par TCI (expérimental)', 'aud.tciStart': 'Ouvrir le flux', 'aud.tciStop': 'Fermer le flux', 'aud.tciRec': 'Enregistrer 10 s pour écoute', 'aud.tciRecBusy': 'Enregistrement…', 'aud.tciSilent': 'silence', 'aud.tciHint': "Prend l'audio de réception directement sur la radio via TCI, sans câble audio virtuel ni seconde carte son. Réception seulement pour l'instant — le manipulateur vocal utilise toujours les périphériques ci-dessus. Nécessite le CAT réglé sur TCI.", 'aud.monitorHint': "Écoute directe du poste (codec USB pour l'instant ; audio réseau plus tard).",
'aud.txTitle': 'Activer le PTT et envoyer votre micro vers le poste (périphérique « Vers la radio »)', 'aud.talkRadio': '🎙 Parler à la radio (TX)', 'aud.stopTalk': '■ Arrêter de parler (TX)',
'aud.txOn': 'ÉMISSION — micro → Vers la radio, PTT activé. Cliquez pour arrêter.', 'aud.txHint': "Micro direct → poste avec PTT (USB pour l'instant ; TX réseau plus tard).",
'aud.recorder': 'Enregistreur de QSO', 'aud.recordEvery': 'Enregistrer chaque QSO dans un fichier audio (Depuis la radio + votre micro)', 'aud.recFolder': 'Dossier des enregistrements', 'aud.browse': 'Parcourir…',