diff --git a/__init__.py b/__init__.py
index 1fea9ec..6b0afdb 100755
--- a/__init__.py
+++ b/__init__.py
@@ -79,7 +79,7 @@ __version__ = '2.0.0'
 __all__ = [
     # Services
     'ConfigService', 'VolumeService', 'PathService',
-    
+
     # Sound
     'Sound', 
     'play_bgm', 
@@ -97,32 +97,32 @@ __all__ = [
     'cut_scene',
     'play_random_falling',
     'calculate_volume_and_pan',
-    
+
     # Speech
     'messagebox',
     'speak',
     'Speech',
-    
+
     # Scoreboard
     'Scoreboard',
-    
+
     # Input
     'get_input', 'check_for_exit', 'pause_game',
-    
+
     # Display
     'display_text', 'initialize_gui',
-    
+
     # Menu
     'game_menu', 'learn_sounds', 'instructions', 'credits', 'donate', 'exit_game', 'high_scores', 'has_high_scores',
-    
+
     # Game class
     'Game',
-    
+
     # Utils
     'check_for_updates', 'get_version_tuple', 'check_compatibility',
     'sanitize_filename', 'lerp', 'smooth_step', 'distance_2d',
     'x_powerbar', 'y_powerbar', 'generate_tone',
-    
+
     # Re-exported functions from pygame, math, random
     'get_ticks', 'delay', 'wait',
     'sin', 'cos', 'sqrt', 'floor', 'ceil',
@@ -141,10 +141,10 @@ def initialize_gui_with_services(gameTitle):
     """Wrapper around initialize_gui that initializes services."""
     # Initialize path service
     pathService.initialize(gameTitle)
-    
+
     # Connect config service to path service
     configService.set_game_info(gameTitle, pathService)
-    
+
     # Call original initialize_gui
     return _originalInitializeGui(gameTitle)
 
@@ -159,11 +159,11 @@ def scoreboard_init_with_services(self, score=0, configService=None, speech=None
     # Use global services if not specified
     if configService is None:
         configService = ConfigService.get_instance()
-        
+
         # Ensure pathService is connected if using defaults
         if not hasattr(configService, 'pathService') and pathService.game_path is not None:
             configService.pathService = pathService
-    
+
     # Call original init with services
     _originalScoreboardInit(self, score, configService, speech)
 
diff --git a/config.py b/config.py
index a5715bc..6eaf55a 100644
--- a/config.py
+++ b/config.py
@@ -13,10 +13,10 @@ from xdg import BaseDirectory
 
 class Config:
     """Configuration management class for Storm Games."""
-    
+
     def __init__(self, gameTitle):
         """Initialize configuration system for a game.
-        
+
         Args:
             gameTitle (str): Title of the game
         """
@@ -24,19 +24,19 @@ class Config:
         self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
         self.gamePath = os.path.join(self.globalPath, 
                                      str.lower(str.replace(gameTitle, " ", "-")))
-        
+
         # Create game directory if it doesn't exist
         if not os.path.exists(self.gamePath):
             os.makedirs(self.gamePath)
-            
+
         # Initialize config parsers
         self.localConfig = configparser.ConfigParser()
         self.globalConfig = configparser.ConfigParser()
-        
+
         # Load existing configurations
         self.read_local_config()
         self.read_global_config()
-    
+
     def read_local_config(self):
         """Read local configuration from file."""
         try:
@@ -44,7 +44,7 @@ class Config:
                 self.localConfig.read_file(configFile)
         except:
             pass
-    
+
     def read_global_config(self):
         """Read global configuration from file."""
         try:
@@ -52,12 +52,12 @@ class Config:
                 self.globalConfig.read_file(configFile)
         except:
             pass
-    
+
     def write_local_config(self):
         """Write local configuration to file."""
         with open(os.path.join(self.gamePath, "config.ini"), 'w') as configFile:
             self.localConfig.write(configFile)
-    
+
     def write_global_config(self):
         """Write global configuration to file."""
         with open(os.path.join(self.globalPath, "config.ini"), 'w') as configFile:
@@ -71,7 +71,7 @@ globalPath = ""
 
 def write_config(writeGlobal=False):
     """Write configuration to file.
-    
+
     Args:
         writeGlobal (bool): If True, write to global config, otherwise local (default: False)
     """
@@ -84,7 +84,7 @@ def write_config(writeGlobal=False):
 
 def read_config(readGlobal=False):
     """Read configuration from file.
-    
+
     Args:
         readGlobal (bool): If True, read global config, otherwise local (default: False)
     """
diff --git a/display.py b/display.py
index 23c2749..c9753be 100644
--- a/display.py
+++ b/display.py
@@ -23,81 +23,81 @@ displayTextUsageInstructions = False
 
 def initialize_gui(gameTitle):
     """Initialize the game GUI and sound system.
-    
+
     Args:
         gameTitle (str): Title of the game
-        
+
     Returns:
         dict: Dictionary of loaded sound objects
     """
     # Initialize path service with game title
     pathService = PathService.get_instance().initialize(gameTitle)
-    
+
     # Seed the random generator to the clock
     random.seed()
-    
+
     # Set game's name
     setproctitle(str.lower(str.replace(gameTitle, " ", "-")))
-    
+
     # Initialize pygame
     pygame.init()
     pygame.display.set_mode((800, 600))
     pygame.display.set_caption(gameTitle)
-    
+
     # Set up audio system
     pygame.mixer.pre_init(44100, -16, 2, 1024)
     pygame.mixer.init()
     pygame.mixer.set_num_channels(32)
     pygame.mixer.set_reserved(0)  # Reserve channel for cut scenes
-    
+
     # Enable key repeat for volume controls
     pygame.key.set_repeat(500, 100)
-    
+
     # Load sound files recursively including subdirectories
     soundData = {}
     try:
         import os
-        
+
         soundDir = "sounds/"
         # Walk through directory tree
         for dirPath, dirNames, fileNames in os.walk(soundDir):
             # Get relative path from soundDir
             relPath = os.path.relpath(dirPath, soundDir)
-            
+
             # Process each file
             for fileName in fileNames:
                 # Check if file is a valid sound file
                 if fileName.lower().endswith(('.ogg', '.wav')):
                     # Full path to the sound file
                     fullPath = os.path.join(dirPath, fileName)
-                    
+
                     # Create sound key (remove extension)
                     baseName = os.path.splitext(fileName)[0]
-                    
+
                     # If in root sounds dir, just use basename
                     if relPath == '.':
                         soundKey = baseName
                     else:
                         # Otherwise use relative path + basename, normalized with forward slashes
                         soundKey = os.path.join(relPath, baseName).replace('\\', '/')
-                    
+
                     # Load the sound
                     soundData[soundKey] = pygame.mixer.Sound(fullPath)
     except Exception as e:
         print("Error loading sounds:", e)
         Speech.get_instance().speak("Error loading sounds.", False)
         soundData = {}
-    
+
     # Play intro sound if available
     from .sound import cut_scene
     if 'game-intro' in soundData:
         cut_scene(soundData, 'game-intro')
-    
+
     return soundData
 
 def display_text(text):
     """Display and speak text with navigation controls.
-    
+
     Allows users to:
     - Navigate text line by line with arrow keys (skipping blank lines)
     - Listen to full text with space
@@ -107,20 +107,20 @@ def display_text(text):
         - Alt+PageUp/PageDown: Master volume up/down
         - Alt+Home/End: Background music volume up/down
         - Alt+Insert/Delete: Sound effects volume up/down
-    
+
     Args:
         text (list): List of text lines to display
     """
     # Get service instances
     speech = Speech.get_instance()
     volumeService = VolumeService.get_instance()
-    
+
     # Store original text with blank lines for copying
     originalText = text.copy()
-    
+
     # Create navigation text by filtering out blank lines
     navText = [line for line in text if line.strip()]
-    
+
     # Add instructions at the start on the first display
     global displayTextUsageInstructions
     if not displayTextUsageInstructions:
@@ -129,20 +129,20 @@ def display_text(text):
                     "or t to copy the entire text. Press enter or escape when you are done reading.")
         navText.insert(0, instructions)
         displayTextUsageInstructions = True
-    
+
     # Add end marker
     navText.append("End of text.")
-    
+
     currentIndex = 0
     speech.speak(navText[currentIndex])
-    
+
     while True:
         event = pygame.event.wait()
         if event.type == pygame.KEYDOWN:
             # Check for Alt modifier
             mods = pygame.key.get_mods()
             altPressed = mods & pygame.KMOD_ALT
-            
+
             # Volume controls (require Alt)
             if altPressed:
                 if event.key == pygame.K_PAGEUP:
@@ -160,26 +160,26 @@ def display_text(text):
             else:
                 if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
                     return
-                    
+
                 if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
                     currentIndex += 1
                     speech.speak(navText[currentIndex])
-                    
+
                 if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
                     currentIndex -= 1
                     speech.speak(navText[currentIndex])
-                    
+
                 if event.key == pygame.K_SPACE:
                     # Join with newlines to preserve spacing in speech
                     speech.speak('\n'.join(originalText[1:-1]))
-                    
+
                 if event.key == pygame.K_c:
                     try:
                         pyperclip.copy(navText[currentIndex])
                         speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
                     except:
                         speech.speak("Failed to copy the text to the clipboard.")
-                        
+
                 if event.key == pygame.K_t:
                     try:
                         # Join with newlines to preserve blank lines in full text
@@ -187,6 +187,6 @@ def display_text(text):
                         speech.speak("Copied entire message to the clipboard.")
                     except:
                         speech.speak("Failed to copy the text to the clipboard.")
-                    
+
         event = pygame.event.clear()
         time.sleep(0.001)
diff --git a/input.py b/input.py
index f82fef5..b0960cc 100644
--- a/input.py
+++ b/input.py
@@ -15,11 +15,11 @@ from .speech import speak
 
 def get_input(prompt="Enter text:", text=""):
     """Display a dialog box for text input.
-    
+
     Args:
         prompt (str): Prompt text to display (default: "Enter text:")
         text (str): Initial text in input box (default: "")
-        
+
     Returns:
         str: User input text, or None if cancelled
     """
@@ -66,7 +66,7 @@ def pause_game():
 
 def check_for_exit():
     """Check if user has pressed escape key.
-    
+
     Returns:
         bool: True if escape was pressed, False otherwise
     """
diff --git a/menu.py b/menu.py
index 70135a0..31355db 100644
--- a/menu.py
+++ b/menu.py
@@ -27,7 +27,7 @@ from .services import PathService, ConfigService
 
 def game_menu(sounds, playCallback=None, *customOptions):
     """Display and handle the main game menu with standard and custom options.
-    
+
     Standard menu structure:
     1. Play (always first)
     2. High Scores
@@ -37,53 +37,53 @@ def game_menu(sounds, playCallback=None, *customOptions):
     6. Credits (if available)
     7. Donate
     8. Exit
-    
+
     Handles navigation with:
     - Up/Down arrows for selection
     - Home/End for first/last option
     - Enter to select
     - Escape to exit
     - Volume controls (with Alt modifier)
-        
+
     Args:
         sounds (dict): Dictionary of sound objects
         playCallback (function, optional): Callback function for the "play" option.
             If None, "play" is returned as a string like other options.
         *customOptions: Additional custom options to include after play but before standard ones
-        
+
     Returns:
         str: Selected menu option or "exit" if user pressed escape
     """
     # Get speech instance
     speech = Speech.get_instance()
-    
+
     # Start with Play option
     allOptions = ["play"]
-    
+
     # Add high scores option if scores exist
     if Scoreboard.has_high_scores():
         allOptions.append("high_scores")
-    
+
     # Add custom options (other menu items, etc.)
     allOptions.extend(customOptions)
-    
+
     # Add standard options in preferred order
     allOptions.append("learn_sounds")
-    
+
     # Check for instructions file
     if os.path.isfile('files/instructions.txt'):
         allOptions.append("instructions")
-        
+
     # Check for credits file
     if os.path.isfile('files/credits.txt'):
         allOptions.append("credits")
-        
+
     # Final options
     allOptions.extend(["donate", "exit_game"])
-    
+
     # Track if music was previously playing
     musicWasPlaying = pygame.mixer.music.get_busy()
-    
+
     # Only start menu music if no music is currently playing
     if not musicWasPlaying:
         try:
@@ -91,23 +91,23 @@ def game_menu(sounds, playCallback=None, *customOptions):
             play_bgm("sounds/music_menu.ogg")
         except:
             pass
-            
+
     loop = True
     pygame.mixer.stop()
     currentIndex = 0
     lastSpoken = -1  # Track last spoken index
-    
+
     while loop:
         if currentIndex != lastSpoken:
             speech.speak(allOptions[currentIndex])
             lastSpoken = currentIndex
-            
+
         event = pygame.event.wait()
         if event.type == pygame.KEYDOWN:
             # Check for Alt modifier
             mods = pygame.key.get_mods()
             altPressed = mods & pygame.KMOD_ALT
-            
+
             # Volume controls (require Alt)
             if altPressed:
                 if event.key == pygame.K_PAGEUP:
@@ -169,9 +169,9 @@ def game_menu(sounds, playCallback=None, *customOptions):
                             time.sleep(sounds['menu-select'].get_length())
                         except:
                             pass
-                        
+
                         selectedOption = allOptions[currentIndex]
-                        
+
                         # Special case for exit_game with fade
                         if selectedOption == "exit_game":
                             exit_game(500 if pygame.mixer.music.get_busy() else 0)
@@ -202,7 +202,7 @@ def game_menu(sounds, playCallback=None, *customOptions):
                                 pygame.mixer.music.pause()
                             except:
                                 pass
-                                
+
                             # Handle standard options
                             if selectedOption == "instructions":
                                 instructions()
@@ -214,7 +214,7 @@ def game_menu(sounds, playCallback=None, *customOptions):
                                 Scoreboard.display_high_scores()
                             elif selectedOption == "donate":
                                 donate()
-                            
+
                             # Unpause music after function returns
                             try:
                                 # Check if music is actually paused before trying to unpause
@@ -248,105 +248,105 @@ def game_menu(sounds, playCallback=None, *customOptions):
                         except:
                             pass
                         return allOptions[currentIndex]
-                    
+
         event = pygame.event.clear()
         time.sleep(0.001)
 
 def learn_sounds(sounds):
     """Interactive menu for learning game sounds.
-    
+
     Allows users to:
     - Navigate through available sounds with up/down arrows
     - Navigate between sound categories (folders) using Page Up/Page Down or Left/Right arrows
     - Play selected sounds with Enter
     - Return to menu with Escape
-    
+
     Excluded sounds:
     - Files in folders named 'ambience' (at any level)
     - Files in any directory starting with '.'
     - Files starting with 'game-intro', 'music_menu', or '_'
-    
+
     Args:
         sounds (dict): Dictionary of available sound objects
-        
+
     Returns:
         str: "menu" if user exits with escape
     """
     # Get speech instance
     speech = Speech.get_instance()
-    
+
     # Define exclusion criteria
     excludedPrefixes = ["game-intro", "music_menu", "_"]
     excludedDirs = ["ambience", "."]
-    
+
     # Organize sounds by directory
     soundsByDir = {}
-    
+
     # Process each sound key in the dictionary
     for soundKey in sounds.keys():
         # Skip if key has any excluded prefix
         if any(soundKey.lower().startswith(prefix.lower()) for prefix in excludedPrefixes):
             continue
-            
+
         # Split key into path parts
         parts = soundKey.split('/')
-        
+
         # Skip if any part of the path is an excluded directory
         if any(part.lower() == dirName.lower() or part.startswith('.') for part in parts for dirName in excludedDirs):
             continue
-        
+
         # Determine the directory
         if '/' in soundKey:
             directory = soundKey.split('/')[0]
         else:
             directory = 'root'  # Root directory sounds
-            
+
         # Add to sounds by directory
         if directory not in soundsByDir:
             soundsByDir[directory] = []
         soundsByDir[directory].append(soundKey)
-    
+
     # Sort each directory's sounds
     for directory in soundsByDir:
         soundsByDir[directory].sort()
-    
+
     # If no sounds found, inform the user and return
     if not soundsByDir:
         speech.speak("No sounds available to learn.")
         return "menu"
-    
+
     # Get list of directories in sorted order
     directories = sorted(soundsByDir.keys())
-    
+
     # Start with first directory
     currentDirIndex = 0
     currentDir = directories[currentDirIndex]
     currentSoundKeys = soundsByDir[currentDir]
     currentSoundIndex = 0
-    
+
     # Display appropriate message based on number of directories
     if len(directories) > 1:
         messagebox(f"Starting with {currentDir if currentDir != 'root' else 'root directory'} sounds. Use left and right arrows or page up and page down to navigate categories.")
-    
+
     # Track last spoken to avoid repetition
     lastSpoken = -1
     directoryChanged = True  # Flag to track if directory just changed
-    
+
     # Flag to track when to exit the loop
     returnToMenu = False
-    
+
     while not returnToMenu:
         # Announce current sound
         if currentSoundIndex != lastSpoken:
             totalSounds = len(currentSoundKeys)
             soundName = currentSoundKeys[currentSoundIndex]
-            
+
             # Remove directory prefix if present
             if '/' in soundName:
                 displayName = '/'.join(soundName.split('/')[1:])
             else:
                 displayName = soundName
-            
+
             # If directory just changed, include directory name in announcement
             if directoryChanged:
                 dirDescription = "Root directory" if currentDir == 'root' else currentDir
@@ -354,24 +354,24 @@ def learn_sounds(sounds):
                 directoryChanged = False  # Reset flag after announcement
             else:
                 announcement = f"{displayName}, {currentSoundIndex + 1} of {totalSounds}"
-                
+
             speech.speak(announcement)
             lastSpoken = currentSoundIndex
-            
+
         event = pygame.event.wait()
         if event.type == pygame.KEYDOWN:
             if event.key == pygame.K_ESCAPE:
                 returnToMenu = True
-                
+
             # Sound navigation
             elif event.key in [pygame.K_DOWN, pygame.K_s] and currentSoundIndex < len(currentSoundKeys) - 1:
                 pygame.mixer.stop()
                 currentSoundIndex += 1
-                
+
             elif event.key in [pygame.K_UP, pygame.K_w] and currentSoundIndex > 0:
                 pygame.mixer.stop()
                 currentSoundIndex -= 1
-                
+
             # Directory navigation
             elif event.key in [pygame.K_PAGEDOWN, pygame.K_RIGHT] and currentDirIndex < len(directories) - 1:
                 pygame.mixer.stop()
@@ -381,7 +381,7 @@ def learn_sounds(sounds):
                 currentSoundIndex = 0
                 directoryChanged = True  # Set flag on directory change
                 lastSpoken = -1  # Force announcement
-                
+
             elif event.key in [pygame.K_PAGEUP, pygame.K_LEFT] and currentDirIndex > 0:
                 pygame.mixer.stop()
                 currentDirIndex -= 1
@@ -390,7 +390,7 @@ def learn_sounds(sounds):
                 currentSoundIndex = 0
                 directoryChanged = True  # Set flag on directory change
                 lastSpoken = -1  # Force announcement
-                
+
             # Play sound
             elif event.key == pygame.K_RETURN:
                 try:
@@ -400,16 +400,16 @@ def learn_sounds(sounds):
                 except Exception as e:
                     print(f"Error playing sound: {e}")
                     speech.speak("Could not play sound.")
-                    
+
         event = pygame.event.clear()
         pygame.event.pump()  # Process pygame's internal events
         time.sleep(0.001)
-        
+
     return "menu"
 
 def instructions():
     """Display game instructions from file.
-    
+
     Reads and displays instructions from 'files/instructions.txt'.
     If file is missing, displays an error message.
     """
@@ -441,7 +441,7 @@ def credits():
 
 def donate():
     """Open the donation webpage.
-    
+
     Opens the Ko-fi donation page.
     """
     webbrowser.open('https://ko-fi.com/stormux')
@@ -449,20 +449,20 @@ def donate():
 
 def exit_game(fade=0):
     """Clean up and exit the game properly.
-    
+
     Args:
         fade (int): Milliseconds to fade out music before exiting.
                       0 means stop immediately (default)
     """
     # Force clear any pending events to prevent hanging
     pygame.event.clear()
-    
+
     # Stop all mixer channels first
     try:
         pygame.mixer.stop()
     except Exception as e:
         print(f"Warning: Could not stop mixer channels: {e}")
-    
+
     # Get speech instance and handle all providers
     try:
         speech = Speech.get_instance()
@@ -473,7 +473,7 @@ def exit_game(fade=0):
             print(f"Warning: Could not close speech: {e}")
     except Exception as e:
         print(f"Warning: Could not get speech instance: {e}")
-    
+
     # Handle music based on fade parameter
     try:
         if fade > 0 and pygame.mixer.music.get_busy():
@@ -484,13 +484,13 @@ def exit_game(fade=0):
             pygame.mixer.music.stop()
     except Exception as e:
         print(f"Warning: Could not handle music during exit: {e}")
-    
+
     # Clean up pygame
     try:
         pygame.quit()
     except Exception as e:
         print(f"Warning: Error during pygame.quit(): {e}")
-    
+
     # Use os._exit for immediate termination
     import os
     os._exit(0)
diff --git a/scoreboard.py b/scoreboard.py
index 5c7c131..904b00c 100644
--- a/scoreboard.py
+++ b/scoreboard.py
@@ -18,10 +18,10 @@ from .config import localConfig, write_config, read_config
 
 class Scoreboard:
     """Handles high score tracking with player names."""
-    
+
     def __init__(self, score=0, configService=None, speech=None):
         """Initialize scoreboard.
-        
+
         Args:
             score (int): Initial score (default: 0)
             configService (ConfigService): Config service (default: global instance)
@@ -29,15 +29,15 @@ class Scoreboard:
         """
         # Ensure services are properly initialized
         self._ensure_services()
-        
+
         self.configService = configService or ConfigService.get_instance()
         self.speech = speech or Speech.get_instance()
         self.currentScore = score
         self.highScores = []
-        
+
         # For backward compatibility
         read_config()
-    
+
         try:
             # Try to use configService
             self.configService.localConfig.add_section("scoreboard")
@@ -47,7 +47,7 @@ class Scoreboard:
                 localConfig.add_section("scoreboard")
             except:
                 pass
-        
+
         # Load existing high scores
         for i in range(1, 11):
             try:
@@ -72,15 +72,15 @@ class Scoreboard:
                         'name': "Player",
                         'score': 0
                     })
