Update to v102r07 release.

byuu says:

Changelog:

  - PCE: emulated PSG volume controls (vastly enhances audio quality)
  - PCE: emulated PSG noise as a square wave (somewhat enhances audio
    quality)
  - PCE: added save state support (currently broken and deadlocks the
    emulator though)

Thankfully, MAME had some rather easy to read code on how the volume
adjustment works, which they apparently ripped out of expired patents.
Hooray!

The two remaining sound issues are:

1. the random number generator for the noise channel is definitely not
hardware accurate. But it won't affect the sound quality at all. You'd
only be able to tell the difference by looking at hex bytes of a stream
rip.
2. I have no clue how to emulate the LFO (frequency modulation). A comment
in MAME's code (they also don't emulate it) advises that they aren't
aware of any games that even use it. But I'm there has to be at least one?

Given LFO not being used, and the RNG not really mattering all that much
... the sound's pretty close to perfect now.
This commit is contained in:
Tim Allen
2017-02-13 10:09:03 +11:00
parent fa6cbac251
commit 7c9b78b7bb
25 changed files with 331 additions and 45 deletions

View File

@@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "102.06"; static const string Version = "102.07";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@@ -7,6 +7,7 @@ CPU cpu;
#include "io.cpp" #include "io.cpp"
#include "irq.cpp" #include "irq.cpp"
#include "timer.cpp" #include "timer.cpp"
#include "serialization.cpp"
auto CPU::Enter() -> void { auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main(); while(true) scheduler.synchronize(), cpu.main();

View File

@@ -19,8 +19,15 @@ struct CPU : Processor::HuC6280, Thread {
//timer.cpp //timer.cpp
auto timerStep(uint clocks) -> void; auto timerStep(uint clocks) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
vector<Thread*> peripherals; vector<Thread*> peripherals;
private:
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
struct IRQ { struct IRQ {
//irq.cpp //irq.cpp
auto pending() const -> bool; auto pending() const -> bool;
@@ -59,10 +66,6 @@ struct CPU : Processor::HuC6280, Thread {
struct IO { struct IO {
uint8 mdr; uint8 mdr;
} io; } io;
private:
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
}; };
extern CPU cpu; extern CPU cpu;

View File

@@ -0,0 +1,21 @@
auto CPU::serialize(serializer& s) -> void {
HuC6280::serialize(s);
Thread::serialize(s);
s.array(ram, Model::PCEngine() ? 0x2000 : 0x8000);
s.array(bram);
s.integer(irq.disableExternal);
s.integer(irq.disableVDC);
s.integer(irq.disableTimer);
s.integer(irq.pendingIRQ);
s.integer(irq.pendingVector);
s.integer(timer.enable);
s.integer(timer.latch);
s.integer(timer.value);
s.integer(timer.clock);
s.integer(timer.line);
s.integer(io.mdr);
}

View File

@@ -10,7 +10,7 @@ Settings settings;
Interface::Interface() { Interface::Interface() {
information.overscan = true; information.overscan = true;
information.capability.states = false; information.capability.states = true;
information.capability.cheats = false; information.capability.cheats = false;
Port controllerPort{ID::Port::Controller, "Controller Port"}; Port controllerPort{ID::Port::Controller, "Controller Port"};
@@ -108,11 +108,12 @@ auto Interface::run() -> void {
} }
auto Interface::serialize() -> serializer { auto Interface::serialize() -> serializer {
return {}; system.runToSave();
return system.serialize();
} }
auto Interface::unserialize(serializer& s) -> bool { auto Interface::unserialize(serializer& s) -> bool {
return false; return system.unserialize(s);
} }
auto Interface::cap(const string& name) -> bool { auto Interface::cap(const string& name) -> bool {

View File

@@ -1,7 +1,6 @@
auto PSG::Channel::power(uint id) -> void { auto PSG::Channel::power(uint id) -> void {
this->id = id; this->id = id;
memory::fill(&io, sizeof(IO)); memory::fill(&io, sizeof(IO));
memory::fill(&output, sizeof(Output));
} }
auto PSG::Channel::run() -> void { auto PSG::Channel::run() -> void {
@@ -19,22 +18,12 @@ auto PSG::Channel::run() -> void {
if(--io.noisePeriod == 0) { if(--io.noisePeriod == 0) {
io.noisePeriod = ~io.noiseFrequency << 7; io.noisePeriod = ~io.noiseFrequency << 7;
//todo: this should be a square wave; PRNG algorithm is also unknown io.noiseSample = nall::random() & 1 ? ~0 : 0;
io.noiseSample = nall::random();
} }
return sample(io.noiseSample); return sample(io.noiseSample);
} }
auto PSG::Channel::loadWavePeriod() -> void {
io.wavePeriod = io.waveFrequency;
}
auto PSG::Channel::loadWaveSample() -> void {
io.waveSample = io.waveBuffer[io.waveOffset];
}
auto PSG::Channel::sample(uint5 sample) -> void { auto PSG::Channel::sample(uint5 sample) -> void {
output.left = sample << 8; //<< io.volume << io.volumeLeft; io.output = sample;
output.right = sample << 8; //<< io.volume << io.volumeRight;
} }

View File

@@ -49,7 +49,7 @@ auto PSG::Channel::write(uint4 addr, uint8 data) -> void {
io.waveOffset++; io.waveOffset++;
io.waveSample = io.waveBuffer[io.waveOffset]; io.waveSample = io.waveBuffer[io.waveOffset];
} }
io.volume = data.bits(0,3); io.volume = data.bits(0,4);
io.direct = data.bit(6); io.direct = data.bit(6);
io.enable = data.bit(7); io.enable = data.bit(7);
} }

View File

@@ -5,25 +5,43 @@ namespace PCEngine {
PSG psg; PSG psg;
#include "io.cpp" #include "io.cpp"
#include "channel.cpp" #include "channel.cpp"
#include "serialization.cpp"
auto PSG::Enter() -> void { auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main(); while(true) scheduler.synchronize(), psg.main();
} }
auto PSG::main() -> void { auto PSG::main() -> void {
uint left = 0, right = 0; static const uint5 volumeScale[16] = {
0x00, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f,
0x10, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, 0x1f,
};
uint5 lmal = volumeScale[io.volumeLeft];
uint5 rmal = volumeScale[io.volumeRight];
double outputLeft = 0.0;
double outputRight = 0.0;
for(auto C : range(6)) { for(auto C : range(6)) {
uint5 al = channel[C].io.volume;
uint5 lal = volumeScale[channel[C].io.volumeLeft];
uint5 ral = volumeScale[channel[C].io.volumeRight];
uint5 volumeLeft = min(0x1f, (0x1f - lmal) + (0x1f - lal) + (0x1f - al));
uint5 volumeRight = min(0x1f, (0x1f - rmal) + (0x1f - ral) + (0x1f - al));
channel[C].run(); channel[C].run();
if(C == 1 && io.lfoEnable) { if(C == 1 && io.lfoEnable) {
//todo: frequency modulation of channel 0 using channel 1's output //todo: frequency modulation of channel 0 using channel 1's output
} else { } else {
left += channel[C].output.left; outputLeft += channel[C].io.output * volumeScalar[volumeLeft];
right += channel[C].output.right; outputRight += channel[C].io.output * volumeScalar[volumeRight];
} }
} }
stream->sample(left / 32768.0, right / 32768.0); //normalize 0.0 to 65536.0 => -1.0 to +1.0
stream->sample(outputLeft / 32768.0 - 1.0, outputRight / 32768.0 - 1.0);
step(1); step(1);
} }
@@ -38,6 +56,14 @@ auto PSG::power() -> void {
memory::fill(&io, sizeof(IO)); memory::fill(&io, sizeof(IO));
for(auto C : range(6)) channel[C].power(C); for(auto C : range(6)) channel[C].power(C);
double level = 65536.0 / 6.0 / 32.0; //max volume / channels / steps
double step = 48.0 / 32.0; //48dB volume range spread over 32 steps
for(uint n : range(31)) {
volumeScalar[n] = level;
level /= pow(10.0, step / 20.0);
}
volumeScalar[31] = 0.0;
} }
} }

View File

@@ -12,6 +12,9 @@ struct PSG : Thread {
//io.cpp //io.cpp
auto write(uint4 addr, uint8 data) -> void; auto write(uint4 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private: private:
struct IO { struct IO {
uint3 channel; uint3 channel;
@@ -26,8 +29,6 @@ private:
//channel.cpp //channel.cpp
auto power(uint id) -> void; auto power(uint id) -> void;
auto run() -> void; auto run() -> void;
auto loadWavePeriod() -> void;
auto loadWaveSample() -> void;
auto sample(uint5 sample) -> void; auto sample(uint5 sample) -> void;
//io.cpp //io.cpp
@@ -35,7 +36,7 @@ private:
struct IO { struct IO {
uint12 waveFrequency; uint12 waveFrequency;
uint4 volume; uint5 volume;
uint1 direct; uint1 direct;
uint1 enable; uint1 enable;
uint4 volumeLeft; uint4 volumeLeft;
@@ -49,15 +50,14 @@ private:
uint5 waveOffset; uint5 waveOffset;
uint12 noisePeriod; uint12 noisePeriod;
uint5 noiseSample; uint5 noiseSample;
} io;
struct Output { uint5 output;
uint left; } io;
uint right;
} output;
uint id; uint id;
} channel[6]; } channel[6];
double volumeScalar[32];
}; };
extern PSG psg; extern PSG psg;

View File

@@ -0,0 +1,28 @@
auto PSG::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(io.channel);
s.integer(io.volumeLeft);
s.integer(io.volumeRight);
s.integer(io.lfoFrequency);
s.integer(io.lfoControl);
s.integer(io.lfoEnable);
for(auto C : range(6)) {
s.integer(channel[C].io.waveFrequency);
s.integer(channel[C].io.volume);
s.integer(channel[C].io.direct);
s.integer(channel[C].io.enable);
s.integer(channel[C].io.volumeLeft);
s.integer(channel[C].io.volumeRight);
s.array(channel[C].io.waveBuffer);
s.integer(channel[C].io.noiseFrequency);
s.integer(channel[C].io.noiseEnable);
s.integer(channel[C].io.wavePeriod);
s.integer(channel[C].io.waveSample);
s.integer(channel[C].io.waveOffset);
s.integer(channel[C].io.noisePeriod);
s.integer(channel[C].io.noiseSample);
s.integer(channel[C].io.output);
}
}

View File

@@ -0,0 +1,67 @@
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);
cpu.serialize(s);
vce.serialize(s);
vpc.serialize(s);
vdc0.serialize(s);
vdc1.serialize(s);
psg.serialize(s);
}
auto System::serialize(serializer& s) -> void {
}

View File

@@ -5,11 +5,20 @@ namespace PCEngine {
System system; System system;
Scheduler scheduler; Scheduler scheduler;
#include "peripherals.cpp" #include "peripherals.cpp"
#include "serialization.cpp"
auto System::run() -> void { auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) vce.refresh(); if(scheduler.enter() == Scheduler::Event::Frame) vce.refresh();
} }
auto System::runToSave() -> void {
scheduler.synchronize(cpu);
scheduler.synchronize(vce);
scheduler.synchronize(vdc0);
scheduler.synchronize(vdc1);
scheduler.synchronize(psg);
}
auto System::load(Emulator::Interface* interface, Model model) -> bool { auto System::load(Emulator::Interface* interface, Model model) -> bool {
information = {}; information = {};
information.model = model; information.model = model;
@@ -22,6 +31,7 @@ auto System::load(Emulator::Interface* interface, Model model) -> bool {
if(!cartridge.load()) return false; if(!cartridge.load()) return false;
cpu.load(); cpu.load();
serializeInit();
this->interface = interface; this->interface = interface;
information.colorburst = Emulator::Constants::Colorburst::NTSC; information.colorburst = Emulator::Constants::Colorburst::NTSC;
return information.loaded = true; return information.loaded = true;

View File

@@ -6,6 +6,7 @@ struct System {
inline auto colorburst() const -> double { return information.colorburst; } inline auto colorburst() const -> double { return information.colorburst; }
auto run() -> void; auto run() -> void;
auto runToSave() -> void;
auto load(Emulator::Interface*, Model) -> bool; auto load(Emulator::Interface*, Model) -> bool;
auto save() -> void; auto save() -> void;
@@ -13,6 +14,13 @@ struct System {
auto power() -> 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: private:
Emulator::Interface* interface = nullptr; Emulator::Interface* interface = nullptr;
@@ -21,6 +29,7 @@ private:
Model model = Model::PCEngine; Model model = Model::PCEngine;
string manifest; string manifest;
double colorburst = 0.0; double colorburst = 0.0;
uint serializeSize = 0;
} information; } information;
}; };

View File

@@ -0,0 +1,13 @@
auto VCE::serialize(serializer& s) -> void {
Thread::serialize(s);
s.array(cram.data);
s.integer(cram.address);
s.integer(timing.hclock);
s.integer(timing.vclock);
s.integer(io.clock);
s.integer(io.extraLine);
s.integer(io.grayscale);
}

View File

@@ -5,6 +5,7 @@ namespace PCEngine {
VCE vce; VCE vce;
#include "memory.cpp" #include "memory.cpp"
#include "io.cpp" #include "io.cpp"
#include "serialization.cpp"
auto VCE::Enter() -> void { auto VCE::Enter() -> void {
while(true) scheduler.synchronize(), vce.main(); while(true) scheduler.synchronize(), vce.main();

View File

@@ -13,6 +13,9 @@ struct VCE : Thread {
auto read(uint3 addr) -> uint8; auto read(uint3 addr) -> uint8;
auto write(uint3 addr, uint8 data) -> void; auto write(uint3 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private: private:
uint32 buffer[1365 * 263]; uint32 buffer[1365 * 263];
@@ -21,10 +24,8 @@ private:
auto read(uint9 addr) -> uint9; auto read(uint9 addr) -> uint9;
auto write(uint9 addr, bool a0, uint8 data) -> void; auto write(uint9 addr, bool a0, uint8 data) -> void;
uint9 address;
private:
uint9 data[0x200]; uint9 data[0x200];
uint9 address;
} cram; } cram;
struct Timing { struct Timing {

View File

@@ -0,0 +1,83 @@
auto VDC::serialize(serializer& s) -> void {
Thread::serialize(s);
s.array(vram.data);
s.integer(vram.addressRead);
s.integer(vram.addressWrite);
s.integer(vram.addressIncrement);
s.integer(vram.dataRead);
s.integer(vram.dataWrite);
s.array(satb.data);
s.integer(timing.horizontalSyncWidth);
s.integer(timing.horizontalDisplayStart);
s.integer(timing.horizontalDisplayLength);
s.integer(timing.horizontalDisplayEnd);
s.integer(timing.verticalSyncWidth);
s.integer(timing.verticalDisplayStart);
s.integer(timing.verticalDisplayLength);
s.integer(timing.verticalDisplayEnd);
s.integer(timing.vpulse);
s.integer(timing.hpulse);
s.integer(timing.hclock);
s.integer(timing.vclock);
s.integer(timing.hoffset);
s.integer(timing.voffset);
s.integer(timing.hstart);
s.integer(timing.vstart);
s.integer(timing.hlength);
s.integer(timing.vlength);
s.integer(irq.enableCollision);
s.integer(irq.enableOverflow);
s.integer(irq.enableLineCoincidence);
s.integer(irq.enableVblank);
s.integer(irq.enableTransferVRAM);
s.integer(irq.enableTransferSATB);
s.integer(irq.pendingCollision);
s.integer(irq.pendingOverflow);
s.integer(irq.pendingLineCoincidence);
s.integer(irq.pendingVblank);
s.integer(irq.pendingTransferVRAM);
s.integer(irq.pendingTransferSATB);
s.integer(irq.line);
s.integer(dma.sourceIncrementMode);
s.integer(dma.targetIncrementMode);
s.integer(dma.satbRepeat);
s.integer(dma.source);
s.integer(dma.target);
s.integer(dma.length);
s.integer(dma.satbSource);
s.integer(dma.vramActive);
s.integer(dma.satbActive);
s.integer(dma.satbPending);
s.integer(dma.satbOffset);
s.integer(background.enable);
s.integer(background.hscroll);
s.integer(background.vscroll);
s.integer(background.vcounter);
s.integer(background.width);
s.integer(background.height);
s.integer(background.hoffset);
s.integer(background.voffset);
s.integer(background.color);
s.integer(background.palette);
s.integer(sprite.enable);
s.integer(sprite.color);
s.integer(sprite.palette);
s.integer(sprite.priority);
//todo: serialize array<sprite.objects>
s.integer(io.address);
s.integer(io.externalSync);
s.integer(io.displayOutput);
s.integer(io.dramRefresh);
s.integer(io.lineCoincidence);
s.integer(io.vramAccess);
s.integer(io.spriteAccess);
s.integer(io.cgMode);
}

View File

@@ -10,6 +10,7 @@ VDC vdc1;
#include "dma.cpp" #include "dma.cpp"
#include "background.cpp" #include "background.cpp"
#include "sprite.cpp" #include "sprite.cpp"
#include "serialization.cpp"
auto VDC::Enter() -> void { auto VDC::Enter() -> void {
while(true) { while(true) {

View File

@@ -16,6 +16,9 @@ struct VDC : Thread {
auto read(uint2 addr) -> uint8; auto read(uint2 addr) -> uint8;
auto write(uint2 addr, uint8 data) -> void; auto write(uint2 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private: private:
uint9 data; uint9 data;
@@ -24,15 +27,14 @@ private:
auto read(uint16 addr) -> uint16; auto read(uint16 addr) -> uint16;
auto write(uint16 addr, uint16 data) -> void; auto write(uint16 addr, uint16 data) -> void;
uint16 data[0x8000];
uint16 addressRead; uint16 addressRead;
uint16 addressWrite; uint16 addressWrite;
uint16 addressIncrement; uint16 addressIncrement;
uint16 dataRead; uint16 dataRead;
uint16 dataWrite; uint16 dataWrite;
private:
uint16 data[0x8000];
} vram; } vram;
struct SATB { struct SATB {
@@ -40,7 +42,6 @@ private:
auto read(uint8 addr) -> uint16; auto read(uint8 addr) -> uint16;
auto write(uint8 addr, uint16 data) -> void; auto write(uint8 addr, uint16 data) -> void;
private:
uint16 data[0x100]; uint16 data[0x100];
} satb; } satb;
@@ -191,10 +192,6 @@ private:
uint2 vramAccess; uint2 vramAccess;
uint2 spriteAccess; uint2 spriteAccess;
bool cgMode; bool cgMode;
//$0400 CR
bool colorBlur;
bool grayscale;
} io; } io;
}; };

View File

@@ -0,0 +1,9 @@
auto VPC::serialize(serializer& s) -> void {
for(auto n : range(4)) {
s.integer(settings[n].enableVDC0);
s.integer(settings[n].enableVDC1);
s.integer(settings[n].priority);
}
s.array(window);
s.integer(select);
}

View File

@@ -3,6 +3,7 @@
namespace PCEngine { namespace PCEngine {
VPC vpc; VPC vpc;
#include "serialization.cpp"
auto VPC::bus(uint hclock) -> uint9 { auto VPC::bus(uint hclock) -> uint9 {
//bus values are direct CRAM entry indexes: //bus values are direct CRAM entry indexes:

View File

@@ -8,6 +8,9 @@ struct VPC {
auto write(uint5 addr, uint8 data) -> void; auto write(uint5 addr, uint8 data) -> void;
auto store(uint2 addr, uint8 data) -> void; auto store(uint2 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private: private:
struct Settings { struct Settings {
bool enableVDC0; bool enableVDC0;

View File

@@ -26,6 +26,7 @@ namespace Processor {
#include "instruction.cpp" #include "instruction.cpp"
#include "instructions.cpp" #include "instructions.cpp"
#include "disassembler.cpp" #include "disassembler.cpp"
#include "serialization.cpp"
#undef A #undef A
#undef X #undef X
#undef Y #undef Y

View File

@@ -106,6 +106,9 @@ struct HuC6280 {
//disassembler.cpp //disassembler.cpp
auto disassemble(uint16 pc) -> string; auto disassemble(uint16 pc) -> string;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Flags { struct Flags {
bool c; //carry bool c; //carry
bool z; //zero bool z; //zero

View File

@@ -0,0 +1,18 @@
auto HuC6280::serialize(serializer& s) -> void {
s.integer(r.a);
s.integer(r.x);
s.integer(r.y);
s.integer(r.s);
s.integer(r.pc);
s.array(r.mpr);
s.integer(r.mdr);
s.integer(r.p.c);
s.integer(r.p.z);
s.integer(r.p.i);
s.integer(r.p.d);
s.integer(r.p.b);
s.integer(r.p.t);
s.integer(r.p.v);
s.integer(r.p.n);
s.integer(r.cs);
}