use crate::{
apu::PULSE_TABLE,
cart::Cart,
common::{Clock, Regional, Reset, ResetKind, Sample, Sram},
mapper::{self, Map, Mapper, vrc_irq::VrcIrq},
mem::{Banks, Memory},
ppu::{CIRam, Mirroring},
};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[must_use]
pub enum Revision {
#[default]
A,
B,
}
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Regs {
pub banking_mode: u8,
pub prg: [usize; 4],
pub chr: [usize; 8],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Vrc6 {
pub chr_rom: Memory<Box<[u8]>>,
pub prg_rom: Memory<Box<[u8]>>,
pub prg_ram: Memory<Box<[u8]>>,
pub regs: Regs,
pub revision: Revision,
pub mirroring: Mirroring,
pub irq: VrcIrq,
pub audio: Audio,
pub nt_banks: [usize; 4],
pub chr_banks: Banks,
pub prg_ram_banks: Banks,
pub prg_rom_banks: Banks,
}
impl Vrc6 {
const PRG_RAM_SIZE: usize = 8 * 1024;
const PRG_WINDOW: usize = 8 * 1024;
const CHR_WINDOW: usize = 1024;
pub fn load(
cart: &Cart,
chr_rom: Memory<Box<[u8]>>,
prg_rom: Memory<Box<[u8]>>,
revision: Revision,
) -> Result<Mapper, mapper::Error> {
let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);
let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;
let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_RAM_SIZE)?;
let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;
let mut vrc6 = Self {
chr_rom,
prg_rom,
prg_ram,
regs: Regs::default(),
revision,
mirroring: cart.mirroring(),
irq: VrcIrq::default(),
audio: Audio::new(),
nt_banks: [0; 4],
chr_banks,
prg_ram_banks,
prg_rom_banks,
};
vrc6.prg_rom_banks.set(3, vrc6.prg_rom_banks.last());
Ok(vrc6.into())
}
#[inline(always)]
#[must_use]
pub const fn prg_ram_enabled(&self) -> bool {
self.regs.banking_mode & 0x80 == 0x80
}
pub fn set_nametables(&mut self, nametables: &[usize]) {
for (bank, page) in nametables.iter().enumerate() {
self.set_nametable_page(bank, *page);
}
}
pub fn set_mirroring(&mut self, mirroring: Mirroring) {
self.mirroring = mirroring;
match self.mirroring {
Mirroring::Vertical => self.set_nametables(&[0, 1, 0, 1]),
Mirroring::Horizontal => self.set_nametables(&[0, 0, 1, 1]),
Mirroring::SingleScreenA => self.set_nametables(&[0, 0, 0, 0]),
Mirroring::SingleScreenB => self.set_nametables(&[1, 1, 1, 1]),
Mirroring::FourScreen => self.set_nametables(&[0, 1, 2, 3]),
}
}
pub const fn set_nametable_page(&mut self, bank: usize, page: usize) {
self.nt_banks[bank] = page;
}
pub fn update_chr_banks(&mut self) {
let (mask, or_mask) = if self.regs.banking_mode & 0x20 == 0x20 {
(0xFE, 1)
} else {
(0xFF, 0)
};
match self.regs.banking_mode & 0x03 {
0 => {
self.chr_banks.set(0, self.regs.chr[0]);
self.chr_banks.set(1, self.regs.chr[1]);
self.chr_banks.set(2, self.regs.chr[2]);
self.chr_banks.set(3, self.regs.chr[3]);
self.chr_banks.set(4, self.regs.chr[4]);
self.chr_banks.set(5, self.regs.chr[5]);
self.chr_banks.set(6, self.regs.chr[6]);
self.chr_banks.set(7, self.regs.chr[7]);
}
1 => {
self.chr_banks.set(0, self.regs.chr[0] & mask);
self.chr_banks.set(1, (self.regs.chr[0] & mask) | or_mask);
self.chr_banks.set(2, self.regs.chr[1] & mask);
self.chr_banks.set(3, (self.regs.chr[1] & mask) | or_mask);
self.chr_banks.set(4, self.regs.chr[2] & mask);
self.chr_banks.set(5, (self.regs.chr[2] & mask) | or_mask);
self.chr_banks.set(6, self.regs.chr[3] & mask);
self.chr_banks.set(7, (self.regs.chr[3] & mask) | or_mask);
}
_ => {
self.chr_banks.set(0, self.regs.chr[0]);
self.chr_banks.set(1, self.regs.chr[1]);
self.chr_banks.set(2, self.regs.chr[2]);
self.chr_banks.set(3, self.regs.chr[3]);
self.chr_banks.set(4, self.regs.chr[4] & mask);
self.chr_banks.set(5, (self.regs.chr[4] & mask) | or_mask);
self.chr_banks.set(6, self.regs.chr[5] & mask);
self.chr_banks.set(7, (self.regs.chr[5] & mask) | or_mask);
}
}
if self.regs.banking_mode & 0x10 == 0x10 {
self.set_mirroring(Mirroring::FourScreen);
match self.regs.banking_mode & 0x2F {
0x20 | 0x27 => {
self.set_nametable_page(0, self.regs.chr[6] & 0xFE);
self.set_nametable_page(1, (self.regs.chr[6] & 0xFE) | 1);
self.set_nametable_page(2, self.regs.chr[7] & 0xFE);
self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);
}
0x23 | 0x24 => {
self.set_nametable_page(0, self.regs.chr[6] & 0xFE);
self.set_nametable_page(1, self.regs.chr[7] & 0xFE);
self.set_nametable_page(2, (self.regs.chr[6] & 0xFE) | 1);
self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);
}
0x28 | 0x2F => {
self.set_nametable_page(0, self.regs.chr[6] & 0xFE);
self.set_nametable_page(1, self.regs.chr[6] & 0xFE);
self.set_nametable_page(2, self.regs.chr[7] & 0xFE);
self.set_nametable_page(3, self.regs.chr[7] & 0xFE);
}
0x2B | 0x2C => {
self.set_nametable_page(0, (self.regs.chr[6] & 0xFE) | 1);
self.set_nametable_page(1, (self.regs.chr[7] & 0xFE) | 1);
self.set_nametable_page(2, (self.regs.chr[6] & 0xFE) | 1);
self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);
}
_ => match self.regs.banking_mode & 0x07 {
0 | 6 | 7 => {
self.set_nametable_page(0, self.regs.chr[6]);
self.set_nametable_page(1, self.regs.chr[6]);
self.set_nametable_page(2, self.regs.chr[7]);
self.set_nametable_page(3, self.regs.chr[7]);
}
1 | 5 => {
self.set_nametable_page(0, self.regs.chr[4]);
self.set_nametable_page(1, self.regs.chr[5]);
self.set_nametable_page(2, self.regs.chr[6]);
self.set_nametable_page(3, self.regs.chr[7]);
}
_ => {
self.set_nametable_page(0, self.regs.chr[6]);
self.set_nametable_page(1, self.regs.chr[7]);
self.set_nametable_page(2, self.regs.chr[6]);
self.set_nametable_page(3, self.regs.chr[7]);
}
},
}
} else {
match self.regs.banking_mode & 0x2F {
0x20 | 0x27 => self.set_mirroring(Mirroring::Vertical),
0x23 | 0x24 => self.set_mirroring(Mirroring::Horizontal),
0x28 | 0x2F => self.set_mirroring(Mirroring::SingleScreenA),
0x2B | 0x2C => self.set_mirroring(Mirroring::SingleScreenB),
_ => {
self.set_mirroring(Mirroring::FourScreen);
match self.regs.banking_mode & 0x07 {
0 | 6 | 7 => {
self.set_nametable_page(0, self.regs.chr[6] & 0x01);
self.set_nametable_page(1, self.regs.chr[6] & 0x01);
self.set_nametable_page(2, self.regs.chr[7] & 0x01);
self.set_nametable_page(3, self.regs.chr[7] & 0x01);
}
1 | 5 => {
self.set_nametable_page(0, self.regs.chr[4] & 0x01);
self.set_nametable_page(1, self.regs.chr[5] & 0x01);
self.set_nametable_page(2, self.regs.chr[6] & 0x01);
self.set_nametable_page(3, self.regs.chr[7] & 0x01);
}
_ => {
self.set_nametable_page(0, self.regs.chr[6] & 0x01);
self.set_nametable_page(1, self.regs.chr[7] & 0x01);
self.set_nametable_page(2, self.regs.chr[6] & 0x01);
self.set_nametable_page(3, self.regs.chr[7] & 0x01);
}
}
}
}
}
}
}
impl Map for Vrc6 {
#[inline(always)]
fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {
match addr {
0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],
0x2000..=0x3EFF => {
let addr = addr - 0x2000;
let a10 = (self.nt_banks[((addr >> 10) & 0x03) as usize] << 10) as u16;
let addr = a10 | (!a10 & addr);
if self.regs.banking_mode & 0x10 == 0x00 {
ciram[addr.into()]
} else {
self.chr_rom[self.chr_banks.translate(addr)]
}
}
_ => 0,
}
}
#[inline(always)]
fn prg_peek(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF if self.prg_ram_enabled() => {
self.prg_ram[self.prg_ram_banks.translate(addr)]
}
0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],
_ => 0,
}
}
fn prg_write(&mut self, mut addr: u16, val: u8) {
match addr {
0x6000..=0x7FFF => {
if self.prg_ram_enabled() {
self.prg_ram[self.prg_ram_banks.translate(addr)] = val;
}
}
_ => {
if self.revision == Revision::B {
addr = (addr & 0xFFFC) | ((addr & 0x01) << 1) | ((addr & 0x02) >> 1);
}
match addr & 0xF003 {
0x8000..=0x8003 => {
self.prg_rom_banks
.set_range(0, 1, ((val & 0x0F) << 1).into());
}
0x9000..=0x9003 | 0xA000..=0xA002 | 0xB000..=0xB002 => {
self.audio.write_register(addr, val);
}
0xB003 => {
self.regs.banking_mode = val;
self.update_chr_banks();
}
0xC000..=0xC003 => {
self.prg_rom_banks.set(2, (val & 0x1F).into());
}
0xD000..=0xD003 => {
self.regs.chr[(addr & 0x03) as usize] = val.into();
self.update_chr_banks();
}
0xE000..=0xE003 => {
self.regs.chr[(4 + (addr & 0x03)) as usize] = val.into();
self.update_chr_banks();
}
0xF000 => self.irq.write_reload(val),
0xF001 => self.irq.write_control(val),
0xF002 => self.irq.acknowledge(),
_ => (),
}
}
}
}
fn irq_pending(&self) -> bool {
self.irq.irq_pending
}
#[inline(always)]
fn mirroring(&self) -> Mirroring {
self.mirroring
}
}
impl Reset for Vrc6 {
fn reset(&mut self, kind: ResetKind) {
self.irq.reset(kind);
self.audio.reset(kind);
}
}
impl Clock for Vrc6 {
fn clock(&mut self) {
self.irq.clock();
self.audio.clock();
}
}
impl Regional for Vrc6 {}
impl Sram for Vrc6 {}
impl Sample for Vrc6 {
fn output(&self) -> f32 {
self.audio.output()
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Audio {
pub pulse1: Pulse,
pub pulse2: Pulse,
pub saw: Saw,
pub halt: bool,
pub out: f32,
}
impl Default for Audio {
fn default() -> Self {
Self::new()
}
}
impl Audio {
const fn new() -> Self {
Self {
pulse1: Pulse::new(),
pulse2: Pulse::new(),
saw: Saw::new(),
halt: false,
out: 0.0,
}
}
#[must_use]
fn output(&self) -> f32 {
let pulse_scale = PULSE_TABLE[PULSE_TABLE.len() - 1] / 15.0;
pulse_scale * self.out
}
fn write_register(&mut self, addr: u16, val: u8) {
match addr & 0xF003 {
0x9000..=0x9002 => self.pulse1.write_register(addr, val),
0x9003 => {
self.halt = val & 0x01 == 0x01;
let freq_shift = if val & 0x04 == 0x04 {
8
} else if val & 0x02 == 0x02 {
4
} else {
0
};
self.pulse1.set_freq_shift(freq_shift);
self.pulse2.set_freq_shift(freq_shift);
self.saw.set_freq_shift(freq_shift);
}
0xA000..=0xA002 => self.pulse2.write_register(addr, val),
0xB000..=0xB002 => self.saw.write_register(addr, val),
_ => unreachable!("impossible Vrc6Audio register: {}", addr),
}
}
}
impl Clock for Audio {
fn clock(&mut self) {
if !self.halt {
self.pulse1.clock();
self.pulse2.clock();
self.saw.clock();
self.out = self.pulse1.volume() + self.pulse2.volume() + self.saw.volume();
}
}
}
impl Reset for Audio {
fn reset(&mut self, _kind: ResetKind) {
self.halt = false;
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Pulse {
pub enabled: bool,
pub volume: u8,
pub duty_cycle: u8,
pub ignore_duty: bool,
pub frequency: u16,
pub timer: u16,
pub step: u8,
pub freq_shift: u8,
}
impl Default for Pulse {
fn default() -> Self {
Self::new()
}
}
impl Pulse {
const fn new() -> Self {
Self {
enabled: false,
volume: 0,
duty_cycle: 0,
ignore_duty: false,
frequency: 1,
timer: 1,
step: 0,
freq_shift: 0,
}
}
fn write_register(&mut self, addr: u16, val: u8) {
match addr & 0x03 {
0 => {
self.volume = val & 0x0F;
self.duty_cycle = (val & 0x70) >> 4;
self.ignore_duty = val & 0x80 == 0x80;
}
1 => self.frequency = (self.frequency & 0x0F00) | u16::from(val),
2 => {
self.frequency = ((u16::from(val) & 0x0F) << 8) | (self.frequency & 0xFF);
self.enabled = val & 0x80 == 0x80;
if !self.enabled {
self.step = 0;
}
}
_ => unreachable!("impossible Vrc6Pulse register: {}", addr),
}
}
const fn set_freq_shift(&mut self, val: u8) {
self.freq_shift = val;
}
fn volume(&self) -> f32 {
if self.enabled && (self.ignore_duty || self.step <= self.duty_cycle) {
f32::from(self.volume)
} else {
0.0
}
}
}
impl Clock for Pulse {
fn clock(&mut self) {
if self.enabled {
self.timer -= 1;
if self.timer == 0 {
self.step = (self.step + 1) & 0x0F;
self.timer = (self.frequency >> self.freq_shift) + 1;
}
}
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Saw {
pub enabled: bool,
pub accum: u8,
pub accum_rate: u8,
pub frequency: u16,
pub timer: u16,
pub step: u8,
pub freq_shift: u8,
}
impl Default for Saw {
fn default() -> Self {
Self::new()
}
}
impl Saw {
const fn new() -> Self {
Self {
enabled: false,
accum: 0,
accum_rate: 0,
frequency: 1,
timer: 1,
step: 0,
freq_shift: 0,
}
}
fn write_register(&mut self, addr: u16, val: u8) {
match addr & 0x03 {
0 => {
self.accum_rate = val & 0x3F;
}
1 => self.frequency = (self.frequency & 0x0F00) | u16::from(val),
2 => {
self.frequency = ((u16::from(val) & 0x0F) << 8) | (self.frequency & 0xFF);
self.enabled = val & 0x80 == 0x80;
if !self.enabled {
self.accum = 0;
self.step = 0;
}
}
_ => unreachable!("impossible Vrc6Saw register: {}", addr),
}
}
const fn set_freq_shift(&mut self, val: u8) {
self.freq_shift = val;
}
fn volume(&self) -> f32 {
if self.enabled {
f32::from(self.accum >> 3)
} else {
0.0
}
}
}
impl Clock for Saw {
fn clock(&mut self) {
if self.enabled {
self.timer -= 1;
if self.timer == 0 {
self.step = (self.step + 1) % 14;
self.timer = (self.frequency >> self.freq_shift) + 1;
if self.step == 0 {
self.accum = 0;
} else if self.step & 0x01 == 0x00 {
self.accum += self.accum_rate;
}
}
}
}
}