-    
+
         # Sort high scores by score value in descending order
         self.highScores.sort(key=lambda x: x['score'], reverse=True)
-    
+
     def _ensure_services(self):
         """Ensure PathService and ConfigService are properly initialized."""
         # Get PathService and make sure it has a game name
         pathService = PathService.get_instance()
-        
+
         # If no game name yet, try to get from pygame window title
         if not pathService.gameName:
             try:
@@ -89,55 +89,55 @@ class Scoreboard:
                     pathService.gameName = pygame.display.get_caption()[0]
             except:
                 pass
-        
+
         # Initialize path service if we have a game name but no paths set up
         if pathService.gameName and not pathService.gamePath:
             pathService.initialize(pathService.gameName)
-        
+
         # Get ConfigService and connect to PathService
         configService = ConfigService.get_instance()
         if not hasattr(configService, 'pathService') or not configService.pathService:
             if pathService.gameName:
                 configService.set_game_info(pathService.gameName, pathService)
-        
+
         # Ensure the game directory exists
         if pathService.gamePath and not os.path.exists(pathService.gamePath):
             try:
                 os.makedirs(pathService.gamePath)
             except Exception as e:
                 print(f"Error creating game directory: {e}")
-                
+
     def get_score(self):
         """Get current score."""
         return self.currentScore
