Remove default empty state of sf::Music

This commit is contained in:
vittorioromeo 2024-06-08 20:05:39 +02:00 committed by Vittorio Romeo
parent e7d67cfa2a
commit 52ce862a00
13 changed files with 252 additions and 215 deletions

View File

@ -46,9 +46,7 @@ void playSound()
void playMusic(const std::filesystem::path& filename) void playMusic(const std::filesystem::path& filename)
{ {
// Load an ogg music file // Load an ogg music file
sf::Music music; auto music = sf::Music::openFromFile("resources" / filename).value();
if (!music.openFromFile("resources" / filename))
return;
// Display music information // Display music information
std::cout << filename << ":" << '\n' std::cout << filename << ":" << '\n'

View File

@ -12,6 +12,7 @@
#include <vector> #include <vector>
#include <cmath> #include <cmath>
#include <cstdlib>
namespace namespace
@ -114,20 +115,23 @@ public:
m_listener.setFillColor(sf::Color::Red); m_listener.setFillColor(sf::Color::Red);
// Load the music file // 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::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
std::abort();
}
// Set the music to loop // Set the music to loop
m_music.setLoop(true); m_music->setLoop(true);
// Set attenuation to a nice value // 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 void onUpdate(float /*time*/, float x, float y) override
{ {
m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; 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 void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
@ -145,19 +149,19 @@ public:
// Synchronize listener audio position with graphical position // Synchronize listener audio position with graphical position
sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f});
m_music.play(); m_music->play();
} }
void onStop() override void onStop() override
{ {
m_music.stop(); m_music->stop();
} }
private: private:
sf::CircleShape m_listener{20.f}; sf::CircleShape m_listener{20.f};
sf::CircleShape m_soundShape{20.f}; sf::CircleShape m_soundShape{20.f};
sf::Vector2f m_position; sf::Vector2f m_position;
sf::Music m_music; std::optional<sf::Music> m_music;
}; };
@ -173,20 +177,23 @@ public:
m_volumeText(getFont(), "Volume: " + std::to_string(m_volume)) m_volumeText(getFont(), "Volume: " + std::to_string(m_volume))
{ {
// Load the music file // 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::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
std::abort();
}
// Set the music to loop // Set the music to loop
m_music.setLoop(true); m_music->setLoop(true);
// We don't care about attenuation in this effect // We don't care about attenuation in this effect
m_music.setAttenuation(0.f); m_music->setAttenuation(0.f);
// Set initial pitch // Set initial pitch
m_music.setPitch(m_pitch); m_music->setPitch(m_pitch);
// Set initial volume // 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_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}); 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_pitch = std::clamp(2.f * x, 0.f, 2.f);
m_volume = std::clamp(100.f * (1.f - y), 0.f, 100.f); m_volume = std::clamp(100.f * (1.f - y), 0.f, 100.f);
m_music.setPitch(m_pitch); m_music->setPitch(m_pitch);
m_music.setVolume(m_volume); m_music->setVolume(m_volume);
m_pitchText.setString("Pitch: " + std::to_string(m_pitch)); m_pitchText.setString("Pitch: " + std::to_string(m_pitch));
m_volumeText.setString("Volume: " + std::to_string(m_volume)); 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 // so that the music is right on top of the listener
sf::Listener::setPosition({0.f, 0.f, 0.f}); sf::Listener::setPosition({0.f, 0.f, 0.f});
m_music.play(); m_music->play();
} }
void onStop() override void onStop() override
{ {
m_music.stop(); m_music->stop();
} }
private: private:
float m_pitch{1.f}; float m_pitch{1.f};
float m_volume{100.f}; float m_volume{100.f};
sf::Text m_pitchText; sf::Text m_pitchText;
sf::Text m_volumeText; sf::Text m_volumeText;
sf::Music m_music; std::optional<sf::Music> m_music;
}; };
@ -276,20 +283,23 @@ public:
makeCone(m_soundConeInner, innerConeAngle); makeCone(m_soundConeInner, innerConeAngle);
// Load the music file // 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::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
std::abort();
}
// Set the music to loop // Set the music to loop
m_music.setLoop(true); m_music->setLoop(true);
// Set attenuation factor // Set attenuation factor
m_music.setAttenuation(m_attenuation); m_music->setAttenuation(m_attenuation);
// Set direction to face "downwards" // 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 // Set cone
m_music.setCone({innerConeAngle, outerConeAngle, 0.f}); m_music->setCone({innerConeAngle, outerConeAngle, 0.f});
m_text.setString( m_text.setString(
"Attenuation factor dampens full volume of sound while within inner cone based on distance to " "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 void onUpdate(float /*time*/, float x, float y) override
{ {
m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; 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 void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
@ -326,22 +336,22 @@ public:
// Synchronize listener audio position with graphical position // Synchronize listener audio position with graphical position
sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f});
m_music.play(); m_music->play();
} }
void onStop() override void onStop() override
{ {
m_music.stop(); m_music->stop();
} }
private: private:
sf::CircleShape m_listener{20.f}; sf::CircleShape m_listener{20.f};
sf::CircleShape m_soundShape{20.f}; sf::CircleShape m_soundShape{20.f};
sf::ConvexShape m_soundConeOuter; sf::ConvexShape m_soundConeOuter;
sf::ConvexShape m_soundConeInner; sf::ConvexShape m_soundConeInner;
sf::Text m_text; sf::Text m_text;
sf::Vector2f m_position; sf::Vector2f m_position;
sf::Music m_music; std::optional<sf::Music> m_music;
float m_attenuation{0.01f}; float m_attenuation{0.01f};
}; };
@ -626,7 +636,7 @@ public:
void onUpdate([[maybe_unused]] float time, float x, float y) override void onUpdate([[maybe_unused]] float time, float x, float y) override
{ {
m_position = {windowWidth * x - 10.f, windowHeight * y - 10.f}; 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 void onDraw(sf::RenderTarget& target, sf::RenderStates states) const override
@ -646,12 +656,12 @@ public:
// Synchronize listener audio position with graphical position // Synchronize listener audio position with graphical position
sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f}); sf::Listener::setPosition({m_listener.getPosition().x, m_listener.getPosition().y, 0.f});
m_music.play(); m_music->play();
} }
void onStop() override void onStop() override
{ {
m_music.stop(); m_music->stop();
} }
protected: protected:
@ -667,19 +677,22 @@ protected:
m_instructions.setPosition({windowWidth / 2.f - 250.f, windowHeight * 3.f / 4.f}); m_instructions.setPosition({windowWidth / 2.f - 250.f, windowHeight * 3.f / 4.f});
// Load the music file // 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::cerr << "Failed to load " << (resourcesDir() / "doodle_pop.ogg").string() << std::endl;
std::abort();
}
// Set the music to loop // Set the music to loop
m_music.setLoop(true); m_music->setLoop(true);
// Set attenuation to a nice value // Set attenuation to a nice value
m_music.setAttenuation(0.0f); m_music->setAttenuation(0.0f);
} }
sf::Music& getMusic() sf::Music& getMusic()
{ {
return m_music; return *m_music;
} }
const std::shared_ptr<bool>& getEnabled() const const std::shared_ptr<bool>& getEnabled() const
@ -696,13 +709,13 @@ private:
m_enabledText.setString(*m_enabled ? "Processing: Enabled" : "Processing: Disabled"); m_enabledText.setString(*m_enabled ? "Processing: Enabled" : "Processing: Disabled");
} }
sf::CircleShape m_listener{20.f}; sf::CircleShape m_listener{20.f};
sf::CircleShape m_soundShape{20.f}; sf::CircleShape m_soundShape{20.f};
sf::Vector2f m_position; sf::Vector2f m_position;
sf::Music m_music; std::optional<sf::Music> m_music;
std::shared_ptr<bool> m_enabled{std::make_shared<bool>(true)}; std::shared_ptr<bool> m_enabled{std::make_shared<bool>(true)};
sf::Text m_enabledText; sf::Text m_enabledText;
sf::Text m_instructions; sf::Text m_instructions;
}; };

