audio.rsannotatedaudio.rssource131 lines · 4.5 KB · raw
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}