Split WASM and native versions, and move iced support code to native

This commit is contained in:
2026-03-27 00:27:34 -05:00
parent 3010469c8a
commit b433148843
23 changed files with 2330 additions and 1012 deletions

3
.helix/languages.toml Normal file
View File

@@ -0,0 +1,3 @@
[language-server.rust-analyzer]
config = { cargo = { features = "all" } }

1279
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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>

View File

@@ -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()
}
}
}

View File

@@ -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())

View File

@@ -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,

View File

@@ -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
View 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);
}

View File

@@ -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(())
}
}
}

View File

@@ -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
}

View File

@@ -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),

View File

@@ -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
}
}
}
}

View File

@@ -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
View 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
View 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
View 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
View 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

Binary file not shown.

14
wasm/wasm_bg.wasm.d.ts vendored Normal file
View 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;