rs_audio/
player.rs

1use std::collections::HashMap;
2use std::thread;
3use std::{sync::mpsc, time::Duration};
4
5use rodio::source::SineWave;
6use rodio::{OutputStream, Sink, Source};
7
8use crate::{BPMChoice, Note, WaveForm};
9
10#[derive(Debug, Clone)]
11pub struct Song {
12    pub bpm: BPMChoice,
13    pub notes: Vec<Note>,
14}
15
16impl Song {
17    pub fn new(notes: Vec<Note>, bpm: BPMChoice) -> Self {
18        Self { bpm, notes }
19    }
20}
21
22impl Default for Song {
23    fn default() -> Self {
24        Self {
25            bpm: BPMChoice::Default,
26            notes: vec![Note::default()],
27        }
28    }
29}
30
31pub struct AudioManager {
32    tx: mpsc::Sender<AudioCommand>,
33    next_track_id: usize,
34}
35
36#[derive(Debug, Clone)]
37pub enum AudioCommand {
38    PlayTrack { id: usize, song: Song },
39    StopTrack(usize),
40    SetVolume(usize, f32),
41    StopAll,
42}
43
44impl AudioManager {
45    pub fn new() -> Self {
46        let (tx, rx) = mpsc::channel();
47        let mut _next_track_id = 0;
48
49        // Spawn the dedicated audio thread
50        thread::spawn(move || {
51            let (_stream, handle) = OutputStream::try_default().unwrap();
52            let mut sinks: HashMap<usize, Sink> = HashMap::new();
53
54            // audio thread loop
55            while let Ok(command) = rx.recv() {
56                match command {
57                    AudioCommand::PlayTrack { id, song } => {
58                        // create a new sink for this track
59                        let sink = Sink::try_new(&handle).unwrap();
60
61                        for note in song.notes {
62                            let source = match note.wave {
63                                WaveForm::Sine => Box::new(SineWave::new(note.freq as f32)),
64                                _ => Box::new(note.to_approx_sine()),
65                            };
66                            sink.append(
67                                source
68                                    .take_duration(Duration::from_secs_f64(note.dur))
69                                    .amplify(note.vol),
70                            );
71                        }
72
73                        // Store the sink for potential later control
74                        sinks.insert(id, sink);
75                    }
76                    AudioCommand::StopTrack(id) => {
77                        if let Some(sink) = sinks.get(&id) {
78                            sink.stop(); // Stop only this specific track
79                            sinks.remove(&id);
80                        }
81                    }
82                    AudioCommand::SetVolume(id, volume) => {
83                        if let Some(sink) = sinks.get(&id) {
84                            sink.set_volume(volume);
85                        }
86                    }
87                    AudioCommand::StopAll => {
88                        for sink in sinks.values() {
89                            sink.stop();
90                        }
91                        sinks.clear();
92                    }
93                }
94            }
95        });
96
97        AudioManager {
98            tx,
99            next_track_id: 0,
100        }
101    }
102
103    pub fn play(&mut self, song: Song) -> usize {
104        let track_id = self.next_track_id;
105        self.next_track_id += 1;
106
107        let _ = self.tx.send(AudioCommand::PlayTrack { id: track_id, song });
108
109        track_id // return ID for later control
110    }
111
112    pub fn stop(&self, track_id: usize) {
113        let _ = self.tx.send(AudioCommand::StopTrack(track_id));
114    }
115
116    pub fn set_volume(&self, track_id: usize, volume: f32) {
117        let _ = self.tx.send(AudioCommand::SetVolume(track_id, volume));
118    }
119
120    pub fn stop_all(&self) {
121        let _ = self.tx.send(AudioCommand::StopAll);
122    }
123}