From 04072b278bba1a3cdb3315e47ec433fb7cf282bf Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Thu, 9 Mar 2017 07:20:40 +1100 Subject: [PATCH] Update to v102r16 release. byuu says: Changelog: - Emulator::Stream now allows adding low-pass and high-pass filters dynamically - also accepts a pass# count; each pass is a second-order biquad butterworth IIR filter - Emulator::Stream no longer automatically filters out >20KHz frequencies for all streams - FC: added 20Hz high-pass filter; 20KHz low-pass filter - GB: removed simple 'magic constant' high-pass filter of unknown cutoff frequency (missed this one in the last WIP) - GB,SGB,GBC: added 20Hz high-pass filter; 20KHz low-pass filter - MS,GG,MD/PSG: added 20Hz high-pass filter; 20KHz low-pass filter - MD: added save state support (but it's completely broken for now; sorry) - MD/YM2612: fixed Voice#3 per-operator pitch support (fixes sound effects in Streets of Rage, etc) - PCE: added 20Hz high-pass filter; 20KHz low-pass filter - WS,WSC: added 20Hz high-pass filter; 20KHz low-pass filter So, the point of the low-pass filters is to remove frequencies above human hearing. If we don't do this, then resampling will introduce aliasing that results in sounds that are audible to the human ear. Which basically an annoying buzzing sound. You'll definitely hear the improvement from these in games like Mega Man 2 on the NES. Of course, these already existed before, so this WIP won't sound better than previous WIPs. The high-pass filters are a little more complicated. Their main role is to remove DC bias and help to center the audio stream. I don't understand how they do this at all, but ... that's what everyone who knows what they're talking about says, thus ... so be it. I have set all of the high-pass filters to 20Hz, which is below the limit of human hearing. Now this is where it gets really interesting ... technically, some of these systems actually cut off a lot of range. For instance, the GBA should technically use an 800Hz high-pass filter when output is done through the system's speakers. But of course, if you plug in headphones, you can hear the lower frequencies. Now 800Hz ... you definitely can hear. At that level, nearly all of the bass is stripped out and the audio is very tinny. Just like the real system. But for now, I don't want to emulate the audio being crushed that badly. I'm sticking with 20Hz everywhere since it won't negatively affect audio quality. In fact, you should not be able to hear any difference between this WIP and the previous WIP. But theoretically, DC bias should mostly be removed as a result of these new filters. It may be that we need to raise the values on some cores in the future, but I don't want to do that until we know for certain that we have to. What I can say is that compared to even older WIPs than r15 ... the removal of the simple one-pole low-pass and high-pass filters with the newer three-pass, second-order filters should result in much better attenuation (less distortion of audible frequencies.) Probably not enough to be noticeable in a blind test, though. --- higan/audio/audio.hpp | 8 ++- higan/audio/stream.cpp | 34 ++++++--- higan/emulator/emulator.hpp | 2 +- higan/fc/apu/apu.cpp | 4 +- higan/gb/apu/apu.cpp | 16 ++--- higan/gb/apu/apu.hpp | 5 -- higan/gb/apu/sequencer.cpp | 10 --- higan/gba/apu/apu.cpp | 3 +- higan/md/apu/apu.cpp | 1 + higan/md/apu/apu.hpp | 9 ++- higan/md/apu/serialization.cpp | 8 +++ higan/md/bus/bus.cpp | 1 + higan/md/bus/bus.hpp | 6 ++ higan/md/bus/serialization.cpp | 8 +++ higan/md/cartridge/cartridge.cpp | 1 + higan/md/cartridge/cartridge.hpp | 3 + higan/md/cartridge/serialization.cpp | 3 + higan/md/cpu/cpu.cpp | 1 + higan/md/cpu/cpu.hpp | 3 + higan/md/cpu/serialization.cpp | 7 ++ higan/md/interface/interface.cpp | 9 ++- higan/md/interface/interface.hpp | 2 + higan/md/md.hpp | 3 + higan/md/psg/psg.cpp | 3 + higan/md/psg/psg.hpp | 9 +++ higan/md/psg/serialization.cpp | 29 ++++++++ higan/md/system/serialization.cpp | 69 ++++++++++++++++++ higan/md/system/system.cpp | 13 +++- higan/md/system/system.hpp | 9 +++ higan/md/vdp/io.cpp | 3 +- higan/md/vdp/serialization.cpp | 96 ++++++++++++++++++++++++++ higan/md/vdp/vdp.cpp | 1 + higan/md/vdp/vdp.hpp | 49 +++++++++---- higan/md/ym2612/io.cpp | 26 +++---- higan/md/ym2612/serialization.cpp | 83 ++++++++++++++++++++++ higan/md/ym2612/ym2612.cpp | 7 +- higan/md/ym2612/ym2612.hpp | 9 +++ higan/ms/psg/psg.cpp | 2 + higan/pce/psg/psg.cpp | 4 +- higan/processor/m68k/m68k.cpp | 1 + higan/processor/m68k/m68k.hpp | 3 + higan/processor/m68k/serialization.cpp | 20 ++++++ higan/sfc/coprocessor/icd2/icd2.cpp | 7 +- higan/sfc/coprocessor/msu1/msu1.cpp | 2 +- higan/sfc/dsp/dsp.cpp | 2 +- higan/ws/apu/apu.cpp | 4 +- 46 files changed, 514 insertions(+), 84 deletions(-) create mode 100644 higan/md/apu/serialization.cpp create mode 100644 higan/md/bus/serialization.cpp create mode 100644 higan/md/cartridge/serialization.cpp create mode 100644 higan/md/cpu/serialization.cpp create mode 100644 higan/md/psg/serialization.cpp create mode 100644 higan/md/system/serialization.cpp create mode 100644 higan/md/vdp/serialization.cpp create mode 100644 higan/md/ym2612/serialization.cpp create mode 100644 higan/processor/m68k/serialization.cpp 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);