From a21ff570eecdcef336c1585ecc96552436ccbebf Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 16 Jun 2015 08:26:47 +1000 Subject: [PATCH] Update to v094r26 release (open beta). byuu says: Obviously, this is a fairly major WIP. It's the first public release in 17 months. The entire UI has been rewritten (for the 74th time), and is now internally called tomoko. The official releases will be named higan (both the binaries and title bar.) Missing features from v094: - ananke is missing (this means you will need v094 to create game folders to be loaded) - key assignments are limited to one physical button = one mapping (no multi-mapping) - shader support is missing - audio/video profiling is missing - DIP switch window is missing (used by NSS Actraiser with a special manifest; that's about it) - alternate paths for game system folders and configuration BML files There's some new stuff, but not much. This isn't going to be an exciting WIP in terms of features. It's more about being a brand new release with the brand new hiro port and its shared memory model. The goal is to get these WIPs stable, get v095 out, and then finally start improving the actual emulation again after that. --- emulator/emulator.hpp | 6 +- emulator/interface.hpp | 90 +++++++++---------- hiro/windows/layout.cpp | 9 ++ hiro/windows/window.cpp | 15 +++- hiro/windows/window.hpp | 1 + target-tomoko/configuration/configuration.cpp | 16 +++- target-tomoko/configuration/configuration.hpp | 15 +++- target-tomoko/presentation/presentation.cpp | 72 +++++++-------- target-tomoko/program/interface.cpp | 44 ++++++--- target-tomoko/program/program.cpp | 16 ++-- target-tomoko/program/program.hpp | 2 + target-tomoko/program/utility.cpp | 28 +++++- target-tomoko/settings/advanced.cpp | 16 ++-- target-tomoko/settings/audio.cpp | 71 +++++++++++++++ target-tomoko/settings/settings.hpp | 48 ++++++++++ target-tomoko/settings/timing.cpp | 12 +++ target-tomoko/settings/video.cpp | 30 +++++++ 17 files changed, 372 insertions(+), 119 deletions(-) diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 1c9d1022..38c23a05 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "094.25"; + static const char Version[] = "094.26"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; @@ -52,7 +52,7 @@ template struct hook; template struct hook { function callback; - R operator()(P... p) const { + auto operator()(P... p) const -> R { #if defined(DEBUGGER) if(callback) return callback(std::forward

(p)...); #endif @@ -67,7 +67,7 @@ template struct hook { template hook(R (C::*function)(P...) const, C* object) { callback = {function, object}; } template hook(const L& function) { callback = function; } - hook& operator=(const hook& hook) { callback = hook.callback; return *this; } + auto operator=(const hook& source) -> hook& { callback = source.callback; return *this; } }; #if defined(DEBUGGER) diff --git a/emulator/interface.hpp b/emulator/interface.hpp index 7c1ca515..0248eded 100644 --- a/emulator/interface.hpp +++ b/emulator/interface.hpp @@ -47,70 +47,70 @@ struct Interface { vector port; struct Bind { - virtual void loadRequest(unsigned, string, string) {} - virtual void loadRequest(unsigned, string) {} - virtual void saveRequest(unsigned, string) {} - virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) { return 0u; } - virtual void videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) {} - virtual void audioSample(int16_t, int16_t) {} - virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; } - virtual void inputRumble(unsigned, unsigned, unsigned, bool) {} - virtual unsigned dipSettings(const Markup::Node&) { return 0; } - virtual string path(unsigned) { return ""; } - virtual string server() { return ""; } - virtual void notify(string text) { print(text, "\n"); } + virtual auto loadRequest(unsigned, string, string) -> void {} + virtual auto loadRequest(unsigned, string) -> void {} + virtual auto saveRequest(unsigned, string) -> void {} + virtual auto videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) -> uint32_t { return 0u; } + virtual auto videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) -> void {} + virtual auto audioSample(int16_t, int16_t) -> void {} + virtual auto inputPoll(unsigned, unsigned, unsigned) -> int16_t { return 0; } + virtual auto inputRumble(unsigned, unsigned, unsigned, bool) -> void {} + virtual auto dipSettings(const Markup::Node&) -> unsigned { return 0; } + virtual auto path(unsigned) -> string { return ""; } + virtual auto server() -> string { return ""; } + virtual auto notify(string text) -> void { print(text, "\n"); } }; Bind* bind = nullptr; //callback bindings (provided by user interface) - void loadRequest(unsigned id, string name, string type) { return bind->loadRequest(id, name, type); } - void loadRequest(unsigned id, string path) { return bind->loadRequest(id, path); } - void saveRequest(unsigned id, string path) { return bind->saveRequest(id, path); } - uint32_t videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, alpha, red, green, blue); } - void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(palette, data, pitch, width, height); } - void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); } - int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); } - void inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { return bind->inputRumble(port, device, input, enable); } - unsigned dipSettings(const Markup::Node& node) { return bind->dipSettings(node); } - string path(unsigned group) { return bind->path(group); } - string server() { return bind->server(); } - template void notify(Args&&... args) { return bind->notify({std::forward(args)...}); } + auto loadRequest(unsigned id, string name, string type) -> void { return bind->loadRequest(id, name, type); } + auto loadRequest(unsigned id, string path) -> void { return bind->loadRequest(id, path); } + auto saveRequest(unsigned id, string path) -> void { return bind->saveRequest(id, path); } + auto videoColor(unsigned source, uint16_t alpha, uint16_t red, uint16_t green, uint16_t blue) -> uint32_t { return bind->videoColor(source, alpha, red, green, blue); } + auto videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) -> void { return bind->videoRefresh(palette, data, pitch, width, height); } + auto audioSample(int16_t lsample, int16_t rsample) -> void { return bind->audioSample(lsample, rsample); } + auto inputPoll(unsigned port, unsigned device, unsigned input) -> int16_t { return bind->inputPoll(port, device, input); } + auto inputRumble(unsigned port, unsigned device, unsigned input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); } + auto dipSettings(const Markup::Node& node) -> unsigned { return bind->dipSettings(node); } + auto path(unsigned group) -> string { return bind->path(group); } + auto server() -> string { return bind->server(); } + template auto notify(P&&... p) -> void { return bind->notify({forward

