experimental dynamic rate control feature in audio component (TBD: merge into SimpleSdl2Audio as an option)
This commit is contained in:
		
							parent
							
								
									72366248d7
								
							
						
					
					
						commit
						b2c2c93537
					
				
					 2 changed files with 50 additions and 20 deletions
				
			
		| 
						 | 
				
			
			@ -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(SimpleSdl2GamepadComponent::new(&mut sdl_context))?;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,13 +12,22 @@ 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 {
 | 
			
		||||
        self.audio_buffer.extend(stereo_pcm);
 | 
			
		||||
        if self.started {
 | 
			
		||||
            self.audio_buffer.extend(stereo_pcm);
 | 
			
		||||
        }
 | 
			
		||||
        stereo_pcm.len() / 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -33,25 +42,47 @@ impl RetroCallbacks 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.started = false;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
 | 
			
		||||
        if let Ok(converter) = Self::make_converter(self.src_freq, self.queue.spec()) {
 | 
			
		||||
            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();
 | 
			
		||||
        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
 | 
			
		||||
        }
 | 
			
		||||
        ControlFlow::Continue
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 desired_spec = AudioSpecDesired {
 | 
			
		||||
            freq: None,
 | 
			
		||||
| 
						 | 
				
			
			@ -61,31 +92,30 @@ impl Sdl2RateControlledAudioComponent {
 | 
			
		|||
 | 
			
		||||
        let queue = AudioQueue::open_queue(&audio, None, &desired_spec)?;
 | 
			
		||||
 | 
			
		||||
        let mut src_freq = 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,
 | 
			
		||||
        // so we can just default to the old libsnes default value.
 | 
			
		||||
        if src_freq == 0.0 {
 | 
			
		||||
            src_freq = 32040.5;
 | 
			
		||||
        }
 | 
			
		||||
        //  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 `* 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
 | 
			
		||||
        // this, we can at least get some quasi-fixed-point precision going on...
 | 
			
		||||
        AudioCVT::new(
 | 
			
		||||
            AudioFormat::s16_sys(),
 | 
			
		||||
            2,
 | 
			
		||||
            (src_freq * 64.0).round() as i32,
 | 
			
		||||
            (src_freq * 16.0).round() as i32,
 | 
			
		||||
            dest_spec.format,
 | 
			
		||||
            dest_spec.channels,
 | 
			
		||||
            dest_spec.freq * 64,
 | 
			
		||||
            dest_spec.freq * 16,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue