use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::sched::{AccessKind, LazyHandle};
use crate::core::space::{
AccessConstraints, MemAttrs, MemOps, MemResult, Region as MmioRegion, RegionRef,
};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{AtomicBool, AtomicU64, LockRank, Mutex, Ordering};
use crate::core::value::Width;
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink};
pub const REGISTER_BASE: u64 = 0xff10;
pub const REGISTER_LEN: u64 = 0x30;
pub const REGISTER_REGION: &str = "regs";
pub const DIV_APU_PIN: &str = "div-apu";
pub const SAMPLE_DIVISOR: u64 = 128;
pub const SAMPLE_RATE: u64 = 4_194_304 / SAMPLE_DIVISOR;
pub const RING_FRAMES: usize = SAMPLE_RATE as usize / 4;
const DUTY: [[u8; 8]; 4] = [
[0, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1, 1, 1], [0, 1, 1, 1, 1, 1, 1, 0], ];
const NOISE_DIVISOR: [u32; 8] = [8, 16, 32, 48, 64, 80, 96, 112];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Envelope {
reg: u8,
volume: u8,
timer: u8,
}
impl Envelope {
fn period(self) -> u8 {
self.reg & 0x07
}
fn increasing(self) -> bool {
self.reg & 0x08 != 0
}
fn dac_on(self) -> bool {
self.reg & 0xf8 != 0
}
fn trigger(&mut self) {
self.volume = self.reg >> 4;
self.timer = self.period();
}
fn step(&mut self) {
if self.period() == 0 {
return;
}
if self.timer > 0 {
self.timer -= 1;
}
if self.timer != 0 {
return;
}
self.timer = self.period();
if self.increasing() && self.volume < 15 {
self.volume += 1;
} else if !self.increasing() && self.volume > 0 {
self.volume -= 1;
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Length {
counter: u16,
enabled: bool,
}
impl Length {
fn set(&mut self, value: u16, max: u16) {
self.counter = max - value;
}
fn step(&mut self) -> bool {
if !self.enabled || self.counter == 0 {
return false;
}
self.counter -= 1;
self.counter == 0
}
fn trigger(&mut self, max: u16) {
if self.counter == 0 {
self.counter = max;
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Square {
enabled: bool,
duty_reg: u8,
envelope: Envelope,
length: Length,
freq: u16,
timer: u32,
phase: u8,
sweep_reg: u8,
sweep_timer: u8,
sweep_shadow: u16,
sweep_on: bool,
sweep_negated: bool,
}
impl Square {
fn period(&self) -> u32 {
(2048 - u32::from(self.freq & 0x7ff)) * 4
}
fn advance(&mut self, clocks: u64) {
if !self.enabled {
return;
}
let mut left = clocks;
while left > 0 {
let step = left.min(u64::from(self.timer));
self.timer -= step as u32;
left -= step;
if self.timer == 0 {
self.timer = self.period();
self.phase = (self.phase + 1) & 7;
}
}
}
fn output(&self) -> u8 {
if !self.enabled || !self.envelope.dac_on() {
return 0;
}
DUTY[usize::from(self.duty_reg >> 6)][usize::from(self.phase)] * self.envelope.volume
}
fn trigger(&mut self) {
self.enabled = self.envelope.dac_on();
self.length.trigger(64);
self.envelope.trigger();
self.timer = self.period();
self.sweep_shadow = self.freq & 0x7ff;
self.sweep_timer = if self.sweep_reg >> 4 & 7 == 0 {
8
} else {
self.sweep_reg >> 4 & 7
};
self.sweep_on = (self.sweep_reg >> 4) & 7 != 0 || self.sweep_reg & 7 != 0;
self.sweep_negated = false;
if self.sweep_reg & 7 != 0 && self.sweep_next().is_none() {
self.enabled = false;
}
}
fn sweep_next(&mut self) -> Option<u16> {
let shift = self.sweep_reg & 7;
let delta = self.sweep_shadow >> shift;
if self.sweep_reg & 0x08 != 0 {
self.sweep_negated = true;
Some(self.sweep_shadow.wrapping_sub(delta) & 0x7ff)
} else {
let next = self.sweep_shadow + delta;
(next <= 2047).then_some(next)
}
}
fn step_sweep(&mut self) {
if !self.sweep_on {
return;
}
if self.sweep_timer > 0 {
self.sweep_timer -= 1;
}
if self.sweep_timer != 0 {
return;
}
let period = (self.sweep_reg >> 4) & 7;
self.sweep_timer = if period == 0 { 8 } else { period };
if period == 0 {
return;
}
match self.sweep_next() {
Some(next) if self.sweep_reg & 7 != 0 => {
self.sweep_shadow = next;
self.freq = next;
if self.sweep_next().is_none() {
self.enabled = false;
}
}
Some(_) => {}
None => self.enabled = false,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Wave {
enabled: bool,
dac: bool,
level: u8,
length: Length,
freq: u16,
timer: u32,
position: u8,
sample: u8,
ram: [u8; 16],
}
impl Wave {
fn period(&self) -> u32 {
(2048 - u32::from(self.freq & 0x7ff)) * 2
}
fn advance(&mut self, clocks: u64) {
if !self.enabled {
return;
}
let mut left = clocks;
while left > 0 {
let step = left.min(u64::from(self.timer));
self.timer -= step as u32;
left -= step;
if self.timer == 0 {
self.timer = self.period();
self.position = (self.position + 1) & 31;
let byte = self.ram[usize::from(self.position / 2)];
self.sample = if self.position.is_multiple_of(2) {
byte >> 4
} else {
byte & 0x0f
};
}
}
}
fn output(&self) -> u8 {
if !self.enabled || !self.dac {
return 0;
}
match self.level {
0 => 0,
1 => self.sample,
2 => self.sample >> 1,
_ => self.sample >> 2,
}
}
fn trigger(&mut self) {
self.enabled = self.dac;
self.length.trigger(256);
self.timer = self.period();
self.position = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Noise {
enabled: bool,
envelope: Envelope,
length: Length,
poly: u8,
timer: u32,
lfsr: u16,
}
impl Default for Noise {
fn default() -> Self {
Noise {
enabled: false,
envelope: Envelope::default(),
length: Length::default(),
poly: 0,
timer: 0,
lfsr: 0x7fff,
}
}
}
impl Noise {
fn period(&self) -> u32 {
NOISE_DIVISOR[usize::from(self.poly & 7)] << (self.poly >> 4)
}
fn advance(&mut self, clocks: u64) {
if !self.enabled {
return;
}
let mut left = clocks;
while left > 0 {
let step = left.min(u64::from(self.timer));
self.timer -= step as u32;
left -= step;
if self.timer == 0 {
self.timer = self.period().max(1);
let feedback = (self.lfsr ^ (self.lfsr >> 1)) & 1;
self.lfsr = (self.lfsr >> 1) | (feedback << 14);
if self.poly & 0x08 != 0 {
self.lfsr = (self.lfsr & !0x40) | (feedback << 6);
}
}
}
}
fn output(&self) -> u8 {
if !self.enabled || !self.envelope.dac_on() {
return 0;
}
u8::from(self.lfsr & 1 == 0) * self.envelope.volume
}
fn trigger(&mut self) {
self.enabled = self.envelope.dac_on();
self.length.trigger(64);
self.envelope.trigger();
self.timer = self.period().max(1);
self.lfsr = 0x7fff;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Engine {
powered: bool,
ch1: Square,
ch2: Square,
ch3: Wave,
ch4: Noise,
nr50: u8,
nr51: u8,
frame_step: u8,
sample_timer: u64,
}
impl Default for Engine {
fn default() -> Self {
Engine {
powered: false,
ch1: Square::default(),
ch2: Square::default(),
ch3: Wave::default(),
ch4: Noise::default(),
nr50: 0,
nr51: 0,
frame_step: 0,
sample_timer: SAMPLE_DIVISOR,
}
}
}
impl Engine {
fn read_nr52(&self) -> u8 {
let mut byte = 0x70;
if self.powered {
byte |= 0x80;
}
byte |= u8::from(self.ch1.enabled);
byte |= u8::from(self.ch2.enabled) << 1;
byte |= u8::from(self.ch3.enabled) << 2;
byte |= u8::from(self.ch4.enabled) << 3;
byte
}
fn step_frame(&mut self) {
if !self.powered {
return;
}
let step = self.frame_step;
self.frame_step = (self.frame_step + 1) & 7;
if step.is_multiple_of(2) {
if self.ch1.length.step() {
self.ch1.enabled = false;
}
if self.ch2.length.step() {
self.ch2.enabled = false;
}
if self.ch3.length.step() {
self.ch3.enabled = false;
}
if self.ch4.length.step() {
self.ch4.enabled = false;
}
}
if step == 2 || step == 6 {
self.ch1.step_sweep();
}
if step == 7 {
self.ch1.envelope.step();
self.ch2.envelope.step();
self.ch4.envelope.step();
}
}
fn sample(&self) -> (i16, i16) {
if !self.powered {
return (0, 0);
}
let outs = [
self.ch1.output(),
self.ch2.output(),
self.ch3.output(),
self.ch4.output(),
];
let mut right = 0i32;
let mut left = 0i32;
for (i, out) in outs.iter().enumerate() {
let signed = i32::from(*out) * 2 - 15;
if self.nr51 & (1 << i) != 0 {
right += signed;
}
if self.nr51 & (1 << (i + 4)) != 0 {
left += signed;
}
}
let vol_right = i32::from(self.nr50 & 7) + 1;
let vol_left = i32::from((self.nr50 >> 4) & 7) + 1;
(
(left * vol_left * 68) as i16,
(right * vol_right * 68) as i16,
)
}
}
struct Shared {
engine: Mutex<Engine>,
ring: Mutex<VecDeque<(i16, i16)>>,
lazy: Mutex<Option<LazyHandle>>,
tick: AtomicU64,
recording: AtomicBool,
}
impl fmt::Debug for Shared {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Shared")
.field("engine", &self.engine)
.finish_non_exhaustive()
}
}
impl Shared {
fn publish(&self, _engine: &Engine, now: u64) {
self.tick.store(now, Ordering::Relaxed);
}
fn sync(&self, attrs: MemAttrs) {
let handle = self.lazy.lock().clone();
let Some(handle) = handle else {
return;
};
let kind = if attrs.debug {
AccessKind::Debug
} else {
AccessKind::Guest
};
let _ = handle.sync(kind);
}
}
pub struct GbApu {
shared: Arc<Shared>,
regs_region: RegionRef,
pin: Mutex<Option<Arc<FramePin>>>,
}
impl fmt::Debug for GbApu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GbApu")
.field("engine", &self.shared.engine)
.finish_non_exhaustive()
}
}
impl Default for GbApu {
fn default() -> Self {
GbApu::new()
}
}
impl GbApu {
#[must_use]
pub fn new() -> GbApu {
let shared = Arc::new(Shared {
engine: Mutex::with_rank(LockRank::DEVICE, Engine::default()),
ring: Mutex::new(VecDeque::new()),
lazy: Mutex::new(None),
tick: AtomicU64::new(0),
recording: AtomicBool::new(false),
});
let regs_region = Arc::new(MmioRegion::io(
"gb.apu.regs",
REGISTER_LEN,
Arc::new(ApuPort {
shared: Arc::clone(&shared),
}) as Arc<dyn MemOps>,
));
GbApu {
shared,
regs_region,
pin: Mutex::new(None),
}
}
pub fn from_props(props: &Props) -> Result<GbApu> {
let mut r = props.reader();
let record = r.or("record", false)?;
r.finish()?;
let apu = GbApu::new();
apu.set_recording(record);
Ok(apu)
}
pub fn set_recording(&self, on: bool) {
self.shared.recording.store(on, Ordering::Relaxed);
if !on {
self.shared.ring.lock().clear();
}
}
pub fn take_samples(&self) -> Vec<(i16, i16)> {
let mut ring = self.shared.ring.lock();
ring.drain(..).collect()
}
#[must_use]
pub fn queued_samples(&self) -> usize {
self.shared.ring.lock().len()
}
#[must_use]
pub fn status(&self) -> u8 {
self.shared.engine.lock().read_nr52()
}
#[must_use]
pub fn powered(&self) -> bool {
self.shared.engine.lock().powered
}
#[must_use]
pub fn frame_step(&self) -> u8 {
self.shared.engine.lock().frame_step
}
pub fn step_frame_sequencer(&self) {
self.shared.engine.lock().step_frame();
}
pub fn attach_lazy(&self, handle: LazyHandle) {
*self.shared.lazy.lock() = Some(handle);
}
#[must_use]
pub fn read_register(&self, offset: u64) -> u8 {
read_reg(&self.shared.engine.lock(), offset)
}
pub fn write_register(&self, offset: u64, value: u8) {
write_reg(&mut self.shared.engine.lock(), offset, value);
}
pub fn advance_to(&self, target: u64) {
let mut engine = self.shared.engine.lock();
let mut now = self.shared.tick.load(Ordering::Relaxed);
if target <= now {
return;
}
let recording = self.shared.recording.load(Ordering::Relaxed);
while now < target {
let step = (target - now).min(engine.sample_timer);
engine.ch1.advance(step);
engine.ch2.advance(step);
engine.ch3.advance(step);
engine.ch4.advance(step);
engine.sample_timer -= step;
now += step;
if engine.sample_timer == 0 {
engine.sample_timer = SAMPLE_DIVISOR;
if recording {
let frame = engine.sample();
let mut ring = self.shared.ring.lock();
if ring.len() >= RING_FRAMES {
ring.pop_front();
}
ring.push_back(frame);
}
}
}
self.shared.publish(&engine, now);
}
}
const READ_MASK: [u8; 0x17] = [
0x80, 0x3f, 0x00, 0xff, 0xbf, 0xff, 0x3f, 0x00, 0xff, 0xbf, 0x7f, 0xff, 0x9f, 0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x70, ];
fn read_reg(engine: &Engine, offset: u64) -> u8 {
let raw = match offset {
0x00 => engine.ch1.sweep_reg,
0x01 => engine.ch1.duty_reg,
0x02 => engine.ch1.envelope.reg,
0x03 => (engine.ch1.freq & 0xff) as u8,
0x04 => u8::from(engine.ch1.length.enabled) << 6,
0x06 => engine.ch2.duty_reg,
0x07 => engine.ch2.envelope.reg,
0x08 => (engine.ch2.freq & 0xff) as u8,
0x09 => u8::from(engine.ch2.length.enabled) << 6,
0x0a => u8::from(engine.ch3.dac) << 7,
0x0c => engine.ch3.level << 5,
0x0d => (engine.ch3.freq & 0xff) as u8,
0x0e => u8::from(engine.ch3.length.enabled) << 6,
0x11 => engine.ch4.envelope.reg,
0x12 => engine.ch4.poly,
0x13 => u8::from(engine.ch4.length.enabled) << 6,
0x14 => engine.nr50,
0x15 => engine.nr51,
0x16 => return engine.read_nr52(),
0x20..=0x2f => return engine.ch3.ram[(offset - 0x20) as usize],
_ => 0,
};
let mask = READ_MASK.get(offset as usize).copied().unwrap_or(0xff);
raw | mask
}
fn write_reg(engine: &mut Engine, offset: u64, value: u8) {
if (0x20..=0x2f).contains(&offset) {
engine.ch3.ram[(offset - 0x20) as usize] = value;
return;
}
if offset == 0x16 {
let on = value & 0x80 != 0;
if on == engine.powered {
return;
}
engine.powered = on;
if !on {
let ram = engine.ch3.ram;
let sample_timer = engine.sample_timer;
*engine = Engine::default();
engine.ch3.ram = ram;
engine.sample_timer = sample_timer;
}
return;
}
if !engine.powered {
return;
}
match offset {
0x00 => {
engine.ch1.sweep_reg = value;
if value & 0x08 == 0 && engine.ch1.sweep_negated {
engine.ch1.enabled = false;
}
}
0x01 => {
engine.ch1.duty_reg = value;
engine.ch1.length.set(u16::from(value & 0x3f), 64);
}
0x02 => {
engine.ch1.envelope.reg = value;
if !engine.ch1.envelope.dac_on() {
engine.ch1.enabled = false;
}
}
0x03 => engine.ch1.freq = (engine.ch1.freq & 0x700) | u16::from(value),
0x04 => {
engine.ch1.freq = (engine.ch1.freq & 0xff) | (u16::from(value & 7) << 8);
engine.ch1.length.enabled = value & 0x40 != 0;
if value & 0x80 != 0 {
engine.ch1.trigger();
}
}
0x06 => {
engine.ch2.duty_reg = value;
engine.ch2.length.set(u16::from(value & 0x3f), 64);
}
0x07 => {
engine.ch2.envelope.reg = value;
if !engine.ch2.envelope.dac_on() {
engine.ch2.enabled = false;
}
}
0x08 => engine.ch2.freq = (engine.ch2.freq & 0x700) | u16::from(value),
0x09 => {
engine.ch2.freq = (engine.ch2.freq & 0xff) | (u16::from(value & 7) << 8);
engine.ch2.length.enabled = value & 0x40 != 0;
if value & 0x80 != 0 {
engine.ch2.trigger();
}
}
0x0a => {
engine.ch3.dac = value & 0x80 != 0;
if !engine.ch3.dac {
engine.ch3.enabled = false;
}
}
0x0b => engine.ch3.length.set(u16::from(value), 256),
0x0c => engine.ch3.level = (value >> 5) & 3,
0x0d => engine.ch3.freq = (engine.ch3.freq & 0x700) | u16::from(value),
0x0e => {
engine.ch3.freq = (engine.ch3.freq & 0xff) | (u16::from(value & 7) << 8);
engine.ch3.length.enabled = value & 0x40 != 0;
if value & 0x80 != 0 {
engine.ch3.trigger();
}
}
0x10 => engine.ch4.length.set(u16::from(value & 0x3f), 64),
0x11 => {
engine.ch4.envelope.reg = value;
if !engine.ch4.envelope.dac_on() {
engine.ch4.enabled = false;
}
}
0x12 => engine.ch4.poly = value,
0x13 => {
engine.ch4.length.enabled = value & 0x40 != 0;
if value & 0x80 != 0 {
engine.ch4.trigger();
}
}
0x14 => engine.nr50 = value,
0x15 => engine.nr51 = value,
_ => {}
}
}
struct ApuPort {
shared: Arc<Shared>,
}
impl fmt::Debug for ApuPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ApuPort").finish_non_exhaustive()
}
}
impl MemOps for ApuPort {
fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
self.shared.sync(attrs);
*byte = read_reg(&self.shared.engine.lock(), offset);
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
self.shared.sync(attrs);
write_reg(&mut self.shared.engine.lock(), offset, *value);
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::IO.with_widths(Width::U8, Width::U8)
}
}
#[derive(Debug)]
pub struct FramePin {
shared: Arc<Shared>,
inputs: FanIn,
last: AtomicBool,
}
impl WireSink for FramePin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let now = self.inputs.resolve(Resolve::Or).is_high();
let was = self.last.swap(now, Ordering::AcqRel);
if now && !was {
self.shared.engine.lock().step_frame();
}
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "gb.apu",
version: 1,
summary: "Game Boy sound unit: two square channels, wave, noise, and the 512 Hz sequencer",
properties: &[PropertySpec {
name: "record",
kind: ValueKind::Bool,
required: false,
summary: "keep output frames in a ring for a host audio sink to drain",
}],
construct: |props| Ok(Box::new(GbApu::from_props(props)?) as Box<dyn Device>),
};
pub fn register(reg: &mut crate::core::Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for GbApu {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn region(&self, name: &str) -> Option<RegionRef> {
(name.is_empty() || name == REGISTER_REGION).then(|| Arc::clone(&self.regs_region))
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
if port != DIV_APU_PIN {
return None;
}
let pin = Arc::new(FramePin {
shared: Arc::clone(&self.shared),
inputs: FanIn::new(sources),
last: AtomicBool::new(false),
});
*self.pin.lock() = Some(Arc::clone(&pin));
Some(SinkPin { sink: pin, line: 0 })
}
fn reset(&self, _kind: ResetKind) {
let mut engine = self.shared.engine.lock();
*engine = Engine::default();
self.shared.tick.store(0, Ordering::Relaxed);
self.shared.publish(&engine, 0);
drop(engine);
self.shared.ring.lock().clear();
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let engine = self.shared.engine.lock().clone();
w.write_bool(engine.powered)?;
w.write_u8(engine.nr50)?;
w.write_u8(engine.nr51)?;
w.write_u8(engine.frame_step)?;
w.write_u64(engine.sample_timer)?;
for ch in [&engine.ch1, &engine.ch2] {
w.write_bool(ch.enabled)?;
w.write_u8(ch.duty_reg)?;
w.write_u8(ch.envelope.reg)?;
w.write_u8(ch.envelope.volume)?;
w.write_u8(ch.envelope.timer)?;
w.write_u16(ch.length.counter)?;
w.write_bool(ch.length.enabled)?;
w.write_u16(ch.freq)?;
w.write_u32(ch.timer)?;
w.write_u8(ch.phase)?;
w.write_u8(ch.sweep_reg)?;
w.write_u8(ch.sweep_timer)?;
w.write_u16(ch.sweep_shadow)?;
w.write_bool(ch.sweep_on)?;
w.write_bool(ch.sweep_negated)?;
}
w.write_bool(engine.ch3.enabled)?;
w.write_bool(engine.ch3.dac)?;
w.write_u8(engine.ch3.level)?;
w.write_u16(engine.ch3.length.counter)?;
w.write_bool(engine.ch3.length.enabled)?;
w.write_u16(engine.ch3.freq)?;
w.write_u32(engine.ch3.timer)?;
w.write_u8(engine.ch3.position)?;
w.write_u8(engine.ch3.sample)?;
w.write_bytes(&engine.ch3.ram)?;
w.write_bool(engine.ch4.enabled)?;
w.write_u8(engine.ch4.envelope.reg)?;
w.write_u8(engine.ch4.envelope.volume)?;
w.write_u8(engine.ch4.envelope.timer)?;
w.write_u16(engine.ch4.length.counter)?;
w.write_bool(engine.ch4.length.enabled)?;
w.write_u8(engine.ch4.poly)?;
w.write_u32(engine.ch4.timer)?;
w.write_u16(engine.ch4.lfsr)?;
w.write_u64(self.shared.tick.load(Ordering::Relaxed))?;
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut engine = Engine {
powered: r.read_bool()?,
nr50: r.read_u8()?,
nr51: r.read_u8()?,
frame_step: r.read_u8()?,
sample_timer: r.read_u64()?,
..Engine::default()
};
for index in 0..2 {
let ch = Square {
enabled: r.read_bool()?,
duty_reg: r.read_u8()?,
envelope: Envelope {
reg: r.read_u8()?,
volume: r.read_u8()?,
timer: r.read_u8()?,
},
length: Length {
counter: r.read_u16()?,
enabled: r.read_bool()?,
},
freq: r.read_u16()?,
timer: r.read_u32()?,
phase: r.read_u8()?,
sweep_reg: r.read_u8()?,
sweep_timer: r.read_u8()?,
sweep_shadow: r.read_u16()?,
sweep_on: r.read_bool()?,
sweep_negated: r.read_bool()?,
};
if index == 0 {
engine.ch1 = ch;
} else {
engine.ch2 = ch;
}
}
engine.ch3.enabled = r.read_bool()?;
engine.ch3.dac = r.read_bool()?;
engine.ch3.level = r.read_u8()?;
engine.ch3.length.counter = r.read_u16()?;
engine.ch3.length.enabled = r.read_bool()?;
engine.ch3.freq = r.read_u16()?;
engine.ch3.timer = r.read_u32()?;
engine.ch3.position = r.read_u8()?;
engine.ch3.sample = r.read_u8()?;
let ram = r.read_bytes()?;
if ram.len() != 16 {
return Err(crate::core::Error::State(String::from(
"the sound unit's snapshot has the wrong wave-RAM size",
)));
}
engine.ch3.ram.copy_from_slice(ram);
engine.ch4.enabled = r.read_bool()?;
engine.ch4.envelope.reg = r.read_u8()?;
engine.ch4.envelope.volume = r.read_u8()?;
engine.ch4.envelope.timer = r.read_u8()?;
engine.ch4.length.counter = r.read_u16()?;
engine.ch4.length.enabled = r.read_bool()?;
engine.ch4.poly = r.read_u8()?;
engine.ch4.timer = r.read_u32()?;
engine.ch4.lfsr = r.read_u16()?;
let tick = r.read_u64()?;
let mut slot = self.shared.engine.lock();
*slot = engine;
self.shared.publish(&slot, tick);
Ok(())
}
fn is_lazy(&self) -> bool {
true
}
fn current_tick(&self) -> u64 {
self.shared.tick.load(Ordering::Relaxed)
}
fn advance_to(&self, tick: u64) {
GbApu::advance_to(self, tick);
}
fn next_event_tick(&self) -> Option<u64> {
None
}
fn attach_lazy(&self, handle: LazyHandle) {
GbApu::attach_lazy(self, handle);
}
}
impl crate::machine::Instance for GbApu {}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS.name, |props| Ok(Arc::new(GbApu::from_props(props)?)))
}
#[must_use]
pub fn schema() -> crate::machine::validate::ClassSchema {
use crate::machine::validate::{ClassSchema, PortDir, PropSchema};
ClassSchema::new(CLASS.name)
.prop(PropSchema::new("record", ValueKind::Bool))
.port(DIV_APU_PIN, PortDir::In)
.region(REGISTER_REGION)
}