Compare commits

2 Commits

Author SHA1 Message Date
Logan Cusano
aee6e40792 Revert voice activity changes 2025-07-14 22:25:36 -04:00
Logan Cusano
84cef3119f revert 2025-07-14 22:20:22 -04:00
2 changed files with 30 additions and 42 deletions

View File

@@ -3,6 +3,9 @@ import math
import pyaudio import pyaudio
import asyncio import asyncio
from internal.logger import create_logger from internal.logger import create_logger
# You need to import the base AudioSource class from your specific library.
# This is a common path, but yours might be different.
from discord import AudioSource from discord import AudioSource
LOGGER = create_logger(__name__) LOGGER = create_logger(__name__)
@@ -33,7 +36,7 @@ class NoiseGateSource(AudioSource):
# Ensure we have a full frame of data. # Ensure we have a full frame of data.
if len(pcm_data) != FRAME_SIZE: if len(pcm_data) != FRAME_SIZE:
return return SILENT_FRAME
# Calculate volume to check against the threshold. # Calculate volume to check against the threshold.
rms = audioop.rms(pcm_data, 2) rms = audioop.rms(pcm_data, 2)
@@ -42,7 +45,7 @@ class NoiseGateSource(AudioSource):
if self.ng_fadeout_count > 0: if self.ng_fadeout_count > 0:
self.ng_fadeout_count -= 1 self.ng_fadeout_count -= 1
return pcm_data # Return the (silent) data to complete the fade return pcm_data # Return the (silent) data to complete the fade
return return SILENT_FRAME
db = 20 * math.log10(rms) db = 20 * math.log10(rms)
@@ -56,8 +59,8 @@ class NoiseGateSource(AudioSource):
self.ng_fadeout_count -= 1 self.ng_fadeout_count -= 1
return pcm_data return pcm_data
# Otherwise, the gate is closed. # Otherwise, the gate is closed. Send silence.
return return SILENT_FRAME
except Exception as e: except Exception as e:
LOGGER.error(f"Error in NoiseGateSource.read: {e}", exc_info=True) LOGGER.error(f"Error in NoiseGateSource.read: {e}", exc_info=True)
@@ -65,10 +68,9 @@ class NoiseGateSource(AudioSource):
def cleanup(self) -> None: def cleanup(self) -> None:
"""Called when the player stops.""" """Called when the player stops."""
if self.audio_stream: # The AudioStreamManager now handles cleanup.
self.audio_stream.stop_stream() LOGGER.info("Audio source cleanup called.")
self.audio_stream.close() pass
LOGGER.info("Audio stream cleaned up.")
class AudioStreamManager: class AudioStreamManager:
"""Manages the PyAudio instance and input stream.""" """Manages the PyAudio instance and input stream."""

View File

