From 5d273c526596a1fccc1d1367165f473236803b25 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 4 May 2012 22:47:41 +1000 Subject: [PATCH] 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. --- bsnes/emulator/emulator.hpp | 2 +- bsnes/emulator/interface.hpp | 38 +-- bsnes/fc/interface/interface.cpp | 77 +++--- bsnes/fc/interface/interface.hpp | 6 + bsnes/gb/interface/interface.cpp | 89 ++---- bsnes/gb/interface/interface.hpp | 3 + bsnes/gba/interface/interface.cpp | 63 ++--- bsnes/gba/interface/interface.hpp | 3 + bsnes/sfc/cartridge/markup.cpp | 6 +- bsnes/sfc/controller/justifier/justifier.cpp | 22 +- bsnes/sfc/controller/justifier/justifier.hpp | 1 + bsnes/sfc/controller/multitap/multitap.cpp | 6 +- bsnes/sfc/controller/usart/usart.cpp | 3 +- bsnes/sfc/interface/interface.cpp | 261 +++++++++--------- bsnes/sfc/interface/interface.hpp | 14 + bsnes/target-ethos/Makefile | 3 +- bsnes/target-ethos/bootstrap.cpp | 3 +- .../configuration/configuration.cpp | 15 + .../configuration/configuration.hpp | 21 ++ bsnes/target-ethos/ethos.cpp | 30 +- bsnes/target-ethos/ethos.hpp | 1 + bsnes/target-ethos/general/browser.cpp | 34 +-- bsnes/target-ethos/general/browser.hpp | 6 +- bsnes/target-ethos/general/presentation.cpp | 127 ++++++--- bsnes/target-ethos/general/presentation.hpp | 28 +- bsnes/target-ethos/input/hotkeys.cpp | 92 +++++- bsnes/target-ethos/input/input.cpp | 10 +- bsnes/target-ethos/input/input.hpp | 1 + bsnes/target-ethos/interface/interface.cpp | 59 +++- bsnes/target-ethos/settings/audio.cpp | 65 +++++ bsnes/target-ethos/settings/audio.hpp | 17 ++ bsnes/target-ethos/settings/hotkey.cpp | 13 +- bsnes/target-ethos/settings/input.cpp | 53 ++-- bsnes/target-ethos/settings/input.hpp | 4 + bsnes/target-ethos/settings/settings.cpp | 5 +- bsnes/target-ethos/settings/video.cpp | 55 ++++ bsnes/target-ethos/settings/video.hpp | 16 ++ bsnes/target-ethos/utility/utility.cpp | 77 +++++- bsnes/target-ethos/utility/utility.hpp | 15 + bsnes/target-ethos/window/window.cpp | 33 +++ bsnes/target-ethos/window/window.hpp | 18 ++ 41 files changed, 964 insertions(+), 431 deletions(-) create mode 100755 bsnes/target-ethos/window/window.cpp create mode 100755 bsnes/target-ethos/window/window.hpp diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 84cd4c52..7a05e711 100755 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { 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 License[] = "GPLv3"; } diff --git a/bsnes/emulator/interface.hpp b/bsnes/emulator/interface.hpp index 403850fb..05eb0521 100755 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -8,34 +8,31 @@ struct Interface { string name; unsigned width; unsigned height; + bool overscan; double aspectRatio; unsigned frequency; bool resettable; struct Media { string name; - string filter; + string extension; }; vector media; } information; - struct Firmware { - string displayname; - string name; - unsigned id; - }; - vector firmware; - struct Media { - string displayname; - string path; - string name; - string filter; unsigned id; + string name; + string extension; + string path; }; + vector firmware; struct Schema : Media { vector slot; + Schema(const Media &media) { + id = media.id, name = media.name, extension = media.extension, path = media.path; + } }; vector schema; @@ -46,19 +43,19 @@ struct Interface { vector memory; struct Port { - string name; unsigned id; + string name; struct Device { - string name; unsigned id; + string name; struct Input { - string name; - unsigned type; //0 = digital, 1 = analog unsigned id; + unsigned type; //0 = digital, 1 = analog + string name; unsigned guid; }; vector input; - vector displayinput; + vector order; }; vector device; }; @@ -72,7 +69,7 @@ struct Interface { function mediaRequest; } 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) { if(callback.videoColor) return callback.videoColor(source, red, green, blue); return (red >> 8) << 16 | (green >> 8) << 8 | (blue >> 8) << 0; @@ -102,10 +99,15 @@ struct Interface { virtual void unload() {} //system interface + virtual void connect(unsigned port, unsigned device) {} virtual void power() {} virtual void reset() {} virtual void run() {} + //state functions + virtual serializer serialize() = 0; + virtual bool unserialize(serializer&) = 0; + //utility functions virtual void updatePalette() {} }; diff --git a/bsnes/fc/interface/interface.cpp b/bsnes/fc/interface/interface.cpp index 1654f0ee..4c748802 100755 --- a/bsnes/fc/interface/interface.cpp +++ b/bsnes/fc/interface/interface.cpp @@ -43,6 +43,15 @@ void Interface::run() { system.run(); } +serializer Interface::serialize() { + system.runtosave(); + return system.serialize(); +} + +bool Interface::unserialize(serializer &s) { + return system.unserialize(s); +} + void Interface::updatePalette() { video.generate_palette(); } @@ -53,64 +62,40 @@ Interface::Interface() { information.name = "Famicom"; information.width = 256; information.height = 240; + information.overscan = true; information.aspectRatio = 8.0 / 7.0; information.frequency = 1789772; information.resettable = true; - information.media.append({"Famicom", "*.fc"}); + information.media.append({"Famicom", "fc"}); + + schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"}); { - Schema schema; - schema.displayname = "Famicom"; - 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); - } + Port port{0, "Port 1"}; + port.device.append(controller()); this->port.append(port); } { - Port port; - port.name = "Port 2"; - 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); - } + Port port{1, "Port 2"}; + port.device.append(controller()); 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; +} + } diff --git a/bsnes/fc/interface/interface.hpp b/bsnes/fc/interface/interface.hpp index 1c93de25..4351fac4 100755 --- a/bsnes/fc/interface/interface.hpp +++ b/bsnes/fc/interface/interface.hpp @@ -19,9 +19,15 @@ struct Interface : Emulator::Interface { void reset(); void run(); + serializer serialize(); + bool unserialize(serializer&); + void updatePalette(); Interface(); + +private: + Port::Device controller(); }; extern Interface *interface; diff --git a/bsnes/gb/interface/interface.cpp b/bsnes/gb/interface/interface.cpp index 40eb3595..4c473649 100755 --- a/bsnes/gb/interface/interface.cpp +++ b/bsnes/gb/interface/interface.cpp @@ -58,6 +58,15 @@ void Interface::run() { system.run(); } +serializer Interface::serialize() { + system.runtosave(); + return system.serialize(); +} + +bool Interface::unserialize(serializer &s) { + return system.unserialize(s); +} + void Interface::updatePalette() { video.generate_palette(); } @@ -68,72 +77,34 @@ Interface::Interface() { information.name = "Game Boy"; information.width = 160; information.height = 144; + information.overscan = false; information.aspectRatio = 1.0; information.frequency = 4194304; information.resettable = false; - information.media.append({"Game Boy", "*.gb"}); - information.media.append({"Game Boy Color", "*.gbc"}); + information.media.append({"Game Boy", "gb" }); + 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; - 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 port{0, "Device"}; { - Port::Device device; - device.name = "Controller"; - device.id = 0; - device.input.append({"Up", 0, 0}); - device.input.append({"Down", 0, 1}); - device.input.append({"Left", 0, 2}); - device.input.append({"Right", 0, 3}); - device.input.append({"B", 0, 4}); - device.input.append({"A", 0, 5}); - device.input.append({"Select", 0, 6}); - device.input.append({"Start", 0, 7}); - device.displayinput = {0, 1, 2, 3, 4, 5, 6, 7}; + Port::Device device{0, "Controller"}; + device.input.append({0, 0, "Up" }); + device.input.append({1, 0, "Down" }); + device.input.append({2, 0, "Left" }); + device.input.append({3, 0, "Right" }); + device.input.append({4, 0, "B" }); + device.input.append({5, 0, "A" }); + device.input.append({6, 0, "Select"}); + device.input.append({7, 0, "Start" }); + device.order = {0, 1, 2, 3, 4, 5, 6, 7}; port.device.append(device); } this->port.append(port); diff --git a/bsnes/gb/interface/interface.hpp b/bsnes/gb/interface/interface.hpp index 0bd79b7c..4ebdaf80 100755 --- a/bsnes/gb/interface/interface.hpp +++ b/bsnes/gb/interface/interface.hpp @@ -27,6 +27,9 @@ struct Interface : Emulator::Interface { void reset(); void run(); + serializer serialize(); + bool unserialize(serializer&); + void updatePalette(); Interface(); diff --git a/bsnes/gba/interface/interface.cpp b/bsnes/gba/interface/interface.cpp index 66b2e022..10674a3e 100755 --- a/bsnes/gba/interface/interface.cpp +++ b/bsnes/gba/interface/interface.cpp @@ -62,6 +62,15 @@ void Interface::run() { system.run(); } +serializer Interface::serialize() { + system.runtosave(); + return system.serialize(); +} + +bool Interface::unserialize(serializer &s) { + return system.unserialize(s); +} + void Interface::updatePalette() { video.generate_palette(); } @@ -72,48 +81,32 @@ Interface::Interface() { information.name = "Game Boy Advance"; information.width = 240; information.height = 160; + information.overscan = false; information.aspectRatio = 1.0; information.frequency = 32768; 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; - 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 port{0, "Device"}; { - 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({"Right", 0, 4}); - device.input.append({"Left", 0, 5}); - device.input.append({"Up", 0, 6}); - device.input.append({"Down", 0, 7}); - device.input.append({"R", 0, 8}); - device.input.append({"L", 0, 9}); - device.displayinput = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3}; + 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, "Right" }); + device.input.append({5, 0, "Left" }); + device.input.append({6, 0, "Up" }); + device.input.append({7, 0, "Down" }); + device.input.append({8, 0, "R" }); + device.input.append({9, 0, "L" }); + device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3}; port.device.append(device); } this->port.append(port); diff --git a/bsnes/gba/interface/interface.hpp b/bsnes/gba/interface/interface.hpp index 736eadbe..38a4e82e 100755 --- a/bsnes/gba/interface/interface.hpp +++ b/bsnes/gba/interface/interface.hpp @@ -22,6 +22,9 @@ struct Interface : Emulator::Interface { void reset(); void run(); + serializer serialize(); + bool unserialize(serializer&); + void updatePalette(); Interface(); diff --git a/bsnes/sfc/cartridge/markup.cpp b/bsnes/sfc/cartridge/markup.cpp index ab75d8c1..9ecbee2a 100755 --- a/bsnes/sfc/cartridge/markup.cpp +++ b/bsnes/sfc/cartridge/markup.cpp @@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) { if(root.exists() == false) return; 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)); @@ -372,7 +372,7 @@ void Cartridge::parse_markup_bsx(XML::Node &root) { has_bs_cart = root["mmio"].exists(); 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"]) { if(node.name != "map") continue; @@ -400,7 +400,7 @@ void Cartridge::parse_markup_sufamiturbo(XML::Node &root) { if(root.exists() == false) return; 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) { if(slot.name != "slot") continue; diff --git a/bsnes/sfc/controller/justifier/justifier.cpp b/bsnes/sfc/controller/justifier/justifier.cpp index fdb904da..9cda5432 100755 --- a/bsnes/sfc/controller/justifier/justifier.cpp +++ b/bsnes/sfc/controller/justifier/justifier.cpp @@ -18,8 +18,8 @@ void Justifier::enter() { } if(next < prev) { - int nx1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::X); - int ny1 = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Y); + int nx1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::X); + int ny1 = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Y); nx1 += player1.x; ny1 += player1.y; player1.x = max(-16, min(256 + 16, nx1)); @@ -27,8 +27,8 @@ void Justifier::enter() { } if(next < prev && chained) { - int nx2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::X); - int ny2 = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Y); + int nx2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::X); + int ny2 = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Y); nx2 += player2.x; ny2 += player2.y; player2.x = max(-16, min(256 + 16, nx2)); @@ -44,13 +44,13 @@ uint2 Justifier::data() { if(counter >= 32) return 1; if(counter == 0) { - player1.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Trigger); - player1.start = interface->inputPoll(port, (unsigned)Input::Device::Justifier, 0 << 16 | (unsigned)Input::JustifierID::Start); + player1.trigger = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Trigger); + player1.start = interface->inputPoll(port, device, 0 + (unsigned)Input::JustifierID::Start); } if(counter == 0 && chained) { - player2.trigger = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Trigger); - player2.start = interface->inputPoll(port, (unsigned)Input::Device::Justifiers, 1 << 16 | (unsigned)Input::JustifierID::Start); + player2.trigger = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Trigger); + player2.start = interface->inputPoll(port, device, 4 + (unsigned)Input::JustifierID::Start); } switch(counter++) { @@ -100,7 +100,11 @@ void Justifier::latch(bool data) { 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); latched = 0; counter = 0; diff --git a/bsnes/sfc/controller/justifier/justifier.hpp b/bsnes/sfc/controller/justifier/justifier.hpp index f927acf6..da467aa1 100755 --- a/bsnes/sfc/controller/justifier/justifier.hpp +++ b/bsnes/sfc/controller/justifier/justifier.hpp @@ -6,6 +6,7 @@ struct Justifier : Controller { //private: const bool chained; //true if the second justifier is attached to the first + const unsigned device; bool latched; unsigned counter; diff --git a/bsnes/sfc/controller/multitap/multitap.cpp b/bsnes/sfc/controller/multitap/multitap.cpp index 09748397..c53d9cc9 100755 --- a/bsnes/sfc/controller/multitap/multitap.cpp +++ b/bsnes/sfc/controller/multitap/multitap.cpp @@ -8,18 +8,20 @@ uint2 Multitap::data() { index = counter1; if(index >= 16) return 3; counter1++; + if(index >= 12) return 0; port1 = 0; //controller 1 port2 = 1; //controller 2 } else { index = counter2; if(index >= 16) return 3; counter2++; + if(index >= 12) return 0; port1 = 2; //controller 3 port2 = 3; //controller 4 } - bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 << 16 | index); - bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 << 16 | index); + bool data1 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port1 * 12 + index); + bool data2 = interface->inputPoll(port, (unsigned)Input::Device::Multitap, port2 * 12 + index); return (data2 << 1) | (data1 << 0); } diff --git a/bsnes/sfc/controller/usart/usart.cpp b/bsnes/sfc/controller/usart/usart.cpp index 236a2ca3..dc0b8eba 100755 --- a/bsnes/sfc/controller/usart/usart.cpp +++ b/bsnes/sfc/controller/usart/usart.cpp @@ -68,7 +68,8 @@ uint2 USART::data() { //Joypad if(iobit()) { 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++; return result; } diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index c0a8c5dd..2c5dab3c 100755 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -83,6 +83,10 @@ void Interface::unload() { cartridge.unload(); } +void Interface::connect(unsigned port, unsigned device) { + input.connect(port, (Input::Device)device); +} + void Interface::power() { system.power(); } @@ -95,6 +99,15 @@ void Interface::run() { system.run(); } +serializer Interface::serialize() { + system.runtosave(); + return system.serialize(); +} + +bool Interface::unserialize(serializer &s) { + return system.unserialize(s); +} + void Interface::updatePalette() { video.generate_palette(); } @@ -105,157 +118,159 @@ Interface::Interface() { information.name = "Super Famicom"; information.width = 256; information.height = 240; + information.overscan = true; information.aspectRatio = 8.0 / 7.0; information.frequency = 32040; information.resettable = true; - information.media.append({"Super Famicom", "*.sfc"}); - information.media.append({"BS-X Satellaview", "*.bs"}); - information.media.append({"Sufami Turbo", "*.st"}); - information.media.append({"Super Game Boy", "*.gb"}); + information.media.append({"Super Famicom", "sfc"}); + information.media.append({"BS-X Satellaview", "bs" }); + information.media.append({"Sufami Turbo", "st" }); + information.media.append({"Super Game Boy", "gb" }); + + firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"}); { - Firmware firmware; - 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; + Schema schema(Media{ID::ROM, "Super Famicom", "sfc", "program.rom"}); this->schema.append(schema); } { - Schema schema; - schema.displayname = "Super Game Boy"; - 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); - } + Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"}); + schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"}); this->schema.append(schema); } { - Schema schema; - schema.displayname = "BS-X Satellaview"; - 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); - } + Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"}); + schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"}); this->schema.append(schema); } { - Schema schema; - schema.displayname = "Sufami Turbo"; - schema.path = "Sufami Turbo.sfc/"; - 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); - } + Schema schema(Media{ID::ROM, "Sufami Turbo", "sfc", "program.rom"}); + schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"}); + schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"}); this->schema.append(schema); } { - Port port; - port.name = "Port 1"; - port.id = 0; - { - Port::Device device; - device.name = "None"; - 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); - } + Port port{0, "Port 1"}; + port.device.append(none()); + port.device.append(controller()); + port.device.append(multitap()); + port.device.append(mouse()); + port.device.append(usart()); this->port.append(port); } { - Port port; - port.name = "Port 2"; - port.id = 1; - { - Port::Device device; - device.name = "None"; - 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); - } + Port port{1, "Port 2"}; + port.device.append(none()); + port.device.append(controller()); + port.device.append(multitap()); + port.device.append(mouse()); + port.device.append(superScope()); + port.device.append(justifier()); + port.device.append(justifiers()); 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; +} + } diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index f4323e90..ec0b1bc7 100755 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -27,13 +27,27 @@ struct Interface : Emulator::Interface { void save(unsigned id, const stream &stream); void unload(); + void connect(unsigned port, unsigned device); void power(); void reset(); void run(); + serializer serialize(); + bool unserialize(serializer&); + void updatePalette(); 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; diff --git a/bsnes/target-ethos/Makefile b/bsnes/target-ethos/Makefile index 5ce67b7c..baf0a0a8 100755 --- a/bsnes/target-ethos/Makefile +++ b/bsnes/target-ethos/Makefile @@ -7,7 +7,7 @@ include gb/Makefile include gba/Makefile 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 += $(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-utility.o: $(ui)/utility/utility.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-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) diff --git a/bsnes/target-ethos/bootstrap.cpp b/bsnes/target-ethos/bootstrap.cpp index 869d7a0e..a48b5925 100755 --- a/bsnes/target-ethos/bootstrap.cpp +++ b/bsnes/target-ethos/bootstrap.cpp @@ -17,10 +17,9 @@ void Application::bootstrap() { system->callback.audioSample = {&Interface::audioSample, interface}; system->callback.inputPoll = {&Interface::inputPoll, interface}; system->callback.mediaRequest = {&Interface::mediaRequest, interface}; - system->updatePalette(); 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); } } diff --git a/bsnes/target-ethos/configuration/configuration.cpp b/bsnes/target-ethos/configuration/configuration.cpp index 8e53a966..c05b3dfa 100755 --- a/bsnes/target-ethos/configuration/configuration.cpp +++ b/bsnes/target-ethos/configuration/configuration.cpp @@ -2,8 +2,23 @@ Configuration *config = nullptr; Configuration::Configuration() { + append(video.synchronize = false, "Video::Synchronize"); append(video.scaleMode = 0, "Video::ScaleMode"); 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(); } diff --git a/bsnes/target-ethos/configuration/configuration.hpp b/bsnes/target-ethos/configuration/configuration.hpp index b83b5a4c..5606cc25 100755 --- a/bsnes/target-ethos/configuration/configuration.hpp +++ b/bsnes/target-ethos/configuration/configuration.hpp @@ -1,9 +1,30 @@ struct Configuration : configuration { struct Video { + bool synchronize; unsigned scaleMode; bool aspectCorrection; + bool maskOverscan; + unsigned maskOverscanHorizontal; + unsigned maskOverscanVertical; + unsigned saturation; + unsigned gamma; + unsigned luminance; } 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 save(); Configuration(); diff --git a/bsnes/target-ethos/ethos.cpp b/bsnes/target-ethos/ethos.cpp index b78e9d29..45914787 100755 --- a/bsnes/target-ethos/ethos.cpp +++ b/bsnes/target-ethos/ethos.cpp @@ -19,8 +19,11 @@ string Application::path(const string &filename) { void Application::run() { 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); return; } @@ -61,9 +64,14 @@ Application::Application(int argc, char **argv) { monospaceFont = "Liberation Mono, 8"; } + video.driver("OpenGL"); + audio.driver("ALSA"); + input.driver("SDL"); + config = new Configuration; utility = new Utility; inputManager = new InputManager; + windowManager = new WindowManager; browser = new Browser; presentation = new Presentation; videoSettings = new VideoSettings; @@ -71,31 +79,26 @@ Application::Application(int argc, char **argv) { inputSettings = new InputSettings; hotkeySettings = new HotkeySettings; settings = new Settings; - + windowManager->loadGeometry(); presentation->setVisible(); - video.driver("OpenGL"); video.set(Video::Handle, presentation->viewport.handle()); - video.set(Video::Synchronize, false); - video.set(Video::Depth, 24u); + if(!video.cap(Video::Depth) || !video.set(Video::Depth, depth = 30u)) { + video.set(Video::Depth, depth = 24u); + } video.init(); - audio.driver("ALSA"); audio.set(Audio::Handle, presentation->viewport.handle()); - audio.set(Audio::Synchronize, true); - audio.set(Audio::Latency, 80u); - audio.set(Audio::Frequency, 48000u); audio.init(); - input.driver("SDL"); input.set(Input::Handle, presentation->viewport.handle()); input.init(); dspaudio.setPrecision(16); - dspaudio.setVolume(2.0); dspaudio.setBalance(0.0); - dspaudio.setResampler(DSP::ResampleEngine::Sinc); - dspaudio.setResamplerFrequency(48000u); + dspaudio.setFrequency(96000); + + utility->synchronizeRuby(); while(quit == false) { OS::processEvents(); @@ -106,6 +109,7 @@ Application::Application(int argc, char **argv) { config->save(); browser->saveConfiguration(); inputManager->saveConfiguration(); + windowManager->saveGeometry(); } Application::~Application() { diff --git a/bsnes/target-ethos/ethos.hpp b/bsnes/target-ethos/ethos.hpp index eaef9379..6a595460 100755 --- a/bsnes/target-ethos/ethos.hpp +++ b/bsnes/target-ethos/ethos.hpp @@ -21,6 +21,7 @@ using namespace ruby; #include "interface/interface.hpp" #include "utility/utility.hpp" #include "input/input.hpp" +#include "window/window.hpp" #include "general/general.hpp" #include "settings/settings.hpp" diff --git a/bsnes/target-ethos/general/browser.cpp b/bsnes/target-ethos/general/browser.cpp index f2a4fa4c..e3598429 100755 --- a/bsnes/target-ethos/general/browser.cpp +++ b/bsnes/target-ethos/general/browser.cpp @@ -3,6 +3,7 @@ Browser *browser = nullptr; Browser::Browser() { bootstrap(); setGeometry({128, 128, 640, 400}); + windowManager->append(this, "Browser"); layout.setMargin(5); pathBrowse.setText("Browse ..."); @@ -50,7 +51,7 @@ void Browser::synchronize() { openButton.setEnabled(fileList.selected()); if(fileList.selected()) { for(auto &folder : folderList) { - if(folder.filter == filter) { + if(folder.extension == extension) { folder.selection = fileList.selection(); } } @@ -66,7 +67,7 @@ void Browser::bootstrap() { for(auto &media : emulator->information.media) { bool found = false; for(auto &folder : folderList) { - if(folder.filter == filter) { + if(folder.extension == media.extension) { found = true; break; } @@ -74,7 +75,7 @@ void Browser::bootstrap() { if(found == true) continue; Folder folder; - folder.filter = media.filter; + folder.extension = media.extension; folder.path = application->basepath; folder.selection = 0; folderList.append(folder); @@ -82,21 +83,21 @@ void Browser::bootstrap() { } for(auto &folder : folderList) { - config.append(folder.path, folder.filter); - config.append(folder.selection, string{folder.filter, "::selection"}); + config.append(folder.path, folder.extension); + config.append(folder.selection, string{folder.extension, "::selection"}); } config.load(application->path("paths.cfg")); config.save(application->path("paths.cfg")); } -string Browser::select(const string &title, const string &filter) { - this->filter = filter; +string Browser::select(const string &title, const string &extension) { + this->extension = extension; string path; unsigned selection = 0; for(auto &folder : folderList) { - if(folder.filter == filter) { + if(folder.extension == extension) { path = folder.path; selection = folder.selection; break; @@ -105,8 +106,9 @@ string Browser::select(const string &title, const string &filter) { if(path.empty()) path = application->basepath; setPath(path, selection); - filterLabel.setText({"Files of type: ", filter}); + filterLabel.setText({"Files of type: *.", extension}); + audio.clear(); setTitle(title); setModal(); setVisible(); @@ -123,7 +125,7 @@ string Browser::select(const string &title, const string &filter) { void Browser::setPath(const string &path, unsigned selection) { //save path for next browser selection for(auto &folder : folderList) { - if(folder.filter == filter) folder.path = path; + if(folder.extension == extension) folder.path = path; } this->path = path; @@ -135,7 +137,6 @@ void Browser::setPath(const string &path, unsigned selection) { lstring contents = directory::folders(path); for(auto &filename : contents) { - string filter = {this->filter, "/"}; if(!filename.wildcard(R"(*.??/)") && !filename.wildcard(R"(*.???/)")) { string name = filename; name.rtrim<1>("/"); @@ -146,12 +147,11 @@ void Browser::setPath(const string &path, unsigned selection) { } for(auto &filename : contents) { - string filter = {this->filter, "/"}; + string suffix = {".", this->extension, "/"}; if(filename.wildcard(R"(*.??/)") || filename.wildcard(R"(*.???/)")) { - if(filename.wildcard(filter)) { + if(filename.endswith(suffix)) { string name = filename; - filter.ltrim<1>("*"); - name.rtrim<1>(filter); + name.rtrim<1>(suffix); filenameList.append(filename); fileList.append(name); } @@ -166,8 +166,8 @@ void Browser::setPath(const string &path, unsigned selection) { void Browser::fileListActivate() { unsigned selection = fileList.selection(); string filename = filenameList[selection]; - string filter = {this->filter, "/"}; - if(filename.wildcard(filter) == false) return setPath({path, filename}); + string suffix = {this->extension, "/"}; + if(filename.endswith(suffix) == false) return setPath({path, filename}); setVisible(false); dialogActive = false; diff --git a/bsnes/target-ethos/general/browser.hpp b/bsnes/target-ethos/general/browser.hpp index 7162cf7e..ab2843c9 100755 --- a/bsnes/target-ethos/general/browser.hpp +++ b/bsnes/target-ethos/general/browser.hpp @@ -9,7 +9,7 @@ struct Browser : Window { Label filterLabel; Button openButton; - string select(const string &title, const string &filter); + string select(const string &title, const string &extension); void saveConfiguration(); void synchronize(); void bootstrap(); @@ -18,7 +18,7 @@ struct Browser : Window { private: configuration config; struct Folder { - string filter; + string extension; string path; unsigned selection; }; @@ -27,7 +27,7 @@ private: bool dialogActive; string outputFilename; - string filter; + string extension; string path; lstring filenameList; diff --git a/bsnes/target-ethos/general/presentation.cpp b/bsnes/target-ethos/general/presentation.cpp index 5f2de4a2..e1b2272c 100755 --- a/bsnes/target-ethos/general/presentation.cpp +++ b/bsnes/target-ethos/general/presentation.cpp @@ -1,11 +1,11 @@ Presentation *presentation = nullptr; void Presentation::synchronize() { - for(auto &system : emulatorList) system->menu.setVisible(false); - for(auto &system : emulatorList) { - if(system->interface == application->active) { - activeSystem = system; - system->menu.setVisible(true); + for(auto &emulator : emulatorList) emulator->menu.setVisible(false); + for(auto &emulator : emulatorList) { + if(emulator->interface == application->active) { + active = emulator; + emulator->menu.setVisible(true); } } @@ -15,22 +15,26 @@ void Presentation::synchronize() { case 2: stretchVideo.setChecked(); break; } 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) { - if(activeSystem) activeSystem->menu.setText(name); + if(active) active->menu.setText(name); } -Presentation::Presentation() : activeSystem(nullptr) { +Presentation::Presentation() : active(nullptr) { bootstrap(); - - setTitle({Emulator::Name, " v", Emulator::Version}); setGeometry({1024, 600, 720, 480}); + windowManager->append(this, "Presentation"); + + setTitle({::Emulator::Name, " v", ::Emulator::Version}); setBackgroundColor({0, 0, 0}); - setMenuFont(application->normalFont); setMenuVisible(); - setStatusFont(application->boldFont); setStatusVisible(); loadMenu.setText("Load"); @@ -40,20 +44,39 @@ Presentation::Presentation() : activeSystem(nullptr) { scaleVideo.setText("Scale"); stretchVideo.setText("Stretch"); 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 ..."); 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"); 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); append(settingsMenu); 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(configurationSettings); 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); append(layout); @@ -66,7 +89,13 @@ Presentation::Presentation() : activeSystem(nullptr) { scaleVideo.onActivate = [&] { config->video.scaleMode = 1; utility->resize(); }; stretchVideo.onActivate = [&] { config->video.scaleMode = 2; 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); }; synchronize(); @@ -74,33 +103,65 @@ Presentation::Presentation() : activeSystem(nullptr) { void Presentation::bootstrap() { for(auto &emulator : application->emulator) { - System *system = new System; - system->interface = emulator; + auto iEmulator = new Emulator; + iEmulator->interface = emulator; for(auto &schema : emulator->schema) { Item *item = new Item; - item->setText({schema.displayname, " ..."}); + item->setText({schema.name, " ..."}); 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); - system->power.setText("Power"); - system->reset.setText("Reset"); - system->unload.setText("Unload"); + iEmulator->menu.setText(emulator->information.name); + iEmulator->power.setText("Power"); + iEmulator->reset.setText("Reset"); + 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) - system->menu.append(system->reset); - system->menu.append(system->separator); - system->menu.append(system->unload); + iEmulator->menu.append(iEmulator->reset); + iEmulator->menu.append(*new Separator); + 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}; - system->reset.onActivate = {&Utility::reset, utility}; - system->unload.onActivate = {&Utility::unload, utility}; + iEmulator->power.onActivate = {&Utility::power, utility}; + iEmulator->reset.onActivate = {&Utility::reset, utility}; + iEmulator->unload.onActivate = {&Utility::unload, utility}; - emulatorList.append(system); + emulatorList.append(iEmulator); } } diff --git a/bsnes/target-ethos/general/presentation.hpp b/bsnes/target-ethos/general/presentation.hpp index 7c65686e..a187071a 100755 --- a/bsnes/target-ethos/general/presentation.hpp +++ b/bsnes/target-ethos/general/presentation.hpp @@ -2,28 +2,44 @@ struct Presentation : Window { FixedLayout layout; Viewport viewport; - struct System { - Emulator::Interface *interface; + struct Emulator { + ::Emulator::Interface *interface; Menu menu; Item power; Item reset; - Separator separator; Item unload; + Separator controllerSeparator; + struct Port { + Menu menu; + set group; + vector device; + }; + vector port; function callback; }; - vector emulatorList; + vector emulatorList; Menu loadMenu; - vector loadList; + vector loadListDirect; + vector loadListSlotted; Menu settingsMenu; Menu videoMenu; RadioItem centerVideo; RadioItem scaleVideo; RadioItem stretchVideo; CheckItem aspectCorrection; + CheckItem maskOverscan; + CheckItem synchronizeVideo; + CheckItem synchronizeAudio; + CheckItem muteAudio; Item configurationSettings; Menu toolsMenu; + Menu saveStateMenu; + Item saveStateItem[5]; + Menu loadStateMenu; + Item loadStateItem[5]; + Separator stateMenuSeparator; Item resizeWindow; void synchronize(); @@ -32,7 +48,7 @@ struct Presentation : Window { Presentation(); private: - System *activeSystem; + Emulator *active; }; extern Presentation *presentation; diff --git a/bsnes/target-ethos/input/hotkeys.cpp b/bsnes/target-ethos/input/hotkeys.cpp index 744e3a12..6781101d 100755 --- a/bsnes/target-ethos/input/hotkeys.cpp +++ b/bsnes/target-ethos/input/hotkeys.cpp @@ -2,21 +2,37 @@ void InputManager::appendHotkeys() { { auto hotkey = new HotkeyInput; hotkey->name = "Toggle Fullscreen Mode"; - hotkey->mapping = "KB0::Alt,KB0::Return"; - hotkey->logic = 1; - hotkeyMap.append(hotkey); + hotkey->mapping = "KB0::F11"; hotkey->press = [] { 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; hotkey->name = "Fast Forward"; hotkey->mapping = "KB0::Tilde"; - hotkey->logic = 1; - hotkeyMap.append(hotkey); hotkey->press = [] { video.set(Video::Synchronize, false); @@ -24,8 +40,8 @@ void InputManager::appendHotkeys() { }; hotkey->release = [] { - video.set(Video::Synchronize, false); - audio.set(Audio::Synchronize, true); + video.set(Video::Synchronize, ::config->video.synchronize); + audio.set(Audio::Synchronize, ::config->audio.synchronize); }; } @@ -33,8 +49,6 @@ void InputManager::appendHotkeys() { auto hotkey = new HotkeyInput; hotkey->name = "Power Cycle"; hotkey->mapping = "None"; - hotkey->logic = 1; - hotkeyMap.append(hotkey); hotkey->press = [] { utility->power(); @@ -45,16 +59,70 @@ void InputManager::appendHotkeys() { auto hotkey = new HotkeyInput; hotkey->name = "Soft Reset"; hotkey->mapping = "None"; - hotkey->logic = 1; - hotkeyMap.append(hotkey); hotkey->press = [] { 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) { - config.append(hotkey->mapping, hotkey->name); + string name = {"Hotkey::", hotkey->name}; + name.replace(" ", ""); + config.append(hotkey->mapping, name); } } diff --git a/bsnes/target-ethos/input/input.cpp b/bsnes/target-ethos/input/input.cpp index a659b7a3..333a50c9 100755 --- a/bsnes/target-ethos/input/input.cpp +++ b/bsnes/target-ethos/input/input.cpp @@ -144,6 +144,13 @@ int16_t AnalogInput::poll() { // +HotkeyInput::HotkeyInput() { + logic = 1; //AND + inputManager->hotkeyMap.append(this); +} + +// + void InputManager::bind() { for(auto &input : inputMap) input->bind(); for(auto &input : hotkeyMap) input->bind(); @@ -174,6 +181,7 @@ void InputManager::saveConfiguration() { } InputManager::InputManager() { + inputManager = this; bootstrap(); } @@ -182,7 +190,7 @@ void InputManager::bootstrap() { for(auto &emulator : application->emulator) { for(auto &port : emulator->port) { for(auto &device : port.device) { - for(auto &number : device.displayinput) { + for(auto &number : device.order) { auto &input = device.input[number]; AbstractInput *abstract = nullptr; diff --git a/bsnes/target-ethos/input/input.hpp b/bsnes/target-ethos/input/input.hpp index 142a1df3..9a982f58 100755 --- a/bsnes/target-ethos/input/input.hpp +++ b/bsnes/target-ethos/input/input.hpp @@ -32,6 +32,7 @@ struct AnalogInput : AbstractInput { struct HotkeyInput : DigitalInput { function press; function release; + HotkeyInput(); }; struct InputManager { diff --git a/bsnes/target-ethos/interface/interface.cpp b/bsnes/target-ethos/interface/interface.cpp index 82b35b48..10897174 100755 --- a/bsnes/target-ethos/interface/interface.cpp +++ b/bsnes/target-ethos/interface/interface.cpp @@ -1,9 +1,42 @@ #include "../ethos.hpp" Interface *interface = nullptr; -uint32_t Interface::videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { - red >>= 8, green >>= 8, blue >>= 8; - return red << 16 | green << 8 | blue << 0; +uint32_t Interface::videoColor(unsigned source, uint16_t r, uint16_t g, uint16_t b) { + if(config->video.saturation != 100) { + 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) { @@ -17,6 +50,21 @@ void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned widt 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.refresh(); } @@ -43,16 +91,17 @@ void Interface::audioSample(int16_t lsample, int16_t rsample) { } 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; return inputManager->inputMap[guid]->poll(); } 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; string markup; markup.readfile({pathname, "manifest.xml"}); - mmapstream stream({pathname, media.name}); + mmapstream stream({pathname, media.path}); system().load(media.id, stream, markup); } diff --git a/bsnes/target-ethos/settings/audio.cpp b/bsnes/target-ethos/settings/audio.cpp index 34e99b7c..9aae9e69 100755 --- a/bsnes/target-ethos/settings/audio.cpp +++ b/bsnes/target-ethos/settings/audio.cpp @@ -1,8 +1,73 @@ AudioSettings *audioSettings = nullptr; +AudioSlider::AudioSlider() { + append(name, {75, 0}); + append(value, {75, 0}); + append(slider, {~0, 0}); +} + AudioSettings::AudioSettings() { title.setFont(application->titleFont); 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(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(); } diff --git a/bsnes/target-ethos/settings/audio.hpp b/bsnes/target-ethos/settings/audio.hpp index 91ecc5d8..22a2386f 100755 --- a/bsnes/target-ethos/settings/audio.hpp +++ b/bsnes/target-ethos/settings/audio.hpp @@ -1,6 +1,23 @@ +struct AudioSlider : HorizontalLayout { + Label name; + Label value; + HorizontalSlider slider; + + AudioSlider(); +}; + struct AudioSettings : SettingsLayout { Label title; + HorizontalLayout controlLayout; + Label frequencyLabel; + ComboBox frequency; + Label latencyLabel; + ComboBox latency; + Label resamplerLabel; + ComboBox resampler; + AudioSlider volume; + void synchronize(); AudioSettings(); }; diff --git a/bsnes/target-ethos/settings/hotkey.cpp b/bsnes/target-ethos/settings/hotkey.cpp index 1b5e8043..89d9b1f3 100755 --- a/bsnes/target-ethos/settings/hotkey.cpp +++ b/bsnes/target-ethos/settings/hotkey.cpp @@ -29,6 +29,8 @@ void HotkeySettings::refresh() { inputList.reset(); for(auto &hotkey : inputManager->hotkeyMap) { string mapping = hotkey->mapping; + mapping.replace("KB0::", ""); + mapping.replace("MS0::", "Mouse::"); mapping.replace(",", " and "); inputList.append(hotkey->name, mapping); } @@ -44,23 +46,22 @@ void HotkeySettings::assignInput() { activeInput = inputManager->hotkeyMap[inputList.selection()]; settings->setStatusText({"Set assignment for [", activeInput->name, "] ..."}); - settings->panelList.setEnabled(false); - inputList.setEnabled(false); - clearButton.setEnabled(false); + settings->layout.setEnabled(false); + setEnabled(false); } void HotkeySettings::inputEvent(unsigned scancode, int16_t value) { using nall::Mouse; if(activeInput == nullptr) return; + if(value != 1) return; if(Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode)) return; if(Joypad::isAnyAxis(scancode)) return; if(activeInput->bind(scancode, value) == false) return; activeInput = nullptr; settings->setStatusText(""); - settings->panelList.setEnabled(true); - inputList.setEnabled(true); - clearButton.setEnabled(true); + settings->layout.setEnabled(true); + setEnabled(true); refresh(); } diff --git a/bsnes/target-ethos/settings/input.cpp b/bsnes/target-ethos/settings/input.cpp index 761cb024..75687f7b 100755 --- a/bsnes/target-ethos/settings/input.cpp +++ b/bsnes/target-ethos/settings/input.cpp @@ -3,11 +3,18 @@ InputSettings *inputSettings = nullptr; InputSettings::InputSettings() : activeInput(nullptr) { title.setFont(application->titleFont); title.setText("Input Settings"); + focusLabel.setText("When Focus is Lost:"); + focusPause.setText("Pause Emulation"); + focusAllow.setText("Allow Input"); inputList.setHeaderText("Name", "Mapping"); inputList.setHeaderVisible(); clearButton.setText("Clear"); 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); selectionLayout.append(systemList, {~0, 0}, 5); selectionLayout.append(portList, {~0, 0}, 5); @@ -24,6 +31,12 @@ InputSettings::InputSettings() : activeInput(nullptr) { 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}; portList.onChange = {&InputSettings::portChanged, this}; deviceList.onChange = {&InputSettings::deviceChanged, this}; @@ -43,11 +56,11 @@ void InputSettings::synchronize() { assign[1].setVisible(false); assign[2].setVisible(false); } else { - unsigned number = activeDevice().displayinput[inputList.selection()]; + unsigned number = activeDevice().order[inputList.selection()]; auto &input = activeDevice().input[number]; - activeInput = inputManager->inputMap[input.guid]; + auto selectedInput = inputManager->inputMap[input.guid]; - if(dynamic_cast(activeInput)) { + if(dynamic_cast(selectedInput)) { assign[0].setText("Mouse Left"); assign[1].setText("Mouse Middle"); assign[2].setText("Mouse Right"); @@ -56,7 +69,7 @@ void InputSettings::synchronize() { assign[2].setVisible(true); } - if(dynamic_cast(activeInput)) { + if(dynamic_cast(selectedInput)) { assign[0].setText("Mouse X-axis"); assign[1].setText("Mouse Y-axis"); assign[0].setVisible(true); @@ -98,10 +111,12 @@ void InputSettings::portChanged() { void InputSettings::deviceChanged() { inputList.reset(); - for(unsigned number : activeDevice().displayinput) { + for(unsigned number : activeDevice().order) { auto &input = activeDevice().input[number]; auto abstract = inputManager->inputMap(input.guid); string mapping = abstract->mapping; + mapping.replace("KB0::", ""); + mapping.replace("MS0::", "Mouse::"); mapping.replace(",", " or "); inputList.append(input.name, mapping); } @@ -109,31 +124,24 @@ void InputSettings::deviceChanged() { } void InputSettings::clearInput() { - unsigned number = activeDevice().displayinput[inputList.selection()]; + unsigned number = activeDevice().order[inputList.selection()]; auto &input = activeDevice().input[number]; activeInput = inputManager->inputMap[input.guid]; inputEvent(Scancode::None, 1); } void InputSettings::assignInput() { - unsigned number = activeDevice().displayinput[inputList.selection()]; + unsigned number = activeDevice().order[inputList.selection()]; auto &input = activeDevice().input[number]; activeInput = inputManager->inputMap[input.guid]; settings->setStatusText({"Set assignment for [", activeDevice().name, "::", input.name, "] ..."}); - settings->panelList.setEnabled(false); - systemList.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); + settings->layout.setEnabled(false); + setEnabled(false); } void InputSettings::assignMouseInput(unsigned n) { - unsigned number = activeDevice().displayinput[inputList.selection()]; + unsigned number = activeDevice().order[inputList.selection()]; auto &input = activeDevice().input[number]; activeInput = inputManager->inputMap[input.guid]; @@ -155,14 +163,7 @@ void InputSettings::inputEvent(unsigned scancode, int16_t value, bool allowMouse activeInput = nullptr; deviceChanged(); settings->setStatusText(""); - settings->panelList.setEnabled(true); - systemList.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); + settings->layout.setEnabled(true); + setEnabled(true); synchronize(); } diff --git a/bsnes/target-ethos/settings/input.hpp b/bsnes/target-ethos/settings/input.hpp index a67a65ea..e5fb593d 100755 --- a/bsnes/target-ethos/settings/input.hpp +++ b/bsnes/target-ethos/settings/input.hpp @@ -1,5 +1,9 @@ struct InputSettings : SettingsLayout { Label title; + HorizontalLayout focusLayout; + Label focusLabel; + CheckBox focusPause; + CheckBox focusAllow; HorizontalLayout selectionLayout; ComboBox systemList; ComboBox portList; diff --git a/bsnes/target-ethos/settings/settings.cpp b/bsnes/target-ethos/settings/settings.cpp index bd5e166b..5d0c2991 100755 --- a/bsnes/target-ethos/settings/settings.cpp +++ b/bsnes/target-ethos/settings/settings.cpp @@ -16,9 +16,10 @@ SettingsLayout::SettingsLayout() { } Settings::Settings() { - setTitle("Configuration Settings"); setGeometry({128, 128, 640, 360}); - setStatusFont(application->boldFont); + windowManager->append(this, "Settings"); + + setTitle("Configuration Settings"); setStatusVisible(); layout.setMargin(5); diff --git a/bsnes/target-ethos/settings/video.cpp b/bsnes/target-ethos/settings/video.cpp index 1ef0d2e5..73eedd67 100755 --- a/bsnes/target-ethos/settings/video.cpp +++ b/bsnes/target-ethos/settings/video.cpp @@ -1,8 +1,63 @@ VideoSettings *videoSettings = nullptr; +VideoSlider::VideoSlider() { + append(name, {75, 0}); + append(value, {75, 0}); + append(slider, {~0, 0}); +} + VideoSettings::VideoSettings() { title.setFont(application->titleFont); 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(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(); } diff --git a/bsnes/target-ethos/settings/video.hpp b/bsnes/target-ethos/settings/video.hpp index 18cd68e6..ccc30d33 100755 --- a/bsnes/target-ethos/settings/video.hpp +++ b/bsnes/target-ethos/settings/video.hpp @@ -1,6 +1,22 @@ +struct VideoSlider : HorizontalLayout { + Label name; + Label value; + HorizontalSlider slider; + + VideoSlider(); +}; + struct VideoSettings : SettingsLayout { Label title; + Label colorAdjustment; + VideoSlider saturation; + VideoSlider gamma; + VideoSlider luminance; + Label overscanAdjustment; + VideoSlider overscanHorizontal; + VideoSlider overscanVertical; + void synchronize(); VideoSettings(); }; diff --git a/bsnes/target-ethos/utility/utility.cpp b/bsnes/target-ethos/utility/utility.cpp index 0a6f8b97..403a8cc1 100755 --- a/bsnes/target-ethos/utility/utility.cpp +++ b/bsnes/target-ethos/utility/utility.cpp @@ -8,9 +8,8 @@ void Utility::setInterface(Emulator::Interface *emulator) { } void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) { - string pathname; - if(!schema.path.empty()) pathname = application->path(schema.path); - if(!directory::exists(pathname)) pathname = browser->select(schema.displayname, schema.filter); + string pathname = application->path({schema.name, ".", schema.extension, "/"}); + if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension); if(!directory::exists(pathname)) return; loadMedia(emulator, schema, pathname); @@ -23,7 +22,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi string manifest; 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); for(auto &memory : system().memory) { @@ -37,7 +36,7 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi string displayname = pathname; displayname.rtrim<1>("/"); presentation->setTitle(notdir(nall::basename(displayname))); - presentation->setSystemName(media.displayname); + presentation->setSystemName(media.name); 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() { if(application->active == nullptr) return; system().power(); @@ -68,6 +72,39 @@ void Utility::unload() { 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) { if(application->active == nullptr) return; Geometry geometry = presentation->geometry(); @@ -132,6 +169,32 @@ void Utility::toggleFullScreen() { resize(); } -void Utility::setStatusText(const string &text) { - presentation->setStatusText(text); +void Utility::updateStatus() { + 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; } diff --git a/bsnes/target-ethos/utility/utility.hpp b/bsnes/target-ethos/utility/utility.hpp index 64e09a6d..e3c5491c 100755 --- a/bsnes/target-ethos/utility/utility.hpp +++ b/bsnes/target-ethos/utility/utility.hpp @@ -6,13 +6,28 @@ struct Utility { void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname); void saveMedia(); + void connect(unsigned port, unsigned device); void power(); void reset(); void unload(); + void saveState(unsigned slot); + void loadState(unsigned slot); + + void synchronizeRuby(); void resize(bool resizeWindow = false); void toggleFullScreen(); + + void updateStatus(); void setStatusText(const string &text); + void showMessage(const string &message); + + Utility(); + +private: + string statusText; + string statusMessage; + time_t statusTime; }; extern Utility *utility; diff --git a/bsnes/target-ethos/window/window.cpp b/bsnes/target-ethos/window/window.cpp new file mode 100755 index 00000000..f663b056 --- /dev/null +++ b/bsnes/target-ethos/window/window.cpp @@ -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); + } +} diff --git a/bsnes/target-ethos/window/window.hpp b/bsnes/target-ethos/window/window.hpp new file mode 100755 index 00000000..90659a48 --- /dev/null +++ b/bsnes/target-ethos/window/window.hpp @@ -0,0 +1,18 @@ +struct WindowManager { + struct WindowList { + Window *window; + string name; + string geometry; + }; + vector windowList; + + void append(Window *window, const string &name); + void loadGeometry(); + void saveGeometry(); + void hideAll(); + +private: + configuration config; +}; + +extern WindowManager *windowManager;