fix: replaying a QSO used the voice keyer's level and overdrove the rig
A panadapter shot settles it: answering an S5 station, the replayed recording goes out several times wider and taller than the station being answered — in WAV as well as MP3, so this is not the encoder. One setting served two sources that have nothing in common. A voice-keyer message is a microphone at speaking distance and legitimately wants 195%; a QSO recording is a receiver's line output, which is far hotter. The same 195% put it into the transmitter flat out, ALC pinned, noise and voice alike. The QSO playback now has its own level, defaulting to 100%. The voice keyer keeps the setting it had, so nobody's F-key messages change.
This commit is contained in:
@@ -141,6 +141,15 @@ const (
|
||||
keyAudioQSODir = "audio.qso_dir" // folder for QSO recordings
|
||||
keyAudioPreroll = "audio.preroll_seconds" // rolling-buffer pre-roll length
|
||||
keyAudioTXGain = "audio.tx_gain" // voice-keyer playback level % (100 = as recorded)
|
||||
// Replaying a QSO recording needs its OWN level, and by a wide margin.
|
||||
//
|
||||
// A voice-keyer message is a microphone at speaking distance; a QSO
|
||||
// recording is a receiver's line output, which is far hotter. Sharing one
|
||||
// setting meant the 195% an operator needs for their F-key messages was
|
||||
// also applied to a recording, which then hit the transmitter flat out —
|
||||
// visible on the panadapter as a signal several times the size of the S5
|
||||
// station being answered.
|
||||
keyAudioQSOPlayGain = "audio.qso_play_gain" // QSO-recording playback level %
|
||||
keyAudioPTTMethod = "audio.ptt_method" // "none" (VOX) | "rts" | "dtr"
|
||||
keyAudioPTTPort = "audio.ptt_port" // COM port for serial PTT
|
||||
keyAudioFormat = "audio.qso_format" // "wav" | "mp3"
|
||||
@@ -6958,6 +6967,7 @@ type AudioSettings struct {
|
||||
FromGain int `json:"from_gain"` // From Radio (RX) mix level %, default 100
|
||||
MicGain int `json:"mic_gain"` // mic mix level %, default 100
|
||||
TXGain int `json:"tx_gain"` // voice-keyer playback level %, default 100
|
||||
QSOPlayGain int `json:"qso_play_gain"` // QSO-recording playback level %, default 100
|
||||
}
|
||||
|
||||
// ListAudioInputDevices / ListAudioOutputDevices enumerate WASAPI endpoints
|
||||
@@ -6967,14 +6977,14 @@ func (a *App) ListAudioOutputDevices() ([]audio.Device, error) { return audio.Li
|
||||
|
||||
// GetAudioSettings returns the stored audio config (preroll defaults to 8s).
|
||||
func (a *App) GetAudioSettings() (AudioSettings, error) {
|
||||
out := AudioSettings{PrerollSeconds: 8, PTTMethod: "none", Format: "wav", FromGain: 100, MicGain: 100, TXGain: 100}
|
||||
out := AudioSettings{PrerollSeconds: 8, PTTMethod: "none", Format: "wav", FromGain: 100, MicGain: 100, TXGain: 100, QSOPlayGain: 100}
|
||||
if a.settings == nil {
|
||||
return out, nil
|
||||
}
|
||||
m, err := a.settings.GetMany(a.ctx,
|
||||
keyAudioFromRadio, keyAudioToRadio, keyAudioRecDevice, keyAudioListenDevice,
|
||||
keyAudioQSORecord, keyAudioQSODir, keyAudioPreroll, keyAudioPTTMethod, keyAudioPTTPort, keyAudioFormat,
|
||||
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain)
|
||||
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain, keyAudioQSOPlayGain)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -7005,6 +7015,9 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
|
||||
if n, _ := strconv.Atoi(m[keyAudioTXGain]); n > 0 && n <= 400 {
|
||||
out.TXGain = n
|
||||
}
|
||||
if n, _ := strconv.Atoi(m[keyAudioQSOPlayGain]); n > 0 && n <= 400 {
|
||||
out.QSOPlayGain = n
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -7036,6 +7049,9 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
|
||||
}
|
||||
// Up to 400 %: a mic recorded quietly needs real amplification, and the
|
||||
// clamp in the player keeps it from wrapping into noise.
|
||||
if s.QSOPlayGain <= 0 || s.QSOPlayGain > 400 {
|
||||
s.QSOPlayGain = 100
|
||||
}
|
||||
if s.TXGain <= 0 || s.TXGain > 400 {
|
||||
s.TXGain = 100
|
||||
}
|
||||
@@ -7053,6 +7069,7 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
|
||||
keyAudioFromGain: strconv.Itoa(s.FromGain),
|
||||
keyAudioMicGain: strconv.Itoa(s.MicGain),
|
||||
keyAudioTXGain: strconv.Itoa(s.TXGain),
|
||||
keyAudioQSOPlayGain: strconv.Itoa(s.QSOPlayGain),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -7777,7 +7794,7 @@ func (a *App) QSOAudioPlayOnAir() error {
|
||||
a.dvkPttKeyed = true
|
||||
a.pttMu.Unlock()
|
||||
}
|
||||
if err := a.audioMgr.Play(cfg.ToRadio, path, cfg.TXGain); err != nil {
|
||||
if err := a.audioMgr.Play(cfg.ToRadio, path, cfg.QSOPlayGain); err != nil {
|
||||
applog.Printf("qso-rec: playback on %q failed: %v", cfg.ToRadio, err)
|
||||
a.pttMu.Lock()
|
||||
keyed := a.dvkPttKeyed
|
||||
|
||||
+4
-2
@@ -10,7 +10,8 @@
|
||||
"The \"From radio\" and mic levels take effect as you drag the slider, and the RX level now applies to what you HEAR through OpsLog, not only to recordings.",
|
||||
"The padlocks on frequency, band, mode and times no longer flicker under the pointer and refuse the click.",
|
||||
"The callsign field is wider, taking the room from the RST pair.",
|
||||
"MP3 recordings no longer carry a hiss of their own: the 16→32 kHz conversion mirrored the whole spectrum only 7 dB down. It is now 57 dB down."
|
||||
"MP3 recordings no longer carry a hiss of their own: the 16→32 kHz conversion mirrored the whole spectrum only 7 dB down. It is now 57 dB down.",
|
||||
"Replaying a QSO recording on the air has its own level, separate from the voice keyer: a recording comes from a receiver line output and needs far less gain than a message spoken into a microphone."
|
||||
],
|
||||
"fr": [
|
||||
"Les filtres du cluster gagnent NEW POTA et NEW COUNTY, à côté des pastilles de statut existantes.",
|
||||
@@ -20,7 +21,8 @@
|
||||
"Les niveaux « From radio » et micro agissent pendant que vous déplacez le curseur, et le niveau RX s'applique désormais à ce que vous ENTENDEZ dans OpsLog, pas seulement aux enregistrements.",
|
||||
"Les cadenas de fréquence, bande, mode et heures ne scintillent plus sous le curseur et acceptent le clic.",
|
||||
"Le champ indicatif est plus large, la place venant de la paire RST.",
|
||||
"Les enregistrements MP3 ne portent plus un souffle qui leur est propre : la conversion 16→32 kHz recopiait tout le spectre à seulement 7 dB en dessous. Il est désormais à 57 dB."
|
||||
"Les enregistrements MP3 ne portent plus un souffle qui leur est propre : la conversion 16→32 kHz recopiait tout le spectre à seulement 7 dB en dessous. Il est désormais à 57 dB.",
|
||||
"La relecture d'un enregistrement de QSO a son propre niveau, distinct du manipulateur vocal : une prise vient de la sortie ligne d'un récepteur et demande bien moins de gain qu'un message dit au micro."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1198,13 +1198,13 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
from_radio: string; to_radio: string; recording_device: string; listening_device: string;
|
||||
qso_record: boolean; qso_dir: string; preroll_seconds: number;
|
||||
ptt_method: 'none' | 'cat' | 'rts' | 'dtr'; ptt_port: string; format: 'wav' | 'mp3';
|
||||
from_gain: number; mic_gain: number; tx_gain: number;
|
||||
from_gain: number; mic_gain: number; tx_gain: number; qso_play_gain: number;
|
||||
};
|
||||
type AudioDev = { id: string; name: string; default: boolean };
|
||||
const [audioCfg, setAudioCfg] = useState<AudioSettings>({
|
||||
from_radio: '', to_radio: '', recording_device: '', listening_device: '',
|
||||
qso_record: false, qso_dir: '', preroll_seconds: 8, ptt_method: 'none', ptt_port: '', format: 'wav',
|
||||
from_gain: 100, mic_gain: 100, tx_gain: 100,
|
||||
from_gain: 100, mic_gain: 100, tx_gain: 100, qso_play_gain: 100,
|
||||
});
|
||||
const [audioInputs, setAudioInputs] = useState<AudioDev[]>([]);
|
||||
const [audioOutputs, setAudioOutputs] = useState<AudioDev[]>([]);
|
||||
@@ -5118,6 +5118,12 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
onChange={(e) => { const v = parseInt(e.target.value, 10); setAudioField({ from_gain: v }); AudioApplyLevels(v, audioCfg.mic_gain); }} className="w-48 accent-primary" />
|
||||
<span className="font-mono text-xs w-12 text-right">{audioCfg.from_gain}%</span>
|
||||
</div>
|
||||
<Label className="text-sm">{t('aud.qsoPlayLevel')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="range" min={10} max={300} step={5} value={audioCfg.qso_play_gain}
|
||||
onChange={(e) => setAudioField({ qso_play_gain: parseInt(e.target.value, 10) })} className="w-48 accent-primary" />
|
||||
<span className="font-mono text-xs w-12 text-right">{audioCfg.qso_play_gain}%</span>
|
||||
</div>
|
||||
<Label className="text-sm">{t('aud.micLevel')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="range" min={10} max={300} step={5} value={audioCfg.mic_gain}
|
||||
|
||||
@@ -424,7 +424,7 @@ const en: Dict = {
|
||||
'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…',
|
||||
'aud.preroll': 'Pre-roll (seconds)', 'aud.format': 'File format', 'aud.wav': 'WAV (lossless, larger)', 'aud.mp3': 'MP3 (compressed, small)',
|
||||
'aud.fromLevel': 'From Radio level', 'aud.txLevel': 'Voice keyer level', 'aud.txLevelHint': 'Level of the recorded messages sent to the radio. Raise it if your voice keyer is much quieter than your microphone; Play previews at this same level. If the radio transmits almost nothing, its modulation source is still the front microphone: on an FTDX10 set MENU → SSB MOD SOURCE to REAR (the USB input).', 'aud.micLevel': 'Mic level', 'aud.levelHint': 'If your voice is louder than the station, lower Mic level.',
|
||||
'aud.fromLevel': 'From Radio level', 'aud.txLevel': 'Voice keyer level', 'aud.txLevelHint': 'Level of the recorded messages sent to the radio. Raise it if your voice keyer is much quieter than your microphone; Play previews at this same level. If the radio transmits almost nothing, its modulation source is still the front microphone: on an FTDX10 set MENU → SSB MOD SOURCE to REAR (the USB input).', 'aud.micLevel': 'Mic level', 'aud.qsoPlayLevel': 'QSO playback level', 'aud.levelHint': 'If your voice is louder than the station, lower Mic level.',
|
||||
'aud.autoSend': 'Auto-send the recording to the station by e-mail when I log a QSO',
|
||||
'aud.dvkTitle': 'Voice keyer messages (F1–F6)', 'aud.pttMethod': 'PTT method', 'aud.pttNone': 'None (VOX)', 'aud.pttCat': 'CAT (the radio link)', 'aud.pttRts': 'Serial RTS', 'aud.pttDtr': 'Serial DTR',
|
||||
'aud.testPtt': 'Test PTT', 'aud.pttPort': 'PTT COM port', 'aud.pickPort': 'Pick a COM port', 'aud.selectPort': '— select —', 'aud.refresh': 'Refresh',
|
||||
@@ -815,7 +815,7 @@ const fr: Dict = {
|
||||
'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…',
|
||||
'aud.preroll': 'Pré-enregistrement (secondes)', 'aud.format': 'Format de fichier', 'aud.wav': 'WAV (sans perte, plus volumineux)', 'aud.mp3': 'MP3 (compressé, léger)',
|
||||
'aud.fromLevel': 'Niveau depuis la radio', 'aud.txLevel': 'Niveau du voice keyer', 'aud.txLevelHint': "Niveau des messages enregistrés envoyés à la radio. Augmentez-le si votre voice keyer est bien plus faible que votre micro ; Lire fait entendre ce même niveau. Si la radio n'émet presque rien, c'est que sa source de modulation est restée le micro de façade : sur un FTDX10, réglez MENU → SSB MOD SOURCE sur REAR (l'entrée USB).", 'aud.micLevel': 'Niveau micro', 'aud.levelHint': 'Si votre voix est plus forte que la station, baissez le niveau micro.',
|
||||
'aud.fromLevel': 'Niveau depuis la radio', 'aud.txLevel': 'Niveau du voice keyer', 'aud.txLevelHint': "Niveau des messages enregistrés envoyés à la radio. Augmentez-le si votre voice keyer est bien plus faible que votre micro ; Lire fait entendre ce même niveau. Si la radio n'émet presque rien, c'est que sa source de modulation est restée le micro de façade : sur un FTDX10, réglez MENU → SSB MOD SOURCE sur REAR (l'entrée USB).", 'aud.micLevel': 'Niveau micro', 'aud.qsoPlayLevel': 'Niveau de relecture QSO', 'aud.levelHint': 'Si votre voix est plus forte que la station, baissez le niveau micro.',
|
||||
'aud.autoSend': "Envoyer automatiquement l'enregistrement à la station par e-mail lorsque j'enregistre un QSO",
|
||||
'aud.dvkTitle': 'Messages du manipulateur vocal (F1–F6)', 'aud.pttMethod': 'Méthode PTT', 'aud.pttNone': 'Aucune (VOX)', 'aud.pttCat': 'CAT (la liaison radio)', 'aud.pttRts': 'RTS série', 'aud.pttDtr': 'DTR série',
|
||||
'aud.testPtt': 'Tester le PTT', 'aud.pttPort': 'Port COM du PTT', 'aud.pickPort': 'Choisir un port COM', 'aud.selectPort': '— choisir —', 'aud.refresh': 'Actualiser',
|
||||
|
||||
@@ -1608,6 +1608,7 @@ export namespace main {
|
||||
from_gain: number;
|
||||
mic_gain: number;
|
||||
tx_gain: number;
|
||||
qso_play_gain: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AudioSettings(source);
|
||||
@@ -1628,6 +1629,7 @@ export namespace main {
|
||||
this.from_gain = source["from_gain"];
|
||||
this.mic_gain = source["mic_gain"];
|
||||
this.tx_gain = source["tx_gain"];
|
||||
this.qso_play_gain = source["qso_play_gain"];
|
||||
}
|
||||
}
|
||||
export class AutostartLaunchResult {
|
||||
|
||||
Reference in New Issue
Block a user