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::LockstepBus`.
11
12#![no_std]
13#![warn(missing_docs)]
14// The APU is full of orthogonal hardware-latch booleans that map directly to
15// real chip state; collapsing into enums obscures the model.
16#![allow(clippy::struct_excessive_bools)]
17// Many small mutator helpers are pure register-bit unpackers; the pedantic
18// `const fn` lint generates a wave of suggestions that don't change behavior.
19// We accept the lint at module level rather than salt every method.
20#![allow(clippy::missing_const_for_fn)]
21// Floating-point exact comparisons (`x == 0.0`, `phase >= 1.0`) are deliberate
22// initial-state checks against zero; using `EPSILON` is the wrong tool for
23// the test ROM coverage we're targeting.
24#![allow(clippy::float_cmp, clippy::while_float)]
25// "NESdev" is a proper noun, not a code identifier.
26#![allow(clippy::doc_markdown)]
27// Match arms collapse only sometimes; we keep them split for readability
28// because the address ranges document the register layout.
29#![allow(clippy::match_same_arms)]
30// Performance-sensitive math wants explicit FMA / no-FMA control.  The
31// pedantic `suboptimal_flops` lint suggests `mul_add` which has different
32// rounding properties — we don't accept that for a deterministic build.
33#![allow(clippy::suboptimal_flops)]
34
35extern crate alloc;
36
37mod apu;
38mod blip;
39mod blip_kernel;
40mod dmc;
41mod envelope;
42mod frame_counter;
43mod length;
44mod mixer;
45mod noise;
46mod opll;
47#[cfg(feature = "debug-hooks")]
48pub mod provenance;
49mod pulse;
50mod snapshot;
51mod triangle;
52
53pub use apu::{Apu, ApuBus, CHANNEL_GAIN_UNITY, CHANNEL_MASK_ALL};
54pub use blip::{BlipBuf, CPU_HZ_NTSC, CPU_HZ_PAL};
55pub use dmc::Dmc;
56pub use dmc::REENABLE_BUMP;
57pub use dmc::SUBPOS_DELAY;
58pub use envelope::Envelope;
59pub use frame_counter::{FrameCounter, FrameEvents, Mode as FrameCounterMode};
60pub use length::{LENGTH_TABLE, LengthCounter};
61pub use mixer::{FilterChain, FilterModel, Mixer, OnePole};
62pub use noise::{NTSC_NOISE_PERIODS, Noise, PAL_NOISE_PERIODS};
63pub use opll::{
64    ChipType as OpllChipType, OPLL_SNAPSHOT_LEN, OPLL_SNAPSHOT_VERSION, Opll, OpllStateError,
65    Patch as OpllPatch,
66};
67pub use pulse::Pulse;
68pub use snapshot::{APU_SNAPSHOT_VERSION, ApuSnapshotError};
69pub use triangle::Triangle;
70
71/// NES region — picks clock dividers and per-region tables.
72#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
73pub enum Region {
74    /// NTSC (60 Hz, 1.7898 MHz CPU).
75    Ntsc,
76    /// PAL (50 Hz, 1.6626 MHz CPU).
77    Pal,
78    /// Dendy (PAL famiclone with NTSC-like timing).
79    Dendy,
80}
81
82/// Returns the crate version string.
83#[must_use]
84pub const fn version() -> &'static str {
85    env!("CARGO_PKG_VERSION")
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn version_is_non_empty() {
94        assert!(!version().is_empty());
95    }
96}