From 67b2dc827d1116655768cc050602dd4cd7683d94 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 21 Feb 2026 23:20:17 +0100 Subject: [PATCH] fix: make !volume apply immediately during playback stream_audio now accepts a callable for volume, re-read on each PCM frame instead of capturing a static float at track start. Co-Authored-By: Claude Opus 4.6 --- docs/CHEATSHEET.md | 4 ++-- docs/USAGE.md | 2 +- plugins/music.py | 7 ++++--- src/derp/mumble.py | 13 +++++++++---- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index fc7345b..66cc4a9 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -556,8 +556,8 @@ HTML stripped on receive, escaped on send. IRC-only commands are no-ops. !volume 75 # Set volume (0-100, default 50) ``` -Requires: `yt-dlp`, `ffmpeg`, `libopus.so.0` on the host. -Max 50 tracks in queue. Volume applies on next track. +Requires: `yt-dlp`, `ffmpeg`, `libopus` on the host. +Max 50 tracks in queue. Volume changes take effect immediately. Mumble-only: `!play` replies with error on other adapters, others silently no-op. ## Plugin Template diff --git a/docs/USAGE.md b/docs/USAGE.md index 30f7bf3..ebbac03 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1576,7 +1576,7 @@ and voice transmission. ``` - Queue holds up to 50 tracks -- Volume applies from the next track (default: 50%) +- Volume takes effect immediately during playback (default: 50%) - Title resolved via `yt-dlp --get-title` before playback - Audio pipeline: `yt-dlp | ffmpeg` subprocess, PCM fed to pymumble - Commands are Mumble-only; `!play` on other adapters replies with an error, diff --git a/plugins/music.py b/plugins/music.py index 2a29c00..137cdcf 100644 --- a/plugins/music.py +++ b/plugins/music.py @@ -78,10 +78,11 @@ async def _play_loop(bot) -> None: done = asyncio.Event() ps["done_event"] = done - volume = ps["volume"] / 100.0 try: await bot.stream_audio( - track.url, volume=volume, on_done=done, + track.url, + volume=lambda: ps["volume"] / 100.0, + on_done=done, ) except asyncio.CancelledError: raise @@ -269,7 +270,7 @@ async def cmd_volume(bot, message): Usage: !volume Show current volume - !volume <0-100> Set volume (applies on next track) + !volume <0-100> Set volume (takes effect immediately) """ if not _is_mumble(bot): return diff --git a/src/derp/mumble.py b/src/derp/mumble.py index 703b397..fefdfc8 100644 --- a/src/derp/mumble.py +++ b/src/derp/mumble.py @@ -421,7 +421,7 @@ class MumbleBot: self, url: str, *, - volume: float = 0.5, + volume=0.5, on_done=None, ) -> None: """Stream audio from URL through yt-dlp|ffmpeg to voice channel. @@ -432,12 +432,16 @@ class MumbleBot: Feeds raw PCM to pymumble's sound_output which handles Opus encoding, packetization, and timing. + + ``volume`` may be a float (static) or a callable returning float + (dynamic, re-read each frame). """ if self._mumble is None: return + _get_vol = volume if callable(volume) else lambda: volume log.info("stream_audio: starting pipeline for %s (vol=%.0f%%)", - url, volume * 100) + url, _get_vol() * 100) proc = await asyncio.create_subprocess_exec( "sh", "-c", @@ -457,8 +461,9 @@ class MumbleBot: if len(pcm) < _FRAME_BYTES: pcm += b"\x00" * (_FRAME_BYTES - len(pcm)) - if volume != 1.0: - pcm = _scale_pcm(pcm, volume) + vol = _get_vol() + if vol != 1.0: + pcm = _scale_pcm(pcm, vol) self._mumble.sound_output.add_sound(pcm) frames += 1