audio.rsannotatedaudio.rssource351 lines · 12.2 KB · raw
1//! Genesis audio resampling, filtering, and mixing code
2
3use crate::api::GenesisHardware;
4use crate::bus::GenesisAudioOutput;
5use bincode::{Decode, Encode};
6use dsp::design::FilterType;
7use dsp::iir::{FirstOrderIirFilter, IirFilter, SecondOrderIirFilter};
8use dsp::sinc::{PerformanceSincResampler, QualitySincResampler};
9use genesis_config::{GenesisEmulatorConfig, LowPassSettings};
10use jgenesis_common::frontend::{AudioOutput, TimingMode};
11use s32x_core::api::Sega32XAudioOutput;
12use s32x_core::audio::PwmResampler;
13use segacd_core::api::SegaCdAudioOutput;
14use segacd_core::audio::SegaCdAudioFilter;
15
16const NTSC_GENESIS_MCLK_FREQUENCY: f64 = genesis_components::NTSC_GENESIS_MCLK_FREQUENCY;
17const PAL_GENESIS_MCLK_FREQUENCY: f64 = genesis_components::PAL_GENESIS_MCLK_FREQUENCY;
18
19const PCM_FREQUENCY: f64 = segacd_core::audio::PCM_FREQUENCY;
20const CD_DA_FREQUENCY: f64 = segacd_core::audio::CD_DA_FREQUENCY;
21
22// -7 dB (10 ^ -7/20)
23const PSG_COEFFICIENT: f64 = 0.44668359215096315;
24
25// -6 dB (10 ^ -6/20)
26const PCM_COEFFICIENT: f64 = 0.5011872336272722;
27
28// -7 dB (10 ^ -7/20)
29const CD_COEFFICIENT: f64 = 0.44668359215096315;
30
31// -2 dB (10 ^ -2/20))
32const PWM_COEFFICIENT: f64 = 0.7943282347242815;
33
34#[must_use]
35pub fn new_ym2612_low_pass<const N: usize>(timing_mode: TimingMode, cutoff: u32) -> IirFilter<N> {
36    dsp::design::butterworth(cutoff.into(), ym2612_frequency(timing_mode), FilterType::LowPass)
37}
38
39#[must_use]
40pub fn new_psg_low_pass(timing_mode: TimingMode, cutoff: u32) -> FirstOrderIirFilter {
41    dsp::design::butterworth(cutoff.into(), psg_frequency(timing_mode), FilterType::LowPass)
42}
43
44#[must_use]
45pub fn new_ym2612_dc_offset(timing_mode: TimingMode) -> FirstOrderIirFilter {
46    dsp::design::butterworth(5.0, ym2612_frequency(timing_mode), FilterType::HighPass)
47}
48
49#[must_use]
50pub fn new_psg_dc_offset(timing_mode: TimingMode) -> FirstOrderIirFilter {
51    dsp::design::butterworth(5.0, psg_frequency(timing_mode), FilterType::HighPass)
52}
53
54#[derive(Debug, Clone, Encode, Decode)]
55struct VolumeMultipliers {
56    ym2612: f64,
57    psg: f64,
58    pcm: f64,
59    cd: f64,
60    pwm: f64,
61}
62
63impl VolumeMultipliers {
64    fn from_config(config: &GenesisEmulatorConfig) -> Self {
65        Self {
66            ym2612: volume_multiplier(config.ym2612_enabled, config.ym2612_volume_adjustment_db),
67            psg: PSG_COEFFICIENT
68                * volume_multiplier(config.psg_enabled, config.psg_volume_adjustment_db),
69            pcm: PCM_COEFFICIENT
70                * volume_multiplier(
71                    config.sega_cd.pcm_enabled,
72                    config.sega_cd.pcm_volume_adjustment_db,
73                ),
74            cd: CD_COEFFICIENT
75                * volume_multiplier(
76                    config.sega_cd.cd_audio_enabled,
77                    config.sega_cd.cd_volume_adjustment_db,
78                ),
79            pwm: PWM_COEFFICIENT
80                * volume_multiplier(
81                    config.sega_32x.pwm_enabled,
82                    config.sega_32x.pwm_volume_adjustment_db,
83                ),
84        }
85    }
86}
87
88#[must_use]
89fn volume_multiplier(enabled: bool, adjustment_db: f64) -> f64 {
90    if !enabled {
91        return 0.0;
92    }
93
94    // Decibels to linear
95    10.0_f64.powf(adjustment_db / 20.0)
96}
97
98#[derive(Debug, Clone, Encode, Decode)]
99pub struct GenesisAudioFilter {
100    ym2612_dc_offset_l: FirstOrderIirFilter,
101    ym2612_dc_offset_r: FirstOrderIirFilter,
102    psg_dc_offset: FirstOrderIirFilter,
103    low_pass_settings: LowPassSettings,
104    ym2612_gen_low_pass_l: FirstOrderIirFilter,
105    ym2612_gen_low_pass_r: FirstOrderIirFilter,
106    ym2612_2nd_low_pass_l: SecondOrderIirFilter,
107    ym2612_2nd_low_pass_r: SecondOrderIirFilter,
108    psg_low_pass: FirstOrderIirFilter,
109}
110
111impl GenesisAudioFilter {
112    #[must_use]
113    pub fn new(timing_mode: TimingMode, low_pass_settings: LowPassSettings) -> Self {
114        Self {
115            ym2612_dc_offset_l: new_ym2612_dc_offset(timing_mode),
116            ym2612_dc_offset_r: new_ym2612_dc_offset(timing_mode),
117            psg_dc_offset: new_psg_dc_offset(timing_mode),
118            low_pass_settings,
119            ym2612_gen_low_pass_l: new_ym2612_low_pass(
120                timing_mode,
121                low_pass_settings.genesis_cutoff,
122            ),
123            ym2612_gen_low_pass_r: new_ym2612_low_pass(
124                timing_mode,
125                low_pass_settings.genesis_cutoff,
126            ),
127            ym2612_2nd_low_pass_l: new_ym2612_low_pass(
128                timing_mode,
129                low_pass_settings.ym2612_2nd_cutoff,
130            ),
131            ym2612_2nd_low_pass_r: new_ym2612_low_pass(
132                timing_mode,
133                low_pass_settings.ym2612_2nd_cutoff,
134            ),
135            psg_low_pass: new_psg_low_pass(timing_mode, low_pass_settings.genesis_cutoff),
136        }
137    }
138
139    #[must_use]
140    pub fn filter_ym2612(&mut self, (sample_l, sample_r): (f64, f64)) -> (f64, f64) {
141        let (mut sample_l, mut sample_r) =
142            (self.ym2612_dc_offset_l.filter(sample_l), self.ym2612_dc_offset_r.filter(sample_r));
143
144        if self.low_pass_settings.ym2612_2nd_enabled {
145            sample_l = self.ym2612_2nd_low_pass_l.filter(sample_l);
146            sample_r = self.ym2612_2nd_low_pass_r.filter(sample_r);
147        }
148
149        if self.low_pass_settings.genesis_enabled {
150            sample_l = self.ym2612_gen_low_pass_l.filter(sample_l);
151            sample_r = self.ym2612_gen_low_pass_r.filter(sample_r);
152        }
153
154        (sample_l, sample_r)
155    }
156
157    #[must_use]
158    pub fn filter_psg(&mut self, sample: f64) -> f64 {
159        let sample = self.psg_dc_offset.filter(sample);
160
161        if !self.low_pass_settings.genesis_enabled {
162            return sample;
163        }
164
165        self.psg_low_pass.filter(sample)
166    }
167
168    pub fn reload_config(&mut self, timing_mode: TimingMode, config: &GenesisEmulatorConfig) {
169        let low_pass_settings = LowPassSettings::from_config(config);
170        if self.low_pass_settings == low_pass_settings {
171            return;
172        }
173
174        *self = Self::new(timing_mode, low_pass_settings);
175    }
176}
177
178#[must_use]
179pub fn ym2612_frequency(timing_mode: TimingMode) -> f64 {
180    let genesis_mclk_frequency = match timing_mode {
181        TimingMode::Ntsc => NTSC_GENESIS_MCLK_FREQUENCY,
182        TimingMode::Pal => PAL_GENESIS_MCLK_FREQUENCY,
183    };
184
185    genesis_mclk_frequency / 7.0 / 6.0 / 24.0
186}
187
188#[must_use]
189pub fn psg_frequency(timing_mode: TimingMode) -> f64 {
190    let genesis_mclk_frequency = match timing_mode {
191        TimingMode::Ntsc => NTSC_GENESIS_MCLK_FREQUENCY,
192        TimingMode::Pal => PAL_GENESIS_MCLK_FREQUENCY,
193    };
194
195    genesis_mclk_frequency / 15.0 / 16.0
196}
197
198#[derive(Debug, Clone, Encode, Decode)]
199pub struct GenesisAudioResampler {
200    filter: GenesisAudioFilter,
201    sega_cd_filter: SegaCdAudioFilter,
202    ym2612_resampler: QualitySincResampler<2>,
203    psg_resampler: PerformanceSincResampler<1>,
204    pcm_resampler: QualitySincResampler<2>,
205    cd_resampler: QualitySincResampler<2>,
206    pwm_resampler: PwmResampler,
207    volumes: VolumeMultipliers,
208    sega_cd_present: bool,
209    s32x_present: bool,
210}
211
212impl GenesisAudioResampler {
213    #[must_use]
214    pub fn new(
215        hardware: GenesisHardware,
216        timing_mode: TimingMode,
217        config: &GenesisEmulatorConfig,
218    ) -> Self {
219        Self {
220            filter: GenesisAudioFilter::new(timing_mode, LowPassSettings::from_config(config)),
221            sega_cd_filter: SegaCdAudioFilter::new(config),
222            ym2612_resampler: QualitySincResampler::new(ym2612_frequency(timing_mode), 48000.0),
223            psg_resampler: PerformanceSincResampler::new(psg_frequency(timing_mode), 48000.0),
224            pcm_resampler: QualitySincResampler::new(PCM_FREQUENCY, 48000.0),
225            cd_resampler: QualitySincResampler::new(CD_DA_FREQUENCY, 48000.0),
226            pwm_resampler: PwmResampler::new(config, 48000),
227            volumes: VolumeMultipliers::from_config(config),
228            sega_cd_present: hardware.has_sega_cd(),
229            s32x_present: hardware.has_32x(),
230        }
231    }
232
233    /// Push all samples that are ready to the given audio output.
234    ///
235    /// # Errors
236    ///
237    /// Will propagate any error returned by the audio output while pushing samples.
238    // Should never panic, the unwraps are guarded by length checks
239    #[allow(clippy::missing_panics_doc)]
240    pub fn output_samples<A: AudioOutput>(&mut self, audio_output: &mut A) -> Result<(), A::Err> {
241        let sample_count = [
242            self.ym2612_resampler.output_buffer_len(),
243            self.psg_resampler.output_buffer_len(),
244            if self.sega_cd_present { self.pcm_resampler.output_buffer_len() } else { usize::MAX },
245            if self.sega_cd_present { self.cd_resampler.output_buffer_len() } else { usize::MAX },
246            if self.s32x_present { self.pwm_resampler.output_buffer_len() } else { usize::MAX },
247        ]
248        .into_iter()
249        .min()
250        .unwrap();
251
252        for _ in 0..sample_count {
253            let [ym2612_l, ym2612_r] = self
254                .ym2612_resampler
255                .output_buffer_pop_front()
256                .unwrap()
257                .map(|sample| sample * self.volumes.ym2612);
258            let [psg] = self
259                .psg_resampler
260                .output_buffer_pop_front()
261                .unwrap()
262                .map(|sample| sample * self.volumes.psg);
263
264            let [pcm_l, pcm_r] = if self.sega_cd_present {
265                self.pcm_resampler
266                    .output_buffer_pop_front()
267                    .unwrap()
268                    .map(|sample| sample * self.volumes.pcm)
269            } else {
270                [0.0; 2]
271            };
272
273            let [cd_l, cd_r] = if self.sega_cd_present {
274                self.cd_resampler
275                    .output_buffer_pop_front()
276                    .unwrap()
277                    .map(|sample| sample * self.volumes.cd)
278            } else {
279                [0.0; 2]
280            };
281
282            let [pwm_l, pwm_r] = if self.s32x_present {
283                self.pwm_resampler
284                    .output_buffer_pop_front()
285                    .unwrap()
286                    .map(|sample| sample * self.volumes.pwm)
287            } else {
288                [0.0; 2]
289            };
290
291            let sample_l = (ym2612_l + psg + pcm_l + cd_l + pwm_l).clamp(-1.0, 1.0);
292            let sample_r = (ym2612_r + psg + pcm_r + cd_r + pwm_r).clamp(-1.0, 1.0);
293
294            audio_output.push_sample(sample_l, sample_r)?;
295        }
296
297        Ok(())
298    }
299
300    pub fn reload_config(&mut self, timing_mode: TimingMode, config: &GenesisEmulatorConfig) {
301        self.volumes = VolumeMultipliers::from_config(config);
302
303        self.filter.reload_config(timing_mode, config);
304        self.sega_cd_filter.reload_config(config);
305        self.pwm_resampler.reload_config(config);
306    }
307
308    pub fn update_output_frequency(&mut self, output_frequency: u64) {
309        let output_frequency = output_frequency as f64;
310
311        self.ym2612_resampler.update_output_frequency(output_frequency);
312        self.psg_resampler.update_output_frequency(output_frequency);
313        self.pcm_resampler.update_output_frequency(output_frequency);
314        self.cd_resampler.update_output_frequency(output_frequency);
315        self.pwm_resampler.update_output_frequency(output_frequency as u64);
316    }
317}
318
319impl GenesisAudioOutput for GenesisAudioResampler {
320    fn collect_ym2612(&mut self, sample: (f64, f64)) {
321        let (sample_l, sample_r) = self.filter.filter_ym2612(sample);
322        self.ym2612_resampler.collect([sample_l, sample_r]);
323    }
324
325    fn collect_psg(&mut self, sample: f64) {
326        let sample = self.filter.filter_psg(sample);
327        self.psg_resampler.collect([sample]);
328    }
329}
330
331impl SegaCdAudioOutput for GenesisAudioResampler {
332    fn collect_pcm(&mut self, sample: (f64, f64)) {
333        let (sample_l, sample_r) = self.sega_cd_filter.filter_pcm(sample);
334        self.pcm_resampler.collect([sample_l, sample_r]);
335    }
336
337    fn collect_cd(&mut self, sample: (f64, f64)) {
338        let (sample_l, sample_r) = self.sega_cd_filter.filter_cd_da(sample);
339        self.cd_resampler.collect([sample_l, sample_r]);
340    }
341}
342
343impl Sega32XAudioOutput for GenesisAudioResampler {
344    fn collect_pwm(&mut self, (sample_l, sample_r): (f64, f64)) {
345        self.pwm_resampler.collect_sample(sample_l, sample_r);
346    }
347
348    fn update_pwm_source_frequency(&mut self, frequency: f64) {
349        self.pwm_resampler.update_source_frequency(frequency);
350    }
351}