From 528e407d774d09f7758b1333f8b348349aac92d7 Mon Sep 17 00:00:00 2001 From: Matt Styles Date: Sat, 6 Jul 2024 11:51:17 +0100 Subject: [PATCH 01/12] fix include paths on linux --- samples/golf/src/golf/Networking.hpp | 3 ++- samples/golf/src/golf/server/ServerVoice.hpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/golf/src/golf/Networking.hpp b/samples/golf/src/golf/Networking.hpp index 1b2944b9d..1765cc59e 100644 --- a/samples/golf/src/golf/Networking.hpp +++ b/samples/golf/src/golf/Networking.hpp @@ -1,6 +1,6 @@ /*----------------------------------------------------------------------- -Matt Marchant 2022 +Matt Marchant 2022 - 2024 http://trederia.blogspot.com Super Video Golf - zlib licence. @@ -34,5 +34,6 @@ source distribution. namespace net = gns; #else #include +#include namespace net = cro; #endif diff --git a/samples/golf/src/golf/server/ServerVoice.hpp b/samples/golf/src/golf/server/ServerVoice.hpp index 61c947c6c..2f2a31c9c 100644 --- a/samples/golf/src/golf/server/ServerVoice.hpp +++ b/samples/golf/src/golf/server/ServerVoice.hpp @@ -30,7 +30,7 @@ source distribution. #pragma once #include "../CommonConsts.hpp" -#include "Networking.hpp" +#include "../Networking.hpp" #include From b1036db8895e6ebcdbe0ea1f9050afdcf34c7e03 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 10:22:17 +0100 Subject: [PATCH 02/12] add lens flare toggle to options menu --- crogine.sln | 3 +- samples/golf/buildnumber.h | 4 +- samples/golf/src/GolfGame.cpp | 9 +++ samples/golf/src/golf/Networking.hpp | 1 + samples/golf/src/golf/OptionsEnum.inl | 1 + samples/golf/src/golf/OptionsState.cpp | 65 +++++++++++++++++-- samples/golf/src/golf/SharedStateData.hpp | 1 + samples/scratchpad/src/MyApp.cpp | 2 +- .../src/trackoverlay/TrackOverlayState.cpp | 2 +- 9 files changed, 78 insertions(+), 10 deletions(-) diff --git a/crogine.sln b/crogine.sln index 50b805c90..f90881702 100644 --- a/crogine.sln +++ b/crogine.sln @@ -268,6 +268,7 @@ Global {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|ARM64.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.ActiveCfg = Debug|x64 + {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x64.Build.0 = Debug|x64 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.Debug|x86.ActiveCfg = Debug|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 {64579103-DEC3-41E4-971F-9C0DF996BE8B}.DebugASan|ARM.ActiveCfg = DebugASan|Win32 @@ -290,7 +291,6 @@ Global {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|ARM64.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.ActiveCfg = Debug|x64 - {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x64.Build.0 = Debug|x64 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.ActiveCfg = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.Debug|x86.Build.0 = Debug|Win32 {B1559428-CDF8-4797-8766-0EA62BDD6D1B}.DebugASan|Any CPU.ActiveCfg = DebugASan|Win32 @@ -321,7 +321,6 @@ Global {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|ARM64.ActiveCfg = Debug|x64 {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|ARM64.Build.0 = Debug|x64 {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|x64.ActiveCfg = Debug|x64 - {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|x64.Build.0 = Debug|x64 {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|x86.ActiveCfg = Debug|Win32 {155BC29B-5143-44CF-AC90-82B82B056E48}.Debug|x86.Build.0 = Debug|Win32 {155BC29B-5143-44CF-AC90-82B82B056E48}.DebugASan|Any CPU.ActiveCfg = Debug|x64 diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index 96dda1d91..458e025b3 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6730 -#define BUILDNUMBER_STR "6730" +#define BUILDNUMBER 6737 +#define BUILDNUMBER_STR "6737" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 0b8285d2a..4585c5b1f 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -1338,6 +1338,10 @@ void GolfGame::loadPreferences() { m_sharedData.crowdDensity = std::clamp(prop.getValue(), 0, CrowdDensityCount - 1); } + else if (name == "use_flare") + { + m_sharedData.useLensFlare = prop.getValue(); + } } } } @@ -1437,6 +1441,10 @@ void GolfGame::loadPreferences() { m_sharedData.useSwingput = prop.getValue(); } + else if (name == "use_flare") + { + m_sharedData.useLensFlare = prop.getValue(); + } } } } @@ -1551,6 +1559,7 @@ void GolfGame::savePreferences() cfg.addProperty("clubset").setValue(m_sharedData.preferredClubSet); cfg.addProperty("press_hold").setValue(m_sharedData.pressHold); cfg.addProperty("use_tts").setValue(m_sharedData.useTTS); + cfg.addProperty("use_flare").setValue(m_sharedData.useLensFlare); cfg.save(path); diff --git a/samples/golf/src/golf/Networking.hpp b/samples/golf/src/golf/Networking.hpp index 1765cc59e..9f8a1a93b 100644 --- a/samples/golf/src/golf/Networking.hpp +++ b/samples/golf/src/golf/Networking.hpp @@ -31,6 +31,7 @@ source distribution. #ifdef USE_GNS #include +#include namespace net = gns; #else #include diff --git a/samples/golf/src/golf/OptionsEnum.inl b/samples/golf/src/golf/OptionsEnum.inl index 98cb5245e..01c89634e 100644 --- a/samples/golf/src/golf/OptionsEnum.inl +++ b/samples/golf/src/golf/OptionsEnum.inl @@ -73,6 +73,7 @@ enum OptionsIndex AVShadowR, AVCrowdL, AVCrowdR, + AVLensFlare, Controls = 300, diff --git a/samples/golf/src/golf/OptionsState.cpp b/samples/golf/src/golf/OptionsState.cpp index e807ab11a..a1f94119d 100644 --- a/samples/golf/src/golf/OptionsState.cpp +++ b/samples/golf/src/golf/OptionsState.cpp @@ -2571,7 +2571,7 @@ void OptionsState::buildAVMenu(cro::Entity parent, const cro::SpriteSheet& sprit entity = createHighlight(glm::vec2(286.f, 10.f)); entity.setLabel("Very high density crowds may cause a drop in performance."); entity.getComponent().setSelectionIndex(AVCrowdL); - entity.getComponent().setNextIndex(AVCrowdR, WindowApply); + entity.getComponent().setNextIndex(AVCrowdR, AVLensFlare); #ifdef _WIN32 if (!Social::isSteamdeck()) { @@ -2596,6 +2596,7 @@ void OptionsState::buildAVMenu(cro::Entity parent, const cro::SpriteSheet& sprit entity.getComponent().setPrevIndex(AVCrowdL, AVShadowR); entity.getComponent().callbacks[cro::UIInput::ButtonDown] = crowdChanged; + //text to speech #ifdef _WIN32 if (!Social::isSteamdeck()) { @@ -2629,8 +2630,8 @@ void OptionsState::buildAVMenu(cro::Entity parent, const cro::SpriteSheet& sprit entity = createHighlight(glm::vec2(69.f, -1.f)); entity.setLabel("Enables text-to-speech for in game text chat.\nUse the Speech option in Windows Control Panel to set advanced options."); entity.getComponent().setSelectionIndex(AVTextToSpeech); - entity.getComponent().setNextIndex(AVCrowdL, WindowAdvanced); - entity.getComponent().setPrevIndex(AVCrowdR, AVBeacon); + entity.getComponent().setNextIndex(AVLensFlare, WindowAdvanced); + entity.getComponent().setPrevIndex(AVLensFlare, AVBeacon); entity.getComponent().callbacks[cro::UIInput::ButtonDown] = uiSystem.addCallback([&](cro::Entity e, cro::ButtonEvent evt) { @@ -2644,6 +2645,62 @@ void OptionsState::buildAVMenu(cro::Entity parent, const cro::SpriteSheet& sprit optEnt.getComponent().addChild(entity.getComponent()); } #endif + + //lens flare + entity = m_scene.createEntity(); + entity.addComponent().setPosition({ 217.f, -5.f, 0.1f }); + entity.addComponent(); + entity.addComponent() = spriteSheet.getSprite("use_flare"); + parent.getComponent().addChild(entity.getComponent()); + auto optEnt = entity; + + //checkbox centre + entity = m_scene.createEntity(); + entity.addComponent().setPosition(glm::vec3(71.f, 1.f, HighlightOffset)); + entity.addComponent().getVertexData() = + { + cro::Vertex2D(glm::vec2(0.f, 7.f), TextGoldColour), + cro::Vertex2D(glm::vec2(0.f), TextGoldColour), + cro::Vertex2D(glm::vec2(7.f), TextGoldColour), + cro::Vertex2D(glm::vec2(7.f, 0.f), TextGoldColour) + }; + entity.getComponent().updateLocalBounds(); + entity.addComponent().active = true; + entity.getComponent().function = + [&](cro::Entity e, float) + { + float scale = m_sharedData.useLensFlare ? 1.f : 0.f; + e.getComponent().setScale(glm::vec2(scale)); + }; + optEnt.getComponent().addChild(entity.getComponent()); + + //highlight + entity = createHighlight(glm::vec2(69.f, -1.f)); + //entity.setLabel("Enables text-to-speech for in game text chat.\nUse the Speech option in Windows Control Panel to set advanced options."); + entity.getComponent().setSelectionIndex(AVLensFlare); +#ifdef _WIN32 + if (!Social::isSteamdeck()) + { + entity.getComponent().setNextIndex(AVTextToSpeech, WindowApply); + entity.getComponent().setPrevIndex(AVTextToSpeech, AVCrowdL); + } + else +#endif + { + entity.getComponent().setNextIndex(AVBeacon, WindowApply); + entity.getComponent().setPrevIndex(AVBeacon, AVCrowdL); + } + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = + uiSystem.addCallback([&](cro::Entity e, cro::ButtonEvent evt) + { + if (activated(evt)) + { + m_sharedData.useLensFlare = !m_sharedData.useLensFlare; + m_audioEnts[AudioID::Accept].getComponent().play(); + m_scene.getActiveCamera().getComponent().active = true; + } + }); + optEnt.getComponent().addChild(entity.getComponent()); } void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, const cro::SpriteSheet& spriteSheet) @@ -3945,7 +4002,7 @@ void OptionsState::createButtons(cro::Entity parent, std::int32_t menuID, std::u #endif upLeftA = AVBeacon; upLeftB = AVBeaconL; - upRightA = AVCrowdL; + upRightA = AVLensFlare; upRightB = AVCrowdR; break; case MenuID::Controls: diff --git a/samples/golf/src/golf/SharedStateData.hpp b/samples/golf/src/golf/SharedStateData.hpp index 9cf394d80..0274601e0 100644 --- a/samples/golf/src/golf/SharedStateData.hpp +++ b/samples/golf/src/golf/SharedStateData.hpp @@ -270,6 +270,7 @@ struct SharedStateData final std::int32_t crowdDensity = 1; bool pressHold = false; //press and hold the action button to select power bool useTTS = false; + bool useLensFlare = true; std::int32_t baseState = 0; //used to tell which state we're returning to from errors etc std::unique_ptr sharedResources; diff --git a/samples/scratchpad/src/MyApp.cpp b/samples/scratchpad/src/MyApp.cpp index a7d10a773..03109de6b 100644 --- a/samples/scratchpad/src/MyApp.cpp +++ b/samples/scratchpad/src/MyApp.cpp @@ -175,7 +175,7 @@ bool MyApp::initialise() m_stateStack.registerState(States::ScratchPad::TrackOverlay); #ifdef CRO_DEBUG_ - m_stateStack.pushState(States::ScratchPad::Retro); + m_stateStack.pushState(States::ScratchPad::TrackOverlay); //m_stateStack.pushState(States::ScratchPad::MainMenu); #else m_stateStack.pushState(States::ScratchPad::MainMenu); diff --git a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp index 1dbc7c8f8..fe7b5dcb6 100644 --- a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp +++ b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp @@ -54,7 +54,7 @@ namespace constexpr glm::vec3 TextPosition(ThumbSize.x + 80.f, ThumbSize.y - 40.f, TextDepth); constexpr float TransitionSpeed = 1.f; //seconds - const cro::Colour BannerColour = cro::Colour(0.f, 0.f, 0.f, 0.3f); + const cro::Colour BannerColour = cro::Colour::Transparent;// cro::Colour(0.f, 0.f, 0.f, 0.3f); const std::string ThumbVertex = R"( From 1cbf41dc4430dcd1c96d0d1f684410046fa3f6bd Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 10:33:17 +0100 Subject: [PATCH 03/12] make lens flare respect current visibility option --- samples/golf/buildnumber.h | 4 ++-- samples/golf/src/golf/GolfStateCameras.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index 458e025b3..be26be32a 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6737 -#define BUILDNUMBER_STR "6737" +#define BUILDNUMBER 6738 +#define BUILDNUMBER_STR "6738" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/golf/GolfStateCameras.cpp b/samples/golf/src/golf/GolfStateCameras.cpp index 0c3fe4a92..8c2cc18db 100644 --- a/samples/golf/src/golf/GolfStateCameras.cpp +++ b/samples/golf/src/golf/GolfStateCameras.cpp @@ -1830,6 +1830,13 @@ void GolfState::setCameraPosition(glm::vec3 position, float height, float viewOf void GolfState::updateLensFlare(cro::Entity e, float) { + if (!m_sharedData.useLensFlare) + { + e.getComponent().setScale(glm::vec2(0.f)); + return; + } + e.getComponent().setScale(glm::vec2(1.f)); + const auto ndcVisible = [](glm::vec2 p) { return p.x >= -1.f && p.x <= 1.f && p.y >= -1.f && p.y <= 1.f; From 44acceaf03e3bc208001f677e5cf730796bc6d69 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 14:04:56 +0100 Subject: [PATCH 04/12] add class for streaming playlist files from disc asynchronously --- .../crogine/audio/sound_system/Playlist.hpp | 103 +++++++++ crogine/src/CMakeLists.txt | 1 + crogine/src/audio/sound_system/Playlist.cpp | 215 ++++++++++++++++++ .../src/trackoverlay/TrackOverlayState.cpp | 22 ++ .../src/trackoverlay/TrackOverlayState.hpp | 7 + windows/crowin.vcxproj | 2 + windows/crowin.vcxproj.filters | 6 + 7 files changed, 356 insertions(+) create mode 100644 crogine/include/crogine/audio/sound_system/Playlist.hpp create mode 100644 crogine/src/audio/sound_system/Playlist.cpp diff --git a/crogine/include/crogine/audio/sound_system/Playlist.hpp b/crogine/include/crogine/audio/sound_system/Playlist.hpp new file mode 100644 index 000000000..c9c8f60dd --- /dev/null +++ b/crogine/include/crogine/audio/sound_system/Playlist.hpp @@ -0,0 +1,103 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2024 +http://trederia.blogspot.com + +crogine - Zlib license. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace cro +{ + /* + \brief Loads and buffers audio files asynchronously so + that they may be streamed continuously to a buffer such + as DynamicAudioStream. + */ + class CRO_EXPORT_API Playlist final + { + public: + Playlist(); //TODO specify a format the audio must match - currently fixed to 16bit stereo + ~Playlist(); + + Playlist(const Playlist&) = delete; + Playlist(Playlist&&) = delete; + Playlist& operator = (const Playlist) = delete; + Playlist& operator = (Playlist&&) = delete; + + + /*! + \brief Add a file path to a file to addd to the playlist + NOTE this must be the FULL PATH to the file, if a relative + path is intended, eg in the assets directory, then make + sure to prepend the path with FileSystem::getResourcePath() + */ + void addPath(const std::string&); + + + /*! + \brief returns a pointer 16 bit Stereo PCM samples + \param count A reference to a std::int32_t which is filled + with the number of *samples* pointed to by the return value - may be 0!! + */ + const std::int16_t* getData(std::int32_t& count); + + private: + + static constexpr std::size_t MaxBuffers = 3u; + + //main thread only + std::array m_silence = {}; //returned when no audio is available + std::atomic m_outBuffer; //current buffer from which data is returned + std::int32_t m_outputOffset; //where we are in the current buffer + + //worker thread only + std::atomic m_inBuffer; //next buffer to be filled with data from thread + + //touched by both threads + std::vector m_filePaths; + std::atomic m_fileIndex; + + std::array, MaxBuffers> m_buffers = {}; + + + std::atomic_bool m_threadRunning; + std::atomic_bool m_loadNextFile; + std::thread m_thread; + std::mutex m_mutex; + + void threadFunc(); + }; +} \ No newline at end of file diff --git a/crogine/src/CMakeLists.txt b/crogine/src/CMakeLists.txt index 2957403e9..3f8e82929 100644 --- a/crogine/src/CMakeLists.txt +++ b/crogine/src/CMakeLists.txt @@ -16,6 +16,7 @@ set(PROJECT_SRC ${PROJECT_DIR}/audio/sound_system/MusicPlayer.cpp ${PROJECT_DIR}/audio/sound_system/OpusEncoder.cpp + ${PROJECT_DIR}/audio/sound_system/Playlist.cpp ${PROJECT_DIR}/audio/sound_system/SoundRecorder.cpp ${PROJECT_DIR}/audio/sound_system/SoundSource.cpp ${PROJECT_DIR}/audio/sound_system/SoundStream.cpp diff --git a/crogine/src/audio/sound_system/Playlist.cpp b/crogine/src/audio/sound_system/Playlist.cpp new file mode 100644 index 000000000..2c2ba69fb --- /dev/null +++ b/crogine/src/audio/sound_system/Playlist.cpp @@ -0,0 +1,215 @@ +/*----------------------------------------------------------------------- + +Matt Marchant 2024 +http://trederia.blogspot.com + +crogine - Zlib license. + +This software is provided 'as-is', without any express or +implied warranty.In no event will the authors be held +liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute +it freely, subject to the following restrictions : + +1. The origin of this software must not be misrepresented; +you must not claim that you wrote the original software. +If you use this software in a product, an acknowledgment +in the product documentation would be appreciated but +is not required. + +2. Altered source versions must be plainly marked as such, +and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any +source distribution. + +-----------------------------------------------------------------------*/ + +#include "../VorbisLoader.hpp" +#include "../WavLoader.hpp" +#include "../Mp3Loader.hpp" + +#include +#include +#include + +#include +#include +#include + +using namespace cro; + +namespace +{ + constexpr std::size_t MaxFiles = 50; + constexpr std::int32_t ChannelCount = 2; +} + +Playlist::Playlist() + : m_outBuffer (0), + m_outputOffset (0), + m_inBuffer (0), + m_fileIndex (0), + m_threadRunning (true), + m_loadNextFile (false), + m_thread (&Playlist::threadFunc, this) +{ + std::fill(m_silence.begin(), m_silence.end(), std::int16_t(0)); +} + +Playlist::~Playlist() +{ + m_threadRunning = false; + m_thread.join(); +} + +//public +void Playlist::addPath(const std::string& path) +{ + if (cro::FileSystem::fileExists(path)) + { + if (m_filePaths.size() < MaxFiles) + { + std::scoped_lock lock(m_mutex); + m_filePaths.push_back(path); + } + else + { + LogW << path << ": not added to playlist, max files reached." << std::endl; + } + } + else + { + LogW << path << ": not added to playlist, file doesn't exist." << std::endl; + } +} + +const std::int16_t* Playlist::getData(std::int32_t& samples) +{ + //return silence if nothing is loaded + if (m_inBuffer == m_outBuffer || + m_buffers[m_outBuffer].empty()) + { + if (!m_filePaths.empty()) + { + m_loadNextFile = true; + } + + samples = static_cast(m_silence.size() / ChannelCount); + return m_silence.data(); + } + + //arbitrary amount - we make sure below to not load the next buffer + //until we know it's safe to do so, so let's send ~double samples per sec (at 60Hz) + static constexpr std::int32_t SampleCount = (44100 / 30) * ChannelCount; + auto available = std::min((static_cast(m_buffers[m_outBuffer].size()) - m_outputOffset), SampleCount); + + samples = available / ChannelCount; //ONE SAMPLE includes 2 CHANNELS + const auto* retVal = &m_buffers[m_outBuffer][m_outputOffset]; + + m_outputOffset += available; + if (m_outputOffset >= m_buffers[m_outBuffer].size()) + { + //only load the next buffer if we're ready + //otherwise buffer loading will loop around and + //load over the top of the current buffer... + m_loadNextFile = (((m_inBuffer + MaxBuffers) - m_outBuffer) % MaxBuffers) == 2; + + m_outputOffset = 0; + m_outBuffer = (m_outBuffer + 1) % MaxBuffers; + } + + return retVal; +} + + +//private +void Playlist::threadFunc() +{ + while (m_threadRunning) + { + if (m_loadNextFile) + { + std::unique_ptr audioFile; + { + std::scoped_lock lock(m_mutex); + auto ext = FileSystem::getFileExtension(m_filePaths[m_fileIndex]); + if (ext == ".ogg") + { + audioFile = std::make_unique(); + } + else if (ext == ".wav") + { + audioFile = std::make_unique(); + } + else if (ext == ".mp3") + { + audioFile = std::make_unique(); + } + else + { + //better to just refuse loading than to get stuck in + //an infinte loop searching for the next file... + m_loadNextFile = false; + m_fileIndex = (m_fileIndex + 1) % m_filePaths.size(); + LogW << m_filePaths[m_fileIndex] << " not a valid audio file" << std::endl; + } + + if (audioFile) + { + if (!audioFile->open(m_filePaths[m_fileIndex]) + || audioFile->getFormat() != Detail::PCMData::Format::STEREO16 + || audioFile->getSampleRate() != 44100) //hmm what about 48KHz? + { + m_loadNextFile = false; + m_fileIndex = (m_fileIndex + 1) % m_filePaths.size(); + LogW << m_filePaths[m_fileIndex] << ": could not open file, or file not 16 bit stereo" << std::endl; + + audioFile.reset(); + } + } + } + + if (audioFile) + { + m_buffers[m_inBuffer].clear(); + + //set a max file length of ~15 minutes + static constexpr std::size_t MaxFileSize = 15 * 60 * 44100 * ChannelCount * sizeof(std::int16_t); + + std::vector temp; + const auto& data = audioFile->getData(); + while (data.data + && temp.size() < MaxFileSize) + { + auto old = temp.size(); + temp.resize(old + data.size); + + std::memcpy(&temp[old], data.data, data.size); + audioFile->getData(); //this actually updates the supposedly const data reference... not the greatest design + } + + if (!temp.empty()) + { + { + std::scoped_lock lock(m_mutex); + m_fileIndex = (m_fileIndex + 1) % m_filePaths.size(); + } + + m_buffers[m_inBuffer].resize(temp.size() / sizeof(std::int16_t)); + std::memcpy(m_buffers[m_inBuffer].data(), temp.data(), temp.size()); + + m_inBuffer = (m_inBuffer + 1) % MaxBuffers; + m_loadNextFile = (((m_inBuffer + MaxBuffers) - m_outBuffer) % MaxBuffers) != 2; //always want to be 2 buffers ahead of output + } + } + + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + } + } +} \ No newline at end of file diff --git a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp index fe7b5dcb6..f9ca97fc2 100644 --- a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp +++ b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include +#include #include #include #include @@ -111,6 +113,7 @@ void main() TrackOverlayState::TrackOverlayState(cro::StateStack& stack, cro::State::Context context) : cro::State (stack, context), + m_audioStream (2,44100), m_gameScene (context.appInstance.getMessageBus()), m_uiScene (context.appInstance.getMessageBus()), m_currentIndex (0), @@ -124,6 +127,16 @@ TrackOverlayState::TrackOverlayState(cro::StateStack& stack, cro::State::Context }); cro::App::getInstance().setClearColour(cro::Colour(0.f, 1.f, 0.f, 0.f)); + + m_playlist.addPath("assets/avatar_voices.mp3"); + m_playlist.addPath("assets/bass_loop.mp3"); + m_playlist.addPath("assets/loop.wav"); + m_playlist.addPath("assets/avatar_voices.ogg"); + m_playlist.addPath("assets/menu.ogg"); + + m_audioEnt = m_uiScene.createEntity(); + m_audioEnt.addComponent(); + m_audioEnt.addComponent(m_audioStream).play(); } //public @@ -180,6 +193,14 @@ void TrackOverlayState::handleMessage(const cro::Message& msg) bool TrackOverlayState::simulate(float dt) { + if (m_audioEnt.getComponent().getState() == cro::AudioEmitter::State::Playing) + { + std::int32_t size = 0; + const auto* data = m_playlist.getData(size); + + m_audioStream.updateBuffer(data, size); + } + m_gameScene.simulate(dt); m_uiScene.simulate(dt); return true; @@ -199,6 +220,7 @@ void TrackOverlayState::addSystems() m_gameScene.addSystem(mb); m_gameScene.addSystem(mb); + m_uiScene.addSystem(mb); m_uiScene.addSystem(mb); m_uiScene.addSystem(mb); m_uiScene.addSystem(mb); diff --git a/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp b/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp index e95413409..ddd49e824 100644 --- a/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp +++ b/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp @@ -4,6 +4,9 @@ #include "../StateIDs.hpp" +#include +#include + #include #include #include @@ -29,6 +32,10 @@ class TrackOverlayState final : public cro::State, public cro::GuiClient private: + cro::Playlist m_playlist; + cro::DynamicAudioStream m_audioStream; + cro::Entity m_audioEnt; + cro::Scene m_gameScene; cro::Scene m_uiScene; cro::ResourceCollection m_resources; diff --git a/windows/crowin.vcxproj b/windows/crowin.vcxproj index 7493be59a..827a7f658 100644 --- a/windows/crowin.vcxproj +++ b/windows/crowin.vcxproj @@ -48,6 +48,7 @@ + @@ -269,6 +270,7 @@ + diff --git a/windows/crowin.vcxproj.filters b/windows/crowin.vcxproj.filters index 10ae7aab0..1e5704cc6 100644 --- a/windows/crowin.vcxproj.filters +++ b/windows/crowin.vcxproj.filters @@ -759,6 +759,9 @@ Header Files\audio\sound system\effects chain + + Header Files\audio\sound system + @@ -1280,6 +1283,9 @@ Source Files\audio\sound system\effects chain + + Source Files\audio\sound system + From 6e575d84f8ac309fcb80a2334fbd9a06a50c2661 Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 15:45:45 +0100 Subject: [PATCH 05/12] refactor golf game to use streaming playlist --- .../crogine/audio/sound_system/Playlist.hpp | 12 +- crogine/src/audio/sound_system/Playlist.cpp | 12 ++ samples/golf/buildnumber.h | 4 +- samples/golf/src/GolfGame.cpp | 54 +++++ samples/golf/src/GolfGame.hpp | 1 + samples/golf/src/M3UPlaylist.cpp | 51 ++--- samples/golf/src/M3UPlaylist.hpp | 17 +- samples/golf/src/golf/DrivingState.cpp | 192 +++++++++++------- samples/golf/src/golf/DrivingState.hpp | 4 + samples/golf/src/golf/GameConsts.cpp | 140 ------------- samples/golf/src/golf/GameConsts.hpp | 7 - samples/golf/src/golf/GolfState.cpp | 1 + samples/golf/src/golf/GolfState.hpp | 3 + samples/golf/src/golf/GolfStateAssets.cpp | 81 ++++++-- samples/golf/src/golf/SharedStateData.hpp | 3 + .../src/trackoverlay/TrackOverlayState.cpp | 19 -- .../src/trackoverlay/TrackOverlayState.hpp | 8 - 17 files changed, 307 insertions(+), 302 deletions(-) diff --git a/crogine/include/crogine/audio/sound_system/Playlist.hpp b/crogine/include/crogine/audio/sound_system/Playlist.hpp index c9c8f60dd..b14c98409 100644 --- a/crogine/include/crogine/audio/sound_system/Playlist.hpp +++ b/crogine/include/crogine/audio/sound_system/Playlist.hpp @@ -44,7 +44,7 @@ namespace cro /* \brief Loads and buffers audio files asynchronously so that they may be streamed continuously to a buffer such - as DynamicAudioStream. + as DynamicAudioStream. Currently only supports 44.1KHz, 16 bit Stereo */ class CRO_EXPORT_API Playlist final { @@ -70,10 +70,16 @@ namespace cro /*! \brief returns a pointer 16 bit Stereo PCM samples \param count A reference to a std::int32_t which is filled - with the number of *samples* pointed to by the return value - may be 0!! + with the number of *samples* pointed to by the return value */ const std::int16_t* getData(std::int32_t& count); + /*! + \brief Returns a COPY of the tracklist (as it otherwise gets manipulated + by multiple threads...) + */ + std::vector getTrackList() const; + private: static constexpr std::size_t MaxBuffers = 3u; @@ -96,7 +102,7 @@ namespace cro std::atomic_bool m_threadRunning; std::atomic_bool m_loadNextFile; std::thread m_thread; - std::mutex m_mutex; + mutable std::mutex m_mutex; void threadFunc(); }; diff --git a/crogine/src/audio/sound_system/Playlist.cpp b/crogine/src/audio/sound_system/Playlist.cpp index 2c2ba69fb..4f84f832a 100644 --- a/crogine/src/audio/sound_system/Playlist.cpp +++ b/crogine/src/audio/sound_system/Playlist.cpp @@ -124,6 +124,18 @@ const std::int16_t* Playlist::getData(std::int32_t& samples) return retVal; } +std::vector Playlist::getTrackList() const +{ + std::vector retVal; + { + std::scoped_lock lock(m_mutex); + for (const auto& t : m_filePaths) + { + retVal.push_back(cro::FileSystem::getFileName(t)); + } + } + return retVal; +} //private void Playlist::threadFunc() diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index be26be32a..c1c609c65 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6738 -#define BUILDNUMBER_STR "6738" +#define BUILDNUMBER 6757 +#define BUILDNUMBER_STR "6757" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 4585c5b1f..039de82e8 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -75,6 +75,7 @@ source distribution. #include "golf/XPAwardStrings.hpp" #include "ImTheme.hpp" +#include "M3UPlaylist.hpp" #include #include @@ -1511,6 +1512,7 @@ void GolfGame::loadPreferences() { cro::FileSystem::createDirectory(Social::getBaseContentPath() + u8"music"); } + loadMusic(); } void GolfGame::savePreferences() @@ -1831,6 +1833,58 @@ void GolfGame::loadAvatars() m_sharedData.localConnectionData.playerData[0] = m_profileData.playerProfiles[0]; } +void GolfGame::loadMusic() +{ + //parse any music files into a playlist + M3UPlaylist m3uPlaylist(Social::getBaseContentPath() + u8"music/"); + + if (m3uPlaylist.getTrackCount() == 0) + { + const auto loadFiles = [&](const std::string& path, const std::string& root) + { + const auto files = cro::FileSystem::listFiles(path); + for (const auto& file : files) + { + //this checks the file has a valid extension + //and limits the number of files loaded + m3uPlaylist.addTrack(root + file); + } + m3uPlaylist.shuffle(); + }; + + +#ifdef USE_GNS + //see if the soundtrack is installed and prefer that + auto soundtrackPath = Social::getSoundTrackPath(); + if (!soundtrackPath.empty() + && cro::FileSystem::directoryExists(soundtrackPath + u8"/mp3/")) + { + loadFiles(soundtrackPath + u8"/mp3/", soundtrackPath + u8"/mp3/"); + } + + if (m3uPlaylist.getTrackCount() == 0) +#endif + { + //look in the fallback dir + const auto MusicDir = u8"assets/golf/sound/music/"; + if (cro::FileSystem::directoryExists(cro::FileSystem::getResourcePath() + MusicDir)) + { + loadFiles(cro::FileSystem::getResourcePath() + MusicDir, MusicDir); + } + } + } + + if (cro::Console::getConvarValue("shuffle_music")) + { + m3uPlaylist.shuffle(); + } + + for (const auto& fp : m3uPlaylist.getFilePaths()) + { + m_sharedData.playlist.addPath(fp); + } +} + void GolfGame::recreatePostProcess() { m_uniformIDs[UniformID::Time] = m_postShader->getUniformID("u_time"); diff --git a/samples/golf/src/GolfGame.hpp b/samples/golf/src/GolfGame.hpp index 705dbb1e8..91cd82c32 100644 --- a/samples/golf/src/GolfGame.hpp +++ b/samples/golf/src/GolfGame.hpp @@ -108,6 +108,7 @@ class GolfGame final : public cro::App, public cro::GuiClient, public cro::Conso void loadPreferences(); void savePreferences(); void loadAvatars(); + void loadMusic(); void recreatePostProcess(); void applyPostProcess(); bool setShader(const char*); diff --git a/samples/golf/src/M3UPlaylist.cpp b/samples/golf/src/M3UPlaylist.cpp index 41dfa9740..8fc641f52 100644 --- a/samples/golf/src/M3UPlaylist.cpp +++ b/samples/golf/src/M3UPlaylist.cpp @@ -50,9 +50,8 @@ namespace constexpr std::size_t MaxTotalFiles = 64; } -M3UPlaylist::M3UPlaylist(cro::AudioResource& ar, const std::string& searchDir, std::uint32_t maxFiles) - : m_audioResource (ar), - m_currentIndex (0) +M3UPlaylist::M3UPlaylist(const std::string& searchDir, std::uint32_t maxFiles) + : m_currentIndex (0) { if (cro::FileSystem::directoryExists(searchDir)) { @@ -115,7 +114,7 @@ bool M3UPlaylist::loadPlaylist(const std::string& path) const auto parseString = [&](const std::string str) { if (str.find(FilePrefix) == 0 - && m_resourceIDs.size() < MaxTotalFiles) + && m_filePaths.size() < MaxTotalFiles) { auto fp = str.substr(FilePrefix.size()); std::replace(fp.begin(), fp.end(), '\\', '/'); @@ -132,11 +131,7 @@ bool M3UPlaylist::loadPlaylist(const std::string& path) if (cro::FileSystem::fileExists(fp)) { - const auto id = m_audioResource.load(fp, true); - if (id != -1) - { - m_resourceIDs.emplace_back(id, cro::FileSystem::getFileName(fp)); - } + m_filePaths.push_back(fp); } } }; @@ -149,7 +144,7 @@ bool M3UPlaylist::loadPlaylist(const std::string& path) } parseString(fileString.substr(prev)); - return !m_resourceIDs.empty(); + return !m_filePaths.empty(); } LogE << path << " file is empty" << std::endl; @@ -165,54 +160,42 @@ void M3UPlaylist::addTrack(const std::string& path) std::string(".wav"), }; - if (m_resourceIDs.size() < MaxTotalFiles && + if (m_filePaths.size() < MaxTotalFiles && std::find(ValidExt.begin(), ValidExt.end(), cro::FileSystem::getFileExtension(path)) != ValidExt.end()) { - const auto id = m_audioResource.load(path, true); - if (id != -1) + if (cro::FileSystem::fileExists(path)) { - m_resourceIDs.emplace_back(id, cro::FileSystem::getFileName(path)); + m_filePaths.push_back(path); } } } void M3UPlaylist::shuffle() { - std::shuffle(m_resourceIDs.begin(), m_resourceIDs.end(), cro::Util::Random::rndEngine); + std::shuffle(m_filePaths.begin(), m_filePaths.end(), cro::Util::Random::rndEngine); } void M3UPlaylist::nextTrack() { - if (!m_resourceIDs.empty()) + if (!m_filePaths.empty()) { - m_currentIndex = (m_currentIndex + 1) % m_resourceIDs.size(); + m_currentIndex = (m_currentIndex + 1) % m_filePaths.size(); } } void M3UPlaylist::prevTrack() { - if (!m_resourceIDs.empty()) + if (!m_filePaths.empty()) { - m_currentIndex = (m_currentIndex + (m_resourceIDs.size() - 1)) % m_resourceIDs.size(); + m_currentIndex = (m_currentIndex + (m_filePaths.size() - 1)) % m_filePaths.size(); } } -std::int32_t M3UPlaylist::getCurrentTrack() const +const std::string& M3UPlaylist::getCurrentTrack() const { - if (m_resourceIDs.empty()) + if (m_filePaths.empty()) { - return -1; + return {}; } - return m_resourceIDs[m_currentIndex].first; -} - -const std::string& M3UPlaylist::getCurrentTrackName() const -{ - if (!m_resourceIDs.empty()) - { - return m_resourceIDs[m_currentIndex].second; - } - - static std::string ret; - return ret; + return m_filePaths[m_currentIndex]; } \ No newline at end of file diff --git a/samples/golf/src/M3UPlaylist.hpp b/samples/golf/src/M3UPlaylist.hpp index ee609f2fb..dfceb6b23 100644 --- a/samples/golf/src/M3UPlaylist.hpp +++ b/samples/golf/src/M3UPlaylist.hpp @@ -47,7 +47,7 @@ class M3UPlaylist final public: //takes a single directory through which to search for //playlists. Doesn't support recursive searching (no subdirs) - explicit M3UPlaylist(cro::AudioResource&, const std::string& searchFolder, std::uint32_t maxFiles = 25); + explicit M3UPlaylist(const std::string& searchFolder, std::uint32_t maxFiles = 25); //load another file and appeand it to the list bool loadPlaylist(const std::string& path); @@ -65,17 +65,16 @@ class M3UPlaylist final void prevTrack(); - //returns the resource ID of the current track or -1 if playlist is empty - std::int32_t getCurrentTrack() const; - const std::string& getCurrentTrackName() const; + const std::string& getCurrentTrack() const; std::size_t getCurrentIndex() const { return m_currentIndex; } - std::size_t getTrackCount() const { return m_resourceIDs.size(); } + std::size_t getTrackCount() const { return m_filePaths.size(); } -private: - cro::AudioResource& m_audioResource; - std::vector> m_resourceIDs; + const std::vector& getFilePaths() const { return m_filePaths; } + +private: std::size_t m_currentIndex; - std::string m_defaultFile; + + std::vector m_filePaths; }; diff --git a/samples/golf/src/golf/DrivingState.cpp b/samples/golf/src/golf/DrivingState.cpp index 879263b45..4d21165d5 100644 --- a/samples/golf/src/golf/DrivingState.cpp +++ b/samples/golf/src/golf/DrivingState.cpp @@ -249,6 +249,7 @@ DrivingState::DrivingState(cro::StateStack& stack, cro::State::Context context, m_sharedData (sd), m_profileData (sp), m_inputParser (sd, nullptr), + m_musicStream (2,44100), m_gameScene (context.appInstance.getMessageBus(), 512), m_skyScene (context.appInstance.getMessageBus()), m_uiScene (context.appInstance.getMessageBus(), 512), @@ -1294,29 +1295,29 @@ void DrivingState::initAudio() entity.addComponent().function = [](cro::Entity e, float dt) - { - static constexpr float Speed = 6.3f; - const float MaxLen = glm::length2((Start - End) / 2.f); + { + static constexpr float Speed = 6.3f; + const float MaxLen = glm::length2((Start - End) / 2.f); - auto& tx = e.getComponent(); - auto dir = glm::normalize(tx.getRightVector()); //scaling means this isn't normalised :/ - tx.move(dir * Speed * dt); + auto& tx = e.getComponent(); + auto dir = glm::normalize(tx.getRightVector()); //scaling means this isn't normalised :/ + tx.move(dir * Speed * dt); - float currLen = glm::length2((Start + ((Start + End) / 2.f)) - tx.getPosition()); - float scale = std::max(1.f - (currLen / MaxLen), 0.001f); //can't scale to 0 because it breaks normalizing the right vector above - tx.setScale({ scale, scale, scale }); + float currLen = glm::length2((Start + ((Start + End) / 2.f)) - tx.getPosition()); + float scale = std::max(1.f - (currLen / MaxLen), 0.001f); //can't scale to 0 because it breaks normalizing the right vector above + tx.setScale({ scale, scale, scale }); - if (tx.getPosition().x > End.x) - { - tx.setPosition(Start); - e.getComponent().active = false; - } - }; + if (tx.getPosition().x > End.x) + { + tx.setPosition(Start); + e.getComponent().active = false; + } + }; auto material = m_resources.materials.get(m_materialIDs[MaterialID::CelTextured]); applyMaterialData(md, material); entity.getComponent().setMaterial(0, material); - + //engine entity.addComponent(); //always needs one in case audio doesn't exist if (as.hasEmitter("plane")) @@ -1340,50 +1341,50 @@ void DrivingState::initAudio() entity.getComponent().setUserData(); entity.getComponent().function = [plane01, plane02, church, planeEnt](cro::Entity e, float dt) mutable - { - auto& [currTime, timeOut, activeEnt] = e.getComponent().getUserData(); - - if (!activeEnt.isValid() - || activeEnt.getComponent().getState() == cro::AudioEmitter::State::Stopped) { - currTime += dt; + auto& [currTime, timeOut, activeEnt] = e.getComponent().getUserData(); - if (currTime > timeOut) + if (!activeEnt.isValid() + || activeEnt.getComponent().getState() == cro::AudioEmitter::State::Stopped) { - currTime = 0.f; - timeOut = static_cast(cro::Util::Random::value(120, 240)); + currTime += dt; - auto id = cro::Util::Random::value(0, 3); - if (id == 0) + if (currTime > timeOut) { - //fly the plane - if (planeEnt.isValid()) + currTime = 0.f; + timeOut = static_cast(cro::Util::Random::value(120, 240)); + + auto id = cro::Util::Random::value(0, 3); + if (id == 0) { - planeEnt.getComponent().active = true; - planeEnt.getComponent().play(); - activeEnt = planeEnt; + //fly the plane + if (planeEnt.isValid()) + { + planeEnt.getComponent().active = true; + planeEnt.getComponent().play(); + activeEnt = planeEnt; + } } - } - else if (id == 1) - { - if (church.getComponent().getState() == cro::AudioEmitter::State::Stopped) + else if (id == 1) { - church.getComponent().play(); - activeEnt = church; + if (church.getComponent().getState() == cro::AudioEmitter::State::Stopped) + { + church.getComponent().play(); + activeEnt = church; + } } - } - else - { - auto ent = (id == 2) ? plane01 : plane02; - if (ent.getComponent().getState() == cro::AudioEmitter::State::Stopped) + else { - ent.getComponent().play(); - activeEnt = ent; + auto ent = (id == 2) ? plane01 : plane02; + if (ent.getComponent().getState() == cro::AudioEmitter::State::Stopped) + { + ent.getComponent().play(); + activeEnt = ent; + } } } } - } - }; + }; } @@ -1401,45 +1402,100 @@ void DrivingState::initAudio() LogE << "Invalid AudioScape file was found" << std::endl; } + //fades in the audio auto entity = m_gameScene.createEntity(); entity.addComponent().active = true; entity.getComponent().setUserData(0.f); entity.getComponent().function = [&](cro::Entity e, float dt) - { - auto& progress = e.getComponent().getUserData(); - progress = std::min(1.f, progress + dt); + { + auto& progress = e.getComponent().getUserData(); + progress = std::min(1.f, progress + dt); - cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::Effects); - cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::UserMusic); + cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::Effects); + cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::UserMusic); - if (progress == 1) + if (progress == 1) + { + e.getComponent().active = false; + m_gameScene.destroyEntity(e); + } + }; + + //TODO this is duplicated in the main game too so we could share this in a freefunc + registerCommand("list_tracks", [&](const std::string&) { - e.getComponent().active = false; - m_gameScene.destroyEntity(e); - } - }; + const auto& trackList = m_sharedData.playlist.getTrackList(); + + if (!trackList.empty()) + { + for (const auto& str : trackList) + { + cro::Console::print(str); + } + } + else + { + cro::Console::print("No music loaded"); + } + }); - auto playlist = createMusicPlayer(m_gameScene, m_resources.audio, m_gameScene.getActiveCamera()); - if (playlist.isValid()) + if (!m_sharedData.playlist.getTrackList().empty()) { - registerCommand("list_tracks", [playlist](const std::string&) + auto gameMusic = m_gameScene.getActiveCamera(); + + entity = m_gameScene.createEntity(); + entity.addComponent(m_musicStream).setMixerChannel(MixerChannel::UserMusic); + entity.addComponent().active = true; + entity.getComponent().function = + [&, gameMusic](cro::Entity e, float dt) { - const auto& trackEnts = playlist.getComponent().getUserData().playlist; - - if (!trackEnts.empty()) + //set the mixer channel to inverse valaue of main music channel + //while the incidental music is playing + if (gameMusic.isValid()) { - for (auto e : trackEnts) + //fade out if the menu music is playing, ie in a transition + const float target = gameMusic.getComponent().getState() == cro::AudioEmitter::State::Playing ? 1.f - std::ceil(cro::AudioMixer::getVolume(MixerChannel::Music)) : 1.f; + float vol = cro::AudioMixer::getPrefadeVolume(MixerChannel::UserMusic); + if (target < vol) { - cro::Console::print(e.getLabel()); + vol = std::max(0.f, vol - (dt * 2.f)); } + else + { + vol = std::min(1.f, vol + dt); + } + cro::AudioMixer::setPrefadeVolume(vol, MixerChannel::UserMusic); } - else + + + //check the current music state and pause when volume is low else play the + //next track when we stop playing. + const float currVol = cro::AudioMixer::getVolume(MixerChannel::UserMusic); + auto state = e.getComponent().getState(); + + if (state == cro::AudioEmitter::State::Playing) { - cro::Console::print("No music loaded"); + if (currVol < MinMusicVolume) + { + e.getComponent().pause(); + } } - }); + else if ((state == cro::AudioEmitter::State::Paused + && currVol > MinMusicVolume) || state == cro::AudioEmitter::State::Stopped) + { + e.getComponent().play(); + } + + + if (e.getComponent().getState() == cro::AudioEmitter::State::Playing) + { + std::int32_t samples = 0; + const auto* data = m_sharedData.playlist.getData(samples); + m_musicStream.updateBuffer(data, samples); + } + }; } } diff --git a/samples/golf/src/golf/DrivingState.hpp b/samples/golf/src/golf/DrivingState.hpp index 14085ef24..e8396716f 100644 --- a/samples/golf/src/golf/DrivingState.hpp +++ b/samples/golf/src/golf/DrivingState.hpp @@ -37,6 +37,8 @@ source distribution. #include "GameConsts.hpp" #include "BallTrail.hpp" +#include + #include #include #include @@ -98,6 +100,8 @@ class DrivingState final : public cro::State, public cro::GuiClient, public cro: const SharedProfileData& m_profileData; InputParser m_inputParser; + cro::DynamicAudioStream m_musicStream; + cro::Scene m_gameScene; cro::Scene m_skyScene; cro::Scene m_uiScene; diff --git a/samples/golf/src/golf/GameConsts.cpp b/samples/golf/src/golf/GameConsts.cpp index 02537eb87..6944f5e09 100644 --- a/samples/golf/src/golf/GameConsts.cpp +++ b/samples/golf/src/golf/GameConsts.cpp @@ -44,144 +44,4 @@ bool hasPSLayout(std::int32_t controllerID) } #endif return cro::GameController::hasPSLayout(controllerID); -} - -[[nodiscard]] cro::Entity createMusicPlayer(cro::Scene& scene, cro::AudioResource& audio, cro::Entity gameMusic) -{ - //parse any music files into a playlist - M3UPlaylist m3uPlaylist(audio, Social::getBaseContentPath() + "music/"); - - if (m3uPlaylist.getTrackCount() == 0) - { - const auto loadFiles = [&](const std::string& path, const std::string& root) - { - const auto files = cro::FileSystem::listFiles(path); - for (const auto& file : files) - { - //this checks the file has a valid extension - //and limits the number of files loaded - m3uPlaylist.addTrack(root + file); - } - m3uPlaylist.shuffle(); - }; - - -#ifdef USE_GNS - //see if the soundtrack is installed and prefer that - auto soundtrackPath = Social::getSoundTrackPath(); - if (!soundtrackPath.empty() - && cro::FileSystem::directoryExists(soundtrackPath + u8"/mp3/")) - { - loadFiles(soundtrackPath + u8"/mp3/", soundtrackPath + u8"/mp3/"); - } - - if (m3uPlaylist.getTrackCount() == 0) -#endif - { - //look in the fallback dir - const auto MusicDir = "assets/golf/sound/music/"; - if (cro::FileSystem::directoryExists(cro::FileSystem::getResourcePath() + MusicDir)) - { - loadFiles(cro::FileSystem::getResourcePath() + MusicDir, MusicDir); - } - } - } - - if (cro::Console::getConvarValue("shuffle_music")) - { - m3uPlaylist.shuffle(); - } - - - //if the playlist isnt empty, create the music playing entities - if (auto trackCount = m3uPlaylist.getTrackCount(); trackCount != 0) - { - auto playerEnt = scene.createEntity(); - playerEnt.addComponent(); - playerEnt.addComponent().active = true; - playerEnt.getComponent().setUserData(); - playerEnt.getComponent().function = - [gameMusic](cro::Entity e, float dt) mutable - { - //set the mixer channel to inverse valaue of main music channel - //while the incidental music is playing - if (gameMusic.isValid()) - { - //fade out if the menu music is playing, ie in a transition - const float target = gameMusic.getComponent().getState() == cro::AudioEmitter::State::Playing ? 1.f - std::ceil(cro::AudioMixer::getVolume(MixerChannel::Music)) : 1.f; - float vol = cro::AudioMixer::getPrefadeVolume(MixerChannel::UserMusic); - if (target < vol) - { - vol = std::max(0.f, vol - (dt * 2.f)); - } - else - { - vol = std::min(1.f, vol + dt); - } - cro::AudioMixer::setPrefadeVolume(vol, MixerChannel::UserMusic); - } - - - //if the playlist is empty just disable this ent - auto& [playlist, currentIndex] = e.getComponent().getUserData(); - if (playlist.empty()) - { - e.getComponent().active = false; - return; - } - - - //else check the current music state and pause when volume is low else play the - //next track when we stop playing. - auto musicEnt = playlist[currentIndex]; - const float currVol = cro::AudioMixer::getVolume(MixerChannel::UserMusic); - auto state = musicEnt.getComponent().getState(); - - if (state == cro::AudioEmitter::State::Playing) - { - if (currVol < MinMusicVolume) - { - musicEnt.getComponent().pause(); - } - } - else if (state == cro::AudioEmitter::State::Paused - && currVol > MinMusicVolume) - { - musicEnt.getComponent().play(); - } - else if (state == cro::AudioEmitter::State::Stopped) - { - currentIndex = (currentIndex + 1) % playlist.size(); - playlist[currentIndex].getComponent().play(); - } - }; - - - auto& playlist = playerEnt.getComponent().getUserData().playlist; - - //this is a fudge because there's a bug preventing reassigning streams - for (auto i = 0u; i < trackCount; ++i) - { - const auto id = m3uPlaylist.getCurrentTrack(); - const std::string trackName = m3uPlaylist.getCurrentTrackName(); - m3uPlaylist.nextTrack(); - - auto entity = scene.createEntity(); - entity.setLabel(trackName); - entity.addComponent(); - entity.addComponent().setSource(audio.get(id)); - entity.getComponent().setMixerChannel(MixerChannel::UserMusic); - entity.getComponent().setVolume(0.6f); - - playlist.push_back(entity); - } - if (!playlist.empty()) - { - playlist[0].getComponent().play(); - } - - return playerEnt; - } - - return {}; } \ No newline at end of file diff --git a/samples/golf/src/golf/GameConsts.hpp b/samples/golf/src/golf/GameConsts.hpp index e82753873..fbf33ae3f 100644 --- a/samples/golf/src/golf/GameConsts.hpp +++ b/samples/golf/src/golf/GameConsts.hpp @@ -842,13 +842,6 @@ static inline void applyMaterialData(const cro::ModelDefinition& modelDef, cro:: } } -struct MusicPlayerData final -{ - std::vector playlist; - std::size_t currentIndex = 0; -}; -[[nodiscard]] cro::Entity createMusicPlayer(cro::Scene& scene, cro::AudioResource&, cro::Entity gameMusic); - //finds an intersecting point on the water plane. static inline bool planeIntersect(const glm::mat4& camTx, glm::vec3& result) { diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 82ce86b0d..4412ad39f 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -161,6 +161,7 @@ namespace GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, SharedStateData& sd) : cro::State (stack, context), + m_musicStream (2,44100), m_sharedData (sd), m_gameScene (context.appInstance.getMessageBus(), 1024/*, cro::INFO_FLAG_SYSTEM_TIME | cro::INFO_FLAG_SYSTEMS_ACTIVE*/), m_skyScene (context.appInstance.getMessageBus(), 512), diff --git a/samples/golf/src/golf/GolfState.hpp b/samples/golf/src/golf/GolfState.hpp index bfd1d0d19..1044842d8 100644 --- a/samples/golf/src/golf/GolfState.hpp +++ b/samples/golf/src/golf/GolfState.hpp @@ -46,6 +46,8 @@ source distribution. #include "League.hpp" #include "server/ServerPacketData.hpp" +#include + #include #include #include @@ -122,6 +124,7 @@ class GolfState final : public cro::State, public cro::GuiClient, public cro::Co private: cro::ResourceCollection m_resources; + cro::DynamicAudioStream m_musicStream; SharedStateData& m_sharedData; cro::Scene m_gameScene; diff --git a/samples/golf/src/golf/GolfStateAssets.cpp b/samples/golf/src/golf/GolfStateAssets.cpp index 188cb5ecb..1f646b2e1 100644 --- a/samples/golf/src/golf/GolfStateAssets.cpp +++ b/samples/golf/src/golf/GolfStateAssets.cpp @@ -2487,28 +2487,85 @@ void GolfState::initAudio(bool loadTrees) m_gameScene.getActiveCamera().addComponent(); LogE << "Invalid AudioScape file was found" << std::endl; } - auto playlist = createMusicPlayer(m_gameScene, m_resources.audio, m_gameScene.getActiveCamera()); - if (playlist.isValid()) + + //TODO this is all the same as the driving range, so we can wrap this up in a free func + + registerCommand("list_tracks", [&](const std::string&) + { + const auto& trackList = m_sharedData.playlist.getTrackList(); + + if (!trackList.empty()) + { + for (const auto& str : trackList) + { + cro::Console::print(str); + } + } + else + { + cro::Console::print("No music loaded"); + } + }); + + if (!m_sharedData.playlist.getTrackList().empty()) { - registerCommand("list_tracks", [playlist](const std::string&) + auto gameMusic = m_gameScene.getActiveCamera(); + + auto entity = m_gameScene.createEntity(); + entity.addComponent(m_musicStream).setMixerChannel(MixerChannel::UserMusic); + entity.addComponent().active = true; + entity.getComponent().function = + [&, gameMusic](cro::Entity e, float dt) { - const auto& trackEnts = playlist.getComponent().getUserData().playlist; - - if (!trackEnts.empty()) + //set the mixer channel to inverse valaue of main music channel + //while the incidental music is playing + if (gameMusic.isValid()) { - for (auto e : trackEnts) + //fade out if the menu music is playing, ie in a transition + const float target = gameMusic.getComponent().getState() == cro::AudioEmitter::State::Playing ? 1.f - std::ceil(cro::AudioMixer::getVolume(MixerChannel::Music)) : 1.f; + float vol = cro::AudioMixer::getPrefadeVolume(MixerChannel::UserMusic); + if (target < vol) { - cro::Console::print(e.getLabel()); + vol = std::max(0.f, vol - (dt * 2.f)); } + else + { + vol = std::min(1.f, vol + dt); + } + cro::AudioMixer::setPrefadeVolume(vol, MixerChannel::UserMusic); } - else + + + //check the current music state and pause when volume is low else play the + //next track when we stop playing. + const float currVol = cro::AudioMixer::getVolume(MixerChannel::UserMusic); + auto state = e.getComponent().getState(); + + if (state == cro::AudioEmitter::State::Playing) { - cro::Console::print("No music loaded"); + if (currVol < MinMusicVolume) + { + e.getComponent().pause(); + } } - }); + else if ((state == cro::AudioEmitter::State::Paused + && currVol > MinMusicVolume) || state == cro::AudioEmitter::State::Stopped) + { + e.getComponent().play(); + } + + + if (e.getComponent().getState() == cro::AudioEmitter::State::Playing) + { + std::int32_t samples = 0; + const auto* data = m_sharedData.playlist.getData(samples); + m_musicStream.updateBuffer(data, samples); + } + }; } + if (loadTrees) { const std::array paths = @@ -2572,7 +2629,7 @@ void GolfState::initAudio(bool loadTrees) [&](cro::Entity e, float dt) { auto& progress = e.getComponent().getUserData(); - progress = std::min(1.f, progress + dt); + progress = std::min(1.f, progress + (dt / 5.f)); cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::Effects); cro::AudioMixer::setPrefadeVolume(cro::Util::Easing::easeOutQuad(progress), MixerChannel::Environment); diff --git a/samples/golf/src/golf/SharedStateData.hpp b/samples/golf/src/golf/SharedStateData.hpp index 0274601e0..5aea5d7bd 100644 --- a/samples/golf/src/golf/SharedStateData.hpp +++ b/samples/golf/src/golf/SharedStateData.hpp @@ -36,6 +36,7 @@ source distribution. #include "LeagueNames.hpp" #include "server/Server.hpp" +#include #include #include #include @@ -86,6 +87,8 @@ enum class GameMode struct SharedCourseData; struct SharedStateData final { + cro::Playlist playlist; + bool showCredits = false; std::int32_t leagueTable = 0; //which league table to display when opening league browser SharedCourseData* courseData = nullptr; //only valid when MenuState is active diff --git a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp index f9ca97fc2..9732d8e39 100644 --- a/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp +++ b/samples/scratchpad/src/trackoverlay/TrackOverlayState.cpp @@ -113,7 +113,6 @@ void main() TrackOverlayState::TrackOverlayState(cro::StateStack& stack, cro::State::Context context) : cro::State (stack, context), - m_audioStream (2,44100), m_gameScene (context.appInstance.getMessageBus()), m_uiScene (context.appInstance.getMessageBus()), m_currentIndex (0), @@ -127,16 +126,6 @@ TrackOverlayState::TrackOverlayState(cro::StateStack& stack, cro::State::Context }); cro::App::getInstance().setClearColour(cro::Colour(0.f, 1.f, 0.f, 0.f)); - - m_playlist.addPath("assets/avatar_voices.mp3"); - m_playlist.addPath("assets/bass_loop.mp3"); - m_playlist.addPath("assets/loop.wav"); - m_playlist.addPath("assets/avatar_voices.ogg"); - m_playlist.addPath("assets/menu.ogg"); - - m_audioEnt = m_uiScene.createEntity(); - m_audioEnt.addComponent(); - m_audioEnt.addComponent(m_audioStream).play(); } //public @@ -193,14 +182,6 @@ void TrackOverlayState::handleMessage(const cro::Message& msg) bool TrackOverlayState::simulate(float dt) { - if (m_audioEnt.getComponent().getState() == cro::AudioEmitter::State::Playing) - { - std::int32_t size = 0; - const auto* data = m_playlist.getData(size); - - m_audioStream.updateBuffer(data, size); - } - m_gameScene.simulate(dt); m_uiScene.simulate(dt); return true; diff --git a/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp b/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp index ddd49e824..35cab8976 100644 --- a/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp +++ b/samples/scratchpad/src/trackoverlay/TrackOverlayState.hpp @@ -4,9 +4,6 @@ #include "../StateIDs.hpp" -#include -#include - #include #include #include @@ -31,11 +28,6 @@ class TrackOverlayState final : public cro::State, public cro::GuiClient void render() override; private: - - cro::Playlist m_playlist; - cro::DynamicAudioStream m_audioStream; - cro::Entity m_audioEnt; - cro::Scene m_gameScene; cro::Scene m_uiScene; cro::ResourceCollection m_resources; From dfdb0adf44515b5acd596390d4df599d477cff7b Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 17:22:24 +0100 Subject: [PATCH 06/12] default streaming playlist to 48khz sample rate resample audio on load if it doesn't meet playlist criteria --- .../crogine/audio/sound_system/Playlist.hpp | 5 +- crogine/src/audio/sound_system/Playlist.cpp | 84 +++++++++++++++++-- samples/golf/buildnumber.h | 4 +- samples/golf/src/golf/DrivingState.cpp | 2 +- samples/golf/src/golf/GolfState.cpp | 2 +- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/crogine/include/crogine/audio/sound_system/Playlist.hpp b/crogine/include/crogine/audio/sound_system/Playlist.hpp index b14c98409..abfbf2598 100644 --- a/crogine/include/crogine/audio/sound_system/Playlist.hpp +++ b/crogine/include/crogine/audio/sound_system/Playlist.hpp @@ -44,12 +44,13 @@ namespace cro /* \brief Loads and buffers audio files asynchronously so that they may be streamed continuously to a buffer such - as DynamicAudioStream. Currently only supports 44.1KHz, 16 bit Stereo + as DynamicAudioStream. Output is 48KHz 16 bit Stereo + files are resampled on load if necessary */ class CRO_EXPORT_API Playlist final { public: - Playlist(); //TODO specify a format the audio must match - currently fixed to 16bit stereo + Playlist(); ~Playlist(); Playlist(const Playlist&) = delete; diff --git a/crogine/src/audio/sound_system/Playlist.cpp b/crogine/src/audio/sound_system/Playlist.cpp index 4f84f832a..de1a089da 100644 --- a/crogine/src/audio/sound_system/Playlist.cpp +++ b/crogine/src/audio/sound_system/Playlist.cpp @@ -31,6 +31,8 @@ source distribution. #include "../WavLoader.hpp" #include "../Mp3Loader.hpp" +#include + #include #include #include @@ -45,6 +47,7 @@ namespace { constexpr std::size_t MaxFiles = 50; constexpr std::int32_t ChannelCount = 2; + constexpr std::int32_t SampleRate = 48000; } Playlist::Playlist() @@ -103,7 +106,7 @@ const std::int16_t* Playlist::getData(std::int32_t& samples) //arbitrary amount - we make sure below to not load the next buffer //until we know it's safe to do so, so let's send ~double samples per sec (at 60Hz) - static constexpr std::int32_t SampleCount = (44100 / 30) * ChannelCount; + static constexpr std::int32_t SampleCount = (SampleRate / 30) * ChannelCount; auto available = std::min((static_cast(m_buffers[m_outBuffer].size()) - m_outputOffset), SampleCount); samples = available / ChannelCount; //ONE SAMPLE includes 2 CHANNELS @@ -171,13 +174,14 @@ void Playlist::threadFunc() if (audioFile) { - if (!audioFile->open(m_filePaths[m_fileIndex]) - || audioFile->getFormat() != Detail::PCMData::Format::STEREO16 - || audioFile->getSampleRate() != 44100) //hmm what about 48KHz? + if (!audioFile->open(m_filePaths[m_fileIndex])) { m_loadNextFile = false; m_fileIndex = (m_fileIndex + 1) % m_filePaths.size(); - LogW << m_filePaths[m_fileIndex] << ": could not open file, or file not 16 bit stereo" << std::endl; + LogW << m_filePaths[m_fileIndex] << ": could not open file" << std::endl; + + //TODO erase the path from the file list and adjust indices accordingly otherwise + //we get stuck in an endless loop of unloadability audioFile.reset(); } @@ -189,7 +193,7 @@ void Playlist::threadFunc() m_buffers[m_inBuffer].clear(); //set a max file length of ~15 minutes - static constexpr std::size_t MaxFileSize = 15 * 60 * 44100 * ChannelCount * sizeof(std::int16_t); + static constexpr std::size_t MaxFileSize = 15 * 60 * SampleRate * ChannelCount * sizeof(std::int16_t); std::vector temp; const auto& data = audioFile->getData(); @@ -203,8 +207,76 @@ void Playlist::threadFunc() audioFile->getData(); //this actually updates the supposedly const data reference... not the greatest design } + if (!temp.empty()) { + //resmaple music not in the correct format + if (audioFile->getFormat() != Detail::PCMData::Format::STEREO16 + || audioFile->getSampleRate() != SampleRate) + { + SDL_AudioFormat inFormat = AUDIO_S16; + std::uint8_t inChannels = 1; + + switch (audioFile->getFormat()) + { + default: + case Detail::PCMData::Format::NONE: + //just instert silence + temp.clear(); + break; + case Detail::PCMData::Format::STEREO8: + inChannels = 2; + [[fallthrough]]; + case Detail::PCMData::Format::MONO8: + inFormat = AUDIO_U8; + break; + case Detail::PCMData::Format::STEREO16: + //we still have to handle this case if the sample rate doesn't match + inChannels = 2; + [[fallthrough]]; + case Detail::PCMData::Format::MONO16: + //do nothing these are the default input settings + break; + } + + + if (temp.empty()) + { + //we got an invalid format + temp.resize(100); + std::fill(temp.begin(), temp.end(), 0); + } + else + { + //resample + auto* stream = SDL_NewAudioStream(inFormat, inChannels, audioFile->getSampleRate(), AUDIO_S16, ChannelCount, SampleRate); + SDL_AudioStreamPut(stream, temp.data(), temp.size()); + SDL_AudioStreamFlush(stream); + + std::int32_t count = 0; + std::vector resampled; + std::vector buff(102400); + + do + { + count = SDL_AudioStreamGet(stream, buff.data(), buff.size()); + + if (count > 0) + { + auto old = resampled.size(); + resampled.resize(old + count); + std::memcpy(&resampled[old], buff.data(), count); + } + + } while (count > 0); //might be negative on error + + temp.swap(resampled); + SDL_FreeAudioStream(stream); + } + } + + + { std::scoped_lock lock(m_mutex); m_fileIndex = (m_fileIndex + 1) % m_filePaths.size(); diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index c1c609c65..d7587a490 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6757 -#define BUILDNUMBER_STR "6757" +#define BUILDNUMBER 6759 +#define BUILDNUMBER_STR "6759" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/golf/DrivingState.cpp b/samples/golf/src/golf/DrivingState.cpp index 4d21165d5..54852a7f1 100644 --- a/samples/golf/src/golf/DrivingState.cpp +++ b/samples/golf/src/golf/DrivingState.cpp @@ -249,7 +249,7 @@ DrivingState::DrivingState(cro::StateStack& stack, cro::State::Context context, m_sharedData (sd), m_profileData (sp), m_inputParser (sd, nullptr), - m_musicStream (2,44100), + m_musicStream (2,48000), m_gameScene (context.appInstance.getMessageBus(), 512), m_skyScene (context.appInstance.getMessageBus()), m_uiScene (context.appInstance.getMessageBus(), 512), diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 4412ad39f..435264709 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -161,7 +161,7 @@ namespace GolfState::GolfState(cro::StateStack& stack, cro::State::Context context, SharedStateData& sd) : cro::State (stack, context), - m_musicStream (2,44100), + m_musicStream (2,48000), m_sharedData (sd), m_gameScene (context.appInstance.getMessageBus(), 1024/*, cro::INFO_FLAG_SYSTEM_TIME | cro::INFO_FLAG_SYSTEMS_ACTIVE*/), m_skyScene (context.appInstance.getMessageBus(), 512), From cadc7f056a63d1ed2b5bd3b390732b7c112f177f Mon Sep 17 00:00:00 2001 From: fallahn Date: Mon, 8 Jul 2024 18:01:16 +0100 Subject: [PATCH 07/12] fix some warnings --- samples/golf/buildnumber.h | 4 ++-- samples/golf/src/M3UPlaylist.cpp | 3 ++- samples/golf/src/golf/DrivingState.cpp | 1 + samples/golf/src/golf/GolfState.cpp | 4 ++-- samples/golf/src/golf/GolfStateAssets.cpp | 1 + samples/golf/src/golf/GolfStateUI.cpp | 3 --- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index d7587a490..d7ffaf93f 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6759 -#define BUILDNUMBER_STR "6759" +#define BUILDNUMBER 6761 +#define BUILDNUMBER_STR "6761" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/M3UPlaylist.cpp b/samples/golf/src/M3UPlaylist.cpp index 8fc641f52..f153ed092 100644 --- a/samples/golf/src/M3UPlaylist.cpp +++ b/samples/golf/src/M3UPlaylist.cpp @@ -195,7 +195,8 @@ const std::string& M3UPlaylist::getCurrentTrack() const { if (m_filePaths.empty()) { - return {}; + static const std::string ret; + return ret; } return m_filePaths[m_currentIndex]; } \ No newline at end of file diff --git a/samples/golf/src/golf/DrivingState.cpp b/samples/golf/src/golf/DrivingState.cpp index 54852a7f1..e4f941e0d 100644 --- a/samples/golf/src/golf/DrivingState.cpp +++ b/samples/golf/src/golf/DrivingState.cpp @@ -1447,6 +1447,7 @@ void DrivingState::initAudio() entity = m_gameScene.createEntity(); entity.addComponent(m_musicStream).setMixerChannel(MixerChannel::UserMusic); + entity.getComponent().setVolume(0.6f); entity.addComponent().active = true; entity.getComponent().function = [&, gameMusic](cro::Entity e, float dt) diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 435264709..16f91da1d 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -465,13 +465,13 @@ bool GolfState::handleEvent(const cro::Event& evt) m_uiScene.getSystem()->sendCommand(cmd); }; - const auto showMapOverview = [&]() + /*const auto showMapOverview = [&]() { if (m_sharedData.minimapData.active) { requestStackPush(StateID::MapOverview); } - }; + };*/ const auto toggleMiniZoom = [&]() { diff --git a/samples/golf/src/golf/GolfStateAssets.cpp b/samples/golf/src/golf/GolfStateAssets.cpp index 1f646b2e1..04556d186 100644 --- a/samples/golf/src/golf/GolfStateAssets.cpp +++ b/samples/golf/src/golf/GolfStateAssets.cpp @@ -2513,6 +2513,7 @@ void GolfState::initAudio(bool loadTrees) auto entity = m_gameScene.createEntity(); entity.addComponent(m_musicStream).setMixerChannel(MixerChannel::UserMusic); + entity.getComponent().setVolume(0.6f); entity.addComponent().active = true; entity.getComponent().function = [&, gameMusic](cro::Entity e, float dt) diff --git a/samples/golf/src/golf/GolfStateUI.cpp b/samples/golf/src/golf/GolfStateUI.cpp index 176e58463..a488a71f5 100644 --- a/samples/golf/src/golf/GolfStateUI.cpp +++ b/samples/golf/src/golf/GolfStateUI.cpp @@ -3168,8 +3168,6 @@ void GolfState::updateScoreboard(bool updateParDiff) entry.player = 0;// ConstVal::NullValue; entry.leaguePlayer = true; - bool overPar = false; - for (auto j = 0u; j < /*leagueScores[i].size()*/m_holeData.size(); ++j) { auto s = leagueScores[i][j]; @@ -3185,7 +3183,6 @@ void GolfState::updateScoreboard(bool updateParDiff) auto diff = static_cast(s) - m_holeData[j].par; entry.parDiff += diff; - overPar = (diff > 0); } } From f7aa729d2d528f02967ca5e46c04cbf869522bae Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 9 Jul 2024 10:36:33 +0100 Subject: [PATCH 08/12] add using left mouse as action button --- samples/golf/buildnumber.h | 4 ++-- samples/golf/src/golf/InputParser.cpp | 10 ++++++++++ samples/golf/src/golf/Swingput.cpp | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index d7ffaf93f..7b41df35b 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6761 -#define BUILDNUMBER_STR "6761" +#define BUILDNUMBER 6763 +#define BUILDNUMBER_STR "6763" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/golf/InputParser.cpp b/samples/golf/src/golf/InputParser.cpp index 6ba8b0386..550b366ff 100644 --- a/samples/golf/src/golf/InputParser.cpp +++ b/samples/golf/src/golf/InputParser.cpp @@ -422,6 +422,16 @@ void InputParser::handleEvent(const cro::Event& evt) { m_mouseWheel += evt.wheel.y; } + else if (evt.type == SDL_MOUSEBUTTONDOWN + && !m_swingput.isActive()) + { + m_inputFlags |= InputFlag::Action; + } + else if (evt.type == SDL_MOUSEBUTTONUP + && !m_swingput.isActive()) + { + m_inputFlags &= ~InputFlag::Action; + } /*else if (evt.type == SDL_MOUSEMOTION) { m_mouseMove += evt.motion.xrel; diff --git a/samples/golf/src/golf/Swingput.cpp b/samples/golf/src/golf/Swingput.cpp index 519e4febe..6226b6de6 100644 --- a/samples/golf/src/golf/Swingput.cpp +++ b/samples/golf/src/golf/Swingput.cpp @@ -166,7 +166,7 @@ bool Swingput::handleEvent(const cro::Event& evt, std::uint16_t& inputFlags, std m_mouseSwing.startStroke(); return true; } - return false; + return isActive(); case SDL_MOUSEBUTTONUP: if (evt.button.button == SDL_BUTTON_RIGHT) { @@ -174,7 +174,7 @@ bool Swingput::handleEvent(const cro::Event& evt, std::uint16_t& inputFlags, std m_mouseSwing.endStroke(); return true; } - return false; + return isActive(); case SDL_MOUSEMOTION: if (m_state == State::Swing) { From a6a13dc026cddc08ad20fe5bcd43e6c4d3c2cba6 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 9 Jul 2024 11:02:44 +0100 Subject: [PATCH 09/12] disable mouse click for action on the steam deck --- samples/golf/buildnumber.h | 4 ++-- samples/golf/src/golf/InputParser.cpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index 7b41df35b..3bd3912ba 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6763 -#define BUILDNUMBER_STR "6763" +#define BUILDNUMBER 6764 +#define BUILDNUMBER_STR "6764" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/golf/InputParser.cpp b/samples/golf/src/golf/InputParser.cpp index 550b366ff..308e65436 100644 --- a/samples/golf/src/golf/InputParser.cpp +++ b/samples/golf/src/golf/InputParser.cpp @@ -423,12 +423,11 @@ void InputParser::handleEvent(const cro::Event& evt) m_mouseWheel += evt.wheel.y; } else if (evt.type == SDL_MOUSEBUTTONDOWN - && !m_swingput.isActive()) + && !Social::isSteamdeck()) { m_inputFlags |= InputFlag::Action; } - else if (evt.type == SDL_MOUSEBUTTONUP - && !m_swingput.isActive()) + else if (evt.type == SDL_MOUSEBUTTONUP) { m_inputFlags &= ~InputFlag::Action; } From 5846abec43cc4b3eb3a7bc49e682c12a9dbc6cb3 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 9 Jul 2024 11:43:07 +0100 Subject: [PATCH 10/12] add pre-buffering of music playlist --- crogine/include/crogine/audio/sound_system/Playlist.hpp | 6 ++++++ crogine/src/audio/sound_system/Playlist.cpp | 9 +++++++++ samples/golf/buildnumber.h | 4 ++-- samples/golf/src/GolfGame.cpp | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crogine/include/crogine/audio/sound_system/Playlist.hpp b/crogine/include/crogine/audio/sound_system/Playlist.hpp index abfbf2598..07bc52774 100644 --- a/crogine/include/crogine/audio/sound_system/Playlist.hpp +++ b/crogine/include/crogine/audio/sound_system/Playlist.hpp @@ -81,6 +81,12 @@ namespace cro */ std::vector getTrackList() const; + /*! + \brief Pre-loads the first buffer (if possible) - use this to + prime the audio input without having to wait until the first call to getData() + */ + void precache(); + private: static constexpr std::size_t MaxBuffers = 3u; diff --git a/crogine/src/audio/sound_system/Playlist.cpp b/crogine/src/audio/sound_system/Playlist.cpp index de1a089da..7036d1235 100644 --- a/crogine/src/audio/sound_system/Playlist.cpp +++ b/crogine/src/audio/sound_system/Playlist.cpp @@ -140,6 +140,15 @@ std::vector Playlist::getTrackList() const return retVal; } +void Playlist::precache() +{ + if (!m_filePaths.empty() + && m_inBuffer == m_outBuffer) + { + m_loadNextFile = true; + } +} + //private void Playlist::threadFunc() { diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index 3bd3912ba..b308e81a9 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6764 -#define BUILDNUMBER_STR "6764" +#define BUILDNUMBER 6766 +#define BUILDNUMBER_STR "6766" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 039de82e8..4a7cb576d 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -1883,6 +1883,7 @@ void GolfGame::loadMusic() { m_sharedData.playlist.addPath(fp); } + m_sharedData.playlist.precache(); } void GolfGame::recreatePostProcess() From 5954a557586e81fae9028e63bdc1839880e80e79 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 9 Jul 2024 12:00:41 +0100 Subject: [PATCH 11/12] prevent mouse input when window loses focus --- samples/golf/buildnumber.h | 4 ++-- samples/golf/src/golf/GolfState.cpp | 32 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index b308e81a9..be350fe5d 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6766 -#define BUILDNUMBER_STR "6766" +#define BUILDNUMBER 6767 +#define BUILDNUMBER_STR "6767" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/golf/GolfState.cpp b/samples/golf/src/golf/GolfState.cpp index 16f91da1d..1177e3271 100644 --- a/samples/golf/src/golf/GolfState.cpp +++ b/samples/golf/src/golf/GolfState.cpp @@ -994,6 +994,38 @@ bool GolfState::handleEvent(const cro::Event& evt) } } + else if (evt.type == SDL_WINDOWEVENT) + { + switch (evt.window.event) + { + default: break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + //this needs to be delayed a frame so mouse clincking on the + //open window doesn't get sent to the input parser + if (m_currentPlayer.client == m_sharedData.localConnectionData.connectionID + && !m_sharedData.localConnectionData.playerData[m_currentPlayer.player].isCPU) + { + auto entity = m_uiScene.createEntity(); + entity.addComponent().active = true; + entity.getComponent().function = + [&](cro::Entity e, float) + { + m_inputParser.setSuspended(false); + e.getComponent().active = false; + m_uiScene.destroyEntity(e); + }; + } + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + if (m_currentPlayer.client == m_sharedData.localConnectionData.connectionID + && !m_sharedData.localConnectionData.playerData[m_currentPlayer.player].isCPU) + { + m_inputParser.setSuspended(true); + } + break; + } + } + m_gameScene.forwardEvent(evt); m_skyScene.forwardEvent(evt); m_uiScene.forwardEvent(evt); From 07c99ec96c625b6bf9ffd2e728774dd2d6a06534 Mon Sep 17 00:00:00 2001 From: fallahn Date: Tue, 9 Jul 2024 12:18:57 +0100 Subject: [PATCH 12/12] add option to disable mouse click input --- samples/golf/buildnumber.h | 4 +- samples/golf/src/GolfGame.cpp | 5 +++ samples/golf/src/golf/InputParser.cpp | 2 +- samples/golf/src/golf/OptionsEnum.inl | 1 + samples/golf/src/golf/OptionsState.cpp | 48 +++++++++++++++++++++-- samples/golf/src/golf/SharedStateData.hpp | 1 + 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/samples/golf/buildnumber.h b/samples/golf/buildnumber.h index be350fe5d..fb0138387 100644 --- a/samples/golf/buildnumber.h +++ b/samples/golf/buildnumber.h @@ -3,7 +3,7 @@ #ifndef BUILD_NUMBER_H_ #define BUILD_NUMBER_H_ -#define BUILDNUMBER 6767 -#define BUILDNUMBER_STR "6767" +#define BUILDNUMBER 6771 +#define BUILDNUMBER_STR "6771" #endif /* BUILD_NUMBER_H_ */ diff --git a/samples/golf/src/GolfGame.cpp b/samples/golf/src/GolfGame.cpp index 4a7cb576d..04d592edf 100644 --- a/samples/golf/src/GolfGame.cpp +++ b/samples/golf/src/GolfGame.cpp @@ -1446,6 +1446,10 @@ void GolfGame::loadPreferences() { m_sharedData.useLensFlare = prop.getValue(); } + else if (name == "use_mouse_action") + { + m_sharedData.useMouseAction = prop.getValue(); + } } } } @@ -1562,6 +1566,7 @@ void GolfGame::savePreferences() cfg.addProperty("press_hold").setValue(m_sharedData.pressHold); cfg.addProperty("use_tts").setValue(m_sharedData.useTTS); cfg.addProperty("use_flare").setValue(m_sharedData.useLensFlare); + cfg.addProperty("use_mouse_action").setValue(m_sharedData.useMouseAction); cfg.save(path); diff --git a/samples/golf/src/golf/InputParser.cpp b/samples/golf/src/golf/InputParser.cpp index 308e65436..75f82d463 100644 --- a/samples/golf/src/golf/InputParser.cpp +++ b/samples/golf/src/golf/InputParser.cpp @@ -423,7 +423,7 @@ void InputParser::handleEvent(const cro::Event& evt) m_mouseWheel += evt.wheel.y; } else if (evt.type == SDL_MOUSEBUTTONDOWN - && !Social::isSteamdeck()) + && m_sharedData.useMouseAction) { m_inputFlags |= InputFlag::Action; } diff --git a/samples/golf/src/golf/OptionsEnum.inl b/samples/golf/src/golf/OptionsEnum.inl index 01c89634e..f94c2c0e1 100644 --- a/samples/golf/src/golf/OptionsEnum.inl +++ b/samples/golf/src/golf/OptionsEnum.inl @@ -94,6 +94,7 @@ enum OptionsIndex CtrlLB, CtrlInvX, CtrlInvY, + CtrlMouseAction, CtrlVib, CtrlAltPower, CtrlSwg, diff --git a/samples/golf/src/golf/OptionsState.cpp b/samples/golf/src/golf/OptionsState.cpp index a1f94119d..47df016ab 100644 --- a/samples/golf/src/golf/OptionsState.cpp +++ b/samples/golf/src/golf/OptionsState.cpp @@ -3038,6 +3038,7 @@ void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, c auto mouseText = createText(glm::vec2(20.f, 96.f), st.str()); createText(glm::vec2(26.f, 63.f), "Invert X"); createText(glm::vec2(26.f, 47.f), "Invert Y"); + createText(glm::vec2(26.f, 31.f), "Mouse Action"); createText(glm::vec2(112.f, 63.f), "Use Vibration"); createText(glm::vec2(112.f, 47.f), "Hold For Power"); createText(glm::vec2(112.f, 31.f), "Enable Swingput"); @@ -3365,7 +3366,7 @@ void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, c entity.setLabel("With either trigger held, pull back on a thumbstick to charge the power.\nPush forward on the stick to make your shot. Timing is important!"); entity.getComponent().setSelectionIndex(CtrlSwg); entity.getComponent().setNextIndex(CtrlRight, WindowAdvanced); - entity.getComponent().setPrevIndex(CtrlReset, CtrlAltPower); + entity.getComponent().setPrevIndex(CtrlMouseAction, CtrlAltPower); entity.getComponent().callbacks[cro::UIInput::ButtonDown] = uiSystem.addCallback( [&](cro::Entity, cro::ButtonEvent evt) mutable { @@ -3401,7 +3402,7 @@ void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, c entity = createSquareHighlight(glm::vec2(11.f, 38.f)); entity.setLabel("Invert the controller Y axis when in camera mode"); entity.getComponent().setSelectionIndex(CtrlInvY); - entity.getComponent().setNextIndex(CtrlAltPower, CtrlReset); + entity.getComponent().setNextIndex(CtrlAltPower, CtrlMouseAction); entity.getComponent().setPrevIndex(CtrlB, CtrlInvX); entity.getComponent().callbacks[cro::UIInput::ButtonDown] = uiSystem.addCallback( [&](cro::Entity e, cro::ButtonEvent evt) mutable @@ -3435,9 +3436,48 @@ void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, c parent.getComponent().addChild(entity.getComponent()); + //enable mouse click for action button + entity = createSquareHighlight(glm::vec2(11.f, 22.f)); + entity.setLabel("Allow using Left Mouse Button as Action Button"); + entity.getComponent().setSelectionIndex(CtrlMouseAction); + entity.getComponent().setNextIndex(CtrlSwg, CtrlReset); + entity.getComponent().setPrevIndex(CtrlSwg, CtrlInvY); + entity.getComponent().callbacks[cro::UIInput::ButtonDown] = uiSystem.addCallback( + [&](cro::Entity e, cro::ButtonEvent evt) mutable + { + if (activated(evt)) + { + m_sharedData.useMouseAction = !m_sharedData.useMouseAction; + m_audioEnts[AudioID::Back].getComponent().play(); + + m_scene.getActiveCamera().getComponent().active = true; + } + }); + + entity = m_scene.createEntity(); + entity.addComponent().setPosition(glm::vec3(13.f, 24.f, HighlightOffset)); + entity.addComponent().getVertexData() = + { + cro::Vertex2D(glm::vec2(0.f, 7.f), TextGoldColour), + cro::Vertex2D(glm::vec2(0.f), TextGoldColour), + cro::Vertex2D(glm::vec2(7.f), TextGoldColour), + cro::Vertex2D(glm::vec2(7.f, 0.f), TextGoldColour) + }; + entity.getComponent().updateLocalBounds(); + entity.addComponent().active = true; + entity.getComponent().function = + [&](cro::Entity e, float) + { + float scale = m_sharedData.useMouseAction ? 1.f : 0.f; + e.getComponent().setScale(glm::vec2(scale)); + }; + parent.getComponent().addChild(entity.getComponent()); + + + //reset to defaults entity = m_scene.createEntity(); - entity.addComponent().setPosition(glm::vec3(32.f, 11.f, HighlightOffset)); + entity.addComponent().setPosition(glm::vec3(32.f, -1.f, HighlightOffset)); entity.addComponent() = m_menuSounds.getEmitter("switch"); entity.addComponent(); entity.addComponent() = spriteSheet.getSprite("small_highlight"); @@ -3445,7 +3485,7 @@ void OptionsState::buildControlMenu(cro::Entity parent, cro::Entity buttonEnt, c entity.addComponent().setGroup(MenuID::Controls); entity.getComponent().setSelectionIndex(CtrlReset); entity.getComponent().setNextIndex(CtrlSwg, WindowCredits); - entity.getComponent().setPrevIndex(CtrlA, CtrlInvY); + entity.getComponent().setPrevIndex(CtrlA, CtrlMouseAction); auto bounds = entity.getComponent().getTextureBounds(); entity.getComponent().area = bounds; entity.getComponent().callbacks[cro::UIInput::Selected] = uiSystem.addCallback( diff --git a/samples/golf/src/golf/SharedStateData.hpp b/samples/golf/src/golf/SharedStateData.hpp index 5aea5d7bd..91848b613 100644 --- a/samples/golf/src/golf/SharedStateData.hpp +++ b/samples/golf/src/golf/SharedStateData.hpp @@ -274,6 +274,7 @@ struct SharedStateData final bool pressHold = false; //press and hold the action button to select power bool useTTS = false; bool useLensFlare = true; + bool useMouseAction = true; std::int32_t baseState = 0; //used to tell which state we're returning to from errors etc std::unique_ptr sharedResources;