View File

@ -40,6 +40,31 @@ namespace sf
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
class SFML_AUDIO_API AudioResource 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: protected:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Default constructor /// \brief Default constructor
@ -51,7 +76,7 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const std::shared_ptr<void> m_device; //!< Sound device std::shared_ptr<void> m_device; //!< Sound device
}; };
} // namespace sf } // namespace sf

View File

@ -29,12 +29,11 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Audio/Export.hpp> #include <SFML/Audio/Export.hpp>
#include <SFML/Audio/InputSoundFile.hpp>
#include <SFML/Audio/SoundStream.hpp> #include <SFML/Audio/SoundStream.hpp>
#include <filesystem> #include <filesystem>
#include <mutex> #include <memory>
#include <vector> #include <optional>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -44,6 +43,7 @@ namespace sf
{ {
class Time; class Time;
class InputStream; class InputStream;
class InputSoundFile;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Streamed music played from an audio file /// \brief Streamed music played from an audio file
@ -72,6 +72,18 @@ public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
~Music() override; ~Music() override;
////////////////////////////////////////////////////////////
/// \brief Move constructor
///
////////////////////////////////////////////////////////////
Music(Music&&) noexcept;
////////////////////////////////////////////////////////////
/// \brief Move assignment
///
////////////////////////////////////////////////////////////
Music& operator=(Music&&) noexcept;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Open a music from an audio file /// \brief Open a music from an audio file
/// ///
@ -86,12 +98,12 @@ public:
/// ///
/// \param filename Path of the music file to open /// \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 /// \see openFromMemory, openFromStream
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromFile(const std::filesystem::path& filename); [[nodiscard]] static std::optional<Music> openFromFile(const std::filesystem::path& filename);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Open a music from an audio file in memory /// \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 data Pointer to the file data in memory
/// \param sizeInBytes Size of the data to load, in bytes /// \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 /// \see openFromFile, openFromStream
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromMemory(const void* data, std::size_t sizeInBytes); [[nodiscard]] static std::optional<Music> openFromMemory(const void* data, std::size_t sizeInBytes);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Open a music from an audio file in a custom stream /// \brief Open a music from an audio file in a custom stream
@ -130,12 +142,12 @@ public:
/// ///
/// \param stream Source stream to read from /// \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 /// \see openFromFile, openFromMemory
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromStream(InputStream& stream); [[nodiscard]] static std::optional<Music> openFromStream(InputStream& stream);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the total duration of the music /// \brief Get the total duration of the music
@ -219,11 +231,17 @@ protected:
std::optional<std::uint64_t> onLoop() override; std::optional<std::uint64_t> onLoop() override;
private: private:
////////////////////////////////////////////////////////////
/// \brief Try opening the music file from an optional input sound file
///
////////////////////////////////////////////////////////////
[[nodiscard]] static std::optional<Music> tryOpenFromFile(std::optional<InputSoundFile>&& optFile);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Initialize the internal state after loading a new music /// \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 /// \brief Helper to convert an sf::Time to a sample position
@ -248,10 +266,8 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::optional<InputSoundFile> m_file; //!< The streamed music file struct Impl;
std::vector<std::int16_t> m_samples; //!< Temporary buffer of samples std::unique_ptr<Impl> m_impl; //!< Implementation details
std::recursive_mutex m_mutex; //!< Mutex protecting the data
Span<std::uint64_t> m_loopSpan; //!< Loop Range Specifier
}; };
} // namespace sf } // namespace sf

