Skip to main content

arcane_engine/audio/
mod.rs

1use std::collections::HashMap;
2use std::io::Cursor;
3use std::sync::mpsc;
4
5/// Commands sent from the main thread to the audio thread.
6pub enum AudioCommand {
7    LoadSound { id: u32, data: Vec<u8> },
8    PlaySound { id: u32, volume: f32, looping: bool },
9    StopSound { id: u32 },
10    StopAll,
11    SetMasterVolume { volume: f32 },
12    Shutdown,
13}
14
15pub type AudioSender = mpsc::Sender<AudioCommand>;
16pub type AudioReceiver = mpsc::Receiver<AudioCommand>;
17
18/// Create a channel for sending audio commands to the audio thread.
19pub fn audio_channel() -> (AudioSender, AudioReceiver) {
20    mpsc::channel()
21}
22
23/// Spawn the audio thread. It owns the rodio OutputStream and processes commands.
24pub fn start_audio_thread(rx: AudioReceiver) -> std::thread::JoinHandle<()> {
25    std::thread::spawn(move || {
26        // Initialize rodio output stream
27        let stream_handle = match rodio::OutputStream::try_default() {
28            Ok((stream, handle)) => {
29                // Leak the stream so it lives as long as the thread
30                std::mem::forget(stream);
31                handle
32            }
33            Err(e) => {
34                eprintln!("[audio] Failed to initialize audio output: {e}");
35                // Drain commands without playing
36                while let Ok(cmd) = rx.recv() {
37                    if matches!(cmd, AudioCommand::Shutdown) {
38                        break;
39                    }
40                }
41                return;
42            }
43        };
44
45        let mut sounds: HashMap<u32, Vec<u8>> = HashMap::new();
46        let mut sinks: HashMap<u32, rodio::Sink> = HashMap::new();
47        let mut master_volume: f32 = 1.0;
48
49        loop {
50            let cmd = match rx.recv() {
51                Ok(cmd) => cmd,
52                Err(_) => break, // Channel closed
53            };
54
55            match cmd {
56                AudioCommand::LoadSound { id, data } => {
57                    sounds.insert(id, data);
58                }
59                AudioCommand::PlaySound { id, volume, looping } => {
60                    if let Some(data) = sounds.get(&id) {
61                        match rodio::Sink::try_new(&stream_handle) {
62                            Ok(sink) => {
63                                sink.set_volume(volume * master_volume);
64                                let cursor = Cursor::new(data.clone());
65                                match rodio::Decoder::new(cursor) {
66                                    Ok(source) => {
67                                        if looping {
68                                            sink.append(rodio::source::Source::repeat_infinite(source));
69                                        } else {
70                                            sink.append(source);
71                                        }
72                                        sink.play();
73                                        sinks.insert(id, sink);
74                                    }
75                                    Err(e) => {
76                                        eprintln!("[audio] Failed to decode sound {id}: {e}");
77                                    }
78                                }
79                            }
80                            Err(e) => {
81                                eprintln!("[audio] Failed to create sink for sound {id}: {e}");
82                            }
83                        }
84                    }
85                }
86                AudioCommand::StopSound { id } => {
87                    if let Some(sink) = sinks.remove(&id) {
88                        sink.stop();
89                    }
90                }
91                AudioCommand::StopAll => {
92                    for (_, sink) in sinks.drain() {
93                        sink.stop();
94                    }
95                }
96                AudioCommand::SetMasterVolume { volume } => {
97                    master_volume = volume;
98                    for sink in sinks.values() {
99                        sink.set_volume(volume);
100                    }
101                }
102                AudioCommand::Shutdown => break,
103            }
104        }
105    })
106}