1#![allow(clippy::excessive_precision)] 2 3mod constants; 4 5use crate::api::NesEmulatorConfig; 6use bincode::{Decode, Encode}; 7use dsp::design::FilterType; 8use dsp::iir::FirstOrderIirFilter; 9use dsp::sinc::PerformanceSincResampler; 10use jgenesis_common::audio::fir_resampler::{FirKernel, LpfCoefficients, MonoFirResampler}; 11use jgenesis_common::frontend::{AudioOutput, TimingMode}; 12use jgenesis_proc_macros::MatchEachVariantMacro; 13use nes_config::NesAudioResampler; 14 15// 236.25MHz / 11 / 12 16const NTSC_NES_AUDIO_FREQUENCY: f64 = 1789772.7272727272727273; 17pub const NTSC_NES_NATIVE_DISPLAY_RATE: f64 = 60.0988; 18 19// 26.6017125.MHz / 16 20const PAL_NES_AUDIO_FREQUENCY: f64 = 1662607.03125; 21pub const PAL_NES_NATIVE_DISPLAY_RATE: f64 = 50.0070; 22 23trait TimingModeAudioExt { 24 fn nes_audio_frequency(self) -> f64; 25 26 fn nes_native_display_rate(self) -> f64; 27 28 fn refresh_rate_multiplier(self) -> f64; 29} 30 31impl TimingModeAudioExt for TimingMode { 32 fn nes_audio_frequency(self) -> f64 { 33 match self { 34 Self::Ntsc => NTSC_NES_AUDIO_FREQUENCY, 35 Self::Pal => PAL_NES_AUDIO_FREQUENCY, 36 } 37 } 38 39 fn nes_native_display_rate(self) -> f64 { 40 match self { 41 Self::Ntsc => NTSC_NES_NATIVE_DISPLAY_RATE, 42 Self::Pal => PAL_NES_NATIVE_DISPLAY_RATE, 43 } 44 } 45 46 fn refresh_rate_multiplier(self) -> f64 { 47 match self { 48 Self::Ntsc => 1.0, 49 Self::Pal => 50.0 / 60.0, 50 } 51 } 52} 53 54fn compute_source_frequency(timing_mode: TimingMode, apply_refresh_rate_adjustment: bool) -> f64 { 55 let refresh_rate_multiplier = if apply_refresh_rate_adjustment { 56 timing_mode.refresh_rate_multiplier() * 60.0 / timing_mode.nes_native_display_rate() 57 } else { 58 1.0 59 }; 60 61 timing_mode.nes_audio_frequency() * refresh_rate_multiplier 62} 63 64fn new_dc_offset_filter(timing_mode: TimingMode) -> FirstOrderIirFilter { 65 dsp::design::butterworth(5.0, timing_mode.nes_audio_frequency(), FilterType::HighPass) 66} 67 68#[derive(Debug, Clone, Encode, Decode)] 69struct NesFirKernel; 70 71impl FirKernel<{ constants::LPF_TAPS }> for NesFirKernel { 72 #[inline] 73 fn lpf_coefficients() -> &'static LpfCoefficients<{ constants::LPF_TAPS }> { 74 constants::LPF_COEFFICIENTS 75 } 76} 77 78type NesFirResampler = MonoFirResampler<{ constants::LPF_TAPS }, NesFirKernel>; 79 80#[derive(Debug, Clone, Encode, Decode, MatchEachVariantMacro)] 81enum ResamplerImpl { 82 LowPassNearestNeighbor(NesFirResampler), 83 WindowedSinc(PerformanceSincResampler<1>), 84} 85 86impl ResamplerImpl { 87 fn collect(&mut self, sample: f64) { 88 match_each_variant!(self, resampler => resampler.collect([sample])); 89 } 90 91 fn output_buffer_pop_front(&mut self) -> Option<[f64; 1]> { 92 match_each_variant!(self, resampler => resampler.output_buffer_pop_front()) 93 } 94 95 fn update_source_frequency(&mut self, source_frequency: f64) { 96 match_each_variant!(self, resampler => resampler.update_source_frequency(source_frequency)); 97 } 98 99 fn update_output_frequency(&mut self, output_frequency: u64) { 100 match_each_variant!(self, resampler => resampler.update_output_frequency(output_frequency as f64)); 101 } 102 103 fn resampler_type(&self) -> NesAudioResampler { 104 match self { 105 Self::LowPassNearestNeighbor { .. } => NesAudioResampler::LowPassNearestNeighbor, 106 Self::WindowedSinc { .. } => NesAudioResampler::WindowedSinc, 107 } 108 } 109} 110 111#[derive(Debug, Clone, Encode, Decode)] 112pub struct AudioResampler { 113 timing_mode: TimingMode, 114 dc_offset_filter: FirstOrderIirFilter, 115 resampler: ResamplerImpl, 116 output_frequency: u64, 117} 118 119impl AudioResampler { 120 pub fn new(timing_mode: TimingMode, config: &NesEmulatorConfig) -> Self { 121 let source_frequency = 122 compute_source_frequency(timing_mode, config.audio_refresh_rate_adjustment); 123 let output_frequency = 48000; 124 let resampler = 125 create_audio_resampler(config.audio_resampler, source_frequency, output_frequency); 126 127 Self { 128 timing_mode, 129 dc_offset_filter: new_dc_offset_filter(timing_mode), 130 resampler, 131 output_frequency, 132 } 133 } 134 135 pub fn collect_sample(&mut self, sample: f64) { 136 self.resampler.collect(self.dc_offset_filter.filter(sample)); 137 } 138 139 pub fn output_samples<A: AudioOutput>(&mut self, audio_output: &mut A) -> Result<(), A::Err> { 140 while let Some([sample]) = self.resampler.output_buffer_pop_front() { 141 audio_output.push_sample(sample, sample)?; 142 } 143 144 Ok(()) 145 } 146 147 pub fn reload_config(&mut self, config: &NesEmulatorConfig) { 148 let source_frequency = 149 compute_source_frequency(self.timing_mode, config.audio_refresh_rate_adjustment); 150 if self.resampler.resampler_type() != config.audio_resampler { 151 log::info!("Changing resampler type to {:?}", config.audio_resampler); 152 self.resampler = create_audio_resampler( 153 config.audio_resampler, 154 source_frequency, 155 self.output_frequency, 156 ); 157 } else { 158 self.resampler.update_source_frequency(source_frequency); 159 } 160 } 161 162 pub fn update_output_frequency(&mut self, output_frequency: u64) { 163 self.resampler.update_output_frequency(output_frequency); 164 self.output_frequency = output_frequency; 165 } 166} 167 168fn create_audio_resampler( 169 resampler_type: NesAudioResampler, 170 source_frequency: f64, 171 output_frequency: u64, 172) -> ResamplerImpl { 173 match resampler_type { 174 NesAudioResampler::LowPassNearestNeighbor => ResamplerImpl::LowPassNearestNeighbor( 175 NesFirResampler::new(source_frequency, output_frequency), 176 ), 177 NesAudioResampler::WindowedSinc => ResamplerImpl::WindowedSinc( 178 PerformanceSincResampler::new(source_frequency, output_frequency as f64), 179 ), 180 } 181}