Skip to content
Snippets Groups Projects
apu.rs 35.8 KiB
Newer Older
use std::collections::VecDeque;

use crate::{gb::GameBoy, 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],
];

const CH4_DIVISORS: [u8; 8] = [8, 16, 32, 48, 64, 80, 96, 112];

pub enum Channel {
    Ch1,
    Ch2,
    Ch3,
    Ch4,
}

pub struct Apu {
    ch1_envelope_sequence: u8,
    ch1_envelope_enabled: bool,
    ch1_sweep_sequence: u8,
    ch1_sweep_slope: u8,
    ch1_sweep_increase: bool,
    ch1_sweep_pace: u8,
    ch1_length_timer: u8,
    ch1_wave_duty: u8,
    ch1_pace: u8,
    ch1_direction: u8,
    ch1_volume: u8,
    ch1_wave_length: u16,
    ch1_length_enabled: bool,
    ch1_enabled: bool,

    ch2_envelope_sequence: u8,
    ch2_envelope_enabled: bool,
    ch2_length_timer: u8,
    ch2_wave_duty: u8,
    ch2_pace: u8,
    ch2_direction: u8,
    ch2_volume: u8,
    ch2_wave_length: u16,
    ch2_length_enabled: bool,
    ch2_enabled: bool,
    ch3_position: u8,
    ch3_output: u8,
    ch3_dac: bool,
    ch3_length_timer: u16,
    ch3_output_level: u8,
    ch3_wave_length: u16,
    ch3_length_enabled: bool,
    ch3_enabled: bool,

    ch4_timer: i32,
    ch4_envelope_sequence: u8,
    ch4_envelope_enabled: bool,
    ch4_output: u8,
    ch4_length_timer: u8,
    ch4_pace: u8,
    ch4_direction: u8,
    ch4_volume: u8,
    ch4_divisor: u8,
    ch4_width_mode: bool,
    ch4_clock_shift: u8,
    ch4_lfsr: u16,
    ch4_length_enabled: bool,
    ch4_enabled: bool,

    right_enabled: bool,
    left_enabled: bool,
    sound_enabled: bool,

    ch1_out_enabled: bool,
    ch2_out_enabled: bool,
    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.

    /// The number of audion channels that are going to be
João Magalhães's avatar
João Magalhães committed
    /// 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,
    audio_buffer: VecDeque<u8>,
    audio_buffer_max: usize,

