doukutsu-rs/src/sound/mod.rs

205 lines
5.6 KiB
Rust
Raw Normal View History

2020-09-02 22:58:11 +00:00
use std::sync::{mpsc, RwLock};
use std::sync::mpsc::{Receiver, Sender};
use bitflags::_core::time::Duration;
use cpal::Sample;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::engine_constants::EngineConstants;
use crate::ggez::{Context, filesystem, GameResult};
use crate::ggez::GameError::AudioError;
use crate::sound::organya::Song;
use crate::sound::playback::PlaybackEngine;
use crate::sound::wave_bank::SoundBank;
use crate::str;
2020-08-18 16:46:07 +00:00
pub mod pixtone;
2020-09-02 22:58:11 +00:00
mod wave_bank;
mod organya;
mod playback;
mod stuff;
mod wav;
2020-08-18 16:46:07 +00:00
pub struct SoundManager {
2020-09-02 22:58:11 +00:00
tx: Sender<PlaybackMessage>,
current_song_id: usize,
2020-08-18 16:46:07 +00:00
}
2020-09-02 22:58:11 +00:00
static SONGS: [&'static str; 42] = [
"XXXX",
"WANPAKU",
"ANZEN",
"GAMEOVER",
"GRAVITY",
"WEED",
"MDOWN2",
"FIREEYE",
"VIVI",
"MURA",
"FANFALE1",
"GINSUKE",
"CEMETERY",
"PLANT",
"KODOU",
"FANFALE3",
"FANFALE2",
"DR",
"ESCAPE",
"JENKA",
"MAZE",
"ACCESS",
"IRONH",
"GRAND",
"Curly",
"OSIDE",
"REQUIEM",
"WANPAK2",
"QUIET",
"LASTCAVE",
"BALCONY",
"LASTBTL",
"LASTBT3",
"ENDING",
"ZONBIE",
"BDOWN",
"HELL",
"JENKA2",
"MARINE",
"BALLOS",
"TOROKO",
"WHITE"
];
2020-08-19 19:11:32 +00:00
2020-08-18 16:46:07 +00:00
impl SoundManager {
2020-09-02 22:58:11 +00:00
pub fn new(ctx: &mut Context) -> GameResult<SoundManager> {
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
let host = cpal::default_host();
let device = host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
let config = device.default_output_config()?;
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/pixtone.pcm")?)?;
std::thread::spawn(move || {
if let Err(err) = match config.sample_format() {
cpal::SampleFormat::F32 => run::<f32>(rx, bnk, &device, &config.into()),
cpal::SampleFormat::I16 => run::<i16>(rx, bnk, &device, &config.into()),
cpal::SampleFormat::U16 => run::<u16>(rx, bnk, &device, &config.into()),
} {
log::error!("Something went wrong in audio thread: {}", err);
}
});
Ok(SoundManager {
tx: tx.clone(),
current_song_id: 0,
})
2020-08-18 16:46:07 +00:00
}
2020-09-02 22:58:11 +00:00
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
if self.current_song_id == song_id {
return Ok(());
}
2020-08-18 16:46:07 +00:00
2020-09-02 22:58:11 +00:00
if let Some(song_name) = SONGS.get(song_id) {
let org = organya::Song::load_from(filesystem::open(ctx, ["/base/Org/", &song_name.to_lowercase(), ".org"].join(""))?)?;
log::info!("Playing song: {}", song_name);
2020-08-18 16:46:07 +00:00
2020-09-02 22:58:11 +00:00
self.current_song_id = song_id;
self.tx.send(PlaybackMessage::PlaySong(org));
}
2020-08-18 16:46:07 +00:00
Ok(())
}
}
2020-09-02 22:58:11 +00:00
enum PlaybackMessage {
Stop,
PlaySong(Song),
}
#[derive(PartialEq, Eq)]
enum PlaybackState {
Stopped,
Playing,
}
fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
device: &cpal::Device, config: &cpal::StreamConfig) -> GameResult where
T: cpal::Sample,
{
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
let mut state = PlaybackState::Stopped;
let mut new_song: Option<Song> = None;
let mut engine = PlaybackEngine::new(Song::empty(), &bank);
log::info!("Audio format: {} {}", sample_rate, channels);
engine.set_sample_rate(sample_rate as usize);
engine.loops = usize::MAX;
let mut buf = vec![0x8080; 441];
let mut index = 0;
let mut frames = engine.render_to(&mut buf);
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
match rx.try_recv() {
Ok(PlaybackMessage::PlaySong(song)) => {
engine.start_song(song, &bank);
for i in &mut buf[0..frames] { *i = 0x8080 };
frames = engine.render_to(&mut buf);
index = 0;
state = PlaybackState::Playing;
}
_ => {}
}
for frame in data.chunks_mut(channels) {
let sample: u16 = {
if state == PlaybackState::Stopped {
0x8000
} else if index < frames {
let sample = buf[index];
index += 1;
if index & 1 == 0 { (sample & 0xff) << 8 } else { sample & 0xff00 }
} else {
for i in &mut buf[0..frames] { *i = 0x8080 };
frames = engine.render_to(&mut buf);
index = 0;
let sample = buf[0];
(sample & 0xff) << 8
}
};
let value: T = cpal::Sample::from::<u16>(&sample);
for sample in frame.iter_mut() {
*sample = value;
}
}
},
err_fn,
)?;
stream.play()?;
loop {
std::thread::sleep(Duration::from_millis(10));
}
}
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> u16)
where
T: cpal::Sample,
{
for frame in output.chunks_mut(channels) {
let value: T = cpal::Sample::from::<u16>(&next_sample());
for sample in frame.iter_mut() {
*sample = value;
}
}
}