From 1b85628a401aebd0147cf1f3711cb84324eb27a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 27 Feb 2023 15:39:00 +0000
Subject: [PATCH] feat: initial kind of working audio for CH 2 This a very work
 in progress situation that requires stability. The most important part is the
 fact that it's possible for an external entity (SDL) to call an audio tick
 and get a volume.

---
 frontends/sdl/src/audio.rs |  45 ++++++-----
 frontends/sdl/src/main.rs  | 154 ++++++++++++++++++++++++-------------
 src/apu.rs                 |  55 +++++++++++--
 src/gb.rs                  |  16 +++-
 4 files changed, 186 insertions(+), 84 deletions(-)

diff --git a/frontends/sdl/src/audio.rs b/frontends/sdl/src/audio.rs
index 7a41d23a..2b5480bc 100644
--- a/frontends/sdl/src/audio.rs
+++ b/frontends/sdl/src/audio.rs
@@ -1,32 +1,38 @@
+use boytacean::gb::{AudioProvider, GameBoy};
 use sdl2::{
-    audio::{AudioCallback, AudioDevice, AudioSpecDesired},
+    audio::{AudioCallback, AudioDevice, AudioSpec, AudioSpecDesired},
     AudioSubsystem, Sdl,
 };
+use std::sync::{Arc, Mutex};
 
 pub struct AudioWave {
-    phase_inc: f32,
+    /// Specification of the audion settings that have been put in place
+    /// for the playing of this audio wave.
+    spec: AudioSpec,
 
-    phase: f32,
+    /// The object that is going to be used as the provider of the audio
+    /// operation.
+    audio_provider: Arc<Mutex<Box<GameBoy>>>,
 
-    volume: f32,
-
-    /// The relative amount of time (as a percentage decimal) the low level
-    /// is going to be present during a period (cycle).
-    /// From [Wikipedia](https://en.wikipedia.org/wiki/Duty_cycle).
-    duty_cycle: f32,
+    /// 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);
+
         for x in out.iter_mut() {
-            *x = if self.phase < (1.0 - self.duty_cycle) {
-                self.volume
-            } else {
-                -self.volume
-            };
-            self.phase = (self.phase + self.phase_inc) % 1.0;
+            *x = match self.audio_provider.lock() {
+                Ok(mut provider) => {
+                    let value = provider.tick_apu(self.spec.freq as u32) as f32 / 7.0;
+                    value
+                }
+                Err(_) => 0.0,
+            }
         }
     }
 }
