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 {
while(true) {
while(streams) {
for(auto& stream : streams) {
if(!stream->pending()) return;
}

View File

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

View File

@ -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) {

View File

@ -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/";

View File

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

View File

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

View File

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

View File

@ -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)) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 {
inline static auto SG1000() -> bool;
inline static auto SC3000() -> bool;
inline static auto MasterSystem() -> 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
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)) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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;
install:
install: all
ifeq ($(shell id -un),root)
$(error "make install should not be run as root")
else ifeq ($(platform),windows)

View File

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

View File

@ -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 {

View File

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

View File

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

View File

@ -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),)

View File

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

View File

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

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/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:"

View File

@ -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"

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