mirror of
https://github.com/FranLMSP/rmg-001.git
synced 2024-11-23 10:12:11 +00:00
Compare commits
3 Commits
060dbde966
...
3f2824714f
Author | SHA1 | Date | |
---|---|---|---|
3f2824714f | |||
c109346dcf | |||
d6ef80afe4 |
29
src/bus.rs
29
src/bus.rs
@ -1,6 +1,7 @@
|
|||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use crate::utils::join_bytes;
|
use crate::utils::join_bytes;
|
||||||
use crate::rom::{ROM, load_rom};
|
use crate::rom::{ROM, load_rom};
|
||||||
|
use crate::ram::{RAM, DMGRAM, CGBRAM, WRAM_BANK_SELECT_ADDRESS};
|
||||||
use crate::ppu::{
|
use crate::ppu::{
|
||||||
PPU,
|
PPU,
|
||||||
DMA_ADDRESS,
|
DMA_ADDRESS,
|
||||||
@ -29,6 +30,7 @@ pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE;
|
|||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
data: [u8; 0x10000],
|
data: [u8; 0x10000],
|
||||||
pub rom: Box<dyn ROM>,
|
pub rom: Box<dyn ROM>,
|
||||||
|
pub ram: Box<dyn RAM>,
|
||||||
pub ppu: PPU,
|
pub ppu: PPU,
|
||||||
pub joypad: Joypad,
|
pub joypad: Joypad,
|
||||||
pub timer: Timer,
|
pub timer: Timer,
|
||||||
@ -51,10 +53,16 @@ impl Bus {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let info = rom.info().clone();
|
||||||
|
let cgb_mode = info.cgb_features() || info.cgb_only();
|
||||||
let mut bus = Self {
|
let mut bus = Self {
|
||||||
data: [0x00; 0x10000],
|
data: [0x00; 0x10000],
|
||||||
rom,
|
rom,
|
||||||
ppu: PPU::new(),
|
ram: match cgb_mode {
|
||||||
|
true => Box::new(CGBRAM::new()),
|
||||||
|
false => Box::new(DMGRAM::new()),
|
||||||
|
},
|
||||||
|
ppu: PPU::new(cgb_mode),
|
||||||
joypad: Joypad::new(),
|
joypad: Joypad::new(),
|
||||||
timer: Timer::new(),
|
timer: Timer::new(),
|
||||||
sound: Sound::new(),
|
sound: Sound::new(),
|
||||||
@ -90,10 +98,14 @@ impl Bus {
|
|||||||
pub fn read(&self, address: u16) -> u8 {
|
pub fn read(&self, address: u16) -> u8 {
|
||||||
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
|
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
|
||||||
return self.rom.read(address);
|
return self.rom.read(address);
|
||||||
|
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return self.ram.read(address);
|
||||||
|
} else if ECHO_RAM.contains(&address) {
|
||||||
|
return self.ram.read(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF));
|
||||||
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
||||||
return self.interrupts.read(address);
|
return self.interrupts.read(address);
|
||||||
} else if VIDEO_RAM.contains(&address) {
|
} else if VIDEO_RAM.contains(&address) {
|
||||||
return self.ppu.read_vram(address);
|
return self.ppu.read_vram_external(address);
|
||||||
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
||||||
return self.ppu.read_oam(address);
|
return self.ppu.read_oam(address);
|
||||||
} else if PPU::is_io_register(address) {
|
} else if PPU::is_io_register(address) {
|
||||||
@ -121,17 +133,12 @@ impl Bus {
|
|||||||
self.rom.write(address, data);
|
self.rom.write(address, data);
|
||||||
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
||||||
self.interrupts.write(address, data);
|
self.interrupts.write(address, data);
|
||||||
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) {
|
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
self.data[address as usize] = data;
|
self.ram.write(address, data);
|
||||||
// Copy to the ECHO RAM
|
|
||||||
if address <= 0xDDFF {
|
|
||||||
self.data[(ECHO_RAM.min().unwrap() + (address - WORK_RAM_1.min().unwrap())) as usize] = data;
|
|
||||||
}
|
|
||||||
} else if EXTERNAL_RAM.contains(&address) {
|
} else if EXTERNAL_RAM.contains(&address) {
|
||||||
self.rom.write(address, data);
|
self.rom.write(address, data);
|
||||||
} else if ECHO_RAM.contains(&address) {
|
} else if ECHO_RAM.contains(&address) {
|
||||||
self.data[address as usize] = data;
|
self.ram.write(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF), data);
|
||||||
self.data[(WORK_RAM_1.min().unwrap() + (address - ECHO_RAM.min().unwrap())) as usize] = data; // Copy to the working RAM
|
|
||||||
} else if Timer::is_io_register(address) {
|
} else if Timer::is_io_register(address) {
|
||||||
self.timer.set_register(address, data);
|
self.timer.set_register(address, data);
|
||||||
} else if Sound::is_io_register(address) {
|
} else if Sound::is_io_register(address) {
|
||||||
@ -140,7 +147,7 @@ impl Bus {
|
|||||||
let byte = self.data[address as usize];
|
let byte = self.data[address as usize];
|
||||||
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
|
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
|
||||||
} else if VIDEO_RAM.contains(&address) {
|
} else if VIDEO_RAM.contains(&address) {
|
||||||
return self.ppu.write_vram(address, data);
|
return self.ppu.write_vram_external(address, data);
|
||||||
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
||||||
return self.ppu.write_oam(address, data);
|
return self.ppu.write_oam(address, data);
|
||||||
} else if address == DMA_ADDRESS {
|
} else if address == DMA_ADDRESS {
|
||||||
|
@ -4,6 +4,7 @@ pub mod ppu;
|
|||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod rom;
|
pub mod rom;
|
||||||
|
pub mod ram;
|
||||||
pub mod bus;
|
pub mod bus;
|
||||||
pub mod interrupts;
|
pub mod interrupts;
|
||||||
pub mod joypad;
|
pub mod joypad;
|
||||||
|
119
src/ppu.rs
119
src/ppu.rs
@ -26,6 +26,7 @@ pub const OBJECT_PALETTE_0_ADDRESS: u16 = 0xFF48;
|
|||||||
pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49;
|
pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49;
|
||||||
pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A;
|
pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A;
|
||||||
pub const WINDOW_X_ADDRESS: u16 = 0xFF4B;
|
pub const WINDOW_X_ADDRESS: u16 = 0xFF4B;
|
||||||
|
pub const VRAM_BANK_SELECT_ADDRESS: u16 = 0xFF4F;
|
||||||
|
|
||||||
pub const TILE_MAP_ADDRESS: u16 = 0x9800;
|
pub const TILE_MAP_ADDRESS: u16 = 0x9800;
|
||||||
|
|
||||||
@ -126,6 +127,26 @@ pub enum LCDStatus {
|
|||||||
ModeFlag(LCDStatusModeFlag),
|
ModeFlag(LCDStatusModeFlag),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BgAttributes {
|
||||||
|
bg_to_oam_priority: bool,
|
||||||
|
vertical_flip: bool,
|
||||||
|
horizontal_flip: bool,
|
||||||
|
vram_bank: u8,
|
||||||
|
palette_number: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BgAttributes {
|
||||||
|
pub fn new(byte: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
bg_to_oam_priority: get_bit(byte, BitIndex::I7),
|
||||||
|
vertical_flip: get_bit(byte, BitIndex::I6),
|
||||||
|
horizontal_flip: get_bit(byte, BitIndex::I5),
|
||||||
|
vram_bank: get_bit(byte, BitIndex::I3) as u8,
|
||||||
|
palette_number: byte & 0b111,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Sprite {
|
struct Sprite {
|
||||||
x: u8,
|
x: u8,
|
||||||
y: u8,
|
y: u8,
|
||||||
@ -136,6 +157,8 @@ struct Sprite {
|
|||||||
y_flip: bool,
|
y_flip: bool,
|
||||||
over_bg: bool,
|
over_bg: bool,
|
||||||
bit_pixels: Option<[u8; 8]>,
|
bit_pixels: Option<[u8; 8]>,
|
||||||
|
vram_bank: u8,
|
||||||
|
palette_number: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl Sprite {
|
||||||
@ -229,13 +252,15 @@ pub struct PPU {
|
|||||||
scroll_y: u8,
|
scroll_y: u8,
|
||||||
window_x: u8,
|
window_x: u8,
|
||||||
window_y: u8,
|
window_y: u8,
|
||||||
io_registers: [u8; 12],
|
io_registers: [u8; 16],
|
||||||
vram: [u8; 0x2000],
|
vram: [u8; 0x2000 * 2],
|
||||||
oam: [u8; 0xA0],
|
oam: [u8; 0xA0],
|
||||||
|
vram_bank: u8,
|
||||||
|
cgb_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PPU {
|
impl PPU {
|
||||||
pub fn new() -> Self {
|
pub fn new(cgb_mode: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: false,
|
state: false,
|
||||||
background_priority: false,
|
background_priority: false,
|
||||||
@ -256,21 +281,41 @@ impl PPU {
|
|||||||
scroll_y: 0,
|
scroll_y: 0,
|
||||||
window_x: 0,
|
window_x: 0,
|
||||||
window_y: 0,
|
window_y: 0,
|
||||||
io_registers: [0; 12],
|
io_registers: [0; 16],
|
||||||
vram: [0; 0x2000],
|
vram: [0; 0x2000 * 2],
|
||||||
oam: [0; 0xA0],
|
oam: [0; 0xA0],
|
||||||
|
vram_bank: 0,
|
||||||
|
cgb_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_io_register(address: u16) -> bool {
|
pub fn set_vram_bank(&mut self, bank: u8) {
|
||||||
address >= 0xFF40 && address <= 0xFF4B
|
if self.cgb_mode {
|
||||||
|
self.vram_bank = bank & 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_vram(&self, address: u16) -> u8 {
|
pub fn get_vram_bank(&self) -> u8 {
|
||||||
|
self.vram_bank | 0xFE
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_io_register(address: u16) -> bool {
|
||||||
|
address >= 0xFF40 && address <= 0xFF4F
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_vram_external(&self, address: u16) -> u8 {
|
||||||
|
self.vram[((address + (0x2000 * self.vram_bank as u16)) - 0x8000) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_vram_external(&mut self, address: u16, data: u8) {
|
||||||
|
self.vram[((address + (0x2000 * self.vram_bank as u16)) - 0x8000) as usize] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_vram(&self, address: u16) -> u8 {
|
||||||
self.vram[(address - 0x8000) as usize]
|
self.vram[(address - 0x8000) as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_vram(&mut self, address: u16, data: u8) {
|
fn write_vram(&mut self, address: u16, data: u8) {
|
||||||
self.vram[(address - 0x8000) as usize] = data;
|
self.vram[(address - 0x8000) as usize] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +328,9 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_register(&self, address: u16) -> u8 {
|
pub fn get_register(&self, address: u16) -> u8 {
|
||||||
if address == LCD_CONTROL_ADDRESS {
|
if address == VRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return self.get_vram_bank();
|
||||||
|
} else if address == LCD_CONTROL_ADDRESS {
|
||||||
return self.lcd_control;
|
return self.lcd_control;
|
||||||
} else if address == LCD_Y_ADDRESS {
|
} else if address == LCD_Y_ADDRESS {
|
||||||
return self.lcd_y;
|
return self.lcd_y;
|
||||||
@ -292,7 +339,9 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_register(&mut self, address: u16, data: u8) {
|
pub fn set_register(&mut self, address: u16, data: u8) {
|
||||||
if address == LCD_Y_ADDRESS {
|
if address == VRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return self.set_vram_bank(data);
|
||||||
|
} else if address == LCD_Y_ADDRESS {
|
||||||
return;
|
return;
|
||||||
} else if address == LCD_CONTROL_ADDRESS {
|
} else if address == LCD_CONTROL_ADDRESS {
|
||||||
self.lcd_control = data;
|
self.lcd_control = data;
|
||||||
@ -465,11 +514,18 @@ impl PPU {
|
|||||||
y_flip: get_bit(attributes, BitIndex::I6),
|
y_flip: get_bit(attributes, BitIndex::I6),
|
||||||
over_bg: get_bit(attributes, BitIndex::I7),
|
over_bg: get_bit(attributes, BitIndex::I7),
|
||||||
bit_pixels: None,
|
bit_pixels: None,
|
||||||
|
vram_bank: match self.cgb_mode && get_bit(attributes, BitIndex::I3) {
|
||||||
|
true => 1,
|
||||||
|
false => 0,
|
||||||
|
},
|
||||||
|
palette_number: attributes & 0b111,
|
||||||
});
|
});
|
||||||
|
|
||||||
addr += 4;
|
addr += 4;
|
||||||
}
|
}
|
||||||
self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x()));
|
if !self.cgb_mode {
|
||||||
|
self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_sprite_pixel(&mut self) -> Option<(Pixel, bool)> {
|
fn find_sprite_pixel(&mut self) -> Option<(Pixel, bool)> {
|
||||||
@ -522,12 +578,17 @@ impl PPU {
|
|||||||
self.force_set_register(LCD_STATUS_ADDRESS, byte);
|
self.force_set_register(LCD_STATUS_ADDRESS, byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (u8, u8) {
|
fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (u8, u8, BgAttributes) {
|
||||||
let index_x = x as u16 / 8;
|
let index_x = x as u16 / 8;
|
||||||
let index_y = (y as u16 / 8) * 32;
|
let index_y = (y as u16 / 8) * 32;
|
||||||
let index = index_x + index_y;
|
let index = index_x + index_y;
|
||||||
let tile_line = (y).rem_euclid(8) * 2;
|
|
||||||
let tile_number = self.read_vram(tilemap_area + index as u16) as u16;
|
let tile_number = self.read_vram(tilemap_area + index as u16) as u16;
|
||||||
|
let mut tile_line = (y).rem_euclid(8);
|
||||||
|
let attributes = BgAttributes::new(self.read_vram(tilemap_area + 0x2000 + index as u16));
|
||||||
|
if self.cgb_mode && attributes.vertical_flip {
|
||||||
|
tile_line = 7 - tile_line;
|
||||||
|
}
|
||||||
|
tile_line = tile_line * 2;
|
||||||
let addr = if default_method {
|
let addr = if default_method {
|
||||||
0x8000 + tile_line as u16 + (tile_number * 16)
|
0x8000 + tile_line as u16 + (tile_number * 16)
|
||||||
} else {
|
} else {
|
||||||
@ -537,7 +598,31 @@ impl PPU {
|
|||||||
(base + tile_line + (tile_number * 16)) as u16
|
(base + tile_line + (tile_number * 16)) as u16
|
||||||
};
|
};
|
||||||
|
|
||||||
(self.read_vram(addr), self.read_vram(addr + 1))
|
if !self.cgb_mode {
|
||||||
|
return (self.read_vram(addr), self.read_vram(addr + 1), attributes);
|
||||||
|
}
|
||||||
|
let byte1 = self.read_vram(addr + (0x2000 * (attributes.vram_bank as u16)));
|
||||||
|
let byte2 = self.read_vram(addr + (0x2000 * (attributes.vram_bank as u16)) + 1);
|
||||||
|
if attributes.horizontal_flip {
|
||||||
|
let byte1 = ((byte1 >> 7) & 0b1) |
|
||||||
|
((byte1 >> 5) & 0b10) |
|
||||||
|
((byte1 >> 3) & 0b100) |
|
||||||
|
((byte1 >> 1) & 0b1000) |
|
||||||
|
((byte1 << 1) & 0b10000) |
|
||||||
|
((byte1 << 3) & 0b100000) |
|
||||||
|
((byte1 << 5) & 0b1000000) |
|
||||||
|
((byte1 << 7) & 0b10000000);
|
||||||
|
let byte2 = ((byte2 >> 7) & 0b1) |
|
||||||
|
((byte2 >> 5) & 0b10) |
|
||||||
|
((byte2 >> 3) & 0b100) |
|
||||||
|
((byte2 >> 1) & 0b1000) |
|
||||||
|
((byte2 << 1) & 0b10000) |
|
||||||
|
((byte2 << 3) & 0b100000) |
|
||||||
|
((byte2 << 5) & 0b1000000) |
|
||||||
|
((byte2 << 7) & 0b10000000);
|
||||||
|
return (byte1, byte2, attributes);
|
||||||
|
}
|
||||||
|
return (byte1, byte2, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_window_pixel(&mut self) -> Option<Pixel> {
|
fn get_window_pixel(&mut self) -> Option<Pixel> {
|
||||||
@ -577,7 +662,7 @@ impl PPU {
|
|||||||
true => 0x9C00,
|
true => 0x9C00,
|
||||||
false => 0x9800,
|
false => 0x9800,
|
||||||
};
|
};
|
||||||
let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
let (tile_byte_1, tile_byte_2, _) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
||||||
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
||||||
self.current_window_pixels = Some(bit_pixels_array);
|
self.current_window_pixels = Some(bit_pixels_array);
|
||||||
|
|
||||||
@ -614,7 +699,7 @@ impl PPU {
|
|||||||
true => 0x9C00,
|
true => 0x9C00,
|
||||||
false => 0x9800,
|
false => 0x9800,
|
||||||
};
|
};
|
||||||
let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
let (tile_byte_1, tile_byte_2, _) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
||||||
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
||||||
self.current_background_pixels = Some(bit_pixels_array);
|
self.current_background_pixels = Some(bit_pixels_array);
|
||||||
|
|
||||||
|
80
src/ram.rs
Normal file
80
src/ram.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
pub const WRAM_BANK_SELECT_ADDRESS: u16 = 0xFF70;
|
||||||
|
|
||||||
|
pub trait RAM {
|
||||||
|
fn read(&self, address: u16) -> u8;
|
||||||
|
fn write(&mut self, address: u16, value: u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DMGRAM {
|
||||||
|
data: [u8; 4096 * 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DMGRAM {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [0; 4096 * 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RAM for DMGRAM {
|
||||||
|
fn read(&self, address: u16) -> u8 {
|
||||||
|
if address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
self.data[(address - 0xC000) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, address: u16, value: u8) {
|
||||||
|
if address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.data[(address - 0xC000) as usize] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct CGBRAM {
|
||||||
|
data: [u8; 4096 * 8],
|
||||||
|
bank: u8,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CGBRAM {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [0; 4096 * 8],
|
||||||
|
bank: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_bank(&mut self, bank: u8) {
|
||||||
|
self.bank = bank;
|
||||||
|
if self.bank > 7 {
|
||||||
|
self.bank = 7;
|
||||||
|
} else if bank == 0 {
|
||||||
|
self.bank = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RAM for CGBRAM {
|
||||||
|
fn read(&self, address: u16) -> u8 {
|
||||||
|
if address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return self.bank;
|
||||||
|
}
|
||||||
|
if address <= 0xCFFF {
|
||||||
|
return self.data[(address - 0xC000) as usize];
|
||||||
|
}
|
||||||
|
self.data[((address - 0xC000) as usize) * (self.bank as usize)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, address: u16, value: u8) {
|
||||||
|
if address == WRAM_BANK_SELECT_ADDRESS {
|
||||||
|
return self.switch_bank(value);
|
||||||
|
} else if address <= 0xCFFF {
|
||||||
|
return self.data[(address - 0xC000) as usize] = value;
|
||||||
|
}
|
||||||
|
self.data[((address - 0xC000) as usize) * (self.bank as usize)] = value;
|
||||||
|
}
|
||||||
|
}
|
11
src/rom.rs
11
src/rom.rs
@ -41,6 +41,7 @@ pub fn load_rom(_filename: &str) -> std::io::Result<Box<dyn ROM>> {
|
|||||||
filename: "".to_string(),
|
filename: "".to_string(),
|
||||||
publisher: "".to_string(),
|
publisher: "".to_string(),
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
|
cgb_features: false,
|
||||||
cgb_only: false,
|
cgb_only: false,
|
||||||
sgb_features: false,
|
sgb_features: false,
|
||||||
has_ram: false,
|
has_ram: false,
|
||||||
@ -150,6 +151,7 @@ pub struct ROMInfo {
|
|||||||
filename: String,
|
filename: String,
|
||||||
publisher: String,
|
publisher: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
cgb_features: bool,
|
||||||
cgb_only: bool,
|
cgb_only: bool,
|
||||||
sgb_features: bool,
|
sgb_features: bool,
|
||||||
has_ram: bool,
|
has_ram: bool,
|
||||||
@ -161,6 +163,14 @@ pub struct ROMInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ROMInfo {
|
impl ROMInfo {
|
||||||
|
pub fn cgb_features(&self) -> bool {
|
||||||
|
self.cgb_features
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cgb_only(&self) -> bool {
|
||||||
|
self.cgb_only
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_filename(&mut self, filename: String) {
|
pub fn set_filename(&mut self, filename: String) {
|
||||||
self.filename = filename;
|
self.filename = filename;
|
||||||
}
|
}
|
||||||
@ -205,6 +215,7 @@ impl ROMInfo {
|
|||||||
},
|
},
|
||||||
publisher: "".to_string(), // TODO: Extract publisher
|
publisher: "".to_string(), // TODO: Extract publisher
|
||||||
title: "".to_string(), // TODO: Extract the game title
|
title: "".to_string(), // TODO: Extract the game title
|
||||||
|
cgb_features: bytes[CGB_FLAG_ADDRESS as usize] == 0x80,
|
||||||
cgb_only: bytes[CGB_FLAG_ADDRESS as usize] == 0xC0,
|
cgb_only: bytes[CGB_FLAG_ADDRESS as usize] == 0xC0,
|
||||||
sgb_features: bytes[SGB_FLAG_ADDRESS as usize] == 0x03,
|
sgb_features: bytes[SGB_FLAG_ADDRESS as usize] == 0x03,
|
||||||
has_ram: match rom_type {
|
has_ram: match rom_type {
|
||||||
|
28
src/sound.rs
28
src/sound.rs
@ -1,7 +1,7 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use cpal::{Stream, StreamConfig, Device, Sample};
|
use cpal::{Stream, StreamConfig, Device, Sample, SampleRate};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use crate::cpu::Cycles;
|
use crate::cpu::Cycles;
|
||||||
use crate::utils::join_bytes;
|
use crate::utils::join_bytes;
|
||||||
@ -34,6 +34,8 @@ pub const NR52_ADDRESS: u16 = 0xFF26;
|
|||||||
|
|
||||||
pub const WAVE_PATTERN_RAM: RangeInclusive<u16> = 0xFF30..=0xFF3F;
|
pub const WAVE_PATTERN_RAM: RangeInclusive<u16> = 0xFF30..=0xFF3F;
|
||||||
|
|
||||||
|
pub const SAMPLE_RATE: u32 = 48000;
|
||||||
|
|
||||||
const WAVE_DUTY_PATTERNS: [[u8; 8]; 4] = [
|
const WAVE_DUTY_PATTERNS: [[u8; 8]; 4] = [
|
||||||
[0, 0, 0, 0, 0, 0, 0, 1],
|
[0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
[0, 0, 0, 0, 0, 0, 1, 1],
|
[0, 0, 0, 0, 0, 0, 1, 1],
|
||||||
@ -48,25 +50,15 @@ struct ChannelTwo {
|
|||||||
frequency_timer: u16,
|
frequency_timer: u16,
|
||||||
duty_position: usize,
|
duty_position: usize,
|
||||||
sample_timer: usize,
|
sample_timer: usize,
|
||||||
sample_rate: usize,
|
|
||||||
buffer_pos: usize,
|
buffer_pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelTwo {
|
impl ChannelTwo {
|
||||||
pub fn new(device: &Device, config: &StreamConfig, sample_rate: usize) -> Self {
|
pub fn new(device: &Device, config: &StreamConfig) -> Self {
|
||||||
let mut count: usize = 0;
|
let mut count: usize = 0;
|
||||||
// let mut count: f32 = 0.0;
|
let buffer = Arc::new(Mutex::new(vec![0.0; SAMPLE_RATE as usize]));
|
||||||
let buffer = Arc::new(Mutex::new(vec![0.0; sample_rate]));
|
|
||||||
let buffer_clone = buffer.clone();
|
let buffer_clone = buffer.clone();
|
||||||
let stream = device.build_output_stream(&config, move |data: &mut [f32], _| {
|
let stream = device.build_output_stream(&config, move |data: &mut [f32], _| {
|
||||||
/* for sample in data.iter_mut() {
|
|
||||||
let y: f32 = ((count / (sample_rate as f32)) * 440.0 * 2.0 * 3.14159).sin().clamp(-1.0, 1.0);
|
|
||||||
*sample = Sample::from(&y);
|
|
||||||
count += 1.0;
|
|
||||||
if count >= sample_rate as f32 {
|
|
||||||
count = 0.0;
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
let b = buffer_clone.lock().unwrap();
|
let b = buffer_clone.lock().unwrap();
|
||||||
for sample in data.iter_mut() {
|
for sample in data.iter_mut() {
|
||||||
*sample = Sample::from(&b[count]);
|
*sample = Sample::from(&b[count]);
|
||||||
@ -84,7 +76,6 @@ impl ChannelTwo {
|
|||||||
duty_position: 0,
|
duty_position: 0,
|
||||||
sample_timer: 0,
|
sample_timer: 0,
|
||||||
buffer_pos: 0,
|
buffer_pos: 0,
|
||||||
sample_rate,
|
|
||||||
buffer,
|
buffer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,10 +105,10 @@ impl ChannelTwo {
|
|||||||
self.duty_position = 0;
|
self.duty_position = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.sample_timer = self.sample_timer.saturating_add(self.sample_rate);
|
self.sample_timer = self.sample_timer.saturating_add(SAMPLE_RATE as usize);
|
||||||
if self.sample_timer >= 4194304 {
|
if self.sample_timer >= 4194304 {
|
||||||
self.update_buffer(duty);
|
self.update_buffer(duty);
|
||||||
self.sample_timer = 0;
|
self.sample_timer -= 4194304;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,13 +139,12 @@ impl Sound {
|
|||||||
.expect("error while querying configs");
|
.expect("error while querying configs");
|
||||||
let supported_config = supported_configs_range.next()
|
let supported_config = supported_configs_range.next()
|
||||||
.expect("no supported config?!")
|
.expect("no supported config?!")
|
||||||
.with_max_sample_rate();
|
.with_sample_rate(SampleRate(SAMPLE_RATE));
|
||||||
// let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err);
|
// let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err);
|
||||||
let sample_rate = supported_config.sample_rate().0;
|
|
||||||
let config: StreamConfig = supported_config.into();
|
let config: StreamConfig = supported_config.into();
|
||||||
return Self {
|
return Self {
|
||||||
io_registers: [0; 48],
|
io_registers: [0; 48],
|
||||||
channel_two: Some(ChannelTwo::new(&device, &config, sample_rate as usize)),
|
channel_two: Some(ChannelTwo::new(&device, &config)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user