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 {
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 License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

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

View File

@ -19,8 +19,15 @@ struct CPU : Processor::HuC6280, Thread {
//timer.cpp
auto timerStep(uint clocks) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
vector<Thread*> peripherals;
private:
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
struct IRQ {
//irq.cpp
auto pending() const -> bool;
@ -59,10 +66,6 @@ struct CPU : Processor::HuC6280, Thread {
struct IO {
uint8 mdr;
} io;
private:
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
};
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() {
information.overscan = true;
information.capability.states = false;
information.capability.states = true;
information.capability.cheats = false;
Port controllerPort{ID::Port::Controller, "Controller Port"};
@ -108,11 +108,12 @@ 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::cap(const string& name) -> bool {

View File

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

View File

@ -49,7 +49,7 @@ auto PSG::Channel::write(uint4 addr, uint8 data) -> void {
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.enable = data.bit(7);
}

View File

@ -5,25 +5,43 @@ namespace PCEngine {
PSG psg;
#include "io.cpp"
#include "channel.cpp"
#include "serialization.cpp"
auto PSG::Enter() -> void {
while(true) scheduler.synchronize(), psg.main();
}
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)) {
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();
if(C == 1 && io.lfoEnable) {
//todo: frequency modulation of channel 0 using channel 1's output
} else {
left += channel[C].output.left;
right += channel[C].output.right;
outputLeft += channel[C].io.output * volumeScalar[volumeLeft];
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);
}
@ -38,6 +56,14 @@ auto PSG::power() -> void {
memory::fill(&io, sizeof(IO));
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
auto write(uint4 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
struct IO {
uint3 channel;
@ -26,8 +29,6 @@ private:
//channel.cpp
auto power(uint id) -> void;
auto run() -> void;
auto loadWavePeriod() -> void;
auto loadWaveSample() -> void;
auto sample(uint5 sample) -> void;
//io.cpp
@ -35,7 +36,7 @@ private:
struct IO {
uint12 waveFrequency;
uint4 volume;
uint5 volume;
uint1 direct;
uint1 enable;
uint4 volumeLeft;
@ -49,15 +50,14 @@ private:
uint5 waveOffset;
uint12 noisePeriod;
uint5 noiseSample;
} io;
struct Output {
uint left;
uint right;
} output;
uint5 output;
} io;
uint id;
} channel[6];
double volumeScalar[32];
};
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;
Scheduler scheduler;
#include "peripherals.cpp"
#include "serialization.cpp"
auto System::run() -> void {
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 {
information = {};
information.model = model;
@ -22,6 +31,7 @@ auto System::load(Emulator::Interface* interface, Model model) -> bool {
if(!cartridge.load()) return false;
cpu.load();
serializeInit();
this->interface = interface;
information.colorburst = Emulator::Constants::Colorburst::NTSC;
return information.loaded = true;

View File

@ -6,6 +6,7 @@ struct System {
inline auto colorburst() const -> double { return information.colorburst; }
auto run() -> void;
auto runToSave() -> void;
auto load(Emulator::Interface*, Model) -> bool;
auto save() -> void;
@ -13,6 +14,13 @@ struct System {
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;
@ -21,6 +29,7 @@ private:
Model model = Model::PCEngine;
string manifest;
double colorburst = 0.0;
uint serializeSize = 0;
} 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;
#include "memory.cpp"
#include "io.cpp"
#include "serialization.cpp"
auto VCE::Enter() -> void {
while(true) scheduler.synchronize(), vce.main();

View File

@ -13,6 +13,9 @@ struct VCE : Thread {
auto read(uint3 addr) -> uint8;
auto write(uint3 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
uint32 buffer[1365 * 263];
@ -21,10 +24,8 @@ private:
auto read(uint9 addr) -> uint9;
auto write(uint9 addr, bool a0, uint8 data) -> void;
uint9 address;
private:
uint9 data[0x200];
uint9 address;
} cram;
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 "background.cpp"
#include "sprite.cpp"
#include "serialization.cpp"
auto VDC::Enter() -> void {
while(true) {

View File

@ -16,6 +16,9 @@ struct VDC : Thread {
auto read(uint2 addr) -> uint8;
auto write(uint2 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private:
uint9 data;
@ -24,15 +27,14 @@ private:
auto read(uint16 addr) -> uint16;
auto write(uint16 addr, uint16 data) -> void;
uint16 data[0x8000];
uint16 addressRead;
uint16 addressWrite;
uint16 addressIncrement;
uint16 dataRead;
uint16 dataWrite;
private:
uint16 data[0x8000];
} vram;
struct SATB {
@ -40,7 +42,6 @@ private:
auto read(uint8 addr) -> uint16;
auto write(uint8 addr, uint16 data) -> void;
private:
uint16 data[0x100];
} satb;
@ -191,10 +192,6 @@ private:
uint2 vramAccess;
uint2 spriteAccess;
bool cgMode;
//$0400 CR
bool colorBlur;
bool grayscale;
} 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 {
VPC vpc;
#include "serialization.cpp"
auto VPC::bus(uint hclock) -> uint9 {
//bus values are direct CRAM entry indexes:

View File

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

View File

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

View File

@ -106,6 +106,9 @@ struct HuC6280 {
//disassembler.cpp
auto disassemble(uint16 pc) -> string;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Flags {
bool c; //carry
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);
}