Update to v088r12 release.

byuu says:

Changelog:
- all hotkeys from target-ui now exist in target-ethos
- controller port menus now show up when you load a system (hidden if
  there are no options to choose from)
- tools menu auto-hides with no game open ... not much point to it then
- since we aren't using RawInput's multi-KB/MS support anyway, input and
  hotkey mappings remove KB0:: and turn MS0:: into Mouse::, makes it
  a lot easier to read
- added mute audio, sync video, sync audio, mask overscan
- added video settings: saturation, gamma, luminance, overscan
  horizontal, overscan vertical
- added audio settings: frequency, latency, resampler, volume
- added input settings: when focus is lost [ ] pause emulator [ ] allow
  input
- pausing and autopausing works
- status messages hooked up (show a message in status bar for a few
  seconds, then revert to normal status text)
- sub systems (SGB, BSX, ST) sorted below primary systems list
- added geometry settings cache
- Emulator::Interface cleanups and simplifications
- save states go into (cart foldername.extension/bsnes/state-#.bsa) now.
  Idea is to put emulator-specific data in their own subfolders

Caveats / Missing:
- SGB input does not work
- Sufami Turbo second slot doesn't work yet
- BS-X BIOS won't show the data pack
- need XML mapping information window
- need cheat editor and cheat database
- need state manager
- need video shaders
- need driver selection
- need NSS DIP switch settings
- need to hide controllers that have no inputs from the input mapping
  list

So for video settings, I used to have contrast/brightness/gamma.
Contrast was just a multiplier on intensity of each channel, and
brightness was an addition or subtraction against each channel. They
kind of overlapped and weren't that effective. The new setup has
saturation, gamma and luminance.

Saturation of 100% is normal. If you lower it, color information goes
away. 0% = grayscale. If you raise it, color intensity increases (and
clamps.) This is wonderful for GBA games, since they are oversaturated
to fucking death. Of course we'll want to normalize that inside the
core, so the same sat. value works on all systems, but for now it's
nice. If you raise saturation above 100%, it basically acts like
contrast used to. It's just that lowering it fades to grayscale rather
than black.

Adding doesn't really work well for brightness, it throws off the
relative distance between channels and looks like shit.  So now we have
luminance, which takes over the old contrast <100% role, and just fades
the pixels toward black. Obviously, luminance > 100% would be the same
as saturation > 100%, so that isn't allowed, it caps at 100% now.
Gamma's the same old function. Gamma curve on the lower-half of the
color range.
Effects are applied in the order they appear in the GUI: color ->
saturate -> gammify -> luminate -> output.
This commit is contained in:
Tim Allen 2012-05-04 22:47:41 +10:00
parent 8703d57030
commit 5d273c5265
41 changed files with 964 additions and 431 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator { namespace Emulator {
static const char Name[] = "bsnes"; static const char Name[] = "bsnes";
static const char Version[] = "088.11"; static const char Version[] = "088.12";
static const char Author[] = "byuu"; static const char Author[] = "byuu";
static const char License[] = "GPLv3"; static const char License[] = "GPLv3";
} }

View File

@ -8,34 +8,31 @@ struct Interface {
string name; string name;
unsigned width; unsigned width;
unsigned height; unsigned height;
bool overscan;
double aspectRatio; double aspectRatio;
unsigned frequency; unsigned frequency;
bool resettable; bool resettable;
struct Media { struct Media {
string name; string name;
string filter; string extension;
}; };
vector<Media> media; vector<Media> media;
} information; } information;
struct Firmware {
string displayname;
string name;
unsigned id;
};
vector<Firmware> firmware;
struct Media { struct Media {
string displayname;
string path;
string name;
string filter;
unsigned id; unsigned id;
string name;
string extension;
string path;
}; };
vector<Media> firmware;
struct Schema : Media { struct Schema : Media {
vector<Media> slot; vector<Media> slot;
Schema(const Media &media) {
id = media.id, name = media.name, extension = media.extension, path = media.path;
}
}; };
vector<Schema> schema; vector<Schema> schema;
@ -46,19 +43,19 @@ struct Interface {
vector<Memory> memory; vector<Memory> memory;
struct Port { struct Port {
string name;
unsigned id; unsigned id;
string name;
struct Device { struct Device {
string name;
unsigned id; unsigned id;
string name;
struct Input { struct Input {
string name;
unsigned type; //0 = digital, 1 = analog
unsigned id; unsigned id;
unsigned type; //0 = digital, 1 = analog
string name;
unsigned guid; unsigned guid;
}; };
vector<Input> input; vector<Input> input;
vector<unsigned> displayinput; vector<unsigned> order;
}; };
vector<Device> device; vector<Device> device;
}; };
@ -72,7 +69,7 @@ struct Interface {
function<void (Media)> mediaRequest; function<void (Media)> mediaRequest;
} callback; } callback;
//audio/visual bindings (provided by user interface) //callback bindings (provided by user interface)
virtual uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { virtual uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) {
if(callback.videoColor) return callback.videoColor(source, red, green, blue); if(callback.videoColor) return callback.videoColor(source, red, green, blue);
return (red >> 8) << 16 | (green >> 8) << 8 | (blue >> 8) << 0; return (red >> 8) << 16 | (green >> 8) << 8 | (blue >> 8) << 0;
@ -102,10 +99,15 @@ struct Interface {
virtual void unload() {} virtual void unload() {}
//system interface //system interface
virtual void connect(unsigned port, unsigned device) {}
virtual void power() {} virtual void power() {}
virtual void reset() {} virtual void reset() {}
virtual void run() {} virtual void run() {}
//state functions
virtual serializer serialize() = 0;
virtual bool unserialize(serializer&) = 0;
//utility functions //utility functions
virtual void updatePalette() {} virtual void updatePalette() {}
}; };

View File

@ -43,6 +43,15 @@ void Interface::run() {
system.run(); system.run();
} }
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer &s) {
return system.unserialize(s);
}
void Interface::updatePalette() { void Interface::updatePalette() {
video.generate_palette(); video.generate_palette();
} }
@ -53,64 +62,40 @@ Interface::Interface() {
information.name = "Famicom"; information.name = "Famicom";
information.width = 256; information.width = 256;
information.height = 240; information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0; information.aspectRatio = 8.0 / 7.0;
information.frequency = 1789772; information.frequency = 1789772;
information.resettable = true; information.resettable = true;
information.media.append({"Famicom", "*.fc"}); information.media.append({"Famicom", "fc"});
schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"});
{ {
Schema schema; Port port{0, "Port 1"};
schema.displayname = "Famicom"; port.device.append(controller());
schema.name = "program.rom";
schema.filter = "*.fc";
schema.id = ID::ROM;
this->schema.append(schema);
}
{
Port port;
port.name = "Port 1";
port.id = 0;
{
Port::Device device;
device.name = "Controller";
device.id = 0;
device.input.append({"A", 0, 0});
device.input.append({"B", 0, 1});
device.input.append({"Select", 0, 2});
device.input.append({"Start", 0, 3});
device.input.append({"Up", 0, 4});
device.input.append({"Down", 0, 5});
device.input.append({"Left", 0, 6});
device.input.append({"Right", 0, 7});
device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3};
port.device.append(device);
}
this->port.append(port); this->port.append(port);
} }
{ {
Port port; Port port{1, "Port 2"};
port.name = "Port 2"; port.device.append(controller());
port.id = 1;
{
Port::Device device;
device.name = "Controller";
device.id = 0;
device.input.append({"A", 0, 0});
device.input.append({"B", 0, 1});
device.input.append({"Select", 0, 2});
device.input.append({"Start", 0, 3});
device.input.append({"Up", 0, 4});
device.input.append({"Down", 0, 5});
device.input.append({"Left", 0, 6});
device.input.append({"Right", 0, 7});
device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3};
port.device.append(device);
}
this->port.append(port); this->port.append(port);
} }
} }
Emulator::Interface::Port::Device Interface::controller() {
Port::Device device{0, "Controller"};
device.input.append({0, 0, "A" });
device.input.append({1, 0, "B" });
device.input.append({2, 0, "Select"});
device.input.append({3, 0, "Start" });
device.input.append({4, 0, "Up" });
device.input.append({5, 0, "Down" });
device.input.append({6, 0, "Left" });
device.input.append({7, 0, "Right" });
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
return device;
}
} }

View File

@ -19,9 +19,15 @@ struct Interface : Emulator::Interface {
void reset(); void reset();
void run(); void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette(); void updatePalette();
Interface(); Interface();
private:
Port::Device controller();
}; };
extern Interface *interface; extern Interface *interface;

View File

