Compare commits
	
		
			95 Commits
		
	
	
		
			192bdffd64
			...
			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 | |||
| 08bd33b4d4 | |||
| 2b5926e533 | |||
| 1e27875dc3 | |||
| 1566d60fd4 | |||
| ab99e24f64 | 
							
								
								
									
										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" | version = "0.1.0" | ||||||
| authors = ["Rekai Musuka <rekai@musuka.dev>"] | authors = ["Rekai Musuka <rekai@musuka.dev>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
|  | resolver = "2" | ||||||
|  |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # 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" | bitfield = "^0.13" | ||||||
| clap = "^2.33" | clap = "^2.33" | ||||||
| gilrs = "^0.8" | gilrs = "^0.8" | ||||||
| pixels = "^0.3" | pixels = "^0.6" | ||||||
| winit = "^0.24" | winit = "^0.25" | ||||||
| winit_input_helper = "^0.9" | winit_input_helper = "^0.10" | ||||||
| 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" } |  | ||||||
| rodio = "^0.14" | rodio = "^0.14" | ||||||
| crossbeam-channel = "^0.5" | rtrb = "^0.1.4" | ||||||
|  |  | ||||||
| [profile.release] | [profile.release] | ||||||
| debug = true | 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, | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										149
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -1,26 +1,25 @@ | |||||||
|  | use crate::apu::Apu; | ||||||
| use crate::cartridge::Cartridge; | use crate::cartridge::Cartridge; | ||||||
| use crate::high_ram::HighRam; | use crate::high_ram::HighRam; | ||||||
| use crate::interrupt::{Interrupt, InterruptFlag}; | use crate::interrupt::{Interrupt, InterruptFlag}; | ||||||
| use crate::joypad::Joypad; | use crate::joypad::Joypad; | ||||||
| use crate::ppu::{Ppu, PpuMode}; | use crate::ppu::{Ppu, PpuMode}; | ||||||
| use crate::serial::Serial; | use crate::serial::Serial; | ||||||
| use crate::sound::{AudioSender, Sound}; |  | ||||||
| use crate::timer::Timer; | use crate::timer::Timer; | ||||||
| use crate::work_ram::{VariableWorkRam, WorkRam}; | 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 { | pub struct Bus { | ||||||
|     boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long |     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||||
|     cartridge: Option<Cartridge>, |     cart: Option<Cartridge>, | ||||||
|     pub ppu: Ppu, |     pub(crate) ppu: Ppu, | ||||||
|     work_ram: WorkRam, |     work_ram: WorkRam, | ||||||
|     var_ram: VariableWorkRam, |     var_ram: VariableWorkRam, | ||||||
|     pub(crate) timer: Timer, |     pub(crate) timer: Timer, | ||||||
|     int: Interrupt, |     int: Interrupt, | ||||||
|     snd: Sound, |     pub(crate) apu: Apu, | ||||||
|     high_ram: HighRam, |     high_ram: HighRam, | ||||||
|     serial: Serial, |     serial: Serial, | ||||||
|     pub(crate) joypad: Joypad, |     pub(crate) joypad: Joypad, | ||||||
| @@ -30,13 +29,13 @@ impl Default for Bus { | |||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             boot: None, |             boot: None, | ||||||
|             cartridge: None, |             cart: None, | ||||||
|             ppu: Default::default(), |             ppu: Default::default(), | ||||||
|             work_ram: Default::default(), |             work_ram: Default::default(), | ||||||
|             var_ram: Default::default(), |             var_ram: Default::default(), | ||||||
|             timer: Default::default(), |             timer: Default::default(), | ||||||
|             int: Default::default(), |             int: Default::default(), | ||||||
|             snd: Default::default(), |             apu: Default::default(), | ||||||
|             high_ram: Default::default(), |             high_ram: Default::default(), | ||||||
|             serial: Default::default(), |             serial: Default::default(), | ||||||
|             joypad: Default::default(), |             joypad: Default::default(), | ||||||
| @@ -45,44 +44,41 @@ impl Default for Bus { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Bus { | impl Bus { | ||||||
|     pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> { |     pub(crate) fn with_boot(rom: [u8; 256]) -> Self { | ||||||
|         let mut file = File::open(path)?; |         Self { | ||||||
|         let mut boot_rom = [0u8; 256]; |             boot: Some(rom), | ||||||
|  |  | ||||||
|         file.read_exact(&mut boot_rom)?; |  | ||||||
|  |  | ||||||
|         Ok(Self { |  | ||||||
|             boot: Some(boot_rom), |  | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { |     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||||
|         self.cartridge = Some(Cartridge::new(path)?); |         self.cart = Some(Cartridge::new(rom)); | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { |     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||||
|         self.cartridge.as_ref()?.title() |         self.cart.as_ref()?.title() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn pass_audio_src(&mut self, sender: AudioSender) { |     #[allow(dead_code)] | ||||||
|         self.snd.set_audio_src(sender) |     pub(crate) fn boot_mapped(&self) -> bool { | ||||||
|     } |         self.boot.is_some() | ||||||
|  |  | ||||||
|     pub(crate) fn is_mpsc_still_full(&mut self) -> bool { |  | ||||||
|         self.snd.is_mpsc_still_full() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn clock(&mut self) { |     pub(crate) fn clock(&mut self) { | ||||||
|         self.ppu.clock(); |         self.tick(4); | ||||||
|         self.timer.clock(); |  | ||||||
|         self.snd.clock(self.timer.divider); |  | ||||||
|         self.clock_dma(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn clock_dma(&mut self) { |     fn tick(&mut self, limit: u8) { | ||||||
|         if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() { |         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); |             let byte = self.oam_read_byte(src_addr); | ||||||
|             self.oam_write_byte(dest_addr, byte); |             self.oam_write_byte(dest_addr, byte); | ||||||
|         } |         } | ||||||
| @@ -101,13 +97,13 @@ impl Bus { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 match self.cartridge.as_ref() { |                 match self.cart.as_ref() { | ||||||
|                     Some(cart) => cart.read_byte(addr), |                     Some(cart) => cart.read_byte(addr), | ||||||
|                     None => panic!("Tried to read from a non-existent cartridge"), |                     None => panic!("Tried to read from a non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM |             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 |                 // 8KB External RAM | ||||||
|                 Some(cart) => cart.read_byte(addr), |                 Some(cart) => cart.read_byte(addr), | ||||||
|                 None => panic!("Tried to read from a non-existent cartridge"), |                 None => panic!("Tried to read from a non-existent cartridge"), | ||||||
| @@ -154,7 +150,7 @@ impl BusIo for Bus { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 match self.cartridge.as_ref() { |                 match self.cart.as_ref() { | ||||||
|                     Some(cart) => cart.read_byte(addr), |                     Some(cart) => cart.read_byte(addr), | ||||||
|                     None => panic!("Tried to read from a non-existent cartridge"), |                     None => panic!("Tried to read from a non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
| @@ -166,7 +162,7 @@ impl BusIo for Bus { | |||||||
|                     _ => self.ppu.read_byte(addr), |                     _ => self.ppu.read_byte(addr), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { |             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 Some(cart) => cart.read_byte(addr), |                 Some(cart) => cart.read_byte(addr), | ||||||
|                 None => panic!("Tried to read from a non-existent cartridge"), |                 None => panic!("Tried to read from a non-existent cartridge"), | ||||||
| @@ -220,29 +216,11 @@ impl BusIo for Bus { | |||||||
|                     0x01 => self.serial.next, |                     0x01 => self.serial.next, | ||||||
|                     0x02 => self.serial.ctrl.into(), |                     0x02 => self.serial.ctrl.into(), | ||||||
|                     0x04 => (self.timer.divider >> 8) as u8, |                     0x04 => (self.timer.divider >> 8) as u8, | ||||||
|                     0x05 => self.timer.counter, |                     0x05 => self.timer.tima(), | ||||||
|                     0x06 => self.timer.modulo, |                     0x06 => self.timer.modulo, | ||||||
|                     0x07 => self.timer.ctrl.into(), |                     0x07 => self.timer.ctrl.into(), | ||||||
|                     0x0F => self.interrupt_flag().into(), |                     0x0F => self.interrupt_flag().into(), | ||||||
|                     0x10 => self.snd.ch1.sweep.into(), |                     0x10..=0x3F => self.apu.read_byte(addr), | ||||||
|                     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], |  | ||||||
|                     0x40 => self.ppu.ctrl.into(), |                     0x40 => self.ppu.ctrl.into(), | ||||||
|                     0x41 => self.ppu.stat.into(), |                     0x41 => self.ppu.stat.into(), | ||||||
|                     0x42 => self.ppu.pos.scroll_y, |                     0x42 => self.ppu.pos.scroll_y, | ||||||
| @@ -255,8 +233,10 @@ impl BusIo for Bus { | |||||||
|                     0x49 => self.ppu.monochrome.obj_palette_1.into(), |                     0x49 => self.ppu.monochrome.obj_palette_1.into(), | ||||||
|                     0x4A => self.ppu.pos.window_y, |                     0x4A => self.ppu.pos.window_y, | ||||||
|                     0x4B => self.ppu.pos.window_x, |                     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 => { |             0xFF80..=0xFFFE => { | ||||||
| @@ -275,7 +255,7 @@ impl BusIo for Bus { | |||||||
|             0x0000..=0x7FFF => { |             0x0000..=0x7FFF => { | ||||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) |                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) |                 // 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), |                     Some(cart) => cart.write_byte(addr, byte), | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |                     None => panic!("Tried to write into non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
| @@ -289,7 +269,7 @@ impl BusIo for Bus { | |||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => { |             0xA000..=0xBFFF => { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 match self.cartridge.as_mut() { |                 match self.cart.as_mut() { | ||||||
|                     Some(cart) => cart.write_byte(addr, byte), |                     Some(cart) => cart.write_byte(addr, byte), | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |                     None => panic!("Tried to write into non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
| @@ -324,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 => { |             0xFF00..=0xFF7F => { | ||||||
|                 // IO Registers |                 // IO Registers | ||||||
|  |  | ||||||
| @@ -335,32 +315,11 @@ impl BusIo for Bus { | |||||||
|                     0x01 => self.serial.next = byte, |                     0x01 => self.serial.next = byte, | ||||||
|                     0x02 => self.serial.ctrl = byte.into(), |                     0x02 => self.serial.ctrl = byte.into(), | ||||||
|                     0x04 => self.timer.divider = 0x0000, |                     0x04 => self.timer.divider = 0x0000, | ||||||
|                     0x05 => self.timer.counter = byte, |                     0x05 => self.timer.set_tima(byte), | ||||||
|                     0x06 => self.timer.modulo = byte, |                     0x06 => self.timer.modulo = byte, | ||||||
|                     0x07 => self.timer.ctrl = byte.into(), |                     0x07 => self.timer.ctrl = byte.into(), | ||||||
|                     0x0F => self.set_interrupt_flag(byte), |                     0x0F => self.set_interrupt_flag(byte), | ||||||
|                     0x10 => self.snd.ch1.sweep = byte.into(), |                     0x10..=0x3F => self.apu.write_byte(addr, byte), | ||||||
|                     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, |  | ||||||
|                     0x40 => self.ppu.ctrl = byte.into(), |                     0x40 => self.ppu.ctrl = byte.into(), | ||||||
|                     0x41 => self.ppu.stat.update(byte), |                     0x41 => self.ppu.stat.update(byte), | ||||||
|                     0x42 => self.ppu.pos.scroll_y = byte, |                     0x42 => self.ppu.pos.scroll_y = byte, | ||||||
| @@ -392,8 +351,7 @@ impl BusIo for Bus { | |||||||
|                             self.boot = None; |                             self.boot = None; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     0x7F => {} // Tetris tries to write to this non-existent IO Address |                     _ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr), | ||||||
|                     _ => unimplemented!("Unable to write to {:#06X} in I/O Registers", addr), |  | ||||||
|                 }; |                 }; | ||||||
|             } |             } | ||||||
|             0xFF80..=0xFFFE => { |             0xFF80..=0xFFFE => { | ||||||
| @@ -408,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 { | impl Bus { | ||||||
|     fn interrupt_flag(&self) -> InterruptFlag { |     fn interrupt_flag(&self) -> InterruptFlag { | ||||||
|         // Read the current interrupt information from the PPU |         // Read the current interrupt information from the PPU | ||||||
|   | |||||||
							
								
								
									
										565
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										565
									
								
								src/cartridge.rs
									
									
									
									
									
								
							| @@ -1,137 +1,99 @@ | |||||||
| use std::fs::File; |  | ||||||
| use std::io::{self, Read}; |  | ||||||
| use std::path::Path; |  | ||||||
|  |  | ||||||
| use crate::bus::BusIo; | use crate::bus::BusIo; | ||||||
|  |  | ||||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||||
| const ROM_SIZE_ADDRESS: usize = 0x0148; | const ROM_SIZE_ADDRESS: usize = 0x0148; | ||||||
| const MBC_TYPE_ADDRESS: usize = 0x0147; | 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 { | pub(crate) struct Cartridge { | ||||||
|     memory: Vec<u8>, |     memory: Vec<u8>, | ||||||
|     title: Option<String>, |     title: Option<String>, | ||||||
|     mbc: Box<dyn MemoryBankController>, |     mbc: Box<dyn MBCIo>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cartridge { | impl Cartridge { | ||||||
|     pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> { |     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||||
|         let mut memory = vec![]; |  | ||||||
|         let mut rom = File::open(path)?; |  | ||||||
|         rom.read_to_end(&mut memory)?; |  | ||||||
|  |  | ||||||
|         let title = Self::find_title(&memory); |         let title = Self::find_title(&memory); | ||||||
|         eprintln!("Cartridge Title: {:?}", title); |         eprintln!("Cartridge Title: {:?}", title); | ||||||
|  |  | ||||||
|         Ok(Self { |         Self { | ||||||
|             mbc: Self::detect_mbc(&memory), |             mbc: Self::detect_mbc(&memory), | ||||||
|             title, |             title, | ||||||
|             memory, |             memory, | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MemoryBankController> { |     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||||
|         let ram_size = Self::find_ram_size(memory); |         let ram_size = Self::detect_ram_info(memory); | ||||||
|         let bank_count = Self::find_bank_count(memory); |         let rom_size = Self::detect_rom_info(memory); | ||||||
|         let mbc_kind = Self::find_mbc(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 Ram Size: {} bytes", ram_cap); | ||||||
|         eprintln!("Cartridge ROM Size: {} bytes", bank_count.size()); |         eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity()); | ||||||
|         eprintln!("MBC Type: {:?}", mbc_kind); |         eprintln!("MBC Type: {:?}", mbc_kind); | ||||||
|  |  | ||||||
|         match mbc_kind { |         match mbc_kind { | ||||||
|             MbcKind::None => Box::new(NoMbc {}), |             MBCKind::None => Box::new(NoMBC), | ||||||
|             MbcKind::Mbc1 => { |             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), | ||||||
|                 let mbc = Mbc1 { |             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving | ||||||
|                     ram_size, |             MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), | ||||||
|                     ram: vec![0; ram_byte_count as usize], |             MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving | ||||||
|                     bank_count, |             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), | ||||||
|                     ..Default::default() |             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 | ||||||
|                 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"), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn find_title(memory: &[u8]) -> Option<String> { |     fn find_title(memory: &[u8]) -> Option<String> { | ||||||
|         // FIXME: Get rid of magic values and handle cases |         let slice = &memory[ROM_TITLE_RANGE]; | ||||||
|         // where 0x134..0x143 reads past the length of the |         let with_nulls = std::str::from_utf8(slice).ok(); | ||||||
|         // string |         let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim()); | ||||||
|  |  | ||||||
|         let slice = &memory[0x134..0x143]; |         match trimmed { | ||||||
|  |             Some("") | None => None, | ||||||
|         let str_with_nulls = std::str::from_utf8(slice).ok(); |             Some(_) => trimmed.map(String::from), | ||||||
|         str_with_nulls.map(|s| s.trim_matches('\0').to_string()) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn title(&self) -> Option<&str> { |     pub(crate) fn title(&self) -> Option<&str> { | ||||||
|         self.title.as_deref() |         self.title.as_deref() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn find_ram_size(memory: &[u8]) -> RamSize { |     fn detect_ram_info(memory: &[u8]) -> RamSize { | ||||||
|         let id = memory[RAM_SIZE_ADDRESS]; |         let id = memory[RAM_SIZE_ADDRESS]; | ||||||
|         id.into() |         id.into() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn find_bank_count(memory: &[u8]) -> BankCount { |     fn detect_rom_info(memory: &[u8]) -> RomSize { | ||||||
|         let id = memory[ROM_SIZE_ADDRESS]; |         let id = memory[ROM_SIZE_ADDRESS]; | ||||||
|         id.into() |         id.into() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn find_mbc(memory: &[u8]) -> MbcKind { |     fn find_mbc(memory: &[u8]) -> MBCKind { | ||||||
|         let id = memory[MBC_TYPE_ADDRESS]; |         use MBCKind::*; | ||||||
|  |  | ||||||
|         // TODO: Refactor this to match the other enums in this module |         match memory[MBC_TYPE_ADDRESS] { | ||||||
|         match id { |             0x00 => None, | ||||||
|             0x00 => MbcKind::None, |             0x01 | 0x02 => MBC1, | ||||||
|             0x01 => MbcKind::Mbc1, |             0x03 => MBC1WithBattery, | ||||||
|             0x02 => MbcKind::Mbc1, |             0x05 => MBC2, | ||||||
|             0x03 => MbcKind::Mbc1WithBattery, |             0x06 => MBC2WithBattery, | ||||||
|             0x19 => MbcKind::Mbc5, |             0x19 | 0x1A => MBC5, | ||||||
|             0x13 => MbcKind::Mbc3WithBattery, |             0x1B => MBC5WithBattery, | ||||||
|             0x11 => MbcKind::Mbc3, |             0x13 => MBC3WithBattery, | ||||||
|             _ => unimplemented!("id {:#04X} is an unsupported MBC", id), |             0x11 | 0x12 => MBC3, | ||||||
|  |             id => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl BusIo for Cartridge { | impl BusIo for Cartridge { | ||||||
|     fn read_byte(&self, addr: u16) -> u8 { |     fn read_byte(&self, addr: u16) -> u8 { | ||||||
|         use MbcResult::*; |         use MBCResult::*; | ||||||
|  |  | ||||||
|         match self.mbc.handle_read(addr) { |         match self.mbc.handle_read(addr) { | ||||||
|             Address(addr) => self.memory[addr], |             Address(addr) => self.memory[addr], | ||||||
| @@ -144,92 +106,90 @@ impl BusIo for Cartridge { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| struct Mbc1 { | struct MBC1 { | ||||||
|     /// 5-bit number |     /// 5-bit number | ||||||
|     rom_bank: u8, |     rom_bank: u8, | ||||||
|     /// 2-bit number |     /// 2-bit number | ||||||
|     ram_bank: u8, |     ram_bank: u8, | ||||||
|     mode: bool, |     mode: bool, | ||||||
|     ram_size: RamSize, |     ram_size: RamSize, | ||||||
|     ram: Vec<u8>, |     memory: Vec<u8>, | ||||||
|     bank_count: BankCount, |     rom_size: RomSize, | ||||||
|     ram_enabled: bool, |     mem_enabled: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Mbc1 { | impl MBC1 { | ||||||
|     fn default() -> Self { |     fn new(ram_size: RamSize, rom_size: RomSize) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             rom_bank: 0x01, |             rom_bank: 0x01, | ||||||
|  |             memory: vec![0; ram_size.capacity() as usize], | ||||||
|  |             ram_size, | ||||||
|  |             rom_size, | ||||||
|             ram_bank: Default::default(), |             ram_bank: Default::default(), | ||||||
|             mode: Default::default(), |             mode: Default::default(), | ||||||
|             ram_size: Default::default(), |             mem_enabled: Default::default(), | ||||||
|             ram: Default::default(), |  | ||||||
|             bank_count: Default::default(), |  | ||||||
|             ram_enabled: Default::default(), |  | ||||||
|         } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| impl Mbc1 { |  | ||||||
|     fn zero_bank(&self) -> u8 { |     fn zero_bank(&self) -> u8 { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         match self.bank_count { |         match self.rom_size { | ||||||
|             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, |             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, | ||||||
|             SixtyFour => (self.ram_bank & 0x01) << 5, |             SixtyFour => (self.ram_bank & 0x01) << 5, | ||||||
|             OneHundredTwentyEight => (self.ram_bank & 0x03) << 5, |             OneTwentyEight => (self.ram_bank & 0x03) << 5, | ||||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), |             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn _mbcm_zero_bank(&self) -> u8 { |     fn _mbcm_zero_bank(&self) -> u8 { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         match self.bank_count { |         match self.rom_size { | ||||||
|             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, |             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, | ||||||
|             SixtyFour => (self.ram_bank & 0x03) << 4, |             SixtyFour => (self.ram_bank & 0x03) << 4, | ||||||
|             OneHundredTwentyEight => (self.ram_bank & 0x03) << 5, |             OneTwentyEight => (self.ram_bank & 0x03) << 5, | ||||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), |             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn high_bank(&self) -> u8 { |     fn high_bank(&self) -> u8 { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         let base = self.rom_bank & self.rom_size_mask(); |         let base = self.rom_bank & self.rom_size_mask(); | ||||||
|  |  | ||||||
|         match self.bank_count { |         match self.rom_size { | ||||||
|             None | Four | Eight | Sixteen | ThirtyTwo => base, |             None | Four | Eight | Sixteen | ThirtyTwo => base, | ||||||
|             SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5), |             SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5), | ||||||
|             OneHundredTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5), |             OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5), | ||||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), |             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn rom_size_mask(&self) -> u8 { |     fn rom_size_mask(&self) -> u8 { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         match self.bank_count { |         match self.rom_size { | ||||||
|             None => 0b00000001, |             None => 0b00000001, | ||||||
|             Four => 0b00000011, |             Four => 0b00000011, | ||||||
|             Eight => 0b00000111, |             Eight => 0b00000111, | ||||||
|             Sixteen => 0b00001111, |             Sixteen => 0b00001111, | ||||||
|             ThirtyTwo | SixtyFour | OneHundredTwentyEight => 0b00011111, |             ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111, | ||||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), |             _ => 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::*; |         use RamSize::*; | ||||||
|  |  | ||||||
|         match self.ram_size { |         match self.ram_size { | ||||||
|             _2KB | _8KB => (addr - 0xA000) % self.ram_size.len() as u16, |             Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(), | ||||||
|             _32KB => { |             Four => { | ||||||
|                 if self.mode { |                 if self.mode { | ||||||
|                     0x2000 * self.ram_bank as u16 + (addr - 0xA000) |                     0x2000 * self.ram_bank as usize + (addr as usize - 0xA000) | ||||||
|                 } else { |                 } else { | ||||||
|                     addr - 0xA000 |                     addr as usize - 0xA000 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), |             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), | ||||||
| @@ -237,49 +197,39 @@ impl Mbc1 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl MemoryBankController for Mbc1 { | impl MBCIo for MBC1 { | ||||||
|     fn handle_read(&self, addr: u16) -> MbcResult { |     fn handle_read(&self, addr: u16) -> MBCResult { | ||||||
|         use MbcResult::*; |         use MBCResult::*; | ||||||
|  |  | ||||||
|         match addr { |         match addr { | ||||||
|             0x0000..=0x3FFF => { |             0x0000..=0x3FFF if self.mode => { | ||||||
|                 if self.mode { |  | ||||||
|                 Address(0x4000 * self.zero_bank() as usize + addr as usize) |                 Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||||
|                 } else { |  | ||||||
|                     Address(addr as usize) |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |             0x0000..=0x3FFF => Address(addr as usize), | ||||||
|             0x4000..=0x7FFF => { |             0x4000..=0x7FFF => { | ||||||
|                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) |                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => { |             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||||
|                 if self.ram_enabled { |                 Value(self.memory[self.ram_addr(addr)]) | ||||||
|                     Value(self.ram[self.ram_addr(addr) as usize]) |  | ||||||
|                 } else { |  | ||||||
|                     Value(0xFF) |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |             0xA000..=0xBFFF => Value(0xFF), | ||||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), |             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { |     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||||
|         match addr { |         match addr { | ||||||
|             0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A, |             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, | ||||||
|             0x2000..=0x3FFF => { |             0x2000..=0x3FFF => { | ||||||
|                 self.rom_bank = if byte == 0x00 { |                 let value = byte & 0x1F; | ||||||
|                     0x01 |                 let masked_value = byte & self.rom_size_mask(); | ||||||
|                 } else { |                 self.rom_bank = if value == 0 { 0x01 } else { masked_value }; | ||||||
|                     byte & self.rom_size_mask() |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 self.rom_bank &= 0x1F; |  | ||||||
|             } |             } | ||||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, |             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, | ||||||
|             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, |             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, | ||||||
|             0xA000..=0xBFFF if self.ram_enabled => { |             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||||
|                 let ram_addr = self.ram_addr(addr) as usize; |                 let ram_addr = self.ram_addr(addr); | ||||||
|                 self.ram[ram_addr] = byte; |                 self.memory[ram_addr] = byte; | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write |             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write | ||||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), |             _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), | ||||||
| @@ -293,32 +243,47 @@ enum MBC3Device { | |||||||
|     RealTimeClock, |     RealTimeClock, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Default)] | #[derive(Debug)] | ||||||
| struct MBC3 { | struct MBC3 { | ||||||
|     /// 7-bit Number |     /// 7-bit Number | ||||||
|     rom_bank: u8, |     rom_bank: u8, | ||||||
|     /// 2-bit Number |     /// 2-bit Number | ||||||
|     ram_bank: u8, |     ram_bank: u8, | ||||||
|  |  | ||||||
|     devices_enabled: bool, |     devs_enabled: bool, | ||||||
|     currently_mapped: Option<MBC3Device>, |     mapped: Option<MBC3Device>, | ||||||
|     ram_size: RamSize, |     memory: Vec<u8>, | ||||||
|     ram: Vec<u8>, |  | ||||||
|  |     // RTC Data Latch Previous Write | ||||||
|  |     prev_latch_write: Option<u8>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl MemoryBankController for MBC3 { | impl MBC3 { | ||||||
|     fn handle_read(&self, addr: u16) -> MbcResult { |     fn new(ram_cap: usize) -> Self { | ||||||
|         use MbcResult::*; |         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 { |         let res = match addr { | ||||||
|             0x0000..=0x3FFF => Address(addr as usize), |             0x0000..=0x3FFF => Address(addr as usize), | ||||||
|             0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), |             0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), | ||||||
|             0xA000..=0xBFFF => match self.currently_mapped { |             0xA000..=0xBFFF => match self.mapped { | ||||||
|                 Some(MBC3Device::ExternalRam) if self.devices_enabled => { |                 Some(MBC3Device::ExternalRam) if self.devs_enabled => { | ||||||
|                     Value(self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) |                     Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||||
|                 } |                 } | ||||||
|                 Some(MBC3Device::RealTimeClock) if self.devices_enabled => { |                 Some(MBC3Device::RealTimeClock) if self.devs_enabled => { | ||||||
|                     unimplemented!("Reading from MBC3 RTC is currently unsupported") |                     todo!("Return Latched value of register") | ||||||
|                 } |                 } | ||||||
|                 _ => Value(0xFF), |                 _ => Value(0xFF), | ||||||
|             }, |             }, | ||||||
| @@ -330,29 +295,37 @@ impl MemoryBankController for MBC3 { | |||||||
|  |  | ||||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { |     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||||
|         match addr { |         match addr { | ||||||
|             0x000..=0x1FFF => self.devices_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one |             0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one | ||||||
|             0x2000..=0x3FFF => match byte { |             0x2000..=0x3FFF => { | ||||||
|                 0x00 => self.rom_bank = 0x01, |                 self.rom_bank = match byte { | ||||||
|                 byte => self.rom_bank = byte & 0x7F, |                     0x00 => 0x01, | ||||||
|             }, |                     byte => byte & 0x7F, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             0x4000..=0x5FFF => match byte { |             0x4000..=0x5FFF => match byte { | ||||||
|                 0x00 | 0x01 | 0x02 | 0x03 => { |                 0x00 | 0x01 | 0x02 | 0x03 => { | ||||||
|                     self.ram_bank = byte & 0x03; |                     self.ram_bank = byte & 0x03; | ||||||
|                     self.currently_mapped = Some(MBC3Device::ExternalRam); |                     self.mapped = Some(MBC3Device::ExternalRam); | ||||||
|                 } |                 } | ||||||
|                 0x08 | 0x09 | 0x0A | 0x0B | 0x0C => { |                 0x08 | 0x09 | 0x0A | 0x0B | 0x0C => { | ||||||
|                     self.currently_mapped = Some(MBC3Device::RealTimeClock); |                     self.mapped = Some(MBC3Device::RealTimeClock); | ||||||
|                     unimplemented!("RTC in MBC3 is currently unimplemented") |  | ||||||
|                 } |                 } | ||||||
|                 _ => {} |                 _ => {} | ||||||
|             }, |             }, | ||||||
|             0x6000..=0x7FFF => unimplemented!("RTC Data Latch is currently unimplemented"), |             0x6000..=0x7FFF => { | ||||||
|             0xA000..=0xBFFF => match self.currently_mapped { |                 if let Some(0x00) = self.prev_latch_write { | ||||||
|                 Some(MBC3Device::ExternalRam) if self.devices_enabled => { |                     if byte == 0x01 { | ||||||
|                     self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte |                         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)] | #[derive(Debug)] | ||||||
| struct NoMbc {} | struct MBC5 { | ||||||
|  |     /// 9-bit number | ||||||
|  |     rom_bank: u16, | ||||||
|  |     /// 4-bit number | ||||||
|  |     ram_bank: u8, | ||||||
|  |  | ||||||
| impl MemoryBankController for NoMbc { |     rom_cap: usize, | ||||||
|     fn handle_read(&self, addr: u16) -> MbcResult { |  | ||||||
|         MbcResult::Address(addr as 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) { |     fn handle_write(&mut self, _addr: u16, _byte: u8) { | ||||||
| @@ -374,67 +467,57 @@ impl MemoryBankController for NoMbc { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| trait MemoryBankController: CloneMbc { | trait MBCIo { | ||||||
|     fn handle_read(&self, addr: u16) -> MbcResult; |     fn handle_read(&self, addr: u16) -> MBCResult; | ||||||
|     fn handle_write(&mut self, addr: u16, byte: u8); |     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)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum MbcResult { | enum MBCResult { | ||||||
|     Address(usize), |     Address(usize), | ||||||
|     Value(u8), |     Value(u8), | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum MbcKind { | enum MBCKind { | ||||||
|     None, |     None, | ||||||
|     Mbc1, |     MBC1, | ||||||
|     Mbc1WithBattery, |     MBC1WithBattery, | ||||||
|     Mbc5, |     MBC2, | ||||||
|     Mbc3WithBattery, |     MBC2WithBattery, | ||||||
|     Mbc3, |     MBC3, | ||||||
|  |     MBC3WithBattery, | ||||||
|  |     MBC5, | ||||||
|  |     MBC5WithBattery, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for MbcKind { | impl Default for MBCKind { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self::None |         Self::None | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy, PartialEq)] | ||||||
| enum RamSize { | enum RamSize { | ||||||
|     None = 0x00, |     None = 0x00, | ||||||
|     _2KB = 0x01, |     Unused = 0x01, | ||||||
|     _8KB = 0x02, |     One = 0x02, | ||||||
|     _32KB = 0x03,  // Split into 4 RAM banks |     Four = 0x03, | ||||||
|     _128KB = 0x04, // Split into 16 RAM banks |     Sixteen = 0x04, | ||||||
|     _64KB = 0x05,  // Split into 8 RAm Banks |     Eight = 0x05, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl RamSize { | impl RamSize { | ||||||
|     fn len(&self) -> u32 { |     fn capacity(&self) -> usize { | ||||||
|         use RamSize::*; |         use RamSize::*; | ||||||
|  |  | ||||||
|         match *self { |         match *self { | ||||||
|             None => 0, |             None => 0, | ||||||
|             _2KB => 2_048, |             Unused => 0x800, | ||||||
|             _8KB => 8_192, |             One => 0x2000, | ||||||
|             _32KB => 32_768, |             Four => 0x8000, | ||||||
|             _128KB => 131_072, |             Sixteen => 0x20000, | ||||||
|             _64KB => 65_536, |             Eight => 0x10000, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -451,63 +534,49 @@ impl From<u8> for RamSize { | |||||||
|  |  | ||||||
|         match byte { |         match byte { | ||||||
|             0x00 => None, |             0x00 => None, | ||||||
|             0x01 => _2KB, |             0x01 => Unused, | ||||||
|             0x02 => _8KB, |             0x02 => One, | ||||||
|             0x03 => _32KB, |             0x03 => Four, | ||||||
|             0x04 => _128KB, |             0x04 => Sixteen, | ||||||
|             0x05 => _64KB, |             0x05 => Eight, | ||||||
|             _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte), |             _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum BankCount { | enum RomSize { | ||||||
|     None = 0x00,              // 32KB (also called Two) |     None = 0x00,              // 32KB (also called Two) | ||||||
|     Four = 0x01,              // 64KB |     Four = 0x01,              // 64KB | ||||||
|     Eight = 0x02,             // 128KB |     Eight = 0x02,             // 128KB | ||||||
|     Sixteen = 0x03,           // 256KB |     Sixteen = 0x03,           // 256KB | ||||||
|     ThirtyTwo = 0x04,         // 512KB |     ThirtyTwo = 0x04,         // 512KB | ||||||
|     SixtyFour = 0x05,         // 1MB |     SixtyFour = 0x05,         // 1MB | ||||||
|     OneHundredTwentyEight = 0x06, // 2MB |     OneTwentyEight = 0x06,    // 2MB | ||||||
|     TwoHundredFiftySix = 0x07,    // 4MB |     TwoFiftySix = 0x07,       // 4MB | ||||||
|     FiveHundredTwelve = 0x08, // 8MB |     FiveHundredTwelve = 0x08, // 8MB | ||||||
|     SeventyTwo = 0x52,        // 1.1MB |     SeventyTwo = 0x52,        // 1.1MB | ||||||
|     Eighty = 0x53,            // 1.2MB |     Eighty = 0x53,            // 1.2MB | ||||||
|     NinetySix = 0x54,         // 1.5MB |     NinetySix = 0x54,         // 1.5MB | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for BankCount { | impl RomSize { | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::None |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl BankCount { |  | ||||||
|     // https://hacktix.github.io/GBEDG/mbcs/#rom-size |     // https://hacktix.github.io/GBEDG/mbcs/#rom-size | ||||||
|     fn size(self) -> u32 { |     fn capacity(&self) -> usize { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         match self { |         match self { | ||||||
|             None => 32_768, |             SeventyTwo => 0x120000, | ||||||
|             Four => 65_536, |             Eighty => 0x140000, | ||||||
|             Eight => 131_072, |             NinetySix => 0x180000, | ||||||
|             Sixteen => 262_144, |             _ => 0x8000 << *self as u8, | ||||||
|             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, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<u8> for BankCount { | impl From<u8> for RomSize { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         use BankCount::*; |         use RomSize::*; | ||||||
|  |  | ||||||
|         match byte { |         match byte { | ||||||
|             0x00 => None, |             0x00 => None, | ||||||
| @@ -516,8 +585,8 @@ impl From<u8> for BankCount { | |||||||
|             0x03 => Sixteen, |             0x03 => Sixteen, | ||||||
|             0x04 => ThirtyTwo, |             0x04 => ThirtyTwo, | ||||||
|             0x05 => SixtyFour, |             0x05 => SixtyFour, | ||||||
|             0x06 => OneHundredTwentyEight, |             0x06 => OneTwentyEight, | ||||||
|             0x07 => TwoHundredFiftySix, |             0x07 => TwoFiftySix, | ||||||
|             0x08 => FiveHundredTwelve, |             0x08 => FiveHundredTwelve, | ||||||
|             0x52 => SeventyTwo, |             0x52 => SeventyTwo, | ||||||
|             0x53 => Eighty, |             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 { |     fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|         todo!("Implement Debug for Box<dyn MBC> Trait Object"); |         todo!("Implement Debug for Box<dyn MBC> Trait Object"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl std::clone::Clone for Box<dyn MemoryBankController> { | impl Default for Box<dyn MBCIo> { | ||||||
|     fn clone(&self) -> Self { |  | ||||||
|         self.clone_mbc() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for Box<dyn MemoryBankController> { |  | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Box::new(Mbc1::default()) |         Box::new(NoMBC) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										315
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										315
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,26 +1,21 @@ | |||||||
| use crate::bus::{Bus, BusIo}; | use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||||
| use crate::instruction::{Cycle, Instruction}; | use crate::instruction::Instruction; | ||||||
| use crate::interrupt::{InterruptEnable, InterruptFlag}; | use crate::interrupt::{InterruptEnable, InterruptFlag}; | ||||||
| use crate::joypad::Joypad; | use crate::Cycle; | ||||||
| use crate::ppu::Ppu; |  | ||||||
| use crate::sound::AudioSender; |  | ||||||
| use crate::timer::Timer; |  | ||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Default)] | #[derive(Debug, Default)] | ||||||
| pub struct Cpu { | pub struct Cpu { | ||||||
|     pub bus: Bus, |     pub bus: Bus, | ||||||
|     reg: Registers, |     reg: Registers, | ||||||
|     flags: Flags, |     flags: Flags, | ||||||
|     ime: ImeState, |     ime: ImeState, | ||||||
|     // TODO: Merge halted and state properties |  | ||||||
|     halted: Option<HaltState>, |  | ||||||
|     state: State, |     state: State, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub fn new() -> Self { |     pub(crate) fn without_boot() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             reg: Registers { |             reg: Registers { | ||||||
|                 a: 0x01, |                 a: 0x01, | ||||||
| @@ -38,15 +33,11 @@ impl Cpu { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { |     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||||
|         Ok(Self { |         Self { | ||||||
|             bus: Bus::with_boot(path)?, |             bus: Bus::with_boot(rom), | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     pub fn set_audio_src(&mut self, sender: AudioSender) { |  | ||||||
|         self.bus.pass_audio_src(sender) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn ime(&self) -> ImeState { |     pub(crate) fn ime(&self) -> ImeState { | ||||||
| @@ -57,89 +48,100 @@ impl Cpu { | |||||||
|         self.ime = state; |         self.ime = state; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn halt(&mut self, state: HaltState) { |     pub(crate) fn halt_cpu(&mut self, kind: HaltKind) { | ||||||
|         self.halted = Some(state); |         self.state = State::Halt(kind); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn resume(&mut self) { |     fn resume_execution(&mut self) { | ||||||
|         self.halted = None; |         self.state = State::Execute; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn halted(&self) -> Option<HaltState> { |     pub(crate) fn is_halted(&self) -> bool { | ||||||
|         self.halted |         match self.state { | ||||||
|  |             State::Halt(_) => true, | ||||||
|  |             _ => false, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn inc_pc(&mut self) { |     pub(crate) fn halt_kind(&self) -> Option<HaltKind> { | ||||||
|         self.reg.pc += 1; |         match self.state { | ||||||
|  |             State::Halt(kind) => Some(kind), | ||||||
|  |             _ => None, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     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() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     fn fetch(&self) -> u8 { |     /// Fetch an [Instruction] from the memory bus | ||||||
|         self.bus.read_byte(self.reg.pc) |     /// (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 { |     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 { |     fn execute(&mut self, instruction: Instruction) -> Cycle { | ||||||
|         Instruction::execute(self, instruction) |         Instruction::execute(self, instruction) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`] | ||||||
|  |     /// routine. | ||||||
|  |     /// | ||||||
|  |     /// Handle HALT and interrupts. | ||||||
|     pub fn step(&mut self) -> Cycle { |     pub fn step(&mut self) -> Cycle { | ||||||
|         // if !self.bus.boot_enabled() { |         // Log instructions | ||||||
|  |         // if self.reg.pc > 0xFF { | ||||||
|         //     let out = std::io::stdout(); |         //     let out = std::io::stdout(); | ||||||
|         //     let handle = out.lock(); |         //     let _ = self._print_logs(out.lock()); | ||||||
|  |  | ||||||
|         //     self.log_state(handle).unwrap(); |  | ||||||
|         // } |         // } | ||||||
|  |  | ||||||
|         let cycles = match self.halted() { |         if let Some(elapsed) = self.handle_interrupt() { | ||||||
|             Some(state) => { |             return elapsed; | ||||||
|                 use HaltState::*; |         } | ||||||
|  |  | ||||||
|                 match state { |         if let Some(kind) = self.halt_kind() { | ||||||
|                     ImeEnabled | NonePending => Cycle::new(4), |             use HaltKind::*; | ||||||
|  |  | ||||||
|  |             self.bus.clock(); | ||||||
|  |  | ||||||
|  |             let elapsed = match kind { | ||||||
|  |                 ImeEnabled | NonePending => 4, | ||||||
|                 SomePending => todo!("Implement HALT bug"), |                 SomePending => todo!("Implement HALT bug"), | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             None => { |  | ||||||
|                 let opcode = self.fetch(); |  | ||||||
|                 self.inc_pc(); |  | ||||||
|  |  | ||||||
|                 let instr = self.decode(opcode); |  | ||||||
|                 let cycles = self.execute(instr); |  | ||||||
|  |  | ||||||
|                 self.check_ime(); |  | ||||||
|  |  | ||||||
|                 cycles |  | ||||||
|             } |  | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|         // FIXME: Regression, this is not a proper fix, |             return elapsed; | ||||||
|         // the FIFO behaves weirdly using this code |  | ||||||
|         let pending: u32 = cycles.into(); |  | ||||||
|         let mut offset = 0; |  | ||||||
|         for _ in 0..(pending + offset) { |  | ||||||
|             if !self.bus.is_mpsc_still_full() { |  | ||||||
|                 self.bus.clock(); |  | ||||||
|             } else { |  | ||||||
|                 offset += 1; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.handle_interrupts(); |         let opcode = self.fetch(); | ||||||
|  |         let instr = self.decode(opcode); | ||||||
|  |         let elapsed = self.execute(instr); | ||||||
|  |         self.handle_ei(); | ||||||
|  |  | ||||||
|         cycles |         // 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 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -154,98 +156,80 @@ impl BusIo for Cpu { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 { |     pub(crate) fn bus(&self) -> &Bus { | ||||||
|         self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call |         &self.bus | ||||||
|         self.bus.read_byte(addr) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 { |     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||||
|         self.inc_pc(); |         &mut self.bus | ||||||
|         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 write_word(&mut self, addr: u16, word: u16) { |     fn handle_ei(&mut self) { | ||||||
|         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) { |  | ||||||
|         match self.ime { |         match self.ime { | ||||||
|             ImeState::Pending => { |             ImeState::EiExecuted => self.ime = ImeState::Pending, | ||||||
|                 // This is within the context of the EI instruction, we need to not update EI until the end of the |             ImeState::Pending => self.ime = ImeState::Enabled, | ||||||
|                 // next executed Instruction |             ImeState::Disabled | ImeState::Enabled => {} | ||||||
|                 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 |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn handle_interrupts(&mut self) { |     pub(crate) fn int_request(&self) -> u8 { | ||||||
|         let req = self.read_byte(0xFF0F); |         self.read_byte(0xFF0F) | ||||||
|         let enabled = self.read_byte(0xFFFF); |     } | ||||||
|  |  | ||||||
|         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 |             // When we're here either a HALT with IME set or | ||||||
|             // a HALT with IME not set and No pending Interrupts was called |             // 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 |                 // The if self.ime() below correctly follows the "resuming from HALT" behaviour so | ||||||
|                 // nothing actually needs to be added here. This is just documentation |                 // nothing actually needs to be added here. This is just documentation | ||||||
|                 // since it's a bit weird why nothing is being done |                 // since it's a bit weird why nothing is being done | ||||||
|  |  | ||||||
|                 self.resume() |                 self.resume_execution(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let ImeState::Enabled = self.ime() { |         match self.ime() { | ||||||
|             let mut req: InterruptFlag = req.into(); |             ImeState::Enabled => { | ||||||
|             let enabled: InterruptEnable = enabled.into(); |                 let mut irq: InterruptFlag = irq.into(); | ||||||
|  |                 let enable: InterruptEnable = enable.into(); | ||||||
|  |  | ||||||
|             let vector = if req.vblank() && enabled.vblank() { |                 let rst_vector = if irq.vblank() && enable.vblank() { | ||||||
|                     // Handle VBlank Interrupt |                     // Handle VBlank Interrupt | ||||||
|                 req.set_vblank(false); |                     irq.set_vblank(false); | ||||||
|  |  | ||||||
|                     // INT 40h |                     // INT 40h | ||||||
|                     Some(0x40) |                     Some(0x40) | ||||||
|             } else if req.lcd_stat() && enabled.lcd_stat() { |                 } else if irq.lcd_stat() && enable.lcd_stat() { | ||||||
|                     // Handle LCD STAT Interrupt |                     // Handle LCD STAT Interrupt | ||||||
|                 req.set_lcd_stat(false); |                     irq.set_lcd_stat(false); | ||||||
|  |  | ||||||
|                     // INT 48h |                     // INT 48h | ||||||
|                     Some(0x48) |                     Some(0x48) | ||||||
|             } else if req.timer() && enabled.timer() { |                 } else if irq.timer() && enable.timer() { | ||||||
|                     // Handle Timer Interrupt |                     // Handle Timer Interrupt | ||||||
|                 req.set_timer(false); |                     irq.set_timer(false); | ||||||
|  |  | ||||||
|                     // INT 50h |                     // INT 50h | ||||||
|                     Some(0x50) |                     Some(0x50) | ||||||
|             } else if req.serial() && enabled.serial() { |                 } else if irq.serial() && enable.serial() { | ||||||
|                     // Handle Serial Interrupt |                     // Handle Serial Interrupt | ||||||
|                 req.set_serial(false); |                     irq.set_serial(false); | ||||||
|  |  | ||||||
|                     // INT 58h |                     // INT 58h | ||||||
|                     Some(0x58) |                     Some(0x58) | ||||||
|             } else if req.joypad() && enabled.joypad() { |                 } else if irq.joypad() && enable.joypad() { | ||||||
|                     // Handle Joypad Interrupt |                     // Handle Joypad Interrupt | ||||||
|                 req.set_joypad(false); |                     irq.set_joypad(false); | ||||||
|  |  | ||||||
|                     // INT 60h |                     // INT 60h | ||||||
|                     Some(0x60) |                     Some(0x60) | ||||||
| @@ -253,25 +237,27 @@ impl Cpu { | |||||||
|                     None |                     None | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|             let _ = match vector { |                 match rst_vector { | ||||||
|                 Some(address) => { |                     Some(vector) => { | ||||||
|                         //  Write the Changes to 0xFF0F and 0xFFFF registers |                         //  Write the Changes to 0xFF0F and 0xFFFF registers | ||||||
|                     self.write_byte(0xFF0F, req.into()); |                         self.write_byte(0xFF0F, irq.into()); | ||||||
|  |  | ||||||
|                         // Disable all future interrupts |                         // Disable all future interrupts | ||||||
|                         self.set_ime(ImeState::Disabled); |                         self.set_ime(ImeState::Disabled); | ||||||
|                     Instruction::reset(self, address) |                         Some(Instruction::reset(self, vector)) | ||||||
|                     } |                     } | ||||||
|                 None => Cycle::new(0), // NO Interrupts were enabled and / or requested |                     None => None, | ||||||
|             }; |                 } | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum State { | enum State { | ||||||
|     Execute, |     Execute, | ||||||
|     // Halt, |     Halt(HaltKind), | ||||||
|     // Stop, |     // Stop, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -293,7 +279,6 @@ impl Cpu { | |||||||
|             E => self.reg.e = value, |             E => self.reg.e = value, | ||||||
|             H => self.reg.h = value, |             H => self.reg.h = value, | ||||||
|             L => self.reg.l = value, |             L => self.reg.l = value, | ||||||
|             Flag => self.flags = value.into(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -308,7 +293,6 @@ impl Cpu { | |||||||
|             E => self.reg.e, |             E => self.reg.e, | ||||||
|             H => self.reg.h, |             H => self.reg.h, | ||||||
|             L => self.reg.l, |             L => self.reg.l, | ||||||
|             Flag => self.flags.into(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -357,13 +341,20 @@ impl Cpu { | |||||||
|         &self.flags |         &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) { |     pub(crate) fn set_flags(&mut self, flags: Flags) { | ||||||
|         self.flags = flags; |         self.flags = flags; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | 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, "A: {:02X} ", self.reg.a)?; | ||||||
|         write!(w, "F: {:02X} ", u8::from(self.flags))?; |         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||||
|         write!(w, "B: {:02X} ", self.reg.b)?; |         write!(w, "B: {:02X} ", self.reg.b)?; | ||||||
| @@ -378,30 +369,39 @@ impl Cpu { | |||||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; |         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 + 2))?; | ||||||
|         write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; |         write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||||
|         writeln!(w, "| {:?}", instr)?; |         writeln!(w, "| {:?}", self._dbg_instr())?; | ||||||
|         w.flush() |         w.flush() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> { |     fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> { | ||||||
|         write!(writer, "A: {:02X} ", self.reg.a)?; |         write!(w, "A: {:02X} ", self.reg.a)?; | ||||||
|         write!(writer, "F: {:02X} ", u8::from(self.flags))?; |         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||||
|         write!(writer, "B: {:02X} ", self.reg.b)?; |         write!(w, "B: {:02X} ", self.reg.b)?; | ||||||
|         write!(writer, "C: {:02X} ", self.reg.c)?; |         write!(w, "C: {:02X} ", self.reg.c)?; | ||||||
|         write!(writer, "D: {:02X} ", self.reg.d)?; |         write!(w, "D: {:02X} ", self.reg.d)?; | ||||||
|         write!(writer, "E: {:02X} ", self.reg.e)?; |         write!(w, "E: {:02X} ", self.reg.e)?; | ||||||
|         write!(writer, "H: {:02X} ", self.reg.h)?; |         write!(w, "H: {:02X} ", self.reg.h)?; | ||||||
|         write!(writer, "L: {:02X} ", self.reg.l)?; |         write!(w, "L: {:02X} ", self.reg.l)?; | ||||||
|         write!(writer, "SP: {:04X} ", self.reg.sp)?; |         write!(w, "SP: {:04X} ", self.reg.sp)?; | ||||||
|         write!(writer, "PC: 00:{:04X} ", self.reg.pc)?; |         write!(w, "PC: 00:{:04X} ", self.reg.pc)?; | ||||||
|         write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?; |         write!(w, "({:02X} ", self.read_byte(self.reg.pc))?; | ||||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||||
|         writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?; |         writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||||
|         writer.flush() |         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 { | pub(crate) enum Register { | ||||||
|     A, |     A, | ||||||
|     B, |     B, | ||||||
| @@ -410,10 +410,9 @@ pub(crate) enum Register { | |||||||
|     E, |     E, | ||||||
|     H, |     H, | ||||||
|     L, |     L, | ||||||
|     Flag, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum RegisterPair { | pub(crate) enum RegisterPair { | ||||||
|     AF, |     AF, | ||||||
|     BC, |     BC, | ||||||
| @@ -423,7 +422,7 @@ pub(crate) enum RegisterPair { | |||||||
|     PC, |     PC, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone, Default)] | #[derive(Debug, Default)] | ||||||
| struct Registers { | struct Registers { | ||||||
|     a: u8, |     a: u8, | ||||||
|     b: u8, |     b: u8, | ||||||
| @@ -508,7 +507,7 @@ impl From<u8> for Flags { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum HaltState { | pub(crate) enum HaltKind { | ||||||
|     ImeEnabled, |     ImeEnabled, | ||||||
|     NonePending, |     NonePending, | ||||||
|     SomePending, |     SomePending, | ||||||
| @@ -517,8 +516,8 @@ pub(crate) enum HaltState { | |||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum ImeState { | pub(crate) enum ImeState { | ||||||
|     Disabled, |     Disabled, | ||||||
|  |     EiExecuted, | ||||||
|     Pending, |     Pending, | ||||||
|     PendingEnd, |  | ||||||
|     Enabled, |     Enabled, | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										137
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,60 +1,121 @@ | |||||||
| use crate::cpu::Cpu as SM83; | use crate::apu::gen::SampleProducer; | ||||||
| use crate::instruction::Cycle; | use crate::cpu::Cpu; | ||||||
| use crate::joypad; | use crate::joypad::{self, Joypad}; | ||||||
| use crate::ppu::Ppu; | use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||||
| use anyhow::Result; |  | ||||||
| use gilrs::Gilrs; | use gilrs::Gilrs; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit_input_helper::WinitInputHelper; | ||||||
|  |  | ||||||
| pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | 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 | pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | ||||||
| const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | ||||||
| const GAMEPAD_ENABLED: bool = false; |  | ||||||
|  |  | ||||||
| pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||||
|     let mut cpu = match boot_path { |     let mut elapsed = 0; | ||||||
|         Some(path) => SM83::boot_new(path)?, |  | ||||||
|         None => SM83::new(), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     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 { |     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) |     while elapsed < CYCLES_IN_FRAME { | ||||||
| } |         elapsed += emu.step(); | ||||||
|  |  | ||||||
| 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); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     elapsed |     elapsed | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { | pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||||
|     if GAMEPAD_ENABLED { |     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||||
|         if let Some(event) = gamepad.next_event() { | } | ||||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); |  | ||||||
|  | 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); |     fn step(&mut self) -> Cycle { | ||||||
|     game_boy.step() |         self.cpu.step() | ||||||
|     } |     } | ||||||
|  |  | ||||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { |     fn load_cart(&mut self, rom: Vec<u8>) { | ||||||
|     ppu.copy_to_gui(frame); |         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 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 } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										2852
									
								
								src/instruction.rs
									
									
									
									
									
								
							
							
						
						
									
										2852
									
								
								src/instruction.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct Interrupt { | pub(crate) struct Interrupt { | ||||||
|     pub(crate) flag: InterruptFlag, |     pub(crate) flag: InterruptFlag, | ||||||
|     pub(crate) enable: InterruptEnable, |     pub(crate) enable: InterruptEnable, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit_input_helper::WinitInputHelper; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| pub struct Joypad { | pub struct Joypad { | ||||||
|     /// 0xFF00 | P1/JOYP - Player 1 Joypad |     /// 0xFF00 | P1/JOYP - Player 1 Joypad | ||||||
|     pub(crate) p1: u8, |     pub(crate) p1: u8, | ||||||
| @@ -48,7 +48,7 @@ impl Joypad { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct JoypadState { | struct JoypadState { | ||||||
|     // Direction Row |     // Direction Row | ||||||
|     dpad_down: ButtonEvent, |     dpad_down: ButtonEvent, | ||||||
| @@ -120,49 +120,57 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { | |||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Down) { |     if input.key_pressed(VirtualKeyCode::Down) { | ||||||
|         state.dpad_down.update(true, irq); |         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); |         state.dpad_down.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Up) { |     if input.key_pressed(VirtualKeyCode::Up) { | ||||||
|         state.dpad_up.update(true, irq); |         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); |         state.dpad_up.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Left) { |     if input.key_pressed(VirtualKeyCode::Left) { | ||||||
|         state.dpad_left.update(true, irq); |         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); |         state.dpad_left.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Right) { |     if input.key_pressed(VirtualKeyCode::Right) { | ||||||
|         state.dpad_right.update(true, irq); |         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); |         state.dpad_right.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::T) { |     if input.key_pressed(VirtualKeyCode::T) { | ||||||
|         state.start.update(true, irq); |         state.start.update(true, irq); | ||||||
|     } else if input.key_released(VirtualKeyCode::T) { |     } | ||||||
|  |     if input.key_released(VirtualKeyCode::T) { | ||||||
|         state.start.update(false, irq); |         state.start.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Y) { |     if input.key_pressed(VirtualKeyCode::Y) { | ||||||
|         state.select.update(true, irq); |         state.select.update(true, irq); | ||||||
|     } else if input.key_released(VirtualKeyCode::Y) { |     } | ||||||
|  |     if input.key_released(VirtualKeyCode::Y) { | ||||||
|         state.select.update(false, irq); |         state.select.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Z) { |     if input.key_pressed(VirtualKeyCode::Z) { | ||||||
|         state.south.update(true, irq); |         state.south.update(true, irq); | ||||||
|     } else if input.key_released(VirtualKeyCode::Z) { |     } | ||||||
|  |     if input.key_released(VirtualKeyCode::Z) { | ||||||
|         state.south.update(false, irq); |         state.south.update(false, irq); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::X) { |     if input.key_pressed(VirtualKeyCode::X) { | ||||||
|         state.east.update(true, irq); |         state.east.update(true, irq); | ||||||
|     } else if input.key_released(VirtualKeyCode::X) { |     } | ||||||
|  |     if input.key_released(VirtualKeyCode::X) { | ||||||
|         state.east.update(false, irq); |         state.east.update(false, irq); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,21 +1,19 @@ | |||||||
| pub use gui::Egui; | pub use apu::gen::AudioSPSC; | ||||||
| pub use instruction::Cycle; | pub type Cycle = u64; | ||||||
| pub use sound::AudioMPSC; |  | ||||||
|  |  | ||||||
| pub const GB_WIDTH: usize = 160; | pub const GB_WIDTH: usize = 160; | ||||||
| pub const GB_HEIGHT: usize = 144; | pub const GB_HEIGHT: usize = 144; | ||||||
|  |  | ||||||
|  | mod apu; | ||||||
| mod bus; | mod bus; | ||||||
| mod cartridge; | mod cartridge; | ||||||
| mod cpu; | mod cpu; | ||||||
| pub mod emu; | pub mod emu; | ||||||
| mod gui; |  | ||||||
| mod high_ram; | mod high_ram; | ||||||
| mod instruction; | mod instruction; | ||||||
| mod interrupt; | mod interrupt; | ||||||
| mod joypad; | mod joypad; | ||||||
| mod ppu; | mod ppu; | ||||||
| mod serial; | mod serial; | ||||||
| mod sound; |  | ||||||
| mod timer; | mod timer; | ||||||
| mod work_ram; | mod work_ram; | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,17 +1,20 @@ | |||||||
|  | use std::convert::TryInto; | ||||||
|  |  | ||||||
| use anyhow::{anyhow, Result}; | use anyhow::{anyhow, Result}; | ||||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | 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 gilrs::Gilrs; | ||||||
| use pixels::{Pixels, SurfaceTexture}; | use pixels::{PixelsBuilder, SurfaceTexture}; | ||||||
| use rodio::{OutputStream, Sink}; | use rodio::{OutputStream, Sink}; | ||||||
| use std::time::Instant; | use winit::dpi::{LogicalSize, PhysicalSize}; | ||||||
| use winit::dpi::LogicalSize; |  | ||||||
| use winit::event::{Event, VirtualKeyCode}; | use winit::event::{Event, VirtualKeyCode}; | ||||||
| use winit::event_loop::{ControlFlow, EventLoop}; | use winit::event_loop::{ControlFlow, EventLoop}; | ||||||
| use winit::window::{Window, WindowBuilder}; | use winit::window::{Window, WindowBuilder}; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit_input_helper::WinitInputHelper; | ||||||
|  |  | ||||||
| const SCALE: f64 = 2.0; | const WINDOW_SCALE: usize = 3; | ||||||
|  | const AUDIO_ENABLED: bool = false; | ||||||
|  |  | ||||||
| fn main() -> Result<()> { | fn main() -> Result<()> { | ||||||
|     let app = App::new(crate_name!()) |     let app = App::new(crate_name!()) | ||||||
| @@ -38,68 +41,58 @@ fn main() -> Result<()> { | |||||||
|         ) |         ) | ||||||
|         .get_matches(); |         .get_matches(); | ||||||
|  |  | ||||||
|     // `rom` is a required value in every situation so this will |     let mut emu_build = | ||||||
|     // always exist. |         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||||
|     let rom_path = m |  | ||||||
|         .value_of("rom") |  | ||||||
|         .expect("Required value 'rom' was provided"); |  | ||||||
|  |  | ||||||
|     let mut game_boy = |     if let Some(path) = m.value_of("boot") { | ||||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator"); |         emu_build = emu_build.with_boot(path)?; | ||||||
|     let cartridge_title = gb::emu::rom_title(&game_boy); |     } | ||||||
|  |  | ||||||
|     // Initialize Gamepad Support |     let mut emu = emu_build.finish(); | ||||||
|     let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input"); |     let rom_title = emu.title(); | ||||||
|  |  | ||||||
|  |     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||||
|  |  | ||||||
|     // Initialize GUI |     // Initialize GUI | ||||||
|     let event_loop = EventLoop::new(); |     let event_loop = EventLoop::new(); | ||||||
|     let mut input = WinitInputHelper::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 size = window.inner_size(); | ||||||
|         let scale_factor = window.scale_factor(); |  | ||||||
|         let surface_texture = SurfaceTexture::new(size.width, size.height, &window); |         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 |     // Initialize Audio | ||||||
|     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); |     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); | ||||||
|     let sink = Sink::try_new(&stream_handle).expect("Initialize Audio Sink"); |  | ||||||
|  |     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 | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         emu.set_prod(prod); | ||||||
|  |  | ||||||
|         std::thread::spawn(move || { |         std::thread::spawn(move || { | ||||||
|         sink.append(recv); |  | ||||||
|  |  | ||||||
|             sink.sleep_until_end(); |             sink.sleep_until_end(); | ||||||
|         }); |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     let mut now = Instant::now(); |  | ||||||
|     let mut cycle_count: Cycle = Default::default(); |     let mut cycle_count: Cycle = Default::default(); | ||||||
|  |  | ||||||
|     event_loop.run(move |event, _, control_flow| { |     event_loop.run(move |event, _, control_flow| { | ||||||
|         // Update egui |  | ||||||
|         egui.handle_event(&event); |  | ||||||
|  |  | ||||||
|         if let Event::RedrawRequested(_) = event { |         if let Event::RedrawRequested(_) = event { | ||||||
|             // Prepare egui |             if pixels | ||||||
|             egui.prepare(&game_boy); |                 .render() | ||||||
|  |  | ||||||
|             // 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 |  | ||||||
|                 .map_err(|e| anyhow!("pixels.render() failed: {}", e)) |                 .map_err(|e| anyhow!("pixels.render() failed: {}", e)) | ||||||
|                 .is_err() |                 .is_err() | ||||||
|             { |             { | ||||||
| @@ -114,26 +107,21 @@ fn main() -> Result<()> { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if let Some(scale_factor) = input.scale_factor() { |  | ||||||
|                 egui.scale_factor(scale_factor); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if let Some(size) = input.window_resized() { |             if let Some(size) = input.window_resized() { | ||||||
|                 pixels.resize_surface(size.width, size.height); |                 pixels.resize_surface(size.width, size.height); | ||||||
|                 egui.resize(size.width, size.height); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             let delta = now.elapsed().subsec_nanos(); |             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||||
|             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); |  | ||||||
|  |  | ||||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { |             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||||
|                 // Draw Frame |                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||||
|                 cycle_count = Cycle::new(0); |  | ||||||
|  |  | ||||||
|                 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(); |                 window.request_redraw(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -141,14 +129,16 @@ fn main() -> Result<()> { | |||||||
| } | } | ||||||
|  |  | ||||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | 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() |     Ok(WindowBuilder::new() | ||||||
|         .with_title(title) |         .with_title(title) | ||||||
|         .with_inner_size(size) |         .with_min_inner_size(logical) | ||||||
|         .with_min_inner_size(size) |         .with_inner_size(physical) | ||||||
|         .with_resizable(true) |         .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)?) |         .build(event_loop)?) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										338
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										338
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| use crate::bus::BusIo; | use crate::bus::BusIo; | ||||||
| use crate::instruction::Cycle; | use crate::Cycle; | ||||||
| use crate::GB_HEIGHT; | use crate::GB_HEIGHT; | ||||||
| use crate::GB_WIDTH; | use crate::GB_WIDTH; | ||||||
| use dma::DirectMemoryAccess; | use dma::DirectMemoryAccess; | ||||||
| @@ -8,7 +8,7 @@ use std::convert::TryInto; | |||||||
| pub(crate) use types::PpuMode; | pub(crate) use types::PpuMode; | ||||||
| use types::{ | use types::{ | ||||||
|     BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette, |     BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette, | ||||||
|     ObjectPaletteId, ObjectSize, Pixels, RenderPriority, TileDataAddress, |     ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| mod dma; | 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 DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes(); | ||||||
| const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes(); | const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes(); | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| pub struct Ppu { | pub struct Ppu { | ||||||
|     pub(crate) int: Interrupt, |     pub(crate) int: Interrupt, | ||||||
|     /// 0xFF40 | LCDC - LCD Control |     /// 0xFF40 | LCDC - LCD Control | ||||||
| @@ -47,10 +47,14 @@ pub struct Ppu { | |||||||
|     pub(crate) dma: DirectMemoryAccess, |     pub(crate) dma: DirectMemoryAccess, | ||||||
|     scan_state: OamScanState, |     scan_state: OamScanState, | ||||||
|     fetch: PixelFetcher, |     fetch: PixelFetcher, | ||||||
|     fifo: FifoRenderer, |     fifo: PixelFifo, | ||||||
|     obj_buffer: ObjectBuffer, |     obj_buffer: ObjectBuffer, | ||||||
|     frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, |     frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, | ||||||
|     window_stat: WindowStatus, |     window_stat: WindowStatus, | ||||||
|  |  | ||||||
|  |     scanline_start: bool, | ||||||
|  |     to_discard: u8, | ||||||
|  |  | ||||||
|     x_pos: u8, |     x_pos: u8, | ||||||
|     cycle: Cycle, |     cycle: Cycle, | ||||||
| } | } | ||||||
| @@ -66,7 +70,7 @@ impl BusIo for Ppu { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Ppu { | impl Ppu { | ||||||
|     pub(crate) fn clock(&mut self) { |     pub(crate) fn tick(&mut self) { | ||||||
|         self.cycle += 1; |         self.cycle += 1; | ||||||
|  |  | ||||||
|         if !self.ctrl.lcd_enabled() { |         if !self.ctrl.lcd_enabled() { | ||||||
| @@ -75,7 +79,7 @@ impl Ppu { | |||||||
|  |  | ||||||
|         match self.stat.mode() { |         match self.stat.mode() { | ||||||
|             PpuMode::OamScan => { |             PpuMode::OamScan => { | ||||||
|                 if self.cycle >= 80.into() { |                 if self.cycle >= 80 { | ||||||
|                     self.stat.set_mode(PpuMode::Drawing); |                     self.stat.set_mode(PpuMode::Drawing); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -84,7 +88,7 @@ impl Ppu { | |||||||
|             PpuMode::Drawing => { |             PpuMode::Drawing => { | ||||||
|                 if self.ctrl.lcd_enabled() { |                 if self.ctrl.lcd_enabled() { | ||||||
|                     // Only Draw when the LCD Is Enabled |                     // Only Draw when the LCD Is Enabled | ||||||
|                     self.draw(self.cycle.into()); |                     self.draw(self.cycle); | ||||||
|                 } else { |                 } else { | ||||||
|                     self.reset(); |                     self.reset(); | ||||||
|                 } |                 } | ||||||
| @@ -105,6 +109,8 @@ impl Ppu { | |||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     self.x_pos = 0; |                     self.x_pos = 0; | ||||||
|  |                     self.scanline_start = true; | ||||||
|  |                     self.to_discard = 0; | ||||||
|  |  | ||||||
|                     self.fetch.hblank_reset(); |                     self.fetch.hblank_reset(); | ||||||
|                     self.window_stat.hblank_reset(); |                     self.window_stat.hblank_reset(); | ||||||
| @@ -119,7 +125,7 @@ impl Ppu { | |||||||
|             PpuMode::HBlank => { |             PpuMode::HBlank => { | ||||||
|                 // This mode will always end at 456 cycles |                 // This mode will always end at 456 cycles | ||||||
|  |  | ||||||
|                 if self.cycle >= 456.into() { |                 if self.cycle >= 456 { | ||||||
|                     self.cycle %= 456; |                     self.cycle %= 456; | ||||||
|                     self.pos.line_y += 1; |                     self.pos.line_y += 1; | ||||||
|  |  | ||||||
| @@ -161,7 +167,7 @@ impl Ppu { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             PpuMode::VBlank => { |             PpuMode::VBlank => { | ||||||
|                 if self.cycle > 456.into() { |                 if self.cycle > 456 { | ||||||
|                     self.cycle %= 456; |                     self.cycle %= 456; | ||||||
|                     self.pos.line_y += 1; |                     self.pos.line_y += 1; | ||||||
|  |  | ||||||
| @@ -222,23 +228,26 @@ impl Ppu { | |||||||
|         self.scan_state.next(); |         self.scan_state.next(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn draw(&mut self, _cycle: u32) { |     fn draw(&mut self, _cycle: Cycle) { | ||||||
|         use FetcherState::*; |         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 { |         let obj_attr = loop { | ||||||
|             match iter.flatten().next() { |             match iter.next() { | ||||||
|                 Some(attr) => { |                 Some(attr_opt) => { | ||||||
|  |                     if let Some(attr) = attr_opt { | ||||||
|                         if attr.x <= (self.x_pos + 8) { |                         if attr.x <= (self.x_pos + 8) { | ||||||
|                             self.fetch.back.reset(); |                             self.fetch.back.reset(); | ||||||
|                             self.fetch.back.pause(); |                             self.fetch.back.pause(); | ||||||
|                             self.fifo.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 { |             match self.fetch.obj.state { | ||||||
|                 TileNumber => { |                 TileNumber => { | ||||||
|                     self.fetch.obj.tile.with_id(attr.tile_index); |                     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), |                 SleepOne => self.fetch.obj.next(TileLow), | ||||||
|                 TileLowByte => { |                 TileLow => { | ||||||
|                     let obj_size = self.ctrl.obj_size(); |                     let obj_size = self.ctrl.obj_size(); | ||||||
|  |  | ||||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, 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); |                     let byte = self.read_byte(addr); | ||||||
|                     self.fetch.obj.tile.with_low_byte(byte); |                     self.fetch.obj.tile.with_low_byte(byte); | ||||||
|  |  | ||||||
|                     self.fetch.obj.next(ToHighByteSleep); |                     self.fetch.obj.next(SleepTwo); | ||||||
|                 } |                 } | ||||||
|                 ToHighByteSleep => self.fetch.obj.next(TileHighByte), |                 SleepTwo => self.fetch.obj.next(TileHigh), | ||||||
|                 TileHighByte => { |                 TileHigh => { | ||||||
|                     let obj_size = self.ctrl.obj_size(); |                     let obj_size = self.ctrl.obj_size(); | ||||||
|  |  | ||||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, 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); |                     let byte = self.read_byte(addr + 1); | ||||||
|                     self.fetch.obj.tile.with_high_byte(byte); |                     self.fetch.obj.tile.with_high_byte(byte); | ||||||
|  |  | ||||||
|                     self.fetch.obj.next(ToFifoSleep); |                     self.fetch.obj.next(SleepThree); | ||||||
|                 } |                 } | ||||||
|                 ToFifoSleep => self.fetch.obj.next(SendToFifoOne), |                 SleepThree => self.fetch.obj.next(ToFifoOne), | ||||||
|                 SendToFifoOne => { |                 ToFifoOne => { | ||||||
|                     // Load into Fifo |                     // Load into Fifo | ||||||
|                     let (high, low) = self |                     let (high, low) = self | ||||||
|                         .fetch |                         .fetch | ||||||
| @@ -282,25 +291,21 @@ impl Ppu { | |||||||
|  |  | ||||||
|                     let tbpp = Pixels::from_bytes(high, low); |                     let tbpp = Pixels::from_bytes(high, low); | ||||||
|  |  | ||||||
|                     let palette = match attr.flags.palette() { |                     let palette_kind = 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 x_flip = attr.flags.x_flip(); |                     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 x = if x_flip { 7 - i } else { i }; | ||||||
|  |  | ||||||
|                         let priority = attr.flags.priority(); |                         let priority = attr.flags.priority(); | ||||||
|                         let shade = palette.shade(tbpp.shade_id(x)); |                         let shade_id = tbpp.shade_id(x); | ||||||
|  |  | ||||||
|                         let fifo_info = ObjectFifoInfo { |                         let fifo_info = ObjPixelProperty { | ||||||
|                             shade, |                             shade_id, | ||||||
|                             palette, |                             palette_kind, | ||||||
|                             priority, |                             priority, | ||||||
|                         }; |                         }; | ||||||
|  |  | ||||||
| @@ -309,11 +314,11 @@ impl Ppu { | |||||||
|  |  | ||||||
|                     self.fetch.back.resume(); |                     self.fetch.back.resume(); | ||||||
|                     self.fifo.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,86 +348,61 @@ impl Ppu { | |||||||
|                     self.fetch.back.tile.with_id(id); |                     self.fetch.back.tile.with_id(id); | ||||||
|  |  | ||||||
|                     // Move on to the Next state in 2 T-cycles |                     // 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), |                 SleepOne => self.fetch.back.next(TileLow), | ||||||
|                 TileLowByte => { |                 TileLow => { | ||||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); |                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||||
|  |  | ||||||
|                     let low = self.read_byte(addr); |                     let low = self.read_byte(addr); | ||||||
|                     self.fetch.back.tile.with_low_byte(low); |                     self.fetch.back.tile.with_low_byte(low); | ||||||
|  |  | ||||||
|                     self.fetch.back.next(ToHighByteSleep); |                     self.fetch.back.next(SleepTwo); | ||||||
|                 } |                 } | ||||||
|                 ToHighByteSleep => self.fetch.back.next(TileHighByte), |                 SleepTwo => self.fetch.back.next(TileHigh), | ||||||
|                 TileHighByte => { |                 TileHigh => { | ||||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); |                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||||
|  |  | ||||||
|                     let high = self.read_byte(addr + 1); |                     let high = self.read_byte(addr + 1); | ||||||
|                     self.fetch.back.tile.with_high_byte(high); |                     self.fetch.back.tile.with_high_byte(high); | ||||||
|  |  | ||||||
|                     self.fetch.back.next(ToFifoSleep); |                     self.fetch.back.next(SleepThree); | ||||||
|                 } |                 } | ||||||
|                 ToFifoSleep => self.fetch.back.next(SendToFifoOne), |                 SleepThree => self.fetch.back.next(ToFifoOne), | ||||||
|                 SendToFifoOne => { |                 ToFifoOne => { | ||||||
|                     self.fetch.back.next(SendToFifoTwo); |                     self.fetch.back.next(ToFifoTwo); | ||||||
|                 } |                 } | ||||||
|                 SendToFifoTwo => { |                 ToFifoTwo => { | ||||||
|                     let palette = &self.monochrome.bg_palette; |                     if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) { | ||||||
|                     self.fetch.send_to_fifo(&mut self.fifo, palette); |  | ||||||
|                         self.fetch.x_pos += 1; |                         self.fetch.x_pos += 1; | ||||||
|  |  | ||||||
|                         self.fetch.back.next(TileNumber); |                         self.fetch.back.next(TileNumber); | ||||||
|                         self.fetch.back.tile = Default::default(); |                         self.fetch.back.tile = Default::default(); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if self.fifo.is_enabled() { |         if self.fifo.is_enabled() { | ||||||
|             use RenderPriority::*; |             if self.x_pos == 0 && self.scanline_start { | ||||||
|  |                 self.to_discard = self.pos.scroll_x % 8; | ||||||
|             if self.x_pos == 0 && !self.fifo.back.is_empty() { |                 self.scanline_start = false; | ||||||
|                 let to_discard = self.pos.scroll_x % 8; |  | ||||||
|  |  | ||||||
|                 for _ in 0..to_discard { |  | ||||||
|                     let _ = self.fifo.back.pop_front(); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if self.to_discard > 0 && !self.fifo.back.is_empty() { | ||||||
|  |                 let _ = self.fifo.back.pop_front(); | ||||||
|  |                 self.to_discard -= 1; | ||||||
|  |  | ||||||
|  |                 // Delay the PPU by one cycle | ||||||
|  |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Handle Background Pixel and Sprite FIFO |             // Handle Background Pixel and Sprite FIFO | ||||||
|             let bg_enabled = self.ctrl.bg_win_enabled(); |             if let Some(rgba) = self.clock_fifo().map(GrayShade::into_rgba) { | ||||||
|             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() { |  | ||||||
|                 let y = self.pos.line_y as usize; |                 let y = self.pos.line_y as usize; | ||||||
|                 let x = self.x_pos as usize; |                 let x = self.x_pos as usize; | ||||||
|  |  | ||||||
|                 let i = (GB_WIDTH * 4) * y + (x * 4); |                 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; |                 self.x_pos += 1; | ||||||
|             } |             } | ||||||
| @@ -438,8 +418,51 @@ impl Ppu { | |||||||
|         self.frame_buf.swap_with_slice(&mut blank); |         self.frame_buf.swap_with_slice(&mut blank); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { |     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); |         &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) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -447,7 +470,7 @@ impl Default for Ppu { | |||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             vram: Box::new([0u8; VRAM_SIZE]), |             vram: Box::new([0u8; VRAM_SIZE]), | ||||||
|             cycle: Cycle::new(0), |             cycle: Default::default(), | ||||||
|             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), |             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), | ||||||
|             int: Default::default(), |             int: Default::default(), | ||||||
|             ctrl: Default::default(), |             ctrl: Default::default(), | ||||||
| @@ -462,11 +485,13 @@ impl Default for Ppu { | |||||||
|             window_stat: Default::default(), |             window_stat: Default::default(), | ||||||
|             dma: Default::default(), |             dma: Default::default(), | ||||||
|             x_pos: 0, |             x_pos: 0, | ||||||
|  |             scanline_start: true, | ||||||
|  |             to_discard: Default::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct Interrupt { | pub(crate) struct Interrupt { | ||||||
|     _vblank: bool, |     _vblank: bool, | ||||||
|     _lcd_stat: bool, |     _lcd_stat: bool, | ||||||
| @@ -490,7 +515,7 @@ impl Interrupt { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct ScreenPosition { | pub(crate) struct ScreenPosition { | ||||||
|     /// 0xFF42 | SCY - Scroll Y |     /// 0xFF42 | SCY - Scroll Y | ||||||
|     pub(crate) scroll_y: u8, |     pub(crate) scroll_y: u8, | ||||||
| @@ -506,7 +531,7 @@ pub(crate) struct ScreenPosition { | |||||||
|     pub(crate) window_x: u8, |     pub(crate) window_x: u8, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct Monochrome { | pub(crate) struct Monochrome { | ||||||
|     /// 0xFF47 | BGP - Background Palette Data |     /// 0xFF47 | BGP - Background Palette Data | ||||||
|     pub(crate) bg_palette: BackgroundPalette, |     pub(crate) bg_palette: BackgroundPalette, | ||||||
| @@ -516,7 +541,7 @@ pub(crate) struct Monochrome { | |||||||
|     pub(crate) obj_palette_1: ObjectPalette, |     pub(crate) obj_palette_1: ObjectPalette, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| pub(crate) struct ObjectAttributeTable { | pub(crate) struct ObjectAttributeTable { | ||||||
|     buf: Box<[u8; OAM_SIZE]>, |     buf: Box<[u8; OAM_SIZE]>, | ||||||
| } | } | ||||||
| @@ -583,75 +608,42 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| struct ObjectBuffer { | struct ObjectBuffer { | ||||||
|     buf: [Option<ObjectAttribute>; OBJECT_LIMIT], |     inner: [Option<ObjectAttribute>; OBJECT_LIMIT], | ||||||
|     len: usize, |     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 { | impl ObjectBuffer { | ||||||
|     fn is_full(&self) -> bool { |     fn is_full(&self) -> bool { | ||||||
|         self.len == OBJECT_LIMIT |         self.len == OBJECT_LIMIT | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn clear(&mut self) { |     fn clear(&mut self) { | ||||||
|         self.buf = [Default::default(); 10]; |         self.inner = [Default::default(); 10]; | ||||||
|         self.len = 0; |         self.len = 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn add(&mut self, attr: ObjectAttribute) { |     fn add(&mut self, attr: ObjectAttribute) { | ||||||
|         self.buf[self.len] = Some(attr); |         self.inner[self.len] = Some(attr); | ||||||
|         self.len += 1; |         self.len += 1; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn remove(&mut self, attr: &ObjectAttribute) { |     fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> { | ||||||
|         let maybe_index = self.buf.iter().position(|maybe_attr| match maybe_attr { |         self.inner.iter_mut() | ||||||
|             Some(other_attr) => attr == other_attr, |  | ||||||
|             None => false, |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         if let Some(i) = maybe_index { |  | ||||||
|             self.buf[i] = None; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ObjectBuffer { | impl Default for ObjectBuffer { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             buf: [Default::default(); OBJECT_LIMIT], |             inner: [Default::default(); OBJECT_LIMIT], | ||||||
|             len: Default::default(), |             len: Default::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct PixelFetcher { | struct PixelFetcher { | ||||||
|     x_pos: u8, |     x_pos: u8, | ||||||
|     back: BackgroundFetcher, |     back: BackgroundFetcher, | ||||||
| @@ -720,7 +712,11 @@ impl PixelFetcher { | |||||||
|         tile_data_addr + (offset * 2) |         tile_data_addr + (offset * 2) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn send_to_fifo(&self, fifo: &mut FifoRenderer, palette: &BackgroundPalette) { |     fn send_to_fifo(&self, fifo: &mut PixelFifo) -> Result<(), ()> { | ||||||
|  |         if !fifo.back.is_empty() { | ||||||
|  |             return Err(()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         let (high, low) = self |         let (high, low) = self | ||||||
|             .back |             .back | ||||||
|             .tile |             .tile | ||||||
| @@ -729,14 +725,14 @@ impl PixelFetcher { | |||||||
|  |  | ||||||
|         let tbpp = Pixels::from_bytes(high, low); |         let tbpp = Pixels::from_bytes(high, low); | ||||||
|  |  | ||||||
|         if fifo.back.is_empty() { |  | ||||||
|         for x in 0..Pixels::PIXEL_COUNT { |         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); |             fifo.back.push_back(fifo_info); | ||||||
|         } |         } | ||||||
|         } |  | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 { |     fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 { | ||||||
| @@ -766,7 +762,7 @@ trait Fetcher { | |||||||
|     fn hblank_reset(&mut self); |     fn hblank_reset(&mut self); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| struct BackgroundFetcher { | struct BackgroundFetcher { | ||||||
|     state: FetcherState, |     state: FetcherState, | ||||||
|     tile: TileBuilder, |     tile: TileBuilder, | ||||||
| @@ -832,7 +828,7 @@ impl Default for BackgroundFetcher { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct ObjectFetcher { | struct ObjectFetcher { | ||||||
|     state: FetcherState, |     state: FetcherState, | ||||||
|     tile: TileBuilder, |     tile: TileBuilder, | ||||||
| @@ -854,7 +850,7 @@ impl Fetcher for ObjectFetcher { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct WindowLineCounter { | struct WindowLineCounter { | ||||||
|     count: u8, |     count: u8, | ||||||
| } | } | ||||||
| @@ -876,13 +872,13 @@ impl WindowLineCounter { | |||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum FetcherState { | enum FetcherState { | ||||||
|     TileNumber, |     TileNumber, | ||||||
|     ToLowByteSleep, |     SleepOne, | ||||||
|     TileLowByte, |     TileLow, | ||||||
|     ToHighByteSleep, |     SleepTwo, | ||||||
|     TileHighByte, |     TileHigh, | ||||||
|     ToFifoSleep, |     SleepThree, | ||||||
|     SendToFifoOne, |     ToFifoOne, | ||||||
|     SendToFifoTwo, |     ToFifoTwo, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for FetcherState { | impl Default for FetcherState { | ||||||
| @@ -891,28 +887,28 @@ impl Default for FetcherState { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct BackgroundFifoInfo { | struct BgPixelProperty { | ||||||
|     shade: GrayShade, |     shade_id: u8, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct ObjectFifoInfo { | struct ObjPixelProperty { | ||||||
|     shade: Option<GrayShade>, |     shade_id: u8, | ||||||
|     palette: ObjectPalette, |     palette_kind: ObjectPaletteKind, | ||||||
|     priority: RenderPriority, |     priority: RenderPriority, | ||||||
| } | } | ||||||
|  |  | ||||||
| // FIXME: Fifo Registers have a known size. Are heap allocations | // FIXME: Fifo Registers have a known size. Are heap allocations | ||||||
| // really necessary here? | // really necessary here? | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| struct FifoRenderer { | struct PixelFifo { | ||||||
|     back: VecDeque<BackgroundFifoInfo>, |     back: VecDeque<BgPixelProperty>, | ||||||
|     obj: VecDeque<ObjectFifoInfo>, |     obj: VecDeque<ObjPixelProperty>, | ||||||
|     enabled: bool, |     enabled: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl FifoRenderer { | impl PixelFifo { | ||||||
|     fn is_enabled(&self) -> bool { |     fn is_enabled(&self) -> bool { | ||||||
|         self.enabled |         self.enabled | ||||||
|     } |     } | ||||||
| @@ -926,7 +922,7 @@ impl FifoRenderer { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for FifoRenderer { | impl Default for PixelFifo { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             back: VecDeque::with_capacity(8), |             back: VecDeque::with_capacity(8), | ||||||
| @@ -936,7 +932,7 @@ impl Default for FifoRenderer { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct TileBuilder { | struct TileBuilder { | ||||||
|     id: Option<u8>, |     id: Option<u8>, | ||||||
|     low: Option<u8>, |     low: Option<u8>, | ||||||
| @@ -961,7 +957,7 @@ impl TileBuilder { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct OamScanState { | struct OamScanState { | ||||||
|     count: u8, |     count: u8, | ||||||
|     mode: OamScanMode, |     mode: OamScanMode, | ||||||
| @@ -982,8 +978,8 @@ impl OamScanState { | |||||||
|         self.count |         self.count | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn mode(&self) -> OamScanMode { |     fn mode(&self) -> &OamScanMode { | ||||||
|         self.mode |         &self.mode | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn next(&mut self) { |     fn next(&mut self) { | ||||||
| @@ -1008,7 +1004,7 @@ impl Default for OamScanMode { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct WindowStatus { | struct WindowStatus { | ||||||
|     /// This will be true if WY == LY at any point in the frame thus far |     /// This will be true if WY == LY at any point in the frame thus far | ||||||
|     coincidence: bool, |     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) struct DirectMemoryAccess { | ||||||
|     pub(crate) state: DmaState, |     pub(crate) state: DmaState, | ||||||
|     cycle: Cycle, |     cycle: Cycle, | ||||||
| @@ -9,7 +9,7 @@ pub(crate) struct DirectMemoryAccess { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl DirectMemoryAccess { | impl DirectMemoryAccess { | ||||||
|     pub(crate) fn clock(&mut self) -> Option<(u16, u16)> { |     pub(crate) fn tick(&mut self) -> Option<(u16, u16)> { | ||||||
|         match self.state { |         match self.state { | ||||||
|             DmaState::Pending => { |             DmaState::Pending => { | ||||||
|                 self.cycle += 1; |                 self.cycle += 1; | ||||||
| @@ -28,7 +28,7 @@ impl DirectMemoryAccess { | |||||||
|  |  | ||||||
|                 let src_addr = self |                 let src_addr = self | ||||||
|                     .start |                     .start | ||||||
|                     .addr |                     .0 | ||||||
|                     .as_mut() |                     .as_mut() | ||||||
|                     .expect("Source Address present during DMA Transfer"); |                     .expect("Source Address present during DMA Transfer"); | ||||||
|  |  | ||||||
| @@ -56,9 +56,9 @@ impl DirectMemoryAccess { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn reset(&mut self) { |     fn reset(&mut self) { | ||||||
|         self.cycle = Cycle::new(0); |         self.cycle = 0; | ||||||
|         self.state = DmaState::Disabled; |         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)] | #[derive(Debug, Clone, Copy, Default)] | ||||||
| pub(crate) struct DmaAddress { | pub(crate) struct DmaAddress(Option<u16>); | ||||||
|     /// The current *source* address of the DMA Transfer |  | ||||||
|     /// |  | ||||||
|     /// NB: Will be None if no DMA Transfer is in progress |  | ||||||
|     addr: Option<u16>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl DmaAddress { | impl DmaAddress { | ||||||
|     pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) { |     pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) { | ||||||
|         let start = (byte as u16) << 8; |         let start = (byte as u16) << 8; | ||||||
|  |  | ||||||
|         self.addr = Some(start); |         self.0 = Some(start); | ||||||
|         *state = DmaState::Pending; |         *state = DmaState::Pending; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<DmaAddress> for u8 { | impl From<DmaAddress> for u8 { | ||||||
|     fn from(ctrl: DmaAddress) -> Self { |     fn from(ctrl: DmaAddress) -> Self { | ||||||
|         match ctrl.addr { |         match ctrl.0 { | ||||||
|             Some(addr) => (addr >> 8) as u8, |             Some(addr) => (addr >> 8) as u8, | ||||||
|             None => 0xFF, // TODO: What garbage value should be here? |             None => 0xFF, // TODO: What garbage value should be here? | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -332,7 +332,7 @@ bitfield! { | |||||||
|     pub from into RenderPriority, priority, set_priority: 7, 7; |     pub from into RenderPriority, priority, set_priority: 7, 7; | ||||||
|     pub y_flip, set_y_flip: 6; |     pub y_flip, set_y_flip: 6; | ||||||
|     pub x_flip, set_x_flip: 5; |     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 {} | impl Eq for ObjectFlags {} | ||||||
| @@ -368,23 +368,29 @@ impl Default for ObjectFlags { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum ObjectPaletteId { | pub enum ObjectPaletteKind { | ||||||
|     Zero = 0, |     Zero = 0, | ||||||
|     One = 1, |     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 { |     fn from(byte: u8) -> Self { | ||||||
|         match byte & 0b01 { |         match byte & 0b01 { | ||||||
|             0b00 => ObjectPaletteId::Zero, |             0b00 => ObjectPaletteKind::Zero, | ||||||
|             0b01 => ObjectPaletteId::One, |             0b01 => ObjectPaletteKind::One, | ||||||
|             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), |             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<ObjectPaletteId> for u8 { | impl From<ObjectPaletteKind> for u8 { | ||||||
|     fn from(palette_num: ObjectPaletteId) -> Self { |     fn from(palette_num: ObjectPaletteKind) -> Self { | ||||||
|         palette_num as u8 |         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; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct Serial { | pub(crate) struct Serial { | ||||||
|     /// 0xFF01 | SB - Serial Transfer Data |     /// 0xFF01 | SB - Serial Transfer Data | ||||||
|     pub(crate) next: u8, |     pub(crate) next: u8, | ||||||
|   | |||||||
							
								
								
									
										1235
									
								
								src/sound.rs
									
									
									
									
									
								
							
							
						
						
									
										1235
									
								
								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; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| pub(crate) struct Timer { | pub(crate) struct Timer { | ||||||
|     /// 0xFF07 | TAC - Timer Control |     /// 0xFF07 | TAC - Timer Control | ||||||
|     pub(crate) ctrl: TimerControl, |     pub(crate) ctrl: TimerControl, | ||||||
|     /// 0xFF05 | TIMA - Timer Counter |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|     pub(crate) counter: u8, |     counter: u8, | ||||||
|     /// 0xFF06 | TMA - Timer Modulo |     /// 0xFF06 | TMA - Timer Modulo | ||||||
|     pub(crate) modulo: u8, |     pub(crate) modulo: u8, | ||||||
|     /// 0xFF04 | DIV - Divider Register |     /// 0xFF04 | DIV - Divider Register | ||||||
|     pub(crate) divider: u16, |     pub(crate) divider: u16, | ||||||
|     prev_and_result: Option<u8>, |  | ||||||
|  |     and_result: Option<u8>, | ||||||
|     interrupt: bool, |     interrupt: bool, | ||||||
|  |     state: State, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Timer { | impl Timer { | ||||||
|     pub(crate) fn clock(&mut self) { |     pub(crate) fn tick(&mut self) { | ||||||
|  |         use State::*; | ||||||
|         use TimerSpeed::*; |         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); |         self.divider = self.divider.wrapping_add(1); | ||||||
|  |  | ||||||
|         // Get Bit Position |         // Get Bit Position | ||||||
| @@ -29,27 +43,34 @@ impl Timer { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let bit = (self.divider >> bit) as u8 & 0x01; |         let bit = (self.divider >> bit) as u8 & 0x01; | ||||||
|         let timer_enable = self.ctrl.enabled() as u8; |         let new_result = bit & self.ctrl.enabled() as u8; | ||||||
|         let and_result = bit & timer_enable; |  | ||||||
|  |  | ||||||
|         if let Some(0x01) = self.prev_and_result { |         if let Some(0x01) = self.and_result { | ||||||
|             if and_result == 0x00 { |             if new_result == 0x00 { | ||||||
|                 // Falling Edge, increase TIMA Register |                 // 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) { |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|         let (result, did_overflow) = self.counter.overflowing_add(1); |     pub(crate) fn tima(&self) -> u8 { | ||||||
|  |         self.counter | ||||||
|  |     } | ||||||
|  |  | ||||||
|         self.counter = if did_overflow { |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|             self.interrupt = true; |     pub(crate) fn set_tima(&mut self, byte: u8) { | ||||||
|             self.modulo |         use State::*; | ||||||
|         } else { |  | ||||||
|             result |         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) { |     pub(crate) fn set_interrupt(&mut self, value: bool) { | ||||||
|         self.interrupt = value; |         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 { | impl Default for Timer { | ||||||
| @@ -70,7 +112,8 @@ impl Default for Timer { | |||||||
|             modulo: 0, |             modulo: 0, | ||||||
|             divider: 0, |             divider: 0, | ||||||
|             interrupt: false, |             interrupt: false, | ||||||
|             prev_and_result: None, |             and_result: None, | ||||||
|  |             state: State::Normal, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -132,3 +175,11 @@ impl From<TimerControl> for u8 { | |||||||
|         ctrl.0 |         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 WORK_RAM_START_ADDRESS: usize = 0xC000; | ||||||
| const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000; | const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| pub(crate) struct WorkRam { | pub(crate) struct WorkRam { | ||||||
|     bank: Box<[u8; WORK_RAM_SIZE]>, |     bank: Box<[u8; WORK_RAM_SIZE]>, | ||||||
| } | } | ||||||
| @@ -28,48 +28,25 @@ impl Default for WorkRam { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| enum BankNumber { |  | ||||||
|     One = 1, |  | ||||||
|     Two = 2, |  | ||||||
|     Three = 3, |  | ||||||
|     Four = 4, |  | ||||||
|     Five = 5, |  | ||||||
|     Six = 6, |  | ||||||
|     Seven = 7, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub(crate) struct VariableWorkRam { | pub(crate) struct VariableWorkRam { | ||||||
|     current: BankNumber, |     buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour | ||||||
|     bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for VariableWorkRam { | impl Default for VariableWorkRam { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             current: BankNumber::One, |             buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]), | ||||||
|             bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 { | impl BusIo for VariableWorkRam { | ||||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { |     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 { |     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