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>, writer: Option>, } struct MySdlAudio { must_resample: bool, reader: Consumer, } 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 { 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 { 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, Producer)> { 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)) } }