110 lines
3.4 KiB
Rust
110 lines
3.4 KiB
Rust
use crate::prelude::*;
|
|
|
|
use std::path::Path;
|
|
|
|
use sdl2::Sdl;
|
|
use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired};
|
|
use ringbuf::{Consumer, Producer, RingBuffer};
|
|
use ferretro_base::retro::ffi::SystemTiming;
|
|
|
|
pub struct SimpleSdl2AudioThreadComponent {
|
|
sdl_audio: sdl2::AudioSubsystem,
|
|
device: Option<AudioDevice<MySdlAudio>>,
|
|
writer: Option<Producer<i16>>,
|
|
}
|
|
|
|
struct MySdlAudio {
|
|
must_resample: bool,
|
|
reader: Consumer<i16>,
|
|
}
|
|
|
|
impl AudioCallback for MySdlAudio {
|
|
type Channel = i16;
|
|
|
|
fn callback(&mut self, data: &mut [Self::Channel]) {
|
|
if self.must_resample {
|
|
todo!("correct software resampling")
|
|
}
|
|
let buf_len = data.len();
|
|
|
|
let mut filled = self.reader.pop_slice(data);
|
|
if filled == 0 {
|
|
data.fill(0);
|
|
} else {
|
|
while filled < buf_len {
|
|
let remaining = buf_len - filled;
|
|
if remaining < filled {
|
|
data.copy_within(..remaining, filled);
|
|
} else {
|
|
data.copy_within(..filled, filled);
|
|
}
|
|
filled *= 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RetroCallbacks for SimpleSdl2AudioThreadComponent {
|
|
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
|
|
if let Some(writer) = &mut self.writer {
|
|
writer.push_slice(stereo_pcm);
|
|
}
|
|
stereo_pcm.len() / 2
|
|
}
|
|
|
|
fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option<bool> {
|
|
let timing = &av_info.timing;
|
|
let (device, writer) = Self::new_device_from_timing(&self.sdl_audio, timing).ok()?;
|
|
self.device = Some(device);
|
|
self.writer = Some(writer);
|
|
Some(true)
|
|
}
|
|
}
|
|
|
|
impl RetroComponent for SimpleSdl2AudioThreadComponent {
|
|
fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> crate::base::Result<()> {
|
|
let timing = &retro.get_system_av_info().timing;
|
|
let (device, writer) = Self::new_device_from_timing(&self.sdl_audio, timing)?;
|
|
self.device = Some(device);
|
|
self.writer = Some(writer);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SimpleSdl2AudioThreadComponent {
|
|
pub fn new(sdl_context: &mut Sdl) -> crate::base::Result<Self> {
|
|
Ok(SimpleSdl2AudioThreadComponent {
|
|
sdl_audio: sdl_context.audio()?,
|
|
device: None,
|
|
writer: None,
|
|
})
|
|
}
|
|
|
|
fn new_device_from_timing(audio: &sdl2::AudioSubsystem, timing: &SystemTiming) -> crate::base::Result<(AudioDevice<MySdlAudio>, Producer<i16>)> {
|
|
const CHANNELS: usize = 2;
|
|
const BUF_FRAMES: usize = 4;
|
|
let samples_per_frame = timing.sample_rate / timing.fps;
|
|
let buf_len = samples_per_frame.ceil() as usize * CHANNELS * BUF_FRAMES * 2;
|
|
let (writer, reader) = RingBuffer::new(buf_len).split();
|
|
|
|
let desired_spec = AudioSpecDesired {
|
|
freq: Some(timing.sample_rate.round() as i32),
|
|
channels: Some(CHANNELS as u8),
|
|
samples: Some(samples_per_frame.ceil() as u16 * BUF_FRAMES as u16),
|
|
};
|
|
|
|
let device = audio.open_playback(
|
|
None,
|
|
&desired_spec,
|
|
move |spec| {
|
|
let must_resample = { spec.freq != desired_spec.freq.unwrap() };
|
|
MySdlAudio {
|
|
must_resample, // todo: include freqs from & to
|
|
reader,
|
|
}
|
|
})?;
|
|
device.resume();
|
|
Ok((device, writer))
|
|
}
|
|
}
|