diff --git a/src/apu.rs b/src/apu.rs
index 3c934c3d3b6139bb9945ba83594feb02974b6632..e9f343bd11d99f0380b791fb0d9787ef2e58f952 100644
--- a/src/apu.rs
+++ b/src/apu.rs
@@ -44,9 +44,9 @@ pub struct Apu {
     ch2_enabled: bool,
 
     // @TODO start using this once we're ready for CH3
-    //ch3_timer: u16,
-    //ch3_sequence: u8,
-    //ch3_output: u8,
+    ch3_timer: u16,
+    ch3_position: u8,
+    ch3_output: u8,
     ch3_dac: bool,
     ch3_length_timer: u8,
     ch3_output_level: u8,
@@ -95,9 +95,9 @@ impl Apu {
             ch2_enabled: false,
 
             // @TODO start using this once we're ready for CH3
-            //ch3_timer: 0,
-            //ch3_sequence: 0,
-            //ch3_output: 0,
+            ch3_timer: 0,
+            ch3_position: 0,
+            ch3_output: 0,
             ch3_dac: false,
             ch3_length_timer: 0x0,
             ch3_output_level: 0x0,
@@ -184,11 +184,6 @@ impl Apu {
                     (self.ch2_wave_length & 0x00ff) | (((value & 0x07) as u16) << 8);
                 self.ch2_sound_length |= value & 0x40 == 0x40;
                 self.ch2_enabled |= value & 0x80 == 0x80;
-                if value & 0x80 == 0x80 {
-                    //self.ch2_timer = 0;
-                    //self.ch2_sequence = 0;
-                    // @TODO improve this reset operation
-                }
             }
 
             // 0xFF1A — NR30: Channel 3 DAC enable
@@ -201,7 +196,7 @@ impl Apu {
             }
             // 0xFF1C — NR32: Channel 3 output level
             0xff1c => {
-                self.ch3_output_level = value & 0x60 >> 5;
+                self.ch3_output_level = (value & 0x60) >> 5;
             }
             // 0xFF1D — NR33: Channel 3 wavelength low [write-only]
             0xff1d => {
@@ -217,7 +212,7 @@ impl Apu {
 
             // 0xFF30-0xFF3F — Wave pattern RAM
             0xff30..=0xff3f => {
-                self.wave_ram[addr as usize - 0xff30] = value;
+                self.wave_ram[addr as usize & 0x000f] = value;
             }
 
             _ => warnln!("Writing in unknown APU location 0x{:04x}", addr),
@@ -225,7 +220,7 @@ impl Apu {
     }
 
     pub fn output(&self) -> u8 {
-        self.ch1_output + self.ch2_output
+        self.ch1_output + self.ch2_output + self.ch3_output
     }
 
     pub fn audio_buffer(&self) -> &Vec<u8> {
@@ -275,44 +270,13 @@ impl Apu {
             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;
-            self.ch1_sequence = (self.ch1_sequence + 1) & 7;
-
-            if self.ch1_enabled {
-                self.ch1_output =
-                    if DUTY_TABLE[self.ch1_wave_duty as usize][self.ch1_sequence as usize] == 1 {
-                        self.ch1_volume
-                    } else {
-                        0
-                    };
-            } else {
-                self.ch1_output = 0;
-            }
-        }
-
-        self.ch2_timer = self.ch2_timer.saturating_sub(1);
-        if self.ch2_timer == 0 {
-            self.ch2_timer = (2048 - self.ch2_wave_length) << 2;
-            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;
-            }
-        }
+        self.tick_ch_all();
 
         self.output_timer = self.output_timer.saturating_sub(1);
         if self.output_timer == 0 {
             self.audio_buffer.push(self.output());
-            // @TODO target sampling rate is hardcoded, need to softcode this
+            // @TODO the CPU clock is hardcoded here, we must handle situations
+            // where there's some kind of overclock
             self.output_timer = (4194304.0 / self.sampling_frequency as f32) as u16;
         }
     }
@@ -340,7 +304,13 @@ impl Apu {
                     self.ch2_length_timer = 0;
                 }
             }
-            Channel::Ch3 => (),
+            Channel::Ch3 => {
+                self.ch3_length_timer = self.ch3_length_timer.saturating_add(1);
+                if self.ch3_length_timer >= 64 {
+                    self.ch3_enabled = false;
+                    self.ch3_length_timer = 0;
+                }
+            }
             Channel::Ch4 => (),
         }
     }
@@ -351,7 +321,7 @@ impl Apu {
         }
         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 divisor = 1u16 << 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);
@@ -365,6 +335,83 @@ impl Apu {
             self.ch1_sweep_sequence = 0;
         }
     }
+
+    fn tick_ch_all(&mut self) {
+        self.tick_ch1();
+        self.tick_ch2();
+        self.tick_ch3();
+    }
+
+    fn tick_ch1(&mut self) {
+        self.ch1_timer = self.ch1_timer.saturating_sub(1);
+        if self.ch1_timer > 0 {
+            return;
+        }
+
+        if self.ch1_enabled {
+            self.ch1_output =
+                if DUTY_TABLE[self.ch1_wave_duty as usize][self.ch1_sequence as usize] == 1 {
+                    self.ch1_volume
+                } else {
+                    0
+                };
+        } else {
+            self.ch1_output = 0;
+        }
+
+        self.ch1_timer = (2048 - self.ch1_wave_length) << 2;
+        self.ch1_sequence = (self.ch1_sequence + 1) & 7;
+    }
+
+    fn tick_ch2(&mut self) {
+        self.ch2_timer = self.ch2_timer.saturating_sub(1);
+        if self.ch2_timer > 0 {
+            return;
+        }
+
+        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;
+        }
+
+        self.ch2_timer = (2048 - self.ch2_wave_length) << 2;
+        self.ch2_sequence = (self.ch2_sequence + 1) & 7;
+    }
+
+    fn tick_ch3(&mut self) {
+        self.ch3_timer = self.ch3_timer.saturating_sub(1);
+        if self.ch3_timer > 0 {
+            return;
+        }
+
+        if self.ch3_enabled {
+            let wave_index = self.ch3_position >> 1;
+            let mut output = self.wave_ram[wave_index as usize];
+            output = if (self.ch3_position & 0x01) == 0x01 {
+                output & 0x0f
+            } else {
+                (output & 0xf0) >> 4
+            };
+            if self.ch3_output_level > 0 {
+                output >>= self.ch3_output_level - 1;
+            } else {
+                output = 0;
+            }
+            //println!("{} {}", self.ch3_output, self.ch3_output_level);
+            self.ch3_output = output;
+        } else {
+            self.ch3_output = 0;
+        }
+
+        self.ch3_timer = (2048 - self.ch3_wave_length) << 1;
+        self.ch3_position = (self.ch3_position + 1) & 31;
+    }
 }
 
 impl Default for Apu {