Partial audio implementation
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 10s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 10s
This commit is contained in:
279
src/apu.rs
279
src/apu.rs
@@ -1,5 +1,13 @@
|
||||
use std::iter::repeat_n;
|
||||
|
||||
use iced::{
|
||||
Element, Font,
|
||||
widget::{column, text},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
pub enum None {}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DutyVol(u8);
|
||||
@@ -38,6 +46,8 @@ struct PulseChannel {
|
||||
|
||||
cur_time: u16,
|
||||
cur_length: u8,
|
||||
cur: u8,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl PulseChannel {
|
||||
@@ -50,6 +60,8 @@ impl PulseChannel {
|
||||
length_timer_high: LengthTimerHigh(0),
|
||||
cur_time: 0,
|
||||
cur_length: 0,
|
||||
cur: 0,
|
||||
sample: 0,
|
||||
}
|
||||
}
|
||||
fn use_envelope(&self) -> bool {
|
||||
@@ -61,58 +73,229 @@ impl PulseChannel {
|
||||
fn timer(&self) -> u16 {
|
||||
self.timer_low as u16 | ((self.length_timer_high.timer_high() as u16) << 8)
|
||||
}
|
||||
fn length(&self) -> u8 {
|
||||
self.length_timer_high.length()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
// TODO
|
||||
self.cur_time = self.timer();
|
||||
self.cur_length = self.length_timer_high.length();
|
||||
self.cur = 0;
|
||||
}
|
||||
pub fn clock(&mut self) {
|
||||
if self.cur_time == 0 {
|
||||
self.cur_time = self.timer();
|
||||
self.cur = (self.cur + 1) % 8;
|
||||
const DUTY: [[bool; 8]; 4] = [
|
||||
[false, true, false, false, false, false, false, false],
|
||||
[false, true, true, false, false, false, false, false],
|
||||
[false, true, true, true, true, false, false, false],
|
||||
[true, false, false, true, true, true, true, true],
|
||||
];
|
||||
self.sample = if DUTY[self.duty_vol.duty() as usize][self.cur as usize] {
|
||||
self.volume()
|
||||
} else {
|
||||
0x00
|
||||
};
|
||||
} else {
|
||||
self.cur_time -= 1;
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
pub fn q_frame_clock(&mut self) {
|
||||
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
||||
self.cur_length -= 1;
|
||||
}
|
||||
}
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
||||
self.cur_length -= 1;
|
||||
}
|
||||
self.q_frame_clock();
|
||||
// if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
||||
// self.cur_length -= 1;
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
"
|
||||
Square Channel
|
||||
"
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
pub struct CounterLoad(u8);
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LengthCounter(u8);
|
||||
impl Debug;
|
||||
halt, set_halt: 7;
|
||||
value, set_value: 6, 0;
|
||||
}
|
||||
|
||||
bitfield::bitfield! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct LengthLoad(u8);
|
||||
impl Debug;
|
||||
load, set_load: 7, 3;
|
||||
timer_high, set_timer_high: 2, 0;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TriangleChannel {
|
||||
enabled: bool,
|
||||
length: LengthCounter,
|
||||
timer_low: u8,
|
||||
length_load: LengthLoad,
|
||||
reload: bool,
|
||||
|
||||
length_counter: u16,
|
||||
cur: u8,
|
||||
cur_time: u16,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl TriangleChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
length: LengthCounter(0),
|
||||
timer_low: 0,
|
||||
length_load: LengthLoad(0),
|
||||
reload: false,
|
||||
enabled: false,
|
||||
sample: 0,
|
||||
cur_time: 0,
|
||||
cur: 0,
|
||||
length_counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn timer(&self) -> u16 {
|
||||
self.timer_low as u16 | ((self.length_load.timer_high() as u16) << 8)
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
|
||||
pub fn clock(&mut self) {
|
||||
if self.length_counter > 0 && self.timer() > 0 {
|
||||
if self.cur_time == 0 {
|
||||
self.cur_time = self.timer();
|
||||
const SAMPLES: [u8; 32] = [
|
||||
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
];
|
||||
self.cur = (self.cur + 1) % SAMPLES.len() as u8;
|
||||
self.sample = SAMPLES[self.cur as usize];
|
||||
} else {
|
||||
self.cur_time -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn q_frame_clock(&mut self) {
|
||||
if self.reload {
|
||||
self.length_counter = self.length.value() as u16;
|
||||
self.reload = self.length.halt();
|
||||
} else if self.length_counter == 0 {
|
||||
} else {
|
||||
self.length_counter -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
"Triangle Channel
|
||||
Linear Counter - Reload: {0:>3} ${0:02X}
|
||||
Linear Counter - Halted: {1}
|
||||
Period: {2:>3} ${2:04X}
|
||||
Length Counter - Reload Value: {3:>3} ${3:04X}
|
||||
Enabled: {4}
|
||||
Timer: {5:>3} ${5:02X}
|
||||
Frequency: ???
|
||||
Sequence Position: {6:>3} ${6:02X}
|
||||
Length Counter - Counter: {7:>3} ${7:02X}
|
||||
Linear Counter - Counter: {8:>3} ${8:02X}
|
||||
Linear Counter - Reload Flag: {9}
|
||||
Output: {10:>3} ${10:02X}
|
||||
",
|
||||
self.length.value(),
|
||||
self.length.halt(),
|
||||
self.timer(),
|
||||
0,
|
||||
self.enabled,
|
||||
self.cur_time,
|
||||
self.cur,
|
||||
0,
|
||||
self.length_counter,
|
||||
self.reload,
|
||||
self.cur_sample(),
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct NoiseChannel {
|
||||
enabled: bool,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl NoiseChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
sample: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
pub fn clock(&mut self) {}
|
||||
pub fn q_frame_clock(&mut self) {}
|
||||
pub fn h_frame_clock(&mut self) {}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DeltaChannel {
|
||||
enabled: bool,
|
||||
sample: u8,
|
||||
}
|
||||
|
||||
impl DeltaChannel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
sample: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
self.sample
|
||||
}
|
||||
pub fn clock(&mut self) {}
|
||||
pub fn q_frame_clock(&mut self) {}
|
||||
pub fn h_frame_clock(&mut self) {}
|
||||
|
||||
pub fn int(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -131,6 +314,8 @@ pub struct APU {
|
||||
noise: NoiseChannel,
|
||||
dmc: DeltaChannel,
|
||||
frame_counter: FrameCounter,
|
||||
|
||||
samples: Vec<u8>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for APU {
|
||||
@@ -149,15 +334,17 @@ impl APU {
|
||||
Self {
|
||||
pulse_1: PulseChannel::new(),
|
||||
pulse_2: PulseChannel::new(),
|
||||
triangle: TriangleChannel { enabled: false },
|
||||
noise: NoiseChannel { enabled: false },
|
||||
dmc: DeltaChannel { enabled: false },
|
||||
triangle: TriangleChannel::new(),
|
||||
noise: NoiseChannel::new(),
|
||||
dmc: DeltaChannel::new(),
|
||||
frame_counter: FrameCounter {
|
||||
mode_5_step: false,
|
||||
interrupt_enabled: true,
|
||||
count: 0,
|
||||
irq: false,
|
||||
},
|
||||
|
||||
samples: vec![0; 100],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +388,15 @@ impl APU {
|
||||
self.pulse_2.reset();
|
||||
}
|
||||
0x09 => (), // Unused, technically noise channel?
|
||||
0x08 | 0x0A | 0x0B => {
|
||||
// TODO: Triangle channel
|
||||
0x08 => {
|
||||
self.triangle.length.0 = val;
|
||||
}
|
||||
0x0A => {
|
||||
self.triangle.timer_low = val;
|
||||
}
|
||||
0x0B => {
|
||||
self.triangle.length_load.0 = val;
|
||||
self.triangle.reload = true;
|
||||
}
|
||||
0x0D => (), // Unused, technically noise channel?
|
||||
0x0C | 0x0E | 0x0F => {
|
||||
@@ -242,14 +436,41 @@ impl APU {
|
||||
|
||||
fn q_frame_clock(&mut self) {
|
||||
self.pulse_1.q_frame_clock();
|
||||
self.pulse_2.q_frame_clock(); // TODO: clock all
|
||||
self.pulse_2.q_frame_clock();
|
||||
self.triangle.q_frame_clock();
|
||||
self.noise.q_frame_clock();
|
||||
self.dmc.q_frame_clock();
|
||||
}
|
||||
|
||||
fn h_frame_clock(&mut self) {
|
||||
self.pulse_1.q_frame_clock();
|
||||
self.pulse_1.h_frame_clock();
|
||||
self.pulse_2.q_frame_clock();
|
||||
self.pulse_2.h_frame_clock();
|
||||
self.triangle.h_frame_clock();
|
||||
self.noise.h_frame_clock();
|
||||
self.dmc.h_frame_clock();
|
||||
}
|
||||
|
||||
fn gen_sample(&mut self) {
|
||||
macro_rules! lut {
|
||||
($name:ident: [$ty:ty; $len:expr] = |$n:ident| $expr:expr) => {
|
||||
const $name: [$ty; $len] = {
|
||||
let mut table = [0; $len];
|
||||
let mut $n = 0;
|
||||
while $n < $len {
|
||||
table[$n] = $expr;
|
||||
$n += 1;
|
||||
}
|
||||
table
|
||||
};
|
||||
};
|
||||
}
|
||||
lut!(P_LUT: [u8; 32] = |n| (95.52 / (8128.0 / n as f64 + 100.0) * 255.0) as u8);
|
||||
let pulse_out = P_LUT[(self.pulse_1.cur_sample() + self.pulse_2.cur_sample()) as usize];
|
||||
lut!(TND_LUT: [u8; 204] = |n| (163.67 / (24329.0 / n as f64 + 100.0) * 255.0) as u8);
|
||||
let tnd_out = TND_LUT[3 * self.triangle.cur_sample() as usize
|
||||
+ 2 * self.noise.cur_sample() as usize
|
||||
+ self.dmc.cur_sample() as usize];
|
||||
self.samples.push(pulse_out + tnd_out);
|
||||
}
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
||||
@@ -275,10 +496,27 @@ impl APU {
|
||||
|
||||
self.pulse_1.clock();
|
||||
self.pulse_2.clock();
|
||||
self.noise.clock();
|
||||
self.dmc.clock();
|
||||
}
|
||||
if ppu_cycle % 3 == 1 {
|
||||
self.triangle.clock();
|
||||
}
|
||||
if ppu_cycle % (6 * 4) == 1 {
|
||||
self.gen_sample();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_frame_samples(&self) -> &[u8] {
|
||||
// println!("'Frame' of samples: {}", self.samples.len());
|
||||
&self.samples
|
||||
}
|
||||
|
||||
pub fn reset_frame_samples(&mut self) {
|
||||
self.samples.clear();
|
||||
}
|
||||
|
||||
pub fn peek_nmi(&self) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -294,4 +532,15 @@ impl APU {
|
||||
pub fn irq_waiting(&mut self) -> bool {
|
||||
self.frame_counter.irq
|
||||
}
|
||||
|
||||
pub fn view<'s, T: 's>(&'s self) -> Element<'s, T> {
|
||||
column![
|
||||
self.pulse_1.view(),
|
||||
self.pulse_2.view(),
|
||||
self.triangle.view(),
|
||||
self.noise.view(),
|
||||
self.dmc.view(),
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user