Buffers had a lower bound but no upper one, and it is allocated per speaking
user twice over: as a queue of decoded frames and as OpenAL playback
buffers. Each buffer holds up to one maximum sized frame, so a large value
multiplied by a populated channel is a substantial amount of memory.
Cap it, and reject the flag up front so the failure names the flag rather
than surfacing later as a dial error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Record decoder creation, sequence numbers, frame lengths, and decode
results for each tunneled audio packet, and note when a slow listener
has a packet dropped.
Diagnosing a codec or ordering problem previously meant adding print
statements and rebuilding. These sit at info and debug, so they cost
nothing unless -logfile is given.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Append the default port only when the address does not already have
one.
The check looked for any colon, so a bare IPv6 literal such as
2001:db8::1 was treated as already carrying a port and was passed
through unusable, while a bracketed literal without a port never got
one appended.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Give the notification queue a buffer and drop events when it is full.
The channel was unbuffered, so a slow or hung notification command
blocked whichever UI or network callback happened to raise the event.
Expand the command placeholders in a single pass.
Substituting %event, then %who, then %what meant text arriving in an
earlier field could contain a later placeholder and have it expanded,
letting a remote user inject their own text into the command.
Move the command runner behind a build tag and drop the POSIX default
on Windows.
The helper script it pointed at does not exist there, so the default
was a command that could only fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reject a FIFO path that points to an existing non-FIFO object.
Previously we removed whatever was at that path before creating the
pipe, so pointing -fifo at a regular file silently deleted it. An
existing FIFO is reused and only a missing path is created.
Move the implementation into fifo_unix.go and fifo_windows.go.
This lets the Unix build use syscall.Mkfifo directly, which is not
available on Windows.
Prevent the reader from endlessly spinning on a closed FIFO after an
error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Return errors from the save path rather than panicking.
Every write panicked on failure, so a read-only or missing
configuration directory killed a running client. The parent directory
is created when absent, and callers now show the failure in the output
window and carry on.
Write through an unpredictable temporary file.
The old fixed ".tmp" name next to the configuration was a symlink
target an attacker could plant in advance.
Serialize configuration reads and writes.
Hotkey handlers, the audio thread, and the connection callbacks all
touch the same structure, so saves could interleave with updates.
Parse addresses with SplitHostPort.
Splitting on every colon broke IPv6 addresses and panicked outright on
an address with no port. Both now fall back to Mumble's default port.
Fail immediately when an explicitly requested config file is missing
or is not a regular file.
Silently falling back to defaults hid a mistyped -config path.
Supply defaults for the clear-output and scroll-to-top and -bottom
hotkeys.
The UI registered listeners for them but the configuration never
filled the keys in, so the bindings were nil and the keys did nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add -audio-interval to choose a 10, 20, 40, or 60 ms packet duration.
Longer packets cut per-packet overhead on slow or lossy links at the
cost of latency. Anything else is rejected at startup rather than
silently truncated to 10 ms frames.
Step the audio sequence by the frame duration.
Sequence numbers are Mumble timestamps in 10 ms units, so a 60 ms
packet advances the counter by six. Incrementing by one made the
receiver see every packet as arriving far too early.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a per-user jitter buffer with restart resync.
Incoming audio was rendered straight to OpenAL as packets arrived, so
normal network jitter produced gaps and the renderer starved between
frames. Buffer each user's decoded frames for a configurable playout
delay (-jitter-buffer, default 40ms) and let the render thread pull
from that buffer instead.
Drop late packets, and use packet duration to set the expected frame
number.
A packet below the expected frame number is dropped rather than
stalling delivery, and the expected frame is stepped by the packet's
real duration instead of a fixed 10ms.
After detecting a large backward jump in frame numbering, along with a
sustained run of late packets, resync to the new frame numbering.
When a sender switches audio devices, Mumble destroys and recreates
their audio stream. This resets their frame number to zero without
sending a terminator packet, which causes a potentially endless hang
while we wait for a frame number that will not arrive for hours.
Own the OpenAL context on one dedicated render thread.
Contexts are current to an OS thread, so creating sources and buffers
from whichever goroutine happened to be running could operate on no
context at all. Every source and buffer is now created, filled, and
deleted on that thread, and file playback renders through it too.
Report device and capture failures with the device name.
Startup errors said only that a device could not be opened, and a
capture stall was reported as a microphone failure even though it is
normal scheduler timing. Fatal microphone errors now exit instead of
leaving a client that cannot transmit.
Release queued buffers and deactivate the context on shutdown.
Streams were torn down while buffers were still queued on a source,
and the context was destroyed while still current.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add the OCB2-AES128 crypt state used by the Mumble UDP channel.
CryptSetup carries the key and both nonces; the handler installs them
instead of being an unimplemented stub, so UDP audio can be encrypted
and decrypted at all.
Add the Mumble 1.5 native UDP transport.
The socket opens during the handshake so it is ready when CryptSetup
arrives, and audio is sent as a protobuf envelope with a leading type
byte. Advertise ourselves as a 1.5 client so servers select it.
Advance the decrypt IV from the server nonce.
Using the client nonce for inbound packets desynchronized the two
sides after the first packet.
Keep the legacy UDPVoiceOpus payload working inside 1.5 crypto.
A 1.5 server still tunnels the older 0x80 format to older peers, so
both payload shapes have to be decoded from the same envelope.
Fall back to the TCP tunnel until a UDP packet is authenticated.
Nothing confirms the return path works until the server answers, so
audio stays tunneled until then and switches to UDP afterwards.
Tunneled packets are ignored while UDP is active so a frame is never
processed twice.
Add -tcp to force the tunnel and skip UDP entirely.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a log package with debug, info, warn, and error levels.
It wraps a single process-wide logger that the gumble and audio
packages can call into without importing a logging library.
Logging is off unless -logfile names a file.
Anything written to stderr while the terminal UI is running corrupts
the display, so a logger is only installed once an explicit
destination exists. -log sets the level and defaults to warn.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Changed handle_error to accept *Barnard pointer instead of copying struct by value, eliminating race condition warnings from mutex copying.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>