From 9601f26d501a9cff5ca5a4b371258625841d4c06 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Wed, 19 Aug 2020 11:57:45 -0700 Subject: [PATCH 1/4] First pass at audio with QtMultimedia --- CMakeLists.txt | 8 +- oshw-qt/CMakeLists.txt | 4 +- oshw-qt/TWApp.cpp | 3 +- oshw-qt/TWMainWnd.cpp | 189 ++++++++++++++++++++++++++++++++++++++++- oshw-qt/TWMainWnd.h | 20 ++++- 5 files changed, 212 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 05373007..54e55e25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,13 +20,13 @@ else() message(FATAL_ERROR "${OSHW} is not a valid OSHW setting") endif() -# We still require SDL even for the Qt build... -find_package(SDL REQUIRED) -if(OSHW STREQUAL "qt") +if(OSHW STREQUAL "sdl") + find_package(SDL REQUIRED) +elseif(OSHW STREQUAL "qt") set(CMAKE_AUTOUIC TRUE) set(CMAKE_AUTOMOC TRUE) set(CMAKE_AUTORCC TRUE) - find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Xml) + find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Multimedia Xml) add_definitions(-DTWPLUSPLUS) endif() diff --git a/oshw-qt/CMakeLists.txt b/oshw-qt/CMakeLists.txt index 8471d7d5..ac3e4e62 100644 --- a/oshw-qt/CMakeLists.txt +++ b/oshw-qt/CMakeLists.txt @@ -4,8 +4,6 @@ target_sources(oshw-qt PRIVATE ../generic/_in.cpp ../generic/tile.c ../generic/timer.c - ../oshw-sdl/sdlsfx.h - ../oshw-sdl/sdlsfx.c oshwbind.h oshwbind.cpp CCMetaData.h @@ -29,9 +27,9 @@ target_include_directories(oshw-qt PRIVATE ) target_link_libraries(oshw-qt PUBLIC - ${SDL_LIBRARY} Qt5::Core Qt5::Gui Qt5::Widgets + Qt5::Multimedia Qt5::Xml ) diff --git a/oshw-qt/TWApp.cpp b/oshw-qt/TWApp.cpp index 00157845..6e198b36 100644 --- a/oshw-qt/TWApp.cpp +++ b/oshw-qt/TWApp.cpp @@ -88,8 +88,7 @@ bool TileWorldApp::Initialize(bool bSilence, int nSoundBufSize, if ( ! ( _generictimerinitialize(bShowHistogram) && _generictileinitialize() && - _genericinputinitialize() && - _sdlsfxinitialize(bSilence, nSoundBufSize) + _genericinputinitialize() ) ) return false; diff --git a/oshw-qt/TWMainWnd.cpp b/oshw-qt/TWMainWnd.cpp index 9474c143..128d76b0 100644 --- a/oshw-qt/TWMainWnd.cpp +++ b/oshw-qt/TWMainWnd.cpp @@ -53,11 +53,22 @@ extern int pedanticmode; #include #include +#include +#include +#include + #include #include #include +/* Some generic default settings for the audio output. + */ +#define DEFAULT_SND_FREQ 22050 +#define DEFAULT_SND_CHAN 1 +#define DEFAULT_SND_SIZE 16 +#define DEFAULT_SND_TYPE QAudioFormat::SignedInt + class TWStyledItemDelegate : public QStyledItemDelegate { public: @@ -204,6 +215,8 @@ TileWorldMainWnd::TileWorldMainWnd(QWidget* pParent, Qt::WindowFlags flags) m_bWindowClosed(false), m_pSurface(), m_pInvSurface(), + m_bEnableAudio(false), + m_fVolume(1.0), m_nKeyState(), m_shortMessages(), m_bKbdRepeatEnabled(true), @@ -242,7 +255,17 @@ TileWorldMainWnd::TileWorldMainWnd(QWidget* pParent, Qt::WindowFlags flags) m_pTblList->setItemDelegate(new TWStyledItemDelegate(m_pTblList)); m_pTextBrowser->setSearchPaths(QStringList{ QString::fromLocal8Bit(seriesdatdir) }); - + + m_pSoundThread = new QThread(this); + m_pSoundThread->start(); + m_sounds.resize(SND_COUNT); + /*QAudioFormat audioFormat; + audioFormat.setSampleRate(DEFAULT_SND_FREQ); + audioFormat.setChannelCount(DEFAULT_SND_CHAN); + audioFormat.setSampleSize(DEFAULT_SND_SIZE); + audioFormat.setSampleType(DEFAULT_SND_TYPE); + m_pAudioOut = new QAudioOutput(audioFormat, this);*/ + g_pApp->installEventFilter(this); connect(m_pTblList, &QTableView::activated, this, &TileWorldMainWnd::OnListItemActivated); @@ -280,10 +303,13 @@ TileWorldMainWnd::TileWorldMainWnd(QWidget* pParent, Qt::WindowFlags flags) TileWorldMainWnd::~TileWorldMainWnd() { + m_pSoundThread->quit(); g_pApp->removeEventFilter(this); TW_FreeSurface(m_pInvSurface); TW_FreeSurface(m_pSurface); + + m_pSoundThread->wait(1000); } @@ -1360,6 +1386,167 @@ void TileWorldMainWnd::SetSubtitle(const char* szSubtitle) } +/* Activate or deactivate the sound system. Qt manages this for us, so + * we need only track whether it's enabled by the game engine. + */ +int setaudiosystem(int active) +{ + g_pMainWnd->EnableAudio(!!active); + return TRUE; +} + +void TileWorldMainWnd::EnableAudio(bool bEnabled) +{ + m_bEnableAudio = bEnabled; +} + +/* Load a single wave file into memory. The wave data is converted to + * the format expected by the sound device. + */ +int loadsfxfromfile(int index, char const *filename) +{ + return g_pMainWnd->LoadSoundEffect(index, filename); +} + +bool TileWorldMainWnd::LoadSoundEffect(int index, const char* szFilename) +{ + if (index < 0 || index >= m_sounds.size()) + return false; + + freesfx(index); + if (szFilename) + { + QSoundEffect* pSoundEffect = new QSoundEffect(this); + pSoundEffect->setSource(QUrl::fromLocalFile(QString::fromLocal8Bit(szFilename))); + pSoundEffect->setVolume(m_fVolume); + if (index >= SND_ONESHOT_COUNT) + pSoundEffect->setLoopCount(QSoundEffect::Infinite); + pSoundEffect->moveToThread(m_pSoundThread); + m_sounds[index] = pSoundEffect; + } + + return true; +} + +/* Release all memory for the given sound effect. + */ +void freesfx(int index) +{ + g_pMainWnd->FreeSoundEffect(index); +} + +void TileWorldMainWnd::FreeSoundEffect(int index) +{ + if (index < 0 || index >= m_sounds.size()) + return; + + if (m_sounds[index]) + { + // Defer deletion in case the effect is still playing. + m_sounds[index]->deleteLater(); + m_sounds[index] = nullptr; + } +} + +/* Set the current volume level to v. If display is true, the + * new volume level is displayed to the user. + */ +int setvolume(int v, int display) +{ + if (v < 0) + v = 0; + else if (v > 10) + v = 10; + setintsetting("volume", v); + + // Qt uses a floating-point volume in [0.0, 1.0] + g_pMainWnd->SetAudioVolume(qreal(v) / 10.0); + + if (display) { + char buf[16]; + snprintf(buf, sizeof(buf), "Volume: %d", v); + setdisplaymsg(buf, 1000, 1000); + } + return TRUE; +} + +void TileWorldMainWnd::SetAudioVolume(qreal fVolume) +{ + m_fVolume = fVolume; + for (QSoundEffect* pSoundEffect : m_sounds) + { + if (pSoundEffect) + pSoundEffect->setVolume(fVolume); + } +} + +/* Change the current volume level by delta. If display is true, the + * new volume level is displayed to the user. + */ +int changevolume(int delta, int display) +{ + int volume = int(g_pMainWnd->GetAudioVolume() * 10.0); + return setvolume(volume + delta, display); +} + +/* If action is negative, stop playing all sounds immediately. + * Otherwise, just temporarily pause or unpause sound-playing. + */ +void setsoundeffects(int action) +{ + if (action < 0) + { + for (int i = 0; i < SND_COUNT; ++i) + g_pMainWnd->StopSoundEffect(i); + } + else + { + // TODO + } +} + +/* Select the sounds effects to be played. sfx is a bitmask of sound + * effect indexes. Any continuous sounds that are not included in sfx + * are stopped. One-shot sounds that are included in sfx are + * restarted. + */ +void playsoundeffects(unsigned long sfx) +{ + int i; + unsigned long flag; + for (i = 0, flag = 1u; i < SND_COUNT; ++i, flag <<= 1) + { + if (sfx & flag) + g_pMainWnd->PlaySoundEffect(i); + else if (i >= SND_ONESHOT_COUNT) + g_pMainWnd->StopSoundEffect(i); + } +} + +void TileWorldMainWnd::PlaySoundEffect(int index) +{ + if (index < 0 || index >= m_sounds.size()) + return; + + QSoundEffect* pSoundEffect = m_sounds[index]; + if (pSoundEffect) + { + if (index < SND_ONESHOT_COUNT || !pSoundEffect->isPlaying()) + QMetaObject::invokeMethod(pSoundEffect, &QSoundEffect::play); + } +} + +void TileWorldMainWnd::StopSoundEffect(int index) +{ + if (index < 0 || index >= m_sounds.size()) + return; + + QSoundEffect* pSoundEffect = m_sounds[index]; + if (pSoundEffect && pSoundEffect->isPlaying()) + QMetaObject::invokeMethod(pSoundEffect, &QSoundEffect::stop); +} + + /* Display a message to the user. cfile and lineno can be NULL and 0 * respectively; otherwise, they identify the source code location * where this function was called from. prefix is an optional string diff --git a/oshw-qt/TWMainWnd.h b/oshw-qt/TWMainWnd.h index 4550ac0d..71872d6b 100644 --- a/oshw-qt/TWMainWnd.h +++ b/oshw-qt/TWMainWnd.h @@ -23,6 +23,8 @@ #include class QSortFilterProxyModel; +class QAudioOutput; +class QSoundEffect; class TileWorldMainWnd : public QMainWindow, protected Ui::TWMainWnd { @@ -58,7 +60,15 @@ class TileWorldMainWnd : public QMainWindow, protected Ui::TWMainWnd InputPromptType eInputType, int (*pfnInputCallback)()); int GetSelectedRuleset(); void SetSubtitle(const char* szSubtitle); - + + void EnableAudio(bool bEnabled); + bool LoadSoundEffect(int index, const char* szFilename); + void FreeSoundEffect(int index); + void PlaySoundEffect(int index); + void StopSoundEffect(int index); + void SetAudioVolume(qreal fVolume); + qreal GetAudioVolume() const { return m_fVolume; } + void ReadExtensions(gameseries* pSeries); void Narrate(CCX::Text CCX::Level::*pmTxt, bool bForce = false); @@ -99,7 +109,13 @@ private slots: Qt_Surface* m_pSurface; Qt_Surface* m_pInvSurface; TW_Rect m_disploc; - + + //QAudioOutput* m_pAudioOut; + bool m_bEnableAudio; + qreal m_fVolume; + QThread* m_pSoundThread; + QVector m_sounds; + uint8_t m_nKeyState[TWK_LAST]; struct MessageData{ QString sMsg; uint32_t nMsgUntil, nMsgBoldUntil; }; From 56004f9c4cfe718e9cb96cdaae6541a5d528989a Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 3 Sep 2020 15:12:49 -0700 Subject: [PATCH 2/4] Use loopCount to control stopping sounds. The stop() method apparently detaches the underlying device handle, which causes a noticeable delay on the GUI thread when stopping a repeating sound effect. --- oshw-qt/TWMainWnd.cpp | 14 ++++---------- oshw-qt/TWMainWnd.h | 1 - 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/oshw-qt/TWMainWnd.cpp b/oshw-qt/TWMainWnd.cpp index 128d76b0..01ec674a 100644 --- a/oshw-qt/TWMainWnd.cpp +++ b/oshw-qt/TWMainWnd.cpp @@ -256,8 +256,6 @@ TileWorldMainWnd::TileWorldMainWnd(QWidget* pParent, Qt::WindowFlags flags) m_pTextBrowser->setSearchPaths(QStringList{ QString::fromLocal8Bit(seriesdatdir) }); - m_pSoundThread = new QThread(this); - m_pSoundThread->start(); m_sounds.resize(SND_COUNT); /*QAudioFormat audioFormat; audioFormat.setSampleRate(DEFAULT_SND_FREQ); @@ -303,13 +301,10 @@ TileWorldMainWnd::TileWorldMainWnd(QWidget* pParent, Qt::WindowFlags flags) TileWorldMainWnd::~TileWorldMainWnd() { - m_pSoundThread->quit(); g_pApp->removeEventFilter(this); TW_FreeSurface(m_pInvSurface); TW_FreeSurface(m_pSurface); - - m_pSoundThread->wait(1000); } @@ -1419,9 +1414,6 @@ bool TileWorldMainWnd::LoadSoundEffect(int index, const char* szFilename) QSoundEffect* pSoundEffect = new QSoundEffect(this); pSoundEffect->setSource(QUrl::fromLocalFile(QString::fromLocal8Bit(szFilename))); pSoundEffect->setVolume(m_fVolume); - if (index >= SND_ONESHOT_COUNT) - pSoundEffect->setLoopCount(QSoundEffect::Infinite); - pSoundEffect->moveToThread(m_pSoundThread); m_sounds[index] = pSoundEffect; } @@ -1531,8 +1523,10 @@ void TileWorldMainWnd::PlaySoundEffect(int index) QSoundEffect* pSoundEffect = m_sounds[index]; if (pSoundEffect) { + if (index >= SND_ONESHOT_COUNT) + pSoundEffect->setLoopCount(QSoundEffect::Infinite); if (index < SND_ONESHOT_COUNT || !pSoundEffect->isPlaying()) - QMetaObject::invokeMethod(pSoundEffect, &QSoundEffect::play); + pSoundEffect->play(); } } @@ -1543,7 +1537,7 @@ void TileWorldMainWnd::StopSoundEffect(int index) QSoundEffect* pSoundEffect = m_sounds[index]; if (pSoundEffect && pSoundEffect->isPlaying()) - QMetaObject::invokeMethod(pSoundEffect, &QSoundEffect::stop); + pSoundEffect->setLoopCount(0); } diff --git a/oshw-qt/TWMainWnd.h b/oshw-qt/TWMainWnd.h index 71872d6b..d4fbc04c 100644 --- a/oshw-qt/TWMainWnd.h +++ b/oshw-qt/TWMainWnd.h @@ -113,7 +113,6 @@ private slots: //QAudioOutput* m_pAudioOut; bool m_bEnableAudio; qreal m_fVolume; - QThread* m_pSoundThread; QVector m_sounds; uint8_t m_nKeyState[TWK_LAST]; From 8899fe906f7a11152cdc0d16f36f6f3409b13d93 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 3 Sep 2020 15:30:01 -0700 Subject: [PATCH 3/4] Add macOS and Windows CI builds (Qt only). Easier now that SDL isn't required. --- .github/workflows/macos-ci.yml | 26 ++++++++++++++++++++++++++ .github/workflows/ubuntu-ci.yml | 2 +- .github/workflows/win-ci.yml | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/macos-ci.yml create mode 100644 .github/workflows/win-ci.yml diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml new file mode 100644 index 00000000..f48f1630 --- /dev/null +++ b/.github/workflows/macos-ci.yml @@ -0,0 +1,26 @@ +name: macOS-CI +on: [push, pull_request] + +env: + QT_VERSION: "5.12.9" + MACOSX_DEPLOYMENT_TARGET: "10.12" + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: Install Qt5 + uses: jurplel/install-qt-action@v2 + with: + arch: clang_64 + version: ${{ env.QT_VERSION }} + - name: Build tworld2 + run: | + cd $GITHUB_WORKSPACE + mkdir build-qt && cd build-qt + cmake -DCMAKE_BUILD_TYPE=Release -DOSHW=qt \ + -DCMAKE_PREFIX_PATH="$RUNNER_WORKSPACE/Qt/$QT_VERSION/clang_64" \ + -DCMAKE_INSTALL_PREFIX="$GITHUB_WORKSPACE/dist-qt" .. + make -j2 + make install diff --git a/.github/workflows/ubuntu-ci.yml b/.github/workflows/ubuntu-ci.yml index 754caa14..115aea6b 100644 --- a/.github/workflows/ubuntu-ci.yml +++ b/.github/workflows/ubuntu-ci.yml @@ -9,7 +9,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install qtbase5-dev libsdl1.2-dev + sudo apt install qtbase5-dev qtmultimedia5-dev libsdl1.2-dev - name: Build tworld (SDL) run: | mkdir build-sdl && cd build-sdl diff --git a/.github/workflows/win-ci.yml b/.github/workflows/win-ci.yml new file mode 100644 index 00000000..df73f635 --- /dev/null +++ b/.github/workflows/win-ci.yml @@ -0,0 +1,26 @@ +name: Win32-CI +on: [push, pull_request] + +env: + QT_VERSION: "5.15.0" + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + - name: Install Qt5 + uses: jurplel/install-qt-action@v2 + with: + arch: win64_mingw81 + version: ${{ env.QT_VERSION }} + - name: Build tworld2 + run: | + mkdir build + cd build + cmake -G "MinGW Makefiles" -DOSHW=qt ` + "-DCMAKE_PREFIX_PATH=${Env:RUNNER_WORKSPACE}/Qt/${Env:QT_VERSION}/mingw81_64" ` + "-DCMAKE_INSTALL_PREFIX=${Env:$GITHUB_WORKSPACE}/dist-qt" ` + "-DCMAKE_BUILD_TYPE=Debug" .. + cmake --build . + cmake --build . --target install From cd27bf8b27ce115ce57e1db559ca0cf96caa906a Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 3 Sep 2020 17:05:23 -0700 Subject: [PATCH 4/4] Remove unused include dir --- oshw-qt/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/oshw-qt/CMakeLists.txt b/oshw-qt/CMakeLists.txt index ac3e4e62..3d522e8c 100644 --- a/oshw-qt/CMakeLists.txt +++ b/oshw-qt/CMakeLists.txt @@ -23,7 +23,6 @@ target_include_directories(oshw-qt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/generic - ${SDL_INCLUDE_DIR} ) target_link_libraries(oshw-qt PUBLIC