Commit Graph
26 Commits
Author SHA1 Message Date
Storm Dragon 01ca0da7ff Optionally remember transmission state on reconnect. 2026-09-06 18:24:52 -04:00
Storm Dragon 4ef8553c7d First attempt didn't work. 2026-09-06 01:13:31 -04:00
Storm Dragon 2b5fe91d6c Restore transmit state after unmute/deafen. Send proper notifications to other users. 2026-09-06 01:04:10 -04:00
Brandon McGintyandClaude Opus 5 0d62f745ca reuse the stereo Opus encoder across connections
Every connect built a fresh stereo encoder for file playback. Each one holds
a little under a megabyte of encoder state, so on a flaky link that is close
to a megabyte of churn per reconnect for no benefit; the encoder is already
reset when file playback ends.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:32:30 -04:00
Brandon McGintyandClaude Opus 5 5cdb2684b5 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 <noreply@anthropic.com>
2026-08-24 12:32:21 -04:00
Brandon McGintyandClaude Opus 5 77fad24560 release the audio stream a reconnect replaces
connect assigned b.Stream without releasing the stream already there, and
OnDisconnect started a reconnect loop unconditionally, so two connects could
race to install a stream. The loser was simply overwritten.

An overwritten stream is never destroyed, so it keeps its OpenAL device, its
render thread, and its entry in the shared audio listener list, which only
Destroy removes. It therefore stays subscribed for the life of the process
and every later audio packet from every user is dispatched to it as well: a
goroutine, a packet queue and a set of playback buffers per orphan, per
user. The file playback stream and the tone test saver were replaced the
same way.

Release whatever is being replaced, and allow only one reconnect loop at a
time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:32:06 -04:00
Brandon McGintyandClaude Opus 5 872149c977 run all terminal work on the UI goroutine and lock shared client state
Route network and audio callbacks through a bounded UI queue.
Protocol handlers, the audio thread, and key handlers all drew to
termbox widgets directly, which is a data race against the render
loop. postUI is now the only path from a callback to a widget, work is
dropped during shutdown, and the queue never blocks the caller.

Guard the mutable client state with mutexes.
Connection and transmission flags, the selected user, the muted
channel set, and the audio stream pointer were each read and written
from at least two goroutines. Lookups that walk the client's user and
channel maps take the client lock and copy what they need.

Snapshot the display string when a tree item is built.
Tree items held live pointers and formatted themselves during
rendering, so a user removed between rebuild and draw was dereferenced
on the render path.

Cancel reconnect retries on shutdown and release audio before
reconnecting.
Quitting during a retry left the goroutine sleeping until its timer
expired, and a reconnect built a second OpenAL stream on top of the
first. Cleanup is idempotent so repeated disconnect events are safe.

Make UI shutdown idempotent and join the event poller.
Close could be called more than once, and the polling goroutine could
be left trying to deliver an event after Run had returned. Run now
also reports a termbox initialization failure instead of returning nil.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:42:52 -04:00
Brandon McGintyandClaude Opus 5 ecca0a63ed add a tone test mode that bypasses the soundcard
Add -tone-test to transmit a generated 440 Hz Opus tone and save
incoming audio to the file named by -tone-out.
Diagnosing an audio problem previously required a working capture and
playback device, which is exactly what is in question. This mode opens
no OpenAL device at all, so the network and codec path can be tested
on a machine with no sound hardware.

Open the capture file before starting the generator.
Starting transmission first left a tone goroutine running with nowhere
to write when the path was unusable.

Refuse file playback while in tone test mode, and ignore the
microphone volume keys.
Both operate on a stream that does not exist here.

Stop the generator and detach the file saver on disconnect.
A reconnect otherwise attached a second saver to the same file.

Add -auto-transmit to key the microphone as soon as the connection is
up.
Useful for a bot or a monitoring client that should never need a
keypress. It runs from both the connect event and the point where the
audio stream is created, because the server's welcome arrives before
the stream exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:42:52 -04:00
Brandon McGintyandClaude Opus 5 c5baaae6a7 stop file playback processes and their children reliably
Kill ffmpeg's whole process group rather than just the process.
ffmpeg spawns helpers for some inputs, so stopping playback left them
running and holding the output pipe open. The player now starts the
command in its own process group and signals the group.

Wait for the playback worker to finish before starting a new file.
Playing a second file while the first was shutting down left two
workers writing to the same stream.

Make pausing nonblocking.
The pause path could block on a full pipe and hang the UI thread that
requested it.

