From 52ce862a0036aba0ea500b86bc8bd2c7f60c8a9e Mon Sep 17 00:00:00 2001 From: vittorioromeo Date: Sat, 8 Jun 2024 20:05:39 +0200 Subject: [PATCH] Remove default empty state of `sf::Music` --- examples/sound/Sound.cpp | 4 +- examples/sound_effects/SoundEffects.cpp | 119 ++++++++++-------- include/SFML/Audio/AudioResource.hpp | 27 +++- include/SFML/Audio/Music.hpp | 44 ++++--- include/SFML/Audio/SoundSource.hpp | 14 ++- include/SFML/Audio/SoundStream.hpp | 14 ++- src/SFML/Audio/Music.cpp | 161 ++++++++++++------------ src/SFML/Audio/SoundStream.cpp | 8 ++ test/Audio/AudioResource.test.cpp | 4 +- test/Audio/Music.test.cpp | 67 ++-------- test/Audio/SoundSource.test.cpp | 2 +- test/Audio/SoundStream.test.cpp | 2 +- test/install/Install.cpp | 1 - 13 files changed, 252 insertions(+), 215 deletions(-) diff --git a/examples/sound/Sound.cpp b/examples/sound/Sound.cpp index 9b6c77940..3e27dae01 100644 --- a/examples/sound/Sound.cpp +++ b/examples/sound/Sound.cpp @@ -46,9 +46,7 @@ void playSound() void playMusic(const std::filesystem::path& filename) { // Load an ogg music file - sf::Music music; - if (!music.openFromFile("resources" / filename)) - return; + auto music = sf::Music::openFromFile("resources" / filename).value(); // Display music information std::cout << filename << ":" << '\n' diff --git a/examples/sound_effects/SoundEffects.cpp b/examples/sound_effects/SoundEffects.cpp index ea2dcdda4..84f83f075 100644 --- a/examples/sound_effects/SoundEffects.cpp +++ b/examples/sound_effects/SoundEffects.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace @@ -114,20 +115,23 @@ public: m_listener.setFillColor(sf::Color::Red); // Load the music file - if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) + if (!(m_music = sf::Music::openFromFile(resourcesDir() / "doodle_pop.ogg"))) + { std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::abort(); + } // Set the music to loop - m_music.setLoop(true); + m_music->setLoop(true); // Set attenuation to a nice value - m_music.setAttenuation(0.04f); + m_music->setAttenuation(0.04f); } void onUpdate(float /*time*/, float x, float y) override { m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; - m_music.setPosition({m_position.x, m_position.y, 0.f}); + m_music->setPosition({m_position.x, m_position.y, 0.f}); } void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override @@ -145,19 +149,19 @@ public: // Synchronize listener audio position with graphical position sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); - m_music.play(); + m_music->play(); } void onStop() override { - m_music.stop(); + m_music->stop(); } private: - sf::CircleShape m_listener{20.f}; - sf::CircleShape m_soundShape{20.f}; - sf::Vector2f m_position; - sf::Music m_music; + sf::CircleShape m_listener{20.f}; + sf::CircleShape m_soundShape{20.f}; + sf::Vector2f m_position; + std::optional m_music; }; @@ -173,20 +177,23 @@ public: m_volumeText(getFont(), "Volume: " + std::to_string(m_volume)) { // Load the music file - if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) + if (!(m_music = sf::Music::openFromFile(resourcesDir() / "doodle_pop.ogg"))) + { std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::abort(); + } // Set the music to loop - m_music.setLoop(true); + m_music->setLoop(true); // We don't care about attenuation in this effect - m_music.setAttenuation(0.f); + m_music->setAttenuation(0.f); // Set initial pitch - m_music.setPitch(m_pitch); + m_music->setPitch(m_pitch); // Set initial volume - m_music.setVolume(m_volume); + m_music->setVolume(m_volume); m_pitchText.setPosition({windowWidth / 2.f - 120.f, windowHeight / 2.f - 80.f}); m_volumeText.setPosition({windowWidth / 2.f - 120.f, windowHeight / 2.f - 30.f}); @@ -197,8 +204,8 @@ public: m_pitch = std::clamp(2.f * x, 0.f, 2.f); m_volume = std::clamp(100.f * (1.f - y), 0.f, 100.f); - m_music.setPitch(m_pitch); - m_music.setVolume(m_volume); + m_music->setPitch(m_pitch); + m_music->setVolume(m_volume); m_pitchText.setString("Pitch: " + std::to_string(m_pitch)); m_volumeText.setString("Volume: " + std::to_string(m_volume)); @@ -216,20 +223,20 @@ public: // so that the music is right on top of the listener sf::Listener::setPosition({0.f, 0.f, 0.f}); - m_music.play(); + m_music->play(); } void onStop() override { - m_music.stop(); + m_music->stop(); } private: - float m_pitch{1.f}; - float m_volume{100.f}; - sf::Text m_pitchText; - sf::Text m_volumeText; - sf::Music m_music; + float m_pitch{1.f}; + float m_volume{100.f}; + sf::Text m_pitchText; + sf::Text m_volumeText; + std::optional m_music; }; @@ -276,20 +283,23 @@ public: makeCone(m_soundConeInner, innerConeAngle); // Load the music file - if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) + if (!(m_music = sf::Music::openFromFile(resourcesDir() / "doodle_pop.ogg"))) + { std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::abort(); + } // Set the music to loop - m_music.setLoop(true); + m_music->setLoop(true); // Set attenuation factor - m_music.setAttenuation(m_attenuation); + m_music->setAttenuation(m_attenuation); // Set direction to face "downwards" - m_music.setDirection({0.f, 1.f, 0.f}); + m_music->setDirection({0.f, 1.f, 0.f}); // Set cone - m_music.setCone({innerConeAngle, outerConeAngle, 0.f}); + m_music->setCone({innerConeAngle, outerConeAngle, 0.f}); m_text.setString( "Attenuation factor dampens full volume of sound while within inner cone based on distance to " @@ -304,7 +314,7 @@ public: void onUpdate(float /*time*/, float x, float y) override { m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; - m_music.setPosition({m_position.x, m_position.y, 0.f}); + m_music->setPosition({m_position.x, m_position.y, 0.f}); } void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override @@ -326,22 +336,22 @@ public: // Synchronize listener audio position with graphical position sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); - m_music.play(); + m_music->play(); } void onStop() override { - m_music.stop(); + m_music->stop(); } private: - sf::CircleShape m_listener{20.f}; - sf::CircleShape m_soundShape{20.f}; - sf::ConvexShape m_soundConeOuter; - sf::ConvexShape m_soundConeInner; - sf::Text m_text; - sf::Vector2f m_position; - sf::Music m_music; + sf::CircleShape m_listener{20.f}; + sf::CircleShape m_soundShape{20.f}; + sf::ConvexShape m_soundConeOuter; + sf::ConvexShape m_soundConeInner; + sf::Text m_text; + sf::Vector2f m_position; + std::optional m_music; float m_attenuation{0.01f}; }; @@ -626,7 +636,7 @@ public: void onUpdate([[maybe_unused]] float time, float x, float y) override { m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; - m_music.setPosition({m_position.x, m_position.y, 0.f}); + m_music->setPosition({m_position.x, m_position.y, 0.f}); } void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override @@ -646,12 +656,12 @@ public: // Synchronize listener audio position with graphical position sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); - m_music.play(); + m_music->play(); } void onStop() override { - m_music.stop(); + m_music->stop(); } protected: @@ -667,19 +677,22 @@ protected: m_instructions.setPosition({windowWidth / 2.f - 250.f, windowHeight * 3.f / 4.f}); // Load the music file - if (!m_music.openFromFile(resourcesDir() / "doodle_pop.ogg")) + if (!(m_music = sf::Music::openFromFile(resourcesDir() / "doodle_pop.ogg"))) + { std::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl; + std::abort(); + } // Set the music to loop - m_music.setLoop(true); + m_music->setLoop(true); // Set attenuation to a nice value - m_music.setAttenuation(0.0f); + m_music->setAttenuation(0.0f); } sf::Music& getMusic() { - return m_music; + return *m_music; } const std::shared_ptr& getEnabled() const @@ -696,13 +709,13 @@ private: m_enabledText.setString(*m_enabled ? "Processing: Enabled" : "Processing: Disabled"); } - sf::CircleShape m_listener{20.f}; - sf::CircleShape m_soundShape{20.f}; - sf::Vector2f m_position; - sf::Music m_music; - std::shared_ptr m_enabled{std::make_shared(true)}; - sf::Text m_enabledText; - sf::Text m_instructions; + sf::CircleShape m_listener{20.f}; + sf::CircleShape m_soundShape{20.f}; + sf::Vector2f m_position; + std::optional m_music; + std::shared_ptr m_enabled{std::make_shared(true)}; + sf::Text m_enabledText; + sf::Text m_instructions; }; diff --git a/include/SFML/Audio/AudioResource.hpp b/include/SFML/Audio/AudioResource.hpp index f423b3d8b..cfb4dca29 100644 --- a/include/SFML/Audio/AudioResource.hpp +++ b/include/SFML/Audio/AudioResource.hpp @@ -40,6 +40,31 @@ namespace sf //////////////////////////////////////////////////////////// class SFML_AUDIO_API AudioResource { +public: + //////////////////////////////////////////////////////////// + /// \brief Copy constructor + /// + //////////////////////////////////////////////////////////// + AudioResource(const AudioResource&) = default; + + //////////////////////////////////////////////////////////// + /// \brief Copy assignment + /// + //////////////////////////////////////////////////////////// + AudioResource& operator=(const AudioResource&) = default; + + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + AudioResource(AudioResource&&) noexcept = default; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + AudioResource& operator=(AudioResource&&) noexcept = default; + protected: //////////////////////////////////////////////////////////// /// \brief Default constructor @@ -51,7 +76,7 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - const std::shared_ptr m_device; //!< Sound device + std::shared_ptr m_device; //!< Sound device }; } // namespace sf diff --git a/include/SFML/Audio/Music.hpp b/include/SFML/Audio/Music.hpp index b3ec7020c..fa8a2c55b 100644 --- a/include/SFML/Audio/Music.hpp +++ b/include/SFML/Audio/Music.hpp @@ -29,12 +29,11 @@ //////////////////////////////////////////////////////////// #include -#include #include #include -#include -#include +#include +#include #include #include @@ -44,6 +43,7 @@ namespace sf { class Time; class InputStream; +class InputSoundFile; //////////////////////////////////////////////////////////// /// \brief Streamed music played from an audio file @@ -72,6 +72,18 @@ public: //////////////////////////////////////////////////////////// ~Music() override; + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + Music(Music&&) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + Music& operator=(Music&&) noexcept; + //////////////////////////////////////////////////////////// /// \brief Open a music from an audio file /// @@ -86,12 +98,12 @@ public: /// /// \param filename Path of the music file to open /// - /// \return True if loading succeeded, false if it failed + /// \return Music if loading succeeded, `std::nullopt` if it failed /// /// \see openFromMemory, openFromStream /// //////////////////////////////////////////////////////////// - [[nodiscard]] bool openFromFile(const std::filesystem::path& filename); + [[nodiscard]] static std::optional openFromFile(const std::filesystem::path& filename); //////////////////////////////////////////////////////////// /// \brief Open a music from an audio file in memory @@ -109,12 +121,12 @@ public: /// \param data Pointer to the file data in memory /// \param sizeInBytes Size of the data to load, in bytes /// - /// \return True if loading succeeded, false if it failed + /// \return Music if loading succeeded, `std::nullopt` if it failed /// /// \see openFromFile, openFromStream /// //////////////////////////////////////////////////////////// - [[nodiscard]] bool openFromMemory(const void* data, std::size_t sizeInBytes); + [[nodiscard]] static std::optional openFromMemory(const void* data, std::size_t sizeInBytes); //////////////////////////////////////////////////////////// /// \brief Open a music from an audio file in a custom stream @@ -130,12 +142,12 @@ public: /// /// \param stream Source stream to read from /// - /// \return True if loading succeeded, false if it failed + /// \return Music if loading succeeded, `std::nullopt` if it failed /// /// \see openFromFile, openFromMemory /// //////////////////////////////////////////////////////////// - [[nodiscard]] bool openFromStream(InputStream& stream); + [[nodiscard]] static std::optional openFromStream(InputStream& stream); //////////////////////////////////////////////////////////// /// \brief Get the total duration of the music @@ -219,11 +231,17 @@ protected: std::optional onLoop() override; private: + //////////////////////////////////////////////////////////// + /// \brief Try opening the music file from an optional input sound file + /// + //////////////////////////////////////////////////////////// + [[nodiscard]] static std::optional tryOpenFromFile(std::optional&& optFile); + //////////////////////////////////////////////////////////// /// \brief Initialize the internal state after loading a new music /// //////////////////////////////////////////////////////////// - void initialize(); + explicit Music(InputSoundFile&& file); //////////////////////////////////////////////////////////// /// \brief Helper to convert an sf::Time to a sample position @@ -248,10 +266,8 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - std::optional m_file; //!< The streamed music file - std::vector m_samples; //!< Temporary buffer of samples - std::recursive_mutex m_mutex; //!< Mutex protecting the data - Span m_loopSpan; //!< Loop Range Specifier + struct Impl; + std::unique_ptr m_impl; //!< Implementation details }; } // namespace sf diff --git a/include/SFML/Audio/SoundSource.hpp b/include/SFML/Audio/SoundSource.hpp index 46805d6e5..1a2659e2f 100644 --- a/include/SFML/Audio/SoundSource.hpp +++ b/include/SFML/Audio/SoundSource.hpp @@ -149,10 +149,20 @@ public: //////////////////////////////////////////////////////////// /// \brief Copy constructor /// - /// \param copy Instance to copy + //////////////////////////////////////////////////////////// + SoundSource(const SoundSource&) = default; + + //////////////////////////////////////////////////////////// + /// \brief Move constructor /// //////////////////////////////////////////////////////////// - SoundSource(const SoundSource& copy) = default; + SoundSource(SoundSource&&) noexcept = default; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + SoundSource& operator=(SoundSource&&) noexcept = default; //////////////////////////////////////////////////////////// /// \brief Destructor diff --git a/include/SFML/Audio/SoundStream.hpp b/include/SFML/Audio/SoundStream.hpp index 87ff8240c..fdf94da53 100644 --- a/include/SFML/Audio/SoundStream.hpp +++ b/include/SFML/Audio/SoundStream.hpp @@ -67,6 +67,18 @@ public: //////////////////////////////////////////////////////////// ~SoundStream() override; + //////////////////////////////////////////////////////////// + /// \brief Move constructor + /// + //////////////////////////////////////////////////////////// + SoundStream(SoundStream&&) noexcept; + + //////////////////////////////////////////////////////////// + /// \brief Move assignment + /// + //////////////////////////////////////////////////////////// + SoundStream& operator=(SoundStream&&) noexcept; + //////////////////////////////////////////////////////////// /// \brief Start or resume playing the audio stream /// @@ -286,7 +298,7 @@ private: // Member data //////////////////////////////////////////////////////////// struct Impl; - const std::unique_ptr m_impl; //!< Implementation details + std::unique_ptr m_impl; //!< Implementation details }; } // namespace sf diff --git a/src/SFML/Audio/Music.cpp b/src/SFML/Audio/Music.cpp index 485e3f2a4..fc16d55b3 100644 --- a/src/SFML/Audio/Music.cpp +++ b/src/SFML/Audio/Music.cpp @@ -25,6 +25,7 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include #include #include @@ -34,85 +35,92 @@ #include #include -#include - namespace sf { +//////////////////////////////////////////////////////////// +struct Music::Impl +{ + InputSoundFile file; //!< The streamed music file + std::vector samples; //!< Temporary buffer of samples + std::recursive_mutex mutex; //!< Mutex protecting the data + Span loopSpan; //!< Loop Range Specifier + + explicit Impl(InputSoundFile&& theFile) : + file(std::move(theFile)), + + // Resize the internal buffer so that it can contain 1 second of audio samples + samples(file.getSampleRate() * file.getChannelCount()), + + // Compute the music positions + loopSpan{0u, file.getSampleCount()} + { + } +}; + + //////////////////////////////////////////////////////////// Music::~Music() { // We must stop before destroying the file - stop(); + if (m_impl != nullptr) + { + stop(); + } } //////////////////////////////////////////////////////////// -bool Music::openFromFile(const std::filesystem::path& filename) +Music::Music(Music&&) noexcept = default; + + +//////////////////////////////////////////////////////////// +Music& Music::operator=(Music&&) noexcept = default; + + +//////////////////////////////////////////////////////////// +std::optional Music::tryOpenFromFile(std::optional&& optFile) { - // First stop the music if it was already running - stop(); + if (!optFile.has_value()) + return std::nullopt; - // Open the underlying sound file - m_file = sf::InputSoundFile::openFromFile(filename); - if (!m_file) - return false; - - // Perform common initializations - initialize(); - - return true; + // TODO: apply RVO here via passkey idiom + return Music(std::move(*optFile)); } //////////////////////////////////////////////////////////// -bool Music::openFromMemory(const void* data, std::size_t sizeInBytes) +std::optional Music::openFromFile(const std::filesystem::path& filename) { - // First stop the music if it was already running - stop(); - - // Open the underlying sound file - m_file = sf::InputSoundFile::openFromMemory(data, sizeInBytes); - if (!m_file) - return false; - - // Perform common initializations - initialize(); - - return true; + return tryOpenFromFile(InputSoundFile::openFromFile(filename)); } //////////////////////////////////////////////////////////// -bool Music::openFromStream(InputStream& stream) +std::optional Music::openFromMemory(const void* data, std::size_t sizeInBytes) { - // First stop the music if it was already running - stop(); + return tryOpenFromFile(InputSoundFile::openFromMemory(data, sizeInBytes)); +} - // Open the underlying sound file - m_file = sf::InputSoundFile::openFromStream(stream); - if (!m_file) - return false; - // Perform common initializations - initialize(); - - return true; +//////////////////////////////////////////////////////////// +std::optional Music::openFromStream(InputStream& stream) +{ + return tryOpenFromFile(InputSoundFile::openFromStream(stream)); } //////////////////////////////////////////////////////////// Time Music::getDuration() const { - assert(m_file && "Music::getDuration() Cannot get duration until music is opened"); - return m_file->getDuration(); + return m_impl->file.getDuration(); } //////////////////////////////////////////////////////////// Music::TimeSpan Music::getLoopPoints() const { - return TimeSpan{samplesToTime(m_loopSpan.offset), samplesToTime(m_loopSpan.length)}; + return TimeSpan{samplesToTime(m_impl->loopSpan.offset), samplesToTime(m_impl->loopSpan.length)}; } @@ -122,8 +130,7 @@ void Music::setLoopPoints(TimeSpan timePoints) Span samplePoints{timeToSamples(timePoints.offset), timeToSamples(timePoints.length)}; // Check our state. This averts a divide-by-zero. GetChannelCount() is cheap enough to use often - assert(m_file && "Music::setLoopPoints() Cannot set loop points unit music is opened"); - if (getChannelCount() == 0 || m_file->getSampleCount() == 0) + if (getChannelCount() == 0 || m_impl->file.getSampleCount() == 0) { err() << "Music is not in a valid state to assign Loop Points." << std::endl; return; @@ -136,11 +143,12 @@ void Music::setLoopPoints(TimeSpan timePoints) samplePoints.length -= (samplePoints.length % getChannelCount()); // Validate - if (samplePoints.offset >= m_file->getSampleCount()) + if (samplePoints.offset >= m_impl->file.getSampleCount()) { err() << "LoopPoints offset val must be in range [0, Duration)." << std::endl; return; } + if (samplePoints.length == 0) { err() << "LoopPoints length val must be nonzero." << std::endl; @@ -148,10 +156,10 @@ void Music::setLoopPoints(TimeSpan timePoints) } // Clamp End Point - samplePoints.length = std::min(samplePoints.length, m_file->getSampleCount() - samplePoints.offset); + samplePoints.length = std::min(samplePoints.length, m_impl->file.getSampleCount() - samplePoints.offset); // If this change has no effect, we can return without touching anything - if (samplePoints.offset == m_loopSpan.offset && samplePoints.length == m_loopSpan.length) + if (samplePoints.offset == m_impl->loopSpan.offset && samplePoints.length == m_impl->loopSpan.length) return; // When we apply this change, we need to "reset" this instance and its buffer @@ -164,7 +172,7 @@ void Music::setLoopPoints(TimeSpan timePoints) stop(); // Set - m_loopSpan = samplePoints; + m_impl->loopSpan = samplePoints; // Restore if (oldPos != Time::Zero) @@ -179,80 +187,71 @@ void Music::setLoopPoints(TimeSpan timePoints) //////////////////////////////////////////////////////////// bool Music::onGetData(SoundStream::Chunk& data) { - assert(m_file && "Music::onGetData() Cannot perform operation until music is opened"); + const std::lock_guard lock(m_impl->mutex); - const std::lock_guard lock(m_mutex); - - std::size_t toFill = m_samples.size(); - std::uint64_t currentOffset = m_file->getSampleOffset(); - const std::uint64_t loopEnd = m_loopSpan.offset + m_loopSpan.length; + std::size_t toFill = m_impl->samples.size(); + std::uint64_t currentOffset = m_impl->file.getSampleOffset(); + const std::uint64_t loopEnd = m_impl->loopSpan.offset + m_impl->loopSpan.length; // If the loop end is enabled and imminent, request less data. // This will trip an "onLoop()" call from the underlying SoundStream, // and we can then take action. - if (getLoop() && (m_loopSpan.length != 0) && (currentOffset <= loopEnd) && (currentOffset + toFill > loopEnd)) + if (getLoop() && (m_impl->loopSpan.length != 0) && (currentOffset <= loopEnd) && (currentOffset + toFill > loopEnd)) toFill = static_cast(loopEnd - currentOffset); // Fill the chunk parameters - data.samples = m_samples.data(); - data.sampleCount = static_cast(m_file->read(m_samples.data(), toFill)); + data.samples = m_impl->samples.data(); + data.sampleCount = static_cast(m_impl->file.read(m_impl->samples.data(), toFill)); currentOffset += data.sampleCount; // Check if we have stopped obtaining samples or reached either the EOF or the loop end point - return (data.sampleCount != 0) && (currentOffset < m_file->getSampleCount()) && - (currentOffset != loopEnd || m_loopSpan.length == 0); + return (data.sampleCount != 0) && (currentOffset < m_impl->file.getSampleCount()) && + (currentOffset != loopEnd || m_impl->loopSpan.length == 0); } //////////////////////////////////////////////////////////// void Music::onSeek(Time timeOffset) { - assert(m_file && "Music::onSeek() Cannot perform operation until music is opened"); - - const std::lock_guard lock(m_mutex); - m_file->seek(timeOffset); + const std::lock_guard lock(m_impl->mutex); + m_impl->file.seek(timeOffset); } //////////////////////////////////////////////////////////// std::optional Music::onLoop() { - assert(m_file && "Music::onLoop() Cannot perform operation until music is opened"); - // Called by underlying SoundStream so we can determine where to loop. - const std::lock_guard lock(m_mutex); - const std::uint64_t currentOffset = m_file->getSampleOffset(); - if (getLoop() && (m_loopSpan.length != 0) && (currentOffset == m_loopSpan.offset + m_loopSpan.length)) + const std::lock_guard lock(m_impl->mutex); + const std::uint64_t currentOffset = m_impl->file.getSampleOffset(); + + if (getLoop() && (m_impl->loopSpan.length != 0) && (currentOffset == m_impl->loopSpan.offset + m_impl->loopSpan.length)) { // Looping is enabled, and either we're at the loop end, or we're at the EOF // when it's equivalent to the loop end (loop end takes priority). Send us to loop begin - m_file->seek(m_loopSpan.offset); - return m_file->getSampleOffset(); + m_impl->file.seek(m_impl->loopSpan.offset); + return m_impl->file.getSampleOffset(); } - else if (getLoop() && (currentOffset >= m_file->getSampleCount())) + + if (getLoop() && (currentOffset >= m_impl->file.getSampleCount())) { // If we're at the EOF, reset to 0 - m_file->seek(0); + m_impl->file.seek(0); return 0; } + return std::nullopt; } //////////////////////////////////////////////////////////// -void Music::initialize() +Music::Music(InputSoundFile&& file) : m_impl(std::make_unique(std::move(file))) { - // Compute the music positions - m_loopSpan.offset = 0; - m_loopSpan.length = m_file->getSampleCount(); - - // Resize the internal buffer so that it can contain 1 second of audio samples - m_samples.resize(m_file->getSampleRate() * m_file->getChannelCount()); - // Initialize the stream - SoundStream::initialize(m_file->getChannelCount(), m_file->getSampleRate(), m_file->getChannelMap()); + SoundStream::initialize(m_impl->file.getChannelCount(), m_impl->file.getSampleRate(), m_impl->file.getChannelMap()); } + //////////////////////////////////////////////////////////// std::uint64_t Music::timeToSamples(Time position) const { diff --git a/src/SFML/Audio/SoundStream.cpp b/src/SFML/Audio/SoundStream.cpp index ed7cf7319..e15c639af 100644 --- a/src/SFML/Audio/SoundStream.cpp +++ b/src/SFML/Audio/SoundStream.cpp @@ -233,6 +233,14 @@ SoundStream::SoundStream() : m_impl(std::make_unique(this)) SoundStream::~SoundStream() = default; +//////////////////////////////////////////////////////////// +SoundStream::SoundStream(SoundStream&&) noexcept = default; + + +//////////////////////////////////////////////////////////// +SoundStream& SoundStream::operator=(SoundStream&&) noexcept = default; + + //////////////////////////////////////////////////////////// void SoundStream::initialize(unsigned int channelCount, unsigned int sampleRate, const std::vector& channelMap) { diff --git a/test/Audio/AudioResource.test.cpp b/test/Audio/AudioResource.test.cpp index 5cc5e6175..abed2d582 100644 --- a/test/Audio/AudioResource.test.cpp +++ b/test/Audio/AudioResource.test.cpp @@ -4,6 +4,6 @@ static_assert(!std::is_constructible_v); static_assert(std::is_copy_constructible_v); -static_assert(!std::is_copy_assignable_v); +static_assert(std::is_copy_assignable_v); static_assert(std::is_nothrow_move_constructible_v); -static_assert(!std::is_nothrow_move_assignable_v); +static_assert(std::is_nothrow_move_assignable_v); diff --git a/test/Audio/Music.test.cpp b/test/Audio/Music.test.cpp index 2219cf2a6..0e328039a 100644 --- a/test/Audio/Music.test.cpp +++ b/test/Audio/Music.test.cpp @@ -7,8 +7,6 @@ #include #include -#include -#include #include #include @@ -18,8 +16,8 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) { STATIC_CHECK(!std::is_copy_constructible_v); STATIC_CHECK(!std::is_copy_assignable_v); - STATIC_CHECK(!std::is_nothrow_move_constructible_v); - STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::is_nothrow_move_constructible_v); + STATIC_CHECK(std::is_nothrow_move_assignable_v); STATIC_CHECK(std::has_virtual_destructor_v); } @@ -34,39 +32,16 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) CHECK(timeSpan.length == sf::Time::Zero); } - SECTION("Construction") - { - const sf::Music music; - const auto [offset, length] = music.getLoopPoints(); - CHECK(offset == sf::Time::Zero); - CHECK(length == sf::Time::Zero); - CHECK(music.getChannelCount() == 0); - CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::Music::Status::Stopped); - CHECK(music.getPlayingOffset() == sf::Time::Zero); - CHECK(!music.getLoop()); - } - SECTION("openFromFile()") { - sf::Music music; - SECTION("Invalid file") { - REQUIRE(!music.openFromFile("does/not/exist.wav")); - const auto [offset, length] = music.getLoopPoints(); - CHECK(offset == sf::Time::Zero); - CHECK(length == sf::Time::Zero); - CHECK(music.getChannelCount() == 0); - CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::Music::Status::Stopped); - CHECK(music.getPlayingOffset() == sf::Time::Zero); - CHECK(!music.getLoop()); + CHECK(!sf::Music::openFromFile("does/not/exist.wav")); } SECTION("Valid file") { - REQUIRE(music.openFromFile("Audio/ding.mp3")); + const auto music = sf::Music::openFromFile("Audio/ding.mp3").value(); CHECK(music.getDuration() == sf::microseconds(1990884)); const auto [offset, length] = music.getLoopPoints(); CHECK(offset == sf::Time::Zero); @@ -82,25 +57,17 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) SECTION("openFromMemory()") { std::vector memory; - sf::Music music; SECTION("Invalid buffer") { - REQUIRE(!music.openFromMemory(memory.data(), memory.size())); - const auto [offset, length] = music.getLoopPoints(); - CHECK(offset == sf::Time::Zero); - CHECK(length == sf::Time::Zero); - CHECK(music.getChannelCount() == 0); - CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::Music::Status::Stopped); - CHECK(music.getPlayingOffset() == sf::Time::Zero); - CHECK(!music.getLoop()); + CHECK(!sf::Music::openFromMemory(memory.data(), memory.size())); } SECTION("Valid buffer") { memory = loadIntoMemory("Audio/ding.flac"); - REQUIRE(music.openFromMemory(memory.data(), memory.size())); + + const auto music = sf::Music::openFromMemory(memory.data(), memory.size()).value(); CHECK(music.getDuration() == sf::microseconds(1990884)); const auto [offset, length] = music.getLoopPoints(); CHECK(offset == sf::Time::Zero); @@ -116,25 +83,17 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) SECTION("openFromStream()") { sf::FileInputStream stream; - sf::Music music; SECTION("Invalid stream") { - CHECK(!music.openFromStream(stream)); - const auto [offset, length] = music.getLoopPoints(); - CHECK(offset == sf::Time::Zero); - CHECK(length == sf::Time::Zero); - CHECK(music.getChannelCount() == 0); - CHECK(music.getSampleRate() == 0); - CHECK(music.getStatus() == sf::Music::Status::Stopped); - CHECK(music.getPlayingOffset() == sf::Time::Zero); - CHECK(!music.getLoop()); + CHECK(!sf::Music::openFromStream(stream)); } SECTION("Valid stream") { REQUIRE(stream.open("Audio/doodle_pop.ogg")); - REQUIRE(music.openFromStream(stream)); + + const auto music = sf::Music::openFromStream(stream).value(); CHECK(music.getDuration() == sf::microseconds(24002176)); const auto [offset, length] = music.getLoopPoints(); CHECK(offset == sf::Time::Zero); @@ -149,8 +108,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) SECTION("play/pause/stop") { - sf::Music music; - REQUIRE(music.openFromFile("Audio/ding.mp3")); + auto music = sf::Music::openFromFile("Audio/ding.mp3").value(); // Wait for background thread to start music.play(); @@ -173,8 +131,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests()) SECTION("setLoopPoints()") { - sf::Music music; - REQUIRE(music.openFromFile("Audio/killdeer.wav")); + auto music = sf::Music::openFromFile("Audio/killdeer.wav").value(); music.setLoopPoints({sf::seconds(1), sf::seconds(2)}); const auto [offset, length] = music.getLoopPoints(); CHECK(offset == sf::seconds(1)); diff --git a/test/Audio/SoundSource.test.cpp b/test/Audio/SoundSource.test.cpp index 8fafb31dc..41c78fd0d 100644 --- a/test/Audio/SoundSource.test.cpp +++ b/test/Audio/SoundSource.test.cpp @@ -44,7 +44,7 @@ TEST_CASE("[Audio] sf::SoundSource", runAudioDeviceTests()) STATIC_CHECK(std::is_copy_assignable_v); STATIC_CHECK(!std::is_move_constructible_v); STATIC_CHECK(std::is_move_assignable_v); - STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::is_nothrow_move_assignable_v); STATIC_CHECK(std::has_virtual_destructor_v); } diff --git a/test/Audio/SoundStream.test.cpp b/test/Audio/SoundStream.test.cpp index 42d04897a..e90651d0d 100644 --- a/test/Audio/SoundStream.test.cpp +++ b/test/Audio/SoundStream.test.cpp @@ -29,7 +29,7 @@ TEST_CASE("[Audio] sf::SoundStream", runAudioDeviceTests()) STATIC_CHECK(!std::is_copy_constructible_v); STATIC_CHECK(!std::is_copy_assignable_v); STATIC_CHECK(!std::is_nothrow_move_constructible_v); - STATIC_CHECK(!std::is_nothrow_move_assignable_v); + STATIC_CHECK(std::is_nothrow_move_assignable_v); STATIC_CHECK(std::has_virtual_destructor_v); } diff --git a/test/install/Install.cpp b/test/install/Install.cpp index 6027cf7e3..ad78970ad 100644 --- a/test/install/Install.cpp +++ b/test/install/Install.cpp @@ -9,7 +9,6 @@ int main() { // Audio [[maybe_unused]] const sf::SoundBufferRecorder soundBufferRecorder; - [[maybe_unused]] const sf::Music music; // Graphics [[maybe_unused]] const sf::Color color;