experimental dynamic rate control feature in audio component (TBD: merge into SimpleSdl2Audio as an option)
This commit is contained in:
parent
72366248d7
commit
b2c2c93537
|
@ -99,7 +99,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emu.register_component(SimpleSdl2AudioComponent::new(&mut sdl_context)?)?;
|
emu.register_component(Sdl2RateControlledAudioComponent::new(&mut sdl_context)?)?;
|
||||||
|
|
||||||
emu.register_component(SimpleSdl2KeyboardComponent::new(&mut sdl_context)?)?;
|
emu.register_component(SimpleSdl2KeyboardComponent::new(&mut sdl_context)?)?;
|
||||||
emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context))?;
|
emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context))?;
|
||||||
|
|
|
@ -12,13 +12,22 @@ use super::audio::resample;
|
||||||
|
|
||||||
pub struct Sdl2RateControlledAudioComponent {
|
pub struct Sdl2RateControlledAudioComponent {
|
||||||
src_freq: f64,
|
src_freq: f64,
|
||||||
|
ratio: f64,
|
||||||
audio_buffer: Vec<i16>,
|
audio_buffer: Vec<i16>,
|
||||||
queue: AudioQueue<i16>,
|
queue: AudioQueue<i16>,
|
||||||
|
avg_headroom: f64,
|
||||||
|
started: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RetroCallbacks for Sdl2RateControlledAudioComponent {
|
impl RetroCallbacks for Sdl2RateControlledAudioComponent {
|
||||||
|
fn video_refresh(&mut self, _frame: &VideoFrame) {
|
||||||
|
self.started = true;
|
||||||
|
}
|
||||||
|
|
||||||
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
|
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
|
||||||
|
if self.started {
|
||||||
self.audio_buffer.extend(stereo_pcm);
|
self.audio_buffer.extend(stereo_pcm);
|
||||||
|
}
|
||||||
stereo_pcm.len() / 2
|
stereo_pcm.len() / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,13 +42,26 @@ impl RetroCallbacks for Sdl2RateControlledAudioComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RetroComponent for Sdl2RateControlledAudioComponent {
|
impl RetroComponent for Sdl2RateControlledAudioComponent {
|
||||||
fn post_load_game(&mut self, _retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box<dyn Error + Send + Sync>> {
|
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.queue.resume();
|
||||||
|
self.started = false;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
|
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
|
||||||
if let Ok(converter) = Self::make_converter(self.src_freq, self.queue.spec()) {
|
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);
|
let mut samples = std::mem::take(&mut self.audio_buffer);
|
||||||
samples = resample(&converter, samples);
|
samples = resample(&converter, samples);
|
||||||
self.audio_buffer = samples;
|
self.audio_buffer = samples;
|
||||||
|
@ -48,10 +70,19 @@ impl RetroComponent for Sdl2RateControlledAudioComponent {
|
||||||
}
|
}
|
||||||
ControlFlow::Continue
|
ControlFlow::Continue
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Audio sample rate conversion failed: {:?}", e);
|
||||||
|
ControlFlow::Break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ControlFlow::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sdl2RateControlledAudioComponent {
|
impl Sdl2RateControlledAudioComponent {
|
||||||
pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(sdl_context: &mut Sdl) -> crate::base::Result<Self> {
|
||||||
let audio = sdl_context.audio().unwrap();
|
let audio = sdl_context.audio().unwrap();
|
||||||
let desired_spec = AudioSpecDesired {
|
let desired_spec = AudioSpecDesired {
|
||||||
freq: None,
|
freq: None,
|
||||||
|
@ -61,31 +92,30 @@ impl Sdl2RateControlledAudioComponent {
|
||||||
|
|
||||||
let queue = AudioQueue::open_queue(&audio, None, &desired_spec)?;
|
let queue = AudioQueue::open_queue(&audio, None, &desired_spec)?;
|
||||||
|
|
||||||
let mut src_freq = retro.get_system_av_info().timing.sample_rate;
|
// default to the old libsnes default value until after load_game or set_system_av_info.
|
||||||
// HACK: some cores don't report this 'til we get an env call to set_system_av_info,
|
let src_freq = 32040.5;
|
||||||
// so we can just default to the old libsnes default value.
|
|
||||||
if src_freq == 0.0 {
|
|
||||||
src_freq = 32040.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Sdl2RateControlledAudioComponent {
|
Ok(Sdl2RateControlledAudioComponent {
|
||||||
src_freq,
|
src_freq,
|
||||||
|
ratio: 1.0,
|
||||||
audio_buffer: Default::default(),
|
audio_buffer: Default::default(),
|
||||||
queue,
|
queue,
|
||||||
|
avg_headroom: 0.0,
|
||||||
|
started: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result<AudioCVT, String> {
|
fn make_converter(src_freq: f64, dest_spec: &AudioSpec) -> Result<AudioCVT, String> {
|
||||||
// note on the `* 64`: as long as the ratio between src_rate and dst_rate is right,
|
// 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
|
// 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...
|
// this, we can at least get some quasi-fixed-point precision going on...
|
||||||
AudioCVT::new(
|
AudioCVT::new(
|
||||||
AudioFormat::s16_sys(),
|
AudioFormat::s16_sys(),
|
||||||
2,
|
2,
|
||||||
(src_freq * 64.0).round() as i32,
|
(src_freq * 16.0).round() as i32,
|
||||||
dest_spec.format,
|
dest_spec.format,
|
||||||
dest_spec.channels,
|
dest_spec.channels,
|
||||||
dest_spec.freq * 64,
|
dest_spec.freq * 16,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue