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;