feat: record a contact by hand when automatic recording is off
A red dot in the slot the counter will occupy. Click it and the counter starts,
the dot disappears, and logging the QSO writes the file exactly as an automatic
recording would — the save path already keyed off an active take, not off the
setting.
With automatic recording disabled the capture engine is not idle, it is not
running at all, so a manual take starts it. Two consequences handled here:
- It is released again when the take ends — logged, cancelled, or dropped for
being a digital mode. An operator who records one contact does not expect
their microphone held open for the rest of the session. The release is a
deferred call at the top of the save path so every exit goes through it,
rather than one that has to be remembered at each return.
- There is no pre-roll. An automatic recording opens with the seconds BEFORE
the callsign was typed; a manual one can only begin now. The button's
tooltip says so, because otherwise the difference between two files is a
mystery to the person holding them.
The button only appears where it can work: devices configured, automatic
recording off, and a mode whose audio is worth keeping. That state is asked of
the backend rather than inferred from a preference in the interface, so the two
cannot drift apart, and it is re-read when settings are saved.
This commit is contained in:
@@ -538,10 +538,15 @@ type App struct {
|
||||
ampsMu sync.Mutex // guards ampInsts
|
||||
ampInsts map[string]*ampInst // one running client per enabled configured amplifier, by config ID
|
||||
audioMgr *audio.Manager
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
solar *solar.Manager // live space-weather (SFI/SSN/A/K) for the header + QSO stamping
|
||||
lotwUsers *lotwusers.Manager // LoTW user-activity list (badge next to the callsign)
|
||||
scp *scp.Manager // Super Check Partial / N+1 callsign master list
|
||||
qsoRec *audio.Recorder // continuous QSO recorder (rolling pre-roll)
|
||||
// qsoRecManual marks a take the operator started by hand while automatic
|
||||
// recording is OFF. Such a take opened the sound devices itself, so it must
|
||||
// close them again when it ends — an operator who records one contact does
|
||||
// not expect their microphone held open for the rest of the session.
|
||||
qsoRecManual atomic.Bool
|
||||
solar *solar.Manager // live space-weather (SFI/SSN/A/K) for the header + QSO stamping
|
||||
lotwUsers *lotwusers.Manager // LoTW user-activity list (badge next to the callsign)
|
||||
scp *scp.Manager // Super Check Partial / N+1 callsign master list
|
||||
|
||||
// NET Control: persistent net definitions/rosters (global JSON) + the live
|
||||
// session (in-memory only — active stations currently in QSO).
|
||||
@@ -7059,6 +7064,10 @@ func (a *App) saveQSORecording(q *qso.QSO) {
|
||||
applog.Printf("qso-rec: PANIC in saveQSORecording: %v", r)
|
||||
}
|
||||
}()
|
||||
// Every exit from here ends the take, including the digital-mode discard and
|
||||
// the snapshot failure below — so release the devices from one place rather
|
||||
// than remembering to do it at each return.
|
||||
defer a.stopManualQSORecorder()
|
||||
if a.qsoRec == nil || !a.qsoRec.Active() {
|
||||
return
|
||||
}
|
||||
@@ -7536,12 +7545,74 @@ func (a *App) QSOAudioResetClock() bool {
|
||||
return a.qsoRec.Active()
|
||||
}
|
||||
|
||||
// QSOAudioManualReady reports whether a recording can be started BY HAND: the
|
||||
// capture devices are configured, but automatic recording is off.
|
||||
//
|
||||
// It is what decides whether the entry strip offers a record button. Without
|
||||
// it the button would appear for operators who have no sound card configured
|
||||
// and could only ever produce an error.
|
||||
func (a *App) QSOAudioManualReady() bool {
|
||||
if a.qsoRec == nil {
|
||||
return false
|
||||
}
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
return !cfg.QSORecord && strings.TrimSpace(cfg.FromRadio) != ""
|
||||
}
|
||||
|
||||
// QSOAudioManualStart begins a recording when automatic recording is off.
|
||||
//
|
||||
// With the automatic recorder disabled the capture engine is not merely idle,
|
||||
// it is not running at all — so this starts it first. That costs the pre-roll:
|
||||
// an automatic recording opens with the seconds BEFORE the callsign was typed,
|
||||
// a manual one can only start now. Better to say that in a comment than to have
|
||||
// an operator wonder why one file has the DX's call in it and the other does
|
||||
// not.
|
||||
func (a *App) QSOAudioManualStart() bool {
|
||||
if a.qsoRec == nil {
|
||||
return false
|
||||
}
|
||||
if !a.qsoRec.Running() {
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
if strings.TrimSpace(cfg.FromRadio) == "" {
|
||||
return false
|
||||
}
|
||||
if err := a.qsoRec.Start(cfg.FromRadio, cfg.RecordingDevice, cfg.PrerollSeconds); err != nil {
|
||||
applog.Printf("qso-rec: manual start failed: %v", err)
|
||||
return false
|
||||
}
|
||||
fromGain, micGain := float64(cfg.FromGain)/100, float64(cfg.MicGain)/100
|
||||
if cfg.FromGain == 0 {
|
||||
fromGain = 1
|
||||
}
|
||||
if cfg.MicGain == 0 {
|
||||
micGain = 1
|
||||
}
|
||||
a.qsoRec.SetGains(fromGain, micGain)
|
||||
a.qsoRecManual.Store(true)
|
||||
applog.Printf("qso-rec: manual recording started by the operator")
|
||||
}
|
||||
a.qsoRec.BeginQSO()
|
||||
return a.qsoRec.Active()
|
||||
}
|
||||
|
||||
// stopManualQSORecorder releases the sound devices after a hand-started take,
|
||||
// but only if this session opened them. When automatic recording is on the
|
||||
// engine belongs to the app and must keep running for the next contact.
|
||||
func (a *App) stopManualQSORecorder() {
|
||||
if a.qsoRec == nil || !a.qsoRecManual.Swap(false) {
|
||||
return
|
||||
}
|
||||
a.qsoRec.Stop()
|
||||
applog.Printf("qso-rec: manual recording finished — sound devices released")
|
||||
}
|
||||
|
||||
// QSOAudioCancel drops the in-progress recording (callsign cleared, QSO
|
||||
// abandoned without logging).
|
||||
func (a *App) QSOAudioCancel() {
|
||||
if a.qsoRec != nil {
|
||||
a.qsoRec.DiscardQSO()
|
||||
}
|
||||
a.stopManualQSORecorder()
|
||||
}
|
||||
|
||||
// RestartQSORecorder applies new audio settings to the running recorder.
|
||||
|
||||
Reference in New Issue
Block a user