Commit 90da6917 authored by Tim Allen's avatar Tim Allen

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.
parent 598076e4
Pipeline #40931953 failed with stage
in 13 minutes and 55 seconds
......@@ -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;
}
......
#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;
......
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) {
for(uint pass : range(passes)) {
Filter filter{order};
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)) {
if(order == Filter::Order::First) {
DSP::IIR::OnePole::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::OnePole::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::OnePole::Type::HighPass;
filter.onePole.reset(_type, cutoffFrequency, inputFrequency);
Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
channel.filters.append(filter);
}
if(order == Filter::Order::Second) {
DSP::IIR::Biquad::Type _type;
if(type == Filter::Type::LowPass) _type = DSP::IIR::Biquad::Type::LowPass;
if(type == Filter::Type::HighPass) _type = DSP::IIR::Biquad::Type::HighPass;
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(_type, cutoffFrequency, inputFrequency, q);
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
channel.filters.append(filter);
}
}
}
}
channel.filters.append(filter);
auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
for(auto& channel : channels) {
for(uint pass : range(passes)) {
if(order == Filter::Order::First) {
Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency);
channel.filters.append(filter);
}
if(order == Filter::Order::Second) {
Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
channel.filters.append(filter);
}
}
}
}
......@@ -73,9 +90,10 @@ auto Stream::write(const double samples[]) -> void {
for(auto c : range(channels.size())) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& filter : channels[c].filters) {
switch(filter.order) {
case Filter::Order::First: sample = filter.onePole.process(sample); break;
case Filter::Order::Second: sample = filter.biquad.process(sample); break;
switch(filter.mode) {
case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
}
}
for(auto& filter : channels[c].nyquist) {
......
......@@ -28,7 +28,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.66";
static const string Version = "106.67";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";
......
......@@ -74,9 +74,10 @@ auto APU::setSample(int16 sample) -> void {
auto APU::power(bool reset) -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 90.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 440.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 14000.0);
stream->addHighPassFilter( 90.0, Emulator::Filter::Order::First);
stream->addHighPassFilter( 440.0, Emulator::Filter::Order::First);
stream->addLowPassFilter (14000.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
pulse[0].power();
pulse[1].power();
......
......@@ -55,7 +55,8 @@ auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024);
if(!Model::SuperGameBoy()) {
stream = Emulator::audio.createStream(2, frequency());
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
}
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
......
......@@ -77,7 +77,8 @@ auto APU::step(uint clocks) -> void {
auto APU::power() -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(2, frequency() / 64.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
clock = 0;
square1.power();
......
......@@ -37,8 +37,9 @@ auto PSG::step(uint clocks) -> void {
auto PSG::power(bool reset) -> void {
create(PSG::Enter, system.frequency() / 15.0);
stream = Emulator::audio.createStream(1, frequency() / 16.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0);
stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First);
stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
select = 0;
for(auto n : range(15)) {
......
......@@ -157,8 +157,9 @@ auto YM2612::step(uint clocks) -> void {
auto YM2612::power(bool reset) -> void {
create(YM2612::Enter, system.frequency() / 7.0);
stream = Emulator::audio.createStream(2, frequency() / 144.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::LowPass, 2840.0);
stream->addHighPassFilter( 20.0, Emulator::Filter::Order::First);
stream->addLowPassFilter (2840.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
io = {};
lfo = {};
......
......@@ -9,6 +9,20 @@ Cartridge cartridge;
auto Cartridge::load() -> bool {
information = {};
if(Model::SG1000()) {
if(auto loaded = platform->load(ID::SG1000, "SG-1000", "sg1000", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
}
if(Model::SC3000()) {
if(auto loaded = platform->load(ID::SC3000, "SC-3000", "sc3000", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
}
if(Model::MasterSystem()) {
if(auto loaded = platform->load(ID::MasterSystem, "Master System", "ms", {"NTSC", "PAL"})) {
information.pathID = loaded.pathID;
......
......@@ -32,7 +32,7 @@ auto Controller::main() -> void {
auto ControllerPort::connect(uint deviceID) -> void {
delete device;
if(!system.loaded()) return;
if(!Model::MasterSystem()) return;
if(Model::GameGear()) return;
switch(deviceID) { default:
case ID::Device::None: device = new Controller(port); break;
......
......@@ -42,6 +42,16 @@ auto CPU::in(uint8 addr) -> uint8 {
}
case 3: {
if(Model::SG1000() || Model::SC3000()) {
auto port1 = controllerPort1.device->readData();
auto port2 = controllerPort2.device->readData();
if(addr.bit(0) == 0) {
return port1.bits(0,5) << 0 | port2.bits(0,1) << 6;
} else {
return port2.bits(2,5) << 0 | 1 << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7;
}
}
if(Model::MasterSystem()) {
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
auto port1 = controllerPort1.device->readData();
......
......@@ -37,6 +37,13 @@ auto CPU::synchronizing() const -> bool {
//called once per frame
auto CPU::pollPause() -> void {
if(Model::SG1000() || Model::SC3000()) {
static bool pause = 0;
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::SG1000Controls, 0);
if(!pause && state) setNMI(1);
pause = state;
}
if(Model::MasterSystem()) {
static bool pause = 0;
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);
......
......@@ -3,6 +3,8 @@
namespace MasterSystem {
Settings settings;
#include "sg-1000.cpp"
#include "sc-3000.cpp"
#include "master-system.cpp"
#include "game-gear.cpp"
......
......@@ -5,6 +5,8 @@ namespace MasterSystem {
struct ID {
enum : uint {
System,
SG1000,
SC3000,
MasterSystem,
GameGear,
};
......@@ -17,6 +19,8 @@ struct ID {
struct Device { enum : uint {
None,
SG1000Controls,
SC3000Controls,
MasterSystemControls,
GameGearControls,
Gamepad,
......@@ -44,6 +48,38 @@ struct Interface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override;
};
struct SG1000Interface : Interface {
auto information() -> Information override;
auto displays() -> vector<Display> override;
auto color(uint32 color) -> uint64 override;
auto ports() -> vector<Port> override;
auto devices(uint port) -> vector<Device> override;
auto inputs(uint device) -> vector<Input> override;
auto load() -> bool override;
auto connected(uint port) -> uint override;
auto connect(uint port, uint device) -> void override;
};
struct SC3000Interface : Interface {
auto information() -> Information override;
auto displays() -> vector<Display> override;
auto color(uint32 color) -> uint64 override;
auto ports() -> vector<Port> override;
auto devices(uint port) -> vector<Device> override;
auto inputs(uint device) -> vector<Input> override;
auto load() -> bool override;
auto connected(uint port) -> uint override;
auto connect(uint port, uint device) -> void override;
};
struct MasterSystemInterface : Interface {
auto information() -> Information override;
......
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);
}
auto SG1000Interface::information() -> Information {
Information information;
information.manufacturer = "Sega";
information.name = "SG-1000";
information.extension = "sg1000";
return information;
}
auto SG1000Interface::displays() -> vector<Display> {
Display display;
display.type = Display::Type::CRT;
display.colors = 1 << 4;
display.width = 256;
display.height = 192;
display.internalWidth = 256;
display.internalHeight = 192;
display.aspectCorrection = 1.0;
if(Region::NTSC()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (262.0 * 684.0);
if(Region::PAL()) display.refreshRate = (system.colorburst() * 15.0 / 5.0) / (312.0 * 684.0);
return {display};
}
auto SG1000Interface::color(uint32 color) -> uint64 {
switch(color.bits(0,3)) {
case 0: return 0x0000'0000'0000ull; //transparent
case 1: return 0x0000'0000'0000ull; //black
case 2: return 0x2121'c8c8'4242ull; //medium green
case 3: return 0x5e5e'dcdc'7878ull; //light green
case 4: return 0x5454'5555'ededull; //dark blue
case 5: return 0x7d7d'7676'fcfcull; //light blue
case 6: return 0xd4d4'5252'4d4dull; //dark red
case 7: return 0x4242'ebeb'f5f5ull; //cyan
case 8: return 0xfcfc'5555'5454ull; //medium red
case 9: return 0xffff'7979'7878ull; //light red
case 10: return 0xd4d4'c1c1'5454ull; //dark yellow
case 11: return 0xe6e6'cece'8080ull; //light yellow
case 12: return 0x2121'b0b0'3b3bull; //dark green
case 13: return 0xc9c9'5b5b'babaull; //magenta
case 14: return 0xcccc'cccc'ccccull; //gray
case 15: return 0xffff'ffff'ffffull; //white
}
unreachable;
}
auto SG1000Interface::ports() -> vector<Port> { return {
{ID::Port::Controller1, "Controller Port 1"},
{ID::Port::Controller2, "Controller Port 2"},
{ID::Port::Hardware, "Hardware" }};
}
auto SG1000Interface::devices(uint port) -> vector<Device> {
if(port == ID::Port::Controller1) return {
{ID::Device::None, "None" },
{ID::Device::Gamepad, "Gamepad"}
};
if(port == ID::Port::Controller2) return {
{ID::Device::None, "None" },
{ID::Device::Gamepad, "Gamepad"}
};
if(port == ID::Port::Hardware) return {
{ID::Device::SG1000Controls, "Controls"}
};
return {};
}
auto SG1000Interface::inputs(uint device) -> vector<Input> {
using Type = Input::Type;
if(device == ID::Device::None) return {
};
if(device == ID::Device::Gamepad) return {
{Type::Hat, "Up" },
{Type::Hat, "Down" },
{Type::Hat, "Left" },
{Type::Hat, "Right"},
{Type::Button, "1" },
{Type::Button, "2" }
};
if(device == ID::Device::SG1000Controls) return {
{Type::Control, "Pause"}
};
return {};
}
auto SG1000Interface::load() -> bool {
return system.load(this, System::Model::SG1000);
}
auto SG1000Interface::connected(uint port) -> uint {
if(port == ID::Port::Controller1) return settings.controllerPort1;
if(port == ID::Port::Controller2) return settings.controllerPort2;
if(port == ID::Port::Hardware) return ID::Device::SG1000Controls;
return 0;
}
auto SG1000Interface::connect(uint port, uint device) -> void {
if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device);
if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device);
}
......@@ -30,6 +30,8 @@ namespace MasterSystem {
};
struct Model {
inline static auto SG1000() -> bool;
inline static auto SC3000() -> bool;
inline static auto MasterSystem() -> bool;
inline static auto GameGear() -> bool;
};
......
......@@ -44,7 +44,8 @@ auto PSG::power() -> void {
//use stereo mode for both; output same sample to both channels for Master System
create(PSG::Enter, system.colorburst() / 16.0);
stream = Emulator::audio.createStream(2, frequency());
stream->addFilter(Emulator::Filter::Order::First, Emulator::Filter::Type::HighPass, 20.0);
stream->addHighPassFilter(20.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
select = 0;