mirror of https://github.com/bsnes-emu/bsnes.git
Update to v102r09 release.
byuu says: Changelog: - MD: restructured DMA to a subclass of VDP - MD: implemented VRAM copy mode (fixes Langrisser II ... mostly) - MS: implemened PSG support [Cydrak] - GG: implemented PSG stereo sound support - MS: use the new struct Model {} design that other cores use The MS/GG PSG should be feature complete, but I don't have good tests for Game Gear stereo mode, nor for the noise channel. There's also a really weird behavior with when to reload the channel counters on volume register writes. I can confirm what Cydrak observed in that following the docs and reloading always creates serious audio distortion problems. So, more research is needed there. To get the correct sound out of the PSG, I have to run it at 3.58MHz / 16, which seems really weird to me. The docs make it sound like it's supposed to run at the full 3.58MHz. If we can really run it at 223.7KHz, then that's help reduce the overhead of PSG emulation, which will definitely come in handy for Mega Drive, and possibly later Mega CD, emulation. I have not implemented the PSG into the Mega Drive just yet. Nor have I implemented save states or cheat code support into the MS/GG cores yet. The latter is next on my list.
This commit is contained in:
parent
d76c0c7e82
commit
8071da4c6a
|
@ -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.08";
|
static const string Version = "102.09";
|
||||||
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/";
|
||||||
|
|
|
@ -1,36 +1,45 @@
|
||||||
auto VDP::dmaRun() -> void {
|
auto VDP::DMA::run() -> void {
|
||||||
if(!io.dmaEnable) return;
|
if(!io.enable || io.wait) return;
|
||||||
if(!io.command.bit(5)) return;
|
if(!vdp.io.command.bit(5)) return;
|
||||||
|
|
||||||
if(io.dmaMode <= 1) return dmaLoad();
|
if(io.mode <= 1) return load();
|
||||||
if(io.dmaMode == 2) return dmaFill();
|
if(io.mode == 2) return fill();
|
||||||
if(io.dmaMode == 3) return dmaCopy();
|
if(io.mode == 3) return copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VDP::dmaLoad() -> void {
|
auto VDP::DMA::load() -> void {
|
||||||
cpu.wait |= Wait::VDP_DMA;
|
cpu.wait |= Wait::VDP_DMA;
|
||||||
|
|
||||||
auto data = busCPU.readWord(io.dmaMode.bit(0) << 23 | io.dmaSource << 1);
|
auto data = busCPU.readWord(io.mode.bit(0) << 23 | io.source << 1);
|
||||||
writeDataPort(data);
|
vdp.writeDataPort(data);
|
||||||
|
|
||||||
io.dmaSource.bits(0,15)++;
|
io.source.bits(0,15)++;
|
||||||
if(--io.dmaLength == 0) {
|
if(--io.length == 0) {
|
||||||
io.command.bit(5) = 0;
|
vdp.io.command.bit(5) = 0;
|
||||||
cpu.wait &=~ Wait::VDP_DMA;
|
cpu.wait &=~ Wait::VDP_DMA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VDP::dmaFill() -> void {
|
auto VDP::DMA::fill() -> void {
|
||||||
if(io.dmaFillWait) return;
|
auto data = io.fill;
|
||||||
|
vdp.writeDataPort(data << 8 | data << 0);
|
||||||
|
|
||||||
auto data = io.dmaFillByte;
|
io.source.bits(0,15)++;
|
||||||
writeDataPort(data << 8 | data << 0);
|
if(--io.length == 0) {
|
||||||
|
vdp.io.command.bit(5) = 0;
|
||||||
io.dmaSource.bits(0,15)++;
|
|
||||||
if(--io.dmaLength == 0) {
|
|
||||||
io.command.bit(5) = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VDP::dmaCopy() -> void {
|
auto VDP::DMA::copy() -> void {
|
||||||
|
auto data = vdp.vram[io.source.bits(0,14)];
|
||||||
|
vdp.writeDataPort(data);
|
||||||
|
|
||||||
|
io.source.bits(0,15)++;
|
||||||
|
if(--io.length == 0) {
|
||||||
|
vdp.io.command.bit(5) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto VDP::DMA::power() -> void {
|
||||||
|
memory::fill(&io, sizeof(IO));
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,9 @@ auto VDP::writeDataPort(uint16 data) -> void {
|
||||||
io.commandPending = false;
|
io.commandPending = false;
|
||||||
|
|
||||||
//DMA VRAM fill
|
//DMA VRAM fill
|
||||||
if(io.dmaFillWait.lower()) {
|
if(dma.io.wait.lower()) {
|
||||||
io.dmaFillByte = data >> 8;
|
dma.io.fill = data >> 8;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//VRAM write
|
//VRAM write
|
||||||
|
@ -132,7 +133,7 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
||||||
|
|
||||||
io.command.bits(2,5) = data.bits(4,7);
|
io.command.bits(2,5) = data.bits(4,7);
|
||||||
io.address.bits(14,15) = data.bits(0,1);
|
io.address.bits(14,15) = data.bits(0,1);
|
||||||
io.dmaFillWait = io.dmaMode == 2 && io.command.bits(4,5) == 2;
|
if(dma.io.mode == 3) dma.io.wait = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,12 +163,12 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
||||||
case 0x01: {
|
case 0x01: {
|
||||||
io.videoMode = data.bit(2);
|
io.videoMode = data.bit(2);
|
||||||
io.overscan = data.bit(3);
|
io.overscan = data.bit(3);
|
||||||
io.dmaEnable = data.bit(4);
|
dma.io.enable = data.bit(4);
|
||||||
io.verticalBlankInterruptEnable = data.bit(5);
|
io.verticalBlankInterruptEnable = data.bit(5);
|
||||||
io.displayEnable = data.bit(6);
|
io.displayEnable = data.bit(6);
|
||||||
io.externalVRAM = data.bit(7);
|
io.externalVRAM = data.bit(7);
|
||||||
|
|
||||||
if(!io.dmaEnable) io.command.bit(5) = 0;
|
if(!dma.io.enable) io.command.bit(5) = 0;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -280,32 +281,33 @@ auto VDP::writeControlPort(uint16 data) -> void {
|
||||||
|
|
||||||
//DMA length
|
//DMA length
|
||||||
case 0x13: {
|
case 0x13: {
|
||||||
io.dmaLength.bits(0,7) = data.bits(0,7);
|
dma.io.length.bits(0,7) = data.bits(0,7);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DMA length
|
//DMA length
|
||||||
case 0x14: {
|
case 0x14: {
|
||||||
io.dmaLength.bits(8,15) = data.bits(0,7);
|
dma.io.length.bits(8,15) = data.bits(0,7);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DMA source
|
//DMA source
|
||||||
case 0x15: {
|
case 0x15: {
|
||||||
io.dmaSource.bits(0,7) = data.bits(0,7);
|
dma.io.source.bits(0,7) = data.bits(0,7);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DMA source
|
//DMA source
|
||||||
case 0x16: {
|
case 0x16: {
|
||||||
io.dmaSource.bits(8,15) = data.bits(0,7);
|
dma.io.source.bits(8,15) = data.bits(0,7);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DMA source
|
//DMA source
|
||||||
case 0x17: {
|
case 0x17: {
|
||||||
io.dmaSource.bits(16,21) = data.bits(0,5);
|
dma.io.source.bits(16,21) = data.bits(0,5);
|
||||||
io.dmaMode = data.bits(6,7);
|
dma.io.mode = data.bits(6,7);
|
||||||
|
dma.io.wait = dma.io.mode.bit(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ auto VDP::main() -> void {
|
||||||
|
|
||||||
auto VDP::step(uint clocks) -> void {
|
auto VDP::step(uint clocks) -> void {
|
||||||
while(clocks--) {
|
while(clocks--) {
|
||||||
dmaRun();
|
dma.run();
|
||||||
Thread::step(1);
|
Thread::step(1);
|
||||||
synchronize(cpu);
|
synchronize(cpu);
|
||||||
synchronize(apu);
|
synchronize(apu);
|
||||||
|
@ -63,6 +63,7 @@ auto VDP::power() -> void {
|
||||||
window.power();
|
window.power();
|
||||||
planeB.power();
|
planeB.power();
|
||||||
sprite.power();
|
sprite.power();
|
||||||
|
dma.power();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,23 @@ struct VDP : Thread {
|
||||||
auto writeControlPort(uint16 data) -> void;
|
auto writeControlPort(uint16 data) -> void;
|
||||||
|
|
||||||
//dma.cpp
|
//dma.cpp
|
||||||
auto dmaRun() -> void;
|
struct DMA {
|
||||||
auto dmaLoad() -> void;
|
auto run() -> void;
|
||||||
auto dmaFill() -> void;
|
auto load() -> void;
|
||||||
auto dmaCopy() -> void;
|
auto fill() -> void;
|
||||||
|
auto copy() -> void;
|
||||||
|
|
||||||
|
auto power() -> void;
|
||||||
|
|
||||||
|
struct IO {
|
||||||
|
uint2 mode;
|
||||||
|
uint22 source;
|
||||||
|
uint16 length;
|
||||||
|
uint8 fill;
|
||||||
|
boolean enable;
|
||||||
|
boolean wait;
|
||||||
|
} io;
|
||||||
|
} dma;
|
||||||
|
|
||||||
//render.cpp
|
//render.cpp
|
||||||
auto scanline() -> void;
|
auto scanline() -> void;
|
||||||
|
@ -119,15 +132,10 @@ private:
|
||||||
auto screenHeight() const -> uint { return io.overscan ? 240 : 224; }
|
auto screenHeight() const -> uint { return io.overscan ? 240 : 224; }
|
||||||
|
|
||||||
uint16 vram[32768];
|
uint16 vram[32768];
|
||||||
uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware
|
|
||||||
uint9 cram[64];
|
uint9 cram[64];
|
||||||
uint10 vsram[40];
|
uint10 vsram[40];
|
||||||
|
|
||||||
struct IO {
|
struct IO {
|
||||||
//internal state
|
|
||||||
boolean dmaFillWait;
|
|
||||||
uint8 dmaFillByte;
|
|
||||||
|
|
||||||
//command
|
//command
|
||||||
uint6 command;
|
uint6 command;
|
||||||
uint16 address;
|
uint16 address;
|
||||||
|
@ -142,7 +150,6 @@ private:
|
||||||
//$01 mode register 2
|
//$01 mode register 2
|
||||||
uint1 videoMode; //0 = Master System; 1 = Mega Drive
|
uint1 videoMode; //0 = Master System; 1 = Mega Drive
|
||||||
uint1 overscan; //0 = 224 lines; 1 = 240 lines
|
uint1 overscan; //0 = 224 lines; 1 = 240 lines
|
||||||
uint1 dmaEnable;
|
|
||||||
uint1 verticalBlankInterruptEnable;
|
uint1 verticalBlankInterruptEnable;
|
||||||
uint1 displayEnable;
|
uint1 displayEnable;
|
||||||
uint1 externalVRAM;
|
uint1 externalVRAM;
|
||||||
|
@ -170,13 +177,6 @@ private:
|
||||||
|
|
||||||
//$0f data port auto-increment value
|
//$0f data port auto-increment value
|
||||||
uint8 dataIncrement;
|
uint8 dataIncrement;
|
||||||
|
|
||||||
//$13-$14 DMA length
|
|
||||||
uint16 dmaLength;
|
|
||||||
|
|
||||||
//$15-$17 DMA source
|
|
||||||
uint22 dmaSource;
|
|
||||||
uint2 dmaMode;
|
|
||||||
} io;
|
} io;
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
|
|
|
@ -19,7 +19,7 @@ auto Bus::in(uint8 addr) -> uint8 {
|
||||||
switch(addr >> 6) {
|
switch(addr >> 6) {
|
||||||
|
|
||||||
case 0: {
|
case 0: {
|
||||||
if(system.model() == Model::GameGear) {
|
if(Model::GameGear()) {
|
||||||
bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6);
|
bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6);
|
||||||
return start << 7 | 0x7f;
|
return start << 7 | 0x7f;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ auto Bus::in(uint8 addr) -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 3: {
|
case 3: {
|
||||||
if(system.model() == Model::MasterSystem) {
|
if(Model::MasterSystem()) {
|
||||||
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
|
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
|
||||||
auto port1 = peripherals.controllerPort1->readData();
|
auto port1 = peripherals.controllerPort1->readData();
|
||||||
auto port2 = peripherals.controllerPort2->readData();
|
auto port2 = peripherals.controllerPort2->readData();
|
||||||
|
@ -46,7 +46,8 @@ auto Bus::in(uint8 addr) -> uint8 {
|
||||||
return port2.bits(2,5) << 0 | reset << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7;
|
return port2.bits(2,5) << 0 | reset << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(system.model() == Model::GameGear) {
|
|
||||||
|
if(Model::GameGear()) {
|
||||||
bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0);
|
bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0);
|
||||||
bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1);
|
bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1);
|
||||||
bool left = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 2);
|
bool left = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 2);
|
||||||
|
@ -61,6 +62,7 @@ auto Bus::in(uint8 addr) -> uint8 {
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +72,16 @@ auto Bus::in(uint8 addr) -> uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Bus::out(uint8 addr, uint8 data) -> void {
|
auto Bus::out(uint8 addr, uint8 data) -> void {
|
||||||
|
if(addr == 0x06) {
|
||||||
|
if(Model::GameGear()) return psg.balance(data);
|
||||||
|
}
|
||||||
|
|
||||||
switch(addr >> 6) {
|
switch(addr >> 6) {
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
return psg.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
case 2: {
|
case 2: {
|
||||||
return !addr.bit(0) ? vdp.data(data) : vdp.control(data);
|
return !addr.bit(0) ? vdp.data(data) : vdp.control(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,16 @@ Cartridge cartridge;
|
||||||
auto Cartridge::load() -> bool {
|
auto Cartridge::load() -> bool {
|
||||||
information = {};
|
information = {};
|
||||||
|
|
||||||
switch(system.model()) {
|
if(Model::MasterSystem()) {
|
||||||
case Model::MasterSystem:
|
|
||||||
if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) {
|
if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) {
|
||||||
information.pathID = pathID();
|
information.pathID = pathID();
|
||||||
} else return false;
|
} else return false;
|
||||||
break;
|
}
|
||||||
case Model::GameGear:
|
|
||||||
|
if(Model::GameGear()) {
|
||||||
if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) {
|
if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) {
|
||||||
information.pathID = pathID();
|
information.pathID = pathID();
|
||||||
} else return false;
|
} else return false;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ auto CPU::step(uint clocks) -> void {
|
||||||
|
|
||||||
//called once per frame
|
//called once per frame
|
||||||
auto CPU::pollPause() -> void {
|
auto CPU::pollPause() -> void {
|
||||||
if(system.model() == Model::MasterSystem) {
|
if(Model::MasterSystem()) {
|
||||||
static bool pause = 0;
|
static bool pause = 0;
|
||||||
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);
|
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);
|
||||||
if(!pause && state) setNMI(1);
|
if(!pause && state) setNMI(1);
|
||||||
|
|
|
@ -53,6 +53,6 @@ auto GameGearInterface::videoColor(uint32 color) -> uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto GameGearInterface::load(uint id) -> bool {
|
auto GameGearInterface::load(uint id) -> bool {
|
||||||
if(id == ID::GameGear) return system.load(this, Model::GameGear);
|
if(id == ID::GameGear) return system.load(this, System::Model::GameGear);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,6 @@ auto MasterSystemInterface::videoColor(uint32 color) -> uint64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MasterSystemInterface::load(uint id) -> bool {
|
auto MasterSystemInterface::load(uint id) -> bool {
|
||||||
if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem);
|
if(id == ID::MasterSystem) return system.load(this, System::Model::MasterSystem);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,6 @@ namespace MasterSystem {
|
||||||
extern Scheduler scheduler;
|
extern Scheduler scheduler;
|
||||||
struct Interface;
|
struct Interface;
|
||||||
|
|
||||||
enum class Model : uint {
|
|
||||||
MasterSystem,
|
|
||||||
GameGear,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Thread : Emulator::Thread {
|
struct Thread : Emulator::Thread {
|
||||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||||
Emulator::Thread::create(entrypoint, frequency);
|
Emulator::Thread::create(entrypoint, frequency);
|
||||||
|
@ -32,6 +27,11 @@ namespace MasterSystem {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Model {
|
||||||
|
inline static auto MasterSystem() -> bool;
|
||||||
|
inline static auto GameGear() -> bool;
|
||||||
|
};
|
||||||
|
|
||||||
#include <ms/controller/controller.hpp>
|
#include <ms/controller/controller.hpp>
|
||||||
|
|
||||||
#include <ms/cpu/cpu.hpp>
|
#include <ms/cpu/cpu.hpp>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
//note: tone is supposed to reload counters on volume writes
|
||||||
|
//however, if this is always done, the output is very grainy
|
||||||
|
//as such, this behavior is suppressed when pitch >= 2 (which is a hack)
|
||||||
|
|
||||||
|
auto PSG::write(uint8 data) -> void {
|
||||||
|
bool l = data.bit(7);
|
||||||
|
if(l) select = data.bits(4,6);
|
||||||
|
|
||||||
|
switch(select) {
|
||||||
|
|
||||||
|
case 0: {
|
||||||
|
if(l) tone0.pitch.bits(0,3) = data.bits(0,3);
|
||||||
|
else tone0.pitch.bits(4,9) = data.bits(0,5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
tone0.volume = data.bits(0,3);
|
||||||
|
if(tone0.pitch < 2) {
|
||||||
|
tone0.output = 1;
|
||||||
|
tone0.counter = tone0.pitch;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
if(l) tone1.pitch.bits(0,3) = data.bits(0,3);
|
||||||
|
else tone1.pitch.bits(4,9) = data.bits(0,5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: {
|
||||||
|
tone1.volume = data.bits(0,3);
|
||||||
|
if(tone1.pitch < 2) {
|
||||||
|
tone1.output = 1;
|
||||||
|
tone1.counter = tone1.pitch;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4: {
|
||||||
|
if(l) tone2.pitch.bits(0,3) = data.bits(0,3);
|
||||||
|
else tone2.pitch.bits(4,9) = data.bits(0,5);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5: {
|
||||||
|
tone2.volume = data.bits(0,3);
|
||||||
|
if(tone2.pitch < 2) {
|
||||||
|
tone2.output = 1;
|
||||||
|
tone2.counter = tone2.pitch;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6: {
|
||||||
|
noise.rate = data.bits(0,1);
|
||||||
|
noise.enable = data.bit(2);
|
||||||
|
noise.lfsr = 0x8000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7: {
|
||||||
|
noise.volume = data.bits(0,3);
|
||||||
|
noise.output = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Game Gear only
|
||||||
|
auto PSG::balance(uint8 data) -> void {
|
||||||
|
tone0.right = data.bit(0);
|
||||||
|
tone1.right = data.bit(1);
|
||||||
|
tone2.right = data.bit(2);
|
||||||
|
noise.right = data.bit(3);
|
||||||
|
tone0.left = data.bit(4);
|
||||||
|
tone1.left = data.bit(5);
|
||||||
|
tone2.left = data.bit(6);
|
||||||
|
noise.left = data.bit(7);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
auto PSG::Noise::run() -> void {
|
||||||
|
auto latch = clock;
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
if(rate < 3) {
|
||||||
|
clock ^= counter & ((16 << rate) - 1) == 0;
|
||||||
|
} else {
|
||||||
|
clock ^= psg.tone2.clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!latch && clock) {
|
||||||
|
auto eor = enable ? ~lfsr >> 3 : 0;
|
||||||
|
lfsr = (lfsr ^ eor) << 15 | lfsr >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = lfsr.bit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PSG::Noise::power() -> void {
|
||||||
|
volume = ~0;
|
||||||
|
counter = 0;
|
||||||
|
enable = 0;
|
||||||
|
rate = 0;
|
||||||
|
lfsr = 0x8000;
|
||||||
|
clock = 0;
|
||||||
|
output = 0;
|
||||||
|
|
||||||
|
left = 1;
|
||||||
|
right = 1;
|
||||||
|
}
|
|
@ -3,14 +3,42 @@
|
||||||
namespace MasterSystem {
|
namespace MasterSystem {
|
||||||
|
|
||||||
PSG psg;
|
PSG psg;
|
||||||
|
#include "io.cpp"
|
||||||
|
#include "tone.cpp"
|
||||||
|
#include "noise.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 {
|
||||||
|
tone0.run();
|
||||||
|
tone1.run();
|
||||||
|
tone2.run();
|
||||||
|
noise.run();
|
||||||
|
|
||||||
|
int left = 0;
|
||||||
|
if(tone0.output && tone0.left) left += levels[tone0.volume];
|
||||||
|
if(tone1.output && tone1.left) left += levels[tone1.volume];
|
||||||
|
if(tone2.output && tone2.left) left += levels[tone2.volume];
|
||||||
|
if(noise.output && noise.left) left += levels[noise.volume];
|
||||||
|
|
||||||
|
lowpassLeft += (left - lowpassLeft) * 20.0 / 256.0;
|
||||||
|
left = left * 2.0 / 6.0 + lowpassLeft * 3.0 / 4.0;
|
||||||
|
left = sclamp<16>(left);
|
||||||
|
|
||||||
|
int right = 0;
|
||||||
|
if(tone0.output && tone0.right) right += levels[tone0.volume];
|
||||||
|
if(tone1.output && tone1.right) right += levels[tone1.volume];
|
||||||
|
if(tone2.output && tone2.right) right += levels[tone2.volume];
|
||||||
|
if(noise.output && noise.right) right += levels[noise.volume];
|
||||||
|
|
||||||
|
lowpassRight += (right - lowpassRight) * 20.0 / 256.0;
|
||||||
|
right = right * 2.0 / 6.0 + lowpassRight * 3.0 / 4.0;
|
||||||
|
right = sclamp<16>(right);
|
||||||
|
|
||||||
step(1);
|
step(1);
|
||||||
stream->sample(0.0, 0.0);
|
stream->sample(left / 32768.0, right / 32768.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PSG::step(uint clocks) -> void {
|
auto PSG::step(uint clocks) -> void {
|
||||||
|
@ -19,8 +47,23 @@ auto PSG::step(uint clocks) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto PSG::power() -> void {
|
auto PSG::power() -> void {
|
||||||
create(PSG::Enter, system.colorburst());
|
//Master System is monaural; Game Gear is stereo
|
||||||
stream = Emulator::audio.createStream(2, system.colorburst());
|
//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());
|
||||||
|
|
||||||
|
select = 0;
|
||||||
|
lowpassLeft = 0;
|
||||||
|
lowpassRight = 0;
|
||||||
|
for(auto n : range(15)) {
|
||||||
|
levels[n] = 0x3fff * pow(2, n * -2.0 / 6.0) + 0.5;
|
||||||
|
}
|
||||||
|
levels[15] = 0;
|
||||||
|
|
||||||
|
tone0.power();
|
||||||
|
tone1.power();
|
||||||
|
tone2.power();
|
||||||
|
noise.power();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,48 @@ struct PSG : Thread {
|
||||||
auto step(uint clocks) -> void;
|
auto step(uint clocks) -> void;
|
||||||
|
|
||||||
auto power() -> void;
|
auto power() -> void;
|
||||||
|
|
||||||
|
//io.cpp
|
||||||
|
auto write(uint8 data) -> void;
|
||||||
|
auto balance(uint8 data) -> void;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Tone {
|
||||||
|
//tone.cpp
|
||||||
|
auto run() -> void;
|
||||||
|
auto power() -> void;
|
||||||
|
|
||||||
|
uint4 volume;
|
||||||
|
uint10 counter;
|
||||||
|
uint10 pitch;
|
||||||
|
uint1 clock;
|
||||||
|
uint1 output;
|
||||||
|
|
||||||
|
uint1 left;
|
||||||
|
uint1 right;
|
||||||
|
} tone0, tone1, tone2;
|
||||||
|
|
||||||
|
struct Noise {
|
||||||
|
//noise.cpp
|
||||||
|
auto run() -> void;
|
||||||
|
auto power() -> void;
|
||||||
|
|
||||||
|
uint4 volume;
|
||||||
|
uint6 counter;
|
||||||
|
uint1 enable;
|
||||||
|
uint2 rate;
|
||||||
|
uint16 lfsr;
|
||||||
|
uint1 clock;
|
||||||
|
uint1 output;
|
||||||
|
|
||||||
|
uint1 left;
|
||||||
|
uint1 right;
|
||||||
|
} noise;
|
||||||
|
|
||||||
|
uint3 select;
|
||||||
|
int lowpassLeft;
|
||||||
|
int lowpassRight;
|
||||||
|
uint16 levels[16];
|
||||||
};
|
};
|
||||||
|
|
||||||
extern PSG psg;
|
extern PSG psg;
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
auto PSG::Tone::run() -> void {
|
||||||
|
clock = 0;
|
||||||
|
if(--counter) return;
|
||||||
|
|
||||||
|
clock = 1;
|
||||||
|
counter = pitch;
|
||||||
|
output ^= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PSG::Tone::power() -> void {
|
||||||
|
volume = ~0;
|
||||||
|
counter = 0;
|
||||||
|
pitch = 0;
|
||||||
|
clock = 0;
|
||||||
|
output = 0;
|
||||||
|
|
||||||
|
left = 1;
|
||||||
|
right = 1;
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ auto Peripherals::reset() -> void {
|
||||||
auto Peripherals::connect(uint port, uint device) -> void {
|
auto Peripherals::connect(uint port, uint device) -> void {
|
||||||
cpu.peripherals.reset();
|
cpu.peripherals.reset();
|
||||||
|
|
||||||
if(system.model() == Model::MasterSystem) {
|
if(Model::MasterSystem()) {
|
||||||
if(port == ID::Port::Controller1) {
|
if(port == ID::Port::Controller1) {
|
||||||
settings.controllerPort1 = device;
|
settings.controllerPort1 = device;
|
||||||
if(!system.loaded()) return;
|
if(!system.loaded()) return;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
struct System {
|
struct System {
|
||||||
|
enum class Model : uint { MasterSystem, GameGear };
|
||||||
|
|
||||||
auto loaded() const -> bool { return information.loaded; }
|
auto loaded() const -> bool { return information.loaded; }
|
||||||
auto model() const -> Model { return information.model; }
|
auto model() const -> Model { return information.model; }
|
||||||
auto colorburst() const -> double { return information.colorburst; }
|
auto colorburst() const -> double { return information.colorburst; }
|
||||||
|
@ -33,3 +35,6 @@ struct Peripherals {
|
||||||
|
|
||||||
extern System system;
|
extern System system;
|
||||||
extern Peripherals peripherals;
|
extern Peripherals peripherals;
|
||||||
|
|
||||||
|
auto Model::MasterSystem() -> bool { return system.model() == System::Model::MasterSystem; }
|
||||||
|
auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; }
|
||||||
|
|
|
@ -51,8 +51,8 @@ auto VDP::data(uint8 data) -> void {
|
||||||
vram[io.address++] = data;
|
vram[io.address++] = data;
|
||||||
} else {
|
} else {
|
||||||
uint mask = 0;
|
uint mask = 0;
|
||||||
if(system.model() == Model::MasterSystem) mask = 0x1f;
|
if(Model::MasterSystem()) mask = 0x1f;
|
||||||
if(system.model() == Model::GameGear) mask = 0x3f;
|
if(Model::GameGear()) mask = 0x3f;
|
||||||
cram[io.address++ & mask] = data;
|
cram[io.address++ & mask] = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ auto VDP::step(uint clocks) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VDP::refresh() -> void {
|
auto VDP::refresh() -> void {
|
||||||
if(system.model() == Model::MasterSystem) {
|
if(Model::MasterSystem()) {
|
||||||
//center the video output vertically in the viewport
|
//center the video output vertically in the viewport
|
||||||
uint32* screen = buffer;
|
uint32* screen = buffer;
|
||||||
if(vlines() == 224) screen += 16 * 256;
|
if(vlines() == 224) screen += 16 * 256;
|
||||||
|
@ -81,7 +81,7 @@ auto VDP::refresh() -> void {
|
||||||
Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 240);
|
Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(system.model() == Model::GameGear) {
|
if(Model::GameGear()) {
|
||||||
Emulator::video.refresh(buffer + 48 * 256 + 48, 256 * sizeof(uint32), 160, 144);
|
Emulator::video.refresh(buffer + 48 * 256 + 48, 256 * sizeof(uint32), 160, 144);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,11 +107,11 @@ auto VDP::power() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VDP::palette(uint5 index) -> uint12 {
|
auto VDP::palette(uint5 index) -> uint12 {
|
||||||
if(system.model() == Model::MasterSystem) {
|
if(Model::MasterSystem()) {
|
||||||
return cram[index];
|
return cram[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(system.model() == Model::GameGear) {
|
if(Model::GameGear()) {
|
||||||
return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8;
|
return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue