feat: stop a QSO recording and play it back on the air
The situation, in the operator's words: working VP2MAA, recording, and he asks to hear his own signal. So: stop, play, and the recording is still saved with the QSO. Stopping FREEZES the take rather than ending it — nothing more is captured, nothing is discarded, and logging writes the file exactly as before. Playback reuses the voice keyer's path: same output device, same PTT keying and release with its generation guard, so a replay cannot be cut short by a stale unkey. Playing is refused while still recording. The transmitted audio would feed back into the same take on any rig monitoring its own transmission, and stopping is one click the operator has already made — it is their statement that the take is finished, not a hoop. The elapsed clock freezes with the recording and resumes from where it stopped, because it now shows the LENGTH OF THE FILE rather than the time since the QSO began. Only a fresh take returns it to zero.
This commit is contained in:
@@ -7606,6 +7606,78 @@ func (a *App) stopManualQSORecorder() {
|
||||
applog.Printf("qso-rec: manual recording finished — sound devices released")
|
||||
}
|
||||
|
||||
// QSOAudioStop freezes the recording without ending it, so it can be played
|
||||
// back and still be saved with the QSO afterwards.
|
||||
func (a *App) QSOAudioStop() bool {
|
||||
if a.qsoRec == nil {
|
||||
return false
|
||||
}
|
||||
return a.qsoRec.PauseQSO()
|
||||
}
|
||||
|
||||
// QSOAudioResume continues a frozen recording, appending to what is there.
|
||||
func (a *App) QSOAudioResume() bool {
|
||||
if a.qsoRec == nil {
|
||||
return false
|
||||
}
|
||||
return a.qsoRec.ResumeQSO()
|
||||
}
|
||||
|
||||
// QSOAudioPlayOnAir transmits the recording made so far, through the DVK's
|
||||
// output device and PTT.
|
||||
//
|
||||
// The situation this is for: you are working a station, you have been recording,
|
||||
// and they ask to hear it. You stop, you send it to them, and the recording is
|
||||
// still saved with the QSO.
|
||||
//
|
||||
// It goes out over the AIR. So it refuses unless the take has been stopped
|
||||
// first: playing while still recording would feed the transmitted audio back
|
||||
// into the same take, and on a rig monitoring its own transmission that is a
|
||||
// loop. Stopping is the operator's statement that the take is finished, and it
|
||||
// is one click they have already made.
|
||||
func (a *App) QSOAudioPlayOnAir() error {
|
||||
if a.qsoRec == nil || a.audioMgr == nil {
|
||||
return fmt.Errorf("audio not initialized")
|
||||
}
|
||||
if !a.qsoRec.Paused() {
|
||||
return fmt.Errorf("stop the recording before playing it on the air")
|
||||
}
|
||||
pcm, err := a.qsoRec.PeekQSO()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// A plain WAV in the data dir, overwritten each time: the player takes a
|
||||
// path, and MP3 encoding would add seconds to something the operator is
|
||||
// waiting on with the transmitter keyed.
|
||||
path := filepath.Join(a.dataDir, "qso_playback.wav")
|
||||
if err := audio.WritePCM(path, pcm); err != nil {
|
||||
return fmt.Errorf("prepare playback: %w", err)
|
||||
}
|
||||
|
||||
cfg, _ := a.GetAudioSettings()
|
||||
if err := a.pttKey(cfg); err != nil {
|
||||
applog.Printf("qso-rec: PTT on failed before playback: %v", err)
|
||||
// Keep going — the audio still reaches the rig and the operator may use VOX.
|
||||
} else if cfg.PTTMethod != "" && cfg.PTTMethod != "none" {
|
||||
a.pttMu.Lock()
|
||||
a.dvkPttKeyed = true
|
||||
a.pttMu.Unlock()
|
||||
}
|
||||
if err := a.audioMgr.Play(cfg.ToRadio, path, cfg.TXGain); err != nil {
|
||||
a.pttMu.Lock()
|
||||
keyed := a.dvkPttKeyed
|
||||
gen := a.pttGen
|
||||
a.dvkPttKeyed = false
|
||||
a.pttMu.Unlock()
|
||||
if keyed {
|
||||
go a.dvkUnkeyPTT(gen)
|
||||
}
|
||||
return err
|
||||
}
|
||||
applog.Printf("qso-rec: playing the recording on the air (%d bytes)", len(pcm))
|
||||
return nil
|
||||
}
|
||||
|
||||
// QSOAudioCancel drops the in-progress recording (callsign cleared, QSO
|
||||
// abandoned without logging).
|
||||
func (a *App) QSOAudioCancel() {
|
||||
|
||||
Reference in New Issue
Block a user