Compare commits
	
		
			90 Commits
		
	
	
		
			08bd33b4d4
			...
			refactor
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9baa15050e | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | |||
| 01064bab69 | |||
| 634bc2d2c0 | |||
| d794a94b68 | |||
| b87e31d3f4 | |||
| 3c2456611e | |||
| b829f05a34 | |||
| afd2d16371 | |||
| 1f8fa48168 | |||
| c6fbb79189 | |||
| 8b78b2943e | |||
| 0af95a1dd3 | |||
| 9fa40f8584 | |||
| b10bc7b4fd | |||
| 4658a0d106 | |||
| f92b9d61ef | |||
| 360a9a7b65 | |||
| 5d64e539a7 | |||
| 22f96a10e7 | |||
| 8fea8eb1ff | |||
| 9b2c91445a | |||
| b9046bceba | |||
| aa22e93049 | |||
| 6215eccb2f | |||
| a77d0a0f62 | |||
| c2f2e2194b | |||
| d68257bb29 | |||
| e27d6dc25b | |||
| 1acb5de19d | |||
| 1b78b248a3 | |||
| 5d6df46a2d | |||
| 7e65d82fef | |||
| 8c9567b610 | |||
| 53dfaf0de2 | |||
| 16c2dd81fc | |||
| 79be38a1e6 | |||
| 8625bec059 | |||
| 0637b771e3 | |||
| 0107fa04c9 | |||
| 6265c8af04 | |||
| 5482a8e75f | |||
| 002dae6826 | |||
| c863dc835c | |||
| d4407cf849 | |||
| de0d147685 | |||
| dd8339e8de | |||
| 05d6475015 | |||
| 32b597a328 | |||
| 33be2e0e83 | |||
| 832e1b7633 | |||
| a549b9feef | |||
| 4d6fc95130 | |||
| 7112cd15e3 | |||
| 2af673d7c5 | |||
| e43cac8041 | |||
| 8724da824e | |||
| 903cfacad3 | |||
| 9d0e099a97 | |||
| d9a3a7b0dd | |||
| 1a4546f4a4 | |||
| bd685a99a4 | |||
| 31abd6dc5c | |||
| c7e3cb5b35 | |||
| e4d77d66ba | |||
| 9e36e86c14 | |||
| 6f6c308d84 | |||
| f429d72882 | |||
| 4fda7587ce | |||
| 3fb182b9cb | |||
| c1430594cf | |||
| 8e2ef58b81 | |||
| b4b84d0cc5 | |||
| 1d8fb8d32a | |||
| cc0cb0dd8a | |||
| fd9a5b51ba | |||
| b9a798d0ad | |||
| a1eadc0bc4 | |||
| ea53f97312 | 
							
								
								
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
    "recommendations": [
 | 
			
		||||
        "matklad.rust-analyzer",
 | 
			
		||||
        "tamasfe.even-better-toml",
 | 
			
		||||
        "serayuzgur.crates",
 | 
			
		||||
        "vadimcn.vscode-lldb",
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1004
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1004
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										12
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ name = "gb"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
authors = ["Rekai Musuka <rekai@musuka.dev>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
resolver = "2"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
@@ -11,14 +12,11 @@ anyhow = "^1.0"
 | 
			
		||||
bitfield = "^0.13"
 | 
			
		||||
clap = "^2.33"
 | 
			
		||||
gilrs = "^0.8"
 | 
			
		||||
pixels = "^0.3"
 | 
			
		||||
winit = "^0.24"
 | 
			
		||||
winit_input_helper = "^0.9"
 | 
			
		||||
egui = "^0.10"
 | 
			
		||||
egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" }
 | 
			
		||||
egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" }
 | 
			
		||||
pixels = "^0.6"
 | 
			
		||||
winit = "^0.25"
 | 
			
		||||
winit_input_helper = "^0.10"
 | 
			
		||||
rodio = "^0.14"
 | 
			
		||||
crossbeam-channel = "^0.5"
 | 
			
		||||
rtrb = "^0.1.4"
 | 
			
		||||
 | 
			
		||||
[profile.release]
 | 
			
		||||
debug = true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										938
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										938
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,938 @@
 | 
			
		||||
use crate::bus::BusIo;
 | 
			
		||||
use crate::emu::SM83_CLOCK_SPEED;
 | 
			
		||||
use gen::SampleProducer;
 | 
			
		||||
use types::ch1::{Sweep, SweepDirection};
 | 
			
		||||
use types::ch3::Volume as Ch3Volume;
 | 
			
		||||
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
 | 
			
		||||
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
 | 
			
		||||
use types::fs::{FrameSequencer, State as FrameSequencerState};
 | 
			
		||||
use types::{ChannelControl, SoundOutput};
 | 
			
		||||
 | 
			
		||||
pub mod gen;
 | 
			
		||||
mod types;
 | 
			
		||||
 | 
			
		||||
const SAMPLE_INCREMENT: u64 = gen::SAMPLE_RATE as u64;
 | 
			
		||||
const WAVE_PATTERN_RAM_LEN: usize = 0x10;
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug)]
 | 
			
		||||
pub struct Apu {
 | 
			
		||||
    ctrl: SoundControl,
 | 
			
		||||
    /// Tone & Sweep
 | 
			
		||||
    ch1: Channel1,
 | 
			
		||||
    /// Tone
 | 
			
		||||
    ch2: Channel2,
 | 
			
		||||
    /// Wave
 | 
			
		||||
    ch3: Channel3,
 | 
			
		||||
    /// Noise
 | 
			
		||||
    ch4: Channel4,
 | 
			
		||||
 | 
			
		||||
    sequencer: FrameSequencer,
 | 
			
		||||
    div_prev: Option<u16>,
 | 
			
		||||
 | 
			
		||||
    prod: Option<SampleProducer<f32>>,
 | 
			
		||||
    sample_counter: u64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BusIo for Apu {
 | 
			
		||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
			
		||||
        match addr & 0x00FF {
 | 
			
		||||
            0x10 => self.ch1.sweep(),
 | 
			
		||||
            0x11 => self.ch1.duty(),
 | 
			
		||||
            0x12 => self.ch1.envelope(),
 | 
			
		||||
            0x14 => self.ch1.freq_hi(),
 | 
			
		||||
            0x16 => self.ch2.duty(),
 | 
			
		||||
            0x17 => self.ch2.envelope(),
 | 
			
		||||
            0x19 => self.ch2.freq_hi(),
 | 
			
		||||
            0x1A => self.ch3.dac_enabled(),
 | 
			
		||||
            0x1C => self.ch3.volume(),
 | 
			
		||||
            0x1E => self.ch3.freq_hi(),
 | 
			
		||||
            0x21 => self.ch4.envelope(),
 | 
			
		||||
            0x22 => self.ch4.poly(),
 | 
			
		||||
            0x23 => self.ch4.frequency(),
 | 
			
		||||
            0x24 => self.ctrl.channel(),
 | 
			
		||||
            0x25 => self.ctrl.output(),
 | 
			
		||||
            0x26 => self.ctrl.status(self),
 | 
			
		||||
            0x30..=0x3F => self.ch3.read_byte(addr),
 | 
			
		||||
            _ => {
 | 
			
		||||
                eprintln!("Read 0xFF from unused IO register {:#06X} [APU]", addr);
 | 
			
		||||
                0xFF
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        match addr & 0x00FF {
 | 
			
		||||
            0x10 if self.ctrl.enabled => self.ch1.set_sweep(byte),
 | 
			
		||||
            0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
 | 
			
		||||
            0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
 | 
			
		||||
            0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
 | 
			
		||||
            0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte),
 | 
			
		||||
            0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
 | 
			
		||||
            0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
 | 
			
		||||
            0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte),
 | 
			
		||||
            0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte),
 | 
			
		||||
            0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte),
 | 
			
		||||
            0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
 | 
			
		||||
            0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
 | 
			
