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",
|
"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]]
|
[[package]]
|
||||||
name = "android-activity"
|
name = "android-activity"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -771,6 +793,20 @@ dependencies = [
|
|||||||
"libm",
|
"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]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@@ -794,6 +830,36 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"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]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -858,6 +924,12 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dasp_sample"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dispatch"
|
name = "dispatch"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2006,6 +2078,15 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mach2"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
@@ -2197,9 +2278,11 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitfield",
|
"bitfield",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"cpal",
|
||||||
"iced",
|
"iced",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
"ringbuf",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2403,6 +2486,31 @@ dependencies = [
|
|||||||
"objc2-quartz-core 0.3.2",
|
"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]]
|
[[package]]
|
||||||
name = "objc2-cloud-kit"
|
name = "objc2-cloud-kit"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2438,6 +2546,29 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"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]]
|
[[package]]
|
||||||
name = "objc2-core-data"
|
name = "objc2-core-data"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2468,7 +2599,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
|
"block2 0.6.2",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
|
"libc",
|
||||||
"objc2 0.6.3",
|
"objc2 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3216,6 +3349,17 @@ version = "0.8.52"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
|
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]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
|||||||
@@ -16,3 +16,5 @@ tokio = { version = "1.48.0", features = ["full"] }
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.20", features = ["ansi", "chrono", "env-filter", "json", "serde"] }
|
tracing-subscriber = { version = "0.3.20", features = ["ansi", "chrono", "env-filter", "json", "serde"] }
|
||||||
bytes = "*"
|
bytes = "*"
|
||||||
|
cpal = "0.17.1"
|
||||||
|
ringbuf = "0.4.8"
|
||||||
|
|||||||
277
src/apu.rs
277
src/apu.rs
@@ -1,5 +1,13 @@
|
|||||||
|
use std::iter::repeat_n;
|
||||||
|
|
||||||
|
use iced::{
|
||||||
|
Element, Font,
|
||||||
|
widget::{column, text},
|
||||||
|
};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
pub enum None {}
|
||||||
|
|
||||||
bitfield::bitfield! {
|
bitfield::bitfield! {
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct DutyVol(u8);
|
pub struct DutyVol(u8);
|
||||||
@@ -38,6 +46,8 @@ struct PulseChannel {
|
|||||||
|
|
||||||
cur_time: u16,
|
cur_time: u16,
|
||||||
cur_length: u8,
|
cur_length: u8,
|
||||||
|
cur: u8,
|
||||||
|
sample: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PulseChannel {
|
impl PulseChannel {
|
||||||
@@ -50,6 +60,8 @@ impl PulseChannel {
|
|||||||
length_timer_high: LengthTimerHigh(0),
|
length_timer_high: LengthTimerHigh(0),
|
||||||
cur_time: 0,
|
cur_time: 0,
|
||||||
cur_length: 0,
|
cur_length: 0,
|
||||||
|
cur: 0,
|
||||||
|
sample: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn use_envelope(&self) -> bool {
|
fn use_envelope(&self) -> bool {
|
||||||
@@ -61,58 +73,229 @@ impl PulseChannel {
|
|||||||
fn timer(&self) -> u16 {
|
fn timer(&self) -> u16 {
|
||||||
self.timer_low as u16 | ((self.length_timer_high.timer_high() as u16) << 8)
|
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) {
|
pub fn reset(&mut self) {
|
||||||
// TODO
|
// TODO
|
||||||
self.cur_time = self.timer();
|
self.cur_time = self.timer();
|
||||||
|
self.cur_length = self.length_timer_high.length();
|
||||||
|
self.cur = 0;
|
||||||
}
|
}
|
||||||
pub fn clock(&mut self) {
|
pub fn clock(&mut self) {
|
||||||
if self.cur_time == 0 {
|
if self.cur_time == 0 {
|
||||||
self.cur_time = self.timer();
|
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 {
|
} else {
|
||||||
self.cur_time -= 1;
|
self.cur_time -= 1;
|
||||||
}
|
}
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
pub fn cur_sample(&self) -> u8 {
|
||||||
|
self.sample
|
||||||
|
}
|
||||||
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.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
||||||
self.cur_length -= 1;
|
self.cur_length -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn h_frame_clock(&mut self) {
|
pub fn h_frame_clock(&mut self) {
|
||||||
if !self.duty_vol.length_counter_halt() && self.cur_length > 0 {
|
self.q_frame_clock();
|
||||||
self.cur_length -= 1;
|
// 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! {
|
bitfield::bitfield! {
|
||||||
pub struct CounterLoad(u8);
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct LengthCounter(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,
|
||||||
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
struct NoiseChannel {
|
struct NoiseChannel {
|
||||||
enabled: bool,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
struct DeltaChannel {
|
struct DeltaChannel {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
sample: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeltaChannel {
|
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 {
|
pub fn int(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view<T>(&self) -> Element<'_, T> {
|
||||||
|
text!("").font(Font::MONOSPACE).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -131,6 +314,8 @@ pub struct APU {
|
|||||||
noise: NoiseChannel,
|
noise: NoiseChannel,
|
||||||
dmc: DeltaChannel,
|
dmc: DeltaChannel,
|
||||||
frame_counter: FrameCounter,
|
frame_counter: FrameCounter,
|
||||||
|
|
||||||
|
samples: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for APU {
|
impl std::fmt::Debug for APU {
|
||||||
@@ -149,15 +334,17 @@ impl APU {
|
|||||||
Self {
|
Self {
|
||||||
pulse_1: PulseChannel::new(),
|
pulse_1: PulseChannel::new(),
|
||||||
pulse_2: PulseChannel::new(),
|
pulse_2: PulseChannel::new(),
|
||||||
triangle: TriangleChannel { enabled: false },
|
triangle: TriangleChannel::new(),
|
||||||
noise: NoiseChannel { enabled: false },
|
noise: NoiseChannel::new(),
|
||||||
dmc: DeltaChannel { enabled: false },
|
dmc: DeltaChannel::new(),
|
||||||
frame_counter: FrameCounter {
|
frame_counter: FrameCounter {
|
||||||
mode_5_step: false,
|
mode_5_step: false,
|
||||||
interrupt_enabled: true,
|
interrupt_enabled: true,
|
||||||
count: 0,
|
count: 0,
|
||||||
irq: false,
|
irq: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
samples: vec![0; 100],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,8 +388,15 @@ impl APU {
|
|||||||
self.pulse_2.reset();
|
self.pulse_2.reset();
|
||||||
}
|
}
|
||||||
0x09 => (), // Unused, technically noise channel?
|
0x09 => (), // Unused, technically noise channel?
|
||||||
0x08 | 0x0A | 0x0B => {
|
0x08 => {
|
||||||
// TODO: Triangle channel
|
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?
|
0x0D => (), // Unused, technically noise channel?
|
||||||
0x0C | 0x0E | 0x0F => {
|
0x0C | 0x0E | 0x0F => {
|
||||||
@@ -242,14 +436,41 @@ impl APU {
|
|||||||
|
|
||||||
fn q_frame_clock(&mut self) {
|
fn q_frame_clock(&mut self) {
|
||||||
self.pulse_1.q_frame_clock();
|
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) {
|
fn h_frame_clock(&mut self) {
|
||||||
self.pulse_1.q_frame_clock();
|
|
||||||
self.pulse_1.h_frame_clock();
|
self.pulse_1.h_frame_clock();
|
||||||
self.pulse_2.q_frame_clock();
|
|
||||||
self.pulse_2.h_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 {
|
pub fn run_one_clock_cycle(&mut self, ppu_cycle: usize) -> bool {
|
||||||
@@ -275,10 +496,27 @@ impl APU {
|
|||||||
|
|
||||||
self.pulse_1.clock();
|
self.pulse_1.clock();
|
||||||
self.pulse_2.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
|
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 {
|
pub fn peek_nmi(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -294,4 +532,15 @@ impl APU {
|
|||||||
pub fn irq_waiting(&mut self) -> bool {
|
pub fn irq_waiting(&mut self) -> bool {
|
||||||
self.frame_counter.irq
|
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)
|
| ((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("HI:", bin32(nes.ppu().background.cur_shift_high)),
|
||||||
labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
|
labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
|
||||||
],
|
],
|
||||||
column![
|
column![
|
||||||
labelled_box("Sprite 0 Hit", false),
|
labelled_box("Sprite 0 Hit", nes.ppu().sprite_zero_hit),
|
||||||
labelled_box("Sprite 0 Overflow", false),
|
labelled_box("Sprite 0 Overflow", nes.ppu().oam.overflow),
|
||||||
labelled_box("Vertical Blank", nes.ppu().vblank),
|
labelled_box("Vertical Blank", nes.ppu().vblank),
|
||||||
labelled_box("Write Toggle", nes.ppu().background.w),
|
labelled_box("Write Toggle", nes.ppu().background.w),
|
||||||
text(""),
|
text(""),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mod ppu;
|
|||||||
pub mod resize_watcher;
|
pub mod resize_watcher;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_roms;
|
mod test_roms;
|
||||||
|
pub mod audio;
|
||||||
|
|
||||||
pub use ppu::{Color, PPU, RenderBuffer};
|
pub use ppu::{Color, PPU, RenderBuffer};
|
||||||
use tokio::io::AsyncReadExt as _;
|
use tokio::io::AsyncReadExt as _;
|
||||||
@@ -349,6 +350,10 @@ impl NES {
|
|||||||
&self.apu
|
&self.apu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apu_mut(&mut self) -> &mut APU {
|
||||||
|
&mut self.apu
|
||||||
|
}
|
||||||
|
|
||||||
pub fn debug_log(&self) -> &DebugLog {
|
pub fn debug_log(&self) -> &DebugLog {
|
||||||
&self.cpu.debug_log
|
&self.cpu.debug_log
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/main.rs
145
src/main.rs
@@ -5,13 +5,13 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
Color, Element,
|
Element,
|
||||||
Length::{Fill, Shrink},
|
Length::{Fill, Shrink},
|
||||||
Point, Rectangle, Renderer, Size, Subscription, Task, Theme,
|
Point, Rectangle, Renderer, Size, Subscription, Task, Theme,
|
||||||
keyboard::{self, Key, Modifiers, key::Named},
|
keyboard::{self, Key, Modifiers, key::Named},
|
||||||
mouse, time,
|
mouse, time,
|
||||||
widget::{
|
widget::{
|
||||||
self, Button, Canvas, button,
|
self, Canvas, button,
|
||||||
canvas::{Frame, Program},
|
canvas::{Frame, Program},
|
||||||
column, container, image, row,
|
column, container, image, row,
|
||||||
},
|
},
|
||||||
@@ -19,13 +19,13 @@ use iced::{
|
|||||||
};
|
};
|
||||||
use nes_emu::{
|
use nes_emu::{
|
||||||
Break, NES,
|
Break, NES,
|
||||||
|
audio::Audio,
|
||||||
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
|
||||||
header_menu::header_menu,
|
header_menu::header_menu,
|
||||||
hex_view::{HexEvent, HexView},
|
hex_view::{HexEvent, HexView},
|
||||||
resize_watcher::resize_watcher,
|
resize_watcher::resize_watcher,
|
||||||
};
|
};
|
||||||
use tokio::{io::AsyncWriteExt, runtime::Runtime};
|
use tokio::{io::AsyncWriteExt, runtime::Runtime};
|
||||||
use tracing::instrument::WithSubscriber;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
// 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"), "/", "ppu_palette_shared.nes");
|
||||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "input_test.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"), "/", "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 = "./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";
|
||||||
|
|
||||||
/// 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;
|
extern crate nes_emu;
|
||||||
|
|
||||||
fn main() -> Result<(), iced::Error> {
|
fn main() -> Result<(), iced::Error> {
|
||||||
@@ -93,6 +81,7 @@ enum WindowType {
|
|||||||
TileViewer,
|
TileViewer,
|
||||||
Palette,
|
Palette,
|
||||||
Debugger,
|
Debugger,
|
||||||
|
Apu,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[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::Debugger) => write!(f, "Open Debugger"),
|
||||||
Self::Open(WindowType::Palette) => write!(f, "Open Palette"),
|
Self::Open(WindowType::Palette) => write!(f, "Open Palette"),
|
||||||
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
|
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::Reset => write!(f, "Reset"),
|
||||||
Self::PowerCycle => write!(f, "Power Cycle"),
|
Self::PowerCycle => write!(f, "Power Cycle"),
|
||||||
Self::OpenRom => write!(f, "Open ROM file"),
|
Self::OpenRom => write!(f, "Open ROM file"),
|
||||||
@@ -132,6 +122,7 @@ struct Emulator {
|
|||||||
debugger: DebuggerState,
|
debugger: DebuggerState,
|
||||||
main_win_size: Size,
|
main_win_size: Size,
|
||||||
prev: [Instant; 2],
|
prev: [Instant; 2],
|
||||||
|
audio: Audio,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -158,38 +149,32 @@ impl Emulator {
|
|||||||
fn new() -> (Self, Task<Message>) {
|
fn new() -> (Self, Task<Message>) {
|
||||||
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
|
let mut nes = nes_emu::NES::load_nes_file(ROM_FILE).expect("Failed to load nes file");
|
||||||
nes.reset();
|
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 {
|
let (win, task) = iced::window::open(Settings {
|
||||||
min_size: None,
|
min_size: None,
|
||||||
..Settings::default()
|
..Settings::default()
|
||||||
});
|
});
|
||||||
let (win_2, task_2) = iced::window::open(Settings {
|
// let (win_2, task_2) = iced::window::open(Settings {
|
||||||
min_size: None,
|
// min_size: None,
|
||||||
..Settings::default()
|
// ..Settings::default()
|
||||||
});
|
// });
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
nes,
|
nes,
|
||||||
windows: HashMap::from_iter([
|
windows: HashMap::from_iter([
|
||||||
(win, WindowType::Main),
|
(win, WindowType::Main),
|
||||||
(win_2, WindowType::Debugger)
|
// (win_2, WindowType::Debugger),
|
||||||
]),
|
]),
|
||||||
debugger: DebuggerState::new(),
|
debugger: DebuggerState::new(),
|
||||||
main_win_size: Size::new(0., 0.),
|
main_win_size: Size::new(0., 0.),
|
||||||
running: false,
|
running: false,
|
||||||
prev: [Instant::now(); 2],
|
prev: [Instant::now(); 2],
|
||||||
|
audio: Audio::init(),
|
||||||
},
|
},
|
||||||
Task::batch([task, task_2]).discard()
|
Task::batch([
|
||||||
// task.discard(),
|
task,
|
||||||
|
// task_2
|
||||||
|
])
|
||||||
|
.discard(), // task.discard(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn title(&self, win: Id) -> String {
|
fn title(&self, win: Id) -> String {
|
||||||
@@ -200,6 +185,7 @@ impl Emulator {
|
|||||||
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
|
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
|
||||||
Some(WindowType::Palette) => "NES Palette Viewer".into(),
|
Some(WindowType::Palette) => "NES Palette Viewer".into(),
|
||||||
Some(WindowType::Debugger) => "NES Debugger".into(),
|
Some(WindowType::Debugger) => "NES Debugger".into(),
|
||||||
|
Some(WindowType::Apu) => "NES APU Debugger".into(),
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,6 +325,17 @@ impl Emulator {
|
|||||||
self.nes.controller_1().set_left(true);
|
self.nes.controller_1().set_left(true);
|
||||||
} else if key == Key::Named(Named::ArrowRight) {
|
} else if key == Key::Named(Named::ArrowRight) {
|
||||||
self.nes.controller_1().set_right(true);
|
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 {
|
keyboard::Event::KeyReleased {
|
||||||
@@ -366,14 +363,27 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Message::Periodic(i) => {
|
Message::Periodic(_i) => {
|
||||||
if self.running {
|
if self.running {
|
||||||
// TODO: this should skip updating to avoid multiple frame skips
|
// TODO: Smarter frame skip
|
||||||
self.prev[1] = self.prev[0];
|
if self.prev[0].elapsed() >= Duration::from_millis(2) {
|
||||||
self.prev[0] = i;
|
|
||||||
|
|
||||||
self.nes.run_one_clock_cycle(&Break::default());
|
self.nes.run_one_clock_cycle(&Break::default());
|
||||||
while !self.nes.run_one_clock_cycle(&Break::default()).ppu_frame {}
|
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,
|
Message::SetRunning(running) => self.running = running,
|
||||||
@@ -387,7 +397,7 @@ impl Emulator {
|
|||||||
window::close_events().map(Message::WindowClosed),
|
window::close_events().map(Message::WindowClosed),
|
||||||
window::open_events().map(Message::WindowOpened),
|
window::open_events().map(Message::WindowOpened),
|
||||||
keyboard::listen().map(Message::Key),
|
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![
|
Some(WindowType::Debugger) => column![
|
||||||
row![
|
row![
|
||||||
debug_disable(
|
|
||||||
button(image("./images/ic_fluent_play_24_filled.png"))
|
button(image("./images/ic_fluent_play_24_filled.png"))
|
||||||
.on_press(Message::SetRunning(true))
|
.on_press(Message::SetRunning(true)),
|
||||||
),
|
|
||||||
debug_disable(
|
|
||||||
button(image("./images/ic_fluent_pause_24_filled.png"))
|
button(image("./images/ic_fluent_pause_24_filled.png"))
|
||||||
.on_press(Message::SetRunning(false))
|
.on_press(Message::SetRunning(false)),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
self.debugger.view(&self.nes).map(Message::Debugger)
|
self.debugger.view(&self.nes).map(Message::Debugger)
|
||||||
]
|
]
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into(),
|
.into(),
|
||||||
|
Some(WindowType::Apu) => {
|
||||||
|
self.nes.apu().view()
|
||||||
|
}
|
||||||
None => panic!("Window not found"),
|
None => panic!("Window not found"),
|
||||||
// _ => todo!(),
|
// _ => todo!(),
|
||||||
}
|
}
|
||||||
@@ -476,6 +485,7 @@ impl Emulator {
|
|||||||
HeaderButton::Open(WindowType::TileMap),
|
HeaderButton::Open(WindowType::TileMap),
|
||||||
HeaderButton::Open(WindowType::TileViewer),
|
HeaderButton::Open(WindowType::TileViewer),
|
||||||
HeaderButton::Open(WindowType::Palette),
|
HeaderButton::Open(WindowType::Palette),
|
||||||
|
HeaderButton::Open(WindowType::Apu),
|
||||||
],
|
],
|
||||||
Message::Header
|
Message::Header
|
||||||
)
|
)
|
||||||
@@ -488,21 +498,6 @@ impl Emulator {
|
|||||||
impl Program<Message> for Emulator {
|
impl Program<Message> for Emulator {
|
||||||
type State = ();
|
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(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
_state: &Self::State,
|
_state: &Self::State,
|
||||||
@@ -511,17 +506,8 @@ impl Program<Message> for Emulator {
|
|||||||
bounds: iced::Rectangle,
|
bounds: iced::Rectangle,
|
||||||
_cursor: mouse::Cursor,
|
_cursor: mouse::Cursor,
|
||||||
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
|
||||||
// let start = Instant::now();
|
let mut frame = Frame::new(renderer, bounds.size());
|
||||||
// const SIZE: f32 = 2.;
|
|
||||||
let mut frame = Frame::new(
|
|
||||||
renderer,
|
|
||||||
bounds.size(), // iced::Size {
|
|
||||||
// width: 256. * 2.,
|
|
||||||
// height: 240. * 2.,
|
|
||||||
// },
|
|
||||||
);
|
|
||||||
frame.scale(2.);
|
frame.scale(2.);
|
||||||
// TODO: use image for better? performance
|
|
||||||
frame.draw_image(
|
frame.draw_image(
|
||||||
Rectangle::new(Point::new(0., 0.), Size::new(256., 240.)),
|
Rectangle::new(Point::new(0., 0.), Size::new(256., 240.)),
|
||||||
widget::canvas::Image::new(widget::image::Handle::from_rgba(
|
widget::canvas::Image::new(widget::image::Handle::from_rgba(
|
||||||
@@ -532,17 +518,6 @@ impl Program<Message> for Emulator {
|
|||||||
.filter_method(image::FilterMethod::Nearest)
|
.filter_method(image::FilterMethod::Nearest)
|
||||||
.snap(true),
|
.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()]
|
vec![frame.into_geometry()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
src/ppu.rs
119
src/ppu.rs
@@ -128,7 +128,8 @@ pub struct OAM {
|
|||||||
oam_read_buffer: u8,
|
oam_read_buffer: u8,
|
||||||
edit_ver: usize,
|
edit_ver: usize,
|
||||||
sprite_output_units: Vec<SpriteOutputUnit>,
|
sprite_output_units: Vec<SpriteOutputUnit>,
|
||||||
overflow: bool,
|
large_sprites: bool,
|
||||||
|
pub overflow: bool,
|
||||||
sprite_offset_0x1000: bool,
|
sprite_offset_0x1000: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +146,7 @@ impl OAM {
|
|||||||
oam_read_buffer: 0,
|
oam_read_buffer: 0,
|
||||||
overflow: false,
|
overflow: false,
|
||||||
sprite_offset_0x1000: false,
|
sprite_offset_0x1000: false,
|
||||||
|
large_sprites: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,18 +274,18 @@ impl OAM {
|
|||||||
// println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
|
// println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
|
||||||
let lo = (s.low >> bit_pos) & 1;
|
let lo = (s.low >> bit_pos) & 1;
|
||||||
let idx = (hi << 1) | lo;
|
let idx = (hi << 1) | lo;
|
||||||
Some((
|
|
||||||
if idx != 0 {
|
if idx != 0 {
|
||||||
idx + s.attrs.palette() * 4 + 0x10
|
Some((
|
||||||
} else {
|
idx + s.attrs.palette() * 4 + 0x10,
|
||||||
0
|
|
||||||
},
|
|
||||||
!s.attrs.priority(),
|
!s.attrs.priority(),
|
||||||
i == 0,
|
i == 0,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
}
|
}
|
||||||
@@ -292,8 +294,10 @@ impl OAM {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Background {
|
pub struct Background {
|
||||||
/// Current vram address, 15 bits
|
/// Current vram address, 15 bits
|
||||||
|
/// yyy NN YYYYY XXXXX (0yyy NNYY YYYX XXXX)
|
||||||
pub v: u16,
|
pub v: u16,
|
||||||
/// Temp vram address, 15 bits
|
/// Temp vram address, 15 bits
|
||||||
|
/// yyy NN YYYYY XXXXX
|
||||||
pub t: u16,
|
pub t: u16,
|
||||||
/// Fine X control, 3 bits
|
/// Fine X control, 3 bits
|
||||||
pub x: u8,
|
pub x: u8,
|
||||||
@@ -301,18 +305,20 @@ pub struct Background {
|
|||||||
/// When false, writes to x
|
/// When false, writes to x
|
||||||
pub w: bool,
|
pub w: bool,
|
||||||
|
|
||||||
|
copy_v: u8,
|
||||||
|
|
||||||
/// When true, v is incremented by 32 after each read
|
/// When true, v is incremented by 32 after each read
|
||||||
pub vram_column: bool,
|
pub vram_column: bool,
|
||||||
pub second_pattern: bool,
|
pub second_pattern: bool,
|
||||||
|
|
||||||
pub cur_nametable: u8,
|
pub cur_nametable: u8,
|
||||||
pub cur_attr: u8,
|
|
||||||
pub next_attr: u8,
|
pub next_attr: u8,
|
||||||
pub next_attr_2: u8,
|
|
||||||
pub cur_high: u8,
|
pub cur_high: u8,
|
||||||
pub cur_low: u8,
|
pub cur_low: u8,
|
||||||
pub cur_shift_high: u32,
|
pub cur_shift_high: u32,
|
||||||
pub cur_shift_low: u32,
|
pub cur_shift_low: u32,
|
||||||
|
pub cur_attr_shift_high: u32,
|
||||||
|
pub cur_attr_shift_low: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -681,11 +687,11 @@ pub struct PPU {
|
|||||||
|
|
||||||
pub mask: Mask,
|
pub mask: Mask,
|
||||||
pub vblank: bool,
|
pub vblank: bool,
|
||||||
sprite_zero_hit: bool,
|
pub sprite_zero_hit: bool,
|
||||||
|
|
||||||
pub palette: Palette,
|
pub palette: Palette,
|
||||||
pub background: Background,
|
pub background: Background,
|
||||||
oam: OAM,
|
pub oam: OAM,
|
||||||
pub render_buffer: RenderBuffer<256, 240>,
|
pub render_buffer: RenderBuffer<256, 240>,
|
||||||
pub dbg_int: bool,
|
pub dbg_int: bool,
|
||||||
pub cycle: usize,
|
pub cycle: usize,
|
||||||
@@ -740,8 +746,6 @@ impl PPU {
|
|||||||
sprite_zero_hit: false,
|
sprite_zero_hit: false,
|
||||||
frame_count: 0,
|
frame_count: 0,
|
||||||
nmi_enabled: false,
|
nmi_enabled: false,
|
||||||
// nmi_waiting: false,
|
|
||||||
// TODO: Is even in the right initial state?
|
|
||||||
even: false,
|
even: false,
|
||||||
scanline: 0,
|
scanline: 0,
|
||||||
pixel: 25,
|
pixel: 25,
|
||||||
@@ -751,16 +755,17 @@ impl PPU {
|
|||||||
t: 0,
|
t: 0,
|
||||||
x: 0,
|
x: 0,
|
||||||
w: false,
|
w: false,
|
||||||
|
copy_v: 0,
|
||||||
vram_column: false,
|
vram_column: false,
|
||||||
second_pattern: false,
|
second_pattern: false,
|
||||||
cur_high: 0,
|
cur_high: 0,
|
||||||
cur_low: 0,
|
cur_low: 0,
|
||||||
cur_shift_high: 0,
|
cur_shift_high: 0,
|
||||||
cur_shift_low: 0,
|
cur_shift_low: 0,
|
||||||
|
cur_attr_shift_high: 0,
|
||||||
|
cur_attr_shift_low: 0,
|
||||||
cur_nametable: 0,
|
cur_nametable: 0,
|
||||||
cur_attr: 0,
|
|
||||||
next_attr: 0,
|
next_attr: 0,
|
||||||
next_attr_2: 0,
|
|
||||||
},
|
},
|
||||||
oam: OAM::new(),
|
oam: OAM::new(),
|
||||||
vram_buffer: 0,
|
vram_buffer: 0,
|
||||||
@@ -791,9 +796,6 @@ impl PPU {
|
|||||||
5 => panic!("ppuscroll is write-only"),
|
5 => panic!("ppuscroll is write-only"),
|
||||||
6 => panic!("ppuaddr is write-only"),
|
6 => panic!("ppuaddr is write-only"),
|
||||||
7 => {
|
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) {
|
let val = match mem.read(self.background.v) {
|
||||||
Value::Value(v) => {
|
Value::Value(v) => {
|
||||||
let val = self.vram_buffer;
|
let val = self.vram_buffer;
|
||||||
@@ -805,10 +807,6 @@ impl PPU {
|
|||||||
offset,
|
offset,
|
||||||
} => self.palette.ram[offset as usize],
|
} => 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();
|
self.increment_v();
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
@@ -819,13 +817,16 @@ impl PPU {
|
|||||||
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
|
||||||
match offset {
|
match offset {
|
||||||
0x00 => {
|
0x00 => {
|
||||||
self.nmi_enabled = val & 0b1000_0000 != 0;
|
|
||||||
self.background.t =
|
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.vram_column = val & 0b0000_0100 != 0;
|
||||||
self.background.second_pattern = val & 0b0001_0000 != 0;
|
|
||||||
self.oam.sprite_offset_0x1000 = val & 0b0000_1000 != 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 => {
|
0x01 => {
|
||||||
// self.dbg_int = true;
|
// self.dbg_int = true;
|
||||||
@@ -841,7 +842,6 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
0x02 => {
|
0x02 => {
|
||||||
todo!("Unable to write to PPU status")
|
todo!("Unable to write to PPU status")
|
||||||
// TODO: ppu status
|
|
||||||
}
|
}
|
||||||
0x03 => self.oam.addr = val,
|
0x03 => self.oam.addr = val,
|
||||||
0x04 => {
|
0x04 => {
|
||||||
@@ -868,19 +868,16 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x06 => {
|
0x06 => {
|
||||||
// TODO: this actually sets T, which is copied to v later (~ a pixel later?)
|
|
||||||
if self.background.w {
|
if self.background.w {
|
||||||
self.background.v =
|
self.background.t =
|
||||||
u16::from_le_bytes([val, self.background.v.to_le_bytes()[1]]);
|
u16::from_le_bytes([val, self.background.t.to_le_bytes()[1]]);
|
||||||
self.background.w = false;
|
self.background.w = false;
|
||||||
|
self.background.copy_v = 2; // Set t to be copied to v in a pixel or so
|
||||||
} else {
|
} else {
|
||||||
self.background.v =
|
self.background.t =
|
||||||
u16::from_le_bytes([self.background.v.to_le_bytes()[0], val & 0b0011_1111]);
|
u16::from_le_bytes([self.background.t.to_le_bytes()[0], val & 0b0011_1111]);
|
||||||
self.background.w = true;
|
self.background.w = true;
|
||||||
}
|
}
|
||||||
// println!("Updating v for ppuaddr write: to {:04X}", self.background.v);
|
|
||||||
// self.dbg_int = true;
|
|
||||||
// todo!("PPUADDR write")
|
|
||||||
}
|
}
|
||||||
0x07 => {
|
0x07 => {
|
||||||
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
// println!("Writing: {:02X}, @{:04X}", val, self.background.v);
|
||||||
@@ -897,7 +894,6 @@ impl PPU {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.increment_v();
|
self.increment_v();
|
||||||
// self.background.v += 1; // TODO: implement inc behavior
|
|
||||||
}
|
}
|
||||||
_ => panic!("No register at {:02X}", offset),
|
_ => panic!("No register at {:02X}", offset),
|
||||||
}
|
}
|
||||||
@@ -910,18 +906,17 @@ impl PPU {
|
|||||||
} else {
|
} else {
|
||||||
self.background.v += 1;
|
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 {
|
pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool {
|
||||||
self.cycle += 1;
|
self.cycle += 1;
|
||||||
self.pixel += 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
|
if self.scanline == 261
|
||||||
&& (self.pixel == 341 || (self.pixel == 340 && self.even && self.rendering_enabled()))
|
&& (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.v = (self.background.v & 0b0000_0100_0001_1111)
|
||||||
| (self.background.t & 0b0111_1011_1110_0000);
|
| (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 {
|
if self.scanline != 261 {
|
||||||
self.oam.ppu_cycle(self.pixel, self.scanline, mem);
|
self.oam.ppu_cycle(self.pixel, self.scanline, mem);
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
if self.pixel == 0 {
|
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.scanline, &self.oam.sprite_output_units[0]));
|
||||||
// dbg!(&self.oam.secondary);
|
// dbg!(&self.oam.secondary);
|
||||||
}
|
}
|
||||||
@@ -963,17 +955,16 @@ impl PPU {
|
|||||||
// self.dbg_int = true;
|
// self.dbg_int = true;
|
||||||
// const POS: u32 = 1 << 15;
|
// const POS: u32 = 1 << 15;
|
||||||
let bit_pos = 15 - self.background.x;
|
let bit_pos = 15 - self.background.x;
|
||||||
// let pos: u32 = 1 << (15 + self.background.x*0); // TODO: handle this correctly
|
|
||||||
// Determine background color
|
// Determine background color
|
||||||
let a = (self.background.cur_shift_high >> bit_pos) & 1;
|
let a = (self.background.cur_shift_high >> bit_pos) & 1;
|
||||||
let b = (self.background.cur_shift_low >> bit_pos) & 1;
|
let b = (self.background.cur_shift_low >> bit_pos) & 1;
|
||||||
let val = (a << 1) | b;
|
let val = (a << 1) | b;
|
||||||
debug_assert!(val < 4);
|
debug_assert!(val < 4);
|
||||||
|
|
||||||
let h_off = ((self.pixel - 1) / 16) % 2;
|
let a = (self.background.cur_attr_shift_high >> bit_pos) & 1;
|
||||||
let v_off = (self.scanline / 16) % 2;
|
let b = (self.background.cur_attr_shift_low >> bit_pos) & 1;
|
||||||
let off = v_off * 4 + h_off * 2;
|
let palette = ((a << 1) | b) as u8;
|
||||||
let palette = (self.background.cur_attr >> off) & 0x3;
|
debug_assert!(palette < 4);
|
||||||
let color = val as u8 + if val != 0 { palette * 4 } else { 0 };
|
let color = val as u8 + if val != 0 { palette * 4 } else { 0 };
|
||||||
|
|
||||||
if self.scanline < 240 && self.pixel < 257 {
|
if self.scanline < 240 && self.pixel < 257 {
|
||||||
@@ -995,12 +986,14 @@ impl PPU {
|
|||||||
self.render_buffer.write(
|
self.render_buffer.write(
|
||||||
self.scanline,
|
self.scanline,
|
||||||
self.pixel - 1,
|
self.pixel - 1,
|
||||||
self.palette.color(color), // self.palette.colors[val as usize],
|
self.palette.color(color),
|
||||||
); // TODO: this should come from shift registers
|
);
|
||||||
}
|
}
|
||||||
if self.pixel < 337 {
|
if self.pixel < 337 {
|
||||||
self.background.cur_shift_high <<= 1;
|
self.background.cur_shift_high <<= 1;
|
||||||
self.background.cur_shift_low <<= 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 {
|
if self.scanline < 240 || self.scanline == 261 {
|
||||||
@@ -1021,8 +1014,8 @@ impl PPU {
|
|||||||
| ((self.background.v >> 2) & 0x07);
|
| ((self.background.v >> 2) & 0x07);
|
||||||
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
// println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
|
||||||
// assert_eq!(addr, addr_2);
|
// assert_eq!(addr, addr_2);
|
||||||
let val = mem.read(addr).reg_map(|_, _| 0); // TODO: handle reg reads
|
let val = mem.read(addr).reg_map(|_, offset| self.palette.ram(offset as u8));
|
||||||
self.background.next_attr_2 = val;
|
self.background.next_attr = val;
|
||||||
} else if self.pixel % 8 == 6 {
|
} else if self.pixel % 8 == 6 {
|
||||||
// BG pattern low
|
// BG pattern low
|
||||||
let addr = self.background.cur_nametable as u16 * 16
|
let addr = self.background.cur_nametable as u16 * 16
|
||||||
@@ -1049,8 +1042,14 @@ impl PPU {
|
|||||||
self.background.cur_high = val;
|
self.background.cur_high = val;
|
||||||
self.background.cur_shift_low |= self.background.cur_low as u32;
|
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_shift_high |= self.background.cur_high as u32;
|
||||||
self.background.cur_attr = self.background.next_attr;
|
let h_off = (self.background.v >> 1) % 2;
|
||||||
self.background.next_attr = self.background.next_attr_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
|
// Inc horizontal
|
||||||
if self.background.v & 0x1F == 31 {
|
if self.background.v & 0x1F == 31 {
|
||||||
self.background.v = (self.background.v & !0x1F) ^ 0x400;
|
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
|
JOY2 = $4017
|
||||||
SNDMODE = $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
|
SNDMODE_NOIRQ = $40
|
||||||
|
|
||||||
.if CLOCK_RATE = 1789773
|
.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