-        
+
     def get_high_scores(self):
         """Get list of high scores."""
         return self.highScores
-        
+
     def decrease_score(self, points=1):
         """Decrease the current score."""
         self.currentScore -= int(points)
         return self
-        
+
     def increase_score(self, points=1):
         """Increase the current score."""
         self.currentScore += int(points)
         return self
-        
+
     def set_score(self, score):
         """Set the current score to a specific value."""
         self.currentScore = int(score)
         return self
-        
+
     def reset_score(self):
         """Reset the current score to zero."""
         self.currentScore = 0
         return self
-        
+
     def check_high_score(self):
         """Check if current score qualifies as a high score.
-        
+
         Returns:
             int: Position (1-10) if high score, None if not
         """
@@ -145,23 +145,23 @@ class Scoreboard:
             if self.currentScore > entry['score']:
                 return i + 1
         return None
-        
+
     def add_high_score(self, name=None):
         """Add current score to high scores if it qualifies.
-        
+
         Args:
             name (str): Player name (if None, will prompt user)
-        
+
         Returns:
             bool: True if score was added, False if not
         """
         # Ensure services are properly set up
         self._ensure_services()
-        
+
         position = self.check_high_score()
         if position is None:
             return False
-        
+
         # Get player name
         if name is None:
             # Import get_input here to avoid circular imports
@@ -169,23 +169,23 @@ class Scoreboard:
             name = get_input("New high score! Enter your name:", "Player")
             if name is None:  # User cancelled
                 name = "Player"
-        
+
         # Insert new score at correct position
         self.highScores.insert(position - 1, {
             'name': name,
             'score': self.currentScore
         })
-    
+
         # Keep only top 10
         self.highScores = self.highScores[:10]
-    
+
         # Save to config - try both methods for maximum compatibility
         try:
             # Try new method first
             for i, entry in enumerate(self.highScores):
                 self.configService.localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
                 self.configService.localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
-            
+
             # Try to write with configService
             try:
                 self.configService.write_local_config()
@@ -196,7 +196,7 @@ class Scoreboard:
                     localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
                     localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
                 write_config()
-                
+
         except Exception as e:
             print(f"Error writing high scores: {e}")
             # If all else fails, try direct old method
@@ -204,7 +204,7 @@ class Scoreboard:
                 localConfig.set("scoreboard", f"score_{i+1}", str(entry['score']))
                 localConfig.set("scoreboard", f"name_{i+1}", entry['name'])
             write_config()
-        
+
         # Announce success
         try:
             self.speech.messagebox(f"Congratulations {name}! You got position {position} on the scoreboard!")
@@ -212,14 +212,14 @@ class Scoreboard:
             # Fallback to global speak function
             from .speech import speak
             speak(f"Congratulations {name}! You got position {position} on the scoreboard!")
-            
+
         time.sleep(1)
         return True
 
     @staticmethod
     def has_high_scores():
         """Check if the current game has any high scores.
-        
+
         Returns:
             bool: True if at least one high score exists, False otherwise
         """
@@ -227,7 +227,7 @@ class Scoreboard:
             # Get PathService to access game name
             pathService = PathService.get_instance()
             gameName = pathService.gameName
-            
+
             # If no game name, try to get from window title
             if not gameName:
                 try:
@@ -237,31 +237,31 @@ class Scoreboard:
                         pathService.gameName = gameName
                 except:
                     pass
-            
+
             # Ensure path service is properly initialized
             if gameName and not pathService.gamePath:
                 pathService.initialize(gameName)
-            
+
             # Get the config file path
             configPath = os.path.join(pathService.gamePath, "config.ini")
-            
+
             # If config file doesn't exist, there are no scores
             if not os.path.exists(configPath):
                 return False
-                
+
             # Ensure config service is properly connected to path service
             configService = ConfigService.get_instance()
             configService.set_game_info(gameName, pathService)
-            
+
             # Create scoreboard using the properly initialized services
             board = Scoreboard(0, configService)
-            
+
             # Force a read of local config to ensure fresh data
             configService.read_local_config()
-            
+
             # Get high scores
             scores = board.get_high_scores()
-            
+
             # Check if any score is greater than zero
             return any(score['score'] > 0 for score in scores)
         except Exception as e:
@@ -271,7 +271,7 @@ class Scoreboard:
     @staticmethod
     def display_high_scores():
         """Display high scores for the current game.
-        
+
         Reads the high scores from Scoreboard class.
         Shows the game name at the top followed by the available scores.
         """
@@ -279,7 +279,7 @@ class Scoreboard:
             # Get PathService to access game name
             pathService = PathService.get_instance()
             gameName = pathService.gameName
-            
+
             # If no game name, try to get from window title
             if not gameName:
                 try:
@@ -289,30 +289,30 @@ class Scoreboard:
                         pathService.gameName = gameName
                 except:
                     pass
-            
+
             # Ensure path service is properly initialized
             if gameName and not pathService.gamePath:
                 pathService.initialize(gameName)
-            
+
             # Ensure config service is properly connected to path service
             configService = ConfigService.get_instance()
             configService.set_game_info(gameName, pathService)
-            
+
             # Create scoreboard using the properly initialized services
             board = Scoreboard(0, configService)
-            
+
             # Force a read of local config to ensure fresh data
             configService.read_local_config()
-            
+
             # Get high scores
             scores = board.get_high_scores()
-            
+
             # Filter out scores with zero points
             validScores = [score for score in scores if score['score'] > 0]
-            
+
             # Prepare the lines to display
             lines = [f"High Scores for {gameName}:"]
-            
+
             # Add scores to the display list
             if validScores:
                 for i, entry in enumerate(validScores, 1):
@@ -320,13 +320,13 @@ class Scoreboard:
                     lines.append(scoreStr)
             else:
                 lines.append("No high scores yet.")
-                
+
             # Display the high scores
             display_text(lines)
         except Exception as e:
             print(f"Error displaying high scores: {e}")
             info = ["Could not display high scores."]
             display_text(info)
-    
+
     # For backward compatibility with older code that might call displayHigh_scores
     displayHigh_scores = display_high_scores
diff --git a/services.py b/services.py
index fb16e54..dd2fd7a 100644
--- a/services.py
+++ b/services.py
@@ -17,37 +17,37 @@ from .config import gamePath, globalPath, write_config, read_config
 
 class ConfigService:
     """Configuration management service."""
-    
+
     _instance = None
-    
+
     @classmethod
     def get_instance(cls):
         """Get or create the singleton instance."""
         if cls._instance is None:
             cls._instance = ConfigService()
         return cls._instance
-    
+
     def __init__(self):
         """Initialize configuration parsers."""
         self.localConfig = configparser.ConfigParser()
         self.globalConfig = configparser.ConfigParser()
         self.gameTitle = None
         self.pathService = None
-        
+
     def set_game_info(self, gameTitle, pathService):
         """Set game information and initialize configs.
-        
+
         Args:
             gameTitle (str): Title of the game
             pathService (PathService): Path service instance
         """
         self.gameTitle = gameTitle
         self.pathService = pathService
-        
+
         # Load existing configurations
         self.read_local_config()
         self.read_global_config()
-    
+
     def read_local_config(self):
         """Read local configuration from file."""
         try:
@@ -66,7 +66,7 @@ class ConfigService:
                 self.localConfig.read_dict(globals().get('localConfig', {}))
         except:
             pass
-    
+
     def read_global_config(self):
         """Read global configuration from file."""
         try:
@@ -85,7 +85,7 @@ class ConfigService:
                 self.globalConfig.read_dict(globals().get('globalConfig', {}))
         except:
             pass
-    
+
     def write_local_config(self):
         """Write local configuration to file."""
         try:
@@ -104,7 +104,7 @@ class ConfigService:
                 write_config(False)
         except Exception as e:
             print(f"Warning: Failed to write local config: {e}")
-    
+
     def write_global_config(self):
         """Write global configuration to file."""
         try:
@@ -127,37 +127,37 @@ class ConfigService:
 
 class VolumeService:
     """Volume management service."""
-    
+
     _instance = None
-    
+
     @classmethod
     def get_instance(cls):
         """Get or create the singleton instance."""
         if cls._instance is None:
             cls._instance = VolumeService()
         return cls._instance
-    
+
     def __init__(self):
         """Initialize volume settings."""
         self.bgmVolume = 0.75     # Default background music volume
         self.sfxVolume = 1.0      # Default sound effects volume
         self.masterVolume = 1.0   # Default master volume
-    
+
     def adjust_master_volume(self, change, pygameMixer=None):
         """Adjust the master volume for all sounds.
-        
+
         Args:
             change (float): Amount to change volume by (positive or negative)
             pygameMixer: Optional pygame.mixer module for real-time updates
         """
         self.masterVolume = max(0.0, min(1.0, self.masterVolume + change))
-        
+
         # Update real-time audio if pygame mixer is provided
         if pygameMixer:
             # Update music volume
             if pygameMixer.music.get_busy():
                 pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
-            
+
             # Update all sound channels
             for i in range(pygameMixer.get_num_channels()):
                 channel = pygameMixer.Channel(i)
@@ -170,29 +170,29 @@ class VolumeService:
                         # Stereo audio
                         left, right = currentVolume
                         channel.set_volume(left * self.masterVolume, right * self.masterVolume)
-    
+
     def adjust_bgm_volume(self, change, pygameMixer=None):
         """Adjust only the background music volume.
-        
+
         Args:
             change (float): Amount to change volume by (positive or negative)
             pygameMixer: Optional pygame.mixer module for real-time updates
         """
         self.bgmVolume = max(0.0, min(1.0, self.bgmVolume + change))
-        
+
         # Update real-time audio if pygame mixer is provided
         if pygameMixer and pygameMixer.music.get_busy():
             pygameMixer.music.set_volume(self.bgmVolume * self.masterVolume)
-    
+
     def adjust_sfx_volume(self, change, pygameMixer=None):
         """Adjust volume for sound effects only.
-        
+
         Args:
             change (float): Amount to change volume by (positive or negative)
             pygameMixer: Optional pygame.mixer module for real-time updates
         """
         self.sfxVolume = max(0.0, min(1.0, self.sfxVolume + change))
-        
+
         # Update real-time audio if pygame mixer is provided
         if pygameMixer:
             # Update all sound channels except reserved ones
@@ -208,18 +208,18 @@ class VolumeService:
                         left, right = currentVolume
                         channel.set_volume(left * self.sfxVolume * self.masterVolume, 
                                           right * self.sfxVolume * self.masterVolume)
-    
+
     def get_bgm_volume(self):
         """Get the current BGM volume with master adjustment.
-        
+
         Returns:
             float: Current adjusted BGM volume
         """
         return self.bgmVolume * self.masterVolume
