diff --git a/approot/messages.xml b/approot/messages.xml index ede3290f..580032ec 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -164,6 +164,12 @@ Cannot save using a demo account! Bad password Old password must be filled in +ReplayGain mode +No ReplayGain +Auto +Track +Album +Replay gain preAmp Artist list mode All artists Album artists diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 571aae0a..78dcda2f 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -164,6 +164,12 @@ Impossible de sauvegarder en utilisant un compte de démo ! Mauvais mot de passe L'ancien mot de passe doit être renseigné +Mode ReplayGain +Pas de ReplayGain +Automatique +Piste +Album +PreAmplification ReplayGain Mode de listage des artistes Tous les artistes Tous les artistes d'album @@ -188,4 +194,5 @@ Oui Non Ce champ ne peut pas être vide +Le nombre doit être compris entre {1} et {2} diff --git a/approot/settings.xml b/approot/settings.xml index cf267f9d..6d8e2409 100644 --- a/approot/settings.xml +++ b/approot/settings.xml @@ -4,161 +4,157 @@ - ${tr:Lms.Settings.appearance} -
-
- -
-
+
+
+ ${tr:Lms.Settings.appearance} +
+
+
+
+ + ${dark-mode-info clas="help-block"} +
+
+
-
- ${dark-mode-info} -
-
-
- ${tr:Lms.Settings.audio} -
-
-
+ ${tr:Lms.Settings.audio} +
${tr:Lms.Settings.audio-settings-are-local}
-
-
-
-
- -
- ${transcode-mode} -
-
- ${transcode-mode-info} -
-
-
- -
- ${transcode-format} -
-
- ${transcode-format-info} -
-
-
- -
-
- ${transcode-bitrate} - kbps +
+
+ +
+ ${transcode-mode} + ${transcode-mode-info class="help-block"} +
+
+
+ +
+ ${transcode-format} + ${transcode-format-info class="help-block"} +
+
+
+ +
+
+ ${transcode-bitrate} + kbps +
+ ${transcode-bitrate-info class="help-block"} +
+
+ +
+ +
+ ${replaygain-mode} + ${replaygain-mode-info class="help-block"} +
+
+
+ +
+
+ ${replaygain-preamp} + dB +
+ ${replaygain-preamp-info class="help-block"} +
-
- ${transcode-bitrate-info} -
-
-
- ${} - ${tr:Lms.Settings.subsonic-api} -
- -
- -
-
-
-
- ${subsonic-transcode-enable-info} -
-
-
- -
- ${subsonic-transcode-format} -
-
- ${subsonic-transcode-format-info} -
-
-
- -
-
- ${subsonic-transcode-bitrate} - kbps + ${} + ${tr:Lms.Settings.subsonic-api} +
+
+
+
+ + ${subsonic-transcode-enable-info class="help-block"} +
+
+
+
+ +
+ ${subsonic-transcode-format} + ${subsonic-transcode-format-info class="help-block"} +
+
+
+ +
+
+ ${subsonic-transcode-bitrate} + kbps +
+ ${subsonic-transcode-bitrate-info class="help-block"} +
+
+
+ +
+ ${subsonic-artist-list-mode} + ${subsonic-artist-list-mode-info class="help-block"} +
-
- ${subsonic-transcode-bitrate-info} -
-
-
- -
- ${subsonic-artist-list-mode} -
-
- ${subsonic-artist-list-mode-info} -
-
+ ${} + ${tr:Lms.Settings.change-password} +
+ ${} +
+ +
+ ${password-old} + ${password-old-info class="help-block"} +
+
+ ${
} +
+ +
+ ${password} + ${password-info class="help-block"} +
+
+
+ +
+ ${password-confirm} + ${password-confirm-info class="help-block"} +
+
-
- ${} - ${tr:Lms.Settings.change-password} -
- ${} -
- -
- ${password-old} -
-
- ${password-old-info} -
-
- ${
} -
- -
- ${password} -
-
- ${password-info} -
-
-
- -
- ${password-confirm} -
-
- ${password-confirm-info} -
-
- -
-
- ${apply-btn class="btn-primary"} ${discard-btn} +
+
+ ${apply-btn class="btn-primary"} ${discard-btn} +
+
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