View File

@ -149,10 +149,20 @@ public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Copy constructor /// \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 /// \brief Destructor

View File

@ -67,6 +67,18 @@ public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
~SoundStream() override; ~SoundStream() override;
////////////////////////////////////////////////////////////
/// \brief Move constructor
///
////////////////////////////////////////////////////////////
SoundStream(SoundStream&&) noexcept;
////////////////////////////////////////////////////////////
/// \brief Move assignment
///
////////////////////////////////////////////////////////////
SoundStream& operator=(SoundStream&&) noexcept;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Start or resume playing the audio stream /// \brief Start or resume playing the audio stream
/// ///
@ -286,7 +298,7 @@ private:
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
struct Impl; struct Impl;
const std::unique_ptr<Impl> m_impl; //!< Implementation details std::unique_ptr<Impl> m_impl; //!< Implementation details
}; };
} // namespace sf } // namespace sf

View File

@ -25,6 +25,7 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Audio/InputSoundFile.hpp>
#include <SFML/Audio/Music.hpp> #include <SFML/Audio/Music.hpp>
#include <SFML/System/Err.hpp> #include <SFML/System/Err.hpp>
@ -34,85 +35,92 @@
#include <mutex> #include <mutex>
#include <ostream> #include <ostream>
#include <cassert>
namespace sf namespace sf
{ {
////////////////////////////////////////////////////////////
struct Music::Impl
{
InputSoundFile file; //!< The streamed music file
std::vector<std::int16_t> samples; //!< Temporary buffer of samples
std::recursive_mutex mutex; //!< Mutex protecting the data
Span<std::uint64_t> 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() Music::~Music()
{ {
// We must stop before destroying the file // 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> Music::tryOpenFromFile(std::optional<InputSoundFile>&& optFile)
{ {
// First stop the music if it was already running if (!optFile.has_value())
stop(); return std::nullopt;
// Open the underlying sound file // TODO: apply RVO here via passkey idiom
m_file = sf::InputSoundFile::openFromFile(filename); return Music(std::move(*optFile));
if (!m_file)
return false;
// Perform common initializations
initialize();
return true;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool Music::openFromMemory(const void* data, std::size_t sizeInBytes) std::optional<Music> Music::openFromFile(const std::filesystem::path& filename)
{ {
// First stop the music if it was already running return tryOpenFromFile(InputSoundFile::openFromFile(filename));
stop();
// Open the underlying sound file
m_file = sf::InputSoundFile::openFromMemory(data, sizeInBytes);
if (!m_file)
return false;
// Perform common initializations
initialize();
return true;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool Music::openFromStream(InputStream& stream) std::optional<Music> Music::openFromMemory(const void* data, std::size_t sizeInBytes)
{ {
// First stop the music if it was already running return tryOpenFromFile(InputSoundFile::openFromMemory(data, sizeInBytes));
stop(); }
// Open the underlying sound file
m_file = sf::InputSoundFile::openFromStream(stream);
if (!m_file)
return false;
// Perform common initializations ////////////////////////////////////////////////////////////
initialize(); std::optional<Music> Music::openFromStream(InputStream& stream)
{
return true; return tryOpenFromFile(InputSoundFile::openFromStream(stream));
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time Music::getDuration() const Time Music::getDuration() const
{ {
assert(m_file && "Music::getDuration() Cannot get duration until music is opened"); return m_impl->file.getDuration();
return m_file->getDuration();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Music::TimeSpan Music::getLoopPoints() const 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<std::uint64_t> samplePoints{timeToSamples(timePoints.offset), timeToSamples(timePoints.length)}; Span<std::uint64_t> samplePoints{timeToSamples(timePoints.offset), timeToSamples(timePoints.length)};
// Check our state. This averts a divide-by-zero. GetChannelCount() is cheap enough to use often // 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_impl->file.getSampleCount() == 0)
if (getChannelCount() == 0 || m_file->getSampleCount() == 0)
{ {
err() << "Music is not in a valid state to assign Loop Points." << std::endl; err() << "Music is not in a valid state to assign Loop Points." << std::endl;
return; return;
@ -136,11 +143,12 @@ void Music::setLoopPoints(TimeSpan timePoints)
samplePoints.length -= (samplePoints.length % getChannelCount()); samplePoints.length -= (samplePoints.length % getChannelCount());
// Validate // 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; err() << "LoopPoints offset val must be in range [0, Duration)." << std::endl;
return; return;
} }
if (samplePoints.length == 0) if (samplePoints.length == 0)
{ {
err() << "LoopPoints length val must be nonzero." << std::endl; err() << "LoopPoints length val must be nonzero." << std::endl;
@ -148,10 +156,10 @@ void Music::setLoopPoints(TimeSpan timePoints)
} }
// Clamp End Point // 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 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; return;
// When we apply this change, we need to "reset" this instance and its buffer // When we apply this change, we need to "reset" this instance and its buffer
@ -164,7 +172,7 @@ void Music::setLoopPoints(TimeSpan timePoints)
stop(); stop();
// Set // Set
m_loopSpan = samplePoints; m_impl->loopSpan = samplePoints;
// Restore // Restore
if (oldPos != Time::Zero) if (oldPos != Time::Zero)
@ -179,80 +187,71 @@ void Music::setLoopPoints(TimeSpan timePoints)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool Music::onGetData(SoundStream::Chunk& data) 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_impl->samples.size();
std::uint64_t currentOffset = m_impl->file.getSampleOffset();
std::size_t toFill = m_samples.size(); const std::uint64_t loopEnd = m_impl->loopSpan.offset + m_impl->loopSpan.length;
std::uint64_t currentOffset = m_file->getSampleOffset();
const std::uint64_t loopEnd = m_loopSpan.offset + m_loopSpan.length;
// If the loop end is enabled and imminent, request less data. // If the loop end is enabled and imminent, request less data.
// This will trip an "onLoop()" call from the underlying SoundStream, // This will trip an "onLoop()" call from the underlying SoundStream,
// and we can then take action. // 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<std::size_t>(loopEnd - currentOffset); toFill = static_cast<std::size_t>(loopEnd - currentOffset);
// Fill the chunk parameters // Fill the chunk parameters
data.samples = m_samples.data(); data.samples = m_impl->samples.data();
data.sampleCount = static_cast<std::size_t>(m_file->read(m_samples.data(), toFill)); data.sampleCount = static_cast<std::size_t>(m_impl->file.read(m_impl->samples.data(), toFill));
currentOffset += data.sampleCount; currentOffset += data.sampleCount;
// Check if we have stopped obtaining samples or reached either the EOF or the loop end point // 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()) && return (data.sampleCount != 0) && (currentOffset < m_impl->file.getSampleCount()) &&
(currentOffset != loopEnd || m_loopSpan.length == 0); (currentOffset != loopEnd || m_impl->loopSpan.length == 0);
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void Music::onSeek(Time timeOffset) void Music::onSeek(Time timeOffset)
{ {
assert(m_file && "Music::onSeek() Cannot perform operation until music is opened"); const std::lock_guard lock(m_impl->mutex);
m_impl->file.seek(timeOffset);
const std::lock_guard lock(m_mutex);
m_file->seek(timeOffset);
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::optional<std::uint64_t> Music::onLoop() std::optional<std::uint64_t> 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. // Called by underlying SoundStream so we can determine where to loop.
const std::lock_guard lock(m_mutex); const std::lock_guard lock(m_impl->mutex);
const std::uint64_t currentOffset = m_file->getSampleOffset(); const std::uint64_t currentOffset = m_impl->file.getSampleOffset();
if (getLoop() && (m_loopSpan.length != 0) && (currentOffset == m_loopSpan.offset + m_loopSpan.length))
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 // 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 // when it's equivalent to the loop end (loop end takes priority). Send us to loop begin
m_file->seek(m_loopSpan.offset); m_impl->file.seek(m_impl->loopSpan.offset);
return m_file->getSampleOffset(); 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 // If we're at the EOF, reset to 0
m_file->seek(0); m_impl->file.seek(0);
return 0; return 0;
} }
return std::nullopt; return std::nullopt;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void Music::initialize() Music::Music(InputSoundFile&& file) : m_impl(std::make_unique<Impl>(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 // 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 std::uint64_t Music::timeToSamples(Time position) const
{ {

View File

@ -233,6 +233,14 @@ SoundStream::SoundStream() : m_impl(std::make_unique<Impl>(this))
SoundStream::~SoundStream() = default; 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<SoundChannel>& channelMap) void SoundStream::initialize(unsigned int channelCount, unsigned int sampleRate, const std::vector<SoundChannel>& channelMap)
{ {

View File

@ -4,6 +4,6 @@
static_assert(!std::is_constructible_v<sf::AudioResource>); static_assert(!std::is_constructible_v<sf::AudioResource>);
static_assert(std::is_copy_constructible_v<sf::AudioResource>); static_assert(std::is_copy_constructible_v<sf::AudioResource>);
static_assert(!std::is_copy_assignable_v<sf::AudioResource>); static_assert(std::is_copy_assignable_v<sf::AudioResource>);
static_assert(std::is_nothrow_move_constructible_v<sf::AudioResource>); static_assert(std::is_nothrow_move_constructible_v<sf::AudioResource>);
static_assert(!std::is_nothrow_move_assignable_v<sf::AudioResource>); static_assert(std::is_nothrow_move_assignable_v<sf::AudioResource>);

View File

@ -7,8 +7,6 @@
#include <AudioUtil.hpp> #include <AudioUtil.hpp>
#include <SystemUtil.hpp> #include <SystemUtil.hpp>
#include <array>
#include <fstream>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
@ -18,8 +16,8 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
{ {
STATIC_CHECK(!std::is_copy_constructible_v<sf::Music>); STATIC_CHECK(!std::is_copy_constructible_v<sf::Music>);
STATIC_CHECK(!std::is_copy_assignable_v<sf::Music>); STATIC_CHECK(!std::is_copy_assignable_v<sf::Music>);
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::Music>); STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::Music>);
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::Music>); STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::Music>);
STATIC_CHECK(std::has_virtual_destructor_v<sf::Music>); STATIC_CHECK(std::has_virtual_destructor_v<sf::Music>);
} }
@ -34,39 +32,16 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
CHECK(timeSpan.length == sf::Time::Zero); 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()") SECTION("openFromFile()")
{ {
sf::Music music;
SECTION("Invalid file") SECTION("Invalid file")
{ {
REQUIRE(!music.openFromFile("does/not/exist.wav")); CHECK(!sf::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());
} }
SECTION("Valid file") 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)); CHECK(music.getDuration() == sf::microseconds(1990884));
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
@ -82,25 +57,17 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("openFromMemory()") SECTION("openFromMemory()")
{ {
std::vector<std::byte> memory; std::vector<std::byte> memory;
sf::Music music;
SECTION("Invalid buffer") SECTION("Invalid buffer")
{ {
REQUIRE(!music.openFromMemory(memory.data(), memory.size())); CHECK(!sf::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());
} }
SECTION("Valid buffer") SECTION("Valid buffer")
{ {
memory = loadIntoMemory("Audio/ding.flac"); 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)); CHECK(music.getDuration() == sf::microseconds(1990884));
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
@ -116,25 +83,17 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("openFromStream()") SECTION("openFromStream()")
{ {
sf::FileInputStream stream; sf::FileInputStream stream;
sf::Music music;
SECTION("Invalid stream") SECTION("Invalid stream")
{ {
CHECK(!music.openFromStream(stream)); CHECK(!sf::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());
} }
SECTION("Valid stream") SECTION("Valid stream")
{ {
REQUIRE(stream.open("Audio/doodle_pop.ogg")); 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)); CHECK(music.getDuration() == sf::microseconds(24002176));
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
@ -149,8 +108,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("play/pause/stop") SECTION("play/pause/stop")
{ {
sf::Music music; auto music = sf::Music::openFromFile("Audio/ding.mp3").value();
REQUIRE(music.openFromFile("Audio/ding.mp3"));
// Wait for background thread to start // Wait for background thread to start
music.play(); music.play();
@ -173,8 +131,7 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("setLoopPoints()") SECTION("setLoopPoints()")
{ {
sf::Music music; auto music = sf::Music::openFromFile("Audio/killdeer.wav").value();
REQUIRE(music.openFromFile("Audio/killdeer.wav"));
music.setLoopPoints({sf::seconds(1), sf::seconds(2)}); music.setLoopPoints({sf::seconds(1), sf::seconds(2)});
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::seconds(1)); CHECK(offset == sf::seconds(1));

View File

@ -44,7 +44,7 @@ TEST_CASE("[Audio] sf::SoundSource", runAudioDeviceTests())
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundSource>); STATIC_CHECK(std::is_copy_assignable_v<sf::SoundSource>);
STATIC_CHECK(!std::is_move_constructible_v<sf::SoundSource>); STATIC_CHECK(!std::is_move_constructible_v<sf::SoundSource>);
STATIC_CHECK(std::is_move_assignable_v<sf::SoundSource>); STATIC_CHECK(std::is_move_assignable_v<sf::SoundSource>);
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundSource>); STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::SoundSource>);
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundSource>); STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundSource>);
} }

View File

@ -29,7 +29,7 @@ TEST_CASE("[Audio] sf::SoundStream", runAudioDeviceTests())
STATIC_CHECK(!std::is_copy_constructible_v<sf::SoundStream>); STATIC_CHECK(!std::is_copy_constructible_v<sf::SoundStream>);
STATIC_CHECK(!std::is_copy_assignable_v<sf::SoundStream>); STATIC_CHECK(!std::is_copy_assignable_v<sf::SoundStream>);
STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::SoundStream>); STATIC_CHECK(!std::is_nothrow_move_constructible_v<sf::SoundStream>);
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundStream>); STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::SoundStream>);
STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundStream>); STATIC_CHECK(std::has_virtual_destructor_v<sf::SoundStream>);
} }

View File

@ -9,7 +9,6 @@ int main()
{ {
// Audio // Audio
[[maybe_unused]] const sf::SoundBufferRecorder soundBufferRecorder; [[maybe_unused]] const sf::SoundBufferRecorder soundBufferRecorder;
[[maybe_unused]] const sf::Music music;
// Graphics // Graphics
[[maybe_unused]] const sf::Color color; [[maybe_unused]] const sf::Color color;