-    
+
     def get_sfx_volume(self):
         """Get the current SFX volume with master adjustment.
-        
+
         Returns:
             float: Current adjusted SFX volume
         """
@@ -228,32 +228,32 @@ class VolumeService:
 
 class PathService:
     """Path management service."""
-    
+
     _instance = None
-    
+
     @classmethod
     def get_instance(cls):
         """Get or create the singleton instance."""
         if cls._instance is None:
             cls._instance = PathService()
         return cls._instance
-    
+
     def __init__(self):
         """Initialize path variables."""
         self.globalPath = None
         self.gamePath = None
         self.gameName = None
-        
+
         # Try to initialize from global variables for backward compatibility
         global gamePath, globalPath
         if gamePath:
             self.gamePath = gamePath
         if globalPath:
             self.globalPath = globalPath
-    
+
     def initialize(self, gameTitle):
         """Initialize paths for a game.
-        
+
         Args:
             gameTitle (str): Title of the game
         """
@@ -261,14 +261,14 @@ class PathService:
         self.globalPath = os.path.join(BaseDirectory.xdg_config_home, "storm-games")
         self.gamePath = os.path.join(self.globalPath, 
                                       str.lower(str.replace(gameTitle, " ", "-")))
-        
+
         # Create game directory if it doesn't exist
         if not os.path.exists(self.gamePath):
             os.makedirs(self.gamePath)
-            
+
         # Update global variables for backward compatibility
         global gamePath, globalPath
         gamePath = self.gamePath
         globalPath = self.globalPath
-        
+
         return self
diff --git a/sound.py b/sound.py
index bf01427..de38a17 100644
--- a/sound.py
+++ b/sound.py
@@ -4,7 +4,7 @@
 
 Provides functionality for:
 - Playing background music and sound effects
