Compare commits
8 Commits
ab7ee0988f
...
b7c1d9b8a0
| Author | SHA1 | Date | |
|---|---|---|---|
| b7c1d9b8a0 | |||
| bdf51448f7 | |||
| 078da395ed | |||
| 195d94ddb0 | |||
| cd48f4c4c8 | |||
| 2fb40e7b10 | |||
| f07a0032eb | |||
| 4cf6225133 |
@ -4,7 +4,11 @@ version = "0.1.0"
|
||||
authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"]
|
||||
edition = '2018'
|
||||
|
||||
[features]
|
||||
default = ["audio"]
|
||||
audio = ["pulse-simple"]
|
||||
|
||||
[dependencies]
|
||||
sdl2 = "*"
|
||||
libc = "*"
|
||||
pulse-simple = "*"
|
||||
pulse-simple = { version = "*", optional = true }
|
||||
|
||||
94
src/cpu.rs
94
src/cpu.rs
@ -141,12 +141,7 @@ impl CPU {
|
||||
fn adc_r(&mut self, val: u8) {
|
||||
let old: u8 = self.regs[REG_A];
|
||||
let mut new: u8 = old;
|
||||
let c: u8;
|
||||
if self.flags & FLAG_C == FLAG_C {
|
||||
c = 1;
|
||||
} else {
|
||||
c = 0;
|
||||
}
|
||||
let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
|
||||
new = new.wrapping_add(val);
|
||||
new = new.wrapping_add(c);
|
||||
self.regs[REG_A] = new;
|
||||
@ -172,12 +167,7 @@ impl CPU {
|
||||
fn sbc_r(&mut self, val: u8) {
|
||||
let old: u8 = self.regs[REG_A];
|
||||
let mut new: u8 = old as u8;
|
||||
let c: u8;
|
||||
if self.flags & FLAG_C == FLAG_C {
|
||||
c = 1;
|
||||
} else {
|
||||
c = 0;
|
||||
}
|
||||
let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
|
||||
new = new.wrapping_sub(val);
|
||||
new = new.wrapping_sub(c);
|
||||
|
||||
@ -227,12 +217,11 @@ impl CPU {
|
||||
println!("RLC {}", REG_NAMES[reg_id]);
|
||||
}
|
||||
|
||||
let nval: u8;
|
||||
if val & 0x80 == 0x80 {
|
||||
nval = val << 1 | 1;
|
||||
let nval = if val & 0x80 == 0x80 {
|
||||
val << 1 | 1
|
||||
} else {
|
||||
nval = val << 1;
|
||||
}
|
||||
val << 1
|
||||
};
|
||||
self.set_8bit_reg(reg_id, nval);
|
||||
|
||||
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
|
||||
@ -266,12 +255,7 @@ impl CPU {
|
||||
}
|
||||
|
||||
let carry = self.flags & FLAG_C > 0;
|
||||
let nval: u8;
|
||||
if !carry {
|
||||
nval = val << 1;
|
||||
} else {
|
||||
nval = val << 1 | 1;
|
||||
}
|
||||
let nval = if !carry { val << 1 } else { val << 1 | 1 };
|
||||
self.set_8bit_reg(reg_id, nval);
|
||||
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
|
||||
self.set_clear_flag(FLAG_Z, nval == 0);
|
||||
@ -287,12 +271,7 @@ impl CPU {
|
||||
}
|
||||
|
||||
let carry = self.flags & FLAG_C > 0;
|
||||
let v: u8;
|
||||
if !carry {
|
||||
v = val >> 1;
|
||||
} else {
|
||||
v = (val >> 1) | 0x80;
|
||||
}
|
||||
let v = if !carry { val >> 1 } else { (val >> 1) | 0x80 };
|
||||
self.set_8bit_reg(reg_id, v);
|
||||
self.set_clear_flag(FLAG_C, val & 1 == 1);
|
||||
self.set_clear_flag(FLAG_Z, v == 0);
|
||||
@ -788,7 +767,7 @@ impl CPU {
|
||||
self.clear_flag(FLAG_N);
|
||||
|
||||
// Some magic formula
|
||||
self.set_clear_flag(FLAG_C, val1 as usize + val2 as usize & 0x10000 == 0x10000);
|
||||
self.set_clear_flag(FLAG_C, (val1 as usize + val2 as usize) & 0x10000 == 0x10000);
|
||||
self.set_clear_flag(FLAG_H, (val1 ^ val2 ^ res) & 0x1000 == 0x1000);
|
||||
8
|
||||
}
|
||||
@ -948,10 +927,8 @@ impl CPU {
|
||||
// Check for interrupts.
|
||||
if self.ime {
|
||||
self.check_interrupts(true);
|
||||
} else if self.halted {
|
||||
if self.check_interrupts(false) {
|
||||
self.halted = false;
|
||||
}
|
||||
} else if self.halted && self.check_interrupts(false) {
|
||||
self.halted = false;
|
||||
}
|
||||
|
||||
let mut cycles: u8 = 255;
|
||||
@ -961,13 +938,27 @@ impl CPU {
|
||||
instruction = self.read_byte(self.ip);
|
||||
if self.debug {
|
||||
print!(
|
||||
"{:#06x}: [SP: {:#04X}] i={:02X}. ",
|
||||
"{:#06X}: [SP: {:#04X}] i={:02X}. ",
|
||||
&self.ip, &self.sp, &instruction
|
||||
);
|
||||
for i in 0..6 {
|
||||
print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i));
|
||||
/*
|
||||
for (idx, reg) in REG_NAMES.iter().enumerate() {
|
||||
print!("{}: {:02X} ", reg, self.get_8bit_reg(idx));
|
||||
}
|
||||
print!("A: {:02X} ", self.regs[REG_A]);
|
||||
*/
|
||||
|
||||
print!(
|
||||
"AF={:02X}{:02X} BC={:02X}{:02X} DE={:02X}{:02X} HL={:02X}{:02X} ",
|
||||
self.get_8bit_reg(REG_N_A),
|
||||
self.get_8bit_reg(REG_N_F),
|
||||
self.get_8bit_reg(REG_N_B),
|
||||
self.get_8bit_reg(REG_N_C),
|
||||
self.get_8bit_reg(REG_N_D),
|
||||
self.get_8bit_reg(REG_N_E),
|
||||
self.get_8bit_reg(REG_N_H),
|
||||
self.get_8bit_reg(REG_N_L)
|
||||
);
|
||||
print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF));
|
||||
|
||||
// Flags
|
||||
@ -1000,9 +991,10 @@ impl CPU {
|
||||
let carry = val & 0x80 == 0x80;
|
||||
self.set_clear_flag(FLAG_C, carry);
|
||||
if !carry {
|
||||
self.regs[REG_A] = self.regs[REG_A] << 1;
|
||||
self.regs[REG_A] <<= 1;
|
||||
} else {
|
||||
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
|
||||
self.regs[REG_A] <<= 1;
|
||||
self.regs[REG_A] |= 1;
|
||||
}
|
||||
self.clear_flag(FLAG_Z);
|
||||
self.clear_flag(FLAG_N);
|
||||
@ -1038,9 +1030,10 @@ impl CPU {
|
||||
let val = self.regs[REG_A];
|
||||
self.set_clear_flag(FLAG_C, val & 1 == 1);
|
||||
if val & 1 == 0 {
|
||||
self.regs[REG_A] = self.regs[REG_A] >> 1;
|
||||
self.regs[REG_A] >>= 1;
|
||||
} else {
|
||||
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80;
|
||||
self.regs[REG_A] >>= 1;
|
||||
self.regs[REG_A] |= 0x80;
|
||||
}
|
||||
self.clear_flag(FLAG_Z);
|
||||
self.clear_flag(FLAG_N);
|
||||
@ -1069,9 +1062,10 @@ impl CPU {
|
||||
let val = self.regs[REG_A];
|
||||
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
|
||||
if !carry {
|
||||
self.regs[REG_A] = self.regs[REG_A] << 1;
|
||||
self.regs[REG_A] <<= 1;
|
||||
} else {
|
||||
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
|
||||
self.regs[REG_A] <<= 1;
|
||||
self.regs[REG_A] |= 1;
|
||||
}
|
||||
self.clear_flag(FLAG_Z);
|
||||
self.clear_flag(FLAG_N);
|
||||
@ -1108,9 +1102,10 @@ impl CPU {
|
||||
let val = self.regs[REG_A];
|
||||
self.set_clear_flag(FLAG_C, val & 1 == 1);
|
||||
if !carry {
|
||||
self.regs[REG_A] = self.regs[REG_A] >> 1;
|
||||
self.regs[REG_A] >>= 1;
|
||||
} else {
|
||||
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80;
|
||||
self.regs[REG_A] >>= 1;
|
||||
self.regs[REG_A] |= 0x80;
|
||||
}
|
||||
self.clear_flag(FLAG_Z);
|
||||
self.clear_flag(FLAG_N);
|
||||
@ -1577,12 +1572,11 @@ impl CPU {
|
||||
if self.debug {
|
||||
println!("ADD SP, {:02X}", arg);
|
||||
}
|
||||
let t: u16;
|
||||
if arg > 0 {
|
||||
t = self.sp.wrapping_add(arg as u16);
|
||||
let t = if arg > 0 {
|
||||
self.sp.wrapping_add(arg as u16)
|
||||
} else {
|
||||
t = self.sp.wrapping_sub((-arg) as u16);
|
||||
}
|
||||
self.sp.wrapping_sub((-arg) as u16)
|
||||
};
|
||||
let sp = self.sp;
|
||||
self.clear_flag(FLAG_N);
|
||||
self.clear_flag(FLAG_Z);
|
||||
|
||||
238
src/display.rs
238
src/display.rs
@ -65,7 +65,7 @@ const MONOCHROME_PALETTE: &'static [[u8; 3]; 4] = &[
|
||||
#[derive(Copy, Clone)]
|
||||
struct CgbPalette([u8; 8]);
|
||||
impl CgbPalette {
|
||||
fn get_color(&self, n: usize) -> sdl2::pixels::Color {
|
||||
fn get_color(self, n: usize) -> sdl2::pixels::Color {
|
||||
if n == 0 {
|
||||
return sdl2::pixels::Color::RGB(255, 255, 255);
|
||||
}
|
||||
@ -73,15 +73,34 @@ impl CgbPalette {
|
||||
let r = (v & 0b1_1111) as u8;
|
||||
let g = ((v >> 5) & 0b1_1111) as u8;
|
||||
let b = ((v >> 10) & 0b1_1111) as u8;
|
||||
// According to some code:
|
||||
// Real colors:
|
||||
let r = r as u16;
|
||||
let g = g as u16;
|
||||
let b = b as u16;
|
||||
let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8;
|
||||
let mapped_g = ((g * 3 + b) << 1) as u8;
|
||||
let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8;
|
||||
sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b)
|
||||
|
||||
if false {
|
||||
sdl2::pixels::Color::RGB(r << 3, g << 3, b << 3)
|
||||
} else {
|
||||
// According to some code:
|
||||
// Real colors:
|
||||
let r = r as u16;
|
||||
let g = g as u16;
|
||||
let b = b as u16;
|
||||
let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8;
|
||||
let mapped_g = ((g * 3 + b) << 1) as u8;
|
||||
let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8;
|
||||
sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CgbPalette {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Palette: ")?;
|
||||
for n in 0..4 {
|
||||
let v = ((self.0[2 * n + 1] as u16) << 8) | (self.0[2 * n] as u16);
|
||||
let r = (v & 0b1_1111) as u8;
|
||||
let g = ((v >> 5) & 0b1_1111) as u8;
|
||||
let b = ((v >> 10) & 0b1_1111) as u8;
|
||||
write!(f, "{:02X}{:02X}{:02X} ", r, g, b)?;
|
||||
}
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +146,7 @@ impl Default for Pixel {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum DisplayMode {
|
||||
ReadOAMMemory,
|
||||
ReadFullMemory,
|
||||
@ -368,11 +387,16 @@ impl Display {
|
||||
self.background_palette_autoinc = (val >> 7) != 0;
|
||||
}
|
||||
0xFF69 => {
|
||||
if self.current_mode == DisplayMode::ReadFullMemory {
|
||||
println!("Trying to write to palette memory while being accessed!");
|
||||
}
|
||||
|
||||
let idx = self.background_palette_index as usize;
|
||||
if idx < 64 {
|
||||
self.background_palette_cgb[idx / 8].0[idx % 8] = val;
|
||||
} else {
|
||||
panic!("OOB palette w/ autoinc");
|
||||
println!("OOB palette w/ autoinc");
|
||||
return;
|
||||
}
|
||||
if self.background_palette_autoinc {
|
||||
self.background_palette_index += 1;
|
||||
@ -387,7 +411,8 @@ impl Display {
|
||||
if idx < 64 {
|
||||
self.object_palette_cgb[idx / 8].0[idx % 8] = val;
|
||||
} else {
|
||||
panic!("OOB obj palette w/ autoinc");
|
||||
println!("OOB obj palette w/ autoinc");
|
||||
return;
|
||||
}
|
||||
if self.object_palette_autoinc {
|
||||
self.object_palette_index += 1;
|
||||
@ -405,7 +430,7 @@ impl Display {
|
||||
}
|
||||
self.windowx = val;
|
||||
}
|
||||
0xFF4f => self.vram_bank = val,
|
||||
0xFF4F => self.vram_bank = val,
|
||||
_ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr),
|
||||
}
|
||||
}
|
||||
@ -560,15 +585,14 @@ impl Display {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_sprites(&mut self, sprites: &Vec<Sprite>) {
|
||||
fn render_sprites(&mut self, sprites: &[Sprite]) {
|
||||
if self.control & CTRL_BG_SPRITE_ENABLE > 0 {
|
||||
let mut num_rendered: u8 = 0;
|
||||
for i in 0..39 {
|
||||
for sprite in sprites.iter().take(39) {
|
||||
// Gameboy limitation
|
||||
if num_rendered >= 10 {
|
||||
break;
|
||||
}
|
||||
let sprite = &sprites[i];
|
||||
|
||||
// Skip hidden sprites
|
||||
if sprite.is_hidden() {
|
||||
@ -583,13 +607,10 @@ impl Display {
|
||||
|
||||
// Is this sprite on the current line?
|
||||
let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0;
|
||||
let actual_h = match wide_mode {
|
||||
true => 16,
|
||||
false => 8,
|
||||
};
|
||||
let actual_h = if wide_mode { 16 } else { 8 };
|
||||
|
||||
if sprite.is_y_flipped() {
|
||||
panic!("Sorry, no y flip support yet");
|
||||
eprintln!("Sorry, no y flip support yet, rendering as-is");
|
||||
}
|
||||
|
||||
if y.wrapping_add(actual_h) > render_y && y <= render_y {
|
||||
@ -621,10 +642,7 @@ impl Display {
|
||||
}
|
||||
}
|
||||
|
||||
let x_maybe_flipped: u8 = match sprite.is_x_flipped() {
|
||||
true => x_o ^ 7,
|
||||
false => x_o,
|
||||
};
|
||||
let x_maybe_flipped: u8 = if sprite.is_x_flipped() { x_o ^ 7 } else { x_o };
|
||||
b1 = (tile_line_1 & 1 << (7 - x_o)) > 0;
|
||||
b2 = (tile_line_2 & 1 << (7 - x_o)) > 0;
|
||||
|
||||
@ -661,7 +679,7 @@ impl Display {
|
||||
PixelOrigin::Sprite,
|
||||
);
|
||||
*/
|
||||
let c = ((b1 as u8) * 2 + b2 as u8) as usize;
|
||||
let c = ((b2 as u8) * 2 + b1 as u8) as usize;
|
||||
if c == 0 {
|
||||
continue;
|
||||
}
|
||||
@ -735,16 +753,7 @@ impl Display {
|
||||
|
||||
// This is the non-CGB priority.
|
||||
// Smaller x coord = higher.
|
||||
use std::cmp;
|
||||
queue.sort_by(|x, y| {
|
||||
if x.x > y.x {
|
||||
cmp::Ordering::Greater
|
||||
} else if x.x < y.x {
|
||||
cmp::Ordering::Less
|
||||
} else {
|
||||
cmp::Ordering::Equal
|
||||
}
|
||||
});
|
||||
queue.sort_by(|x, y| x.x.cmp(&y.x));
|
||||
queue.reverse();
|
||||
|
||||
// Render background
|
||||
@ -780,8 +789,7 @@ impl Display {
|
||||
|
||||
// Obtain tile information
|
||||
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
|
||||
// TODO: Colored background
|
||||
let addr = tile_base_addr + (tile_offset_y as usize) * 2;
|
||||
let addr = tile_base_addr + (tile_offset_y as usize) * 2; // two bytes per row
|
||||
|
||||
let tile_line_1 = vram[addr];
|
||||
let tile_line_2 = vram[addr + 1];
|
||||
@ -801,7 +809,7 @@ impl Display {
|
||||
let b2: bool = (tile_line_2 & b2) != 0;
|
||||
|
||||
// Lookup the color
|
||||
let c = ((b1 as u8) * 2 + b2 as u8) as usize;
|
||||
let c = ((b2 as u8) * 2 + b1 as u8) as usize;
|
||||
let origin = match c {
|
||||
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
|
||||
_ => PixelOrigin::Background,
|
||||
@ -835,87 +843,95 @@ impl Display {
|
||||
if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE {
|
||||
// Draw 'window' over the background.
|
||||
// Screen coordinates of the top left corner are WX-7, WY
|
||||
// Quick check if the window is visible at all.
|
||||
if self.windowx < 167 && self.windowy < 144 {
|
||||
//let rx = self.windowx.wrapping_add(7);
|
||||
let rx = 7u8.wrapping_sub(self.windowx);
|
||||
let ry = render_y.wrapping_add(self.windowy);
|
||||
for r_x in 0..160u8 {
|
||||
let render_x = r_x.wrapping_add(rx);
|
||||
// Absolute render coordinates
|
||||
let tile_index_x: u8 = render_x >> 3;
|
||||
let tile_index_y: u8 = ry >> 3;
|
||||
let tile_offset_x: u8 = render_x & 7;
|
||||
let tile_offset_y: u8 = ry & 7;
|
||||
let window_y_offset = render_y as i16 - self.windowy as i16;
|
||||
if self.windowx < 7 {
|
||||
eprintln!("Window X position < 7, bailing out");
|
||||
} else if window_y_offset >= 144 || window_y_offset < 0 {
|
||||
// Not visible
|
||||
} else {
|
||||
let window_y_offset = window_y_offset as u8;
|
||||
let window_x_offset = self.windowx - 7;
|
||||
|
||||
let vram_offset: usize =
|
||||
window_map + (tile_index_y as usize) * 32 + tile_index_x as usize;
|
||||
for r_x in 0..(160u8 - window_x_offset) {
|
||||
let render_x = r_x.wrapping_add(window_x_offset);
|
||||
// Absolute render coordinates
|
||||
let tile_index_x: u8 = render_x >> 3;
|
||||
let tile_index_y: u8 = window_y_offset >> 3;
|
||||
let tile_offset_x: u8 = render_x & 7;
|
||||
let tile_offset_y: u8 = window_y_offset & 7;
|
||||
|
||||
// Obtain tile ID in this area
|
||||
let tile_id = self.vram0[vram_offset];
|
||||
let vram_offset: usize =
|
||||
window_map + (tile_index_y as usize) * 32 + tile_index_x as usize;
|
||||
|
||||
let bg_attribs = self.get_background_attribute(
|
||||
//tile_index_x as usize, tile_index_y as usize
|
||||
vram_offset,
|
||||
);
|
||||
// Obtain tile ID in this area
|
||||
let tile_id = self.vram0[vram_offset];
|
||||
|
||||
// Get BG map attributes
|
||||
let vram = if bg_attribs.vram_bank_number() == 0 {
|
||||
&*self.vram0
|
||||
} else {
|
||||
&*self.vram1
|
||||
};
|
||||
let bg_attribs = self.get_background_attribute(
|
||||
//tile_index_x as usize, tile_index_y as usize
|
||||
vram_offset,
|
||||
);
|
||||
|
||||
// TODO: Priority
|
||||
let tile_offset_x = if bg_attribs.vertical_flip() {
|
||||
7 ^ tile_offset_x
|
||||
} else {
|
||||
tile_offset_x
|
||||
};
|
||||
let tile_offset_y = if bg_attribs.horizontal_flip() {
|
||||
7 ^ tile_offset_y
|
||||
} else {
|
||||
tile_offset_y
|
||||
};
|
||||
// Get BG map attributes
|
||||
let vram = if bg_attribs.vram_bank_number() == 0 {
|
||||
&*self.vram0
|
||||
} else {
|
||||
&*self.vram1
|
||||
};
|
||||
|
||||
// Obtain tile information
|
||||
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
|
||||
let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2;
|
||||
let tile_line_1 = vram[tile_addr];
|
||||
let tile_line_2 = vram[tile_addr + 1];
|
||||
// TODO: Priority
|
||||
let tile_offset_x = if bg_attribs.vertical_flip() {
|
||||
7 ^ tile_offset_x
|
||||
} else {
|
||||
tile_offset_x
|
||||
};
|
||||
let tile_offset_y = if bg_attribs.horizontal_flip() {
|
||||
7 ^ tile_offset_y
|
||||
} else {
|
||||
tile_offset_y
|
||||
};
|
||||
|
||||
// Get the correct bit
|
||||
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0;
|
||||
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0;
|
||||
// Obtain tile information
|
||||
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
|
||||
let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2;
|
||||
let tile_line_1 = vram[tile_addr];
|
||||
let tile_line_2 = vram[tile_addr + 1];
|
||||
|
||||
let c = (b1 as u8) * 2 + b2 as u8;
|
||||
let origin = match c {
|
||||
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
|
||||
_ => PixelOrigin::Window,
|
||||
};
|
||||
let c = self.background_palette_cgb[bg_attribs.palette_number()]
|
||||
.get_color(c as usize);
|
||||
self.set_pixel(render_x, render_y, c, origin);
|
||||
/*
|
||||
let lookup: [u8; 4] = [
|
||||
self.background_palette & 3,
|
||||
(self.background_palette >> 2) & 3,
|
||||
(self.background_palette >> 4) & 3,
|
||||
(self.background_palette >> 6) & 3,
|
||||
];
|
||||
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize];
|
||||
// Get the correct bit
|
||||
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0;
|
||||
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0;
|
||||
|
||||
// Draw stuff. We're currently only in monochrome mode
|
||||
let origin = match c {
|
||||
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
|
||||
_ => PixelOrigin::Window,
|
||||
};
|
||||
self.set_pixel(
|
||||
render_x,
|
||||
render_y,
|
||||
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]),
|
||||
origin,
|
||||
);
|
||||
*/
|
||||
let c = (b2 as u8) * 2 + b1 as u8;
|
||||
let origin = match c {
|
||||
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
|
||||
_ => PixelOrigin::Window,
|
||||
};
|
||||
let c = self.background_palette_cgb[bg_attribs.palette_number()]
|
||||
.get_color(c as usize);
|
||||
self.set_pixel(render_x, render_y, c, origin);
|
||||
/*
|
||||
let lookup: [u8; 4] = [
|
||||
self.background_palette & 3,
|
||||
(self.background_palette >> 2) & 3,
|
||||
(self.background_palette >> 4) & 3,
|
||||
(self.background_palette >> 6) & 3,
|
||||
];
|
||||
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize];
|
||||
|
||||
// Draw stuff. We're currently only in monochrome mode
|
||||
let origin = match c {
|
||||
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
|
||||
_ => PixelOrigin::Window,
|
||||
};
|
||||
self.set_pixel(
|
||||
render_x,
|
||||
render_y,
|
||||
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]),
|
||||
origin,
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ pub struct Interconnect {
|
||||
impl Interconnect {
|
||||
pub fn new(bios: Box<[u8]>, rom: Box<[u8]>, save_file: Option<String>) -> Interconnect {
|
||||
Interconnect {
|
||||
bios: bios,
|
||||
bios,
|
||||
cartridge: cartridge::Cartridge::new(rom, save_file),
|
||||
ram: vec![0; WRAM_SIZE].into_boxed_slice(),
|
||||
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@ -1,5 +1,7 @@
|
||||
// let's try to write our own, awesome emulator.
|
||||
// gameboy (color?)
|
||||
// To make things more readable at points
|
||||
#![allow(clippy::identity_op)]
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
@ -24,10 +26,11 @@ fn main() {
|
||||
} else {
|
||||
let bios_path = &args[1];
|
||||
let rom_path = &args[2];
|
||||
let mut save_file: Option<String> = None;
|
||||
if args.len() == 4 {
|
||||
save_file = Some(args[3].clone());
|
||||
}
|
||||
let save_file = if args.len() == 4 {
|
||||
Some(args[3].clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bios = read_file(&bios_path).unwrap();
|
||||
let rom = read_file(&rom_path).unwrap();
|
||||
|
||||
@ -22,7 +22,7 @@ pub struct NoMBC {
|
||||
|
||||
impl NoMBC {
|
||||
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> NoMBC {
|
||||
NoMBC { rom: rom, ram: ram }
|
||||
NoMBC { rom, ram }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -71,9 +71,9 @@ impl VolumeEnvelope {
|
||||
impl AudioModule<u8> for VolumeEnvelope {
|
||||
fn transform(&self, sample: u8) -> u8 {
|
||||
if sample > 0 {
|
||||
self.current_volume
|
||||
128 + self.current_volume
|
||||
} else {
|
||||
0
|
||||
128 - self.current_volume
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
src/sound/mod.rs
195
src/sound/mod.rs
@ -1,16 +1,21 @@
|
||||
#[cfg(feature = "audio")]
|
||||
extern crate pulse_simple;
|
||||
mod envelope;
|
||||
mod length;
|
||||
mod square;
|
||||
mod wave;
|
||||
|
||||
#[cfg(feature = "audio")]
|
||||
use self::pulse_simple::Playback;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
#[cfg(feature = "audio")]
|
||||
use std::thread;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const OUTPUT_SAMPLE_RATE: usize = 48100;
|
||||
//const OUTPUT_SAMPLE_RATE: usize = 32768;
|
||||
|
||||
@ -233,10 +238,11 @@ impl Default for Channel3VolumeSetting {
|
||||
impl Channel3VolumeSetting {
|
||||
fn transform(&self, sample: u8) -> u8 {
|
||||
match self {
|
||||
Self::Muted => 0,
|
||||
Self::Original => sample,
|
||||
Self::Half => sample >> 1,
|
||||
Self::Quarter => sample >> 2,
|
||||
Self::Muted => 128,
|
||||
// Actually making it louder as the wave src is quite silent.
|
||||
Self::Original => ((sample - 120) << 1) + 112,
|
||||
Self::Half => sample,
|
||||
Self::Quarter => ((sample - 120) >> 1) + 124,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,7 +302,7 @@ impl Channel3 {
|
||||
pub fn sample(&self) -> u8 {
|
||||
let input = self.wave_gen.sample();
|
||||
// TODO(?): Follow the chain
|
||||
// let input = self.length_counter.transform(input);
|
||||
let input = self.length_counter.transform(input);
|
||||
self.volume_ctrl.transform(input)
|
||||
}
|
||||
|
||||
@ -309,10 +315,6 @@ impl Channel3 {
|
||||
// TODO: Sweep
|
||||
}
|
||||
|
||||
if self.tick_state == 7 {
|
||||
// self.envelope.clock();
|
||||
}
|
||||
|
||||
self.tick_state += 1;
|
||||
|
||||
if self.tick_state == 8 {
|
||||
@ -336,6 +338,7 @@ pub struct SoundManager {
|
||||
pub sound_object: Arc<Mutex<Sound>>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
do_exit: Arc<AtomicBool>,
|
||||
sound_enabled: bool,
|
||||
}
|
||||
|
||||
impl Drop for SoundManager {
|
||||
@ -351,6 +354,7 @@ impl SoundManager {
|
||||
sound_object: Arc::new(Mutex::new(Sound::new())),
|
||||
handle: None,
|
||||
do_exit: Arc::new(AtomicBool::new(false)),
|
||||
sound_enabled: true,
|
||||
};
|
||||
|
||||
res.launch_thread();
|
||||
@ -359,48 +363,57 @@ impl SoundManager {
|
||||
|
||||
fn launch_thread(&mut self) {
|
||||
let obj = self.sound_object.clone();
|
||||
if false {
|
||||
return;
|
||||
}
|
||||
|
||||
let do_exit = self.do_exit.clone();
|
||||
self.handle = Some(
|
||||
thread::Builder::new()
|
||||
.name("Audio".into())
|
||||
.spawn(move || {
|
||||
// PulseAudio playback object
|
||||
let playback = Playback::new("GBC", "Output", None, (OUTPUT_SAMPLE_RATE) as _);
|
||||
#[cfg(feature = "audio")]
|
||||
{
|
||||
if self.sound_enabled {
|
||||
self.handle = Some(
|
||||
thread::Builder::new()
|
||||
.name("Audio".into())
|
||||
.spawn(move || {
|
||||
// PulseAudio playback object
|
||||
let playback =
|
||||
Playback::new("GBC", "Output", None, (OUTPUT_SAMPLE_RATE) as _);
|
||||
|
||||
// Counter, used for calling the 512 Hz timer
|
||||
let mut counter = 0;
|
||||
// Counter, used for calling the 512 Hz timer
|
||||
let mut counter = 0;
|
||||
|
||||
while !do_exit.load(Ordering::Relaxed) {
|
||||
for _ in 0..100 {
|
||||
let (s1, s2) = {
|
||||
let mut c_obj = obj.lock().unwrap();
|
||||
while !do_exit.load(Ordering::Relaxed) {
|
||||
for _ in 0..100 {
|
||||
if let Some((s1, s2)) = {
|
||||
let mut c_obj = obj.lock().unwrap();
|
||||
|
||||
// Check for 512 Hz timer
|
||||
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
|
||||
c_obj.clock();
|
||||
counter = 0;
|
||||
} else {
|
||||
counter += 1;
|
||||
// Check for 512 Hz timer
|
||||
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
|
||||
c_obj.clock();
|
||||
counter = 0;
|
||||
} else {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
// Sample clock
|
||||
c_obj.sample_clock();
|
||||
|
||||
// Get sample
|
||||
c_obj.sample()
|
||||
} {
|
||||
let samps = [[s1, s2]];
|
||||
playback.write(&samps[..]);
|
||||
} else {
|
||||
let samps = [[128, 128]];
|
||||
playback.write(&samps[..]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sample clock
|
||||
c_obj.sample_clock();
|
||||
|
||||
// Get sample
|
||||
c_obj.sample()
|
||||
};
|
||||
let samps = [[s1, s2]];
|
||||
playback.write(&samps[..]);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
} else {
|
||||
println!("Sound will be redirected to /dev/null ;)");
|
||||
self.handle = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,7 +434,11 @@ impl Sound {
|
||||
self.channel3.sample_clock();
|
||||
}
|
||||
|
||||
pub fn sample(&self) -> (u8, u8) {
|
||||
pub fn sample(&self) -> Option<(u8, u8)> {
|
||||
if self.enabled == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Some basic mixing
|
||||
// Output value
|
||||
let mut s = (0, 0);
|
||||
@ -442,47 +459,55 @@ impl Sound {
|
||||
let c1_sample = self.channel1.sample();
|
||||
let c2_sample = self.channel2.sample();
|
||||
let c3_sample = self.channel3.sample();
|
||||
let c4_sample = 0;
|
||||
let c4_sample = 128;
|
||||
|
||||
if self.enabled != 0 {
|
||||
if self.sound_output_terminal_selector & 1 > 0 {
|
||||
// Output Channel1
|
||||
s.0 += c1_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 2 > 0 {
|
||||
// Output Channel2
|
||||
s.0 += c2_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 4 > 0 {
|
||||
// Output Channel3
|
||||
s.0 += c3_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 8 > 0 {
|
||||
// Output Channel4
|
||||
s.0 += c4_sample;
|
||||
}
|
||||
|
||||
if self.sound_output_terminal_selector & 0x10 > 0 {
|
||||
// Output Channel1
|
||||
s.1 += c1_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x20 > 0 {
|
||||
// Output Channel2
|
||||
s.1 += c2_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x40 > 0 {
|
||||
// Output Channel3
|
||||
s.1 += c3_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x80 > 0 {
|
||||
// Output Channel4
|
||||
s.1 += c4_sample;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 1 > 0 {
|
||||
// Output Channel1
|
||||
s.0 += i16::try_from(c1_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 2 > 0 {
|
||||
// Output Channel2
|
||||
s.0 += i16::try_from(c2_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 4 > 0 {
|
||||
// Output Channel3
|
||||
s.0 += i16::try_from(c3_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 8 > 0 {
|
||||
// Output Channel4
|
||||
s.0 += i16::try_from(c4_sample).unwrap() - 128;
|
||||
}
|
||||
|
||||
s.0 *= s01_volume + 1;
|
||||
s.1 *= s02_volume + 1;
|
||||
s
|
||||
if self.sound_output_terminal_selector & 0x10 > 0 {
|
||||
// Output Channel1
|
||||
s.1 += i16::try_from(c1_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x20 > 0 {
|
||||
// Output Channel2
|
||||
s.1 += i16::try_from(c2_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x40 > 0 {
|
||||
// Output Channel3
|
||||
s.1 += i16::try_from(c3_sample).unwrap() - 128;
|
||||
}
|
||||
if self.sound_output_terminal_selector & 0x80 > 0 {
|
||||
// Output Channel4
|
||||
s.1 += i16::try_from(c4_sample).unwrap() - 128;
|
||||
}
|
||||
|
||||
// TODO: This is too loud (overflowing u8) for some reason, so we're
|
||||
// backing off a little bit from this.
|
||||
/*
|
||||
if s.0 > 30 || s.1 > 30 {
|
||||
println!("Would overflow! s={:?} s1vol={} s2vol={}", s, s01_volume, s02_volume);
|
||||
}
|
||||
*/
|
||||
//s.0 *= i16::try_from(s01_volume).unwrap(); // + 1;
|
||||
//s.1 *= i16::try_from(s02_volume).unwrap(); // + 1;
|
||||
|
||||
let s = (128 + s.0, 128 + s.1);
|
||||
let s = (u8::try_from(s.0).unwrap(), u8::try_from(s.1).unwrap());
|
||||
Some(s)
|
||||
}
|
||||
|
||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||
|
||||
@ -6,11 +6,11 @@ use crate::sound::OUTPUT_SAMPLE_RATE;
|
||||
struct SamplePair(u8);
|
||||
|
||||
impl SamplePair {
|
||||
fn first(&self) -> u8 {
|
||||
fn first(self) -> u8 {
|
||||
self.0 >> 4
|
||||
}
|
||||
|
||||
fn second(&self) -> u8 {
|
||||
fn second(self) -> u8 {
|
||||
self.0 & 0x0F
|
||||
}
|
||||
}
|
||||
@ -66,11 +66,11 @@ impl WaveGenerator {
|
||||
|
||||
let sample = &self.samples[position / 2];
|
||||
match position % 2 {
|
||||
0 => sample.first(),
|
||||
_ => sample.second(),
|
||||
0 => sample.first() + 120,
|
||||
_ => sample.second() + 120,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
128
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user