@ -58,6 +58,15 @@ void Interface::run() {
system.run(); system.run();
} }
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer &s) {
return system.unserialize(s);
}
void Interface::updatePalette() { void Interface::updatePalette() {
video.generate_palette(); video.generate_palette();
} }
@ -68,72 +77,34 @@ Interface::Interface() {
information.name = "Game Boy"; information.name = "Game Boy";
information.width = 160; information.width = 160;
information.height = 144; information.height = 144;
information.overscan = false;
information.aspectRatio = 1.0; information.aspectRatio = 1.0;
information.frequency = 4194304; information.frequency = 4194304;
information.resettable = false; information.resettable = false;
information.media.append({"Game Boy", "*.gb"}); information.media.append({"Game Boy", "gb" });
information.media.append({"Game Boy Color", "*.gbc"}); information.media.append({"Game Boy Color", "gbc"});
firmware.append({ID::GameBoyBootROM, "Game Boy", "sys", "boot.rom"});
firmware.append({ID::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
schema.append(Media{ID::GameBoyROM, "Game Boy", "gb", "program.rom"});
schema.append(Media{ID::GameBoyColorROM, "Game Boy Color", "gbc", "program.rom"});
{ {
Firmware firmware; Port port{0, "Device"};
firmware.displayname = "Game Boy";
firmware.name = "Game Boy.sys/boot.rom";
firmware.id = ID::GameBoyBootROM;
this->firmware.append(firmware);
}
{
Firmware firmware;
firmware.displayname = "Super Game Boy";
firmware.name = "Super Game Boy.sfc/boot.rom";
firmware.id = ID::SuperGameBoyBootROM;
this->firmware.append(firmware);
}
{
Firmware firmware;
firmware.displayname = "Game Boy Color";
firmware.name = "Game Boy Color.sys/boot.rom";
firmware.id = ID::GameBoyColorBootROM;
this->firmware.append(firmware);
}
{
Schema schema;
schema.displayname = "Game Boy";
schema.name = "program.rom";
schema.filter = "*.gb";
schema.id = ID::GameBoyROM;
this->schema.append(schema);
}
{
Schema schema;
schema.displayname = "Game Boy Color";
schema.name = "program.rom";
schema.filter = "*.gbc";
schema.id = ID::GameBoyColorROM;
this->schema.append(schema);
}
{
Port port;
port.name = "Device";
port.id = 0;
{ {
Port::Device device; Port::Device device{0, "Controller"};
device.name = "Controller"; device.input.append({0, 0, "Up" });
device.id = 0; device.input.append({1, 0, "Down" });
device.input.append({"Up", 0, 0}); device.input.append({2, 0, "Left" });
device.input.append({"Down", 0, 1}); device.input.append({3, 0, "Right" });
device.input.append({"Left", 0, 2}); device.input.append({4, 0, "B" });
device.input.append({"Right", 0, 3}); device.input.append({5, 0, "A" });
device.input.append({"B", 0, 4}); device.input.append({6, 0, "Select"});
device.input.append({"A", 0, 5}); device.input.append({7, 0, "Start" });
device.input.append({"Select", 0, 6}); device.order = {0, 1, 2, 3, 4, 5, 6, 7};
device.input.append({"Start", 0, 7});
device.displayinput = {0, 1, 2, 3, 4, 5, 6, 7};
port.device.append(device); port.device.append(device);
} }
this->port.append(port); this->port.append(port);

View File

@ -27,6 +27,9 @@ struct Interface : Emulator::Interface {
void reset(); void reset();
void run(); void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette(); void updatePalette();
Interface(); Interface();

View File

@ -62,6 +62,15 @@ void Interface::run() {
system.run(); system.run();
} }
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer &s) {
return system.unserialize(s);
}
void Interface::updatePalette() { void Interface::updatePalette() {
video.generate_palette(); video.generate_palette();
} }
@ -72,48 +81,32 @@ Interface::Interface() {
information.name = "Game Boy Advance"; information.name = "Game Boy Advance";
information.width = 240; information.width = 240;
information.height = 160; information.height = 160;
information.overscan = false;
information.aspectRatio = 1.0; information.aspectRatio = 1.0;
information.frequency = 32768; information.frequency = 32768;
information.resettable = false; information.resettable = false;
information.media.append({"Game Boy Advance", "*.gba"}); information.media.append({"Game Boy Advance", "gba"});
firmware.append({ID::BIOS, "Game Boy Advance", "sys", "bios.rom"});
schema.append(Media{ID::ROM, "Game Boy Advance", "gba", "program.rom"});
{ {
Firmware firmware; Port port{0, "Device"};
firmware.displayname = "Game Boy Advance";
firmware.name = "Game Boy Advance.sys/bios.rom";
firmware.id = ID::BIOS;
this->firmware.append(firmware);
}
{
Schema schema;
schema.displayname = "Game Boy Advance";
schema.name = "program.rom";
schema.filter = "*.gba";
schema.id = ID::ROM;
this->schema.append(schema);
}
{
Port port;
port.name = "Device";
port.id = 0;
{ {
Port::Device device; Port::Device device{0, "Controller"};
device.name = "Controller"; device.input.append({0, 0, "A" });
device.id = 0; device.input.append({1, 0, "B" });
device.input.append({"A", 0, 0}); device.input.append({2, 0, "Select"});
device.input.append({"B", 0, 1}); device.input.append({3, 0, "Start" });
device.input.append({"Select", 0, 2}); device.input.append({4, 0, "Right" });
device.input.append({"Start", 0, 3}); device.input.append({5, 0, "Left" });
device.input.append({"Right", 0, 4}); device.input.append({6, 0, "Up" });
device.input.append({"Left", 0, 5}); device.input.append({7, 0, "Down" });
device.input.append({"Up", 0, 6}); device.input.append({8, 0, "R" });
device.input.append({"Down", 0, 7}); device.input.append({9, 0, "L" });
device.input.append({"R", 0, 8}); device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
device.input.append({"L", 0, 9});
device.displayinput = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
port.device.append(device); port.device.append(device);
} }
this->port.append(port); this->port.append(port);

View File

@ -22,6 +22,9 @@ struct Interface : Emulator::Interface {
void reset(); void reset();
void run(); void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette(); void updatePalette();
Interface(); Interface();

View File

@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) {
if(root.exists() == false) return; if(root.exists() == false) return;
has_gb_slot = true; has_gb_slot = true;
interface->mediaRequest({"Game Boy", "", "program.rom", "*.gb", 5}); interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
icd2.revision = max(1, numeral(root["revision"].data)); icd2.revision = max(1, numeral(root["revision"].data));
@ -372,7 +372,7 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
has_bs_cart = root["mmio"].exists(); has_bs_cart = root["mmio"].exists();
has_bs_slot = true; has_bs_slot = true;
interface->mediaRequest({"BS-X Satellaview", "", "program.rom", "*.bs", 2}); interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
for(auto &node : root["slot"]) { for(auto &node : root["slot"]) {
if(node.name != "map") continue; if(node.name != "map") continue;
@ -400,7 +400,7 @@ void Cartridge::parse_markup_sufamiturbo(XML::Node &root) {
if(root.exists() == false) return; if(root.exists() == false) return;
has_st_slot = true; has_st_slot = true;
interface->mediaRequest({"Sufami Turbo", "", "program.rom", "*.st", 3}); interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo", "st", "program.rom"});
for(auto &slot : root) { for(auto &slot : root) {
if(slot.name != "slot") continue; if(slot.name != "slot") continue;

View File

@ -18,8 +18,8 @@ void Justifier::enter() {
} }
if(next < prev) { if(next < prev) {
int nx1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::X); int nx1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::X);
int ny1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Y); int ny1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Y);
nx1 += player1.x; nx1 += player1.x;
ny1 += player1.y; ny1 += player1.y;
player1.x = max(-16, min(256 + 16, nx1)); player1.x = max(-16, min(256 + 16, nx1));
@ -27,8 +27,8 @@ void Justifier::enter() {
} }
if(next < prev && chained) { if(next < prev && chained) {
int nx2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::X); int nx2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::X);
int ny2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Y); int ny2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Y);
nx2 += player2.x; nx2 += player2.x;
ny2 += player2.y; ny2 += player2.y;
player2.x = max(-16, min(256 + 16, nx2)); player2.x = max(-16, min(256 + 16, nx2));
@ -44,13 +44,13 @@ uint2 Justifier::data() {
if(counter >= 32) return 1; if(counter >= 32) return 1;
if(counter == 0) { if(counter == 0) {
player1.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Trigger); player1.trigger = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Trigger);
player1.start = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Start); player1.start = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Start);
} }
if(counter == 0 && chained) { if(counter == 0 && chained) {
player2.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Trigger); player2.trigger = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Trigger);
player2.start = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Start); player2.start = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Start);
} }
switch(counter++) { switch(counter++) {
@ -100,7 +100,11 @@ void Justifier::latch(bool data) {
if(latched == 0) active = !active; //toggle between both controllers, even when unchained if(latched == 0) active = !active; //toggle between both controllers, even when unchained
} }
Justifier::Justifier(bool port, bool chained) : Controller(port), chained(chained) { Justifier::Justifier(bool port, bool chained):
Controller(port),
chained(chained),
device(chained == false ? (unsigned)Input::Device::Justifier : (unsigned)Input::Device::Justifiers)
{
create(Controller::Enter, 21477272); create(Controller::Enter, 21477272);
latched = 0; latched = 0;
counter = 0; counter = 0;

View File

@ -6,6 +6,7 @@ struct Justifier : Controller {
//private: //private:
const bool chained; //true if the second justifier is attached to the first const bool chained; //true if the second justifier is attached to the first
const unsigned device;
bool latched; bool latched;
unsigned counter; unsigned counter;

View File

@ -8,18 +8,20 @@ uint2 Multitap::data() {
index = counter1; index = counter1;
if(index >= 16) return 3; if(index >= 16) return 3;
counter1++; counter1++;
if(index >= 12) return 0;
port1 = 0; //controller 1 port1 = 0; //controller 1
port2 = 1; //controller 2 port2 = 1; //controller 2
} else { } else {
index = counter2; index = counter2;
if(index >= 16) return 3; if(index >= 16) return 3;
counter2++; counter2++;
if(index >= 12) return 0;
port1 = 2; //controller 3 port1 = 2; //controller 3
port2 = 3; //controller 4 port2 = 3; //controller 4
} }
bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 << 16 | index); bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 * 12 + index);
bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 << 16 | index); bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 * 12 + index);
return (data2 << 1) | (data1 << 0); return (data2 << 1) | (data1 << 0);
} }

View File

@ -68,7 +68,8 @@ uint2 USART::data() {
//Joypad //Joypad
if(iobit()) { if(iobit()) {
if(counter >= 16) return 1; if(counter >= 16) return 1;
uint2 result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter); uint2 result = 0;
if(counter < 12) result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter);
if(latched == 0) counter++; if(latched == 0) counter++;
return result; return result;
} }

View File