-- Positional audio
+- 2D positional audio (x,y)
 - Volume controls
 """
 
@@ -13,8 +13,7 @@ import pygame
 import random
 import re
 import time
-from os import listdir
-from os.path import isfile, join
+import math
 from .services import VolumeService
 
 # Global instance for backward compatibility
@@ -22,380 +21,207 @@ volumeService = VolumeService.get_instance()
 
 class Sound:
     """Handles sound loading and playback."""
-    
+
     def __init__(self, soundDir="sounds/", volumeService=None):
-        """Initialize sound system.
-        
-        Args:
-            soundDir (str): Directory containing sound files (default: "sounds/")
-            volumeService (VolumeService): Volume service (default: global instance)
-        """
+        """Initialize sound system."""
         self.soundDir = soundDir
         self.sounds = {}
         self.volumeService = volumeService or VolumeService.get_instance()
-        
-        # Initialize pygame mixer if not already done
+
         if not pygame.mixer.get_init():
             pygame.mixer.pre_init(44100, -16, 2, 1024)
             pygame.mixer.init()
             pygame.mixer.set_num_channels(32)
-            pygame.mixer.set_reserved(0)  # Reserve channel for cut scenes
-        
-        # Load sounds
+            pygame.mixer.set_reserved(0)
+
         self.load_sounds()
-    
+
     def load_sounds(self):
-        """Load all sound files from the sound directory and its subdirectories.
-    
-        Searches recursively through subdirectories and loads all sound files with
-        .ogg or .wav extensions. Sound names are stored as relative paths from the
-        sound directory, with directory separators replaced by forward slashes.
-        """
+        """Load all sound files from the sound directory and its subdirectories."""
         try:
-            # Walk through directory tree
-            for dirPath, dirNames, fileNames in os.walk(self.soundDir):
-                # Get relative path from soundDir
+            for dirPath, _, fileNames in os.walk(self.soundDir):
                 relPath = os.path.relpath(dirPath, self.soundDir)
-            
-                # Process each file
+
                 for fileName in fileNames:
-                    # Check if file is a valid sound file
                     if fileName.lower().endswith(('.ogg', '.wav')):
-                        # Full path to the sound file
                         fullPath = os.path.join(dirPath, fileName)
-                        
-                        # Create sound key (remove extension)
                         baseName = os.path.splitext(fileName)[0]
-                    
-                        # If in root sounds dir, just use basename
-                        if relPath == '.':
-                            soundKey = baseName
-                        else:
-                            # Otherwise use relative path + basename, normalized with forward slashes
-                            soundKey = os.path.join(relPath, baseName).replace('\\', '/')
-                    
-                        # Load the sound
+
+                        soundKey = baseName if relPath == '.' else os.path.join(relPath, baseName).replace('\\', '/')
                         self.sounds[soundKey] = pygame.mixer.Sound(fullPath)
-                    
         except Exception as e:
             print(f"Error loading sounds: {e}")
-    
-    def play_sound(self, soundName, volume=1.0, loop=False):
-        """Play a sound with current volume settings applied.
-        
-        Args:
-            soundName (str): Name of sound to play
-            volume (float): Base volume for the sound (0.0-1.0, default: 1.0)
-            
-        Returns:
-            pygame.mixer.Channel: The channel the sound is playing on
-        """
-        if soundName not in self.sounds:
-            return None
-            
-        sound = self.sounds[soundName]
-        if loop:
-            channel = sound.play(-1)
-        else:
-            channel = sound.play()
-        if channel:
-            channel.set_volume(volume * self.volumeService.get_sfx_volume())
-        return channel
-    
-    def calculate_volume_and_pan(self, playerPos, objPos, maxDistance=12):
-        """Calculate volume and stereo panning based on relative positions.
-        
-        Args:
-            playerPos (float): Player's position on x-axis
-            objPos (float): Object's position on x-axis
-            maxDistance (float): Maximum audible distance (default: 12)
-            
-        Returns:
-            tuple: (volume, left_vol, right_vol) values between 0 and 1
-        """
-        distance = abs(playerPos - objPos)
-        
-        if distance > maxDistance:
-            return 0, 0, 0  # No sound if out of range
-            
-        # Calculate volume (non-linear scaling for more noticeable changes)
-        # Apply masterVolume as the maximum possible volume
-        volume = (((maxDistance - distance) / maxDistance) ** 1.5) * self.volumeService.masterVolume
-        
-        # Determine left/right based on relative position
-        if playerPos < objPos:
-            # Object is to the right
-            left = max(0, 1 - (objPos - playerPos) / maxDistance)
-            right = 1
-        elif playerPos > objPos:
-            # Object is to the left
-            left = 1
-            right = max(0, 1 - (playerPos - objPos) / maxDistance)
-        else:
-            # Player is on the object
-            left = right = 1
-            
-        return volume, left, right
-    
-    def obj_play(self, soundName, playerPos, objPos, loop=True):
-        """Play a sound with positional audio.
-        
-        Args:
-            soundName (str): Name of sound to play
-            playerPos (float): Player's position for audio panning
-            objPos (float): Object's position for audio panning
-            loop (bool): Whether to loop the sound (default: True)
-            
-        Returns:
-            pygame.mixer.Channel: Sound channel object, or None if out of range
-        """
-        if soundName not in self.sounds:
-            return None
-            
-        volume, left, right = self.calculate_volume_and_pan(playerPos, objPos)
-        if volume == 0:
-            return None  # Don't play if out of range
-            
-        # Play the sound on a new channel
-        channel = self.sounds[soundName].play(-1 if loop else 0)
-        if channel:
-            channel.set_volume(
-                volume * left * self.volumeService.sfxVolume,
-                volume * right * self.volumeService.sfxVolume
-            )
-        return channel
-    
-    def obj_update(self, channel, playerPos, objPos):
-        """Update positional audio for a playing sound.
-        
-        Args:
-            channel: Sound channel to update
-            playerPos (float): New player position
-            objPos (float): New object position
-            
-        Returns:
-            pygame.mixer.Channel: Updated channel, or None if sound should stop
-        """
-        if channel is None:
-            return None
-            
-        volume, left, right = self.calculate_volume_and_pan(playerPos, objPos)
-        if volume == 0:
-            channel.stop()
-            return None
-            
-        # Apply the volume and pan
-        channel.set_volume(
-            volume * left * self.volumeService.sfxVolume,
-            volume * right * self.volumeService.sfxVolume
-        )
-        return channel
-    
-    def obj_stop(self, channel):
-        """Stop a playing sound channel.
-        
-        Args:
-            channel: Sound channel to stop
-            
-        Returns:
-            None if stopped successfully, otherwise returns original channel
-        """
-        try:
-            channel.stop()
-            return None
-        except:
-            return channel
-    
-    def play_ambiance(self, soundNames, probability, randomLocation=False):
-        """Play random ambient sounds with optional positional audio.
-        
-        Args:
-            soundNames (list): List of possible sound names to choose from
-            probability (int): Chance to play (1-100)
-            randomLocation (bool): Whether to randomize stereo position
-            
-        Returns:
-            pygame.mixer.Channel: Sound channel if played, None otherwise
-        """
-        # Check if any of the sounds in the list is already playing
-        for soundName in soundNames:
-            if pygame.mixer.find_channel(True) and pygame.mixer.find_channel(True).get_busy():
-                return None
-                
-        if random.randint(1, 100) > probability:
-            return None
-            
-        # Choose a random sound from the list
-        ambianceSound = random.choice(soundNames)
-        if ambianceSound not in self.sounds:
-            return None
-            
-        channel = self.sounds[ambianceSound].play()
-        
-        if randomLocation and channel:
-            leftVolume = random.random() * self.volumeService.get_sfx_volume()
-            rightVolume = random.random() * self.volumeService.get_sfx_volume()
-            channel.set_volume(leftVolume, rightVolume)
-            
-        return channel
-    
-    def play_random(self, soundPrefix, pause=False, interrupt=False):
-        """Play a random variation of a sound.
-        
-        Args:
-            soundPrefix (str): Base name of sound (will match all starting with this)
-            pause (bool): Whether to pause execution until sound finishes
-            interrupt (bool): Whether to interrupt other sounds
-        """
-        keys = []
-        for i in self.sounds.keys():
-            if re.match("^" + soundPrefix + ".*", i):
-                keys.append(i)
-                
-        if not keys:  # No matching sounds found
-            return None
-            
-        randomKey = random.choice(keys)
-        
-        if interrupt:
-            self.cut_scene(randomKey)
-            return
-        
-        channel = self.sounds[randomKey].play()
-        sfxVolume = self.volumeService.get_sfx_volume()
-        
-        if channel:
-            channel.set_volume(sfxVolume, sfxVolume)
-        
-        if pause:
-            time.sleep(self.sounds[randomKey].get_length())
-            
-        return channel
-    
-    def play_random_positional(self, soundPrefix, playerX, objectX):
-        """Play a random variation of a sound with positional audio.
-        
-        Args:
-            soundPrefix (str): Base name of sound to match
-            playerX (float): Player's x position
-            objectX (float): Object's x position
-            
-        Returns:
-            pygame.mixer.Channel: Sound channel if played, None otherwise
-        """
-        keys = [k for k in self.sounds.keys() if k.startswith(soundPrefix)]
-        if not keys:
-            return None
-        
-        randomKey = random.choice(keys)
-        volume, left, right = self.calculate_volume_and_pan(playerX, objectX)
-        
-        if volume == 0:
-            return None
-            
-        channel = self.sounds[randomKey].play()
-        if channel:
-            channel.set_volume(
-                volume * left * self.volumeService.sfxVolume,
-                volume * right * self.volumeService.sfxVolume
-            )
-        return channel
-    
-    def play_directional_sound(self, soundName, playerPos, objPos, centerDistance=3, volume=1.0):
-        """Play a sound with simplified directional audio.
-        
-        For sounds that need to be heard clearly regardless of distance, but still provide
-        directional feedback. Sound plays at full volume but pans left/right based on relative position.
-        
-        Args:
-            soundName (str): Name of sound to play
-            playerPos (float): Player's x position
-            objPos (float): Object's x position
-            centerDistance (float): Distance within which sound plays center (default: 3)
-            volume (float): Base volume multiplier (0.0-1.0, default: 1.0)
-            
-        Returns:
-            pygame.mixer.Channel: The channel the sound is playing on
-        """
-        if soundName not in self.sounds:
-            return None
-            
-        channel = self.sounds[soundName].play()
-        if channel:
-            # Apply volume settings
-            finalVolume = volume * self.volumeService.get_sfx_volume()
-            
-            # If player is within centerDistance tiles of object, play in center
-            if abs(playerPos - objPos) <= centerDistance:
-                # Equal volume in both speakers (center)
-                channel.set_volume(finalVolume, finalVolume)
-            elif playerPos > objPos:
-                # Object is to the left of player
-                channel.set_volume(finalVolume, (finalVolume + 0.01) / 2)
-            else:
-                # Object is to the right of player
-                channel.set_volume((finalVolume + 0.01) / 2, finalVolume)
-        return channel
-    
-    def cut_scene(self, soundName):
-        """Play a sound as a cut scene, stopping other sounds.
-        
-        Args:
-            soundName (str): Name of sound to play
-        """
-        if soundName not in self.sounds:
-            return
-            
+
+    def _find_matching_sound(self, pattern):
+        """Find a random sound matching the pattern."""
+        keys = [k for k in self.sounds.keys() if re.match("^" + pattern + ".*", k)]
+        return random.choice(keys) if keys else None
+
+    def _handle_cutscene(self, soundName):
+        """Play a sound as a cut scene."""
         pygame.event.clear()
         pygame.mixer.stop()
-        
-        # Get the reserved channel (0) for cut scenes
+
         channel = pygame.mixer.Channel(0)
-        
-        # Apply the appropriate volume settings
         sfxVolume = self.volumeService.get_sfx_volume()
         channel.set_volume(sfxVolume, sfxVolume)
-        
-        # Play the sound
+
         channel.play(self.sounds[soundName])
-        
+
         while pygame.mixer.get_busy():
             for event in pygame.event.get():
                 if event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_RETURN, pygame.K_SPACE]:
                     pygame.mixer.stop()
-                    return
+                    return None
             pygame.time.delay(10)
-    
-    def play_random_falling(self, soundPrefix, playerX, objectX, startY, 
-                           currentY=0, maxY=20, existingChannel=None):
-        """Play or update a falling sound with positional audio and volume based on height.
-        
-        Args:
-            soundPrefix (str): Base name of sound to match
-            playerX (float): Player's x position
-            objectX (float): Object's x position
-            startY (float): Starting Y position (0-20, higher = quieter start)
-            currentY (float): Current Y position (0 = ground level) (default: 0)
-            maxY (float): Maximum Y value (default: 20)
-            existingChannel: Existing sound channel to update (default: None)
-            
-        Returns:
-            pygame.mixer.Channel: Sound channel for updating position/volume,
-                                or None if sound should stop
-        """
-        # Calculate horizontal positioning
-        volume, left, right = self.calculate_volume_and_pan(playerX, objectX)
-        
-        # Calculate vertical fall volume multiplier (0 at maxY, 1 at y=0)
+
+        return None
+
+    def _get_stereo_panning(self, playerPos, objPos, centerDistance=None):
+        """Calculate stereo panning based on positions."""
+        # Extract x-positions
+        playerX = playerPos[0] if isinstance(playerPos, (tuple, list)) else playerPos
+        objX = objPos[0] if isinstance(objPos, (tuple, list)) else objPos
+
+        # For directional sound with fixed distance
+        if centerDistance is not None:
+            if abs(playerX - objX) <= centerDistance:
+                return (1, 1)  # Center
+            elif playerX > objX:
+                return (1, 0.505)  # Left
+            else:
+                return (0.505, 1)  # Right
+
+        # Calculate regular panning
+        volume, left, right = self.calculate_volume_and_pan(playerPos, objPos)
+        return (volume * left, volume * right) if volume > 0 else (0, 0)
+
+    def play_sound(self, soundName, volume=1.0, loop=False, playerPos=None, objPos=None, 
+                  centerDistance=None, pattern=False, interrupt=False, pause=False, cutScene=False):
+        """Unified method to play sounds with various options."""
+        # Resolve sound name if pattern matching is requested
+        if pattern:
+            soundName = self._find_matching_sound(soundName)
+            if not soundName:
+                return None
+
+        # Check if sound exists
+        if soundName not in self.sounds:
+            return None
+
+        # Handle cut scene mode
+        if cutScene:
+            return self._handle_cutscene(soundName)
+
+        # Handle interrupt (stop other sounds)
+        if interrupt:
+            pygame.event.clear()
+            pygame.mixer.stop()
+
+        # Play the sound
+        channel = self.sounds[soundName].play(-1 if loop else 0)
+        if not channel:
+            return None
+
+        # Apply appropriate volume settings
+        sfx_volume = self.volumeService.get_sfx_volume()
+
+        # Handle positional audio if positions are provided
+        if playerPos is not None and objPos is not None:
+            # Calculate stereo panning
+            left_vol, right_vol = self._get_stereo_panning(playerPos, objPos, centerDistance)
+
+            # Don't play if out of range
+            if left_vol == 0 and right_vol == 0:
+                channel.stop()
+                return None
+
+            # Apply positional volume adjustments
+            channel.set_volume(volume * left_vol * sfx_volume, volume * right_vol * sfx_volume)
+        else:
+            # Non-positional sound
+            channel.set_volume(volume * sfx_volume)
+
+        # Pause execution if requested
+        if pause:
+            time.sleep(self.sounds[soundName].get_length())
+
+        return channel
+
+    def calculate_volume_and_pan(self, playerPos, objPos, maxDistance=12):
+        """Calculate volume and stereo panning based on relative positions."""
+        # Determine if we're using 2D or 1D positioning
+        if isinstance(playerPos, (tuple, list)) and isinstance(objPos, (tuple, list)):
+            # 2D distance calculation
+            distance = math.sqrt((playerPos[0] - objPos[0])**2 + (playerPos[1] - objPos[1])**2)
+            playerX, objX = playerPos[0], objPos[0]
+        else:
+            # 1D calculation (backward compatible)
+            distance = abs(playerPos - objPos)
+            playerX, objX = playerPos, objPos
+
+        if distance > maxDistance:
+            return 0, 0, 0  # No sound if out of range
+
+        # Calculate volume (non-linear scaling for more noticeable changes)
+        volume = (((maxDistance - distance) / maxDistance) ** 1.5) * self.volumeService.masterVolume
+
+        # Determine left/right based on relative position
+        if playerX < objX:  # Object is to the right
+            left = max(0, 1 - (objX - playerX) / maxDistance)
+            right = 1
+        elif playerX > objX:  # Object is to the left
+            left = 1
+            right = max(0, 1 - (playerX - objX) / maxDistance)
+        else:  # Player is on the object
+            left = right = 1
+
+        return volume, left, right
+
+    def update_sound_position(self, channel, playerPos, objPos):
+        """Update positional audio for a playing sound."""
+        if not channel:
+            return None
+
+        # Calculate new stereo panning
+        left_vol, right_vol = self._get_stereo_panning(playerPos, objPos)
+
+        # Stop if out of range
+        if left_vol == 0 and right_vol == 0:
+            channel.stop()
+            return None
+
+        # Apply the volume and pan
+        channel.set_volume(left_vol * self.volumeService.sfxVolume, right_vol * self.volumeService.sfxVolume)
+        return channel
+
+    def stop_sound(self, channel):
+        """Stop a playing sound channel."""
+        if channel:
+            try:
+                channel.stop()
+            except:
+                pass
+        return None
+
+    def play_falling_sound(self, soundPrefix, playerPos, objPos, startY, currentY=0, maxY=20, existingChannel=None):
+        """Play or update a sound with positional audio that changes with height."""
+        # Extract positions
+        playerX = playerPos[0] if isinstance(playerPos, (tuple, list)) else playerPos
+        objX = objPos[0] if isinstance(objPos, (tuple, list)) else objPos
+
+        # Calculate volumes
+        volume, left, right = self.calculate_volume_and_pan(playerX, objX)
+
+        # Apply vertical fall multiplier (0 at maxY, 1 at y=0)
         fallMultiplier = 1 - (currentY / maxY)
-        
-        # Adjust final volumes
         finalVolume = volume * fallMultiplier
         finalLeft = left * finalVolume
         finalRight = right * finalVolume
-        
-        if existingChannel is not None:
-            if volume == 0:  # Out of audible range
+
+        # Update existing channel or create new one
+        if existingChannel:
+            if volume == 0:
                 existingChannel.stop()
                 return None
             existingChannel.set_volume(
@@ -403,17 +229,17 @@ class Sound:
                 finalRight * self.volumeService.sfxVolume
             )
             return existingChannel
-        else:  # Need to create new channel
-            if volume == 0:  # Don't start if out of range
+        else:
+            if volume == 0:
                 return None
-                
-            # Find matching sound files
-            keys = [k for k in self.sounds.keys() if k.startswith(soundPrefix)]
-            if not keys:
+
+            # Find a matching sound
+            soundName = self._find_matching_sound(soundPrefix)
+            if not soundName:
                 return None
-                
-            randomKey = random.choice(keys)
-            channel = self.sounds[randomKey].play()
+
+            # Play the sound
+            channel = self.sounds[soundName].play()
             if channel:
                 channel.set_volume(
                     finalLeft * self.volumeService.sfxVolume,
@@ -421,362 +247,282 @@ class Sound:
                 )
             return channel
 
+    def play_bgm(self, musicFile):
+        """Play background music with proper volume settings."""
+        try:
+            pygame.mixer.music.stop()
+            pygame.mixer.music.load(musicFile)
+            pygame.mixer.music.set_volume(self.volumeService.get_bgm_volume())
+            pygame.mixer.music.play(-1)
+        except Exception as e:
+            print(f"Error playing background music: {e}")
 
-# Global functions for backward compatibility
+    def adjust_master_volume(self, change):
+        """Adjust the master volume for all sounds."""
+        self.volumeService.adjust_master_volume(change, pygame.mixer)
 
-def play_bgm(musicFile):
-    """Play background music with proper volume settings.
-    
-    Args:
-        musicFile (str): Path to the music file to play
-    """
-    try:
-        pygame.mixer.music.stop()
-        pygame.mixer.music.load(musicFile)
-        pygame.mixer.music.set_volume(volumeService.get_bgm_volume())
-        pygame.mixer.music.play(-1)  # Loop indefinitely
-    except Exception as e:
-        pass
+    def adjust_bgm_volume(self, change):
+        """Adjust only the background music volume."""
+        self.volumeService.adjust_bgm_volume(change, pygame.mixer)
 
-def adjust_master_volume(change):
-    """Adjust the master volume for all sounds.
-    
-    Args:
-        change (float): Amount to change volume by (positive or negative)
-    """
-    volumeService.adjust_master_volume(change, pygame.mixer)
+    def adjust_sfx_volume(self, change):
+        """Adjust volume for sound effects only."""
+        self.volumeService.adjust_sfx_volume(change, pygame.mixer)
 
-def adjust_bgm_volume(change):
-    """Adjust only the background music volume.
-    
-    Args:
-        change (float): Amount to change volume by (positive or negative)
-    """
-    volumeService.adjust_bgm_volume(change, pygame.mixer)
 
-def adjust_sfx_volume(change):
-    """Adjust volume for sound effects only.
-    
-    Args:
-        change (float): Amount to change volume by (positive or negative)
-    """
-    volumeService.adjust_sfx_volume(change, pygame.mixer)
+# Optimized helper functions for global use
+def _get_stereo_panning(playerPos, objPos, centerDistance=None, maxDistance=12):
+    """Simplified panning calculation."""
+    # Extract x-positions
+    playerX = playerPos[0] if isinstance(playerPos, (tuple, list)) else playerPos
+    objX = objPos[0] if isinstance(objPos, (tuple, list)) else objPos
 
-def calculate_volume_and_pan(playerPos, objPos):
-    """Calculate volume and stereo panning based on relative positions.
-    
-    Args:
-        playerPos (float): Player's position on x-axis
-        objPos (float): Object's position on x-axis
-        
-    Returns:
-        tuple: (volume, left_vol, right_vol) values between 0 and 1
-    """
-    distance = abs(playerPos - objPos)
-    maxDistance = 12  # Maximum audible distance
-    
-    if distance > maxDistance:
-        return 0, 0, 0  # No sound if out of range
-        
-    # Calculate volume (non-linear scaling for more noticeable changes)
-    # Apply masterVolume as the maximum possible volume
-    volume = (((maxDistance - distance) / maxDistance) ** 1.5) * volumeService.masterVolume
-    
-    # Determine left/right based on relative position
-    if playerPos < objPos:
-        # Object is to the right
-        left = max(0, 1 - (objPos - playerPos) / maxDistance)
-        right = 1
-    elif playerPos > objPos:
-        # Object is to the left
-        left = 1
-        right = max(0, 1 - (playerPos - objPos) / maxDistance)
-    else:
-        # Player is on the object
-        left = right = 1
-        
-    return volume, left, right
-
-def play_sound(sound, volume=1.0, loop=False):
-    """Play a sound with current volume settings applied.
-    
-    Args:
-        sound: pygame Sound object to play
-        volume: base volume for the sound (0.0-1.0, default: 1.0)
-        
-    Returns:
-        pygame.mixer.Channel: The channel the sound is playing on
-    """
-    if loop:
-        channel = sound.play(-1)
-    else:
-        channel = sound.play()
-    if channel:
-        channel.set_volume(volume * volumeService.get_sfx_volume())
-    return channel
-
-def obj_play(sounds, soundName, playerPos, objPos, loop=True):
-    """Play a sound with positional audio.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Name of sound to play
-        playerPos (float): Player's position for audio panning
-        objPos (float): Object's position for audio panning
-        loop (bool): Whether to loop the sound (default: True)
-        
-    Returns:
-        pygame.mixer.Channel: Sound channel object, or None if out of range
-    """
-    volume, left, right = calculate_volume_and_pan(playerPos, objPos)
-    if volume == 0:
-        return None  # Don't play if out of range
-        
-    # Play the sound on a new channel
-    channel = sounds[soundName].play(-1 if loop else 0)
-    if channel:
-        channel.set_volume(volume * left * volumeService.sfxVolume,
-            volume * right * volumeService.sfxVolume)
-    return channel
-
-def obj_update(channel, playerPos, objPos):
-    """Update positional audio for a playing sound.
-    
-    Args:
-        channel: Sound channel to update
-        playerPos (float): New player position
-        objPos (float): New object position
-        
-    Returns:
-        pygame.mixer.Channel: Updated channel, or None if sound should stop
-    """
-    if channel is None:
-        return None
-        
-    volume, left, right = calculate_volume_and_pan(playerPos, objPos)
-    if volume == 0:
-        channel.stop()
-        return None
-        
-    # Apply the volume and pan
-    channel.set_volume(volume * left * volumeService.sfxVolume,
-        volume * right * volumeService.sfxVolume)
-    return channel
-
-def obj_stop(channel):
-    """Stop a playing sound channel.
-    
-    Args:
-        channel: Sound channel to stop
-        
-    Returns:
-        None if stopped successfully, otherwise returns original channel
-    """
-    try:
-        channel.stop()
-        return None
-    except:
-        return channel
-
-def play_ambiance(sounds, soundNames, probability, randomLocation=False):
-    """Play random ambient sounds with optional positional audio.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundNames (list): List of possible sound names to choose from
-        probability (int): Chance to play (1-100)
-        randomLocation (bool): Whether to randomize stereo position
-        
-    Returns:
-        pygame.mixer.Channel: Sound channel if played, None otherwise
-    """
-    # Check if any of the sounds in the list is already playing
-    for soundName in soundNames:
-        if pygame.mixer.find_channel(True) and pygame.mixer.find_channel(True).get_busy():
-            return None
-            
-    if random.randint(1, 100) > probability:
-        return None
-        
-    # Choose a random sound from the list
-    ambianceSound = random.choice(soundNames)
-    channel = sounds[ambianceSound].play()
-    
-    if randomLocation and channel:
-        leftVolume = random.random() * volumeService.get_sfx_volume()
-        rightVolume = random.random() * volumeService.get_sfx_volume()
-        channel.set_volume(leftVolume, rightVolume)
-        
-    return channel
-
-def play_random(sounds, soundName, pause=False, interrupt=False):
-    """Play a random variation of a sound.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Base name of sound (will match all starting with this)
-        pause (bool): Whether to pause execution until sound finishes
-        interrupt (bool): Whether to interrupt other sounds
-    """
-    key = []
-    for i in sounds.keys():
-        if re.match("^" + soundName + ".*", i):
-            key.append(i)
-            
-    if not key:  # No matching sounds found
-        return
-        
-    randomKey = random.choice(key)
-    
-    if interrupt:
-        cut_scene(sounds, randomKey)
-        return
-    
-    channel = sounds[randomKey].play()
-    if channel:
-        sfxVolume = volumeService.get_sfx_volume()
-        channel.set_volume(sfxVolume, sfxVolume)
-    
-    if pause:
-        time.sleep(sounds[randomKey].get_length())
-
-def play_random_positional(sounds, soundName, playerX, objectX):
-    """Play a random variation of a sound with positional audio.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Base name of sound to match
-        playerX (float): Player's x position
-        objectX (float): Object's x position
-        
-    Returns:
-        pygame.mixer.Channel: Sound channel if played, None otherwise
-    """
-    keys = [k for k in sounds.keys() if k.startswith(soundName)]
-    if not keys:
-        return None
-    
-    randomKey = random.choice(keys)
-    volume, left, right = calculate_volume_and_pan(playerX, objectX)
-    
-    if volume == 0:
-        return None
-        
-    channel = sounds[randomKey].play()
-    if channel:
-        channel.set_volume(volume * left * volumeService.sfxVolume,
-            volume * right * volumeService.sfxVolume)
-    return channel
-
-def play_directional_sound(sounds, soundName, playerPos, objPos, centerDistance=3, volume=1.0):
-    """Play a sound with simplified directional audio.
-    
-    For sounds that need to be heard clearly regardless of distance, but still provide
-    directional feedback. Sound plays at full volume but pans left/right based on relative position.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Name of sound to play
-        playerPos (float): Player's x position
-        objPos (float): Object's x position
-        centerDistance (float): Distance within which sound plays center (default: 3)
-        volume (float): Base volume multiplier (0.0-1.0, default: 1.0)
-        
-    Returns:
-        pygame.mixer.Channel: The channel the sound is playing on
-    """
-    channel = sounds[soundName].play()
-    if channel:
-        # Apply volume settings
-        finalVolume = volume * volumeService.get_sfx_volume()
-        
-        # If player is within centerDistance tiles of object, play in center
-        if abs(playerPos - objPos) <= centerDistance:
-            # Equal volume in both speakers (center)
-            channel.set_volume(finalVolume, finalVolume)
-        elif playerPos > objPos:
-            # Object is to the left of player
-            channel.set_volume(finalVolume, (finalVolume + 0.01) / 2)
+    # For directional sound with fixed distance
+    if centerDistance is not None:
+        if abs(playerX - objX) <= centerDistance:
+            return (1, 1)  # Center
+        elif playerX > objX:
+            return (1, 0.505)  # Left
         else:
-            # Object is to the right of player
-            channel.set_volume((finalVolume + 0.01) / 2, finalVolume)
-    return channel
+            return (0.505, 1)  # Right
 
-def cut_scene(sounds, soundName):
-    """Play a sound as a cut scene, stopping other sounds.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Name of sound to play
-    """
+    # Calculate distance
+    if isinstance(playerPos, (tuple, list)) and isinstance(objPos, (tuple, list)):
+        distance = math.sqrt((playerPos[0] - objPos[0])**2 + (playerPos[1] - objPos[1])**2)
+    else:
+        distance = abs(playerPos - objPos)
+
+    if distance > maxDistance:
+        return (0, 0)  # No sound if out of range
+
+    # Calculate volume (non-linear scaling for more noticeable changes)
+    volume = (((maxDistance - distance) / maxDistance) ** 1.5) * volumeService.masterVolume
+
+    # Determine left/right based on relative position
+    if playerX < objX:  # Object is to the right
+        left = max(0, 1 - (objX - playerX) / maxDistance)
+        right = 1
+    elif playerX > objX:  # Object is to the left
+        left = 1
+        right = max(0, 1 - (playerX - objX) / maxDistance)
+    else:  # Player is on the object
+        left = right = 1
+
+    return (volume * left, volume * right)
+
+def _play_cutscene(sound, sounds=None):
+    """Play a sound as a cut scene."""
     pygame.event.clear()
     pygame.mixer.stop()
-    
-    # Get the reserved channel (0) for cut scenes
+
     channel = pygame.mixer.Channel(0)
-    
-    # Apply the appropriate volume settings
     sfxVolume = volumeService.get_sfx_volume()
     channel.set_volume(sfxVolume, sfxVolume)
-    
-    # Play the sound
-    channel.play(sounds[soundName])
-    
+
+    # Determine which sound to play
+    if isinstance(sound, pygame.mixer.Sound):
+        channel.play(sound)
+    elif isinstance(sounds, dict) and sound in sounds:
+        channel.play(sounds[sound])
+    elif isinstance(sounds, Sound) and sound in sounds.sounds:
+        channel.play(sounds.sounds[sound])
+    else:
+        return None
+
+    # Wait for completion or key press
     while pygame.mixer.get_busy():
         for event in pygame.event.get():
             if event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_RETURN, pygame.K_SPACE]:
                 pygame.mixer.stop()
-                return
+                return None
         pygame.time.delay(10)
 
-def play_random_falling(sounds, soundName, playerX, objectX, startY, 
-                       currentY=0, maxY=20, existingChannel=None):
-    """Play or update a falling sound with positional audio and volume based on height.
-    
-    Args:
-        sounds (dict): Dictionary of sound objects
-        soundName (str): Base name of sound to match
-        playerX (float): Player's x position
-        objectX (float): Object's x position
-        startY (float): Starting Y position (0-20, higher = quieter start)
-        currentY (float): Current Y position (0 = ground level) (default: 0)
-        maxY (float): Maximum Y value (default: 20)
-        existingChannel: Existing sound channel to update (default: None)
-        
-    Returns:
-        pygame.mixer.Channel: Sound channel for updating position/volume,
-                            or None if sound should stop
-    """
-    # Calculate horizontal positioning
-    volume, left, right = calculate_volume_and_pan(playerX, objectX)
-    
-    # Calculate vertical fall volume multiplier (0 at maxY, 1 at y=0)
-    fallMultiplier = 1 - (currentY / maxY)
-    
-    # Adjust final volumes
-    finalVolume = volume * fallMultiplier
-    finalLeft = left * finalVolume
-    finalRight = right * finalVolume
-    
-    if existingChannel is not None:
-        if volume == 0:  # Out of audible range
-            existingChannel.stop()
+    return None
+
+def _find_matching_sound(soundPattern, sounds):
+    """Find sounds matching a pattern in a dictionary."""
+    if isinstance(sounds, Sound):
+        keys = [k for k in sounds.sounds.keys() if re.match("^" + soundPattern + ".*", k)]
+    else:
+        keys = [k for k in sounds.keys() if re.match("^" + soundPattern + ".*", k)]
+    return random.choice(keys) if keys else None
+
+# Global functions for backward compatibility
+def play_bgm(musicFile):
+    """Play background music with proper volume settings."""
+    try:
+        pygame.mixer.music.stop()
+        pygame.mixer.music.load(musicFile)
+        pygame.mixer.music.set_volume(volumeService.get_bgm_volume())
+        pygame.mixer.music.play(-1)
+    except: pass
+
+def adjust_master_volume(change):
+    """Adjust the master volume."""
+    volumeService.adjust_master_volume(change, pygame.mixer)
+
+def adjust_bgm_volume(change):
+    """Adjust background music volume."""
+    volumeService.adjust_bgm_volume(change, pygame.mixer)
+
+def adjust_sfx_volume(change):
+    """Adjust sound effects volume."""
+    volumeService.adjust_sfx_volume(change, pygame.mixer)
+
+def calculate_volume_and_pan(playerPos, objPos, maxDistance=12):
+    """Calculate volume and stereo panning."""
+    left_vol, right_vol = _get_stereo_panning(playerPos, objPos, None, maxDistance)
+    # Convert to old format (volume, left, right)
+    if left_vol == 0 and right_vol == 0:
+        return 0, 0, 0
+    elif left_vol >= right_vol:
+        volume = left_vol
+        return volume, 1, right_vol/left_vol
+    else:
+        volume = right_vol
+        return volume, left_vol/right_vol, 1
+
+def play_sound(sound_or_name, volume=1.0, loop=False, playerPos=None, objPos=None, 
+              centerDistance=None, pattern=False, interrupt=False, pause=False, 
+              cutScene=False, sounds=None):
+    """Unified sound playing function with backward compatibility."""
+    # Handle cut scene mode
+    if cutScene:
+        return _play_cutscene(sound_or_name, sounds)
+
+    # Handle pattern matching
+    if pattern and isinstance(sound_or_name, str) and sounds:
+        matched_sound = _find_matching_sound(sound_or_name, sounds)
+        if not matched_sound:
             return None
-        existingChannel.set_volume(finalLeft * volumeService.sfxVolume,
-            finalRight * volumeService.sfxVolume)
-        return existingChannel
-    else:  # Need to create new channel
-        if volume == 0:  # Don't start if out of range
-            return None
-            
-        # Find matching sound files
-        keys = [k for k in sounds.keys() if k.startswith(soundName)]
-        if not keys:
-            return None
-            
-        randomKey = random.choice(keys)
-        channel = sounds[randomKey].play()
+        sound_or_name = matched_sound
+
+    # Handle interrupt
+    if interrupt:
+        pygame.event.clear()
+        pygame.mixer.stop()
+
+    # Case 1: Sound instance provided
+    if isinstance(sound_or_name, Sound):
+        return sound_or_name.play_sound(sound_or_name, volume, loop, playerPos, objPos, 
+                                       centerDistance, False, False, pause, False)
+
+    # Case 2: Sound name with Sound instance
+    elif isinstance(sounds, Sound) and isinstance(sound_or_name, str):
+        return sounds.play_sound(sound_or_name, volume, loop, playerPos, objPos,
+                                centerDistance, False, False, pause, False)
+
+    # Case 3: Direct pygame.Sound
+    elif isinstance(sound_or_name, pygame.mixer.Sound):
+        channel = sound_or_name.play(-1 if loop else 0)
         if channel:
-            channel.set_volume(finalLeft * volumeService.sfxVolume,
-                finalRight * volumeService.sfxVolume)
+            channel.set_volume(volume * volumeService.get_sfx_volume())
         return channel
+
+    # 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
+        channel = sounds[sound_or_name].play(-1 if loop else 0)
+        if not channel:
+            return None
+
+        # Apply volume settings
+        sfx_vol = volumeService.get_sfx_volume()
+
+        # Handle positional audio
+        if playerPos is not None and objPos is not None:
+            left_vol, right_vol = _get_stereo_panning(playerPos, objPos, centerDistance)
+            if left_vol == 0 and right_vol == 0:
+                channel.stop()
+                return None
+            channel.set_volume(volume * left_vol * sfx_vol, volume * right_vol * sfx_vol)
+        else:
+            channel.set_volume(volume * sfx_vol)
+
+        # Pause if requested
+        if pause:
+            time.sleep(sounds[sound_or_name].get_length())
+
+        return channel
+
+    return None
+
+def obj_update(channel, playerPos, objPos):
+    """Update positional audio for a playing sound."""
+    if not channel:
+        return None
+
+    left_vol, right_vol = _get_stereo_panning(playerPos, objPos)
+    if left_vol == 0 and right_vol == 0:
+        channel.stop()
+        return None
+
+    channel.set_volume(left_vol * volumeService.sfxVolume, right_vol * volumeService.sfxVolume)
+    return channel
+
+def obj_stop(channel):
+    """Stop a sound channel."""
+    if channel:
+        try: channel.stop()
+        except: pass
+    return None
+
+# Extremely concise lambda definitions for legacy functions
+obj_play = lambda sounds, soundName, playerPos, objPos, loop=True: play_sound(
+    soundName, 1.0, loop, playerPos, objPos, None, False, False, False, False, sounds)
+
+play_ambiance = lambda sounds, soundNames, probability, randomLocation=False: play_sound(
+    random.choice(soundNames) if random.randint(1, 100) <= probability and not any(
+    pygame.mixer.find_channel(True) and pygame.mixer.find_channel(True).get_busy() 
+    for _ in ([soundNames] if isinstance(soundNames, str) else soundNames)) else None,
+    1.0, False, None, None, None, False, False, False, False,
+    sounds if not isinstance(sounds, Sound) else None)
+
+play_random = lambda sounds, soundName, pause=False, interrupt=False: play_sound(
+    soundName, 1.0, False, None, None, None, True, interrupt, pause, False, sounds)
+
+play_random_positional = lambda sounds, soundName, playerX, objectX: play_sound(
+    soundName, 1.0, False, playerX, objectX, None, True, False, False, False, sounds)
+
+play_directional_sound = lambda sounds, soundName, playerPos, objPos, centerDistance=3, volume=1.0: play_sound(
+    soundName, volume, False, playerPos, objPos, centerDistance, False, False, False, False, sounds)
+
+cut_scene = lambda sounds, soundName: _play_cutscene(soundName, sounds)
+
+def play_random_falling(sounds, soundName, playerX, objectX, startY, currentY=0, maxY=20, existingChannel=None):
+    """Handle falling sound."""
+    if isinstance(sounds, Sound):
+        return sounds.play_falling_sound(soundName, playerX, objectX, startY, currentY, maxY, existingChannel)
+
+    # Legacy implementation
+    left_vol, right_vol = _get_stereo_panning(playerX, objectX)
+    if left_vol == 0 and right_vol == 0:
+        if existingChannel:
+            existingChannel.stop()
+        return None
+
+    # Calculate fall multiplier
+    fallMultiplier = 1 - (currentY / maxY)
+    finalLeft = left_vol * fallMultiplier
+    finalRight = right_vol * fallMultiplier
+
+    if existingChannel:
+        existingChannel.set_volume(
+            finalLeft * volumeService.sfxVolume,
+            finalRight * volumeService.sfxVolume
+        )
+        return existingChannel
+
+    # Find matching sound
+    matched_sound = _find_matching_sound(soundName, sounds)
+    if not matched_sound:
+        return None
+
+    # Play the sound
+    channel = sounds[matched_sound].play()
+    if channel:
+        channel.set_volume(
+            finalLeft * volumeService.sfxVolume,
+            finalRight * volumeService.sfxVolume
+        )
+    return channel
diff --git a/speech.py b/speech.py
index 7e9a591..4da3f28 100644
--- a/speech.py
+++ b/speech.py
@@ -15,26 +15,26 @@ from sys import exit
 
 class Speech:
     """Handles text-to-speech functionality."""
-    
+
     _instance = None
-    
+
     @classmethod
     def get_instance(cls):
         """Get or create the singleton instance."""
         if cls._instance is None:
             cls._instance = Speech()
         return cls._instance
-    
+
     def __init__(self):
         """Initialize speech system with available provider."""
         # Handle speech delays so we don't get stuttering
         self.lastSpoken = {"text": None, "time": 0}
         self.speechDelay = 250  # ms
-        
+
         # Try to initialize a speech provider
         self.provider = None
         self.providerName = None
-        
+
         # Try speechd first
         try:
             import speechd
@@ -44,7 +44,7 @@ class Speech:
             return
         except ImportError:
             pass
-            
+
         # Try accessible_output2 next
         try:
             import accessible_output2.outputs.auto
@@ -54,31 +54,31 @@ class Speech:
             return
         except ImportError:
             pass
-            
+
         # No speech providers found
         print("No speech providers found.")
-    
+
     def speak(self, text, interrupt=True):
         """Speak text using the configured speech provider and display on screen.
