diff --git a/CMakeLists.txt b/CMakeLists.txt index d5a8c7ad1..986477c22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -708,7 +708,7 @@ if(NOT BUILD_HYDRA_CORE AND NOT BUILD_LIBRETRO_CORE) docs/img/rsob_icon.png docs/img/rstarstruck_icon.png docs/img/rpog_icon.png docs/img/rsyn_icon.png docs/img/settings_icon.png docs/img/display_icon.png docs/img/speaker_icon.png docs/img/sparkling_icon.png docs/img/battery_icon.png docs/img/sdcard_icon.png - docs/img/rnap_icon.png docs/img/rcow_icon.png + docs/img/rnap_icon.png docs/img/rcow_icon.png docs/img/skyemu_icon.png ) else() set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp src/panda_sdl/mappings.cpp) diff --git a/docs/img/skyemu_icon.png b/docs/img/skyemu_icon.png new file mode 100644 index 000000000..d5f3b9d4c Binary files /dev/null and b/docs/img/skyemu_icon.png differ diff --git a/include/config.hpp b/include/config.hpp index 7361a40d1..6041286dc 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,12 +1,21 @@ #pragma once #include +#include #include "audio/dsp_core.hpp" -#include "renderer.hpp" #include "frontend_settings.hpp" +#include "renderer.hpp" struct AudioDeviceConfig { + // Audio curve to use for volumes between 0-100 + enum class VolumeCurve : int { + Cubic = 0, // Samples are scaled by volume ^ 3 + Linear = 1, // Samples are scaled by volume + }; + float volumeRaw = 1.0f; + VolumeCurve volumeCurve = VolumeCurve::Cubic; + bool muteAudio = false; float getVolume() const { @@ -16,6 +25,9 @@ struct AudioDeviceConfig { return volumeRaw; } + + static VolumeCurve volumeCurveFromString(std::string inString); + static const char* volumeCurveToString(VolumeCurve curve); }; // Remember to initialize every field here to its default value otherwise bad things will happen diff --git a/include/frontend_settings.hpp b/include/frontend_settings.hpp index aaf9eaf0e..1a78ab66f 100644 --- a/include/frontend_settings.hpp +++ b/include/frontend_settings.hpp @@ -19,6 +19,7 @@ struct FrontendSettings { Rsyn = 1, Rnap = 2, Rcow = 3, + SkyEmu = 4, }; Theme theme = Theme::Dark; diff --git a/src/config.cpp b/src/config.cpp index a8c88a68f..fd4d969d4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,5 +1,7 @@ #include "config.hpp" +#include +#include #include #include #include @@ -105,6 +107,7 @@ void EmulatorConfig::load() { // Our volume ranges from 0.0 (muted) to 2.0 (boosted, using a logarithmic scale). 1.0 is the "default" volume, ie we don't adjust the PCM // samples at all. audioDeviceConfig.volumeRaw = float(std::clamp(toml::find_or(audio, "AudioVolume", 1.0), 0.0, 2.0)); + audioDeviceConfig.volumeCurve = AudioDeviceConfig::volumeCurveFromString(toml::find_or(audio, "VolumeCurve", "cubic")); } } @@ -188,6 +191,7 @@ void EmulatorConfig::save() { data["Audio"]["EnableAACAudio"] = aacEnabled; data["Audio"]["MuteAudio"] = audioDeviceConfig.muteAudio; data["Audio"]["AudioVolume"] = double(audioDeviceConfig.volumeRaw); + data["Audio"]["VolumeCurve"] = std::string(AudioDeviceConfig::volumeCurveToString(audioDeviceConfig.volumeCurve)); data["Audio"]["PrintDSPFirmware"] = printDSPFirmware; data["Battery"]["ChargerPlugged"] = chargerPlugged; @@ -203,3 +207,26 @@ void EmulatorConfig::save() { file << data; file.close(); } + +AudioDeviceConfig::VolumeCurve AudioDeviceConfig::volumeCurveFromString(std::string inString) { + // Transform to lower-case to make the setting case-insensitive + std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (inString == "cubic") { + return VolumeCurve::Cubic; + } else if (inString == "linear") { + return VolumeCurve::Linear; + } + + // Default to cubic curve + return VolumeCurve::Cubic; +} + +const char* AudioDeviceConfig::volumeCurveToString(AudioDeviceConfig::VolumeCurve curve) { + switch (curve) { + case VolumeCurve::Linear: return "linear"; + + case VolumeCurve::Cubic: + default: return "cubic"; + } +} \ No newline at end of file diff --git a/src/core/audio/miniaudio_device.cpp b/src/core/audio/miniaudio_device.cpp index b513450ed..550fb039d 100644 --- a/src/core/audio/miniaudio_device.cpp +++ b/src/core/audio/miniaudio_device.cpp @@ -134,6 +134,12 @@ void MiniAudioDevice::init(Samples& samples, bool safe) { } else { // If our volume is in [0.0, 1.0) then just multiply by the volume. No need to clamp, since there is no danger of our samples wrapping // around due to overflow + + // If we're applying cubic volume curve, raise volume to the power of 3 + if (self->audioSettings.volumeCurve == AudioDeviceConfig::VolumeCurve::Cubic) { + audioVolume = audioVolume * audioVolume * audioVolume; + } + for (usize i = 0; i < samplesWritten; i += 2) { s16 l = s16(float(sample[0]) * audioVolume); s16 r = s16(float(sample[1]) * audioVolume); diff --git a/src/frontend_settings.cpp b/src/frontend_settings.cpp index 16bae3615..498ba5004 100644 --- a/src/frontend_settings.cpp +++ b/src/frontend_settings.cpp @@ -38,10 +38,8 @@ FrontendSettings::WindowIcon FrontendSettings::iconFromString(std::string inStri std::transform(inString.begin(), inString.end(), inString.begin(), [](unsigned char c) { return std::tolower(c); }); static const std::unordered_map map = { - {"rpog", WindowIcon::Rpog}, - {"rsyn", WindowIcon::Rsyn}, - {"rcow", WindowIcon::Rcow}, - {"rnap", WindowIcon::Rnap}, + {"rpog", WindowIcon::Rpog}, {"rsyn", WindowIcon::Rsyn}, {"rcow", WindowIcon::Rcow}, + {"rnap", WindowIcon::Rnap}, {"skyemu", WindowIcon::SkyEmu}, }; if (auto search = map.find(inString); search != map.end()) { @@ -57,6 +55,7 @@ const char* FrontendSettings::iconToString(WindowIcon icon) { case WindowIcon::Rsyn: return "rsyn"; case WindowIcon::Rcow: return "rcow"; case WindowIcon::Rnap: return "rnap"; + case WindowIcon::SkyEmu: return "skyemu"; case WindowIcon::Rpog: default: return "rpog"; diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp index 2d7e6b4cb..2485c677f 100644 --- a/src/panda_qt/cheats_window.cpp +++ b/src/panda_qt/cheats_window.cpp @@ -69,7 +69,7 @@ void CheatEntryWidget::editClicked() { } CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) { - setWindowTitle("Edit Cheat"); + setWindowTitle(tr("Edit Cheat")); setAttribute(Qt::WA_DeleteOnClose); setModal(true); @@ -161,7 +161,7 @@ void CheatEditDialog::rejected() { CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) : QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) { - setWindowTitle("Cheats"); + setWindowTitle(tr("Cheats")); mainWindow = static_cast(parent); QVBoxLayout* layout = new QVBoxLayout; diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp index 92fa0741e..99b38b24d 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -85,6 +85,7 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win iconSelect->addItem(tr("Happy panda (colourful)")); iconSelect->addItem(tr("Sleepy panda")); iconSelect->addItem(tr("Cow panda")); + iconSelect->addItem(tr("The penguin from SkyEmu")); iconSelect->setCurrentIndex(static_cast(config.frontendSettings.icon)); connect(iconSelect, &QComboBox::currentIndexChanged, this, [&](int index) { @@ -239,6 +240,16 @@ ConfigWindow::ConfigWindow(ConfigCallback configCallback, MainWindowCallback win connectCheckbox(muteAudio, config.audioDeviceConfig.muteAudio); audioLayout->addRow(muteAudio); + QComboBox* volumeCurveType = new QComboBox; + volumeCurveType->addItem(tr("Cubic")); + volumeCurveType->addItem(tr("Linear")); + volumeCurveType->setCurrentIndex(static_cast(config.audioDeviceConfig.volumeCurve)); + connect(volumeCurveType, &QComboBox::currentIndexChanged, this, [&](int index) { + config.audioDeviceConfig.volumeCurve = static_cast(index); + updateConfig(); + }); + audioLayout->addRow(tr("Volume curve"), volumeCurveType); + QSpinBox* volumeRaw = new QSpinBox(); volumeRaw->setRange(0, 200); volumeRaw->setValue(config.audioDeviceConfig.volumeRaw * 100); @@ -398,6 +409,7 @@ void ConfigWindow::setIcon(WindowIcon icon) { case WindowIcon::Rsyn: updateIcon(":/docs/img/rsyn_icon.png"); break; case WindowIcon::Rnap: updateIcon(":/docs/img/rnap_icon.png"); break; case WindowIcon::Rcow: updateIcon(":/docs/img/rcow_icon.png"); break; + case WindowIcon::SkyEmu: updateIcon(":/docs/img/skyemu_icon.png"); break; case WindowIcon::Rpog: default: updateIcon(":/docs/img/rpog_icon.png"); break; diff --git a/src/panda_qt/patch_window.cpp b/src/panda_qt/patch_window.cpp index 6ba73e842..6096d89aa 100644 --- a/src/panda_qt/patch_window.cpp +++ b/src/panda_qt/patch_window.cpp @@ -12,7 +12,7 @@ #include "io_file.hpp" PatchWindow::PatchWindow(QWidget* parent) : QWidget(parent, Qt::Window) { - setWindowTitle("ROM patcher"); + setWindowTitle(tr("ROM patcher")); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(6, 6, 6, 6); diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp index 24febd0d1..7ac1d5f28 100644 --- a/src/panda_qt/text_editor.cpp +++ b/src/panda_qt/text_editor.cpp @@ -9,7 +9,7 @@ using namespace Zep; TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText) : QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) { - setWindowTitle("Lua Editor"); + setWindowTitle(tr("Lua Editor")); resize(600, 600); // Register our extensions