    clock_freq: u32,
    pub fn new(sampling_rate: u16, channels: u8, buffer_size: f32, clock_freq: u32) -> Self {
            ch1_timer: 0,
            ch1_sequence: 0,
            ch1_envelope_sequence: 0,
            ch1_envelope_enabled: false,
            ch1_sweep_sequence: 0,
            ch1_sweep_slope: 0x0,
            ch1_sweep_increase: true,
            ch1_sweep_pace: 0x0,
            ch1_length_timer: 0x0,
            ch1_wave_duty: 0x0,
            ch1_pace: 0x0,
            ch1_direction: 0x0,
            ch1_volume: 0x0,
            ch1_wave_length: 0x0,
            ch1_length_enabled: false,
            ch1_enabled: false,

            ch2_timer: 0,
            ch2_sequence: 0,
            ch2_envelope_sequence: 0,
            ch2_envelope_enabled: false,
            ch2_length_timer: 0x0,
            ch2_wave_duty: 0x0,
            ch2_pace: 0x0,
            ch2_direction: 0x0,
            ch2_volume: 0x0,
            ch2_wave_length: 0x0,
            ch2_length_enabled: false,
            ch2_enabled: false,
            ch3_timer: 0,
            ch3_position: 0,
            ch3_output: 0,
            ch3_dac: false,
            ch3_length_timer: 0x0,
            ch3_output_level: 0x0,
            ch3_wave_length: 0x0,
            ch3_length_enabled: false,
            ch3_enabled: false,

            ch4_envelope_sequence: 0,
            ch4_envelope_enabled: false,
            ch4_output: 0,
            ch4_length_timer: 0x0,
            ch4_pace: 0x0,
            ch4_direction: 0x0,
            ch4_volume: 0x0,
            ch4_divisor: 0x0,
            ch4_width_mode: false,
            ch4_clock_shift: 0x0,
            ch4_lfsr: 0x0,
            ch4_length_enabled: false,
            ch4_enabled: false,

            left_enabled: true,
            right_enabled: true,
            sound_enabled: true,
            ch1_out_enabled: true,
            ch2_out_enabled: true,
            ch3_out_enabled: true,
            ch4_out_enabled: true,
            wave_ram: [0u8; 16],
            sampling_rate,

            sequencer: 0,
            sequencer_step: 0,
            audio_buffer: VecDeque::with_capacity(
                (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,
João Magalhães's avatar
João Magalhães committed
            clock_freq,
    pub fn reset(&mut self) {
        self.ch1_timer = 0;
        self.ch1_sequence = 0;
        self.ch1_envelope_sequence = 0;
        self.ch1_envelope_enabled = false;
        self.ch1_sweep_sequence = 0;
        self.ch1_output = 0;
        self.ch1_dac = false;
        self.ch1_sweep_slope = 0x0;
        self.ch1_sweep_increase = true;
        self.ch1_sweep_pace = 0x0;
        self.ch1_length_timer = 0x0;
        self.ch1_wave_duty = 0x0;
        self.ch1_pace = 0x0;
        self.ch1_direction = 0x0;
        self.ch1_volume = 0x0;
        self.ch1_wave_length = 0x0;
        self.ch1_length_enabled = false;
        self.ch1_enabled = false;

        self.ch2_timer = 0;
        self.ch2_sequence = 0;
        self.ch2_envelope_sequence = 0;
        self.ch2_envelope_enabled = false;
        self.ch2_output = 0;
        self.ch2_dac = false;
        self.ch2_length_timer = 0x0;
        self.ch2_wave_duty = 0x0;
        self.ch2_pace = 0x0;
        self.ch2_direction = 0x0;
        self.ch2_volume = 0x0;
        self.ch2_wave_length = 0x0;
        self.ch2_length_enabled = false;
        self.ch2_enabled = false;

        self.ch3_timer = 0;
        self.ch3_position = 0;
        self.ch3_output = 0;
        self.ch3_dac = false;
        self.ch3_length_timer = 0x0;
        self.ch3_output_level = 0x0;
        self.ch3_wave_length = 0x0;
        self.ch3_length_enabled = false;
        self.ch3_enabled = false;

        self.ch4_timer = 0;
        self.ch4_envelope_sequence = 0;
        self.ch4_envelope_enabled = false;
        self.ch4_output = 0;
        self.ch4_dac = false;
        self.ch4_length_timer = 0x0;
        self.ch4_pace = 0x0;
        self.ch4_direction = 0x0;
        self.ch4_volume = 0x0;
        self.ch4_divisor = 0x0;
        self.ch4_width_mode = false;
        self.ch4_clock_shift = 0x0;
        self.ch4_lfsr = 0x0;
        self.ch4_length_enabled = false;
        self.ch4_enabled = false;

        self.glob_panning = 0x0;

        self.left_enabled = true;
        self.right_enabled = true;
        self.sound_enabled = true;
        self.sequencer = 0;
        self.sequencer_step = 0;
        self.output_timer = 0;

        self.clear_audio_buffer()
    }

    pub fn clock(&mut self, cycles: u16) {
        if !self.sound_enabled {
            return;
        }

João Magalhães's avatar
João Magalhães committed
        self.sequencer += cycles;
        if self.sequencer >= 8192 {
            // each of these steps runs at 512/8 Hz = 64Hz,
            // meaning a complete loop runs at 512 Hz
            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 => {
                    self.tick_envelope_all();
                }
                _ => (),
            }

            self.sequencer -= 8192;
            self.sequencer_step = (self.sequencer_step + 1) & 7;
        }

        self.tick_ch_all(cycles);

        self.output_timer = self.output_timer.saturating_sub(cycles as i16);
        if self.output_timer <= 0 {
            // verifies if we've reached the maximum allowed size for the
            // audio buffer and if that's the case an item is removed from
            // 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 {
                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 && self.channels > 1 {
                self.audio_buffer.push_back(self.output());
            }

            // calculates the rate at which a new audio sample should be
            // created based on the (base/CPU) clock frequency and the
            // sampling rate, this is basically the amount of APU clock
            // calls that should be performed until an audio sample is created
            self.output_timer += (self.clock_freq as f32 / self.sampling_rate as f32) as i16;
    pub fn read(&mut self, addr: u16) -> u8 {
João Magalhães's avatar
João Magalhães committed
        match addr {
            // 0xFF10 — NR10: Channel 1 sweep
            0xff10 => {
                (self.ch1_sweep_slope & 0x07)
                    | (if self.ch1_sweep_increase { 0x00 } else { 0x08 })
                    | ((self.ch1_sweep_pace & 0x07) << 4)
            // 0xFF11 — NR11: Channel 1 length timer & duty cycle
            0xff11 => ((self.ch1_wave_duty & 0x03) << 6) | 0x3f,
            // 0xFF12 — NR12: Channel 1 volume & envelope
            0xff12 => {
                (self.ch1_pace & 0x07)
                    | ((self.ch1_direction & 0x01) << 3)
                    | ((self.ch1_volume & 0x0f) << 4)
            }
            // 0xFF13 — NR13: Channel 1 wavelength low
            0xff13 => 0xff,
            // 0xFF14 — NR14: Channel 1 wavelength high & control
            0xff14 => (if self.ch1_length_enabled { 0x40 } else { 0x00 }) | 0xbf,
            // 0xFF15 — Not used
            0xff15 => 0xff,
            // 0xFF16 — NR21: Channel 2 length timer & duty cycle
João Magalhães's avatar
João Magalhães committed
            0xff16 => ((self.ch2_wave_duty & 0x03) << 6) | 0x3f,
            // 0xFF17 — NR22: Channel 2 volume & envelope
            0xff17 => {
                (self.ch2_pace & 0x07)
                    | ((self.ch2_direction & 0x01) << 3)
                    | ((self.ch2_volume & 0x0f) << 4)
            }
            // 0xFF18 — NR23: Channel 2 wavelength low
            0xff18 => 0xff,
            // 0xFF19 — NR24: Channel 2 wavelength high & control
            0xff19 => (if self.ch2_length_enabled { 0x40 } else { 0x00 }) | 0xbf,
            // 0xFF1A — NR30: Channel 3 DAC enable
            0xff1a => (if self.ch3_dac { 0x80 } else { 0x00 }) | 0x7f,
            // 0xFF1B — NR31: Channel 3 length timer
            // 0xFF1C — NR32: Channel 3 output level
            0xff1c => ((self.ch3_output_level & 0x03) << 5) | 0x9f,
            // 0xFF1D — NR33: Channel 3 wavelength low
            0xff1d => 0xff,
            // 0xFF1E — NR34: Channel 3 wavelength high & control
            0xff1e => (if self.ch3_length_enabled { 0x40 } else { 0x00 }) | 0xbf,
            // 0xFF1F — Not used
            0xff1f => 0xff,
            // 0xFF20 — NR41: Channel 4 length timer
            // 0xFF21 — NR42: Channel 4 volume & envelope
            0xff21 => {
                (self.ch4_pace & 0x07)
                    | ((self.ch4_direction & 0x01) << 3)
                    | ((self.ch4_volume & 0x0f) << 4)
            }
            // 0xFF22 — NR43: Channel 4 frequency & randomness
            0xff22 => {
                (self.ch4_divisor & 0x07)
                    | if self.ch4_width_mode { 0x08 } else { 0x00 }
                    | ((self.ch4_clock_shift & 0x0f) << 4)
            }
            // 0xFF23 — NR44: Channel 4 control
            0xff23 => (if self.ch4_length_enabled { 0x40 } else { 0x00 }) | 0xbf,
            // 0xFF24 — NR50: Master volume & VIN panning
            0xff24 => self.master,
            // 0xFF25 — NR51: Sound panning
            0xff25 => self.glob_panning,
            // 0xFF26 — NR52: Sound on/off
João Magalhães's avatar
João Magalhães committed
            0xff26 =>
            {
João Magalhães's avatar
João Magalhães committed
                #[allow(clippy::bool_to_int_with_if)]
Loading
Loading full blame...