feat: implement volume boost functionality with signal-based multiplier and audio processing override

This commit is contained in:
mmacierzanka committed 2026-09-11 01:46:16 +02:00
1 parent 9143c6029a
commit 3dd30804dc
13 files changed
+294 -8

No files matched your search

+25 -3
View File
@@ -8,6 +8,15 @@ import discord
FRAME_LENGTH_SECONDS = 0.02
try:
import audioop
except ImportError: # pragma: no cover
try:
import audioop_lts as audioop
except ImportError:
audioop = None
class YTDLSource(discord.PCMVolumeTransformer):
"""Źródło audio (strumień YouTube lub plik lokalny) z metadanymi utworu."""
@@ -40,7 +49,20 @@ class YTDLSource(discord.PCMVolumeTransformer):
return bool(url and str(url).startswith("http"))
def read(self) -> bytes:
data = super().read()
if data:
self.elapsed += FRAME_LENGTH_SECONDS
# discord.PCMVolumeTransformer clamps volume to min(self._volume, 2.0).
# We read self.original directly and use audioop.mul without the 2.0 cap
# so that boosted volume overdrive produces authentic PCM clipping / distortion (przester).
if audioop is None:
data = super().read()
if data:
self.elapsed += FRAME_LENGTH_SECONDS
return data
data = self.original.read()
if not data:
return b""
self.elapsed += FRAME_LENGTH_SECONDS
if self._volume != 1.0:
return audioop.mul(data, 2, self._volume)
return data
+8
View File
@@ -7,6 +7,8 @@ import discord
from zyrarbot.audio.ytdl_source import YTDLSource
from zyrarbot.music.loop_mode import LoopMode
VOLUME_BOOST_MULTIPLIER: float = 10.0
@dataclass
class GuildMusicState:
@@ -21,10 +23,16 @@ class GuildMusicState:
current: YTDLSource | None = None
index: int = -1
volume: float = 1.0
volume_boost: bool = False
loop_mode: LoopMode = LoopMode.OFF
jump_pending: bool = False
notification_channel: discord.abc.Messageable | None = None
@property
def effective_volume(self) -> float:
"""Zwraca głośność z uwzględnieniem trybu podbicia (mnożnik VOLUME_BOOST_MULTIPLIER)."""
return self.volume * VOLUME_BOOST_MULTIPLIER if self.volume_boost else self.volume
def reset(self) -> None:
self.queue.clear()
self.current = None
+18 -5
View File
@@ -207,11 +207,24 @@ class MusicPlayer:
"filepath": track.filepath,
}
def set_volume(self, guild_id: int, volume: float) -> None:
self.state(guild_id).volume = volume
def _apply_volume(self, guild_id: int) -> None:
state = self.state(guild_id)
voice_client = self.voice_client(guild_id)
if voice_client and voice_client.source:
voice_client.source.volume = volume
voice_client.source.volume = state.effective_volume
def set_volume(self, guild_id: int, volume: float) -> None:
self.state(guild_id).volume = volume
self._apply_volume(guild_id)
def set_volume_boost(self, guild_id: int, boost: bool) -> bool:
self.state(guild_id).volume_boost = boost
self._apply_volume(guild_id)
return boost
def toggle_volume_boost(self, guild_id: int) -> bool:
state = self.state(guild_id)
return self.set_volume_boost(guild_id, not state.volume_boost)
def set_loop_mode(self, guild_id: int, mode: LoopMode) -> None:
self.state(guild_id).loop_mode = mode
@@ -320,7 +333,7 @@ class MusicPlayer:
track = await self._tracks.recreate(current, loop=self._bot.loop, start_at=position)
track.played = True
track.volume = state.volume
track.volume = state.effective_volume
track.plays_left = self.plays_left(current, guild_id)
track.elapsed = position
state.queue[state.index] = track
@@ -426,7 +439,7 @@ class MusicPlayer:
track.played = True
track.plays_left = self.plays_left(track, guild_id)
track.volume = state.volume
track.volume = state.effective_volume
state.queue[next_index] = track
if track.plays_left <= 0 and state.loop_mode.loops_track:
@@ -0,0 +1,7 @@
from pydantic import BaseModel
class VolumeBoostRequest(BaseModel):
"""Włączenie, wyłączenie lub przełączenie trybu 500% głośności."""
enabled: bool | None = None
@@ -21,6 +21,7 @@ from zyrarbot.ui.api.requests.loop_request import LoopRequest
from zyrarbot.ui.api.requests.play_file_request import PlayFileRequest
from zyrarbot.ui.api.requests.play_request import PlayRequest
from zyrarbot.ui.api.requests.seek_request import SeekRequest
from zyrarbot.ui.api.requests.volume_boost_request import VolumeBoostRequest
from zyrarbot.ui.api.requests.volume_request import VolumeRequest
from zyrarbot.ui.api.status_broadcaster import StatusBroadcaster
@@ -173,6 +174,21 @@ async def set_volume(
return {"status": "success", "volume": request.volume}
@router.post("/volume_boost")
async def set_volume_boost(
request: VolumeBoostRequest | None = None,
guild: discord.Guild = Depends(get_active_guild),
player: MusicPlayer = Depends(get_player),
broadcaster: StatusBroadcaster = Depends(get_broadcaster),
) -> dict[str, Any]:
if request is not None and request.enabled is not None:
boost = player.set_volume_boost(guild.id, request.enabled)
else:
boost = player.toggle_volume_boost(guild.id)
await broadcaster.broadcast()
return {"status": "success", "volume_boost": boost}
@router.post("/loop")
async def set_loop(
request: LoopRequest,
+5
View File
@@ -5,6 +5,7 @@ from typing import Any
import discord
from zyrarbot.audio.ytdl_source import YTDLSource
from zyrarbot.music.guild_music_state import VOLUME_BOOST_MULTIPLIER
from zyrarbot.music.music_player import MusicPlayer
from zyrarbot.music.notification_channel_resolver import NotificationChannelResolver
from zyrarbot.ui.bot.guild_resolver import GuildResolver
@@ -49,6 +50,8 @@ class StatusService:
"loop_single": False,
"loop_playlist": False,
"volume": 100,
"volume_boost": False,
"volume_boost_multiplier": VOLUME_BOOST_MULTIPLIER,
"current": None,
"queue": [],
"queue_index": -1,
@@ -70,6 +73,8 @@ class StatusService:
"loop_single": state.loop_mode.loops_track,
"loop_playlist": state.loop_mode.loops_queue,
"volume": int(state.volume * 100),
"volume_boost": state.volume_boost,
"volume_boost_multiplier": VOLUME_BOOST_MULTIPLIER,
"current": self._current_payload(state.current, guild.id),
"queue": [self._track_payload(track, guild.id) for track in state.queue],
"queue_index": state.index,
+14
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import discord
from discord import app_commands
from zyrarbot.music.guild_music_state import VOLUME_BOOST_MULTIPLIER
from zyrarbot.music.loop_mode import LoopMode
from zyrarbot.ui.bot.cogs.common import MusicCogBase
from zyrarbot.ui.bot.discord_theme import message_embed
@@ -111,6 +112,19 @@ class ControlCog(MusicCogBase, name="Control"):
embed=message_embed("Głośność", f"Ustawiono głośność na {volume}%.")
)
@app_commands.command(name="volumeboost", description="Przełącz tryb podbicia głośności")
async def volume_boost(self, interaction: discord.Interaction) -> None:
boost = self.player.toggle_volume_boost(interaction.guild.id)
await self.player.trigger_state_change()
boost_percent = int(VOLUME_BOOST_MULTIPLIER * 100)
status_text = f"włączony ({boost_percent}%)" if boost else "wyłączony (100%)"
await interaction.response.send_message(
embed=message_embed(
"Podbicie głośności",
f"⚡ Tryb podbicia głośności ({boost_percent}%) jest teraz **{status_text}**.",
)
)
@app_commands.command(name="nowplaying", description="Pokaż aktualnie odtwarzany utwór")
async def now_playing(self, interaction: discord.Interaction) -> None:
current = self.player.state(interaction.guild.id).current