1mod constants; 2 3use crate::api::GameBoyEmulatorConfig; 4use bincode::{Decode, Encode}; 5use dsp::design::FilterType; 6use dsp::iir::FirstOrderIirFilter; 7use dsp::sinc::PerformanceSincResampler; 8use gb_config::GbAudioResampler; 9use jgenesis_common::audio::fir_resampler::{FirKernel, LpfCoefficients, StereoFirResampler}; 10use jgenesis_common::frontend::AudioOutput; 11use jgenesis_proc_macros::MatchEachVariantMacro; 12 13pub const GB_APU_FREQUENCY: f64 = (1 << 21) as f64; 14 15fn new_dc_offset_filter() -> FirstOrderIirFilter { 16 dsp::design::butterworth(5.0, GB_APU_FREQUENCY, FilterType::HighPass) 17} 18 19#[derive(Debug, Clone, Encode, Decode)] 20struct GbFirKernel; 21 22impl FirKernel<{ constants::LPF_TAPS }> for GbFirKernel { 23 #[inline] 24 fn lpf_coefficients() -> &'static LpfCoefficients<{ constants::LPF_TAPS }> { 25 constants::LPF_COEFFICIENTS 26 } 27} 28 29type GbFirResampler = StereoFirResampler<{ constants::LPF_TAPS }, GbFirKernel>; 30 31#[derive(Debug, Clone, Encode, Decode, MatchEachVariantMacro)] 32enum ResamplerImpl { 33 LowPassNearestNeighbor(GbFirResampler), 34 WindowedSinc(PerformanceSincResampler<2>), 35} 36 37impl ResamplerImpl { 38 fn collect(&mut self, samples: [f64; 2]) { 39 match_each_variant!(self, resampler => resampler.collect(samples)); 40 } 41 42 fn output_buffer_pop_front(&mut self) -> Option<[f64; 2]> { 43 match_each_variant!(self, resampler => resampler.output_buffer_pop_front()) 44 } 45 46 fn update_source_frequency(&mut self, source_frequency: f64) { 47 match_each_variant!(self, resampler => resampler.update_source_frequency(source_frequency)); 48 } 49 50 fn update_output_frequency(&mut self, output_frequency: u64) { 51 match_each_variant!(self, resampler => resampler.update_output_frequency(output_frequency as f64)); 52 } 53 54 fn resampler_type(&self) -> GbAudioResampler { 55 match self { 56 Self::LowPassNearestNeighbor { .. } => GbAudioResampler::LowPassNearestNeighbor, 57 Self::WindowedSinc { .. } => GbAudioResampler::WindowedSinc, 58 } 59 } 60} 61 62#[derive(Debug, Clone, Encode, Decode)] 63pub struct GameBoyResampler { 64 dc_offset_l: FirstOrderIirFilter, 65 dc_offset_r: FirstOrderIirFilter, 66 resampler: ResamplerImpl, 67 output_frequency: u64, 68} 69 70impl GameBoyResampler { 71 pub fn new(config: &GameBoyEmulatorConfig) -> Self { 72 let output_frequency = 48000; 73 Self { 74 dc_offset_l: new_dc_offset_filter(), 75 dc_offset_r: new_dc_offset_filter(), 76 resampler: create_resampler(config, output_frequency), 77 output_frequency, 78 } 79 } 80 81 pub fn collect_sample(&mut self, sample_l: f64, sample_r: f64) { 82 self.resampler 83 .collect([self.dc_offset_l.filter(sample_l), self.dc_offset_r.filter(sample_r)]); 84 } 85 86 pub fn output_samples<A: AudioOutput>(&mut self, audio_output: &mut A) -> Result<(), A::Err> { 87 while let Some([sample_l, sample_r]) = self.resampler.output_buffer_pop_front() { 88 audio_output.push_sample(sample_l, sample_r)?; 89 } 90 91 Ok(()) 92 } 93 94 pub fn reload_config(&mut self, config: &GameBoyEmulatorConfig) { 95 if config.audio_resampler != self.resampler.resampler_type() { 96 log::info!("Changing resampler type to {:?}", config.audio_resampler); 97 self.resampler = create_resampler(config, self.output_frequency); 98 } else { 99 self.resampler.update_source_frequency(gb_source_frequency(config.audio_60hz_hack)); 100 } 101 } 102 103 pub fn update_output_frequency(&mut self, output_frequency: u64) { 104 self.resampler.update_output_frequency(output_frequency); 105 self.output_frequency = output_frequency; 106 } 107} 108 109fn gb_source_frequency(audio_60hz_hack: bool) -> f64 { 110 if audio_60hz_hack { 111 // The Game Boy's precise refresh rate is 4.194304 MHz / (154 lines * 456 cycles/line) 112 // which is approximately 59.73 Hz. 113 // To target 60 FPS, pretend the APU is (60 / ~59.73) faster 114 GB_APU_FREQUENCY * 60.0 / (4_194_304.0 / (154.0 * 456.0)) 115 } else { 116 GB_APU_FREQUENCY 117 } 118} 119 120fn create_resampler(config: &GameBoyEmulatorConfig, output_frequency: u64) -> ResamplerImpl { 121 let source_frequency = gb_source_frequency(config.audio_60hz_hack); 122 123 match config.audio_resampler { 124 GbAudioResampler::LowPassNearestNeighbor => ResamplerImpl::LowPassNearestNeighbor( 125 GbFirResampler::new(source_frequency, output_frequency), 126 ), 127 GbAudioResampler::WindowedSinc => ResamplerImpl::WindowedSinc( 128 PerformanceSincResampler::new(source_frequency, output_frequency as f64), 129 ), 130 } 131}