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