audio.rsannotatedaudio.rssource351 lines · 12.2 KB · raw

Genesis audio resampling, filtering, and mixing code

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;
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;

-7 dB (10 ^ -7/20)

23const PSG_COEFFICIENT: f64 = 0.44668359215096315;

-6 dB (10 ^ -6/20)

26const PCM_COEFFICIENT: f64 = 0.5011872336272722;

-7 dB (10 ^ -7/20)

29const CD_COEFFICIENT: f64 = 0.44668359215096315;

-2 dB (10 ^ -2/20))

32const PWM_COEFFICIENT: f64 = 0.7943282347242815;
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    }

Push all samples that are ready to the given audio output.

Errors

Will propagate any error returned by the audio output while pushing samples. 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    }
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}