Major progress on APU pulse channels & organization
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 9s
This commit is contained in:
417
src/apu.rs
417
src/apu.rs
@@ -6,7 +6,64 @@ use iced::{
|
|||||||
};
|
};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
pub enum None {}
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct LengthTimerHigh(u8);
|
||||||
|
impl Debug;
|
||||||
|
length, set_length: 7, 3;
|
||||||
|
u16;
|
||||||
|
timer_high, set_timer_high: 2, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LengthCounter {
|
||||||
|
current: u16,
|
||||||
|
silenced: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LengthCounter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
current: 0,
|
||||||
|
silenced: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clock(&mut self) {
|
||||||
|
if self.current == 0 {
|
||||||
|
self.silenced = true;
|
||||||
|
} else {
|
||||||
|
self.current -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, length: u8) {
|
||||||
|
// self.reg.0 = val;
|
||||||
|
self.silenced = false;
|
||||||
|
const LENGTH_LUT: [u16; 0x20] = [
|
||||||
|
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20,
|
||||||
|
96, 22, 192, 24, 72, 26, 16, 28, 32, 3,
|
||||||
|
];
|
||||||
|
self.current = LENGTH_LUT[length as usize] + 1; // I think?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn silenced(&self) -> bool {
|
||||||
|
self.silenced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -28,26 +85,23 @@ bitfield::bitfield! {
|
|||||||
shift, set_shift: 2, 0;
|
shift, set_shift: 2, 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bitfield::bitfield! {
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct LengthTimerHigh(u8);
|
|
||||||
impl Debug;
|
|
||||||
length, set_length: 7, 3;
|
|
||||||
timer_high, set_timer_high: 2, 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PulseChannel {
|
struct PulseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
duty_vol: DutyVol,
|
duty_vol: DutyVol,
|
||||||
sweep: Sweep,
|
sweep: Sweep,
|
||||||
timer_low: u8,
|
sweep_reload: bool,
|
||||||
length_timer_high: LengthTimerHigh,
|
|
||||||
|
|
||||||
cur_time: u16,
|
counter: LengthCounter,
|
||||||
cur_length: u8,
|
|
||||||
|
period: u16,
|
||||||
|
period_timer: u16,
|
||||||
cur: u8,
|
cur: u8,
|
||||||
sample: u8,
|
sample: u8,
|
||||||
|
|
||||||
|
envelope_start: bool,
|
||||||
|
envelope_counter: u8,
|
||||||
|
envelope_divider: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PulseChannel {
|
impl PulseChannel {
|
||||||
@@ -56,33 +110,55 @@ impl PulseChannel {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
duty_vol: DutyVol(0),
|
duty_vol: DutyVol(0),
|
||||||
sweep: Sweep(0),
|
sweep: Sweep(0),
|
||||||
timer_low: 0,
|
sweep_reload: false,
|
||||||
length_timer_high: LengthTimerHigh(0),
|
counter: LengthCounter::new(),
|
||||||
cur_time: 0,
|
period: 0,
|
||||||
cur_length: 0,
|
period_timer: 0,
|
||||||
cur: 0,
|
cur: 0,
|
||||||
sample: 0,
|
sample: 0,
|
||||||
|
envelope_start: false,
|
||||||
|
envelope_counter: 0,
|
||||||
|
envelope_divider: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn use_envelope(&self) -> bool {
|
|
||||||
!self.duty_vol.const_vol()
|
pub fn write(&mut self, offset: u16, val: u8) {
|
||||||
}
|
match offset {
|
||||||
fn volume(&self) -> u8 {
|
0x00 => {
|
||||||
self.duty_vol.volume()
|
self.duty_vol.0 = val;
|
||||||
}
|
self.envelope_start = true;
|
||||||
fn timer(&self) -> u16 {
|
}
|
||||||
self.timer_low as u16 | ((self.length_timer_high.timer_high() as u16) << 8)
|
0x01 => {
|
||||||
|
self.sweep.0 = val;
|
||||||
|
self.sweep_reload = true;
|
||||||
|
},
|
||||||
|
0x02 => self.period = self.period & 0x700 | (val as u16),
|
||||||
|
0x03 => {
|
||||||
|
let reg = LengthTimerHigh(val);
|
||||||
|
self.counter.write(reg.length());
|
||||||
|
self.period = (self.period & 0xFF) | (reg.timer_high() << 8);
|
||||||
|
self.period_timer = self.period;
|
||||||
|
self.cur = 0;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
fn volume(&self) -> u8 {
|
||||||
// TODO
|
if self.duty_vol.const_vol() {
|
||||||
self.cur_time = self.timer();
|
self.duty_vol.volume()
|
||||||
self.cur_length = self.length_timer_high.length();
|
} else {
|
||||||
self.cur = 0;
|
self.envelope_counter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock(&mut self) {
|
pub fn clock(&mut self) {
|
||||||
if self.cur_time == 0 {
|
if !self.enabled {
|
||||||
self.cur_time = self.timer();
|
self.sample = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.period_timer == 0 {
|
||||||
|
self.period_timer = self.period;
|
||||||
self.cur = (self.cur + 1) % 8;
|
self.cur = (self.cur + 1) % 8;
|
||||||
const DUTY: [[bool; 8]; 4] = [
|
const DUTY: [[bool; 8]; 4] = [
|
||||||
[false, true, false, false, false, false, false, false],
|
[false, true, false, false, false, false, false, false],
|
||||||
@@ -96,30 +172,81 @@ impl PulseChannel {
|
|||||||
0x00
|
0x00
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
self.cur_time -= 1;
|
self.period_timer -= 1;
|
||||||
}
|
}
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
pub fn cur_sample(&self) -> u8 {
|
pub fn cur_sample(&self) -> u8 {
|
||||||
self.sample
|
if self.enabled && !self.counter.silenced() {
|
||||||
|
self.sample
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn q_frame_clock(&mut self) {
|
pub fn q_frame_clock(&mut self) {
|
||||||
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
if !self.enabled {
|
||||||
self.cur_length -= 1;
|
return;
|
||||||
|
}
|
||||||
|
if !self.duty_vol.length_counter_halt() {
|
||||||
|
self.counter.clock();
|
||||||
|
} else {
|
||||||
|
self.counter.silenced = false;
|
||||||
|
}
|
||||||
|
if !self.duty_vol.const_vol() {
|
||||||
|
if self.envelope_start || self.envelope_counter == 0 {
|
||||||
|
self.envelope_counter = 0xF;
|
||||||
|
self.envelope_divider = self.duty_vol.volume();
|
||||||
|
self.envelope_start = false;
|
||||||
|
} else if self.envelope_divider == 0 {
|
||||||
|
self.envelope_divider = self.duty_vol.volume();
|
||||||
|
self.envelope_counter -= 1;
|
||||||
|
} else {
|
||||||
|
self.envelope_divider -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn h_frame_clock(&mut self) {
|
pub fn h_frame_clock(&mut self) {
|
||||||
self.q_frame_clock();
|
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> {
|
pub fn view<T>(&self) -> Element<'_, T> {
|
||||||
text!(
|
text!(
|
||||||
"
|
"Square Channel
|
||||||
Square Channel
|
Evelope Volume: {0:>3} ${0:02X}
|
||||||
"
|
Constant Volume: {1}
|
||||||
|
Length Counter - Halted: {2}
|
||||||
|
Duty: {3:>3} ${3:02X}
|
||||||
|
Sweep - Shift: {4:>3} ${4:02X}
|
||||||
|
Sweep - Negate: {5}
|
||||||
|
Sweep - Period: {6:>3} ${6:02X}
|
||||||
|
Sweep - Enabled: {7}
|
||||||
|
Period: {8:>3} ${8:04X}
|
||||||
|
Length Counter - Reload Value: {9:>3} ${9:04X}
|
||||||
|
Enabled: {10}
|
||||||
|
Timer: {11:>3} ${11:04X}
|
||||||
|
Duty Position: {12:>3} ${12:02X}
|
||||||
|
Length Counter - Counter: {13:>3} ${13:02X}
|
||||||
|
Envelope - Counter: {14:>3} ${14:02X}
|
||||||
|
Envelope - Divider: {15:>3} ${15:02X}
|
||||||
|
Output: {16:>3} ${16:02X}
|
||||||
|
",
|
||||||
|
self.duty_vol.volume(),
|
||||||
|
self.duty_vol.const_vol(),
|
||||||
|
self.duty_vol.length_counter_halt(),
|
||||||
|
self.duty_vol.duty(),
|
||||||
|
self.sweep.shift(),
|
||||||
|
self.sweep.negate(),
|
||||||
|
self.sweep.period(),
|
||||||
|
self.sweep.enable(),
|
||||||
|
self.period,
|
||||||
|
0,
|
||||||
|
self.enabled,
|
||||||
|
self.period_timer,
|
||||||
|
self.cur, // ?
|
||||||
|
self.counter.current,
|
||||||
|
self.envelope_counter,
|
||||||
|
self.envelope_divider,
|
||||||
|
self.cur_sample(),
|
||||||
)
|
)
|
||||||
.font(Font::MONOSPACE)
|
.font(Font::MONOSPACE)
|
||||||
.into()
|
.into()
|
||||||
@@ -128,51 +255,59 @@ impl PulseChannel {
|
|||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct LengthCounter(u8);
|
pub struct LengthCounterReg(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
halt, set_halt: 7;
|
halt, set_halt: 7;
|
||||||
value, set_value: 6, 0;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
struct TriangleChannel {
|
struct TriangleChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
length: LengthCounter,
|
length: LengthCounterReg,
|
||||||
timer_low: u8,
|
|
||||||
length_load: LengthLoad,
|
|
||||||
reload: bool,
|
reload: bool,
|
||||||
|
counter: LengthCounter,
|
||||||
|
|
||||||
length_counter: u16,
|
length_counter: u16,
|
||||||
cur: u8,
|
cur: u8,
|
||||||
cur_time: u16,
|
period: u16,
|
||||||
|
period_timer: u16,
|
||||||
sample: u8,
|
sample: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TriangleChannel {
|
impl TriangleChannel {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
length: LengthCounter(0),
|
length: LengthCounterReg(0),
|
||||||
timer_low: 0,
|
counter: LengthCounter::new(),
|
||||||
length_load: LengthLoad(0),
|
|
||||||
reload: false,
|
reload: false,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
sample: 0,
|
sample: 0,
|
||||||
cur_time: 0,
|
period: 0,
|
||||||
|
period_timer: 0,
|
||||||
cur: 0,
|
cur: 0,
|
||||||
length_counter: 0,
|
length_counter: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timer(&self) -> u16 {
|
pub fn write(&mut self, offset: u16, val: u8) {
|
||||||
self.timer_low as u16 | ((self.length_load.timer_high() as u16) << 8)
|
match offset {
|
||||||
|
0x00 => {
|
||||||
|
self.length.0 = val;
|
||||||
|
}
|
||||||
|
0x01 => (),
|
||||||
|
0x02 => self.period = self.period & 0x700 | (val as u16),
|
||||||
|
0x03 => {
|
||||||
|
// self.length_load.0 = val;
|
||||||
|
// self.reload = true;
|
||||||
|
let reg = LengthTimerHigh(val);
|
||||||
|
self.counter.write(reg.length());
|
||||||
|
self.period = (self.period & 0xFF) | (reg.timer_high() << 8);
|
||||||
|
self.period_timer = self.period;
|
||||||
|
self.cur = 0;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cur_sample(&self) -> u8 {
|
pub fn cur_sample(&self) -> u8 {
|
||||||
@@ -180,9 +315,9 @@ impl TriangleChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock(&mut self) {
|
pub fn clock(&mut self) {
|
||||||
if self.length_counter > 0 && self.timer() > 0 {
|
if self.length_counter > 0 && self.period > 0 {
|
||||||
if self.cur_time == 0 {
|
if self.period_timer == 0 {
|
||||||
self.cur_time = self.timer();
|
self.period_timer = self.period;
|
||||||
const SAMPLES: [u8; 32] = [
|
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,
|
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,
|
8, 9, 10, 11, 12, 13, 14, 15,
|
||||||
@@ -190,7 +325,7 @@ impl TriangleChannel {
|
|||||||
self.cur = (self.cur + 1) % SAMPLES.len() as u8;
|
self.cur = (self.cur + 1) % SAMPLES.len() as u8;
|
||||||
self.sample = SAMPLES[self.cur as usize];
|
self.sample = SAMPLES[self.cur as usize];
|
||||||
} else {
|
} else {
|
||||||
self.cur_time -= 1;
|
self.period_timer -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,10 +362,10 @@ Output: {10:>3} ${10:02X}
|
|||||||
",
|
",
|
||||||
self.length.value(),
|
self.length.value(),
|
||||||
self.length.halt(),
|
self.length.halt(),
|
||||||
self.timer(),
|
self.period,
|
||||||
0,
|
0,
|
||||||
self.enabled,
|
self.enabled,
|
||||||
self.cur_time,
|
self.period_timer,
|
||||||
self.cur,
|
self.cur,
|
||||||
0,
|
0,
|
||||||
self.length_counter,
|
self.length_counter,
|
||||||
@@ -242,26 +377,93 @@ Output: {10:>3} ${10:02X}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct NoiseEnvelope(u8);
|
||||||
|
impl Debug;
|
||||||
|
length_counter_halt, set_length_counter_halt: 5;
|
||||||
|
constant_volume, set_constant_volume: 4;
|
||||||
|
volume, set_volume: 3, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ModePeriod(u8);
|
||||||
|
impl Debug;
|
||||||
|
mode, set_mode: 7;
|
||||||
|
period, set_period: 3, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LengthCounterLoad(u8);
|
||||||
|
impl Debug;
|
||||||
|
length, set_length: 7, 3;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct NoiseChannel {
|
struct NoiseChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
sample: u8,
|
evelope: NoiseEnvelope,
|
||||||
|
mode_period: ModePeriod,
|
||||||
|
length_counter_load: LengthCounterLoad,
|
||||||
|
|
||||||
|
shr: u16,
|
||||||
|
count: u16,
|
||||||
|
length_counter: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoiseChannel {
|
impl NoiseChannel {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
sample: 0,
|
evelope: NoiseEnvelope(0),
|
||||||
|
mode_period: ModePeriod(0),
|
||||||
|
length_counter_load: LengthCounterLoad(0),
|
||||||
|
shr: 1,
|
||||||
|
count: 0,
|
||||||
|
length_counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, offset: u16, val: u8) {
|
||||||
|
match offset {
|
||||||
|
0x00 => (),
|
||||||
|
0x01 => (),
|
||||||
|
0x02 => (),
|
||||||
|
0x03 => (),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cur_sample(&self) -> u8 {
|
pub fn cur_sample(&self) -> u8 {
|
||||||
self.sample
|
if self.length_counter == 0 || self.shr & 1 == 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.evelope.volume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn clock(&mut self) {
|
||||||
|
const PERIODS: &[u16] = &[
|
||||||
|
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4096,
|
||||||
|
];
|
||||||
|
if self.count == PERIODS[self.mode_period.period() as usize] / 2 {
|
||||||
|
self.count = 0;
|
||||||
|
let bit_0 = (self.shr & 0b1) << 14;
|
||||||
|
let other = if self.mode_period.mode() {
|
||||||
|
(self.shr & 0b100_0000) << 8
|
||||||
|
} else {
|
||||||
|
(self.shr & 0b10) << 13
|
||||||
|
};
|
||||||
|
self.shr = (self.shr >> 1) | (bit_0 ^ other);
|
||||||
|
} else {
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn clock(&mut self) {}
|
|
||||||
pub fn q_frame_clock(&mut self) {}
|
pub fn q_frame_clock(&mut self) {}
|
||||||
pub fn h_frame_clock(&mut self) {}
|
pub fn h_frame_clock(&mut self) {
|
||||||
|
self.q_frame_clock();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view<T>(&self) -> Element<'_, T> {
|
pub fn view<T>(&self) -> Element<'_, T> {
|
||||||
text!("").font(Font::MONOSPACE).into()
|
text!("").font(Font::MONOSPACE).into()
|
||||||
@@ -282,6 +484,16 @@ impl DeltaChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write(&mut self, offset: u16, val: u8) {
|
||||||
|
match offset {
|
||||||
|
0x00 => (),
|
||||||
|
0x01 => (),
|
||||||
|
0x02 => (),
|
||||||
|
0x03 => (),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cur_sample(&self) -> u8 {
|
pub fn cur_sample(&self) -> u8 {
|
||||||
self.sample
|
self.sample
|
||||||
}
|
}
|
||||||
@@ -373,48 +585,12 @@ impl APU {
|
|||||||
pub fn write_reg(&mut self, offset: u16, val: u8) {
|
pub fn write_reg(&mut self, offset: u16, val: u8) {
|
||||||
// println!("APU write: {offset:02X} <= {val:02X}");
|
// println!("APU write: {offset:02X} <= {val:02X}");
|
||||||
match offset {
|
match offset {
|
||||||
0x00 => self.pulse_1.duty_vol.0 = val,
|
0x00..0x04 => self.pulse_1.write(offset - 0x00, val),
|
||||||
0x01 => self.pulse_1.sweep.0 = val,
|
0x04..0x08 => self.pulse_2.write(offset - 0x04, val),
|
||||||
0x02 => self.pulse_1.timer_low = val,
|
0x08..0x0C => self.triangle.write(offset - 0x08, val),
|
||||||
0x03 => {
|
0x0C..0x10 => self.noise.write(offset - 0x0C, val),
|
||||||
self.pulse_1.length_timer_high.0 = val;
|
0x10..0x14 => self.dmc.write(offset - 0x10, val),
|
||||||
self.pulse_1.reset();
|
0x14 => (),
|
||||||
}
|
|
||||||
0x04 => self.pulse_2.duty_vol.0 = val,
|
|
||||||
0x05 => self.pulse_2.sweep.0 = val,
|
|
||||||
0x06 => self.pulse_2.timer_low = val,
|
|
||||||
0x07 => {
|
|
||||||
self.pulse_2.length_timer_high.0 = val;
|
|
||||||
self.pulse_2.reset();
|
|
||||||
}
|
|
||||||
0x09 => (), // Unused, technically noise 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 => {
|
|
||||||
// TODO: Noise channel
|
|
||||||
}
|
|
||||||
0x10 => {
|
|
||||||
assert_eq!(val, 0x00);
|
|
||||||
// TODO: implement this value
|
|
||||||
}
|
|
||||||
0x11 => {
|
|
||||||
// TODO: load dmc counter with (val & 7F)
|
|
||||||
}
|
|
||||||
0x12 => {
|
|
||||||
// TODO: DMC
|
|
||||||
}
|
|
||||||
0x13 => {
|
|
||||||
// TODO: DMC
|
|
||||||
}
|
|
||||||
0x15 => {
|
0x15 => {
|
||||||
self.dmc.enabled = val & 0b0001_0000 != 0;
|
self.dmc.enabled = val & 0b0001_0000 != 0;
|
||||||
self.noise.enabled = val & 0b0000_1000 != 0;
|
self.noise.enabled = val & 0b0000_1000 != 0;
|
||||||
@@ -451,19 +627,6 @@ impl APU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gen_sample(&mut self) {
|
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);
|
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];
|
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);
|
lut!(TND_LUT: [u8; 204] = |n| (163.67 / (24329.0 / n as f64 + 100.0) * 255.0) as u8);
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ impl Audio {
|
|||||||
|
|
||||||
pub fn pause(&mut self) {
|
pub fn pause(&mut self) {
|
||||||
self.paused.store(true, std::sync::atomic::Ordering::Release);
|
self.paused.store(true, std::sync::atomic::Ordering::Release);
|
||||||
|
let _ = self._stream.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn submit(&mut self, samples: &[u8]) {
|
pub fn submit(&mut self, samples: &[u8]) {
|
||||||
@@ -89,6 +90,7 @@ impl Audio {
|
|||||||
self.last = self.rb.occupied_len();
|
self.last = self.rb.occupied_len();
|
||||||
if self.last > 9000 {
|
if self.last > 9000 {
|
||||||
self.paused.store(false, std::sync::atomic::Ordering::Release);
|
self.paused.store(false, std::sync::atomic::Ordering::Release);
|
||||||
|
let _ = self._stream.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ use tracing_subscriber::EnvFilter;
|
|||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "scrolling_colors.nes");
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "scrolling_colors.nes");
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "sprites.nes");
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "sprites.nes");
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1.nes");
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1.nes");
|
||||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_triangle.nes");
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_channel_1_evelope.nes");
|
||||||
|
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_pulse_1.nes");
|
||||||
|
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "apu_triangle.nes");
|
||||||
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
|
||||||
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
// const ROM_FILE: &str = "./cpu_timing_test.nes";
|
||||||
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
||||||
|
|||||||
97
src/test_roms/apu_pulse_1.s
Normal file
97
src/test_roms/apu_pulse_1.s
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
.include "testing.s"
|
||||||
|
.include "audio_inp.s"
|
||||||
|
|
||||||
|
zp_res TIMER_LOW
|
||||||
|
; zp_res TIMER_LOW
|
||||||
|
; zp_res TIMER_LOW
|
||||||
|
; zp_res TIMER_LOW
|
||||||
|
|
||||||
|
reset:
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
ldx #$FF
|
||||||
|
txs
|
||||||
|
|
||||||
|
lda #$01
|
||||||
|
sta SNDCHN
|
||||||
|
lda #$AF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||||
|
sta PULSE_CH1_DLCV
|
||||||
|
lda #$7F ;
|
||||||
|
sta PULSE_CH1_SWEEP
|
||||||
|
lda #$6F
|
||||||
|
sta PULSE_CH1_TLOW
|
||||||
|
sta TIMER_LOW
|
||||||
|
lda #$F0
|
||||||
|
sta PULSE_CH1_LCTH
|
||||||
|
; PULSE_CH1_DLCV = $4000
|
||||||
|
; PULSE_CH1_SWEEP = $4001
|
||||||
|
; PULSE_CH1_TLOW = $4002
|
||||||
|
; PULSE_CH1_LCTH = $4003
|
||||||
|
|
||||||
|
jsr init_view
|
||||||
|
|
||||||
|
load_ppu_addr $2041
|
||||||
|
write_char_x 't'
|
||||||
|
write_char_x 'i'
|
||||||
|
write_char_x 'm'
|
||||||
|
write_char_x 'e'
|
||||||
|
write_char_x 'r'
|
||||||
|
write_char_x ' '
|
||||||
|
write_char_x 'l'
|
||||||
|
write_char_x 'o'
|
||||||
|
write_char_x 'w'
|
||||||
|
write_char_x ':'
|
||||||
|
|
||||||
|
load_ppu_addr $2061
|
||||||
|
; write_char_x 't'
|
||||||
|
; write_char_x 'i'
|
||||||
|
; write_char_x 'm'
|
||||||
|
; write_char_x 'e'
|
||||||
|
; write_char_x 'r'
|
||||||
|
; write_char_x ' '
|
||||||
|
; write_char_x 'h'
|
||||||
|
; write_char_x 'o'
|
||||||
|
; write_char_x 'w'
|
||||||
|
; write_char_x ':'
|
||||||
|
|
||||||
|
load_ppu_addr $204C
|
||||||
|
lda TIMER_LOW
|
||||||
|
jsr write_hex_a
|
||||||
|
|
||||||
|
jsr enable_rendering
|
||||||
|
loop:
|
||||||
|
jmp loop
|
||||||
|
|
||||||
|
; Joystick is in A and JOYTEMP
|
||||||
|
zp_res JOY_PREV
|
||||||
|
audio_nmi:
|
||||||
|
EOR JOY_PREV
|
||||||
|
STA JOY_PREV
|
||||||
|
lda JOYTEMP
|
||||||
|
and #JOY_UP_MASK
|
||||||
|
and JOY_PREV
|
||||||
|
beq next_1
|
||||||
|
load_ppu_addr $204C
|
||||||
|
lda #$01
|
||||||
|
adc TIMER_LOW
|
||||||
|
sta TIMER_LOW
|
||||||
|
sta PULSE_CH1_TLOW
|
||||||
|
jsr write_hex_a
|
||||||
|
next_1:
|
||||||
|
lda JOYTEMP
|
||||||
|
and #JOY_DOWN_MASK
|
||||||
|
and JOY_PREV
|
||||||
|
beq next_2
|
||||||
|
load_ppu_addr $204C
|
||||||
|
lda #$FF
|
||||||
|
adc TIMER_LOW
|
||||||
|
sta TIMER_LOW
|
||||||
|
sta PULSE_CH1_TLOW
|
||||||
|
jsr write_hex_a
|
||||||
|
next_2:
|
||||||
|
lda JOYTEMP
|
||||||
|
STA JOY_PREV
|
||||||
|
rts
|
||||||
|
|
||||||
|
irq:
|
||||||
|
rti
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
.include "testing.s"
|
.include "testing.s"
|
||||||
|
.include "minimal_ppu.s"
|
||||||
|
|
||||||
reset:
|
reset:
|
||||||
sei
|
sei
|
||||||
@@ -6,15 +7,6 @@ reset:
|
|||||||
ldx #$FF
|
ldx #$FF
|
||||||
txs
|
txs
|
||||||
|
|
||||||
; Init PPU
|
|
||||||
bit PPUSTATUS
|
|
||||||
vwait1:
|
|
||||||
bit PPUSTATUS
|
|
||||||
bpl vwait1
|
|
||||||
vwait2:
|
|
||||||
bit PPUSTATUS
|
|
||||||
bpl vwait2
|
|
||||||
|
|
||||||
lda #$01
|
lda #$01
|
||||||
sta SNDCHN
|
sta SNDCHN
|
||||||
lda #$BF ; Duty 2, LC halted, Constant volume, volume = F
|
lda #$BF ; Duty 2, LC halted, Constant volume, volume = F
|
||||||
@@ -31,34 +23,7 @@ vwait2:
|
|||||||
; PULSE_CH1_TLOW = $4002
|
; PULSE_CH1_TLOW = $4002
|
||||||
; PULSE_CH1_LCTH = $4003
|
; PULSE_CH1_LCTH = $4003
|
||||||
|
|
||||||
lda #$80
|
jmp init_ppu_and_wait
|
||||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
|
||||||
lda #$00
|
|
||||||
loop:
|
|
||||||
; sta SNDMODE
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
; nop
|
|
||||||
jmp loop
|
|
||||||
|
|
||||||
; zp_res
|
; zp_res
|
||||||
zp_res TIMER_LOW
|
zp_res TIMER_LOW
|
||||||
|
|||||||
60
src/test_roms/apu_pulse_channel_1_evelope.s
Normal file
60
src/test_roms/apu_pulse_channel_1_evelope.s
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
.include "testing.s"
|
||||||
|
.include "joysticks.s"
|
||||||
|
.include "minimal_ppu.s"
|
||||||
|
|
||||||
|
reset:
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
ldx #$FF
|
||||||
|
txs
|
||||||
|
|
||||||
|
lda #$01
|
||||||
|
sta SNDCHN
|
||||||
|
lda #$AF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||||
|
sta PULSE_CH1_DLCV
|
||||||
|
lda #$7F ;
|
||||||
|
sta PULSE_CH1_SWEEP
|
||||||
|
lda #$6F
|
||||||
|
sta PULSE_CH1_TLOW
|
||||||
|
sta TIMER_LOW
|
||||||
|
lda #$F0
|
||||||
|
sta PULSE_CH1_LCTH
|
||||||
|
; PULSE_CH1_DLCV = $4000
|
||||||
|
; PULSE_CH1_SWEEP = $4001
|
||||||
|
; PULSE_CH1_TLOW = $4002
|
||||||
|
; PULSE_CH1_LCTH = $4003
|
||||||
|
|
||||||
|
jmp init_ppu_and_wait
|
||||||
|
|
||||||
|
; zp_res
|
||||||
|
zp_res TIMER_LOW
|
||||||
|
update_audio:
|
||||||
|
adc TIMER_LOW
|
||||||
|
sta PULSE_CH1_TLOW
|
||||||
|
sta TIMER_LOW
|
||||||
|
|
||||||
|
rts
|
||||||
|
|
||||||
|
zp_res PRESSED_A
|
||||||
|
zp_res PRESSED_B
|
||||||
|
|
||||||
|
nmi:
|
||||||
|
nmi_start
|
||||||
|
jsr read_joy1
|
||||||
|
|
||||||
|
and #$80
|
||||||
|
beq not_a
|
||||||
|
lda #$08
|
||||||
|
jsr update_audio
|
||||||
|
not_a:
|
||||||
|
lda JOYTEMP
|
||||||
|
and #$40
|
||||||
|
beq not_b
|
||||||
|
lda #$01
|
||||||
|
jsr update_audio
|
||||||
|
not_b:
|
||||||
|
nmi_end
|
||||||
|
|
||||||
|
|
||||||
|
irq:
|
||||||
|
rti
|
||||||
109
src/test_roms/common/audio_inp.s
Normal file
109
src/test_roms/common/audio_inp.s
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
.include "joysticks.s"
|
||||||
|
; Characters for view
|
||||||
|
.include "bitmap_font.s"
|
||||||
|
|
||||||
|
zp_res TEMP
|
||||||
|
|
||||||
|
.macro load_ppu_addr addr
|
||||||
|
lda #.hibyte(addr)
|
||||||
|
sta PPUADDR
|
||||||
|
lda #.lobyte(addr)
|
||||||
|
sta PPUADDR ; Load $2000
|
||||||
|
.endmacro
|
||||||
|
|
||||||
|
init_view:
|
||||||
|
; Init PPU
|
||||||
|
bit PPUSTATUS
|
||||||
|
vwait1:
|
||||||
|
bit PPUSTATUS
|
||||||
|
bpl vwait1
|
||||||
|
vwait2:
|
||||||
|
bit PPUSTATUS
|
||||||
|
bpl vwait2
|
||||||
|
|
||||||
|
lda #$00
|
||||||
|
sta PPUCTRL ; NMI off, PPU slave, Small sprites, 0 addresses
|
||||||
|
|
||||||
|
; Fill NT0
|
||||||
|
load_ppu_addr $2000
|
||||||
|
ldx #$00
|
||||||
|
ldy #$04
|
||||||
|
lda #EMPTY
|
||||||
|
fill_loop:
|
||||||
|
sta PPUDATA
|
||||||
|
dex
|
||||||
|
bne fill_loop
|
||||||
|
dey
|
||||||
|
bne fill_loop
|
||||||
|
|
||||||
|
load_ppu_addr $23C0
|
||||||
|
ldx #$C0
|
||||||
|
lda #$00
|
||||||
|
palette_loop:
|
||||||
|
sta PPUDATA
|
||||||
|
dex
|
||||||
|
bne palette_loop
|
||||||
|
|
||||||
|
load_ppu_addr $3F00
|
||||||
|
lda #$0f
|
||||||
|
sta PPUDATA
|
||||||
|
lda #$20
|
||||||
|
sta PPUDATA
|
||||||
|
sta PPUDATA
|
||||||
|
sta PPUDATA
|
||||||
|
lda #$0f
|
||||||
|
sta PPUDATA
|
||||||
|
lda #$09
|
||||||
|
sta PPUDATA
|
||||||
|
sta PPUDATA
|
||||||
|
sta PPUDATA
|
||||||
|
rts
|
||||||
|
|
||||||
|
enable_rendering:
|
||||||
|
lda #$00
|
||||||
|
sta PPUSCROLL
|
||||||
|
sta PPUSCROLL
|
||||||
|
lda #%00001110
|
||||||
|
sta PPUMASK
|
||||||
|
lda #$80
|
||||||
|
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||||
|
rts
|
||||||
|
|
||||||
|
nmi:
|
||||||
|
lda PPUSTATUS
|
||||||
|
lda #%00000110
|
||||||
|
sta PPUMASK ; Force blank
|
||||||
|
|
||||||
|
jsr read_joy1
|
||||||
|
jsr audio_nmi
|
||||||
|
|
||||||
|
.macro bit_lsr val
|
||||||
|
.scope
|
||||||
|
lsr
|
||||||
|
bcs carry_set
|
||||||
|
ldx #EMPTY
|
||||||
|
jmp carry_clear
|
||||||
|
carry_set:
|
||||||
|
ldx val
|
||||||
|
carry_clear:
|
||||||
|
stx PPUDATA
|
||||||
|
.endscope
|
||||||
|
.endmacro
|
||||||
|
load_ppu_addr $2008
|
||||||
|
lda JOYTEMP
|
||||||
|
bit_lsr #RIGHT
|
||||||
|
bit_lsr #LEFT
|
||||||
|
bit_lsr #DOWN
|
||||||
|
bit_lsr #UP
|
||||||
|
bit_lsr #PILL
|
||||||
|
bit_lsr #PILL
|
||||||
|
bit_lsr #$0B
|
||||||
|
bit_lsr #$0A
|
||||||
|
|
||||||
|
lda #$00
|
||||||
|
sta PPUSCROLL
|
||||||
|
sta PPUSCROLL
|
||||||
|
|
||||||
|
lda #%00001110
|
||||||
|
sta PPUMASK ; Enable rendering
|
||||||
|
rti
|
||||||
530
src/test_roms/common/bitmap_font.s
Normal file
530
src/test_roms/common/bitmap_font.s
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
.pushseg
|
||||||
|
.segment "CHARS"
|
||||||
|
.repeat 2 ; 0
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01001100
|
||||||
|
.byte %01010100
|
||||||
|
.byte %01100100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 1
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00110000
|
||||||
|
.byte %01010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %01111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 2
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00100000
|
||||||
|
.byte %01111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 3
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 4
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00101000
|
||||||
|
.byte %01001000
|
||||||
|
.byte %11111100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00001000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 5
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111110
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %00000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %00111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 6
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111100
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %00111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 7
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111110
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 8
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; 9
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111100
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; A
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %11111100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; B
|
||||||
|
.byte %00000000
|
||||||
|
.byte %11111000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %11111000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %11111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; C
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %01111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; D
|
||||||
|
.byte %00000000
|
||||||
|
.byte %11111000
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %10000100
|
||||||
|
.byte %11111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; E
|
||||||
|
.byte %00000000
|
||||||
|
.byte %11111100
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %11111000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %11111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; F
|
||||||
|
.byte %00000000
|
||||||
|
.byte %11111100
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %11110000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; G
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %10000010
|
||||||
|
.byte %10000000
|
||||||
|
.byte %10001110
|
||||||
|
.byte %10000010
|
||||||
|
.byte %10000010
|
||||||
|
.byte %01111110
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; H
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01111110
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; I
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %01111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; J
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %01001000
|
||||||
|
.byte %00110000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; K
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01001000
|
||||||
|
.byte %01010000
|
||||||
|
.byte %01100000
|
||||||
|
.byte %01010000
|
||||||
|
.byte %01001000
|
||||||
|
.byte %01000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; L
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; M
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01100110
|
||||||
|
.byte %01011010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; N
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01100010
|
||||||
|
.byte %01010010
|
||||||
|
.byte %01001010
|
||||||
|
.byte %01000110
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; O
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; P
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01111100
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.byte %01000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; Q
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01001100
|
||||||
|
.byte %00111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; R
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01111100
|
||||||
|
.byte %01010000
|
||||||
|
.byte %01001000
|
||||||
|
.byte %01000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; S
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111100
|
||||||
|
.byte %01000010
|
||||||
|
.byte %00100000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00111000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; T
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111100
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; U
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %01000010
|
||||||
|
.byte %00111100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; V
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00101000
|
||||||
|
.byte %00101000
|
||||||
|
.byte %00101000
|
||||||
|
.byte %00010000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; W
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01010100
|
||||||
|
.byte %01101100
|
||||||
|
.byte %01000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; X
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00101000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00101000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; Y
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01000100
|
||||||
|
.byte %01000100
|
||||||
|
.byte %00101000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
.repeat 2 ; Z
|
||||||
|
.byte %00000000
|
||||||
|
.byte %01111110
|
||||||
|
.byte %00000010
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00100000
|
||||||
|
.byte %01111110
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
HEX = $24
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00010100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00010100
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
COLON = $25
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00011000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
UP = $26
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00111000
|
||||||
|
.byte %01010100
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
DOWN = $27
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %01010100
|
||||||
|
.byte %00111000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
LEFT = $28
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00100000
|
||||||
|
.byte %01111110
|
||||||
|
.byte %00100000
|
||||||
|
.byte %00010000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
RIGHT = $29
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00000100
|
||||||
|
.byte %01111110
|
||||||
|
.byte %00000100
|
||||||
|
.byte %00001000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
PILL = $2A
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00111100
|
||||||
|
.byte %01111110
|
||||||
|
.byte %00111100
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
EMPTY = $2B
|
||||||
|
.repeat 2 ; $
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.byte %00000000
|
||||||
|
.endrepeat
|
||||||
|
|
||||||
|
FONT_END = $2C
|
||||||
|
.popseg
|
||||||
|
|
||||||
|
.macro write_char_x chr
|
||||||
|
.if chr >= 'a' && chr <= 'z'
|
||||||
|
ldx #(chr - 'a' + 10)
|
||||||
|
.elseif chr >= 'A' && chr <= 'Z'
|
||||||
|
ldx #(chr - 'A' + 10)
|
||||||
|
.elseif chr >= '0' && chr <= '9'
|
||||||
|
ldx #(chr - '0')
|
||||||
|
.elseif chr = ' '
|
||||||
|
ldx #EMPTY
|
||||||
|
.elseif chr = ':'
|
||||||
|
ldx #COLON
|
||||||
|
.elseif chr = '$'
|
||||||
|
ldx #HEX
|
||||||
|
.else
|
||||||
|
.error "Unhandled character chr: " chr
|
||||||
|
.endif
|
||||||
|
stx PPUDATA
|
||||||
|
.endmacro
|
||||||
|
|
||||||
|
zp_res HEX_TEMP
|
||||||
|
|
||||||
|
write_hex_a:
|
||||||
|
sta HEX_TEMP
|
||||||
|
lsr
|
||||||
|
lsr
|
||||||
|
lsr
|
||||||
|
lsr
|
||||||
|
and #$F
|
||||||
|
sta PPUDATA
|
||||||
|
lda HEX_TEMP
|
||||||
|
and #$F
|
||||||
|
sta PPUDATA
|
||||||
|
rts
|
||||||
30
src/test_roms/common/joysticks.s
Normal file
30
src/test_roms/common/joysticks.s
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
JOY_RIGHT_MASK = %00000001
|
||||||
|
JOY_LEFT_MASK = %00000010
|
||||||
|
JOY_DOWN_MASK = %00000100
|
||||||
|
JOY_UP_MASK = %00001000
|
||||||
|
JOY_START_MASK = %00010000
|
||||||
|
JOY_SELECT_MASK = %00100000
|
||||||
|
JOY_B_MASK = %01000000
|
||||||
|
JOY_A_MASK = %10000000
|
||||||
|
|
||||||
|
zp_res JOYTEMP
|
||||||
|
; Reads joy_stick 1 into a (and JOYTEMP)
|
||||||
|
read_joy1:
|
||||||
|
; Init joysticks for reading
|
||||||
|
lda #$01
|
||||||
|
sta JOY1
|
||||||
|
lda #$00
|
||||||
|
sta JOY1
|
||||||
|
|
||||||
|
; Read JOY1
|
||||||
|
.repeat 8
|
||||||
|
asl A
|
||||||
|
sta JOYTEMP
|
||||||
|
lda JOY1
|
||||||
|
and #$01
|
||||||
|
ora JOYTEMP
|
||||||
|
.endrepeat
|
||||||
|
sta JOYTEMP
|
||||||
|
rts
|
||||||
|
|
||||||
37
src/test_roms/common/minimal_ppu.s
Normal file
37
src/test_roms/common/minimal_ppu.s
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
init_ppu_and_wait:
|
||||||
|
; Init PPU
|
||||||
|
bit PPUSTATUS
|
||||||
|
vwait1:
|
||||||
|
bit PPUSTATUS
|
||||||
|
bpl vwait1
|
||||||
|
vwait2:
|
||||||
|
bit PPUSTATUS
|
||||||
|
bpl vwait2
|
||||||
|
|
||||||
|
lda #$80
|
||||||
|
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||||
|
lda #$00
|
||||||
|
infinite_loop:
|
||||||
|
jmp infinite_loop
|
||||||
|
|
||||||
|
.macro nmi_start
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
pha
|
||||||
|
lda PPUSTATUS
|
||||||
|
lda #%00000110
|
||||||
|
sta PPUMASK ; Force blank
|
||||||
|
.endmacro
|
||||||
|
.macro nmi_end
|
||||||
|
lda #%00001110
|
||||||
|
sta PPUMASK ; Enable rendering
|
||||||
|
pla
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
tax
|
||||||
|
pla
|
||||||
|
rti
|
||||||
|
.endmacro
|
||||||
Reference in New Issue
Block a user