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:
Tim Allen 2017-03-09 07:20:40 +11:00
parent 7e7003fd29
commit 04072b278b
46 changed files with 514 additions and 84 deletions

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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/";

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -3,6 +3,7 @@
namespace MegaDrive {
APU apu;
#include "serialization.cpp"
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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));

View File

@ -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;

View File

@ -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);
}

View File

@ -3,6 +3,7 @@
namespace MegaDrive {
Cartridge cartridge;
#include "serialization.cpp"
auto Cartridge::load() -> bool {
information = {};

View File

@ -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;

View File

@ -0,0 +1,3 @@
auto Cartridge::serialize(serializer& s) -> void {
if(ram.size) s.array(ram.data, ram.size);
}

View File

@ -3,6 +3,7 @@
namespace MegaDrive {
CPU cpu;
#include "serialization.cpp"
auto CPU::Enter() -> void {
cpu.boot();

View File

@ -19,6 +19,9 @@ struct CPU : Processor::M68K, Thread {
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
vector<Thread*> peripherals;
private:

View File

@ -0,0 +1,7 @@
auto CPU::serialize(serializer& s) -> void {
M68K::serialize(s);
Thread::serialize(s);
s.integer(state.interruptLine);
s.integer(state.interruptPending);
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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)) {

View File

@ -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;

View File

@ -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);
}

View File

@ -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 {
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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 = {};

View File

@ -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;

View File

@ -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)) {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);