render pulse channels

This commit is contained in:
Alex Janka 2023-02-15 15:10:22 +11:00
parent 7ff06f151e
commit 579d13d0ef
6 changed files with 140 additions and 8 deletions

19
Cargo.lock generated
View file

@ -248,6 +248,7 @@ dependencies = [
"gilrs",
"minifb",
"rand",
"samplerate",
"spin_sleep",
]
@ -385,6 +386,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "libsamplerate-sys"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28853b399f78f8281cd88d333b54a63170c4275f6faea66726a2bea5cca72e0d"
dependencies = [
"cmake",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
@ -675,6 +685,15 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "samplerate"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e032b2b24715c4f982f483ea3abdb3c9ba444d9f63e87b2843d6f998f5ba2698"
dependencies = [
"libsamplerate-sys",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"

View file

@ -11,3 +11,4 @@ spin_sleep = "1.1.1"
minifb = "0.23"
rand = "0.8.5"
gilrs = "0.10.1"
samplerate = "0.2.4"

View file

@ -160,6 +160,7 @@ impl Cpu {
}
self.gpu.mode = DrawMode::VBlank;
self.render_window();
self.next_audio();
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0));
}

View file

@ -1,5 +1,11 @@
use samplerate::{convert, ConverterType};
use crate::{
processor::memory::{masked_update, Address},
processor::{
memory::{masked_update, Address},
timer::CLOCK_SPEED,
Cpu,
},
util::{get_bit, set_or_clear_bit},
};
@ -46,6 +52,7 @@ pub struct Apu {
vol_left: u8,
vol_right: u8,
div_apu: u8,
buffer: Vec<f32>,
}
impl Default for Apu {
@ -58,10 +65,13 @@ impl Default for Apu {
vol_left: 7,
vol_right: 7,
div_apu: 0,
buffer: vec![],
}
}
}
const SAMPLE_RATE: u32 = 44100;
impl Apu {
pub fn div_apu_tick(&mut self) {
self.div_apu = self.div_apu.wrapping_add(1);
@ -76,6 +86,31 @@ impl Apu {
}
}
fn tick(&mut self, steps: usize) {
self.buffer.append(
&mut self
.channels
.one
.tick(steps)
.into_iter()
.zip(self.channels.two.tick(steps).into_iter())
.map(|(one, two)| (one + two) / 2.)
.collect(),
);
}
fn next_audio(&mut self) {
let _converted = convert(
CLOCK_SPEED as u32,
SAMPLE_RATE,
1,
ConverterType::Linear,
&self.buffer,
)
.unwrap();
self.buffer.clear();
}
pub fn get_register(&self, addr: Address) -> u8 {
if addr == 0xFF26
|| addr == 0xFF11
@ -189,3 +224,13 @@ impl Apu {
self.mem[addr_el] = masked_update(self.mem[addr_el], data, mask);
}
}
impl Cpu {
pub fn advance_apu_clock(&mut self, steps: usize) {
self.memory.apu.tick(steps);
}
pub fn next_audio(&mut self) {
self.memory.apu.next_audio();
}
}

View file

@ -39,32 +39,80 @@ impl Default for Envelope {
}
}
enum DutyCycle {
// 0b00
TwelvePointFive,
// 0b01
TwentyFive,
// 0b10
Fifty,
// 0b11
SeventyFive,
}
impl From<u8> for DutyCycle {
fn from(value: u8) -> Self {
match value & 0b11 {
0b00 => Self::TwelvePointFive,
0b01 => Self::TwentyFive,
0b10 => Self::Fifty,
0b11 => Self::SeventyFive,
_ => panic!("bad match!"),
}
}
}
impl DutyCycle {
fn as_waveform(&self) -> [u8; 8] {
match self {
DutyCycle::TwelvePointFive => [0, 0, 0, 0, 0, 0, 0, 1],
DutyCycle::TwentyFive => [1, 0, 0, 0, 0, 0, 0, 1],
DutyCycle::Fifty => [1, 0, 0, 0, 0, 1, 1, 1],
DutyCycle::SeventyFive => [0, 1, 1, 1, 1, 1, 1, 0],
}
}
fn as_u8(&self) -> u8 {
match self {
DutyCycle::TwelvePointFive => 0b00,
DutyCycle::TwentyFive => 0b01,
DutyCycle::Fifty => 0b10,
DutyCycle::SeventyFive => 0b11,
}
}
}
pub(super) struct PwmChannel {
pub(super) enabled: bool,
pub(super) pan_left: bool,
pub(super) pan_right: bool,
sweep: Sweep,
duty_cycle: u8,
duty_cycle: DutyCycle,
length_enable: bool,
length_timer: u8,
envelope: Envelope,
queued_envelope: Envelope,
wavelength: u16,
wave_timer: u16,
wave_position: usize,
}
impl PwmChannel {
pub(super) fn new(enabled: bool, pan_left: bool, pan_right: bool) -> Self {
let wavelength = 0x700;
Self {
enabled,
pan_left,
pan_right,
sweep: Sweep::default(),
duty_cycle: 2,
duty_cycle: DutyCycle::Fifty,
length_enable: false,
length_timer: 63,
envelope: Envelope::default(),
queued_envelope: Envelope::default(),
wavelength: 0x700,
wavelength,
wave_timer: set_wave_timer(wavelength),
wave_position: 0,
}
}
@ -73,6 +121,19 @@ impl PwmChannel {
self.envelope = self.queued_envelope;
}
pub fn tick(&mut self, steps: usize) -> Vec<f32> {
(0..steps)
.map(|_| {
let b;
(self.wave_timer, b) = self.wave_timer.overflowing_sub(1);
if b {
self.wave_position = (self.wave_position + 1) % 8;
}
(self.duty_cycle.as_waveform()[self.wave_position] as f32 * (-2.)) + 1.
})
.collect()
}
pub(super) fn update_sweep(&mut self, pace: u8, mode: EnvelopeMode, slope: u8) {
self.sweep.pace = pace;
self.sweep.mode = mode;
@ -88,12 +149,12 @@ impl PwmChannel {
}
pub(super) fn update_length_timer_and_duty_cycle(&mut self, data: u8) {
self.length_timer = (data & 0b11000000) >> 6;
self.duty_cycle = data & 0b11111;
self.duty_cycle = DutyCycle::from(data >> 6);
self.length_timer = data & !(0b11 << 6);
}
pub(super) fn get_length_timer_and_duty_cycle(&self) -> u8 {
((self.duty_cycle & 0b11) << 6) | 0b11111
(self.duty_cycle.as_u8() << 6) | !(0b11 << 6)
}
pub(super) fn update_volume_and_envelope(&mut self, data: u8) {
@ -134,6 +195,10 @@ impl PwmChannel {
}
}
fn set_wave_timer(wavelength: u16) -> u16 {
(2048 - wavelength) * 4
}
pub(super) struct WaveChannel {
pub(super) enabled: bool,
pub(super) pan_left: bool,

View file

@ -20,7 +20,7 @@ impl Timers {
}
// Hz
const CLOCK_SPEED: usize = 4194304;
pub const CLOCK_SPEED: usize = 4194304;
// this will need to change when cgb mode is implemented
// as it uses bit 5 in double speed mode
const AUDIO_BIT: u8 = 4;
@ -30,6 +30,7 @@ impl Cpu {
let clock_cycles = (machine_cycles as usize) * 4;
self.advance_gpu_clock(clock_cycles);
self.advance_apu_clock(clock_cycles);
self.timers.div_counter += clock_cycles;
let mut div_diff = (self.timers.div_counter / 256) as u8;