From 90da691717040cd902a84c06fa2fe4b658490253 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 21 Dec 2018 11:01:14 +1100 Subject: [PATCH] Update to v106r67 release. byuu says: Changelog: - added all pre-requisite to make install rule (note: only for higan, icarus so far) - added SG-1000 emulation - added SC-3000 emulation (no keyboard support yet) - added MS graphics mode 1 emulation (SC-1000) - added MS graphics mode 2 emulation (F-16 Fighter) - improve Audio::process() to prevent a possible hang - higan: repeat monaural audio to both left+right speakers - icarus: add heuristics for importing MSX games (not emulated in higan yet in this WIP) - added DC bias removal filter [jsd1982] - improved Audio::Stream::reset() [jsd1982] I was under the impression that the 20hz highpass filter would have removed DC bias ... if not, then I don't know why I added that filter to all of the emulation cores that have it. In any case, if anyone is up for helping me out ... if we could analyze the output with and without the DC bias filter to see if it's actually helping, then I'll enable it if it is. To enable it, edit higan/audio/stream.cpp::addDCRemovalFilter() and remove the return statement at the top of the function. --- higan/audio/audio.cpp | 2 +- higan/audio/audio.hpp | 16 ++- higan/audio/stream.cpp | 62 ++++++---- higan/emulator/emulator.hpp | 2 +- higan/fc/apu/apu.cpp | 7 +- higan/gb/apu/apu.cpp | 3 +- higan/gba/apu/apu.cpp | 3 +- higan/md/psg/psg.cpp | 5 +- higan/md/ym2612/ym2612.cpp | 5 +- higan/ms/cartridge/cartridge.cpp | 14 +++ higan/ms/controller/controller.cpp | 2 +- higan/ms/cpu/bus.cpp | 10 ++ higan/ms/cpu/cpu.cpp | 7 ++ higan/ms/interface/interface.cpp | 2 + higan/ms/interface/interface.hpp | 36 ++++++ higan/ms/interface/sc-3000.cpp | 105 ++++++++++++++++ higan/ms/interface/sg-1000.cpp | 105 ++++++++++++++++ higan/ms/ms.hpp | 2 + higan/ms/psg/psg.cpp | 3 +- higan/ms/system/system.cpp | 4 +- higan/ms/system/system.hpp | 4 +- higan/ms/vdp/background.cpp | 138 +++++++++++++++------ higan/ms/vdp/io.cpp | 39 +++--- higan/ms/vdp/serialization.cpp | 20 ++-- higan/ms/vdp/sprite.cpp | 153 +++++++++++++++++------- higan/ms/vdp/vdp.cpp | 39 +++--- higan/ms/vdp/vdp.hpp | 49 ++++---- higan/pce/psg/psg.cpp | 3 +- higan/sfc/coprocessor/icd/icd.cpp | 3 +- higan/systems/SC-3000.sys/manifest.bml | 1 + higan/systems/SG-1000.sys/manifest.bml | 1 + higan/target-bsnes/GNUmakefile | 2 +- higan/target-higan/GNUmakefile | 2 +- higan/target-higan/program/platform.cpp | 7 +- higan/target-higan/program/program.cpp | 6 + higan/ws/apu/apu.cpp | 3 +- icarus/GNUmakefile | 2 +- icarus/core/core.cpp | 13 +- icarus/core/core.hpp | 18 +++ icarus/core/msx.cpp | 39 ++++++ icarus/core/sc-3000.cpp | 39 ++++++ icarus/core/sg-1000.cpp | 39 ++++++ icarus/heuristics/msx.cpp | 31 +++++ icarus/heuristics/sc-3000.cpp | 32 +++++ icarus/heuristics/sg-1000.cpp | 32 +++++ icarus/icarus.cpp | 11 +- icarus/ui/scan-dialog.cpp | 10 +- nall/dsp/iir/dc-removal.hpp | 29 +++++ 48 files changed, 942 insertions(+), 218 deletions(-) create mode 100644 higan/ms/interface/sc-3000.cpp create mode 100644 higan/ms/interface/sg-1000.cpp create mode 100644 higan/systems/SC-3000.sys/manifest.bml create mode 100644 higan/systems/SG-1000.sys/manifest.bml create mode 100644 icarus/core/msx.cpp create mode 100644 icarus/core/sc-3000.cpp create mode 100644 icarus/core/sg-1000.cpp create mode 100644 icarus/heuristics/msx.cpp create mode 100644 icarus/heuristics/sc-3000.cpp create mode 100644 icarus/heuristics/sg-1000.cpp create mode 100644 nall/dsp/iir/dc-removal.hpp diff --git a/higan/audio/audio.cpp b/higan/audio/audio.cpp index 22132005..dd3ba81e 100644 --- a/higan/audio/audio.cpp +++ b/higan/audio/audio.cpp @@ -39,7 +39,7 @@ auto Audio::createStream(uint channels, double frequency) -> shared_pointer void { - while(true) { + while(streams) { for(auto& stream : streams) { if(!stream->pending()) return; } diff --git a/higan/audio/audio.hpp b/higan/audio/audio.hpp index 7597e256..53cf50fc 100644 --- a/higan/audio/audio.hpp +++ b/higan/audio/audio.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -37,12 +38,13 @@ private: }; struct Filter { - enum class Order : uint { First, Second }; - enum class Type : uint { LowPass, HighPass }; + enum class Mode : uint { DCRemoval, OnePole, Biquad } mode; + enum class Type : uint { None, LowPass, HighPass } type; + enum class Order : uint { None, First, Second } order; - Order order; - DSP::IIR::OnePole onePole; //first-order - DSP::IIR::Biquad biquad; //second-order + DSP::IIR::DCRemoval dcRemoval; + DSP::IIR::OnePole onePole; + DSP::IIR::Biquad biquad; }; struct Stream { @@ -50,7 +52,9 @@ struct Stream { auto setFrequency(double inputFrequency, maybe outputFrequency = nothing) -> void; - auto addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes = 1) -> void; + auto addDCRemovalFilter() -> void; + auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void; + auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void; auto pending() const -> bool; auto read(double samples[]) -> uint; diff --git a/higan/audio/stream.cpp b/higan/audio/stream.cpp index 587a644d..b9817fb9 100644 --- a/higan/audio/stream.cpp +++ b/higan/audio/stream.cpp @@ -1,14 +1,12 @@ -auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void { - this->inputFrequency = inputFrequency; - this->outputFrequency = outputFrequency; - +auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void { channels.reset(); - channels.resize(channels_); + channels.resize(channelCount); for(auto& channel : channels) { channel.filters.reset(); - channel.resampler.reset(inputFrequency, outputFrequency); } + + setFrequency(inputFrequency, outputFrequency); } auto Stream::setFrequency(double inputFrequency, maybe outputFrequency) -> void { @@ -35,27 +33,46 @@ auto Stream::setFrequency(double inputFrequency, maybe outputFrequency) } } -auto Stream::addFilter(Filter::Order order, Filter::Type type, double cutoffFrequency, uint passes) -> void { +auto Stream::addDCRemovalFilter() -> void { + return; //todo: test to ensure this is desirable before enabling + for(auto& channel : channels) { + Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None}; + channel.filters.append(filter); + } +} + +auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void { for(auto& channel : channels) { for(uint pass : range(passes)) { - Filter filter{order}; - if(order == Filter::Order::First) { - DSP::IIR::OnePole::Type _type; - if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass; - if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass; - filter.onePole.reset(_type, cutoffFrequency, inputFrequency); + Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First}; + filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency); + channel.filters.append(filter); } - if(order == Filter::Order::Second) { - DSP::IIR::Biquad::Type _type; - if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass; - if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass; + Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second}; double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); - filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q); + filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q); + channel.filters.append(filter); } + } + } +} - channel.filters.append(filter); +auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void { + for(auto& channel : channels) { + for(uint pass : range(passes)) { + if(order == Filter::Order::First) { + Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First}; + filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency); + channel.filters.append(filter); + } + if(order == Filter::Order::Second) { + Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second}; + double q = DSP::IIR::Biquad::butterworth(passes * 2, pass); + filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q); + channel.filters.append(filter); + } } } } @@ -73,9 +90,10 @@ auto Stream::write(const double samples[]) -> void { for(auto c : range(channels.size())) { double sample = samples[c] + 1e-25; //constant offset used to suppress denormals for(auto& filter : channels[c].filters) { - switch(filter.order) { - case Filter::Order::First: sample = filter.onePole.process(sample); break; - case Filter::Order::Second: sample = filter.biquad.process(sample); break; + switch(filter.mode) { + case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break; + case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break; + case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break; } } for(auto& filter : channels[c].nyquist) { diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index f4038825..6f9416fe 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -28,7 +28,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.66"; + static const string Version = "106.67"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/fc/apu/apu.cpp b/higan/fc/apu/apu.cpp index 433cfba2..45a55189 100644 --- a/higan/fc/apu/apu.cpp +++ b/higan/fc/apu/apu.cpp @@ -74,9 +74,10 @@ auto APU::setSample(int16 sample) -> void { auto APU::power(bool reset) -> void { create(APU::Enter, system.frequency()); stream = Emulator::audio.createStream(1, frequency() / rate()); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0); + stream->addHighPassFilter( 90.0, Emulator::Filter::Order::First); + stream->addHighPassFilter( 440.0, Emulator::Filter::Order::First); + stream->addLowPassFilter (14000.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); pulse[0].power(); pulse[1].power(); diff --git a/higan/gb/apu/apu.cpp b/higan/gb/apu/apu.cpp index 347c96ff..851459f3 100644 --- a/higan/gb/apu/apu.cpp +++ b/higan/gb/apu/apu.cpp @@ -55,7 +55,8 @@ auto APU::power() -> void { create(Enter, 2 * 1024 * 1024); if(!Model::SuperGameBoy()) { stream = Emulator::audio.createStream(2, frequency()); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); } for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this; diff --git a/higan/gba/apu/apu.cpp b/higan/gba/apu/apu.cpp index 61aa3a56..b44b64d4 100644 --- a/higan/gba/apu/apu.cpp +++ b/higan/gba/apu/apu.cpp @@ -77,7 +77,8 @@ auto APU::step(uint clocks) -> void { auto APU::power() -> void { create(APU::Enter, system.frequency()); stream = Emulator::audio.createStream(2, frequency() / 64.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); clock = 0; square1.power(); diff --git a/higan/md/psg/psg.cpp b/higan/md/psg/psg.cpp index f9f6b6ca..391b7282 100644 --- a/higan/md/psg/psg.cpp +++ b/higan/md/psg/psg.cpp @@ -37,8 +37,9 @@ auto PSG::step(uint clocks) -> void { auto PSG::power(bool reset) -> void { create(PSG::Enter, system.frequency() / 15.0); stream = Emulator::audio.createStream(1, frequency() / 16.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0); + stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First); + stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); select = 0; for(auto n : range(15)) { diff --git a/higan/md/ym2612/ym2612.cpp b/higan/md/ym2612/ym2612.cpp index c3bf5fde..888cb22c 100644 --- a/higan/md/ym2612/ym2612.cpp +++ b/higan/md/ym2612/ym2612.cpp @@ -157,8 +157,9 @@ auto YM2612::step(uint clocks) -> void { auto YM2612::power(bool reset) -> void { create(YM2612::Enter, system.frequency() / 7.0); stream = Emulator::audio.createStream(2, frequency() / 144.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0); + stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First); + stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); io = {}; lfo = {}; diff --git a/higan/ms/cartridge/cartridge.cpp b/higan/ms/cartridge/cartridge.cpp index 81611903..620fb395 100644 --- a/higan/ms/cartridge/cartridge.cpp +++ b/higan/ms/cartridge/cartridge.cpp @@ -9,6 +9,20 @@ Cartridge cartridge; auto Cartridge::load() -> bool { information = {}; + if(Model::SG1000()) { + if(auto loaded = platform->load(ID::SG1000, "SG-1000", "sg1000", {"NTSC", "PAL"})) { + information.pathID = loaded.pathID; + information.region = loaded.option; + } else return false; + } + + if(Model::SC3000()) { + if(auto loaded = platform->load(ID::SC3000, "SC-3000", "sc3000", {"NTSC", "PAL"})) { + information.pathID = loaded.pathID; + information.region = loaded.option; + } else return false; + } + if(Model::MasterSystem()) { if(auto loaded = platform->load(ID::MasterSystem, "Master System", "ms", {"NTSC", "PAL"})) { information.pathID = loaded.pathID; diff --git a/higan/ms/controller/controller.cpp b/higan/ms/controller/controller.cpp index 91bcfa38..618b96fb 100644 --- a/higan/ms/controller/controller.cpp +++ b/higan/ms/controller/controller.cpp @@ -32,7 +32,7 @@ auto Controller::main() -> void { auto ControllerPort::connect(uint deviceID) -> void { delete device; if(!system.loaded()) return; - if(!Model::MasterSystem()) return; + if(Model::GameGear()) return; switch(deviceID) { default: case ID::Device::None: device = new Controller(port); break; diff --git a/higan/ms/cpu/bus.cpp b/higan/ms/cpu/bus.cpp index 48c1a6dd..45dbf6d9 100644 --- a/higan/ms/cpu/bus.cpp +++ b/higan/ms/cpu/bus.cpp @@ -42,6 +42,16 @@ auto CPU::in(uint8 addr) -> uint8 { } case 3: { + if(Model::SG1000() || Model::SC3000()) { + auto port1 = controllerPort1.device->readData(); + auto port2 = controllerPort2.device->readData(); + if(addr.bit(0) == 0) { + return port1.bits(0,5) << 0 | port2.bits(0,1) << 6; + } else { + return port2.bits(2,5) << 0 | 1 << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7; + } + } + if(Model::MasterSystem()) { bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0); auto port1 = controllerPort1.device->readData(); diff --git a/higan/ms/cpu/cpu.cpp b/higan/ms/cpu/cpu.cpp index a60aef25..6e023c03 100644 --- a/higan/ms/cpu/cpu.cpp +++ b/higan/ms/cpu/cpu.cpp @@ -37,6 +37,13 @@ auto CPU::synchronizing() const -> bool { //called once per frame auto CPU::pollPause() -> void { + if(Model::SG1000() || Model::SC3000()) { + static bool pause = 0; + bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::SG1000Controls, 0); + if(!pause && state) setNMI(1); + pause = state; + } + if(Model::MasterSystem()) { static bool pause = 0; bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1); diff --git a/higan/ms/interface/interface.cpp b/higan/ms/interface/interface.cpp index e7d55251..291ac599 100644 --- a/higan/ms/interface/interface.cpp +++ b/higan/ms/interface/interface.cpp @@ -3,6 +3,8 @@ namespace MasterSystem { Settings settings; +#include "sg-1000.cpp" +#include "sc-3000.cpp" #include "master-system.cpp" #include "game-gear.cpp" diff --git a/higan/ms/interface/interface.hpp b/higan/ms/interface/interface.hpp index c2d2e537..c1a42546 100644 --- a/higan/ms/interface/interface.hpp +++ b/higan/ms/interface/interface.hpp @@ -5,6 +5,8 @@ namespace MasterSystem { struct ID { enum : uint { System, + SG1000, + SC3000, MasterSystem, GameGear, }; @@ -17,6 +19,8 @@ struct ID { struct Device { enum : uint { None, + SG1000Controls, + SC3000Controls, MasterSystemControls, GameGearControls, Gamepad, @@ -44,6 +48,38 @@ struct Interface : Emulator::Interface { auto set(const string& name, const any& value) -> bool override; }; +struct SG1000Interface : Interface { + auto information() -> Information override; + + auto displays() -> vector override; + auto color(uint32 color) -> uint64 override; + + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + + auto load() -> bool override; + + auto connected(uint port) -> uint override; + auto connect(uint port, uint device) -> void override; +}; + +struct SC3000Interface : Interface { + auto information() -> Information override; + + auto displays() -> vector override; + auto color(uint32 color) -> uint64 override; + + auto ports() -> vector override; + auto devices(uint port) -> vector override; + auto inputs(uint device) -> vector override; + + auto load() -> bool override; + + auto connected(uint port) -> uint override; + auto connect(uint port, uint device) -> void override; +}; + struct MasterSystemInterface : Interface { auto information() -> Information override; diff --git a/higan/ms/interface/sc-3000.cpp b/higan/ms/interface/sc-3000.cpp new file mode 100644 index 00000000..429ecc16 --- /dev/null +++ b/higan/ms/interface/sc-3000.cpp @@ -0,0 +1,105 @@ +auto SC3000Interface::information() -> Information { + Information information; + information.manufacturer = "Sega"; + information.name = "SC-3000"; + information.extension = "sc3000"; + return information; +} + +auto SC3000Interface::displays() -> vector { + Display display; + display.type = Display::Type::CRT; + display.colors = 1 << 4; + display.width = 256; + display.height = 192; + display.internalWidth = 256; + display.internalHeight = 192; + display.aspectCorrection = 1.0; + if(Region::NTSC()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (262.0 * 684.0); + if(Region::PAL()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (312.0 * 684.0); + return {display}; +} + +auto SC3000Interface::color(uint32 color) -> uint64 { + switch(color.bits(0,3)) { + case 0: return 0x0000'0000'0000ull; //transparent + case 1: return 0x0000'0000'0000ull; //black + case 2: return 0x2121'c8c8'4242ull; //medium green + case 3: return 0x5e5e'dcdc'7878ull; //light green + case 4: return 0x5454'5555'ededull; //dark blue + case 5: return 0x7d7d'7676'fcfcull; //light blue + case 6: return 0xd4d4'5252'4d4dull; //dark red + case 7: return 0x4242'ebeb'f5f5ull; //cyan + case 8: return 0xfcfc'5555'5454ull; //medium red + case 9: return 0xffff'7979'7878ull; //light red + case 10: return 0xd4d4'c1c1'5454ull; //dark yellow + case 11: return 0xe6e6'cece'8080ull; //light yellow + case 12: return 0x2121'b0b0'3b3bull; //dark green + case 13: return 0xc9c9'5b5b'babaull; //magenta + case 14: return 0xcccc'cccc'ccccull; //gray + case 15: return 0xffff'ffff'ffffull; //white + } + unreachable; +} + +auto SC3000Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Hardware, "Hardware" }}; +} + +auto SC3000Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Hardware) return { + {ID::Device::SC3000Controls, "Controls"} + }; + + return {}; +} + +auto SC3000Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::Gamepad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right"}, + {Type::Button, "1" }, + {Type::Button, "2" } + }; + + if(device == ID::Device::SC3000Controls) return { + {Type::Control, "Pause"} + }; + + return {}; +} + +auto SC3000Interface::load() -> bool { + return system.load(this, System::Model::SC3000); +} + +auto SC3000Interface::connected(uint port) -> uint { + if(port == ID::Port::Controller1) return settings.controllerPort1; + if(port == ID::Port::Controller2) return settings.controllerPort2; + if(port == ID::Port::Hardware) return ID::Device::SC3000Controls; + return 0; +} + +auto SC3000Interface::connect(uint port, uint device) -> void { + if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device); + if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device); +} diff --git a/higan/ms/interface/sg-1000.cpp b/higan/ms/interface/sg-1000.cpp new file mode 100644 index 00000000..97c338cf --- /dev/null +++ b/higan/ms/interface/sg-1000.cpp @@ -0,0 +1,105 @@ +auto SG1000Interface::information() -> Information { + Information information; + information.manufacturer = "Sega"; + information.name = "SG-1000"; + information.extension = "sg1000"; + return information; +} + +auto SG1000Interface::displays() -> vector { + Display display; + display.type = Display::Type::CRT; + display.colors = 1 << 4; + display.width = 256; + display.height = 192; + display.internalWidth = 256; + display.internalHeight = 192; + display.aspectCorrection = 1.0; + if(Region::NTSC()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (262.0 * 684.0); + if(Region::PAL()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (312.0 * 684.0); + return {display}; +} + +auto SG1000Interface::color(uint32 color) -> uint64 { + switch(color.bits(0,3)) { + case 0: return 0x0000'0000'0000ull; //transparent + case 1: return 0x0000'0000'0000ull; //black + case 2: return 0x2121'c8c8'4242ull; //medium green + case 3: return 0x5e5e'dcdc'7878ull; //light green + case 4: return 0x5454'5555'ededull; //dark blue + case 5: return 0x7d7d'7676'fcfcull; //light blue + case 6: return 0xd4d4'5252'4d4dull; //dark red + case 7: return 0x4242'ebeb'f5f5ull; //cyan + case 8: return 0xfcfc'5555'5454ull; //medium red + case 9: return 0xffff'7979'7878ull; //light red + case 10: return 0xd4d4'c1c1'5454ull; //dark yellow + case 11: return 0xe6e6'cece'8080ull; //light yellow + case 12: return 0x2121'b0b0'3b3bull; //dark green + case 13: return 0xc9c9'5b5b'babaull; //magenta + case 14: return 0xcccc'cccc'ccccull; //gray + case 15: return 0xffff'ffff'ffffull; //white + } + unreachable; +} + +auto SG1000Interface::ports() -> vector { return { + {ID::Port::Controller1, "Controller Port 1"}, + {ID::Port::Controller2, "Controller Port 2"}, + {ID::Port::Hardware, "Hardware" }}; +} + +auto SG1000Interface::devices(uint port) -> vector { + if(port == ID::Port::Controller1) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Controller2) return { + {ID::Device::None, "None" }, + {ID::Device::Gamepad, "Gamepad"} + }; + + if(port == ID::Port::Hardware) return { + {ID::Device::SG1000Controls, "Controls"} + }; + + return {}; +} + +auto SG1000Interface::inputs(uint device) -> vector { + using Type = Input::Type; + + if(device == ID::Device::None) return { + }; + + if(device == ID::Device::Gamepad) return { + {Type::Hat, "Up" }, + {Type::Hat, "Down" }, + {Type::Hat, "Left" }, + {Type::Hat, "Right"}, + {Type::Button, "1" }, + {Type::Button, "2" } + }; + + if(device == ID::Device::SG1000Controls) return { + {Type::Control, "Pause"} + }; + + return {}; +} + +auto SG1000Interface::load() -> bool { + return system.load(this, System::Model::SG1000); +} + +auto SG1000Interface::connected(uint port) -> uint { + if(port == ID::Port::Controller1) return settings.controllerPort1; + if(port == ID::Port::Controller2) return settings.controllerPort2; + if(port == ID::Port::Hardware) return ID::Device::SG1000Controls; + return 0; +} + +auto SG1000Interface::connect(uint port, uint device) -> void { + if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device); + if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device); +} diff --git a/higan/ms/ms.hpp b/higan/ms/ms.hpp index c1c02dc8..2fdced1a 100644 --- a/higan/ms/ms.hpp +++ b/higan/ms/ms.hpp @@ -30,6 +30,8 @@ namespace MasterSystem { }; struct Model { + inline static auto SG1000() -> bool; + inline static auto SC3000() -> bool; inline static auto MasterSystem() -> bool; inline static auto GameGear() -> bool; }; diff --git a/higan/ms/psg/psg.cpp b/higan/ms/psg/psg.cpp index 770d76c4..e86a2ec6 100644 --- a/higan/ms/psg/psg.cpp +++ b/higan/ms/psg/psg.cpp @@ -44,7 +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->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); select = 0; for(auto n : range(15)) { diff --git a/higan/ms/system/system.cpp b/higan/ms/system/system.cpp index ad46185b..c4a1d46c 100644 --- a/higan/ms/system/system.cpp +++ b/higan/ms/system/system.cpp @@ -50,7 +50,7 @@ auto System::save() -> void { } auto System::unload() -> void { - if(MasterSystem::Model::MasterSystem()) { + if(!MasterSystem::Model::GameGear()) { cpu.peripherals.reset(); controllerPort1.unload(); controllerPort2.unload(); @@ -71,7 +71,7 @@ auto System::power() -> void { psg.power(); scheduler.primary(cpu); - if(MasterSystem::Model::MasterSystem()) { + if(!MasterSystem::Model::GameGear()) { controllerPort1.power(ID::Port::Controller1); controllerPort2.power(ID::Port::Controller2); diff --git a/higan/ms/system/system.hpp b/higan/ms/system/system.hpp index 222a7141..57b4d28f 100644 --- a/higan/ms/system/system.hpp +++ b/higan/ms/system/system.hpp @@ -1,5 +1,5 @@ struct System { - enum class Model : uint { MasterSystem, GameGear }; + enum class Model : uint { SG1000, SC3000, MasterSystem, GameGear }; enum class Region : uint { NTSC, PAL }; auto loaded() const -> bool { return information.loaded; } @@ -38,6 +38,8 @@ private: extern System system; +auto Model::SG1000() -> bool { return system.model() == System::Model::SG1000; } +auto Model::SC3000() -> bool { return system.model() == System::Model::SC3000; } 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/background.cpp b/higan/ms/vdp/background.cpp index eb3132dd..f78897c9 100644 --- a/higan/ms/vdp/background.cpp +++ b/higan/ms/vdp/background.cpp @@ -1,58 +1,122 @@ -auto VDP::Background::scanline() -> void { - state.x = 0; - state.y = vdp.io.vcounter; +auto VDP::Background::run(uint8 hoffset, uint9 voffset) -> void { + output = {}; + switch(vdp.io.mode) { + case 0b0000: return graphics1(hoffset, voffset); + case 0b0001: return; + case 0b0010: return graphics2(hoffset, voffset); + case 0b0011: return; + case 0b0100: return; + case 0b0101: return; + case 0b0110: return; + case 0b0111: return; + case 0b1000: return graphics3(hoffset, voffset, 192); + case 0b1001: return; + case 0b1010: return graphics3(hoffset, voffset, 192); + case 0b1011: return graphics3(hoffset, voffset, 224); + case 0b1100: return graphics3(hoffset, voffset, 192); + case 0b1101: return; + case 0b1110: return graphics3(hoffset, voffset, 240); + case 0b1111: return graphics3(hoffset, voffset, 192); + } } -auto VDP::Background::run() -> void { - uint8 hoffset = state.x++; - uint9 voffset = state.y; +auto VDP::Background::graphics1(uint8 hoffset, uint9 voffset) -> void { + uint14 nameTableAddress; + nameTableAddress.bits( 0, 4) = hoffset.bits(3,7); + nameTableAddress.bits( 5, 9) = voffset.bits(3,7); + nameTableAddress.bits(10,13) = vdp.io.nameTableAddress; + uint8 pattern = vdp.vram[nameTableAddress]; - if(voffset >= vdp.vlines() - || hoffset < (vdp.io.hscroll & 7) - ) { - output.color = 0; - output.palette = 0; - output.priority = 0; - return; + uint14 patternAddress; + patternAddress.bits( 0, 2) = voffset.bits(0,2); + patternAddress.bits( 3,10) = pattern; + patternAddress.bits(11,13) = vdp.io.patternTableAddress; + + uint14 colorAddress; //d5 = 0 + colorAddress.bits(0, 4) = pattern.bits(3,7); + colorAddress.bits(6,13) = vdp.io.colorTableAddress; + + uint8 color = vdp.vram[colorAddress]; + uint3 index = hoffset ^ 7; + if(vdp.vram[patternAddress].bit(index)) { + output.color = color.bits(4,7); + } else { + output.color = color.bits(0,3); } +} - bool hscroll = !vdp.io.horizontalScrollLock || voffset >= 16; - bool vscroll = !vdp.io.verticalScrollLock || hoffset <= 191; +auto VDP::Background::graphics2(uint8 hoffset, uint9 voffset) -> void { + uint14 nameTableAddress; + nameTableAddress.bits( 0, 4) = hoffset.bits(3,7); + nameTableAddress.bits( 5, 9) = voffset.bits(3,7); + nameTableAddress.bits(10,13) = vdp.io.nameTableAddress; + uint8 pattern = vdp.vram[nameTableAddress]; - if(hscroll) hoffset -= vdp.io.hscroll; - if(vscroll) voffset += vdp.io.vscroll; + uint14 patternAddress; + patternAddress.bits(0, 2) = voffset.bits(0,2); + patternAddress.bits(3,10) = pattern; + if(voffset >= 64 && voffset <= 127) patternAddress.bit(11) = vdp.io.patternTableAddress.bit(0); + if(voffset >= 128 && voffset <= 191) patternAddress.bit(12) = vdp.io.patternTableAddress.bit(1); + patternAddress.bit(13) = vdp.io.patternTableAddress.bit(2); + + uint14 colorAddress; + colorAddress.bits(0, 2) = voffset.bits(0,2); + colorAddress.bits(3,10) = pattern; + if(voffset >= 64 && voffset <= 127) colorAddress.bit(11) = vdp.io.patternTableAddress.bit(0); + if(voffset >= 128 && voffset <= 191) colorAddress.bit(12) = vdp.io.patternTableAddress.bit(1); + colorAddress.bit(13) = vdp.io.colorTableAddress.bit(7); + + uint8 colorMask = vdp.io.colorTableAddress.bits(0,6) << 1 | 1; + uint8 color = vdp.vram[colorAddress] & colorMask; + uint3 index = hoffset ^ 7; + if(vdp.vram[patternAddress].bit(index)) { + output.color = color.bits(4,7); + } else { + output.color = color.bits(0,3); + } +} + +auto VDP::Background::graphics3(uint8 hoffset, uint9 voffset, uint vlines) -> void { + if(hoffset < vdp.io.hscroll.bits(0,2)) return; + + if(!vdp.io.horizontalScrollLock || voffset >= 16) hoffset -= vdp.io.hscroll; + if(!vdp.io.verticalScrollLock || hoffset <= 191) voffset += vdp.io.vscroll; uint14 nameTableAddress; - if(vdp.vlines() == 192) { + if(vlines == 192) { if(voffset >= 224) voffset -= 224; - nameTableAddress = vdp.io.nameTableAddress << 11; + nameTableAddress.bits( 1, 5) = hoffset.bits(3,7); + nameTableAddress.bits( 6,10) = voffset.bits(3,7); + nameTableAddress.bits(11,13) = vdp.io.nameTableAddress.bits(1,3); } else { - voffset &= 255; - nameTableAddress = (vdp.io.nameTableAddress & ~1) << 11 | 0x700; + voffset += 224; + nameTableAddress.bits( 1, 5) = hoffset.bits(3,7); + nameTableAddress.bits( 6,11) = voffset.bits(3,8); + nameTableAddress.bits(12,13) = vdp.io.nameTableAddress.bits(2,3); } - nameTableAddress += ((voffset >> 3) << 6) + ((hoffset >> 3) << 1); - uint16 tiledata; - tiledata = vdp.vram[nameTableAddress + 0] << 0; - tiledata |= vdp.vram[nameTableAddress + 1] << 8; + uint16 pattern; + pattern.byte(0) = vdp.vram[nameTableAddress | 0]; + pattern.byte(1) = vdp.vram[nameTableAddress | 1]; - uint14 patternAddress = tiledata.bits(0,8) << 5; - if(tiledata.bit(9)) hoffset ^= 7; - if(tiledata.bit(10)) voffset ^= 7; - output.palette = tiledata.bit(11); - output.priority = tiledata.bit(12); + if(pattern.bit( 9)) hoffset ^= 7; //hflip + if(pattern.bit(10)) voffset ^= 7; //vflip + output.palette = pattern.bit(11); + output.priority = pattern.bit(12); - auto index = 7 - (hoffset & 7); - patternAddress += (voffset & 7) << 2; - output.color.bit(0) = vdp.vram[patternAddress + 0].bit(index); - output.color.bit(1) = vdp.vram[patternAddress + 1].bit(index); - output.color.bit(2) = vdp.vram[patternAddress + 2].bit(index); - output.color.bit(3) = vdp.vram[patternAddress + 3].bit(index); + uint14 patternAddress; + patternAddress.bits(2, 4) = voffset.bits(0,2); + patternAddress.bits(5,13) = pattern.bits(0,8); + + uint3 index = hoffset ^ 7; + output.color.bit(0) = vdp.vram[patternAddress | 0].bit(index); + output.color.bit(1) = vdp.vram[patternAddress | 1].bit(index); + output.color.bit(2) = vdp.vram[patternAddress | 2].bit(index); + output.color.bit(3) = vdp.vram[patternAddress | 3].bit(index); if(output.color == 0) output.priority = 0; } auto VDP::Background::power() -> void { - state = {}; output = {}; } diff --git a/higan/ms/vdp/io.cpp b/higan/ms/vdp/io.cpp index d8a35eb1..36755065 100644 --- a/higan/ms/vdp/io.cpp +++ b/higan/ms/vdp/io.cpp @@ -1,16 +1,9 @@ auto VDP::vcounter() -> uint8 { - if(io.lines240) { - //NTSC 256x240 - return io.vcounter; - } else if(io.lines224) { - //NTSC 256x224 - return io.vcounter <= 234 ? io.vcounter : io.vcounter - 6; - } else { - //NTSC 256x192 - return io.vcounter <= 218 ? io.vcounter : io.vcounter - 6; + switch(io.mode) { + default: return io.vcounter <= 218 ? io.vcounter : io.vcounter - 6; //256x192 + case 0b1011: return io.vcounter <= 234 ? io.vcounter : io.vcounter - 6; //256x224 + case 0b1110: return io.vcounter; //256x240 } - - unreachable; } auto VDP::hcounter() -> uint8 { @@ -51,9 +44,8 @@ auto VDP::data(uint8 data) -> void { vram[io.address++] = data; } else { uint mask = 0; - if(Model::MasterSystem()) mask = 0x1f; - if(Model::GameGear()) mask = 0x3f; - cram[io.address++ & mask] = data; + if(Model::MasterSystem()) cram[io.address++ & 0x1f] = data; + if(Model::GameGear()) cram[io.address++ & 0x3f] = data; } } @@ -83,8 +75,8 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void { //mode control 1 case 0x0: { io.externalSync = data.bit(0); - io.extendedHeight = data.bit(1); - io.mode4 = data.bit(2); + io.mode.bit(1) = data.bit(1); + io.mode.bit(3) = data.bit(2) & !Model::SG1000() & !Model::SC3000(); io.spriteShift = data.bit(3); io.lineInterrupts = data.bit(4); io.leftClip = data.bit(5); @@ -97,8 +89,8 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void { case 0x1: { io.spriteDouble = data.bit(0); io.spriteTile = data.bit(1); - io.lines240 = data.bit(3); - io.lines224 = data.bit(4); + io.mode.bit(2) = data.bit(3); + io.mode.bit(0) = data.bit(4); io.frameInterrupts = data.bit(5); io.displayEnable = data.bit(6); return; @@ -106,8 +98,7 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void { //name table base address case 0x2: { - io.nameTableMask = data.bit(0); - io.nameTableAddress = data.bits(1,3); + io.nameTableAddress = data.bits(0,3); return; } @@ -119,21 +110,19 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void { //pattern table base address case 0x4: { - io.patternTableAddress = data.bits(0,7); + io.patternTableAddress = data.bits(0,2); return; } //sprite attribute table base address case 0x5: { - io.spriteAttributeTableMask = data.bit(0); - io.spriteAttributeTableAddress = data.bits(1,6); + io.spriteAttributeTableAddress = data.bits(0,6); return; } //sprite pattern table base address case 0x6: { - io.spritePatternTableMask = data.bits(0,1); - io.spritePatternTableAddress = data.bit(2); + io.spritePatternTableAddress = data.bits(0,2); return; } diff --git a/higan/ms/vdp/serialization.cpp b/higan/ms/vdp/serialization.cpp index 0caccf01..1b13c658 100644 --- a/higan/ms/vdp/serialization.cpp +++ b/higan/ms/vdp/serialization.cpp @@ -21,8 +21,6 @@ auto VDP::serialize(serializer& s) -> void { s.integer(io.address); s.integer(io.vramLatch); s.integer(io.externalSync); - s.integer(io.extendedHeight); - s.integer(io.mode4); s.integer(io.spriteShift); s.integer(io.lineInterrupts); s.integer(io.leftClip); @@ -30,17 +28,13 @@ auto VDP::serialize(serializer& s) -> void { s.integer(io.verticalScrollLock); s.integer(io.spriteDouble); s.integer(io.spriteTile); - s.integer(io.lines240); - s.integer(io.lines224); s.integer(io.frameInterrupts); s.integer(io.displayEnable); - s.integer(io.nameTableMask); + s.integer(io.mode); s.integer(io.nameTableAddress); s.integer(io.colorTableAddress); s.integer(io.patternTableAddress); - s.integer(io.spriteAttributeTableMask); s.integer(io.spriteAttributeTableAddress); - s.integer(io.spritePatternTableMask); s.integer(io.spritePatternTableAddress); s.integer(io.backdropColor); s.integer(io.hscroll); @@ -49,16 +43,18 @@ auto VDP::serialize(serializer& s) -> void { } auto VDP::Background::serialize(serializer& s) -> void { - s.integer(state.x); - s.integer(state.y); s.integer(output.color); s.integer(output.palette); s.integer(output.priority); } auto VDP::Sprite::serialize(serializer& s) -> void { - s.integer(state.x); - s.integer(state.y); s.integer(output.color); - //todo: array is not serializable + for(auto& object : objects) { + s.integer(object.x); + s.integer(object.y); + s.integer(object.pattern); + s.integer(object.color); + } + s.integer(objectsValid); } diff --git a/higan/ms/vdp/sprite.cpp b/higan/ms/vdp/sprite.cpp index 7fa574bf..494f534c 100644 --- a/higan/ms/vdp/sprite.cpp +++ b/higan/ms/vdp/sprite.cpp @@ -1,68 +1,133 @@ -auto VDP::Sprite::scanline() -> void { - state.x = 0; - state.y = vdp.io.vcounter; - objects.reset(); - +auto VDP::Sprite::setup(uint9 voffset) -> void { + objectsValid = 0; uint limit = vdp.io.spriteTile ? 15 : 7; - uint14 attributeAddress = vdp.io.spriteAttributeTableAddress << 8; - for(uint index : range(64)) { - uint8 y = vdp.vram[attributeAddress + index]; - uint8 x = vdp.vram[attributeAddress + 0x80 + (index << 1)]; - uint8 pattern = vdp.vram[attributeAddress + 0x81 + (index << 1)]; - if(vdp.vlines() == 192 && y == 0xd0) break; - if(vdp.io.spriteShift) x -= 8; - y += 1; - if(state.y < y) continue; - if(state.y > y + limit) continue; + if(!vdp.io.mode.bit(3)) { + uint14 attributeAddress; + attributeAddress.bits(7,13) = vdp.io.spriteAttributeTableAddress; + for(uint index : range(32)) { + uint8 y = vdp.vram[attributeAddress++]; + if(y == 0xd0) break; - if(limit == 15) pattern.bit(0) = 0; + uint8 x = vdp.vram[attributeAddress++]; + uint8 pattern = vdp.vram[attributeAddress++]; + uint8 extra = vdp.vram[attributeAddress++]; - objects.append({x, y, pattern}); - if(objects.size() == 8) { - vdp.io.spriteOverflow = 1; - break; + if(extra.bit(7)) x -= 32; + y += 1; + if(voffset < y) continue; + if(voffset > y + limit) continue; + + if(limit == 15) pattern.bits(0,1) = 0; + + objects[objectsValid] = {x, y, pattern, extra.bits(0,3)}; + if(++objectsValid == 4) { + vdp.io.spriteOverflow = 1; + break; + } + } + } else { + uint14 attributeAddress; + attributeAddress.bits(8,13) = vdp.io.spriteAttributeTableAddress.bits(1,6); + for(uint index : range(64)) { + uint8 y = vdp.vram[attributeAddress + index]; + uint8 x = vdp.vram[attributeAddress + 0x80 + (index << 1)]; + uint8 pattern = vdp.vram[attributeAddress + 0x81 + (index << 1)]; + if(vdp.vlines() == 192 && y == 0xd0) break; + + if(vdp.io.spriteShift) x -= 8; + y += 1; + if(voffset < y) continue; + if(voffset > y + limit) continue; + + if(limit == 15) pattern.bit(0) = 0; + + objects[objectsValid] = {x, y, pattern}; + if(++objectsValid == 8) { + vdp.io.spriteOverflow = 1; + break; + } } } } -auto VDP::Sprite::run() -> void { - output.color = 0; +auto VDP::Sprite::run(uint8 hoffset, uint9 voffset) -> void { + output = {}; + switch(vdp.io.mode) { + case 0b0000: return graphics1(hoffset, voffset); + case 0b0001: return; + case 0b0010: return graphics2(hoffset, voffset); + case 0b0011: return; + case 0b0100: return; + case 0b0101: return; + case 0b0110: return; + case 0b0111: return; + case 0b1000: return graphics3(hoffset, voffset, 192); + case 0b1001: return; + case 0b1010: return graphics3(hoffset, voffset, 192); + case 0b1011: return graphics3(hoffset, voffset, 224); + case 0b1100: return graphics3(hoffset, voffset, 192); + case 0b1101: return; + case 0b1110: return graphics3(hoffset, voffset, 240); + case 0b1111: return graphics3(hoffset, voffset, 192); + } +} - if(state.y >= vdp.vlines()) return; +auto VDP::Sprite::graphics1(uint8 hoffset, uint9 voffset) -> void { + //todo: are sprites different in graphics mode 1? + return graphics2(hoffset, voffset); +} +auto VDP::Sprite::graphics2(uint8 hoffset, uint9 voffset) -> void { uint limit = vdp.io.spriteTile ? 15 : 7; - for(auto& o : objects) { - if(state.x < o.x) continue; - if(state.x > o.x + 7) continue; + for(uint objectIndex : range(objectsValid)) { + auto& o = objects[objectIndex]; + if(hoffset < o.x) continue; + if(hoffset > o.x + limit) continue; - uint x = state.x - o.x; - uint y = state.y - o.y; + uint x = hoffset - o.x; + uint y = voffset - o.y; - uint14 address = vdp.io.spritePatternTableAddress << 13; - address += o.pattern << 5; - address += (y & limit) << 2; + uint14 address; + address.bits( 0,10) = (o.pattern << 3) + (x >> 3 << 4) + (y & limit); + address.bits(11,13) = vdp.io.spritePatternTableAddress; - auto index = 7 - (x & 7); + uint3 index = x ^ 7; + if(vdp.vram[address].bit(index)) { + if(output.color) { vdp.io.spriteCollision = true; break; } + output.color = o.color; + } + } +} + +auto VDP::Sprite::graphics3(uint8 hoffset, uint9 voffset, uint vlines) -> void { + uint limit = vdp.io.spriteTile ? 15 : 7; + for(uint objectIndex : range(objectsValid)) { + auto& o = objects[objectIndex]; + if(hoffset < o.x) continue; + if(hoffset > o.x + 7) continue; + + uint x = hoffset - o.x; + uint y = voffset - o.y; + + uint14 address; + address.bits(2,12) = (o.pattern << 3) + (y & limit); + address.bit (13) = vdp.io.spritePatternTableAddress.bit(2); + + uint3 index = x ^ 7; uint4 color; - color.bit(0) = vdp.vram[address + 0].bit(index); - color.bit(1) = vdp.vram[address + 1].bit(index); - color.bit(2) = vdp.vram[address + 2].bit(index); - color.bit(3) = vdp.vram[address + 3].bit(index); + color.bit(0) = vdp.vram[address | 0].bit(index); + color.bit(1) = vdp.vram[address | 1].bit(index); + color.bit(2) = vdp.vram[address | 2].bit(index); + color.bit(3) = vdp.vram[address | 3].bit(index); if(color == 0) continue; - if(output.color) { - vdp.io.spriteCollision = true; - break; - } - + if(output.color) { vdp.io.spriteCollision = true; break; } output.color = color; } - - state.x++; } auto VDP::Sprite::power() -> void { - state = {}; output = {}; + objectsValid = 0; } diff --git a/higan/ms/vdp/vdp.cpp b/higan/ms/vdp/vdp.cpp index df948dbf..a7077d6a 100644 --- a/higan/ms/vdp/vdp.cpp +++ b/higan/ms/vdp/vdp.cpp @@ -26,16 +26,14 @@ auto VDP::main() -> void { io.intFrame = 1; } - background.scanline(); - sprite.scanline(); - //684 clocks/scanline uint y = io.vcounter; + sprite.setup(y); if(y < vlines()) { uint32* screen = buffer + (24 + y) * 256; for(uint x : range(256)) { - background.run(); - sprite.run(); + background.run(x, y); + sprite.run(x, y); step(2); uint12 color = palette(16 | io.backdropColor); @@ -74,6 +72,12 @@ auto VDP::step(uint clocks) -> void { } auto VDP::refresh() -> void { + if(Model::SG1000() || Model::SC3000()) { + uint32* screen = buffer; + screen += 24 * 256; + Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 192); + } + if(Model::MasterSystem()) { //center the video output vertically in the viewport uint32* screen = buffer; @@ -89,9 +93,11 @@ auto VDP::refresh() -> void { } auto VDP::vlines() -> uint { - if(io.lines240) return 240; - if(io.lines224) return 224; - return 192; + switch(io.mode) { + default: return 192; + case 0b1011: return 224; + case 0b1110: return 240; + } } auto VDP::vblank() -> bool { @@ -109,14 +115,15 @@ auto VDP::power() -> void { } auto VDP::palette(uint5 index) -> uint12 { - if(Model::MasterSystem()) { - return cram[index]; - } - - if(Model::GameGear()) { - return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8; - } - + if(Model::SG1000() || Model::SC3000()) return index.bits(0,3); + //Master System and Game Gear approximate TMS9918A colors by converting to RGB6 palette colors + static uint6 palette[16] = { + 0x00, 0x00, 0x08, 0x0c, 0x10, 0x30, 0x01, 0x3c, + 0x02, 0x03, 0x05, 0x0f, 0x04, 0x33, 0x15, 0x3f, + }; + if(!io.mode.bit(3)) return palette[index.bits(0,3)]; + if(Model::MasterSystem()) return cram[index]; + if(Model::GameGear()) return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8; return 0; } diff --git a/higan/ms/vdp/vdp.hpp b/higan/ms/vdp/vdp.hpp index 90928407..e0475ce9 100644 --- a/higan/ms/vdp/vdp.hpp +++ b/higan/ms/vdp/vdp.hpp @@ -1,4 +1,4 @@ -//TI TMS9918A (derivative) +//Texas Instruments TMS9918A (derivative) struct VDP : Thread { static auto Enter() -> void; @@ -23,19 +23,16 @@ struct VDP : Thread { //background.cpp struct Background { - auto scanline() -> void; - auto run() -> void; + auto run(uint8 hoffset, uint9 voffset) -> void; + auto graphics1(uint8 hoffset, uint9 voffset) -> void; + auto graphics2(uint8 hoffset, uint9 voffset) -> void; + auto graphics3(uint8 hoffset, uint9 voffset, uint vlines) -> void; auto power() -> void; //serialization.cpp auto serialize(serializer&) -> void; - struct State { - uint x = 0; - uint y = 0; - } state; - struct Output { uint4 color; uint1 palette; @@ -45,8 +42,11 @@ struct VDP : Thread { //sprite.cpp struct Sprite { - auto scanline() -> void; - auto run() -> void; + auto setup(uint9 voffset) -> void; + auto run(uint8 hoffset, uint9 voffset) -> void; + auto graphics1(uint8 hoffset, uint9 voffset) -> void; + auto graphics2(uint8 hoffset, uint9 voffset) -> void; + auto graphics3(uint8 hoffset, uint9 voffset, uint vlines) -> void; auto power() -> void; @@ -57,18 +57,15 @@ struct VDP : Thread { uint8 x; uint8 y; uint8 pattern; + uint4 color; }; - struct State { - uint x = 0; - uint y = 0; - } state; - struct Output { uint4 color; } output; - adaptive_array objects; + array objects; + uint objectsValid; } sprite; //serialization.cpp @@ -79,7 +76,7 @@ private: uint32 buffer[256 * 264]; uint8 vram[0x4000]; - uint8 cram[0x40]; //MS = 0x20, GG = 0x40 + uint8 cram[0x40]; //SG + MS = 0x20, GG = 0x40 struct IO { uint vcounter = 0; //vertical counter @@ -105,8 +102,6 @@ private: //$00 mode control 1 bool externalSync = 0; - bool extendedHeight = 0; - bool mode4 = 0; bool spriteShift = 0; bool lineInterrupts = 0; bool leftClip = 0; @@ -116,28 +111,26 @@ private: //$01 mode control 2 bool spriteDouble = 0; bool spriteTile = 0; - bool lines240 = 0; - bool lines224 = 0; bool frameInterrupts = 0; bool displayEnable = 0; + //$00 + $01 + uint4 mode; + //$02 name table base address - uint1 nameTableMask; - uint3 nameTableAddress; + uint4 nameTableAddress; //$03 color table base address uint8 colorTableAddress; //$04 pattern table base address - uint8 patternTableAddress; + uint3 patternTableAddress; //$05 sprite attribute table base address - uint1 spriteAttributeTableMask; - uint6 spriteAttributeTableAddress; + uint7 spriteAttributeTableAddress; //$06 sprite pattern table base address - uint2 spritePatternTableMask; - uint1 spritePatternTableAddress; + uint3 spritePatternTableAddress; //$07 backdrop color uint4 backdropColor; diff --git a/higan/pce/psg/psg.cpp b/higan/pce/psg/psg.cpp index 934aab24..c0f18129 100644 --- a/higan/pce/psg/psg.cpp +++ b/higan/pce/psg/psg.cpp @@ -52,7 +52,8 @@ auto PSG::step(uint clocks) -> void { auto PSG::power() -> void { create(PSG::Enter, system.colorburst()); stream = Emulator::audio.createStream(2, frequency()); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); io = {}; for(auto C : range(6)) channel[C].power(C); diff --git a/higan/sfc/coprocessor/icd/icd.cpp b/higan/sfc/coprocessor/icd/icd.cpp index e1a0bae9..5ac180a0 100644 --- a/higan/sfc/coprocessor/icd/icd.cpp +++ b/higan/sfc/coprocessor/icd/icd.cpp @@ -46,7 +46,8 @@ auto ICD::power() -> void { //SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator create(ICD::Enter, (Frequency ? Frequency : system.cpuFrequency()) / 5.0); stream = Emulator::audio.createStream(2, frequency() / 2.0); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); r6003 = 0x00; r6004 = 0xff; diff --git a/higan/systems/SC-3000.sys/manifest.bml b/higan/systems/SC-3000.sys/manifest.bml new file mode 100644 index 00000000..fa6204a1 --- /dev/null +++ b/higan/systems/SC-3000.sys/manifest.bml @@ -0,0 +1 @@ +system name:SC-3000 diff --git a/higan/systems/SG-1000.sys/manifest.bml b/higan/systems/SG-1000.sys/manifest.bml new file mode 100644 index 00000000..4b1d86f1 --- /dev/null +++ b/higan/systems/SG-1000.sys/manifest.bml @@ -0,0 +1 @@ +system name:SG-1000 diff --git a/higan/target-bsnes/GNUmakefile b/higan/target-bsnes/GNUmakefile index a00a0d29..ea2aec4e 100644 --- a/higan/target-bsnes/GNUmakefile +++ b/higan/target-bsnes/GNUmakefile @@ -33,7 +33,7 @@ endif verbose: hiro.verbose ruby.verbose nall.verbose all; -install: +install: all ifeq ($(shell id -un),root) $(error "make install should not be run as root") else ifeq ($(platform),windows) diff --git a/higan/target-higan/GNUmakefile b/higan/target-higan/GNUmakefile index 851cdd62..71e5d9d4 100644 --- a/higan/target-higan/GNUmakefile +++ b/higan/target-higan/GNUmakefile @@ -33,7 +33,7 @@ endif verbose: hiro.verbose ruby.verbose nall.verbose all; -install: +install: all ifeq ($(shell id -un),root) $(error "make install should not be run as root") else ifeq ($(platform),windows) diff --git a/higan/target-higan/program/platform.cpp b/higan/target-higan/program/platform.cpp index 20207a34..dd58a24a 100644 --- a/higan/target-higan/program/platform.cpp +++ b/higan/target-higan/program/platform.cpp @@ -93,7 +93,12 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint } auto Program::audioSample(const double* samples, uint channels) -> void { - audio->output(samples); + if(channels == 1) { + double stereo[] = {samples[0], samples[0]}; + audio->output(stereo); + } else { + audio->output(samples); + } } auto Program::inputPoll(uint port, uint device, uint input) -> int16 { diff --git a/higan/target-higan/program/program.cpp b/higan/target-higan/program/program.cpp index e0848a4e..cb5caed0 100644 --- a/higan/target-higan/program/program.cpp +++ b/higan/target-higan/program/program.cpp @@ -24,6 +24,12 @@ Program::Program(Arguments arguments) { #ifdef CORE_SFC emulators.append(new SuperFamicom::Interface); #endif +#ifdef CORE_MS + emulators.append(new MasterSystem::SG1000Interface); +#endif +#ifdef CORE_MS + emulators.append(new MasterSystem::SC3000Interface); +#endif #ifdef CORE_MS emulators.append(new MasterSystem::MasterSystemInterface); #endif diff --git a/higan/ws/apu/apu.cpp b/higan/ws/apu/apu.cpp index f7285061..245b4c2e 100644 --- a/higan/ws/apu/apu.cpp +++ b/higan/ws/apu/apu.cpp @@ -68,7 +68,8 @@ auto APU::step(uint clocks) -> void { auto APU::power() -> void { create(APU::Enter, 3'072'000); stream = Emulator::audio.createStream(2, frequency()); - stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); + stream->addHighPassFilter(20.0, Emulator::Filter::Order::First); + stream->addDCRemovalFilter(); bus.map(this, 0x004a, 0x004c); bus.map(this, 0x004e, 0x0050); diff --git a/icarus/GNUmakefile b/icarus/GNUmakefile index ea5ee7cc..1b134009 100644 --- a/icarus/GNUmakefile +++ b/icarus/GNUmakefile @@ -34,7 +34,7 @@ endif $(call delete,obj/*) $(call delete,out/*) -install: +install: all ifeq ($(platform),macos) cp -R out/$(name).app /Applications/$(name).app else ifneq ($(filter $(platform),linux bsd),) diff --git a/icarus/core/core.cpp b/icarus/core/core.cpp index 0dae2b59..895e673e 100644 --- a/icarus/core/core.cpp +++ b/icarus/core/core.cpp @@ -1,10 +1,13 @@ Icarus::Icarus() { Database::Famicom = BML::unserialize(string::read(locate("Database/Famicom.bml"))); Database::SuperFamicom = BML::unserialize(string::read(locate("Database/Super Famicom.bml"))); + Database::SG1000 = BML::unserialize(string::read(locate("Database/SG-1000.bml"))); + Database::SC3000 = BML::unserialize(string::read(locate("Database/SC-3000.bml"))); Database::MasterSystem = BML::unserialize(string::read(locate("Database/Master System.bml"))); Database::MegaDrive = BML::unserialize(string::read(locate("Database/Mega Drive.bml"))); Database::PCEngine = BML::unserialize(string::read(locate("Database/PC Engine.bml"))); Database::SuperGrafx = BML::unserialize(string::read(locate("Database/SuperGrafx.bml"))); + Database::MSX = BML::unserialize(string::read(locate("Database/MSX.bml"))); Database::GameBoy = BML::unserialize(string::read(locate("Database/Game Boy.bml"))); Database::GameBoyColor = BML::unserialize(string::read(locate("Database/Game Boy Color.bml"))); Database::GameBoyAdvance = BML::unserialize(string::read(locate("Database/Game Boy Advance.bml"))); @@ -41,10 +44,13 @@ auto Icarus::manifest(string location) -> string { auto type = Location::suffix(location).downcase(); if(type == ".fc") return famicomManifest(location); if(type == ".sfc") return superFamicomManifest(location); + if(type == ".sg1000") return sg1000Manifest(location); + if(type == ".sc3000") return sc3000Manifest(location); if(type == ".ms") return masterSystemManifest(location); if(type == ".md") return megaDriveManifest(location); if(type == ".pce") return pcEngineManifest(location); - if(type == ".sg") return superGrafxManifest(location); + if(type == ".sgx") return superGrafxManifest(location); + if(type == ".msx") return msxManifest(location); if(type == ".gb") return gameBoyManifest(location); if(type == ".gbc") return gameBoyColorManifest(location); if(type == ".gba") return gameBoyAdvanceManifest(location); @@ -85,10 +91,13 @@ auto Icarus::import(string location) -> string { if(type == ".fc" || type == ".nes") return famicomImport(buffer, location); if(type == ".sfc" || type == ".smc") return superFamicomImport(buffer, location); + if(type == ".sg1000" || type == ".sg") return sg1000Import(buffer, location); + if(type == ".sc3000" || type == ".sc") return sc3000Import(buffer, location); if(type == ".ms" || type == ".sms") return masterSystemImport(buffer, location); if(type == ".md" || type == ".smd" || type == ".gen") return megaDriveImport(buffer, location); if(type == ".pce") return pcEngineImport(buffer, location); - if(type == ".sg" || type == ".sgx") return superGrafxImport(buffer, location); + if(type == ".sgx") return superGrafxImport(buffer, location); + if(type == ".msx") return msxImport(buffer, location); if(type == ".gb") return gameBoyImport(buffer, location); if(type == ".gbc") return gameBoyColorImport(buffer, location); if(type == ".gba") return gameBoyAdvanceImport(buffer, location); diff --git a/icarus/core/core.hpp b/icarus/core/core.hpp index f9dd53c5..e2ca8f19 100644 --- a/icarus/core/core.hpp +++ b/icarus/core/core.hpp @@ -46,6 +46,16 @@ struct Icarus { auto superFamicomManifest(vector& buffer, string location) -> string; auto superFamicomImport(vector& buffer, string location) -> string; + //sg-1000.cpp + auto sg1000Manifest(string location) -> string; + auto sg1000Manifest(vector& buffer, string location) -> string; + auto sg1000Import(vector& buffer, string location) -> string; + + //sc-3000.cpp + auto sc3000Manifest(string location) -> string; + auto sc3000Manifest(vector& buffer, string location) -> string; + auto sc3000Import(vector& buffer, string location) -> string; + //master-system.cpp auto masterSystemManifest(string location) -> string; auto masterSystemManifest(vector& buffer, string location) -> string; @@ -66,6 +76,11 @@ struct Icarus { auto superGrafxManifest(vector& buffer, string location) -> string; auto superGrafxImport(vector& buffer, string location) -> string; + //msx.cpp + auto msxManifest(string location) -> string; + auto msxManifest(vector& buffer, string location) -> string; + auto msxImport(vector& buffer, string location) -> string; + //game-boy.cpp auto gameBoyManifest(string location) -> string; auto gameBoyManifest(vector& buffer, string location) -> string; @@ -119,10 +134,13 @@ private: namespace Database { Markup::Node Famicom; Markup::Node SuperFamicom; + Markup::Node SG1000; + Markup::Node SC3000; Markup::Node MasterSystem; Markup::Node MegaDrive; Markup::Node PCEngine; Markup::Node SuperGrafx; + Markup::Node MSX; Markup::Node GameBoy; Markup::Node GameBoyColor; Markup::Node GameBoyAdvance; diff --git a/icarus/core/msx.cpp b/icarus/core/msx.cpp new file mode 100644 index 00000000..b9d00a55 --- /dev/null +++ b/icarus/core/msx.cpp @@ -0,0 +1,39 @@ +auto Icarus::msxManifest(string location) -> string { + vector buffer; + concatenate(buffer, {location, "program.rom"}); + return msxManifest(buffer, location); +} + +auto Icarus::msxManifest(vector& buffer, string location) -> string { + if(settings["icarus/UseDatabase"].boolean()) { + auto digest = Hash::SHA256(buffer).digest(); + for(auto game : Database::MSX.find("game")) { + if(game["sha256"].text() == digest) return BML::serialize(game); + } + } + + if(settings["icarus/UseHeuristics"].boolean()) { + Heuristics::MSX game{buffer, location}; + if(auto manifest = game.manifest()) return manifest; + } + + return {}; +} + +auto Icarus::msxImport(vector& buffer, string location) -> string { + auto name = Location::prefix(location); + auto source = Location::path(location); + string target{settings["Library/Location"].text(), "MSX/", name, ".msx/"}; + + auto manifest = msxManifest(buffer, location); + if(!manifest) return failure("failed to parse ROM image"); + + if(!create(target)) return failure("library path unwritable"); + if(exists({source, name, ".sav"}) && !exists({target, "save.ram"})) { + copy({source, name, ".sav"}, {target, "save.ram"}); + } + + if(settings["icarus/CreateManifests"].boolean()) write({target, "manifest.bml"}, manifest); + write({target, "program.rom"}, buffer); + return success(target); +} diff --git a/icarus/core/sc-3000.cpp b/icarus/core/sc-3000.cpp new file mode 100644 index 00000000..eb9d8906 --- /dev/null +++ b/icarus/core/sc-3000.cpp @@ -0,0 +1,39 @@ +auto Icarus::sc3000Manifest(string location) -> string { + vector buffer; + concatenate(buffer, {location, "program.rom"}); + return sc3000Manifest(buffer, location); +} + +auto Icarus::sc3000Manifest(vector& buffer, string location) -> string { + if(settings["icarus/UseDatabase"].boolean()) { + auto digest = Hash::SHA256(buffer).digest(); + for(auto game : Database::SC3000.find("game")) { + if(game["sha256"].text() == digest) return BML::serialize(game); + } + } + + if(settings["icarus/UseHeuristics"].boolean()) { + Heuristics::SC3000 game{buffer, location}; + if(auto manifest = game.manifest()) return manifest; + } + + return {}; +} + +auto Icarus::sc3000Import(vector& buffer, string location) -> string { + auto name = Location::prefix(location); + auto source = Location::path(location); + string target{settings["Library/Location"].text(), "SC-3000/", name, ".sc3000/"}; + + auto manifest = sc3000Manifest(buffer, location); + if(!manifest) return failure("failed to parse ROM image"); + + if(!create(target)) return failure("library path unwritable"); + if(exists({source, name, ".sav"}) && !exists({target, "save.ram"})) { + copy({source, name, ".sav"}, {target, "save.ram"}); + } + + if(settings["icarus/CreateManifests"].boolean()) write({target, "manifest.bml"}, manifest); + write({target, "program.rom"}, buffer); + return success(target); +} diff --git a/icarus/core/sg-1000.cpp b/icarus/core/sg-1000.cpp new file mode 100644 index 00000000..73b58291 --- /dev/null +++ b/icarus/core/sg-1000.cpp @@ -0,0 +1,39 @@ +auto Icarus::sg1000Manifest(string location) -> string { + vector buffer; + concatenate(buffer, {location, "program.rom"}); + return sg1000Manifest(buffer, location); +} + +auto Icarus::sg1000Manifest(vector& buffer, string location) -> string { + if(settings["icarus/UseDatabase"].boolean()) { + auto digest = Hash::SHA256(buffer).digest(); + for(auto game : Database::SG1000.find("game")) { + if(game["sha256"].text() == digest) return BML::serialize(game); + } + } + + if(settings["icarus/UseHeuristics"].boolean()) { + Heuristics::SG1000 game{buffer, location}; + if(auto manifest = game.manifest()) return manifest; + } + + return {}; +} + +auto Icarus::sg1000Import(vector& buffer, string location) -> string { + auto name = Location::prefix(location); + auto source = Location::path(location); + string target{settings["Library/Location"].text(), "SG-1000/", name, ".sg1000/"}; + + auto manifest = sg1000Manifest(buffer, location); + if(!manifest) return failure("failed to parse ROM image"); + + if(!create(target)) return failure("library path unwritable"); + if(exists({source, name, ".sav"}) && !exists({target, "save.ram"})) { + copy({source, name, ".sav"}, {target, "save.ram"}); + } + + if(settings["icarus/CreateManifests"].boolean()) write({target, "manifest.bml"}, manifest); + write({target, "program.rom"}, buffer); + return success(target); +} diff --git a/icarus/heuristics/msx.cpp b/icarus/heuristics/msx.cpp new file mode 100644 index 00000000..6470cef3 --- /dev/null +++ b/icarus/heuristics/msx.cpp @@ -0,0 +1,31 @@ +namespace Heuristics { + +struct MSX { + MSX(vector& data, string location); + explicit operator bool() const; + auto manifest() const -> string; + +private: + vector& data; + string location; +}; + +MSX::MSX(vector& data, string location) : data(data), location(location) { +} + +MSX::operator bool() const { + return (bool)data; +} + +auto MSX::manifest() const -> string { + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" board\n"); + output.append(Memory{}.type("ROM").size(data.size()).content("Program").text()); + return output; +} + +} diff --git a/icarus/heuristics/sc-3000.cpp b/icarus/heuristics/sc-3000.cpp new file mode 100644 index 00000000..6f24f26c --- /dev/null +++ b/icarus/heuristics/sc-3000.cpp @@ -0,0 +1,32 @@ +namespace Heuristics { + +struct SC3000 { + SC3000(vector& data, string location); + explicit operator bool() const; + auto manifest() const -> string; + +private: + vector& data; + string location; +}; + +SC3000::SC3000(vector& data, string location) : data(data), location(location) { +} + +SC3000::operator bool() const { + return (bool)data; +} + +auto SC3000::manifest() const -> string { + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" board\n"); + output.append(Memory{}.type("ROM").size(data.size()).content("Program").text()); + output.append(Memory{}.type("RAM").size(0x8000).content("Save").text()); + return output; +} + +} diff --git a/icarus/heuristics/sg-1000.cpp b/icarus/heuristics/sg-1000.cpp new file mode 100644 index 00000000..1a5f24ed --- /dev/null +++ b/icarus/heuristics/sg-1000.cpp @@ -0,0 +1,32 @@ +namespace Heuristics { + +struct SG1000 { + SG1000(vector& data, string location); + explicit operator bool() const; + auto manifest() const -> string; + +private: + vector& data; + string location; +}; + +SG1000::SG1000(vector& data, string location) : data(data), location(location) { +} + +SG1000::operator bool() const { + return (bool)data; +} + +auto SG1000::manifest() const -> string { + string output; + output.append("game\n"); + output.append(" sha256: ", Hash::SHA256(data).digest(), "\n"); + output.append(" label: ", Location::prefix(location), "\n"); + output.append(" name: ", Location::prefix(location), "\n"); + output.append(" board\n"); + output.append(Memory{}.type("ROM").size(data.size()).content("Program").text()); + output.append(Memory{}.type("RAM").size(0x8000).content("Save").text()); + return output; +} + +} diff --git a/icarus/icarus.cpp b/icarus/icarus.cpp index efd02356..4614445b 100644 --- a/icarus/icarus.cpp +++ b/icarus/icarus.cpp @@ -19,10 +19,13 @@ Settings settings; #include "heuristics/heuristics.cpp" #include "heuristics/famicom.cpp" #include "heuristics/super-famicom.cpp" +#include "heuristics/sg-1000.cpp" +#include "heuristics/sc-3000.cpp" #include "heuristics/master-system.cpp" #include "heuristics/mega-drive.cpp" #include "heuristics/pc-engine.cpp" #include "heuristics/supergrafx.cpp" +#include "heuristics/msx.cpp" #include "heuristics/game-boy.cpp" #include "heuristics/game-boy-advance.cpp" #include "heuristics/game-gear.cpp" @@ -34,10 +37,13 @@ Settings settings; #include "core/core.cpp" #include "core/famicom.cpp" #include "core/super-famicom.cpp" +#include "core/sg-1000.cpp" +#include "core/sc-3000.cpp" #include "core/master-system.cpp" #include "core/mega-drive.cpp" #include "core/pc-engine.cpp" #include "core/supergrafx.cpp" +#include "core/msx.cpp" #include "core/game-boy.cpp" #include "core/game-boy-color.cpp" #include "core/game-boy-advance.cpp" @@ -85,10 +91,13 @@ auto nall::main(Arguments arguments) -> void { .setFilters("ROM Files|" "*.fc:*.nes:" "*.sfc:*.smc:" + "*.sg1000:*.sg:" + "*.sc3000:*.sc:" "*.ms:*.sms:" "*.md:*.smd:*.gen:" "*.pce:" - "*.sg:*.sgx:" + "*.sgx:" + "*.msx:" "*.gb:" "*.gbc:" "*.gba:" diff --git a/icarus/ui/scan-dialog.cpp b/icarus/ui/scan-dialog.cpp index 5c60212e..e57df071 100644 --- a/icarus/ui/scan-dialog.cpp +++ b/icarus/ui/scan-dialog.cpp @@ -102,10 +102,13 @@ auto ScanDialog::gamePakType(const string& type) -> bool { return type == ".sys" || type == ".fc" || type == ".sfc" + || type == ".sg1000" + || type == ".sc3000" || type == ".ms" || type == ".md" || type == ".pce" - || type == ".sg" + || type == ".sgx" + || type == ".msx" || type == ".gb" || type == ".gbc" || type == ".gba" @@ -121,10 +124,13 @@ auto ScanDialog::gameRomType(const string& type) -> bool { return type == ".zip" || type == ".fc" || type == ".nes" || type == ".sfc" || type == ".smc" + || type == ".sg1000" || type == ".sg" + || type == ".sc3000" || type == ".sc" || type == ".ms" || type == ".sms" || type == ".md" || type == ".smd" || type == ".gen" || type == ".pce" - || type == ".sg" || type == ".sgx" + || type == ".sgx" + || type == ".msx" || type == ".gb" || type == ".gbc" || type == ".gba" diff --git a/nall/dsp/iir/dc-removal.hpp b/nall/dsp/iir/dc-removal.hpp new file mode 100644 index 00000000..1fc17e65 --- /dev/null +++ b/nall/dsp/iir/dc-removal.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +//DC offset removal IIR filter + +namespace nall { namespace DSP { namespace IIR { + +struct DCRemoval { + inline auto reset() -> void; + inline auto process(double in) -> double; //normalized sample (-1.0 to +1.0) + +private: + double x; + double y; +}; + +auto DCRemoval::reset() -> void { + x = 0.0; + y = 0.0; +} + +auto DCRemoval::process(double in) -> double { + x = 0.999 * x + in - y; + y = in; + return x; +} + +}}}