mirror of https://github.com/bsnes-emu/bsnes.git
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.
This commit is contained in:
parent
7e7003fd29
commit
04072b278b
|
@ -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<DSP::IIR::Biquad> iir;
|
||||
vector<DSP::IIR::Biquad> filters;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
||||
double inputFrequency;
|
||||
double outputFrequency;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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/";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace MegaDrive {
|
||||
|
||||
APU apu;
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace MegaDrive {
|
||||
|
||||
Cartridge cartridge;
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
auto Cartridge::serialize(serializer& s) -> void {
|
||||
if(ram.size) s.array(ram.data, ram.size);
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace MegaDrive {
|
||||
|
||||
CPU cpu;
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
cpu.boot();
|
||||
|
|
|
@ -19,6 +19,9 @@ struct CPU : Processor::M68K, Thread {
|
|||
|
||||
auto power() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
private:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
auto CPU::serialize(serializer& s) -> void {
|
||||
M68K::serialize(s);
|
||||
Thread::serialize(s);
|
||||
|
||||
s.integer(state.interruptLine);
|
||||
s.integer(state.interruptPending);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <emulator/emulator.hpp>
|
||||
#include <emulator/thread.hpp>
|
||||
#include <emulator/scheduler.hpp>
|
||||
#include <emulator/cheat.hpp>
|
||||
|
||||
#include <processor/m68k/m68k.hpp>
|
||||
#include <processor/z80/z80.hpp>
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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> region) -> bool {
|
||||
information = {};
|
||||
|
||||
|
@ -20,6 +30,7 @@ auto System::load(Emulator::Interface* interface, maybe<Region> 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;
|
||||
|
|
|
@ -10,12 +10,20 @@ struct System {
|
|||
auto colorburst() const -> double { return information.colorburst; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load(Emulator::Interface*, maybe<Region> = 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<Object, 80> 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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 = {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -248,6 +248,9 @@ struct M68K {
|
|||
template<uint Size> 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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue