Skip to content

Commit

Permalink
feat: snapshot (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
nomyfan authored Apr 7, 2024
1 parent fe2a55f commit d4b1b49
Show file tree
Hide file tree
Showing 39 changed files with 932 additions and 187 deletions.
34 changes: 34 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ anyhow = "1.0.81"
mockall = "0.12.1"
web-time = "1.1.0"
wasm-bindgen = "0.2.92"
bincode = "1.3.3"
serde = { version = "1.0", features = ["derive"] }

[profile.release]
# Tell `rustc` to optimize for small code size.
Expand Down
60 changes: 60 additions & 0 deletions app/gameboy/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,66 @@ function App() {
gameboy.play();
}}
/>
<button
onClick={() => {
const bytes = gameboy.takeSnapshot();
const blob = new Blob([bytes], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.style.display = "none";
a.download = "snapshot.ss";
a.click();
setTimeout(() => {
a.remove();
URL.revokeObjectURL(url);
}, 1000);
}}
>
Take snapshot
</button>
<input
type="file"
accept=".ss"
onChange={(evt) => {
const file = evt.target.files?.[0];
if (!file) {
return;
}

const reader = new FileReader();
reader.onload = () => {
const buffer = new Uint8Array(reader.result as ArrayBuffer);
try {
gameboy.restoreSnapshot(buffer);
evt.target.value = "";
} catch (err) {
if (err instanceof Error) {
const message = err.message;
const parseGameBoyError = (errorMessage: string) => {
const RE = /^\[(E\w+?\d+?)\]/;
const match = RE.exec(errorMessage);
if (match) {
const code = match[1];
const message = errorMessage.replace(RE, "");
return { code, message };
} else {
return null;
}
};
const gbError = parseGameBoyError(message);
if (gbError) {
console.error(gbError);
return;
}
}

throw err;
}
};
reader.readAsArrayBuffer(file);
}}
/>
</div>
);
}
Expand Down
10 changes: 10 additions & 0 deletions app/gameboy/src/gameboy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ class GameBoyControl {
this.keyState = state;
}
}

takeSnapshot() {
this.ensureInstalled();
return this.instance_!.takeSnapshot();
}

restoreSnapshot(snapshot: Uint8Array) {
this.ensureInstalled();
this.instance_!.restoreSnapshot(snapshot);
}
}

export { GameBoyControl, JoypadKey };
1 change: 1 addition & 0 deletions crates/apu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ edition = "2021"
gb_shared = { workspace = true }
log = { workspace = true }
blip_buf-rs = "0.1.1"
serde = { workspace = true }
4 changes: 4 additions & 0 deletions crates/apu/src/blipbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ impl BlipBuf {
self.clock_time = 0;
self.buf.read_samples(buffer, samples_avail, false) as usize
}

pub(crate) fn clear(&mut self) {
self.buf.clear();
}
}
2 changes: 2 additions & 0 deletions crates/apu/src/channel/envelope.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use gb_shared::is_bit_set;
use serde::{Deserialize, Serialize};

use super::Frame;