@ -83,6 +83,10 @@ void Interface::unload() {
cartridge.unload(); cartridge.unload();
} }
void Interface::connect(unsigned port, unsigned device) {
input.connect(port, (Input::Device)device);
}
void Interface::power() { void Interface::power() {
system.power(); system.power();
} }
@ -95,6 +99,15 @@ void Interface::run() {
system.run(); system.run();
} }
serializer Interface::serialize() {
system.runtosave();
return system.serialize();
}
bool Interface::unserialize(serializer &s) {
return system.unserialize(s);
}
void Interface::updatePalette() { void Interface::updatePalette() {
video.generate_palette(); video.generate_palette();
} }
@ -105,157 +118,159 @@ Interface::Interface() {
information.name = "Super Famicom"; information.name = "Super Famicom";
information.width = 256; information.width = 256;
information.height = 240; information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0; information.aspectRatio = 8.0 / 7.0;
information.frequency = 32040; information.frequency = 32040;
information.resettable = true; information.resettable = true;
information.media.append({"Super Famicom", "*.sfc"}); information.media.append({"Super Famicom", "sfc"});
information.media.append({"BS-X Satellaview", "*.bs"}); information.media.append({"BS-X Satellaview", "bs" });
information.media.append({"Sufami Turbo", "*.st"}); information.media.append({"Sufami Turbo", "st" });
information.media.append({"Super Game Boy", "*.gb"}); information.media.append({"Super Game Boy", "gb" });
firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"});
{ {
Firmware firmware; Schema schema(Media{ID::ROM, "Super Famicom", "sfc", "program.rom"});
firmware.displayname = "Super Famicom";
firmware.name = "Super Famicom.sys/spc700.rom";
firmware.id = ID::IPLROM;
this->firmware.append(firmware);
}
{
Schema schema;
schema.displayname = "Super Famicom";
schema.name = "program.rom";
schema.filter = "*.sfc";
schema.id = ID::ROM;
this->schema.append(schema); this->schema.append(schema);
} }
{ {
Schema schema; Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"});
schema.displayname = "Super Game Boy"; schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
schema.path = "Super Game Boy.sfc/";
schema.name = "program.rom";
schema.filter = "*.sfc";
schema.id = ID::ROM;
{
Media slot;
slot.displayname = "Game Boy";
slot.name = "program.rom";
slot.filter = "*.gb";
slot.id = ID::SuperGameBoyROM;
schema.slot.append(schema);
}
this->schema.append(schema); this->schema.append(schema);
} }
{ {
Schema schema; Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"});
schema.displayname = "BS-X Satellaview"; schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
schema.path = "BS-X Satellaview.sfc/";
schema.name = "program.rom";
schema.filter = "*.sfc";
schema.id = ID::ROM;
{
Media slot;
slot.displayname = "BS-X Satellaview";
slot.name = "program.rom";
slot.filter = "*.bs";
slot.id = ID::BsxFlashROM;
schema.slot.append(slot);
}
this->schema.append(schema); this->schema.append(schema);
} }
{ {
Schema schema; Schema schema(Media{ID::ROM, "Sufami Turbo", "sfc", "program.rom"});
schema.displayname = "Sufami Turbo"; schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"});
schema.path = "Sufami Turbo.sfc/"; schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"});
schema.name = "program.rom";
schema.filter = "*.sfc";
schema.id = ID::ROM;
{
Media slot;
slot.displayname = "Sufami Turbo - Slot A";
slot.name = "program.rom";
slot.filter = "*.st";
slot.id = ID::SufamiTurboSlotAROM;
schema.slot.append(slot);
}
{
Media slot;
slot.displayname = "Sufami Turbo - Slot B";
slot.name = "program.rom";
slot.filter = "*.st";
slot.id = ID::SufamiTurboSlotBROM;
schema.slot.append(slot);
}
this->schema.append(schema); this->schema.append(schema);
} }
{ {
Port port; Port port{0, "Port 1"};
port.name = "Port 1"; port.device.append(none());
port.id = 0; port.device.append(controller());
{ port.device.append(multitap());
Port::Device device; port.device.append(mouse());
device.name = "None"; port.device.append(usart());
device.id = 0;
port.device.append(device);
}
{
Port::Device device;
device.name = "Controller";
device.id = 1;
device.input.append({"B", 0, 0});
device.input.append({"Y", 0, 1});
device.input.append({"Select", 0, 2});
device.input.append({"Start", 0, 3});
device.input.append({"Up", 0, 4});
device.input.append({"Down", 0, 5});
device.input.append({"Left", 0, 6});
device.input.append({"Right", 0, 7});
device.input.append({"A", 0, 8});
device.input.append({"X", 0, 9});
device.input.append({"L", 0, 10});
device.input.append({"R", 0, 11});
device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
port.device.append(device);
}
this->port.append(port); this->port.append(port);
} }
{ {
Port port; Port port{1, "Port 2"};
port.name = "Port 2"; port.device.append(none());
port.id = 1; port.device.append(controller());
{ port.device.append(multitap());
Port::Device device; port.device.append(mouse());
device.name = "None"; port.device.append(superScope());
device.id = 0; port.device.append(justifier());
port.device.append(device); port.device.append(justifiers());
}
{
Port::Device device;
device.name = "Controller";
device.id = 1;
device.input.append({"B", 0, 0});
device.input.append({"Y", 0, 1});
device.input.append({"Select", 0, 2});
device.input.append({"Start", 0, 3});
device.input.append({"Up", 0, 4});
device.input.append({"Down", 0, 5});
device.input.append({"Left", 0, 6});
device.input.append({"Right", 0, 7});
device.input.append({"A", 0, 8});
device.input.append({"X", 0, 9});
device.input.append({"L", 0, 10});
device.input.append({"R", 0, 11});
device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
port.device.append(device);
}
this->port.append(port); this->port.append(port);
} }
} }
Emulator::Interface::Port::Device Interface::none() {
Port::Device device{0, "None"};
return device;
}
Emulator::Interface::Port::Device Interface::controller() {
Port::Device device{1, "Controller"};
device.input.append({ 0, 0, "B" });
device.input.append({ 1, 0, "Y" });
device.input.append({ 2, 0, "Select"});
device.input.append({ 3, 0, "Start" });
device.input.append({ 4, 0, "Up" });
device.input.append({ 5, 0, "Down" });
device.input.append({ 6, 0, "Left" });
device.input.append({ 7, 0, "Right" });
device.input.append({ 8, 0, "A" });
device.input.append({ 9, 0, "X" });
device.input.append({10, 0, "L" });
device.input.append({11, 0, "R" });
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
return device;
}
Emulator::Interface::Port::Device Interface::multitap() {
Port::Device device{2, "Multitap"};
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
}
return device;
}
Emulator::Interface::Port::Device Interface::mouse() {
Port::Device device{3, "Mouse"};
device.input.append({0, 1, "X-axis"});
device.input.append({1, 1, "Y-axis"});
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.order = {0, 1, 2, 3};
return device;
}
Emulator::Interface::Port::Device Interface::superScope() {
Port::Device device{4, "Super Scope"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Cursor" });
device.input.append({4, 0, "Turbo" });
device.input.append({5, 0, "Pause" });
device.order = {0, 1, 2, 3, 4, 5};
return device;
}
Emulator::Interface::Port::Device Interface::justifier() {
Port::Device device{5, "Justifier"};
device.input.append({0, 1, "X-axis" });
device.input.append({1, 1, "Y-axis" });
device.input.append({2, 0, "Trigger"});
device.input.append({3, 0, "Start" });
device.order = {0, 1, 2, 3};
return device;
}
Emulator::Interface::Port::Device Interface::justifiers() {
Port::Device device{6, "Justifiers"};
device.input.append({0, 1, "Port 1 - X-axis" });
device.input.append({1, 1, "Port 1 - Y-axis" });
device.input.append({2, 0, "Port 1 - Trigger"});
device.input.append({3, 0, "Port 1 - Start" });
device.order.append(0, 1, 2, 3);
device.input.append({4, 1, "Port 2 - X-axis" });
device.input.append({5, 1, "Port 2 - Y-axis" });
device.input.append({6, 0, "Port 2 - Trigger"});
device.input.append({7, 0, "Port 2 - Start" });
device.order.append(4, 5, 6, 7);
return device;
}
Emulator::Interface::Port::Device Interface::usart() {
Port::Device device{7, "Serial USART"};
return device;
}
} }

View File

@ -27,13 +27,27 @@ struct Interface : Emulator::Interface {
void save(unsigned id, const stream &stream); void save(unsigned id, const stream &stream);
void unload(); void unload();
void connect(unsigned port, unsigned device);
void power(); void power();
void reset(); void reset();
void run(); void run();
serializer serialize();
bool unserialize(serializer&);
void updatePalette(); void updatePalette();
Interface(); Interface();
private:
Port::Device none();
Port::Device controller();
Port::Device multitap();
Port::Device mouse();
Port::Device superScope();
Port::Device justifier();
Port::Device justifiers();
Port::Device usart();
}; };
extern Interface *interface; extern Interface *interface;

View File

@ -7,7 +7,7 @@ include gb/Makefile
include gba/Makefile include gba/Makefile
name := ethos name := ethos
ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-general ui-settings ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-window ui-general ui-settings
ui_objects += phoenix ruby ui_objects += phoenix ruby
ui_objects += $(if $(call streq,$(platform),win),resource) ui_objects += $(if $(call streq,$(platform),win),resource)
@ -43,6 +43,7 @@ obj/ui-configuration.o: $(ui)/configuration/configuration.cpp $(call rwildcard,$
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/) obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/) obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/)
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/) obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
obj/ui-window.o: $(ui)/window/window.cpp $(call rwildcard,$(ui)/)
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/) obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)

View File

@ -17,10 +17,9 @@ void Application::bootstrap() {
system->callback.audioSample = {&Interface::audioSample, interface}; system->callback.audioSample = {&Interface::audioSample, interface};
system->callback.inputPoll = {&Interface::inputPoll, interface}; system->callback.inputPoll = {&Interface::inputPoll, interface};
system->callback.mediaRequest = {&Interface::mediaRequest, interface}; system->callback.mediaRequest = {&Interface::mediaRequest, interface};
system->updatePalette();
for(auto &firmware : system->firmware) { for(auto &firmware : system->firmware) {
filestream fs{application->path(firmware.name)}; filestream fs{application->path({firmware.name, ".", firmware.extension, "/", firmware.path})};
system->load(firmware.id, fs); system->load(firmware.id, fs);
} }
} }

View File

@ -2,8 +2,23 @@
Configuration *config = nullptr; Configuration *config = nullptr;
Configuration::Configuration() { Configuration::Configuration() {
append(video.synchronize = false, "Video::Synchronize");
append(video.scaleMode = 0, "Video::ScaleMode"); append(video.scaleMode = 0, "Video::ScaleMode");
append(video.aspectCorrection = true, "Video::AspectCorrection"); append(video.aspectCorrection = true, "Video::AspectCorrection");
append(video.maskOverscan = false, "Video::MaskOverscan");
append(video.maskOverscanHorizontal = 8, "Video::MaskOverscan::Horizontal");
append(video.maskOverscanVertical = 8, "Video::MaskOverscan::Vertical");
append(video.saturation = 100, "Video::Saturation");
append(video.gamma = 150, "Video::Gamma");
append(video.luminance = 100, "Video::Luminance");
append(audio.synchronize = true, "Audio::Synchronize");
append(audio.frequency = 48000, "Audio::Frequency");
append(audio.latency = 60, "Audio::Latency");
append(audio.resampler = 2, "Audio::Resampler");
append(audio.volume = 100, "Audio::Volume");
append(audio.mute = false, "Audio::Mute");
append(input.focusPause = false, "Input::Focus::Pause");
append(input.focusAllow = false, "Input::Focus::AllowInput");
load(); load();
} }

View File

