Se.Cherkasov 08f7ad8bdf
Some checks failed
CI / rust (push) Has been cancelled
fix(audio): fix DMC loop byte skip, add DC blocker, lazy cpal stream
Three audio bugs fixed:

1. DMC loop mode skipped the last byte of each sample iteration.
   provide_dmc_dma_byte() was immediately setting dmc_dma_request on
   loop restart while the sample buffer was still full, causing the
   while-loop in clock_cpu_cycles to service a second DMA immediately
   and overwrite the valid buffer. Per NES hardware spec, the reader
   only fills an empty buffer — the request is now left to clock_dmc
   when the output unit actually empties the buffer into the shift
   register. Fixes intermittent clicking/crackling in games that use
   looped DMC samples (BGM, SFX).

2. Missing DC blocker (high-pass filter) in AudioMixer. The NES APU
   has a capacitor-coupled output stage that blocks DC bias. Without
   it, abrupt channel state changes (length counter expiry, sweep
   mute, triangle period < 2) produce DC steps that manifest as
   audible clicks. Added a one-pole IIR high-pass filter at ~5 Hz
   applied after the existing low-pass filter.

3. cpal stream was opened at application startup with
   BufferSize::Fixed(256), forcing PipeWire/PulseAudio to run the
   entire audio graph at a 5.3 ms quantum. This disrupted other audio
   applications (browsers, media players) even when no ROM was loaded.
   Fixed by: (a) creating the stream lazily on the first push_samples
   call so no device is touched until a ROM is running, and (b)
   switching to BufferSize::Default so the audio server chooses the
   quantum instead of the emulator imposing one. Ring buffer capacity
   increased from 1536 to 4096 samples to absorb larger server quanta.
2026-03-15 10:41:19 +03:00

nesemu

NES/Famicom emulation workspace in Rust.

The workspace is built around a reusable core library. It also contains optional adapter crates and a GTK4 desktop frontend for manual testing.

What Is Here

  • nesemu: core emulation library
  • nesemu-adapter-api: backend-agnostic adapter traits
  • nesemu-adapter-headless: null/headless adapter implementations
  • nesemu-desktop: GTK4 desktop frontend

What The Core Library Provides

  • CPU, PPU, APU, bus, and cartridge mapper emulation
  • iNES ROM parsing
  • Save/load state support
  • Host-facing runtime wrappers for frame execution and pacing
  • Public API and behavior tests

Quick Start

Add the main crate as a dependency:

[dependencies]
nesemu = { path = "../nesemu" }

Enable optional adapter support if needed:

[dependencies]
nesemu = { path = "../nesemu", features = ["adapter-api", "adapter-headless"] }

Recommended import style:

use nesemu::prelude::*;

Minimal setup:

use nesemu::{Cpu6502, NativeBus, create_mapper, parse_rom};

let rom_bytes = std::fs::read("game.nes")?;
let rom = parse_rom(&rom_bytes)?;
let mapper = create_mapper(rom)?;
let mut bus = NativeBus::new(mapper);
let mut cpu = Cpu6502::default();
cpu.reset(&mut bus);

Higher-level runtime setup:

use nesemu::{FRAME_RGBA_BYTES, NesRuntime};

let rom_bytes = std::fs::read("game.nes")?;
let mut runtime = NesRuntime::from_rom_bytes(&rom_bytes)?;
runtime.run_until_frame_complete()?;

let mut rgba = vec![0; FRAME_RGBA_BYTES];
runtime.render_frame_rgba(&mut rgba)?;

Desktop Frontend

Run the GTK4 desktop frontend:

cargo run -p nesemu-desktop -- path/to/game.nes

Linux build requirements: GTK4 development packages and pkg-config (for example on Debian/Ubuntu: libgtk-4-dev pkg-config).

Controls:

  • Esc: quit
  • P: pause/resume
  • Open ROM: load .nes file
  • Arrow keys: D-pad
  • X: A
  • Z: B
  • Enter: Start
  • Left Shift / Right Shift: Select

Development

cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features

Documentation Map

Description
No description provided
Readme 437 KiB
Languages
Rust 100%