From 2faa3e751230cbf1cb3bc374055ea7135c35656f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 24 Jul 2023 23:20:28 +0100
Subject: [PATCH] chore: audio support for libretro

---
 frontends/libretro/src/lib.rs | 19 +++++++++++++++-
 src/apu.rs                    | 41 ++++++++++++++++++++---------------
 2 files changed, 41 insertions(+), 19 deletions(-)

diff --git a/frontends/libretro/src/lib.rs b/frontends/libretro/src/lib.rs
index d59c4bd0..202727ad 100644
--- a/frontends/libretro/src/lib.rs
+++ b/frontends/libretro/src/lib.rs
@@ -9,7 +9,7 @@ use std::{
 };
 
 use boytacean::{
-    gb::GameBoy,
+    gb::{AudioProvider, GameBoy},
     pad::PadKey,
     ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH, RGB1555_SIZE},
     rom::Cartridge,
@@ -288,6 +288,8 @@ pub extern "C" fn retro_set_controller_port_device() {
 #[no_mangle]
 pub extern "C" fn retro_run() {
     let emulator = unsafe { EMULATOR.as_mut().unwrap() };
+    let sample_batch_cb = unsafe { AUDIO_SAMPLE_BATCH_CALLBACK.as_ref().unwrap() };
+    let channels = emulator.audio_channels();
 
     let mut counter_cycles = 0_u32;
     let cycle_limit = 4194304 / 60; //@TODO this is super tricky
@@ -304,6 +306,21 @@ pub extern "C" fn retro_run() {
         // include the advance of both the CPU, PPU, APU
         // and any other frequency based component of the system
         counter_cycles += emulator.clock() as u32;
+
+        // obtains the audio buffer reference and queues it
+        // in a batch manner using the audio callback at the
+        // the end of the operation clears the buffer
+        let audio_buffer = emulator
+            .audio_buffer()
+            .iter()
+            .map(|v| *v as i16 * 256)
+            .collect::<Vec<i16>>();
+
+        sample_batch_cb(
+            audio_buffer.as_ptr(),
+            audio_buffer.len() / channels as usize,
+        );
+        emulator.clear_audio_buffer();
     }
 
     unsafe {
diff --git a/src/apu.rs b/src/apu.rs
index 367ad0ed..94906908 100644
--- a/src/apu.rs
+++ b/src/apu.rs
@@ -2,8 +2,6 @@ use std::collections::VecDeque;
 
 use crate::{gb::GameBoy, warnln};
 
-const CHANNELS: u8 = 2;
-
 const DUTY_TABLE: [[u8; 8]; 4] = [
     [0, 0, 0, 0, 0, 0, 0, 1],
     [1, 0, 0, 0, 0, 0, 0, 1],
@@ -93,9 +91,22 @@ pub struct Apu {
     ch3_out_enabled: bool,
     ch4_out_enabled: bool,
 
+    /// The RAM that is used to sore the wave information
+    /// to be used in channel 3 audio
     wave_ram: [u8; 16],
 
+    /// The rate at which audio samples are going to be
+    /// taken, ideally this value should be aligned with
+    /// the sampling rate of the output device. A typical
+    /// sampling rate would be of 44.1kHz.
     sampling_rate: u16,
+
+    /// The number of audion channels that are going to be
+    /// outputted as part fo the audio buffer)  
+    channels: u8,
+
+    /// Internal sequencer counter that runs at 512Hz
+    /// used for the activation of the tick actions.
     sequencer: u16,
     sequencer_step: u8,
     output_timer: i16,
@@ -106,7 +117,7 @@ pub struct Apu {
 }
 
 impl Apu {
-    pub fn new(sampling_rate: u16, buffer_size: f32, clock_freq: u32) -> Self {
+    pub fn new(sampling_rate: u16, channels: u8, buffer_size: f32, clock_freq: u32) -> Self {
         Self {
             ch1_timer: 0,
             ch1_sequence: 0,
@@ -180,25 +191,18 @@ impl Apu {
             ch3_out_enabled: true,
             ch4_out_enabled: true,
 
-            /// The RAM that is used to sore the wave information
-            /// to be used in channel 3 audio
             wave_ram: [0u8; 16],
 
-            /// The rate at which audio samples are going to be
-            /// taken, ideally this value should be aligned with
-            /// the sampling rate of the output device. A typical
-            /// sampling rate would be of 44.1kHz.
             sampling_rate,
+            channels,
 
-            /// Internal sequencer counter that runs at 512Hz
-            /// used for the activation of the tick actions.
             sequencer: 0,
             sequencer_step: 0,
             output_timer: 0,
             audio_buffer: VecDeque::with_capacity(
-                (sampling_rate as f32 * buffer_size) as usize * CHANNELS as usize,
+                (sampling_rate as f32 * buffer_size) as usize * channels as usize,
             ),
-            audio_buffer_max: (sampling_rate as f32 * buffer_size) as usize * CHANNELS as usize,
+            audio_buffer_max: (sampling_rate as f32 * buffer_size) as usize * channels as usize,
             clock_freq,
         }
     }
@@ -324,13 +328,14 @@ impl Apu {
             // the buffer (avoiding overflow) and then then the new audio
             // volume item is added to the queue
             if self.audio_buffer.len() >= self.audio_buffer_max {
-                self.audio_buffer.pop_front();
-                self.audio_buffer.pop_front();
+                for _ in 0..self.channels {
+                    self.audio_buffer.pop_front();
+                }
             }
             if self.left_enabled {
                 self.audio_buffer.push_back(self.output());
             }
-            if self.right_enabled {
+            if self.right_enabled && self.channels > 1 {
                 self.audio_buffer.push_back(self.output());
             }
 
@@ -729,7 +734,7 @@ impl Apu {
     }
 
     pub fn channels(&self) -> u8 {
-        CHANNELS
+        self.channels
     }
 
     pub fn audio_buffer(&self) -> &VecDeque<u8> {
@@ -1061,7 +1066,7 @@ impl Apu {
 
 impl Default for Apu {
     fn default() -> Self {
-        Self::new(44100, 1.0, GameBoy::CPU_FREQ)
+        Self::new(44100, 2, 1.0, GameBoy::CPU_FREQ)
     }
 }
 
-- 
GitLab