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 {
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<typename T> struct hook;
template<typename R, typename... P> struct hook<R (P...)> {
function<R (P...)> callback;
R operator()(P... p) const {
auto operator()(P... p) const -> R {
#if defined(DEBUGGER)
if(callback) return callback(std::forward<P>(p)...);
#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 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)

View File

@ -47,70 +47,70 @@ struct Interface {
vector<Port> 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<typename... Args> void notify(Args&&... args) { return bind->notify({std::forward<Args>(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<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(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 {}
};
}

View File

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

View File

@ -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));
}
}
//

View File

@ -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;

View File

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

View File

@ -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;

View File

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

View File

@ -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);

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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