From 3b7d5edcda98b5a4217dc7fbede3eb326118c8c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Wed, 1 Mar 2023 12:01:46 +0000
Subject: [PATCH] =?UTF-8?q?feat:=20initial=20length=20and=20sweep=20audio?=
 =?UTF-8?q?=20support=20The=20emulator=20is=20now=20sounding=20more=20like?=
 =?UTF-8?q?=20a=20real=20audio=20player=20=F0=9F=8E=A7.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 frontends/sdl/src/main.rs |   6 +-
 src/apu.rs                | 130 +++++++++++++++++++++++++++++++++-----
 2 files changed, 120 insertions(+), 16 deletions(-)

diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs
index 8cd7607d..4dcb15e3 100644
--- a/frontends/sdl/src/main.rs
+++ b/frontends/sdl/src/main.rs
@@ -22,6 +22,10 @@ const SCREEN_SCALE: f32 = 2.0;
 /// The base title to be used in the window.
 static TITLE: &str = "Boytacean";
 
+/// Base audio volume to be used as the basis of the
+/// amplification level of the volume
+static VOLUME: f32 = 100.0;
+
 pub struct Benchmark {
     count: usize,
 }
@@ -312,7 +316,7 @@ impl Emulator {
                             .system
                             .audio_buffer()
                             .iter()
-                            .map(|v| *v as f32 / 14.0)
+                            .map(|v| *v as f32 / VOLUME)
                             .collect::<Vec<f32>>();
                         audio.device.queue_audio(&audio_buffer).unwrap();
                     }
diff --git a/src/apu.rs b/src/apu.rs
index 588c1e4e..3c934c3d 100644
--- a/src/apu.rs
+++ b/src/apu.rs
@@ -7,9 +7,17 @@ const DUTY_TABLE: [[u8; 8]; 4] = [
     [0, 1, 1, 1, 1, 1, 1, 0],
 ];
 
