fix(audio): the radio is offered only where it can actually be

Adding it to the device lists put it in all four dropdowns, and an
operator picked it as their RECORDING MIC — which would have recorded the
station they were listening to instead of their own voice, on every voice
message and every QSO recording. It is not a microphone, and it is not a
pair of speakers either: the audio going the other way is transmit audio.
So it is listed for 'From radio' and 'To radio', and taken out of the mic
and the listening dropdowns.

And the RX monitor is disabled when the radio IS the source, with a line
saying why: the TCI stream is pushed into the QSO recorder, and nothing
plays it to the speakers. The button was there, enabled, doing nothing
audible — which is worse than a button that says it cannot.
This commit is contained in:
2026-08-26 19:04:02 +02:00
parent 98c49f6bbc
commit afde919e5b
2 changed files with 19 additions and 6 deletions
+17 -4
View File
@@ -6773,6 +6773,13 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
xiegu: t('cat.optXiegu'),
} as Record<string, string>)[catCfg.backend] ?? '';
// The radio-over-network entry, which ListAudioInputDevices and
// ListAudioOutputDevices add when the CAT link can carry audio. It is a
// real choice for two of the four fields and nonsense for the other two.
const NET_DEVICE = 'net:radio';
const soundCardsOnly = (devs: AudioDev[]) => devs.filter((d) => d.id !== NET_DEVICE);
const fromRadioIsNetwork = audioCfg.from_radio === NET_DEVICE;
const deviceSelect = (
field: keyof AudioSettings,
devices: AudioDev[],
@@ -6807,10 +6814,16 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
{deviceSelect('from_radio', audioInputs, t('aud.phFromRadio'))}
<Label className="text-sm">{t('aud.toRadio')}</Label>
{deviceSelect('to_radio', audioOutputs, t('aud.phToRadio'))}
{/* The radio is offered for the two fields it can BE where the
received audio comes from, and where the voice keyer sends
its messages and taken out of the other two. It is not a
microphone: choosing it here would record the station you are
listening to instead of your own voice. And it is not a pair of
speakers: the audio going that way is transmit audio. */}
<Label className="text-sm">{t('aud.recMic')}</Label>
{deviceSelect('recording_device', audioInputs, t('aud.phRecMic'))}
{deviceSelect('recording_device', soundCardsOnly(audioInputs), t('aud.phRecMic'))}
<Label className="text-sm">{t('aud.listening')}</Label>
{deviceSelect('listening_device', audioOutputs, t('aud.phListening'))}
{deviceSelect('listening_device', soundCardsOnly(audioOutputs), t('aud.phListening'))}
</div>
<p className="text-[11px] text-muted-foreground">
<strong>{t('aud.fromRadioShort')}</strong> {t('aud.explainFrom')}{' '}
@@ -6828,8 +6841,8 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
size="sm"
className="h-8"
onClick={toggleMonitor}
disabled={!monitorOn && !audioCfg.from_radio}
title={t('aud.monitorTitle')}
disabled={!monitorOn && (!audioCfg.from_radio || fromRadioIsNetwork)}
title={fromRadioIsNetwork ? t('aud.monitorNoTci') : t('aud.monitorTitle')}
>
{monitorOn ? t('aud.stopListening') : t('aud.listenRadio')}
</Button>