ferretro/ferretro_components/src/provided/sdl2/audio.rs

117 lines
3.7 KiB
Rust

use crate::prelude::*;
use std::error::Error;
use std::path::Path;
use std::time::Duration;
use sdl2::Sdl;
use sdl2::audio::{AudioCallback, AudioDevice, AudioFormat, AudioSpec, AudioSpecDesired};
struct MySdlAudio {
audio_spec: AudioSpec,
audio_receiver: crossbeam_channel::Receiver<Vec<i16>>,
}
impl AudioCallback for MySdlAudio {
type Channel = i16;
fn callback(&mut self, out: &mut [Self::Channel]) {
if self.audio_spec.format == AudioFormat::S16LSB {
if let Ok(samples) = self.audio_receiver.recv_timeout(Duration::from_millis(500)) {
out.copy_from_slice(&samples[..out.len()]);
}
}
}
}
pub struct SimpleSdl2AudioComponent {
sample_rate: f64,
audio_buffer: Vec<i16>,
audio_spec: AudioSpec,
audio_device: AudioDevice<MySdlAudio>,
audio_sender: crossbeam_channel::Sender<Vec<i16>>,
}
impl RetroCallbacks for SimpleSdl2AudioComponent {
fn audio_sample(&mut self, left: i16, right: i16) {
self.audio_buffer.push(left);
self.audio_buffer.push(right);
self.send_audio_samples()
}
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize {
self.audio_buffer.extend(stereo_pcm);
self.send_audio_samples();
stereo_pcm.len()
}
fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option<bool> {
self.sample_rate = av_info.timing.sample_rate;
// TODO: change/reinitialize
Some(true)
}
}
impl RetroComponent for SimpleSdl2AudioComponent {
fn post_load_game(&mut self, _retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box<dyn Error>> {
self.audio_device.resume();
Ok(())
}
}
impl SimpleSdl2AudioComponent {
pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Self {
let mut sample_rate = retro.get_system_av_info().timing.sample_rate;
// HACK: some cores don't report this 'til we get an env call to set_system_av_info...
// which is too late for this constructor to pass along to SDL, so...
// less than ideal, but default to the old libsnes default value i guess.
if sample_rate == 0.0 {
sample_rate = 32040.0;
}
let (audio_sender, audio_receiver) = crossbeam_channel::bounded(2);
let audio = sdl_context.audio().unwrap();
let desired_spec = AudioSpecDesired {
freq: Some(sample_rate.round() as i32),
channels: Some(2),
samples: None,
};
let mut audio_spec = None;
let audio_device = audio
.open_playback(None, &desired_spec, |spec| {
if spec.format != AudioFormat::S16LSB {
eprintln!("unsupported audio format {:?}", spec.format);
}
audio_spec = Some(spec.clone());
MySdlAudio {
audio_spec: spec,
audio_receiver,
}
})
.unwrap();
SimpleSdl2AudioComponent {
sample_rate,
audio_buffer: Default::default(),
audio_spec: audio_spec.unwrap(),
audio_device,
audio_sender,
}
}
pub fn silence_buffer(&self) {
let _ = self.audio_sender.try_send(vec![0; self.audio_spec.samples as usize * self.audio_spec.channels as usize]);
}
fn send_audio_samples(&mut self) {
let stereo_samples = self.audio_spec.samples as usize * 2;
while self.audio_buffer.len() >= stereo_samples {
let remainder = self.audio_buffer.split_off(stereo_samples);
let msg = std::mem::replace(&mut self.audio_buffer, remainder);
let _ = self.audio_sender.try_send(msg);
}
}
}