1use crate::config::CommonConfig; 2use jgenesis_common::audio::DynamicResamplingRate; 3use jgenesis_common::frontend::AudioOutput; 4use sdl3::AudioSubsystem; 5use sdl3::audio::{AudioCallback, AudioFormat, AudioSpec, AudioStream, AudioStreamWithCallback}; 6use std::cell::RefCell; 7use std::collections::VecDeque; 8use std::rc::Rc; 9use std::sync::{Arc, Mutex, MutexGuard}; 10use std::thread::Thread; 11use std::time::Duration; 12use std::{cmp, thread}; 13use thiserror::Error; 14 15// Always output in stereo 16const CHANNELS: i32 = 2; 17 18// Number of samples to buffer before locking and pushing to the audio queue 19const INTERNAL_AUDIO_BUFFER_LEN: usize = 16; 20 21#[derive(Debug, Error)] 22pub enum AudioError { 23 #[error("Error opening SDL3 audio stream: {0}")] 24 OpenStream(sdl3::Error), 25 #[error("Error pausing SDL3 audio stream: {0}")] 26 PauseStream(sdl3::Error), 27 #[error("Error pushing audio samples to SDL3 audio stream: {0}")] 28 QueueAudio(sdl3::Error), 29} 30 31pub type AudioResult<T> = Result<T, AudioError>; 32 33struct AudioCallbackState { 34 queue: VecDeque<(f32, f32)>, 35 last_sample: (f32, f32), 36 hardware_queue_size: u32, 37 unpark_threshold: u32, 38 emulator_thread: Thread, 39 error: Option<sdl3::Error>, 40} 41 42impl AudioCallbackState { 43 fn new(config: &CommonConfig) -> Self { 44 Self { 45 queue: VecDeque::with_capacity(2 * config.audio_buffer_size as usize), 46 last_sample: (0.0, 0.0), 47 hardware_queue_size: config.audio_hardware_queue_size, 48 unpark_threshold: audio_sync_threshold(config), 49 emulator_thread: thread::current(), 50 error: None, 51 } 52 } 53} 54 55struct AudioQueueCallback { 56 state: Arc<Mutex<AudioCallbackState>>, 57} 58 59impl AudioCallback<f32> for AudioQueueCallback { 60 fn callback(&mut self, stream: &mut AudioStream, requested: i32) { 61 if requested <= 0 { 62 return; 63 } 64 65 let mut state = self.state.lock().unwrap(); 66 67 let stereo_samples = cmp::max(state.hardware_queue_size, ((requested + 1) / 2) as u32); 68 for _ in 0..stereo_samples { 69 // Repeating the last sample on buffer underruns prevents awful noises for some systems (e.g. 32X) 70 let (sample_l, sample_r) = state.queue.pop_front().unwrap_or(state.last_sample); 71 state.last_sample = (sample_l, sample_r); 72 73 if let Err(err) = stream.put_data_f32(&[sample_l, sample_r]) { 74 log::error!("Error pushing audio samples: {err}"); 75 state.error = Some(err); 76 state.emulator_thread.unpark(); 77 break; 78 } 79 } 80 81 if state.queue.len() <= state.unpark_threshold as usize { 82 state.emulator_thread.unpark(); 83 } 84 } 85} 86 87pub struct SdlAudioOutputHandle { 88 audio_subsystem: Rc<RefCell<AudioSubsystem>>, 89 audio_stream: AudioStreamWithCallback<AudioQueueCallback>, 90 callback_state: Arc<Mutex<AudioCallbackState>>, 91 output_frequency: u64, 92} 93 94pub struct SdlAudioOutput { 95 callback_state: Arc<Mutex<AudioCallbackState>>, 96 muted: bool, 97 audio_buffer: Vec<(f32, f32)>, 98 audio_sync: bool, 99 audio_sync_threshold: u32, 100 dynamic_resampling_ratio_enabled: bool, 101 dynamic_resampling_rate: DynamicResamplingRate, 102 output_frequency: u64, 103 audio_buffer_size: u32, 104 audio_gain_multiplier: f64, 105 sample_count: u64, 106 speed_multiplier: u64, 107} 108 109impl SdlAudioOutputHandle { 110 pub fn reload_config(&mut self, config: &CommonConfig) -> AudioResult<()> { 111 let freq_changed = self.output_frequency != config.audio_output_frequency; 112 113 self.output_frequency = config.audio_output_frequency; 114 115 if freq_changed { 116 // Recreate audio stream on sample rate changes 117 118 log::info!("Recreating SDL audio queue with freq {}", config.audio_output_frequency); 119 120 self.audio_stream.pause().map_err(AudioError::PauseStream)?; 121 122 self.audio_stream = open_audio_stream( 123 &self.audio_subsystem.borrow(), 124 config, 125 Arc::clone(&self.callback_state), 126 )?; 127 } 128 129 { 130 let mut state = self.callback_state.lock().unwrap(); 131 state.hardware_queue_size = config.audio_hardware_queue_size; 132 state.unpark_threshold = audio_sync_threshold(config); 133 134 // Truncate audio queue on config reloads if it is way oversized OR if sample rate changed 135 if freq_changed || state.queue.len() >= (4 * config.audio_buffer_size) as usize { 136 state.queue.clear(); 137 } 138 } 139 140 Ok(()) 141 } 142 143 pub fn set_emulator_thread(&self, thread: Thread) { 144 self.callback_state.lock().unwrap().emulator_thread = thread; 145 } 146} 147 148impl SdlAudioOutput { 149 pub fn create_and_init( 150 audio_subsystem: Rc<RefCell<AudioSubsystem>>, 151 config: &CommonConfig, 152 ) -> AudioResult<(Self, SdlAudioOutputHandle)> { 153 let callback_state = Arc::new(Mutex::new(AudioCallbackState::new(config))); 154 155 let audio_stream = 156 open_audio_stream(&audio_subsystem.borrow(), config, Arc::clone(&callback_state))?; 157 158 let audio_output = Self { 159 muted: config.mute_audio, 160 callback_state: Arc::clone(&callback_state), 161 audio_buffer: Vec::with_capacity(INTERNAL_AUDIO_BUFFER_LEN), 162 audio_sync: config.audio_sync, 163 audio_sync_threshold: audio_sync_threshold(config), 164 dynamic_resampling_ratio_enabled: config.audio_dynamic_resampling_ratio, 165 dynamic_resampling_rate: DynamicResamplingRate::new( 166 config.audio_output_frequency as u32, 167 config.audio_buffer_size, 168 ), 169 output_frequency: config.audio_output_frequency, 170 audio_buffer_size: config.audio_buffer_size, 171 audio_gain_multiplier: decibels_to_multiplier(config.audio_gain_db), 172 sample_count: 0, 173 speed_multiplier: 1, 174 }; 175 176 let handle = SdlAudioOutputHandle { 177 audio_subsystem, 178 audio_stream, 179 callback_state, 180 output_frequency: config.audio_output_frequency, 181 }; 182 183 Ok((audio_output, handle)) 184 } 185 186 pub fn reload_config(&mut self, config: &CommonConfig) { 187 let freq_changed = self.output_frequency != config.audio_output_frequency; 188 let buffer_size_changed = self.audio_buffer_size != config.audio_buffer_size; 189 190 self.muted = config.mute_audio; 191 self.audio_sync = config.audio_sync; 192 self.dynamic_resampling_ratio_enabled = config.audio_dynamic_resampling_ratio; 193 self.output_frequency = config.audio_output_frequency; 194 self.audio_buffer_size = config.audio_buffer_size; 195 self.audio_gain_multiplier = decibels_to_multiplier(config.audio_gain_db); 196 self.audio_sync_threshold = audio_sync_threshold(config); 197 198 if freq_changed || buffer_size_changed { 199 self.dynamic_resampling_rate 200 .update_config(self.output_frequency as u32, self.audio_buffer_size); 201 } 202 } 203 204 pub fn set_speed_multiplier(&mut self, speed_multiplier: u64) { 205 self.speed_multiplier = speed_multiplier; 206 } 207 208 pub fn adjust_dynamic_resampling_ratio(&mut self) { 209 if !self.dynamic_resampling_ratio_enabled { 210 return; 211 } 212 213 let audio_queue_len = self.callback_state.lock().unwrap().queue.len(); 214 self.dynamic_resampling_rate.adjust(audio_queue_len as u32); 215 } 216 217 pub fn output_frequency(&self) -> u64 { 218 if self.dynamic_resampling_ratio_enabled { 219 self.dynamic_resampling_rate.current_output_frequency().into() 220 } else { 221 self.output_frequency 222 } 223 } 224} 225 226fn open_audio_stream( 227 audio: &AudioSubsystem, 228 config: &CommonConfig, 229 callback_state: Arc<Mutex<AudioCallbackState>>, 230) -> AudioResult<AudioStreamWithCallback<AudioQueueCallback>> { 231 let audio_callback = AudioQueueCallback { state: callback_state }; 232 233 let stream = audio 234 .open_playback_stream( 235 &AudioSpec { 236 freq: Some(config.audio_output_frequency as i32), 237 channels: Some(CHANNELS), 238 format: Some(AudioFormat::f32_sys()), 239 }, 240 audio_callback, 241 ) 242 .map_err(AudioError::OpenStream)?; 243 stream.resume().map_err(AudioError::OpenStream)?; 244 245 Ok(stream) 246} 247 248fn decibels_to_multiplier(decibels: f64) -> f64 { 249 10.0_f64.powf(decibels / 20.0) 250} 251 252impl AudioOutput for SdlAudioOutput { 253 type Err = AudioError; 254 255 #[inline] 256 fn push_sample(&mut self, mut sample_l: f64, mut sample_r: f64) -> Result<(), Self::Err> { 257 self.sample_count += 1; 258 if !self.sample_count.is_multiple_of(self.speed_multiplier) { 259 return Ok(()); 260 } 261 262 if self.muted { 263 sample_l = 0.0; 264 sample_r = 0.0; 265 } 266 267 sample_l *= self.audio_gain_multiplier; 268 sample_r *= self.audio_gain_multiplier; 269 270 self.audio_buffer.push((sample_l as f32, sample_r as f32)); 271 272 if self.audio_buffer.len() < INTERNAL_AUDIO_BUFFER_LEN { 273 return Ok(()); 274 } 275 276 let queue_threshold = self.audio_sync_threshold as usize; 277 278 let mut state_lock = if self.audio_sync { 279 perform_audio_sync(&self.callback_state, queue_threshold)? 280 } else { 281 let state_lock = self.callback_state.lock().unwrap(); 282 283 if state_lock.queue.len() > queue_threshold { 284 // Audio queue is full; drop samples 285 log::debug!("Dropping audio samples because buffer is full"); 286 self.audio_buffer.clear(); 287 return Ok(()); 288 } 289 290 state_lock 291 }; 292 293 state_lock.queue.extend(&self.audio_buffer); 294 let callback_error = state_lock.error.take(); 295 296 drop(state_lock); 297 298 self.audio_buffer.clear(); 299 300 match callback_error { 301 None => Ok(()), 302 Some(err) => Err(AudioError::QueueAudio(err)), 303 } 304 } 305} 306 307fn audio_sync_threshold(config: &CommonConfig) -> u32 { 308 if config.audio_dynamic_resampling_ratio { 309 // If dynamic resampling ratio is enabled, let the audio buffer grow to double size 310 // before dropping samples because the audio buffer size is also the target length 311 // for dynamic resampling 312 2 * config.audio_buffer_size 313 } else { 314 config.audio_buffer_size 315 } 316} 317 318fn perform_audio_sync( 319 state: &Arc<Mutex<AudioCallbackState>>, 320 queue_threshold: usize, 321) -> AudioResult<MutexGuard<'_, AudioCallbackState>> { 322 // Block until audio queue is not full 323 loop { 324 { 325 let mut state = state.lock().unwrap(); 326 327 if let Some(err) = state.error.take() { 328 return Err(AudioError::QueueAudio(err)); 329 } 330 331 if state.queue.len() <= queue_threshold { 332 return Ok(state); 333 } 334 } 335 336 thread::park_timeout(Duration::from_secs(1)); 337 } 338}