Install and reset the stereo encoder under the client lock.
File playback swapped the encoder field directly while a voice frame
could be encoding with it, and a finished file left the encoder
carrying state into the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:42:52 -04:00
Brandon McGintyandClaude Opus 5 fbb6a148ff restore the saved microphone volume on connect
Apply the persisted microphone volume when the stream is created.
The value was written to the configuration on every adjustment but
never read back, so the microphone returned to full gain on each
start.

Read a saved volume of zero as zero rather than as a missing value.
A user who muted their microphone and quit came back unmuted.

Save the configuration after a volume change and report a failure.
The setter only updated the in-memory value, so the new level was lost
unless something else happened to save afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:42:52 -04:00
Brandon McGintyandClaude Opus 5 cb4f91596e make microphone AGC less aggressive and switchable
Lower the gain ceiling and slow the attack of the automatic gain
control.
It amplified room noise between words hard enough to be audible as a
rising hiss whenever the speaker paused.

Add an AGC toggle on F12 and an /agc command.
AGC has always been applied unconditionally, which is wrong for a
microphone that is already levelled by hardware or by the system
mixer. The preference is saved and reapplied on connect, defaulting to
on so existing setups are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:42:52 -04:00
Brandon McGintyandClaude Opus 5 60470ad091 make per-user audio state thread-safe
Move volume, boost, mute, and OpenAL source behind accessors guarded
by a per-user mutex.
The audio goroutine reads these fields on every decoded packet while
the UI writes them from key handlers, which is a data race on the
gain a stream is currently rendering with.

Also add the per-user sequence fields the decoder needs to notice
gaps in a user's audio stream.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 14:22:33 -04:00
Storm Dragon 9bc514e74f Remove voice effects 2026-07-14 18:41:50 -04:00
Storm Dragon 342f934029 Improve action menu escape handling
Make the F11 action menu close through a shared close action so Escape and the explicit Close actions menu item follow the same path. Treat Escape-prefixed Up and Down events as close inputs while the action menu is active, which handles termbox InputAlt behavior after pressing Escape. Preserve and restore the user/channel tree selection across action menu open and close, and preserve selection across live tree rebuilds.

Tested with: GOCACHE=/tmp/barnard-go-cache go test ./...
2026-06-28 23:57:29 -04:00
Storm Dragon 1f1f72202e Hopefully fixed notification regression. 2026-05-20 18:36:49 -04:00
Storm Dragon cc483685ef Add actions menu admin features 2026-05-19 01:06:01 -04:00
Storm Dragon 69674a0dab Add standards-aware recording 2026-05-14 00:42:30 -04:00
Storm Dragon e3f90a76c0 Fix problem with not being notified of disconnects. 2025-12-12 15:13:37 -05:00
Storm Dragon fae372bb78 Added /file and /stop commands. 2025-11-30 20:31:06 -05:00
Storm DragonandClaude f96cb1f79b Add real-time voice effects for outgoing audio
Implements 7 voice effects that can be cycled through with F12:
- None (default)
- Echo: Single repeating delay with feedback (250ms)
- Reverb: Multiple short delays without feedback
- High Pitch: Chipmunk voice using cubic interpolation
- Low Pitch: Deep voice effect
- Robot: Ring modulation for robotic sound
- Chorus: Layered voices with pitch variations

The effects are applied after noise suppression and AGC in the audio
pipeline. Selected effect is persisted to config file. Includes
comprehensive documentation in README.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-13 16:27:08 -04:00
Storm Dragon df7159bad1 Initial noise suppression added. 2025-08-16 21:29:52 -04:00
Storm Dragon 657ff1dbef Fixed muting so that it should be consistant. meaning, if a muted user leaves and comes back they should still be muted. Channel mute and unmute now specifically sets mute/unmute for each user so the whole channel is in deed muted or unmuted. 2025-07-05 18:25:29 -04:00
Storm Dragon 8c62babd48 Make sure channels are initialized to unmuted when you join. 2025-01-29 00:51:13 -05:00
Storm Dragon c7362dfc36 Now when people join after a channel has been muted the will be muted too. 2025-01-29 00:24:59 -05:00
Storm Dragon 2e337db3c5 Updated everything for dependencies. All sub packages are now part of the project. This was a massive update, hopefully won't have to be reverted. 2025-01-16 17:03:01 -05:00
Storm Dragon 3f0246a4f8 Initial commit, lots of cleanup and stuff to do, it may not work. 2025-01-15 23:43:44 -05:00