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}