-        
+
         Args:
             text (str): Text to speak and display
             interrupt (bool): Whether to interrupt current speech (default: True)
         """
         if not self.provider:
             return
-            
+
         currentTime = pygame.time.get_ticks()
-        
+
         # Check if this is the same text within the delay window
         if (self.lastSpoken["text"] == text and 
             currentTime - self.lastSpoken["time"] < self.speechDelay):
             return
-        
+
         # Update last spoken tracking
         self.lastSpoken["text"] = text
         self.lastSpoken["time"] = currentTime
-        
+
         # Proceed with speech
         if self.providerName == "speechd":
             if interrupt:
@@ -86,12 +86,12 @@ class Speech:
             self.spd.say(text)
         elif self.providerName == "accessible_output2":
             self.ao2.speak(text, interrupt=interrupt)
-        
+
         # Display the text on screen
         screen = pygame.display.get_surface()
         if not screen:
             return
-            
+
         font = pygame.font.Font(None, 36)
         # Wrap the text
         maxWidth = screen.get_width() - 40  # Leave a 20-pixel margin on each side
@@ -109,7 +109,7 @@ class Speech:
             screen.blit(surface, textRect)
             currentY += surface.get_height()
         pygame.display.flip()
-    
+
     def close(self):
         """Clean up speech resources."""
         if self.providerName == "speechd":
@@ -120,7 +120,7 @@ _speechInstance = None
 
 def speak(text, interrupt=True):
     """Speak text using the global speech instance.
-    
+
     Args:
         text (str): Text to speak and display
         interrupt (bool): Whether to interrupt current speech (default: True)
diff --git a/utils.py b/utils.py
index 14b0503..4c0092e 100644
--- a/utils.py
+++ b/utils.py
@@ -26,137 +26,137 @@ from .scoreboard import Scoreboard
 
 class Game:
     """Central class to manage all game systems."""
-    
+
     def __init__(self, title):
         """Initialize a new game.
-        
+
         Args:
             title (str): Title of the game
         """
         self.title = title
-        
+
         # Initialize services
         self.pathService = PathService.get_instance().initialize(title)
         self.configService = ConfigService.get_instance()
         self.configService.set_game_info(title, self.pathService)
         self.volumeService = VolumeService.get_instance()
-        
+
         # Initialize game components (lazy loaded)
         self._speech = None
         self._sound = None
         self._scoreboard = None
-        
+
         # Display text instructions flag
         self.displayTextUsageInstructions = False
-    
+
     @property
     def speech(self):
         """Get the speech system (lazy loaded).
-        
+
         Returns:
             Speech: Speech system instance
         """
         if not self._speech:
             self._speech = Speech.get_instance()
         return self._speech
-    
+
     @property
     def sound(self):
         """Get the sound system (lazy loaded).
-        
+
         Returns:
             Sound: Sound system instance
         """
         if not self._sound:
             self._sound = Sound("sounds/", self.volumeService)
         return self._sound
