Split WASM and native versions, and move iced support code to native
This commit is contained in:
3
.helix/languages.toml
Normal file
3
.helix/languages.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[language-server.rust-analyzer]
|
||||
config = { cargo = { features = "all" } }
|
||||
|
||||
1279
Cargo.lock
generated
1279
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
54
Cargo.toml
54
Cargo.toml
@@ -3,18 +3,52 @@ name = "nes-emu"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
iced = [
|
||||
"dep:iced", "iced/debug", "iced/canvas", "iced/lazy", "iced/image", "iced/advanced", "tokio", "fs"
|
||||
]
|
||||
fs = []
|
||||
tokio = ["iced/tokio", "dep:tokio", "tokio/time", "tokio/fs"]
|
||||
iced_wasm = ["dep:iced"]
|
||||
audio = ["dep:cpal", "dep:ringbuf"]
|
||||
web = ["dep:web-sys", "dep:web-sys"]
|
||||
|
||||
[dependencies]
|
||||
bitfield = "0.19.3"
|
||||
# iced = { version = "0.14.0", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||
iced = { path = "../iced", features = ["debug", "canvas", "tokio", "lazy", "image", "advanced"] }
|
||||
iced_core = { path = "../iced/core", features = ["advanced"] }
|
||||
rfd = "0.17.2"
|
||||
# iced_graphics = { version = "0.14.0", features = ["geometry", "image"] }
|
||||
# iced_widget = { version = "0.13.4", features = ["canvas", "image"] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
thiserror = "2.0.18"
|
||||
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"
|
||||
|
||||
# yew = { version = "0.23", features = ["csr"], optional = true }
|
||||
web-sys = { version = "*", features = [
|
||||
"HtmlCanvasElement", "CanvasRenderingContext2d",
|
||||
"Window", "Document", "KeyboardEvent",
|
||||
"DocumentTimeline", "AudioContextState",
|
||||
"HtmlButtonElement",
|
||||
"HtmlInputElement", "FileList", "File", "Blob",
|
||||
"ImageBitmap", "ImageData",
|
||||
"AudioContext", "AudioContextOptions",
|
||||
"AudioBuffer", "AudioBufferOptions", "AudioDestinationNode", "AudioBufferSourceNode",
|
||||
"GainNode", "AudioParam", "DelayNode"
|
||||
], optional = true }
|
||||
cpal = { version = "0.17.1", optional = true}
|
||||
ringbuf = { version = "0.4.8", optional = true}
|
||||
iced = { path = "../iced", features = [], optional = true }
|
||||
# iced_core = { path = "../iced/core", features = ["advanced"], optional = true }
|
||||
rfd = { version = "0.17.2", optional = true }
|
||||
tokio = { version = "1.48.0", features = [], optional = true }
|
||||
log = "*"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "native"
|
||||
required-features = ["iced", "rfd", "audio"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm"
|
||||
required-features = ["web"]
|
||||
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Yew App</title>
|
||||
<style>
|
||||
canvas {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
122
src/apu.rs
122
src/apu.rs
@@ -1,9 +1,5 @@
|
||||
use std::iter::repeat_n;
|
||||
|
||||
use iced::{
|
||||
Element, Font,
|
||||
widget::{column, text},
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
macro_rules! lut {
|
||||
@@ -89,8 +85,10 @@ bitfield::bitfield! {
|
||||
struct PulseChannel {
|
||||
enabled: bool,
|
||||
duty_vol: DutyVol,
|
||||
|
||||
sweep: Sweep,
|
||||
sweep_reload: bool,
|
||||
sweep_counter: u8,
|
||||
|
||||
counter: LengthCounter,
|
||||
|
||||
@@ -102,15 +100,17 @@ struct PulseChannel {
|
||||
envelope_start: bool,
|
||||
envelope_counter: u8,
|
||||
envelope_divider: u8,
|
||||
extra: u16,
|
||||
}
|
||||
|
||||
impl PulseChannel {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(extra: u16) -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
duty_vol: DutyVol(0),
|
||||
sweep: Sweep(0),
|
||||
sweep_reload: false,
|
||||
sweep_counter: 0,
|
||||
counter: LengthCounter::new(),
|
||||
period: 0,
|
||||
period_timer: 0,
|
||||
@@ -119,6 +119,7 @@ impl PulseChannel {
|
||||
envelope_start: false,
|
||||
envelope_counter: 0,
|
||||
envelope_divider: 0,
|
||||
extra,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ impl PulseChannel {
|
||||
0x01 => {
|
||||
self.sweep.0 = val;
|
||||
self.sweep_reload = true;
|
||||
},
|
||||
}
|
||||
0x02 => self.period = self.period & 0x700 | (val as u16),
|
||||
0x03 => {
|
||||
let reg = LengthTimerHigh(val);
|
||||
@@ -143,7 +144,7 @@ impl PulseChannel {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn volume(&self) -> u8 {
|
||||
if self.duty_vol.const_vol() {
|
||||
self.duty_vol.volume()
|
||||
@@ -151,7 +152,7 @@ impl PulseChannel {
|
||||
self.envelope_counter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn clock(&mut self) {
|
||||
if !self.enabled {
|
||||
self.sample = 0;
|
||||
@@ -177,7 +178,7 @@ impl PulseChannel {
|
||||
// TODO
|
||||
}
|
||||
pub fn cur_sample(&self) -> u8 {
|
||||
if self.enabled && !self.counter.silenced() {
|
||||
if self.enabled && !self.counter.silenced() && !self.sweep_silenced() {
|
||||
self.sample
|
||||
} else {
|
||||
0
|
||||
@@ -205,30 +206,54 @@ impl PulseChannel {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn target_period(&self) -> u16 {
|
||||
let amt = self.period >> self.sweep.shift();
|
||||
if self.sweep.negate() {
|
||||
self.period.saturating_sub(amt + self.extra)
|
||||
} else {
|
||||
self.period + amt
|
||||
}
|
||||
}
|
||||
fn sweep_silenced(&self) -> bool {
|
||||
self.period < 8 || self.target_period() > 0x7FF
|
||||
}
|
||||
pub fn h_frame_clock(&mut self) {
|
||||
self.q_frame_clock();
|
||||
if self.sweep.enable() {
|
||||
if self.sweep_reload {
|
||||
self.sweep_counter = self.sweep.period();
|
||||
self.sweep_reload = false;
|
||||
} else if self.sweep_counter == 0 {
|
||||
self.sweep_counter = self.sweep.period();
|
||||
if self.period < 8 || self.target_period() < 0x7FF {
|
||||
self.period = self.target_period();
|
||||
}
|
||||
} else {
|
||||
self.sweep_counter -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
pub fn view(&self) -> String {
|
||||
format!(
|
||||
"Square Channel
|
||||
Evelope Volume: {0:>3} ${0:02X}
|
||||
Evelope Volume: {0:>4} ${0:02X}
|
||||
Constant Volume: {1}
|
||||
Length Counter - Halted: {2}
|
||||
Duty: {3:>3} ${3:02X}
|
||||
Sweep - Shift: {4:>3} ${4:02X}
|
||||
Duty: {3:>4} ${3:02X}
|
||||
Sweep - Shift: {4:>4} ${4:02X}
|
||||
Sweep - Negate: {5}
|
||||
Sweep - Period: {6:>3} ${6:02X}
|
||||
Sweep - Period: {6:>4} ${6:02X}
|
||||
Sweep - Enabled: {7}
|
||||
Period: {8:>3} ${8:04X}
|
||||
Length Counter - Reload Value: {9:>3} ${9:04X}
|
||||
Period: {8:>4} ${8:04X}
|
||||
Length Counter - Reload Value: {9:>4} ${9:04X}
|
||||
Enabled: {10}
|
||||
Timer: {11:>3} ${11:04X}
|
||||
Duty Position: {12:>3} ${12:02X}
|
||||
Length Counter - Counter: {13:>3} ${13:02X}
|
||||
Envelope - Counter: {14:>3} ${14:02X}
|
||||
Envelope - Divider: {15:>3} ${15:02X}
|
||||
Output: {16:>3} ${16:02X}
|
||||
Timer: {11:>4} ${11:04X}
|
||||
Duty Position: {12:>4} ${12:02X}
|
||||
Length Counter - Counter: {13:>4} ${13:02X}
|
||||
Envelope - Counter: {14:>4} ${14:02X}
|
||||
Envelope - Divider: {15:>4} ${15:02X}
|
||||
Output: {16:>4} ${16:02X}
|
||||
",
|
||||
self.duty_vol.volume(),
|
||||
self.duty_vol.const_vol(),
|
||||
@@ -242,14 +267,12 @@ Output: {16:>3} ${16:02X}
|
||||
0,
|
||||
self.enabled,
|
||||
self.period_timer,
|
||||
self.cur, // ?
|
||||
self.cur, // ?
|
||||
self.counter.current,
|
||||
self.envelope_counter,
|
||||
self.envelope_divider,
|
||||
self.cur_sample(),
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,8 +367,8 @@ impl TriangleChannel {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!(
|
||||
pub fn view(&self) -> String {
|
||||
format!(
|
||||
"Triangle Channel
|
||||
Linear Counter - Reload: {0:>3} ${0:02X}
|
||||
Linear Counter - Halted: {1}
|
||||
@@ -372,8 +395,6 @@ Output: {10:>3} ${10:02X}
|
||||
self.reload,
|
||||
self.cur_sample(),
|
||||
)
|
||||
.font(Font::MONOSPACE)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,8 +486,8 @@ impl NoiseChannel {
|
||||
self.q_frame_clock();
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
pub fn view(&self) -> String {
|
||||
format!("")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,8 +526,8 @@ impl DeltaChannel {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn view<T>(&self) -> Element<'_, T> {
|
||||
text!("").font(Font::MONOSPACE).into()
|
||||
pub fn view(&self) -> String {
|
||||
format!("")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,8 +565,8 @@ impl std::fmt::Debug for APU {
|
||||
impl APU {
|
||||
pub fn init() -> Self {
|
||||
Self {
|
||||
pulse_1: PulseChannel::new(),
|
||||
pulse_2: PulseChannel::new(),
|
||||
pulse_1: PulseChannel::new(1),
|
||||
pulse_2: PulseChannel::new(0),
|
||||
triangle: TriangleChannel::new(),
|
||||
noise: NoiseChannel::new(),
|
||||
dmc: DeltaChannel::new(),
|
||||
@@ -695,15 +716,26 @@ 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()
|
||||
#[cfg(feature = "iced")]
|
||||
mod apu_iced {
|
||||
use super::*;
|
||||
use iced::{
|
||||
Element, Font,
|
||||
widget::{column, text},
|
||||
};
|
||||
|
||||
impl APU {
|
||||
pub fn view<'s, T: 's>(&'s self) -> Element<'s, T> {
|
||||
column![
|
||||
text(self.pulse_1.view()).font(Font::MONOSPACE),
|
||||
text(self.pulse_2.view()).font(Font::MONOSPACE),
|
||||
text(self.triangle.view()).font(Font::MONOSPACE),
|
||||
text(self.noise.view()).font(Font::MONOSPACE),
|
||||
text(self.dmc.view()).font(Font::MONOSPACE),
|
||||
]
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ use iced::{
|
||||
scrollable, text,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{Break, CycleResult, NES, PPU, mem::Mapped};
|
||||
use nes_emu::{Break, CycleResult, Mapped, NES, PPU};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebuggerState {
|
||||
@@ -86,6 +85,8 @@ where
|
||||
text(format!("{val:032b}"))
|
||||
}
|
||||
|
||||
type Type = NES;
|
||||
|
||||
impl DebuggerState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -106,23 +107,23 @@ impl DebuggerState {
|
||||
row![column![
|
||||
text("Status"),
|
||||
row![
|
||||
labelled("A:", text(format!("{:02X}", nes.cpu.a))),
|
||||
labelled("X:", text(format!("{:02X}", nes.cpu.x))),
|
||||
labelled("Y:", text(format!("{:02X}", nes.cpu.y))),
|
||||
labelled("PC:", text(format!("{:04X}", nes.cpu.pc))),
|
||||
labelled("A:", text(format!("{:02X}", nes.cpu().a))),
|
||||
labelled("X:", text(format!("{:02X}", nes.cpu().x))),
|
||||
labelled("Y:", text(format!("{:02X}", nes.cpu().y))),
|
||||
labelled("PC:", text(format!("{:04X}", nes.cpu().pc))),
|
||||
labelled("Cycle:", text(format!("{}", nes.cpu_cycle()))),
|
||||
labelled("SP:", text(format!("{:02X}", nes.cpu.sp))),
|
||||
labelled("SP:", text(format!("{:02X}", nes.cpu().sp))),
|
||||
]
|
||||
.spacing(5.),
|
||||
row![
|
||||
labelled("P:", text(format!("{:02X}", nes.cpu.status.0))),
|
||||
labelled_box("Carry", nes.cpu.status.carry()),
|
||||
labelled_box("Zero", nes.cpu.status.zero()),
|
||||
labelled_box("Interrupt", nes.cpu.status.interrupt_disable()),
|
||||
labelled("P:", text(format!("{:02X}", nes.cpu().status.0))),
|
||||
labelled_box("Carry", nes.cpu().status.carry()),
|
||||
labelled_box("Zero", nes.cpu().status.zero()),
|
||||
labelled_box("Interrupt", nes.cpu().status.interrupt_disable()),
|
||||
labelled_box("--", false),
|
||||
labelled_box("--", false),
|
||||
labelled_box("Overflow", nes.cpu.status.overflow()),
|
||||
labelled_box("Negative", nes.cpu.status.negative()),
|
||||
labelled_box("Overflow", nes.cpu().status.overflow()),
|
||||
labelled_box("Negative", nes.cpu().status.negative()),
|
||||
]
|
||||
.spacing(5.),
|
||||
row![
|
||||
@@ -246,7 +247,7 @@ impl DebuggerState {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
|
||||
fn run_n_clock_cycles(nes: &mut Type, n: usize) {
|
||||
for _ in 0..n {
|
||||
if nes.run_one_clock_cycle(&Break::default()).dbg_int || nes.halted() {
|
||||
break;
|
||||
@@ -321,7 +322,7 @@ impl DebuggerState {
|
||||
ppu_scanline: true,
|
||||
..Break::default()
|
||||
},
|
||||
|_, n| n.ppu.scanline == self.to_scan_line,
|
||||
|_, n| n.ppu().scanline == self.to_scan_line,
|
||||
),
|
||||
DebuggerMessage::RunFrames => Self::run_n_clock_cycles(nes, self.frames * 341 * 262),
|
||||
DebuggerMessage::RunBreakpoint => Self::run_until(
|
||||
@@ -330,7 +331,7 @@ impl DebuggerState {
|
||||
break_points: vec![self.breakpoint as u16],
|
||||
..Break::default()
|
||||
},
|
||||
|_, nes| nes.cpu.pc as usize == self.breakpoint,
|
||||
|_, nes| nes.cpu().pc as usize == self.breakpoint,
|
||||
),
|
||||
// DebuggerMessage::Run => {
|
||||
// Self::run_until(nes, &Break { ..Break::default() }, |_, nes| nes.halted())
|
||||
@@ -1,42 +1,25 @@
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
sync::Arc,
|
||||
fmt::{self, Display}, ops::Deref
|
||||
};
|
||||
|
||||
use iced::{
|
||||
Element, Fill, Font, Padding, Task,
|
||||
advanced::graphics::text::{
|
||||
FontSystem,
|
||||
cosmic_text::{
|
||||
Attrs, Family, Metrics,
|
||||
fontdb::{ID, Query},
|
||||
},
|
||||
font_system,
|
||||
},
|
||||
keyboard::{Key, key::Named},
|
||||
mouse::{Button, ScrollDelta},
|
||||
widget::{column, lazy, row, text},
|
||||
};
|
||||
use iced_core::{
|
||||
Clipboard, Color, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Text, Vector,
|
||||
Widget,
|
||||
layout::{Limits, Node},
|
||||
mouse::{Cursor, Interaction},
|
||||
overlay,
|
||||
renderer::{Quad, Style},
|
||||
widget::{
|
||||
Operation, Tree,
|
||||
tree::{State, Tag},
|
||||
},
|
||||
advanced::{layout::{Limits, Node}, overlay, renderer::{Quad, Style}, text::Renderer, widget::{tree::{State, Tag}, Operation, Tree}, Clipboard, Layout, Shell, Text, Widget}, alignment::Vertical, keyboard::{key::Named, Key}, mouse::{Button, Cursor, Interaction, ScrollDelta}, widget::{column, lazy, text}, Color, Element, Event, Fill, Font, Length, Padding, Pixels, Point, Rectangle, Size, Task, Vector
|
||||
};
|
||||
// use iced_core::{
|
||||
// Clipboard, Color, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Text, Vector,
|
||||
// Widget,
|
||||
// layout::{Limits, Node},
|
||||
// mouse::{Cursor, Interaction},
|
||||
// overlay,
|
||||
// renderer::{Quad, Style},
|
||||
// widget::{
|
||||
// Operation, Tree,
|
||||
// tree::{State, Tag},
|
||||
// },
|
||||
// };
|
||||
use nes_emu::Memory;
|
||||
|
||||
use crate::{PPU, mem::Mapped, ppu::PPUMMRegisters};
|
||||
|
||||
pub trait Memory {
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
fn edit_ver(&self) -> usize;
|
||||
}
|
||||
use nes_emu::{PPU, Mapped, PPUMMRegisters};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Cpu<'a>(&'a Mapped);
|
||||
@@ -169,12 +152,22 @@ impl HexView {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferSlice<'a>(pub &'a [u8]);
|
||||
|
||||
impl Deref for BufferSlice<'_> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Buffer {
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
}
|
||||
|
||||
impl Buffer for &[u8] {
|
||||
impl Buffer for BufferSlice<'_> {
|
||||
fn peek(&self, val: usize) -> Option<u8> {
|
||||
self.get(val).copied()
|
||||
}
|
||||
@@ -193,7 +186,7 @@ impl<M: Memory> Buffer for M {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hex_editor<B: Buffer, M, R: iced_core::text::Renderer>(raw: B) -> HexEditor<B, M, R> {
|
||||
pub fn hex_editor<B: Buffer, M, R: Renderer>(raw: B) -> HexEditor<B, M, R> {
|
||||
HexEditor {
|
||||
val: raw,
|
||||
on_edit: None,
|
||||
@@ -209,7 +202,7 @@ pub struct HexEdit {
|
||||
new_value: u8,
|
||||
}
|
||||
|
||||
pub struct HexEditor<B, M, R: iced_core::text::Renderer> {
|
||||
pub struct HexEditor<B, M, R: Renderer> {
|
||||
val: B,
|
||||
font_size: Option<Pixels>,
|
||||
font: Option<R::Font>,
|
||||
@@ -218,7 +211,7 @@ pub struct HexEditor<B, M, R: iced_core::text::Renderer> {
|
||||
|
||||
impl<B, M, R> HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
R: Renderer,
|
||||
{
|
||||
pub fn font(mut self, font: R::Font) -> Self {
|
||||
self.font = Some(font);
|
||||
@@ -228,7 +221,7 @@ where
|
||||
|
||||
impl<B: Buffer, M, R> HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
R: Renderer,
|
||||
{
|
||||
fn value_bounds(&self, renderer: &R, layout: Layout<'_>) -> Rectangle {
|
||||
let size = self.font_size.unwrap_or(renderer.default_size());
|
||||
@@ -296,7 +289,7 @@ const LINE_HEIGHT: f32 = 1.3;
|
||||
|
||||
impl<B: Buffer, M, T, R> Widget<M, T, R> for HexEditor<B, M, R>
|
||||
where
|
||||
R: iced_core::text::Renderer,
|
||||
R: Renderer,
|
||||
{
|
||||
fn tag(&self) -> Tag {
|
||||
Tag::of::<HexEditorState>()
|
||||
@@ -354,10 +347,10 @@ where
|
||||
size,
|
||||
line_height: size.into(),
|
||||
font,
|
||||
align_x: iced_core::text::Alignment::Left,
|
||||
align_y: iced_core::alignment::Vertical::Top,
|
||||
shaping: iced_core::text::Shaping::Basic,
|
||||
wrapping: iced_core::text::Wrapping::None,
|
||||
align_x: iced::advanced::text::Alignment::Left,
|
||||
align_y: Vertical::Top,
|
||||
shaping: iced::advanced::text::Shaping::Basic,
|
||||
wrapping: iced::advanced::text::Wrapping::None,
|
||||
hint_factor: None,
|
||||
},
|
||||
pos,
|
||||
@@ -444,10 +437,10 @@ where
|
||||
size,
|
||||
line_height: size.into(),
|
||||
font,
|
||||
align_x: iced_core::text::Alignment::Left,
|
||||
align_y: iced_core::alignment::Vertical::Top,
|
||||
shaping: iced_core::text::Shaping::Basic,
|
||||
wrapping: iced_core::text::Wrapping::None,
|
||||
align_x: iced::advanced::text::Alignment::Left,
|
||||
align_y: Vertical::Top,
|
||||
shaping: iced::advanced::text::Shaping::Basic,
|
||||
wrapping: iced::advanced::text::Wrapping::None,
|
||||
hint_factor: None,
|
||||
},
|
||||
pos,
|
||||
@@ -19,14 +19,19 @@ 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_subscriber::EnvFilter;
|
||||
mod debugger;
|
||||
use debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image};
|
||||
mod resize_watcher;
|
||||
use resize_watcher::resize_watcher;
|
||||
mod header_menu;
|
||||
use header_menu::header_menu;
|
||||
mod hex_view;
|
||||
use hex_view::{HexEvent, HexView};
|
||||
mod audio;
|
||||
use audio::Audio;
|
||||
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
|
||||
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "crc_check.nes");
|
||||
@@ -151,6 +156,7 @@ 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();
|
||||
nes.debug_log_mut().enable();
|
||||
let (win, task) = iced::window::open(Settings {
|
||||
min_size: None,
|
||||
..Settings::default()
|
||||
@@ -234,7 +240,10 @@ impl Emulator {
|
||||
.and_then(|p| {
|
||||
Task::future(async move {
|
||||
// println!("Opening: {}", p.path().display());
|
||||
NES::async_load_nes_file(p.path()).await.ok()
|
||||
NES::async_load_nes_file(p.path()).await.ok().map(|mut nes| {
|
||||
nes.debug_log_mut().enable();
|
||||
nes
|
||||
})
|
||||
})
|
||||
})
|
||||
.and_then(|n| Task::done(Message::OpenRom(n)));
|
||||
@@ -375,7 +384,7 @@ impl Emulator {
|
||||
&& !self.nes.halted()
|
||||
{
|
||||
count += 1;
|
||||
if count > 100000 {
|
||||
if count > 90_000 {
|
||||
println!("Loop overran...");
|
||||
break;
|
||||
}
|
||||
300
src/bin/wasm/main.rs
Normal file
300
src/bin/wasm/main.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
use std::sync::{
|
||||
Arc, Mutex,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use nes_emu::{Break, NES};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use web_sys::{
|
||||
js_sys::{Function, Uint8Array}, wasm_bindgen::{closure::Closure, Clamped, JsValue, UnwrapThrowExt}, window, AudioBuffer, AudioBufferOptions, AudioContext, AudioContextOptions, AudioContextState, CanvasRenderingContext2d, HtmlButtonElement, HtmlCanvasElement, HtmlInputElement, ImageBitmap, ImageData, KeyboardEvent
|
||||
};
|
||||
|
||||
const DEFAULT_ROM: &[u8] = include_bytes!(concat!(
|
||||
env!("ROM_DIR"),
|
||||
"/input_test.nes" // TODO: select default rom
|
||||
));
|
||||
|
||||
pub fn main() {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
console_log::init().expect("Initialize logger");
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let win = window().expect_throw("Window is undefined");
|
||||
let doc = win.document().expect_throw("Document is undefined");
|
||||
let canvas = doc
|
||||
.get_element_by_id("main_view")
|
||||
.expect_throw("Failed to get canvas element");
|
||||
let canvas = HtmlCanvasElement::from(JsValue::from(canvas));
|
||||
|
||||
let ctx =
|
||||
CanvasRenderingContext2d::from(JsValue::from(canvas.get_context("2d").unwrap().unwrap()));
|
||||
let cl = Arc::new(Closure::new(move |image| {
|
||||
ctx.reset();
|
||||
ctx.draw_image_with_image_bitmap(&ImageBitmap::from(image), 0., 0.)
|
||||
.expect_throw("Failed to draw image");
|
||||
}));
|
||||
let audio_ctx_opts = AudioContextOptions::new();
|
||||
audio_ctx_opts.set_sample_rate(60. * 3723.);
|
||||
// audio_ctx_opts.set_
|
||||
let audio = AudioContext::new_with_context_options(&audio_ctx_opts)
|
||||
.expect_throw("Failed to create audio context");
|
||||
let _ = audio.suspend().expect_throw("Failed to suspend AudioCtx");
|
||||
|
||||
const BUFFER_SIZE: usize = 1 << 10;
|
||||
const SAMPLE_RATE: f64 = 60. * 3723.;
|
||||
let audio_buffer_opts = AudioBufferOptions::new(BUFFER_SIZE as u32, SAMPLE_RATE as f32);
|
||||
audio_buffer_opts.set_number_of_channels(1);
|
||||
// let audio_buffer =
|
||||
// AudioBuffer::new(&audio_buffer_opts).expect_throw("Failed to create audio buffer");
|
||||
// let mut raw_buf = [0.; BUFFER_SIZE];
|
||||
// for i in 0..raw_buf.len() {
|
||||
// raw_buf[i] = (i as f32 / 200.).sin()
|
||||
// }
|
||||
// audio_buffer
|
||||
// .copy_to_channel(&raw_buf, 0)
|
||||
// .expect_throw("Failed to copy audio data");
|
||||
let gain_node = audio
|
||||
.create_gain()
|
||||
.expect_throw("Failed to create gain node");
|
||||
gain_node.set_channel_count(1);
|
||||
gain_node.gain().set_value(0.01);
|
||||
gain_node
|
||||
.connect_with_audio_node_and_output(&audio.destination(), 0)
|
||||
.expect_throw("Failed to connect to audio destination");
|
||||
// gain_node
|
||||
// .connect_with_audio_node_and_output(&audio.destination(), 1)
|
||||
// .expect_throw("Failed to connect to audio destination 2");
|
||||
let delay_node = audio
|
||||
.create_delay()
|
||||
.expect_throw("Failed to create delay node");
|
||||
delay_node.set_channel_count(1);
|
||||
delay_node.delay_time().set_value(0.);
|
||||
delay_node
|
||||
.connect_with_audio_node(&gain_node)
|
||||
.expect_throw("Failed to connect to audio destination");
|
||||
// source_node.start_with_when(when)
|
||||
|
||||
let nes = Arc::new(Mutex::new({
|
||||
let mut n = NES::load_nes_file_mem(DEFAULT_ROM).unwrap();
|
||||
n.reset();
|
||||
|
||||
n
|
||||
}));
|
||||
// let timeout = Arc::new(Mutex::new(None as Option<i32>));
|
||||
let poisoned = Arc::new(AtomicBool::new(false));
|
||||
// let timeout_cpy = timeout.clone();
|
||||
|
||||
{
|
||||
let audio = audio.clone();
|
||||
let el = doc.get_element_by_id("pause").unwrap();
|
||||
let element = HtmlButtonElement::from(JsValue::from(el.clone()));
|
||||
let f: Closure<dyn Fn(JsValue)> = Closure::new(move |_e| {
|
||||
if audio.state() == AudioContextState::Suspended {
|
||||
let _ = audio.resume();
|
||||
element.set_inner_text("Pause");
|
||||
} else {
|
||||
let _ = audio.suspend();
|
||||
element.set_inner_text("Play");
|
||||
}
|
||||
});
|
||||
el.add_event_listener_with_callback("click", &Function::from(f.into_js_value()))
|
||||
.expect_throw("Failed to setup pause callback");
|
||||
}
|
||||
{
|
||||
let nes = nes.clone();
|
||||
let poisoned = poisoned.clone();
|
||||
let element = HtmlInputElement::from(JsValue::from(doc.get_element_by_id("file").unwrap()));
|
||||
element.set_accept(".nes");
|
||||
let load: Closure<dyn FnMut(JsValue)> = Closure::new(move |v| {
|
||||
let v = Uint8Array::from(v);
|
||||
// info!("{v:?}");
|
||||
let new_emu = NES::load_nes_file_mem(&v.to_vec()).expect("Failed to load nes file");
|
||||
if poisoned.swap(true, Ordering::AcqRel) == false {
|
||||
let mut nes = nes.lock().unwrap();
|
||||
*nes = new_emu;
|
||||
nes.power_cycle();
|
||||
if poisoned.swap(false, Ordering::AcqRel) == true {}
|
||||
}
|
||||
});
|
||||
let f: Closure<dyn Fn(JsValue)> = Closure::new(move |_e| {
|
||||
if let Some(file) = element.files().and_then(|l| l.get(0)) {
|
||||
let _ = file.bytes().then(&load);
|
||||
}
|
||||
});
|
||||
doc.get_element_by_id("load")
|
||||
.unwrap()
|
||||
.add_event_listener_with_callback("click", &Function::from(f.into_js_value()))
|
||||
.expect_throw("Failed to setup pause callback");
|
||||
}
|
||||
{
|
||||
let nes = nes.clone();
|
||||
let poisoned = poisoned.clone();
|
||||
let f: Closure<dyn Fn(JsValue)> = Closure::new(move |e| {
|
||||
if poisoned.swap(true, Ordering::AcqRel) == false {
|
||||
let e = KeyboardEvent::from(e);
|
||||
let key = e.key();
|
||||
// info!("{}", key);
|
||||
if key == "ArrowLeft" {
|
||||
nes.lock().unwrap().controller_1().set_left(true);
|
||||
} else if key == "ArrowRight" {
|
||||
nes.lock().unwrap().controller_1().set_right(true);
|
||||
} else if key == "ArrowUp" {
|
||||
nes.lock().unwrap().controller_1().set_up(true);
|
||||
} else if key == "ArrowDown" {
|
||||
nes.lock().unwrap().controller_1().set_down(true);
|
||||
} else if key == "a" || key == "A" {
|
||||
nes.lock().unwrap().controller_1().set_a(true);
|
||||
} else if key == "s" || key == "S" {
|
||||
nes.lock().unwrap().controller_1().set_b(true);
|
||||
} else if key == "q" || key == "Q" {
|
||||
nes.lock().unwrap().controller_1().set_start(true);
|
||||
} else if key == "w" || key == "W" {
|
||||
nes.lock().unwrap().controller_1().set_select(true);
|
||||
}
|
||||
if poisoned.swap(false, Ordering::AcqRel) == true {}
|
||||
}
|
||||
});
|
||||
win.set_onkeydown(Some(&Function::from(f.into_js_value())));
|
||||
}
|
||||
{
|
||||
let nes = nes.clone();
|
||||
let poisoned = poisoned.clone();
|
||||
let f: Closure<dyn Fn(JsValue)> = Closure::new(move |e| {
|
||||
if poisoned.swap(true, Ordering::AcqRel) == false {
|
||||
let e = KeyboardEvent::from(e);
|
||||
let key = e.key();
|
||||
// info!("{}", key);
|
||||
if key == "ArrowLeft" {
|
||||
nes.lock().unwrap().controller_1().set_left(false);
|
||||
} else if key == "ArrowRight" {
|
||||
nes.lock().unwrap().controller_1().set_right(false);
|
||||
} else if key == "ArrowUp" {
|
||||
nes.lock().unwrap().controller_1().set_up(false);
|
||||
} else if key == "ArrowDown" {
|
||||
nes.lock().unwrap().controller_1().set_down(false);
|
||||
} else if key == "a" || key == "A" {
|
||||
nes.lock().unwrap().controller_1().set_a(false);
|
||||
} else if key == "s" || key == "S" {
|
||||
nes.lock().unwrap().controller_1().set_b(false);
|
||||
} else if key == "q" || key == "Q" {
|
||||
nes.lock().unwrap().controller_1().set_start(false);
|
||||
} else if key == "w" || key == "W" {
|
||||
nes.lock().unwrap().controller_1().set_select(false);
|
||||
}
|
||||
if poisoned.swap(false, Ordering::AcqRel) == true {}
|
||||
}
|
||||
});
|
||||
win.set_onkeyup(Some(&Function::from(f.into_js_value())));
|
||||
}
|
||||
|
||||
// let timeline = win.document().unwrap().timeline();
|
||||
let mut last_frame = audio.current_time();
|
||||
let mut raw_audio_buffer = [0f32; BUFFER_SIZE];
|
||||
let mut cur_pos = 0;
|
||||
let mut cur_total = BUFFER_SIZE; // Start time in samples, set to buffer size...
|
||||
let period = Arc::new(Mutex::new(None as Option<Function>));
|
||||
let period_cl = period.clone();
|
||||
let periodic: Closure<dyn FnMut(JsValue)> = Closure::new(move |_v| {
|
||||
let win = window().expect_throw("Window is undefined");
|
||||
const FRAME_TIME: f64 = 1. / 60.;
|
||||
let mut cancel = false;
|
||||
if last_frame + FRAME_TIME > audio.current_time() {
|
||||
last_frame += FRAME_TIME;
|
||||
if audio.state() == AudioContextState::Running {
|
||||
if poisoned.swap(true, Ordering::AcqRel) {
|
||||
cancel = true;
|
||||
// if let Some(_id) = timeout_cpy.lock().ok().and_then(|v| *v) {
|
||||
// win.clear_interval_with_handle(id);
|
||||
// win.cancel_animation_frame(id);
|
||||
info!("Cleared interval due to poison");
|
||||
// } else {
|
||||
// info!("Not yet set id");
|
||||
// }
|
||||
} else {
|
||||
let mut n = nes.lock().unwrap();
|
||||
// info!("Running frame");
|
||||
let mut count = 0;
|
||||
while !n.halted() && !n.run_one_clock_cycle(&Break::default()).ppu_frame {
|
||||
count += 1;
|
||||
if count > 90_000 {
|
||||
info!("Loop overran");
|
||||
break;
|
||||
}
|
||||
}
|
||||
info!("New samples: {}", n.apu().get_frame_samples().len());
|
||||
// for s in n.apu().get_frame_samples() {
|
||||
// // raw_audio_buffer[cur_pos] = ((*s as f32) - 127.5) / 128.;
|
||||
// raw_audio_buffer[cur_pos] = (*s as f32) / 256.;
|
||||
// cur_pos += 1;
|
||||
// if cur_pos == BUFFER_SIZE {
|
||||
// cur_pos = 0;
|
||||
// cur_total += BUFFER_SIZE;
|
||||
// let audio_buffer = AudioBuffer::new(&audio_buffer_opts)
|
||||
// .expect_throw("Failed to create audio buffer");
|
||||
// audio_buffer
|
||||
// .copy_to_channel(&raw_audio_buffer, 0)
|
||||
// .expect_throw("Failed to copy audio data");
|
||||
// // info!("S: {:?}", audio_buffer.get_channel_data(0));
|
||||
|
||||
// let source_node = audio
|
||||
// .create_buffer_source()
|
||||
// .expect_throw("Failed to create buffer source");
|
||||
// source_node.set_loop(true);
|
||||
// source_node.set_buffer(Some(&audio_buffer));
|
||||
// source_node
|
||||
// .connect_with_audio_node(&gain_node)
|
||||
// .expect_throw("Failed to connect to gain node");
|
||||
// source_node
|
||||
// .start_with_when((cur_total as f64) / SAMPLE_RATE)
|
||||
// .expect_throw("Failed to start source_node");
|
||||
// info!(
|
||||
// "At {cur_total} samples, {:02.04}s ahead",
|
||||
// (cur_total as f64) / SAMPLE_RATE - audio.current_time()
|
||||
// );
|
||||
// // TODO: create and play audio node
|
||||
// }
|
||||
// }
|
||||
n.apu_mut().reset_frame_samples(); // Discard audio samples
|
||||
// info!("Completed Frame in {} cycles", count);
|
||||
let data = Clamped(n.ppu().render_buffer.raw_image());
|
||||
// info!("Creating bitmap");
|
||||
let image = ImageData::new_with_u8_clamped_array(data, 256)
|
||||
.expect_throw("Image data could not be created");
|
||||
|
||||
let data = win
|
||||
.create_image_bitmap_with_image_data(&image)
|
||||
.expect_throw("Bitmap could not be created");
|
||||
|
||||
let _ = data.then(&cl);
|
||||
drop(n);
|
||||
if !poisoned.swap(false, Ordering::AcqRel) {
|
||||
panic!("Poisoned logic invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !cancel {
|
||||
win.request_animation_frame(period_cl.lock().unwrap().as_ref().unwrap())
|
||||
.expect_throw("Failed to setup animation frame");
|
||||
}
|
||||
});
|
||||
let js = Function::from(periodic.into_js_value());
|
||||
let win = window().expect_throw("Window is undefined");
|
||||
// let timeout_id = win
|
||||
// .set_interval_with_callback_and_timeout_and_arguments_0(&Function::from(js), 16)
|
||||
// .expect_throw("Failed to setup timeout");
|
||||
// let timeout_id =
|
||||
win
|
||||
.request_animation_frame(&js)
|
||||
.expect_throw("Failed to setup animation frame");
|
||||
*period.lock().unwrap() = Some(js);
|
||||
// *timeout.lock().unwrap() = Some(timeout_id);
|
||||
}
|
||||
36
src/debug.rs
36
src/debug.rs
@@ -2,6 +2,7 @@ use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DebugLog {
|
||||
enabled: bool,
|
||||
current: String,
|
||||
history: Vec<String>,
|
||||
max_history: Option<NonZeroUsize>,
|
||||
@@ -11,6 +12,7 @@ pub struct DebugLog {
|
||||
impl DebugLog {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
current: String::new(),
|
||||
history: vec![],
|
||||
max_history: None,
|
||||
@@ -47,25 +49,41 @@ impl DebugLog {
|
||||
pub fn pop(&mut self) -> Option<String> {
|
||||
self.history.pop()
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
self.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Write for DebugLog {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
let tmp = self.current.write_str(s);
|
||||
self.rotate();
|
||||
tmp
|
||||
if self.enabled {
|
||||
let tmp = self.current.write_str(s);
|
||||
self.rotate();
|
||||
tmp
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_char(&mut self, c: char) -> std::fmt::Result {
|
||||
let tmp = self.current.write_char(c);
|
||||
self.rotate();
|
||||
tmp
|
||||
if self.enabled {
|
||||
let tmp = self.current.write_char(c);
|
||||
self.rotate();
|
||||
tmp
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::fmt::Result {
|
||||
let tmp = self.current.write_fmt(args);
|
||||
self.rotate();
|
||||
tmp
|
||||
if self.enabled {
|
||||
let tmp = self.current.write_fmt(args);
|
||||
self.rotate();
|
||||
tmp
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
src/lib.rs
28
src/lib.rs
@@ -2,18 +2,19 @@ mod apu;
|
||||
mod controllers;
|
||||
mod cpu;
|
||||
pub mod debug;
|
||||
pub mod debugger;
|
||||
pub mod header_menu;
|
||||
pub mod hex_view;
|
||||
// pub mod debugger;
|
||||
// pub mod header_menu;
|
||||
// pub mod hex_view;
|
||||
mod mem;
|
||||
mod ppu;
|
||||
pub mod resize_watcher;
|
||||
#[cfg(test)]
|
||||
mod test_roms;
|
||||
pub mod audio;
|
||||
pub use mem::{Memory, Mapped};
|
||||
|
||||
pub use ppu::{Color, PPU, RenderBuffer};
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
pub use ppu::{Color, PPU, RenderBuffer, PPUMMRegisters};
|
||||
pub use cpu::{Cpu, CycleResult, CPUMMRegisters};
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// use tokio::io::AsyncReadExt as _;
|
||||
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
@@ -22,9 +23,9 @@ use thiserror::Error;
|
||||
use crate::{
|
||||
apu::APU,
|
||||
controllers::{ControllerState, Controllers},
|
||||
cpu::{CPUMMRegisters, ClockState, Cpu, CycleResult, DmaState},
|
||||
cpu::{ClockState, DmaState},
|
||||
debug::DebugLog,
|
||||
mem::{CpuMem, Mapped, PpuMem},
|
||||
mem::{CpuMem, PpuMem},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -115,13 +116,18 @@ impl std::fmt::Debug for NES {
|
||||
}
|
||||
|
||||
impl NES {
|
||||
#[cfg(feature = "fs")]
|
||||
pub fn load_nes_file(file: impl AsRef<Path>) -> Result<Self> {
|
||||
let mut raw = Vec::new();
|
||||
File::open(file)?.read_to_end(&mut raw)?;
|
||||
Self::load_nes_file_mem(&raw)
|
||||
}
|
||||
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "fs", feature = "tokio"))]
|
||||
pub async fn async_load_nes_file(file: impl AsRef<Path>) -> Result<Self> {
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
|
||||
let mut raw = Vec::new();
|
||||
tokio::fs::File::open(file)
|
||||
.await?
|
||||
@@ -342,6 +348,10 @@ impl NES {
|
||||
&self.ppu.render_buffer
|
||||
}
|
||||
|
||||
pub fn cpu(&self) -> &Cpu {
|
||||
&self.cpu
|
||||
}
|
||||
|
||||
pub fn ppu(&self) -> &PPU {
|
||||
&self.ppu
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
use std::{fmt, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState, hex_view::Memory,
|
||||
CPUMMRegisters, PPU, apu::APU, controllers::Controllers, cpu::DmaState,
|
||||
ppu::PPUMMRegisters,
|
||||
};
|
||||
|
||||
pub trait Memory {
|
||||
fn peek(&self, val: usize) -> Option<u8>;
|
||||
fn len(&self) -> usize;
|
||||
fn edit_ver(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value<R> {
|
||||
Value(u8),
|
||||
|
||||
497
src/ppu.rs
497
src/ppu.rs
@@ -1,11 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use iced::{
|
||||
Point, Size,
|
||||
advanced::graphics::geometry::Renderer,
|
||||
widget::canvas::{Fill, Frame},
|
||||
};
|
||||
|
||||
use crate::mem::{Mapped, PpuMem, Value};
|
||||
|
||||
@@ -16,12 +11,6 @@ pub struct Color {
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Into<Fill> for Color {
|
||||
fn into(self) -> Fill {
|
||||
iced::Color::from_rgb8(self.r, self.g, self.b).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
|
||||
@@ -66,6 +55,10 @@ impl<const W: usize, const H: usize> RenderBuffer<W, H> {
|
||||
Bytes::copy_from_slice(&self.raw_rgba)
|
||||
}
|
||||
|
||||
pub fn raw_image(&self) -> &[u8] {
|
||||
&self.raw_rgba
|
||||
}
|
||||
|
||||
pub fn assert_eq(&self, other: &Self) {
|
||||
// if self.buffer != other.buffer {
|
||||
for y in 0..H {
|
||||
@@ -1126,238 +1119,6 @@ impl PPU {
|
||||
pub fn oam_edit_ver(&self) -> usize {
|
||||
self.oam.edit_ver
|
||||
}
|
||||
|
||||
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||
for y in 0..60 {
|
||||
for x in 0..64 {
|
||||
let row = y % 30;
|
||||
let col = x % 32;
|
||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let attr = mem
|
||||
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
|
||||
// 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 = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => self.palette.color(0),
|
||||
(true, false) => self.palette.color(1 + 4 * palette),
|
||||
(false, true) => self.palette.color(2 + 4 * palette),
|
||||
(true, true) => self.palette.color(3 + 4 * palette),
|
||||
// (false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
// (true, false) => Color {
|
||||
// r: 64,
|
||||
// g: 64,
|
||||
// b: 64,
|
||||
// },
|
||||
// (false, true) => Color {
|
||||
// r: 128,
|
||||
// g: 128,
|
||||
// b: 128,
|
||||
// },
|
||||
// (true, true) => Color {
|
||||
// r: 255,
|
||||
// g: 255,
|
||||
// b: 255,
|
||||
// },
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// for
|
||||
// let pat = mem.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 64 && y < 60 {
|
||||
let row = y % 30;
|
||||
let col = x % 32;
|
||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
|
||||
let attr = mem
|
||||
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
Some(format!(
|
||||
"Row, Column: {}, {}
|
||||
X, Y: {}, {}
|
||||
Tilemap address: ${:04X}
|
||||
|
||||
Tile Index: ${:02X}
|
||||
Tile Address (PPU): ${:04X}
|
||||
Tile Address (CHR): ${:04X}
|
||||
|
||||
Palette Index {}
|
||||
Palette Address ${:04X}
|
||||
|
||||
Attribute Address ${:04X}
|
||||
Attribute data: ${:02X}
|
||||
",
|
||||
row,
|
||||
col,
|
||||
col * 8,
|
||||
row * 8,
|
||||
off + col + row * 32,
|
||||
name,
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
(attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
|
||||
((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
|
||||
col / 4 + row / 4 * 8 + 0x3C0 + off,
|
||||
attr,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
let name = (y * 16 + x) * 16;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
(true, false) => Color {
|
||||
r: 64,
|
||||
g: 64,
|
||||
b: 64,
|
||||
},
|
||||
(false, true) => Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
},
|
||||
(true, true) => Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
let name = (y * 16 + x) * 16;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32 + 130.,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
(true, false) => Color {
|
||||
r: 64,
|
||||
g: 64,
|
||||
b: 64,
|
||||
},
|
||||
(false, true) => Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
},
|
||||
(true, true) => Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// for
|
||||
// let pat = mem.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 16 && y < 32 {
|
||||
Some(format!(
|
||||
"Tile address (PPU): {:04X}\nTile address (CHR): {:04X}\nIndex: {:02X}",
|
||||
(y * 16 + x) * 16,
|
||||
(y * 16 + x) * 16,
|
||||
((y % 16) * 16 + x),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
|
||||
for y in 0..8 {
|
||||
for x in 0..4 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(x as f32 * 10., y as f32 * 10.),
|
||||
Size::new(10., 10.),
|
||||
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 10.) as usize;
|
||||
let y = (cursor.y / 10.) as usize;
|
||||
if x < 4 && y < 8 {
|
||||
Some(format!(
|
||||
"Index: {:02X}\nValue: {:02X}\nColor code: {}",
|
||||
x + y * 4,
|
||||
self.palette.ram[x + y * 4] & 0x3F,
|
||||
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1414,3 +1175,253 @@ mod tests {
|
||||
assert_eq!(ppu.background.w, false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "iced")]
|
||||
mod ppu_iced {
|
||||
use super::*;
|
||||
use iced::{
|
||||
Point, Size,
|
||||
advanced::graphics::geometry::Renderer,
|
||||
widget::canvas::{Fill, Frame},
|
||||
};
|
||||
|
||||
impl Into<Fill> for Color {
|
||||
fn into(self) -> Fill {
|
||||
iced::Color::from_rgb8(self.r, self.g, self.b).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl PPU {
|
||||
pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||
for y in 0..60 {
|
||||
for x in 0..64 {
|
||||
let row = y % 30;
|
||||
let col = x % 32;
|
||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let attr = mem
|
||||
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
// attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
|
||||
// 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 = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => self.palette.color(0),
|
||||
(true, false) => self.palette.color(1 + 4 * palette),
|
||||
(false, true) => self.palette.color(2 + 4 * palette),
|
||||
(true, true) => self.palette.color(3 + 4 * palette),
|
||||
// (false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
// (true, false) => Color {
|
||||
// r: 64,
|
||||
// g: 64,
|
||||
// b: 64,
|
||||
// },
|
||||
// (false, true) => Color {
|
||||
// r: 128,
|
||||
// g: 128,
|
||||
// b: 128,
|
||||
// },
|
||||
// (true, true) => Color {
|
||||
// r: 255,
|
||||
// g: 255,
|
||||
// b: 255,
|
||||
// },
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// for
|
||||
// let pat = mem.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 64 && y < 60 {
|
||||
let row = y % 30;
|
||||
let col = x % 32;
|
||||
let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
|
||||
let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
|
||||
let attr = mem
|
||||
.peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
|
||||
.unwrap();
|
||||
Some(format!(
|
||||
"Row, Column: {}, {}
|
||||
X, Y: {}, {}
|
||||
Tilemap address: ${:04X}
|
||||
|
||||
Tile Index: ${:02X}
|
||||
Tile Address (PPU): ${:04X}
|
||||
Tile Address (CHR): ${:04X}
|
||||
|
||||
Palette Index {}
|
||||
Palette Address ${:04X}
|
||||
|
||||
Attribute Address ${:04X}
|
||||
Attribute data: ${:02X}
|
||||
",
|
||||
row,
|
||||
col,
|
||||
col * 8,
|
||||
row * 8,
|
||||
off + col + row * 32,
|
||||
name,
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
name * 16
|
||||
+ if self.background.second_pattern {
|
||||
0x1000
|
||||
} else {
|
||||
0
|
||||
},
|
||||
(attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
|
||||
((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
|
||||
col / 4 + row / 4 * 8 + 0x3C0 + off,
|
||||
attr,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
let name = (y * 16 + x) * 16;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
(true, false) => Color {
|
||||
r: 64,
|
||||
g: 64,
|
||||
b: 64,
|
||||
},
|
||||
(false, true) => Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
},
|
||||
(true, true) => Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
let name = (y * 16 + x) * 16;
|
||||
for y_off in 0..8 {
|
||||
let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
|
||||
let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
|
||||
for bit in 0..8 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(
|
||||
x as f32 * 8. + 8. - bit as f32,
|
||||
y as f32 * 8. + y_off as f32 + 130.,
|
||||
),
|
||||
Size::new(1., 1.),
|
||||
match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
|
||||
(false, false) => Color { r: 0, g: 0, b: 0 },
|
||||
(true, false) => Color {
|
||||
r: 64,
|
||||
g: 64,
|
||||
b: 64,
|
||||
},
|
||||
(false, true) => Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
},
|
||||
(true, true) => Color {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// for
|
||||
// let pat = mem.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 8.) as usize;
|
||||
let y = (cursor.y / 8.) as usize;
|
||||
if x < 16 && y < 32 {
|
||||
Some(format!(
|
||||
"Tile address (PPU): {:04X}\nTile address (CHR): {:04X}\nIndex: {:02X}",
|
||||
(y * 16 + x) * 16,
|
||||
(y * 16 + x) * 16,
|
||||
((y % 16) * 16 + x),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
|
||||
for y in 0..8 {
|
||||
for x in 0..4 {
|
||||
frame.fill_rectangle(
|
||||
Point::new(x as f32 * 10., y as f32 * 10.),
|
||||
Size::new(10., 10.),
|
||||
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
|
||||
let x = (cursor.x / 10.) as usize;
|
||||
let y = (cursor.y / 10.) as usize;
|
||||
if x < 4 && y < 8 {
|
||||
Some(format!(
|
||||
"Index: {:02X}\nValue: {:02X}\nColor code: {}",
|
||||
x + y * 4,
|
||||
self.palette.ram[x + y * 4] & 0x3F,
|
||||
self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
.include "audio_inp.s"
|
||||
|
||||
zp_res TIMER_LOW
|
||||
; zp_res TIMER_LOW
|
||||
; zp_res TIMER_LOW
|
||||
zp_res SWEEP
|
||||
zp_res DLCV
|
||||
; zp_res TIMER_LOW
|
||||
|
||||
reset:
|
||||
@@ -14,10 +14,12 @@ reset:
|
||||
|
||||
lda #$01
|
||||
sta SNDCHN
|
||||
lda #$AF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||
lda #$BF ; Duty 2, LC halted, evelope enabled, volume = F
|
||||
sta PULSE_CH1_DLCV
|
||||
sta DLCV
|
||||
lda #$7F ;
|
||||
sta PULSE_CH1_SWEEP
|
||||
sta SWEEP
|
||||
lda #$6F
|
||||
sta PULSE_CH1_TLOW
|
||||
sta TIMER_LOW
|
||||
@@ -43,21 +45,32 @@ reset:
|
||||
write_char_x ':'
|
||||
|
||||
load_ppu_addr $2061
|
||||
; write_char_x 't'
|
||||
; write_char_x 'i'
|
||||
; write_char_x 'm'
|
||||
; write_char_x 'e'
|
||||
; write_char_x 'r'
|
||||
; write_char_x ' '
|
||||
; write_char_x 'h'
|
||||
; write_char_x 'o'
|
||||
; write_char_x 'w'
|
||||
; write_char_x ':'
|
||||
write_char_x 's'
|
||||
write_char_x 'w'
|
||||
write_char_x 'e'
|
||||
write_char_x 'e'
|
||||
write_char_x 'p'
|
||||
write_char_x ':'
|
||||
|
||||
load_ppu_addr $2081
|
||||
write_char_x 'd'
|
||||
write_char_x 'l'
|
||||
write_char_x 'c'
|
||||
write_char_x 'v'
|
||||
write_char_x ':'
|
||||
|
||||
load_ppu_addr $204C
|
||||
lda TIMER_LOW
|
||||
jsr write_hex_a
|
||||
|
||||
load_ppu_addr $206C
|
||||
lda SWEEP
|
||||
jsr write_hex_a
|
||||
|
||||
load_ppu_addr $208C
|
||||
lda DLCV
|
||||
jsr write_hex_a
|
||||
|
||||
jsr enable_rendering
|
||||
loop:
|
||||
jmp loop
|
||||
@@ -67,28 +80,83 @@ loop:
|
||||
audio_nmi:
|
||||
EOR JOY_PREV
|
||||
STA JOY_PREV
|
||||
|
||||
lda JOYTEMP
|
||||
and #JOY_UP_MASK
|
||||
and JOY_PREV
|
||||
beq next_1
|
||||
beq down
|
||||
load_ppu_addr $204C
|
||||
lda #$01
|
||||
adc TIMER_LOW
|
||||
sta TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
jsr write_hex_a
|
||||
next_1:
|
||||
|
||||
down:
|
||||
lda JOYTEMP
|
||||
and #JOY_DOWN_MASK
|
||||
and JOY_PREV
|
||||
beq next_2
|
||||
beq left
|
||||
load_ppu_addr $204C
|
||||
lda #$FF
|
||||
adc TIMER_LOW
|
||||
sta TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
jsr write_hex_a
|
||||
next_2:
|
||||
|
||||
left:
|
||||
lda JOYTEMP
|
||||
and #JOY_LEFT_MASK
|
||||
and JOY_PREV
|
||||
beq right
|
||||
load_ppu_addr $206C
|
||||
lda #$FF
|
||||
adc SWEEP
|
||||
sta SWEEP
|
||||
sta PULSE_CH1_SWEEP
|
||||
jsr write_hex_a
|
||||
|
||||
right:
|
||||
lda JOYTEMP
|
||||
and #JOY_RIGHT_MASK
|
||||
and JOY_PREV
|
||||
beq joy_a
|
||||
load_ppu_addr $206C
|
||||
lda #$01
|
||||
adc SWEEP
|
||||
sta SWEEP
|
||||
sta PULSE_CH1_SWEEP
|
||||
jsr write_hex_a
|
||||
|
||||
joy_a:
|
||||
lda JOYTEMP
|
||||
and #JOY_A_MASK
|
||||
and JOY_PREV
|
||||
beq joy_b
|
||||
load_ppu_addr $208C
|
||||
lda DLCV
|
||||
eor #$10
|
||||
sta DLCV
|
||||
sta PULSE_CH1_DLCV
|
||||
jsr write_hex_a
|
||||
; sta PULSE_CH1_DLCV
|
||||
lda #$F0
|
||||
sta PULSE_CH1_LCTH
|
||||
|
||||
joy_b:
|
||||
lda JOYTEMP
|
||||
and #JOY_B_MASK
|
||||
and JOY_PREV
|
||||
beq done
|
||||
load_ppu_addr $204C
|
||||
lda #$00
|
||||
; adc TIMER_LOW
|
||||
; sta TIMER_LOW
|
||||
sta PULSE_CH1_TLOW
|
||||
; jsr write_hex_a
|
||||
done:
|
||||
|
||||
|
||||
lda JOYTEMP
|
||||
STA JOY_PREV
|
||||
rts
|
||||
|
||||
6
wasm.fish
Executable file
6
wasm.fish
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/fish
|
||||
|
||||
# RUSTFLAGS=--cfg=web_sys_unstable_apis
|
||||
cargo build --target wasm32-unknown-unknown --bin wasm --features web --release || exit
|
||||
wasm-bindgen target/wasm32-unknown-unknown/release/wasm.wasm --out-dir wasm --web || exit
|
||||
simple-http-server wasm || exit
|
||||
41
wasm/wasm.d.ts
vendored
Normal file
41
wasm/wasm.d.ts
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly main: (a: number, b: number) => number;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h30acef6f6c0e425e: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__closure__destroy__h88746cad1b24036a: (a: number, b: number) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__hbefc371369f44e63: (a: number, b: number, c: any) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_externrefs: WebAssembly.Table;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
47
wasm/wasm.html
Normal file
47
wasm/wasm.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Wasm test</title>
|
||||
<style>
|
||||
canvas {
|
||||
width: 512px;
|
||||
height: 480px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
gap: 2em;
|
||||
}
|
||||
.col {
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>NES Emulator</h1>
|
||||
<div class="flex">
|
||||
<canvas id="main_view" width="256px" height="240px"></canvas>
|
||||
<div>
|
||||
<h4>Controls:</h4>
|
||||
<p>D-Pad: Arrow keys</p>
|
||||
<p>A: A key</p>
|
||||
<p>B: S key</p>
|
||||
<p>Start: Q key</p>
|
||||
<p>Select: W key</p>
|
||||
<button id="pause">Play</button>
|
||||
</div>
|
||||
<div class="flex col">
|
||||
<h4>Load new ROM:</h4>
|
||||
<input id="file" type="file" />
|
||||
<button id="load">Load ROM</button>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module">
|
||||
import init from "./wasm.js";
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
649
wasm/wasm.js
Normal file
649
wasm/wasm.js
Normal file
@@ -0,0 +1,649 @@
|
||||
const lAudioContext = (typeof AudioContext !== 'undefined' ? AudioContext : (typeof webkitAudioContext !== 'undefined' ? webkitAudioContext : undefined));
|
||||
let wasm;
|
||||
|
||||
function addToExternrefTable0(obj) {
|
||||
const idx = wasm.__externref_table_alloc();
|
||||
wasm.__wbindgen_externrefs.set(idx, obj);
|
||||
return idx;
|
||||
}
|
||||
|
||||
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => state.dtor(state.a, state.b));
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
if (type == 'number' || type == 'boolean' || val == null) {
|
||||
return `${val}`;
|
||||
}
|
||||
if (type == 'string') {
|
||||
return `"${val}"`;
|
||||
}
|
||||
if (type == 'symbol') {
|
||||
const description = val.description;
|
||||
if (description == null) {
|
||||
return 'Symbol';
|
||||
} else {
|
||||
return `Symbol(${description})`;
|
||||
}
|
||||
}
|
||||
if (type == 'function') {
|
||||
const name = val.name;
|
||||
if (typeof name == 'string' && name.length > 0) {
|
||||
return `Function(${name})`;
|
||||
} else {
|
||||
return 'Function';
|
||||
}
|
||||
}
|
||||
// objects
|
||||
if (Array.isArray(val)) {
|
||||
const length = val.length;
|
||||
let debug = '[';
|
||||
if (length > 0) {
|
||||
debug += debugString(val[0]);
|
||||
}
|
||||
for(let i = 1; i < length; i++) {
|
||||
debug += ', ' + debugString(val[i]);
|
||||
}
|
||||
debug += ']';
|
||||
return debug;
|
||||
}
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches && builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
return toString.call(val);
|
||||
}
|
||||
if (className == 'Object') {
|
||||
// we're a user defined class or Object
|
||||
// JSON.stringify avoids problems with cycles, and is generally much
|
||||
// easier than looping through ownProperties of `val`.
|
||||
try {
|
||||
return 'Object(' + JSON.stringify(val) + ')';
|
||||
} catch (_) {
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
// errors
|
||||
if (val instanceof Error) {
|
||||
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||
}
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
function getArrayF32FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
function getClampedArrayU8FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getUint8ClampedArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
|
||||
let cachedFloat32ArrayMemory0 = null;
|
||||
function getFloat32ArrayMemory0() {
|
||||
if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) {
|
||||
cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedFloat32ArrayMemory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return decodeText(ptr, len);
|
||||
}
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
let cachedUint8ClampedArrayMemory0 = null;
|
||||
function getUint8ClampedArrayMemory0() {
|
||||
if (cachedUint8ClampedArrayMemory0 === null || cachedUint8ClampedArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ClampedArrayMemory0 = new Uint8ClampedArray(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ClampedArrayMemory0;
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
const idx = addToExternrefTable0(e);
|
||||
wasm.__wbindgen_exn_store(idx);
|
||||
}
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
function makeClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
try {
|
||||
return f(state.a, state.b, ...args);
|
||||
} finally {
|
||||
real._wbg_cb_unref();
|
||||
}
|
||||
};
|
||||
real._wbg_cb_unref = () => {
|
||||
if (--state.cnt === 0) {
|
||||
state.dtor(state.a, state.b);
|
||||
state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
};
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
state.a = a;
|
||||
real._wbg_cb_unref();
|
||||
}
|
||||
};
|
||||
real._wbg_cb_unref = () => {
|
||||
if (--state.cnt === 0) {
|
||||
state.dtor(state.a, state.b);
|
||||
state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
};
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8ArrayMemory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = cachedTextEncoder.encodeInto(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
||||
let numBytesDecoded = 0;
|
||||
function decodeText(ptr, len) {
|
||||
numBytesDecoded += len;
|
||||
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
||||
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
numBytesDecoded = len;
|
||||
}
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
const cachedTextEncoder = new TextEncoder();
|
||||
|
||||
if (!('encodeInto' in cachedTextEncoder)) {
|
||||
cachedTextEncoder.encodeInto = function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__h30acef6f6c0e425e(arg0, arg1, arg2) {
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__h30acef6f6c0e425e(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__hbefc371369f44e63(arg0, arg1, arg2) {
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__hbefc371369f44e63(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
const __wbindgen_enum_AudioContextState = ["suspended", "running", "closed"];
|
||||
|
||||
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
} catch (e) {
|
||||
const validResponse = module.ok && EXPECTED_RESPONSE_TYPES.has(module.type);
|
||||
|
||||
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __wbg_get_imports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbg___wbindgen_debug_string_adfb662ae34724b6 = function(arg0, arg1) {
|
||||
const ret = debugString(arg1);
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg___wbindgen_is_undefined_f6b95eab589e0269 = function(arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg___wbindgen_throw_dd24417ed36fc46e = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg__wbg_cb_unref_87dfb5aaa0cbcea7 = function(arg0) {
|
||||
arg0._wbg_cb_unref();
|
||||
};
|
||||
imports.wbg.__wbg_addEventListener_6a82629b3d430a48 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
arg0.addEventListener(getStringFromWasm0(arg1, arg2), arg3);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_bytes_2820db0ab474c07d = function(arg0) {
|
||||
const ret = arg0.bytes();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_call_abb4ff46ce38be40 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_connect_9a58a232bfff28c8 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.connect(arg1, arg2 >>> 0);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_connect_f28a2db518e02462 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.connect(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_copyToChannel_30d90303302ec449 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
arg0.copyToChannel(getArrayF32FromWasm0(arg1, arg2), arg3);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_createBufferSource_156317138619c7b0 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.createBufferSource();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_createDelay_6439a64d02500fa1 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.createDelay();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_createGain_d5704df14f1e271f = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.createGain();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_createImageBitmap_50df7cebf5a3d827 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.createImageBitmap(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_currentTime_7d6df02e29507923 = function(arg0) {
|
||||
const ret = arg0.currentTime;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_debug_9d0c87ddda3dc485 = function(arg0) {
|
||||
console.debug(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_delayTime_56315e7f0ef53379 = function(arg0) {
|
||||
const ret = arg0.delayTime;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_destination_1dd37feba0c0cab6 = function(arg0) {
|
||||
const ret = arg0.destination;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_document_5b745e82ba551ca5 = function(arg0) {
|
||||
const ret = arg0.document;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_drawImage_44577ef8eab8c151 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
arg0.drawImage(arg1, arg2, arg3);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) {
|
||||
let deferred0_0;
|
||||
let deferred0_1;
|
||||
try {
|
||||
deferred0_0 = arg0;
|
||||
deferred0_1 = arg1;
|
||||
console.error(getStringFromWasm0(arg0, arg1));
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_error_7bc7d576a6aaf855 = function(arg0) {
|
||||
console.error(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_files_aa1f009258eadae6 = function(arg0) {
|
||||
const ret = arg0.files;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_gain_435505e65fb96146 = function(arg0) {
|
||||
const ret = arg0.gain;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getContext_01f42b234e833f0a = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.getContext(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_getElementById_e05488d2143c2b21 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.getElementById(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_89bca58298277b24 = function(arg0, arg1) {
|
||||
const ret = arg0[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_info_ce6bcc489c22f6f0 = function(arg0) {
|
||||
console.info(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Window_b5cf7783caa68180 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof Window;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_key_505d33c50799526a = function(arg0, arg1) {
|
||||
const ret = arg1.key;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_length_22ac23eaec9d8053 = function(arg0) {
|
||||
const ret = arg0.length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_log_1d990106d99dacb7 = function(arg0) {
|
||||
console.log(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_new_1ba21ce319a06297 = function() {
|
||||
const ret = new Object();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_8a6f238a6ece86ea = function() {
|
||||
const ret = new Error();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_a2ec8094aa0713cb = function() { return handleError(function (arg0) {
|
||||
const ret = new AudioBuffer(arg0);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_no_args_cb138f77cf6151ee = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_with_context_options_ef5b87cd25cac8d1 = function() { return handleError(function (arg0) {
|
||||
const ret = new lAudioContext(arg0);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_with_u8_clamped_array_e14490b754099e0e = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_prototypesetcall_dfe9b766cdc1f1fd = function(arg0, arg1, arg2) {
|
||||
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
||||
};
|
||||
imports.wbg.__wbg_requestAnimationFrame_994dc4ebde22b8d9 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = arg0.requestAnimationFrame(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_reset_6985caa8186d4f54 = function(arg0) {
|
||||
arg0.reset();
|
||||
};
|
||||
imports.wbg.__wbg_resume_7d320c513be492d2 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.resume();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_set_accept_4af7309453c16421 = function(arg0, arg1, arg2) {
|
||||
arg0.accept = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_set_buffer_25c70ce663d1667c = function(arg0, arg1) {
|
||||
arg0.buffer = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_set_channelCount_4b7d6cec989c8ae3 = function(arg0, arg1) {
|
||||
arg0.channelCount = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_set_innerText_b0abb40240106cb9 = function(arg0, arg1, arg2) {
|
||||
arg0.innerText = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_set_length_957d490e6b543adf = function(arg0, arg1) {
|
||||
arg0.length = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_set_loop_8096808d7e93a3af = function(arg0, arg1) {
|
||||
arg0.loop = arg1 !== 0;
|
||||
};
|
||||
imports.wbg.__wbg_set_number_of_channels_f0c703fb96f63365 = function(arg0, arg1) {
|
||||
arg0.numberOfChannels = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_set_onkeydown_d2621c474b636979 = function(arg0, arg1) {
|
||||
arg0.onkeydown = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_set_onkeyup_913b36756e057c1d = function(arg0, arg1) {
|
||||
arg0.onkeyup = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_set_sample_rate_9ba31b5c895c7108 = function(arg0, arg1) {
|
||||
arg0.sampleRate = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_set_sample_rate_fc3bf6128936f9e5 = function(arg0, arg1) {
|
||||
arg0.sampleRate = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_set_value_9b29412dd286d5d4 = function(arg0, arg1) {
|
||||
arg0.value = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) {
|
||||
const ret = arg1.stack;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_start_865ed2d65a6d353e = function() { return handleError(function (arg0, arg1) {
|
||||
arg0.start(arg1);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_state_c28a610878aaf489 = function(arg0) {
|
||||
const ret = arg0.state;
|
||||
return (__wbindgen_enum_AudioContextState.indexOf(ret) + 1 || 4) - 1;
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_769e6b65d6557335 = function() {
|
||||
const ret = typeof global === 'undefined' ? null : global;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_60cf02db4de8e1c1 = function() {
|
||||
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_SELF_08f5a74c69739274 = function() {
|
||||
const ret = typeof self === 'undefined' ? null : self;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_static_accessor_WINDOW_a8924b26aa92d024 = function() {
|
||||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_suspend_e4145c9878ac544f = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.suspend();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_then_4f95312d68691235 = function(arg0, arg1) {
|
||||
const ret = arg0.then(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_warn_6e567d0d926ff881 = function(arg0) {
|
||||
console.warn(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Ref(String) -> Externref`.
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_8e402a763b70599f = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 25, function: Function { arguments: [Externref], shim_idx: 28, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h88746cad1b24036a, wasm_bindgen__convert__closures_____invoke__hbefc371369f44e63);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_b3587965ddad1edc = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 25, function: Function { arguments: [Externref], shim_idx: 26, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h88746cad1b24036a, wasm_bindgen__convert__closures_____invoke__h30acef6f6c0e425e);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
const table = wasm.__wbindgen_externrefs;
|
||||
const offset = table.grow(4);
|
||||
table.set(0, undefined);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasm = instance.exports;
|
||||
__wbg_init.__wbindgen_wasm_module = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedFloat32ArrayMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
cachedUint8ClampedArrayMemory0 = null;
|
||||
|
||||
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({module} = module)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (typeof module_or_path !== 'undefined') {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({module_or_path} = module_or_path)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module_or_path === 'undefined') {
|
||||
module_or_path = new URL('wasm_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync };
|
||||
export default __wbg_init;
|
||||
BIN
wasm/wasm_bg.wasm
Normal file
BIN
wasm/wasm_bg.wasm
Normal file
Binary file not shown.
14
wasm/wasm_bg.wasm.d.ts
vendored
Normal file
14
wasm/wasm_bg.wasm.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const main: (a: number, b: number) => number;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h30acef6f6c0e425e: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__closure__destroy__h88746cad1b24036a: (a: number, b: number) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__hbefc371369f44e63: (a: number, b: number, c: any) => void;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_externrefs: WebAssembly.Table;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
Reference in New Issue
Block a user