diff --git a/sound.py b/sound.py index f6cc95a..1548eff 100644 --- a/sound.py +++ b/sound.py @@ -19,6 +19,72 @@ from .services import VolumeService # Global instance for backward compatibility volumeService = VolumeService.get_instance() +def find_silent_channels(): + """Find channels that are playing but with zero volume (effectively silent).""" + silent_channels = [] + for n in range(pygame.mixer.get_num_channels()): + channel = pygame.mixer.Channel(n) + if channel.get_busy(): + # Get current volume setting (single value for both channels) + volume = channel.get_volume() + # Consider silent if volume is effectively zero + if volume <= 0.001: + silent_channels.append(channel) + return silent_channels + +def find_available_channel(): + """Find an available channel, prioritizing silent channels over stopping active ones.""" + # Try to find an idle channel first + for n in range(pygame.mixer.get_num_channels()): + channel = pygame.mixer.Channel(n) + if not channel.get_busy(): + return channel + + # If no idle channels, look for silent ones to reclaim + silent_channels = find_silent_channels() + if silent_channels: + # Stop the first silent channel and return it + channel = silent_channels[0] + channel.stop() + return channel + + # If no silent channels, stop the first busy channel as last resort + # This ensures sounds always play rather than being silently dropped + channel = pygame.mixer.Channel(0) + channel.stop() + return channel + +def get_channel_usage(): + """Return channel usage info for debugging.""" + total = pygame.mixer.get_num_channels() + busy = sum(1 for n in range(total) if pygame.mixer.Channel(n).get_busy()) + silent = len(find_silent_channels()) + return f"Channels: {busy}/{total} busy ({silent} silent)" + +def print_channel_debug(): + """Print detailed channel usage for debugging.""" + total = pygame.mixer.get_num_channels() + print(f"Channel usage: {get_channel_usage()}") + + busy_channels = [] + silent_channels = [] + + for n in range(total): + channel = pygame.mixer.Channel(n) + if channel.get_busy(): + volume = channel.get_volume() + if volume <= 0.001: + silent_channels.append(n) + else: + busy_channels.append(n) + + if busy_channels: + print(f"Active channels: {busy_channels}") + if silent_channels: + print(f"Silent channels: {silent_channels}") + if not busy_channels and not silent_channels: + print("No busy channels") + class Sound: """Handles sound loading and playback.""" @@ -120,10 +186,12 @@ class Sound: pygame.event.clear() pygame.mixer.stop() - # Play the sound + # Play the sound with guaranteed channel allocation channel = self.sounds[soundName].play(-1 if loop else 0) if not channel: - return None + # If no channel available, force allocation + channel = find_available_channel() + channel.play(self.sounds[soundName], -1 if loop else 0) # Apply appropriate volume settings sfx_volume = self.volumeService.get_sfx_volume() @@ -240,8 +308,13 @@ class Sound: if not soundName: return None - # Play the sound + # Play the sound with guaranteed channel allocation channel = self.sounds[soundName].play() + if not channel: + # If no channel available, force allocation + channel = find_available_channel() + channel.play(self.sounds[soundName]) + if channel: channel.set_volume( finalLeft * self.volumeService.sfxVolume, @@ -425,10 +498,12 @@ def play_sound(sound_or_name, volume=1.0, loop=False, playerPos=None, objPos=Non # Case 4: Sound name with dictionary elif isinstance(sounds, dict) and isinstance(sound_or_name, str) and sound_or_name in sounds: - # Play the sound + # Play the sound with guaranteed channel allocation channel = sounds[sound_or_name].play(-1 if loop else 0) if not channel: - return None + # If no channel available, force allocation + channel = find_available_channel() + channel.play(sounds[sound_or_name], -1 if loop else 0) # Apply volume settings sfx_vol = volumeService.get_sfx_volume() @@ -522,8 +597,13 @@ def play_random_falling(sounds, soundName, playerX, objectX, startY, currentY=0, if not matched_sound: return None - # Play the sound + # Play the sound with guaranteed channel allocation channel = sounds[matched_sound].play() + if not channel: + # If no channel available, force allocation + channel = find_available_channel() + channel.play(sounds[matched_sound]) + if channel: channel.set_volume( finalLeft * volumeService.sfxVolume,