From 54e2179337e34983856a11afe03b503f5729173a Mon Sep 17 00:00:00 2001 From: tGecko Date: Thu, 26 Sep 2024 08:12:41 +0200 Subject: [PATCH] Add playback of background/title music in game list (#1033) * add playback of background/title music * clang_format * add windows multimedia build instructions * fix typo accidentally made to arm * address comments * loop music * feedback * fix CI * add newline * playBGM off by default --------- Co-authored-by: Charles --- .github/workflows/build.yml | 4 +- CMakeLists.txt | 6 ++- documents/building-windows.md | 4 +- src/common/config.cpp | 14 ++++++- src/common/config.h | 2 + src/qt_gui/background_music_player.cpp | 32 +++++++++++++++ src/qt_gui/background_music_player.h | 28 +++++++++++++ src/qt_gui/game_grid_frame.cpp | 9 +++++ src/qt_gui/game_grid_frame.h | 2 + src/qt_gui/game_info.h | 1 + src/qt_gui/game_list_frame.cpp | 9 +++++ src/qt_gui/game_list_frame.h | 2 + src/qt_gui/game_list_utils.h | 1 + src/qt_gui/main_window.cpp | 20 +++++++++ src/qt_gui/main_window.h | 3 ++ src/qt_gui/settings_dialog.cpp | 5 ++- src/qt_gui/settings_dialog.ui | 56 +++++++++++++++++++++----- 17 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 src/qt_gui/background_music_player.cpp create mode 100644 src/qt_gui/background_music_player.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e55bd3..6b7d9501 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,6 +113,7 @@ jobs: target: desktop arch: win64_msvc2019_64 archives: qtbase qttools + modules: qtmultimedia - name: Cache CMake Configuration uses: actions/cache@v4 @@ -237,6 +238,7 @@ jobs: target: desktop arch: clang_64 archives: qtbase qttools + modules: qtmultimedia - name: Cache CMake Configuration uses: actions/cache@v4 @@ -341,7 +343,7 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev - name: Cache CMake Configuration uses: actions/cache@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index c03cc3bc..c9ea1591 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,7 +144,7 @@ add_subdirectory(externals) include_directories(src) if(ENABLE_QT_GUI) - find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network) + find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent LinguistTools Network Multimedia) qt_standard_project_setup() set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) @@ -653,6 +653,8 @@ qt_add_resources(RESOURCE_FILES src/shadps4.qrc) set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/about_dialog.h src/qt_gui/about_dialog.ui + src/qt_gui/background_music_player.cpp + src/qt_gui/background_music_player.h src/qt_gui/cheats_patches.cpp src/qt_gui/cheats_patches.h src/qt_gui/check_update.cpp @@ -760,7 +762,7 @@ else() endif() if (ENABLE_QT_GUI) - target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network) + target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) add_definitions(-DENABLE_QT_GUI) endif() diff --git a/documents/building-windows.md b/documents/building-windows.md index fb1bb93c..48fd09c4 100644 --- a/documents/building-windows.md +++ b/documents/building-windows.md @@ -80,7 +80,7 @@ Normal x86-based computers, follow: 1. Open "MSYS2 MINGW64" from your new applications 2. Run `pacman -Syu`, let it complete; 3. Run `pacman -S --needed git mingw-w64-x86_64-binutils mingw-w64-x86_64-clang mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-ffmpeg` - 1. Optional (Qt only): run `pacman -S --needed mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools` + 1. Optional (Qt only): run `pacman -S --needed mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-qt6-multimedia` 4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4` 5. Run `cd shadPS4` 6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` @@ -94,7 +94,7 @@ ARM64-based computers, follow: 1. Open "MSYS2 CLANGARM64" from your new applications 2. Run `pacman -Syu`, let it complete; 3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg` - 1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools` + 1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools mingw-w64-clang-aarch64-qt6-multimedia` 4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4` 5. Run `cd shadPS4` 6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"` diff --git a/src/common/config.cpp b/src/common/config.cpp index b5248c89..a5ef95bb 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -11,6 +11,7 @@ namespace Config { static bool isNeo = false; static bool isFullscreen = false; +static bool playBGM = false; static u32 screenWidth = 1280; static u32 screenHeight = 720; static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select @@ -64,6 +65,10 @@ bool isFullscreenMode() { return isFullscreen; } +bool getPlayBGM() { + return playBGM; +} + u32 getScreenWidth() { return screenWidth; } @@ -220,6 +225,10 @@ void setFullscreenMode(bool enable) { isFullscreen = enable; } +void setPlayBGM(bool enable) { + playBGM = enable; +} + void setLanguage(u32 language) { m_language = language; } @@ -379,6 +388,7 @@ void load(const std::filesystem::path& path) { isNeo = toml::find_or(general, "isPS4Pro", false); isFullscreen = toml::find_or(general, "Fullscreen", false); + playBGM = toml::find_or(general, "playBGM", false); logFilter = toml::find_or(general, "logFilter", ""); logType = toml::find_or(general, "logType", "sync"); userName = toml::find_or(general, "userName", "shadPS4"); @@ -473,6 +483,7 @@ void save(const std::filesystem::path& path) { data["General"]["isPS4Pro"] = isNeo; data["General"]["Fullscreen"] = isFullscreen; + data["General"]["playBGM"] = playBGM; data["General"]["logFilter"] = logFilter; data["General"]["logType"] = logType; data["General"]["userName"] = userName; @@ -524,6 +535,7 @@ void save(const std::filesystem::path& path) { void setDefaultValues() { isNeo = false; isFullscreen = false; + playBGM = false; screenWidth = 1280; screenHeight = 720; logFilter = ""; @@ -550,4 +562,4 @@ void setDefaultValues() { gpuId = -1; } -} // namespace Config \ No newline at end of file +} // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 63dca08d..ef71d5f0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -13,6 +13,7 @@ void save(const std::filesystem::path& path); bool isNeoMode(); bool isFullscreenMode(); +bool getPlayBGM(); std::string getLogFilter(); std::string getLogType(); std::string getUserName(); @@ -47,6 +48,7 @@ void setGpuId(s32 selectedGpuId); void setScreenWidth(u32 width); void setScreenHeight(u32 height); void setFullscreenMode(bool enable); +void setPlayBGM(bool enable); void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); diff --git a/src/qt_gui/background_music_player.cpp b/src/qt_gui/background_music_player.cpp new file mode 100644 index 00000000..37d87709 --- /dev/null +++ b/src/qt_gui/background_music_player.cpp @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "background_music_player.h" + +BackgroundMusicPlayer::BackgroundMusicPlayer(QObject* parent) : QObject(parent) { + m_mediaPlayer = new QMediaPlayer(this); + m_audioOutput = new QAudioOutput(this); + m_mediaPlayer->setAudioOutput(m_audioOutput); + m_mediaPlayer->setLoops(QMediaPlayer::Infinite); +} + +void BackgroundMusicPlayer::playMusic(const QString& snd0path) { + if (snd0path.isEmpty()) { + stopMusic(); + return; + } + const auto newMusic = QUrl::fromLocalFile(snd0path); + if (m_mediaPlayer->playbackState() == QMediaPlayer::PlayingState && + m_currentMusic == newMusic) { + // already playing the correct music + return; + } + + m_currentMusic = newMusic; + m_mediaPlayer->setSource(newMusic); + m_mediaPlayer->play(); +} + +void BackgroundMusicPlayer::stopMusic() { + m_mediaPlayer->stop(); +} diff --git a/src/qt_gui/background_music_player.h b/src/qt_gui/background_music_player.h new file mode 100644 index 00000000..52f44f43 --- /dev/null +++ b/src/qt_gui/background_music_player.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +class BackgroundMusicPlayer : public QObject { + Q_OBJECT + +public: + static BackgroundMusicPlayer& getInstance() { + static BackgroundMusicPlayer instance; + return instance; + } + + void playMusic(const QString& snd0path); + void stopMusic(); + +private: + BackgroundMusicPlayer(QObject* parent = nullptr); + + QMediaPlayer* m_mediaPlayer; + QAudioOutput* m_audioOutput; + QUrl m_currentMusic; +}; \ No newline at end of file diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 9fba0c47..96ec0e93 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -39,6 +39,15 @@ GameGridFrame::GameGridFrame(std::shared_ptr game_info_get, QWidg }); } +void GameGridFrame::PlayBackgroundMusic(QTableWidgetItem* item) { + if (!item) { + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + const auto snd0path = QString::fromStdString(m_game_info->m_games[item->row()].snd0_path); + BackgroundMusicPlayer::getInstance().playMusic(snd0path); +} + void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool fromSearch) { QVector m_games_; this->clearContents(); diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 50b53a58..0083fd68 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -5,6 +5,7 @@ #include +#include "background_music_player.h" #include "common/config.h" #include "game_info.h" #include "game_list_utils.h" @@ -19,6 +20,7 @@ Q_SIGNALS: public Q_SLOTS: void SetGridBackgroundImage(int row, int column); void RefreshGridBackgroundImage(); + void PlayBackgroundMusic(QTableWidgetItem* item); private: QImage backgroundImage; diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index a4bcd20e..5ef64f07 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -32,6 +32,7 @@ public: QString iconpath = QString::fromStdString(game.icon_path); game.icon = QImage(iconpath); game.pic_path = game.path + "/sce_sys/pic1.png"; + game.snd0_path = game.path + "/sce_sys/snd0.at9"; if (const auto title = psf.GetString("TITLE"); title.has_value()) { game.name = *title; } diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index b17da127..bb3fd2e6 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -68,6 +68,15 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QWidg }); } +void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { + if (!item) { + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + const auto snd0path = QString::fromStdString(m_game_info->m_games[item->row()].snd0_path); + BackgroundMusicPlayer::getInstance().playMusic(snd0path); +} + void GameListFrame::PopulateGameList() { this->setRowCount(m_game_info->m_games.size()); ResizeIcons(icon_size); diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index ab70e6f6..a1ec5c56 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -5,6 +5,7 @@ #include +#include "background_music_player.h" #include "game_info.h" #include "game_list_utils.h" #include "gui_context_menus.h" @@ -21,6 +22,7 @@ public Q_SLOTS: void RefreshListBackgroundImage(); void SortNameAscending(int columnIndex); void SortNameDescending(int columnIndex); + void PlayBackgroundMusic(QTableWidgetItem* item); private: void SetTableItem(int row, int column, QString itemStr); diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 476c9003..93757d07 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -7,6 +7,7 @@ struct GameInfo { std::string path; // root path of game directory (normally directory that contains eboot.bin) std::string icon_path; // path of icon0.png std::string pic_path; // path of pic1.png + std::string snd0_path; // path of snd0.at9 QImage icon; std::string size; // variables extracted from param.sfo diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 535e470f..6a1e0b42 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -501,9 +501,29 @@ void MainWindow::CreateConnects() { isIconBlack = false; } }); + + connect(m_game_grid_frame.get(), &QTableWidget::cellClicked, this, + &MainWindow::PlayBackgroundMusic); + connect(m_game_list_frame.get(), &QTableWidget::cellClicked, this, + &MainWindow::PlayBackgroundMusic); +} + +void MainWindow::PlayBackgroundMusic() { + if (isGameRunning || !Config::getPlayBGM()) { + BackgroundMusicPlayer::getInstance().stopMusic(); + return; + } + int itemID = isTableList ? m_game_list_frame->currentItem()->row() + : m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt + + m_game_grid_frame->crtColumn; + + const auto snd0path = QString::fromStdString(m_game_info->m_games[itemID].snd0_path); + BackgroundMusicPlayer::getInstance().playMusic(snd0path); } void MainWindow::StartGame() { + isGameRunning = true; + BackgroundMusicPlayer::getInstance().stopMusic(); QString gamePath = ""; int table_mode = Config::getTableMode(); if (table_mode == 0) { diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 6da94de1..9294ef8c 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -7,6 +7,7 @@ #include #include +#include "background_music_player.h" #include "common/config.h" #include "common/path_util.h" #include "core/file_format/psf.h" @@ -63,9 +64,11 @@ private: void BootGame(); void AddRecentFiles(QString filePath); void LoadTranslation(); + void PlayBackgroundMusic(); QIcon RecolorIcon(const QIcon& icon, bool isWhite); bool isIconBlack = false; bool isTableList = true; + bool isGameRunning = false; QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; QActionGroup* m_theme_act_group = nullptr; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 1986be5b..f05b0f92 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -134,6 +134,9 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge auto checkUpdate = new CheckUpdate(true); checkUpdate->exec(); }); + + connect(ui->playBGMCheckBox, &QCheckBox::stateChanged, this, + [](int val) { Config::setPlayBGM(val); }); } // GPU TAB @@ -192,7 +195,7 @@ void SettingsDialog::LoadValuesFromConfig() { ui->dumpShadersCheckBox->setChecked(Config::dumpShaders()); ui->nullGpuCheckBox->setChecked(Config::nullGpu()); ui->dumpPM4CheckBox->setChecked(Config::dumpPM4()); - + ui->playBGMCheckBox->setChecked(Config::getPlayBGM()); ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode()); ui->showSplashCheckBox->setChecked(Config::showSplash()); ui->ps4proCheckBox->setChecked(Config::isNeoMode()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 5184aa0a..0601ac53 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -52,7 +52,7 @@ 0 0 836 - 428 + 432 @@ -70,7 +70,7 @@ - + @@ -340,19 +340,55 @@ + + + + + + + 0 + 0 + + + + GUI Settings + + + + + 10 + 30 + 241 + 41 + + + + + + + + 0 + 0 + + + + Play title music + + + + + + + + + - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Expanding - - - - 500 - 20 - + QSizePolicy::Policy::Expanding