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.
This commit is contained in:
Tim Allen 2015-06-16 08:26:47 +10:00
parent bb3c69a30d
commit a21ff570ee
17 changed files with 372 additions and 119 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator { namespace Emulator {
static const char Name[] = "higan"; 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 Author[] = "byuu";
static const char License[] = "GPLv3"; static const char License[] = "GPLv3";
static const char Website[] = "http://byuu.org/"; static const char Website[] = "http://byuu.org/";
@ -52,7 +52,7 @@ template<typename T> struct hook;
template<typename R, typename... P> struct hook<R (P...)> { template<typename R, typename... P> struct hook<R (P...)> {
function<R (P...)> callback; function<R (P...)> callback;
R operator()(P... p) const { auto operator()(P... p) const -> R {
#if defined(DEBUGGER) #if defined(DEBUGGER)
if(callback) return callback(std::forward<P>(p)...); if(callback) return callback(std::forward<P>(p)...);
#endif #endif
@ -67,7 +67,7 @@ template<typename R, typename... P> struct hook<R (P...)> {
template<typename C> hook(R (C::*function)(P...) const, C* object) { callback = {function, object}; } template<typename C> hook(R (C::*function)(P...) const, C* object) { callback = {function, object}; }
template<typename L> hook(const L& function) { callback = function; } template<typename L> 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) #if defined(DEBUGGER)

View File

@ -47,70 +47,70 @@ struct Interface {
vector<Port> port; vector<Port> port;
struct Bind { struct Bind {
virtual void loadRequest(unsigned, string, string) {} virtual auto loadRequest(unsigned, string, string) -> void {}
virtual void loadRequest(unsigned, string) {} virtual auto loadRequest(unsigned, string) -> void {}
virtual void saveRequest(unsigned, string) {} virtual auto saveRequest(unsigned, string) -> void {}
virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) { return 0u; } virtual auto videoColor(unsigned, uint16_t, uint16_t, uint16_t, uint16_t) -> uint32_t { return 0u; }
virtual void videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) {} virtual auto videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) -> void {}
virtual void audioSample(int16_t, int16_t) {} virtual auto audioSample(int16_t, int16_t) -> void {}
virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; } virtual auto inputPoll(unsigned, unsigned, unsigned) -> int16_t { return 0; }
virtual void inputRumble(unsigned, unsigned, unsigned, bool) {} virtual auto inputRumble(unsigned, unsigned, unsigned, bool) -> void {}
virtual unsigned dipSettings(const Markup::Node&) { return 0; } virtual auto dipSettings(const Markup::Node&) -> unsigned { return 0; }
virtual string path(unsigned) { return ""; } virtual auto path(unsigned) -> string { return ""; }
virtual string server() { return ""; } virtual auto server() -> string { return ""; }
virtual void notify(string text) { print(text, "\n"); } virtual auto notify(string text) -> void { print(text, "\n"); }
}; };
Bind* bind = nullptr; Bind* bind = nullptr;
//callback bindings (provided by user interface) //callback bindings (provided by user interface)
void loadRequest(unsigned id, string name, string type) { return bind->loadRequest(id, name, type); } auto loadRequest(unsigned id, string name, string type) -> void { return bind->loadRequest(id, name, type); }
void loadRequest(unsigned id, string path) { return bind->loadRequest(id, path); } auto loadRequest(unsigned id, string path) -> void { return bind->loadRequest(id, path); }
void saveRequest(unsigned id, string path) { return bind->saveRequest(id, path); } auto saveRequest(unsigned id, string path) -> void { 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); } 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); }
void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(palette, data, pitch, width, height); } 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); }
void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); } auto audioSample(int16_t lsample, int16_t rsample) -> void { return bind->audioSample(lsample, rsample); }
int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); } auto inputPoll(unsigned port, unsigned device, unsigned input) -> int16_t { return bind->inputPoll(port, device, input); }
void inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { return bind->inputRumble(port, device, input, enable); } auto inputRumble(unsigned port, unsigned device, unsigned input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
unsigned dipSettings(const Markup::Node& node) { return bind->dipSettings(node); } auto dipSettings(const Markup::Node& node) -> unsigned { return bind->dipSettings(node); }
string path(unsigned group) { return bind->path(group); } auto path(unsigned group) -> string { return bind->path(group); }
string server() { return bind->server(); } auto server() -> string { return bind->server(); }
template<typename... Args> void notify(Args&&... args) { return bind->notify({std::forward<Args>(args)...}); } template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
//information //information
virtual string title() = 0; virtual auto title() -> string = 0;
virtual double videoFrequency() = 0; virtual auto videoFrequency() -> double = 0;
virtual double audioFrequency() = 0; virtual auto audioFrequency() -> double = 0;
//media interface //media interface
virtual bool loaded() { return false; } virtual auto loaded() -> bool { return false; }
virtual string sha256() { return ""; } virtual auto sha256() -> string { return ""; }
virtual unsigned group(unsigned id) = 0; virtual auto group(unsigned id) -> unsigned = 0;
virtual void load(unsigned id) {} virtual auto load(unsigned id) -> void {}
virtual void save() {} virtual auto save() -> void {}
virtual void load(unsigned id, const stream& memory) {} virtual auto load(unsigned id, const stream& memory) -> void {}
virtual void save(unsigned id, const stream& memory) {} virtual auto save(unsigned id, const stream& memory) -> void {}
virtual void unload() {} virtual auto unload() -> void {}
//system interface //system interface
virtual void connect(unsigned port, unsigned device) {} virtual auto connect(unsigned port, unsigned device) -> void {}
virtual void power() {} virtual auto power() -> void {}
virtual void reset() {} virtual auto reset() -> void {}
virtual void run() {} virtual auto run() -> void {}
//time functions //time functions
virtual bool rtc() { return false; } virtual auto rtc() -> bool { return false; }
virtual void rtcsync() {} virtual auto rtcsync() -> void {}
//state functions //state functions
virtual serializer serialize() = 0; virtual auto serialize() -> serializer = 0;
virtual bool unserialize(serializer&) = 0; virtual auto unserialize(serializer&) -> bool = 0;
//cheat functions //cheat functions
virtual void cheatSet(const lstring& = lstring{}) {} virtual auto cheatSet(const lstring& = lstring{}) -> void {}
//utility functions //utility functions
enum class PaletteMode : unsigned { Literal, Channel, Standard, Emulation }; enum class PaletteMode : unsigned { Literal, Channel, Standard, Emulation };
virtual void paletteUpdate(PaletteMode mode) {} virtual auto paletteUpdate(PaletteMode mode) -> void {}
}; };
} }

