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(); }