diff --git a/frontends/sdl/src/audio.rs b/frontends/sdl/src/audio.rs index cb0282620847480f24cf740543db808745671701..94e897f245a65bd9f2ab95ba469388fd60d81e24 100644 --- a/frontends/sdl/src/audio.rs +++ b/frontends/sdl/src/audio.rs @@ -1,78 +1,26 @@ -use boytacean::gb::{AudioProvider, GameBoy}; use sdl2::{ - audio::{AudioCallback, AudioDevice, AudioSpec, AudioSpecDesired}, + audio::{AudioQueue, AudioSpecDesired}, AudioSubsystem, Sdl, }; -use std::sync::{Arc, Mutex}; - -pub struct AudioWave { - /// Specification of the audion settings that have been put in place - /// for the playing of this audio wave. - spec: AudioSpec, - - /// The object that is going to be used as the provider of the audio - /// operation. - audio_provider: Arc<Mutex<Box<GameBoy>>>, - - /// The number of audio ticks that have passed since the beginning - /// of the audio playback, the value wraps around (avoids overflow). - ticks: usize, -} - -impl AudioCallback for AudioWave { - type Channel = f32; - - fn callback(&mut self, out: &mut [f32]) { - self.ticks = self.ticks.wrapping_add(out.len() as usize); - - out.fill(0.0); - - match self.audio_provider.try_lock() { - Ok(provider) => { - for (place, data) in out.iter_mut().zip(provider.output_buffer_apu().iter()) { - *place = *data as f32 / 7.0; - } - } - Err(_) => (), - } - - self.audio_provider.lock().unwrap().clear_buffer_apu(); - - /* - for x in out.iter_mut() { - *x = match self.audio_provider.lock() { - Ok(mut provider) => { - let value = provider.output_clock_apu(1, self.spec.freq as u32) as f32 / 7.0; - value - } - Err(_) => 0.0, - } - }*/ - } -} pub struct Audio { - pub device: AudioDevice<AudioWave>, + pub device: AudioQueue<f32>, pub audio_subsystem: AudioSubsystem, } impl Audio { - pub fn new(sdl: &Sdl, audio_provider: Arc<Mutex<Box<GameBoy>>>) -> Self { + pub fn new(sdl: &Sdl) -> Self { let audio_subsystem = sdl.audio().unwrap(); let desired_spec = AudioSpecDesired { freq: Some(44100), channels: Some(1), - samples: None, + samples: Some(4096), }; - let device = audio_subsystem - .open_playback(None, &desired_spec, |spec| AudioWave { - spec: spec, - audio_provider: audio_provider, - ticks: 0, - }) - .unwrap(); + // creates the queue that is going to be used to update the + // audio stream with new values during the main loop + let device = audio_subsystem.open_queue(None, &desired_spec).unwrap(); // starts the playback by resuming the audio // device's activity diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index 2d97c7c75c0255bb530e09b7614184f90db2e4fb..a5cf1c02ca0951cbd86bd684f42b18361ad5780a 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -6,17 +6,13 @@ pub mod graphics; use audio::Audio; use boytacean::{ - gb::GameBoy, + gb::{AudioProvider, GameBoy}, pad::PadKey, ppu::{PaletteInfo, PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH}, }; use graphics::{surface_from_bytes, Graphics}; use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl}; -use std::{ - cmp::max, - sync::{Arc, Mutex}, - time::SystemTime, -}; +use std::{cmp::max, time::SystemTime}; /// The scale at which the screen is going to be drawn /// meaning the ratio between Game Boy resolution and @@ -43,7 +39,7 @@ impl Default for Benchmark { } pub struct Emulator { - system: Arc<Mutex<Box<GameBoy>>>, + system: GameBoy, graphics: Option<Graphics>, audio: Option<Audio>, logic_frequency: u32, @@ -56,7 +52,7 @@ pub struct Emulator { } impl Emulator { - pub fn new(system: Arc<Mutex<Box<GameBoy>>>) -> Self { + pub fn new(system: GameBoy) -> Self { Self { system, graphics: None, @@ -99,10 +95,10 @@ impl Emulator { } } - pub fn start(&mut self, screen_scale: f32, audio_provider: Arc<Mutex<Box<GameBoy>>>) { + pub fn start(&mut self, screen_scale: f32) { let sdl = sdl2::init().unwrap(); self.start_graphics(&sdl, screen_scale); - self.start_audio(&sdl, audio_provider); + self.start_audio(&sdl); } pub fn start_graphics(&mut self, sdl: &Sdl, screen_scale: f32) { @@ -117,13 +113,12 @@ impl Emulator { )); } - pub fn start_audio(&mut self, sdl: &Sdl, audio_provider: Arc<Mutex<Box<GameBoy>>>) { - self.audio = Some(Audio::new(sdl, audio_provider)); + pub fn start_audio(&mut self, sdl: &Sdl) { + self.audio = Some(Audio::new(sdl)); } pub fn load_rom(&mut self, path: &str) { - let mut system = self.system.lock().unwrap(); - let rom = system.load_rom_file(path); + let rom = self.system.load_rom_file(path); println!( "========= Cartridge =========\n{}\n=============================", rom @@ -145,7 +140,7 @@ impl Emulator { let initial = SystemTime::now(); for _ in 0..count { - cycles += self.system.lock().unwrap().clock() as u32; + cycles += self.system.clock() as u32; } let delta = initial.elapsed().unwrap().as_millis() as f32 / 1000.0; @@ -159,8 +154,6 @@ impl Emulator { pub fn toggle_palette(&mut self) { self.system - .lock() - .unwrap() .ppu() .set_palette_colors(self.palettes[self.palette_index].colors()); self.palette_index = (self.palette_index + 1) % self.palettes.len(); @@ -239,7 +232,7 @@ impl Emulator { .. } => { if let Some(key) = key_to_pad(keycode) { - self.system.lock().unwrap().key_press(key) + self.system.key_press(key) } } Event::KeyUp { @@ -247,12 +240,12 @@ impl Emulator { .. } => { if let Some(key) = key_to_pad(keycode) { - self.system.lock().unwrap().key_lift(key) + self.system.key_lift(key) } } Event::DropFile { filename, .. } => { - self.system.lock().unwrap().reset(); - self.system.lock().unwrap().load_boot_default(); + self.system.reset(); + self.system.load_boot_default(); self.load_rom(&filename); } _ => (), @@ -283,33 +276,44 @@ impl Emulator { break; } + // runs the Game Boy clock, this operation should + // include the advance of both the CPU, PPU, APU + // and any other frequency based component of the system + counter_cycles += self.system.clock() as u32; + + // in case a V-Blank state has been reached a new frame is available + // then the frame must be pushed into SDL for display + if self.system.ppu_mode() == PpuMode::VBlank + && self.system.ppu_frame() != last_frame { - // obtains a locked reference to the system that is going to be - // valid under the current block - let mut system = self.system.lock().unwrap(); - - // runs the Game Boy clock, this operation should - // include the advance of both the CPU, PPU, APU - // and any other frequency based component of the system - counter_cycles += system.clock() as u32; - - // in case a V-Blank state has been reached a new frame is available - // then the frame must be pushed into SDL for display - if system.ppu_mode() == PpuMode::VBlank && system.ppu_frame() != last_frame - { - // obtains the frame buffer of the Game Boy PPU and uses it - // to update the stream texture, that will latter be copied - // to the canvas - let frame_buffer = system.frame_buffer().as_ref(); - texture - .update(None, frame_buffer, DISPLAY_WIDTH * 3) - .unwrap(); - - // obtains the index of the current PPU frame, this value - // is going to be used to detect for new frame presence - last_frame = system.ppu_frame(); - } + // obtains the frame buffer of the Game Boy PPU and uses it + // to update the stream texture, that will latter be copied + // to the canvas + let frame_buffer = self.system.frame_buffer().as_ref(); + texture + .update(None, frame_buffer, DISPLAY_WIDTH * 3) + .unwrap(); + + // obtains the index of the current PPU frame, this value + // is going to be used to detect for new frame presence + last_frame = self.system.ppu_frame(); } + + // obtains the new audio buffer and queues it into the audio + // subsystem ready to be processed + let audio_buffer = self + .system + .audio_buffer() + .iter() + .map(|v| *v as f32 / 7.0) + .collect::<Vec<f32>>(); + self.audio + .as_mut() + .unwrap() + .device + .queue_audio(&audio_buffer) + .unwrap(); + self.system.clear_audio_buffer(); } // in case there's at least one new frame that was drawn during @@ -370,16 +374,14 @@ impl Emulator { fn main() { // creates a new Game Boy instance and loads both the boot ROM // and the initial game ROM to "start the engine" - let game_boy = Arc::new(Mutex::new(Box::new(GameBoy::new()))); - game_boy.try_lock().unwrap().as_mut().load_boot_default(); + let mut game_boy = GameBoy::new(); + game_boy.load_boot_default(); - // creates a new generic emulator structure loads the default + // creates a new generic emulator structure then starts + // both the video and audio sub-systems, loads default // ROM file and starts running it let mut emulator = Emulator::new(game_boy); - - let game_boy_ref = emulator.system.clone(); - - emulator.start(SCREEN_SCALE, game_boy_ref); + emulator.start(SCREEN_SCALE); emulator.load_rom("../../res/roms/pocket.gb"); emulator.toggle_palette(); emulator.run(); diff --git a/src/gb.rs b/src/gb.rs index 8861dc704d0672d355f0a71f226f0fba2d04f845..7bb4b788470144b6a28487fcf5df738f038c2917 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -56,8 +56,8 @@ pub struct Registers { pub trait AudioProvider { fn output_apu(&self) -> u8; fn output_clock_apu(&mut self, cycles: u8, freq: u32) -> u8; - fn output_buffer_apu(&self) -> &Vec<u8>; - fn clear_buffer_apu(&mut self); + fn audio_buffer(&self) -> &Vec<u8>; + fn clear_audio_buffer(&mut self); } #[cfg_attr(feature = "wasm", wasm_bindgen)] @@ -393,11 +393,11 @@ impl AudioProvider for GameBoy { self.apu_i().output() } - fn output_buffer_apu(&self) -> &Vec<u8> { + fn audio_buffer(&self) -> &Vec<u8> { self.apu_i().output_buffer() } - fn clear_buffer_apu(&mut self) { + fn clear_audio_buffer(&mut self) { self.apu().clear_buffer() } }