		||||
            0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte),
 | 
			
		||||
            0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte),
 | 
			
		||||
            0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
 | 
			
		||||
            0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
 | 
			
		||||
            0x22 if self.ctrl.enabled => self.ch4.set_poly(byte),
 | 
			
		||||
            0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte),
 | 
			
		||||
            0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
 | 
			
		||||
            0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
 | 
			
		||||
            0x26 => self.set_status(byte),
 | 
			
		||||
            0x30..=0x3F => self.ch3.write_byte(addr, byte),
 | 
			
		||||
            _ if !self.ctrl.enabled => {}
 | 
			
		||||
            _ => eprintln!(
 | 
			
		||||
                "Wrote {:#04X} to unused IO register {:#06X} [APU]",
 | 
			
		||||
                byte, addr
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Apu {
 | 
			
		||||
    pub(crate) fn tick(&mut self, div: u16) {
 | 
			
		||||
        self.sample_counter += SAMPLE_INCREMENT;
 | 
			
		||||
 | 
			
		||||
        // Frame Sequencer (512Hz)
 | 
			
		||||
        if self.is_falling_edge(12, div) {
 | 
			
		||||
            use FrameSequencerState::*;
 | 
			
		||||
 | 
			
		||||
            match self.sequencer.state() {
 | 
			
		||||
                Length => self.clock_length(),
 | 
			
		||||
                LengthAndSweep => {
 | 
			
		||||
                    self.clock_length();
 | 
			
		||||
                    self.clock_sweep();
 | 
			
		||||
                }
 | 
			
		||||
                Envelope => self.clock_envelope(),
 | 
			
		||||
                Nothing => {}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            self.sequencer.next();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.div_prev = Some(div);
 | 
			
		||||
 | 
			
		||||
        self.ch1.tick();
 | 
			
		||||
        self.ch2.tick();
 | 
			
		||||
        self.ch3.tick();
 | 
			
		||||
        self.ch4.tick();
 | 
			
		||||
 | 
			
		||||
        if self.sample_counter >= SM83_CLOCK_SPEED {
 | 
			
		||||
            self.sample_counter %= SM83_CLOCK_SPEED;
 | 
			
		||||
 | 
			
		||||
            if let Some(ref mut prod) = self.prod {
 | 
			
		||||
                if prod.available_blocking() {
 | 
			
		||||
                    // Sample the APU
 | 
			
		||||
 | 
			
		||||
                    let (left, right) = self.ctrl.out.ch1();
 | 
			
		||||
                    let ch1_left = if left { self.ch1.amplitude() } else { 0.0 };
 | 
			
		||||
                    let ch1_right = if right { self.ch1.amplitude() } else { 0.0 };
 | 
			
		||||
 | 
			
		||||
                    let (left, right) = self.ctrl.out.ch2();
 | 
			
		||||
                    let ch2_left = if left { self.ch2.amplitude() } else { 0.0 };
 | 
			
		||||
                    let ch2_right = if right { self.ch2.amplitude() } else { 0.0 };
 | 
			
		||||
 | 
			
		||||
                    let (left, right) = self.ctrl.out.ch3();
 | 
			
		||||
                    let ch3_left = if left { self.ch3.amplitude() } else { 0.0 };
 | 
			
		||||
                    let ch3_right = if right { self.ch3.amplitude() } else { 0.0 };
 | 
			
		||||
 | 
			
		||||
                    let (left, right) = self.ctrl.out.ch4();
 | 
			
		||||
                    let ch4_left = if left { self.ch4.amplitude() } else { 0.0 };
 | 
			
		||||
                    let ch4_right = if right { self.ch4.amplitude() } else { 0.0 };
 | 
			
		||||
 | 
			
		||||
                    let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0;
 | 
			
		||||
                    let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0;
 | 
			
		||||
 | 
			
		||||
                    let left_sample = (self.ctrl.channel.left_volume() + 1.0) * left_mixed;
 | 
			
		||||
                    let right_sample = (self.ctrl.channel.right_volume() + 1.0) * right_mixed;
 | 
			
		||||
 | 
			
		||||
                    prod.push(left_sample)
 | 
			
		||||
                        .and(prod.push(right_sample))
 | 
			
		||||
                        .expect("Add samples to ring buffer");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF26 | NR52 - Sound On/Off
 | 
			
		||||
    pub(crate) fn set_status(&mut self, byte: u8) {
 | 
			
		||||
        self.ctrl.enabled = (byte >> 7) & 0x01 == 0x01;
 | 
			
		||||
 | 
			
		||||
        if self.ctrl.enabled {
 | 
			
		||||
            // Frame Sequencer reset to Step 0
 | 
			
		||||
            self.sequencer.reset();
 | 
			
		||||
 | 
			
		||||
            // Square Duty units are reset to first step
 | 
			
		||||
            self.ch1.duty_pos = 0;
 | 
			
		||||
            self.ch2.duty_pos = 0;
 | 
			
		||||
 | 
			
		||||
            // Wave Channel's sample buffer reset to 0
 | 
			
		||||
            self.ch3.offset = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !self.ctrl.enabled {
 | 
			
		||||
            self.reset();
 | 
			
		||||
        } else {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
 | 
			
		||||
        self.prod = Some(prod);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn reset(&mut self) {
 | 
			
		||||
        self.ch1.sweep = Default::default();
 | 
			
		||||
        self.ch1.duty = Default::default();
 | 
			
		||||
        self.ch1.envelope = Default::default();
 | 
			
		||||
        self.ch1.freq_lo = Default::default();
 | 
			
		||||
        self.ch1.freq_hi = Default::default();
 | 
			
		||||
 | 
			
		||||
        self.ch2.duty = Default::default();
 | 
			
		||||
        self.ch2.envelope = Default::default();
 | 
			
		||||
        self.ch2.freq_lo = Default::default();
 | 
			
		||||
        self.ch2.freq_hi = Default::default();
 | 
			
		||||
 | 
			
		||||
        self.ch3.dac_enabled = Default::default();
 | 
			
		||||
        self.ch3.len = Default::default();
 | 
			
		||||
        self.ch3.volume = Default::default();
 | 
			
		||||
        self.ch3.freq_lo = Default::default();
 | 
			
		||||
        self.ch3.freq_hi = Default::default();
 | 
			
		||||
 | 
			
		||||
        self.ch4.len = Default::default();
 | 
			
		||||
        self.ch4.envelope = Default::default();
 | 
			
		||||
        self.ch4.poly = Default::default();
 | 
			
		||||
        self.ch4.freq = Default::default();
 | 
			
		||||
 | 
			
		||||
        self.ctrl.channel = Default::default();
 | 
			
		||||
        self.ctrl.out = Default::default();
 | 
			
		||||
 | 
			
		||||
        // Disable the Channels
 | 
			
		||||
        self.ch1.enabled = Default::default();
 | 
			
		||||
        self.ch2.enabled = Default::default();
 | 
			
		||||
        self.ch3.enabled = Default::default();
 | 
			
		||||
        self.ch4.enabled = Default::default();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) {
 | 
			
		||||
        if freq_hi.length_disable() && *length_timer > 0 {
 | 
			
		||||
            *length_timer -= 1;
 | 
			
		||||
 | 
			
		||||
            // Check in this scope ensures (only) the above subtraction
 | 
			
		||||
            // made length_timer 0
 | 
			
		||||
            if *length_timer == 0 {
 | 
			
		||||
                *enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
 | 
			
		||||
        if freq.length_disable() && *length_timer > 0 {
 | 
			
		||||
            *length_timer -= 1;
 | 
			
		||||
 | 
			
		||||
            // Check in this scope ensures (only) the above subtraction
 | 
			
		||||
            // made length_timer 0
 | 
			
		||||
            if *length_timer == 0 {
 | 
			
		||||
                *enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clock_length(&mut self) {
 | 
			
		||||
        Self::process_length(
 | 
			
		||||
            &self.ch1.freq_hi,
 | 
			
		||||
            &mut self.ch1.length_timer,
 | 
			
		||||
            &mut self.ch1.enabled,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self::process_length(
 | 
			
		||||
            &self.ch2.freq_hi,
 | 
			
		||||
            &mut self.ch2.length_timer,
 | 
			
		||||
            &mut self.ch2.enabled,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self::process_length(
 | 
			
		||||
            &self.ch3.freq_hi,
 | 
			
		||||
            &mut self.ch3.length_timer,
 | 
			
		||||
            &mut self.ch3.enabled,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self::ch4_process_length(
 | 
			
		||||
            &self.ch4.freq,
 | 
			
		||||
            &mut self.ch4.length_timer,
 | 
			
		||||
            &mut self.ch4.enabled,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clock_sweep(&mut self) {
 | 
			
		||||
        if self.ch1.sweep_timer != 0 {
 | 
			
		||||
            self.ch1.sweep_timer -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.ch1.sweep_timer == 0 {
 | 
			
		||||
            let period = self.ch1.sweep.period();
 | 
			
		||||
            self.ch1.sweep_timer = if period == 0 { 8 } else { period };
 | 
			
		||||
 | 
			
		||||
            if self.ch1.sweep_enabled && period != 0 {
 | 
			
		||||
                let new_freq = self.ch1.calc_sweep_freq();
 | 
			
		||||
 | 
			
		||||
                if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 {
 | 
			
		||||
                    self.ch1.set_frequency(new_freq);
 | 
			
		||||
                    self.ch1.shadow_freq = new_freq;
 | 
			
		||||
 | 
			
		||||
                    let _ = self.ch1.calc_sweep_freq();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn process_envelope(envelope: &VolumeEnvelope, period_timer: &mut u8, current_volume: &mut u8) {
 | 
			
		||||
        use EnvelopeDirection::*;
 | 
			
		||||
 | 
			
		||||
        if envelope.period() != 0 {
 | 
			
		||||
            if *period_timer != 0 {
 | 
			
		||||
                *period_timer -= 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if *period_timer == 0 {
 | 
			
		||||
                *period_timer = envelope.period();
 | 
			
		||||
 | 
			
		||||
                match envelope.direction() {
 | 
			
		||||
                    Decrease if *current_volume > 0x00 => *current_volume -= 1,
 | 
			
		||||
                    Increase if *current_volume < 0x0F => *current_volume += 1,
 | 
			
		||||
                    _ => {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clock_envelope(&mut self) {
 | 
			
		||||
        // Channels 1, 2 and 4 have Volume Envelopes
 | 
			
		||||
 | 
			
		||||
        Self::process_envelope(
 | 
			
		||||
            &self.ch1.envelope,
 | 
			
		||||
            &mut self.ch1.period_timer,
 | 
			
		||||
            &mut self.ch1.current_volume,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self::process_envelope(
 | 
			
		||||
            &self.ch2.envelope,
 | 
			
		||||
            &mut self.ch2.period_timer,
 | 
			
		||||
            &mut self.ch2.current_volume,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Self::process_envelope(
 | 
			
		||||
            &self.ch4.envelope,
 | 
			
		||||
            &mut self.ch4.period_timer,
 | 
			
		||||
            &mut self.ch4.current_volume,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_falling_edge(&self, bit: u8, div: u16) -> bool {
 | 
			
		||||
        match self.div_prev {
 | 
			
		||||
            Some(p) => (p >> bit & 0x01) == 0x01 && (div >> bit & 0x01) == 0x00,
 | 
			
		||||
            None => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct SoundControl {
 | 
			
		||||
    /// 0xFF24 | NR50 - Channel Control
 | 
			
		||||
    channel: ChannelControl,
 | 
			
		||||
    /// 0xFF25 | NR51 - Selection of Sound output terminal
 | 
			
		||||
    out: SoundOutput,
 | 
			
		||||
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SoundControl {
 | 
			
		||||
    /// 0xFF24 | NR50 - Channel Control
 | 
			
		||||
    pub(crate) fn channel(&self) -> u8 {
 | 
			
		||||
        u8::from(self.channel)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF24 | NR50 - Channel Control
 | 
			
		||||
    pub(crate) fn set_channel(&mut self, byte: u8) {
 | 
			
		||||
        if self.enabled {
 | 
			
		||||
            self.channel = byte.into();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF25 | NR51 - Selection of Sound output terminal
 | 
			
		||||
    pub(crate) fn output(&self) -> u8 {
 | 
			
		||||
        u8::from(self.out)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF25 | NR51 - Selection of Sound output terminal
 | 
			
		||||
    pub(crate) fn set_output(&mut self, byte: u8) {
 | 
			
		||||
        if self.enabled {
 | 
			
		||||
            self.out = byte.into();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF26 | NR52 - Sound On/Off
 | 
			
		||||
    pub(crate) fn status(&self, apu: &Apu) -> u8 {
 | 
			
		||||
        (self.enabled as u8) << 7
 | 
			
		||||
            | (apu.ch4.enabled as u8) << 3
 | 
			
		||||
            | (apu.ch3.enabled as u8) << 2
 | 
			
		||||
            | (apu.ch2.enabled as u8) << 1
 | 
			
		||||
            | apu.ch1.enabled as u8
 | 
			
		||||
            | 0x70
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Channel1 {
 | 
			
		||||
    /// 0xFF10 | NR10 - Channel 1 Sweep Register
 | 
			
		||||
    sweep: Sweep,
 | 
			
		||||
    /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
 | 
			
		||||
    duty: SoundDuty,
 | 
			
		||||
    /// 0xFF12 | NR12 - Channel 1 Volume Envelope
 | 
			
		||||
    envelope: VolumeEnvelope,
 | 
			
		||||
    /// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only)
 | 
			
		||||
    freq_lo: u8,
 | 
			
		||||
    /// 0xFF14 | NR14 - Channel 1 Frequency high
 | 
			
		||||
    freq_hi: FrequencyHigh,
 | 
			
		||||
 | 
			
		||||
    // Envelope Functionality
 | 
			
		||||
    period_timer: u8,
 | 
			
		||||
    current_volume: u8,
 | 
			
		||||
 | 
			
		||||
    // Sweep Functionality
 | 
			
		||||
    sweep_timer: u8,
 | 
			
		||||
    shadow_freq: u16,
 | 
			
		||||
    sweep_enabled: bool,
 | 
			
		||||
 | 
			
		||||
    // Length Functionality
 | 
			
		||||
    length_timer: u16,
 | 
			
		||||
 | 
			
		||||
    freq_timer: u16,
 | 
			
		||||
    duty_pos: u8,
 | 
			
		||||
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Channel1 {
 | 
			
		||||
    /// 0xFF10 | NR10 - Channel 1 Sweep Register
 | 
			
		||||
    pub(crate) fn sweep(&self) -> u8 {
 | 
			
		||||
        u8::from(self.sweep) | 0x80
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF10 | NR10 - Channel 1 Sweep Register
 | 
			
		||||
    pub(crate) fn set_sweep(&mut self, byte: u8) {
 | 
			
		||||
        self.sweep = byte.into()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
 | 
			
		||||
    pub(crate) fn duty(&self) -> u8 {
 | 
			
		||||
        u8::from(self.duty) | 0x3F
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
 | 
			
		||||
    pub(crate) fn set_duty(&mut self, byte: u8) {
 | 
			
		||||
        self.duty = byte.into();
 | 
			
		||||
        self.length_timer = 64 - self.duty.sound_length() as u16;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF12 | NR12 - Channel 1 Volume Envelope
 | 
			
		||||
    pub fn envelope(&self) -> u8 {
 | 
			
		||||
        u8::from(self.envelope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF12 | NR12 - Channel 1 Volume Envelope
 | 
			
		||||
    pub(crate) fn set_envelope(&mut self, byte: u8) {
 | 
			
		||||
        self.envelope = byte.into();
 | 
			
		||||
 | 
			
		||||
        if !self.is_dac_enabled() {
 | 
			
		||||
            self.enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only)
 | 
			
		||||
    pub(crate) fn set_freq_lo(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_lo = byte;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF14 | NR14 - Channel 1 Frequency high
 | 
			
		||||
    pub(crate) fn freq_hi(&self) -> u8 {
 | 
			
		||||
        u8::from(self.freq_hi) | 0xBF
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF14 | NR14 - Channel 1 Frequency high
 | 
			
		||||
    pub(crate) fn set_freq_hi(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_hi = byte.into();
 | 
			
		||||
 | 
			
		||||
        // If this bit is set, a trigger event occurs
 | 
			
		||||
        if self.freq_hi.initial() {
 | 
			
		||||
            if self.is_dac_enabled() {
 | 
			
		||||
                self.enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Length behaviour during trigger event
 | 
			
		||||
            if self.length_timer == 0 {
 | 
			
		||||
                self.length_timer = 64;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Envelope Behaviour during trigger event
 | 
			
		||||
            self.period_timer = self.envelope.period();
 | 
			
		||||
            self.current_volume = self.envelope.init_vol();
 | 
			
		||||
 | 
			
		||||
            // Sweep behaviour during trigger event
 | 
			
		||||
            let sweep_period = self.sweep.period();
 | 
			
		||||
            let sweep_shift = self.sweep.shift_count();
 | 
			
		||||
 | 
			
		||||
            self.shadow_freq = self.frequency();
 | 
			
		||||
            self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period };
 | 
			
		||||
 | 
			
		||||
            self.sweep_enabled = sweep_period != 0 || sweep_shift != 0;
 | 
			
		||||
 | 
			
		||||
            if sweep_shift != 0 {
 | 
			
		||||
                let _ = self.calc_sweep_freq();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn tick(&mut self) {
 | 
			
		||||
        if self.freq_timer != 0 {
 | 
			
		||||
            self.freq_timer -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.freq_timer == 0 {
 | 
			
		||||
            self.freq_timer = (2048 - self.frequency()) * 4;
 | 
			
		||||
            self.duty_pos = (self.duty_pos + 1) % 8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn amplitude(&self) -> f32 {
 | 
			
		||||
        if self.is_dac_enabled() {
 | 
			
		||||
            let sample = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume;
 | 
			
		||||
            let input = if self.enabled { sample } else { 0 };
 | 
			
		||||
 | 
			
		||||
            (input as f32 / 7.5) - 1.0
 | 
			
		||||
        } else {
 | 
			
		||||
            0.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn calc_sweep_freq(&mut self) -> u16 {
 | 
			
		||||
        use SweepDirection::*;
 | 
			
		||||
 | 
			
		||||
        let shadow_freq_shifted = self.shadow_freq >> self.sweep.shift_count();
 | 
			
		||||
        let new_freq = match self.sweep.direction() {
 | 
			
		||||
            Increase => self.shadow_freq + shadow_freq_shifted,
 | 
			
		||||
            Decrease => self.shadow_freq - shadow_freq_shifted,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Overflow check
 | 
			
		||||
        if new_freq > 2047 {
 | 
			
		||||
            self.enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        new_freq
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_frequency(&mut self, word: u16) {
 | 
			
		||||
        let freq_bits = word & 0x07FF;
 | 
			
		||||
        self.freq_lo = (freq_bits & 0x00FF) as u8;
 | 
			
		||||
        self.freq_hi
 | 
			
		||||
            .set_freq_bits(((freq_bits & 0x0700) >> 8) as u8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn frequency(&self) -> u16 {
 | 
			
		||||
        (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_dac_enabled(&self) -> bool {
 | 
			
		||||
        self.envelope.0 & 0xF8 != 0x00
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Channel2 {
 | 
			
		||||
    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
			
		||||
    duty: SoundDuty,
 | 
			
		||||
    /// 0xFF17 | NR22 - Channel 2 Volume ENvelope
 | 
			
		||||
    envelope: VolumeEnvelope,
 | 
			
		||||
    /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
 | 
			
		||||
    freq_lo: u8,
 | 
			
		||||
    /// 0xFF19 | NR24 - Channel 2 Frequency high
 | 
			
		||||
    freq_hi: FrequencyHigh,
 | 
			
		||||
 | 
			
		||||
    // Envelope Functionality
 | 
			
		||||
    period_timer: u8,
 | 
			
		||||
    current_volume: u8,
 | 
			
		||||
 | 
			
		||||
    // Length Functionality
 | 
			
		||||
    length_timer: u16,
 | 
			
		||||
 | 
			
		||||
    freq_timer: u16,
 | 
			
		||||
    duty_pos: u8,
 | 
			
		||||
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Channel2 {
 | 
			
		||||
    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
			
		||||
    pub(crate) fn duty(&self) -> u8 {
 | 
			
		||||
        u8::from(self.duty) | 0x3F
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
			
		||||
    pub(crate) fn set_duty(&mut self, byte: u8) {
 | 
			
		||||
        self.duty = byte.into();
 | 
			
		||||
        self.length_timer = 64 - self.duty.sound_length() as u16;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF17 | NR22 - Channel 2 Volume ENvelope
 | 
			
		||||
    pub(crate) fn envelope(&self) -> u8 {
 | 
			
		||||
        u8::from(self.envelope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF17 | NR22 - Channel 2 Volume ENvelope
 | 
			
		||||
    pub(crate) fn set_envelope(&mut self, byte: u8) {
 | 
			
		||||
        self.envelope = byte.into();
 | 
			
		||||
 | 
			
		||||
        if !self.is_dac_enabled() {
 | 
			
		||||
            self.enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
 | 
			
		||||
    pub(crate) fn set_freq_lo(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_lo = byte;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF19 | NR24 - Channel 2 Frequency high
 | 
			
		||||
    pub(crate) fn freq_hi(&self) -> u8 {
 | 
			
		||||
        u8::from(self.freq_hi) | 0xBF
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF19 | NR24 - Channel 2 Frequency high
 | 
			
		||||
    pub(crate) fn set_freq_hi(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_hi = byte.into();
 | 
			
		||||
 | 
			
		||||
        if self.freq_hi.initial() {
 | 
			
		||||
            // Envelope behaviour during trigger event
 | 
			
		||||
            self.period_timer = self.envelope.period();
 | 
			
		||||
            self.current_volume = self.envelope.init_vol();
 | 
			
		||||
 | 
			
		||||
            // Length behaviour during trigger event
 | 
			
		||||
            if self.length_timer == 0 {
 | 
			
		||||
                self.length_timer = 64;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if self.is_dac_enabled() {
 | 
			
		||||
                self.enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn amplitude(&self) -> f32 {
 | 
			
		||||
        if self.is_dac_enabled() {
 | 
			
		||||
            let sample = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume;
 | 
			
		||||
            let input = if self.enabled { sample } else { 0 };
 | 
			
		||||
 | 
			
		||||
            (input as f32 / 7.5) - 1.0
 | 
			
		||||
        } else {
 | 
			
		||||
            0.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn tick(&mut self) {
 | 
			
		||||
        if self.freq_timer != 0 {
 | 
			
		||||
            self.freq_timer -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.freq_timer == 0 {
 | 
			
		||||
            self.freq_timer = (2048 - self.frequency()) * 4;
 | 
			
		||||
            self.duty_pos = (self.duty_pos + 1) % 8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn frequency(&self) -> u16 {
 | 
			
		||||
        (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_dac_enabled(&self) -> bool {
 | 
			
		||||
        self.envelope.0 & 0xF8 != 0x00
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Channel3 {
 | 
			
		||||
    /// 0xFF1A | NR30 - Channel 3 Sound on/off
 | 
			
		||||
    dac_enabled: bool,
 | 
			
		||||
    /// 0xFF1B | NR31 - Sound Length
 | 
			
		||||
    len: u8,
 | 
			
		||||
    /// 0xFF1C | NR32 - Channel 3 Volume
 | 
			
		||||
    volume: Ch3Volume,
 | 
			
		||||
    /// 0xFF1D | NR33 - Channel 3 Frequency low (lower 8 bits)
 | 
			
		||||
    freq_lo: u8,
 | 
			
		||||
    /// 0xFF1E | NR34 - Channel 3 Frequency high
 | 
			
		||||
    freq_hi: FrequencyHigh,
 | 
			
		||||
 | 
			
		||||
    wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
 | 
			
		||||
 | 
			
		||||
    // Length Functionality
 | 
			
		||||
    length_timer: u16,
 | 
			
		||||
 | 
			
		||||
    freq_timer: u16,
 | 
			
		||||
    offset: u8,
 | 
			
		||||
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BusIo for Channel3 {
 | 
			
		||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
			
		||||
        if self.enabled {
 | 
			
		||||
            self.wave_ram[self.offset as usize / 2]
 | 
			
		||||
        } else {
 | 
			
		||||
            self.wave_ram[(addr - Self::WAVE_RAM_START_ADDR) as usize]
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        if self.enabled {
 | 
			
		||||
            self.wave_ram[self.offset as usize / 2] = byte;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.wave_ram[(addr - Self::WAVE_RAM_START_ADDR) as usize] = byte;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Channel3 {
 | 
			
		||||
    const WAVE_RAM_START_ADDR: u16 = 0xFF30;
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1A | NR30 - Channel 3 Sound on/off
 | 
			
		||||
    pub(crate) fn dac_enabled(&self) -> u8 {
 | 
			
		||||
        ((self.dac_enabled as u8) << 7) | 0x7F
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1A | NR30 - Channel 3 Sound on/off
 | 
			
		||||
    pub(crate) fn set_dac_enabled(&mut self, byte: u8) {
 | 
			
		||||
        self.dac_enabled = (byte >> 7) & 0x01 == 0x01;
 | 
			
		||||
 | 
			
		||||
        if !self.dac_enabled {
 | 
			
		||||
            self.enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1B | NR31 - Sound Length
 | 
			
		||||
    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
			
		||||
        self.len = byte;
 | 
			
		||||
        self.length_timer = 256 - self.len as u16;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1C | NR32 - Channel 3 Volume
 | 
			
		||||
    pub(crate) fn volume(&self) -> u8 {
 | 
			
		||||
        ((self.volume as u8) << 5) | 0x9F
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1C | NR32 - Channel 3 Volume
 | 
			
		||||
    pub(crate) fn set_volume(&mut self, byte: u8) {
 | 
			
		||||
        use Ch3Volume::*;
 | 
			
		||||
 | 
			
		||||
        self.volume = match (byte >> 5) & 0x03 {
 | 
			
		||||
            0b00 => Mute,
 | 
			
		||||
            0b01 => Full,
 | 
			
		||||
            0b10 => Half,
 | 
			
		||||
            0b11 => Quarter,
 | 
			
		||||
            _ => unreachable!("{:#04X} is not a valid value for Channel3Volume", byte),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1D | NR33 - Channel 3 Frequency low (lower 8 bits)
 | 
			
		||||
    pub(crate) fn set_freq_lo(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_lo = byte;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1E | NR34 - Channel 3 Frequency high
 | 
			
		||||
    pub(crate) fn freq_hi(&self) -> u8 {
 | 
			
		||||
        u8::from(self.freq_hi) | 0xBF
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF1E | NR34 - Channel 3 Frequency high
 | 
			
		||||
    pub(crate) fn set_freq_hi(&mut self, byte: u8) {
 | 
			
		||||
        self.freq_hi = byte.into();
 | 
			
		||||
 | 
			
		||||
        if self.freq_hi.initial() {
 | 
			
		||||
            // Length behaviour during trigger event
 | 
			
		||||
            if self.length_timer == 0 {
 | 
			
		||||
                self.length_timer = 256;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if self.dac_enabled {
 | 
			
		||||
                self.enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn tick(&mut self) {
 | 
			
		||||
        if self.freq_timer != 0 {
 | 
			
		||||
            self.freq_timer -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.freq_timer == 0 {
 | 
			
		||||
            self.freq_timer = (2048 - self.frequency()) * 2;
 | 
			
		||||
            self.offset = (self.offset + 1) % (WAVE_PATTERN_RAM_LEN * 2) as u8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn amplitude(&self) -> f32 {
 | 
			
		||||
        if self.dac_enabled {
 | 
			
		||||
            let sample = self.read_sample(self.offset) >> self.volume.shift_count();
 | 
			
		||||
            let input = if self.enabled { sample } else { 0 };
 | 
			
		||||
 | 
			
		||||
            (input as f32 / 7.5) - 1.0
 | 
			
		||||
        } else {
 | 
			
		||||
            0.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn read_sample(&self, index: u8) -> u8 {
 | 
			
		||||
        let i = (index / 2) as usize;
 | 
			
		||||
 | 
			
		||||
        if index % 2 == 0 {
 | 
			
		||||
            // We grab the high nibble on even indexes
 | 
			
		||||
            self.wave_ram[i] >> 4
 | 
			
		||||
        } else {
 | 
			
		||||
            // We grab the low nibble on odd indexes
 | 
			
		||||
            self.wave_ram[i] & 0x0F
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn frequency(&self) -> u16 {
 | 
			
		||||
        (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Channel4 {
 | 
			
		||||
    /// 0xFF20 | NR41 - Channel 4 Sound Length
 | 
			
		||||
    len: u8,
 | 
			
		||||
    /// 0xFF21 | NR42 - Channel 4 Volume Envelope
 | 
			
		||||
    envelope: VolumeEnvelope,
 | 
			
		||||
    /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter
 | 
			
		||||
    poly: PolynomialCounter,
 | 
			
		||||
    /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
 | 
			
		||||
    freq: Ch4Frequency,
 | 
			
		||||
 | 
			
		||||
    // Envelope Functionality
 | 
			
		||||
    period_timer: u8,
 | 
			
		||||
    current_volume: u8,
 | 
			
		||||
 | 
			
		||||
    // Length Functionality
 | 
			
		||||
    length_timer: u16,
 | 
			
		||||
 | 
			
		||||
    /// Linear Feedback Shift Register (15-bit)
 | 
			
		||||
    lf_shift: u16,
 | 
			
		||||
 | 
			
		||||
    freq_timer: u16,
 | 
			
		||||
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Channel4 {
 | 
			
		||||
    /// 0xFF20 | NR41 - Channel 4 Sound Length
 | 
			
		||||
    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
			
		||||
        self.len = byte & 0x3F;
 | 
			
		||||
        self.length_timer = 64 - self.len as u16;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF21 | NR42 - Channel 4 Volume Envelope
 | 
			
		||||
    pub(crate) fn envelope(&self) -> u8 {
 | 
			
		||||
        u8::from(self.envelope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF21 | NR42 - Channel 4 Volume Envelope
 | 
			
		||||
    pub(crate) fn set_envelope(&mut self, byte: u8) {
 | 
			
		||||
        self.envelope = byte.into();
 | 
			
		||||
 | 
			
		||||
        if !self.is_dac_enabled() {
 | 
			
		||||
            self.enabled = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter
 | 
			
		||||
    pub(crate) fn poly(&self) -> u8 {
 | 
			
		||||
        u8::from(self.poly)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter
 | 
			
		||||
    pub(crate) fn set_poly(&mut self, byte: u8) {
 | 
			
		||||
        self.poly = byte.into();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
 | 
			
		||||
    pub(crate) fn frequency(&self) -> u8 {
 | 
			
		||||
        u8::from(self.freq) | 0xBF
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
 | 
			
		||||
    pub(crate) fn set_frequency(&mut self, byte: u8) {
 | 
			
		||||
        self.freq = byte.into();
 | 
			
		||||
 | 
			
		||||
        if self.freq.initial() {
 | 
			
		||||
            // Envelope behaviour during trigger event
 | 
			
		||||
            self.period_timer = self.envelope.period();
 | 
			
		||||
            self.current_volume = self.envelope.init_vol();
 | 
			
		||||
 | 
			
		||||
            // Length behaviour during trigger event
 | 
			
		||||
            if self.length_timer == 0 {
 | 
			
		||||
                self.length_timer = 64;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // LFSR behaviour during trigger event
 | 
			
		||||
            self.lf_shift = 0x7FFF;
 | 
			
		||||
 | 
			
		||||
            if self.is_dac_enabled() {
 | 
			
		||||
                self.enabled = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn tick(&mut self) {
 | 
			
		||||
        if self.freq_timer != 0 {
 | 
			
		||||
            self.freq_timer -= 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.freq_timer == 0 {
 | 
			
		||||
            let divisor = Self::divisor(self.poly.divisor_code()) as u16;
 | 
			
		||||
            self.freq_timer = divisor << self.poly.shift_count();
 | 
			
		||||
 | 
			
		||||
            let xor_result = (self.lf_shift & 0x01) ^ ((self.lf_shift & 0x02) >> 1);
 | 
			
		||||
            self.lf_shift = (self.lf_shift >> 1) | xor_result << 14;
 | 
			
		||||
 | 
			
		||||
            if let CounterWidth::Long = self.poly.counter_width() {
 | 
			
		||||
                self.lf_shift = (self.lf_shift & !(0x01 << 6)) | xor_result << 6;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn amplitude(&self) -> f32 {
 | 
			
		||||
        if self.is_dac_enabled() {
 | 
			
		||||
            let sample = (!self.lf_shift & 0x01) as u8 * self.current_volume;
 | 
			
		||||
            let input = if self.enabled { sample } else { 0 };
 | 
			
		||||
 | 
			
		||||
            (input as f32 / 7.5) - 1.0
 | 
			
		||||
        } else {
 | 
			
		||||
            0.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_dac_enabled(&self) -> bool {
 | 
			
		||||
        self.envelope.0 & 0xF8 != 0x00
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn divisor(code: u8) -> u8 {
 | 
			
		||||
        if code == 0 {
 | 
			
		||||
            return 8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        code << 4
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
use rodio::Source;
 | 
			
		||||
use rtrb::{Consumer, Producer, PushError, RingBuffer};
 | 
			
		||||
 | 
			
		||||
pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz
 | 
			
		||||
const CHANNEL_COUNT: usize = 2;
 | 
			
		||||
const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels
 | 
			
		||||
 | 
			
		||||
pub struct AudioSPSC<T> {
 | 
			
		||||
    inner: RingBuffer<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Default for AudioSPSC<T> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            inner: RingBuffer::new(BUFFER_CAPACITY),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> AudioSPSC<T> {
 | 
			
		||||
    pub fn new(capacity: usize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            inner: RingBuffer::new(capacity),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) {
 | 
			
		||||
        let (prod, cons) = self.inner.split();
 | 
			
		||||
 | 
			
		||||
        (
 | 
			
		||||
            SampleProducer { inner: prod },
 | 
			
		||||
            SampleConsumer { inner: cons },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct SampleProducer<T> {
 | 
			
		||||
    inner: Producer<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> SampleProducer<T> {
 | 
			
		||||
    pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> {
 | 
			
		||||
        self.inner.push(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub(crate) fn available(&self) -> bool {
 | 
			
		||||
        self.inner.slots() > 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn available_blocking(&self) -> bool {
 | 
			
		||||
        loop {
 | 
			
		||||
            if self.inner.slots() > 2 {
 | 
			
		||||
                break true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> std::fmt::Debug for SampleProducer<T> {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>()))
 | 
			
		||||
            .finish_non_exhaustive()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct SampleConsumer<T> {
 | 
			
		||||
    inner: Consumer<T>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Iterator for SampleConsumer<f32> {
 | 
			
		||||
    type Item = f32;
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) -> Option<Self::Item> {
 | 
			
		||||
        // As of 2021-07-28, PopError can only be Empty
 | 
			
		||||
        Some(self.inner.pop().unwrap_or_default())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Source for SampleConsumer<f32> {
 | 
			
		||||
    fn current_frame_len(&self) -> Option<usize> {
 | 
			
		||||
        // A frame changes when the samples rate or
 | 
			
		||||
        // number of channels change. This will never happen, so
 | 
			
		||||
        // we return
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn channels(&self) -> u16 {
 | 
			
		||||
        // The Gameboy supports two channels
 | 
			
		||||
        CHANNEL_COUNT as u16
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn sample_rate(&self) -> u32 {
 | 
			
		||||
        SAMPLE_RATE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn total_duration(&self) -> Option<std::time::Duration> {
 | 
			
		||||
        // The duration of this source is infinite
 | 
			
		||||
        None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										577
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,577 @@
 | 
			
		||||
use bitfield::bitfield;
 | 
			
		||||
 | 
			
		||||
pub(crate) mod ch1 {
 | 
			
		||||
    use super::bitfield;
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct Sweep(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        _period, _: 6, 4;
 | 
			
		||||
        from into SweepDirection, _direction, _: 3, 3;
 | 
			
		||||
        _shift_count, _: 2, 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Sweep {
 | 
			
		||||
        pub(crate) fn period(&self) -> u8 {
 | 
			
		||||
            self._period()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn shift_count(&self) -> u8 {
 | 
			
		||||
            self._shift_count()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn direction(&self) -> SweepDirection {
 | 
			
		||||
            self._direction()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for Sweep {}
 | 
			
		||||
    impl Clone for Sweep {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Sweep {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for Sweep {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<Sweep> for u8 {
 | 
			
		||||
        fn from(sweep: Sweep) -> Self {
 | 
			
		||||
            sweep.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum SweepDirection {
 | 
			
		||||
        Increase = 0,
 | 
			
		||||
        Decrease = 1,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for SweepDirection {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            match byte & 0x01 {
 | 
			
		||||
                0b00 => Self::Increase,
 | 
			
		||||
                0b01 => Self::Decrease,
 | 
			
		||||
                _ => unreachable!("{:04X} is not a valid value for SweepChange", byte),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<SweepDirection> for u8 {
 | 
			
		||||
        fn from(change: SweepDirection) -> Self {
 | 
			
		||||
            change as u8
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) mod ch3 {
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum Volume {
 | 
			
		||||
        Mute = 0,
 | 
			
		||||
        Full = 1,
 | 
			
		||||
        Half = 2,
 | 
			
		||||
        Quarter = 3,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Volume {
 | 
			
		||||
        pub(crate) fn shift_count(&self) -> u8 {
 | 
			
		||||
            use Volume::*;
 | 
			
		||||
 | 
			
		||||
            match *self {
 | 
			
		||||
                Mute => 4,
 | 
			
		||||
                Full => 0,
 | 
			
		||||
                Half => 1,
 | 
			
		||||
                Quarter => 2,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Volume {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self::Mute
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) mod ch4 {
 | 
			
		||||
    use super::bitfield;
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct PolynomialCounter(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        _shift_count, _: 7, 4;
 | 
			
		||||
        from into CounterWidth, _counter_width, _: 3, 3;
 | 
			
		||||
        _divisor_code, _: 2, 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl PolynomialCounter {
 | 
			
		||||
        pub(crate) fn divisor_code(&self) -> u8 {
 | 
			
		||||
            self._divisor_code()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn counter_width(&self) -> CounterWidth {
 | 
			
		||||
            self._counter_width()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn shift_count(&self) -> u8 {
 | 
			
		||||
            self._shift_count()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for PolynomialCounter {}
 | 
			
		||||
    impl Clone for PolynomialCounter {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for PolynomialCounter {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for PolynomialCounter {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<PolynomialCounter> for u8 {
 | 
			
		||||
        fn from(poly: PolynomialCounter) -> Self {
 | 
			
		||||
            poly.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum CounterWidth {
 | 
			
		||||
        Long,  // 15 bits long
 | 
			
		||||
        Short, // 7 bits long
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for CounterWidth {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            match byte & 0x01 {
 | 
			
		||||
                0b00 => Self::Short,
 | 
			
		||||
                0b01 => Self::Long,
 | 
			
		||||
                _ => unreachable!("{:#04X} is not a valid value for CounterWidth"),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<CounterWidth> for u8 {
 | 
			
		||||
        fn from(counter_width: CounterWidth) -> Self {
 | 
			
		||||
            counter_width as u8
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct Frequency(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        _initial, _: 7;
 | 
			
		||||
        _length_disable, _: 6;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Frequency {
 | 
			
		||||
        pub(crate) fn length_disable(&self) -> bool {
 | 
			
		||||
            self._length_disable()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn initial(&self) -> bool {
 | 
			
		||||
            self._initial()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for Frequency {}
 | 
			
		||||
    impl Clone for Frequency {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for Frequency {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for Frequency {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte & 0xC0) // Only bits 7 and 6 hold anything of value
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<Frequency> for u8 {
 | 
			
		||||
        fn from(state: Frequency) -> Self {
 | 
			
		||||
            state.0 & 0x40 // Only bit 6 holds anything of value
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) mod common {
 | 
			
		||||
    use super::bitfield;
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct FrequencyHigh(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        _initial, _: 7;
 | 
			
		||||
        _length_disable, _: 6;
 | 
			
		||||
        pub freq_bits, set_freq_bits: 2, 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl FrequencyHigh {
 | 
			
		||||
        pub(crate) fn initial(&self) -> bool {
 | 
			
		||||
            self._initial()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn length_disable(&self) -> bool {
 | 
			
		||||
            self._length_disable()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for FrequencyHigh {}
 | 
			
		||||
    impl Clone for FrequencyHigh {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for FrequencyHigh {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for FrequencyHigh {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<FrequencyHigh> for u8 {
 | 
			
		||||
        fn from(freq: FrequencyHigh) -> Self {
 | 
			
		||||
            freq.0 & 0x40 // Only bit 6 can be read
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct VolumeEnvelope(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        _init_vol, _: 7, 4;
 | 
			
		||||
        from into EnvelopeDirection, _direction, _: 3, 3;
 | 
			
		||||
        _period, _: 2, 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl VolumeEnvelope {
 | 
			
		||||
        pub(crate) fn init_vol(&self) -> u8 {
 | 
			
		||||
            self._init_vol()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn direction(&self) -> EnvelopeDirection {
 | 
			
		||||
            self._direction()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn period(&self) -> u8 {
 | 
			
		||||
            self._period()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for VolumeEnvelope {}
 | 
			
		||||
    impl Clone for VolumeEnvelope {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for VolumeEnvelope {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for VolumeEnvelope {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<VolumeEnvelope> for u8 {
 | 
			
		||||
        fn from(envelope: VolumeEnvelope) -> Self {
 | 
			
		||||
            envelope.0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum EnvelopeDirection {
 | 
			
		||||
        Decrease = 0,
 | 
			
		||||
        Increase = 1,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for EnvelopeDirection {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            match byte & 0b01 {
 | 
			
		||||
                0b00 => Self::Decrease,
 | 
			
		||||
                0b01 => Self::Increase,
 | 
			
		||||
                _ => unreachable!("{:#04X} is not a valid value for EnvelopeDirection", byte),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<EnvelopeDirection> for u8 {
 | 
			
		||||
        fn from(direction: EnvelopeDirection) -> Self {
 | 
			
		||||
            direction as u8
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for EnvelopeDirection {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self::Decrease
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bitfield! {
 | 
			
		||||
        pub struct SoundDuty(u8);
 | 
			
		||||
        impl Debug;
 | 
			
		||||
        from into WavePattern, _wave_pattern, _: 7, 6;
 | 
			
		||||
        _sound_length, _: 5, 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl SoundDuty {
 | 
			
		||||
        pub(crate) fn wave_pattern(&self) -> WavePattern {
 | 
			
		||||
            self._wave_pattern()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn sound_length(&self) -> u8 {
 | 
			
		||||
            self._sound_length()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Copy for SoundDuty {}
 | 
			
		||||
    impl Clone for SoundDuty {
 | 
			
		||||
        fn clone(&self) -> Self {
 | 
			
		||||
            *self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for SoundDuty {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for SoundDuty {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            Self(byte)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<SoundDuty> for u8 {
 | 
			
		||||
        fn from(duty: SoundDuty) -> Self {
 | 
			
		||||
            duty.0 & 0xC0 // Only bits 7 and 6 can be read
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum WavePattern {
 | 
			
		||||
        OneEighth = 0,     // 12.5% ( _-------_-------_------- )
 | 
			
		||||
        OneQuarter = 1,    // 25%   ( __------__------__------ )
 | 
			
		||||
        OneHalf = 2,       // 50%   ( ____----____----____---- ) (normal)
 | 
			
		||||
        ThreeQuarters = 3, // 75%   ( ______--______--______-- )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl WavePattern {
 | 
			
		||||
        pub(crate) const fn amplitude(&self, index: u8) -> u8 {
 | 
			
		||||
            use WavePattern::*;
 | 
			
		||||
            let i = 7 - index; // an index of 0 should get the highest bit
 | 
			
		||||
 | 
			
		||||
            match *self {
 | 
			
		||||
                OneEighth => (0b00000001 >> i) & 0x01,
 | 
			
		||||
                OneQuarter => (0b10000001 >> i) & 0x01,
 | 
			
		||||
                OneHalf => (0b10000111 >> i) & 0x01,
 | 
			
		||||
                ThreeQuarters => (0b01111110 >> i) & 0x01,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for WavePattern {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self::OneEighth // Rationale: OneEighth is 0x00
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<WavePattern> for u8 {
 | 
			
		||||
        fn from(pattern: WavePattern) -> Self {
 | 
			
		||||
            pattern as Self
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl From<u8> for WavePattern {
 | 
			
		||||
        fn from(byte: u8) -> Self {
 | 
			
		||||
            match byte & 0b11 {
 | 
			
		||||
                0b00 => Self::OneEighth,
 | 
			
		||||
                0b01 => Self::OneQuarter,
 | 
			
		||||
                0b10 => Self::OneHalf,
 | 
			
		||||
                0b11 => Self::ThreeQuarters,
 | 
			
		||||
                _ => unreachable!("{:#04X} is not a valid value for WavePattern", byte),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bitfield! {
 | 
			
		||||
    pub struct SoundOutput(u8);
 | 
			
		||||
    impl Debug;
 | 
			
		||||
    pub ch4_left, _: 7;
 | 
			
		||||
    pub ch3_left, _: 6;
 | 
			
		||||
    pub ch2_left, _: 5;
 | 
			
		||||
    pub ch1_left, _: 4;
 | 
			
		||||
    pub ch4_right, _: 3;
 | 
			
		||||
    pub ch3_right, _: 2;
 | 
			
		||||
    pub ch2_right, _: 1;
 | 
			
		||||
    pub ch1_right, _: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SoundOutput {
 | 
			
		||||
    pub(super) fn ch1(&self) -> (bool, bool) {
 | 
			
		||||
        (self.ch1_left(), self.ch1_right())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn ch2(&self) -> (bool, bool) {
 | 
			
		||||
        (self.ch2_left(), self.ch2_right())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn ch3(&self) -> (bool, bool) {
 | 
			
		||||
        (self.ch3_left(), self.ch3_right())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(super) fn ch4(&self) -> (bool, bool) {
 | 
			
		||||
        (self.ch4_left(), self.ch4_right())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Copy for SoundOutput {}
 | 
			
		||||
impl Clone for SoundOutput {
 | 
			
		||||
    fn clone(&self) -> Self {
 | 
			
		||||
        *self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for SoundOutput {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self(0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<u8> for SoundOutput {
 | 
			
		||||
    fn from(byte: u8) -> Self {
 | 
			
		||||
        Self(byte)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<SoundOutput> for u8 {
 | 
			
		||||
    fn from(output: SoundOutput) -> Self {
 | 
			
		||||
        output.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bitfield! {
 | 
			
		||||
    pub struct ChannelControl(u8);
 | 
			
		||||
    impl Debug;
 | 
			
		||||
    vin_left, _: 7;
 | 
			
		||||
    _left_volume, _: 6, 4;
 | 
			
		||||
    vin_right, _: 3;
 | 
			
		||||
    _right_volume, _: 2, 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ChannelControl {
 | 
			
		||||
    pub(crate) fn left_volume(&self) -> f32 {
 | 
			
		||||
        self._left_volume() as f32
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn right_volume(&self) -> f32 {
 | 
			
		||||
        self._right_volume() as f32
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Copy for ChannelControl {}
 | 
			
		||||
impl Clone for ChannelControl {
 | 
			
		||||
    fn clone(&self) -> Self {
 | 
			
		||||
        *self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ChannelControl {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self(0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<u8> for ChannelControl {
 | 
			
		||||
    fn from(byte: u8) -> Self {
 | 
			
		||||
        Self(byte)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ChannelControl> for u8 {
 | 
			
		||||
    fn from(ctrl: ChannelControl) -> Self {
 | 
			
		||||
        ctrl.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(super) mod fs {
 | 
			
		||||
    #[derive(Debug)]
 | 
			
		||||
    pub(crate) struct FrameSequencer {
 | 
			
		||||
        step: u8,
 | 
			
		||||
        state: State,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl Default for FrameSequencer {
 | 
			
		||||
        fn default() -> Self {
 | 
			
		||||
            Self {
 | 
			
		||||
                step: Default::default(),
 | 
			
		||||
                state: State::Length,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl FrameSequencer {
 | 
			
		||||
        pub(crate) fn next(&mut self) {
 | 
			
		||||
            use State::*;
 | 
			
		||||
 | 
			
		||||
            self.step = (self.step + 1) % 8;
 | 
			
		||||
            self.state = match self.step {
 | 
			
		||||
                1 | 3 | 5 => Nothing,
 | 
			
		||||
                0 | 4 => Length,
 | 
			
		||||
                2 | 6 => LengthAndSweep,
 | 
			
		||||
                7 => Envelope,
 | 
			
		||||
                _ => unreachable!("Step {} is invalid for the Frame Sequencer", self.step),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn state(&self) -> State {
 | 
			
		||||
            self.state
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub(crate) fn reset(&mut self) {
 | 
			
		||||
            self.step = Default::default();
 | 
			
		||||
            self.state = State::Length;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Clone, Copy)]
 | 
			
		||||
    pub(crate) enum State {
 | 
			
		||||
        Length,
 | 
			
		||||
        Nothing,
 | 
			
		||||
        LengthAndSweep,
 | 
			
		||||
        Envelope,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										153
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								src/bus.rs
									
									
									
									
									
								
							@@ -1,26 +1,25 @@
 | 
			
		||||
use crate::apu::Apu;
 | 
			
		||||
use crate::cartridge::Cartridge;
 | 
			
		||||
use crate::high_ram::HighRam;
 | 
			
		||||
use crate::interrupt::{Interrupt, InterruptFlag};
 | 
			
		||||
use crate::joypad::Joypad;
 | 
			
		||||
use crate::ppu::{Ppu, PpuMode};
 | 
			
		||||
use crate::serial::Serial;
 | 
			
		||||
use crate::sound::{AudioSender, Sound};
 | 
			
		||||
use crate::timer::Timer;
 | 
			
		||||
use crate::work_ram::{VariableWorkRam, WorkRam};
 | 
			
		||||
use std::{fs::File, io::Read};
 | 
			
		||||
 | 
			
		||||
const BOOT_ROM_SIZE: usize = 0x100;
 | 
			
		||||
pub(crate) const BOOT_SIZE: usize = 0x100;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct Bus {
 | 
			
		||||
    boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long
 | 
			
		||||
    cartridge: Option<Cartridge>,
 | 
			
		||||
    pub ppu: Ppu,
 | 
			
		||||
    boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
 | 
			
		||||
    cart: Option<Cartridge>,
 | 
			
		||||
    pub(crate) ppu: Ppu,
 | 
			
		||||
    work_ram: WorkRam,
 | 
			
		||||
    var_ram: VariableWorkRam,
 | 
			
		||||
    pub(crate) timer: Timer,
 | 
			
		||||
    int: Interrupt,
 | 
			
		||||
    snd: Sound,
 | 
			
		||||
    pub(crate) apu: Apu,
 | 
			
		||||
    high_ram: HighRam,
 | 
			
		||||
    serial: Serial,
 | 
			
		||||
    pub(crate) joypad: Joypad,
 | 
			
		||||
@@ -30,13 +29,13 @@ impl Default for Bus {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            boot: None,
 | 
			
		||||
            cartridge: None,
 | 
			
		||||
            cart: None,
 | 
			
		||||
            ppu: Default::default(),
 | 
			
		||||
            work_ram: Default::default(),
 | 
			
		||||
            var_ram: Default::default(),
 | 
			
		||||
            timer: Default::default(),
 | 
			
		||||
            int: Default::default(),
 | 
			
		||||
            snd: Default::default(),
 | 
			
		||||
            apu: Default::default(),
 | 
			
		||||
            high_ram: Default::default(),
 | 
			
		||||
            serial: Default::default(),
 | 
			
		||||
            joypad: Default::default(),
 | 
			
		||||
@@ -45,48 +44,41 @@ impl Default for Bus {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Bus {
 | 
			
		||||
    pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        let mut file = File::open(path)?;
 | 
			
		||||
        let mut boot_rom = [0u8; 256];
 | 
			
		||||
 | 
			
		||||
        file.read_exact(&mut boot_rom)?;
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            boot: Some(boot_rom),
 | 
			
		||||
    pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            boot: Some(rom),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
 | 
			
		||||
        self.cartridge = Some(Cartridge::new(path)?);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
 | 
			
		||||
        self.cart = Some(Cartridge::new(rom));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn rom_title(&self) -> Option<&str> {
 | 
			
		||||
        self.cartridge.as_ref()?.title()
 | 
			
		||||
    pub(crate) fn cart_title(&self) -> Option<&str> {
 | 
			
		||||
        self.cart.as_ref()?.title()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn pass_audio_src(&mut self, sender: AudioSender<f32>) {
 | 
			
		||||
        self.snd.set_audio_src(sender)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn is_full(&self) -> bool {
 | 
			
		||||
        self.snd.is_full()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn flush_samples(&mut self) {
 | 
			
		||||
        self.snd.flush_samples()
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub(crate) fn boot_mapped(&self) -> bool {
 | 
			
		||||
        self.boot.is_some()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn clock(&mut self) {
 | 
			
		||||
        self.ppu.clock();
 | 
			
		||||
        self.timer.clock();
 | 
			
		||||
        self.snd.clock(self.timer.divider);
 | 
			
		||||
        self.clock_dma();
 | 
			
		||||
        self.tick(4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clock_dma(&mut self) {
 | 
			
		||||
        if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() {
 | 
			
		||||
    fn tick(&mut self, limit: u8) {
 | 
			
		||||
        for _ in 0..limit {
 | 
			
		||||
            self.timer.tick();
 | 
			
		||||
            self.ppu.tick();
 | 
			
		||||
            self.apu.tick(self.timer.divider);
 | 
			
		||||
            self.dma_tick()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn dma_tick(&mut self) {
 | 
			
		||||
        if let Some((src_addr, dest_addr)) = self.ppu.dma.tick() {
 | 
			
		||||
            let byte = self.oam_read_byte(src_addr);
 | 
			
		||||
            self.oam_write_byte(dest_addr, byte);
 | 
			
		||||
        }
 | 
			
		||||
@@ -105,13 +97,13 @@ impl Bus {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                match self.cartridge.as_ref() {
 | 
			
		||||
                match self.cart.as_ref() {
 | 
			
		||||
                    Some(cart) => cart.read_byte(addr),
 | 
			
		||||
                    None => panic!("Tried to read from a non-existent cartridge"),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
 | 
			
		||||
            0xA000..=0xBFFF => match self.cartridge.as_ref() {
 | 
			
		||||
            0xA000..=0xBFFF => match self.cart.as_ref() {
 | 
			
		||||
                // 8KB External RAM
 | 
			
		||||
                Some(cart) => cart.read_byte(addr),
 | 
			
		||||
                None => panic!("Tried to read from a non-existent cartridge"),
 | 
			
		||||
@@ -158,7 +150,7 @@ impl BusIo for Bus {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                match self.cartridge.as_ref() {
 | 
			
		||||
                match self.cart.as_ref() {
 | 
			
		||||
                    Some(cart) => cart.read_byte(addr),
 | 
			
		||||
                    None => panic!("Tried to read from a non-existent cartridge"),
 | 
			
		||||
                }
 | 
			
		||||
@@ -170,7 +162,7 @@ impl BusIo for Bus {
 | 
			
		||||
                    _ => self.ppu.read_byte(addr),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => match self.cartridge.as_ref() {
 | 
			
		||||
            0xA000..=0xBFFF => match self.cart.as_ref() {
 | 
			
		||||
                // 8KB External RAM
 | 
			
		||||
                Some(cart) => cart.read_byte(addr),
 | 
			
		||||
                None => panic!("Tried to read from a non-existent cartridge"),
 | 
			
		||||
@@ -224,29 +216,11 @@ impl BusIo for Bus {
 | 
			
		||||
                    0x01 => self.serial.next,
 | 
			
		||||
                    0x02 => self.serial.ctrl.into(),
 | 
			
		||||
                    0x04 => (self.timer.divider >> 8) as u8,
 | 
			
		||||
                    0x05 => self.timer.counter,
 | 
			
		||||
                    0x05 => self.timer.tima(),
 | 
			
		||||
                    0x06 => self.timer.modulo,
 | 
			
		||||
                    0x07 => self.timer.ctrl.into(),
 | 
			
		||||
                    0x0F => self.interrupt_flag().into(),
 | 
			
		||||
                    0x10 => self.snd.ch1.sweep.into(),
 | 
			
		||||
                    0x11 => self.snd.ch1.duty(),
 | 
			
		||||
                    0x12 => self.snd.ch1.envelope.into(),
 | 
			
		||||
                    0x14 => self.snd.ch1.freq_hi(),
 | 
			
		||||
                    0x16 => self.snd.ch2.duty(),
 | 
			
		||||
                    0x17 => self.snd.ch2.envelope.into(),
 | 
			
		||||
                    0x19 => self.snd.ch2.freq_hi(),
 | 
			
		||||
                    0x1A => self.snd.ch3.enabled(),
 | 
			
		||||
                    0x1B => self.snd.ch3.len(),
 | 
			
		||||
                    0x1C => self.snd.ch3.volume(),
 | 
			
		||||
                    0x1E => self.snd.ch3.freq_hi(),
 | 
			
		||||
                    0x20 => self.snd.ch4.len(),
 | 
			
		||||
                    0x21 => self.snd.ch4.envelope.into(),
 | 
			
		||||
                    0x22 => self.snd.ch4.poly.into(),
 | 
			
		||||
                    0x23 => self.snd.ch4.freq_data(),
 | 
			
		||||
                    0x24 => self.snd.ctrl.channel.into(),
 | 
			
		||||
                    0x25 => self.snd.ctrl.output.into(),
 | 
			
		||||
                    0x26 => self.snd.ctrl.status(&self.snd),
 | 
			
		||||
                    0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30],
 | 
			
		||||
                    0x10..=0x3F => self.apu.read_byte(addr),
 | 
			
		||||
                    0x40 => self.ppu.ctrl.into(),
 | 
			
		||||
                    0x41 => self.ppu.stat.into(),
 | 
			
		||||
                    0x42 => self.ppu.pos.scroll_y,
 | 
			
		||||
@@ -259,8 +233,10 @@ impl BusIo for Bus {
 | 
			
		||||
                    0x49 => self.ppu.monochrome.obj_palette_1.into(),
 | 
			
		||||
                    0x4A => self.ppu.pos.window_y,
 | 
			
		||||
                    0x4B => self.ppu.pos.window_x,
 | 
			
		||||
                    0x4D => 0xFF, // CGB Specific Register
 | 
			
		||||
                    _ => unimplemented!("Unable to read {:#06X} in I/O Registers", addr),
 | 
			
		||||
                    _ => {
 | 
			
		||||
                        eprintln!("Read 0xFF from unused IO register {:#06X}.", addr);
 | 
			
		||||
                        0xFF
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0xFF80..=0xFFFE => {
 | 
			
		||||
@@ -279,7 +255,7 @@ impl BusIo for Bus {
 | 
			
		||||
            0x0000..=0x7FFF => {
 | 
			
		||||
                // 16KB ROM bank 00 (ends at 0x3FFF)
 | 
			
		||||
                // and 16KB ROM Bank 01 -> NN (switchable via MB)
 | 
			
		||||
                match self.cartridge.as_mut() {
 | 
			
		||||
                match self.cart.as_mut() {
 | 
			
		||||
                    Some(cart) => cart.write_byte(addr, byte),
 | 
			
		||||
                    None => panic!("Tried to write into non-existent cartridge"),
 | 
			
		||||
                }
 | 
			
		||||
@@ -293,7 +269,7 @@ impl BusIo for Bus {
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => {
 | 
			
		||||
                // 8KB External RAM
 | 
			
		||||
                match self.cartridge.as_mut() {
 | 
			
		||||
                match self.cart.as_mut() {
 | 
			
		||||
                    Some(cart) => cart.write_byte(addr, byte),
 | 
			
		||||
                    None => panic!("Tried to write into non-existent cartridge"),
 | 
			
		||||
                }
 | 
			
		||||
@@ -328,7 +304,7 @@ impl BusIo for Bus {
 | 
			
		||||
                    _ => {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing.
 | 
			
		||||
            0xFEA0..=0xFEFF => {} // FIXME: As far as I know, writes to here do nothing.
 | 
			
		||||
            0xFF00..=0xFF7F => {
 | 
			
		||||
                // IO Registers
 | 
			
		||||
 | 
			
		||||
@@ -339,32 +315,11 @@ impl BusIo for Bus {
 | 
			
		||||
                    0x01 => self.serial.next = byte,
 | 
			
		||||
                    0x02 => self.serial.ctrl = byte.into(),
 | 
			
		||||
                    0x04 => self.timer.divider = 0x0000,
 | 
			
		||||
                    0x05 => self.timer.counter = byte,
 | 
			
		||||
                    0x05 => self.timer.set_tima(byte),
 | 
			
		||||
                    0x06 => self.timer.modulo = byte,
 | 
			
		||||
                    0x07 => self.timer.ctrl = byte.into(),
 | 
			
		||||
                    0x0F => self.set_interrupt_flag(byte),
 | 
			
		||||
                    0x10 => self.snd.ch1.sweep = byte.into(),
 | 
			
		||||
                    0x11 => self.snd.ch1.set_duty(byte),
 | 
			
		||||
                    0x12 => self.snd.ch1.envelope = byte.into(),
 | 
			
		||||
                    0x13 => self.snd.ch1.set_freq_lo(byte),
 | 
			
		||||
                    0x14 => self.snd.ch1.set_freq_hi(byte),
 | 
			
		||||
                    0x16 => self.snd.ch2.set_duty(byte),
 | 
			
		||||
                    0x17 => self.snd.ch2.envelope = byte.into(),
 | 
			
		||||
                    0x18 => self.snd.ch2.set_freq_lo(byte),
 | 
			
		||||
                    0x19 => self.snd.ch2.set_freq_hi(byte),
 | 
			
		||||
                    0x1A => self.snd.ch3.set_enabled(byte),
 | 
			
		||||
                    0x1B => self.snd.ch3.set_len(byte),
 | 
			
		||||
                    0x1C => self.snd.ch3.set_volume(byte),
 | 
			
		||||
                    0x1D => self.snd.ch3.set_freq_lo(byte),
 | 
			
		||||
                    0x1E => self.snd.ch3.set_freq_hi(byte),
 | 
			
		||||
                    0x20 => self.snd.ch4.set_len(byte),
 | 
			
		||||
                    0x21 => self.snd.ch4.envelope = byte.into(),
 | 
			
		||||
                    0x22 => self.snd.ch4.poly = byte.into(),
 | 
			
		||||
                    0x23 => self.snd.ch4.set_freq_data(byte),
 | 
			
		||||
                    0x24 => self.snd.ctrl.channel = byte.into(),
 | 
			
		||||
                    0x25 => self.snd.ctrl.output = byte.into(),
 | 
			
		||||
                    0x26 => self.snd.ctrl.set_status(byte), // FIXME: Should we control which bytes are written to here?
 | 
			
		||||
                    0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30] = byte,
 | 
			
		||||
                    0x10..=0x3F => self.apu.write_byte(addr, byte),
 | 
			
		||||
                    0x40 => self.ppu.ctrl = byte.into(),
 | 
			
		||||
                    0x41 => self.ppu.stat.update(byte),
 | 
			
		||||
                    0x42 => self.ppu.pos.scroll_y = byte,
 | 
			
		||||
@@ -396,8 +351,7 @@ impl BusIo for Bus {
 | 
			
		||||
                            self.boot = None;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    0x7F => {} // Tetris tries to write to this non-existent IO Address
 | 
			
		||||
                    _ => unimplemented!("Unable to write to {:#06X} in I/O Registers", addr),
 | 
			
		||||
                    _ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr),
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            0xFF80..=0xFFFE => {
 | 
			
		||||
@@ -412,17 +366,6 @@ impl BusIo for Bus {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Bus {
 | 
			
		||||
    pub(crate) fn read_word(&self, addr: u16) -> u16 {
 | 
			
		||||
        (self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
 | 
			
		||||
        self.write_byte(addr + 1, (word >> 8) as u8);
 | 
			
		||||
        self.write_byte(addr, (word & 0x00FF) as u8);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Bus {
 | 
			
		||||
    fn interrupt_flag(&self) -> InterruptFlag {
 | 
			
		||||
        // Read the current interrupt information from the PPU
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										587
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										587
									
								
								src/cartridge.rs
									
									
									
									
									
								
							@@ -1,137 +1,99 @@
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::{self, Read};
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
 | 
			
		||||
use crate::bus::BusIo;
 | 
			
		||||
 | 
			
		||||
const RAM_SIZE_ADDRESS: usize = 0x0149;
 | 
			
		||||
const ROM_SIZE_ADDRESS: usize = 0x0148;
 | 
			
		||||
const MBC_TYPE_ADDRESS: usize = 0x0147;
 | 
			
		||||
const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Cartridge {
 | 
			
		||||
    memory: Vec<u8>,
 | 
			
		||||
    title: Option<String>,
 | 
			
		||||
    mbc: Box<dyn MemoryBankController>,
 | 
			
		||||
    mbc: Box<dyn MBCIo>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cartridge {
 | 
			
		||||
    pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
 | 
			
		||||
        let mut memory = vec![];
 | 
			
		||||
        let mut rom = File::open(path)?;
 | 
			
		||||
        rom.read_to_end(&mut memory)?;
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn new(memory: Vec<u8>) -> Self {
 | 
			
		||||
        let title = Self::find_title(&memory);
 | 
			
		||||
        eprintln!("Cartridge Title: {:?}", title);
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mbc: Self::detect_mbc(&memory),
 | 
			
		||||
            title,
 | 
			
		||||
            memory,
 | 
			
		||||
        })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn detect_mbc(memory: &[u8]) -> Box<dyn MemoryBankController> {
 | 
			
		||||
        let ram_size = Self::find_ram_size(memory);
 | 
			
		||||
        let bank_count = Self::find_bank_count(memory);
 | 
			
		||||
    fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
 | 
			
		||||
        let ram_size = Self::detect_ram_info(memory);
 | 
			
		||||
        let rom_size = Self::detect_rom_info(memory);
 | 
			
		||||
        let mbc_kind = Self::find_mbc(memory);
 | 
			
		||||
        let ram_byte_count = ram_size.len();
 | 
			
		||||
        let ram_cap = ram_size.capacity();
 | 
			
		||||
        let rom_cap = rom_size.capacity();
 | 
			
		||||
 | 
			
		||||
        eprintln!("Cartridge Ram Size: {} bytes", ram_size.len());
 | 
			
		||||
        eprintln!("Cartridge ROM Size: {} bytes", bank_count.size());
 | 
			
		||||
        eprintln!("Cartridge Ram Size: {} bytes", ram_cap);
 | 
			
		||||
        eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity());
 | 
			
		||||
        eprintln!("MBC Type: {:?}", mbc_kind);
 | 
			
		||||
 | 
			
		||||
        match mbc_kind {
 | 
			
		||||
            MbcKind::None => Box::new(NoMbc {}),
 | 
			
		||||
            MbcKind::Mbc1 => {
 | 
			
		||||
                let mbc = Mbc1 {
 | 
			
		||||
                    ram_size,
 | 
			
		||||
                    ram: vec![0; ram_byte_count as usize],
 | 
			
		||||
                    bank_count,
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Box::new(mbc)
 | 
			
		||||
            }
 | 
			
		||||
            MbcKind::Mbc1WithBattery => {
 | 
			
		||||
                // TODO: Implement Saving
 | 
			
		||||
                let mbc = Mbc1 {
 | 
			
		||||
                    ram_size,
 | 
			
		||||
                    ram: vec![0; ram_byte_count as usize],
 | 
			
		||||
                    bank_count,
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Box::new(mbc)
 | 
			
		||||
            }
 | 
			
		||||
            MbcKind::Mbc3WithBattery => {
 | 
			
		||||
                // TODO: Implement Saving
 | 
			
		||||
                let mbc = MBC3 {
 | 
			
		||||
                    ram_size,
 | 
			
		||||
                    ram: vec![0; ram_byte_count as usize],
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Box::new(mbc)
 | 
			
		||||
            }
 | 
			
		||||
            MbcKind::Mbc3 => {
 | 
			
		||||
                let mbc = MBC3 {
 | 
			
		||||
                    ram_size,
 | 
			
		||||
                    ram: vec![0; ram_byte_count as usize],
 | 
			
		||||
                    ..Default::default()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                Box::new(mbc)
 | 
			
		||||
            }
 | 
			
		||||
            MbcKind::Mbc5 => todo!("Implement MBC5"),
 | 
			
		||||
            MBCKind::None => Box::new(NoMBC),
 | 
			
		||||
            MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
 | 
			
		||||
            MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving
 | 
			
		||||
            MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
 | 
			
		||||
            MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving
 | 
			
		||||
            MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
 | 
			
		||||
            MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving
 | 
			
		||||
            MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
 | 
			
		||||
            MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn find_title(memory: &[u8]) -> Option<String> {
 | 
			
		||||
        // FIXME: Get rid of magic values and handle cases
 | 
			
		||||
        // where 0x134..0x143 reads past the length of the
 | 
			
		||||
        // string
 | 
			
		||||
        let slice = &memory[ROM_TITLE_RANGE];
 | 
			
		||||
        let with_nulls = std::str::from_utf8(slice).ok();
 | 
			
		||||
        let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim());
 | 
			
		||||
 | 
			
		||||
        let slice = &memory[0x134..0x143];
 | 
			
		||||
 | 
			
		||||
        let str_with_nulls = std::str::from_utf8(slice).ok();
 | 
			
		||||
        str_with_nulls.map(|s| s.trim_matches('\0').to_string())
 | 
			
		||||
        match trimmed {
 | 
			
		||||
            Some("") | None => None,
 | 
			
		||||
            Some(_) => trimmed.map(String::from),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn title(&self) -> Option<&str> {
 | 
			
		||||
        self.title.as_deref()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn find_ram_size(memory: &[u8]) -> RamSize {
 | 
			
		||||
    fn detect_ram_info(memory: &[u8]) -> RamSize {
 | 
			
		||||
        let id = memory[RAM_SIZE_ADDRESS];
 | 
			
		||||
        id.into()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn find_bank_count(memory: &[u8]) -> BankCount {
 | 
			
		||||
    fn detect_rom_info(memory: &[u8]) -> RomSize {
 | 
			
		||||
        let id = memory[ROM_SIZE_ADDRESS];
 | 
			
		||||
        id.into()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn find_mbc(memory: &[u8]) -> MbcKind {
 | 
			
		||||
        let id = memory[MBC_TYPE_ADDRESS];
 | 
			
		||||
    fn find_mbc(memory: &[u8]) -> MBCKind {
 | 
			
		||||
        use MBCKind::*;
 | 
			
		||||
 | 
			
		||||
        // TODO: Refactor this to match the other enums in this module
 | 
			
		||||
        match id {
 | 
			
		||||
            0x00 => MbcKind::None,
 | 
			
		||||
            0x01 => MbcKind::Mbc1,
 | 
			
		||||
            0x02 => MbcKind::Mbc1,
 | 
			
		||||
            0x03 => MbcKind::Mbc1WithBattery,
 | 
			
		||||
            0x19 => MbcKind::Mbc5,
 | 
			
		||||
            0x13 => MbcKind::Mbc3WithBattery,
 | 
			
		||||
            0x11 => MbcKind::Mbc3,
 | 
			
		||||
            _ => unimplemented!("id {:#04X} is an unsupported MBC", id),
 | 
			
		||||
        match memory[MBC_TYPE_ADDRESS] {
 | 
			
		||||
            0x00 => None,
 | 
			
		||||
            0x01 | 0x02 => MBC1,
 | 
			
		||||
            0x03 => MBC1WithBattery,
 | 
			
		||||
            0x05 => MBC2,
 | 
			
		||||
            0x06 => MBC2WithBattery,
 | 
			
		||||
            0x19 | 0x1A => MBC5,
 | 
			
		||||
            0x1B => MBC5WithBattery,
 | 
			
		||||
            0x13 => MBC3WithBattery,
 | 
			
		||||
            0x11 | 0x12 => MBC3,
 | 
			
		||||
            id => unimplemented!("id {:#04X} is an unsupported MBC", id),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BusIo for Cartridge {
 | 
			
		||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
			
		||||
        use MbcResult::*;
 | 
			
		||||
        use MBCResult::*;
 | 
			
		||||
 | 
			
		||||
        match self.mbc.handle_read(addr) {
 | 
			
		||||
            Address(addr) => self.memory[addr],
 | 
			
		||||
@@ -144,92 +106,90 @@ impl BusIo for Cartridge {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct Mbc1 {
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MBC1 {
 | 
			
		||||
    /// 5-bit number
 | 
			
		||||
    rom_bank: u8,
 | 
			
		||||
    /// 2-bit number
 | 
			
		||||
    ram_bank: u8,
 | 
			
		||||
    mode: bool,
 | 
			
		||||
    ram_size: RamSize,
 | 
			
		||||
    ram: Vec<u8>,
 | 
			
		||||
    bank_count: BankCount,
 | 
			
		||||
    ram_enabled: bool,
 | 
			
		||||
    memory: Vec<u8>,
 | 
			
		||||
    rom_size: RomSize,
 | 
			
		||||
    mem_enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Mbc1 {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
impl MBC1 {
 | 
			
		||||
    fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            rom_bank: 0x01,
 | 
			
		||||
            memory: vec![0; ram_size.capacity() as usize],
 | 
			
		||||
            ram_size,
 | 
			
		||||
            rom_size,
 | 
			
		||||
            ram_bank: Default::default(),
 | 
			
		||||
            mode: Default::default(),
 | 
			
		||||
            ram_size: Default::default(),
 | 
			
		||||
            ram: Default::default(),
 | 
			
		||||
            bank_count: Default::default(),
 | 
			
		||||
            ram_enabled: Default::default(),
 | 
			
		||||
            mem_enabled: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Mbc1 {
 | 
			
		||||
    fn zero_bank(&self) -> u8 {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        match self.bank_count {
 | 
			
		||||
        match self.rom_size {
 | 
			
		||||
            None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
 | 
			
		||||
            SixtyFour => (self.ram_bank & 0x01) << 5,
 | 
			
		||||
            OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
 | 
			
		||||
            OneTwentyEight => (self.ram_bank & 0x03) << 5,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn _mbcm_zero_bank(&self) -> u8 {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        match self.bank_count {
 | 
			
		||||
        match self.rom_size {
 | 
			
		||||
            None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
 | 
			
		||||
            SixtyFour => (self.ram_bank & 0x03) << 4,
 | 
			
		||||
            OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
 | 
			
		||||
            OneTwentyEight => (self.ram_bank & 0x03) << 5,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn high_bank(&self) -> u8 {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        let base = self.rom_bank & self.rom_size_mask();
 | 
			
		||||
 | 
			
		||||
        match self.bank_count {
 | 
			
		||||
        match self.rom_size {
 | 
			
		||||
            None | Four | Eight | Sixteen | ThirtyTwo => base,
 | 
			
		||||
            SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5),
 | 
			
		||||
            OneHundredTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
 | 
			
		||||
            OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rom_size_mask(&self) -> u8 {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        match self.bank_count {
 | 
			
		||||
        match self.rom_size {
 | 
			
		||||
            None => 0b00000001,
 | 
			
		||||
            Four => 0b00000011,
 | 
			
		||||
            Eight => 0b00000111,
 | 
			
		||||
            Sixteen => 0b00001111,
 | 
			
		||||
            ThirtyTwo | SixtyFour | OneHundredTwentyEight => 0b00011111,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
 | 
			
		||||
            ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111,
 | 
			
		||||
            _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn ram_addr(&self, addr: u16) -> u16 {
 | 
			
		||||
    fn ram_addr(&self, addr: u16) -> usize {
 | 
			
		||||
        use RamSize::*;
 | 
			
		||||
 | 
			
		||||
        match self.ram_size {
 | 
			
		||||
            _2KB | _8KB => (addr - 0xA000) % self.ram_size.len() as u16,
 | 
			
		||||
            _32KB => {
 | 
			
		||||
            Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(),
 | 
			
		||||
            Four => {
 | 
			
		||||
                if self.mode {
 | 
			
		||||
                    0x2000 * self.ram_bank as u16 + (addr - 0xA000)
 | 
			
		||||
                    0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)
 | 
			
		||||
                } else {
 | 
			
		||||
                    addr - 0xA000
 | 
			
		||||
                    addr as usize - 0xA000
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => unreachable!("RAM size can not be greater than 32KB on MBC1"),
 | 
			
		||||
@@ -237,49 +197,39 @@ impl Mbc1 {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MemoryBankController for Mbc1 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MbcResult {
 | 
			
		||||
        use MbcResult::*;
 | 
			
		||||
impl MBCIo for MBC1 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult {
 | 
			
		||||
        use MBCResult::*;
 | 
			
		||||
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x3FFF => {
 | 
			
		||||
                if self.mode {
 | 
			
		||||
                    Address(0x4000 * self.zero_bank() as usize + addr as usize)
 | 
			
		||||
                } else {
 | 
			
		||||
                    Address(addr as usize)
 | 
			
		||||
                }
 | 
			
		||||
            0x0000..=0x3FFF if self.mode => {
 | 
			
		||||
                Address(0x4000 * self.zero_bank() as usize + addr as usize)
 | 
			
		||||
            }
 | 
			
		||||
            0x0000..=0x3FFF => Address(addr as usize),
 | 
			
		||||
            0x4000..=0x7FFF => {
 | 
			
		||||
                Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => {
 | 
			
		||||
                if self.ram_enabled {
 | 
			
		||||
                    Value(self.ram[self.ram_addr(addr) as usize])
 | 
			
		||||
                } else {
 | 
			
		||||
                    Value(0xFF)
 | 
			
		||||
                }
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
 | 
			
		||||
                Value(self.memory[self.ram_addr(addr)])
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => Value(0xFF),
 | 
			
		||||
            _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_write(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A,
 | 
			
		||||
            0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A,
 | 
			
		||||
            0x2000..=0x3FFF => {
 | 
			
		||||
                self.rom_bank = if byte == 0x00 {
 | 
			
		||||
                    0x01
 | 
			
		||||
                } else {
 | 
			
		||||
                    byte & self.rom_size_mask()
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                self.rom_bank &= 0x1F;
 | 
			
		||||
                let value = byte & 0x1F;
 | 
			
		||||
                let masked_value = byte & self.rom_size_mask();
 | 
			
		||||
                self.rom_bank = if value == 0 { 0x01 } else { masked_value };
 | 
			
		||||
            }
 | 
			
		||||
            0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
 | 
			
		||||
            0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
 | 
			
		||||
            0xA000..=0xBFFF if self.ram_enabled => {
 | 
			
		||||
                let ram_addr = self.ram_addr(addr) as usize;
 | 
			
		||||
                self.ram[ram_addr] = byte;
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
 | 
			
		||||
                let ram_addr = self.ram_addr(addr);
 | 
			
		||||
                self.memory[ram_addr] = byte;
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
 | 
			
		||||
            _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
 | 
			
		||||
@@ -293,32 +243,47 @@ enum MBC3Device {
 | 
			
		||||
    RealTimeClock,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MBC3 {
 | 
			
		||||
    /// 7-bit Number
 | 
			
		||||
    rom_bank: u8,
 | 
			
		||||
    /// 2-bit Number
 | 
			
		||||
    ram_bank: u8,
 | 
			
		||||
 | 
			
		||||
    devices_enabled: bool,
 | 
			
		||||
    currently_mapped: Option<MBC3Device>,
 | 
			
		||||
    ram_size: RamSize,
 | 
			
		||||
    ram: Vec<u8>,
 | 
			
		||||
    devs_enabled: bool,
 | 
			
		||||
    mapped: Option<MBC3Device>,
 | 
			
		||||
    memory: Vec<u8>,
 | 
			
		||||
 | 
			
		||||
    // RTC Data Latch Previous Write
 | 
			
		||||
    prev_latch_write: Option<u8>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MemoryBankController for MBC3 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MbcResult {
 | 
			
		||||
        use MbcResult::*;
 | 
			
		||||
impl MBC3 {
 | 
			
		||||
    fn new(ram_cap: usize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            memory: vec![0; ram_cap],
 | 
			
		||||
            rom_bank: Default::default(),
 | 
			
		||||
            ram_bank: Default::default(),
 | 
			
		||||
            devs_enabled: Default::default(),
 | 
			
		||||
            mapped: Default::default(),
 | 
			
		||||
            prev_latch_write: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MBCIo for MBC3 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult {
 | 
			
		||||
        use MBCResult::*;
 | 
			
		||||
 | 
			
		||||
        let res = match addr {
 | 
			
		||||
            0x0000..=0x3FFF => Address(addr as usize),
 | 
			
		||||
            0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
 | 
			
		||||
            0xA000..=0xBFFF => match self.currently_mapped {
 | 
			
		||||
                Some(MBC3Device::ExternalRam) if self.devices_enabled => {
 | 
			
		||||
                    Value(self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
 | 
			
		||||
            0xA000..=0xBFFF => match self.mapped {
 | 
			
		||||
                Some(MBC3Device::ExternalRam) if self.devs_enabled => {
 | 
			
		||||
                    Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
 | 
			
		||||
                }
 | 
			
		||||
                Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
 | 
			
		||||
                    unimplemented!("Reading from MBC3 RTC is currently unsupported")
 | 
			
		||||
                Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
 | 
			
		||||
                    todo!("Return Latched value of register")
 | 
			
		||||
                }
 | 
			
		||||
                _ => Value(0xFF),
 | 
			
		||||
            },
 | 
			
		||||
@@ -330,29 +295,37 @@ impl MemoryBankController for MBC3 {
 | 
			
		||||
 | 
			
		||||
    fn handle_write(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x000..=0x1FFF => self.devices_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
 | 
			
		||||
            0x2000..=0x3FFF => match byte {
 | 
			
		||||
                0x00 => self.rom_bank = 0x01,
 | 
			
		||||
                byte => self.rom_bank = byte & 0x7F,
 | 
			
		||||
            },
 | 
			
		||||
            0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
 | 
			
		||||
            0x2000..=0x3FFF => {
 | 
			
		||||
                self.rom_bank = match byte {
 | 
			
		||||
                    0x00 => 0x01,
 | 
			
		||||
                    byte => byte & 0x7F,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            0x4000..=0x5FFF => match byte {
 | 
			
		||||
                0x00 | 0x01 | 0x02 | 0x03 => {
 | 
			
		||||
                    self.ram_bank = byte & 0x03;
 | 
			
		||||
                    self.currently_mapped = Some(MBC3Device::ExternalRam);
 | 
			
		||||
                    self.mapped = Some(MBC3Device::ExternalRam);
 | 
			
		||||
                }
 | 
			
		||||
                0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
 | 
			
		||||
                    self.currently_mapped = Some(MBC3Device::RealTimeClock);
 | 
			
		||||
                    unimplemented!("RTC in MBC3 is currently unimplemented")
 | 
			
		||||
                    self.mapped = Some(MBC3Device::RealTimeClock);
 | 
			
		||||
                }
 | 
			
		||||
                _ => {}
 | 
			
		||||
            },
 | 
			
		||||
            0x6000..=0x7FFF => unimplemented!("RTC Data Latch is currently unimplemented"),
 | 
			
		||||
            0xA000..=0xBFFF => match self.currently_mapped {
 | 
			
		||||
                Some(MBC3Device::ExternalRam) if self.devices_enabled => {
 | 
			
		||||
                    self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
 | 
			
		||||
            0x6000..=0x7FFF => {
 | 
			
		||||
                if let Some(0x00) = self.prev_latch_write {
 | 
			
		||||
                    if byte == 0x01 {
 | 
			
		||||
                        todo!("Perform Data Latch")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
 | 
			
		||||
                    unimplemented!("Writing to MBC3 RTC is currently unsupported")
 | 
			
		||||
                self.prev_latch_write = Some(byte);
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => match self.mapped {
 | 
			
		||||
                Some(MBC3Device::ExternalRam) if self.devs_enabled => {
 | 
			
		||||
                    self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
 | 
			
		||||
                }
 | 
			
		||||
                Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
 | 
			
		||||
                    todo!("Write to RTC")
 | 
			
		||||
                }
 | 
			
		||||
                _ => {}
 | 
			
		||||
            },
 | 
			
		||||
@@ -361,12 +334,132 @@ impl MemoryBankController for MBC3 {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
struct NoMbc {}
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MBC5 {
 | 
			
		||||
    /// 9-bit number
 | 
			
		||||
    rom_bank: u16,
 | 
			
		||||
    /// 4-bit number
 | 
			
		||||
    ram_bank: u8,
 | 
			
		||||
 | 
			
		||||
impl MemoryBankController for NoMbc {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MbcResult {
 | 
			
		||||
        MbcResult::Address(addr as usize)
 | 
			
		||||
    rom_cap: usize,
 | 
			
		||||
 | 
			
		||||
    memory: Vec<u8>,
 | 
			
		||||
    mem_enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MBC5 {
 | 
			
		||||
    fn new(ram_cap: usize, rom_cap: usize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            rom_bank: 0x01,
 | 
			
		||||
            memory: vec![0; ram_cap],
 | 
			
		||||
            rom_cap,
 | 
			
		||||
            ram_bank: Default::default(),
 | 
			
		||||
            mem_enabled: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn bank_addr(&self, addr: u16) -> usize {
 | 
			
		||||
        (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MBCIo for MBC5 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult {
 | 
			
		||||
        use MBCResult::*;
 | 
			
		||||
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x3FFF => Address(addr as usize),
 | 
			
		||||
            0x4000..=0x7FFF => Address(self.bank_addr(addr)),
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled => {
 | 
			
		||||
                Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => Value(0xFF),
 | 
			
		||||
            _ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_write(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A,
 | 
			
		||||
            0x2000..=0x2FFF => self.rom_bank = (self.rom_bank & 0x0100) | byte as u16,
 | 
			
		||||
            0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
 | 
			
		||||
            0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled => {
 | 
			
		||||
                self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => {}
 | 
			
		||||
            _ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct MBC2 {
 | 
			
		||||
    /// 4-bit number
 | 
			
		||||
    rom_bank: u8,
 | 
			
		||||
    memory: Box<[u8; Self::RAM_SIZE]>,
 | 
			
		||||
    mem_enabled: bool,
 | 
			
		||||
 | 
			
		||||
    rom_cap: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MBC2 {
 | 
			
		||||
    const RAM_SIZE: usize = 0x0200;
 | 
			
		||||
 | 
			
		||||
    fn new(rom_cap: usize) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            rom_bank: 0x01,
 | 
			
		||||
            memory: Box::new([0; Self::RAM_SIZE]),
 | 
			
		||||
            mem_enabled: Default::default(),
 | 
			
		||||
            rom_cap,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn rom_addr(&self, addr: u16) -> usize {
 | 
			
		||||
        (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MBCIo for MBC2 {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult {
 | 
			
		||||
        use MBCResult::*;
 | 
			
		||||
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x3FFF => Address(addr as usize),
 | 
			
		||||
            0x4000..=0x7FFF => Address(self.rom_addr(addr)),
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled => {
 | 
			
		||||
                let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
 | 
			
		||||
                Value(self.memory[mbc2_addr] | 0xF0)
 | 
			
		||||
            }
 | 
			
		||||
            0xA000..=0xBFFF => Value(0xFF),
 | 
			
		||||
            _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_write(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        let nybble = byte & 0x0F;
 | 
			
		||||
 | 
			
		||||
        match addr {
 | 
			
		||||
            0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => {
 | 
			
		||||
                self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble };
 | 
			
		||||
            }
 | 
			
		||||
            0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
 | 
			
		||||
            0xA000..=0xBFFF if self.mem_enabled => {
 | 
			
		||||
                let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
 | 
			
		||||
                self.memory[mbc2_addr] = nybble;
 | 
			
		||||
            }
 | 
			
		||||
            0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
 | 
			
		||||
            _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct NoMBC;
 | 
			
		||||
 | 
			
		||||
impl MBCIo for NoMBC {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult {
 | 
			
		||||
        MBCResult::Address(addr as usize)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_write(&mut self, _addr: u16, _byte: u8) {
 | 
			
		||||
@@ -374,67 +467,57 @@ impl MemoryBankController for NoMbc {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
trait MemoryBankController: CloneMbc {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MbcResult;
 | 
			
		||||
trait MBCIo {
 | 
			
		||||
    fn handle_read(&self, addr: u16) -> MBCResult;
 | 
			
		||||
    fn handle_write(&mut self, addr: u16, byte: u8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
trait CloneMbc {
 | 
			
		||||
    fn clone_mbc(&self) -> Box<dyn MemoryBankController>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> CloneMbc for T
 | 
			
		||||
where
 | 
			
		||||
    T: MemoryBankController + Clone + 'static,
 | 
			
		||||
{
 | 
			
		||||
    fn clone_mbc<'a>(&self) -> Box<dyn MemoryBankController> {
 | 
			
		||||
        Box::new(self.clone())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum MbcResult {
 | 
			
		||||
enum MBCResult {
 | 
			
		||||
    Address(usize),
 | 
			
		||||
    Value(u8),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum MbcKind {
 | 
			
		||||
enum MBCKind {
 | 
			
		||||
    None,
 | 
			
		||||
    Mbc1,
 | 
			
		||||
    Mbc1WithBattery,
 | 
			
		||||
    Mbc5,
 | 
			
		||||
    Mbc3WithBattery,
 | 
			
		||||
    Mbc3,
 | 
			
		||||
    MBC1,
 | 
			
		||||
    MBC1WithBattery,
 | 
			
		||||
    MBC2,
 | 
			
		||||
    MBC2WithBattery,
 | 
			
		||||
    MBC3,
 | 
			
		||||
    MBC3WithBattery,
 | 
			
		||||
    MBC5,
 | 
			
		||||
    MBC5WithBattery,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for MbcKind {
 | 
			
		||||
impl Default for MBCKind {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq)]
 | 
			
		||||
enum RamSize {
 | 
			
		||||
    None = 0x00,
 | 
			
		||||
    _2KB = 0x01,
 | 
			
		||||
    _8KB = 0x02,
 | 
			
		||||
    _32KB = 0x03,  // Split into 4 RAM banks
 | 
			
		||||
    _128KB = 0x04, // Split into 16 RAM banks
 | 
			
		||||
    _64KB = 0x05,  // Split into 8 RAm Banks
 | 
			
		||||
    Unused = 0x01,
 | 
			
		||||
    One = 0x02,
 | 
			
		||||
    Four = 0x03,
 | 
			
		||||
    Sixteen = 0x04,
 | 
			
		||||
    Eight = 0x05,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RamSize {
 | 
			
		||||
    fn len(&self) -> u32 {
 | 
			
		||||
    fn capacity(&self) -> usize {
 | 
			
		||||
        use RamSize::*;
 | 
			
		||||
 | 
			
		||||
        match *self {
 | 
			
		||||
            None => 0,
 | 
			
		||||
            _2KB => 2_048,
 | 
			
		||||
            _8KB => 8_192,
 | 
			
		||||
            _32KB => 32_768,
 | 
			
		||||
            _128KB => 131_072,
 | 
			
		||||
            _64KB => 65_536,
 | 
			
		||||
            Unused => 0x800,
 | 
			
		||||
            One => 0x2000,
 | 
			
		||||
            Four => 0x8000,
 | 
			
		||||
            Sixteen => 0x20000,
 | 
			
		||||
            Eight => 0x10000,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -451,63 +534,49 @@ impl From<u8> for RamSize {
 | 
			
		||||
 | 
			
		||||
        match byte {
 | 
			
		||||
            0x00 => None,
 | 
			
		||||
            0x01 => _2KB,
 | 
			
		||||
            0x02 => _8KB,
 | 
			
		||||
            0x03 => _32KB,
 | 
			
		||||
            0x04 => _128KB,
 | 
			
		||||
            0x05 => _64KB,
 | 
			
		||||
            0x01 => Unused,
 | 
			
		||||
            0x02 => One,
 | 
			
		||||
            0x03 => Four,
 | 
			
		||||
            0x04 => Sixteen,
 | 
			
		||||
            0x05 => Eight,
 | 
			
		||||
            _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum BankCount {
 | 
			
		||||
    None = 0x00,                  // 32KB (also called Two)
 | 
			
		||||
    Four = 0x01,                  // 64KB
 | 
			
		||||
    Eight = 0x02,                 // 128KB
 | 
			
		||||
    Sixteen = 0x03,               // 256KB
 | 
			
		||||
    ThirtyTwo = 0x04,             // 512KB
 | 
			
		||||
    SixtyFour = 0x05,             // 1MB
 | 
			
		||||
    OneHundredTwentyEight = 0x06, // 2MB
 | 
			
		||||
    TwoHundredFiftySix = 0x07,    // 4MB
 | 
			
		||||
    FiveHundredTwelve = 0x08,     // 8MB
 | 
			
		||||
    SeventyTwo = 0x52,            // 1.1MB
 | 
			
		||||
    Eighty = 0x53,                // 1.2MB
 | 
			
		||||
    NinetySix = 0x54,             // 1.5MB
 | 
			
		||||
enum RomSize {
 | 
			
		||||
    None = 0x00,              // 32KB (also called Two)
 | 
			
		||||
    Four = 0x01,              // 64KB
 | 
			
		||||
    Eight = 0x02,             // 128KB
 | 
			
		||||
    Sixteen = 0x03,           // 256KB
 | 
			
		||||
    ThirtyTwo = 0x04,         // 512KB
 | 
			
		||||
    SixtyFour = 0x05,         // 1MB
 | 
			
		||||
    OneTwentyEight = 0x06,    // 2MB
 | 
			
		||||
    TwoFiftySix = 0x07,       // 4MB
 | 
			
		||||
    FiveHundredTwelve = 0x08, // 8MB
 | 
			
		||||
    SeventyTwo = 0x52,        // 1.1MB
 | 
			
		||||
    Eighty = 0x53,            // 1.2MB
 | 
			
		||||
    NinetySix = 0x54,         // 1.5MB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for BankCount {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::None
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BankCount {
 | 
			
		||||
impl RomSize {
 | 
			
		||||
    // https://hacktix.github.io/GBEDG/mbcs/#rom-size
 | 
			
		||||
    fn size(self) -> u32 {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
    fn capacity(&self) -> usize {
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        match self {
 | 
			
		||||
            None => 32_768,
 | 
			
		||||
            Four => 65_536,
 | 
			
		||||
            Eight => 131_072,
 | 
			
		||||
            Sixteen => 262_144,
 | 
			
		||||
            ThirtyTwo => 524_288,
 | 
			
		||||
            SixtyFour => 1_048_576,
 | 
			
		||||
            OneHundredTwentyEight => 2_097_152,
 | 
			
		||||
            TwoHundredFiftySix => 4_194_304,
 | 
			
		||||
            FiveHundredTwelve => 8_388_608,
 | 
			
		||||
            SeventyTwo => 1_179_648,
 | 
			
		||||
            Eighty => 1_310_720,
 | 
			
		||||
            NinetySix => 1_572_864,
 | 
			
		||||
            SeventyTwo => 0x120000,
 | 
			
		||||
            Eighty => 0x140000,
 | 
			
		||||
            NinetySix => 0x180000,
 | 
			
		||||
            _ => 0x8000 << *self as u8,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<u8> for BankCount {
 | 
			
		||||
impl From<u8> for RomSize {
 | 
			
		||||
    fn from(byte: u8) -> Self {
 | 
			
		||||
        use BankCount::*;
 | 
			
		||||
        use RomSize::*;
 | 
			
		||||
 | 
			
		||||
        match byte {
 | 
			
		||||
            0x00 => None,
 | 
			
		||||
@@ -516,8 +585,8 @@ impl From<u8> for BankCount {
 | 
			
		||||
            0x03 => Sixteen,
 | 
			
		||||
            0x04 => ThirtyTwo,
 | 
			
		||||
            0x05 => SixtyFour,
 | 
			
		||||
            0x06 => OneHundredTwentyEight,
 | 
			
		||||
            0x07 => TwoHundredFiftySix,
 | 
			
		||||
            0x06 => OneTwentyEight,
 | 
			
		||||
            0x07 => TwoFiftySix,
 | 
			
		||||
            0x08 => FiveHundredTwelve,
 | 
			
		||||
            0x52 => SeventyTwo,
 | 
			
		||||
            0x53 => Eighty,
 | 
			
		||||
@@ -527,20 +596,14 @@ impl From<u8> for BankCount {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Debug for Box<dyn MemoryBankController> {
 | 
			
		||||
impl std::fmt::Debug for Box<dyn MBCIo> {
 | 
			
		||||
    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        todo!("Implement Debug for Box<dyn MBC> Trait Object");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::clone::Clone for Box<dyn MemoryBankController> {
 | 
			
		||||
    fn clone(&self) -> Self {
 | 
			
		||||
        self.clone_mbc()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Box<dyn MemoryBankController> {
 | 
			
		||||
impl Default for Box<dyn MBCIo> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Box::new(Mbc1::default())
 | 
			
		||||
        Box::new(NoMBC)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										374
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										374
									
								
								src/cpu.rs
									
									
									
									
									
								
							@@ -1,26 +1,21 @@
 | 
			
		||||
use crate::bus::{Bus, BusIo};
 | 
			
		||||
use crate::instruction::{Cycle, Instruction};
 | 
			
		||||
use crate::bus::{Bus, BusIo, BOOT_SIZE};
 | 
			
		||||
use crate::instruction::Instruction;
 | 
			
		||||
use crate::interrupt::{InterruptEnable, InterruptFlag};
 | 
			
		||||
use crate::joypad::Joypad;
 | 
			
		||||
use crate::ppu::Ppu;
 | 
			
		||||
use crate::sound::AudioSender;
 | 
			
		||||
use crate::timer::Timer;
 | 
			
		||||
use crate::Cycle;
 | 
			
		||||
use bitfield::bitfield;
 | 
			
		||||
use std::fmt::{Display, Formatter, Result as FmtResult};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub struct Cpu {
 | 
			
		||||
    pub bus: Bus,
 | 
			
		||||
    reg: Registers,
 | 
			
		||||
    flags: Flags,
 | 
			
		||||
    ime: ImeState,
 | 
			
		||||
    // TODO: Merge halted and state properties
 | 
			
		||||
    halted: Option<HaltState>,
 | 
			
		||||
    state: State,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpu {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
    pub(crate) fn without_boot() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            reg: Registers {
 | 
			
		||||
                a: 0x01,
 | 
			
		||||
@@ -38,15 +33,11 @@ impl Cpu {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn boot_new(path: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            bus: Bus::with_boot(path)?,
 | 
			
		||||
    pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            bus: Bus::with_boot(rom),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_audio_src(&mut self, sender: AudioSender<f32>) {
 | 
			
		||||
        self.bus.pass_audio_src(sender)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn ime(&self) -> ImeState {
 | 
			
		||||
@@ -57,88 +48,100 @@ impl Cpu {
 | 
			
		||||
        self.ime = state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn halt(&mut self, state: HaltState) {
 | 
			
		||||
        self.halted = Some(state);
 | 
			
		||||
    pub(crate) fn halt_cpu(&mut self, kind: HaltKind) {
 | 
			
		||||
        self.state = State::Halt(kind);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn resume(&mut self) {
 | 
			
		||||
        self.halted = None;
 | 
			
		||||
    fn resume_execution(&mut self) {
 | 
			
		||||
        self.state = State::Execute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn halted(&self) -> Option<HaltState> {
 | 
			
		||||
        self.halted
 | 
			
		||||
    pub(crate) fn is_halted(&self) -> bool {
 | 
			
		||||
        match self.state {
 | 
			
		||||
            State::Halt(_) => true,
 | 
			
		||||
            _ => false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn inc_pc(&mut self) {
 | 
			
		||||
        self.reg.pc += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
 | 
			
		||||
        self.bus.load_cartridge(path)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn rom_title(&self) -> Option<&str> {
 | 
			
		||||
        self.bus.rom_title()
 | 
			
		||||
    pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
 | 
			
		||||
        match self.state {
 | 
			
		||||
            State::Halt(kind) => Some(kind),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpu {
 | 
			
		||||
    fn fetch(&self) -> u8 {
 | 
			
		||||
        self.bus.read_byte(self.reg.pc)
 | 
			
		||||
    /// Fetch an [Instruction] from the memory bus
 | 
			
		||||
    /// (4 cycles)
 | 
			
		||||
    fn fetch(&mut self) -> u8 {
 | 
			
		||||
        let byte = self.read_byte(self.reg.pc);
 | 
			
		||||
        self.bus.clock();
 | 
			
		||||
        self.reg.pc += 1;
 | 
			
		||||
        byte
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Decode a byte into an [SM83](Cpu) [Instruction]
 | 
			
		||||
    ///
 | 
			
		||||
    /// If opcode == 0xCB, then decoding costs 4 cycles.
 | 
			
		||||
    /// Otherwise, decoding is free
 | 
			
		||||
    pub(crate) fn decode(&mut self, opcode: u8) -> Instruction {
 | 
			
		||||
        Instruction::from_byte(self, opcode)
 | 
			
		||||
        if opcode == 0xCB {
 | 
			
		||||
            Instruction::decode(self.fetch(), true)
 | 
			
		||||
        } else {
 | 
			
		||||
            Instruction::decode(opcode, false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute an [Instruction].
 | 
			
		||||
    ///
 | 
			
		||||
    /// The amount of cycles necessary to execute an instruction range from
 | 
			
		||||
    /// 0 to 20 T-cycles
 | 
			
		||||
    fn execute(&mut self, instruction: Instruction) -> Cycle {
 | 
			
		||||
        Instruction::execute(self, instruction)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`]
 | 
			
		||||
    /// routine.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Handle HALT and interrupts.
 | 
			
		||||
    pub fn step(&mut self) -> Cycle {
 | 
			
		||||
        let cycles = match self.halted() {
 | 
			
		||||
            Some(state) => {
 | 
			
		||||
                use HaltState::*;
 | 
			
		||||
        // Log instructions
 | 
			
		||||
        // if self.reg.pc > 0xFF {
 | 
			
		||||
        //     let out = std::io::stdout();
 | 
			
		||||
        //     let _ = self._print_logs(out.lock());
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
                match state {
 | 
			
		||||
                    ImeEnabled | NonePending => Cycle::new(4),
 | 
			
		||||
                    SomePending => todo!("Implement HALT bug"),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            None => {
 | 
			
		||||
                let opcode = self.fetch();
 | 
			
		||||
                self.inc_pc();
 | 
			
		||||
 | 
			
		||||
                let instr = self.decode(opcode);
 | 
			
		||||
 | 
			
		||||
                // let out = std::io::stdout();
 | 
			
		||||
                // let _ = self._debug_log(out.lock(), &instr);
 | 
			
		||||
 | 
			
		||||
                let cycles = self.execute(instr);
 | 
			
		||||
 | 
			
		||||
                self.check_ime();
 | 
			
		||||
 | 
			
		||||
                cycles
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // TODO: With how we currently handle audio
 | 
			
		||||
        // this --while being correct-- incurs a performance penalty
 | 
			
		||||
        // as our emu is audio-bound.
 | 
			
		||||
        let mut elapsed = 0x00;
 | 
			
		||||
        let pending: u32 = cycles.into();
 | 
			
		||||
        while elapsed < pending {
 | 
			
		||||
            if !self.bus.is_full() {
 | 
			
		||||
                self.bus.clock();
 | 
			
		||||
                elapsed += 1;
 | 
			
		||||
            } else {
 | 
			
		||||
                self.bus.flush_samples();
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(elapsed) = self.handle_interrupt() {
 | 
			
		||||
            return elapsed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.handle_interrupts();
 | 
			
		||||
        if let Some(kind) = self.halt_kind() {
 | 
			
		||||
            use HaltKind::*;
 | 
			
		||||
 | 
			
		||||
        cycles
 | 
			
		||||
            self.bus.clock();
 | 
			
		||||
 | 
			
		||||
            let elapsed = match kind {
 | 
			
		||||
                ImeEnabled | NonePending => 4,
 | 
			
		||||
                SomePending => todo!("Implement HALT bug"),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return elapsed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let opcode = self.fetch();
 | 
			
		||||
        let instr = self.decode(opcode);
 | 
			
		||||
        let elapsed = self.execute(instr);
 | 
			
		||||
        self.handle_ei();
 | 
			
		||||
 | 
			
		||||
        // For use in Blargg's Test ROMs
 | 
			
		||||
        if self.read_byte(0xFF02) == 0x81 {
 | 
			
		||||
            let c = self.read_byte(0xFF01) as char;
 | 
			
		||||
            self.write_byte(0xFF02, 0x00);
 | 
			
		||||
            eprint!("{}", c);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        elapsed
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -153,124 +156,108 @@ impl BusIo for Cpu {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpu {
 | 
			
		||||
    pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 {
 | 
			
		||||
        self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call
 | 
			
		||||
        self.bus.read_byte(addr)
 | 
			
		||||
    pub(crate) fn bus(&self) -> &Bus {
 | 
			
		||||
        &self.bus
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 {
 | 
			
		||||
        self.inc_pc();
 | 
			
		||||
        self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call
 | 
			
		||||
        self.bus.read_word(addr)
 | 
			
		||||
    pub(crate) fn bus_mut(&mut self) -> &mut Bus {
 | 
			
		||||
        &mut self.bus
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
 | 
			
		||||
        self.bus.write_word(addr, word)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpu {
 | 
			
		||||
    pub fn ppu(&mut self) -> &Ppu {
 | 
			
		||||
        &self.bus.ppu
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
 | 
			
		||||
        &mut self.bus.joypad
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn timer(&self) -> &Timer {
 | 
			
		||||
        &self.bus.timer
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn check_ime(&mut self) {
 | 
			
		||||
    fn handle_ei(&mut self) {
 | 
			
		||||
        match self.ime {
 | 
			
		||||
            ImeState::Pending => {
 | 
			
		||||
                // This is within the context of the EI instruction, we need to not update EI until the end of the
 | 
			
		||||
                // next executed Instruction
 | 
			
		||||
                self.ime = ImeState::PendingEnd;
 | 
			
		||||
            }
 | 
			
		||||
            ImeState::PendingEnd => {
 | 
			
		||||
                // The Instruction after EI has now been executed, so we want to enable the IME flag here
 | 
			
		||||
                self.ime = ImeState::Enabled;
 | 
			
		||||
            }
 | 
			
		||||
            ImeState::Disabled | ImeState::Enabled => {} // Do Nothing
 | 
			
		||||
            ImeState::EiExecuted => self.ime = ImeState::Pending,
 | 
			
		||||
            ImeState::Pending => self.ime = ImeState::Enabled,
 | 
			
		||||
            ImeState::Disabled | ImeState::Enabled => {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_interrupts(&mut self) {
 | 
			
		||||
        let req = self.read_byte(0xFF0F);
 | 
			
		||||
        let enabled = self.read_byte(0xFFFF);
 | 
			
		||||
    pub(crate) fn int_request(&self) -> u8 {
 | 
			
		||||
        self.read_byte(0xFF0F)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        if self.halted.is_some() {
 | 
			
		||||
    pub(crate) fn int_enable(&self) -> u8 {
 | 
			
		||||
        self.read_byte(0xFFFF)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn handle_interrupt(&mut self) -> Option<Cycle> {
 | 
			
		||||
        let irq = self.int_request();
 | 
			
		||||
        let enable = self.int_enable();
 | 
			
		||||
 | 
			
		||||
        // TODO: Ensure that this behaviour is correct
 | 
			
		||||
        if self.is_halted() {
 | 
			
		||||
            // When we're here either a HALT with IME set or
 | 
			
		||||
            // a HALT with IME not set and No pending Interrupts was called
 | 
			
		||||
 | 
			
		||||
            if req & enabled != 0 {
 | 
			
		||||
            if irq & enable != 0 {
 | 
			
		||||
                // The if self.ime() below correctly follows the "resuming from HALT" behaviour so
 | 
			
		||||
                // nothing actually needs to be added here. This is just documentation
 | 
			
		||||
                // since it's a bit weird why nothing is being done
 | 
			
		||||
 | 
			
		||||
                self.resume()
 | 
			
		||||
                self.resume_execution();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let ImeState::Enabled = self.ime() {
 | 
			
		||||
            let mut req: InterruptFlag = req.into();
 | 
			
		||||
            let enabled: InterruptEnable = enabled.into();
 | 
			
		||||
        match self.ime() {
 | 
			
		||||
            ImeState::Enabled => {
 | 
			
		||||
                let mut irq: InterruptFlag = irq.into();
 | 
			
		||||
                let enable: InterruptEnable = enable.into();
 | 
			
		||||
 | 
			
		||||
            let vector = if req.vblank() && enabled.vblank() {
 | 
			
		||||
                // Handle VBlank Interrupt
 | 
			
		||||
                req.set_vblank(false);
 | 
			
		||||
                let rst_vector = if irq.vblank() && enable.vblank() {
 | 
			
		||||
                    // Handle VBlank Interrupt
 | 
			
		||||
                    irq.set_vblank(false);
 | 
			
		||||
 | 
			
		||||
                // INT 40h
 | 
			
		||||
                Some(0x40)
 | 
			
		||||
            } else if req.lcd_stat() && enabled.lcd_stat() {
 | 
			
		||||
                // Handle LCD STAT Interrupt
 | 
			
		||||
                req.set_lcd_stat(false);
 | 
			
		||||
                    // INT 40h
 | 
			
		||||
                    Some(0x40)
 | 
			
		||||
                } else if irq.lcd_stat() && enable.lcd_stat() {
 | 
			
		||||
                    // Handle LCD STAT Interrupt
 | 
			
		||||
                    irq.set_lcd_stat(false);
 | 
			
		||||
 | 
			
		||||
                // INT 48h
 | 
			
		||||
                Some(0x48)
 | 
			
		||||
            } else if req.timer() && enabled.timer() {
 | 
			
		||||
                // Handle Timer Interrupt
 | 
			
		||||
                req.set_timer(false);
 | 
			
		||||
                    // INT 48h
 | 
			
		||||
                    Some(0x48)
 | 
			
		||||
                } else if irq.timer() && enable.timer() {
 | 
			
		||||
                    // Handle Timer Interrupt
 | 
			
		||||
                    irq.set_timer(false);
 | 
			
		||||
 | 
			
		||||
                // INT 50h
 | 
			
		||||
                Some(0x50)
 | 
			
		||||
            } else if req.serial() && enabled.serial() {
 | 
			
		||||
                // Handle Serial Interrupt
 | 
			
		||||
                req.set_serial(false);
 | 
			
		||||
                    // INT 50h
 | 
			
		||||
                    Some(0x50)
 | 
			
		||||
                } else if irq.serial() && enable.serial() {
 | 
			
		||||
                    // Handle Serial Interrupt
 | 
			
		||||
                    irq.set_serial(false);
 | 
			
		||||
 | 
			
		||||
                // INT 58h
 | 
			
		||||
                Some(0x58)
 | 
			
		||||
            } else if req.joypad() && enabled.joypad() {
 | 
			
		||||
                // Handle Joypad Interrupt
 | 
			
		||||
                req.set_joypad(false);
 | 
			
		||||
                    // INT 58h
 | 
			
		||||
                    Some(0x58)
 | 
			
		||||
                } else if irq.joypad() && enable.joypad() {
 | 
			
		||||
                    // Handle Joypad Interrupt
 | 
			
		||||
                    irq.set_joypad(false);
 | 
			
		||||
 | 
			
		||||
                // INT 60h
 | 
			
		||||
                Some(0x60)
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            };
 | 
			
		||||
                    // INT 60h
 | 
			
		||||
                    Some(0x60)
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
            let _ = match vector {
 | 
			
		||||
                Some(address) => {
 | 
			
		||||
                    //  Write the Changes to 0xFF0F and 0xFFFF registers
 | 
			
		||||
                    self.write_byte(0xFF0F, req.into());
 | 
			
		||||
                match rst_vector {
 | 
			
		||||
                    Some(vector) => {
 | 
			
		||||
                        //  Write the Changes to 0xFF0F and 0xFFFF registers
 | 
			
		||||
                        self.write_byte(0xFF0F, irq.into());
 | 
			
		||||
 | 
			
		||||
                    // Disable all future interrupts
 | 
			
		||||
                    self.set_ime(ImeState::Disabled);
 | 
			
		||||
                    Instruction::reset(self, address)
 | 
			
		||||
                        // Disable all future interrupts
 | 
			
		||||
                        self.set_ime(ImeState::Disabled);
 | 
			
		||||
                        Some(Instruction::reset(self, vector))
 | 
			
		||||
                    }
 | 
			
		||||
                    None => None,
 | 
			
		||||
                }
 | 
			
		||||
                None => Cycle::new(0), // NO Interrupts were enabled and / or requested
 | 
			
		||||
            };
 | 
			
		||||
            }
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone)]
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum State {
 | 
			
		||||
    Execute,
 | 
			
		||||
    // Halt,
 | 
			
		||||
    Halt(HaltKind),
 | 
			
		||||
    // Stop,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -292,7 +279,6 @@ impl Cpu {
 | 
			
		||||
            E => self.reg.e = value,
 | 
			
		||||
            H => self.reg.h = value,
 | 
			
		||||
            L => self.reg.l = value,
 | 
			
		||||
            Flag => self.flags = value.into(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -307,7 +293,6 @@ impl Cpu {
 | 
			
		||||
            E => self.reg.e,
 | 
			
		||||
            H => self.reg.h,
 | 
			
		||||
            L => self.reg.l,
 | 
			
		||||
            Flag => self.flags.into(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -356,13 +341,20 @@ impl Cpu {
 | 
			
		||||
        &self.flags
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn update_flags(&mut self, z: bool, n: bool, h: bool, c: bool) {
 | 
			
		||||
        self.flags.set_z(z);
 | 
			
		||||
        self.flags.set_n(n);
 | 
			
		||||
        self.flags.set_h(h);
 | 
			
		||||
        self.flags.set_c(c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn set_flags(&mut self, flags: Flags) {
 | 
			
		||||
        self.flags = flags;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Cpu {
 | 
			
		||||
    fn _debug_log(&self, mut w: impl std::io::Write, instr: &Instruction) -> std::io::Result<()> {
 | 
			
		||||
    fn _print_debug(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
 | 
			
		||||
        write!(w, "A: {:02X} ", self.reg.a)?;
 | 
			
		||||
        write!(w, "F: {:02X} ", u8::from(self.flags))?;
 | 
			
		||||
        write!(w, "B: {:02X} ", self.reg.b)?;
 | 
			
		||||
@@ -376,31 +368,40 @@ impl Cpu {
 | 
			
		||||
        write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
			
		||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
			
		||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
			
		||||
        write!(w, "{:02X}) ", self.read_byte(self.reg.pc + 3))?;
 | 
			
		||||
        writeln!(w, "| {:?}", instr)?;
 | 
			
		||||
        write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
			
		||||
        writeln!(w, "| {:?}", self._dbg_instr())?;
 | 
			
		||||
        w.flush()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> {
 | 
			
		||||
        write!(writer, "A: {:02X} ", self.reg.a)?;
 | 
			
		||||
        write!(writer, "F: {:02X} ", u8::from(self.flags))?;
 | 
			
		||||
        write!(writer, "B: {:02X} ", self.reg.b)?;
 | 
			
		||||
        write!(writer, "C: {:02X} ", self.reg.c)?;
 | 
			
		||||
        write!(writer, "D: {:02X} ", self.reg.d)?;
 | 
			
		||||
        write!(writer, "E: {:02X} ", self.reg.e)?;
 | 
			
		||||
        write!(writer, "H: {:02X} ", self.reg.h)?;
 | 
			
		||||
        write!(writer, "L: {:02X} ", self.reg.l)?;
 | 
			
		||||
        write!(writer, "SP: {:04X} ", self.reg.sp)?;
 | 
			
		||||
        write!(writer, "PC: 00:{:04X} ", self.reg.pc)?;
 | 
			
		||||
        write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
			
		||||
        write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
			
		||||
        write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
			
		||||
        writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
			
		||||
        writer.flush()
 | 
			
		||||
    fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
 | 
			
		||||
        write!(w, "A: {:02X} ", self.reg.a)?;
 | 
			
		||||
        write!(w, "F: {:02X} ", u8::from(self.flags))?;
 | 
			
		||||
        write!(w, "B: {:02X} ", self.reg.b)?;
 | 
			
		||||
        write!(w, "C: {:02X} ", self.reg.c)?;
 | 
			
		||||
        write!(w, "D: {:02X} ", self.reg.d)?;
 | 
			
		||||
        write!(w, "E: {:02X} ", self.reg.e)?;
 | 
			
		||||
        write!(w, "H: {:02X} ", self.reg.h)?;
 | 
			
		||||
        write!(w, "L: {:02X} ", self.reg.l)?;
 | 
			
		||||
        write!(w, "SP: {:04X} ", self.reg.sp)?;
 | 
			
		||||
        write!(w, "PC: 00:{:04X} ", self.reg.pc)?;
 | 
			
		||||
        write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
			
		||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
			
		||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
			
		||||
        writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
			
		||||
        w.flush()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn _dbg_instr(&self) -> Instruction {
 | 
			
		||||
        let byte = self.read_byte(self.reg.pc);
 | 
			
		||||
        if byte == 0xCB {
 | 
			
		||||
            Instruction::decode(self.read_byte(self.reg.pc + 1), true)
 | 
			
		||||
        } else {
 | 
			
		||||
            Instruction::decode(byte, false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone)]
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub(crate) enum Register {
 | 
			
		||||
    A,
 | 
			
		||||
    B,
 | 
			
		||||
@@ -409,10 +410,9 @@ pub(crate) enum Register {
 | 
			
		||||
    E,
 | 
			
		||||
    H,
 | 
			
		||||
    L,
 | 
			
		||||
    Flag,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone)]
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub(crate) enum RegisterPair {
 | 
			
		||||
    AF,
 | 
			
		||||
    BC,
 | 
			
		||||
@@ -422,7 +422,7 @@ pub(crate) enum RegisterPair {
 | 
			
		||||
    PC,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Copy, Clone, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct Registers {
 | 
			
		||||
    a: u8,
 | 
			
		||||
    b: u8,
 | 
			
		||||
@@ -507,7 +507,7 @@ impl From<u8> for Flags {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub(crate) enum HaltState {
 | 
			
		||||
pub(crate) enum HaltKind {
 | 
			
		||||
    ImeEnabled,
 | 
			
		||||
    NonePending,
 | 
			
		||||
    SomePending,
 | 
			
		||||
@@ -516,8 +516,8 @@ pub(crate) enum HaltState {
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub(crate) enum ImeState {
 | 
			
		||||
    Disabled,
 | 
			
		||||
    EiExecuted,
 | 
			
		||||
    Pending,
 | 
			
		||||
    PendingEnd,
 | 
			
		||||
    Enabled,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										139
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								src/emu.rs
									
									
									
									
									
								
							@@ -1,60 +1,121 @@
 | 
			
		||||
use crate::cpu::Cpu as SM83;
 | 
			
		||||
use crate::instruction::Cycle;
 | 
			
		||||
use crate::joypad;
 | 
			
		||||
use crate::ppu::Ppu;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use crate::apu::gen::SampleProducer;
 | 
			
		||||
use crate::cpu::Cpu;
 | 
			
		||||
use crate::joypad::{self, Joypad};
 | 
			
		||||
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
 | 
			
		||||
use gilrs::Gilrs;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use winit_input_helper::WinitInputHelper;
 | 
			
		||||
 | 
			
		||||
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
 | 
			
		||||
pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines
 | 
			
		||||
pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
 | 
			
		||||
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
 | 
			
		||||
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
 | 
			
		||||
const GAMEPAD_ENABLED: bool = false;
 | 
			
		||||
 | 
			
		||||
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
 | 
			
		||||
    let mut cpu = match boot_path {
 | 
			
		||||
        Some(path) => SM83::boot_new(path)?,
 | 
			
		||||
        None => SM83::new(),
 | 
			
		||||
    };
 | 
			
		||||
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
 | 
			
		||||
    let mut elapsed = 0;
 | 
			
		||||
 | 
			
		||||
    eprintln!("Initialized GB Emulator");
 | 
			
		||||
    if let Some(event) = gamepad.next_event() {
 | 
			
		||||
        joypad::handle_gamepad_input(emu.joyp_mut(), event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cpu.load_cartridge(rom_path)?;
 | 
			
		||||
    Ok(cpu)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn rom_title(game_boy: &SM83) -> &str {
 | 
			
		||||
    game_boy.rom_title().unwrap_or(DEFAULT_TITLE)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn run(
 | 
			
		||||
    game_boy: &mut SM83,
 | 
			
		||||
    gamepad: &mut Gilrs,
 | 
			
		||||
    input: &WinitInputHelper,
 | 
			
		||||
    pending: Cycle,
 | 
			
		||||
) -> Cycle {
 | 
			
		||||
    let mut elapsed = Cycle::new(0);
 | 
			
		||||
 | 
			
		||||
    while elapsed < pending {
 | 
			
		||||
        elapsed += run_unsynced(game_boy, gamepad, input);
 | 
			
		||||
    joypad::handle_keyboard_input(emu.joyp_mut(), key);
 | 
			
		||||
    while elapsed < CYCLES_IN_FRAME {
 | 
			
		||||
        elapsed += emu.step();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    elapsed
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
 | 
			
		||||
    if GAMEPAD_ENABLED {
 | 
			
		||||
        if let Some(event) = gamepad.next_event() {
 | 
			
		||||
            joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
 | 
			
		||||
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
 | 
			
		||||
    buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Emulator {
 | 
			
		||||
    cpu: Cpu,
 | 
			
		||||
    timestamp: Cycle,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Emulator {
 | 
			
		||||
    fn new(cpu: Cpu) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            cpu,
 | 
			
		||||
            timestamp: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
 | 
			
		||||
    game_boy.step()
 | 
			
		||||
    fn step(&mut self) -> Cycle {
 | 
			
		||||
        self.cpu.step()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn load_cart(&mut self, rom: Vec<u8>) {
 | 
			
		||||
        self.cpu.bus_mut().load_cart(rom)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn joyp_mut(&mut self) -> &mut Joypad {
 | 
			
		||||
        &mut self.cpu.bus_mut().joypad
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
 | 
			
		||||
        self.cpu.bus_mut().apu.attach_producer(prod)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn title(&self) -> &str {
 | 
			
		||||
        self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
 | 
			
		||||
    ppu.copy_to_gui(frame);
 | 
			
		||||
pub mod build {
 | 
			
		||||
    use std::fs::File;
 | 
			
		||||
    use std::io::{Read, Result};
 | 
			
		||||
    use std::path::Path;
 | 
			
		||||
 | 
			
		||||
    use crate::bus::BOOT_SIZE;
 | 
			
		||||
    use crate::cpu::Cpu;
 | 
			
		||||
 | 
			
		||||
    use super::Emulator;
 | 
			
		||||
 | 
			
		||||
    #[derive(Debug, Default)]
 | 
			
		||||
    pub struct EmulatorBuilder {
 | 
			
		||||
        boot: Option<[u8; BOOT_SIZE]>,
 | 
			
		||||
        cart: Option<Vec<u8>>,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl EmulatorBuilder {
 | 
			
		||||
        pub fn new() -> Self {
 | 
			
		||||
            Default::default()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
 | 
			
		||||
            let mut file = File::open(path.as_ref())?;
 | 
			
		||||
 | 
			
		||||
            let mut buf = [0x00; BOOT_SIZE];
 | 
			
		||||
            file.read_exact(&mut buf)?;
 | 
			
		||||
 | 
			
		||||
            self.boot = Some(buf);
 | 
			
		||||
            Ok(self)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
 | 
			
		||||
            let mut file = File::open(path.as_ref())?;
 | 
			
		||||
 | 
			
		||||
            let mut buf = Vec::new();
 | 
			
		||||
            file.read_to_end(&mut buf)?;
 | 
			
		||||
 | 
			
		||||
            self.cart = Some(buf);
 | 
			
		||||
            Ok(self)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pub fn finish(mut self) -> Emulator {
 | 
			
		||||
            let mut emu = Emulator::new(match self.boot {
 | 
			
		||||
                Some(rom) => Cpu::with_boot(rom),
 | 
			
		||||
                None => Cpu::without_boot(),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if let Some(rom) = self.cart.take() {
 | 
			
		||||
                emu.load_cart(rom)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            emu
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										321
									
								
								src/gui.rs
									
									
									
									
									
								
							
							
						
						
									
										321
									
								
								src/gui.rs
									
									
									
									
									
								
							@@ -1,321 +0,0 @@
 | 
			
		||||
use crate::bus::BusIo;
 | 
			
		||||
use crate::cpu::{Cpu as SM83, Register, RegisterPair};
 | 
			
		||||
use egui::{ClippedMesh, FontDefinitions};
 | 
			
		||||
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
 | 
			
		||||
use egui_winit_platform::{Platform, PlatformDescriptor};
 | 
			
		||||
use pixels::{wgpu, PixelsContext};
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use wgpu::TextureFormat::Bgra8UnormSrgb;
 | 
			
		||||
 | 
			
		||||
// Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs
 | 
			
		||||
 | 
			
		||||
/// Manages all state required for rendering egui over `Pixels`.
 | 
			
		||||
pub struct Egui {
 | 
			
		||||
    // State for egui.
 | 
			
		||||
    start_time: Instant,
 | 
			
		||||
    platform: Platform,
 | 
			
		||||
    screen_desc: ScreenDescriptor,
 | 
			
		||||
    render_pass: RenderPass,
 | 
			
		||||
    paint_jobs: Vec<ClippedMesh>,
 | 
			
		||||
 | 
			
		||||
    pub config: Configuration,
 | 
			
		||||
 | 
			
		||||
    show_flags: bool,
 | 
			
		||||
    show_cpu_info: bool,
 | 
			
		||||
    show_registers: bool,
 | 
			
		||||
    show_int: bool,
 | 
			
		||||
    show_timer: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Egui {
 | 
			
		||||
    /// Create egui.
 | 
			
		||||
    pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self {
 | 
			
		||||
        let platform = Platform::new(PlatformDescriptor {
 | 
			
		||||
            physical_width: width,
 | 
			
		||||
            physical_height: height,
 | 
			
		||||
            scale_factor,
 | 
			
		||||
            font_definitions: FontDefinitions::default(),
 | 
			
		||||
            style: Default::default(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let screen_desc = ScreenDescriptor {
 | 
			
		||||
            physical_width: width,
 | 
			
		||||
            physical_height: height,
 | 
			
		||||
            scale_factor: scale_factor as f32,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb);
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            start_time: Instant::now(),
 | 
			
		||||
            platform,
 | 
			
		||||
            screen_desc,
 | 
			
		||||
            render_pass,
 | 
			
		||||
            paint_jobs: Vec::new(),
 | 
			
		||||
            config: Default::default(),
 | 
			
		||||
            show_flags: false,
 | 
			
		||||
            show_cpu_info: false,
 | 
			
		||||
            show_registers: false,
 | 
			
		||||
            show_int: false,
 | 
			
		||||
            show_timer: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handle input events from the window manager.
 | 
			
		||||
    pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) {
 | 
			
		||||
        self.platform.handle_event(event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Resize egui.
 | 
			
		||||
    pub fn resize(&mut self, width: u32, height: u32) {
 | 
			
		||||
        self.screen_desc.physical_width = width;
 | 
			
		||||
        self.screen_desc.physical_height = height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update scaling factor.
 | 
			
		||||
    pub fn scale_factor(&mut self, scale_factor: f64) {
 | 
			
		||||
        self.screen_desc.scale_factor = scale_factor as f32;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Prepare egui.
 | 
			
		||||
    pub fn prepare(&mut self, game_boy: &SM83) {
 | 
			
		||||
        self.platform
 | 
			
		||||
            .update_time(self.start_time.elapsed().as_secs_f64());
 | 
			
		||||
 | 
			
		||||
        // Begin the egui frame.
 | 
			
		||||
        self.platform.begin_frame();
 | 
			
		||||
 | 
			
		||||
        // Draw the demo application.
 | 
			
		||||
        self.ui(&self.platform.context(), game_boy);
 | 
			
		||||
 | 
			
		||||
        // End the egui frame and create all paint jobs to prepare for rendering.
 | 
			
		||||
        let (_output, paint_commands) = self.platform.end_frame();
 | 
			
		||||
        self.paint_jobs = self.platform.context().tessellate(paint_commands);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create the UI using egui.
 | 
			
		||||
    fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) {
 | 
			
		||||
        egui::TopPanel::top("menubar_container").show(ctx, |ui| {
 | 
			
		||||
            egui::menu::bar(ui, |ui| {
 | 
			
		||||
                egui::menu::menu(ui, "File", |ui| {
 | 
			
		||||
                    if ui.button("Configuration").clicked() {
 | 
			
		||||
                        self.config.show = true;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                egui::menu::menu(ui, "Status", |ui| {
 | 
			
		||||
                    if ui.button("Flags").clicked() {
 | 
			
		||||
                        self.show_flags = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ui.button("CPU Information").clicked() {
 | 
			
		||||
                        self.show_cpu_info = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ui.button("Registers").clicked() {
 | 
			
		||||
                        self.show_registers = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ui.button("Interrupts").clicked() {
 | 
			
		||||
                        self.show_int = true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ui.button("Timer").clicked() {
 | 
			
		||||
                        self.show_timer = true;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("Cpu Flags")
 | 
			
		||||
            .open(&mut self.show_flags)
 | 
			
		||||
            .show(ctx, |ui| {
 | 
			
		||||
                let flags = game_boy.flags();
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    let _ = ui.selectable_label(flags.z(), "Zero");
 | 
			
		||||
                    let _ = ui.selectable_label(flags.n(), "Negative");
 | 
			
		||||
                    let _ = ui.selectable_label(flags.h(), "Half-Carry");
 | 
			
		||||
                    let _ = ui.selectable_label(flags.c(), "Carry");
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("Timer")
 | 
			
		||||
            .open(&mut self.show_timer)
 | 
			
		||||
            .show(ctx, |ui| {
 | 
			
		||||
                let timer = game_boy.timer();
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("DIV");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", timer.divider));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("TIMA");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", timer.counter));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("TMA");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", timer.modulo));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("TAC");
 | 
			
		||||
                    ui.monospace(format!("{:?}", timer.ctrl));
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("Registers")
 | 
			
		||||
            .open(&mut self.show_registers)
 | 
			
		||||
            .show(ctx, |ui| {
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("A");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::A)));
 | 
			
		||||
 | 
			
		||||
                    ui.label("F");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::Flag)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("B");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::B)));
 | 
			
		||||
 | 
			
		||||
                    ui.label("C");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::C)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("D");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::D)));
 | 
			
		||||
 | 
			
		||||
                    ui.label("E");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::E)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("H");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::H)));
 | 
			
		||||
 | 
			
		||||
                    ui.label("L");
 | 
			
		||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::L)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("AF");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("BC");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("DE");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("HL");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL)));
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("Cpu Information")
 | 
			
		||||
            .open(&mut self.show_cpu_info)
 | 
			
		||||
            .show(ctx, |ui| {
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("PC");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("SP");
 | 
			
		||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP)));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("IME");
 | 
			
		||||
                    ui.label(format!("{:?}", game_boy.ime()));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    ui.label("HALT");
 | 
			
		||||
                    ui.label(format!("{:?}", game_boy.halted()));
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("IRQ Information")
 | 
			
		||||
            .open(&mut self.show_int)
 | 
			
		||||
            .show(ctx, |ui| {
 | 
			
		||||
                let req = game_boy.read_byte(0xFF0F);
 | 
			
		||||
                let enabled = game_boy.read_byte(0xFFFF);
 | 
			
		||||
 | 
			
		||||
                ui.heading("Interrupt Requests");
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK");
 | 
			
		||||
                    let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT");
 | 
			
		||||
                    let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER");
 | 
			
		||||
                    let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL");
 | 
			
		||||
                    let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                ui.heading("Interrupt Enable");
 | 
			
		||||
                ui.horizontal(|ui| {
 | 
			
		||||
                    let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK");
 | 
			
		||||
                    let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT");
 | 
			
		||||
                    let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER");
 | 
			
		||||
                    let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL");
 | 
			
		||||
                    let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD");
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        egui::Window::new("Configuration")
 | 
			
		||||
            .open(&mut self.config.show)
 | 
			
		||||
            .show(ctx, |_ui| {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Render egui.
 | 
			
		||||
    pub fn render(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        encoder: &mut wgpu::CommandEncoder,
 | 
			
		||||
        render_target: &wgpu::TextureView,
 | 
			
		||||
        context: &PixelsContext,
 | 
			
		||||
    ) {
 | 
			
		||||
        // Upload all resources to the GPU.
 | 
			
		||||
        self.render_pass.update_texture(
 | 
			
		||||
            &context.device,
 | 
			
		||||
            &context.queue,
 | 
			
		||||
            &self.platform.context().texture(),
 | 
			
		||||
        );
 | 
			
		||||
        self.render_pass
 | 
			
		||||
            .update_user_textures(&context.device, &context.queue);
 | 
			
		||||
        self.render_pass.update_buffers(
 | 
			
		||||
            &context.device,
 | 
			
		||||
            &context.queue,
 | 
			
		||||
            &self.paint_jobs,
 | 
			
		||||
            &self.screen_desc,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Record all render passes.
 | 
			
		||||
        self.render_pass.execute(
 | 
			
		||||
            encoder,
 | 
			
		||||
            render_target,
 | 
			
		||||
            &self.paint_jobs,
 | 
			
		||||
            &self.screen_desc,
 | 
			
		||||
            None,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Configuration {
 | 
			
		||||
    /// Show Configuration egui menu
 | 
			
		||||
    show: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Configuration {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self { show: false }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2970
									
								
								src/instruction.rs
									
									
									
									
									
								
							
							
						
						
									
										2970
									
								
								src/instruction.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
use bitfield::bitfield;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Interrupt {
 | 
			
		||||
    pub(crate) flag: InterruptFlag,
 | 
			
		||||
    pub(crate) enable: InterruptEnable,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
 | 
			
		||||
use winit_input_helper::WinitInputHelper;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct Joypad {
 | 
			
		||||
    /// 0xFF00 | P1/JOYP - Player 1 Joypad
 | 
			
		||||
    pub(crate) p1: u8,
 | 
			
		||||
@@ -48,7 +48,7 @@ impl Joypad {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct JoypadState {
 | 
			
		||||
    // Direction Row
 | 
			
		||||
    dpad_down: ButtonEvent,
 | 
			
		||||
@@ -120,49 +120,57 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Down) {
 | 
			
		||||
        state.dpad_down.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Down) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Down) {
 | 
			
		||||
        state.dpad_down.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Up) {
 | 
			
		||||
        state.dpad_up.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Up) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Up) {
 | 
			
		||||
        state.dpad_up.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Left) {
 | 
			
		||||
        state.dpad_left.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Left) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Left) {
 | 
			
		||||
        state.dpad_left.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Right) {
 | 
			
		||||
        state.dpad_right.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Right) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Right) {
 | 
			
		||||
        state.dpad_right.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::T) {
 | 
			
		||||
        state.start.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::T) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::T) {
 | 
			
		||||
        state.start.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Y) {
 | 
			
		||||
        state.select.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Y) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Y) {
 | 
			
		||||
        state.select.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::Z) {
 | 
			
		||||
        state.south.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::Z) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::Z) {
 | 
			
		||||
        state.south.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if input.key_pressed(VirtualKeyCode::X) {
 | 
			
		||||
        state.east.update(true, irq);
 | 
			
		||||
    } else if input.key_released(VirtualKeyCode::X) {
 | 
			
		||||
    }
 | 
			
		||||
    if input.key_released(VirtualKeyCode::X) {
 | 
			
		||||
        state.east.update(false, irq);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,19 @@
 | 
			
		||||
pub use gui::Egui;
 | 
			
		||||
pub use instruction::Cycle;
 | 
			
		||||
pub use sound::AudioMPSC;
 | 
			
		||||
pub use apu::gen::AudioSPSC;
 | 
			
		||||
pub type Cycle = u64;
 | 
			
		||||
 | 
			
		||||
pub const GB_WIDTH: usize = 160;
 | 
			
		||||
pub const GB_HEIGHT: usize = 144;
 | 
			
		||||
 | 
			
		||||
mod apu;
 | 
			
		||||
mod bus;
 | 
			
		||||
mod cartridge;
 | 
			
		||||
mod cpu;
 | 
			
		||||
pub mod emu;
 | 
			
		||||
mod gui;
 | 
			
		||||
mod high_ram;
 | 
			
		||||
mod instruction;
 | 
			
		||||
mod interrupt;
 | 
			
		||||
mod joypad;
 | 
			
		||||
mod ppu;
 | 
			
		||||
mod serial;
 | 
			
		||||
mod sound;
 | 
			
		||||
mod timer;
 | 
			
		||||
mod work_ram;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										120
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										120
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,17 +1,20 @@
 | 
			
		||||
use std::convert::TryInto;
 | 
			
		||||
 | 
			
		||||
use anyhow::{anyhow, Result};
 | 
			
		||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
 | 
			
		||||
use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH};
 | 
			
		||||
use gb::emu::build::EmulatorBuilder;
 | 
			
		||||
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
 | 
			
		||||
use gilrs::Gilrs;
 | 
			
		||||
use pixels::{Pixels, SurfaceTexture};
 | 
			
		||||
use pixels::{PixelsBuilder, SurfaceTexture};
 | 
			
		||||
use rodio::{OutputStream, Sink};
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
use winit::dpi::LogicalSize;
 | 
			
		||||
use winit::dpi::{LogicalSize, PhysicalSize};
 | 
			
		||||
use winit::event::{Event, VirtualKeyCode};
 | 
			
		||||
use winit::event_loop::{ControlFlow, EventLoop};
 | 
			
		||||
use winit::window::{Window, WindowBuilder};
 | 
			
		||||
use winit_input_helper::WinitInputHelper;
 | 
			
		||||
 | 
			
		||||
const SCALE: f64 = 2.0;
 | 
			
		||||
const WINDOW_SCALE: usize = 3;
 | 
			
		||||
const AUDIO_ENABLED: bool = false;
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<()> {
 | 
			
		||||
    let app = App::new(crate_name!())
 | 
			
		||||
@@ -38,68 +41,58 @@ fn main() -> Result<()> {
 | 
			
		||||
        )
 | 
			
		||||
        .get_matches();
 | 
			
		||||
 | 
			
		||||
    // `rom` is a required value in every situation so this will
 | 
			
		||||
    // always exist.
 | 
			
		||||
    let rom_path = m
 | 
			
		||||
        .value_of("rom")
 | 
			
		||||
        .expect("Required value 'rom' was provided");
 | 
			
		||||
    let mut emu_build =
 | 
			
		||||
        EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?;
 | 
			
		||||
 | 
			
		||||
    let mut game_boy =
 | 
			
		||||
        gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator");
 | 
			
		||||
    let cartridge_title = gb::emu::rom_title(&game_boy);
 | 
			
		||||
    if let Some(path) = m.value_of("boot") {
 | 
			
		||||
        emu_build = emu_build.with_boot(path)?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Initialize Gamepad Support
 | 
			
		||||
    let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input");
 | 
			
		||||
    let mut emu = emu_build.finish();
 | 
			
		||||
    let rom_title = emu.title();
 | 
			
		||||
 | 
			
		||||
    let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
 | 
			
		||||
 | 
			
		||||
    // Initialize GUI
 | 
			
		||||
    let event_loop = EventLoop::new();
 | 
			
		||||
    let mut input = WinitInputHelper::new();
 | 
			
		||||
    let window = create_window(&event_loop, cartridge_title)?;
 | 
			
		||||
    let window = create_window(&event_loop, rom_title)?;
 | 
			
		||||
 | 
			
		||||
    let (mut pixels, mut egui) = {
 | 
			
		||||
    let mut pixels = {
 | 
			
		||||
        let size = window.inner_size();
 | 
			
		||||
        let scale_factor = window.scale_factor();
 | 
			
		||||
        let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
 | 
			
		||||
        let pixels = Pixels::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)?;
 | 
			
		||||
        let egui = Egui::new(size.width, size.height, scale_factor, pixels.context());
 | 
			
		||||
 | 
			
		||||
        (pixels, egui)
 | 
			
		||||
        PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
 | 
			
		||||
            .enable_vsync(false)
 | 
			
		||||
            .build()?
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (send, recv) = AudioMPSC::new();
 | 
			
		||||
    game_boy.set_audio_src(send);
 | 
			
		||||
 | 
			
		||||
    // Initialize Audio
 | 
			
		||||
    let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
 | 
			
		||||
    let sink = Sink::try_new(&stream_handle).expect("Initialize Audio Sink");
 | 
			
		||||
 | 
			
		||||
    std::thread::spawn(move || {
 | 
			
		||||
        sink.append(recv);
 | 
			
		||||
    if AUDIO_ENABLED {
 | 
			
		||||
        let spsc: AudioSPSC<f32> = Default::default();
 | 
			
		||||
        let (prod, cons) = spsc.init();
 | 
			
		||||
        let sink = {
 | 
			
		||||
            let s = Sink::try_new(&stream_handle)?;
 | 
			
		||||
            s.append(cons);
 | 
			
		||||
            s.set_volume(0.1);
 | 
			
		||||
            s
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        sink.sleep_until_end();
 | 
			
		||||
    });
 | 
			
		||||
        emu.set_prod(prod);
 | 
			
		||||
 | 
			
		||||
        std::thread::spawn(move || {
 | 
			
		||||
            sink.sleep_until_end();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut now = Instant::now();
 | 
			
		||||
    let mut cycle_count: Cycle = Default::default();
 | 
			
		||||
 | 
			
		||||
    event_loop.run(move |event, _, control_flow| {
 | 
			
		||||
        // Update egui
 | 
			
		||||
        egui.handle_event(&event);
 | 
			
		||||
 | 
			
		||||
        if let Event::RedrawRequested(_) = event {
 | 
			
		||||
            // Prepare egui
 | 
			
		||||
            egui.prepare(&game_boy);
 | 
			
		||||
 | 
			
		||||
            // Render everything together
 | 
			
		||||
            let render_result = pixels.render_with(|encoder, target, ctx| {
 | 
			
		||||
                // Render the texture
 | 
			
		||||
                ctx.scaling_renderer.render(encoder, target);
 | 
			
		||||
 | 
			
		||||
                // Render egui
 | 
			
		||||
                egui.render(encoder, target, ctx);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if render_result
 | 
			
		||||
            if pixels
 | 
			
		||||
                .render()
 | 
			
		||||
                .map_err(|e| anyhow!("pixels.render() failed: {}", e))
 | 
			
		||||
                .is_err()
 | 
			
		||||
            {
 | 
			
		||||
@@ -114,26 +107,21 @@ fn main() -> Result<()> {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(scale_factor) = input.scale_factor() {
 | 
			
		||||
                egui.scale_factor(scale_factor);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(size) = input.window_resized() {
 | 
			
		||||
                pixels.resize_surface(size.width, size.height);
 | 
			
		||||
                egui.resize(size.width, size.height);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let delta = now.elapsed().subsec_nanos();
 | 
			
		||||
            now = Instant::now();
 | 
			
		||||
 | 
			
		||||
            let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos());
 | 
			
		||||
            cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, &input, pending);
 | 
			
		||||
            cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
 | 
			
		||||
 | 
			
		||||
            if cycle_count >= gb::emu::CYCLES_IN_FRAME {
 | 
			
		||||
                // Draw Frame
 | 
			
		||||
                cycle_count = Cycle::new(0);
 | 
			
		||||
                cycle_count %= gb::emu::CYCLES_IN_FRAME;
 | 
			
		||||
 | 
			
		||||
                gb::emu::draw(game_boy.ppu(), pixels.get_frame());
 | 
			
		||||
                let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels
 | 
			
		||||
                    .get_frame()
 | 
			
		||||
                    .try_into()
 | 
			
		||||
                    .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4");
 | 
			
		||||
 | 
			
		||||
                gb::emu::draw_frame(&emu, buf);
 | 
			
		||||
                window.request_redraw();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -141,14 +129,16 @@ fn main() -> Result<()> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
 | 
			
		||||
    let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * SCALE);
 | 
			
		||||
    let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64);
 | 
			
		||||
    let physical = PhysicalSize::new(
 | 
			
		||||
        (GB_WIDTH * WINDOW_SCALE) as f32,
 | 
			
		||||
        (GB_HEIGHT * WINDOW_SCALE) as f32,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Ok(WindowBuilder::new()
 | 
			
		||||
        .with_title(title)
 | 
			
		||||
        .with_inner_size(size)
 | 
			
		||||
        .with_min_inner_size(size)
 | 
			
		||||
        .with_min_inner_size(logical)
 | 
			
		||||
        .with_inner_size(physical)
 | 
			
		||||
        .with_resizable(true)
 | 
			
		||||
        .with_decorations(true)
 | 
			
		||||
        .with_transparent(false)
 | 
			
		||||
        // .with_drag_and_drop(false) // OleInitialize failed error if this is set to true
 | 
			
		||||
        .build(event_loop)?)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										335
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										335
									
								
								src/ppu.rs
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
use crate::bus::BusIo;
 | 
			
		||||
use crate::instruction::Cycle;
 | 
			
		||||
use crate::Cycle;
 | 
			
		||||
use crate::GB_HEIGHT;
 | 
			
		||||
use crate::GB_WIDTH;
 | 
			
		||||
use dma::DirectMemoryAccess;
 | 
			
		||||
@@ -8,7 +8,7 @@ use std::convert::TryInto;
 | 
			
		||||
pub(crate) use types::PpuMode;
 | 
			
		||||
use types::{
 | 
			
		||||
    BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette,
 | 
			
		||||
    ObjectPaletteId, ObjectSize, Pixels, RenderPriority, TileDataAddress,
 | 
			
		||||
    ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
mod dma;
 | 
			
		||||
@@ -33,7 +33,7 @@ const LIGHT_GRAY: [u8; 4] = 0xAEBA89FFu32.to_be_bytes();
 | 
			
		||||
const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes();
 | 
			
		||||
const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes();
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct Ppu {
 | 
			
		||||
    pub(crate) int: Interrupt,
 | 
			
		||||
    /// 0xFF40 | LCDC - LCD Control
 | 
			
		||||
@@ -47,10 +47,14 @@ pub struct Ppu {
 | 
			
		||||
    pub(crate) dma: DirectMemoryAccess,
 | 
			
		||||
    scan_state: OamScanState,
 | 
			
		||||
    fetch: PixelFetcher,
 | 
			
		||||
    fifo: FifoRenderer,
 | 
			
		||||
    fifo: PixelFifo,
 | 
			
		||||
    obj_buffer: ObjectBuffer,
 | 
			
		||||
    frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>,
 | 
			
		||||
    window_stat: WindowStatus,
 | 
			
		||||
 | 
			
		||||
    scanline_start: bool,
 | 
			
		||||
    to_discard: u8,
 | 
			
		||||
 | 
			
		||||
    x_pos: u8,
 | 
			
		||||
    cycle: Cycle,
 | 
			
		||||
}
 | 
			
		||||
@@ -66,7 +70,7 @@ impl BusIo for Ppu {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Ppu {
 | 
			
		||||
    pub(crate) fn clock(&mut self) {
 | 
			
		||||
    pub(crate) fn tick(&mut self) {
 | 
			
		||||
        self.cycle += 1;
 | 
			
		||||
 | 
			
		||||
        if !self.ctrl.lcd_enabled() {
 | 
			
		||||
@@ -75,7 +79,7 @@ impl Ppu {
 | 
			
		||||
 | 
			
		||||
        match self.stat.mode() {
 | 
			
		||||
            PpuMode::OamScan => {
 | 
			
		||||
                if self.cycle >= 80.into() {
 | 
			
		||||
                if self.cycle >= 80 {
 | 
			
		||||
                    self.stat.set_mode(PpuMode::Drawing);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -84,7 +88,7 @@ impl Ppu {
 | 
			
		||||
            PpuMode::Drawing => {
 | 
			
		||||
                if self.ctrl.lcd_enabled() {
 | 
			
		||||
                    // Only Draw when the LCD Is Enabled
 | 
			
		||||
                    self.draw(self.cycle.into());
 | 
			
		||||
                    self.draw(self.cycle);
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.reset();
 | 
			
		||||
                }
 | 
			
		||||
@@ -105,6 +109,8 @@ impl Ppu {
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    self.x_pos = 0;
 | 
			
		||||
                    self.scanline_start = true;
 | 
			
		||||
                    self.to_discard = 0;
 | 
			
		||||
 | 
			
		||||
                    self.fetch.hblank_reset();
 | 
			
		||||
                    self.window_stat.hblank_reset();
 | 
			
		||||
@@ -119,7 +125,7 @@ impl Ppu {
 | 
			
		||||
            PpuMode::HBlank => {
 | 
			
		||||
                // This mode will always end at 456 cycles
 | 
			
		||||
 | 
			
		||||
                if self.cycle >= 456.into() {
 | 
			
		||||
                if self.cycle >= 456 {
 | 
			
		||||
                    self.cycle %= 456;
 | 
			
		||||
                    self.pos.line_y += 1;
 | 
			
		||||
 | 
			
		||||
@@ -161,7 +167,7 @@ impl Ppu {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            PpuMode::VBlank => {
 | 
			
		||||
                if self.cycle > 456.into() {
 | 
			
		||||
                if self.cycle > 456 {
 | 
			
		||||
                    self.cycle %= 456;
 | 
			
		||||
                    self.pos.line_y += 1;
 | 
			
		||||
 | 
			
		||||
@@ -222,23 +228,26 @@ impl Ppu {
 | 
			
		||||
        self.scan_state.next();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn draw(&mut self, _cycle: u32) {
 | 
			
		||||
    fn draw(&mut self, _cycle: Cycle) {
 | 
			
		||||
        use FetcherState::*;
 | 
			
		||||
 | 
			
		||||
        let iter = &mut self.obj_buffer.iter();
 | 
			
		||||
        let mut iter = self.obj_buffer.iter_mut();
 | 
			
		||||
        let default = &mut None;
 | 
			
		||||
 | 
			
		||||
        let obj_attr = loop {
 | 
			
		||||
            match iter.flatten().next() {
 | 
			
		||||
                Some(attr) => {
 | 
			
		||||
                    if attr.x <= (self.x_pos + 8) {
 | 
			
		||||
                        self.fetch.back.reset();
 | 
			
		||||
                        self.fetch.back.pause();
 | 
			
		||||
                        self.fifo.pause();
 | 
			
		||||
            match iter.next() {
 | 
			
		||||
                Some(attr_opt) => {
 | 
			
		||||
                    if let Some(attr) = attr_opt {
 | 
			
		||||
                        if attr.x <= (self.x_pos + 8) {
 | 
			
		||||
                            self.fetch.back.reset();
 | 
			
		||||
                            self.fetch.back.pause();
 | 
			
		||||
                            self.fifo.pause();
 | 
			
		||||
 | 
			
		||||
                        break Some(*attr);
 | 
			
		||||
                            break attr_opt;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                None => break None,
 | 
			
		||||
                None => break default,
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -246,10 +255,10 @@ impl Ppu {
 | 
			
		||||
            match self.fetch.obj.state {
 | 
			
		||||
                TileNumber => {
 | 
			
		||||
                    self.fetch.obj.tile.with_id(attr.tile_index);
 | 
			
		||||
                    self.fetch.obj.next(ToLowByteSleep);
 | 
			
		||||
                    self.fetch.obj.next(SleepOne);
 | 
			
		||||
                }
 | 
			
		||||
                ToLowByteSleep => self.fetch.obj.next(TileLowByte),
 | 
			
		||||
                TileLowByte => {
 | 
			
		||||
                SleepOne => self.fetch.obj.next(TileLow),
 | 
			
		||||
                TileLow => {
 | 
			
		||||
                    let obj_size = self.ctrl.obj_size();
 | 
			
		||||
 | 
			
		||||
                    let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
 | 
			
		||||
@@ -257,10 +266,10 @@ impl Ppu {
 | 
			
		||||
                    let byte = self.read_byte(addr);
 | 
			
		||||
                    self.fetch.obj.tile.with_low_byte(byte);
 | 
			
		||||
 | 
			
		||||
                    self.fetch.obj.next(ToHighByteSleep);
 | 
			
		||||
                    self.fetch.obj.next(SleepTwo);
 | 
			
		||||
                }
 | 
			
		||||
                ToHighByteSleep => self.fetch.obj.next(TileHighByte),
 | 
			
		||||
                TileHighByte => {
 | 
			
		||||
                SleepTwo => self.fetch.obj.next(TileHigh),
 | 
			
		||||
                TileHigh => {
 | 
			
		||||
                    let obj_size = self.ctrl.obj_size();
 | 
			
		||||
 | 
			
		||||
                    let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
 | 
			
		||||
@@ -268,10 +277,10 @@ impl Ppu {
 | 
			
		||||
                    let byte = self.read_byte(addr + 1);
 | 
			
		||||
                    self.fetch.obj.tile.with_high_byte(byte);
 | 
			
		||||
 | 
			
		||||
                    self.fetch.obj.next(ToFifoSleep);
 | 
			
		||||
                    self.fetch.obj.next(SleepThree);
 | 
			
		||||
                }
 | 
			
		||||
                ToFifoSleep => self.fetch.obj.next(SendToFifoOne),
 | 
			
		||||
                SendToFifoOne => {
 | 
			
		||||
                SleepThree => self.fetch.obj.next(ToFifoOne),
 | 
			
		||||
                ToFifoOne => {
 | 
			
		||||
                    // Load into Fifo
 | 
			
		||||
                    let (high, low) = self
 | 
			
		||||
                        .fetch
 | 
			
		||||
@@ -282,25 +291,21 @@ impl Ppu {
 | 
			
		||||
 | 
			
		||||
                    let tbpp = Pixels::from_bytes(high, low);
 | 
			
		||||
 | 
			
		||||
                    let palette = match attr.flags.palette() {
 | 
			
		||||
                        ObjectPaletteId::Zero => self.monochrome.obj_palette_0,
 | 
			
		||||
                        ObjectPaletteId::One => self.monochrome.obj_palette_1,
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    let end = Pixels::PIXEL_COUNT - self.fifo.obj.len();
 | 
			
		||||
                    let start = Pixels::PIXEL_COUNT - end;
 | 
			
		||||
 | 
			
		||||
                    let palette_kind = attr.flags.palette();
 | 
			
		||||
                    let x_flip = attr.flags.x_flip();
 | 
			
		||||
 | 
			
		||||
                    for i in start..Pixels::PIXEL_COUNT {
 | 
			
		||||
                    let pixel_count = (attr.x - self.x_pos) as usize;
 | 
			
		||||
                    let start = self.fifo.obj.len();
 | 
			
		||||
 | 
			
		||||
                    for i in start..pixel_count {
 | 
			
		||||
                        let x = if x_flip { 7 - i } else { i };
 | 
			
		||||
 | 
			
		||||
                        let priority = attr.flags.priority();
 | 
			
		||||
                        let shade = palette.shade(tbpp.shade_id(x));
 | 
			
		||||
                        let shade_id = tbpp.shade_id(x);
 | 
			
		||||
 | 
			
		||||
                        let fifo_info = ObjectFifoInfo {
 | 
			
		||||
                            shade,
 | 
			
		||||
                            palette,
 | 
			
		||||
                        let fifo_info = ObjPixelProperty {
 | 
			
		||||
                            shade_id,
 | 
			
		||||
                            palette_kind,
 | 
			
		||||
                            priority,
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
@@ -309,11 +314,11 @@ impl Ppu {
 | 
			
		||||
 | 
			
		||||
                    self.fetch.back.resume();
 | 
			
		||||
                    self.fifo.resume();
 | 
			
		||||
                    self.obj_buffer.remove(&attr);
 | 
			
		||||
                    let _ = std::mem::take(obj_attr);
 | 
			
		||||
 | 
			
		||||
                    self.fetch.obj.next(SendToFifoTwo);
 | 
			
		||||
                    self.fetch.obj.next(ToFifoTwo);
 | 
			
		||||
                }
 | 
			
		||||
                SendToFifoTwo => self.fetch.obj.reset(),
 | 
			
		||||
                ToFifoTwo => self.fetch.obj.reset(),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -343,34 +348,32 @@ impl Ppu {
 | 
			
		||||
                    self.fetch.back.tile.with_id(id);
 | 
			
		||||
 | 
			
		||||
                    // Move on to the Next state in 2 T-cycles
 | 
			
		||||
                    self.fetch.back.next(ToLowByteSleep);
 | 
			
		||||
                    self.fetch.back.next(SleepOne);
 | 
			
		||||
                }
 | 
			
		||||
                ToLowByteSleep => self.fetch.back.next(TileLowByte),
 | 
			
		||||
                TileLowByte => {
 | 
			
		||||
                SleepOne => self.fetch.back.next(TileLow),
 | 
			
		||||
                TileLow => {
 | 
			
		||||
                    let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
 | 
			
		||||
 | 
			
		||||
                    let low = self.read_byte(addr);
 | 
			
		||||
                    self.fetch.back.tile.with_low_byte(low);
 | 
			
		||||
 | 
			
		||||
                    self.fetch.back.next(ToHighByteSleep);
 | 
			
		||||
                    self.fetch.back.next(SleepTwo);
 | 
			
		||||
                }
 | 
			
		||||
                ToHighByteSleep => self.fetch.back.next(TileHighByte),
 | 
			
		||||
                TileHighByte => {
 | 
			
		||||
                SleepTwo => self.fetch.back.next(TileHigh),
 | 
			
		||||
                TileHigh => {
 | 
			
		||||
                    let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
 | 
			
		||||
 | 
			
		||||
                    let high = self.read_byte(addr + 1);
 | 
			
		||||
                    self.fetch.back.tile.with_high_byte(high);
 | 
			
		||||
 | 
			
		||||
                    self.fetch.back.next(ToFifoSleep);
 | 
			
		||||
                    self.fetch.back.next(SleepThree);
 | 
			
		||||
                }
 | 
			
		||||
                ToFifoSleep => self.fetch.back.next(SendToFifoOne),
 | 
			
		||||
                SendToFifoOne => {
 | 
			
		||||
                    self.fetch.back.next(SendToFifoTwo);
 | 
			
		||||
                SleepThree => self.fetch.back.next(ToFifoOne),
 | 
			
		||||
                ToFifoOne => {
 | 
			
		||||
                    self.fetch.back.next(ToFifoTwo);
 | 
			
		||||
                }
 | 
			
		||||
                SendToFifoTwo => {
 | 
			
		||||
                    let palette = &self.monochrome.bg_palette;
 | 
			
		||||
 | 
			
		||||
                    if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo, palette) {
 | 
			
		||||
                ToFifoTwo => {
 | 
			
		||||
                    if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) {
 | 
			
		||||
                        self.fetch.x_pos += 1;
 | 
			
		||||
                        self.fetch.back.next(TileNumber);
 | 
			
		||||
                        self.fetch.back.tile = Default::default();
 | 
			
		||||
@@ -380,50 +383,26 @@ impl Ppu {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.fifo.is_enabled() {
 | 
			
		||||
            use RenderPriority::*;
 | 
			
		||||
            if self.x_pos == 0 && self.scanline_start {
 | 
			
		||||
                self.to_discard = self.pos.scroll_x % 8;
 | 
			
		||||
                self.scanline_start = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if self.x_pos == 0 && !self.fifo.back.is_empty() {
 | 
			
		||||
                let to_discard = self.pos.scroll_x % 8;
 | 
			
		||||
            if self.to_discard > 0 && !self.fifo.back.is_empty() {
 | 
			
		||||
                let _ = self.fifo.back.pop_front();
 | 
			
		||||
                self.to_discard -= 1;
 | 
			
		||||
 | 
			
		||||
                for _ in 0..to_discard {
 | 
			
		||||
                    let _ = self.fifo.back.pop_front();
 | 
			
		||||
                }
 | 
			
		||||
                // Delay the PPU by one cycle
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle Background Pixel and Sprite FIFO
 | 
			
		||||
            let bg_enabled = self.ctrl.bg_win_enabled();
 | 
			
		||||
            let obj_enabled = self.ctrl.obj_enabled();
 | 
			
		||||
            let i0_colour = self.monochrome.bg_palette.i0_colour();
 | 
			
		||||
 | 
			
		||||
            // FIXME: Is this the correct behaviour
 | 
			
		||||
            let rgba_opt = self.fifo.back.pop_front().map(|bg_info| {
 | 
			
		||||
                let bg_shade = if bg_enabled { bg_info.shade } else { i0_colour };
 | 
			
		||||
 | 
			
		||||
                match self.fifo.obj.pop_front() {
 | 
			
		||||
                    Some(obj_info) => {
 | 
			
		||||
                        if obj_enabled {
 | 
			
		||||
                            match (obj_info.shade, obj_info.priority) {
 | 
			
		||||
                                (Some(obj_shade), BackgroundAndWindow) => match bg_info.shade {
 | 
			
		||||
                                    GrayShade::White => obj_shade.into_rgba(),
 | 
			
		||||
                                    _ => bg_shade.into_rgba(),
 | 
			
		||||
                                },
 | 
			
		||||
                                (Some(obj_shade), Object) => obj_shade.into_rgba(),
 | 
			
		||||
                                (None, _) => bg_shade.into_rgba(),
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            bg_shade.into_rgba()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    None => bg_shade.into_rgba(),
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if let Some(rgba) = rgba_opt.as_ref() {
 | 
			
		||||
            if let Some(rgba) = self.clock_fifo().map(GrayShade::into_rgba) {
 | 
			
		||||
                let y = self.pos.line_y as usize;
 | 
			
		||||
                let x = self.x_pos as usize;
 | 
			
		||||
 | 
			
		||||
                let i = (GB_WIDTH * 4) * y + (x * 4);
 | 
			
		||||
                self.frame_buf[i..(i + rgba.len())].copy_from_slice(rgba);
 | 
			
		||||
                self.frame_buf[i..(i + rgba.len())].copy_from_slice(&rgba);
 | 
			
		||||
 | 
			
		||||
                self.x_pos += 1;
 | 
			
		||||
            }
 | 
			
		||||
@@ -439,8 +418,51 @@ impl Ppu {
 | 
			
		||||
        self.frame_buf.swap_with_slice(&mut blank);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn copy_to_gui(&self, frame: &mut [u8]) {
 | 
			
		||||
        frame.copy_from_slice(self.frame_buf.as_ref());
 | 
			
		||||
    pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
			
		||||
        &self.frame_buf
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clock_fifo(&mut self) -> Option<GrayShade> {
 | 
			
		||||
        use ObjectPaletteKind::*;
 | 
			
		||||
        use RenderPriority::*;
 | 
			
		||||
 | 
			
		||||
        let obj_palette_0 = &self.monochrome.obj_palette_0;
 | 
			
		||||
        let obj_palette_1 = &self.monochrome.obj_palette_1;
 | 
			
		||||
 | 
			
		||||
        match self.fifo.back.pop_front() {
 | 
			
		||||
            Some(bg_pixel) => match self.fifo.obj.pop_front() {
 | 
			
		||||
                Some(obj_pixel) if self.ctrl.obj_enabled() => match obj_pixel.priority {
 | 
			
		||||
                    Object | BackgroundAndWindow if obj_pixel.shade_id == 0 => {
 | 
			
		||||
                        Some(self.bg_pixel(bg_pixel.shade_id))
 | 
			
		||||
                    }
 | 
			
		||||
                    BackgroundAndWindow if bg_pixel.shade_id != 0 => {
 | 
			
		||||
                        Some(self.bg_pixel(bg_pixel.shade_id))
 | 
			
		||||
                    }
 | 
			
		||||
                    Object | BackgroundAndWindow => {
 | 
			
		||||
                        let maybe_sprite = match obj_pixel.palette_kind {
 | 
			
		||||
                            Zero => obj_palette_0.shade(obj_pixel.shade_id),
 | 
			
		||||
                            One => obj_palette_1.shade(obj_pixel.shade_id),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        let sprite = maybe_sprite
 | 
			
		||||
                            .expect("Sprite w/ a colour id of 0 has already been handled");
 | 
			
		||||
                        Some(sprite)
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                _ => Some(self.bg_pixel(bg_pixel.shade_id)),
 | 
			
		||||
            },
 | 
			
		||||
            None => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn bg_pixel(&self, shade_id: u8) -> GrayShade {
 | 
			
		||||
        let bg_palette = &self.monochrome.bg_palette;
 | 
			
		||||
 | 
			
		||||
        if self.ctrl.bg_win_enabled() {
 | 
			
		||||
            bg_palette.shade(shade_id)
 | 
			
		||||
        } else {
 | 
			
		||||
            bg_palette.shade(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -448,7 +470,7 @@ impl Default for Ppu {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            vram: Box::new([0u8; VRAM_SIZE]),
 | 
			
		||||
            cycle: Cycle::new(0),
 | 
			
		||||
            cycle: Default::default(),
 | 
			
		||||
            frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
 | 
			
		||||
            int: Default::default(),
 | 
			
		||||
            ctrl: Default::default(),
 | 
			
		||||
@@ -463,11 +485,13 @@ impl Default for Ppu {
 | 
			
		||||
            window_stat: Default::default(),
 | 
			
		||||
            dma: Default::default(),
 | 
			
		||||
            x_pos: 0,
 | 
			
		||||
            scanline_start: true,
 | 
			
		||||
            to_discard: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Interrupt {
 | 
			
		||||
    _vblank: bool,
 | 
			
		||||
    _lcd_stat: bool,
 | 
			
		||||
@@ -491,7 +515,7 @@ impl Interrupt {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct ScreenPosition {
 | 
			
		||||
    /// 0xFF42 | SCY - Scroll Y
 | 
			
		||||
    pub(crate) scroll_y: u8,
 | 
			
		||||
@@ -507,7 +531,7 @@ pub(crate) struct ScreenPosition {
 | 
			
		||||
    pub(crate) window_x: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Monochrome {
 | 
			
		||||
    /// 0xFF47 | BGP - Background Palette Data
 | 
			
		||||
    pub(crate) bg_palette: BackgroundPalette,
 | 
			
		||||
@@ -517,7 +541,7 @@ pub(crate) struct Monochrome {
 | 
			
		||||
    pub(crate) obj_palette_1: ObjectPalette,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct ObjectAttributeTable {
 | 
			
		||||
    buf: Box<[u8; OAM_SIZE]>,
 | 
			
		||||
}
 | 
			
		||||
@@ -584,75 +608,42 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct ObjectBuffer {
 | 
			
		||||
    buf: [Option<ObjectAttribute>; OBJECT_LIMIT],
 | 
			
		||||
    inner: [Option<ObjectAttribute>; OBJECT_LIMIT],
 | 
			
		||||
    len: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ObjectBuffer {
 | 
			
		||||
    fn iter(&self) -> std::slice::Iter<'_, Option<ObjectAttribute>> {
 | 
			
		||||
        self.into_iter()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> IntoIterator for &'a ObjectBuffer {
 | 
			
		||||
    type Item = &'a Option<ObjectAttribute>;
 | 
			
		||||
 | 
			
		||||
    type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>;
 | 
			
		||||
 | 
			
		||||
    fn into_iter(self) -> Self::IntoIter {
 | 
			
		||||
        self.buf.iter()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> IntoIterator for &'a mut ObjectBuffer {
 | 
			
		||||
    type Item = &'a Option<ObjectAttribute>;
 | 
			
		||||
 | 
			
		||||
    type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>;
 | 
			
		||||
 | 
			
		||||
    fn into_iter(self) -> Self::IntoIter {
 | 
			
		||||
        self.buf.iter()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ObjectBuffer {
 | 
			
		||||
    fn is_full(&self) -> bool {
 | 
			
		||||
        self.len == OBJECT_LIMIT
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn clear(&mut self) {
 | 
			
		||||
        self.buf = [Default::default(); 10];
 | 
			
		||||
        self.inner = [Default::default(); 10];
 | 
			
		||||
        self.len = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn add(&mut self, attr: ObjectAttribute) {
 | 
			
		||||
        self.buf[self.len] = Some(attr);
 | 
			
		||||
        self.inner[self.len] = Some(attr);
 | 
			
		||||
        self.len += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn remove(&mut self, attr: &ObjectAttribute) {
 | 
			
		||||
        let maybe_index = self.buf.iter().position(|maybe_attr| match maybe_attr {
 | 
			
		||||
            Some(other_attr) => attr == other_attr,
 | 
			
		||||
            None => false,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if let Some(i) = maybe_index {
 | 
			
		||||
            self.buf[i] = None;
 | 
			
		||||
        }
 | 
			
		||||
    fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> {
 | 
			
		||||
        self.inner.iter_mut()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ObjectBuffer {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            buf: [Default::default(); OBJECT_LIMIT],
 | 
			
		||||
            inner: [Default::default(); OBJECT_LIMIT],
 | 
			
		||||
            len: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct PixelFetcher {
 | 
			
		||||
    x_pos: u8,
 | 
			
		||||
    back: BackgroundFetcher,
 | 
			
		||||
@@ -721,7 +712,7 @@ impl PixelFetcher {
 | 
			
		||||
        tile_data_addr + (offset * 2)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn send_to_fifo(&self, fifo: &mut FifoRenderer, palette: &BackgroundPalette) -> Result<(), ()> {
 | 
			
		||||
    fn send_to_fifo(&self, fifo: &mut PixelFifo) -> Result<(), ()> {
 | 
			
		||||
        if !fifo.back.is_empty() {
 | 
			
		||||
            return Err(());
 | 
			
		||||
        }
 | 
			
		||||
@@ -735,9 +726,9 @@ impl PixelFetcher {
 | 
			
		||||
        let tbpp = Pixels::from_bytes(high, low);
 | 
			
		||||
 | 
			
		||||
        for x in 0..Pixels::PIXEL_COUNT {
 | 
			
		||||
            let shade = palette.shade(tbpp.shade_id(x));
 | 
			
		||||
            let shade_id = tbpp.shade_id(x);
 | 
			
		||||
 | 
			
		||||
            let fifo_info = BackgroundFifoInfo { shade };
 | 
			
		||||
            let fifo_info = BgPixelProperty { shade_id };
 | 
			
		||||
            fifo.back.push_back(fifo_info);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -771,7 +762,7 @@ trait Fetcher {
 | 
			
		||||
    fn hblank_reset(&mut self);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct BackgroundFetcher {
 | 
			
		||||
    state: FetcherState,
 | 
			
		||||
    tile: TileBuilder,
 | 
			
		||||
@@ -837,7 +828,7 @@ impl Default for BackgroundFetcher {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct ObjectFetcher {
 | 
			
		||||
    state: FetcherState,
 | 
			
		||||
    tile: TileBuilder,
 | 
			
		||||
@@ -859,7 +850,7 @@ impl Fetcher for ObjectFetcher {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct WindowLineCounter {
 | 
			
		||||
    count: u8,
 | 
			
		||||
}
 | 
			
		||||
@@ -881,13 +872,13 @@ impl WindowLineCounter {
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum FetcherState {
 | 
			
		||||
    TileNumber,
 | 
			
		||||
    ToLowByteSleep,
 | 
			
		||||
    TileLowByte,
 | 
			
		||||
    ToHighByteSleep,
 | 
			
		||||
    TileHighByte,
 | 
			
		||||
    ToFifoSleep,
 | 
			
		||||
    SendToFifoOne,
 | 
			
		||||
    SendToFifoTwo,
 | 
			
		||||
    SleepOne,
 | 
			
		||||
    TileLow,
 | 
			
		||||
    SleepTwo,
 | 
			
		||||
    TileHigh,
 | 
			
		||||
    SleepThree,
 | 
			
		||||
    ToFifoOne,
 | 
			
		||||
    ToFifoTwo,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for FetcherState {
 | 
			
		||||
@@ -896,28 +887,28 @@ impl Default for FetcherState {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
struct BackgroundFifoInfo {
 | 
			
		||||
    shade: GrayShade,
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct BgPixelProperty {
 | 
			
		||||
    shade_id: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
struct ObjectFifoInfo {
 | 
			
		||||
    shade: Option<GrayShade>,
 | 
			
		||||
    palette: ObjectPalette,
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct ObjPixelProperty {
 | 
			
		||||
    shade_id: u8,
 | 
			
		||||
    palette_kind: ObjectPaletteKind,
 | 
			
		||||
    priority: RenderPriority,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME: Fifo Registers have a known size. Are heap allocations
 | 
			
		||||
// really necessary here?
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
struct FifoRenderer {
 | 
			
		||||
    back: VecDeque<BackgroundFifoInfo>,
 | 
			
		||||
    obj: VecDeque<ObjectFifoInfo>,
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct PixelFifo {
 | 
			
		||||
    back: VecDeque<BgPixelProperty>,
 | 
			
		||||
    obj: VecDeque<ObjPixelProperty>,
 | 
			
		||||
    enabled: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FifoRenderer {
 | 
			
		||||
impl PixelFifo {
 | 
			
		||||
    fn is_enabled(&self) -> bool {
 | 
			
		||||
        self.enabled
 | 
			
		||||
    }
 | 
			
		||||
@@ -931,7 +922,7 @@ impl FifoRenderer {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for FifoRenderer {
 | 
			
		||||
impl Default for PixelFifo {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            back: VecDeque::with_capacity(8),
 | 
			
		||||
@@ -941,7 +932,7 @@ impl Default for FifoRenderer {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct TileBuilder {
 | 
			
		||||
    id: Option<u8>,
 | 
			
		||||
    low: Option<u8>,
 | 
			
		||||
@@ -966,7 +957,7 @@ impl TileBuilder {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct OamScanState {
 | 
			
		||||
    count: u8,
 | 
			
		||||
    mode: OamScanMode,
 | 
			
		||||
@@ -987,8 +978,8 @@ impl OamScanState {
 | 
			
		||||
        self.count
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn mode(&self) -> OamScanMode {
 | 
			
		||||
        self.mode
 | 
			
		||||
    fn mode(&self) -> &OamScanMode {
 | 
			
		||||
        &self.mode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) {
 | 
			
		||||
@@ -1013,7 +1004,7 @@ impl Default for OamScanMode {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
struct WindowStatus {
 | 
			
		||||
    /// This will be true if WY == LY at any point in the frame thus far
 | 
			
		||||
    coincidence: bool,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use crate::instruction::Cycle;
 | 
			
		||||
use crate::Cycle;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct DirectMemoryAccess {
 | 
			
		||||
    pub(crate) state: DmaState,
 | 
			
		||||
    cycle: Cycle,
 | 
			
		||||
@@ -9,7 +9,7 @@ pub(crate) struct DirectMemoryAccess {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DirectMemoryAccess {
 | 
			
		||||
    pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
 | 
			
		||||
    pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
 | 
			
		||||
        match self.state {
 | 
			
		||||
            DmaState::Pending => {
 | 
			
		||||
                self.cycle += 1;
 | 
			
		||||
@@ -28,7 +28,7 @@ impl DirectMemoryAccess {
 | 
			
		||||
 | 
			
		||||
                let src_addr = self
 | 
			
		||||
                    .start
 | 
			
		||||
                    .addr
 | 
			
		||||
                    .0
 | 
			
		||||
                    .as_mut()
 | 
			
		||||
                    .expect("Source Address present during DMA Transfer");
 | 
			
		||||
 | 
			
		||||
@@ -56,9 +56,9 @@ impl DirectMemoryAccess {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn reset(&mut self) {
 | 
			
		||||
        self.cycle = Cycle::new(0);
 | 
			
		||||
        self.cycle = 0;
 | 
			
		||||
        self.state = DmaState::Disabled;
 | 
			
		||||
        self.start.addr = None;
 | 
			
		||||
        self.start.0 = None;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -75,26 +75,21 @@ impl Default for DmaState {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone, Copy)]
 | 
			
		||||
pub(crate) struct DmaAddress {
 | 
			
		||||
    /// The current *source* address of the DMA Transfer
 | 
			
		||||
    ///
 | 
			
		||||
    /// NB: Will be None if no DMA Transfer is in progress
 | 
			
		||||
    addr: Option<u16>,
 | 
			
		||||
}
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
pub(crate) struct DmaAddress(Option<u16>);
 | 
			
		||||
 | 
			
		||||
impl DmaAddress {
 | 
			
		||||
    pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
 | 
			
		||||
        let start = (byte as u16) << 8;
 | 
			
		||||
 | 
			
		||||
        self.addr = Some(start);
 | 
			
		||||
        self.0 = Some(start);
 | 
			
		||||
        *state = DmaState::Pending;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<DmaAddress> for u8 {
 | 
			
		||||
    fn from(ctrl: DmaAddress) -> Self {
 | 
			
		||||
        match ctrl.addr {
 | 
			
		||||
        match ctrl.0 {
 | 
			
		||||
            Some(addr) => (addr >> 8) as u8,
 | 
			
		||||
            None => 0xFF, // TODO: What garbage value should be here?
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -332,7 +332,7 @@ bitfield! {
 | 
			
		||||
    pub from into RenderPriority, priority, set_priority: 7, 7;
 | 
			
		||||
    pub y_flip, set_y_flip: 6;
 | 
			
		||||
    pub x_flip, set_x_flip: 5;
 | 
			
		||||
    pub from into ObjectPaletteId, palette, set_palette: 4, 4;
 | 
			
		||||
    pub from into ObjectPaletteKind, palette, set_palette: 4, 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Eq for ObjectFlags {}
 | 
			
		||||
@@ -368,23 +368,29 @@ impl Default for ObjectFlags {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub enum ObjectPaletteId {
 | 
			
		||||
pub enum ObjectPaletteKind {
 | 
			
		||||
    Zero = 0,
 | 
			
		||||
    One = 1,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<u8> for ObjectPaletteId {
 | 
			
		||||
impl Default for ObjectPaletteKind {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Zero
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<u8> for ObjectPaletteKind {
 | 
			
		||||
    fn from(byte: u8) -> Self {
 | 
			
		||||
        match byte & 0b01 {
 | 
			
		||||
            0b00 => ObjectPaletteId::Zero,
 | 
			
		||||
            0b01 => ObjectPaletteId::One,
 | 
			
		||||
            0b00 => ObjectPaletteKind::Zero,
 | 
			
		||||
            0b01 => ObjectPaletteKind::One,
 | 
			
		||||
            _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ObjectPaletteId> for u8 {
 | 
			
		||||
    fn from(palette_num: ObjectPaletteId) -> Self {
 | 
			
		||||
impl From<ObjectPaletteKind> for u8 {
 | 
			
		||||
    fn from(palette_num: ObjectPaletteKind) -> Self {
 | 
			
		||||
        palette_num as u8
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
use crate::Cycle;
 | 
			
		||||
use std::collections::BinaryHeap;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct Scheduler {
 | 
			
		||||
    timestamp: Cycle,
 | 
			
		||||
    queue: BinaryHeap<Event>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Scheduler {
 | 
			
		||||
    pub(crate) fn init() -> Self {
 | 
			
		||||
        let mut scheduler = Self {
 | 
			
		||||
            timestamp: Default::default(),
 | 
			
		||||
            queue: Default::default(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        scheduler.push(Event {
 | 
			
		||||
            kind: EventKind::TimestampOverflow,
 | 
			
		||||
            timestamp: Cycle::MAX,
 | 
			
		||||
            cb: |_delay| panic!("Reached Cycle::MAX"),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        scheduler
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn push(&mut self, event: Event) {
 | 
			
		||||
        self.queue.push(event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn step(&mut self, cycles: Cycle) {
 | 
			
		||||
        self.timestamp += cycles;
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            let should_pop = match self.queue.peek() {
 | 
			
		||||
                Some(event) => self.timestamp >= event.timestamp,
 | 
			
		||||
                None => false,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if !should_pop {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let event = self.queue.pop().expect("Pop Event from Scheduler Queue");
 | 
			
		||||
 | 
			
		||||
            (event.cb)(self.timestamp - event.timestamp);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct Event {
 | 
			
		||||
    kind: EventKind,
 | 
			
		||||
    cb: fn(Cycle),
 | 
			
		||||
    pub(crate) timestamp: Cycle,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Eq for Event {}
 | 
			
		||||
impl PartialEq for Event {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.kind == other.kind && self.timestamp == other.timestamp
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialOrd for Event {
 | 
			
		||||
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
			
		||||
        Some(self.cmp(other))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Ord for Event {
 | 
			
		||||
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
			
		||||
        self.timestamp.cmp(&other.timestamp)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
pub(crate) enum EventKind {
 | 
			
		||||
    TimestampOverflow,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use bitfield::bitfield;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Default)]
 | 
			
		||||
#[derive(Debug, Default)]
 | 
			
		||||
pub(crate) struct Serial {
 | 
			
		||||
    /// 0xFF01 | SB - Serial Transfer Data
 | 
			
		||||
    pub(crate) next: u8,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1269
									
								
								src/sound.rs
									
									
									
									
									
								
							
							
						
						
									
										1269
									
								
								src/sound.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										87
									
								
								src/timer.rs
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								src/timer.rs
									
									
									
									
									
								
							@@ -1,23 +1,37 @@
 | 
			
		||||
use bitfield::bitfield;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct Timer {
 | 
			
		||||
    /// 0xFF07 | TAC - Timer Control
 | 
			
		||||
    pub(crate) ctrl: TimerControl,
 | 
			
		||||
    /// 0xFF05 | TIMA - Timer Counter
 | 
			
		||||
    pub(crate) counter: u8,
 | 
			
		||||
    counter: u8,
 | 
			
		||||
    /// 0xFF06 | TMA - Timer Modulo
 | 
			
		||||
    pub(crate) modulo: u8,
 | 
			
		||||
    /// 0xFF04 | DIV - Divider Register
 | 
			
		||||
    pub(crate) divider: u16,
 | 
			
		||||
    prev_and_result: Option<u8>,
 | 
			
		||||
 | 
			
		||||
    and_result: Option<u8>,
 | 
			
		||||
    interrupt: bool,
 | 
			
		||||
    state: State,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Timer {
 | 
			
		||||
    pub(crate) fn clock(&mut self) {
 | 
			
		||||
    pub(crate) fn tick(&mut self) {
 | 
			
		||||
        use State::*;
 | 
			
		||||
        use TimerSpeed::*;
 | 
			
		||||
 | 
			
		||||
        match self.state {
 | 
			
		||||
            TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
 | 
			
		||||
            LoadTMA => {
 | 
			
		||||
                self.counter = self.modulo;
 | 
			
		||||
                self.interrupt = true;
 | 
			
		||||
 | 
			
		||||
                self.next();
 | 
			
		||||
            }
 | 
			
		||||
            Normal => {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.divider = self.divider.wrapping_add(1);
 | 
			
		||||
 | 
			
		||||
        // Get Bit Position
 | 
			
		||||
@@ -29,27 +43,34 @@ impl Timer {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let bit = (self.divider >> bit) as u8 & 0x01;
 | 
			
		||||
        let timer_enable = self.ctrl.enabled() as u8;
 | 
			
		||||
        let and_result = bit & timer_enable;
 | 
			
		||||
        let new_result = bit & self.ctrl.enabled() as u8;
 | 
			
		||||
 | 
			
		||||
        if let Some(0x01) = self.prev_and_result {
 | 
			
		||||
            if and_result == 0x00 {
 | 
			
		||||
        if let Some(0x01) = self.and_result {
 | 
			
		||||
            if new_result == 0x00 {
 | 
			
		||||
                // Falling Edge, increase TIMA Register
 | 
			
		||||
                self.increment_tima();
 | 
			
		||||
                self.inc_counter();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.prev_and_result = Some(and_result);
 | 
			
		||||
        self.and_result = Some(new_result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn increment_tima(&mut self) {
 | 
			
		||||
        let (result, did_overflow) = self.counter.overflowing_add(1);
 | 
			
		||||
    /// 0xFF05 | TIMA - Timer Counter
 | 
			
		||||
    pub(crate) fn tima(&self) -> u8 {
 | 
			
		||||
        self.counter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        self.counter = if did_overflow {
 | 
			
		||||
            self.interrupt = true;
 | 
			
		||||
            self.modulo
 | 
			
		||||
        } else {
 | 
			
		||||
            result
 | 
			
		||||
    /// 0xFF05 | TIMA - Timer Counter
 | 
			
		||||
    pub(crate) fn set_tima(&mut self, byte: u8) {
 | 
			
		||||
        use State::*;
 | 
			
		||||
 | 
			
		||||
        match self.state {
 | 
			
		||||
            Normal | AbortedTIMAOverflow(_) => self.counter = byte,
 | 
			
		||||
            TIMAOverflow(step) => {
 | 
			
		||||
                self.counter = byte;
 | 
			
		||||
                self.state = AbortedTIMAOverflow(step);
 | 
			
		||||
            }
 | 
			
		||||
            LoadTMA => {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +81,27 @@ impl Timer {
 | 
			
		||||
    pub(crate) fn set_interrupt(&mut self, value: bool) {
 | 
			
		||||
        self.interrupt = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn inc_counter(&mut self) {
 | 
			
		||||
        let (sum, did_overflow) = self.counter.overflowing_add(1);
 | 
			
		||||
        self.counter = if did_overflow { 0 } else { sum };
 | 
			
		||||
 | 
			
		||||
        if did_overflow {
 | 
			
		||||
            self.state = State::TIMAOverflow(0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn next(&mut self) {
 | 
			
		||||
        use State::*;
 | 
			
		||||
 | 
			
		||||
        self.state = match self.state {
 | 
			
		||||
            Normal | LoadTMA => Normal,
 | 
			
		||||
            AbortedTIMAOverflow(4) => Normal,
 | 
			
		||||
            TIMAOverflow(4) => LoadTMA,
 | 
			
		||||
            AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
 | 
			
		||||
            TIMAOverflow(step) => TIMAOverflow(step + 1),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for Timer {
 | 
			
		||||
@@ -70,7 +112,8 @@ impl Default for Timer {
 | 
			
		||||
            modulo: 0,
 | 
			
		||||
            divider: 0,
 | 
			
		||||
            interrupt: false,
 | 
			
		||||
            prev_and_result: None,
 | 
			
		||||
            and_result: None,
 | 
			
		||||
            state: State::Normal,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -132,3 +175,11 @@ impl From<TimerControl> for u8 {
 | 
			
		||||
        ctrl.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
enum State {
 | 
			
		||||
    TIMAOverflow(u8),
 | 
			
		||||
    AbortedTIMAOverflow(u8),
 | 
			
		||||
    Normal,
 | 
			
		||||
    LoadTMA,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ const VARIABLE_WORK_RAM_SIZE: usize = WORK_RAM_SIZE;
 | 
			
		||||
const WORK_RAM_START_ADDRESS: usize = 0xC000;
 | 
			
		||||
const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct WorkRam {
 | 
			
		||||
    bank: Box<[u8; WORK_RAM_SIZE]>,
 | 
			
		||||
}
 | 
			
		||||
@@ -28,48 +28,25 @@ impl Default for WorkRam {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
enum BankNumber {
 | 
			
		||||
    One = 1,
 | 
			
		||||
    Two = 2,
 | 
			
		||||
    Three = 3,
 | 
			
		||||
    Four = 4,
 | 
			
		||||
    Five = 5,
 | 
			
		||||
    Six = 6,
 | 
			
		||||
    Seven = 7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub(crate) struct VariableWorkRam {
 | 
			
		||||
    current: BankNumber,
 | 
			
		||||
    bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
 | 
			
		||||
    buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for VariableWorkRam {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            current: BankNumber::One,
 | 
			
		||||
            bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]),
 | 
			
		||||
            buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VariableWorkRam {
 | 
			
		||||
    fn set_current_bank(&mut self, bank: BankNumber) {
 | 
			
		||||
        self.current = bank;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_current_bank(&self) -> BankNumber {
 | 
			
		||||
        self.current
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BusIo for VariableWorkRam {
 | 
			
		||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
			
		||||
        self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
 | 
			
		||||
        self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
			
		||||
        self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
 | 
			
		||||
        self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user