feat: a level control for the voice keyer, and name every CAT backend in its PTT list
The messages went to the radio exactly as recorded. Nothing in OpsLog could raise them, so a microphone captured quietly drove the rig quietly and the only remedies were the radio's own USB input menu or the Windows mixer — which is where the operator was heading. There is now a level from 10 to 400 %, applied with clamping (a wrap would turn loud speech into noise on the air), and Play previews at that same level so the adjustment is made against what will actually be transmitted. The PTT method list also named only OmniRig, Flex, Icom and TCI, so choosing a native Yaesu left "CAT" with no backend beside it — which reads as "there is no CAT PTT for my radio" and sends the operator to RTS on a COM port that has nothing to do with the rig. Same list-needing-every-member shape as three earlier bugs in this feature. The TestPTT log line had the same rot: it said "CAT via OmniRig" whatever backend was running.
This commit is contained in:
@@ -137,6 +137,7 @@ const (
|
||||
keyAudioQSORecord = "audio.qso_record" // "1" → auto-record every QSO
|
||||
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)
|
||||
keyAudioPTTMethod = "audio.ptt_method" // "none" (VOX) | "rts" | "dtr"
|
||||
keyAudioPTTPort = "audio.ptt_port" // COM port for serial PTT
|
||||
keyAudioFormat = "audio.qso_format" // "wav" | "mp3"
|
||||
@@ -6794,6 +6795,7 @@ type AudioSettings struct {
|
||||
Format string `json:"format"` // "wav" | "mp3"
|
||||
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
|
||||
}
|
||||
|
||||
// ListAudioInputDevices / ListAudioOutputDevices enumerate WASAPI endpoints
|
||||
@@ -6803,14 +6805,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}
|
||||
out := AudioSettings{PrerollSeconds: 8, PTTMethod: "none", Format: "wav", FromGain: 100, MicGain: 100, TXGain: 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)
|
||||
keyAudioFromGain, keyAudioMicGain, keyAudioTXGain)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -6838,6 +6840,9 @@ func (a *App) GetAudioSettings() (AudioSettings, error) {
|
||||
if n, _ := strconv.Atoi(m[keyAudioMicGain]); n > 0 && n <= 400 {
|
||||
out.MicGain = n
|
||||
}
|
||||
if n, _ := strconv.Atoi(m[keyAudioTXGain]); n > 0 && n <= 400 {
|
||||
out.TXGain = n
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -6867,6 +6872,11 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
|
||||
if s.MicGain <= 0 || s.MicGain > 400 {
|
||||
s.MicGain = 100
|
||||
}
|
||||
// Up to 400 %: a mic recorded quietly needs real amplification, and the
|
||||
// clamp in the player keeps it from wrapping into noise.
|
||||
if s.TXGain <= 0 || s.TXGain > 400 {
|
||||
s.TXGain = 100
|
||||
}
|
||||
for k, v := range map[string]string{
|
||||
keyAudioFromRadio: s.FromRadio,
|
||||
keyAudioToRadio: s.ToRadio,
|
||||
@@ -6880,6 +6890,7 @@ func (a *App) SaveAudioSettings(s AudioSettings) error {
|
||||
keyAudioFormat: format,
|
||||
keyAudioFromGain: strconv.Itoa(s.FromGain),
|
||||
keyAudioMicGain: strconv.Itoa(s.MicGain),
|
||||
keyAudioTXGain: strconv.Itoa(s.TXGain),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, k, v); err != nil {
|
||||
return err
|
||||
@@ -8356,7 +8367,7 @@ func (a *App) DVKPlay(slot int) error {
|
||||
a.dvkPttKeyed = true
|
||||
a.pttMu.Unlock()
|
||||
}
|
||||
if err := a.audioMgr.Play(cfg.ToRadio, path); err != nil {
|
||||
if err := a.audioMgr.Play(cfg.ToRadio, path, cfg.TXGain); err != nil {
|
||||
a.pttMu.Lock()
|
||||
keyed := a.dvkPttKeyed
|
||||
gen := a.pttGen
|
||||
@@ -8400,7 +8411,8 @@ func (a *App) unkeyIfCurrent(gen int64) {
|
||||
}
|
||||
|
||||
// pttKey keys the transmitter using the configured method:
|
||||
// - "cat" → OmniRig (sets the Tx parameter to PM_TX)
|
||||
// - "cat" → whichever CAT backend is active (OmniRig, Flex, Icom, TCI,
|
||||
// Yaesu, Xiegu — they all implement SetPTT)
|
||||
// - "rts"/"dtr" → open the COM port and assert that line, held during TX
|
||||
// - "none" → VOX, nothing to do
|
||||
func (a *App) pttKey(cfg AudioSettings) error {
|
||||
@@ -8490,7 +8502,16 @@ func (a *App) TestPTT(cfg AudioSettings) error {
|
||||
if cfg.PTTMethod == "rts" || cfg.PTTMethod == "dtr" {
|
||||
applog.Printf("ptt: TestPTT method=%q port=%q", cfg.PTTMethod, cfg.PTTPort)
|
||||
} else {
|
||||
applog.Printf("ptt: TestPTT method=%q (CAT via OmniRig — serial port not used)", cfg.PTTMethod)
|
||||
// Name the backend that will actually key — "via OmniRig" was left over
|
||||
// from when that was the only one, and it sent operators on a native
|
||||
// backend looking for a problem with OmniRig that they do not even run.
|
||||
backend := "no CAT backend"
|
||||
if a.cat != nil {
|
||||
if st := a.cat.State(); st.Backend != "" {
|
||||
backend = st.Backend
|
||||
}
|
||||
}
|
||||
applog.Printf("ptt: TestPTT method=%q (CAT via %s — serial port not used)", cfg.PTTMethod, backend)
|
||||
}
|
||||
if cfg.PTTMethod == "" || cfg.PTTMethod == "none" {
|
||||
return fmt.Errorf("PTT method is None (VOX) — pick CAT, RTS or DTR first")
|
||||
@@ -8524,7 +8545,9 @@ func (a *App) DVKPreview(slot int) error {
|
||||
return fmt.Errorf("audio not initialized")
|
||||
}
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
return a.audioMgr.Play(cfg.ListeningDevice, a.dvkPath(slot))
|
||||
// Preview at the SAME level the rig will get, or the operator adjusts against
|
||||
// a sound that is not the one going on the air.
|
||||
return a.audioMgr.Play(cfg.ListeningDevice, a.dvkPath(slot), cfg.TXGain)
|
||||
}
|
||||
|
||||
// DVKStop halts any voice-keyer playback.
|
||||
|
||||
Reference in New Issue
Block a user