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:
144
Cargo.lock
generated
144
Cargo.lock
generated
@@ -70,6 +70,28 @@ dependencies = [
|
||||
"equator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
@@ -771,6 +793,20 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-text"
|
||||
version = "0.15.0"
|
||||
@@ -794,6 +830,36 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b1f9c7312f19fc2fa12fd7acaf38de54e8320ba10d1a02dcbe21038def51ccb"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"objc2 0.6.3",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-avf-audio",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@@ -858,6 +924,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@@ -2006,6 +2078,15 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -2197,9 +2278,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitfield",
|
||||
"bytes",
|
||||
"cpal",
|
||||
"iced",
|
||||
"iced_core",
|
||||
"rfd",
|
||||
"ringbuf",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -2403,6 +2486,31 @@ dependencies = [
|
||||
"objc2-quartz-core 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-audio-toolbox"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-avf-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
|
||||
dependencies = [
|
||||
"objc2 0.6.3",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.2.2"
|
||||
@@ -2438,6 +2546,29 @@ dependencies = [
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
@@ -2468,7 +2599,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
]
|
||||
|
||||
@@ -3216,6 +3349,17 @@ version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
|
||||
@@ -16,3 +16,5 @@ tokio = { version = "1.48.0", features = ["full"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.20", features = ["ansi", "chrono", "env-filter", "json", "serde"] }
|
||||
bytes = "*"
|
||||
cpal = "0.17.1"
|
||||
ringbuf = "0.4.8"
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
94
src/audio.rs
Normal file
94
src/audio.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::AtomicBool, mpsc::{channel, Sender, TryRecvError}, Arc
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use cpal::{
|
||||
Device, FrameCount, Host, SampleFormat, Stream,
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
};
|
||||
use ringbuf::{
|
||||
HeapRb, SharedRb,
|
||||
traits::{Consumer, Observer, Producer, Split},
|
||||
wrap::caching::Caching,
|
||||
};
|
||||
|
||||
type SampleTx = Caching<Arc<HeapRb<u8>>, true, false>;
|
||||
|
||||
// TODO: Audio should be run through a low-pass filter,
|
||||
// a high-pass filter, as well as some kind of envelope
|
||||
// around pause events
|
||||
pub struct Audio {
|
||||
_host: Host,
|
||||
_device: Device,
|
||||
_stream: Stream,
|
||||
rb: SampleTx,
|
||||
last: usize,
|
||||
max: usize,
|
||||
paused: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Audio {
|
||||
pub fn init() -> Self {
|
||||
const BUFFER_SIZE: usize = 1 << 10;
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_output_device().unwrap();
|
||||
// let mut configs = device.supported_output_configs().unwrap();
|
||||
// dbg!(configs.find(|c| c.sample_format() == SampleFormat::U8));
|
||||
// let v = dbg!(configs.next().unwrap().buffer_size());
|
||||
// let mut t = 0;
|
||||
let (prod, mut cons) = SharedRb::new(BUFFER_SIZE * 1024 * 1024).split();
|
||||
let paused = Arc::new(AtomicBool::new(true));
|
||||
let paused_inner = Arc::clone(&paused);
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&cpal::StreamConfig {
|
||||
channels: 1,
|
||||
sample_rate: 60 * 3723,
|
||||
buffer_size: cpal::BufferSize::Fixed(BUFFER_SIZE as FrameCount),
|
||||
},
|
||||
move |a: &mut [u8], _b| {
|
||||
if !paused_inner.load(std::sync::atomic::Ordering::Acquire) {
|
||||
let taken = cons.pop_slice(a);
|
||||
a[taken..].fill(128);
|
||||
}
|
||||
},
|
||||
|e| eprintln!("Audio: {e}"),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
Self {
|
||||
_host: host,
|
||||
_device: device,
|
||||
_stream: stream,
|
||||
rb: prod,
|
||||
paused,
|
||||
last: 0,
|
||||
max: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
self.paused.store(true, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn submit(&mut self, samples: &[u8]) {
|
||||
let start = self.rb.occupied_len();
|
||||
self.max = self.max.max(self.last - start);
|
||||
println!("Buffer size: {:07}, Max: {:07}", start, self.max);
|
||||
println!(
|
||||
"Adding: {:07}, Played: {:07}",
|
||||
samples.len(),
|
||||
self.last - start
|
||||
);
|
||||
self.rb.push_slice(samples);
|
||||
self.last = self.rb.occupied_len();
|
||||
if self.last > 9000 {
|
||||
self.paused.store(false, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,13 +154,13 @@ impl DebuggerState {
|
||||
| ((nes.ppu().background.v >> 2) & 0x07)
|
||||
)
|
||||
),
|
||||
labelled("AT:", hex8(nes.ppu().background.cur_attr)),
|
||||
labelled("AT:", hex8(nes.ppu().background.next_attr)),
|
||||
labelled("HI:", bin32(nes.ppu().background.cur_shift_high)),
|
||||
labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
|
||||
],
|
||||
column![
|
||||
labelled_box("Sprite 0 Hit", false),
|
||||
labelled_box("Sprite 0 Overflow", false),
|
||||
labelled_box("Sprite 0 Hit", nes.ppu().sprite_zero_hit),
|
||||
labelled_box("Sprite 0 Overflow", nes.ppu().oam.overflow),
|
||||
labelled_box("Vertical Blank", nes.ppu().vblank),
|
||||
labelled_box("Write Toggle", nes.ppu().background.w),
|
||||
text(""),
|
||||
|
||||
@@ -10,6 +10,7 @@ mod ppu;
|
||||
pub mod resize_watcher;
|
||||
#[cfg(test)]
|
||||
mod test_roms;
|
||||
pub mod audio;
|
||||
|
||||
pub use ppu::{Color, PPU, RenderBuffer};
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
@@ -349,6 +350,10 @@ impl NES {
|
||||
&self.apu
|
||||
}
|
||||
|
||||
pub fn apu_mut(&mut self) -> &mut APU {
|
||||
&mut self.apu
|
||||
}
|
||||
|
||||
pub fn debug_log(&self) -> &DebugLog {
|
||||
&self.cpu.debug_log
|
||||
}
|
||||
|
||||
151
src/main.rs
151
src/main.rs
@@ -5,13 +5,13 @@ use std::{
|
||||
};
|
||||
|
||||
use iced::{
|
||||
Color, Element,
|
||||
Element,
|
||||
Length::{Fill, Shrink},
|
||||
Point, Rectangle, Renderer, Size, Subscription, Task, Theme,
|
||||
keyboard::{self, Key, Modifiers, key::Named},
|
||||
mouse, time,
|
||||
widget::{
|
||||
self, Button, Canvas, button,
|
||||
self, Canvas, button,
|
||||
canvas::{Frame, Program},
|
||||
column, container, image, row,
|
||||
},
|
||||
@@ -19,13 +19,13 @@ use iced::{
|
||||
};
|
||||
use nes_emu::{
|
||||
Break, NES,
|
||||
audio::Audio,
|
||||
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
||||
header_menu::header_menu,
|
||||
hex_view::{HexEvent, HexView},
|
||||
resize_watcher::resize_watcher,
|
||||
};
|
||||
use tokio::{io::AsyncWriteExt, runtime::Runtime};
|
||||
use tracing::instrument::WithSubscriber;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
||||
@@ -37,26 +37,14 @@ use tracing_subscriber::EnvFilter;
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_palette_shared.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "input_test.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "scrolling.nes");
|
||||
const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "sprites.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"), "/", "apu_pulse_channel_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 = "./cpu_timing_test.nes";
|
||||
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
|
||||
|
||||
/// Disable button in debug mode - used to disable play/pause buttons since the performance isn't fast enough
|
||||
fn debug_disable<'a, Message, Theme, Renderer>(
|
||||
btn: Button<'a, Message, Theme, Renderer>,
|
||||
) -> Button<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Theme: button::Catalog + 'a,
|
||||
Renderer: iced::advanced::Renderer,
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
btn.on_press_maybe(None)
|
||||
} else {
|
||||
btn
|
||||
}
|
||||
}
|
||||
|
||||
extern crate nes_emu;
|
||||
|
||||
fn main() -> Result<(), iced::Error> {
|
||||
@@ -93,6 +81,7 @@ enum WindowType {
|
||||
TileViewer,
|
||||
Palette,
|
||||
Debugger,
|
||||
Apu,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -118,6 +107,7 @@ impl fmt::Display for HeaderButton {
|
||||
Self::Open(WindowType::Debugger) => write!(f, "Open Debugger"),
|
||||
Self::Open(WindowType::Palette) => write!(f, "Open Palette"),
|
||||
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
|
||||
Self::Open(WindowType::Apu) => write!(f, "Open APU info"),
|
||||
Self::Reset => write!(f, "Reset"),
|
||||
Self::PowerCycle => write!(f, "Power Cycle"),
|
||||
Self::OpenRom => write!(f, "Open ROM file"),
|
||||
@@ -132,6 +122,7 @@ struct Emulator {
|
||||
debugger: DebuggerState,
|
||||
main_win_size: Size,
|
||||
prev: [Instant; 2],
|
||||
audio: Audio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -158,38 +149,32 @@ impl Emulator {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
|
||||
nes.reset();
|
||||
// TODO: remove
|
||||
// let mut count = 10;
|
||||
// while !nes.halted() {
|
||||
// if nes.run_one_clock_cycle().ppu_frame {
|
||||
// count -= 1;
|
||||
// if count <= 0 {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
let (win, task) = iced::window::open(Settings {
|
||||
min_size: None,
|
||||
..Settings::default()
|
||||
});
|
||||
let (win_2, task_2) = iced::window::open(Settings {
|
||||
min_size: None,
|
||||
..Settings::default()
|
||||
});
|
||||
// let (win_2, task_2) = iced::window::open(Settings {
|
||||
// min_size: None,
|
||||
// ..Settings::default()
|
||||
// });
|
||||
(
|
||||
Self {
|
||||
nes,
|
||||
windows: HashMap::from_iter([
|
||||
(win, WindowType::Main),
|
||||
(win_2, WindowType::Debugger)
|
||||
// (win_2, WindowType::Debugger),
|
||||
]),
|
||||
debugger: DebuggerState::new(),
|
||||
main_win_size: Size::new(0., 0.),
|
||||
running: false,
|
||||
prev: [Instant::now(); 2],
|
||||
audio: Audio::init(),
|
||||
},
|
||||
Task::batch([task, task_2]).discard()
|
||||
// task.discard(),
|
||||
Task::batch([
|
||||
task,
|
||||
// task_2
|
||||
])
|
||||
.discard(), // task.discard(),
|
||||
)
|
||||
}
|
||||
fn title(&self, win: Id) -> String {
|
||||
@@ -200,6 +185,7 @@ impl Emulator {
|
||||
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
|
||||
Some(WindowType::Palette) => "NES Palette Viewer".into(),
|
||||
Some(WindowType::Debugger) => "NES Debugger".into(),
|
||||
Some(WindowType::Apu) => "NES APU Debugger".into(),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -339,6 +325,17 @@ impl Emulator {
|
||||
self.nes.controller_1().set_left(true);
|
||||
} else if key == Key::Named(Named::ArrowRight) {
|
||||
self.nes.controller_1().set_right(true);
|
||||
} else if key == Key::Named(Named::Play) || key == Key::Named(Named::MediaPlay)
|
||||
{
|
||||
self.running = true;
|
||||
} else if key == Key::Named(Named::Pause)
|
||||
|| key == Key::Named(Named::MediaPause)
|
||||
{
|
||||
self.running = false;
|
||||
} else if key == Key::Named(Named::MediaPlayPause)
|
||||
|| key == Key::Named(Named::Space)
|
||||
{
|
||||
self.running = !self.running;
|
||||
}
|
||||
}
|
||||
keyboard::Event::KeyReleased {
|
||||
@@ -366,14 +363,27 @@ impl Emulator {
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Message::Periodic(i) => {
|
||||
Message::Periodic(_i) => {
|
||||
if self.running {
|
||||
// TODO: this should skip updating to avoid multiple frame skips
|
||||
self.prev[1] = self.prev[0];
|
||||
self.prev[0] = i;
|
||||
|
||||
self.nes.run_one_clock_cycle(&Break::default());
|
||||
while !self.nes.run_one_clock_cycle(&Break::default()).ppu_frame {}
|
||||
// TODO: Smarter frame skip
|
||||
if self.prev[0].elapsed() >= Duration::from_millis(2) {
|
||||
self.nes.run_one_clock_cycle(&Break::default());
|
||||
let mut count = 0;
|
||||
while !self.nes.run_one_clock_cycle(&Break::default()).ppu_frame
|
||||
&& !self.nes.halted()
|
||||
{
|
||||
count += 1;
|
||||
if count > 100000 {
|
||||
println!("Loop overran...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.prev[0] = Instant::now();
|
||||
self.audio.submit(self.nes.apu().get_frame_samples());
|
||||
self.nes.apu_mut().reset_frame_samples();
|
||||
}
|
||||
} else {
|
||||
self.audio.pause();
|
||||
}
|
||||
}
|
||||
Message::SetRunning(running) => self.running = running,
|
||||
@@ -387,7 +397,7 @@ impl Emulator {
|
||||
window::close_events().map(Message::WindowClosed),
|
||||
window::open_events().map(Message::WindowOpened),
|
||||
keyboard::listen().map(Message::Key),
|
||||
time::every(Duration::from_millis(1000 / 60)).map(Message::Periodic),
|
||||
time::every(Duration::from_secs_f64(1./60.)).map(Message::Periodic),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -436,20 +446,19 @@ impl Emulator {
|
||||
}
|
||||
Some(WindowType::Debugger) => column![
|
||||
row![
|
||||
debug_disable(
|
||||
button(image("./images/ic_fluent_play_24_filled.png"))
|
||||
.on_press(Message::SetRunning(true))
|
||||
),
|
||||
debug_disable(
|
||||
button(image("./images/ic_fluent_pause_24_filled.png"))
|
||||
.on_press(Message::SetRunning(false))
|
||||
),
|
||||
button(image("./images/ic_fluent_play_24_filled.png"))
|
||||
.on_press(Message::SetRunning(true)),
|
||||
button(image("./images/ic_fluent_pause_24_filled.png"))
|
||||
.on_press(Message::SetRunning(false)),
|
||||
],
|
||||
self.debugger.view(&self.nes).map(Message::Debugger)
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into(),
|
||||
Some(WindowType::Apu) => {
|
||||
self.nes.apu().view()
|
||||
}
|
||||
None => panic!("Window not found"),
|
||||
// _ => todo!(),
|
||||
}
|
||||
@@ -476,6 +485,7 @@ impl Emulator {
|
||||
HeaderButton::Open(WindowType::TileMap),
|
||||
HeaderButton::Open(WindowType::TileViewer),
|
||||
HeaderButton::Open(WindowType::Palette),
|
||||
HeaderButton::Open(WindowType::Apu),
|
||||
],
|
||||
Message::Header
|
||||
)
|
||||
@@ -488,21 +498,6 @@ impl Emulator {
|
||||
impl Program<Message> for Emulator {
|
||||
type State = ();
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
_state: &mut Self::State,
|
||||
_event: &iced::Event,
|
||||
_bounds: Rectangle,
|
||||
_cursor: iced::advanced::mouse::Cursor,
|
||||
) -> Option<widget::Action<Message>> {
|
||||
// ~ 60 fps, I think?
|
||||
// Some(widget::Action::request_redraw_at(
|
||||
// Instant::now() + Duration::from_millis(10),
|
||||
// ))
|
||||
// Some(widget::Action::request_redraw())
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Self::State,
|
||||
@@ -511,17 +506,8 @@ impl Program<Message> for Emulator {
|
||||
bounds: iced::Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
||||
// let start = Instant::now();
|
||||
// const SIZE: f32 = 2.;
|
||||
let mut frame = Frame::new(
|
||||
renderer,
|
||||
bounds.size(), // iced::Size {
|
||||
// width: 256. * 2.,
|
||||
// height: 240. * 2.,
|
||||
// },
|
||||
);
|
||||
let mut frame = Frame::new(renderer, bounds.size());
|
||||
frame.scale(2.);
|
||||
// TODO: use image for better? performance
|
||||
frame.draw_image(
|
||||
Rectangle::new(Point::new(0., 0.), Size::new(256., 240.)),
|
||||
widget::canvas::Image::new(widget::image::Handle::from_rgba(
|
||||
@@ -532,17 +518,6 @@ impl Program<Message> for Emulator {
|
||||
.filter_method(image::FilterMethod::Nearest)
|
||||
.snap(true),
|
||||
);
|
||||
// for y in 0..240 {
|
||||
// for x in 0..256 {
|
||||
// let c = self.nes.image().read(y, x);
|
||||
// frame.fill_rectangle(
|
||||
// Point::new(x as f32, y as f32),
|
||||
// Size::new(1., 1.),
|
||||
// Color::from_rgb8(c.r, c.g, c.b),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// println!("Rendered frame in {}ms", start.elapsed().as_millis());
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
127
src/ppu.rs
127
src/ppu.rs
@@ -128,7 +128,8 @@ pub struct OAM {
|
||||
oam_read_buffer: u8,
|
||||
edit_ver: usize,
|
||||
sprite_output_units: Vec<SpriteOutputUnit>,
|
||||
overflow: bool,
|
||||
large_sprites: bool,
|
||||
pub overflow: bool,
|
||||
sprite_offset_0x1000: bool,
|
||||
}
|
||||
|
||||
@@ -145,6 +146,7 @@ impl OAM {
|
||||
oam_read_buffer: 0,
|
||||
overflow: false,
|
||||
sprite_offset_0x1000: false,
|
||||
large_sprites: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,15 +274,15 @@ impl OAM {
|
||||
// println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
|
||||
let lo = (s.low >> bit_pos) & 1;
|
||||
let idx = (hi << 1) | lo;
|
||||
Some((
|
||||
if idx != 0 {
|
||||
idx + s.attrs.palette() * 4 + 0x10
|
||||
} else {
|
||||
0
|
||||
},
|
||||
!s.attrs.priority(),
|
||||
i == 0,
|
||||
))
|
||||
if idx != 0 {
|
||||
Some((
|
||||
idx + s.attrs.palette() * 4 + 0x10,
|
||||
!s.attrs.priority(),
|
||||
i == 0,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -292,8 +294,10 @@ impl OAM {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Background {
|
||||
/// Current vram address, 15 bits
|
||||
/// yyy NN YYYYY XXXXX (0yyy NNYY YYYX XXXX)
|
||||
pub v: u16,
|
||||
/// Temp vram address, 15 bits
|
||||
/// yyy NN YYYYY XXXXX
|
||||
pub t: u16,
|
||||
/// Fine X control, 3 bits
|
||||
pub x: u8,
|
||||
@@ -301,18 +305,20 @@ pub struct Background {
|
||||
/// When false, writes to x
|
||||
pub w: bool,
|
||||
|
||||
copy_v: u8,
|
||||
|
||||
/// When true, v is incremented by 32 after each read
|
||||
pub vram_column: bool,
|
||||
pub second_pattern: bool,
|
||||
|
||||
pub cur_nametable: u8,
|
||||
pub cur_attr: u8,
|
||||
pub next_attr: u8,
|
||||
pub next_attr_2: u8,
|
||||
pub cur_high: u8,
|
||||
pub cur_low: u8,
|
||||
pub cur_shift_high: u32,
|
||||
pub cur_shift_low: u32,
|
||||
pub cur_attr_shift_high: u32,
|
||||
pub cur_attr_shift_low: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -681,11 +687,11 @@ pub struct PPU {
|
||||
|
||||
pub mask: Mask,
|
||||
pub vblank: bool,
|
||||
sprite_zero_hit: bool,
|
||||
pub sprite_zero_hit: bool,
|
||||
|
||||
pub palette: Palette,
|
||||
pub background: Background,
|
||||
oam: OAM,
|
||||
pub oam: OAM,
|
||||
pub render_buffer: RenderBuffer<256, 240>,
|
||||
pub dbg_int: bool,
|
||||
pub cycle: usize,
|
||||
@@ -740,8 +746,6 @@ impl PPU {
|
||||
sprite_zero_hit: false,
|
||||
frame_count: 0,
|
||||
nmi_enabled: false,
|
||||
// nmi_waiting: false,
|
||||
// TODO: Is even in the right initial state?
|
||||
even: false,
|
||||
scanline: 0,
|
||||
pixel: 25,
|
||||
@@ -751,16 +755,17 @@ impl PPU {
|
||||
t: 0,
|
||||
x: 0,
|
||||
w: false,
|
||||
copy_v: 0,
|
||||
vram_column: false,
|
||||
second_pattern: false,
|
||||
cur_high: 0,
|
||||
cur_low: 0,
|
||||
cur_shift_high: 0,
|
||||
cur_shift_low: 0,
|
||||
cur_attr_shift_high: 0,
|
||||
cur_attr_shift_low: 0,
|
||||
cur_nametable: 0,
|
||||
cur_attr: 0,
|
||||
next_attr: 0,
|
||||
next_attr_2: 0,
|
||||
},
|
||||
oam: OAM::new(),
|
||||
vram_buffer: 0,
|
||||
@@ -791,9 +796,6 @@ impl PPU {
|
||||
5 => panic!("ppuscroll is write-only"),
|
||||
6 => panic!("ppuaddr is write-only"),
|
||||
7 => {
|
||||
// TODO: read buffer only applies to ppu data, not palette ram...
|
||||
// println!("Updating v for ppudata read");
|
||||
// self.vram_buffer =
|
||||
let val = match mem.read(self.background.v) {
|
||||
Value::Value(v) => {
|
||||
let val = self.vram_buffer;
|
||||
@@ -805,10 +807,6 @@ impl PPU {
|
||||
offset,
|
||||
} => self.palette.ram[offset as usize],
|
||||
};
|
||||
// .reg_map(|a, off| match a {
|
||||
// PPUMMRegisters::Palette => self.palette.ram[off as usize],
|
||||
// });
|
||||
// if self.background
|
||||
self.increment_v();
|
||||
val
|
||||
}
|
||||
@@ -819,13 +817,16 @@ impl PPU {
|
||||
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
||||
match offset {
|
||||
0x00 => {
|
||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||
self.background.t =
|
||||
(self.background.t & !0b0001_1000_0000_0000) | (((val & 0b11) as u16) << 10);
|
||||
(self.background.t & !0b0000_1100_0000_0000) | (((val & 0b11) as u16) << 10);
|
||||
self.background.vram_column = val & 0b0000_0100 != 0;
|
||||
self.background.second_pattern = val & 0b0001_0000 != 0;
|
||||
self.oam.sprite_offset_0x1000 = val & 0b0000_1000 != 0;
|
||||
// TODO: other control fields
|
||||
self.background.second_pattern = val & 0b0001_0000 != 0;
|
||||
self.oam.large_sprites = val & 0b0010_0000 != 0;
|
||||
if val & 0b0100_0000 != 0 {
|
||||
println!("WARNING: Bit 6 set in PPUCTRL - may cause damage on physical hardware");
|
||||
}
|
||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
||||
}
|
||||
0x01 => {
|
||||
// self.dbg_int = true;
|
||||
@@ -841,7 +842,6 @@ impl PPU {
|
||||
}
|
||||
0x02 => {
|
||||
todo!("Unable to write to PPU status")
|
||||
// TODO: ppu status
|
||||
}
|
||||
0x03 => self.oam.addr = val,
|
||||
0x04 => {
|
||||
@@ -868,19 +868,16 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
0x06 => {
|
||||
// TODO: this actually sets T, which is copied to v later (~ a pixel later?)
|
||||
if self.background.w {
|
||||
self.background.v =
|
||||
u16::from_le_bytes([val, self.background.v.to_le_bytes()[1]]);
|
||||
self.background.t =
|
||||
u16::from_le_bytes([val, self.background.t.to_le_bytes()[1]]);
|
||||
self.background.w = false;
|
||||
self.background.copy_v = 2; // Set t to be copied to v in a pixel or so
|
||||
} else {
|
||||
self.background.v =
|
||||
u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]);
|
||||
self.background.t =
|
||||
u16::from_le_bytes([self.background.t.to_le_bytes()[0], val & 0b0011_1111]);
|
||||
self.background.w = true;
|
||||
}
|
||||
// println!("Updating v for ppuaddr write: to {:04X}", self.background.v);
|
||||
// self.dbg_int = true;
|
||||
// todo!("PPUADDR write")
|
||||
}
|
||||
0x07 => {
|
||||
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
||||
@@ -897,7 +894,6 @@ impl PPU {
|
||||
}
|
||||
});
|
||||
self.increment_v();
|
||||
// self.background.v += 1; // TODO: implement inc behavior
|
||||
}
|
||||
_ => panic!("No register at {:02X}", offset),
|
||||
}
|
||||
@@ -910,18 +906,17 @@ impl PPU {
|
||||
} else {
|
||||
self.background.v += 1;
|
||||
}
|
||||
// self.background.v = self
|
||||
// .background
|
||||
// .v
|
||||
// .wrapping_add(if self.background.vram_column { 32 } else { 1 });
|
||||
}
|
||||
// pub fn write_oamdma(&mut self, val: u8) {
|
||||
// // TODO: OAM high addr
|
||||
// }
|
||||
|
||||
pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool {
|
||||
self.cycle += 1;
|
||||
self.pixel += 1;
|
||||
if self.background.copy_v > 0 {
|
||||
self.background.copy_v -= 1;
|
||||
if self.background.copy_v == 0 {
|
||||
self.background.v = self.background.t;
|
||||
}
|
||||
}
|
||||
if self.scanline == 261
|
||||
&& (self.pixel == 341 || (self.pixel == 340 && self.even && self.rendering_enabled()))
|
||||
{
|
||||
@@ -944,16 +939,13 @@ impl PPU {
|
||||
self.background.v = (self.background.v & 0b0000_0100_0001_1111)
|
||||
| (self.background.t & 0b0111_1011_1110_0000);
|
||||
}
|
||||
if self.pixel > 280 && self.pixel < 320 {
|
||||
self.background.cur_shift_high <<= 1;
|
||||
self.background.cur_shift_low <<= 1;
|
||||
}
|
||||
if self.scanline != 261 {
|
||||
self.oam.ppu_cycle(self.pixel, self.scanline, mem);
|
||||
}
|
||||
// TODO
|
||||
if self.pixel == 0 {
|
||||
if self.scanline < 9 {
|
||||
if self.scanline == 1 {
|
||||
// let h_scroll_offset = self.background.x as usize + ((self.background.t as usize & 0b11) << 3);
|
||||
// dbg!(h_scroll_offset);
|
||||
// dbg!((self.scanline, &self.oam.sprite_output_units[0]));
|
||||
// dbg!(&self.oam.secondary);
|
||||
}
|
||||
@@ -963,17 +955,16 @@ impl PPU {
|
||||
// self.dbg_int = true;
|
||||
// const POS: u32 = 1 << 15;
|
||||
let bit_pos = 15 - self.background.x;
|
||||
// let pos: u32 = 1 << (15 + self.background.x*0); // TODO: handle this correctly
|
||||
// Determine background color
|
||||
let a = (self.background.cur_shift_high >> bit_pos) & 1;
|
||||
let b = (self.background.cur_shift_low >> bit_pos) & 1;
|
||||
let val = (a << 1) | b;
|
||||
debug_assert!(val < 4);
|
||||
|
||||
let h_off = ((self.pixel - 1) / 16) % 2;
|
||||
let v_off = (self.scanline / 16) % 2;
|
||||
let off = v_off * 4 + h_off * 2;
|
||||
let palette = (self.background.cur_attr >> off) & 0x3;
|
||||
let a = (self.background.cur_attr_shift_high >> bit_pos) & 1;
|
||||
let b = (self.background.cur_attr_shift_low >> bit_pos) & 1;
|
||||
let palette = ((a << 1) | b) as u8;
|
||||
debug_assert!(palette < 4);
|
||||
let color = val as u8 + if val != 0 { palette * 4 } else { 0 };
|
||||
|
||||
if self.scanline < 240 && self.pixel < 257 {
|
||||
@@ -995,12 +986,14 @@ impl PPU {
|
||||
self.render_buffer.write(
|
||||
self.scanline,
|
||||
self.pixel - 1,
|
||||
self.palette.color(color), // self.palette.colors[val as usize],
|
||||
); // TODO: this should come from shift registers
|
||||
self.palette.color(color),
|
||||
);
|
||||
}
|
||||
if self.pixel < 337 {
|
||||
self.background.cur_shift_high <<= 1;
|
||||
self.background.cur_shift_low <<= 1;
|
||||
self.background.cur_attr_shift_high <<= 1;
|
||||
self.background.cur_attr_shift_low <<= 1;
|
||||
}
|
||||
|
||||
if self.scanline < 240 || self.scanline == 261 {
|
||||
@@ -1021,8 +1014,8 @@ impl PPU {
|
||||
| ((self.background.v >> 2) & 0x07);
|
||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||
// assert_eq!(addr, addr_2);
|
||||
let val = mem.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
|
||||
self.background.next_attr_2 = val;
|
||||
let val = mem.read(addr).reg_map(|_, offset| self.palette.ram(offset as u8));
|
||||
self.background.next_attr = val;
|
||||
} else if self.pixel % 8 == 6 {
|
||||
// BG pattern low
|
||||
let addr = self.background.cur_nametable as u16 * 16
|
||||
@@ -1049,8 +1042,14 @@ impl PPU {
|
||||
self.background.cur_high = val;
|
||||
self.background.cur_shift_low |= self.background.cur_low as u32;
|
||||
self.background.cur_shift_high |= self.background.cur_high as u32;
|
||||
self.background.cur_attr = self.background.next_attr;
|
||||
self.background.next_attr = self.background.next_attr_2;
|
||||
let h_off = (self.background.v >> 1) % 2;
|
||||
let v_off = (self.background.v >> 6) % 2;
|
||||
let off = h_off * 2 + v_off * 4;
|
||||
let palette = (self.background.next_attr >> off) & 0b11;
|
||||
self.background.cur_attr_shift_low |=
|
||||
if palette & 0b01 == 0 { 0 } else { 0xFF };
|
||||
self.background.cur_attr_shift_high |=
|
||||
if palette & 0b10 == 0 { 0 } else { 0xFF };
|
||||
// Inc horizontal
|
||||
if self.background.v & 0x1F == 31 {
|
||||
self.background.v = (self.background.v & !0x1F) ^ 0x400;
|
||||
@@ -1080,8 +1079,6 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: Sprite fetches
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
src/test_roms/apu_pulse_channel_1.s
Normal file
139
src/test_roms/apu_pulse_channel_1.s
Normal file
@@ -0,0 +1,139 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$BF ; Duty 2, LC halted, Constant volume, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
lda #$00
|
||||
sta PULSE_CH1_LCTH
|
||||
; PULSE_CH1_DLCV = $4000
|
||||
; PULSE_CH1_SWEEP = $4001
|
||||
; PULSE_CH1_TLOW = $4002
|
||||
; PULSE_CH1_LCTH = $4003
|
||||
|
||||
lda #$80
|
||||
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 TIMER_LOW
|
||||
update_audio:
|
||||
; adc TIMER_LOW
|
||||
; sta PULSE_CH1_TLOW
|
||||
; sta TIMER_LOW
|
||||
|
||||
rts
|
||||
|
||||
zp_res PRESSED_A
|
||||
zp_res PRESSED_B
|
||||
|
||||
nmi:
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
lda JOY1 ; A
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_A
|
||||
beq done
|
||||
stx PRESSED_A
|
||||
txa
|
||||
and #$01
|
||||
beq done
|
||||
|
||||
lda #$08
|
||||
jsr update_audio
|
||||
|
||||
done:
|
||||
bit JOY1 ; B
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_B
|
||||
beq done_b
|
||||
stx PRESSED_B
|
||||
txa
|
||||
and #$01
|
||||
beq done_b
|
||||
|
||||
lda #$F7
|
||||
jsr update_audio
|
||||
|
||||
done_b:
|
||||
bit JOY1 ; Select
|
||||
bit JOY1 ; Start
|
||||
bit JOY1 ; Up
|
||||
bit JOY1 ; Down
|
||||
bit JOY1 ; Left
|
||||
bit JOY1 ; Right
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
irq:
|
||||
rti
|
||||
135
src/test_roms/apu_triangle.s
Normal file
135
src/test_roms/apu_triangle.s
Normal file
@@ -0,0 +1,135 @@
|
||||
.include "testing.s"
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; Init PPU
|
||||
bit PPUSTATUS
|
||||
vwait1:
|
||||
bit PPUSTATUS
|
||||
bpl vwait1
|
||||
vwait2:
|
||||
bit PPUSTATUS
|
||||
bpl vwait2
|
||||
|
||||
lda #$04
|
||||
sta SNDCHN
|
||||
lda #$FF
|
||||
sta TRIANGLE_LINEAR_C
|
||||
lda #$37
|
||||
sta TRIANGLE_TIMER_LOW
|
||||
lda #$00
|
||||
sta TRIANGLE_LEN_T_HIGH
|
||||
; TRIANGLE_LINEAR_C = $4008
|
||||
; TRIANGLE_TIMER_LOW = $400A
|
||||
; TRIANGLE_LEN_T_HIGH = $400B
|
||||
|
||||
lda #$80
|
||||
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 TIMER_LOW
|
||||
update_audio:
|
||||
; adc TIMER_LOW
|
||||
; sta PULSE_CH1_TLOW
|
||||
; sta TIMER_LOW
|
||||
|
||||
rts
|
||||
|
||||
zp_res PRESSED_A
|
||||
zp_res PRESSED_B
|
||||
|
||||
nmi:
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
sta JOY1
|
||||
lda #$00
|
||||
sta JOY1
|
||||
|
||||
lda JOY1 ; A
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_A
|
||||
beq done
|
||||
stx PRESSED_A
|
||||
txa
|
||||
and #$01
|
||||
beq done
|
||||
|
||||
lda #$08
|
||||
jsr update_audio
|
||||
|
||||
done:
|
||||
bit JOY1 ; B
|
||||
and #$01
|
||||
tax
|
||||
eor PRESSED_B
|
||||
beq done_b
|
||||
stx PRESSED_B
|
||||
txa
|
||||
and #$01
|
||||
beq done_b
|
||||
|
||||
lda #$F7
|
||||
jsr update_audio
|
||||
|
||||
done_b:
|
||||
bit JOY1 ; Select
|
||||
bit JOY1 ; Start
|
||||
bit JOY1 ; Up
|
||||
bit JOY1 ; Down
|
||||
bit JOY1 ; Left
|
||||
bit JOY1 ; Right
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
irq:
|
||||
rti
|
||||
@@ -34,6 +34,18 @@ JOY1 = $4016
|
||||
JOY2 = $4017
|
||||
SNDMODE = $4017
|
||||
|
||||
PULSE_CH1_DLCV = $4000
|
||||
PULSE_CH1_SWEEP = $4001
|
||||
PULSE_CH1_TLOW = $4002
|
||||
PULSE_CH1_LCTH = $4003
|
||||
PULSE_CH2_DLCV = $4004
|
||||
PULSE_CH2_SWEEP = $4005
|
||||
PULSE_CH2_TLOW = $4006
|
||||
PULSE_CH2_LCTH = $4007
|
||||
TRIANGLE_LINEAR_C = $4008
|
||||
TRIANGLE_TIMER_LOW = $400A
|
||||
TRIANGLE_LEN_T_HIGH = $400B
|
||||
|
||||
SNDMODE_NOIRQ = $40
|
||||
|
||||
.if CLOCK_RATE = 1789773
|
||||
|
||||
334
src/test_roms/scrolling_colors.s
Normal file
334
src/test_roms/scrolling_colors.s
Normal file
@@ -0,0 +1,334 @@
|
||||
|
||||
.include "testing.s"
|
||||
; patterns_bin "pat.bin"
|
||||
.macro chr b,s
|
||||
.repeat s
|
||||
.byte b
|
||||
.endrepeat
|
||||
.endmacro
|
||||
.pushseg
|
||||
.segment "CHARS"
|
||||
T_EMPTY = 0
|
||||
.repeat 2 ; Empty 0
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
T_TOP_LEFT = 1
|
||||
.repeat 2 ; Top Left 1
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_TOP_RIGHT = 2
|
||||
.repeat 2 ; Top Right 2
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
T_BOTTOM_RIGHT = 3
|
||||
.repeat 2 ; Bottom Right 3
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_BOTTOM_LEFT = 4
|
||||
.repeat 2 ; Bottom Left 4
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.endrepeat
|
||||
T_FORWARD = 5
|
||||
.repeat 2 ; FW 5
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %00001110
|
||||
.byte %00011100
|
||||
.byte %00111000
|
||||
.byte %01110000
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.endrepeat
|
||||
T_BACKWARD = 6
|
||||
.repeat 2 ; BW 6
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01110000
|
||||
.byte %00111000
|
||||
.byte %00011100
|
||||
.byte %00001110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.endrepeat
|
||||
|
||||
T_PILL_L = 7
|
||||
.repeat 2 ; Pill L 7
|
||||
.byte %00111111
|
||||
.byte %01111111
|
||||
.byte %11100000
|
||||
.byte %11000000
|
||||
.byte %11000000
|
||||
.byte %11100000
|
||||
.byte %01111111
|
||||
.byte %00111111
|
||||
.endrepeat
|
||||
T_PILL_R = 8
|
||||
.repeat 2 ; Pill R 8
|
||||
.byte %11111100
|
||||
.byte %11111110
|
||||
.byte %00000111
|
||||
.byte %00000011
|
||||
.byte %00000011
|
||||
.byte %00000111
|
||||
.byte %11111110
|
||||
.byte %11111100
|
||||
.endrepeat
|
||||
|
||||
T_LINE = 9
|
||||
.repeat 2 ; Line
|
||||
.byte %00000000
|
||||
.byte %11111111
|
||||
.byte %11111111
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.byte %00000000
|
||||
.endrepeat
|
||||
|
||||
.popseg
|
||||
|
||||
zp_res Y_SCROLL
|
||||
zp_res X_SCROLL
|
||||
zp_res CTRL_BYTE
|
||||
|
||||
.macro load_ppu_addr addr
|
||||
lda #.hibyte(addr)
|
||||
sta PPUADDR
|
||||
lda #.lobyte(addr)
|
||||
sta PPUADDR ; Load $2000
|
||||
.endmacro
|
||||
|
||||
reset:
|
||||
sei
|
||||
cld
|
||||
ldx #$FF
|
||||
txs
|
||||
|
||||
; 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 #$00
|
||||
fill_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne fill_loop
|
||||
dey
|
||||
bne fill_loop
|
||||
; Set specific tiles
|
||||
load_ppu_addr $2184
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21A4
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E2
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21C6
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21E6
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2204
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $2224
|
||||
lda #T_BOTTOM_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_BOTTOM_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21EA
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
lda #$00
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
lda #T_PILL_L
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D2
|
||||
lda #T_FORWARD
|
||||
sta PPUDATA
|
||||
lda #T_BACKWARD
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F2
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_TOP_RIGHT
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $21D6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
load_ppu_addr $21F6
|
||||
lda #T_TOP_LEFT
|
||||
sta PPUDATA
|
||||
lda #T_PILL_R
|
||||
sta PPUDATA
|
||||
|
||||
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
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$1A
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
lda #$0f
|
||||
sta PPUDATA
|
||||
lda #$21
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
sta PPUDATA
|
||||
|
||||
load_ppu_addr $2000
|
||||
lda #T_TOP_LEFT
|
||||
ldx #$1F
|
||||
line_loop:
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne line_loop
|
||||
|
||||
load_ppu_addr $23C0
|
||||
ldx #$7
|
||||
line_color_loop:
|
||||
lda #%00000000
|
||||
sta PPUDATA
|
||||
lda #%00001111
|
||||
sta PPUDATA
|
||||
dex
|
||||
bne line_color_loop
|
||||
|
||||
|
||||
lda #$00
|
||||
sta PPUSCROLL
|
||||
sta PPUSCROLL
|
||||
lda #%00001110
|
||||
sta PPUMASK
|
||||
lda #$80
|
||||
sta PPUCTRL ; NMI on, PPU slave, Small sprites, 0 addresses
|
||||
sta CTRL_BYTE
|
||||
loop:
|
||||
jmp loop
|
||||
|
||||
nmi:
|
||||
lda PPUSTATUS
|
||||
lda #%00000110
|
||||
sta PPUMASK ; Force blank
|
||||
|
||||
lda #$01
|
||||
adc X_SCROLL
|
||||
sta X_SCROLL
|
||||
sta PPUSCROLL
|
||||
bcc y_scroll
|
||||
lda #$01
|
||||
eor CTRL_BYTE
|
||||
sta CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
y_scroll:
|
||||
clc
|
||||
lda #$00
|
||||
adc Y_SCROLL
|
||||
sta Y_SCROLL
|
||||
sta PPUSCROLL
|
||||
|
||||
lda CTRL_BYTE
|
||||
sta PPUCTRL
|
||||
lda #%00001110
|
||||
sta PPUMASK ; Enable rendering
|
||||
rti
|
||||
exit:
|
||||
stp
|
||||
|
||||
irq:
|
||||
stp
|
||||
Reference in New Issue
Block a user