"""
Audio Demo for LunaEngine 0.2.5+
Showcases the new AudioManager with named channels, curves, balance, and device selection.
"""
import sys
import os
import time
import math
import random
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from lunaengine.core import Scene, LunaEngine
from lunaengine.core.audio import AudioManager, AudioChannel, AudioCurve, AudioEvent
from lunaengine.ui import *
from lunaengine.ui.tween import Tween, EasingType, AnimationHandler
import pygame
import numpy as np
class AudioDemoScene(Scene):
def on_enter(self, previous_scene=None):
self.engine.set_global_theme(ThemeType.GRUVBOX)
def __init__(self, engine: LunaEngine):
super().__init__(engine)
# Sound names
self.sfx_name = "explosion"
self.music_name = "bg_music"
# UI state
self.sfx_volume = 0.8
self.music_volume = 0.7
self.sfx_pitch = 1.0
self.music_pitch = 1.0
self.sfx_balance = 0.0
self.music_balance = 0.0
self.curve_duration = 2.0
# Active channel references for status
self.active_sfx_channels = {} # key -> channel name
self.music_channel = None
# Event log
self.event_log = []
# Load audio files (or create placeholders)
self.load_audio()
# Setup UI tabs
self.setup_ui()
# Register audio events for logging
self.audio_manager.on_event(AudioEvent.PLAYBACK_STARTED, self.on_audio_event)
self.audio_manager.on_event(AudioEvent.PLAYBACK_COMPLETED, self.on_audio_event)
self.audio_manager.on_event(AudioEvent.CURVE_FINISHED, self.on_audio_event)
# Keyboard shortcuts
@engine.on_event(pygame.KEYDOWN)
def on_key(event):
self.handle_key(event.key)
# ---------- Audio Loading ----------
def load_audio(self):
"""Load sound files; if missing, create placeholder tones."""
try:
# Try loading real files
path = os.path.dirname(os.path.abspath(__file__))
sfx_path = f"{path}/../examples/explosion.wav"
music_path = f"{path}/../examples/music.mp3"
if os.path.exists(sfx_path):
self.audio_manager.load_sound(self.sfx_name, sfx_path)
print("Loaded explosion.wav")
else:
self.create_placeholder_sound(self.sfx_name, duration=1.0, freq=440)
if os.path.exists(music_path):
self.audio_manager.load_sound(self.music_name, music_path)
print("Loaded music.mp3")
else:
self.create_placeholder_sound(self.music_name, duration=5.0, freq=523)
except Exception as e:
print(f"Audio loading error: {e}")
self.create_placeholder_sound(self.sfx_name, duration=1.0, freq=440)
self.create_placeholder_sound(self.music_name, duration=5.0, freq=523)
def create_placeholder_sound(self, name: str, duration: float = 1.0, freq: float = 440.0):
"""Generate a simple sine wave tone and load it as a sound."""
try:
import tempfile
import wave
sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False)
tone = np.sin(2 * np.pi * freq * t)
tone = (tone * 32767).astype(np.int16)
stereo = np.column_stack((tone, tone))
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp:
with wave.open(tmp.name, 'wb') as wf:
wf.setnchannels(2)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(stereo.tobytes())
self.audio_manager.load_sound(name, tmp.name)
os.unlink(tmp.name)
print(f"Created placeholder tone '{name}' ({freq}Hz, {duration}s)")
except Exception as e:
print(f"Failed to create placeholder: {e}")
# ---------- UI Setup ----------
def setup_ui(self):
self.engine.set_global_theme(ThemeType.DEFAULT)
# Title
title = TextLabel(512, 30, "Audio Demo - LunaEngine 0.2.5", 36, pivot=(0.5, 0))
self.add_ui_element(title)
# Create main tabs container β adjust height to fit the window (720 - 90 for header = 630)
self.main_tabs = Tabination(25, 80, 980, 610, 20)
self.main_tabs.set_corner_radius((8, 8, 8, 8))
# --- ADD TABS FIRST ---
self.main_tabs.add_tab('Sound Effects')
self.main_tabs.add_tab('Music')
self.main_tabs.add_tab('Transitions & Curves')
self.main_tabs.add_tab('Visualizer')
self.main_tabs.add_tab('Monitor')
self.main_tabs.add_tab('Settings')
# Now fill each tab with content
self.setup_sfx_tab()
self.setup_music_tab()
self.setup_curves_tab()
self.setup_visualizer_tab()
self.setup_monitor_tab()
self.setup_settings_tab()
self.add_ui_element(self.main_tabs)
# FPS display
self.fps_label = TextLabel(self.engine.width - 10, 10, "FPS: --", 14, (100, 255, 100), pivot=(1, 0))
self.add_ui_element(self.fps_label)
# ---------- SFX Tab ----------
def setup_sfx_tab(self):
tab = 'Sound Effects'
# Create three named channels for SFX
self.audio_manager.create_channel('sfx1', volume=0.8, balance=0.0)
self.audio_manager.create_channel('sfx2', volume=0.8, balance=0.0)
self.audio_manager.create_channel('sfx3', volume=0.8, balance=0.0)
# Header
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Sound Effects", 24, (255, 255, 0)))
y = 50
# Channel 1 controls
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Channel 1 (sfx1):", 18))
b1 = Button(200, y, 100, 30, "Play")
b1.set_on_click(lambda: self.play_sfx('sfx1', volume=0.8, pitch=1.0, balance=0.0))
self.main_tabs.add_to_tab(tab, b1)
b1_slow = Button(310, y, 100, 30, "Slow (0.5x)")
b1_slow.set_on_click(lambda: self.play_sfx('sfx1', volume=0.8, pitch=0.5, balance=0.0))
self.main_tabs.add_to_tab(tab, b1_slow)
b1_fast = Button(420, y, 100, 30, "Fast (2.0x)")
b1_fast.set_on_click(lambda: self.play_sfx('sfx1', volume=0.8, pitch=2.0, balance=0.0))
self.main_tabs.add_to_tab(tab, b1_fast)
y += 50
# Channel 2 controls
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Channel 2 (sfx2):", 18))
b2 = Button(200, y, 100, 30, "Play")
b2.set_on_click(lambda: self.play_sfx('sfx2', volume=0.6, pitch=1.0, balance=0.0))
self.main_tabs.add_to_tab(tab, b2)
b2_loop = Button(310, y, 100, 30, "Loop")
b2_loop.set_on_click(lambda: self.play_sfx('sfx2', volume=0.6, pitch=1.0, balance=0.0, loop=True))
self.main_tabs.add_to_tab(tab, b2_loop)
y += 50
# Balance controls for channel 3
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Channel 3 (sfx3) - Balance:", 18))
b_left = Button(200, y, 80, 30, "Left")
b_left.set_on_click(lambda: self.play_sfx('sfx3', volume=0.8, pitch=1.0, balance=-0.8))
self.main_tabs.add_to_tab(tab, b_left)
b_center = Button(290, y, 80, 30, "Center")
b_center.set_on_click(lambda: self.play_sfx('sfx3', volume=0.8, pitch=1.0, balance=0.0))
self.main_tabs.add_to_tab(tab, b_center)
b_right = Button(380, y, 80, 30, "Right")
b_right.set_on_click(lambda: self.play_sfx('sfx3', volume=0.8, pitch=1.0, balance=0.8))
self.main_tabs.add_to_tab(tab, b_right)
y += 50
# Stop all SFX
stop_btn = Button(10, y, 120, 30, "Stop All SFX")
stop_btn.set_on_click(lambda: self.stop_sfx_channels())
self.main_tabs.add_to_tab(tab, stop_btn)
y += 50
self.sfx_status = TextLabel(10, y, "No SFX playing", 16, (200, 200, 200))
self.main_tabs.add_to_tab(tab, self.sfx_status)
# ---------- Music Tab ----------
def setup_music_tab(self):
tab = 'Music'
# Header
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Music Controls", 24, (255, 255, 0)))
# Get the existing music channel (already created by AudioManager)
music_channel = self.audio_manager.get_channel('music')
if music_channel is None:
# Fallback: create it (should not happen)
music_channel = self.audio_manager.create_channel('music', volume=0.7, balance=0.0, loop=True)
else:
# Ensure it's configured correctly
music_channel.volume = 0.7
music_channel.balance = 0.0
music_channel.loop = True
self.music_channel = music_channel
self.music_volume = music_channel.volume
self.music_pitch = music_channel.pitch
self.music_balance = music_channel.balance
y = 50
# Play / Pause / Stop
play_btn = Button(10, y, 100, 30, "Play")
play_btn.set_on_click(lambda: self.play_music())
self.main_tabs.add_to_tab(tab, play_btn)
pause_btn = Button(120, y, 100, 30, "Pause")
pause_btn.set_on_click(lambda: self.pause_music())
self.main_tabs.add_to_tab(tab, pause_btn)
resume_btn = Button(230, y, 100, 30, "Resume")
resume_btn.set_on_click(lambda: self.resume_music())
self.main_tabs.add_to_tab(tab, resume_btn)
stop_btn = Button(340, y, 100, 30, "Stop")
stop_btn.set_on_click(lambda: self.stop_music())
self.main_tabs.add_to_tab(tab, stop_btn)
y += 50
# Volume slider
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Volume:", 16))
vol_slider = Slider(100, y, 200, 20, 0.0, 1.0, self.music_volume)
vol_slider.on_value_changed = lambda v: self.set_music_volume(v)
self.main_tabs.add_to_tab(tab, vol_slider)
self.music_vol_label = TextLabel(310, y, f"{self.music_volume:.2f}", 14)
self.main_tabs.add_to_tab(tab, self.music_vol_label)
y += 40
# Pitch slider
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Pitch:", 16))
pitch_slider = Slider(100, y, 200, 20, 0.5, 2.0, self.music_pitch)
pitch_slider.on_value_changed = lambda v: self.set_music_pitch(v)
self.main_tabs.add_to_tab(tab, pitch_slider)
self.music_pitch_label = TextLabel(310, y, f"{self.music_pitch:.2f}x", 14)
self.main_tabs.add_to_tab(tab, self.music_pitch_label)
y += 40
# Balance slider
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Balance:", 16))
bal_slider = Slider(100, y, 200, 20, -1.0, 1.0, self.music_balance)
bal_slider.on_value_changed = lambda v: self.set_music_balance(v)
self.main_tabs.add_to_tab(tab, bal_slider)
self.music_bal_label = TextLabel(310, y, f"{self.music_balance:.2f}", 14)
self.main_tabs.add_to_tab(tab, self.music_bal_label)
y += 50
self.music_status = TextLabel(10, y, "Stopped", 16, (255, 100, 100))
self.main_tabs.add_to_tab(tab, self.music_status)
y += 30
self.music_pos = TextLabel(10, y, "Position: 0.00s", 14, (200, 200, 200))
self.main_tabs.add_to_tab(tab, self.music_pos)
# ---------- Curves / Transitions Tab ----------
def setup_curves_tab(self):
tab = 'Transitions & Curves'
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Audio Curves & Transitions", 24, (255, 255, 0)))
y = 50
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Volume Curve (0β1β0.5 over 3s):", 16))
btn_vol_curve = Button(300, y, 150, 30, "Apply to SFX1")
btn_vol_curve.set_on_click(lambda: self.apply_volume_curve())
self.main_tabs.add_to_tab(tab, btn_vol_curve)
y += 40
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Pitch Curve (0.5β2.0β1.0 over 2s):", 16))
btn_pitch_curve = Button(320, y, 150, 30, "Apply to SFX2")
btn_pitch_curve.set_on_click(lambda: self.apply_pitch_curve())
self.main_tabs.add_to_tab(tab, btn_pitch_curve)
y += 40
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Balance Curve (LβRβCenter over 2s):", 16))
btn_bal_curve = Button(330, y, 150, 30, "Apply to SFX3")
btn_bal_curve.set_on_click(lambda: self.apply_balance_curve())
self.main_tabs.add_to_tab(tab, btn_bal_curve)
y += 50
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Combined: volume β + pitch β (2s)", 16))
btn_combined = Button(280, y, 150, 30, "Apply to Music")
btn_combined.set_on_click(lambda: self.apply_combined_curve())
self.main_tabs.add_to_tab(tab, btn_combined)
y += 50
self.main_tabs.add_to_tab(tab, TextLabel(10, y, "Curve Duration:", 16))
dur_slider = Slider(150, y, 200, 20, 0.5, 5.0, self.curve_duration)
dur_slider.on_value_changed = lambda v: self.set_curve_duration(v)
self.main_tabs.add_to_tab(tab, dur_slider)
self.curve_dur_label = TextLabel(360, y, f"{self.curve_duration:.1f}s", 14)
self.main_tabs.add_to_tab(tab, self.curve_dur_label)
# ---------- Visualizer Tab ----------
def setup_visualizer_tab(self):
tab = 'Visualizer'
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Audio Visualizer", 24, (255, 255, 0)))
from lunaengine.ui.elements import AudioVisualizer
self.audio_visualizer = AudioVisualizer(
x=20, y=50, width=600, height=200,
style='bars',
source=None,
color_gradient=[(100, 0, 200), (0, 150, 255), (0, 255, 200), (100, 255, 100)]
)
self.main_tabs.add_to_tab(tab, self.audio_visualizer)
controls_y = 270
self.main_tabs.add_to_tab(tab, TextLabel(20, controls_y, "Style:", 16))
style_dd = Dropdown(100, controls_y-10, 120, 30, ['Bars', 'Waveform', 'Circle', 'Spectrum'])
style_dd.set_on_selection_changed(lambda i, v: self.audio_visualizer.set_style(v.lower()))
self.main_tabs.add_to_tab(tab, style_dd)
self.main_tabs.add_to_tab(tab, TextLabel(250, controls_y, "Source:", 16))
src_dd = Dropdown(310, controls_y-10, 120, 30, ['Music', 'SFX1', 'SFX2', 'SFX3'])
src_dd.set_on_selection_changed(lambda i, v: self.set_visualizer_source(v.lower()))
self.main_tabs.add_to_tab(tab, src_dd)
controls_y += 40
self.main_tabs.add_to_tab(tab, TextLabel(20, controls_y, "Sensitivity:", 16))
sens_slider = Slider(120, controls_y, 150, 20, 0.5, 3.0, 1.5)
sens_slider.on_value_changed = lambda v: self.audio_visualizer.set_sensitivity(v)
self.main_tabs.add_to_tab(tab, sens_slider)
self.main_tabs.add_to_tab(tab, TextLabel(300, controls_y, "Smoothing:", 16))
smooth_slider = Slider(390, controls_y, 150, 20, 0.1, 0.9, 0.7)
smooth_slider.on_value_changed = lambda v: self.audio_visualizer.set_smoothing(v)
self.main_tabs.add_to_tab(tab, smooth_slider)
# ---------- Monitor Tab ----------
def setup_monitor_tab(self):
tab = 'Monitor'
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Audio Monitor", 24, (255, 255, 0)))
self.main_tabs.add_to_tab(tab, TextLabel(10, 50, "Active Channels:", 16, (200, 200, 255)))
self.channel_list_label = TextLabel(10, 75, "None", 14, (200, 200, 200))
self.main_tabs.add_to_tab(tab, self.channel_list_label)
self.main_tabs.add_to_tab(tab, TextLabel(10, 120, "Event Log:", 16, (200, 200, 255)))
self.event_log_labels = []
for i in range(8):
lbl = TextLabel(10, 140 + i*22, "", 12, (200, 200, 200))
self.event_log_labels.append(lbl)
self.main_tabs.add_to_tab(tab, lbl)
clear_btn = Button(10, 320, 100, 25, "Clear Log")
clear_btn.set_on_click(lambda: self.clear_event_log())
self.main_tabs.add_to_tab(tab, clear_btn)
# ---------- Settings Tab ----------
def setup_settings_tab(self):
tab = 'Settings'
self.main_tabs.add_to_tab(tab, TextLabel(10, 10, "Audio Settings", 24, (255, 255, 0)))
self.main_tabs.add_to_tab(tab, TextLabel(10, 50, "Audio Output Device:", 16, (200, 200, 255)))
devices = self.audio_manager.list_devices()
if not devices:
devices = ["default"]
self.device_dropdown = Dropdown(10, 75, 300, 30, devices)
self.device_dropdown.set_on_selection_changed(lambda i, v: self.change_device(v))
self.main_tabs.add_to_tab(tab, self.device_dropdown)
self.device_status = TextLabel(10, 120, f"Current: {devices[0] if devices else 'default'}", 14, (200, 200, 200))
self.main_tabs.add_to_tab(tab, self.device_status)
self.main_tabs.add_to_tab(tab, TextLabel(10, 170, "Master Volume:", 16, (200, 200, 255)))
master_slider = Slider(150, 165, 200, 20, 0.0, 1.0, 1.0)
master_slider.on_value_changed = lambda v: self.set_master_volume(v)
self.main_tabs.add_to_tab(tab, master_slider)
reload_btn = Button(10, 210, 150, 30, "Reload Sounds")
reload_btn.set_on_click(lambda: self.reload_sounds())
self.main_tabs.add_to_tab(tab, reload_btn)
# ---------- Audio Control Methods ----------
def play_sfx(self, channel_name, volume=1.0, pitch=1.0, balance=0.0, loop=False):
ch = self.audio_manager.get_channel(channel_name)
if not ch:
self.add_event(f"Channel {channel_name} not found")
return
ch.volume = volume
ch.pitch = pitch
ch.balance = balance
if ch.play(self.sfx_name, loop=loop):
self.add_event(f"SFX on {channel_name}: vol={volume:.2f}, pitch={pitch:.2f}, bal={balance:.2f}")
self.update_sfx_status()
else:
self.add_event(f"Failed to play on {channel_name}")
def stop_sfx_channels(self):
for name in ['sfx1', 'sfx2', 'sfx3']:
ch = self.audio_manager.get_channel(name)
if ch:
ch.stop()
self.add_event("All SFX stopped")
self.update_sfx_status()
def update_sfx_status(self):
active = []
for name in ['sfx1', 'sfx2', 'sfx3']:
ch = self.audio_manager.get_channel(name)
if ch and ch.is_playing():
active.append(name)
if active:
self.sfx_status.set_text(f"Playing: {', '.join(active)}")
else:
self.sfx_status.set_text("No SFX playing")
def play_music(self):
ch = self.music_channel
if ch is None:
ch = self.audio_manager.get_channel('music')
if ch is None:
ch = self.audio_manager.create_channel('music', volume=self.music_volume, balance=self.music_balance, loop=True)
self.music_channel = ch
if ch.play(self.music_name, loop=True):
self.music_status.set_text("Playing")
self.music_status.color = (100, 255, 100)
self.add_event("Music started")
else:
self.add_event("Failed to start music")
def pause_music(self):
ch = self.music_channel
if ch and ch.is_playing():
ch.pause()
self.music_status.set_text("Paused")
self.music_status.color = (255, 255, 100)
self.add_event("Music paused")
def resume_music(self):
ch = self.music_channel
if ch and ch.is_paused():
ch.resume()
self.music_status.set_text("Playing")
self.music_status.color = (100, 255, 100)
self.add_event("Music resumed")
def stop_music(self):
ch = self.music_channel
if ch:
ch.stop()
self.music_status.set_text("Stopped")
self.music_status.color = (255, 100, 100)
self.add_event("Music stopped")
def set_music_volume(self, vol):
self.music_volume = vol
self.music_vol_label.set_text(f"{vol:.2f}")
ch = self.music_channel
if ch:
ch.set_volume(vol)
def set_music_pitch(self, pitch):
self.music_pitch = pitch
self.music_pitch_label.set_text(f"{pitch:.2f}x")
ch = self.music_channel
if ch:
ch.set_pitch(pitch)
def set_music_balance(self, bal):
self.music_balance = bal
self.music_bal_label.set_text(f"{bal:.2f}")
ch = self.music_channel
if ch:
ch.set_balance(bal)
def set_curve_duration(self, dur):
self.curve_duration = dur
self.curve_dur_label.set_text(f"{dur:.1f}s")
# Curve applications
def apply_volume_curve(self):
ch = self.audio_manager.get_channel('sfx1')
if not ch or not ch.is_playing():
self.add_event("SFX1 not playing, cannot apply curve")
return
dur = self.curve_duration
keyframes = [(0.0, 0.0), (dur*0.5, 1.0), (dur, 0.5)]
curve = AudioCurve('volume', keyframes, interpolation='smoothstep')
ch.apply_curve(curve)
self.add_event(f"Volume curve applied to sfx1 ({dur}s)")
def apply_pitch_curve(self):
ch = self.audio_manager.get_channel('sfx2')
if not ch or not ch.is_playing():
self.add_event("SFX2 not playing, cannot apply curve")
return
dur = self.curve_duration
keyframes = [(0.0, 0.5), (dur*0.5, 2.0), (dur, 1.0)]
curve = AudioCurve('pitch', keyframes, interpolation='smoothstep')
ch.apply_curve(curve)
self.add_event(f"Pitch curve applied to sfx2 ({dur}s)")
def apply_balance_curve(self):
ch = self.audio_manager.get_channel('sfx3')
if not ch or not ch.is_playing():
self.add_event("SFX3 not playing, cannot apply curve")
return
dur = self.curve_duration
keyframes = [(0.0, -0.8), (dur*0.5, 0.8), (dur, 0.0)]
curve = AudioCurve('balance', keyframes, interpolation='smoothstep')
ch.apply_curve(curve)
self.add_event(f"Balance curve applied to sfx3 ({dur}s)")
def apply_combined_curve(self):
ch = self.music_channel
if not ch or not ch.is_playing():
self.add_event("Music not playing, cannot apply combined curve")
return
dur = self.curve_duration
vol_curve = AudioCurve('volume', [(0.0, 0.5), (dur, 1.0)], 'smoothstep')
pitch_curve = AudioCurve('pitch', [(0.0, 1.0), (dur, 1.5)], 'smoothstep')
ch.apply_curve(vol_curve)
ch.apply_curve(pitch_curve)
self.add_event(f"Combined volume+pitch curve on music ({dur}s)")
def set_visualizer_source(self, source_name):
if source_name == 'music':
ch = self.music_channel
else:
ch = self.audio_manager.get_channel(source_name)
if ch and ch.is_playing():
self.audio_visualizer.set_source(ch)
else:
self.audio_visualizer.set_source(None)
# ---------- Settings ----------
def change_device(self, device_name):
if self.audio_manager.set_device(device_name):
self.device_status.set_text(f"Current: {device_name}")
self.add_event(f"Device changed to {device_name}")
else:
self.add_event(f"Failed to switch to {device_name}")
def set_master_volume(self, vol):
for ch in self.audio_manager.channels.values():
ch.set_volume(ch.volume * vol)
self.add_event(f"Master volume set to {vol:.2f}")
def reload_sounds(self):
self.add_event("Reloading sounds...")
self.load_audio()
self.add_event("Sounds reloaded")
# ---------- Event Logging ----------
def on_audio_event(self, manager, **kwargs):
event = kwargs.get('event', None)
if event:
self.add_event(f"AudioEvent: {event}")
else:
self.add_event("Audio event occurred")
def add_event(self, text):
self.event_log.append(text)
if len(self.event_log) > 8:
self.event_log.pop(0)
self.update_event_log()
def update_event_log(self):
for i, lbl in enumerate(self.event_log_labels):
if i < len(self.event_log):
lbl.set_text(self.event_log[i])
else:
lbl.set_text("")
def clear_event_log(self):
self.event_log.clear()
for lbl in self.event_log_labels:
lbl.set_text("")
# ---------- Update ----------
def update(self, dt):
self.audio_manager.update(dt)
# Update music position
ch = self.music_channel
if ch and ch.is_playing():
pos = ch.get_position()
self.music_pos.set_text(f"Position: {pos:.2f}s")
else:
self.music_pos.set_text("Position: 0.00s")
fps_stats = self.engine.get_fps_stats()
self.fps_label.set_text(f"FPS: {fps_stats['current_fps']:.1f}")
active = [name for name, ch in self.audio_manager.channels.items() if ch.is_playing()]
if active:
self.channel_list_label.set_text(", ".join(active))
else:
self.channel_list_label.set_text("None")
# ---------- Keyboard ----------
def handle_key(self, key):
if key == pygame.K_1:
self.play_sfx('sfx1', volume=0.8, pitch=1.0)
elif key == pygame.K_2:
self.play_sfx('sfx2', volume=0.7, pitch=1.0)
elif key == pygame.K_3:
self.play_sfx('sfx3', volume=0.8, pitch=1.0, balance=-0.5)
elif key == pygame.K_4:
self.play_music()
elif key == pygame.K_5:
self.pause_music()
elif key == pygame.K_6:
self.resume_music()
elif key == pygame.K_7:
self.stop_music()
elif key == pygame.K_ESCAPE:
self.engine.set_scene("MainMenu")
# ---------- Render ----------
def render(self, renderer):
renderer.fill_screen(ThemeManager.get_color('background'))
renderer.draw_rect(0, 0, self.engine.width, 80, ThemeManager.get_color('background2'))
# UI elements rendered automatically
# ---------- Main ----------
def main():
engine = LunaEngine("Audio Demo - LunaEngine", 1024, 720, debug=True)
engine.fps = 60
engine.add_scene("AudioDemo", AudioDemoScene)
engine.set_scene("AudioDemo")
print("\n=== Audio Demo Controls ===")
print("1-3: Play SFX on channels 1-3")
print("4: Play music")
print("5: Pause music")
print("6: Resume music")
print("7: Stop music")
print("ESC: Exit demo")
print("Use UI tabs for full control\n")
engine.run()
if __name__ == "__main__":
main()