From 5cdb2684b5f0612820acbf9152e10da787665d8d Mon Sep 17 00:00:00 2001 From: Brandon McGinty Date: Mon, 24 Aug 2026 12:32:21 -0400 Subject: [PATCH] keep the tone test output open across a reconnect The saver reserves its output file exclusively, so opening it a second time fails. connect opened a new one per connection and a disconnect closed it, which meant the first reconnect died with "file exists" instead of resuming. Open the saver once and re-attach it on reconnect, and close it on exit instead. Detaching and closing are now separate, since only shutdown wants both. Co-Authored-By: Claude Opus 5 --- barnard.go | 11 ++++++++++- client.go | 29 ++++++++++++++++++++--------- ui.go | 12 ++++++++---- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/barnard.go b/barnard.go index 816fdb4..5b2d71f 100644 --- a/barnard.go +++ b/barnard.go @@ -116,11 +116,20 @@ func (b *Barnard) cleanupConnectionAudio() { b.connectionMutex.Unlock() } -func (b *Barnard) cleanupToneTestAudio() { +// detachToneTestAudio unsubscribes the saver without closing its output, so a +// reconnect can re-attach the same file. The saver's output is opened +// exclusively and cannot be reopened. +func (b *Barnard) detachToneTestAudio() { if b.toneTestSaverDetach != nil { b.toneTestSaverDetach.Detach() b.toneTestSaverDetach = nil } +} + +// cleanupToneTestAudio detaches the saver and closes its output. Use it when +// the client is shutting down, not between connections. +func (b *Barnard) cleanupToneTestAudio() { + b.detachToneTestAudio() if b.toneTestSaver != nil { b.toneTestSaver.Stop() b.toneTestSaver = nil diff --git a/client.go b/client.go index 6e248a0..6eaf93d 100644 --- a/client.go +++ b/client.go @@ -61,15 +61,24 @@ func (b *Barnard) connect(reconnect bool) bool { // --- Tone test mode: skip all OpenAL; generate 440 Hz tone // --- and save incoming audio to a file. - // Open the output first. Starting transmission before this succeeds - // leaves an orphaned tone goroutine when the path is unusable. - saver, err := NewAudioFileSaver(b.ToneTestOutput) - if err != nil { - b.exitWithError(err) - return false + // The output is reserved exclusively, so it can only be opened once. + // A reconnect keeps writing to the saver opened for the first + // connection instead of failing on the file that already exists. + if b.toneTestSaver == nil { + // Open the output first. Starting transmission before this + // succeeds leaves an orphaned tone goroutine when the path is + // unusable. + saver, err := NewAudioFileSaver(b.ToneTestOutput) + if err != nil { + b.exitWithError(err) + return false + } + b.toneTestSaver = saver } - b.toneTestSaver = saver - b.toneTestSaverDetach = b.Client.Config.AttachAudio(saver) + // Detach any registration left over from the previous connection so + // the shared audio listener list does not grow once per reconnect. + b.detachToneTestAudio() + b.toneTestSaverDetach = b.Client.Config.AttachAudio(b.toneTestSaver) b.setConnected(true) if b.toneTestAutoTransmit() { @@ -225,7 +234,9 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) { close(b.toneTestStop) b.toneTestStop = nil } - b.cleanupToneTestAudio() + // Keep the saver's output open: it was reserved exclusively and a + // reconnect re-attaches the same file. It is closed on exit. + b.detachToneTestAudio() } b.Notify("disconnect", "me", reason) diff --git a/ui.go b/ui.go index 6d2964e..e266cea 100644 --- a/ui.go +++ b/ui.go @@ -445,15 +445,19 @@ func (b *Barnard) OnMicVolumeUp(ui *uiterm.Ui, key uiterm.Key) { } func (b *Barnard) OnQuitPress(ui *uiterm.Ui, key uiterm.Key) { - b.stopReconnects() - b.StopRecordingIfActive(true) - b.Client.Disconnect() - b.Ui.Close() + b.shutdown() } func (b *Barnard) CommandExit(ui *uiterm.Ui, cmd string) { + b.shutdown() +} + +// shutdown releases everything that outlives a single connection, including +// the tone test saver's output file, which reconnects deliberately keep open. +func (b *Barnard) shutdown() { b.stopReconnects() b.StopRecordingIfActive(true) + b.cleanupToneTestAudio() b.Client.Disconnect() b.Ui.Close() }