reznez 0.0.0

The high accuracy NES Emulator
Documentation
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use log::info;
use rodio::{OutputStream, Sink};
use rodio::source::Source;

use crate::apu::apu_registers::ApuRegisters;

const SAMPLE_RATE: u32 = 44100;
const MAX_QUEUE_LENGTH: usize = 2 * SAMPLE_RATE as usize;

pub struct Apu {
    pulse_queue: Arc<Mutex<VecDeque<f32>>>,
    muted: Arc<Mutex<bool>>,
}

impl Apu {
    pub fn new(disable_audio: bool) -> Apu {
        // TODO: Select a proper capacity value.
        let pulse_queue = Arc::new(Mutex::new(VecDeque::with_capacity(MAX_QUEUE_LENGTH)));
        let muted = Arc::new(Mutex::new(false));

        if !disable_audio {
            let cloned_queue = pulse_queue.clone();
            let cloned_muted = muted.clone();
            thread::spawn(move || {
                let source = PulseWave::new(cloned_queue, cloned_muted);
                let (_stream, stream_handle) = OutputStream::try_default().unwrap();
                let sink = Sink::try_new(&stream_handle).unwrap();
                sink.append(source);

                loop {
                    thread::sleep(Duration::from_millis(1000));
                }
            });
        }

        Apu {
            pulse_queue,
            muted,
        }
    }

    pub fn mute(&mut self) {
        *self.muted.lock().unwrap() = true;
    }

    pub fn step(&mut self, regs: &mut ApuRegisters) {
        regs.maybe_update_step_mode();
        regs.dmc.maybe_start_dma();

        if regs.clock().is_off_cycle() {
            Apu::off_cycle_step(regs);
        } else {
            self.on_cycle_step(regs);
        }
    }

    fn on_cycle_step(&mut self, regs: &mut ApuRegisters) {
        let cycle = regs.clock().cycle();
        info!(target: "apucycles", "APU cycle: {cycle} (ON)");

        regs.on_cycle_step();

        if regs.clock().raw_cycle() % 20 == 0 {
            let mut queue = self.pulse_queue
                .lock()
                .unwrap();
            if queue.len() < MAX_QUEUE_LENGTH {
                queue.push_back(Apu::mix_samples(regs));
            }
        }

        regs.maybe_set_frame_irq_pending();
        regs.maybe_decrement_counters();
    }

    fn off_cycle_step(regs: &mut ApuRegisters) {
        let cycle = regs.clock().cycle();
        info!(target: "apucycles", "APU cycle: {cycle} (OFF)");
        regs.maybe_set_frame_irq_pending();
        regs.off_cycle_step();
    }

    fn mix_samples(regs: &ApuRegisters) -> f32 {
        let pulse_1 = regs.pulse_1.sample_volume();
        let pulse_2 = regs.pulse_2.sample_volume();
        let triangle = regs.triangle.sample_volume();
        let noise = regs.noise.sample_volume();
        let dmc = regs.dmc.sample_volume();

        let pulse_out = 95.88 / (8128.0 / (pulse_1 + pulse_2) + 100.0);
        let tnd_out = 159.79 / ((1.0 / (triangle / 8227.0 + noise / 12241.0 + dmc / 22368.0)) + 100.0);
        let mix = pulse_out + tnd_out;

        assert!(mix >= 0.0);
        assert!(mix <= 1.0);
        mix
    }
}

#[derive(Clone, Debug)]
pub struct PulseWave {
    queue: Arc<Mutex<VecDeque<f32>>>,
    muted: Arc<Mutex<bool>>,
    previous_value: f32,
}

impl PulseWave {
    #[inline]
    pub fn new(queue: Arc<Mutex<VecDeque<f32>>>, muted: Arc<Mutex<bool>>) -> Self {
        PulseWave {
            queue,
            muted,
            previous_value: 0.0,
        }
    }
}

impl Iterator for PulseWave {
    type Item = f32;

    #[inline]
    fn next(&mut self) -> Option<f32> {
        if *self.muted.lock().unwrap() {
            None
        } else if let Some(value) = self.queue.lock().unwrap().pop_front() {
            self.previous_value = value;
            Some(value)
        } else {
            Some(self.previous_value)
        }
    }
}

impl Source for PulseWave {
    #[inline]
    fn current_frame_len(&self) -> Option<usize> {
        None
    }

    #[inline]
    fn channels(&self) -> u16 {
        1
    }

    #[inline]
    fn sample_rate(&self) -> u32 {
        SAMPLE_RATE
    }

    #[inline]
    fn total_duration(&self) -> Option<Duration> {
        None
    }
}