use include_dir::{Dir, include_dir};
use rodio::{Decoder, OutputStreamBuilder, Sink, Source};
use std::{
error::Error,
io::Cursor,
sync::{
OnceLock,
atomic::{AtomicBool, Ordering},
mpsc,
},
thread,
time::Duration,
};
static ASSETS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/assets");
fn get_waveform(name: &str) -> Option<&'static [u8]> {
ASSETS.get_file(name).map(|f| f.contents())
}
pub const THEME_FAST: &str = "tetrs_theme_fast.wav";
#[allow(dead_code)]
pub const THEME_NORMAL: &str = "tetrs_theme_normal.wav";
pub enum AudioCommand {
Play { name: String, loop_forever: bool },
Pause,
Resume,
TogglePause,
}
static AUDIO_SENDER: OnceLock<mpsc::Sender<AudioCommand>> = OnceLock::new();
static IS_PAUSED: AtomicBool = AtomicBool::new(false);
pub fn get_is_paused() -> bool {
IS_PAUSED.load(Ordering::Relaxed)
}
pub fn init_audio_thread() -> mpsc::Sender<AudioCommand> {
if let Some(s) = AUDIO_SENDER.get() {
return s.clone();
}
let (tx, rx) = mpsc::channel::<AudioCommand>();
thread::spawn(move || {
let stream = match OutputStreamBuilder::open_default_stream() {
Ok(s) => s,
Err(_e) => {
return;
}
};
let mut current_sink: Option<Sink> = None;
for cmd in rx {
match cmd {
AudioCommand::Play { name, loop_forever } => {
IS_PAUSED.store(false, Ordering::Relaxed);
if let Some(s) = current_sink.take() {
s.stop();
}
match get_waveform(&name) {
Some(bytes) => {
let cursor = Cursor::new(bytes);
match Decoder::try_from(cursor) {
Ok(decoder) => {
let sink = Sink::connect_new(&stream.mixer());
if loop_forever {
sink.append(decoder.repeat_infinite());
} else {
sink.append(decoder);
}
sink.play();
current_sink = Some(sink);
}
Err(_e) => {}
}
}
None => {}
}
}
AudioCommand::Pause => {
IS_PAUSED.store(true, Ordering::Relaxed);
if let Some(s) = current_sink.as_ref() {
s.pause();
}
}
AudioCommand::Resume => {
IS_PAUSED.store(false, Ordering::Relaxed);
if let Some(s) = current_sink.as_ref() {
s.play();
}
}
AudioCommand::TogglePause => {
IS_PAUSED.store(!IS_PAUSED.load(Ordering::Relaxed), Ordering::Relaxed);
if let Some(s) = current_sink.as_ref() {
if s.is_paused() {
s.play();
} else {
s.pause();
}
}
}
}
thread::sleep(Duration::from_millis(1));
}
if let Some(s) = current_sink.take() {
s.stop();
}
});
let _ = AUDIO_SENDER.set(tx.clone());
tx
}
pub fn play(name: &str, loop_forever: bool) -> Result<(), Box<dyn Error>> {
let tx = init_audio_thread();
tx.send(AudioCommand::Play {
name: name.to_string(),
loop_forever,
})?;
Ok(())
}
pub fn toggle() -> Result<(), Box<dyn Error>> {
let tx = init_audio_thread();
tx.send(AudioCommand::TogglePause)?;
Ok(())
}
#[allow(dead_code)]
pub fn pause() -> Result<(), Box<dyn Error>> {
let tx = init_audio_thread();
tx.send(AudioCommand::Pause)?;
Ok(())
}
#[allow(dead_code)]
pub fn resume() -> Result<(), Box<dyn Error>> {
let tx = init_audio_thread();
tx.send(AudioCommand::Resume)?;
Ok(())
}