@@ -37,7 +43,7 @@ pub struct Audio {
 }
 
 impl Audio {
-    pub fn new(sdl: &Sdl) -> Self {
+    pub fn new(sdl: &Sdl, audio_provider: Arc<Mutex<Box<GameBoy>>>) -> Self {
         let audio_subsystem = sdl.audio().unwrap();
 
         let desired_spec = AudioSpecDesired {
@@ -48,10 +54,9 @@ impl Audio {
 
         let device = audio_subsystem
             .open_playback(None, &desired_spec, |spec| AudioWave {
-                phase_inc: 440.0 / spec.freq as f32,
-                phase: 0.0,
-                volume: 0.25,
-                duty_cycle: 0.5,
+                spec: spec,
+                audio_provider: audio_provider,
+                ticks: 0,
             })
             .unwrap();
 
diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs
index 5b978925..5844e69a 100644
--- a/frontends/sdl/src/main.rs
+++ b/frontends/sdl/src/main.rs
@@ -6,13 +6,17 @@ 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};
-use std::{cmp::max, time::SystemTime};
+use sdl2::{event::Event, keyboard::Keycode, pixels::PixelFormatEnum, Sdl};
+use std::{
+    cmp::max,
+    sync::{Arc, Mutex},
+    time::SystemTime,
+};
 
 /// The scale at which the screen is going to be drawn
 /// meaning the ratio between Game Boy resolution and
@@ -39,9 +43,9 @@ impl Default for Benchmark {
 }
 
 pub struct Emulator {
-    system: GameBoy,
-    graphics: Graphics,
-    audio: Audio,
+    system: Arc<Mutex<Box<GameBoy>>>,
+    graphics: Option<Graphics>,
+    audio: Option<Audio>,
     logic_frequency: u32,
     visual_frequency: f32,
     next_tick_time: f32,
@@ -51,20 +55,11 @@ pub struct Emulator {
 }
 
 impl Emulator {
-    pub fn new(system: GameBoy, screen_scale: f32) -> Self {
-        let sdl = sdl2::init().unwrap();
-        let graphics = Graphics::new(
-            &sdl,
-            TITLE,
-            DISPLAY_WIDTH as u32,
-            DISPLAY_HEIGHT as u32,
-            screen_scale,
-        );
-        let audio = Audio::new(&sdl);
+    pub fn new(system: Arc<Mutex<Box<GameBoy>>>) -> Self {
         Self {
             system,
-            graphics: graphics,
-            audio: audio,
+            graphics: None,
+            audio: None,
             logic_frequency: GameBoy::CPU_FREQ,
             visual_frequency: GameBoy::VISUAL_FREQ,
             next_tick_time: 0.0,
@@ -102,13 +97,40 @@ impl Emulator {
         }
     }
 
+    pub fn start(&mut self, screen_scale: f32, audio_provider: Arc<Mutex<Box<GameBoy>>>) {
+        let sdl = sdl2::init().unwrap();
+        self.start_graphics(&sdl, screen_scale);
+        self.start_audio(&sdl, audio_provider);
+    }
+
+    pub fn start_graphics(&mut self, sdl: &Sdl, screen_scale: f32) {
+        self.graphics = Some(Graphics::new(
+            &sdl,
+            TITLE,
+            DISPLAY_WIDTH as u32,
+            DISPLAY_HEIGHT as u32,
+            screen_scale,
+        ));
+    }
+
+    pub fn start_audio(&mut self, sdl: &Sdl, audio_provider: Arc<Mutex<Box<GameBoy>>>) {
+        self.audio = Some(Audio::new(sdl, audio_provider));
+    }
+
+    pub fn tick_audio(&mut self, freq: u32) -> u8 {
+        self.system.lock().unwrap().tick_apu(freq)
+    }
+
     pub fn load_rom(&mut self, path: &str) {
-        let rom = self.system.load_rom_file(path);
+        let mut system = self.system.lock().unwrap();
+        let rom = system.load_rom_file(path);
         println!(
             "========= Cartridge =========\n{}\n=============================",
             rom
         );
         self.graphics
+            .as_mut()
+            .unwrap()
             .window_mut()
             .set_title(format!("{} [{}]", TITLE, rom.title()).as_str())
             .unwrap();
@@ -123,7 +145,7 @@ impl Emulator {
         let initial = SystemTime::now();
 
         for _ in 0..count {
-            cycles += self.system.clock() as u32;
+            cycles += self.system.lock().unwrap().clock() as u32;
         }
 
         let delta = initial.elapsed().unwrap().as_millis() as f32 / 1000.0;
@@ -137,6 +159,8 @@ 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();
@@ -146,15 +170,19 @@ impl Emulator {
         // updates the icon of the window to reflect the image
         // and style of the emulator
         let surface = surface_from_bytes(&data::ICON);
-        self.graphics.window_mut().set_icon(&surface);
+        self.graphics
+            .as_mut()
+            .unwrap()
+            .window_mut()
+            .set_icon(&surface);
 
         // creates an accelerated canvas to be used in the drawing
         // then clears it and presents it
-        self.graphics.canvas.present();
+        self.graphics.as_mut().unwrap().canvas.present();
 
         // creates a texture creator for the current canvas, required
         // for the creation of dynamic and static textures
-        let texture_creator = self.graphics.canvas.texture_creator();
+        let texture_creator = self.graphics.as_mut().unwrap().canvas.texture_creator();
 
         // creates the texture streaming that is going to be used
         // as the target for the pixel buffer
@@ -183,7 +211,7 @@ impl Emulator {
 
             // obtains an event from the SDL sub-system to be
             // processed under the current emulation context
-            while let Some(event) = self.graphics.event_pump.poll_event() {
+            while let Some(event) = self.graphics.as_mut().unwrap().event_pump.poll_event() {
                 match event {
                     Event::Quit { .. } => break 'main,
                     Event::KeyDown {
@@ -211,7 +239,7 @@ impl Emulator {
                         ..
                     } => {
                         if let Some(key) = key_to_pad(keycode) {
-                            self.system.key_press(key)
+                            self.system.lock().unwrap().key_press(key)
                         }
                     }
                     Event::KeyUp {
@@ -219,19 +247,19 @@ impl Emulator {
                         ..
                     } => {
                         if let Some(key) = key_to_pad(keycode) {
-                            self.system.key_lift(key)
+                            self.system.lock().unwrap().key_lift(key)
                         }
                     }
                     Event::DropFile { filename, .. } => {
-                        self.system.reset();
-                        self.system.load_boot_default();
+                        self.system.lock().unwrap().reset();
+                        self.system.lock().unwrap().load_boot_default();
                         self.load_rom(&filename);
                     }
                     _ => (),
                 }
             }
 
-            let current_time = self.graphics.timer_subsystem.ticks();
+            let current_time = self.graphics.as_mut().unwrap().timer_subsystem.ticks();
 
             if current_time >= self.next_tick_time_i {
                 // re-starts the counter cycles with the number of pending cycles
@@ -255,24 +283,29 @@ impl Emulator {
                         break;
                     }
 
-                    // runs the Game Boy clock, this operations should
-                    // include the advance of both the CPU and the PPU
-                    counter_cycles += self.system.clock() as u32;
-
-                    if self.system.ppu_mode() == PpuMode::VBlank
-                        && self.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 = 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 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 operations should
+                        // include the advance of both the CPU and the PPU
+                        counter_cycles += system.clock() as u32;
+
+                        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();
+                        }
                     }
                 }
 
@@ -285,15 +318,20 @@ impl Emulator {
                     // clears the graphics canvas, making sure that no garbage
                     // pixel data remaining in the pixel buffer, not doing this would
                     // create visual glitches in OSs like Mac OS X
-                    self.graphics.canvas.clear();
+                    self.graphics.as_mut().unwrap().canvas.clear();
 
                     // copies the texture that was created for the frame (during
                     // the loop part of the tick) to the canvas
-                    self.graphics.canvas.copy(&texture, None, None).unwrap();
+                    self.graphics
+                        .as_mut()
+                        .unwrap()
+                        .canvas
+                        .copy(&texture, None, None)
+                        .unwrap();
 
                     // presents the canvas effectively updating the screen
                     // information presented to the user
-                    self.graphics.canvas.present();
+                    self.graphics.as_mut().unwrap().canvas.present();
                 }
 
                 // calculates the number of ticks that have elapsed since the
@@ -315,9 +353,13 @@ impl Emulator {
                 self.next_tick_time_i = self.next_tick_time.ceil() as u32;
             }
 
-            let current_time = self.graphics.timer_subsystem.ticks();
+            let current_time = self.graphics.as_mut().unwrap().timer_subsystem.ticks();
             let pending_time = self.next_tick_time_i.saturating_sub(current_time);
-            self.graphics.timer_subsystem.delay(pending_time);
+            self.graphics
+                .as_mut()
+                .unwrap()
+                .timer_subsystem
+                .delay(pending_time);
         }
     }
 }
@@ -325,12 +367,16 @@ 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 mut game_boy = GameBoy::new();
-    game_boy.load_boot_default();
+    let game_boy = Arc::new(Mutex::new(Box::new(GameBoy::new())));
+    game_boy.try_lock().unwrap().as_mut().load_boot_default();
 
     // creates a new generic emulator structure loads the default
     // ROM file and starts running it
-    let mut emulator = Emulator::new(game_boy, SCREEN_SCALE);
+    let mut emulator = Emulator::new(game_boy);
+
+    let game_boy_ref = emulator.system.clone();
+
+    emulator.start(SCREEN_SCALE, game_boy_ref);
     emulator.load_rom("../../res/roms/pocket.gb");
     emulator.toggle_palette();
     emulator.run();
diff --git a/src/apu.rs b/src/apu.rs
index 09b6e681..349232a7 100644
--- a/src/apu.rs
+++ b/src/apu.rs
@@ -1,6 +1,16 @@
 use crate::warnln;
 
+const DUTY_TABLE: [[u8; 8]; 4] = [
+    [0, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 0, 0, 0, 0, 0, 1],
+    [1, 0, 0, 0, 0, 1, 1, 1],
+    [0, 1, 1, 1, 1, 1, 1, 0],
+];
+
 pub struct Apu {
+    ch1_timer: u16,
+    ch1_sequence: u8,
+    ch1_output: u8,
     ch1_sweep_slope: u8,
     ch1_sweep_increase: bool,
     ch1_sweep_pace: u8,
@@ -13,6 +23,9 @@ pub struct Apu {
     ch1_sound_length: bool,
     ch1_enabled: bool,
 
+    ch2_timer: u16,
+    ch2_sequence: u8,
+    ch2_output: u8,
     ch2_length_timer: u8,
     ch2_wave_duty: u8,
     ch2_pace: u8,
@@ -26,6 +39,9 @@ pub struct Apu {
 impl Apu {
     pub fn new() -> Self {
         Self {
+            ch1_timer: 0,
+            ch1_sequence: 0,
+            ch1_output: 0,
             ch1_sweep_slope: 0x0,
             ch1_sweep_increase: false,
             ch1_sweep_pace: 0x0,
@@ -38,6 +54,9 @@ impl Apu {
             ch1_sound_length: false,
             ch1_enabled: false,
 
+            ch2_timer: 0,
+            ch2_sequence: 0,
+            ch2_output: 0,
             ch2_length_timer: 0x0,
             ch2_wave_duty: 0x0,
             ch2_pace: 0x0,
@@ -49,13 +68,10 @@ impl Apu {
         }
     }
 
-    pub fn clock(&mut self, cycles: u8) {
-        // @todo implement the clock and allow for the proper
-        // writing of the output buffer at a fixed frequency
-
-        
-
-
+    pub fn clock(&mut self, cycles: u8, freq: u32) {
+        for _ in 0..cycles {
+            self.cycle(freq);
+        }
     }
 
     pub fn read(&mut self, addr: u16) -> u8 {
@@ -127,4 +143,29 @@ impl Apu {
             _ => warnln!("Writing in unknown APU location 0x{:04x}", addr),
         }
     }
+
+    #[inline(always)]
+    pub fn cycle(&mut self, freq: u32) {
+        self.ch2_timer = self.ch2_timer.saturating_sub(1);
+        if self.ch2_timer == 0 {
+            let target_freq = 1048576.0 / (2048.0 - self.ch1_wave_length as f32);
+            self.ch2_timer = (freq as f32 / target_freq) as u16;
+            self.ch2_sequence = (self.ch2_sequence + 1) & 7;
+
+            if self.ch2_enabled {
+                self.ch2_output =
+                    if DUTY_TABLE[self.ch2_wave_duty as usize][self.ch2_sequence as usize] == 1 {
+                        self.ch2_volume
+                    } else {
+                        0
+                    };
+            } else {
+                self.ch2_output = 0;
+            }
+        }
+    }
+
+    pub fn output(&self) -> u8 {
+        self.ch2_output
+    }
 }
diff --git a/src/gb.rs b/src/gb.rs
index 1a747854..125ec06d 100644
--- a/src/gb.rs
+++ b/src/gb.rs
@@ -53,6 +53,10 @@ pub struct Registers {
     pub lyc: u8,
 }
 
+pub trait AudioProvider {
+    fn tick_apu(&mut self, freq: u32) -> u8;
+}
+
 #[cfg_attr(feature = "wasm", wasm_bindgen)]
 impl GameBoy {
     #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
@@ -75,7 +79,6 @@ impl GameBoy {
     pub fn clock(&mut self) -> u8 {
         let cycles = self.cpu_clock();
         self.ppu_clock(cycles);
-        self.apu_clock(cycles);
         self.timer_clock(cycles);
         cycles
     }
@@ -96,8 +99,8 @@ impl GameBoy {
         self.ppu().clock(cycles)
     }
 
-    pub fn apu_clock(&mut self, cycles: u8) {
-        self.apu().clock(cycles)
+    pub fn apu_clock(&mut self, cycles: u8, freq: u32) {
+        self.apu().clock(cycles, freq)
     }
 
     pub fn timer_clock(&mut self, cycles: u8) {
@@ -372,6 +375,13 @@ pub fn hook_impl(info: &PanicInfo) {
     panic(message.as_str());
 }
 
+impl AudioProvider for GameBoy {
+    fn tick_apu(&mut self, freq: u32) -> u8 {
+        self.apu_clock(1, freq);
+        self.apu().output()
+    }
+}
+
 impl Default for GameBoy {
     fn default() -> Self {
         Self::new()
-- 
GitLab