Skip to main content

rustynes_apu/
lib.rs

1//! Cycle-accurate Ricoh 2A03 APU implementation.
2//!
3//! See `docs/apu-2a03.md` for the implementation spec and
4//! `ref-docs/research-report.md` §APU for the source material.
5//!
6//! Five-channel APU (pulse 1, pulse 2, triangle, noise, DMC) with the
7//! lookup-table non-linear mixer, analog highpass / lowpass filter chain,
8//! frame counter (4-step + 5-step modes with the documented IRQ flag),
9//! band-limited synthesis at host sample rate, and DMC sample DMA. The
10//! bus-side DMC DMA scheduling lives in `rustynes-core::SystemBus`.
11
12#![no_std]
13// The chip stack carries no `unsafe`; `forbid` makes that a compile-time
14// guarantee rather than an observation (core audit section 2.1). The FFI
15// and platform `unsafe` lives in the frontend, cheevos and mobile crates.
16#![forbid(unsafe_code)]
17#![warn(missing_docs)]
18// The APU is full of orthogonal hardware-latch booleans that map directly to
19// real chip state; collapsing into enums obscures the model.
20#![allow(clippy::struct_excessive_bools)]
21// Many small mutator helpers are pure register-bit unpackers; the pedantic
22// `const fn` lint generates a wave of suggestions that don't change behavior.
23// We accept the lint at module level rather than salt every method.
24#![allow(clippy::missing_const_for_fn)]
25// Floating-point exact comparisons (`x == 0.0`, `phase >= 1.0`) are deliberate
26// initial-state checks against zero; using `EPSILON` is the wrong tool for
27// the test ROM coverage we're targeting.
28#![allow(clippy::float_cmp, clippy::while_float)]
29// "NESdev" is a proper noun, not a code identifier.
30#![allow(clippy::doc_markdown)]
31// Match arms collapse only sometimes; we keep them split for readability
32// because the address ranges document the register layout.
33#![allow(clippy::match_same_arms)]
34// Performance-sensitive math wants explicit FMA / no-FMA control.  The
35// pedantic `suboptimal_flops` lint suggests `mul_add` which has different
36// rounding properties — we don't accept that for a deterministic build.
37#![allow(clippy::suboptimal_flops)]
38
39extern crate alloc;
40
41mod apu;
42mod blip;
43mod blip_kernel;
44mod dmc;
45mod envelope;
46mod frame_counter;
47mod length;
48mod mixer;
49mod noise;
50mod opll;
51#[cfg(feature = "debug-hooks")]
52pub mod provenance;
53mod pulse;
54mod snapshot;
55mod triangle;
56
57pub use apu::{Apu, CHANNEL_GAIN_UNITY, CHANNEL_MASK_ALL};
58pub use blip::{BlipBuf, CPU_HZ_NTSC, CPU_HZ_PAL};
59pub use dmc::Dmc;
60pub use dmc::REENABLE_BUMP;
61pub use dmc::SUBPOS_DELAY;
62pub use envelope::Envelope;
63pub use frame_counter::{FrameCounter, FrameEvents, Mode as FrameCounterMode};
64pub use length::{LENGTH_TABLE, LengthCounter};
65pub use mixer::{FilterChain, FilterModel, Mixer, OnePole};
66pub use noise::{NTSC_NOISE_PERIODS, Noise, PAL_NOISE_PERIODS};
67pub use opll::{
68    ChipType as OpllChipType, OPLL_SNAPSHOT_LEN, OPLL_SNAPSHOT_VERSION, Opll, OpllStateError,
69    Patch as OpllPatch,
70};
71pub use pulse::Pulse;
72pub use snapshot::{APU_SNAPSHOT_VERSION, ApuSnapshotError};
73pub use triangle::Triangle;
74
75/// NES region — picks clock dividers and per-region tables.
76#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
77pub enum Region {
78    /// NTSC (60 Hz, 1.7898 MHz CPU).
79    Ntsc,
80    /// PAL (50 Hz, 1.6626 MHz CPU).
81    Pal,
82    /// Dendy (PAL famiclone with NTSC-like timing).
83    Dendy,
84}
85
86/// Returns the crate version string.
87#[must_use]
88pub const fn version() -> &'static str {
89    env!("CARGO_PKG_VERSION")
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn version_is_non_empty() {
98        assert_ne!(version(), "");
99    }
100}