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

122 lines
4.0 KiB
Rust

use crate::prelude::*;
use std::error::Error;
use std::path::Path;
use sdl2::Sdl;
use sdl2::audio::{AudioCVT, AudioFormat, AudioQueue, AudioSpec, AudioSpecDesired};
use crate::base::ControlFlow;
use super::audio::resample;
pub struct Sdl2RateControlledAudioComponent {
src_freq: f64,
ratio: f64,
audio_buffer: Vec<i16>,
queue: AudioQueue<i16>,
avg_headroom: f64,
started: bool,
}
impl RetroCallbacks for Sdl2RateControlledAudioComponent {
fn video_refresh(&mut self, _frame: &VideoFrame) {
self.started = true;
}
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
if self.started {
self.audio_buffer.extend(stereo_pcm);
}
stereo_pcm.len() / 2
}
fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option<bool> {
if Self::make_converter(av_info.timing.sample_rate, self.queue.spec()).is_ok() {
self.src_freq = av_info.timing.sample_rate;
Some(true)
} else {
Some(false)
}
}
}
impl RetroComponent for Sdl2RateControlledAudioComponent {
fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box<dyn Error + Send + Sync>> {
self.src_freq = retro.get_system_av_info().timing.sample_rate;
self.queue.resume();
self.started = false;
Ok(())
}
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
if self.src_freq != 0.0 {
if self.started {
let sample_headroom = (self.queue.size() / 4) as f64;
self.avg_headroom = ((self.avg_headroom * 9.0) + sample_headroom) / 10.0;
self.avg_headroom /= 2.0;
let room = self.src_freq / 100.0;
let ratio_raw = ((room - self.avg_headroom) / room).clamp(-1.0, 1.0);
self.ratio = 1.0 + (ratio_raw * 0.01);
}
match Self::make_converter(self.src_freq * self.ratio, self.queue.spec()) {
Ok(converter) => {
if !self.audio_buffer.is_empty() {
let mut samples = std::mem::take(&mut self.audio_buffer);
samples = resample(&converter, samples);
self.audio_buffer = samples;
self.queue.queue(&self.audio_buffer);
self.audio_buffer.clear();
}
ControlFlow::Continue
}
Err(e) => {
eprintln!("Audio sample rate conversion failed: {:?}", e);
ControlFlow::Break
}
}
} else {
ControlFlow::Continue
}
}
}
impl Sdl2RateControlledAudioComponent {
pub fn new(sdl_context: &mut Sdl) -> crate::base::Result<Self> {
let audio = sdl_context.audio().unwrap();
let desired_spec = AudioSpecDesired {
freq: None,
channels: Some(2),
samples: None,
};
let queue = AudioQueue::open_queue(&audio, None, &desired_spec)?;
// default to the old libsnes default value until after load_game or set_system_av_info.
let src_freq = 32040.5;
Ok(Sdl2RateControlledAudioComponent {
src_freq,
ratio: 1.0,
audio_buffer: Default::default(),
queue,
avg_headroom: 0.0,
started: false,
})
}
fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result<AudioCVT, String> {
// note on the `* 16`: as long as the ratio between src_rate and dst_rate is right,
// we should be in the clear -- this is to make up for SDL not giving us floats for
// this, we can at least get some quasi-fixed-point precision going on...
AudioCVT::new(
AudioFormat::s16_sys(),
2,
(src_freq * 16.0).round() as i32,
dest_spec.format,
dest_spec.channels,
dest_spec.freq * 16,
)
}
}