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:
Tim Allen 2018-12-21 11:01:14 +11:00
parent 598076e400
commit 90da691717
48 changed files with 942 additions and 218 deletions

View File

@ -39,7 +39,7 @@ auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stre
} }
auto Audio::process() -> void { auto Audio::process() -> void {
while(true) { while(streams) {
for(auto& stream : streams) { for(auto& stream : streams) {
if(!stream->pending()) return; if(!stream->pending()) return;
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <nall/dsp/iir/dc-removal.hpp>
#include <nall/dsp/iir/one-pole.hpp> #include <nall/dsp/iir/one-pole.hpp>
#include <nall/dsp/iir/biquad.hpp> #include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp> #include <nall/dsp/resampler/cubic.hpp>
@ -37,12 +38,13 @@ private:
}; };
struct Filter { struct Filter {
enum class Order : uint { First, Second }; enum class Mode : uint { DCRemoval, OnePole, Biquad } mode;
enum class Type : uint { LowPass, HighPass }; enum class Type : uint { None, LowPass, HighPass } type;
enum class Order : uint { None, First, Second } order;
Order order; DSP::IIR::DCRemoval dcRemoval;
DSP::IIR::OnePole onePole; //first-order DSP::IIR::OnePole onePole;
DSP::IIR::Biquad biquad; //second-order DSP::IIR::Biquad biquad;
}; };
struct Stream { struct Stream {
@ -50,7 +52,9 @@ struct Stream {
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void; 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 pending() const -> bool;
auto read(double samples[]) -> uint; auto read(double samples[]) -> uint;

View File

@ -1,14 +1,12 @@
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void { auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void {
this->inputFrequency = inputFrequency;
this->outputFrequency = outputFrequency;
channels.reset(); channels.reset();
channels.resize(channels_); channels.resize(channelCount);
for(auto& channel : channels) { for(auto& channel : channels) {
channel.filters.reset(); channel.filters.reset();
channel.resampler.reset(inputFrequency, outputFrequency);
} }
setFrequency(inputFrequency, outputFrequency);
} }
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void { auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
@ -35,28 +33,47 @@ 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(auto& channel : channels) {
for(uint pass : range(passes)) { for(uint pass : range(passes)) {
Filter filter{order};
if(order == Filter::Order::First) { if(order == Filter::Order::First) {
DSP::IIR::OnePole::Type _type; Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass; filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
}
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;
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
}
channel.filters.append(filter); channel.filters.append(filter);
} }
if(order == Filter::Order::Second) {
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
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())) { for(auto c : range(channels.size())) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& filter : channels[c].filters) { for(auto& filter : channels[c].filters) {
switch(filter.order) { switch(filter.mode) {
case Filter::Order::First: sample = filter.onePole.process(sample); break; case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
case Filter::Order::Second: sample = filter.biquad.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) { for(auto& filter : channels[c].nyquist) {

View File

@ -28,7 +28,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; 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 Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "https://byuu.org/"; static const string Website = "https://byuu.org/";

View File

@ -74,9 +74,10 @@ auto APU::setSample(int16 sample) -> void {
auto APU::power(bool reset) -> void { auto APU::power(bool reset) -> void {
create(APU::Enter, system.frequency()); create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate()); stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0); stream->addHighPassFilter( 90.0, Emulator::Filter::Order::First);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0); stream->addHighPassFilter( 440.0, Emulator::Filter::Order::First);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0); stream->addLowPassFilter (14000.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
pulse[0].power(); pulse[0].power();
pulse[1].power(); pulse[1].power();

View File

@ -55,7 +55,8 @@ auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024); create(Enter, 2 * 1024 * 1024);
if(!Model::SuperGameBoy()) { if(!Model::SuperGameBoy()) {
stream = Emulator::audio.createStream(2, frequency()); 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; for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;

View File

@ -77,7 +77,8 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void { auto APU::power() -> void {
create(APU::Enter, system.frequency()); create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(2, frequency() / 64.0); 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; clock = 0;
square1.power(); square1.power();

View File

@ -37,8 +37,9 @@ auto PSG::step(uint clocks) -> void {
auto PSG::power(bool reset) -> void { auto PSG::power(bool reset) -> void {
create(PSG::Enter, system.frequency() / 15.0); create(PSG::Enter, system.frequency() / 15.0);
stream = Emulator::audio.createStream(1, frequency() / 16.0); stream = Emulator::audio.createStream(1, frequency() / 16.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0); stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
select = 0; select = 0;
for(auto n : range(15)) { for(auto n : range(15)) {

View File

@ -157,8 +157,9 @@ auto YM2612::step(uint clocks) -> void {
auto YM2612::power(bool reset) -> void { auto YM2612::power(bool reset) -> void {
create(YM2612::Enter, system.frequency() / 7.0); create(YM2612::Enter, system.frequency() / 7.0);
stream = Emulator::audio.createStream(2, frequency() / 144.0); stream = Emulator::audio.createStream(2, frequency() / 144.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0); stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0); stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
io = {}; io = {};
lfo = {}; lfo = {};

View File

@ -9,6 +9,20 @@ Cartridge cartridge;
auto Cartridge::load() -> bool { auto Cartridge::load() -> bool {
information = {}; 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(Model::MasterSystem()) {
if(auto loaded = platform->load(ID::MasterSystem, "Master System", "ms", {"NTSC", "PAL"})) { if(auto loaded = platform->load(ID::MasterSystem, "Master System", "ms", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID; information.pathID = loaded.pathID;

View File

@ -32,7 +32,7 @@ auto Controller::main() -> void {
auto ControllerPort::connect(uint deviceID) -> void { auto ControllerPort::connect(uint deviceID) -> void {
delete device; delete device;
if(!system.loaded()) return; if(!system.loaded()) return;
if(!Model::MasterSystem()) return; if(Model::GameGear()) return;
switch(deviceID) { default: switch(deviceID) { default:
case ID::Device::None: device = new Controller(port); break; case ID::Device::None: device = new Controller(port); break;

View File

@ -42,6 +42,16 @@ auto CPU::in(uint8 addr) -> uint8 {
} }
case 3: { 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()) { if(Model::MasterSystem()) {
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0); bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
auto port1 = controllerPort1.device->readData(); auto port1 = controllerPort1.device->readData();

View File

@ -37,6 +37,13 @@ auto CPU::synchronizing() const -> bool {
//called once per frame //called once per frame
auto CPU::pollPause() -> void { 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()) { if(Model::MasterSystem()) {
static bool pause = 0; static bool pause = 0;
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1); bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);

View File

@ -3,6 +3,8 @@
namespace MasterSystem { namespace MasterSystem {
Settings settings; Settings settings;
#include "sg-1000.cpp"
#include "sc-3000.cpp"
#include "master-system.cpp" #include "master-system.cpp"
#include "game-gear.cpp" #include "game-gear.cpp"

View File

@ -5,6 +5,8 @@ namespace MasterSystem {
struct ID { struct ID {
enum : uint { enum : uint {
System, System,
SG1000,
SC3000,
MasterSystem, MasterSystem,
GameGear, GameGear,
}; };
@ -17,6 +19,8 @@ struct ID {
struct Device { enum : uint { struct Device { enum : uint {
None, None,
SG1000Controls,
SC3000Controls,
MasterSystemControls, MasterSystemControls,
GameGearControls, GameGearControls,
Gamepad, Gamepad,
@ -44,6 +48,38 @@ struct Interface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override; 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 { struct MasterSystemInterface : Interface {
auto information() -> Information override; auto information() -> Information override;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -30,6 +30,8 @@ namespace MasterSystem {
}; };
struct Model { struct Model {
inline static auto SG1000() -> bool;
inline static auto SC3000() -> bool;
inline static auto MasterSystem() -> bool; inline static auto MasterSystem() -> bool;
inline static auto GameGear() -> bool; inline static auto GameGear() -> bool;
}; };

View File

@ -44,7 +44,8 @@ auto PSG::power() -> void {
//use stereo mode for both; output same sample to both channels for Master System //use stereo mode for both; output same sample to both channels for Master System
create(PSG::Enter, system.colorburst() / 16.0); create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(2, frequency()); 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; select = 0;
for(auto n : range(15)) { for(auto n : range(15)) {

View File

@ -50,7 +50,7 @@ auto System::save() -> void {
} }
auto System::unload() -> void { auto System::unload() -> void {
if(MasterSystem::Model::MasterSystem()) { if(!MasterSystem::Model::GameGear()) {
cpu.peripherals.reset(); cpu.peripherals.reset();
controllerPort1.unload(); controllerPort1.unload();
controllerPort2.unload(); controllerPort2.unload();
@ -71,7 +71,7 @@ auto System::power() -> void {
psg.power(); psg.power();
scheduler.primary(cpu); scheduler.primary(cpu);
if(MasterSystem::Model::MasterSystem()) { if(!MasterSystem::Model::GameGear()) {
controllerPort1.power(ID::Port::Controller1); controllerPort1.power(ID::Port::Controller1);
controllerPort2.power(ID::Port::Controller2); controllerPort2.power(ID::Port::Controller2);

View File

@ -1,5 +1,5 @@
struct System { struct System {
enum class Model : uint { MasterSystem, GameGear }; enum class Model : uint { SG1000, SC3000, MasterSystem, GameGear };
enum class Region : uint { NTSC, PAL }; enum class Region : uint { NTSC, PAL };
auto loaded() const -> bool { return information.loaded; } auto loaded() const -> bool { return information.loaded; }
@ -38,6 +38,8 @@ private:
extern System system; 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::MasterSystem() -> bool { return system.model() == System::Model::MasterSystem; }
auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; } auto Model::GameGear() -> bool { return system.model() == System::Model::GameGear; }

View File

@ -1,58 +1,122 @@
auto VDP::Background::scanline() -> void { auto VDP::Background::run(uint8 hoffset, uint9 voffset) -> void {
state.x = 0; output = {};
state.y = vdp.io.vcounter; 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 { auto VDP::Background::graphics1(uint8 hoffset, uint9 voffset) -> void {
uint8 hoffset = state.x++; uint14 nameTableAddress;
uint9 voffset = state.y; 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() uint14 patternAddress;
|| hoffset < (vdp.io.hscroll & 7) patternAddress.bits( 0, 2) = voffset.bits(0,2);
) { patternAddress.bits( 3,10) = pattern;
output.color = 0; patternAddress.bits(11,13) = vdp.io.patternTableAddress;
output.palette = 0;
output.priority = 0; uint14 colorAddress; //d5 = 0
return; 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; auto VDP::Background::graphics2(uint8 hoffset, uint9 voffset) -> void {
bool vscroll = !vdp.io.verticalScrollLock || hoffset <= 191; 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; uint14 patternAddress;
if(vscroll) voffset += vdp.io.vscroll; 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; uint14 nameTableAddress;
if(vdp.vlines() == 192) { if(vlines == 192) {
if(voffset >= 224) voffset -= 224; 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 { } else {
voffset &= 255; voffset += 224;
nameTableAddress = (vdp.io.nameTableAddress & ~1) << 11 | 0x700; 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; uint16 pattern;
tiledata = vdp.vram[nameTableAddress + 0] << 0; pattern.byte(0) = vdp.vram[nameTableAddress | 0];
tiledata |= vdp.vram[nameTableAddress + 1] << 8; pattern.byte(1) = vdp.vram[nameTableAddress | 1];
uint14 patternAddress = tiledata.bits(0,8) << 5; if(pattern.bit( 9)) hoffset ^= 7; //hflip
if(tiledata.bit(9)) hoffset ^= 7; if(pattern.bit(10)) voffset ^= 7; //vflip
if(tiledata.bit(10)) voffset ^= 7; output.palette = pattern.bit(11);
output.palette = tiledata.bit(11); output.priority = pattern.bit(12);
output.priority = tiledata.bit(12);
auto index = 7 - (hoffset & 7); uint14 patternAddress;
patternAddress += (voffset & 7) << 2; patternAddress.bits(2, 4) = voffset.bits(0,2);
output.color.bit(0) = vdp.vram[patternAddress + 0].bit(index); patternAddress.bits(5,13) = pattern.bits(0,8);
output.color.bit(1) = vdp.vram[patternAddress + 1].bit(index);
output.color.bit(2) = vdp.vram[patternAddress + 2].bit(index); uint3 index = hoffset ^ 7;
output.color.bit(3) = vdp.vram[patternAddress + 3].bit(index); 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; if(output.color == 0) output.priority = 0;
} }
auto VDP::Background::power() -> void { auto VDP::Background::power() -> void {
state = {};
output = {}; output = {};
} }

View File

@ -1,16 +1,9 @@
auto VDP::vcounter() -> uint8 { auto VDP::vcounter() -> uint8 {
if(io.lines240) { switch(io.mode) {
//NTSC 256x240 default: return io.vcounter <= 218 ? io.vcounter : io.vcounter - 6; //256x192
return io.vcounter; case 0b1011: return io.vcounter <= 234 ? io.vcounter : io.vcounter - 6; //256x224
} else if(io.lines224) { case 0b1110: return io.vcounter; //256x240
//NTSC 256x224
return io.vcounter <= 234 ? io.vcounter : io.vcounter - 6;
} else {
//NTSC 256x192
return io.vcounter <= 218 ? io.vcounter : io.vcounter - 6;
} }
unreachable;
} }
auto VDP::hcounter() -> uint8 { auto VDP::hcounter() -> uint8 {
@ -51,9 +44,8 @@ auto VDP::data(uint8 data) -> void {
vram[io.address++] = data; vram[io.address++] = data;
} else { } else {
uint mask = 0; uint mask = 0;
if(Model::MasterSystem()) mask = 0x1f; if(Model::MasterSystem()) cram[io.address++ & 0x1f] = data;
if(Model::GameGear()) mask = 0x3f; if(Model::GameGear()) cram[io.address++ & 0x3f] = data;
cram[io.address++ & mask] = data;
} }
} }
@ -83,8 +75,8 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void {
//mode control 1 //mode control 1
case 0x0: { case 0x0: {
io.externalSync = data.bit(0); io.externalSync = data.bit(0);
io.extendedHeight = data.bit(1); io.mode.bit(1) = data.bit(1);
io.mode4 = data.bit(2); io.mode.bit(3) = data.bit(2) & !Model::SG1000() & !Model::SC3000();
io.spriteShift = data.bit(3); io.spriteShift = data.bit(3);
io.lineInterrupts = data.bit(4); io.lineInterrupts = data.bit(4);
io.leftClip = data.bit(5); io.leftClip = data.bit(5);
@ -97,8 +89,8 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void {
case 0x1: { case 0x1: {
io.spriteDouble = data.bit(0); io.spriteDouble = data.bit(0);
io.spriteTile = data.bit(1); io.spriteTile = data.bit(1);
io.lines240 = data.bit(3); io.mode.bit(2) = data.bit(3);
io.lines224 = data.bit(4); io.mode.bit(0) = data.bit(4);
io.frameInterrupts = data.bit(5); io.frameInterrupts = data.bit(5);
io.displayEnable = data.bit(6); io.displayEnable = data.bit(6);
return; return;
@ -106,8 +98,7 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void {
//name table base address //name table base address
case 0x2: { case 0x2: {
io.nameTableMask = data.bit(0); io.nameTableAddress = data.bits(0,3);
io.nameTableAddress = data.bits(1,3);
return; return;
} }
@ -119,21 +110,19 @@ auto VDP::registerWrite(uint4 addr, uint8 data) -> void {
//pattern table base address //pattern table base address
case 0x4: { case 0x4: {
io.patternTableAddress = data.bits(0,7); io.patternTableAddress = data.bits(0,2);
return; return;
} }
//sprite attribute table base address //sprite attribute table base address
case 0x5: { case 0x5: {
io.spriteAttributeTableMask = data.bit(0); io.spriteAttributeTableAddress = data.bits(0,6);
io.spriteAttributeTableAddress = data.bits(1,6);
return; return;
} }
//sprite pattern table base address //sprite pattern table base address
case 0x6: { case 0x6: {
io.spritePatternTableMask = data.bits(0,1); io.spritePatternTableAddress = data.bits(0,2);
io.spritePatternTableAddress = data.bit(2);
return; return;
} }

View File

@ -21,8 +21,6 @@ auto VDP::serialize(serializer& s) -> void {
s.integer(io.address); s.integer(io.address);
s.integer(io.vramLatch); s.integer(io.vramLatch);
s.integer(io.externalSync); s.integer(io.externalSync);
s.integer(io.extendedHeight);
s.integer(io.mode4);
s.integer(io.spriteShift); s.integer(io.spriteShift);
s.integer(io.lineInterrupts); s.integer(io.lineInterrupts);
s.integer(io.leftClip); s.integer(io.leftClip);
@ -30,17 +28,13 @@ auto VDP::serialize(serializer& s) -> void {
s.integer(io.verticalScrollLock); s.integer(io.verticalScrollLock);
s.integer(io.spriteDouble); s.integer(io.spriteDouble);
s.integer(io.spriteTile); s.integer(io.spriteTile);
s.integer(io.lines240);
s.integer(io.lines224);
s.integer(io.frameInterrupts); s.integer(io.frameInterrupts);
s.integer(io.displayEnable); s.integer(io.displayEnable);
s.integer(io.nameTableMask); s.integer(io.mode);
s.integer(io.nameTableAddress); s.integer(io.nameTableAddress);
s.integer(io.colorTableAddress); s.integer(io.colorTableAddress);
s.integer(io.patternTableAddress); s.integer(io.patternTableAddress);
s.integer(io.spriteAttributeTableMask);
s.integer(io.spriteAttributeTableAddress); s.integer(io.spriteAttributeTableAddress);
s.integer(io.spritePatternTableMask);
s.integer(io.spritePatternTableAddress); s.integer(io.spritePatternTableAddress);
s.integer(io.backdropColor); s.integer(io.backdropColor);
s.integer(io.hscroll); s.integer(io.hscroll);
@ -49,16 +43,18 @@ auto VDP::serialize(serializer& s) -> void {
} }
auto VDP::Background::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.color);
s.integer(output.palette); s.integer(output.palette);
s.integer(output.priority); s.integer(output.priority);
} }
auto VDP::Sprite::serialize(serializer& s) -> void { auto VDP::Sprite::serialize(serializer& s) -> void {
s.integer(state.x);
s.integer(state.y);
s.integer(output.color); 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);
} }

View File

@ -1,10 +1,34 @@
auto VDP::Sprite::scanline() -> void { auto VDP::Sprite::setup(uint9 voffset) -> void {
state.x = 0; objectsValid = 0;
state.y = vdp.io.vcounter;
objects.reset();
uint limit = vdp.io.spriteTile ? 15 : 7; uint limit = vdp.io.spriteTile ? 15 : 7;
uint14 attributeAddress = vdp.io.spriteAttributeTableAddress << 8;
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;
uint8 x = vdp.vram[attributeAddress++];
uint8 pattern = vdp.vram[attributeAddress++];
uint8 extra = vdp.vram[attributeAddress++];
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)) { for(uint index : range(64)) {
uint8 y = vdp.vram[attributeAddress + index]; uint8 y = vdp.vram[attributeAddress + index];
uint8 x = vdp.vram[attributeAddress + 0x80 + (index << 1)]; uint8 x = vdp.vram[attributeAddress + 0x80 + (index << 1)];
@ -13,56 +37,97 @@ auto VDP::Sprite::scanline() -> void {
if(vdp.io.spriteShift) x -= 8; if(vdp.io.spriteShift) x -= 8;
y += 1; y += 1;
if(state.y < y) continue; if(voffset < y) continue;
if(state.y > y + limit) continue; if(voffset > y + limit) continue;
if(limit == 15) pattern.bit(0) = 0; if(limit == 15) pattern.bit(0) = 0;
objects.append({x, y, pattern}); objects[objectsValid] = {x, y, pattern};
if(objects.size() == 8) { if(++objectsValid == 8) {
vdp.io.spriteOverflow = 1; vdp.io.spriteOverflow = 1;
break; break;
} }
} }
} }
}
auto VDP::Sprite::run() -> void { auto VDP::Sprite::run(uint8 hoffset, uint9 voffset) -> void {
output.color = 0; 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; uint limit = vdp.io.spriteTile ? 15 : 7;
for(auto& o : objects) { for(uint objectIndex : range(objectsValid)) {
if(state.x < o.x) continue; auto& o = objects[objectIndex];
if(state.x > o.x + 7) continue; if(hoffset < o.x) continue;
if(hoffset > o.x + limit) continue;
uint x = state.x - o.x; uint x = hoffset - o.x;
uint y = state.y - o.y; uint y = voffset - o.y;
uint14 address = vdp.io.spritePatternTableAddress << 13; uint14 address;
address += o.pattern << 5; address.bits( 0,10) = (o.pattern << 3) + (x >> 3 << 4) + (y & limit);
address += (y & limit) << 2; 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; uint4 color;
color.bit(0) = vdp.vram[address + 0].bit(index); color.bit(0) = vdp.vram[address | 0].bit(index);
color.bit(1) = vdp.vram[address + 1].bit(index); color.bit(1) = vdp.vram[address | 1].bit(index);
color.bit(2) = vdp.vram[address + 2].bit(index); color.bit(2) = vdp.vram[address | 2].bit(index);
color.bit(3) = vdp.vram[address + 3].bit(index); color.bit(3) = vdp.vram[address | 3].bit(index);
if(color == 0) continue; if(color == 0) continue;
if(output.color) { if(output.color) { vdp.io.spriteCollision = true; break; }
vdp.io.spriteCollision = true;
break;
}
output.color = color; output.color = color;
} }
state.x++;
} }
auto VDP::Sprite::power() -> void { auto VDP::Sprite::power() -> void {
state = {};
output = {}; output = {};
objectsValid = 0;
} }

View File

@ -26,16 +26,14 @@ auto VDP::main() -> void {
io.intFrame = 1; io.intFrame = 1;
} }
background.scanline();
sprite.scanline();
//684 clocks/scanline //684 clocks/scanline
uint y = io.vcounter; uint y = io.vcounter;
sprite.setup(y);
if(y < vlines()) { if(y < vlines()) {
uint32* screen = buffer + (24 + y) * 256; uint32* screen = buffer + (24 + y) * 256;
for(uint x : range(256)) { for(uint x : range(256)) {
background.run(); background.run(x, y);
sprite.run(); sprite.run(x, y);
step(2); step(2);
uint12 color = palette(16 | io.backdropColor); uint12 color = palette(16 | io.backdropColor);
@ -74,6 +72,12 @@ auto VDP::step(uint clocks) -> void {
} }
auto VDP::refresh() -> 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()) { if(Model::MasterSystem()) {
//center the video output vertically in the viewport //center the video output vertically in the viewport
uint32* screen = buffer; uint32* screen = buffer;
@ -89,9 +93,11 @@ auto VDP::refresh() -> void {
} }
auto VDP::vlines() -> uint { auto VDP::vlines() -> uint {
if(io.lines240) return 240; switch(io.mode) {
if(io.lines224) return 224; default: return 192;
return 192; case 0b1011: return 224;
case 0b1110: return 240;
}
} }
auto VDP::vblank() -> bool { auto VDP::vblank() -> bool {
@ -109,14 +115,15 @@ auto VDP::power() -> void {
} }
auto VDP::palette(uint5 index) -> uint12 { auto VDP::palette(uint5 index) -> uint12 {
if(Model::MasterSystem()) { if(Model::SG1000() || Model::SC3000()) return index.bits(0,3);
return cram[index]; //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,
if(Model::GameGear()) { 0x02, 0x03, 0x05, 0x0f, 0x04, 0x33, 0x15, 0x3f,
return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8; };
} 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; return 0;
} }

View File

@ -1,4 +1,4 @@
//TI TMS9918A (derivative) //Texas Instruments TMS9918A (derivative)
struct VDP : Thread { struct VDP : Thread {
static auto Enter() -> void; static auto Enter() -> void;
@ -23,19 +23,16 @@ struct VDP : Thread {
//background.cpp //background.cpp
struct Background { struct Background {
auto scanline() -> void; auto run(uint8 hoffset, uint9 voffset) -> void;
auto run() -> 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; auto power() -> void;
//serialization.cpp //serialization.cpp
auto serialize(serializer&) -> void; auto serialize(serializer&) -> void;
struct State {
uint x = 0;
uint y = 0;
} state;
struct Output { struct Output {
uint4 color; uint4 color;
uint1 palette; uint1 palette;
@ -45,8 +42,11 @@ struct VDP : Thread {
//sprite.cpp //sprite.cpp
struct Sprite { struct Sprite {
auto scanline() -> void; auto setup(uint9 voffset) -> 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; auto power() -> void;
@ -57,18 +57,15 @@ struct VDP : Thread {
uint8 x; uint8 x;
uint8 y; uint8 y;
uint8 pattern; uint8 pattern;
uint4 color;
}; };
struct State {
uint x = 0;
uint y = 0;
} state;
struct Output { struct Output {
uint4 color; uint4 color;
} output; } output;
adaptive_array<Object, 8> objects; array<Object[8]> objects;
uint objectsValid;
} sprite; } sprite;
//serialization.cpp //serialization.cpp
@ -79,7 +76,7 @@ private:
uint32 buffer[256 * 264]; uint32 buffer[256 * 264];
uint8 vram[0x4000]; uint8 vram[0x4000];
uint8 cram[0x40]; //MS = 0x20, GG = 0x40 uint8 cram[0x40]; //SG + MS = 0x20, GG = 0x40
struct IO { struct IO {
uint vcounter = 0; //vertical counter uint vcounter = 0; //vertical counter
@ -105,8 +102,6 @@ private:
//$00 mode control 1 //$00 mode control 1
bool externalSync = 0; bool externalSync = 0;
bool extendedHeight = 0;
bool mode4 = 0;
bool spriteShift = 0; bool spriteShift = 0;
bool lineInterrupts = 0; bool lineInterrupts = 0;
bool leftClip = 0; bool leftClip = 0;
@ -116,28 +111,26 @@ private:
//$01 mode control 2 //$01 mode control 2
bool spriteDouble = 0; bool spriteDouble = 0;
bool spriteTile = 0; bool spriteTile = 0;
bool lines240 = 0;
bool lines224 = 0;
bool frameInterrupts = 0; bool frameInterrupts = 0;
bool displayEnable = 0; bool displayEnable = 0;
//$00 + $01
uint4 mode;
//$02 name table base address //$02 name table base address
uint1 nameTableMask; uint4 nameTableAddress;
uint3 nameTableAddress;
//$03 color table base address //$03 color table base address
uint8 colorTableAddress; uint8 colorTableAddress;
//$04 pattern table base address //$04 pattern table base address
uint8 patternTableAddress; uint3 patternTableAddress;
//$05 sprite attribute table base address //$05 sprite attribute table base address
uint1 spriteAttributeTableMask; uint7 spriteAttributeTableAddress;
uint6 spriteAttributeTableAddress;
//$06 sprite pattern table base address //$06 sprite pattern table base address
uint2 spritePatternTableMask; uint3 spritePatternTableAddress;
uint1 spritePatternTableAddress;
//$07 backdrop color //$07 backdrop color
uint4 backdropColor; uint4 backdropColor;

View File

@ -52,7 +52,8 @@ auto PSG::step(uint clocks) -> void {
auto PSG::power() -> void { auto PSG::power() -> void {
create(PSG::Enter, system.colorburst()); create(PSG::Enter, system.colorburst());
stream = Emulator::audio.createStream(2, frequency()); 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 = {}; io = {};
for(auto C : range(6)) channel[C].power(C); for(auto C : range(6)) channel[C].power(C);

View File

@ -46,7 +46,8 @@ auto ICD::power() -> void {
//SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator //SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator
create(ICD::Enter, (Frequency ? Frequency : system.cpuFrequency()) / 5.0); create(ICD::Enter, (Frequency ? Frequency : system.cpuFrequency()) / 5.0);
stream = Emulator::audio.createStream(2, frequency() / 2.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; r6003 = 0x00;
r6004 = 0xff; r6004 = 0xff;

View File

@ -0,0 +1 @@
system name:SC-3000

View File

@ -0,0 +1 @@
system name:SG-1000

View File

@ -33,7 +33,7 @@ endif
verbose: hiro.verbose ruby.verbose nall.verbose all; verbose: hiro.verbose ruby.verbose nall.verbose all;
install: install: all
ifeq ($(shell id -un),root) ifeq ($(shell id -un),root)
$(error "make install should not be run as root") $(error "make install should not be run as root")
else ifeq ($(platform),windows) else ifeq ($(platform),windows)

View File

@ -33,7 +33,7 @@ endif
verbose: hiro.verbose ruby.verbose nall.verbose all; verbose: hiro.verbose ruby.verbose nall.verbose all;
install: install: all
ifeq ($(shell id -un),root) ifeq ($(shell id -un),root)
$(error "make install should not be run as root") $(error "make install should not be run as root")
else ifeq ($(platform),windows) else ifeq ($(platform),windows)

View File

@ -93,8 +93,13 @@ auto Program::videoRefresh(uint displayID, const uint32* data, uint pitch, uint
} }
auto Program::audioSample(const double* samples, uint channels) -> void { auto Program::audioSample(const double* samples, uint channels) -> void {
if(channels == 1) {
double stereo[] = {samples[0], samples[0]};
audio->output(stereo);
} else {
audio->output(samples); audio->output(samples);
} }
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 { auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
if(focused() || settingsManager->input.allowInput.checked()) { if(focused() || settingsManager->input.allowInput.checked()) {

View File

@ -24,6 +24,12 @@ Program::Program(Arguments arguments) {
#ifdef CORE_SFC #ifdef CORE_SFC
emulators.append(new SuperFamicom::Interface); emulators.append(new SuperFamicom::Interface);
#endif #endif
#ifdef CORE_MS
emulators.append(new MasterSystem::SG1000Interface);
#endif
#ifdef CORE_MS
emulators.append(new MasterSystem::SC3000Interface);
#endif
#ifdef CORE_MS #ifdef CORE_MS
emulators.append(new MasterSystem::MasterSystemInterface); emulators.append(new MasterSystem::MasterSystemInterface);
#endif #endif

View File

@ -68,7 +68,8 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void { auto APU::power() -> void {
create(APU::Enter, 3'072'000); create(APU::Enter, 3'072'000);
stream = Emulator::audio.createStream(2, frequency()); 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, 0x004a, 0x004c);
bus.map(this, 0x004e, 0x0050); bus.map(this, 0x004e, 0x0050);

View File

@ -34,7 +34,7 @@ endif
$(call delete,obj/*) $(call delete,obj/*)
$(call delete,out/*) $(call delete,out/*)
install: install: all
ifeq ($(platform),macos) ifeq ($(platform),macos)
cp -R out/$(name).app /Applications/$(name).app cp -R out/$(name).app /Applications/$(name).app
else ifneq ($(filter $(platform),linux bsd),) else ifneq ($(filter $(platform),linux bsd),)

View File

@ -1,10 +1,13 @@
Icarus::Icarus() { Icarus::Icarus() {
Database::Famicom = BML::unserialize(string::read(locate("Database/Famicom.bml"))); Database::Famicom = BML::unserialize(string::read(locate("Database/Famicom.bml")));
Database::SuperFamicom = BML::unserialize(string::read(locate("Database/Super 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::MasterSystem = BML::unserialize(string::read(locate("Database/Master System.bml")));
Database::MegaDrive = BML::unserialize(string::read(locate("Database/Mega Drive.bml"))); Database::MegaDrive = BML::unserialize(string::read(locate("Database/Mega Drive.bml")));
Database::PCEngine = BML::unserialize(string::read(locate("Database/PC Engine.bml"))); Database::PCEngine = BML::unserialize(string::read(locate("Database/PC Engine.bml")));
Database::SuperGrafx = BML::unserialize(string::read(locate("Database/SuperGrafx.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::GameBoy = BML::unserialize(string::read(locate("Database/Game Boy.bml")));
Database::GameBoyColor = BML::unserialize(string::read(locate("Database/Game Boy Color.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"))); 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(); auto type = Location::suffix(location).downcase();
if(type == ".fc") return famicomManifest(location); if(type == ".fc") return famicomManifest(location);
if(type == ".sfc") return superFamicomManifest(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 == ".ms") return masterSystemManifest(location);
if(type == ".md") return megaDriveManifest(location); if(type == ".md") return megaDriveManifest(location);
if(type == ".pce") return pcEngineManifest(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 == ".gb") return gameBoyManifest(location);
if(type == ".gbc") return gameBoyColorManifest(location); if(type == ".gbc") return gameBoyColorManifest(location);
if(type == ".gba") return gameBoyAdvanceManifest(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 == ".fc" || type == ".nes") return famicomImport(buffer, location);
if(type == ".sfc" || type == ".smc") return superFamicomImport(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 == ".ms" || type == ".sms") return masterSystemImport(buffer, location);
if(type == ".md" || type == ".smd" || type == ".gen") return megaDriveImport(buffer, location); if(type == ".md" || type == ".smd" || type == ".gen") return megaDriveImport(buffer, location);
if(type == ".pce") return pcEngineImport(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 == ".gb") return gameBoyImport(buffer, location);
if(type == ".gbc") return gameBoyColorImport(buffer, location); if(type == ".gbc") return gameBoyColorImport(buffer, location);
if(type == ".gba") return gameBoyAdvanceImport(buffer, location); if(type == ".gba") return gameBoyAdvanceImport(buffer, location);

View File

@ -46,6 +46,16 @@ struct Icarus {
auto superFamicomManifest(vector<uint8_t>& buffer, string location) -> string; auto superFamicomManifest(vector<uint8_t>& buffer, string location) -> string;
auto superFamicomImport(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 //master-system.cpp
auto masterSystemManifest(string location) -> string; auto masterSystemManifest(string location) -> string;
auto masterSystemManifest(vector<uint8_t>& buffer, 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 superGrafxManifest(vector<uint8_t>& buffer, string location) -> string;
auto superGrafxImport(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 //game-boy.cpp
auto gameBoyManifest(string location) -> string; auto gameBoyManifest(string location) -> string;
auto gameBoyManifest(vector<uint8_t>& buffer, string location) -> string; auto gameBoyManifest(vector<uint8_t>& buffer, string location) -> string;
@ -119,10 +134,13 @@ private:
namespace Database { namespace Database {
Markup::Node Famicom; Markup::Node Famicom;
Markup::Node SuperFamicom; Markup::Node SuperFamicom;
Markup::Node SG1000;
Markup::Node SC3000;
Markup::Node MasterSystem; Markup::Node MasterSystem;
Markup::Node MegaDrive; Markup::Node MegaDrive;
Markup::Node PCEngine; Markup::Node PCEngine;
Markup::Node SuperGrafx; Markup::Node SuperGrafx;
Markup::Node MSX;
Markup::Node GameBoy; Markup::Node GameBoy;
Markup::Node GameBoyColor; Markup::Node GameBoyColor;
Markup::Node GameBoyAdvance; Markup::Node GameBoyAdvance;

39
icarus/core/msx.cpp Normal file
View File

@ -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);
}

39
icarus/core/sc-3000.cpp Normal file
View File

@ -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);
}

39
icarus/core/sg-1000.cpp Normal file
View File

@ -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);
}

31
icarus/heuristics/msx.cpp Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -19,10 +19,13 @@ Settings settings;
#include "heuristics/heuristics.cpp" #include "heuristics/heuristics.cpp"
#include "heuristics/famicom.cpp" #include "heuristics/famicom.cpp"
#include "heuristics/super-famicom.cpp" #include "heuristics/super-famicom.cpp"
#include "heuristics/sg-1000.cpp"
#include "heuristics/sc-3000.cpp"
#include "heuristics/master-system.cpp" #include "heuristics/master-system.cpp"
#include "heuristics/mega-drive.cpp" #include "heuristics/mega-drive.cpp"
#include "heuristics/pc-engine.cpp" #include "heuristics/pc-engine.cpp"
#include "heuristics/supergrafx.cpp" #include "heuristics/supergrafx.cpp"
#include "heuristics/msx.cpp"
#include "heuristics/game-boy.cpp" #include "heuristics/game-boy.cpp"
#include "heuristics/game-boy-advance.cpp" #include "heuristics/game-boy-advance.cpp"
#include "heuristics/game-gear.cpp" #include "heuristics/game-gear.cpp"
@ -34,10 +37,13 @@ Settings settings;
#include "core/core.cpp" #include "core/core.cpp"
#include "core/famicom.cpp" #include "core/famicom.cpp"
#include "core/super-famicom.cpp" #include "core/super-famicom.cpp"
#include "core/sg-1000.cpp"
#include "core/sc-3000.cpp"
#include "core/master-system.cpp" #include "core/master-system.cpp"
#include "core/mega-drive.cpp" #include "core/mega-drive.cpp"
#include "core/pc-engine.cpp" #include "core/pc-engine.cpp"
#include "core/supergrafx.cpp" #include "core/supergrafx.cpp"
#include "core/msx.cpp"
#include "core/game-boy.cpp" #include "core/game-boy.cpp"
#include "core/game-boy-color.cpp" #include "core/game-boy-color.cpp"
#include "core/game-boy-advance.cpp" #include "core/game-boy-advance.cpp"
@ -85,10 +91,13 @@ auto nall::main(Arguments arguments) -> void {
.setFilters("ROM Files|" .setFilters("ROM Files|"
"*.fc:*.nes:" "*.fc:*.nes:"
"*.sfc:*.smc:" "*.sfc:*.smc:"
"*.sg1000:*.sg:"
"*.sc3000:*.sc:"
"*.ms:*.sms:" "*.ms:*.sms:"
"*.md:*.smd:*.gen:" "*.md:*.smd:*.gen:"
"*.pce:" "*.pce:"
"*.sg:*.sgx:" "*.sgx:"
"*.msx:"
"*.gb:" "*.gb:"
"*.gbc:" "*.gbc:"
"*.gba:" "*.gba:"

View File

@ -102,10 +102,13 @@ auto ScanDialog::gamePakType(const string& type) -> bool {
return type == ".sys" return type == ".sys"
|| type == ".fc" || type == ".fc"
|| type == ".sfc" || type == ".sfc"
|| type == ".sg1000"
|| type == ".sc3000"
|| type == ".ms" || type == ".ms"
|| type == ".md" || type == ".md"
|| type == ".pce" || type == ".pce"
|| type == ".sg" || type == ".sgx"
|| type == ".msx"
|| type == ".gb" || type == ".gb"
|| type == ".gbc" || type == ".gbc"
|| type == ".gba" || type == ".gba"
@ -121,10 +124,13 @@ auto ScanDialog::gameRomType(const string& type) -> bool {
return type == ".zip" return type == ".zip"
|| type == ".fc" || type == ".nes" || type == ".fc" || type == ".nes"
|| type == ".sfc" || type == ".smc" || type == ".sfc" || type == ".smc"
|| type == ".sg1000" || type == ".sg"
|| type == ".sc3000" || type == ".sc"
|| type == ".ms" || type == ".sms" || type == ".ms" || type == ".sms"
|| type == ".md" || type == ".smd" || type == ".gen" || type == ".md" || type == ".smd" || type == ".gen"
|| type == ".pce" || type == ".pce"
|| type == ".sg" || type == ".sgx" || type == ".sgx"
|| type == ".msx"
|| type == ".gb" || type == ".gb"
|| type == ".gbc" || type == ".gbc"
|| type == ".gba" || type == ".gba"

View File

@ -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;
}
}}}