nes-sim 0.1.4

A NES (Famicom) emulator core library written in pure Rust.
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use crate::apu::ExpansionAudioChip;
use crate::savestate::{SaveStateError, StateReader, StateWriter};

const RAM_SIZE: usize = 0x80;
const CHANNEL_COUNT: usize = 8;
const CYCLE_DIVIDER: u8 = 15;

struct Channel {
    phase: u32,
    output: i32,
}

impl Channel {
    fn new() -> Self {
        Self {
            phase: 0,
            output: 0,
        }
    }
}

pub(crate) struct Namco163Audio {
    ram: [u8; RAM_SIZE],
    addr_reg: u8,
    channels: [Channel; CHANNEL_COUNT],
    num_channels: u8,
    cycpos: u8,
    curch: u8,
    lpaccum: i32,
}

impl Namco163Audio {
    pub(crate) fn new() -> Self {
        let mut ram = [0u8; RAM_SIZE];
        ram[0x7F] = 0x10;
        Self {
            ram,
            addr_reg: 0,
            channels: [
                Channel::new(),
                Channel::new(),
                Channel::new(),
                Channel::new(),
                Channel::new(),
                Channel::new(),
                Channel::new(),
                Channel::new(),
            ],
            num_channels: 2,
            cycpos: 0,
            curch: 0,
            lpaccum: 0,
        }
    }

    pub(crate) fn read_ram(&mut self) -> u8 {
        let data = self.ram[(self.addr_reg & 0x7F) as usize];
        if self.addr_reg & 0x80 != 0 {
            self.addr_reg = (self.addr_reg & 0x80) | ((self.addr_reg + 1) & 0x7F);
        }
        data
    }

    pub(crate) fn write_addr(&mut self, data: u8) {
        self.addr_reg = data;
    }

    pub(crate) fn write_ram(&mut self, data: u8) {
        let addr = (self.addr_reg & 0x7F) as usize;
        self.ram[addr] = data;

        if addr >= 0x40 {
            let ch = (addr - 0x40) >> 3;
            self.update_channel(ch);
        }

        if self.addr_reg & 0x80 != 0 {
            self.addr_reg = (self.addr_reg & 0x80) | ((self.addr_reg + 1) & 0x7F);
        }
    }

    fn read_ram_at(&self, addr: usize) -> u8 {
        self.ram[addr & 0x7F]
    }

    fn channel_frequency(&self, ch: usize) -> u32 {
        let off = 0x40 + ch * 8;
        (self.read_ram_at(off) as u32)
            | ((self.read_ram_at(off + 2) as u32) << 8)
            | ((self.read_ram_at(off + 4) as u32 & 0x03) << 16)
    }

    fn channel_wave_len(&self, ch: usize) -> u32 {
        let off = 0x40 + ch * 8;
        let reg4 = self.read_ram_at(off + 4);
        (0x100 - (reg4 & 0xFC) as u32) << 16
    }

    fn channel_wave_start(&self, ch: usize) -> usize {
        let off = 0x40 + ch * 8;
        self.read_ram_at(off + 6) as usize
    }

    fn channel_volume(&self, ch: usize) -> u8 {
        let off = 0x40 + ch * 8;
        self.read_ram_at(off + 7) & 0x0F
    }

    fn channel_enabled(&self, ch: usize) -> bool {
        let off = 0x40 + ch * 8;
        let reg4 = self.read_ram_at(off + 4);
        reg4 & 0xE0 != 0
    }

    fn update_channel(&mut self, ch: usize) {
        self.num_channels = 1 + ((self.read_ram_at(0x7F) >> 4) & 0x07);
        if !self.channel_enabled(ch) || self.channel_volume(ch) == 0 {
            self.channels[ch].output = 0;
        }
    }

    fn get_wave_sample(&self, addr: usize) -> i32 {
        let b = self.read_ram_at(addr >> 1);
        let nibble = if (addr & 1) == 0 { b & 0x0F } else { b >> 4 };
        (nibble as i32) - 8
    }

    fn clock_channel(&mut self, ch: usize) {
        if !self.channel_enabled(ch) {
            self.channels[ch].output = 0;
            return;
        }
        let vol = self.channel_volume(ch);
        if vol == 0 {
            self.channels[ch].output = 0;
            return;
        }
        let freq = self.channel_frequency(ch);
        let wave_len = self.channel_wave_len(ch);
        let wave_start = self.channel_wave_start(ch);

        let phase = self.channels[ch].phase;
        let new_phase = (phase + freq) % wave_len;
        self.channels[ch].phase = new_phase;

        let sample_addr = (wave_start + (new_phase >> 16) as usize) & 0xFF;
        let wave_sample = self.get_wave_sample(sample_addr);
        self.channels[ch].output = wave_sample * (vol as i32) * 16;
    }

    pub(crate) fn tick(&mut self) {
        self.num_channels = 1 + ((self.read_ram_at(0x7F) >> 4) & 0x07);
        self.cycpos = (self.cycpos + 1) % CYCLE_DIVIDER;
        if self.cycpos == 0 {
            self.curch = (self.curch + 1) % self.num_channels;
            self.clock_channel(self.curch as usize);
        }
    }

    pub(crate) fn output(&self) -> f32 {
        let mut sample: i32 = 0;
        for i in 0..self.num_channels as usize {
            sample += self.channels[i].output;
        }
        sample += self.lpaccum;
        (sample as f32) / 4096.0
    }

    pub(crate) fn apply_lowpass(&mut self) {
        let mut sample: i32 = 0;
        for i in 0..self.num_channels as usize {
            sample += self.channels[i].output;
        }
        sample += self.lpaccum;
        self.lpaccum = sample - (sample >> 4);
    }

    pub(crate) fn save_state(&self, writer: &mut StateWriter) {
        writer.write_bytes(&self.ram);
        writer.write_u8(self.addr_reg);
        writer.write_u8(self.num_channels);
        writer.write_u8(self.cycpos);
        writer.write_u8(self.curch);
        writer.write_u32(self.lpaccum as u32);
        for ch in &self.channels {
            writer.write_u32(ch.phase);
            writer.write_u32(ch.output as u32);
        }
    }

    pub(crate) fn load_state(
        &mut self,
        reader: &mut StateReader<'_>,
    ) -> Result<(), SaveStateError> {
        reader.read_bytes_into(&mut self.ram)?;
        self.addr_reg = reader.read_u8()?;
        self.num_channels = reader.read_u8()?;
        self.cycpos = reader.read_u8()?;
        self.curch = reader.read_u8()?;
        self.lpaccum = reader.read_u32()? as i32;
        for ch in &mut self.channels {
            ch.phase = reader.read_u32()?;
            ch.output = reader.read_u32()? as i32;
        }
        Ok(())
    }
}

pub(crate) struct Namco163AudioChip {
    audio: Rc<RefCell<Namco163Audio>>,
}

impl Namco163AudioChip {
    pub(crate) fn new(audio: Rc<RefCell<Namco163Audio>>) -> Self {
        Self { audio }
    }
}

impl ExpansionAudioChip for Namco163AudioChip {
    fn tick_cpu_cycle(&mut self) {
        let mut audio = self.audio.borrow_mut();
        audio.tick();
        audio.apply_lowpass();
    }

    fn output_sample(&self) -> f32 {
        self.audio.borrow().output()
    }
}