Skip to content
Snippets Groups Projects
Verified Commit e675fad1 authored by João Magalhães's avatar João Magalhães :rocket:
Browse files

feat: major improvement in audio structure

Made use of queue support for SDL avoiding the callback approach which greatly simplifies the code and increases the APU accuracy.
parent 52ca43d3
No related branches found
No related tags found
1 merge request!19Initial tentative audio support 🔉
Pipeline #2269 failed
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
......
......@@ -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();
......
......@@ -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()
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment