-
-
+ ${}
+
+
- ${}
-
-
diff --git a/docroot/js/mediaplayer.js b/docroot/js/mediaplayer.js
index bf460e12..c5ebcb3f 100644
--- a/docroot/js/mediaplayer.js
+++ b/docroot/js/mediaplayer.js
@@ -121,6 +121,7 @@ LMS.mediaplayer = function () {
}
var _setReplayGain = function (replayGain) {
+ console.log("Applying gain " + replayGain);
_gainNode.gain.value = Math.pow(10, (_settings.replayGain.preAmpGain + replayGain) / 20);
}
diff --git a/src/lms/ui/LmsTheme.cpp b/src/lms/ui/LmsTheme.cpp
index 9fcc0df1..1ab59b3a 100644
--- a/src/lms/ui/LmsTheme.cpp
+++ b/src/lms/ui/LmsTheme.cpp
@@ -86,8 +86,8 @@ LmsTheme::getStyleSheets(Database::User::UITheme theme)
case Database::User::UITheme::Dark:
return
{
- {"css/bootstrap-darkly.min.css"},
{"resources/themes/bootstrap/3/wt.css"},
+ {"css/bootstrap-darkly.min.css"},
{"css/lms.css"},
{"css/lms-darkly.css"},
};
@@ -95,8 +95,8 @@ LmsTheme::getStyleSheets(Database::User::UITheme theme)
case Database::User::UITheme::Light:
return
{
- {"css/bootstrap-flatly.min.css"},
{"resources/themes/bootstrap/3/wt.css"},
+ {"css/bootstrap-flatly.min.css"},
{"css/lms.css"},
{"css/lms-flatly.css"},
};
diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp
index f141282a..632d025d 100644
--- a/src/lms/ui/MediaPlayer.cpp
+++ b/src/lms/ui/MediaPlayer.cpp
@@ -35,6 +35,7 @@
#include "resource/AudioFileResource.hpp"
#include "utils/String.hpp"
+#include "utils/Utils.hpp"
#include "LmsApplication.hpp"
@@ -56,9 +57,8 @@ static std::string settingsToJSString(const MediaPlayer::Settings& settings)
{
Json::Object replayGain;
- replayGain["mode"] = "TODO";
- replayGain["preAmpGain"] = 0;
- replayGain["clippingPreventionMode"] = "TODO";
+ replayGain["mode"] = static_cast
(settings.replayGain.mode);
+ replayGain["preAmpGain"] = settings.replayGain.preAmpGain;
res["replayGain"] = std::move(replayGain);
}
@@ -67,7 +67,7 @@ static std::string settingsToJSString(const MediaPlayer::Settings& settings)
static
std::optional
-modeFromString(const std::string& str)
+transcodeModeFromString(const std::string& str)
{
const auto value {StringUtils::readAs(str)};
if (!value)
@@ -121,6 +121,38 @@ bitrateFromString(const std::string& str)
return std::nullopt;
}
+static
+std::optional
+replayGainModeFromString(const std::string& str)
+{
+ const auto value {StringUtils::readAs(str)};
+ if (!value)
+ return std::nullopt;
+
+ MediaPlayer::Settings::ReplayGain::Mode mode {static_cast(*value)};
+ switch (mode)
+ {
+ case MediaPlayer::Settings::ReplayGain::Mode::None:
+ case MediaPlayer::Settings::ReplayGain::Mode::Auto:
+ case MediaPlayer::Settings::ReplayGain::Mode::Track:
+ case MediaPlayer::Settings::ReplayGain::Mode::Release:
+ return mode;
+ }
+
+ return std::nullopt;
+}
+
+static
+std::optional
+replayGainPreAmpGainFromString(const std::string& str)
+{
+ const auto value {StringUtils::readAs(str)};
+ if (!value)
+ return std::nullopt;
+
+ return clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
+}
+
static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings)
{
using Settings = MediaPlayer::Settings;
@@ -131,13 +163,24 @@ static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings
MediaPlayer::Settings settings;
- const Json::Value transcodeValue {parsedSettings.get("transcode")};
- if (transcodeValue.type() == Json::Type::Object)
{
- const Json::Object transcode {transcodeValue};
- settings.transcode.mode = modeFromString(transcode.get("mode").toString().orIfNull("")).value_or(Settings::Transcode::defaultMode);
- settings.transcode.format = formatFromString(transcode.get("format").toString().orIfNull("")).value_or(Settings::Transcode::defaultFormat);
- settings.transcode.bitrate = bitrateFromString(transcode.get("bitrate").toString().orIfNull("")).value_or(Settings::Transcode::defaultBitrate);
+ const Json::Value transcodeValue {parsedSettings.get("transcode")};
+ if (transcodeValue.type() == Json::Type::Object)
+ {
+ const Json::Object transcode {transcodeValue};
+ settings.transcode.mode = transcodeModeFromString(transcode.get("mode").toString().orIfNull("")).value_or(Settings::Transcode::defaultMode);
+ settings.transcode.format = formatFromString(transcode.get("format").toString().orIfNull("")).value_or(Settings::Transcode::defaultFormat);
+ settings.transcode.bitrate = bitrateFromString(transcode.get("bitrate").toString().orIfNull("")).value_or(Settings::Transcode::defaultBitrate);
+ }
+ }
+ {
+ const Json::Value replayGainValue {parsedSettings.get("replayGain")};
+ if (replayGainValue.type() == Json::Type::Object)
+ {
+ const Json::Object replayGain {replayGainValue};
+ settings.replayGain.mode = replayGainModeFromString(replayGain.get("mode").toString().orIfNull("")).value_or(Settings::ReplayGain::defaultMode);
+ settings.replayGain.preAmpGain = replayGainPreAmpGainFromString(replayGain.get("preAmpGain").toString().orIfNull("")).value_or(Settings::ReplayGain::defaultPreAmpGain);
+ }
}
return settings;
diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp
index 594624e2..6150756c 100644
--- a/src/lms/ui/MediaPlayer.hpp
+++ b/src/lms/ui/MediaPlayer.hpp
@@ -76,6 +76,8 @@ class MediaPlayer : public Wt::WTemplate
static inline constexpr Mode defaultMode {Mode::None};
static inline constexpr Gain defaultPreAmpGain {};
+ static inline constexpr Gain minPreAmpGain {-15};
+ static inline constexpr Gain maxPreAmpGain {15};
static inline constexpr ClippingPreventionMode defaultClippingPreventionMode {ClippingPreventionMode::None};
Mode mode;
diff --git a/src/lms/ui/PlayQueueView.cpp b/src/lms/ui/PlayQueueView.cpp
index e50b81ac..29b1c37f 100644
--- a/src/lms/ui/PlayQueueView.cpp
+++ b/src/lms/ui/PlayQueueView.cpp
@@ -136,8 +136,13 @@ PlayQueue::PlayQueue()
if (!LmsApp->getUser()->isDemo())
{
- LmsApp->post([=]
+ LmsApp->getMediaPlayer()->settingsLoaded.connect([=]
{
+ if (_mediaPlayerSettingsLoaded)
+ return;
+
+ _mediaPlayerSettingsLoaded = true;
+
std::size_t trackPos {};
{
diff --git a/src/lms/ui/PlayQueueView.hpp b/src/lms/ui/PlayQueueView.hpp
index 222b53f0..5bb8c605 100644
--- a/src/lms/ui/PlayQueueView.hpp
+++ b/src/lms/ui/PlayQueueView.hpp
@@ -81,6 +81,7 @@ class PlayQueue : public Wt::WTemplate
bool _repeatAll {};
bool _radioMode {};
+ bool _mediaPlayerSettingsLoaded {};
Database::IdType _tracklistId {};
Wt::WContainerWidget* _entriesContainer {};
Wt::WPushButton* _showMore {};
diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp
index 244da836..a06db8b8 100644
--- a/src/lms/ui/SettingsView.cpp
+++ b/src/lms/ui/SettingsView.cpp
@@ -21,6 +21,8 @@
#include
#include
+#include
+#include
#include
#include
#include
@@ -51,6 +53,8 @@ class SettingsModel : public Wt::WFormModel
static inline const Field TranscodeModeField {"transcode-mode"};
static inline const Field TranscodeFormatField {"transcode-format"};
static inline const Field TranscodeBitrateField {"transcode-bitrate"};
+ static inline const Field ReplayGainModeField {"replaygain-mode"};
+ static inline const Field ReplayGainPreAmpGainField {"replaygain-preamp"};
static inline const Field SubsonicArtistListModeField {"subsonic-artist-list-mode"};
static inline const Field SubsonicTranscodeEnableField {"subsonic-transcode-enable"};
static inline const Field SubsonicTranscodeFormatField {"subsonic-transcode-format"};
@@ -60,6 +64,7 @@ class SettingsModel : public Wt::WFormModel
static inline const Field PasswordConfirmField {"password-confirm"};
using TranscodeModeModel = ValueStringModel;
+ using ReplayGainModeModel = ValueStringModel;
SettingsModel(bool withOldPassword)
: _withOldPassword {withOldPassword}
@@ -70,6 +75,8 @@ class SettingsModel : public Wt::WFormModel
addField(TranscodeModeField);
addField(TranscodeBitrateField);
addField(TranscodeFormatField);
+ addField(ReplayGainModeField);
+ addField(ReplayGainPreAmpGainField);
addField(SubsonicTranscodeEnableField);
addField(SubsonicTranscodeBitrateField);
addField(SubsonicTranscodeFormatField);
@@ -83,6 +90,12 @@ class SettingsModel : public Wt::WFormModel
setValidator(TranscodeModeField, createMandatoryValidator());
setValidator(TranscodeBitrateField, createMandatoryValidator());
setValidator(TranscodeFormatField, createMandatoryValidator());
+ setValidator(ReplayGainModeField, createMandatoryValidator());
+ {
+ auto preampGainValidator {std::make_unique()};
+ preampGainValidator->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
+ setValidator(ReplayGainPreAmpGainField, std::move(preampGainValidator));
+ }
setValidator(SubsonicTranscodeBitrateField, createMandatoryValidator());
setValidator(SubsonicTranscodeFormatField, createMandatoryValidator());
@@ -92,6 +105,7 @@ class SettingsModel : public Wt::WFormModel
std::shared_ptr getTranscodeModeModel() { return _transcodeModeModel; }
std::shared_ptr getTranscodeBitrateModel() { return _transcodeBitrateModel; }
std::shared_ptr getTranscodeFormatModel() { return _transcodeFormatModel; }
+ std::shared_ptr getReplayGainModeModel() { return _replayGainModeModel; }
std::shared_ptr getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; }
void saveData()
@@ -128,6 +142,12 @@ class SettingsModel : public Wt::WFormModel
if (transcodeBitrateRow)
settings.transcode.bitrate = _transcodeBitrateModel->getValue(*transcodeBitrateRow);
+ auto replayGainModeRow {_replayGainModeModel->getRowFromString(valueText(ReplayGainModeField))};
+ if (replayGainModeRow)
+ settings.replayGain.mode = _replayGainModeModel->getValue(*replayGainModeRow);
+
+ settings.replayGain.preAmpGain = Wt::asNumber(value(ReplayGainPreAmpGainField));
+
LmsApp->getMediaPlayer()->setSettings(settings);
}
@@ -176,6 +196,12 @@ class SettingsModel : public Wt::WFormModel
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromValue(settings.transcode.bitrate)};
if (transcodeBitrateRow)
setValue(TranscodeBitrateField, _transcodeBitrateModel->getString(*transcodeBitrateRow));
+
+ auto replayGainModeRow {_replayGainModeModel->getRowFromValue(settings.replayGain.mode)};
+ if (replayGainModeRow)
+ setValue(ReplayGainModeField, _replayGainModeModel->getString(*replayGainModeRow));
+
+ setValue(ReplayGainPreAmpGainField, settings.replayGain.preAmpGain);
}
setValue(SubsonicTranscodeEnableField, LmsApp->getUser()->getSubsonicTranscodeEnable());
@@ -275,10 +301,6 @@ class SettingsModel : public Wt::WFormModel
_transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.never"), MediaPlayer::Settings::Transcode::Mode::Never);
_transcodeModeModel->add(Wt::WString::tr("Lms.Settings.transcode-mode.if-format-not-supported"), MediaPlayer::Settings::Transcode::Mode::IfFormatNotSupported);
- _subsonicArtistListModeModel = std::make_shared>();
- _subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.all-artists"), User::SubsonicArtistListMode::AllArtists);
- _subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.release-artists"), User::SubsonicArtistListMode::ReleaseArtists);
-
_transcodeBitrateModel = std::make_shared>();
for (const Bitrate bitrate : User::audioTranscodeAllowedBitrates)
{
@@ -291,6 +313,16 @@ class SettingsModel : public Wt::WFormModel
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.matroska_opus"), AudioFormat::MATROSKA_OPUS);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.ogg_vorbis"), AudioFormat::OGG_VORBIS);
_transcodeFormatModel->add(Wt::WString::tr("Lms.Settings.transcode-format.webm_vorbis"), AudioFormat::WEBM_VORBIS);
+
+ _replayGainModeModel = std::make_shared();
+ _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.none"), MediaPlayer::Settings::ReplayGain::Mode::None);
+ _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.auto"), MediaPlayer::Settings::ReplayGain::Mode::Auto);
+ _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.track"), MediaPlayer::Settings::ReplayGain::Mode::Track);
+ _replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.release"), MediaPlayer::Settings::ReplayGain::Mode::Release);
+
+ _subsonicArtistListModeModel = std::make_shared>();
+ _subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.all-artists"), User::SubsonicArtistListMode::AllArtists);
+ _subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.release-artists"), User::SubsonicArtistListMode::ReleaseArtists);
}
bool _withOldPassword {};
@@ -298,6 +330,7 @@ class SettingsModel : public Wt::WFormModel
std::shared_ptr _transcodeModeModel;
std::shared_ptr> _transcodeBitrateModel;
std::shared_ptr> _transcodeFormatModel;
+ std::shared_ptr _replayGainModeModel;
std::shared_ptr> _subsonicArtistListModeModel;
};
@@ -376,15 +409,40 @@ SettingsView::refreshView()
transcodeBitrate->setModel(model->getTranscodeBitrateModel());
t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodeBitrate));
- transcodeModeRaw->sactivated().connect([=]()
+ transcodeModeRaw->activated().connect([=](int row)
{
- auto row {model->getTranscodeModeModel()->getRowFromString(model->valueText(SettingsModel::TranscodeModeField))};
- const bool enable = (row && (model->getTranscodeModeModel()->getValue(*row) != MediaPlayer::Settings::Transcode::Mode::Never));
+ const bool enable {model->getTranscodeModeModel()->getValue(row) != MediaPlayer::Settings::Transcode::Mode::Never};
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
+ if (LmsApp->getMediaPlayer()->getSettings()->transcode.mode == MediaPlayer::Settings::Transcode::Mode::Never)
+ {
+ model->setReadOnly(SettingsModel::TranscodeFormatField, true);
+ model->setReadOnly(SettingsModel::TranscodeBitrateField, true);
+ }
+
+ // Replay gain mode
+ auto replayGainMode {std::make_unique()};
+ auto* replayGainModeRaw {replayGainMode.get()};
+ replayGainMode->setModel(model->getReplayGainModeModel());
+ t->setFormWidget(SettingsModel::ReplayGainModeField, std::move(replayGainMode));
+
+ // Replay gain preampGain
+ auto replayGainPreampGain {std::make_unique()};
+ replayGainPreampGain->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
+ t->setFormWidget(SettingsModel::ReplayGainPreAmpGainField, std::move(replayGainPreampGain));
+
+ replayGainModeRaw->activated().connect([=](int row)
+ {
+ const bool enable {model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None};
+ model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable);
+ t->updateModel(model.get());
+ t->updateView(model.get());
+ });
+ if (LmsApp->getMediaPlayer()->getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None)
+ model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true);
}
// Subsonic