#[derive(Clone, Serialize, Deserialize)]
pub(super) struct Envelope {
frame: Frame,
/// Complete one iteration when it reaches zero.
Expand Down
5 changes: 4 additions & 1 deletion crates/apu/src/channel/frame_sequencer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use serde::{Deserialize, Serialize};

use crate::{clock::Clock, utils::freq_to_period};

#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct FrameSequencer {
clock: Clock,
frame: Frame,
Expand All @@ -26,7 +29,7 @@ impl FrameSequencer {
}
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub(crate) struct Frame(u8);

impl Default for Frame {
Expand Down
2 changes: 2 additions & 0 deletions crates/apu/src/channel/length_counter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use gb_shared::is_bit_set;
use serde::{Deserialize, Serialize};

use super::Frame;

#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct LengthCounter<const MAX: u16> {
pub(super) frame: Frame,
/// When the length timer reaches MAX, the channel is turned off.
Expand Down
10 changes: 10 additions & 0 deletions crates/apu/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ use pulse_channel::PulseChannel;
use sweep::Sweep;
use wave_channel::WaveChannel;

use self::{
noise_channel::NoiseChannelSnapshot, pulse_channel::PulseChannelSnapshot,
wave_channel::WaveChannelSnapshot,
};

pub(crate) type Channel1 = PulseChannel<sweep::SomeSweep>;
pub(crate) type Channel2 = PulseChannel<sweep::NoneSweep>;
pub(crate) type Channel3 = WaveChannel;
pub(crate) type Channel4 = NoiseChannel;

pub(crate) type Channel1Snapshot = PulseChannelSnapshot<sweep::SomeSweep>;
pub(crate) type Channel2Snapshot = PulseChannelSnapshot<sweep::NoneSweep>;
pub(crate) type Channel3Snapshot = WaveChannelSnapshot;
pub(crate) type Channel4Snapshot = NoiseChannelSnapshot;
70 changes: 58 additions & 12 deletions crates/apu/src/channel/noise_channel.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use gb_shared::{is_bit_set, unset_bits, Memory};
use gb_shared::{is_bit_set, unset_bits, Memory, Snapshot};
use serde::{Deserialize, Serialize};

use crate::{blipbuf, clock::Clock};

use super::{Frame, NoiseChannelLengthCounter as LengthCounter, Envelope};
use super::{Envelope, Frame, NoiseChannelLengthCounter as LengthCounter};

#[derive(Clone, Serialize, Deserialize)]
struct Lfsr {
value: u16,
clock: Clock,
Expand Down Expand Up @@ -80,7 +82,7 @@ pub(crate) struct NoiseChannel {
/// Bit7, Trigger.
/// Bit6, Length enable.
nrx4: u8,
blipbuf: blipbuf::BlipBuf,
blipbuf: Option<blipbuf::BlipBuf>,
length_counter: LengthCounter,
envelope: Envelope,
lfsr: Lfsr,
Expand All @@ -99,13 +101,14 @@ impl std::fmt::Debug for NoiseChannel {
}

impl NoiseChannel {
pub(crate) fn new(frequency: u32, sample_rate: u32) -> Self {
pub(crate) fn new(frequency: u32, sample_rate: Option<u32>) -> Self {
let nrx1 = 0;
let nrx2 = 0;
let nrx3 = 0;
let nrx4 = 0;
Self {
blipbuf: blipbuf::BlipBuf::new(frequency, sample_rate, 0),
blipbuf: sample_rate
.map(|sample_rate| blipbuf::BlipBuf::new(frequency, sample_rate, 0)),
nrx1,
nrx2,
nrx3,
Expand All @@ -125,12 +128,11 @@ impl NoiseChannel {
#[inline]
pub(crate) fn step(&mut self, frame: Option<Frame>) {
if let Some(use_volume) = self.lfsr.step(self.nrx3) {
let volume = if use_volume && (self.active()) {
self.envelope.volume() as i32
} else {
0
};
self.blipbuf.add_delta(self.lfsr.clock.div(), volume);
let volume =
if use_volume && (self.active()) { self.envelope.volume() as i32 } else { 0 };
if let Some(blipbuf) = &mut self.blipbuf {
blipbuf.add_delta(self.lfsr.clock.div(), volume);
}
}

if let Some(frame) = frame {
Expand All @@ -144,7 +146,7 @@ impl NoiseChannel {
}

pub(crate) fn read_samples(&mut self, buffer: &mut [i16], duration: u32) -> usize {
self.blipbuf.end(buffer, duration)
self.blipbuf.as_mut().map_or(0, |blipbuf| blipbuf.end(buffer, duration))
}

pub(crate) fn power_off(&mut self) {
Expand Down Expand Up @@ -207,3 +209,47 @@ impl Memory for NoiseChannel {
}
}
}

#[derive(Serialize, Deserialize)]
pub(crate) struct NoiseChannelSnapshot {
nrx1: u8,
nrx2: u8,
nrx3: u8,
nrx4: u8,
length_counter: LengthCounter,
envelope: Envelope,
lfsr: Lfsr,
active: bool,
}

impl Snapshot for NoiseChannel {
type Snapshot = NoiseChannelSnapshot;

fn snapshot(&self) -> Self::Snapshot {
NoiseChannelSnapshot {
nrx1: self.nrx1,
nrx2: self.nrx2,
nrx3: self.nrx3,
nrx4: self.nrx4,
length_counter: self.length_counter.clone(),
envelope: self.envelope.clone(),
lfsr: self.lfsr.clone(),
active: self.active,
}
}

fn restore(&mut self, snapshot: Self::Snapshot) {
self.nrx1 = snapshot.nrx1;
self.nrx2 = snapshot.nrx2;
self.nrx3 = snapshot.nrx3;
self.nrx4 = snapshot.nrx4;
self.length_counter = snapshot.length_counter;
self.envelope = snapshot.envelope;
self.lfsr = snapshot.lfsr;
self.active = snapshot.active;

if let Some(blipbuf) = &mut self.blipbuf {
blipbuf.clear();
}
}
}
Loading

0 comments on commit d4b1b49

Please sign in to comment.