+pub enum Channel {
+    Ch1,
+    Ch2,
+    Ch3,
+    Ch4,
+}
+
 pub struct Apu {
     ch1_timer: u16,
     ch1_sequence: u8,
+    ch1_sweep_sequence: u8,
     ch1_output: u8,
     ch1_sweep_slope: u8,
     ch1_sweep_increase: bool,
@@ -48,6 +56,9 @@ pub struct Apu {
 
     wave_ram: [u8; 16],
 
+    sampling_frequency: u16,
+    sequencer: u16,
+    sequencer_step: u8,
     output_timer: u16,
     audio_buffer: Vec<u8>,
 }
@@ -57,6 +68,7 @@ impl Apu {
         Self {
             ch1_timer: 0,
             ch1_sequence: 0,
+            ch1_sweep_sequence: 0,
             ch1_output: 0,
             ch1_sweep_slope: 0x0,
             ch1_sweep_increase: false,
@@ -95,6 +107,12 @@ impl Apu {
 
             wave_ram: [0u8; 16],
 
+            sampling_frequency: 44100,
+
+            /// 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: Vec::new(),
         }
@@ -103,7 +121,7 @@ impl Apu {
     pub fn clock(&mut self, cycles: u8) {
         // @TODO the performance here requires improvement
         for _ in 0..cycles {
-            self.cycle();
+            self.tick();
         }
     }
 
@@ -118,8 +136,8 @@ impl Apu {
         match addr {
             // 0xFF10 — NR10: Channel 1 sweep
             0xff10 => {
-                self.ch1_sweep_slope = value & 0x03;
-                self.ch1_sweep_increase = value & 0x04 == 0x04;
+                self.ch1_sweep_slope = value & 0x07;
+                self.ch1_sweep_increase = value & 0x08 == 0x00;
                 self.ch1_sweep_pace = (value & 0x70) >> 4;
             }
             // 0xFF11 — NR11: Channel 1 length timer & duty cycle
@@ -206,8 +224,57 @@ impl Apu {
         }
     }
 
+    pub fn output(&self) -> u8 {
+        self.ch1_output + self.ch2_output
+    }
+
+    pub fn audio_buffer(&self) -> &Vec<u8> {
+        &self.audio_buffer
+    }
+
+    pub fn audio_buffer_mut(&mut self) -> &mut Vec<u8> {
+        &mut self.audio_buffer
+    }
+
+    pub fn clear_audio_buffer(&mut self) {
+        self.audio_buffer.clear();
+    }
+
     #[inline(always)]
-    pub fn cycle(&mut self) {
+    fn tick(&mut self) {
+        self.sequencer += 1;
+        if self.sequencer >= 8192 {
+            // each of these steps runs at 518/8 Hz = 64Hz
+            match self.sequencer_step {
+                0 => {
+                    self.tick_length_all();
+                }
+                1 => (),
+                2 => {
+                    self.tick_ch1_sweep();
+                    self.tick_length_all();
+                }
+                3 => (),
+                4 => {
+                    self.tick_length_all();
+                }
+                5 => (),
+                6 => {
+                    self.tick_ch1_sweep();
+                    self.tick_length_all();
+                }
+                7 => {
+                    //channels[0]->envelope_clock();
+                    //channels[1]->envelope_clock();
+                    //channels[3]->envelope_clock();
+                }
+                _ => (),
+            }
+
+            self.sequencer = 0;
+            self.sequencer_step = (self.sequencer_step + 1) & 7;
+        }
+
         self.ch1_timer = self.ch1_timer.saturating_sub(1);
         if self.ch1_timer == 0 {
             self.ch1_timer = (2048 - self.ch1_wave_length) << 2;
@@ -246,24 +313,57 @@ impl Apu {
         if self.output_timer == 0 {
             self.audio_buffer.push(self.output());
             // @TODO target sampling rate is hardcoded, need to softcode this
-            self.output_timer = (4194304.0 / 44100.0) as u16;
+            self.output_timer = (4194304.0 / self.sampling_frequency as f32) as u16;
         }
     }
 
-    pub fn output(&self) -> u8 {
-        self.ch1_output + self.ch2_output
-    }
-
-    pub fn audio_buffer(&self) -> &Vec<u8> {
-        &self.audio_buffer
+    fn tick_length_all(&mut self) {
+        self.tick_length(Channel::Ch1);
+        self.tick_length(Channel::Ch2);
+        self.tick_length(Channel::Ch3);
+        self.tick_length(Channel::Ch4);
     }
 
-    pub fn audio_buffer_mut(&mut self) -> &mut Vec<u8> {
-        &mut self.audio_buffer
+    fn tick_length(&mut self, channel: Channel) {
+        match channel {
+            Channel::Ch1 => {
+                self.ch1_length_timer = self.ch1_length_timer.saturating_add(1);
+                if self.ch1_length_timer >= 64 {
+                    self.ch1_enabled = false;
+                    self.ch1_length_timer = 0;
+                }
+            }
+            Channel::Ch2 => {
+                self.ch2_length_timer = self.ch2_length_timer.saturating_add(1);
+                if self.ch2_length_timer >= 64 {
+                    self.ch2_enabled = false;
+                    self.ch2_length_timer = 0;
+                }
+            }
+            Channel::Ch3 => (),
+            Channel::Ch4 => (),
+        }
     }
 
-    pub fn clear_audio_buffer(&mut self) {
-        self.audio_buffer.clear();
+    fn tick_ch1_sweep(&mut self) {
+        if self.ch1_sweep_pace == 0x0 {
+            return;
+        }
+        self.ch1_sweep_sequence += 1;
+        if self.ch1_sweep_sequence >= self.ch1_sweep_pace {
+            let divisor = (1 as u16) << self.ch1_sweep_slope as u16;
+            let delta = (self.ch1_wave_length as f32 / divisor as f32) as u16;
+            if self.ch1_sweep_increase {
+                self.ch1_wave_length = self.ch1_wave_length.saturating_add(delta);
+            } else {
+                self.ch1_wave_length = self.ch1_wave_length.saturating_sub(delta);
+            }
+            if self.ch1_wave_length > 0x07ff {
+                self.ch1_enabled = false;
+                self.ch1_wave_length = 0x07ff;
+            }
+            self.ch1_sweep_sequence = 0;
+        }
     }
 }
 
-- 
GitLab