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}