2020-09-03 12:04:54 +00:00
|
|
|
use std::sync::mpsc;
|
2020-09-02 22:58:11 +00:00
|
|
|
use std::sync::mpsc::{Receiver, Sender};
|
2020-11-04 23:25:18 +00:00
|
|
|
use std::time::Duration;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
use cpal::Sample;
|
2020-09-02 22:58:11 +00:00
|
|
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
2020-11-04 23:25:18 +00:00
|
|
|
use num_traits::clamp;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
|
|
|
use crate::engine_constants::EngineConstants;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::context::Context;
|
|
|
|
use crate::framework::error::GameResult;
|
|
|
|
use crate::framework::filesystem;
|
2020-09-02 22:58:11 +00:00
|
|
|
use crate::sound::organya::Song;
|
2020-09-16 13:21:30 +00:00
|
|
|
use crate::sound::pixtone::PixTonePlayback;
|
2020-09-03 11:48:56 +00:00
|
|
|
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
|
2020-09-02 22:58:11 +00:00
|
|
|
use crate::sound::wave_bank::SoundBank;
|
|
|
|
use crate::str;
|
2021-01-27 18:20:47 +00:00
|
|
|
use crate::framework::error::GameError::{AudioError, ResourceLoadError, InvalidValue};
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-02 22:58:11 +00:00
|
|
|
mod wave_bank;
|
|
|
|
mod organya;
|
2020-09-16 13:25:28 +00:00
|
|
|
mod pixtone;
|
|
|
|
mod pixtone_sfx;
|
2020-09-02 22:58:11 +00:00
|
|
|
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>,
|
2020-09-05 02:08:59 +00:00
|
|
|
prev_song_id: usize,
|
2020-09-02 22:58:11 +00:00
|
|
|
current_song_id: usize,
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-11 16:30:18 +00:00
|
|
|
static SONGS: [&str; 43] = [
|
|
|
|
"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",
|
|
|
|
"kaze"
|
2020-09-02 22:58:11 +00:00
|
|
|
];
|
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()?;
|
|
|
|
|
2020-12-21 11:57:31 +00:00
|
|
|
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
|
|
|
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(),
|
2020-09-05 02:08:59 +00:00
|
|
|
prev_song_id: 0,
|
2020-09-02 22:58:11 +00:00
|
|
|
current_song_id: 0,
|
|
|
|
})
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
pub fn play_sfx(&mut self, id: u8) {
|
2020-12-25 22:37:57 +00:00
|
|
|
let _ = self.tx.send(PlaybackMessage::PlaySample(id));
|
2020-09-16 13:21:30 +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-03 11:48:56 +00:00
|
|
|
if song_id == 0 {
|
|
|
|
log::info!("Stopping BGM");
|
|
|
|
|
2020-09-05 02:54:43 +00:00
|
|
|
self.prev_song_id = self.current_song_id;
|
2020-09-03 11:48:56 +00:00
|
|
|
self.current_song_id = 0;
|
2020-09-05 02:54:43 +00:00
|
|
|
|
|
|
|
self.tx.send(PlaybackMessage::SaveState)?;
|
2020-09-03 11:48:56 +00:00
|
|
|
self.tx.send(PlaybackMessage::Stop)?;
|
|
|
|
} else if let Some(song_name) = SONGS.get(song_id) {
|
2020-09-05 03:31:55 +00:00
|
|
|
let path = constants.organya_paths
|
|
|
|
.iter()
|
|
|
|
.map(|prefix| [prefix, &song_name.to_lowercase(), ".org"].join(""))
|
|
|
|
.find(|path| filesystem::exists(ctx, path))
|
|
|
|
.ok_or_else(|| ResourceLoadError(format!("BGM {:?} does not exist.", song_name)))?;
|
|
|
|
|
|
|
|
let org = organya::Song::load_from(filesystem::open(ctx, path)?)?;
|
2020-09-03 11:48:56 +00:00
|
|
|
log::info!("Playing BGM: {}", song_name);
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2020-09-05 02:08:59 +00:00
|
|
|
self.prev_song_id = self.current_song_id;
|
2020-09-02 22:58:11 +00:00
|
|
|
self.current_song_id = song_id;
|
2020-09-05 01:36:19 +00:00
|
|
|
self.tx.send(PlaybackMessage::SaveState)?;
|
2020-09-03 11:48:56 +00:00
|
|
|
self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?;
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-09-03 11:48:56 +00:00
|
|
|
|
|
|
|
pub fn save_state(&mut self) -> GameResult {
|
|
|
|
self.tx.send(PlaybackMessage::SaveState)?;
|
2020-09-05 02:08:59 +00:00
|
|
|
self.prev_song_id = self.current_song_id;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn restore_state(&mut self) -> GameResult {
|
|
|
|
self.tx.send(PlaybackMessage::RestoreState)?;
|
2020-09-05 02:08:59 +00:00
|
|
|
self.current_song_id = self.prev_song_id;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-09-04 12:00:09 +00:00
|
|
|
|
|
|
|
pub fn set_speed(&mut self, speed: f32) -> GameResult {
|
|
|
|
if speed <= 0.0 {
|
|
|
|
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
|
|
|
|
}
|
|
|
|
self.tx.send(PlaybackMessage::SetSpeed(speed))?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-10-30 01:29:53 +00:00
|
|
|
|
|
|
|
pub fn current_song(&self) -> usize {
|
|
|
|
self.current_song_id
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
2020-09-02 22:58:11 +00:00
|
|
|
|
|
|
|
enum PlaybackMessage {
|
|
|
|
Stop,
|
2020-09-03 11:48:56 +00:00
|
|
|
PlaySong(Box<Song>),
|
2020-09-16 13:21:30 +00:00
|
|
|
PlaySample(u8),
|
2020-09-04 12:00:09 +00:00
|
|
|
SetSpeed(f32),
|
2020-09-03 11:48:56 +00:00
|
|
|
SaveState,
|
|
|
|
RestoreState,
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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;
|
2020-09-03 11:48:56 +00:00
|
|
|
let mut saved_state: Option<SavedPlaybackState> = None;
|
2020-09-16 13:21:30 +00:00
|
|
|
let mut speed = 1.0;
|
2020-11-05 22:10:15 +00:00
|
|
|
let mut org_engine = PlaybackEngine::new(Song::empty(), &bank);
|
2020-09-16 13:21:30 +00:00
|
|
|
let mut pixtone = PixTonePlayback::new();
|
|
|
|
pixtone.create_samples();
|
|
|
|
|
2020-09-02 22:58:11 +00:00
|
|
|
log::info!("Audio format: {} {}", sample_rate, channels);
|
2020-11-05 22:10:15 +00:00
|
|
|
org_engine.set_sample_rate(sample_rate as usize);
|
|
|
|
org_engine.loops = usize::MAX;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
2020-12-25 22:37:57 +00:00
|
|
|
let buf_size = sample_rate as usize * 10 / 1000;
|
2020-12-20 20:57:17 +00:00
|
|
|
let mut bgm_buf = vec![0x8080; buf_size];
|
|
|
|
let mut pxt_buf = vec![0x8000; buf_size];
|
2020-11-05 22:10:15 +00:00
|
|
|
let mut bgm_index = 0;
|
2020-09-16 13:21:30 +00:00
|
|
|
let mut pxt_index = 0;
|
2020-11-05 22:10:15 +00:00
|
|
|
let mut frames = org_engine.render_to(&mut bgm_buf);
|
2020-09-16 13:21:30 +00:00
|
|
|
pixtone.mix(&mut pxt_buf, sample_rate);
|
2020-09-02 22:58:11 +00:00
|
|
|
|
|
|
|
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
|
|
|
|
|
|
|
|
let stream = device.build_output_stream(
|
|
|
|
config,
|
|
|
|
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
|
2020-09-16 13:21:30 +00:00
|
|
|
loop {
|
|
|
|
match rx.try_recv() {
|
|
|
|
Ok(PlaybackMessage::PlaySong(song)) => {
|
2020-11-05 22:10:15 +00:00
|
|
|
if state == PlaybackState::Stopped {
|
|
|
|
saved_state = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
org_engine.start_song(*song, &bank);
|
2020-09-02 22:58:11 +00:00
|
|
|
|
2020-11-05 22:10:15 +00:00
|
|
|
for i in &mut bgm_buf[0..frames] { *i = 0x8080 };
|
|
|
|
frames = org_engine.render_to(&mut bgm_buf);
|
|
|
|
bgm_index = 0;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
state = PlaybackState::Playing;
|
|
|
|
}
|
|
|
|
Ok(PlaybackMessage::PlaySample(id)) => {
|
|
|
|
pixtone.play_sfx(id);
|
|
|
|
}
|
|
|
|
Ok(PlaybackMessage::Stop) => {
|
2020-11-05 22:10:15 +00:00
|
|
|
if state == PlaybackState::Stopped {
|
|
|
|
saved_state = None;
|
|
|
|
}
|
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
state = PlaybackState::Stopped;
|
|
|
|
}
|
|
|
|
Ok(PlaybackMessage::SetSpeed(new_speed)) => {
|
|
|
|
assert!(new_speed > 0.0);
|
|
|
|
speed = new_speed;
|
2020-11-05 22:10:15 +00:00
|
|
|
org_engine.set_sample_rate((sample_rate / new_speed) as usize);
|
2020-09-16 13:21:30 +00:00
|
|
|
}
|
|
|
|
Ok(PlaybackMessage::SaveState) => {
|
2020-11-05 22:10:15 +00:00
|
|
|
saved_state = Some(org_engine.get_state());
|
2020-09-16 13:21:30 +00:00
|
|
|
}
|
|
|
|
Ok(PlaybackMessage::RestoreState) => {
|
|
|
|
if saved_state.is_some() {
|
2020-11-05 22:10:15 +00:00
|
|
|
org_engine.set_state(saved_state.clone().unwrap(), &bank);
|
2020-09-16 13:21:30 +00:00
|
|
|
saved_state = None;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
if state == PlaybackState::Stopped {
|
2020-11-05 22:10:15 +00:00
|
|
|
org_engine.set_position(0);
|
2020-09-16 13:21:30 +00:00
|
|
|
}
|
2020-09-05 02:54:43 +00:00
|
|
|
|
2020-11-05 22:10:15 +00:00
|
|
|
for i in &mut bgm_buf[0..frames] { *i = 0x8080 };
|
|
|
|
frames = org_engine.render_to(&mut bgm_buf);
|
|
|
|
bgm_index = 0;
|
2020-09-03 11:48:56 +00:00
|
|
|
|
2020-09-16 13:21:30 +00:00
|
|
|
state = PlaybackState::Playing;
|
|
|
|
}
|
2020-09-03 11:48:56 +00:00
|
|
|
}
|
2020-09-16 13:21:30 +00:00
|
|
|
Err(_) => { break; }
|
2020-09-03 11:48:56 +00:00
|
|
|
}
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for frame in data.chunks_mut(channels) {
|
2020-09-28 20:30:11 +00:00
|
|
|
let (org_sample_l, org_sample_r): (u16, u16) = {
|
2020-09-02 22:58:11 +00:00
|
|
|
if state == PlaybackState::Stopped {
|
2020-09-28 20:30:11 +00:00
|
|
|
(0x8000, 0x8000)
|
2020-11-05 22:10:15 +00:00
|
|
|
} else if bgm_index < frames {
|
|
|
|
let sample = bgm_buf[bgm_index];
|
|
|
|
bgm_index += 1;
|
2020-09-28 20:30:11 +00:00
|
|
|
((sample & 0xff) << 8, sample & 0xff00)
|
2020-09-02 22:58:11 +00:00
|
|
|
} else {
|
2020-11-05 22:10:15 +00:00
|
|
|
for i in &mut bgm_buf[0..frames] { *i = 0x8080 };
|
|
|
|
frames = org_engine.render_to(&mut bgm_buf);
|
|
|
|
bgm_index = 0;
|
|
|
|
let sample = bgm_buf[0];
|
2020-09-28 20:30:11 +00:00
|
|
|
((sample & 0xff) << 8, sample & 0xff00)
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
|
|
|
};
|
2020-09-20 15:37:53 +00:00
|
|
|
let pxt_sample: u16 = pxt_buf[pxt_index];
|
2020-09-16 13:21:30 +00:00
|
|
|
|
|
|
|
if pxt_index < (pxt_buf.len() - 1) {
|
|
|
|
pxt_index += 1;
|
|
|
|
} else {
|
|
|
|
pxt_index = 0;
|
|
|
|
for i in pxt_buf.iter_mut() { *i = 0x8000 };
|
|
|
|
pixtone.mix(&mut pxt_buf, sample_rate / speed);
|
|
|
|
}
|
|
|
|
|
2020-09-28 20:30:11 +00:00
|
|
|
if frame.len() >= 2 {
|
|
|
|
let sample_l = clamp(
|
|
|
|
(((org_sample_l ^ 0x8000) as i16) as isize)
|
|
|
|
+ (((pxt_sample ^ 0x8000) as i16) as isize)
|
|
|
|
, -0x7fff, 0x7fff) as u16 ^ 0x8000;
|
|
|
|
let sample_r = clamp(
|
|
|
|
(((org_sample_r ^ 0x8000) as i16) as isize)
|
|
|
|
+ (((pxt_sample ^ 0x8000) as i16) as isize)
|
|
|
|
, -0x7fff, 0x7fff) as u16 ^ 0x8000;
|
|
|
|
|
|
|
|
frame[0] = Sample::from::<u16>(&sample_l);
|
|
|
|
frame[1] = Sample::from::<u16>(&sample_r);
|
|
|
|
} else {
|
|
|
|
let sample = clamp(
|
|
|
|
(((org_sample_l ^ 0x8000) as i16) as isize)
|
|
|
|
+ (((pxt_sample ^ 0x8000) as i16) as isize)
|
|
|
|
, -0x7fff, 0x7fff) as u16 ^ 0x8000;
|
2020-09-02 22:58:11 +00:00
|
|
|
|
2020-09-28 20:30:11 +00:00
|
|
|
frame[0] = Sample::from::<u16>(&sample);
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
err_fn,
|
|
|
|
)?;
|
|
|
|
stream.play()?;
|
|
|
|
|
|
|
|
loop {
|
2020-09-21 14:15:14 +00:00
|
|
|
std::thread::sleep(Duration::from_millis(10));
|
2020-09-02 22:58:11 +00:00
|
|
|
}
|
|
|
|
}
|