View File

@ -9,12 +9,21 @@ auto pLayout::destruct() -> void {
} }
auto pLayout::setEnabled(bool enabled) -> 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 { 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 { auto pLayout::setVisible(bool visible) -> void {
for(auto& sizable : state().sizables) {
if(auto self = sizable->self()) self->setVisible(sizable->visible(true));
}
} }
} }

View File

@ -72,6 +72,9 @@ auto pWindow::setDroppable(bool droppable) -> void {
} }
auto pWindow::setEnabled(bool enabled) -> 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 { auto pWindow::setFocused() -> void {
@ -79,6 +82,12 @@ auto pWindow::setFocused() -> void {
SetFocus(hwnd); 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 { auto pWindow::setFullScreen(bool fullScreen) -> void {
lock(); lock();
if(fullScreen == false) { if(fullScreen == false) {
@ -138,7 +147,7 @@ auto pWindow::setModal(bool modality) -> void {
} }
auto pWindow::setResizable(bool resizable) -> 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); setGeometry(state().geometry);
} }
@ -149,6 +158,10 @@ auto pWindow::setTitle(string text) -> void {
auto pWindow::setVisible(bool visible) -> void { auto pWindow::setVisible(bool visible) -> void {
ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE); ShowWindow(hwnd, visible ? SW_SHOWNORMAL : SW_HIDE);
if(!visible) setModal(false); if(!visible) setModal(false);
if(auto layout = state().layout) {
if(auto self = layout->self()) self->setVisible(layout->visible(true));
}
} }
// //

View File

@ -17,6 +17,7 @@ struct pWindow : pObject {
auto setDroppable(bool droppable) -> void; auto setDroppable(bool droppable) -> void;
auto setEnabled(bool enabled) -> void; auto setEnabled(bool enabled) -> void;
auto setFocused() -> void; auto setFocused() -> void;
auto setFont(const string& font) -> void override;
auto setFullScreen(bool fullScreen) -> void; auto setFullScreen(bool fullScreen) -> void;
auto setGeometry(Geometry geometry) -> void; auto setGeometry(Geometry geometry) -> void;
auto setModal(bool modal) -> void; auto setModal(bool modal) -> void;

View File

@ -1,9 +1,8 @@
#include "../tomoko.hpp" #include "../tomoko.hpp"
ConfigurationManager* configurationManager = nullptr; ConfigurationManager* config = nullptr;
auto config() -> ConfigurationManager& { return *configurationManager; }
ConfigurationManager::ConfigurationManager() { ConfigurationManager::ConfigurationManager() {
configurationManager = this; config = this;
userInterface.append(userInterface.showStatusBar, "ShowStatusBar"); userInterface.append(userInterface.showStatusBar, "ShowStatusBar");
append(userInterface, "UserInterface"); append(userInterface, "UserInterface");
@ -17,6 +16,9 @@ ConfigurationManager::ConfigurationManager() {
video.append(video.aspectCorrection, "AspectCorrection"); video.append(video.aspectCorrection, "AspectCorrection");
video.append(video.filter, "Filter"); video.append(video.filter, "Filter");
video.append(video.colorEmulation, "ColorEmulation"); 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.mask, "Mask");
video.overscan.append(video.overscan.horizontal, "Horizontal"); video.overscan.append(video.overscan.horizontal, "Horizontal");
video.overscan.append(video.overscan.vertical, "Vertical"); video.overscan.append(video.overscan.vertical, "Vertical");
@ -27,11 +29,19 @@ ConfigurationManager::ConfigurationManager() {
audio.append(audio.device, "Device"); audio.append(audio.device, "Device");
audio.append(audio.synchronize, "Synchronize"); audio.append(audio.synchronize, "Synchronize");
audio.append(audio.mute, "Mute"); 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"); append(audio, "Audio");
input.append(input.driver, "Driver"); input.append(input.driver, "Driver");
append(input, "Input"); append(input, "Input");
timing.append(timing.video, "Video");
timing.append(timing.audio, "Audio");
append(timing, "Timing");
load({configpath(), "tomoko/settings.bml"}); load({configpath(), "tomoko/settings.bml"});
if(!library.location) library.location = {userpath(), "Emulation/"}; if(!library.location) library.location = {userpath(), "Emulation/"};
if(!video.driver) video.driver = ruby::video.safestDriver(); if(!video.driver) video.driver = ruby::video.safestDriver();

View File

@ -17,6 +17,9 @@ struct ConfigurationManager : Configuration::Document {
bool aspectCorrection = true; bool aspectCorrection = true;
string filter = "Blur"; string filter = "Blur";
bool colorEmulation = true; bool colorEmulation = true;
unsigned saturation = 100;
unsigned gamma = 100;
unsigned luminance = 100;
struct Overscan : Configuration::Node { struct Overscan : Configuration::Node {
bool mask = false; bool mask = false;
@ -30,12 +33,20 @@ struct ConfigurationManager : Configuration::Document {
string device; string device;
bool synchronize = true; bool synchronize = true;
bool mute = false; bool mute = false;
unsigned volume = 100;
unsigned frequency = 48000;
unsigned latency = 60;
string resampler = "Sinc";
} audio; } audio;
struct Input : Configuration::Node { struct Input : Configuration::Node {
string driver; string driver;
} input; } input;
struct Timing : Configuration::Node {
double video = 60.0;
double audio = 48000.0;
} timing;
}; };
extern ConfigurationManager* configurationManager; extern ConfigurationManager* config;
auto config() -> ConfigurationManager&;

View File

@ -10,10 +10,10 @@ Presentation::Presentation() {
if(!media.bootable) continue; if(!media.bootable) continue;
auto item = new MenuItem{&libraryMenu}; auto item = new MenuItem{&libraryMenu};
item->setText({media.name, " ..."}).onActivate([=] { item->setText({media.name, " ..."}).onActivate([=] {
directory::create({config().library.location, media.name}); directory::create({config->library.location, media.name});
auto location = BrowserDialog() auto location = BrowserDialog()
.setTitle({"Load ", media.name}) .setTitle({"Load ", media.name})
.setPath({config().library.location, media.name}) .setPath({config->library.location, media.name})
.setFilters(string{media.name, "|*.", media.type}) .setFilters(string{media.name, "|*.", media.type})
.openFolder(); .openFolder();
if(directory::exists(location)) { if(directory::exists(location)) {
@ -31,52 +31,52 @@ Presentation::Presentation() {
settingsMenu.setText("Settings"); settingsMenu.setText("Settings");
videoScaleMenu.setText("Video Scale"); videoScaleMenu.setText("Video Scale");
if(config().video.scale == "Small") videoScaleSmall.setChecked(); if(config->video.scale == "Small") videoScaleSmall.setChecked();
if(config().video.scale == "Normal") videoScaleNormal.setChecked(); if(config->video.scale == "Normal") videoScaleNormal.setChecked();
if(config().video.scale == "Large") videoScaleLarge.setChecked(); if(config->video.scale == "Large") videoScaleLarge.setChecked();
videoScaleSmall.setText("Small").onActivate([&] { videoScaleSmall.setText("Small").onActivate([&] {
config().video.scale = "Small"; config->video.scale = "Small";
resizeViewport(); resizeViewport();
}); });
videoScaleNormal.setText("Normal").onActivate([&] { videoScaleNormal.setText("Normal").onActivate([&] {
config().video.scale = "Normal"; config->video.scale = "Normal";
resizeViewport(); resizeViewport();
}); });
videoScaleLarge.setText("Large").onActivate([&] { videoScaleLarge.setText("Large").onActivate([&] {
config().video.scale = "Large"; config->video.scale = "Large";
resizeViewport(); resizeViewport();
}); });
aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] { aspectCorrection.setText("Aspect Correction").setChecked(config->video.aspectCorrection).onToggle([&] {
config().video.aspectCorrection = aspectCorrection.checked(); config->video.aspectCorrection = aspectCorrection.checked();
resizeViewport(); resizeViewport();
}); });
videoFilterMenu.setText("Video Filter"); videoFilterMenu.setText("Video Filter");
if(config().video.filter == "None") videoFilterNone.setChecked(); if(config->video.filter == "None") videoFilterNone.setChecked();
if(config().video.filter == "Blur") videoFilterBlur.setChecked(); if(config->video.filter == "Blur") videoFilterBlur.setChecked();
videoFilterNone.setText("None").onActivate([&] { config().video.filter = "None"; program->updateVideoFilter(); }); videoFilterNone.setText("None").onActivate([&] { config->video.filter = "None"; program->updateVideoFilter(); });
videoFilterBlur.setText("Blur").onActivate([&] { config().video.filter = "Blur"; program->updateVideoFilter(); }); videoFilterBlur.setText("Blur").onActivate([&] { config->video.filter = "Blur"; program->updateVideoFilter(); });
colorEmulation.setText("Color Emulation").setChecked(config().video.colorEmulation).onToggle([&] { colorEmulation.setText("Color Emulation").setChecked(config->video.colorEmulation).onToggle([&] {
config().video.colorEmulation = colorEmulation.checked(); config->video.colorEmulation = colorEmulation.checked();
program->updateVideoPalette(); program->updateVideoPalette();
}); });
maskOverscan.setText("Mask Overscan").setChecked(config().video.overscan.mask).onToggle([&] { maskOverscan.setText("Mask Overscan").setChecked(config->video.overscan.mask).onToggle([&] {
config().video.overscan.mask = maskOverscan.checked(); config->video.overscan.mask = maskOverscan.checked();
}); });
synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] { synchronizeVideo.setText("Synchronize Video").setChecked(config->video.synchronize).onToggle([&] {
config().video.synchronize = synchronizeVideo.checked(); config->video.synchronize = synchronizeVideo.checked();
video.set(Video::Synchronize, config().video.synchronize); video.set(Video::Synchronize, config->video.synchronize);
}); });
synchronizeAudio.setText("Synchronize Audio").setChecked(config().audio.synchronize).onToggle([&] { synchronizeAudio.setText("Synchronize Audio").setChecked(config->audio.synchronize).onToggle([&] {
config().audio.synchronize = synchronizeAudio.checked(); config->audio.synchronize = synchronizeAudio.checked();
audio.set(Audio::Synchronize, config().audio.synchronize); audio.set(Audio::Synchronize, config->audio.synchronize);
}); });
muteAudio.setText("Mute Audio").setChecked(config().audio.mute).onToggle([&] { muteAudio.setText("Mute Audio").setChecked(config->audio.mute).onToggle([&] {
config().audio.mute = muteAudio.checked(); config->audio.mute = muteAudio.checked();
program->dsp.setVolume(config().audio.mute ? 0.0 : 1.0); program->dsp.setVolume(config->audio.mute ? 0.0 : 1.0);
}); });
showStatusBar.setText("Show Status Bar").setChecked(config().userInterface.showStatusBar).onToggle([&] { showStatusBar.setText("Show Status Bar").setChecked(config->userInterface.showStatusBar).onToggle([&] {
config().userInterface.showStatusBar = showStatusBar.checked(); config->userInterface.showStatusBar = showStatusBar.checked();
statusBar.setVisible(config().userInterface.showStatusBar); statusBar.setVisible(config->userInterface.showStatusBar);
if(visible()) resizeViewport(); if(visible()) resizeViewport();
}); });
showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(2); }); showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(2); });
@ -98,7 +98,7 @@ Presentation::Presentation() {
stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); }); stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); });
statusBar.setFont(Font::sans(8, "Bold")); statusBar.setFont(Font::sans(8, "Bold"));
statusBar.setVisible(config().userInterface.showStatusBar); statusBar.setVisible(config->userInterface.showStatusBar);
onClose([&] { program->quit(); }); onClose([&] { program->quit(); });
@ -136,9 +136,9 @@ auto Presentation::updateEmulator() -> void {
auto Presentation::resizeViewport() -> void { auto Presentation::resizeViewport() -> void {
signed scale = 1; signed scale = 1;
if(config().video.scale == "Small" ) scale = 1; if(config->video.scale == "Small" ) scale = 1;
if(config().video.scale == "Normal") scale = 2; if(config->video.scale == "Normal") scale = 2;
if(config().video.scale == "Large" ) scale = 4; if(config->video.scale == "Large" ) scale = 4;
signed width = 256; signed width = 256;
signed height = 240; signed height = 240;
@ -147,7 +147,7 @@ auto Presentation::resizeViewport() -> void {
height = emulator->information.height; height = emulator->information.height;
} }
bool arc = config().video.aspectCorrection; bool arc = config->video.aspectCorrection;
if(fullScreen() == false) { if(fullScreen() == false) {
signed windowWidth = 256 * scale; signed windowWidth = 256 * scale;
@ -200,7 +200,7 @@ auto Presentation::toggleFullScreen() -> void {
setFullScreen(false); setFullScreen(false);
setResizable(false); setResizable(false);
menuBar.setVisible(true); menuBar.setVisible(true);
statusBar.setVisible(config().userInterface.showStatusBar); statusBar.setVisible(config->userInterface.showStatusBar);
} }
Application::processEvents(); Application::processEvents();

View File

@ -2,7 +2,7 @@
auto Program::loadRequest(unsigned id, string name, string type) -> void { auto Program::loadRequest(unsigned id, string name, string type) -> void {
string location = BrowserDialog() string location = BrowserDialog()
.setTitle({"Load ", name}) .setTitle({"Load ", name})
.setPath({config().library.location, name}) .setPath({config->library.location, name})
.setFilters({string{name, "|*.", type}}) .setFilters({string{name, "|*.", type}})
.openFolder(); .openFolder();
if(!directory::exists(location)) return; if(!directory::exists(location)) return;
@ -27,12 +27,36 @@ auto Program::saveRequest(unsigned id, string path) -> void {
return emulator->save(id, stream); return emulator->save(id, stream);
} }
auto Program::videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 { auto Program::videoColor(unsigned source, uint16 a, uint16 r, uint16 g, uint16 b) -> uint32 {
alpha >>= 8; if(config->video.saturation != 100) {
red >>= 8; uint16 grayscale = uclamp<16>((r + g + b) / 3);
green >>= 8; double saturation = config->video.saturation * 0.01;
blue >>= 8; double inverse = max(0.0, 1.0 - saturation);
return alpha << 24 | red << 16 | green << 8 | blue << 0; 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 { 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) { if(emulator->information.overscan && config->video.overscan.mask) {
unsigned h = config().video.overscan.horizontal; unsigned h = config->video.overscan.horizontal;
unsigned v = config().video.overscan.vertical; unsigned v = config->video.overscan.vertical;
if(h) for(auto y : range(height)) { if(h) for(auto y : range(height)) {
memory::fill(output + y * length, 4 * h); memory::fill(output + y * length, 4 * h);

View File

@ -29,26 +29,26 @@ Program::Program() {
presentation->setVisible(); presentation->setVisible();
video.driver(config().video.driver); video.driver(config->video.driver);
video.set(Video::Handle, presentation->viewport.handle()); 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(); } if(!video.init()) { video.driver("None"); video.init(); }
audio.driver(config().audio.driver); audio.driver(config->audio.driver);
audio.set(Audio::Device, config().audio.device); audio.set(Audio::Device, config->audio.device);
audio.set(Audio::Handle, presentation->viewport.handle()); 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::Frequency, 96000u);
audio.set(Audio::Latency, 80u); audio.set(Audio::Latency, 80u);
if(!audio.init()) { audio.driver("None"); audio.init(); } 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()); input.set(Input::Handle, presentation->viewport.handle());
if(!input.init()) { input.driver("None"); input.init(); } if(!input.init()) { input.driver("None"); input.init(); }
dsp.setPrecision(16); dsp.setPrecision(16);
dsp.setBalance(0.0); 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.setFrequency(32040);
dsp.setResampler(DSP::ResampleEngine::Sinc); dsp.setResampler(DSP::ResampleEngine::Sinc);
dsp.setResamplerFrequency(96000); dsp.setResamplerFrequency(96000);
@ -73,7 +73,7 @@ auto Program::main() -> void {
auto Program::quit() -> void { auto Program::quit() -> void {
unloadMedia(); unloadMedia();
configurationManager->quit(); config->quit();
inputManager->quit(); inputManager->quit();
video.term(); video.term();
audio.term(); audio.term();

View File

@ -34,6 +34,8 @@ struct Program : Emulator::Interface::Bind {
auto updateStatusText() -> void; auto updateStatusText() -> void;
auto updateVideoFilter() -> void; auto updateVideoFilter() -> void;
auto updateVideoPalette() -> void; auto updateVideoPalette() -> void;
auto updateAudio() -> void;
auto updateDSP() -> void;
DSP dsp; DSP dsp;
bool pause = false; bool pause = false;

View File

@ -36,14 +36,36 @@ auto Program::updateStatusText() -> void {
} }
auto Program::updateVideoFilter() -> void { auto Program::updateVideoFilter() -> void {
if(config().video.filter == "None") video.set(Video::Filter, Video::FilterNearest); 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 == "Blur") video.set(Video::Filter, Video::FilterLinear);
} }
auto Program::updateVideoPalette() -> void { auto Program::updateVideoPalette() -> void {
if(!emulator) return; if(!emulator) return;
emulator->paletteUpdate(config().video.colorEmulation emulator->paletteUpdate(config->video.colorEmulation
? Emulator::Interface::PaletteMode::Emulation ? Emulator::Interface::PaletteMode::Emulation
: Emulator::Interface::PaletteMode::Standard : 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);
}

View File

@ -6,36 +6,36 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold")); driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold"));
videoLabel.setText("Video:"); 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(";")) { for(auto& driver : string{video.availableDrivers()}.split(";")) {
ComboButtonItem item; ComboButtonItem item;
item.setText(driver); item.setText(driver);
videoDriver.append(item); videoDriver.append(item);
if(config().video.driver == driver) item.setSelected(); if(config->video.driver == driver) item.setSelected();
} }
audioLabel.setText("Audio:"); 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(";")) { for(auto& driver : string{audio.availableDrivers()}.split(";")) {
ComboButtonItem item; ComboButtonItem item;
item.setText(driver); item.setText(driver);
audioDriver.append(item); audioDriver.append(item);
if(config().audio.driver == driver) item.setSelected(); if(config->audio.driver == driver) item.setSelected();
} }
inputLabel.setText("Input:"); 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(";")) { for(auto& driver : string{input.availableDrivers()}.split(";")) {
ComboButtonItem item; ComboButtonItem item;
item.setText(driver); item.setText(driver);
inputDriver.append(item); 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")); libraryLabel.setText("Game Library").setFont(Font::sans(8, "Bold"));
libraryPrefix.setText("Location:"); libraryPrefix.setText("Location:");
libraryLocation.setEditable(false).setText(config().library.location); libraryLocation.setEditable(false).setText(config->library.location);
libraryChange.setText("Change ...").onActivate([&] { libraryChange.setText("Change ...").onActivate([&] {
if(auto location = BrowserDialog().setTitle("Select Library Location").selectFolder()) { if(auto location = BrowserDialog().setTitle("Select Library Location").selectFolder()) {
libraryLocation.setText(config().library.location = location); libraryLocation.setText(config->library.location = location);
} }
}); });
} }

View File

@ -3,4 +3,75 @@ AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
setText("Audio"); setText("Audio");
layout.setMargin(5); 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);
} }

View File

@ -2,12 +2,50 @@ struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*); VideoSettings(TabFrame*);
VerticalLayout layout{this}; 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 { struct AudioSettings : TabFrameItem {
AudioSettings(TabFrame*); AudioSettings(TabFrame*);
VerticalLayout layout{this}; 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 { struct InputSettings : TabFrameItem {
@ -62,6 +100,16 @@ struct TimingSettings : TabFrameItem {
TimingSettings(TabFrame*); TimingSettings(TabFrame*);
VerticalLayout layout{this}; 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 { struct AdvancedSettings : TabFrameItem {

View File

@ -3,4 +3,16 @@ TimingSettings::TimingSettings(TabFrame* parent) : TabFrameItem(parent) {
setText("Timing"); setText("Timing");
layout.setMargin(5); 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();
} }

View File

@ -3,4 +3,34 @@ VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
setText("Video"); setText("Video");
layout.setMargin(5); 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();
} }