Skip to content

Commit

Permalink
Reduce popping sounds
Browse files Browse the repository at this point in the history
  • Loading branch information
nathsou committed Jul 20, 2023
1 parent 27ac16d commit 0756686
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 29 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Working audio
- Auto generated ROM backgrounds in the library
- Save state preview in the saves menu
- Gamepad support
- Supported mappers: [NROM](https://nesdir.github.io/mapper0.html) (0), [MMC1](https://nesdir.github.io/mapper1.html) (1), [UNROM](https://nesdir.github.io/mapper2.html) (2), [MMC3](https://nesdir.github.io/mapper4.html) (4)

## Settings Menu
Expand All @@ -45,7 +46,7 @@ Use the arrow keys to navigate the menus and press enter to validate
- Use an AudioWorkletNode instead of a scriptProcessor
- Lock framerate at 60fps even on higher refresh rate displays
- Support iNes 2.0 roms
- Support gamepads + Joypad2
- Support Joypad2
- Time travel mode (Save states recorded at regular intervals)
- Replay mode (Replay all inputs from the start)
- Different color palettes
Expand Down
39 changes: 17 additions & 22 deletions crate/src/apu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ mod noise;
mod pulse;
mod triangle;

const APU_BUFFER_SIZE: usize = 8 * 1024;
const APU_BUFFER_MASK: u16 = APU_BUFFER_SIZE as u16 - 1;
const APU_BUFFER_SIZE: usize = 16 * 1024;
const CPU_FREQ: f64 = 1789772.5;

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -45,10 +44,8 @@ impl From<FrameMode> for u8 {
#[allow(clippy::upper_case_acronyms)]
pub struct APU {
cycles_per_sample: f64,
samples_per_frame: f64,
buffer: [f32; APU_BUFFER_SIZE],
buffer_write_index: u16,
buffer_read_index: u16,
cycle: u32,
frame_counter: u32,
frame_interrupt: bool,
Expand Down Expand Up @@ -110,10 +107,8 @@ impl APU {
pub fn new(sound_card_sample_rate: f64) -> APU {
APU {
cycles_per_sample: CPU_FREQ / sound_card_sample_rate,
samples_per_frame: sound_card_sample_rate / 60.0,
buffer: [0.0; APU_BUFFER_SIZE],
buffer_write_index: 0,
buffer_read_index: 0,
cycle: 0,
frame_counter: 0,
frame_interrupt: false,
Expand Down Expand Up @@ -146,11 +141,16 @@ impl APU {
}

fn push_sample(&mut self) {
self.samples_pushed += 1;
let sample = self.get_sample();
self.current_sample = Some(sample);
self.buffer[self.buffer_write_index as usize] = sample;
self.buffer_write_index = (self.buffer_write_index + 1) & APU_BUFFER_MASK;

if self.buffer_write_index < APU_BUFFER_SIZE as u16 {
self.buffer[self.buffer_write_index as usize] = sample;
self.samples_pushed += 1;
self.buffer_write_index += 1;
} else {
self.clear_buffer();
}
}

#[inline]
Expand Down Expand Up @@ -306,15 +306,7 @@ impl APU {
}

pub fn remaining_buffered_samples(&self) -> u16 {
if self.buffer_write_index >= self.buffer_read_index {
self.buffer_write_index - self.buffer_read_index
} else {
APU_BUFFER_SIZE as u16 - self.buffer_read_index + self.buffer_write_index
}
}

pub fn remaining_samples_in_frame(&self) -> usize {
(self.samples_per_frame - (self.samples_pushed as f64 % self.samples_per_frame)) as usize
self.buffer_write_index
}

pub fn fill(&mut self, buffer: &mut [f32]) {
Expand All @@ -340,6 +332,11 @@ impl APU {
};
}

pub fn clear_buffer(&mut self) {
self.buffer_write_index = 0;
self.buffer.fill(0.0);
}

#[inline]
pub fn is_asserting_irq(&mut self) -> bool {
let irq = self.frame_interrupt || self.dmc.interrupt_flag;
Expand Down Expand Up @@ -376,12 +373,11 @@ impl savestate::Save for APU {
fn save(&self, parent: &mut savestate::Section) {
let s = parent.create_child("apu");

// ignore cycles_per_sample and samples_per_frame
// as they depend on the sample rate
// ignore cycles_per_sample
// as it depends on the sample rate

s.data.write_f32_slice(&self.buffer);
s.data.write_u16(self.buffer_write_index);
s.data.write_u16(self.buffer_read_index);
s.data.write_u32(self.cycle);
s.data.write_u32(self.frame_counter);
s.data.write_bool(self.frame_interrupt);
Expand All @@ -403,7 +399,6 @@ impl savestate::Save for APU {

s.data.read_f32_slice(&mut self.buffer)?;
self.buffer_write_index = s.data.read_u16()?;
self.buffer_read_index = s.data.read_u16()?;
self.cycle = s.data.read_u32()?;
self.frame_counter = s.data.read_u32()?;
self.frame_interrupt = s.data.read_bool()?;
Expand Down
7 changes: 6 additions & 1 deletion crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl WasmNes {
#[wasm_bindgen(js_name = nextFrame)]
pub fn next_frame(&mut self, buffer: &mut [u8]) {
self.nes.next_frame();
buffer.copy_from_slice(self.nes.get_frame())
buffer.copy_from_slice(self.nes.get_frame());
}

#[wasm_bindgen(js_name = nextSamples)]
Expand Down Expand Up @@ -106,4 +106,9 @@ impl WasmNes {
pub fn fill_audio_buffer(&mut self, buffer: &mut [f32], avoid_underruns: bool) {
self.nes.fill_audio_buffer(buffer, avoid_underruns);
}

#[wasm_bindgen(js_name = clearAudioBuffer)]
pub fn clear_audio_buffer(&mut self) {
self.nes.clear_audio_buffer();
}
}
9 changes: 5 additions & 4 deletions crate/src/nes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,19 @@ impl Nes {
self.cpu.bus.apu.remaining_buffered_samples() as usize;

// ensure that the buffer is filled with enough samples
// wihtout skipping a frame
if remaining_samples_in_bufffer < buffer.len() {
let remaining_samples_in_frame = self.cpu.bus.apu.remaining_samples_in_frame();
let remaining_samples = buffer.len() - remaining_samples_in_bufffer;
let wait_for = remaining_samples.min(remaining_samples_in_frame - 1);
let wait_for = buffer.len() - remaining_samples_in_bufffer + 1;
self.wait_for_samples(wait_for);
}
}

self.cpu.bus.apu.fill(buffer);
}

pub fn clear_audio_buffer(&mut self) {
self.cpu.bus.apu.clear_buffer();
}

pub fn soft_reset(&mut self) {
self.cpu.soft_reset();
}
Expand Down
3 changes: 2 additions & 1 deletion web/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async function setup() {
Nes.initPanicHook();
const store = await createStore();
const ui = createUI(store);
const syncMode = SYNC_VIDEO;
const syncMode = SYNC_BOTH;
const audioBufferSize = AUDIO_BUFFER_SIZE_MAPPING[syncMode];
const avoidUnderruns = syncMode === SYNC_BOTH;
const canvas = document.querySelector<HTMLCanvasElement>('#screen')!;
Expand Down Expand Up @@ -82,6 +82,7 @@ async function setup() {

if (!ui.visible) {
if (nes !== undefined) {
nes.clearAudioBuffer();
await audioCtx.resume();
}
} else {
Expand Down

0 comments on commit 0756686

Please sign in to comment.