rs_audio/
player.rs

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