mirror of https://github.com/bsnes-emu/bsnes.git
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.
This commit is contained in:
parent
598076e400
commit
90da691717
|
@ -39,7 +39,7 @@ auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stre
|
|||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(true) {
|
||||
while(streams) {
|
||||
for(auto& stream : streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/dsp/iir/dc-removal.hpp>
|
||||
#include <nall/dsp/iir/one-pole.hpp>
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
|
@ -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<double> 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;
|
||||
|
|
|
@ -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<double> outputFrequency) -> void {
|
||||
|
@ -35,27 +33,46 @@ auto Stream::setFrequency(double inputFrequency, maybe<double> 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) {
|
||||
|
|
|
@ -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/";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace MasterSystem {
|
||||
|
||||
Settings settings;
|
||||
#include "sg-1000.cpp"
|
||||
#include "sc-3000.cpp"
|
||||
#include "master-system.cpp"
|
||||
#include "game-gear.cpp"
|
||||
|
||||
|
|
|
@ -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<Display> override;
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto ports() -> vector<Port> override;
|
||||
auto devices(uint port) -> vector<Device> override;
|
||||
auto inputs(uint device) -> vector<Input> 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<Display> override;
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto ports() -> vector<Port> override;
|
||||
auto devices(uint port) -> vector<Device> override;
|
||||
auto inputs(uint device) -> vector<Input> 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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
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<Port> { return {
|
||||
{ID::Port::Controller1, "Controller Port 1"},
|
||||
{ID::Port::Controller2, "Controller Port 2"},
|
||||
{ID::Port::Hardware, "Hardware" }};
|
||||
}
|
||||
|
||||
auto SC3000Interface::devices(uint port) -> vector<Device> {
|
||||
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<Input> {
|
||||
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);
|
||||
}
|
|
@ -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;
|
||||
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<Port> { return {
|
||||
{ID::Port::Controller1, "Controller Port 1"},
|
||||
{ID::Port::Controller2, "Controller Port 2"},
|
||||
{ID::Port::Hardware, "Hardware" }};
|
||||
}
|
||||
|
||||
auto SG1000Interface::devices(uint port) -> vector<Device> {
|
||||
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<Input> {
|
||||
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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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 = {};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Object, 8> 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Object, 8> objects;
|
||||
array<Object[8]> 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
system name:SC-3000
|
|
@ -0,0 +1 @@
|
|||
system name:SG-1000
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -46,6 +46,16 @@ struct Icarus {
|
|||
auto superFamicomManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto superFamicomImport(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//sg-1000.cpp
|
||||
auto sg1000Manifest(string location) -> string;
|
||||
auto sg1000Manifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto sg1000Import(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//sc-3000.cpp
|
||||
auto sc3000Manifest(string location) -> string;
|
||||
auto sc3000Manifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto sc3000Import(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//master-system.cpp
|
||||
auto masterSystemManifest(string location) -> string;
|
||||
auto masterSystemManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
@ -66,6 +76,11 @@ struct Icarus {
|
|||
auto superGrafxManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto superGrafxImport(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//msx.cpp
|
||||
auto msxManifest(string location) -> string;
|
||||
auto msxManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto msxImport(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//game-boy.cpp
|
||||
auto gameBoyManifest(string location) -> string;
|
||||
auto gameBoyManifest(vector<uint8_t>& 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;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
auto Icarus::msxManifest(string location) -> string {
|
||||
vector<uint8_t> buffer;
|
||||
concatenate(buffer, {location, "program.rom"});
|
||||
return msxManifest(buffer, location);
|
||||
}
|
||||
|
||||
auto Icarus::msxManifest(vector<uint8_t>& 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<uint8_t>& 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);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
auto Icarus::sc3000Manifest(string location) -> string {
|
||||
vector<uint8_t> buffer;
|
||||
concatenate(buffer, {location, "program.rom"});
|
||||
return sc3000Manifest(buffer, location);
|
||||
}
|
||||
|
||||
auto Icarus::sc3000Manifest(vector<uint8_t>& 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<uint8_t>& 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);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
auto Icarus::sg1000Manifest(string location) -> string {
|
||||
vector<uint8_t> buffer;
|
||||
concatenate(buffer, {location, "program.rom"});
|
||||
return sg1000Manifest(buffer, location);
|
||||
}
|
||||
|
||||
auto Icarus::sg1000Manifest(vector<uint8_t>& 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<uint8_t>& 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);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
namespace Heuristics {
|
||||
|
||||
struct MSX {
|
||||
MSX(vector<uint8_t>& data, string location);
|
||||
explicit operator bool() const;
|
||||
auto manifest() const -> string;
|
||||
|
||||
private:
|
||||
vector<uint8_t>& data;
|
||||
string location;
|
||||
};
|
||||
|
||||
MSX::MSX(vector<uint8_t>& 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
namespace Heuristics {
|
||||
|
||||
struct SC3000 {
|
||||
SC3000(vector<uint8_t>& data, string location);
|
||||
explicit operator bool() const;
|
||||
auto manifest() const -> string;
|
||||
|
||||
private:
|
||||
vector<uint8_t>& data;
|
||||
string location;
|
||||
};
|
||||
|
||||
SC3000::SC3000(vector<uint8_t>& 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
namespace Heuristics {
|
||||
|
||||
struct SG1000 {
|
||||
SG1000(vector<uint8_t>& data, string location);
|
||||
explicit operator bool() const;
|
||||
auto manifest() const -> string;
|
||||
|
||||
private:
|
||||
vector<uint8_t>& data;
|
||||
string location;
|
||||
};
|
||||
|
||||
SG1000::SG1000(vector<uint8_t>& 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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:"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <nall/dsp/dsp.hpp>
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
}}}
|
Loading…
Reference in New Issue