@@ -4,12 +4,11 @@ import os
from discord import VoiceClient, VoiceChannel, opus, Activity, ActivityType, Intents from discord import VoiceClient, VoiceChannel, opus, Activity, ActivityType, Intents
from discord.ext import commands from discord.ext import commands
from typing import Optional, Dict from typing import Optional, Dict
from internal.NoiseGatev2 import AudioStreamManager, NoiseGateSource
from internal.logger import create_logger from internal.logger import create_logger
from internal.NoiseGatev2 import AudioStreamManager, NoiseGateSource
LOGGER = create_logger(__name__) LOGGER = create_logger(__name__)
# Configure discord intents
intents = Intents.default() intents = Intents.default()
intents.voice_states = True intents.voice_states = True
intents.guilds = True intents.guilds = True
@@ -42,18 +41,13 @@ class DiscordBotManager:
@self.bot.event @self.bot.event
async def on_voice_state_update(member, before, after): async def on_voice_state_update(member, before, after):
if member != self.bot.user: if member != self.bot.user: return
return
if before.channel is None and after.channel is not None: if before.channel is None and after.channel is not None:
LOGGER.info(f"{member.name} joined voice channel {after.channel.name}") LOGGER.info(f"{member.name} joined voice channel {after.channel.name}")
self._voice_ready_event.set() self._voice_ready_event.set()
elif before.channel is not None and after.channel is not None and before.channel != after.channel: elif before.channel is not None and after.channel is not None and before.channel != after.channel:
LOGGER.info(f"{member.name} was moved to voice channel {after.channel.name}") LOGGER.info(f"{member.name} was moved to voice channel {after.channel.name}")
if not self._voice_ready_event.is_set(): if not self._voice_ready_event.is_set(): self._voice_ready_event.set()
self._voice_ready_event.set()
elif before.channel is not None and after.channel is None: elif before.channel is not None and after.channel is None:
LOGGER.warning(f"{member.name} left voice channel {before.channel.name}") LOGGER.warning(f"{member.name} left voice channel {before.channel.name}")
guild_id = before.channel.guild.id guild_id = before.channel.guild.id
@@ -75,8 +69,7 @@ class DiscordBotManager:
LOGGER.info("Bot is ready.") LOGGER.info("Bot is ready.")
except asyncio.TimeoutError: except asyncio.TimeoutError:
LOGGER.error("Timeout waiting for bot to become ready.") LOGGER.error("Timeout waiting for bot to become ready.")
if self.bot_task and not self.bot_task.done(): if self.bot_task and not self.bot_task.done(): self.bot_task.cancel()
self.bot_task.cancel()
raise RuntimeError("Bot failed to become ready within timeout.") raise RuntimeError("Bot failed to become ready within timeout.")
async def stop_bot(self): async def stop_bot(self):
@@ -107,14 +100,10 @@ class DiscordBotManager:
voice_client = await channel.connect(timeout=60.0, reconnect=True) voice_client = await channel.connect(timeout=60.0, reconnect=True)
await asyncio.wait_for(self._voice_ready_event.wait(), timeout=15.0) await asyncio.wait_for(self._voice_ready_event.wait(), timeout=15.0)
# Create a single audio manager for this connection
audio_manager = AudioStreamManager(input_device_index=device_id) audio_manager = AudioStreamManager(input_device_index=device_id)
# Create the noise-gated audio source
audio_source = NoiseGateSource(audio_manager.get_stream(), threshold=ng_threshold) audio_source = NoiseGateSource(audio_manager.get_stream(), threshold=ng_threshold)
# Play the source voice_client.play(audio_source, after=lambda e: LOGGER.error(f'Player error: {e}') if e else None)
voice_client.play(audio_source, after=lambda e: print(f'Player error: {e}') if e else None)
self.voice_connections[guild_id] = { self.voice_connections[guild_id] = {
"client": voice_client, "client": voice_client,
@@ -124,6 +113,8 @@ class DiscordBotManager:
except Exception as e: except Exception as e:
LOGGER.error(f"Failed to connect to voice channel: {e}", exc_info=True) LOGGER.error(f"Failed to connect to voice channel: {e}", exc_info=True)
if guild_id in self.voice_connections: # Cleanup if join fails midway
await self.leave_voice_channel(guild_id)
raise raise
async def leave_voice_channel(self, guild_id: int): async def leave_voice_channel(self, guild_id: int):
@@ -137,19 +128,23 @@ class DiscordBotManager:
voice_client.stop() voice_client.stop()
await voice_client.disconnect() await voice_client.disconnect()
# Terminate the audio manager to release PyAudio resources
audio_manager = connection_info.get("audio_manager") audio_manager = connection_info.get("audio_manager")
if audio_manager: if audio_manager:
audio_manager.terminate() audio_manager.terminate()
del self.voice_connections[guild_id] # Use pop to safely remove the key
self.voice_connections.pop(guild_id, None)
LOGGER.info(f"Left guild {guild_id} voice channel.") LOGGER.info(f"Left guild {guild_id} voice channel.")
async def load_opus(self): async def load_opus(self):
# ... this method is unchanged ... if opus.is_loaded():
LOGGER.info("Opus library is already loaded.")
return
processor = platform.machine() processor = platform.machine()
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
LOGGER.debug(f"Processor: {processor}, OS: {os.name}")
LOGGER.debug(f"Attempting to load Opus. Processor: {processor}, OS: {os.name}")
try: try:
if os.name == 'nt': if os.name == 'nt':
if processor == "AMD64": if processor == "AMD64":
@@ -164,20 +159,11 @@ class DiscordBotManager:
LOGGER.info("Loaded OPUS library for armv7l") LOGGER.info("Loaded OPUS library for armv7l")
else: else:
opus.load_opus('libopus.so.0') opus.load_opus('libopus.so.0')
LOGGER.info(f"Loaded system OPUS library for {processor}") LOGGER.info(f"Attempted to load system OPUS library for {processor}")
except Exception as e: except Exception as e:
LOGGER.error(f"Failed to load OPUS library: {e}") LOGGER.error(f"Failed to load OPUS library: {e}")
raise RuntimeError("Could not load a valid Opus library. Voice functionality will fail.") raise RuntimeError("Could not load a valid Opus library. Voice functionality will fail.")
async def set_presence(self, system_name: str): if not opus.is_loaded():
# ... this method is unchanged ... raise RuntimeError("Opus library could not be loaded. Please ensure it is installed correctly.")
if not self.bot or not self.bot.is_ready():
LOGGER.warning("Bot is not ready, cannot set presence.")
return
try:
activity = Activity(type=ActivityType.listening, name=system_name)
await self.bot.change_presence(activity=activity)
LOGGER.info(f"Bot presence set to 'Listening to {system_name}'")
except Exception as pe:
LOGGER.error(f"Unable to set presence: '{pe}'")