diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 681fe677..d6d9f093 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { 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 License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/md/vdp/dma.cpp b/higan/md/vdp/dma.cpp index 67f544be..082ebde1 100644 --- a/higan/md/vdp/dma.cpp +++ b/higan/md/vdp/dma.cpp @@ -1,36 +1,45 @@ -auto VDP::dmaRun() -> void { - if(!io.dmaEnable) return; - if(!io.command.bit(5)) return; +auto VDP::DMA::run() -> void { + if(!io.enable || io.wait) return; + if(!vdp.io.command.bit(5)) return; - if(io.dmaMode <= 1) return dmaLoad(); - if(io.dmaMode == 2) return dmaFill(); - if(io.dmaMode == 3) return dmaCopy(); + if(io.mode <= 1) return load(); + if(io.mode == 2) return fill(); + if(io.mode == 3) return copy(); } -auto VDP::dmaLoad() -> void { +auto VDP::DMA::load() -> void { cpu.wait |= Wait::VDP_DMA; - auto data = busCPU.readWord(io.dmaMode.bit(0) << 23 | io.dmaSource << 1); - writeDataPort(data); + auto data = busCPU.readWord(io.mode.bit(0) << 23 | io.source << 1); + vdp.writeDataPort(data); - io.dmaSource.bits(0,15)++; - if(--io.dmaLength == 0) { - io.command.bit(5) = 0; + io.source.bits(0,15)++; + if(--io.length == 0) { + vdp.io.command.bit(5) = 0; cpu.wait &=~ Wait::VDP_DMA; } } -auto VDP::dmaFill() -> void { - if(io.dmaFillWait) return; +auto VDP::DMA::fill() -> void { + auto data = io.fill; + vdp.writeDataPort(data << 8 | data << 0); - auto data = io.dmaFillByte; - writeDataPort(data << 8 | data << 0); - - io.dmaSource.bits(0,15)++; - if(--io.dmaLength == 0) { - io.command.bit(5) = 0; + io.source.bits(0,15)++; + if(--io.length == 0) { + vdp.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)); } diff --git a/higan/md/vdp/io.cpp b/higan/md/vdp/io.cpp index 66932ddc..28192d1f 100644 --- a/higan/md/vdp/io.cpp +++ b/higan/md/vdp/io.cpp @@ -74,8 +74,9 @@ auto VDP::writeDataPort(uint16 data) -> void { io.commandPending = false; //DMA VRAM fill - if(io.dmaFillWait.lower()) { - io.dmaFillByte = data >> 8; + if(dma.io.wait.lower()) { + dma.io.fill = data >> 8; + return; } //VRAM write @@ -132,7 +133,7 @@ auto VDP::writeControlPort(uint16 data) -> void { io.command.bits(2,5) = data.bits(4,7); 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; } @@ -162,12 +163,12 @@ auto VDP::writeControlPort(uint16 data) -> void { case 0x01: { io.videoMode = data.bit(2); io.overscan = data.bit(3); - io.dmaEnable = data.bit(4); + dma.io.enable = data.bit(4); io.verticalBlankInterruptEnable = data.bit(5); io.displayEnable = data.bit(6); io.externalVRAM = data.bit(7); - if(!io.dmaEnable) io.command.bit(5) = 0; + if(!dma.io.enable) io.command.bit(5) = 0; return; } @@ -280,32 +281,33 @@ auto VDP::writeControlPort(uint16 data) -> void { //DMA length case 0x13: { - io.dmaLength.bits(0,7) = data.bits(0,7); + dma.io.length.bits(0,7) = data.bits(0,7); return; } //DMA length case 0x14: { - io.dmaLength.bits(8,15) = data.bits(0,7); + dma.io.length.bits(8,15) = data.bits(0,7); return; } //DMA source case 0x15: { - io.dmaSource.bits(0,7) = data.bits(0,7); + dma.io.source.bits(0,7) = data.bits(0,7); return; } //DMA source case 0x16: { - io.dmaSource.bits(8,15) = data.bits(0,7); + dma.io.source.bits(8,15) = data.bits(0,7); return; } //DMA source case 0x17: { - io.dmaSource.bits(16,21) = data.bits(0,5); - io.dmaMode = data.bits(6,7); + dma.io.source.bits(16,21) = data.bits(0,5); + dma.io.mode = data.bits(6,7); + dma.io.wait = dma.io.mode.bit(1); return; } diff --git a/higan/md/vdp/vdp.cpp b/higan/md/vdp/vdp.cpp index e465d99c..38f6ec67 100644 --- a/higan/md/vdp/vdp.cpp +++ b/higan/md/vdp/vdp.cpp @@ -42,7 +42,7 @@ auto VDP::main() -> void { auto VDP::step(uint clocks) -> void { while(clocks--) { - dmaRun(); + dma.run(); Thread::step(1); synchronize(cpu); synchronize(apu); @@ -63,6 +63,7 @@ auto VDP::power() -> void { window.power(); planeB.power(); sprite.power(); + dma.power(); } } diff --git a/higan/md/vdp/vdp.hpp b/higan/md/vdp/vdp.hpp index c1169cb6..2c1eb505 100644 --- a/higan/md/vdp/vdp.hpp +++ b/higan/md/vdp/vdp.hpp @@ -19,10 +19,23 @@ struct VDP : Thread { auto writeControlPort(uint16 data) -> void; //dma.cpp - auto dmaRun() -> void; - auto dmaLoad() -> void; - auto dmaFill() -> void; - auto dmaCopy() -> void; + struct DMA { + auto run() -> void; + auto load() -> 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 auto scanline() -> void; @@ -119,15 +132,10 @@ private: auto screenHeight() const -> uint { return io.overscan ? 240 : 224; } uint16 vram[32768]; - uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware uint9 cram[64]; uint10 vsram[40]; struct IO { - //internal state - boolean dmaFillWait; - uint8 dmaFillByte; - //command uint6 command; uint16 address; @@ -142,7 +150,6 @@ private: //$01 mode register 2 uint1 videoMode; //0 = Master System; 1 = Mega Drive uint1 overscan; //0 = 224 lines; 1 = 240 lines - uint1 dmaEnable; uint1 verticalBlankInterruptEnable; uint1 displayEnable; uint1 externalVRAM; @@ -170,13 +177,6 @@ private: //$0f data port auto-increment value uint8 dataIncrement; - - //$13-$14 DMA length - uint16 dmaLength; - - //$15-$17 DMA source - uint22 dmaSource; - uint2 dmaMode; } io; struct State { diff --git a/higan/ms/bus/bus.cpp b/higan/ms/bus/bus.cpp index 36af0197..b85d9055 100644 --- a/higan/ms/bus/bus.cpp +++ b/higan/ms/bus/bus.cpp @@ -19,7 +19,7 @@ auto Bus::in(uint8 addr) -> uint8 { switch(addr >> 6) { case 0: { - if(system.model() == Model::GameGear) { + if(Model::GameGear()) { bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6); return start << 7 | 0x7f; } @@ -36,7 +36,7 @@ auto Bus::in(uint8 addr) -> uint8 { } case 3: { - if(system.model() == Model::MasterSystem) { + if(Model::MasterSystem()) { bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0); auto port1 = peripherals.controllerPort1->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; } } - if(system.model() == Model::GameGear) { + + if(Model::GameGear()) { bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0); bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1); 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; } @@ -70,8 +72,16 @@ auto Bus::in(uint8 addr) -> uint8 { } auto Bus::out(uint8 addr, uint8 data) -> void { + if(addr == 0x06) { + if(Model::GameGear()) return psg.balance(data); + } + switch(addr >> 6) { + case 1: { + return psg.write(data); + } + case 2: { return !addr.bit(0) ? vdp.data(data) : vdp.control(data); } diff --git a/higan/ms/cartridge/cartridge.cpp b/higan/ms/cartridge/cartridge.cpp index 4c25e486..91566cb7 100644 --- a/higan/ms/cartridge/cartridge.cpp +++ b/higan/ms/cartridge/cartridge.cpp @@ -8,17 +8,16 @@ Cartridge cartridge; auto Cartridge::load() -> bool { information = {}; - switch(system.model()) { - case Model::MasterSystem: + if(Model::MasterSystem()) { if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) { information.pathID = pathID(); } else return false; - break; - case Model::GameGear: + } + + if(Model::GameGear()) { if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) { information.pathID = pathID(); } else return false; - break; } if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) { diff --git a/higan/ms/cpu/cpu.cpp b/higan/ms/cpu/cpu.cpp index c616bd8d..1ade980e 100644 --- a/higan/ms/cpu/cpu.cpp +++ b/higan/ms/cpu/cpu.cpp @@ -31,7 +31,7 @@ auto CPU::step(uint clocks) -> void { //called once per frame auto CPU::pollPause() -> void { - if(system.model() == Model::MasterSystem) { + if(Model::MasterSystem()) { static bool pause = 0; bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1); if(!pause && state) setNMI(1); diff --git a/higan/ms/interface/game-gear.cpp b/higan/ms/interface/game-gear.cpp index 38d79dc0..b876084a 100644 --- a/higan/ms/interface/game-gear.cpp +++ b/higan/ms/interface/game-gear.cpp @@ -53,6 +53,6 @@ auto GameGearInterface::videoColor(uint32 color) -> uint64 { } 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; } diff --git a/higan/ms/interface/master-system.cpp b/higan/ms/interface/master-system.cpp index 7bbf3d17..3d208e5d 100644 --- a/higan/ms/interface/master-system.cpp +++ b/higan/ms/interface/master-system.cpp @@ -69,6 +69,6 @@ auto MasterSystemInterface::videoColor(uint32 color) -> uint64 { } 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; } diff --git a/higan/ms/ms.hpp b/higan/ms/ms.hpp index c3ba221b..f7a2f13a 100644 --- a/higan/ms/ms.hpp +++ b/higan/ms/ms.hpp @@ -16,11 +16,6 @@ namespace MasterSystem { extern Scheduler scheduler; struct Interface; - enum class Model : uint { - MasterSystem, - GameGear, - }; - struct Thread : Emulator::Thread { auto create(auto (*entrypoint)() -> void, double frequency) -> void { Emulator::Thread::create(entrypoint, frequency); @@ -32,6 +27,11 @@ namespace MasterSystem { } }; + struct Model { + inline static auto MasterSystem() -> bool; + inline static auto GameGear() -> bool; + }; + #include #include diff --git a/higan/ms/psg/io.cpp b/higan/ms/psg/io.cpp new file mode 100644 index 00000000..844b4345 --- /dev/null +++ b/higan/ms/psg/io.cpp @@ -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); +} diff --git a/higan/ms/psg/noise.cpp b/higan/ms/psg/noise.cpp new file mode 100644 index 00000000..e3c41123 --- /dev/null +++ b/higan/ms/psg/noise.cpp @@ -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; +} diff --git a/higan/ms/psg/psg.cpp b/higan/ms/psg/psg.cpp index 7c2376c2..d824ef69 100644 --- a/higan/ms/psg/psg.cpp +++ b/higan/ms/psg/psg.cpp @@ -3,14 +3,42 @@ namespace MasterSystem { PSG psg; +#include "io.cpp" +#include "tone.cpp" +#include "noise.cpp" auto PSG::Enter() -> void { while(true) scheduler.synchronize(), psg.main(); } 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); - stream->sample(0.0, 0.0); + stream->sample(left / 32768.0, right / 32768.0); } auto PSG::step(uint clocks) -> void { @@ -19,8 +47,23 @@ auto PSG::step(uint clocks) -> void { } auto PSG::power() -> void { - create(PSG::Enter, system.colorburst()); - stream = Emulator::audio.createStream(2, system.colorburst()); + //Master System is monaural; Game Gear is stereo + //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(); } } diff --git a/higan/ms/psg/psg.hpp b/higan/ms/psg/psg.hpp index 9b11454b..69d3c1fd 100644 --- a/higan/ms/psg/psg.hpp +++ b/higan/ms/psg/psg.hpp @@ -8,6 +8,48 @@ struct PSG : Thread { auto step(uint clocks) -> 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; diff --git a/higan/ms/psg/tone.cpp b/higan/ms/psg/tone.cpp new file mode 100644 index 00000000..35877323 --- /dev/null +++ b/higan/ms/psg/tone.cpp @@ -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; +} diff --git a/higan/ms/system/peripherals.cpp b/higan/ms/system/peripherals.cpp index 5133ebdb..da23e6b0 100644 --- a/higan/ms/system/peripherals.cpp +++ b/higan/ms/system/peripherals.cpp @@ -15,7 +15,7 @@ auto Peripherals::reset() -> void { auto Peripherals::connect(uint port, uint device) -> void { cpu.peripherals.reset(); - if(system.model() == Model::MasterSystem) { + if(Model::MasterSystem()) { if(port == ID::Port::Controller1) { settings.controllerPort1 = device; if(!system.loaded()) return; diff --git a/higan/ms/system/system.hpp b/higan/ms/system/system.hpp index 8d8e1240..d1ee6e89 100644 --- a/higan/ms/system/system.hpp +++ b/higan/ms/system/system.hpp @@ -1,4 +1,6 @@ struct System { + enum class Model : uint { MasterSystem, GameGear }; + auto loaded() const -> bool { return information.loaded; } auto model() const -> Model { return information.model; } auto colorburst() const -> double { return information.colorburst; } @@ -33,3 +35,6 @@ struct Peripherals { extern System system; extern Peripherals peripherals; + +auto Model::MasterSystem() -> bool { return system.model() == System::Model::MasterSystem; } +auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; } diff --git a/higan/ms/vdp/io.cpp b/higan/ms/vdp/io.cpp index 3a716a46..d8a35eb1 100644 --- a/higan/ms/vdp/io.cpp +++ b/higan/ms/vdp/io.cpp @@ -51,8 +51,8 @@ auto VDP::data(uint8 data) -> void { vram[io.address++] = data; } else { uint mask = 0; - if(system.model() == Model::MasterSystem) mask = 0x1f; - if(system.model() == Model::GameGear) mask = 0x3f; + if(Model::MasterSystem()) mask = 0x1f; + if(Model::GameGear()) mask = 0x3f; cram[io.address++ & mask] = data; } } diff --git a/higan/ms/vdp/vdp.cpp b/higan/ms/vdp/vdp.cpp index 3568e024..b8025a24 100644 --- a/higan/ms/vdp/vdp.cpp +++ b/higan/ms/vdp/vdp.cpp @@ -72,7 +72,7 @@ auto VDP::step(uint clocks) -> void { } auto VDP::refresh() -> void { - if(system.model() == Model::MasterSystem) { + if(Model::MasterSystem()) { //center the video output vertically in the viewport uint32* screen = buffer; if(vlines() == 224) screen += 16 * 256; @@ -81,7 +81,7 @@ auto VDP::refresh() -> void { 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); } } @@ -107,11 +107,11 @@ auto VDP::power() -> void { } auto VDP::palette(uint5 index) -> uint12 { - if(system.model() == Model::MasterSystem) { + if(Model::MasterSystem()) { return cram[index]; } - if(system.model() == Model::GameGear) { + if(Model::GameGear()) { return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8; }