Audio Demo

audio_demo.py
"""
Enhanced Audio Demo - Testing OpenAL Audio with Dynamic Control and Smooth Transitions

LOCATION: examples/audio_demo.py

Updated to use the new OpenAL audio system with:
- OpenAL backend with pygame fallback
- Smooth volume and pitch transitions
- Stereo panning support
- Optimized resource management
- Tab-based interface
- Audio visualizer integration
"""

import sys
import os
import time
import math
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from lunaengine.core import Scene, LunaEngine
from lunaengine.core.audio import AudioSystem, AudioChannel, AudioEvent
from lunaengine.ui.elements import *
from lunaengine.ui.tween import Tween, EasingType, AnimationHandler
import pygame
import numpy as np

class AudioDemoScene(Scene):
    """
    Enhanced audio system demonstration with OpenAL support and tab interface.
    """
    
    def on_enter(self, previous_scene=None):
        self.engine.set_global_theme(ThemeType.GRUVBOX_LIGHT)
        return super().on_enter(previous_scene)
    
    def __init__(self, engine: LunaEngine):
        super().__init__(engine)
        
        # Channel management
        self.active_channels = {}
        self.channel_counters = {
            'explosion': 0,
            'music': 0
        }
        
        # Audio state
        self.music_volume = 0.8
        self.sfx_volume = 0.9
        self.playback_speed = 1.0
        self.fade_duration = 2.0
        
        # Sound names (not paths)
        self.explosion_sound_name = "explosion"
        self.music_sound_name = "music"
        
        # Transition controls
        self.volume_transition_duration = 1.0
        self.speed_transition_duration = 1.5
        
        # Visual feedback
        self.visual_effects = []
        self.audio_events_log = []
        
        # Audio information
        self.audio_info = {
            'music_duration': 0.0,
            'sound_duration': 0.0,
            'current_position': 0.0
        }
        
        # Animation handler
        self.animation_handler = AnimationHandler(engine)
        self.animations = {}
        
        # Visualizer state
        self.visualizer_style = 'bars'
        self.visualizer_sensitivity = 1.5
        self.visualizer_smoothing = 0.7
        self.visualizer_source = 'music'  # 'music' or 'sfx'
        
        # Setup audio system
        self.setup_audio_system()
        
        # Setup UI with tabs
        self.setup_ui()
        
        # Load audio files
        self.load_audio_files()
        
        # Register key events
        @engine.on_event(pygame.KEYDOWN)
        def on_key_press(event):
            self.handle_key_press(event.key)
    
    def setup_audio_system(self):
        """Setup audio system with OpenAL support."""
        try:
            # Initialize audio system
            self.audio_system = AudioSystem(num_channels=16)
            print(f"Audio system initialized: OpenAL={self.audio_system.use_openal}")
        except Exception as e:
            print(f"Failed to initialize audio system: {e}")
            # Fallback: create basic system
            self.audio_system = None
    
    def load_audio_files(self):
        """Load audio files for testing."""
        if not self.audio_system:
            print("Audio system not available")
            return
        
        try:
            # Load explosion sound effect
            explosion_path = "./examples/explosion.wav"
            if os.path.exists(explosion_path):
                self.audio_system.load_sound(self.explosion_sound_name, explosion_path)
                sound_info = self.audio_system.get_sound_info(self.explosion_sound_name)
                if sound_info:
                    self.audio_info['sound_duration'] = sound_info.duration
                    print(f"Loaded explosion.wav successfully (duration: {self.audio_info['sound_duration']:.2f}s)")
            else:
                print(f"Warning: explosion.wav not found at {explosion_path}")
                self.create_placeholder_sounds()
            
            # Load music file
            music_path = "./examples/music.mp3"
            if os.path.exists(music_path):
                self.audio_system.load_sound(self.music_sound_name, music_path)
                sound_info = self.audio_system.get_sound_info(self.music_sound_name)
                if sound_info:
                    self.audio_info['music_duration'] = sound_info.duration
                    print(f"Loaded music.mp3 successfully (duration: {self.audio_info['music_duration']:.2f}s)")
            else:
                print(f"Warning: music.mp3 not found at {music_path}")
                # Create placeholder music
                self.create_placeholder_music()
                self.audio_info['music_duration'] = 120.0
                
        except Exception as e:
            print(f"Error loading audio files: {e}")
            self.create_placeholder_sounds()

    def create_placeholder_music(self):
        """Create placeholder music if file is not available."""
        try:
            # Try to use explosion sound as placeholder for music too
            if os.path.exists("./examples/explosion.wav"):
                self.audio_system.load_sound(self.music_sound_name, "./examples/explosion.wav")
                sound_info = self.audio_system.get_sound_info(self.music_sound_name)
                if sound_info:
                    self.audio_info['music_duration'] = sound_info.duration
                print("Using explosion sound as placeholder music")
            else:
                # Create a simple tone as placeholder
                self.create_sine_wave_tone(self.music_sound_name, duration=2.0, frequency=440.0)
                sound_info = self.audio_system.get_sound_info(self.music_sound_name)
                if sound_info:
                    self.audio_info['music_duration'] = sound_info.duration
                print("Created sine wave tone as placeholder music")
        except Exception as e:
            print(f"Error creating placeholder music: {e}")

    def create_sine_wave_tone(self, name: str, duration: float = 1.0, frequency: float = 440.0):
        """Create a simple sine wave tone as placeholder audio."""
        try:
            import numpy as np
            
            sample_rate = 44100
            t = np.linspace(0, duration, int(sample_rate * duration), False)
            
            # Generate sine wave
            tone = np.sin(2 * np.pi * frequency * t)
            
            # Convert to 16-bit PCM
            tone = (tone * 32767).astype(np.int16)
            
            # Create stereo audio (2 channels)
            stereo_tone = np.column_stack((tone, tone))
            
            # Create a temporary WAV file
            import tempfile
            import wave
            
            with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
                with wave.open(tmp_file.name, 'wb') as wav_file:
                    wav_file.setnchannels(2)
                    wav_file.setsampwidth(2)  # 16-bit
                    wav_file.setframerate(sample_rate)
                    wav_file.writeframes(stereo_tone.tobytes())
                
                # Load the temporary file
                self.audio_system.load_sound(name, tmp_file.name)
                
                # Clean up temporary file
                import os
                os.unlink(tmp_file.name)
                
        except Exception as e:
            print(f"Failed to create sine wave tone: {e}")
    
    def create_placeholder_sounds(self):
        """Create placeholder sounds if files are not available."""
        try:
            # Create a simple sound programmatically
            sample_rate = 44100
            duration = 1.0  # seconds
            frames = int(duration * sample_rate)
            
            # Generate a simple square wave
            arr = np.zeros((frames, 2), dtype=np.int16)
            for i in range(frames):
                # 440 Hz square wave
                val = 16000 if (i // 50) % 2 == 0 else -16000
                arr[i] = [val, val]
            
            sound = pygame.sndarray.make_sound(arr)
            self.explosion_sound = sound
            self.audio_info['sound_duration'] = duration
            
            print("Created placeholder explosion sound")
            
        except Exception as e:
            print(f"Error creating placeholder sounds: {e}")
    
    def setup_ui(self):
        """Setup comprehensive audio controls UI with tabs."""
        # Title and basic info
        title = TextLabel(512, 20, "OpenAL Audio Demo", 48, root_point=(0.5, 0))
        self.ui_elements.append(title)
        
        subtitle_text = "OpenAL Backend" if self.audio_system and self.audio_system.use_openal else "Pygame Fallback"
        subtitle = TextLabel(512, 65, f"{subtitle_text} - Smooth Transitions", 20, root_point=(0.5, 0))
        self.ui_elements.append(subtitle)
        
        # Create main tabs container
        self.main_tabs = Tabination(25, 100, 980, 600, 20)
        
        # Add tabs
        self.main_tabs.add_tab('Sound Effects')
        self.setup_sound_effects_tab()
        
        self.main_tabs.add_tab('Music')
        self.setup_music_tab()
        
        self.main_tabs.add_tab('Transitions')
        self.setup_transitions_tab()
        
        self.main_tabs.add_tab('Visualizer')
        self.setup_visualizer_tab()
        
        self.main_tabs.add_tab('Monitor')
        self.setup_monitor_tab()
        
        # Add tabs to UI
        self.ui_elements.append(self.main_tabs)
        
        # Add corner radius to tabs
        self.main_tabs.set_corner_radius((10, 10, 10, 10))
        
        # FPS display
        self.fps_display = TextLabel(self.engine.width - 10, 20, "FPS: --", 16, (100, 255, 100), root_point=(1, 0))
        self.ui_elements.append(self.fps_display)
    
    def setup_sound_effects_tab(self):
        """Setup sound effects controls tab."""
        # Tab title
        self.main_tabs.add_to_tab('Sound Effects', TextLabel(10, 10, "Multi-Channel Sound Effects", 28, (255, 255, 0)))
        
        # Sound duration info
        duration_label = TextLabel(10, 45, f"Sound Duration: {self.audio_info['sound_duration']:.2f}s", 16, 
                                 (150, 170, 190))
        self.main_tabs.add_to_tab('Sound Effects', duration_label)
        
        # Channel 1 controls
        self.main_tabs.add_to_tab('Sound Effects', TextLabel(10, 75, "Channel 1:", 20))
        
        ch1_play = Button(110, 70, 120, 30, "Play", 18)
        ch1_play.set_on_click(lambda: self.play_channel_sound(1, 1.0, 1.0, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', ch1_play)
        
        ch1_slow = Button(240, 70, 120, 30, "Slow (0.5x)", 18)
        ch1_slow.set_on_click(lambda: self.play_channel_sound(1, 1.0, 0.5, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', ch1_slow)
        
        ch1_fast = Button(370, 70, 120, 30, "Fast (2.0x)", 18)
        ch1_fast.set_on_click(lambda: self.play_channel_sound(1, 1.0, 2.0, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', ch1_fast)
        
        # Channel 2 controls
        self.main_tabs.add_to_tab('Sound Effects', TextLabel(10, 115, "Channel 2:", 20))
        
        ch2_play = Button(110, 110, 120, 30, "Play", 18)
        ch2_play.set_on_click(lambda: self.play_channel_sound(2, 0.7, 1.0, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', ch2_play)
        
        ch2_quiet = Button(240, 110, 120, 30, "Quiet (0.3x)", 18)
        ch2_quiet.set_on_click(lambda: self.play_channel_sound(2, 0.3, 1.0, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', ch2_quiet)
        
        ch2_loop = Button(370, 110, 120, 30, "Loop", 18)
        ch2_loop.set_on_click(lambda: self.play_channel_sound(2, 0.7, 1.0, 0.0, True))
        self.main_tabs.add_to_tab('Sound Effects', ch2_loop)
        
        # Pan controls
        pan_label = TextLabel(10, 155, "Pan Controls:", 20)
        self.main_tabs.add_to_tab('Sound Effects', pan_label)
        
        pan_left = Button(130, 150, 80, 30, "Left", 16)
        pan_left.set_on_click(lambda: self.play_channel_sound(3, 0.8, 1.0, -1.0))
        self.main_tabs.add_to_tab('Sound Effects', pan_left)
        
        pan_center = Button(230, 150, 80, 30, "Center", 16)
        pan_center.set_on_click(lambda: self.play_channel_sound(3, 0.8, 1.0, 0.0))
        self.main_tabs.add_to_tab('Sound Effects', pan_center)
        
        pan_right = Button(330, 150, 80, 30, "Right", 16)
        pan_right.set_on_click(lambda: self.play_channel_sound(3, 0.8, 1.0, 1.0))
        self.main_tabs.add_to_tab('Sound Effects', pan_right)
        
        # Dynamic controls
        self.main_tabs.add_to_tab('Sound Effects', TextLabel(10, 195, "Dynamic Control & Transitions", 20))
        
        # Dynamic control for channel 1
        ch1_dynamic = Button(10, 220, 140, 30, "Speed Change", 16)
        ch1_dynamic.set_on_click(lambda: self.dynamic_speed_change(1))
        self.main_tabs.add_to_tab('Sound Effects', ch1_dynamic)
        
        # Smooth volume change for channel 2
        ch2_smooth_vol = Button(160, 220, 140, 30, "Volume Fade", 16)
        ch2_smooth_vol.set_on_click(lambda: self.smooth_volume_change(2))
        self.main_tabs.add_to_tab('Sound Effects', ch2_smooth_vol)
        
        # Stop all channels
        stop_all = Button(310, 220, 140, 30, "Stop All SFX", 16)
        stop_all.set_on_click(self.stop_all_channels)
        self.main_tabs.add_to_tab('Sound Effects', stop_all)
        
        # Channel status display
        self.main_tabs.add_to_tab('Sound Effects', TextLabel(10, 270, "Channel Status:", 20, (255, 200, 100)))
        
        self.channel_status = TextLabel(10, 300, "No active channels", 16, (150, 170, 190))
        self.main_tabs.add_to_tab('Sound Effects', self.channel_status)
    
    def setup_music_tab(self):
        """Setup music controls tab."""
        # Tab title
        self.main_tabs.add_to_tab('Music', TextLabel(10, 10, "Music Controls", 28, (255, 255, 0)))
        
        # Music duration info
        music_duration_label = TextLabel(10, 45, f"Music Duration: {self.audio_info['music_duration']:.2f}s", 16, 
                                       (150, 170, 190))
        self.main_tabs.add_to_tab('Music', music_duration_label)
        
        # Current position display
        self.music_position_label = TextLabel(250, 45, "Position: 0.00s", 16, (150, 170, 190))
        self.main_tabs.add_to_tab('Music', self.music_position_label)
        
        # Music speed controls
        self.main_tabs.add_to_tab('Music', TextLabel(10, 75, "Music Speed:", 20))
        
        self.music_speed_slider = Slider(130, 70, 200, 20, 0.5, 2.0, 1.0)
        self.music_speed_slider.on_value_changed = self.on_music_speed_changed
        self.main_tabs.add_to_tab('Music', self.music_speed_slider)
        
        self.music_speed_value = TextLabel(340, 75, "1.0x", 20)
        self.main_tabs.add_to_tab('Music', self.music_speed_value)
        
        # Music volume controls
        self.main_tabs.add_to_tab('Music', TextLabel(10, 105, "Music Volume:", 20))
        
        self.music_volume_slider = Slider(130, 100, 200, 20, 0.0, 1.0, 0.8)
        self.music_volume_slider.on_value_changed = self.on_music_volume_changed
        self.main_tabs.add_to_tab('Music', self.music_volume_slider)
        
        self.music_volume_value = TextLabel(340, 105, "0.8", 20)
        self.main_tabs.add_to_tab('Music', self.music_volume_value)
        
        # Music control buttons
        play_music = Button(10, 140, 100, 30, "Play", 18)
        play_music.set_on_click(self.play_music)
        self.main_tabs.add_to_tab('Music', play_music)
        
        pause_music = Button(120, 140, 100, 30, "Pause", 18)
        pause_music.set_on_click(self.pause_music)
        self.main_tabs.add_to_tab('Music', pause_music)
        
        resume_music = Button(230, 140, 100, 30, "Resume", 18)
        resume_music.set_on_click(self.resume_music)
        self.main_tabs.add_to_tab('Music', resume_music)
        
        stop_music = Button(340, 140, 100, 30, "Stop", 18)
        stop_music.set_on_click(self.stop_music)
        self.main_tabs.add_to_tab('Music', stop_music)
        
        # Apply speed with transition
        apply_speed_smooth = Button(450, 140, 150, 30, "Smooth Speed", 16)
        apply_speed_smooth.set_on_click(self.apply_music_speed_smooth)
        self.main_tabs.add_to_tab('Music', apply_speed_smooth)
        
        # Music status
        self.main_tabs.add_to_tab('Music', TextLabel(10, 190, "Music Status:", 20, (255, 200, 100)))
        
        self.music_status_label = TextLabel(10, 220, "Stopped", 18, (255, 100, 100))
        self.main_tabs.add_to_tab('Music', self.music_status_label)
    
    def setup_transitions_tab(self):
        """Setup transition controls tab."""
        # Tab title
        self.main_tabs.add_to_tab('Transitions', TextLabel(10, 10, "Smooth Transition Controls", 28, (255, 255, 0)))
        
        # Volume transition controls
        self.main_tabs.add_to_tab('Transitions', TextLabel(10, 50, "Volume Transition:", 18))
        
        self.vol_transition_slider = Slider(170, 45, 150, 20, 0.1, 3.0, self.volume_transition_duration)
        self.vol_transition_slider.on_value_changed = self.on_volume_transition_changed
        self.main_tabs.add_to_tab('Transitions', self.vol_transition_slider)
        
        self.vol_transition_value = TextLabel(330, 50, f"{self.volume_transition_duration:.1f}s", 18)
        self.main_tabs.add_to_tab('Transitions', self.vol_transition_value)
        
        # Speed transition controls
        self.main_tabs.add_to_tab('Transitions', TextLabel(10, 85, "Speed Transition:", 18))
        
        self.speed_transition_slider = Slider(170, 80, 150, 20, 0.1, 3.0, self.speed_transition_duration)
        self.speed_transition_slider.on_value_changed = self.on_speed_transition_changed
        self.main_tabs.add_to_tab('Transitions', self.speed_transition_slider)
        
        self.speed_transition_value = TextLabel(330, 85, f"{self.speed_transition_duration:.1f}s", 18)
        self.main_tabs.add_to_tab('Transitions', self.speed_transition_value)
        
        # Transition demo buttons
        fade_in_music = Button(10, 120, 140, 30, "Fade In Music", 16)
        fade_in_music.set_on_click(self.fade_in_music_demo)
        self.main_tabs.add_to_tab('Transitions', fade_in_music)
        
        fade_out_music = Button(160, 120, 140, 30, "Fade Out Music", 16)
        fade_out_music.set_on_click(self.fade_out_music_demo)
        self.main_tabs.add_to_tab('Transitions', fade_out_music)
        
        smooth_volume_test = Button(310, 120, 160, 30, "Volume Test", 16)
        smooth_volume_test.set_on_click(self.smooth_volume_test)
        self.main_tabs.add_to_tab('Transitions', smooth_volume_test)
        
        # Advanced transitions
        self.main_tabs.add_to_tab('Transitions', TextLabel(10, 170, "Advanced Transitions:", 20, (255, 200, 100)))
        
        # Crossfade example
        crossfade_btn = Button(10, 200, 150, 30, "Crossfade Demo", 16)
        crossfade_btn.set_on_click(self.crossfade_demo)
        self.main_tabs.add_to_tab('Transitions', crossfade_btn)
        
        # Speed ramp example
        speed_ramp_btn = Button(170, 200, 150, 30, "Speed Ramp Demo", 16)
        speed_ramp_btn.set_on_click(self.speed_ramp_demo)
        self.main_tabs.add_to_tab('Transitions', speed_ramp_btn)
        
        # Transition log
        self.main_tabs.add_to_tab('Transitions', TextLabel(10, 250, "Transition Log:", 20, (200, 200, 255)))
        
        self.transition_log = TextLabel(10, 280, "No transitions yet", 14, (150, 170, 190))
        self.main_tabs.add_to_tab('Transitions', self.transition_log)
    
    def setup_visualizer_tab(self):
        """Setup audio visualizer tab."""
        # Tab title
        self.main_tabs.add_to_tab('Visualizer', TextLabel(10, 10, "Audio Visualizer", 28, (255, 255, 0)))
        
        # Create visualizer container frame
        visualizer_frame = UiFrame(10, 50, 450, 200)
        visualizer_frame.set_background_color((30, 30, 40))
        visualizer_frame.set_border((60, 60, 80), 2)
        self.main_tabs.add_to_tab('Visualizer', visualizer_frame)
        
        # Create the audio visualizer
        self.audio_visualizer = AudioVisualizer(
            x=15, 
            y=55, 
            width=440, 
            height=190,
            style=self.visualizer_style,
            source=None,  # Will be set dynamically
            color_gradient=[
                (100, 0, 200),    # Purple
                (0, 150, 255),    # Blue
                (0, 255, 200),    # Cyan
                (100, 255, 100)   # Green
            ]
        )
        
        # Set initial source based on what's playing
        self.update_visualizer_source()
        self.main_tabs.add_to_tab('Visualizer', self.audio_visualizer)
        
        # Controls section
        controls_frame = UiFrame(470, 50, 280, 200)
        controls_frame.set_background_color((40, 40, 50, 200))
        controls_frame.set_border((80, 80, 100), 1)
        self.main_tabs.add_to_tab('Visualizer', controls_frame)
        
        # Visualizer style selection
        self.main_tabs.add_to_tab('Visualizer', TextLabel(475, 55, "Visualization Style:", 16, (200, 200, 255)))
        
        self.style_dropdown = Dropdown(475, 80, 150, 30, 
                                      ['Bars', 'Waveform', 'Circle', 'Particles', 'Spectrum'])
        self.style_dropdown.set_on_selection_changed(lambda i, v: self.change_visualizer_style(v.lower()))
        self.style_dropdown.set_simple_tooltip("Change visualization style")
        self.main_tabs.add_to_tab('Visualizer', self.style_dropdown)
        
        # Audio source selection
        self.main_tabs.add_to_tab('Visualizer', TextLabel(475, 120, "Audio Source:", 16, (200, 200, 255)))
        
        self.source_dropdown = Dropdown(475, 145, 150, 30, 
                                       ['Music', 'Sound Effects', 'Mixed'])
        self.source_dropdown.set_on_selection_changed(lambda i, v: self.change_visualizer_source(v))
        self.source_dropdown.set_simple_tooltip("Select which audio to visualize")
        self.main_tabs.add_to_tab('Visualizer', self.source_dropdown)
        
        # Sensitivity control
        self.main_tabs.add_to_tab('Visualizer', TextLabel(475, 185, "Sensitivity:", 16, (200, 200, 255)))
        
        self.sensitivity_slider = Slider(560, 185, 120, 20, 0.5, 3.0, self.visualizer_sensitivity)
        self.sensitivity_slider.on_value_changed = lambda v: self.change_visualizer_sensitivity(v)
        self.main_tabs.add_to_tab('Visualizer', self.sensitivity_slider)
        
        self.sensitivity_value = TextLabel(690, 190, f"{self.visualizer_sensitivity:.1f}", 14)
        self.main_tabs.add_to_tab('Visualizer', self.sensitivity_value)
        
        # Smoothing control
        self.main_tabs.add_to_tab('Visualizer', TextLabel(475, 215, "Smoothing:", 16, (200, 200, 255)))
        
        self.smoothing_slider = Slider(560, 215, 120, 20, 0.1, 0.9, self.visualizer_smoothing)
        self.smoothing_slider.on_value_changed = lambda v: self.change_visualizer_smoothing(v)
        self.main_tabs.add_to_tab('Visualizer', self.smoothing_slider)
        
        self.smoothing_value = TextLabel(690, 220, f"{self.visualizer_smoothing:.1f}", 14)
        self.main_tabs.add_to_tab('Visualizer', self.smoothing_value)
        
        # Preset buttons
        preset_label = TextLabel(10, 260, "Preset Themes:", 18, (255, 200, 100))
        self.main_tabs.add_to_tab('Visualizer', preset_label)
        
        # Color gradient presets
        fire_btn = Button(10, 290, 100, 30, "Fire", 14)
        fire_btn.set_on_click(lambda: self.apply_color_preset([
            (50, 10, 10),    # Dark red
            (200, 50, 0),    # Orange-red
            (255, 150, 0),   # Orange
            (255, 255, 100)  # Yellow
        ]))
        fire_btn.set_simple_tooltip("Fire color theme")
        self.main_tabs.add_to_tab('Visualizer', fire_btn)
        
        ocean_btn = Button(120, 290, 100, 30, "Ocean", 14)
        ocean_btn.set_on_click(lambda: self.apply_color_preset([
            (0, 20, 40),     # Deep blue
            (0, 80, 120),    # Medium blue
            (0, 180, 200),   # Cyan-blue
            (100, 220, 255)  # Light cyan
        ]))
        ocean_btn.set_simple_tooltip("Ocean color theme")
        self.main_tabs.add_to_tab('Visualizer', ocean_btn)
        
        neon_btn = Button(230, 290, 100, 30, "Neon", 14)
        neon_btn.set_on_click(lambda: self.apply_color_preset([
            (255, 0, 200),   # Pink
            (200, 0, 255),   # Purple
            (0, 200, 255),   # Cyan
            (0, 255, 150)    # Green
        ]))
        neon_btn.set_simple_tooltip("Neon color theme")
        self.main_tabs.add_to_tab('Visualizer', neon_btn)
        
        # Visualizer info
        info_text = "The visualizer responds to audio in real-time.\nTry different styles and sources!"
        info_label = TextLabel(10, 330, info_text, 14, (150, 200, 255))
        self.main_tabs.add_to_tab('Visualizer', info_label)
    
    def setup_monitor_tab(self):
        """Setup audio monitoring tab."""
        # Tab title
        self.main_tabs.add_to_tab('Monitor', TextLabel(10, 10, "Audio System Monitor", 28, (255, 255, 0)))
        
        # System info
        backend_text = f"Backend: {'OpenAL' if self.audio_system and self.audio_system.use_openal else 'Pygame'}"
        self.main_tabs.add_to_tab('Monitor', TextLabel(10, 50, backend_text, 16, (150, 170, 190)))
        
        # Active channels display
        self.active_channels_label = TextLabel(10, 80, "Active Channels: 0", 16, (200, 200, 200))
        self.main_tabs.add_to_tab('Monitor', self.active_channels_label)
        
        # Channel grid visualization
        self.main_tabs.add_to_tab('Monitor', TextLabel(10, 110, "Channel Grid (0-15):", 20, (255, 200, 100)))
        
        # Create channel grid
        self.channel_grid_elements = []
        for i in range(16):
            x = 10 + (i % 8) * 60
            y = 140 + (i // 8) * 60
            
            # Channel box background
            channel_bg = UiFrame(x, y, 50, 50)
            channel_bg.set_background_color((40, 40, 50))
            channel_bg.set_border((100, 100, 120), 1)
            self.channel_grid_elements.append(channel_bg)
            self.main_tabs.add_to_tab('Monitor', channel_bg)
            
            # Channel number
            channel_num = TextLabel(x + 20, y + 15, str(i), 20, (200, 200, 200))
            self.channel_grid_elements.append(channel_num)
            self.main_tabs.add_to_tab('Monitor', channel_num)
            
            # Status indicator (will be updated)
            status_indicator = UiFrame(x + 10, y + 35, 30, 10)
            status_indicator.set_background_color((60, 60, 80))
            self.channel_grid_elements.append(status_indicator)
            self.main_tabs.add_to_tab('Monitor', status_indicator)
        
        # Pan visualization
        self.main_tabs.add_to_tab('Monitor', TextLabel(10, 320, "Stereo Pan Visualization:", 20, (255, 200, 100)))
        
        # Pan visualization background
        pan_bg = UiFrame(10, 350, 300, 40)
        pan_bg.set_background_color((40, 40, 50))
        self.main_tabs.add_to_tab('Monitor', pan_bg)
        
        # Center line
        center_line = UiFrame(160, 350, 2, 40)
        center_line.set_background_color((100, 100, 120))
        self.main_tabs.add_to_tab('Monitor', center_line)
        
        # Labels
        left_label = TextLabel(20, 360, "L", 16, (200, 200, 200))
        self.main_tabs.add_to_tab('Monitor', left_label)
        
        right_label = TextLabel(290, 360, "R", 16, (200, 200, 200))
        self.main_tabs.add_to_tab('Monitor', right_label)
        
        # Audio events log
        self.main_tabs.add_to_tab('Monitor', TextLabel(350, 50, "Audio Events Log:", 20, (255, 200, 100)))
        
        self.event_log_labels = []
        for i in range(6):
            event_label = TextLabel(350, 80 + i * 25, "", 14, (200, 200, 200))
            self.event_log_labels.append(event_label)
            self.main_tabs.add_to_tab('Monitor', event_label)
        
        # Performance info
        self.main_tabs.add_to_tab('Monitor', TextLabel(350, 250, "Performance Info:", 20, (255, 200, 100)))
        
        self.performance_label = TextLabel(350, 280, "CPU Usage: --\nMemory: --", 14, (200, 200, 200))
        self.main_tabs.add_to_tab('Monitor', self.performance_label)
    
    def change_visualizer_style(self, style: str):
        """Change visualizer style."""
        self.visualizer_style = style
        if hasattr(self, 'audio_visualizer'):
            self.audio_visualizer.set_style(style)
        self.add_audio_event(f"Visualizer style: {style}")
    
    def change_visualizer_source(self, source: str):
        """Change visualizer audio source."""
        self.visualizer_source = source.lower()
        self.update_visualizer_source()
        self.add_audio_event(f"Visualizer source: {source}")
    
    def change_visualizer_sensitivity(self, sensitivity: float):
        """Change visualizer sensitivity."""
        self.visualizer_sensitivity = sensitivity
        self.sensitivity_value.set_text(f"{sensitivity:.1f}")
        if hasattr(self, 'audio_visualizer'):
            self.audio_visualizer.set_sensitivity(sensitivity)
    
    def change_visualizer_smoothing(self, smoothing: float):
        """Change visualizer smoothing."""
        self.visualizer_smoothing = smoothing
        self.smoothing_value.set_text(f"{smoothing:.1f}")
        if hasattr(self, 'audio_visualizer'):
            self.audio_visualizer.set_smoothing(smoothing)
    
    def apply_color_preset(self, gradient):
        """Apply a color gradient preset to the visualizer."""
        if hasattr(self, 'audio_visualizer'):
            self.audio_visualizer.set_color_gradient(gradient)
        self.add_audio_event(f"Applied color preset")
    
    def update_visualizer_source(self):
        """Update the visualizer's audio source based on current state."""
        if not hasattr(self, 'audio_visualizer'):
            return
        
        if self.visualizer_source == 'music':
            # Use music channel (channel 0)
            if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
                music_channel = self.audio_system.channels[0]
                if hasattr(music_channel, 'is_playing') and music_channel.is_playing():
                    self.audio_visualizer.set_source(music_channel)
                else:
                    self.audio_visualizer.set_source(None)
        elif self.visualizer_source == 'sound effects':
            # Use the first active sound effect channel
            if self.active_channels:
                first_channel = next(iter(self.active_channels.values()))
                self.audio_visualizer.set_source(first_channel)
            else:
                self.audio_visualizer.set_source(None)
        elif self.visualizer_source == 'mixed':
            # For mixed, we'd ideally combine audio from multiple sources
            # For simplicity, use music if available, otherwise SFX
            if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
                music_channel = self.audio_system.channels[0]
                if hasattr(music_channel, 'is_playing') and music_channel.is_playing():
                    self.audio_visualizer.set_source(music_channel)
                elif self.active_channels:
                    first_channel = next(iter(self.active_channels.values()))
                    self.audio_visualizer.set_source(first_channel)
                else:
                    self.audio_visualizer.set_source(None)
            else:
                self.audio_visualizer.set_source(None)
    
    def play_channel_sound(self, channel_num: int, volume: float, speed: float, pan: float, loop: bool = False):
        """Play sound on specific channel with custom settings."""
        if not self.audio_system:
            self.add_audio_event("Audio system not available")
            return
        
        channel_key = f"explosion_{channel_num}"
        
        # Stop existing sound on this channel if any
        if channel_key in self.active_channels:
            self.active_channels[channel_key].stop()
            del self.active_channels[channel_key]
        
        # Play new sound - note: we subtract 1 because channels are 0-indexed
        channel = self.audio_system.play(
            sound_name=self.explosion_sound_name,
            channel=channel_num - 1,  # Convert to 0-indexed
            volume=volume,
            pitch=speed,
            pan=pan,
            loop=loop
        )
        
        if channel:
            self.active_channels[channel_key] = channel
            self.add_audio_event(f"Channel {channel_num}: vol={volume}, speed={speed}x, pan={pan}")
            
            # Update visualizer if using SFX source
            if self.visualizer_source == 'sound effects' or self.visualizer_source == 'mixed':
                self.update_visualizer_source()
            
            # Update channel status
            self.update_channel_status()
        else:
            self.add_audio_event(f"Failed to play on channel {channel_num}")
    
    def play_music(self):
        """Play background music."""
        if not self.audio_system:
            self.add_audio_event("Audio system not available")
            return
        
        volume = self.music_volume_slider.value
        speed = self.music_speed_slider.value
        
        # Play music - uses channel 0 automatically
        channel = self.audio_system.play_music(
            sound_name=self.music_sound_name,
            volume=volume,
            pitch=speed,
            loop=True
        )
        
        if channel:
            self.add_audio_event(f"Music started (vol: {volume:.1f}, speed: {speed:.1f}x)")
            self.music_status_label.set_text("Playing")
            self.music_status_label.color = (100, 255, 100)
            
            # Update visualizer if using music source
            if self.visualizer_source == 'music' or self.visualizer_source == 'mixed':
                self.update_visualizer_source()
        else:
            self.add_audio_event("Failed to play music")
    
    def pause_music(self):
        """Pause music."""
        if self.audio_system:
            # Get music channel (channel 0)
            if self.audio_system.channels and len(self.audio_system.channels) > 0:
                music_channel = self.audio_system.channels[0]
                if music_channel.is_playing():
                    music_channel.pause()
                    self.add_audio_event("Music paused")
                    self.music_status_label.set_text("Paused")
                    self.music_status_label.color = (255, 255, 100)
    
    def resume_music(self):
        """Resume paused music."""
        if self.audio_system:
            # Get music channel (channel 0)
            if self.audio_system.channels and len(self.audio_system.channels) > 0:
                music_channel = self.audio_system.channels[0]
                if music_channel.is_paused():
                    music_channel.resume()
                    self.add_audio_event("Music resumed")
                    self.music_status_label.set_text("Playing")
                    self.music_status_label.color = (100, 255, 100)
    
    def stop_music(self):
        """Stop music."""
        if self.audio_system:
            # Get music channel (channel 0)
            if self.audio_system.channels and len(self.audio_system.channels) > 0:
                music_channel = self.audio_system.channels[0]
                music_channel.stop()
                self.add_audio_event("Music stopped")
                self.music_status_label.set_text("Stopped")
                self.music_status_label.color = (255, 100, 100)
                
                # Update visualizer
                self.update_visualizer_source()
    
    def dynamic_speed_change(self, channel_num: int):
        """Change speed dynamically with smooth transition."""
        channel_key = f"explosion_{channel_num}"
        if channel_key in self.active_channels:
            channel = self.active_channels[channel_key]
            
            # Cycle through different speeds
            current_pitch = 1.0
            if hasattr(channel, 'pitch'):
                current_pitch = channel.pitch
            
            if current_pitch >= 2.0:
                new_speed = 0.5
            elif current_pitch >= 1.0:
                new_speed = 2.0
            else:
                new_speed = 1.0
            
            # Apply smooth speed transition
            channel.set_pitch(new_speed, self.speed_transition_duration)
            self.add_audio_event(f"Speed transition: {current_pitch:.1f}x → {new_speed:.1f}x")
            self.add_transition_log(f"Ch{channel_num}: Speed {current_pitch:.1f}→{new_speed:.1f}x")
    
    def smooth_volume_change(self, channel_num: int):
        """Demonstrate smooth volume transition."""
        channel_key = f"explosion_{channel_num}"
        if channel_key in self.active_channels and self.active_channels[channel_key].is_playing():
            channel = self.active_channels[channel_key]
            current_volume = channel.volume if hasattr(channel, 'volume') else 1.0
            
            # Cycle through different volumes
            if current_volume >= 0.8:
                new_volume = 0.2
            elif current_volume >= 0.5:
                new_volume = 0.8
            else:
                new_volume = 0.5
            
            # Apply smooth volume transition
            channel.set_volume(new_volume, self.volume_transition_duration)
            self.add_audio_event(f"Ch{channel_num} volume: {current_volume:.1f} → {new_volume:.1f}")
            self.add_transition_log(f"Ch{channel_num}: Volume {current_volume:.1f}→{new_volume:.1f}")
        else:
            self.add_audio_event(f"Channel {channel_num} not active")
    
    def on_music_speed_changed(self, value):
        """Handle music speed slider change."""
        self.playback_speed = value
        self.music_speed_value.set_text(f"{value:.1f}x")
    
    def on_music_volume_changed(self, value):
        """Handle music volume slider change."""
        self.music_volume = value
        self.music_volume_value.set_text(f"{value:.1f}")
        
        # Apply volume change immediately if music is playing on channel 0
        if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing() or music_channel.is_paused():
                music_channel.set_volume(value)
    
    def apply_music_speed_smooth(self):
        """Apply speed change with smooth transition."""
        if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing():
                new_speed = self.music_speed_slider.value
                music_channel.set_pitch(new_speed, self.speed_transition_duration)
                self.add_audio_event(f"Music speed: {new_speed:.1f}x ({self.speed_transition_duration:.1f}s)")
                self.add_transition_log(f"Music: Speed → {new_speed:.1f}x")
            else:
                self.add_audio_event("No music playing")
    
    def on_volume_transition_changed(self, value):
        """Handle volume transition duration change."""
        self.volume_transition_duration = value
        self.vol_transition_value.set_text(f"{value:.1f}s")
    
    def on_speed_transition_changed(self, value):
        """Handle speed transition duration change."""
        self.speed_transition_duration = value
        self.speed_transition_value.set_text(f"{value:.1f}s")
    
    def fade_in_music_demo(self):
        """Demonstrate music fade in."""
        if not self.audio_system:
            self.add_audio_event("Audio system not available")
            return
        
        volume = self.music_volume_slider.value
        speed = self.music_speed_slider.value
        
        # Play music with fade in
        channel = self.audio_system.play_music(
            sound_name=self.music_sound_name,
            volume=0.0,  # Start silent
            pitch=speed,
            loop=True,
            fade_in=self.volume_transition_duration
        )
        
        if channel:
            # Set target volume after starting (with fade)
            channel.set_volume(volume, self.volume_transition_duration)
            self.add_audio_event(f"Music fade in ({self.volume_transition_duration:.1f}s)")
            self.add_transition_log(f"Music: Fade in {self.volume_transition_duration:.1f}s")
            self.music_status_label.set_text("Playing (Fade in)")
            self.music_status_label.color = (100, 255, 100)
            
            # Update visualizer
            self.update_visualizer_source()
        else:
            self.add_audio_event("Failed to fade in music")
    
    def fade_out_music_demo(self):
        """Demonstrate music fade out."""
        if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing() or music_channel.is_paused():
                # Fade out and stop
                music_channel.set_volume(0.0, self.volume_transition_duration)
                # Schedule stop after fade
                import threading
                threading.Timer(self.volume_transition_duration, music_channel.stop).start()
                self.add_audio_event(f"Music fade out ({self.volume_transition_duration:.1f}s)")
                self.add_transition_log(f"Music: Fade out {self.volume_transition_duration:.1f}s")
                self.music_status_label.set_text("Fading out")
                self.music_status_label.color = (255, 200, 100)
            else:
                self.add_audio_event("No music playing")
    
    def smooth_volume_test(self):
        """Test smooth volume transitions on music."""
        if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing():
                current_volume = self.music_volume
                new_volume = 0.3 if current_volume > 0.5 else 0.8
                
                music_channel.set_volume(new_volume, self.volume_transition_duration)
                self.music_volume = new_volume
                self.music_volume_slider.value = new_volume
                self.music_volume_value.set_text(f"{new_volume:.1f}")
                
                self.add_audio_event(f"Music volume: {current_volume:.1f} → {new_volume:.1f}")
                self.add_transition_log(f"Music: Volume {current_volume:.1f}→{new_volume:.1f}")
            else:
                self.add_audio_event("No music playing")
    
    def crossfade_demo(self):
        """Demonstrate crossfade between two sounds."""
        self.add_transition_log("Crossfade demo: Not implemented yet")
        self.add_audio_event("Crossfade demo requires additional audio files")
    
    def speed_ramp_demo(self):
        """Demonstrate speed ramping."""
        if self.audio_system and self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing():
                # Ramp speed up and down
                original_speed = music_channel.pitch
                music_channel.set_pitch(2.0, 1.0)
                # Schedule ramp back down
                import threading
                threading.Timer(1.5, lambda: music_channel.set_pitch(original_speed, 1.0)).start()
                self.add_transition_log(f"Speed ramp: {original_speed:.1f}→2.0→{original_speed:.1f}x")
            else:
                self.add_transition_log("No music playing for speed ramp")
    
    def stop_all_channels(self):
        """Stop all sound effect channels."""
        for channel_key in list(self.active_channels.keys()):
            if channel_key.startswith('explosion_'):
                self.active_channels[channel_key].stop()
                del self.active_channels[channel_key]
        
        if self.audio_system:
            self.audio_system.stop_all()
        self.add_audio_event("All channels stopped")
        self.update_channel_status()
        
        # Update visualizer
        self.update_visualizer_source()
    
    def add_audio_event(self, event_text):
        """Add event to log."""
        self.audio_events_log.append(event_text)
        if len(self.audio_events_log) > 6:
            self.audio_events_log.pop(0)
        
        # Update event log display
        for i, label in enumerate(self.event_log_labels):
            if i < len(self.audio_events_log):
                label.set_text(self.audio_events_log[i])
            else:
                label.set_text("")
        
        # Print to console
        print(f"Audio Event: {event_text}")
    
    def add_transition_log(self, event_text):
        """Add transition event to log."""
        current_text = self.transition_log.text
        if current_text == "No transitions yet":
            new_text = event_text
        else:
            new_text = current_text + "\n" + event_text
        
        # Keep only last 3 lines
        lines = new_text.split('\n')
        if len(lines) > 3:
            new_text = '\n'.join(lines[-3:])
        
        self.transition_log.set_text(new_text)
    
    def update_channel_status(self):
        """Update channel status display."""
        active_channels = len(self.active_channels)
        if active_channels == 0:
            self.channel_status.set_text("No active channels")
            self.channel_status.color = (150, 150, 150)
        else:
            self.channel_status.set_text(f"{active_channels} active channel(s)")
            self.channel_status.color = (100, 255, 100)
    
    def handle_key_press(self, key):
        """Enhanced keyboard input with new features."""
        if key == pygame.K_ESCAPE:
            self.engine.set_scene("MainMenu")
        elif key == pygame.K_1:
            self.play_channel_sound(1, 1.0, 1.0, 0.0)
        elif key == pygame.K_2:
            self.play_channel_sound(2, 0.7, 1.0, 0.0)
        elif key == pygame.K_3:
            self.dynamic_speed_change(1)
        elif key == pygame.K_4:
            self.smooth_volume_change(2)
        elif key == pygame.K_5:
            self.fade_in_music_demo()
        elif key == pygame.K_6:
            self.fade_out_music_demo()
        elif key == pygame.K_p:
            self.pause_music()
        elif key == pygame.K_r:
            self.resume_music()
        elif key == pygame.K_LEFT:
            self.play_channel_sound(3, 0.8, 1.0, -1.0)
        elif key == pygame.K_RIGHT:
            self.play_channel_sound(3, 0.8, 1.0, 1.0)
        elif key == pygame.K_UP:
            self.play_channel_sound(3, 0.8, 1.0, 0.0)
        elif key == pygame.K_TAB:
            # Cycle through tabs (if we have a reference to main_tabs)
            if hasattr(self, 'main_tabs'):
                current_index = self.main_tabs.current_tab_index
                next_index = (current_index + 1) % len(self.main_tabs.tabs)
                self.main_tabs.switch_tab(next_index)
        elif key == pygame.K_v:
            # Switch to visualizer tab
            if hasattr(self, 'main_tabs'):
                self.main_tabs.switch_tab(3)  # Visualizer tab is index 3
        elif key == pygame.K_c:
            # Cycle through visualizer styles
            styles = ['bars', 'waveform', 'circle', 'particles', 'spectrum']
            current_idx = styles.index(self.visualizer_style) if self.visualizer_style in styles else 0
            next_idx = (current_idx + 1) % len(styles)
            self.change_visualizer_style(styles[next_idx])
    
    def update(self, dt):
        """Update scene logic with enhanced audio information."""
        # Update visual effects
        for effect in self.visual_effects[:]:
            effect['timer'] += dt
            if effect['timer'] >= effect['max_time']:
                self.visual_effects.remove(effect)
        
        # Update audio system
        if self.audio_system:
            self.audio_system.update()
        
        # Update visualizer source if needed
        if hasattr(self, 'audio_visualizer'):
            # Update the visualizer's source periodically
            current_time = time.time()
            if not hasattr(self, '_last_source_update'):
                self._last_source_update = 0
            
            if current_time - self._last_source_update > 0.5:  # Update every 0.5 seconds
                self.update_visualizer_source()
                self._last_source_update = current_time
        
        # Update real-time audio information
        self.update_audio_info()
        
        # Update FPS display
        fps_stats = self.engine.get_fps_stats()
        self.fps_display.set_text(f"FPS: {fps_stats['current_fps']:.1f}")
        
        # Update performance info (simulated)
        import random
        cpu_usage = random.randint(5, 25)
        memory_usage = random.randint(100, 300)
        self.performance_label.set_text(f"CPU Usage: {cpu_usage}%\nMemory: {memory_usage}MB")
    
    def update_audio_info(self):
        """Update real-time audio information display."""
        if not self.audio_system:
            return
        
        # Update active channels count
        active_count = 0
        if self.audio_system.channels:
            active_count = sum(1 for channel in self.audio_system.channels 
                            if channel.is_playing() or channel.is_paused())
        
        self.active_channels_label.set_text(f"Active Channels: {active_count}")
        
        # Update channel grid
        for i, channel in enumerate(self.audio_system.channels[:16]):
            if i * 3 + 2 < len(self.channel_grid_elements):
                status_indicator = self.channel_grid_elements[i * 3 + 2]
                if channel.is_playing():
                    status_indicator.set_background_color((50, 200, 50))
                elif hasattr(channel, 'is_paused') and channel.is_paused():
                    status_indicator.set_background_color((200, 200, 50))
                else:
                    status_indicator.set_background_color((60, 60, 80))
        
        # Music state - get channel 0 for music
        if self.audio_system.channels and len(self.audio_system.channels) > 0:
            music_channel = self.audio_system.channels[0]
            if music_channel.is_playing():
                # Music position (if playing)
                position = music_channel.get_playback_position()
                # Get duration from sound info
                sound_info = self.audio_system.get_sound_info(self.music_sound_name)
                if sound_info:
                    duration = sound_info.duration
                    self.music_position_label.set_text(f"Position: {position:.2f}s / {duration:.2f}s")
                else:
                    self.music_position_label.set_text(f"Position: {position:.2f}s")
            else:
                self.music_position_label.set_text("Position: 0.00s")
        else:
            self.music_position_label.set_text("Position: 0.00s")
    
    def render(self, renderer):
        """Render the scene with enhanced visualizations."""
        # Draw background
        current_theme = ThemeManager.get_theme(ThemeManager.get_current_theme())
        renderer.draw_rect(0, 0, 1024, 720, current_theme.background)
        
        # Draw header background
        renderer.draw_rect(0, 0, 1024, 90, current_theme.background2)
        
        # Draw UI elements
        for element in self.ui_elements:
            element.render(renderer)

# Main function
def main():
    """Main entry point."""
    engine = LunaEngine("LunaEngine - OpenAL Audio Demo", 1024, 720, False)
    engine.fps = 60
    
    # Audio system is now initialized in the scene
    print("=== OpenAL Audio System Demo ===")
    print("KEY FEATURES:")
    print("- OpenAL backend with pygame fallback")
    print("- Smooth volume and pitch transitions")
    print("- Stereo panning (left/right)")
    print("- Real-time audio state monitoring")
    print("- Multi-channel sound effects")
    print("- Tab-based interface")
    print("- Audio visualizer with 5 styles")
    print("\nKEYBOARD CONTROLS:")
    print("1-2: Play sounds on channels 1-2")
    print("3: Dynamic speed change (Channel 1)")
    print("4: Smooth volume change (Channel 2)")
    print("5: Fade in music")
    print("6: Fade out music")
    print("P: Pause music")
    print("R: Resume music")
    print("← → ↑: Test panning (left, right, center)")
    print("TAB: Cycle through tabs")
    print("V: Jump to Visualizer tab")
    print("C: Cycle visualizer styles")
    print("ESC: Back to menu")
    
    # Add and start scene
    engine.add_scene("AudioDemo", AudioDemoScene)
    engine.set_scene("AudioDemo")
    
    engine.run()

if __name__ == "__main__":
    main()
About This Example

Enhanced Audio Demo - Testing OpenAL Audio with Dynamic Control and Smooth Transitions