-    
+
     @property
     def scoreboard(self):
         """Get the scoreboard (lazy loaded).
-        
+
         Returns:
             Scoreboard: Scoreboard instance
         """
         if not self._scoreboard:
             self._scoreboard = Scoreboard(self.configService)
         return self._scoreboard
-    
+
     def initialize(self):
         """Initialize the game GUI and sound system.
-        
+
         Returns:
             Game: Self for method chaining
         """
         # Set process title
         setproctitle(str.lower(str.replace(self.title, " ", "")))
-        
+
         # Seed the random generator
         random.seed()
-        
+
         # Initialize pygame
         pygame.init()
         pygame.display.set_mode((800, 600))
         pygame.display.set_caption(self.title)
-        
+
         # Set up audio system
         pygame.mixer.pre_init(44100, -16, 2, 1024)
         pygame.mixer.init()
         pygame.mixer.set_num_channels(32)
         pygame.mixer.set_reserved(0)  # Reserve channel for cut scenes
-        
+
         # Enable key repeat for volume controls
         pygame.key.set_repeat(500, 100)
-        
+
         # Load sound effects
         self.sound
-        
+
         # Play intro sound if available
         if 'game-intro' in self.sound.sounds:
             self.sound.cut_scene('game-intro')
-            
+
         return self
-    
+
     def speak(self, text, interrupt=True):
         """Speak text using the speech system.
-        
+
         Args:
             text (str): Text to speak
             interrupt (bool): Whether to interrupt current speech
-            
+
         Returns:
             Game: Self for method chaining
         """
         self.speech.speak(text, interrupt)
         return self
-    
+
     def play_bgm(self, musicFile):
         """Play background music.
-        
+
         Args:
             musicFile (str): Path to music file
-            
+
         Returns:
             Game: Self for method chaining
         """
         self.sound.play_bgm(musicFile)
         return self
-    
+
     def display_text(self, textLines):
         """Display text with navigation controls.
-        
+
         Args:
             textLines (list): List of text lines
-            
+
         Returns:
             Game: Self for method chaining
         """
         # Store original text with blank lines for copying
         originalText = textLines.copy()
-        
+
         # Create navigation text by filtering out blank lines
         navText = [line for line in textLines if line.strip()]
-        
+
         # Add instructions at the start on the first display
         if not self.displayTextUsageInstructions:
             instructions = ("Press space to read the whole text. Use up and down arrows to navigate "
@@ -164,20 +164,20 @@ class Game:
                         "or t to copy the entire text. Press enter or escape when you are done reading.")
             navText.insert(0, instructions)
             self.displayTextUsageInstructions = True
-        
+
         # Add end marker
         navText.append("End of text.")
-        
+
         currentIndex = 0
         self.speech.speak(navText[currentIndex])
-        
+
         while True:
             event = pygame.event.wait()
             if event.type == pygame.KEYDOWN:
                 # Check for Alt modifier
                 mods = pygame.key.get_mods()
                 altPressed = mods & pygame.KMOD_ALT
-                
+
                 # Volume controls (require Alt)
                 if altPressed:
                     if event.key == pygame.K_PAGEUP:
@@ -195,19 +195,19 @@ class Game:
                 else:
                     if event.key in (pygame.K_ESCAPE, pygame.K_RETURN):
                         return self
-                        
+
                     if event.key in [pygame.K_DOWN, pygame.K_s] and currentIndex < len(navText) - 1:
                         currentIndex += 1
                         self.speech.speak(navText[currentIndex])
-                        
+
                     if event.key in [pygame.K_UP, pygame.K_w] and currentIndex > 0:
                         currentIndex -= 1
                         self.speech.speak(navText[currentIndex])
-                        
+
                     if event.key == pygame.K_SPACE:
                         # Join with newlines to preserve spacing in speech
                         self.speech.speak('\n'.join(originalText[1:-1]))
-                        
+
                     if event.key == pygame.K_c:
                         try:
                             import pyperclip
@@ -215,7 +215,7 @@ class Game:
                             self.speech.speak("Copied " + navText[currentIndex] + " to the clipboard.")
                         except:
                             self.speech.speak("Failed to copy the text to the clipboard.")
-                            
+
                     if event.key == pygame.K_t:
                         try:
                             import pyperclip
@@ -224,10 +224,10 @@ class Game:
                             self.speech.speak("Copied entire message to the clipboard.")
                         except:
                             self.speech.speak("Failed to copy the text to the clipboard.")
-                        
+
             pygame.event.clear()
             time.sleep(0.001)
-    
+
     def exit(self):
         """Clean up and exit the game."""
         if self._speech and self.speech.providerName == "speechd":
@@ -241,12 +241,12 @@ class Game:
 
 def check_for_updates(currentVersion, gameName, url):
     """Check for game updates.
-    
+
     Args:
         currentVersion (str): Current version string (e.g. "1.0.0")
         gameName (str): Name of the game
         url (str): URL to check for updates
-        
+
     Returns:
         dict: Update information or None if no update available
     """
@@ -266,10 +266,10 @@ def check_for_updates(currentVersion, gameName, url):
 
 def get_version_tuple(versionStr):
     """Convert version string to comparable tuple.
-    
+
     Args:
         versionStr (str): Version string (e.g. "1.0.0")
-        
+
     Returns:
         tuple: Version as tuple of integers
     """
@@ -277,11 +277,11 @@ def get_version_tuple(versionStr):
 
 def check_compatibility(requiredVersion, currentVersion):
     """Check if current version meets minimum required version.
-    
+
     Args:
         requiredVersion (str): Minimum required version string
         currentVersion (str): Current version string
-        
+
     Returns:
         bool: True if compatible, False otherwise
     """
@@ -291,10 +291,10 @@ def check_compatibility(requiredVersion, currentVersion):
 
 def sanitize_filename(filename):
     """Sanitize a filename to be safe for all operating systems.
-    
+
     Args:
         filename (str): Original filename
-        
+
     Returns:
         str: Sanitized filename
     """
@@ -309,12 +309,12 @@ def sanitize_filename(filename):
 
 def lerp(start, end, factor):
     """Linear interpolation between two values.
-    
+
     Args:
         start (float): Start value
         end (float): End value
         factor (float): Interpolation factor (0.0-1.0)
-        
+
     Returns:
         float: Interpolated value
     """
@@ -322,12 +322,12 @@ def lerp(start, end, factor):
 
 def smooth_step(edge0, edge1, x):
     """Hermite interpolation between two values.
-    
+
     Args:
         edge0 (float): Start edge
         edge1 (float): End edge
         x (float): Value to interpolate
-        
+
     Returns:
         float: Interpolated value with smooth step
     """
@@ -338,13 +338,13 @@ def smooth_step(edge0, edge1, x):
 
 def distance_2d(x1, y1, x2, y2):
     """Calculate Euclidean distance between two 2D points.
-    
+
     Args:
         x1 (float): X coordinate of first point
         y1 (float): Y coordinate of first point
         x2 (float): X coordinate of second point
         y2 (float): Y coordinate of second point
-        
+
     Returns:
         float: Distance between points
     """
@@ -352,17 +352,17 @@ def distance_2d(x1, y1, x2, y2):
 
 def generate_tone(frequency, duration=0.1, sampleRate=44100, volume=0.2):
     """Generate a tone at the specified frequency.
-    
+
     Args:
         frequency (float): Frequency in Hz
         duration (float): Duration in seconds (default: 0.1)
         sampleRate (int): Sample rate in Hz (default: 44100)
         volume (float): Volume from 0.0 to 1.0 (default: 0.2)
-        
+
     Returns:
         pygame.mixer.Sound: Sound object with the generated tone
     """
-    
+
     t = np.linspace(0, duration, int(sampleRate * duration), False)
     tone = np.sin(2 * np.pi * frequency * t)
     stereoTone = np.vstack((tone, tone)).T  # Create a 2D array for stereo
@@ -372,17 +372,17 @@ def generate_tone(frequency, duration=0.1, sampleRate=44100, volume=0.2):
 
 def x_powerbar():
     """Sound based horizontal power bar
-    
+
     Returns:
         int: Selected position between -50 and 50
     """
-    
+
     clock = pygame.time.Clock()
     screen = pygame.display.get_surface()
     position = -50  # Start from the leftmost position
     direction = 1   # Move right initially
     barHeight = 20
-    
+
     while True:
         frequency = 440  # A4 note
         leftVolume = (50 - position) / 100
@@ -390,7 +390,7 @@ def x_powerbar():
         tone = generate_tone(frequency)
         channel = tone.play()
         channel.set_volume(leftVolume, rightVolume)
-        
+
         # Visual representation
         screen.fill((0, 0, 0))
         barWidth = screen.get_width() - 40  # Leave 20px margin on each side
@@ -398,13 +398,13 @@ def x_powerbar():
         markerPos = int(20 + (position + 50) / 100 * barWidth)
         pygame.draw.rect(screen, (255, 0, 0), (markerPos - 5, screen.get_height() // 2 - barHeight, 10, barHeight * 2))
         pygame.display.flip()
-        
+
         for event in pygame.event.get():
             check_for_exit()
             if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                 channel.stop()
                 return position  # This will return a value between -50 and 50
-                
+
         position += direction
         if position > 50:
             position = 50
@@ -412,27 +412,27 @@ def x_powerbar():
         elif position < -50:
             position = -50
             direction = 1
-            
+
         clock.tick(40) # Speed of bar
 
 def y_powerbar():
     """Sound based vertical power bar
-    
+
     Returns:
         int: Selected power level between 0 and 100
     """
-    
+
     clock = pygame.time.Clock()
     screen = pygame.display.get_surface()
     power = 0
     direction = 1  # 1 for increasing, -1 for decreasing
     barWidth = 20
-    
+
     while True:
         frequency = 220 + (power * 5)  # Adjust these values to change the pitch range
         tone = generate_tone(frequency)
         channel = tone.play()
-        
+
         # Visual representation
         screen.fill((0, 0, 0))
         barHeight = screen.get_height() - 40  # Leave 20px margin on top and bottom
@@ -440,15 +440,15 @@ def y_powerbar():
         markerPos = int(20 + (100 - power) / 100 * barHeight)
         pygame.draw.rect(screen, (255, 0, 0), (screen.get_width() // 2 - barWidth, markerPos - 5, barWidth * 2, 10))
         pygame.display.flip()
-        
+
         for event in pygame.event.get():
             check_for_exit()
             if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                 channel.stop()
                 return power
-                
+
         power += direction
         if power >= 100 or power <= 0:
             direction *= -1  # Reverse direction at limits
-            
+
         clock.tick(40)