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/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/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 60ec3ed26..99b38b24d 100644 --- a/src/panda_qt/config_window.cpp +++ b/src/panda_qt/config_window.cpp @@ -240,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); 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