diff --git a/src/configuration/configuration.cpp b/src/configuration/configuration.cpp index a8d58c8bd..d9146cf4f 100644 --- a/src/configuration/configuration.cpp +++ b/src/configuration/configuration.cpp @@ -268,7 +268,9 @@ ConstString KEY_LINES_OF_SCROLLBACK = "Lines of scrollback"; ConstString KEY_PROXY_LOCAL_PORT = "Local port number"; ConstString KEY_MAP_MODE = "Map Mode"; ConstString KEY_MUSIC_VOLUME = "Music volume"; +ConstString KEY_MUSIC_MUTED = "Music muted"; ConstString KEY_SOUND_VOLUME = "Sound volume"; +ConstString KEY_SOUND_MUTED = "Sound muted"; ConstString KEY_AUDIO_OUTPUT_DEVICE = "Audio output device"; ConstString KEY_AUDIO_UNLOCKED = "Audio unlocked"; ConstString KEY_MAXIMUM_NUMBER_OF_PATHS = "maximum number of paths"; @@ -777,7 +779,9 @@ void Configuration::AudioSettings::read(const QSettings &conf) ? false : conf.value(KEY_AUDIO_UNLOCKED, false).toBool(); m_musicVolume = std::clamp(conf.value(KEY_MUSIC_VOLUME, 50).toInt(), 0, 100); + m_musicMuted = conf.value(KEY_MUSIC_MUTED, false).toBool(); m_soundVolume = std::clamp(conf.value(KEY_SOUND_VOLUME, 50).toInt(), 0, 100); + m_soundMuted = conf.value(KEY_SOUND_MUTED, false).toBool(); m_outputDeviceId = conf.value(KEY_AUDIO_OUTPUT_DEVICE).toByteArray(); } @@ -964,7 +968,9 @@ void Configuration::AudioSettings::write(QSettings &conf) const conf.setValue(KEY_AUDIO_UNLOCKED, m_unlocked); } conf.setValue(KEY_MUSIC_VOLUME, m_musicVolume); + conf.setValue(KEY_MUSIC_MUTED, m_musicMuted); conf.setValue(KEY_SOUND_VOLUME, m_soundVolume); + conf.setValue(KEY_SOUND_MUTED, m_soundMuted); conf.setValue(KEY_AUDIO_OUTPUT_DEVICE, m_outputDeviceId); } diff --git a/src/configuration/configuration.h b/src/configuration/configuration.h index 6f174fd0a..ac6c0f310 100644 --- a/src/configuration/configuration.h +++ b/src/configuration/configuration.h @@ -357,6 +357,8 @@ class NODISCARD Configuration final int m_soundVolume = 50; QByteArray m_outputDeviceId; bool m_unlocked = false; + bool m_musicMuted = false; + bool m_soundMuted = false; public: explicit AudioSettings() = default; @@ -371,6 +373,13 @@ class NODISCARD Configuration final m_changeMonitor.notifyAll(); } + NODISCARD bool isMusicMuted() const { return m_musicMuted; } + void setMusicMuted(const bool muted) + { + m_musicMuted = muted; + m_changeMonitor.notifyAll(); + } + NODISCARD int getSoundVolume() const { return m_soundVolume; } void setSoundVolume(const int volume) { @@ -378,6 +387,13 @@ class NODISCARD Configuration final m_changeMonitor.notifyAll(); } + NODISCARD bool isSoundMuted() const { return m_soundMuted; } + void setSoundMuted(const bool muted) + { + m_soundMuted = muted; + m_changeMonitor.notifyAll(); + } + NODISCARD const QByteArray &getOutputDeviceId() const { return m_outputDeviceId; } void setOutputDeviceId(const QByteArray &id) { diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 110d7b009..1bde98840 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -521,6 +521,12 @@ void MainWindow::wireConnections() &FindRoomsDlg::sig_editSelection, this, &MainWindow::slot_onEditRoomSelection); + + setConfig().audio.registerChangeCallback(m_lifetime, [this]() { + const auto &audio = getConfig().audio; + muteMusicAct->setChecked(audio.isMusicMuted()); + muteSoundAct->setChecked(audio.isSoundMuted()); + }); } void MainWindow::slot_log(const QString &mod, const QString &message) @@ -1027,6 +1033,26 @@ void MainWindow::createActions() rebuildMeshesAct->setStatusTip(tr("Reconstruct the world mesh to fix graphical rendering bugs")); rebuildMeshesAct->setCheckable(false); connect(rebuildMeshesAct, &QAction::triggered, getCanvas(), &MapCanvas::slot_rebuildMeshes); + + muteMusicAct = new QAction(QIcon::fromTheme("audio-volume-muted", QIcon(":/icons/audiocfg.png")), + tr("Mute Music"), + this); + muteMusicAct->setShortcut(tr("Ctrl+M")); + muteMusicAct->setCheckable(true); + muteMusicAct->setStatusTip(tr("Mute/Unmute Music")); + connect(muteMusicAct, &QAction::triggered, this, [](bool checked) { + setConfig().audio.setMusicMuted(checked); + }); + + muteSoundAct = new QAction(QIcon::fromTheme("audio-volume-muted", QIcon(":/icons/audiocfg.png")), + tr("Mute Sound"), + this); + muteSoundAct->setShortcut(tr("Ctrl+Shift+M")); + muteSoundAct->setCheckable(true); + muteSoundAct->setStatusTip(tr("Mute/Unmute Sound")); + connect(muteSoundAct, &QAction::triggered, this, [](bool checked) { + setConfig().audio.setSoundMuted(checked); + }); } static void setConfigMapMode(const MapModeEnum mode) @@ -1211,6 +1237,10 @@ void MainWindow::setupMenuBar() } viewMenu->addAction(alwaysOnTopAct); + QMenu *audioMenu = menuBar()->addMenu(tr("&Audio")); + audioMenu->addAction(muteMusicAct); + audioMenu->addAction(muteSoundAct); + settingsMenu = menuBar()->addMenu(tr("&Tools")); QMenu *clientMenu = settingsMenu->addMenu(QIcon(":/icons/terminal.png"), tr("&Integrated Mud Client")); diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h index c2393c800..43e4fca9c 100644 --- a/src/mainwindow/mainwindow.h +++ b/src/mainwindow/mainwindow.h @@ -238,6 +238,8 @@ class NODISCARD_QOBJECT MainWindow final : public QMainWindow QAction *forceRoomAct = nullptr; QAction *releaseAllPathsAct = nullptr; QAction *rebuildMeshesAct = nullptr; + QAction *muteMusicAct = nullptr; + QAction *muteSoundAct = nullptr; std::unique_ptr m_configDialog; diff --git a/src/media/MusicManager.cpp b/src/media/MusicManager.cpp index cb35d93ce..ce6247e15 100644 --- a/src/media/MusicManager.cpp +++ b/src/media/MusicManager.cpp @@ -242,7 +242,7 @@ void MusicManager::updateVolumes() auto &cfg = getConfig().audio; float masterVol = static_cast(cfg.getMusicVolume()) / 100.0f; - bool canPlay = cfg.isUnlocked() && masterVol > 0; + bool canPlay = cfg.isUnlocked() && masterVol > 0 && !cfg.isMusicMuted(); auto &active = m_channels[m_activeChannel]; if (canPlay && active.player->playbackState() == QMediaPlayer::StoppedState && !active.file.isEmpty()) { @@ -251,7 +251,8 @@ void MusicManager::updateVolumes() } active.player->play(); applyPendingPosition(m_activeChannel); - } else if (masterVol <= 0 && active.player->playbackState() == QMediaPlayer::PlayingState) { + } else if ((masterVol <= 0 || cfg.isMusicMuted()) + && active.player->playbackState() == QMediaPlayer::PlayingState) { if (!active.file.isEmpty()) { m_cachedPositions.insert(active.file, new qint64(active.player->position())); } @@ -293,7 +294,8 @@ void MusicManager::applyPendingPosition(int channelIndex) void MusicManager::updateChannelVolume(int channelIndex) { - float masterVol = static_cast(getConfig().audio.getMusicVolume()) / 100.0f; + auto &cfg = getConfig().audio; + float masterVol = cfg.isMusicMuted() ? 0.0f : static_cast(cfg.getMusicVolume()) / 100.0f; m_channels[channelIndex].audioOutput->setVolume(masterVol * m_channels[channelIndex].fadeVolume); } diff --git a/src/media/SfxManager.cpp b/src/media/SfxManager.cpp index be1db684f..00f482420 100644 --- a/src/media/SfxManager.cpp +++ b/src/media/SfxManager.cpp @@ -32,7 +32,7 @@ SfxManager::SfxManager(MediaLibrary &library, QObject *const parent) void SfxManager::playSound(MAYBE_UNUSED const QString &soundName) { auto &cfg = getConfig().audio; - if (!cfg.isUnlocked() || cfg.getSoundVolume() <= 0) { + if (!cfg.isUnlocked() || cfg.getSoundVolume() <= 0 || cfg.isSoundMuted()) { return; } @@ -60,7 +60,7 @@ void SfxManager::playFromData(const QByteArray &data, const QString &soundName) } auto &cfg = getConfig().audio; - if (!cfg.isUnlocked() || cfg.getSoundVolume() <= 0) { + if (!cfg.isUnlocked() || cfg.getSoundVolume() <= 0 || cfg.isSoundMuted()) { return; } @@ -106,7 +106,9 @@ void SfxManager::startEffect(const QUrl &url, const QString &tmpToDelete) void SfxManager::updateVolume() { #ifndef MMAPPER_NO_AUDIO - m_output->setVolume(static_cast(getConfig().audio.getSoundVolume()) / 100.0f); + auto &cfg = getConfig().audio; + float volume = cfg.isSoundMuted() ? 0.0f : static_cast(cfg.getSoundVolume()) / 100.0f; + m_output->setVolume(volume); #endif }