From 5b99b872f27a0a594c1b7a31dd5faee1cdb4026f Mon Sep 17 00:00:00 2001 From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Date: Sat, 21 Dec 2024 18:26:58 +0800 Subject: [PATCH 1/4] Remove need to use `Mutex` for `BufferedChannel` in `apu` Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> --- plastic_core/src/apu2a03/mod.rs | 63 +++++++++------------------------ plastic_core/src/nes.rs | 2 +- 2 files changed, 17 insertions(+), 48 deletions(-) diff --git a/plastic_core/src/apu2a03/mod.rs b/plastic_core/src/apu2a03/mod.rs index 177a39c..6ebcd05 100644 --- a/plastic_core/src/apu2a03/mod.rs +++ b/plastic_core/src/apu2a03/mod.rs @@ -16,7 +16,6 @@ use envelope::EnvelopedChannel; use length_counter::LengthCountedChannel; use serde::{Deserialize, Serialize}; use std::cell::Cell; -use std::sync::{Arc, Mutex}; use tone_source::{APUChannel, BufferedChannel, TimedAPUChannel}; // for performance @@ -28,33 +27,6 @@ pub const SAMPLE_RATE: u32 = 44100; // APU, is clocked on every CPU clock const SAMPLES_EVERY_N_APU_CLOCK: f64 = CPU_FREQ / (SAMPLE_RATE as f64); -mod buffered_channel_serde { - use super::BufferedChannel; - use serde::{ser::Error, Deserialize, Deserializer, Serialize, Serializer}; - use std::sync::{Arc, Mutex}; - - pub fn serialize( - value: &Arc>, - serializer: S, - ) -> Result - where - S: Serializer, - { - if let Ok(value) = value.lock() { - value.serialize(serializer) - } else { - Err(S::Error::custom("")) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - BufferedChannel::deserialize(deserializer).map(|channel| Arc::new(Mutex::new(channel))) - } -} - #[derive(Serialize, Deserialize)] pub struct APU2A03 { square_pulse_1: LengthCountedChannel, @@ -63,8 +35,7 @@ pub struct APU2A03 { noise: LengthCountedChannel, dmc: Dmc, - #[serde(with = "buffered_channel_serde")] - buffered_channel: Arc>, + buffered_channel: BufferedChannel, is_4_step_squence_mode_hold_value: bool, is_4_step_squence_mode: bool, @@ -84,7 +55,7 @@ pub struct APU2A03 { impl APU2A03 { pub fn new() -> Self { - let buffered_channel = Arc::new(Mutex::new(BufferedChannel::new())); + let buffered_channel = BufferedChannel::new(); Self { square_pulse_1: LengthCountedChannel::new(SquarePulse::new(true)), @@ -96,7 +67,7 @@ impl APU2A03 { dmc: Dmc::new(), - buffered_channel: buffered_channel.clone(), + buffered_channel, is_4_step_squence_mode_hold_value: false, is_4_step_squence_mode: false, @@ -461,21 +432,19 @@ impl APU2A03 { if self.sample_counter >= samples_every_n_apu_clock { let output = self.get_mixer_output(); - if let Ok(mut buffered_channel) = self.buffered_channel.lock() { - buffered_channel.recored_sample(output); + self.buffered_channel.recored_sample(output); - // check for needed change in offset - let change = if buffered_channel.get_is_overusing() { - -0.001 - } else if buffered_channel.get_is_underusing() { - 0.001 - } else { - 0. - }; + // check for needed change in offset + let change = if self.buffered_channel.get_is_overusing() { + -0.001 + } else if self.buffered_channel.get_is_underusing() { + 0.001 + } else { + 0. + }; - self.offset += change; - buffered_channel.clear_using_flags(); - } + self.offset += change; + self.buffered_channel.clear_using_flags(); self.sample_counter -= samples_every_n_apu_clock; } @@ -531,8 +500,8 @@ impl APU2A03 { } } - pub fn take_audio_buffer(&self) -> Vec { - self.buffered_channel.lock().unwrap().take_buffer() + pub fn take_audio_buffer(&mut self) -> Vec { + self.buffered_channel.take_buffer() } } diff --git a/plastic_core/src/nes.rs b/plastic_core/src/nes.rs index 1e92c5e..d9a0422 100644 --- a/plastic_core/src/nes.rs +++ b/plastic_core/src/nes.rs @@ -361,7 +361,7 @@ impl NES { /// The emulator keeps accumulating audio samples until this function is called, /// so its better to call this function even if audio isn't needed in order to free up space. pub fn audio_buffer(&mut self) -> Vec { - self.cpu.bus().apu.take_audio_buffer() + self.cpu.bus_mut().apu.take_audio_buffer() } /// Check if there is no cartridge loaded in the emulator. From 19cb6c5f3d36afd7ec7a640c892e8ee4abcab054 Mon Sep 17 00:00:00 2001 From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:11:55 +0800 Subject: [PATCH 2/4] Added `Dac` to smooth audio signals and remove unneeded code Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> --- plastic_core/src/apu2a03/channel.rs | 73 +++++++++++++++ plastic_core/src/apu2a03/channels/dmc.rs | 2 +- plastic_core/src/apu2a03/channels/noise.rs | 2 +- plastic_core/src/apu2a03/channels/square.rs | 2 +- plastic_core/src/apu2a03/channels/triangle.rs | 2 +- plastic_core/src/apu2a03/envelope.rs | 2 +- plastic_core/src/apu2a03/length_counter.rs | 3 +- plastic_core/src/apu2a03/mod.rs | 55 +++++------- plastic_core/src/apu2a03/tone_source.rs | 88 ------------------- 9 files changed, 100 insertions(+), 129 deletions(-) create mode 100644 plastic_core/src/apu2a03/channel.rs delete mode 100644 plastic_core/src/apu2a03/tone_source.rs diff --git a/plastic_core/src/apu2a03/channel.rs b/plastic_core/src/apu2a03/channel.rs new file mode 100644 index 0000000..a40a59e --- /dev/null +++ b/plastic_core/src/apu2a03/channel.rs @@ -0,0 +1,73 @@ +use serde::{Deserialize, Serialize}; +use std::{ + collections::VecDeque, + ops::{Deref, DerefMut}, +}; + +pub trait APUChannel: Serialize + for<'de> Deserialize<'de> { + fn get_output(&mut self) -> f32; +} + +pub trait TimedAPUChannel: APUChannel { + fn timer_clock(&mut self); +} + +#[derive(Serialize, Deserialize)] +pub struct BufferedChannel { + buffer: VecDeque, +} + +impl BufferedChannel { + pub fn new() -> Self { + Self { + buffer: VecDeque::new(), + } + } + + pub fn recored_sample(&mut self, sample: f32) { + self.buffer.push_back(sample); + } + + pub fn take_buffer(&mut self) -> Vec { + self.buffer.drain(..).collect() + } +} + +#[derive(Serialize, Deserialize)] +#[serde(bound = "C: APUChannel")] +pub struct Dac { + capacitor: f32, + channel: C, +} + +impl Dac { + pub fn new(channel: C) -> Self { + Self { + capacitor: 0., + channel, + } + } + + pub fn dac_output(&mut self) -> f32 { + let dac_in = self.channel.get_output() / 2.2; + let dac_out = dac_in - self.capacitor; + + self.capacitor = dac_in - dac_out * 0.996; + + dac_out + } +} + +impl Deref for Dac { + type Target = C; + + fn deref(&self) -> &Self::Target { + &self.channel + } +} + +impl DerefMut for Dac { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.channel + } +} diff --git a/plastic_core/src/apu2a03/channels/dmc.rs b/plastic_core/src/apu2a03/channels/dmc.rs index afe75c2..7fac646 100644 --- a/plastic_core/src/apu2a03/channels/dmc.rs +++ b/plastic_core/src/apu2a03/channels/dmc.rs @@ -1,4 +1,4 @@ -use super::super::tone_source::{APUChannel, TimedAPUChannel}; +use super::super::channel::{APUChannel, TimedAPUChannel}; use serde::{Deserialize, Serialize}; const DMC_PERIOD_RATES_NTSC: [u16; 0x10] = [ diff --git a/plastic_core/src/apu2a03/channels/noise.rs b/plastic_core/src/apu2a03/channels/noise.rs index 9356825..91c15ee 100644 --- a/plastic_core/src/apu2a03/channels/noise.rs +++ b/plastic_core/src/apu2a03/channels/noise.rs @@ -1,5 +1,5 @@ +use super::super::channel::{APUChannel, TimedAPUChannel}; use super::super::envelope::{EnvelopeGenerator, EnvelopedChannel}; -use super::super::tone_source::{APUChannel, TimedAPUChannel}; use serde::{Deserialize, Serialize}; /// Table for NTSC only diff --git a/plastic_core/src/apu2a03/channels/square.rs b/plastic_core/src/apu2a03/channels/square.rs index c9b8278..075fcaf 100644 --- a/plastic_core/src/apu2a03/channels/square.rs +++ b/plastic_core/src/apu2a03/channels/square.rs @@ -1,6 +1,6 @@ +use super::super::channel::{APUChannel, TimedAPUChannel}; use super::super::envelope::{EnvelopeGenerator, EnvelopedChannel}; use super::super::sequencer::Sequencer; -use super::super::tone_source::{APUChannel, TimedAPUChannel}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] diff --git a/plastic_core/src/apu2a03/channels/triangle.rs b/plastic_core/src/apu2a03/channels/triangle.rs index eda71f6..9a8670d 100644 --- a/plastic_core/src/apu2a03/channels/triangle.rs +++ b/plastic_core/src/apu2a03/channels/triangle.rs @@ -1,5 +1,5 @@ +use super::super::channel::{APUChannel, TimedAPUChannel}; use super::super::sequencer::Sequencer; -use super::super::tone_source::{APUChannel, TimedAPUChannel}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] diff --git a/plastic_core/src/apu2a03/envelope.rs b/plastic_core/src/apu2a03/envelope.rs index c91b38b..e96bb28 100644 --- a/plastic_core/src/apu2a03/envelope.rs +++ b/plastic_core/src/apu2a03/envelope.rs @@ -1,4 +1,4 @@ -use super::tone_source::APUChannel; +use super::channel::APUChannel; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] diff --git a/plastic_core/src/apu2a03/length_counter.rs b/plastic_core/src/apu2a03/length_counter.rs index 1865612..34b047b 100644 --- a/plastic_core/src/apu2a03/length_counter.rs +++ b/plastic_core/src/apu2a03/length_counter.rs @@ -1,5 +1,5 @@ +use super::channel::{APUChannel, TimedAPUChannel}; use super::envelope::{EnvelopeGenerator, EnvelopedChannel}; -use super::tone_source::{APUChannel, TimedAPUChannel}; use serde::{Deserialize, Serialize}; const LEGNTH_COUNTER_TABLE: [u8; 0x20] = [ @@ -67,6 +67,7 @@ impl LengthCounter { } #[derive(Serialize, Deserialize)] +#[serde(bound = "C: APUChannel")] pub struct LengthCountedChannel where C: APUChannel, diff --git a/plastic_core/src/apu2a03/mod.rs b/plastic_core/src/apu2a03/mod.rs index 6ebcd05..699784a 100644 --- a/plastic_core/src/apu2a03/mod.rs +++ b/plastic_core/src/apu2a03/mod.rs @@ -1,9 +1,9 @@ mod apu2a03_registers; +mod channel; mod channels; mod envelope; mod length_counter; mod sequencer; -mod tone_source; use crate::common::{ interconnection::{APUCPUConnection, CPUIrqProvider}, @@ -11,12 +11,12 @@ use crate::common::{ CPU_FREQ, }; use apu2a03_registers::Register; +use channel::{APUChannel, BufferedChannel, Dac, TimedAPUChannel}; use channels::{Dmc, NoiseWave, SquarePulse, TriangleWave}; use envelope::EnvelopedChannel; use length_counter::LengthCountedChannel; use serde::{Deserialize, Serialize}; use std::cell::Cell; -use tone_source::{APUChannel, BufferedChannel, TimedAPUChannel}; // for performance /// The sample rate expected to get from [`NES::audio_buffer`](crate::NES::audio_buffer) @@ -29,11 +29,11 @@ const SAMPLES_EVERY_N_APU_CLOCK: f64 = CPU_FREQ / (SAMPLE_RATE as f64); #[derive(Serialize, Deserialize)] pub struct APU2A03 { - square_pulse_1: LengthCountedChannel, - square_pulse_2: LengthCountedChannel, - triangle: LengthCountedChannel, - noise: LengthCountedChannel, - dmc: Dmc, + square_pulse_1: Dac>, + square_pulse_2: Dac>, + triangle: Dac>, + noise: Dac>, + dmc: Dac, buffered_channel: BufferedChannel, @@ -58,14 +58,14 @@ impl APU2A03 { let buffered_channel = BufferedChannel::new(); Self { - square_pulse_1: LengthCountedChannel::new(SquarePulse::new(true)), - square_pulse_2: LengthCountedChannel::new(SquarePulse::new(false)), + square_pulse_1: Dac::new(LengthCountedChannel::new(SquarePulse::new(true))), + square_pulse_2: Dac::new(LengthCountedChannel::new(SquarePulse::new(false))), - triangle: LengthCountedChannel::new(TriangleWave::new()), + triangle: Dac::new(LengthCountedChannel::new(TriangleWave::new())), - noise: LengthCountedChannel::new(NoiseWave::new()), + noise: Dac::new(LengthCountedChannel::new(NoiseWave::new())), - dmc: Dmc::new(), + dmc: Dac::new(Dmc::new()), buffered_channel, @@ -385,11 +385,11 @@ impl APU2A03 { } fn get_mixer_output(&mut self) -> f32 { - let square_pulse_1 = self.square_pulse_1.get_output(); - let square_pulse_2 = self.square_pulse_2.get_output(); - let triangle = self.triangle.get_output(); - let noise = self.noise.get_output(); - let dmc = self.dmc.get_output(); + let square_pulse_1 = self.square_pulse_1.dac_output(); + let square_pulse_2 = self.square_pulse_2.dac_output(); + let triangle = self.triangle.dac_output(); + let noise = self.noise.dac_output(); + let dmc = self.dmc.dac_output(); let pulse_out = if square_pulse_1 == 0. && square_pulse_2 == 0. { 0. @@ -425,28 +425,13 @@ impl APU2A03 { std::cmp::Ordering::Greater => self.wait_reset -= 1, } - // after how many apu clocks a sample should be recorded - let samples_every_n_apu_clock = SAMPLES_EVERY_N_APU_CLOCK + self.offset; - self.sample_counter += 1.; - if self.sample_counter >= samples_every_n_apu_clock { + if self.sample_counter >= SAMPLES_EVERY_N_APU_CLOCK { let output = self.get_mixer_output(); self.buffered_channel.recored_sample(output); - // check for needed change in offset - let change = if self.buffered_channel.get_is_overusing() { - -0.001 - } else if self.buffered_channel.get_is_underusing() { - 0.001 - } else { - 0. - }; - - self.offset += change; - self.buffered_channel.clear_using_flags(); - - self.sample_counter -= samples_every_n_apu_clock; + self.sample_counter -= SAMPLES_EVERY_N_APU_CLOCK; } // clocked on every CPU cycle @@ -463,7 +448,7 @@ impl APU2A03 { // this is clocked in every CPU cycle, so the numbers are multiplied by 2 match self.cycle { - 7455 => { + 7457 => { self.generate_quarter_frame_clock(); } 14913 => { diff --git a/plastic_core/src/apu2a03/tone_source.rs b/plastic_core/src/apu2a03/tone_source.rs deleted file mode 100644 index e3c522d..0000000 --- a/plastic_core/src/apu2a03/tone_source.rs +++ /dev/null @@ -1,88 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; - -pub trait APUChannel { - fn get_output(&mut self) -> f32; -} - -pub trait TimedAPUChannel: APUChannel { - fn timer_clock(&mut self); -} - -#[derive(Serialize, Deserialize)] -pub struct BufferedChannel { - buffer: VecDeque, - overusing: bool, - underusing: bool, - last: f32, - recent_record: bool, // did a record happen recently - recent_output: bool, // did an output request happen recently - // - // these are used to know if we are now in a bulk recording - // stage, which what happens in the APU -} - -impl BufferedChannel { - pub fn new() -> Self { - Self { - buffer: VecDeque::new(), - overusing: false, - underusing: false, - last: 0., - recent_record: false, - recent_output: false, - } - } - - pub fn get_is_overusing(&self) -> bool { - self.overusing - } - - pub fn get_is_underusing(&self) -> bool { - self.underusing - } - - pub fn clear_using_flags(&mut self) { - self.overusing = false; - self.underusing = false; - } - - pub fn recored_sample(&mut self, sample: f32) { - self.buffer.push_back(sample); - if self.recent_record { - // 60 FPS - if self.buffer.len() > (super::SAMPLE_RATE / 60) as usize && !self.overusing { - self.underusing = true; - } - self.recent_record = false; - } - if self.recent_output { - self.recent_output = false; - self.recent_record = true; - } - } - - pub fn take_buffer(&mut self) -> Vec { - self.buffer.drain(..).collect() - } -} - -impl APUChannel for BufferedChannel { - fn get_output(&mut self) -> f32 { - self.recent_output = true; - - if self.buffer.is_empty() { - self.overusing = true; - self.underusing = false; - - self.last - } else if self.buffer.len() == 1 { - self.last = self.buffer.pop_front().unwrap(); - // this should not reach here, or just one time - // buffer is empty [Problem] - self.last - } else { - self.buffer.pop_front().unwrap() - } - } -} From ed9c093d0b779e3fcd34f491ca9ceda5280580cd Mon Sep 17 00:00:00 2001 From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Date: Thu, 26 Dec 2024 18:49:41 +0800 Subject: [PATCH 3/4] Updated APU to return stereo audio Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> --- plastic_core/src/apu2a03/channel.rs | 1 + plastic_core/src/apu2a03/mod.rs | 3 ++- plastic_core/src/misc.rs | 4 +--- plastic_core/src/nes.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plastic_core/src/apu2a03/channel.rs b/plastic_core/src/apu2a03/channel.rs index a40a59e..3ee52bb 100644 --- a/plastic_core/src/apu2a03/channel.rs +++ b/plastic_core/src/apu2a03/channel.rs @@ -26,6 +26,7 @@ impl BufferedChannel { pub fn recored_sample(&mut self, sample: f32) { self.buffer.push_back(sample); + self.buffer.push_back(sample); } pub fn take_buffer(&mut self) -> Vec { diff --git a/plastic_core/src/apu2a03/mod.rs b/plastic_core/src/apu2a03/mod.rs index 699784a..8ab48bc 100644 --- a/plastic_core/src/apu2a03/mod.rs +++ b/plastic_core/src/apu2a03/mod.rs @@ -11,7 +11,7 @@ use crate::common::{ CPU_FREQ, }; use apu2a03_registers::Register; -use channel::{APUChannel, BufferedChannel, Dac, TimedAPUChannel}; +use channel::{BufferedChannel, Dac, TimedAPUChannel}; use channels::{Dmc, NoiseWave, SquarePulse, TriangleWave}; use envelope::EnvelopedChannel; use length_counter::LengthCountedChannel; @@ -485,6 +485,7 @@ impl APU2A03 { } } + /// Take and return the audio buffer as f32 format stereo (2 channels) pub fn take_audio_buffer(&mut self) -> Vec { self.buffered_channel.take_buffer() } diff --git a/plastic_core/src/misc.rs b/plastic_core/src/misc.rs index b283011..4a2b190 100644 --- a/plastic_core/src/misc.rs +++ b/plastic_core/src/misc.rs @@ -92,7 +92,7 @@ impl Fps { /// `speed_modifier == 1.0` means normal speed pub fn process_audio(audio_buffer: &[f32], speed_modifier: f32) -> Vec { let target_len = (audio_buffer.len() as f32 * speed_modifier).ceil() as usize; - let mut adjusted_buffer = Vec::with_capacity(target_len * 2); + let mut adjusted_buffer = Vec::with_capacity(target_len); for i in 0..target_len { let src_index_f = i as f32 / speed_modifier; @@ -107,8 +107,6 @@ pub fn process_audio(audio_buffer: &[f32], speed_modifier: f32) -> Vec { } else { *audio_buffer.last().unwrap_or(&0.0) }; - // Add the sample twice for left and right channels - adjusted_buffer.push(sample); adjusted_buffer.push(sample); } diff --git a/plastic_core/src/nes.rs b/plastic_core/src/nes.rs index d9a0422..6b63716 100644 --- a/plastic_core/src/nes.rs +++ b/plastic_core/src/nes.rs @@ -353,7 +353,7 @@ impl NES { self.cpu.bus().ppu.tv().display_pixel_buffer() } - /// Take and return the audio buffer as f32 format + /// Take and return the audio buffer as f32 format stereo (2 channels) /// /// **Take** here means that if you call the function again, it will return an empty buffer /// until the emulator runs again. From cc786f2de17dc18f48c97ba33fabc012aba94dbf Mon Sep 17 00:00:00 2001 From: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> Date: Thu, 26 Dec 2024 18:50:25 +0800 Subject: [PATCH 4/4] More accurate timing emulation by using decimal values Using counter allows us to use decimal values for the `CPU_CYCLES_PER_FRAME` number. Signed-off-by: Amjad Alsharafi <26300843+Amjad50@users.noreply.github.com> --- plastic_core/src/nes.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plastic_core/src/nes.rs b/plastic_core/src/nes.rs index 6b63716..65df510 100644 --- a/plastic_core/src/nes.rs +++ b/plastic_core/src/nes.rs @@ -250,6 +250,8 @@ pub struct NES { /// CPU and containing all components through the `CPUBus`. cpu: CPU6502, + + frame_counter: f32, } impl NES { @@ -287,7 +289,11 @@ impl NES { cpu.reset(); - Self { cartridge, cpu } + Self { + cartridge, + cpu, + frame_counter: 0., + } } /// Reset the NES emulator using the same cartridge loaded already. @@ -310,12 +316,14 @@ impl NES { return; } - const N: usize = 29780; // number of CPU cycles per loop, one full frame + const CPU_CYCLES_PER_FRAME: f32 = 29780.5; // number of CPU cycles per loop, one full frame - for _ in 0..N { - self.cpu.bus_mut().apu.clock(); + self.frame_counter += CPU_CYCLES_PER_FRAME; + while self.frame_counter >= 0. { + self.frame_counter -= 1.; self.cpu.run_next(); + self.cpu.bus_mut().apu.clock(); { let ppu = &mut self.cpu.bus_mut().ppu; ppu.clock();