diff --git a/src/lib.rs b/src/lib.rs index c5e39ce..f408bf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub use nesemu_adapter_api as adapter_api; #[cfg(feature = "adapter-headless")] pub use nesemu_adapter_headless as adapter_headless; -pub use native_core::apu::{Apu, ApuStateTail}; +pub use native_core::apu::{Apu, ApuStateTail, ChannelOutputs}; pub use native_core::bus::NativeBus; pub use native_core::cpu::{Cpu6502, CpuBus, CpuError}; pub use native_core::ines::{InesHeader, InesRom, Mirroring, parse_header, parse_rom}; diff --git a/src/native_core/apu/api.rs b/src/native_core/apu/api.rs index 5250745..bf09c1f 100644 --- a/src/native_core/apu/api.rs +++ b/src/native_core/apu/api.rs @@ -1,4 +1,4 @@ -use super::types::{Apu, ApuStateTail}; +use super::types::{Apu, ApuStateTail, ChannelOutputs}; impl Apu { pub fn new() -> Self { @@ -287,4 +287,74 @@ impl Apu { self.noise_timer_counter = state.noise_timer_counter; self.noise_lfsr = if state.noise_lfsr == 0 { 1 } else { state.noise_lfsr }; } + + pub fn channel_outputs(&self) -> ChannelOutputs { + const PULSE_DUTY_TABLE: [[u8; 8]; 4] = [ + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 1, 1, 1, 1, 1], + ]; + const TRIANGLE_SEQUENCE: [u8; 32] = [ + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + ]; + + let pulse1 = { + let duty = (self.io[0x00] >> 6) as usize; + let step = self.pulse_duty_step[0] as usize; + let volume = if (self.io[0x00] & 0x10) != 0 { + self.io[0x00] & 0x0F + } else { + self.envelope_decay[0] + }; + let active = (self.channel_enable_mask & 0x01) != 0 + && self.length_counters[0] > 0 + && PULSE_DUTY_TABLE[duty][step] != 0 + && !self.sweep_mutes_channel(0, 0x02); + if active { volume } else { 0 } + }; + + let pulse2 = { + let duty = (self.io[0x04] >> 6) as usize; + let step = self.pulse_duty_step[1] as usize; + let volume = if (self.io[0x04] & 0x10) != 0 { + self.io[0x04] & 0x0F + } else { + self.envelope_decay[1] + }; + let active = (self.channel_enable_mask & 0x02) != 0 + && self.length_counters[1] > 0 + && PULSE_DUTY_TABLE[duty][step] != 0 + && !self.sweep_mutes_channel(1, 0x06); + if active { volume } else { 0 } + }; + + let triangle = { + let active = (self.channel_enable_mask & 0x04) != 0 + && self.length_counters[2] > 0 + && self.triangle_linear_counter > 0; + if active { + TRIANGLE_SEQUENCE[self.triangle_step as usize & 0x1F] + } else { + 0 + } + }; + + let noise = { + let volume = if (self.io[0x0C] & 0x10) != 0 { + self.io[0x0C] & 0x0F + } else { + self.envelope_decay[2] + }; + let active = (self.channel_enable_mask & 0x08) != 0 + && self.length_counters[3] > 0 + && (self.noise_lfsr & 1) == 0; + if active { volume } else { 0 } + }; + + let dmc = self.dmc_output_level; + + ChannelOutputs { pulse1, pulse2, triangle, noise, dmc } + } } diff --git a/src/native_core/apu/mod.rs b/src/native_core/apu/mod.rs index 075f5ba..3056fd6 100644 --- a/src/native_core/apu/mod.rs +++ b/src/native_core/apu/mod.rs @@ -2,4 +2,4 @@ mod api; mod timing; mod types; -pub use types::{Apu, ApuStateTail}; +pub use types::{Apu, ApuStateTail, ChannelOutputs}; diff --git a/src/native_core/apu/types.rs b/src/native_core/apu/types.rs index b04b47a..795ca55 100644 --- a/src/native_core/apu/types.rs +++ b/src/native_core/apu/types.rs @@ -1,3 +1,12 @@ +#[derive(Debug, Clone, Copy, Default)] +pub struct ChannelOutputs { + pub pulse1: u8, + pub pulse2: u8, + pub triangle: u8, + pub noise: u8, + pub dmc: u8, +} + pub(super) const APU_FRAME_SEQ_4_STEP_CYCLES: u32 = 14_915; pub(super) const APU_FRAME_SEQ_5_STEP_CYCLES: u32 = 18_641; pub(super) const APU_QUARTER_FRAME_1: u32 = 3_729; diff --git a/src/native_core/bus.rs b/src/native_core/bus.rs index 911d1a5..d620ec5 100644 --- a/src/native_core/bus.rs +++ b/src/native_core/bus.rs @@ -58,6 +58,10 @@ impl NativeBus { self.apu.registers() } + pub fn apu_channel_outputs(&self) -> crate::native_core::apu::ChannelOutputs { + self.apu.channel_outputs() + } + pub fn render_frame(&self, out_rgba: &mut [u8], frame_number: u32, buttons: [bool; 8]) { let _ = (frame_number, buttons); let src = self.ppu.frame_buffer();