@ -1,9 +1,30 @@
struct Configuration : configuration { struct Configuration : configuration {
struct Video { struct Video {
bool synchronize;
unsigned scaleMode; unsigned scaleMode;
bool aspectCorrection; bool aspectCorrection;
bool maskOverscan;
unsigned maskOverscanHorizontal;
unsigned maskOverscanVertical;
unsigned saturation;
unsigned gamma;
unsigned luminance;
} video; } video;
struct Audio {
bool synchronize;
unsigned frequency;
unsigned latency;
unsigned resampler;
unsigned volume;
bool mute;
} audio;
struct Input {
bool focusPause;
bool focusAllow;
} input;
void load(); void load();
void save(); void save();
Configuration(); Configuration();

View File

@ -19,8 +19,11 @@ string Application::path(const string &filename) {
void Application::run() { void Application::run() {
inputManager->poll(); inputManager->poll();
utility->updateStatus();
autopause = config->input.focusPause && presentation->focused() == false;
if(active == nullptr || system().loaded() == false) { if(active == nullptr || system().loaded() == false || pause || autopause) {
audio.clear();
usleep(20 * 1000); usleep(20 * 1000);
return; return;
} }
@ -61,9 +64,14 @@ Application::Application(int argc, char **argv) {
monospaceFont = "Liberation Mono, 8"; monospaceFont = "Liberation Mono, 8";
} }
video.driver("OpenGL");
audio.driver("ALSA");
input.driver("SDL");
config = new Configuration; config = new Configuration;
utility = new Utility; utility = new Utility;
inputManager = new InputManager; inputManager = new InputManager;
windowManager = new WindowManager;
browser = new Browser; browser = new Browser;
presentation = new Presentation; presentation = new Presentation;
videoSettings = new VideoSettings; videoSettings = new VideoSettings;
@ -71,31 +79,26 @@ Application::Application(int argc, char **argv) {
inputSettings = new InputSettings; inputSettings = new InputSettings;
hotkeySettings = new HotkeySettings; hotkeySettings = new HotkeySettings;
settings = new Settings; settings = new Settings;
windowManager->loadGeometry();
presentation->setVisible(); presentation->setVisible();
video.driver("OpenGL");
video.set(Video::Handle, presentation->viewport.handle()); video.set(Video::Handle, presentation->viewport.handle());
video.set(Video::Synchronize, false); if(!video.cap(Video::Depth) || !video.set(Video::Depth, depth = 30u)) {
video.set(Video::Depth, 24u); video.set(Video::Depth, depth = 24u);
}
video.init(); video.init();
audio.driver("ALSA");
audio.set(Audio::Handle, presentation->viewport.handle()); audio.set(Audio::Handle, presentation->viewport.handle());
audio.set(Audio::Synchronize, true);
audio.set(Audio::Latency, 80u);
audio.set(Audio::Frequency, 48000u);
audio.init(); audio.init();
input.driver("SDL");
input.set(Input::Handle, presentation->viewport.handle()); input.set(Input::Handle, presentation->viewport.handle());
input.init(); input.init();
dspaudio.setPrecision(16); dspaudio.setPrecision(16);
dspaudio.setVolume(2.0);
dspaudio.setBalance(0.0); dspaudio.setBalance(0.0);
dspaudio.setResampler(DSP::ResampleEngine::Sinc); dspaudio.setFrequency(96000);
dspaudio.setResamplerFrequency(48000u);
utility->synchronizeRuby();
while(quit == false) { while(quit == false) {
OS::processEvents(); OS::processEvents();
@ -106,6 +109,7 @@ Application::Application(int argc, char **argv) {
config->save(); config->save();
browser->saveConfiguration(); browser->saveConfiguration();
inputManager->saveConfiguration(); inputManager->saveConfiguration();
windowManager->saveGeometry();
} }
Application::~Application() { Application::~Application() {

View File

@ -21,6 +21,7 @@ using namespace ruby;
#include "interface/interface.hpp" #include "interface/interface.hpp"
#include "utility/utility.hpp" #include "utility/utility.hpp"
#include "input/input.hpp" #include "input/input.hpp"
#include "window/window.hpp"
#include "general/general.hpp" #include "general/general.hpp"
#include "settings/settings.hpp" #include "settings/settings.hpp"

View File

@ -3,6 +3,7 @@ Browser *browser = nullptr;
Browser::Browser() { Browser::Browser() {
bootstrap(); bootstrap();
setGeometry({128, 128, 640, 400}); setGeometry({128, 128, 640, 400});
windowManager->append(this, "Browser");
layout.setMargin(5); layout.setMargin(5);
pathBrowse.setText("Browse ..."); pathBrowse.setText("Browse ...");
@ -50,7 +51,7 @@ void Browser::synchronize() {
openButton.setEnabled(fileList.selected()); openButton.setEnabled(fileList.selected());
if(fileList.selected()) { if(fileList.selected()) {
for(auto &folder : folderList) { for(auto &folder : folderList) {
if(folder.filter == filter) { if(folder.extension == extension) {
folder.selection = fileList.selection(); folder.selection = fileList.selection();
} }
} }
@ -66,7 +67,7 @@ void Browser::bootstrap() {
for(auto &media : emulator->information.media) { for(auto &media : emulator->information.media) {
bool found = false; bool found = false;
for(auto &folder : folderList) { for(auto &folder : folderList) {
if(folder.filter == filter) { if(folder.extension == media.extension) {
found = true; found = true;
break; break;
} }
@ -74,7 +75,7 @@ void Browser::bootstrap() {
if(found == true) continue; if(found == true) continue;
Folder folder; Folder folder;
folder.filter = media.filter; folder.extension = media.extension;
folder.path = application->basepath; folder.path = application->basepath;
folder.selection = 0; folder.selection = 0;
folderList.append(folder); folderList.append(folder);
@ -82,21 +83,21 @@ void Browser::bootstrap() {
} }
for(auto &folder : folderList) { for(auto &folder : folderList) {
config.append(folder.path, folder.filter); config.append(folder.path, folder.extension);
config.append(folder.selection, string{folder.filter, "::selection"}); config.append(folder.selection, string{folder.extension, "::selection"});
} }
config.load(application->path("paths.cfg")); config.load(application->path("paths.cfg"));
config.save(application->path("paths.cfg")); config.save(application->path("paths.cfg"));
} }
string Browser::select(const string &title, const string &filter) { string Browser::select(const string &title, const string &extension) {
this->filter = filter; this->extension = extension;
string path; string path;
unsigned selection = 0; unsigned selection = 0;
for(auto &folder : folderList) { for(auto &folder : folderList) {
if(folder.filter == filter) { if(folder.extension == extension) {
path = folder.path; path = folder.path;
selection = folder.selection; selection = folder.selection;
break; break;
@ -105,8 +106,9 @@ string Browser::select(const string &title, const string &filter) {
if(path.empty()) path = application->basepath; if(path.empty()) path = application->basepath;
setPath(path, selection); setPath(path, selection);
filterLabel.setText({"Files of type: ", filter}); filterLabel.setText({"Files of type: *.", extension});
audio.clear();
setTitle(title); setTitle(title);
setModal(); setModal();
setVisible(); setVisible();
@ -123,7 +125,7 @@ string Browser::select(const string &title, const string &filter) {
void Browser::setPath(const string &path, unsigned selection) { void Browser::setPath(const string &path, unsigned selection) {
//save path for next browser selection //save path for next browser selection
for(auto &folder : folderList) { for(auto &folder : folderList) {
if(folder.filter == filter) folder.path = path; if(folder.extension == extension) folder.path = path;
} }
this->path = path; this->path = path;
@ -135,7 +137,6 @@ void Browser::setPath(const string &path, unsigned selection) {
lstring contents = directory::folders(path); lstring contents = directory::folders(path);
for(auto &filename : contents) { for(auto &filename : contents) {
string filter = {this->filter, "/"};
if(!filename.wildcard(R"(*.??/)") && !filename.wildcard(R"(*.???/)")) { if(!filename.wildcard(R"(*.??/)") && !filename.wildcard(R"(*.???/)")) {
string name = filename; string name = filename;
name.rtrim<1>("/"); name.rtrim<1>("/");
@ -146,12 +147,11 @@ void Browser::setPath(const string &path, unsigned selection) {
} }
for(auto &filename : contents) { for(auto &filename : contents) {
string filter = {this->filter, "/"}; string suffix = {".", this->extension, "/"};
if(filename.wildcard(R"(*.??/)") || filename.wildcard(R"(*.???/)")) { if(filename.wildcard(R"(*.??/)") || filename.wildcard(R"(*.???/)")) {
if(filename.wildcard(filter)) { if(filename.endswith(suffix)) {
string name = filename; string name = filename;
filter.ltrim<1>("*"); name.rtrim<1>(suffix);
name.rtrim<1>(filter);
filenameList.append(filename); filenameList.append(filename);
fileList.append(name); fileList.append(name);
} }
@ -166,8 +166,8 @@ void Browser::setPath(const string &path, unsigned selection) {
void Browser::fileListActivate() { void Browser::fileListActivate() {
unsigned selection = fileList.selection(); unsigned selection = fileList.selection();
string filename = filenameList[selection]; string filename = filenameList[selection];
string filter = {this->filter, "/"}; string suffix = {this->extension, "/"};
if(filename.wildcard(filter) == false) return setPath({path, filename}); if(filename.endswith(suffix) == false) return setPath({path, filename});
setVisible(false); setVisible(false);
dialogActive = false; dialogActive = false;

View File

@ -9,7 +9,7 @@ struct Browser : Window {
Label filterLabel; Label filterLabel;
Button openButton; Button openButton;
string select(const string &title, const string &filter); string select(const string &title, const string &extension);
void saveConfiguration(); void saveConfiguration();
void synchronize(); void synchronize();
void bootstrap(); void bootstrap();
@ -18,7 +18,7 @@ struct Browser : Window {
private: private:
configuration config; configuration config;
struct Folder { struct Folder {
string filter; string extension;
string path; string path;
unsigned selection; unsigned selection;
}; };
@ -27,7 +27,7 @@ private:
bool dialogActive; bool dialogActive;
string outputFilename; string outputFilename;
string filter; string extension;
string path; string path;
lstring filenameList; lstring filenameList;

View File

@ -1,11 +1,11 @@
Presentation *presentation = nullptr; Presentation *presentation = nullptr;
void Presentation::synchronize() { void Presentation::synchronize() {
for(auto &system : emulatorList) system->menu.setVisible(false); for(auto &emulator : emulatorList) emulator->menu.setVisible(false);
for(auto &system : emulatorList) { for(auto &emulator : emulatorList) {
if(system->interface == application->active) { if(emulator->interface == application->active) {
activeSystem = system; active = emulator;
system->menu.setVisible(true); emulator->menu.setVisible(true);
} }
} }
@ -15,22 +15,26 @@ void Presentation::synchronize() {
case 2: stretchVideo.setChecked(); break; case 2: stretchVideo.setChecked(); break;
} }
aspectCorrection.setChecked(config->video.aspectCorrection); aspectCorrection.setChecked(config->video.aspectCorrection);
resizeWindow.setVisible(application->active && config->video.scaleMode != 2); maskOverscan.setChecked(config->video.maskOverscan);
synchronizeVideo.setChecked(config->video.synchronize);
synchronizeAudio.setChecked(config->audio.synchronize);
muteAudio.setChecked(config->audio.mute);
toolsMenu.setVisible(application->active);
resizeWindow.setVisible(config->video.scaleMode != 2);
} }
void Presentation::setSystemName(const string &name) { void Presentation::setSystemName(const string &name) {
if(activeSystem) activeSystem->menu.setText(name); if(active) active->menu.setText(name);
} }
Presentation::Presentation() : activeSystem(nullptr) { Presentation::Presentation() : active(nullptr) {
bootstrap(); bootstrap();
setTitle({Emulator::Name, " v", Emulator::Version});
setGeometry({1024, 600, 720, 480}); setGeometry({1024, 600, 720, 480});
windowManager->append(this, "Presentation");
setTitle({::Emulator::Name, " v", ::Emulator::Version});
setBackgroundColor({0, 0, 0}); setBackgroundColor({0, 0, 0});
setMenuFont(application->normalFont);
setMenuVisible(); setMenuVisible();
setStatusFont(application->boldFont);
setStatusVisible(); setStatusVisible();
loadMenu.setText("Load"); loadMenu.setText("Load");
@ -40,20 +44,39 @@ Presentation::Presentation() : activeSystem(nullptr) {
scaleVideo.setText("Scale"); scaleVideo.setText("Scale");
stretchVideo.setText("Stretch"); stretchVideo.setText("Stretch");
RadioItem::group(centerVideo, scaleVideo, stretchVideo); RadioItem::group(centerVideo, scaleVideo, stretchVideo);
aspectCorrection.setText("Correct Aspect Ratio"); aspectCorrection.setText("Aspect Correction");
maskOverscan.setText("Mask Overscan");
synchronizeVideo.setText("Synchronize Video");
synchronizeAudio.setText("Synchronize Audio");
muteAudio.setText("Mute Audio");
configurationSettings.setText("Configuration ..."); configurationSettings.setText("Configuration ...");
toolsMenu.setText("Tools"); toolsMenu.setText("Tools");
saveStateMenu.setText("Save State");
for(unsigned n = 0; n < 5; n++) saveStateItem[n].setText({"Slot ", 1 + n});
loadStateMenu.setText("Load State");
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
resizeWindow.setText("Resize Window"); resizeWindow.setText("Resize Window");
append(loadMenu); append(loadMenu);
for(auto &item : loadList) loadMenu.append(*item); for(auto &item : loadListDirect) loadMenu.append(*item);
if(loadListSlotted.size() > 0) {
loadMenu.append(*new Separator);
for(auto &item : loadListSlotted) loadMenu.append(*item);
}
for(auto &system : emulatorList) append(system->menu); for(auto &system : emulatorList) append(system->menu);
append(settingsMenu); append(settingsMenu);
settingsMenu.append(videoMenu); settingsMenu.append(videoMenu);
videoMenu.append(centerVideo, scaleVideo, stretchVideo, *new Separator, aspectCorrection); videoMenu.append(centerVideo, scaleVideo, stretchVideo, *new Separator, aspectCorrection, maskOverscan);
settingsMenu.append(*new Separator);
settingsMenu.append(synchronizeVideo, synchronizeAudio, muteAudio);
settingsMenu.append(*new Separator); settingsMenu.append(*new Separator);
settingsMenu.append(configurationSettings); settingsMenu.append(configurationSettings);
append(toolsMenu); append(toolsMenu);
toolsMenu.append(saveStateMenu);
for(unsigned n = 0; n < 5; n++) saveStateMenu.append(saveStateItem[n]);
toolsMenu.append(loadStateMenu);
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
toolsMenu.append(stateMenuSeparator);
toolsMenu.append(resizeWindow); toolsMenu.append(resizeWindow);
append(layout); append(layout);
@ -66,7 +89,13 @@ Presentation::Presentation() : activeSystem(nullptr) {
scaleVideo.onActivate = [&] { config->video.scaleMode = 1; utility->resize(); }; scaleVideo.onActivate = [&] { config->video.scaleMode = 1; utility->resize(); };
stretchVideo.onActivate = [&] { config->video.scaleMode = 2; utility->resize(); }; stretchVideo.onActivate = [&] { config->video.scaleMode = 2; utility->resize(); };
aspectCorrection.onToggle = [&] { config->video.aspectCorrection = aspectCorrection.checked(); utility->resize(); }; aspectCorrection.onToggle = [&] { config->video.aspectCorrection = aspectCorrection.checked(); utility->resize(); };
configurationSettings.onActivate = [&] { settings->setVisible(); }; maskOverscan.onToggle = [&] { config->video.maskOverscan = maskOverscan.checked(); };
synchronizeVideo.onToggle = [&] { config->video.synchronize = synchronizeVideo.checked(); utility->synchronizeRuby(); };
synchronizeAudio.onToggle = [&] { config->audio.synchronize = synchronizeAudio.checked(); utility->synchronizeRuby(); };
muteAudio.onToggle = [&] { config->audio.mute = muteAudio.checked(); utility->synchronizeRuby(); };
configurationSettings.onActivate = [&] { settings->setVisible(); settings->panelList.setFocused(); };
for(unsigned n = 0; n < 5; n++) saveStateItem[n].onActivate = [=] { utility->saveState(1 + n); };
for(unsigned n = 0; n < 5; n++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
resizeWindow.onActivate = [&] { utility->resize(true); }; resizeWindow.onActivate = [&] { utility->resize(true); };
synchronize(); synchronize();
@ -74,33 +103,65 @@ Presentation::Presentation() : activeSystem(nullptr) {
void Presentation::bootstrap() { void Presentation::bootstrap() {
for(auto &emulator : application->emulator) { for(auto &emulator : application->emulator) {
System *system = new System; auto iEmulator = new Emulator;
system->interface = emulator; iEmulator->interface = emulator;
for(auto &schema : emulator->schema) { for(auto &schema : emulator->schema) {
Item *item = new Item; Item *item = new Item;
item->setText({schema.displayname, " ..."}); item->setText({schema.name, " ..."});
item->onActivate = [=, &schema] { item->onActivate = [=, &schema] {
utility->loadSchema(system->interface, schema); utility->loadSchema(iEmulator->interface, schema);
}; };
loadList.append(item); if(schema.slot.size() == 0) loadListDirect.append(item);
if(schema.slot.size() >= 1) loadListSlotted.append(item);
} }
system->menu.setText(emulator->information.name); iEmulator->menu.setText(emulator->information.name);
system->power.setText("Power"); iEmulator->power.setText("Power");
system->reset.setText("Reset"); iEmulator->reset.setText("Reset");
system->unload.setText("Unload"); iEmulator->unload.setText("Unload");
system->menu.append(system->power); unsigned portNumber = 0;
for(auto &port : emulator->port) {
auto iPort = new Emulator::Port;
iPort->menu.setText(port.name);
iEmulator->port.append(iPort);
unsigned deviceNumber = 0;
for(auto &device : port.device) {
auto iDevice = new RadioItem;
iDevice->setText(device.name);
iDevice->onActivate = [=] { utility->connect(portNumber, deviceNumber); };
iPort->group.append(*iDevice);
iPort->device.append(iDevice);
deviceNumber++;
}
RadioItem::group(iPort->group);
portNumber++;
}
iEmulator->menu.append(iEmulator->power);
if(emulator->information.resettable) if(emulator->information.resettable)
system->menu.append(system->reset); iEmulator->menu.append(iEmulator->reset);
system->menu.append(system->separator); iEmulator->menu.append(*new Separator);
system->menu.append(system->unload); unsigned visiblePorts = 0;
for(auto &iPort : iEmulator->port) {
iEmulator->menu.append(iPort->menu);
if(iPort->device.size() <= 1) iPort->menu.setVisible(false);
else visiblePorts++;
for(auto &iDevice : iPort->device) {
iPort->menu.append(*iDevice);
}
}
iEmulator->menu.append(iEmulator->controllerSeparator);
if(visiblePorts == 0) iEmulator->controllerSeparator.setVisible(false);
iEmulator->menu.append(iEmulator->unload);
system->power.onActivate = {&Utility::power, utility}; iEmulator->power.onActivate = {&Utility::power, utility};
system->reset.onActivate = {&Utility::reset, utility}; iEmulator->reset.onActivate = {&Utility::reset, utility};
system->unload.onActivate = {&Utility::unload, utility}; iEmulator->unload.onActivate = {&Utility::unload, utility};
emulatorList.append(system); emulatorList.append(iEmulator);
} }
} }

View File

@ -2,28 +2,44 @@ struct Presentation : Window {
FixedLayout layout; FixedLayout layout;
Viewport viewport; Viewport viewport;
struct System { struct Emulator {
Emulator::Interface *interface; ::Emulator::Interface *interface;
Menu menu; Menu menu;
Item power; Item power;
Item reset; Item reset;
Separator separator;
Item unload; Item unload;
Separator controllerSeparator;
struct Port {
Menu menu;
set<RadioItem&> group;
vector<RadioItem*> device;
};
vector<Port*> port;
function<void (string)> callback; function<void (string)> callback;
}; };
vector<System*> emulatorList; vector<Emulator*> emulatorList;
Menu loadMenu; Menu loadMenu;
vector<Action*> loadList; vector<Item*> loadListDirect;
vector<Item*> loadListSlotted;
Menu settingsMenu; Menu settingsMenu;
Menu videoMenu; Menu videoMenu;
RadioItem centerVideo; RadioItem centerVideo;
RadioItem scaleVideo; RadioItem scaleVideo;
RadioItem stretchVideo; RadioItem stretchVideo;
CheckItem aspectCorrection; CheckItem aspectCorrection;
CheckItem maskOverscan;
CheckItem synchronizeVideo;
CheckItem synchronizeAudio;
CheckItem muteAudio;
Item configurationSettings; Item configurationSettings;
Menu toolsMenu; Menu toolsMenu;
Menu saveStateMenu;
Item saveStateItem[5];
Menu loadStateMenu;
Item loadStateItem[5];
Separator stateMenuSeparator;
Item resizeWindow; Item resizeWindow;
void synchronize(); void synchronize();
@ -32,7 +48,7 @@ struct Presentation : Window {
Presentation(); Presentation();
private: private:
System *activeSystem; Emulator *active;
}; };
extern Presentation *presentation; extern Presentation *presentation;

View File

@ -2,21 +2,37 @@ void InputManager::appendHotkeys() {
{ {
auto hotkey = new HotkeyInput; auto hotkey = new HotkeyInput;
hotkey->name = "Toggle Fullscreen Mode"; hotkey->name = "Toggle Fullscreen Mode";
hotkey->mapping = "KB0::Alt,KB0::Return"; hotkey->mapping = "KB0::F11";
hotkey->logic = 1;
hotkeyMap.append(hotkey);
hotkey->press = [] { hotkey->press = [] {
utility->toggleFullScreen(); utility->toggleFullScreen();
}; };
} }
{
auto hotkey = new HotkeyInput;
hotkey->name = "Toggle Mouse Capture";
hotkey->mapping = "KB0::F12";
hotkey->press = [] {
input.acquired() ? input.unacquire() : input.acquire();
};
}
{
auto hotkey = new HotkeyInput;
hotkey->name = "Pause Emulation";
hotkey->mapping = "KB0::P";
hotkey->press = [] {
application->pause = !application->pause;
};
}
{ {
auto hotkey = new HotkeyInput; auto hotkey = new HotkeyInput;
hotkey->name = "Fast Forward"; hotkey->name = "Fast Forward";
hotkey->mapping = "KB0::Tilde"; hotkey->mapping = "KB0::Tilde";
hotkey->logic = 1;
hotkeyMap.append(hotkey);
hotkey->press = [] { hotkey->press = [] {
video.set(Video::Synchronize, false); video.set(Video::Synchronize, false);
@ -24,8 +40,8 @@ void InputManager::appendHotkeys() {
}; };
hotkey->release = [] { hotkey->release = [] {
video.set(Video::Synchronize, false); video.set(Video::Synchronize, ::config->video.synchronize);
audio.set(Audio::Synchronize, true); audio.set(Audio::Synchronize, ::config->audio.synchronize);
}; };
} }
@ -33,8 +49,6 @@ void InputManager::appendHotkeys() {
auto hotkey = new HotkeyInput; auto hotkey = new HotkeyInput;
hotkey->name = "Power Cycle"; hotkey->name = "Power Cycle";
hotkey->mapping = "None"; hotkey->mapping = "None";
hotkey->logic = 1;
hotkeyMap.append(hotkey);
hotkey->press = [] { hotkey->press = [] {
utility->power(); utility->power();
@ -45,16 +59,70 @@ void InputManager::appendHotkeys() {
auto hotkey = new HotkeyInput; auto hotkey = new HotkeyInput;
hotkey->name = "Soft Reset"; hotkey->name = "Soft Reset";
hotkey->mapping = "None"; hotkey->mapping = "None";
hotkey->logic = 1;
hotkeyMap.append(hotkey);
hotkey->press = [] { hotkey->press = [] {
utility->reset(); utility->reset();
}; };
} }
static unsigned activeSlot = 1;
{
auto hotkey = new HotkeyInput;
hotkey->name = "Save State";
hotkey->mapping = "KB0::F5";
hotkey->press = [&] {
utility->saveState(activeSlot);
};
}
{
auto hotkey = new HotkeyInput;
hotkey->name = "Load State";
hotkey->mapping = "KB0::F7";
hotkey->press = [&] {
utility->loadState(activeSlot);
};
}
{
auto hotkey = new HotkeyInput;
hotkey->name = "Decrement Slot";
hotkey->mapping = "KB0::F6";
hotkey->press = [&] {
if(--activeSlot == 0) activeSlot = 5;
utility->showMessage({"Selected slot ", activeSlot});
};
}
{
auto hotkey = new HotkeyInput;
hotkey->name = "Increment Slot";
hotkey->mapping = "KB0::F8";
hotkey->press = [&] {
if(++activeSlot == 6) activeSlot = 1;
utility->showMessage({"Selected slot ", activeSlot});
};
}
{
auto hotkey = new HotkeyInput;
hotkey->name = "Close Emulator";
hotkey->mapping = "None";
hotkey->press = [] {
application->quit = true;
};
}
for(auto &hotkey : hotkeyMap) { for(auto &hotkey : hotkeyMap) {
config.append(hotkey->mapping, hotkey->name); string name = {"Hotkey::", hotkey->name};
name.replace(" ", "");
config.append(hotkey->mapping, name);
} }
} }

View File

@ -144,6 +144,13 @@ int16_t AnalogInput::poll() {
// //
HotkeyInput::HotkeyInput() {
logic = 1; //AND
inputManager->hotkeyMap.append(this);
}
//
void InputManager::bind() { void InputManager::bind() {
for(auto &input : inputMap) input->bind(); for(auto &input : inputMap) input->bind();
for(auto &input : hotkeyMap) input->bind(); for(auto &input : hotkeyMap) input->bind();
@ -174,6 +181,7 @@ void InputManager::saveConfiguration() {
} }
InputManager::InputManager() { InputManager::InputManager() {
inputManager = this;
bootstrap(); bootstrap();
} }
@ -182,7 +190,7 @@ void InputManager::bootstrap() {
for(auto &emulator : application->emulator) { for(auto &emulator : application->emulator) {
for(auto &port : emulator->port) { for(auto &port : emulator->port) {
for(auto &device : port.device) { for(auto &device : port.device) {
for(auto &number : device.displayinput) { for(auto &number : device.order) {
auto &input = device.input[number]; auto &input = device.input[number];
AbstractInput *abstract = nullptr; AbstractInput *abstract = nullptr;

View File

@ -32,6 +32,7 @@ struct AnalogInput : AbstractInput {
struct HotkeyInput : DigitalInput { struct HotkeyInput : DigitalInput {
function<void ()> press; function<void ()> press;
function<void ()> release; function<void ()> release;
HotkeyInput();
}; };
struct InputManager { struct InputManager {

View File

@ -1,9 +1,42 @@
#include "../ethos.hpp" #include "../ethos.hpp"
Interface *interface = nullptr; Interface *interface = nullptr;
uint32_t Interface::videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { uint32_t Interface::videoColor(unsigned source, uint16_t r, uint16_t g, uint16_t b) {
red >>= 8, green >>= 8, blue >>= 8; if(config->video.saturation != 100) {
return red << 16 | green << 8 | blue << 0; uint16_t grayscale = uclamp<16>((r + g + b) / 3);
double saturation = config->video.saturation * 0.01;
double inverse = max(0.0, 1.0 - saturation);
r = uclamp<16>(r * saturation + grayscale * inverse);
g = uclamp<16>(g * saturation + grayscale * inverse);
b = uclamp<16>(b * saturation + grayscale * inverse);
}
if(config->video.gamma != 100) {
double exponent = config->video.gamma * 0.01;
double reciprocal = 1.0 / 32767.0;
r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent);
g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent);
b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent);
}
if(config->video.luminance != 100) {
double luminance = config->video.luminance * 0.01;
r = r * luminance;
g = g * luminance;
b = b * luminance;
}
if(application->depth == 30) {
r >>= 6, g >>= 6, b >>= 6;
return r << 20 | g << 10 | b << 0;
}
if(application->depth == 24) {
r >>= 8, g >>= 8, b >>= 8;
return r << 16 | g << 8 | b << 0;
}
return 0u;
} }
void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned width, unsigned height) { void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned width, unsigned height) {
@ -17,6 +50,21 @@ void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned widt
memcpy(output + y * outputPitch, data + y * pitch, 4 * width); memcpy(output + y * outputPitch, data + y * pitch, 4 * width);
} }
if(system().information.overscan && config->video.maskOverscan) {
unsigned h = config->video.maskOverscanHorizontal;
unsigned v = config->video.maskOverscanVertical;
if(h) for(unsigned y = 0; y < height; y++) {
memset(output + y * outputPitch, 0, 4 * h);
memset(output + y * outputPitch + (width - h), 0, 4 * h);
}
if(v) for(unsigned y = 0; y < v; y++) {
memset(output + y * outputPitch, 0, 4 * width);
memset(output + (height - 1 - y) * outputPitch, 0, 4 * width);
}
}
video.unlock(); video.unlock();
video.refresh(); video.refresh();
} }
@ -43,16 +91,17 @@ void Interface::audioSample(int16_t lsample, int16_t rsample) {
} }
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) { int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
if(config->input.focusAllow == false && presentation->focused() == false) return 0;
unsigned guid = system().port[port].device[device].input[input].guid; unsigned guid = system().port[port].device[device].input[input].guid;
return inputManager->inputMap[guid]->poll(); return inputManager->inputMap[guid]->poll();
} }
void Interface::mediaRequest(Emulator::Interface::Media media) { void Interface::mediaRequest(Emulator::Interface::Media media) {
string pathname = browser->select({"Load ", media.displayname}, media.filter); string pathname = browser->select({"Load ", media.name}, media.extension);
if(pathname.empty()) return; if(pathname.empty()) return;
string markup; string markup;
markup.readfile({pathname, "manifest.xml"}); markup.readfile({pathname, "manifest.xml"});
mmapstream stream({pathname, media.name}); mmapstream stream({pathname, media.path});
system().load(media.id, stream, markup); system().load(media.id, stream, markup);
} }

View File

@ -1,8 +1,73 @@
AudioSettings *audioSettings = nullptr; AudioSettings *audioSettings = nullptr;
AudioSlider::AudioSlider() {
append(name, {75, 0});
append(value, {75, 0});
append(slider, {~0, 0});
}
AudioSettings::AudioSettings() { AudioSettings::AudioSettings() {
title.setFont(application->titleFont); title.setFont(application->titleFont);
title.setText("Audio Settings"); title.setText("Audio Settings");
frequencyLabel.setText("Frequency:");
frequency.append("32000hz", "44100hz", "48000hz", "96000hz");
latencyLabel.setText("Latency:");
latency.append("20ms", "40ms", "60ms", "80ms", "100ms");
resamplerLabel.setText("Resampler:");
resampler.append("Linear", "Hermite", "Sinc");
volume.name.setText("Volume:");
volume.slider.setLength(201);
append(title, {~0, 0}, 5); append(title, {~0, 0}, 5);
append(controlLayout, {~0, 0}, 5);
controlLayout.append(frequencyLabel, {0, 0}, 5);
controlLayout.append(frequency, {~0, 0}, 5);
controlLayout.append(latencyLabel, {0, 0}, 5);
controlLayout.append(latency, {~0, 0}, 5);
controlLayout.append(resamplerLabel, {0, 0}, 5);
controlLayout.append(resampler, {~0, 0});
append(volume, {~0, 0});
switch(config->audio.frequency) { default:
case 32000: frequency.setSelection(0); break;
case 44100: frequency.setSelection(1); break;
case 48000: frequency.setSelection(2); break;
case 96000: frequency.setSelection(3); break;
}
switch(config->audio.latency) { default:
case 20: latency.setSelection(0); break;
case 40: latency.setSelection(1); break;
case 60: latency.setSelection(2); break;
case 80: latency.setSelection(3); break;
case 100: latency.setSelection(4); break;
}
resampler.setSelection(config->audio.resampler);
volume.slider.setPosition(config->audio.volume);
frequency.onChange = latency.onChange = resampler.onChange = volume.slider.onChange =
{&AudioSettings::synchronize, this};
synchronize();
}
void AudioSettings::synchronize() {
switch(frequency.selection()) {
case 0: config->audio.frequency = 32000; break;
case 1: config->audio.frequency = 44100; break;
case 2: config->audio.frequency = 48000; break;
case 3: config->audio.frequency = 96000; break;
}
switch(latency.selection()) {
case 0: config->audio.latency = 20; break;
case 1: config->audio.latency = 40; break;
case 2: config->audio.latency = 60; break;
case 3: config->audio.latency = 80; break;
case 4: config->audio.latency = 100; break;
}
config->audio.resampler = resampler.selection();
config->audio.volume = volume.slider.position();
volume.value.setText({config->audio.volume, "%"});
utility->synchronizeRuby();
} }

View File

@ -1,6 +1,23 @@
struct AudioSlider : HorizontalLayout {
Label name;
Label value;
HorizontalSlider slider;
AudioSlider();
};
struct AudioSettings : SettingsLayout { struct AudioSettings : SettingsLayout {
Label title; Label title;
HorizontalLayout controlLayout;
Label frequencyLabel;
ComboBox frequency;
Label latencyLabel;
ComboBox latency;
Label resamplerLabel;
ComboBox resampler;
AudioSlider volume;
void synchronize();
AudioSettings(); AudioSettings();
}; };

View File

@ -29,6 +29,8 @@ void HotkeySettings::refresh() {
inputList.reset(); inputList.reset();
for(auto &hotkey : inputManager->hotkeyMap) { for(auto &hotkey : inputManager->hotkeyMap) {
string mapping = hotkey->mapping; string mapping = hotkey->mapping;
mapping.replace("KB0::", "");
mapping.replace("MS0::", "Mouse::");
mapping.replace(",", " and "); mapping.replace(",", " and ");
inputList.append(hotkey->name, mapping); inputList.append(hotkey->name, mapping);
} }
@ -44,23 +46,22 @@ void HotkeySettings::assignInput() {
activeInput = inputManager->hotkeyMap[inputList.selection()]; activeInput = inputManager->hotkeyMap[inputList.selection()];
settings->setStatusText({"Set assignment for [", activeInput->name, "] ..."}); settings->setStatusText({"Set assignment for [", activeInput->name, "] ..."});
settings->panelList.setEnabled(false); settings->layout.setEnabled(false);
inputList.setEnabled(false); setEnabled(false);
clearButton.setEnabled(false);
} }
void HotkeySettings::inputEvent(unsigned scancode, int16_t value) { void HotkeySettings::inputEvent(unsigned scancode, int16_t value) {
using nall::Mouse; using nall::Mouse;
if(activeInput == nullptr) return; if(activeInput == nullptr) return;
if(value != 1) return;
if(Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode)) return; if(Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode)) return;
if(Joypad::isAnyAxis(scancode)) return; if(Joypad::isAnyAxis(scancode)) return;
if(activeInput->bind(scancode, value) == false) return; if(activeInput->bind(scancode, value) == false) return;
activeInput = nullptr; activeInput = nullptr;
settings->setStatusText(""); settings->setStatusText("");
settings->panelList.setEnabled(true); settings->layout.setEnabled(true);
inputList.setEnabled(true); setEnabled(true);
clearButton.setEnabled(true);
refresh(); refresh();
} }

View File

@ -3,11 +3,18 @@ InputSettings *inputSettings = nullptr;
InputSettings::InputSettings() : activeInput(nullptr) { InputSettings::InputSettings() : activeInput(nullptr) {
title.setFont(application->titleFont); title.setFont(application->titleFont);
title.setText("Input Settings"); title.setText("Input Settings");
focusLabel.setText("When Focus is Lost:");
focusPause.setText("Pause Emulation");
focusAllow.setText("Allow Input");
inputList.setHeaderText("Name", "Mapping"); inputList.setHeaderText("Name", "Mapping");
inputList.setHeaderVisible(); inputList.setHeaderVisible();
clearButton.setText("Clear"); clearButton.setText("Clear");
append(title, {~0, 0}, 5); append(title, {~0, 0}, 5);
append(focusLayout, {~0, 0}, 5);
focusLayout.append(focusLabel, {0, 0}, 5);
focusLayout.append(focusPause, {0, 0}, 5);
focusLayout.append(focusAllow, {0, 0});
append(selectionLayout, {~0, 0}, 5); append(selectionLayout, {~0, 0}, 5);
selectionLayout.append(systemList, {~0, 0}, 5); selectionLayout.append(systemList, {~0, 0}, 5);
selectionLayout.append(portList, {~0, 0}, 5); selectionLayout.append(portList, {~0, 0}, 5);
@ -24,6 +31,12 @@ InputSettings::InputSettings() : activeInput(nullptr) {
systemList.append(emulator->information.name); systemList.append(emulator->information.name);
} }
focusPause.setChecked(config->input.focusPause);
focusAllow.setChecked(config->input.focusAllow);
focusAllow.setEnabled(!config->input.focusPause);
focusPause.onToggle = [&] { config->input.focusPause = focusPause.checked(); focusAllow.setEnabled(!focusPause.checked()); };
focusAllow.onToggle = [&] { config->input.focusAllow = focusAllow.checked(); };
systemList.onChange = {&InputSettings::systemChanged, this}; systemList.onChange = {&InputSettings::systemChanged, this};
portList.onChange = {&InputSettings::portChanged, this}; portList.onChange = {&InputSettings::portChanged, this};
deviceList.onChange = {&InputSettings::deviceChanged, this}; deviceList.onChange = {&InputSettings::deviceChanged, this};
@ -43,11 +56,11 @@ void InputSettings::synchronize() {
assign[1].setVisible(false); assign[1].setVisible(false);
assign[2].setVisible(false); assign[2].setVisible(false);
} else { } else {
unsigned number = activeDevice().displayinput[inputList.selection()]; unsigned number = activeDevice().order[inputList.selection()];
auto &input = activeDevice().input[number]; auto &input = activeDevice().input[number];
activeInput = inputManager->inputMap[input.guid]; auto selectedInput = inputManager->inputMap[input.guid];
if(dynamic_cast<DigitalInput*>(activeInput)) { if(dynamic_cast<DigitalInput*>(selectedInput)) {
assign[0].setText("Mouse Left"); assign[0].setText("Mouse Left");
assign[1].setText("Mouse Middle"); assign[1].setText("Mouse Middle");
assign[2].setText("Mouse Right"); assign[2].setText("Mouse Right");
@ -56,7 +69,7 @@ void InputSettings::synchronize() {
assign[2].setVisible(true); assign[2].setVisible(true);
} }
if(dynamic_cast<AnalogInput*>(activeInput)) { if(dynamic_cast<AnalogInput*>(selectedInput)) {
assign[0].setText("Mouse X-axis"); assign[0].setText("Mouse X-axis");
assign[1].setText("Mouse Y-axis"); assign[1].setText("Mouse Y-axis");
assign[0].setVisible(true); assign[0].setVisible(true);
@ -98,10 +111,12 @@ void InputSettings::portChanged() {
void InputSettings::deviceChanged() { void InputSettings::deviceChanged() {
inputList.reset(); inputList.reset();
for(unsigned number : activeDevice().displayinput) { for(unsigned number : activeDevice().order) {
auto &input = activeDevice().input[number]; auto &input = activeDevice().input[number];
auto abstract = inputManager->inputMap(input.guid); auto abstract = inputManager->inputMap(input.guid);
string mapping = abstract->mapping; string mapping = abstract->mapping;
mapping.replace("KB0::", "");
mapping.replace("MS0::", "Mouse::");
mapping.replace(",", " or "); mapping.replace(",", " or ");
inputList.append(input.name, mapping); inputList.append(input.name, mapping);
} }
@ -109,31 +124,24 @@ void InputSettings::deviceChanged() {
} }
void InputSettings::clearInput() { void InputSettings::clearInput() {
unsigned number = activeDevice().displayinput[inputList.selection()]; unsigned number = activeDevice().order[inputList.selection()];
auto &input = activeDevice().input[number]; auto &input = activeDevice().input[number];
activeInput = inputManager->inputMap[input.guid]; activeInput = inputManager->inputMap[input.guid];
inputEvent(Scancode::None, 1); inputEvent(Scancode::None, 1);
} }
void InputSettings::assignInput() { void InputSettings::assignInput() {
unsigned number = activeDevice().displayinput[inputList.selection()]; unsigned number = activeDevice().order[inputList.selection()];
auto &input = activeDevice().input[number]; auto &input = activeDevice().input[number];
activeInput = inputManager->inputMap[input.guid]; activeInput = inputManager->inputMap[input.guid];
settings->setStatusText({"Set assignment for [", activeDevice().name, "::", input.name, "] ..."}); settings->setStatusText({"Set assignment for [", activeDevice().name, "::", input.name, "] ..."});
settings->panelList.setEnabled(false); settings->layout.setEnabled(false);
systemList.setEnabled(false); setEnabled(false);
portList.setEnabled(false);
deviceList.setEnabled(false);
inputList.setEnabled(false);
assign[0].setEnabled(false);
assign[1].setEnabled(false);
assign[2].setEnabled(false);
clearButton.setEnabled(false);
} }
void InputSettings::assignMouseInput(unsigned n) { void InputSettings::assignMouseInput(unsigned n) {
unsigned number = activeDevice().displayinput[inputList.selection()]; unsigned number = activeDevice().order[inputList.selection()];
auto &input = activeDevice().input[number]; auto &input = activeDevice().input[number];
activeInput = inputManager->inputMap[input.guid]; activeInput = inputManager->inputMap[input.guid];
@ -155,14 +163,7 @@ void InputSettings::inputEvent(unsigned scancode, int16_t value, bool allowMouse
activeInput = nullptr; activeInput = nullptr;
deviceChanged(); deviceChanged();
settings->setStatusText(""); settings->setStatusText("");
settings->panelList.setEnabled(true); settings->layout.setEnabled(true);
systemList.setEnabled(true); setEnabled(true);
portList.setEnabled(true);
deviceList.setEnabled(true);
inputList.setEnabled(true);
assign[0].setEnabled(true);
assign[1].setEnabled(true);
assign[2].setEnabled(true);
clearButton.setEnabled(true);
synchronize(); synchronize();
} }

View File

@ -1,5 +1,9 @@
struct InputSettings : SettingsLayout { struct InputSettings : SettingsLayout {
Label title; Label title;
HorizontalLayout focusLayout;
Label focusLabel;
CheckBox focusPause;
CheckBox focusAllow;
HorizontalLayout selectionLayout; HorizontalLayout selectionLayout;
ComboBox systemList; ComboBox systemList;
ComboBox portList; ComboBox portList;

View File

@ -16,9 +16,10 @@ SettingsLayout::SettingsLayout() {
} }
Settings::Settings() { Settings::Settings() {
setTitle("Configuration Settings");
setGeometry({128, 128, 640, 360}); setGeometry({128, 128, 640, 360});
setStatusFont(application->boldFont); windowManager->append(this, "Settings");
setTitle("Configuration Settings");
setStatusVisible(); setStatusVisible();
layout.setMargin(5); layout.setMargin(5);

View File

@ -1,8 +1,63 @@
VideoSettings *videoSettings = nullptr; VideoSettings *videoSettings = nullptr;
VideoSlider::VideoSlider() {
append(name, {75, 0});
append(value, {75, 0});
append(slider, {~0, 0});
}
VideoSettings::VideoSettings() { VideoSettings::VideoSettings() {
title.setFont(application->titleFont); title.setFont(application->titleFont);
title.setText("Video Settings"); title.setText("Video Settings");
colorAdjustment.setFont(application->boldFont);
colorAdjustment.setText("Color adjustment:");
saturation.name.setText("Saturation:");
saturation.slider.setLength(201);
gamma.name.setText("Gamma:");
gamma.slider.setLength(101);
luminance.name.setText("Luminance:");
luminance.slider.setLength(101);
overscanAdjustment.setFont(application->boldFont);
overscanAdjustment.setText("Overscan mask:");
overscanHorizontal.name.setText("Horizontal:");
overscanHorizontal.slider.setLength(17);
overscanVertical.name.setText("Vertical:");
overscanVertical.slider.setLength(17);
append(title, {~0, 0}, 5); append(title, {~0, 0}, 5);
append(colorAdjustment, {~0, 0});
append(saturation, {~0, 0});
append(gamma, {~0, 0});
append(luminance, {~0, 0}, 5);
append(overscanAdjustment, {~0, 0});
append(overscanHorizontal, {~0, 0});
append(overscanVertical, {~0, 0}, 5);
saturation.slider.setPosition(config->video.saturation);
gamma.slider.setPosition(config->video.gamma - 100);
luminance.slider.setPosition(config->video.luminance);
overscanHorizontal.slider.setPosition(config->video.maskOverscanHorizontal);
overscanVertical.slider.setPosition(config->video.maskOverscanVertical);
synchronize();
saturation.slider.onChange = gamma.slider.onChange = luminance.slider.onChange =
overscanHorizontal.slider.onChange = overscanVertical.slider.onChange =
{&VideoSettings::synchronize, this};
}
void VideoSettings::synchronize() {
config->video.saturation = saturation.slider.position();
config->video.gamma = 100 + gamma.slider.position();
config->video.luminance = luminance.slider.position();
config->video.maskOverscanHorizontal = overscanHorizontal.slider.position();
config->video.maskOverscanVertical = overscanVertical.slider.position();
saturation.value.setText({config->video.saturation, "%"});
gamma.value.setText({config->video.gamma, "%"});
luminance.value.setText({config->video.luminance, "%"});
overscanHorizontal.value.setText({config->video.maskOverscanHorizontal, "px"});
overscanVertical.value.setText({config->video.maskOverscanVertical, "px"});
if(application->active) system().updatePalette();
} }

View File

@ -1,6 +1,22 @@
struct VideoSlider : HorizontalLayout {
Label name;
Label value;
HorizontalSlider slider;
VideoSlider();
};
struct VideoSettings : SettingsLayout { struct VideoSettings : SettingsLayout {
Label title; Label title;
Label colorAdjustment;
VideoSlider saturation;
VideoSlider gamma;
VideoSlider luminance;
Label overscanAdjustment;
VideoSlider overscanHorizontal;
VideoSlider overscanVertical;
void synchronize();
VideoSettings(); VideoSettings();
}; };

View File

@ -8,9 +8,8 @@ void Utility::setInterface(Emulator::Interface *emulator) {
} }
void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) { void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) {
string pathname; string pathname = application->path({schema.name, ".", schema.extension, "/"});
if(!schema.path.empty()) pathname = application->path(schema.path); if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension);
if(!directory::exists(pathname)) pathname = browser->select(schema.displayname, schema.filter);
if(!directory::exists(pathname)) return; if(!directory::exists(pathname)) return;
loadMedia(emulator, schema, pathname); loadMedia(emulator, schema, pathname);
@ -23,7 +22,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi
string manifest; string manifest;
manifest.readfile({pathname, "manifest.xml"}); manifest.readfile({pathname, "manifest.xml"});
auto memory = file::read({pathname, media.name}); auto memory = file::read({pathname, media.path});
system().load(media.id, vectorstream{memory}, manifest); system().load(media.id, vectorstream{memory}, manifest);
for(auto &memory : system().memory) { for(auto &memory : system().memory) {
@ -37,7 +36,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi
string displayname = pathname; string displayname = pathname;
displayname.rtrim<1>("/"); displayname.rtrim<1>("/");
presentation->setTitle(notdir(nall::basename(displayname))); presentation->setTitle(notdir(nall::basename(displayname)));
presentation->setSystemName(media.displayname); presentation->setSystemName(media.name);
resize(); resize();
} }
@ -48,6 +47,11 @@ void Utility::saveMedia() {
} }
} }
void Utility::connect(unsigned port, unsigned device) {
if(application->active == nullptr) return;
system().connect(port, device);
}
void Utility::power() { void Utility::power() {
if(application->active == nullptr) return; if(application->active == nullptr) return;
system().power(); system().power();
@ -68,6 +72,39 @@ void Utility::unload() {
video.clear(); video.clear();
} }
void Utility::saveState(unsigned slot) {
if(application->active == nullptr) return;
serializer s = system().serialize();
if(s.size() == 0) return;
mkdir(string{pathname, "bsnes/"}, 0755);
if(file::write({pathname, "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
showMessage({"Save to slot ", slot});
}
void Utility::loadState(unsigned slot) {
if(application->active == nullptr) return;
auto memory = file::read({pathname, "bsnes/state-", slot, ".bsa"});
if(memory.size() == 0) return;
serializer s(memory.data(), memory.size());
if(system().unserialize(s) == false) return;
showMessage({"Loaded from slot ", slot});
}
void Utility::synchronizeRuby() {
video.set(Video::Synchronize, config->video.synchronize);
audio.set(Audio::Synchronize, config->audio.synchronize);
audio.set(Audio::Frequency, config->audio.frequency);
audio.set(Audio::Latency, config->audio.latency);
switch(config->audio.resampler) {
case 0: dspaudio.setResampler(DSP::ResampleEngine::Linear); break;
case 1: dspaudio.setResampler(DSP::ResampleEngine::Hermite); break;
case 2: dspaudio.setResampler(DSP::ResampleEngine::Sinc); break;
}
dspaudio.setResamplerFrequency(config->audio.frequency);
dspaudio.setVolume(config->audio.mute ? 0.0 : config->audio.volume * 0.01);
}
void Utility::resize(bool resizeWindow) { void Utility::resize(bool resizeWindow) {
if(application->active == nullptr) return; if(application->active == nullptr) return;
Geometry geometry = presentation->geometry(); Geometry geometry = presentation->geometry();
@ -132,6 +169,32 @@ void Utility::toggleFullScreen() {
resize(); resize();
} }
void Utility::setStatusText(const string &text) { void Utility::updateStatus() {
presentation->setStatusText(text); time_t currentTime = time(0);
string text;
if((currentTime - statusTime) <= 2) {
text = statusMessage;
} else if(application->active == nullptr) {
text = "No cartridge loaded";
} else if(application->pause || application->autopause) {
text = "Paused";
} else {
text = statusText;
}
if(text != presentation->statusText()) {
presentation->setStatusText(text);
}
}
void Utility::setStatusText(const string &text) {
statusText = text;
}
void Utility::showMessage(const string &message) {
statusTime = time(0);
statusMessage = message;
}
Utility::Utility() {
statusTime = 0;
} }

View File

@ -6,13 +6,28 @@ struct Utility {
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname); void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
void saveMedia(); void saveMedia();
void connect(unsigned port, unsigned device);
void power(); void power();
void reset(); void reset();
void unload(); void unload();
void saveState(unsigned slot);
void loadState(unsigned slot);
void synchronizeRuby();
void resize(bool resizeWindow = false); void resize(bool resizeWindow = false);
void toggleFullScreen(); void toggleFullScreen();
void updateStatus();
void setStatusText(const string &text); void setStatusText(const string &text);
void showMessage(const string &message);
Utility();
private:
string statusText;
string statusMessage;
time_t statusTime;
}; };
extern Utility *utility; extern Utility *utility;

View File

@ -0,0 +1,33 @@
#include "../ethos.hpp"
WindowManager *windowManager = nullptr;
void WindowManager::append(Window *window, const string &name) {
window->setMenuFont(application->normalFont);
window->setWidgetFont(application->normalFont);
window->setStatusFont(application->boldFont);
windowList.append({window, name, window->geometry().text()});
}
void WindowManager::loadGeometry() {
for(auto &window : windowList) {
config.append(window.geometry, window.name);
}
config.load(application->path("geometry.cfg"));
config.save(application->path("geometry.cfg"));
for(auto &window : windowList) {
window.window->setGeometry(window.geometry);
}
}
void WindowManager::saveGeometry() {
for(auto &window : windowList) {
window.geometry = window.window->geometry().text();
}
config.save(application->path("geometry.cfg"));
}
void WindowManager::hideAll() {
for(auto &window : windowList) {
window.window->setVisible(false);
}
}

View File

@ -0,0 +1,18 @@
struct WindowManager {
struct WindowList {
Window *window;
string name;
string geometry;
};
vector<WindowList> windowList;
void append(Window *window, const string &name);
void loadGeometry();
void saveGeometry();
void hideAll();
private:
configuration config;
};
extern WindowManager *windowManager;