(p)...}); } //information - virtual string title() = 0; - virtual double videoFrequency() = 0; - virtual double audioFrequency() = 0; + virtual auto title() -> string = 0; + virtual auto videoFrequency() -> double = 0; + virtual auto audioFrequency() -> double = 0; //media interface - virtual bool loaded() { return false; } - virtual string sha256() { return ""; } - virtual unsigned group(unsigned id) = 0; - virtual void load(unsigned id) {} - virtual void save() {} - virtual void load(unsigned id, const stream& memory) {} - virtual void save(unsigned id, const stream& memory) {} - virtual void unload() {} + virtual auto loaded() -> bool { return false; } + virtual auto sha256() -> string { return ""; } + virtual auto group(unsigned id) -> unsigned = 0; + virtual auto load(unsigned id) -> void {} + virtual auto save() -> void {} + virtual auto load(unsigned id, const stream& memory) -> void {} + virtual auto save(unsigned id, const stream& memory) -> void {} + virtual auto unload() -> void {} //system interface - virtual void connect(unsigned port, unsigned device) {} - virtual void power() {} - virtual void reset() {} - virtual void run() {} + virtual auto connect(unsigned port, unsigned device) -> void {} + virtual auto power() -> void {} + virtual auto reset() -> void {} + virtual auto run() -> void {} //time functions - virtual bool rtc() { return false; } - virtual void rtcsync() {} + virtual auto rtc() -> bool { return false; } + virtual auto rtcsync() -> void {} //state functions - virtual serializer serialize() = 0; - virtual bool unserialize(serializer&) = 0; + virtual auto serialize() -> serializer = 0; + virtual auto unserialize(serializer&) -> bool = 0; //cheat functions - virtual void cheatSet(const lstring& = lstring{}) {} + virtual auto cheatSet(const lstring& = lstring{}) -> void {} //utility functions enum class PaletteMode : unsigned { Literal, Channel, Standard, Emulation }; - virtual void paletteUpdate(PaletteMode mode) {} + virtual auto paletteUpdate(PaletteMode mode) -> void {} }; } diff --git a/hiro/windows/layout.cpp b/hiro/windows/layout.cpp index dfa22c83..caec0182 100644 --- a/hiro/windows/layout.cpp +++ b/hiro/windows/layout.cpp @@ -9,12 +9,21 @@ auto pLayout::destruct() -> void { } auto pLayout::setEnabled(bool enabled) -> void { + for(auto& sizable : state().sizables) { + if(auto self = sizable->self()) self->setEnabled(sizable->enabled(true)); + } } auto pLayout::setFont(const string& font) -> void { + for(auto& sizable : state().sizables) { + if(auto self = sizable->self()) self->setFont(sizable->font(true)); + } } auto pLayout::setVisible(bool visible) -> void { + for(auto& sizable : state().sizables) { + if(auto self = sizable->self()) self->setVisible(sizable->visible(true)); + } } } diff --git a/hiro/windows/window.cpp b/hiro/windows/window.cpp index 4ecd7893..7ffa206b 100644 --- a/hiro/windows/window.cpp +++ b/hiro/windows/window.cpp @@ -72,6 +72,9 @@ auto pWindow::setDroppable(bool droppable) -> void { } auto pWindow::setEnabled(bool enabled) -> void { + if(auto layout = state().layout) { + if(auto self = layout->self()) self->setEnabled(layout->enabled(true)); + } } auto pWindow::setFocused() -> void { @@ -79,6 +82,12 @@ auto pWindow::setFocused() -> void { SetFocus(hwnd); } +auto pWindow::setFont(const string& font) -> void { + if(auto layout = state().layout) { + if(auto self = layout->self()) self->setFont(layout->font(true)); + } +} + auto pWindow::setFullScreen(bool fullScreen) -> void { lock(); if(fullScreen == false) { @@ -138,7 +147,7 @@ auto pWindow::setModal(bool modality) -> void { } auto pWindow::setResizable(bool resizable) -> void { - SetWindowLongPtr(hwnd, GWL_STYLE, state().resizable ? ResizableStyle : FixedStyle); + SetWindowLongPtr(hwnd, GWL_STYLE, WS_VISIBLE | (state().resizable ? ResizableStyle : FixedStyle)); setGeometry(state().geometry); } @@ -149,6 +158,10 @@ auto pWindow::setTitle(string text) -> void { auto pWindow::setVisible(bool visible) -> void { ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); if(!visible) setModal(false); + + if(auto layout = state().layout) { + if(auto self = layout->self()) self->setVisible(layout->visible(true)); + } } // diff --git a/hiro/windows/window.hpp b/hiro/windows/window.hpp index ad91a34b..bdd83fc7 100644 --- a/hiro/windows/window.hpp +++ b/hiro/windows/window.hpp @@ -17,6 +17,7 @@ struct pWindow : pObject { auto setDroppable(bool droppable) -> void; auto setEnabled(bool enabled) -> void; auto setFocused() -> void; + auto setFont(const string& font) -> void override; auto setFullScreen(bool fullScreen) -> void; auto setGeometry(Geometry geometry) -> void; auto setModal(bool modal) -> void; diff --git a/target-tomoko/configuration/configuration.cpp b/target-tomoko/configuration/configuration.cpp index 781d564b..6135cfc0 100644 --- a/target-tomoko/configuration/configuration.cpp +++ b/target-tomoko/configuration/configuration.cpp @@ -1,9 +1,8 @@ #include "../tomoko.hpp" -ConfigurationManager* configurationManager = nullptr; -auto config() -> ConfigurationManager& { return *configurationManager; } +ConfigurationManager* config = nullptr; ConfigurationManager::ConfigurationManager() { - configurationManager = this; + config = this; userInterface.append(userInterface.showStatusBar, "ShowStatusBar"); append(userInterface, "UserInterface"); @@ -17,6 +16,9 @@ ConfigurationManager::ConfigurationManager() { video.append(video.aspectCorrection, "AspectCorrection"); video.append(video.filter, "Filter"); video.append(video.colorEmulation, "ColorEmulation"); + video.append(video.saturation, "Saturation"); + video.append(video.gamma, "Gamma"); + video.append(video.luminance, "Luminance"); video.overscan.append(video.overscan.mask, "Mask"); video.overscan.append(video.overscan.horizontal, "Horizontal"); video.overscan.append(video.overscan.vertical, "Vertical"); @@ -27,11 +29,19 @@ ConfigurationManager::ConfigurationManager() { audio.append(audio.device, "Device"); audio.append(audio.synchronize, "Synchronize"); audio.append(audio.mute, "Mute"); + audio.append(audio.volume, "Volume"); + audio.append(audio.frequency, "Frequency"); + audio.append(audio.latency, "Latency"); + audio.append(audio.resampler, "Resampler"); append(audio, "Audio"); input.append(input.driver, "Driver"); append(input, "Input"); + timing.append(timing.video, "Video"); + timing.append(timing.audio, "Audio"); + append(timing, "Timing"); + load({configpath(), "tomoko/settings.bml"}); if(!library.location) library.location = {userpath(), "Emulation/"}; if(!video.driver) video.driver = ruby::video.safestDriver(); diff --git a/target-tomoko/configuration/configuration.hpp b/target-tomoko/configuration/configuration.hpp index ca1f5180..9a1c66c5 100644 --- a/target-tomoko/configuration/configuration.hpp +++ b/target-tomoko/configuration/configuration.hpp @@ -17,6 +17,9 @@ struct ConfigurationManager : Configuration::Document { bool aspectCorrection = true; string filter = "Blur"; bool colorEmulation = true; + unsigned saturation = 100; + unsigned gamma = 100; + unsigned luminance = 100; struct Overscan : Configuration::Node { bool mask = false; @@ -30,12 +33,20 @@ struct ConfigurationManager : Configuration::Document { string device; bool synchronize = true; bool mute = false; + unsigned volume = 100; + unsigned frequency = 48000; + unsigned latency = 60; + string resampler = "Sinc"; } audio; struct Input : Configuration::Node { string driver; } input; + + struct Timing : Configuration::Node { + double video = 60.0; + double audio = 48000.0; + } timing; }; -extern ConfigurationManager* configurationManager; -auto config() -> ConfigurationManager&; +extern ConfigurationManager* config; diff --git a/target-tomoko/presentation/presentation.cpp b/target-tomoko/presentation/presentation.cpp index 36ef6b3c..4126fba8 100644 --- a/target-tomoko/presentation/presentation.cpp +++ b/target-tomoko/presentation/presentation.cpp @@ -10,10 +10,10 @@ Presentation::Presentation() { if(!media.bootable) continue; auto item = new MenuItem{&libraryMenu}; item->setText({media.name, " ..."}).onActivate([=] { - directory::create({config().library.location, media.name}); + directory::create({config->library.location, media.name}); auto location = BrowserDialog() .setTitle({"Load ", media.name}) - .setPath({config().library.location, media.name}) + .setPath({config->library.location, media.name}) .setFilters(string{media.name, "|*.", media.type}) .openFolder(); if(directory::exists(location)) { @@ -31,52 +31,52 @@ Presentation::Presentation() { settingsMenu.setText("Settings"); videoScaleMenu.setText("Video Scale"); - if(config().video.scale == "Small") videoScaleSmall.setChecked(); - if(config().video.scale == "Normal") videoScaleNormal.setChecked(); - if(config().video.scale == "Large") videoScaleLarge.setChecked(); + if(config->video.scale == "Small") videoScaleSmall.setChecked(); + if(config->video.scale == "Normal") videoScaleNormal.setChecked(); + if(config->video.scale == "Large") videoScaleLarge.setChecked(); videoScaleSmall.setText("Small").onActivate([&] { - config().video.scale = "Small"; + config->video.scale = "Small"; resizeViewport(); }); videoScaleNormal.setText("Normal").onActivate([&] { - config().video.scale = "Normal"; + config->video.scale = "Normal"; resizeViewport(); }); videoScaleLarge.setText("Large").onActivate([&] { - config().video.scale = "Large"; + config->video.scale = "Large"; resizeViewport(); }); - aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] { - config().video.aspectCorrection = aspectCorrection.checked(); + aspectCorrection.setText("Aspect Correction").setChecked(config->video.aspectCorrection).onToggle([&] { + config->video.aspectCorrection = aspectCorrection.checked(); resizeViewport(); }); videoFilterMenu.setText("Video Filter"); - if(config().video.filter == "None") videoFilterNone.setChecked(); - if(config().video.filter == "Blur") videoFilterBlur.setChecked(); - videoFilterNone.setText("None").onActivate([&] { config().video.filter = "None"; program->updateVideoFilter(); }); - videoFilterBlur.setText("Blur").onActivate([&] { config().video.filter = "Blur"; program->updateVideoFilter(); }); - colorEmulation.setText("Color Emulation").setChecked(config().video.colorEmulation).onToggle([&] { - config().video.colorEmulation = colorEmulation.checked(); + if(config->video.filter == "None") videoFilterNone.setChecked(); + if(config->video.filter == "Blur") videoFilterBlur.setChecked(); + videoFilterNone.setText("None").onActivate([&] { config->video.filter = "None"; program->updateVideoFilter(); }); + videoFilterBlur.setText("Blur").onActivate([&] { config->video.filter = "Blur"; program->updateVideoFilter(); }); + colorEmulation.setText("Color Emulation").setChecked(config->video.colorEmulation).onToggle([&] { + config->video.colorEmulation = colorEmulation.checked(); program->updateVideoPalette(); }); - maskOverscan.setText("Mask Overscan").setChecked(config().video.overscan.mask).onToggle([&] { - config().video.overscan.mask = maskOverscan.checked(); + maskOverscan.setText("Mask Overscan").setChecked(config->video.overscan.mask).onToggle([&] { + config->video.overscan.mask = maskOverscan.checked(); }); - synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] { - config().video.synchronize = synchronizeVideo.checked(); - video.set(Video::Synchronize, config().video.synchronize); + synchronizeVideo.setText("Synchronize Video").setChecked(config->video.synchronize).onToggle([&] { + config->video.synchronize = synchronizeVideo.checked(); + video.set(Video::Synchronize, config->video.synchronize); }); - synchronizeAudio.setText("Synchronize Audio").setChecked(config().audio.synchronize).onToggle([&] { - config().audio.synchronize = synchronizeAudio.checked(); - audio.set(Audio::Synchronize, config().audio.synchronize); + synchronizeAudio.setText("Synchronize Audio").setChecked(config->audio.synchronize).onToggle([&] { + config->audio.synchronize = synchronizeAudio.checked(); + audio.set(Audio::Synchronize, config->audio.synchronize); }); - muteAudio.setText("Mute Audio").setChecked(config().audio.mute).onToggle([&] { - config().audio.mute = muteAudio.checked(); - program->dsp.setVolume(config().audio.mute ? 0.0 : 1.0); + muteAudio.setText("Mute Audio").setChecked(config->audio.mute).onToggle([&] { + config->audio.mute = muteAudio.checked(); + program->dsp.setVolume(config->audio.mute ? 0.0 : 1.0); }); - showStatusBar.setText("Show Status Bar").setChecked(config().userInterface.showStatusBar).onToggle([&] { - config().userInterface.showStatusBar = showStatusBar.checked(); - statusBar.setVisible(config().userInterface.showStatusBar); + showStatusBar.setText("Show Status Bar").setChecked(config->userInterface.showStatusBar).onToggle([&] { + config->userInterface.showStatusBar = showStatusBar.checked(); + statusBar.setVisible(config->userInterface.showStatusBar); if(visible()) resizeViewport(); }); showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(2); }); @@ -98,7 +98,7 @@ Presentation::Presentation() { stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); }); statusBar.setFont(Font::sans(8, "Bold")); - statusBar.setVisible(config().userInterface.showStatusBar); + statusBar.setVisible(config->userInterface.showStatusBar); onClose([&] { program->quit(); }); @@ -136,9 +136,9 @@ auto Presentation::updateEmulator() -> void { auto Presentation::resizeViewport() -> void { signed scale = 1; - if(config().video.scale == "Small" ) scale = 1; - if(config().video.scale == "Normal") scale = 2; - if(config().video.scale == "Large" ) scale = 4; + if(config->video.scale == "Small" ) scale = 1; + if(config->video.scale == "Normal") scale = 2; + if(config->video.scale == "Large" ) scale = 4; signed width = 256; signed height = 240; @@ -147,7 +147,7 @@ auto Presentation::resizeViewport() -> void { height = emulator->information.height; } - bool arc = config().video.aspectCorrection; + bool arc = config->video.aspectCorrection; if(fullScreen() == false) { signed windowWidth = 256 * scale; @@ -200,7 +200,7 @@ auto Presentation::toggleFullScreen() -> void { setFullScreen(false); setResizable(false); menuBar.setVisible(true); - statusBar.setVisible(config().userInterface.showStatusBar); + statusBar.setVisible(config->userInterface.showStatusBar); } Application::processEvents(); diff --git a/target-tomoko/program/interface.cpp b/target-tomoko/program/interface.cpp index f557a7c8..c1e0356a 100644 --- a/target-tomoko/program/interface.cpp +++ b/target-tomoko/program/interface.cpp @@ -2,7 +2,7 @@ auto Program::loadRequest(unsigned id, string name, string type) -> void { string location = BrowserDialog() .setTitle({"Load ", name}) - .setPath({config().library.location, name}) + .setPath({config->library.location, name}) .setFilters({string{name, "|*.", type}}) .openFolder(); if(!directory::exists(location)) return; @@ -27,12 +27,36 @@ auto Program::saveRequest(unsigned id, string path) -> void { return emulator->save(id, stream); } -auto Program::videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 { - alpha >>= 8; - red >>= 8; - green >>= 8; - blue >>= 8; - return alpha << 24 | red << 16 | green << 8 | blue << 0; +auto Program::videoColor(unsigned source, uint16 a, uint16 r, uint16 g, uint16 b) -> uint32 { + if(config->video.saturation != 100) { + uint16 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; + } + + a >>= 8; + r >>= 8; + g >>= 8; + b >>= 8; + return a << 24 | r << 16 | g << 8 | b << 0; } auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned pitch, unsigned width, unsigned height) -> void { @@ -50,9 +74,9 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned p } } - if(emulator->information.overscan && config().video.overscan.mask) { - unsigned h = config().video.overscan.horizontal; - unsigned v = config().video.overscan.vertical; + if(emulator->information.overscan && config->video.overscan.mask) { + unsigned h = config->video.overscan.horizontal; + unsigned v = config->video.overscan.vertical; if(h) for(auto y : range(height)) { memory::fill(output + y * length, 4 * h); diff --git a/target-tomoko/program/program.cpp b/target-tomoko/program/program.cpp index 9bf69aac..6c9167d0 100644 --- a/target-tomoko/program/program.cpp +++ b/target-tomoko/program/program.cpp @@ -29,26 +29,26 @@ Program::Program() { presentation->setVisible(); - video.driver(config().video.driver); + video.driver(config->video.driver); video.set(Video::Handle, presentation->viewport.handle()); - video.set(Video::Synchronize, config().video.synchronize); + video.set(Video::Synchronize, config->video.synchronize); if(!video.init()) { video.driver("None"); video.init(); } - audio.driver(config().audio.driver); - audio.set(Audio::Device, config().audio.device); + audio.driver(config->audio.driver); + audio.set(Audio::Device, config->audio.device); audio.set(Audio::Handle, presentation->viewport.handle()); - audio.set(Audio::Synchronize, config().audio.synchronize); + audio.set(Audio::Synchronize, config->audio.synchronize); audio.set(Audio::Frequency, 96000u); audio.set(Audio::Latency, 80u); if(!audio.init()) { audio.driver("None"); audio.init(); } - input.driver(config().input.driver); + input.driver(config->input.driver); input.set(Input::Handle, presentation->viewport.handle()); if(!input.init()) { input.driver("None"); input.init(); } dsp.setPrecision(16); dsp.setBalance(0.0); - dsp.setVolume(config().audio.mute ? 0.0 : 1.0); + dsp.setVolume(config->audio.mute ? 0.0 : 1.0); dsp.setFrequency(32040); dsp.setResampler(DSP::ResampleEngine::Sinc); dsp.setResamplerFrequency(96000); @@ -73,7 +73,7 @@ auto Program::main() -> void { auto Program::quit() -> void { unloadMedia(); - configurationManager->quit(); + config->quit(); inputManager->quit(); video.term(); audio.term(); diff --git a/target-tomoko/program/program.hpp b/target-tomoko/program/program.hpp index bd53ecf1..65091dd0 100644 --- a/target-tomoko/program/program.hpp +++ b/target-tomoko/program/program.hpp @@ -34,6 +34,8 @@ struct Program : Emulator::Interface::Bind { auto updateStatusText() -> void; auto updateVideoFilter() -> void; auto updateVideoPalette() -> void; + auto updateAudio() -> void; + auto updateDSP() -> void; DSP dsp; bool pause = false; diff --git a/target-tomoko/program/utility.cpp b/target-tomoko/program/utility.cpp index d21a6d21..7ca07306 100644 --- a/target-tomoko/program/utility.cpp +++ b/target-tomoko/program/utility.cpp @@ -36,14 +36,36 @@ auto Program::updateStatusText() -> void { } auto Program::updateVideoFilter() -> void { - if(config().video.filter == "None") video.set(Video::Filter, Video::FilterNearest); - if(config().video.filter == "Blur") video.set(Video::Filter, Video::FilterLinear); + if(config->video.filter == "None") video.set(Video::Filter, Video::FilterNearest); + if(config->video.filter == "Blur") video.set(Video::Filter, Video::FilterLinear); } auto Program::updateVideoPalette() -> void { if(!emulator) return; - emulator->paletteUpdate(config().video.colorEmulation + emulator->paletteUpdate(config->video.colorEmulation ? Emulator::Interface::PaletteMode::Emulation : Emulator::Interface::PaletteMode::Standard ); } + +auto Program::updateAudio() -> void { + audio.set(Audio::Frequency, config->audio.frequency); + audio.set(Audio::Latency, config->audio.latency); + if(auto resampler = config->audio.resampler) { + if(resampler == "Linear" ) dsp.setResampler(DSP::ResampleEngine::Linear); + if(resampler == "Hermite") dsp.setResampler(DSP::ResampleEngine::Hermite); + if(resampler == "Sinc" ) dsp.setResampler(DSP::ResampleEngine::Sinc); + } + dsp.setResamplerFrequency(config->audio.frequency); + dsp.setVolume(config->audio.mute ? 0.0 : config->audio.volume * 0.01); + updateDSP(); +} + +auto Program::updateDSP() -> void { + if(!emulator) return; + if(!config->video.synchronize) return dsp.setFrequency(emulator->audioFrequency()); + + double inputRatio = emulator->audioFrequency() / emulator->videoFrequency(); + double outputRatio = config->timing.audio / config->timing.video; + dsp.setFrequency(inputRatio / outputRatio * config->audio.frequency); +} diff --git a/target-tomoko/settings/advanced.cpp b/target-tomoko/settings/advanced.cpp index 8d36533c..a967a4de 100644 --- a/target-tomoko/settings/advanced.cpp +++ b/target-tomoko/settings/advanced.cpp @@ -6,36 +6,36 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold")); videoLabel.setText("Video:"); - videoDriver.onChange([&] { config().video.driver = videoDriver.selected()->text(); }); + videoDriver.onChange([&] { config->video.driver = videoDriver.selected()->text(); }); for(auto& driver : string{video.availableDrivers()}.split(";")) { ComboButtonItem item; item.setText(driver); videoDriver.append(item); - if(config().video.driver == driver) item.setSelected(); + if(config->video.driver == driver) item.setSelected(); } audioLabel.setText("Audio:"); - audioDriver.onChange([&] { config().audio.driver = audioDriver.selected()->text(); }); + audioDriver.onChange([&] { config->audio.driver = audioDriver.selected()->text(); }); for(auto& driver : string{audio.availableDrivers()}.split(";")) { ComboButtonItem item; item.setText(driver); audioDriver.append(item); - if(config().audio.driver == driver) item.setSelected(); + if(config->audio.driver == driver) item.setSelected(); } inputLabel.setText("Input:"); - inputDriver.onChange([&] { config().input.driver = inputDriver.selected()->text(); }); + inputDriver.onChange([&] { config->input.driver = inputDriver.selected()->text(); }); for(auto& driver : string{input.availableDrivers()}.split(";")) { ComboButtonItem item; item.setText(driver); inputDriver.append(item); - if(config().input.driver == driver) item.setSelected(); + if(config->input.driver == driver) item.setSelected(); } libraryLabel.setText("Game Library").setFont(Font::sans(8, "Bold")); libraryPrefix.setText("Location:"); - libraryLocation.setEditable(false).setText(config().library.location); + libraryLocation.setEditable(false).setText(config->library.location); libraryChange.setText("Change ...").onActivate([&] { if(auto location = BrowserDialog().setTitle("Select Library Location").selectFolder()) { - libraryLocation.setText(config().library.location = location); + libraryLocation.setText(config->library.location = location); } }); } diff --git a/target-tomoko/settings/audio.cpp b/target-tomoko/settings/audio.cpp index 5cfa4185..fcb4989b 100644 --- a/target-tomoko/settings/audio.cpp +++ b/target-tomoko/settings/audio.cpp @@ -3,4 +3,75 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Audio"); layout.setMargin(5); + + frequencyLabel.setText("Frequency:"); + frequencyCombo.append(ComboButtonItem().setText("32000hz")); + frequencyCombo.append(ComboButtonItem().setText("44100hz")); + frequencyCombo.append(ComboButtonItem().setText("48000hz")); + frequencyCombo.append(ComboButtonItem().setText("96000hz")); + switch(config->audio.frequency) { + case 32000: frequencyCombo.item(0)->setSelected(); break; + case 44100: frequencyCombo.item(1)->setSelected(); break; + case 48000: frequencyCombo.item(2)->setSelected(); break; + case 96000: frequencyCombo.item(3)->setSelected(); break; + } + frequencyCombo.onChange([&] { update(); }); + + latencyLabel.setText("Latency:"); + latencyCombo.append(ComboButtonItem().setText("20ms")); + latencyCombo.append(ComboButtonItem().setText("40ms")); + latencyCombo.append(ComboButtonItem().setText("60ms")); + latencyCombo.append(ComboButtonItem().setText("80ms")); + latencyCombo.append(ComboButtonItem().setText("100ms")); + switch(config->audio.latency) { + case 20: latencyCombo.item(0)->setSelected(); break; + case 40: latencyCombo.item(1)->setSelected(); break; + case 60: latencyCombo.item(2)->setSelected(); break; + case 80: latencyCombo.item(3)->setSelected(); break; + case 100: latencyCombo.item(4)->setSelected(); break; + } + latencyCombo.onChange([&] { update(); }); + + resamplerLabel.setText("Resampler:"); + resamplerCombo.append(ComboButtonItem().setText("Linear")); + resamplerCombo.append(ComboButtonItem().setText("Hermite")); + resamplerCombo.append(ComboButtonItem().setText("Sinc")); + if(config->audio.resampler == "Linear" ) resamplerCombo.item(0)->setSelected(); + if(config->audio.resampler == "Hermite") resamplerCombo.item(1)->setSelected(); + if(config->audio.resampler == "Sinc" ) resamplerCombo.item(2)->setSelected(); + resamplerCombo.onChange([&] { update(); }); + + volumeLabel.setText("Volume:"); + volumeSlider.setLength(201).setPosition(config->audio.volume).onChange([&] { updateVolume(); }); + + update(); +} + +auto AudioSettings::update() -> void { + if(auto item = frequencyCombo.selected()) { + if(item->offset() == 0) config->audio.frequency = 32000; + if(item->offset() == 1) config->audio.frequency = 44100; + if(item->offset() == 2) config->audio.frequency = 48000; + if(item->offset() == 3) config->audio.frequency = 96000; + } + if(auto item = latencyCombo.selected()) { + if(item->offset() == 0) config->audio.latency = 20; + if(item->offset() == 1) config->audio.latency = 40; + if(item->offset() == 2) config->audio.latency = 60; + if(item->offset() == 3) config->audio.latency = 80; + if(item->offset() == 4) config->audio.latency = 100; + } + if(auto item = resamplerCombo.selected()) { + if(item->offset() == 0) config->audio.resampler = "Linear"; + if(item->offset() == 1) config->audio.resampler = "Hermite"; + if(item->offset() == 2) config->audio.resampler = "Sinc"; + } + updateVolume(); + program->updateAudio(); +} + +auto AudioSettings::updateVolume() -> void { + config->audio.volume = volumeSlider.position(); + volumeValue.setText({config->audio.volume, "%"}); + program->dsp.setVolume(config->audio.mute ? 0.0 : config->audio.volume * 0.01); } diff --git a/target-tomoko/settings/settings.hpp b/target-tomoko/settings/settings.hpp index 4e8b738c..3e421692 100644 --- a/target-tomoko/settings/settings.hpp +++ b/target-tomoko/settings/settings.hpp @@ -2,12 +2,50 @@ struct VideoSettings : TabFrameItem { VideoSettings(TabFrame*); VerticalLayout layout{this}; + Label colorAdjustmentLabel{&layout, Size{~0, 0}}; + HorizontalLayout saturationLayout{&layout, Size{~0, 0}}; + Label saturationLabel{&saturationLayout, Size{80, 0}}; + Label saturationValue{&saturationLayout, Size{80, 0}}; + HorizontalSlider saturationSlider{&saturationLayout, Size{~0, 0}}; + HorizontalLayout gammaLayout{&layout, Size{~0, 0}}; + Label gammaLabel{&gammaLayout, Size{80, 0}}; + Label gammaValue{&gammaLayout, Size{80, 0}}; + HorizontalSlider gammaSlider{&gammaLayout, Size{~0, 0}}; + HorizontalLayout luminanceLayout{&layout, Size{~0, 0}}; + Label luminanceLabel{&luminanceLayout, Size{80, 0}}; + Label luminanceValue{&luminanceLayout, Size{80, 0}}; + HorizontalSlider luminanceSlider{&luminanceLayout, Size{~0, 0}}; + Label overscanMaskLabel{&layout, Size{~0, 0}}; + HorizontalLayout horizontalMaskLayout{&layout, Size{~0, 0}}; + Label horizontalMaskLabel{&horizontalMaskLayout, Size{80, 0}}; + Label horizontalMaskValue{&horizontalMaskLayout, Size{80, 0}}; + HorizontalSlider horizontalMaskSlider{&horizontalMaskLayout, Size{~0, 0}}; + HorizontalLayout verticalMaskLayout{&layout, Size{~0, 0}}; + Label verticalMaskLabel{&verticalMaskLayout, Size{80, 0}}; + Label verticalMaskValue{&verticalMaskLayout, Size{80, 0}}; + HorizontalSlider verticalMaskSlider{&verticalMaskLayout, Size{~0, 0}}; + + auto update() -> void; }; struct AudioSettings : TabFrameItem { AudioSettings(TabFrame*); VerticalLayout layout{this}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Label frequencyLabel{&controlLayout, Size{0, 0}}; + ComboButton frequencyCombo{&controlLayout, Size{~0, 0}}; + Label latencyLabel{&controlLayout, Size{0, 0}}; + ComboButton latencyCombo{&controlLayout, Size{~0, 0}}; + Label resamplerLabel{&controlLayout, Size{0, 0}}; + ComboButton resamplerCombo{&controlLayout, Size{~0, 0}}; + HorizontalLayout volumeLayout{&layout, Size{~0, 0}}; + Label volumeLabel{&volumeLayout, Size{80, 0}}; + Label volumeValue{&volumeLayout, Size{80, 0}}; + HorizontalSlider volumeSlider{&volumeLayout, Size{~0, 0}}; + + auto update() -> void; + auto updateVolume() -> void; }; struct InputSettings : TabFrameItem { @@ -62,6 +100,16 @@ struct TimingSettings : TabFrameItem { TimingSettings(TabFrame*); VerticalLayout layout{this}; + HorizontalLayout videoLayout{&layout, Size{~0, 0}}; + Label videoLabel{&videoLayout, Size{40, 0}}; + LineEdit videoValue{&videoLayout, Size{80, 0}}; + Button videoAssign{&videoLayout, Size{80, 0}}; + HorizontalLayout audioLayout{&layout, Size{~0, 0}}; + Label audioLabel{&audioLayout, Size{40, 0}}; + LineEdit audioValue{&audioLayout, Size{80, 0}}; + Button audioAssign{&audioLayout, Size{80, 0}}; + + auto update() -> void; }; struct AdvancedSettings : TabFrameItem { diff --git a/target-tomoko/settings/timing.cpp b/target-tomoko/settings/timing.cpp index 0222a664..b46c49b1 100644 --- a/target-tomoko/settings/timing.cpp +++ b/target-tomoko/settings/timing.cpp @@ -3,4 +3,16 @@ TimingSettings::TimingSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Timing"); layout.setMargin(5); + videoLabel.setText("Video:"); + videoValue.setText(config->timing.video).onActivate([&] { update(); }); + videoAssign.setText("Assign").onActivate([&] { update(); }); + audioLabel.setText("Audio:"); + audioValue.setText(config->timing.audio).onActivate([&] { update(); }); + audioAssign.setText("Assign").onActivate([&] { update(); }); +} + +auto TimingSettings::update() -> void { + config->timing.video = real(videoValue.text()); + config->timing.audio = real(audioValue.text()); + program->updateDSP(); } diff --git a/target-tomoko/settings/video.cpp b/target-tomoko/settings/video.cpp index 0dafdb7d..13491a00 100644 --- a/target-tomoko/settings/video.cpp +++ b/target-tomoko/settings/video.cpp @@ -3,4 +3,34 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Video"); layout.setMargin(5); + + colorAdjustmentLabel.setFont(Font::sans(8, "Bold")).setText("Color Adjustment"); + saturationLabel.setText("Saturation:"); + saturationSlider.setLength(201).setPosition(config->video.saturation).onChange([&] { update(); }); + gammaLabel.setText("Gamma:"); + gammaSlider.setLength(101).setPosition(config->video.gamma - 100).onChange([&] { update(); }); + luminanceLabel.setText("Luminance:"); + luminanceSlider.setLength(101).setPosition(config->video.luminance).onChange([&] { update(); }); + + overscanMaskLabel.setFont(Font::sans(8, "Bold")).setText("Overscan Mask"); + horizontalMaskLabel.setText("Horizontal:"); + horizontalMaskSlider.setLength(17).setPosition(config->video.overscan.horizontal).onChange([&] { update(); }); + verticalMaskLabel.setText("Vertical:"); + verticalMaskSlider.setLength(17).setPosition(config->video.overscan.vertical).onChange([&] { update(); }); + + update(); +} + +auto VideoSettings::update() -> void { + config->video.saturation = saturationSlider.position(); + config->video.gamma = 100 + gammaSlider.position(); + config->video.luminance = luminanceSlider.position(); + config->video.overscan.horizontal = horizontalMaskSlider.position(); + config->video.overscan.vertical = verticalMaskSlider.position(); + saturationValue.setText({config->video.saturation, "%"}); + gammaValue.setText({config->video.gamma, "%"}); + luminanceValue.setText({config->video.luminance, "%"}); + horizontalMaskValue.setText({config->video.overscan.horizontal, "px"}); + verticalMaskValue.setText({config->video.overscan.vertical, "px"}); + program->updateVideoPalette(); }