diff --git a/higan/audio/audio.hpp b/higan/audio/audio.hpp index 40f04ad0..d589974f 100644 --- a/higan/audio/audio.hpp +++ b/higan/audio/audio.hpp @@ -40,6 +40,9 @@ private: struct Stream { auto reset(uint channels, double inputFrequency, double outputFrequency) -> void; + auto addLowPassFilter(double cutoffFrequency, uint passes = 1) -> void; + auto addHighPassFilter(double cutoffFrequency, uint passes = 1) -> void; + auto pending() const -> bool; auto read(double* samples) -> uint; auto write(const double* samples) -> void; @@ -50,12 +53,13 @@ struct Stream { } private: - const uint order = 6; //Nth-order filter (must be an even number) struct Channel { - vector iir; + vector filters; DSP::Resampler::Cubic resampler; }; vector channels; + double inputFrequency; + double outputFrequency; friend class Audio; }; diff --git a/higan/audio/stream.cpp b/higan/audio/stream.cpp index b2ad126a..8f16a32d 100644 --- a/higan/audio/stream.cpp +++ b/higan/audio/stream.cpp @@ -1,20 +1,36 @@ auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void { + this->inputFrequency = inputFrequency; + this->outputFrequency = outputFrequency; + channels.reset(); channels.resize(channels_); for(auto& channel : channels) { - if(outputFrequency / inputFrequency <= 0.5) { - channel.iir.resize(order / 2); - for(auto phase : range(order / 2)) { - double q = DSP::IIR::Biquad::butterworth(order, phase); - channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0, inputFrequency, q); - } - } - + channel.filters.reset(); channel.resampler.reset(inputFrequency, outputFrequency); } } +auto Stream::addLowPassFilter(double cutoffFrequency, uint passes) -> void { + for(auto& channel : channels) { + for(auto pass : range(passes)) { + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + channel.filters.append(DSP::IIR::Biquad{}); + channel.filters.right().reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q); + } + } +} + +auto Stream::addHighPassFilter(double cutoffFrequency, uint passes) -> void { + for(auto& channel : channels) { + for(auto pass : range(passes)) { + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + channel.filters.append(DSP::IIR::Biquad{}); + channel.filters.right().reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q); + } + } +} + auto Stream::pending() const -> bool { return channels && channels[0].resampler.pending(); } @@ -27,7 +43,7 @@ auto Stream::read(double* samples) -> uint { auto Stream::write(const double* samples) -> void { for(auto c : range(channels)) { double sample = samples[c] + 1e-25; //constant offset used to suppress denormals - for(auto& iir : channels[c].iir) sample = iir.process(sample); + for(auto& filter : channels[c].filters) sample = filter.process(sample); channels[c].resampler.write(sample); } diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 1867ed80..f1f9b27b 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "102.15"; + static const string Version = "102.16"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/fc/apu/apu.cpp b/higan/fc/apu/apu.cpp index 4f94c2ae..451d6794 100644 --- a/higan/fc/apu/apu.cpp +++ b/higan/fc/apu/apu.cpp @@ -73,7 +73,9 @@ auto APU::setSample(int16 sample) -> void { auto APU::power() -> void { create(APU::Enter, system.colorburst() * 6.0); - stream = Emulator::audio.createStream(1, system.colorburst() / 2.0); + stream = Emulator::audio.createStream(1, frequency() / 12.0); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); pulse[0].power(); pulse[1].power(); diff --git a/higan/gb/apu/apu.cpp b/higan/gb/apu/apu.cpp index 2780df53..240d86a3 100644 --- a/higan/gb/apu/apu.cpp +++ b/higan/gb/apu/apu.cpp @@ -21,10 +21,6 @@ auto APU::main() -> void { noise.run(); sequencer.run(); - hipass(sequencer.center, sequencer.centerBias); - hipass(sequencer.left, sequencer.leftBias); - hipass(sequencer.right, sequencer.rightBias); - if(!Model::SuperGameBoy()) { stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0); } else { @@ -55,15 +51,13 @@ auto APU::main() -> void { synchronize(cpu); } -//filter to remove DC bias -auto APU::hipass(int16& sample, int64& bias) -> void { - bias += ((((int64)sample << 16) - (bias >> 16)) * 57593) >> 16; - sample = sclamp<16>(sample - (bias >> 32)); -} - auto APU::power() -> void { create(Enter, 2 * 1024 * 1024); - if(!Model::SuperGameBoy()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024); + if(!Model::SuperGameBoy()) { + stream = Emulator::audio.createStream(2, frequency()); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); + } for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this; square1.power(); diff --git a/higan/gb/apu/apu.hpp b/higan/gb/apu/apu.hpp index 66c59132..ddc86b1a 100644 --- a/higan/gb/apu/apu.hpp +++ b/higan/gb/apu/apu.hpp @@ -3,7 +3,6 @@ struct APU : Thread, MMIO { static auto Enter() -> void; auto main() -> void; - auto hipass(int16& sample, int64& bias) -> void; auto power() -> void; auto readIO(uint16 addr) -> uint8; @@ -163,10 +162,6 @@ struct APU : Thread, MMIO { int16 center; int16 left; int16 right; - - int64 centerBias; - int64 leftBias; - int64 rightBias; } sequencer; uint3 phase; //high 3-bits of clock counter diff --git a/higan/gb/apu/sequencer.cpp b/higan/gb/apu/sequencer.cpp index 4cd3cd4d..69a239d7 100644 --- a/higan/gb/apu/sequencer.cpp +++ b/higan/gb/apu/sequencer.cpp @@ -3,8 +3,6 @@ auto APU::Sequencer::run() -> void { center = 0; left = 0; right = 0; - - centerBias = leftBias = rightBias = 0; return; } @@ -121,10 +119,6 @@ auto APU::Sequencer::power() -> void { center = 0; left = 0; right = 0; - - centerBias = 0; - leftBias = 0; - rightBias = 0; } auto APU::Sequencer::serialize(serializer& s) -> void { @@ -145,8 +139,4 @@ auto APU::Sequencer::serialize(serializer& s) -> void { s.integer(center); s.integer(left); s.integer(right); - - s.integer(centerBias); - s.integer(leftBias); - s.integer(rightBias); } diff --git a/higan/gba/apu/apu.cpp b/higan/gba/apu/apu.cpp index 4ebc2b3b..07fce221 100644 --- a/higan/gba/apu/apu.cpp +++ b/higan/gba/apu/apu.cpp @@ -74,7 +74,8 @@ auto APU::step(uint clocks) -> void { auto APU::power() -> void { create(APU::Enter, 16'777'216); - stream = Emulator::audio.createStream(2, 16'777'216.0 / 512.0); + stream = Emulator::audio.createStream(2, frequency() / 512.0); + //todo: run sequencer at higher frequency; add low-pass filter square1.power(); square2.power(); diff --git a/higan/md/apu/apu.cpp b/higan/md/apu/apu.cpp index 0720d799..229ace32 100644 --- a/higan/md/apu/apu.cpp +++ b/higan/md/apu/apu.cpp @@ -3,6 +3,7 @@ namespace MegaDrive { APU apu; +#include "serialization.cpp" auto APU::Enter() -> void { while(true) scheduler.synchronize(), apu.main(); diff --git a/higan/md/apu/apu.hpp b/higan/md/apu/apu.hpp index ec0d7b3f..5e65765d 100644 --- a/higan/md/apu/apu.hpp +++ b/higan/md/apu/apu.hpp @@ -11,11 +11,14 @@ struct APU : Processor::Z80, Thread { auto setNMI(bool value) -> void; auto setINT(bool value) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: struct State { - boolean enabled; - boolean nmiLine; - boolean intLine; + uint1 enabled; + uint1 nmiLine; + uint1 intLine; } state; }; diff --git a/higan/md/apu/serialization.cpp b/higan/md/apu/serialization.cpp new file mode 100644 index 00000000..335d7622 --- /dev/null +++ b/higan/md/apu/serialization.cpp @@ -0,0 +1,8 @@ +auto APU::serialize(serializer& s) -> void { + Z80::serialize(s); + Thread::serialize(s); + + s.integer(state.enabled); + s.integer(state.nmiLine); + s.integer(state.intLine); +} diff --git a/higan/md/bus/bus.cpp b/higan/md/bus/bus.cpp index 7b74cc7e..19e90443 100644 --- a/higan/md/bus/bus.cpp +++ b/higan/md/bus/bus.cpp @@ -4,6 +4,7 @@ namespace MegaDrive { BusCPU busCPU; BusAPU busAPU; +#include "serialization.cpp" auto BusCPU::readByte(uint24 addr) -> uint16 { if(addr < 0x400000) return cartridge.read(addr & ~1).byte(!addr.bit(0)); diff --git a/higan/md/bus/bus.hpp b/higan/md/bus/bus.hpp index 542a6904..ad73ebd4 100644 --- a/higan/md/bus/bus.hpp +++ b/higan/md/bus/bus.hpp @@ -7,6 +7,9 @@ struct BusCPU : Processor::M68K::Bus { auto readIO(uint24 addr) -> uint16; auto writeIO(uint24 addr, uint16 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: uint8 ram[64 * 1024]; }; @@ -18,6 +21,9 @@ struct BusAPU : Processor::Z80::Bus { auto in(uint8 addr) -> uint8 override; auto out(uint8 addr, uint8 data) -> void override; + //serialization.cpp + auto serialize(serializer&) -> void; + private: uint8 ram[8 * 1024]; uint9 bank; diff --git a/higan/md/bus/serialization.cpp b/higan/md/bus/serialization.cpp new file mode 100644 index 00000000..fd7e2f71 --- /dev/null +++ b/higan/md/bus/serialization.cpp @@ -0,0 +1,8 @@ +auto BusCPU::serialize(serializer& s) -> void { + s.array(ram); +} + +auto BusAPU::serialize(serializer& s) -> void { + s.array(ram); + s.integer(bank); +} diff --git a/higan/md/cartridge/cartridge.cpp b/higan/md/cartridge/cartridge.cpp index 7a1729e0..0c209e61 100644 --- a/higan/md/cartridge/cartridge.cpp +++ b/higan/md/cartridge/cartridge.cpp @@ -3,6 +3,7 @@ namespace MegaDrive { Cartridge cartridge; +#include "serialization.cpp" auto Cartridge::load() -> bool { information = {}; diff --git a/higan/md/cartridge/cartridge.hpp b/higan/md/cartridge/cartridge.hpp index a8001d4d..9bd3a7b0 100644 --- a/higan/md/cartridge/cartridge.hpp +++ b/higan/md/cartridge/cartridge.hpp @@ -12,6 +12,9 @@ struct Cartridge { auto read(uint24 addr) -> uint16; auto write(uint24 addr, uint16 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct Information { uint pathID = 0; string sha256; diff --git a/higan/md/cartridge/serialization.cpp b/higan/md/cartridge/serialization.cpp new file mode 100644 index 00000000..602e2ad2 --- /dev/null +++ b/higan/md/cartridge/serialization.cpp @@ -0,0 +1,3 @@ +auto Cartridge::serialize(serializer& s) -> void { + if(ram.size) s.array(ram.data, ram.size); +} diff --git a/higan/md/cpu/cpu.cpp b/higan/md/cpu/cpu.cpp index 781f0183..8a7b6b0d 100644 --- a/higan/md/cpu/cpu.cpp +++ b/higan/md/cpu/cpu.cpp @@ -3,6 +3,7 @@ namespace MegaDrive { CPU cpu; +#include "serialization.cpp" auto CPU::Enter() -> void { cpu.boot(); diff --git a/higan/md/cpu/cpu.hpp b/higan/md/cpu/cpu.hpp index 155b3e52..af78586b 100644 --- a/higan/md/cpu/cpu.hpp +++ b/higan/md/cpu/cpu.hpp @@ -19,6 +19,9 @@ struct CPU : Processor::M68K, Thread { auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + vector peripherals; private: diff --git a/higan/md/cpu/serialization.cpp b/higan/md/cpu/serialization.cpp new file mode 100644 index 00000000..ca8e5525 --- /dev/null +++ b/higan/md/cpu/serialization.cpp @@ -0,0 +1,7 @@ +auto CPU::serialize(serializer& s) -> void { + M68K::serialize(s); + Thread::serialize(s); + + s.integer(state.interruptLine); + s.integer(state.interruptPending); +} diff --git a/higan/md/interface/interface.cpp b/higan/md/interface/interface.cpp index f909ea63..ec7fbfc4 100644 --- a/higan/md/interface/interface.cpp +++ b/higan/md/interface/interface.cpp @@ -112,11 +112,16 @@ auto Interface::run() -> void { } auto Interface::serialize() -> serializer { - return {}; + system.runToSave(); + return system.serialize(); } auto Interface::unserialize(serializer& s) -> bool { - return false; + return system.unserialize(s); +} + +auto Interface::cheatSet(const string_vector& list) -> void { + cheat.assign(list); } auto Interface::cap(const string& name) -> bool { diff --git a/higan/md/interface/interface.hpp b/higan/md/interface/interface.hpp index 79b8618e..a7217289 100644 --- a/higan/md/interface/interface.hpp +++ b/higan/md/interface/interface.hpp @@ -43,6 +43,8 @@ struct Interface : Emulator::Interface { auto serialize() -> serializer override; auto unserialize(serializer&) -> bool override; + auto cheatSet(const string_vector& list) -> void override; + auto cap(const string& name) -> bool override; auto get(const string& name) -> any override; auto set(const string& name, const any& value) -> bool override; diff --git a/higan/md/md.hpp b/higan/md/md.hpp index 5f582737..504273ca 100644 --- a/higan/md/md.hpp +++ b/higan/md/md.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,7 +15,9 @@ namespace MegaDrive { #define platform Emulator::platform namespace File = Emulator::File; using Scheduler = Emulator::Scheduler; + using Cheat = Emulator::Cheat; extern Scheduler scheduler; + extern Cheat cheat; struct Wait { enum : uint { diff --git a/higan/md/psg/psg.cpp b/higan/md/psg/psg.cpp index 4014600f..f32e8105 100644 --- a/higan/md/psg/psg.cpp +++ b/higan/md/psg/psg.cpp @@ -6,6 +6,7 @@ PSG psg; #include "io.cpp" #include "tone.cpp" #include "noise.cpp" +#include "serialization.cpp" auto PSG::Enter() -> void { while(true) scheduler.synchronize(), psg.main(); @@ -36,6 +37,8 @@ auto PSG::step(uint clocks) -> void { auto PSG::power() -> void { create(PSG::Enter, system.colorburst() / 16.0); stream = Emulator::audio.createStream(1, frequency()); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); select = 0; for(auto n : range(15)) { diff --git a/higan/md/psg/psg.hpp b/higan/md/psg/psg.hpp index 12fbbf69..cd1e6eaa 100644 --- a/higan/md/psg/psg.hpp +++ b/higan/md/psg/psg.hpp @@ -12,12 +12,18 @@ struct PSG : Thread { //io.cpp auto write(uint8 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: struct Tone { //tone.cpp auto run() -> void; auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + uint4 volume; uint10 counter; uint10 pitch; @@ -29,6 +35,9 @@ private: auto run() -> void; auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + uint4 volume; uint10 counter; uint10 pitch; diff --git a/higan/md/psg/serialization.cpp b/higan/md/psg/serialization.cpp new file mode 100644 index 00000000..42a04120 --- /dev/null +++ b/higan/md/psg/serialization.cpp @@ -0,0 +1,29 @@ +auto PSG::serialize(serializer& s) -> void { + Thread::serialize(s); + + tone0.serialize(s); + tone1.serialize(s); + tone2.serialize(s); + noise.serialize(s); + + s.integer(select); + s.array(levels); +} + +auto PSG::Tone::serialize(serializer& s) -> void { + s.integer(volume); + s.integer(counter); + s.integer(pitch); + s.integer(output); +} + +auto PSG::Noise::serialize(serializer& s) -> void { + s.integer(volume); + s.integer(counter); + s.integer(pitch); + s.integer(enable); + s.integer(rate); + s.integer(lfsr); + s.integer(clock); + s.integer(output); +} diff --git a/higan/md/system/serialization.cpp b/higan/md/system/serialization.cpp new file mode 100644 index 00000000..5d0ba189 --- /dev/null +++ b/higan/md/system/serialization.cpp @@ -0,0 +1,69 @@ +auto System::serializeInit() -> void { + serializer s; + + uint signature = 0; + char version[16] = {0}; + char hash[64] = {0}; + char description[512] = {0}; + + s.integer(signature); + s.array(version); + s.array(hash); + s.array(description); + + serializeAll(s); + information.serializeSize = s.size(); +} + +auto System::serialize() -> serializer { + serializer s{information.serializeSize}; + + uint signature = 0x31545342; + char version[16] = {0}; + char hash[64] = {0}; + char description[512] = {0}; + memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size()); + memory::copy(&hash, (const char*)cartridge.sha256(), 64); + + s.integer(signature); + s.array(version); + s.array(hash); + s.array(description); + + serializeAll(s); + return s; +} + +auto System::unserialize(serializer& s) -> bool { + uint signature = 0; + char version[16] = {0}; + char hash[64] = {0}; + char description[512] = {0}; + + s.integer(signature); + s.array(version); + s.array(hash); + s.array(description); + + if(signature != 0x31545342) return false; + if(string{version} != Emulator::SerializerVersion) return false; + + power(); + serializeAll(s); + return true; +} + +auto System::serializeAll(serializer& s) -> void { + system.serialize(s); + busCPU.serialize(s); + busAPU.serialize(s); + cartridge.serialize(s); + cpu.serialize(s); + apu.serialize(s); + vdp.serialize(s); + psg.serialize(s); + ym2612.serialize(s); +} + +auto System::serialize(serializer& s) -> void { +} diff --git a/higan/md/system/system.cpp b/higan/md/system/system.cpp index 478b537e..ea2fa323 100644 --- a/higan/md/system/system.cpp +++ b/higan/md/system/system.cpp @@ -2,14 +2,24 @@ namespace MegaDrive { -#include "peripherals.cpp" System system; Scheduler scheduler; +Cheat cheat; +#include "peripherals.cpp" +#include "serialization.cpp" auto System::run() -> void { if(scheduler.enter() == Scheduler::Event::Frame) vdp.refresh(); } +auto System::runToSave() -> void { + scheduler.synchronize(cpu); + scheduler.synchronize(apu); + scheduler.synchronize(vdp); + scheduler.synchronize(psg); + scheduler.synchronize(ym2612); +} + auto System::load(Emulator::Interface* interface, maybe region) -> bool { information = {}; @@ -20,6 +30,7 @@ auto System::load(Emulator::Interface* interface, maybe region) -> bool auto document = BML::unserialize(information.manifest); if(!cartridge.load()) return false; + serializeInit(); information.region = Region::NTSCU; information.colorburst = Emulator::Constants::Colorburst::NTSC; this->interface = interface; diff --git a/higan/md/system/system.hpp b/higan/md/system/system.hpp index 87733f1e..579057d9 100644 --- a/higan/md/system/system.hpp +++ b/higan/md/system/system.hpp @@ -10,12 +10,20 @@ struct System { auto colorburst() const -> double { return information.colorburst; } auto run() -> void; + auto runToSave() -> void; auto load(Emulator::Interface*, maybe = nothing) -> bool; auto save() -> void; auto unload() -> void; auto power() -> void; + //serialization.cpp + auto serializeInit() -> void; + auto serialize() -> serializer; + auto unserialize(serializer&) -> bool; + auto serializeAll(serializer&) -> void; + auto serialize(serializer&) -> void; + private: Emulator::Interface* interface = nullptr; @@ -24,6 +32,7 @@ private: Region region = Region::NTSCJ; string manifest; double colorburst = 0.0; + uint serializeSize = 0; } information; }; diff --git a/higan/md/vdp/io.cpp b/higan/md/vdp/io.cpp index 9eadb9d2..298e8550 100644 --- a/higan/md/vdp/io.cpp +++ b/higan/md/vdp/io.cpp @@ -73,7 +73,8 @@ auto VDP::writeDataPort(uint16 data) -> void { io.commandPending = false; //DMA VRAM fill - if(dma.io.wait.lower()) { + if(dma.io.wait) { + dma.io.wait = false; dma.io.fill = data >> 8; //falls through to memory write //causes extra transfer to occur on VRAM fill operations diff --git a/higan/md/vdp/serialization.cpp b/higan/md/vdp/serialization.cpp new file mode 100644 index 00000000..d9e736f0 --- /dev/null +++ b/higan/md/vdp/serialization.cpp @@ -0,0 +1,96 @@ +auto VDP::serialize(serializer& s) -> void { + Thread::serialize(s); + + dma.serialize(s); + planeA.serialize(s); + window.serialize(s); + planeB.serialize(s); + sprite.serialize(s); + + vram.serialize(s); + vsram.serialize(s); + cram.serialize(s); + + s.integer(io.command); + s.integer(io.address); + s.integer(io.commandPending); + s.integer(io.displayOverlayEnable); + s.integer(io.counterLatch); + s.integer(io.horizontalBlankInterruptEnable); + s.integer(io.leftColumnBlank); + s.integer(io.videoMode); + s.integer(io.overscan); + s.integer(io.verticalBlankInterruptEnable); + s.integer(io.displayEnable); + s.integer(io.externalVRAM); + s.integer(io.backgroundColor); + s.integer(io.horizontalInterruptCounter); + s.integer(io.externalInterruptEnable); + s.integer(io.displayWidth); + s.integer(io.interlaceMode); + s.integer(io.shadowHighlightEnable); + s.integer(io.externalColorEnable); + s.integer(io.horizontalSync); + s.integer(io.verticalSync); + s.integer(io.nametableBasePatternA); + s.integer(io.nametableBasePatternB); + s.integer(io.dataIncrement); + + s.integer(latch.overscan); + s.integer(latch.displayWidth); + + s.integer(state.hcounter); + s.integer(state.x); + s.integer(state.y); +} + +auto VDP::DMA::serialize(serializer& s) -> void { + s.integer(io.mode); + s.integer(io.source); + s.integer(io.length); + s.integer(io.fill); + s.integer(io.enable); + s.integer(io.wait); +} + +auto VDP::Background::serialize(serializer& s) -> void { + s.integer(io.nametableAddress); + s.integer(io.nametableWidth); + s.integer(io.nametableHeight); + s.integer(io.horizontalScrollAddress); + s.integer(io.horizontalScrollMode); + s.integer(io.verticalScrollMode); + s.integer(io.horizontalDirection); + s.integer(io.horizontalOffset); + s.integer(io.verticalDirection); + s.integer(io.verticalOffset); + + s.integer(state.horizontalScroll); + s.integer(state.verticalScroll); + + s.integer(output.color); + s.integer(output.priority); +} + +auto VDP::Sprite::serialize(serializer& s) -> void { + s.integer(io.attributeAddress); + s.integer(io.nametableAddressBase); + + s.integer(output.color); + s.integer(output.priority); + + //todo: serialize oam + //todo: serialize objects +} + +auto VDP::VRAM::serialize(serializer& s) -> void { + s.array(memory); +} + +auto VDP::VSRAM::serialize(serializer& s) -> void { + s.array(memory); +} + +auto VDP::CRAM::serialize(serializer& s) -> void { + s.array(memory); +} diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp index 21d64989..03065215 100644 --- a/higan/md/vdp/vdp.cpp +++ b/higan/md/vdp/vdp.cpp @@ -9,6 +9,7 @@ VDP vdp; #include "render.cpp" #include "background.cpp" #include "sprite.cpp" +#include "serialization.cpp" auto VDP::Enter() -> void { while(true) scheduler.synchronize(), vdp.main(); diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp index 6c289089..a0b9af95 100644 --- a/higan/md/vdp/vdp.hpp +++ b/higan/md/vdp/vdp.hpp @@ -18,8 +18,8 @@ struct VDP : Thread { auto readControlPort() -> uint16; auto writeControlPort(uint16 data) -> void; - //dma.cpp struct DMA { + //dma.cpp auto run() -> void; auto load() -> void; auto fill() -> void; @@ -27,13 +27,16 @@ struct VDP : Thread { auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct IO { - uint2 mode; - uint22 source; - uint16 length; - uint8 fill; - boolean enable; - boolean wait; + uint2 mode; + uint22 source; + uint16 length; + uint8 fill; + uint1 enable; + uint1 wait; } io; } dma; @@ -43,10 +46,10 @@ struct VDP : Thread { auto run() -> void; auto outputPixel(uint9 color) -> void; - //background.cpp struct Background { enum class ID : uint { PlaneA, Window, PlaneB } id; + //background.cpp auto isWindowed(uint x, uint y) -> bool; auto updateHorizontalScroll(uint y) -> void; @@ -61,6 +64,9 @@ struct VDP : Thread { auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct IO { uint15 nametableAddress; @@ -85,21 +91,24 @@ struct VDP : Thread { struct Output { uint6 color; - boolean priority; + uint1 priority; } output; }; Background planeA{Background::ID::PlaneA}; Background window{Background::ID::Window}; Background planeB{Background::ID::PlaneB}; - //sprite.cpp struct Sprite { + //sprite.cpp auto write(uint9 addr, uint16 data) -> void; auto scanline(uint y) -> void; auto run(uint x, uint y) -> void; auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct IO { uint15 attributeAddress; uint1 nametableAddressBase; @@ -119,8 +128,8 @@ struct VDP : Thread { }; struct Output { - uint6 color; - boolean priority; + uint6 color; + uint1 priority; } output; array oam; @@ -128,6 +137,9 @@ struct VDP : Thread { }; Sprite sprite; + //serialization.cpp + auto serialize(serializer&) -> void; + private: auto pixelWidth() const -> uint { return latch.displayWidth ? 4 : 5; } auto screenWidth() const -> uint { return latch.displayWidth ? 320 : 256; } @@ -142,6 +154,9 @@ private: auto readByte(uint16 address) const -> uint8; auto writeByte(uint16 address, uint8 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: uint16 memory[32768]; } vram; @@ -152,6 +167,9 @@ private: auto read(uint6 address) const -> uint10; auto write(uint6 address, uint10 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: uint10 memory[40]; } vsram; @@ -162,15 +180,18 @@ private: auto read(uint6 address) const -> uint9; auto write(uint6 address, uint9 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: uint9 memory[64]; } cram; struct IO { //command - uint6 command; + uint6 command; uint16 address; - boolean commandPending; + uint1 commandPending; //$00 mode register 1 uint1 displayOverlayEnable; diff --git a/higan/md/ym2612/io.cpp b/higan/md/ym2612/io.cpp index 6dcd8d43..1d356bb7 100644 --- a/higan/md/ym2612/io.cpp +++ b/higan/md/ym2612/io.cpp @@ -171,24 +171,24 @@ auto YM2612::writeData(uint8 data) -> void { break; } - //... + //per-operator pitch (low) case 0x0a8: { - if(io.address == 0x0a9) voice = 0; - if(io.address == 0x0aa) voice = 1; - if(io.address == 0x0a8) voice = 2; - channels[2][voice].pitch.value = channels[2][voice].pitch.latch | data; - channels[2][voice].octave.value = channels[2][voice].octave.latch; - channels[2][voice].updatePitch(); + if(io.address == 0x0a9) index = 0; + if(io.address == 0x0aa) index = 1; + if(io.address == 0x0a8) index = 2; + channels[2][index].pitch.reload = channels[2][index].pitch.latch | data; + channels[2][index].octave.reload = channels[2][index].octave.latch; + channels[2][index].updatePitch(); break; } - //... + //per-operator pitch (high) case 0x0ac: { - if(io.address == 0x0ad) voice = 0; - if(io.address == 0x0ae) voice = 1; - if(io.address == 0x0ac) voice = 2; - channels[2][voice].pitch.latch = data << 8; - channels[2][voice].octave.latch = data >> 3; + if(io.address == 0x0ad) index = 0; + if(io.address == 0x0ae) index = 1; + if(io.address == 0x0ac) index = 2; + channels[2][index].pitch.latch = data << 8; + channels[2][index].octave.latch = data >> 3; break; } diff --git a/higan/md/ym2612/serialization.cpp b/higan/md/ym2612/serialization.cpp new file mode 100644 index 00000000..bf8fff5c --- /dev/null +++ b/higan/md/ym2612/serialization.cpp @@ -0,0 +1,83 @@ +auto YM2612::serialize(serializer& s) -> void { + Thread::serialize(s); + + s.integer(io.address); + + s.integer(lfo.enable); + s.integer(lfo.rate); + s.integer(lfo.clock); + s.integer(lfo.divider); + + s.integer(dac.enable); + s.integer(dac.sample); + + s.integer(envelope.clock); + s.integer(envelope.divider); + + s.integer(timerA.enable); + s.integer(timerA.irq); + s.integer(timerA.line); + s.integer(timerA.period); + s.integer(timerA.counter); + + s.integer(timerB.enable); + s.integer(timerB.irq); + s.integer(timerB.line); + s.integer(timerB.period); + s.integer(timerB.counter); + s.integer(timerB.divider); + + for(auto n : range(6)) channels[n].serialize(s); +} + +auto YM2612::Channel::serialize(serializer& s) -> void { + s.integer(leftEnable); + s.integer(rightEnable); + s.integer(algorithm); + s.integer(feedback); + s.integer(vibrato); + s.integer(tremolo); + s.integer(mode); + + for(auto n : range(4)) operators[n].serialize(s); +} + +auto YM2612::Channel::Operator::serialize(serializer& s) -> void { + s.integer(keyOn); + s.integer(lfoEnable); + s.integer(detune); + s.integer(multiple); + s.integer(totalLevel); + s.integer(outputLevel); + s.integer(output); + s.integer(prior); + + s.integer(pitch.value); + s.integer(pitch.reload); + s.integer(pitch.latch); + + s.integer(octave.value); + s.integer(octave.reload); + s.integer(octave.latch); + + s.integer(phase.value); + s.integer(phase.delta); + + s.integer(envelope.state); + s.integer(envelope.rate); + s.integer(envelope.divider); + s.integer(envelope.steps); + s.integer(envelope.value); + s.integer(envelope.keyScale); + s.integer(envelope.attackRate); + s.integer(envelope.decayRate); + s.integer(envelope.sustainRate); + s.integer(envelope.sustainLevel); + s.integer(envelope.releaseRate); + + s.integer(ssg.enable); + s.integer(ssg.attack); + s.integer(ssg.alternate); + s.integer(ssg.hold); + s.integer(ssg.invert); +} diff --git a/higan/md/ym2612/ym2612.cpp b/higan/md/ym2612/ym2612.cpp index 2c8c75f5..a880e857 100644 --- a/higan/md/ym2612/ym2612.cpp +++ b/higan/md/ym2612/ym2612.cpp @@ -7,6 +7,7 @@ YM2612 ym2612; #include "timer.cpp" #include "channel.cpp" #include "constants.cpp" +#include "serialization.cpp" auto YM2612::Enter() -> void { while(true) scheduler.synchronize(), ym2612.main(); @@ -42,7 +43,7 @@ auto YM2612::main() -> void { } } - step(1); + step(144); } auto YM2612::sample() -> void { @@ -154,8 +155,8 @@ auto YM2612::step(uint clocks) -> void { } auto YM2612::power() -> void { - create(YM2612::Enter, system.colorburst() * 15.0 / 7.0 / 144.0); - stream = Emulator::audio.createStream(2, frequency()); + create(YM2612::Enter, system.colorburst() * 15.0 / 7.0); + stream = Emulator::audio.createStream(2, frequency() / 144.0); io = {}; lfo = {}; diff --git a/higan/md/ym2612/ym2612.hpp b/higan/md/ym2612/ym2612.hpp index 0722c390..7b2e9a70 100644 --- a/higan/md/ym2612/ym2612.hpp +++ b/higan/md/ym2612/ym2612.hpp @@ -15,6 +15,9 @@ struct YM2612 : Thread { auto writeAddress(uint9 data) -> void; auto writeData(uint8 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: struct IO { uint9 address = 0; @@ -66,6 +69,9 @@ private: //channel.cpp auto power() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + uint1 leftEnable = 1; uint1 rightEnable = 1; @@ -91,6 +97,9 @@ private: auto updatePhase() -> void; auto updateLevel() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + uint1 keyOn = 0; uint1 lfoEnable = 0; uint3 detune = 0; diff --git a/higan/ms/psg/psg.cpp b/higan/ms/psg/psg.cpp index fa4d7526..c3face17 100644 --- a/higan/ms/psg/psg.cpp +++ b/higan/ms/psg/psg.cpp @@ -44,6 +44,8 @@ auto PSG::power() -> void { //use stereo mode for both; output same sample to both channels for Master System create(PSG::Enter, system.colorburst() / 16.0); stream = Emulator::audio.createStream(2, frequency()); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); select = 0; for(auto n : range(15)) { diff --git a/higan/pce/psg/psg.cpp b/higan/pce/psg/psg.cpp index 9af1d441..bebca9a5 100644 --- a/higan/pce/psg/psg.cpp +++ b/higan/pce/psg/psg.cpp @@ -51,7 +51,9 @@ auto PSG::step(uint clocks) -> void { auto PSG::power() -> void { create(PSG::Enter, system.colorburst()); - stream = Emulator::audio.createStream(2, system.colorburst()); + stream = Emulator::audio.createStream(2, frequency()); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); memory::fill(&io, sizeof(IO)); for(auto C : range(6)) channel[C].power(C); diff --git a/higan/processor/m68k/m68k.cpp b/higan/processor/m68k/m68k.cpp index 87d89446..bee799e3 100644 --- a/higan/processor/m68k/m68k.cpp +++ b/higan/processor/m68k/m68k.cpp @@ -12,6 +12,7 @@ enum : bool { Reverse = 1 }; #include "instructions.cpp" #include "disassembler.cpp" #include "instruction.cpp" +#include "serialization.cpp" auto M68K::power() -> void { for(auto& dr : r.d) dr = 0; diff --git a/higan/processor/m68k/m68k.hpp b/higan/processor/m68k/m68k.hpp index ac8606ad..2c28acd6 100644 --- a/higan/processor/m68k/m68k.hpp +++ b/higan/processor/m68k/m68k.hpp @@ -248,6 +248,9 @@ struct M68K { template auto instructionTST(EffectiveAddress ea) -> void; auto instructionUNLK(AddressRegister with) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + //disassembler.cpp auto disassemble(uint32 pc) -> string; auto disassembleRegisters() -> string; diff --git a/higan/processor/m68k/serialization.cpp b/higan/processor/m68k/serialization.cpp new file mode 100644 index 00000000..6076e5ec --- /dev/null +++ b/higan/processor/m68k/serialization.cpp @@ -0,0 +1,20 @@ +auto M68K::serialize(serializer& s) -> void { + s.array(r.d); + s.array(r.a); + s.integer(r.sp); + s.integer(r.pc); + + s.integer(r.c); + s.integer(r.v); + s.integer(r.z); + s.integer(r.n); + s.integer(r.x); + s.integer(r.i); + s.integer(r.s); + s.integer(r.t); + + s.integer(r.stop); + s.integer(r.reset); + + s.integer(opcode); +} diff --git a/higan/sfc/coprocessor/icd2/icd2.cpp b/higan/sfc/coprocessor/icd2/icd2.cpp index a40bfc44..b177a8bd 100644 --- a/higan/sfc/coprocessor/icd2/icd2.cpp +++ b/higan/sfc/coprocessor/icd2/icd2.cpp @@ -46,9 +46,10 @@ auto ICD2::unload() -> void { } auto ICD2::power() -> void { - auto frequency = system.colorburst() * 6.0; - create(ICD2::Enter, frequency / 5); - stream = Emulator::audio.createStream(2, frequency / 10); + create(ICD2::Enter, system.colorburst() * 6.0 / 5.0); + stream = Emulator::audio.createStream(2, frequency() / 2.0); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); r6003 = 0x00; r6004 = 0xff; diff --git a/higan/sfc/coprocessor/msu1/msu1.cpp b/higan/sfc/coprocessor/msu1/msu1.cpp index 261edfcf..8c6c09c0 100644 --- a/higan/sfc/coprocessor/msu1/msu1.cpp +++ b/higan/sfc/coprocessor/msu1/msu1.cpp @@ -52,7 +52,7 @@ auto MSU1::unload() -> void { auto MSU1::power() -> void { create(MSU1::Enter, 44100); - stream = Emulator::audio.createStream(2, 44100.0); + stream = Emulator::audio.createStream(2, frequency()); io.dataSeekOffset = 0; io.dataReadOffset = 0; diff --git a/higan/sfc/dsp/dsp.cpp b/higan/sfc/dsp/dsp.cpp index 3a4d2d5e..8357d039 100644 --- a/higan/sfc/dsp/dsp.cpp +++ b/higan/sfc/dsp/dsp.cpp @@ -230,7 +230,7 @@ auto DSP::load(Markup::Node node) -> bool { auto DSP::power() -> void { create(Enter, 32040.0 * 768.0); - stream = Emulator::audio.createStream(2, 32040.0); + stream = Emulator::audio.createStream(2, frequency() / 768.0); memory::fill(&state, sizeof(State)); state.noise = 0x4000; diff --git a/higan/ws/apu/apu.cpp b/higan/ws/apu/apu.cpp index 078febaa..ff62eddf 100644 --- a/higan/ws/apu/apu.cpp +++ b/higan/ws/apu/apu.cpp @@ -66,7 +66,9 @@ auto APU::step(uint clocks) -> void { auto APU::power() -> void { create(APU::Enter, 3'072'000); - stream = Emulator::audio.createStream(2, 3'072'000.0); + stream = Emulator::audio.createStream(2, frequency()); + stream->addLowPassFilter(20000.0, 3); + stream->addHighPassFilter(20.0, 3); bus.map(this, 0x004a, 0x004c); bus.map(this, 0x004e, 0x0050);