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)
{
// 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'

View File

@ -12,6 +12,7 @@
#include <vector>
#include <cmath>
#include <cstdlib>
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<sf::Music> 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<sf::Music> 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<sf::Music> 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<bool>& 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<bool> m_enabled{std::make_shared<bool>(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<sf::Music> m_music;
std::shared_ptr<bool> m_enabled{std::make_shared<bool>(true)};
sf::Text m_enabledText;
sf::Text m_instructions;
};

View File

@ -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<void> m_device; //!< Sound device
std::shared_ptr<void> m_device; //!< Sound device
};
} // namespace sf

View File

@ -29,12 +29,11 @@
////////////////////////////////////////////////////////////
#include <SFML/Audio/Export.hpp>
#include <SFML/Audio/InputSoundFile.hpp>
#include <SFML/Audio/SoundStream.hpp>
#include <filesystem>
#include <mutex>
#include <vector>
#include <memory>
#include <optional>
#include <cstddef>
#include <cstdint>
@ -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<Music> 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<Music> 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<Music> openFromStream(InputStream& stream);
////////////////////////////////////////////////////////////
/// \brief Get the total duration of the music
@ -219,11 +231,17 @@ protected:
std::optional<std::uint64_t> onLoop() override;
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
///
////////////////////////////////////////////////////////////
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<InputSoundFile> m_file; //!< The streamed music file
std::vector<std::int16_t> m_samples; //!< Temporary buffer of samples
std::recursive_mutex m_mutex; //!< Mutex protecting the data
Span<std::uint64_t> m_loopSpan; //!< Loop Range Specifier
struct Impl;
std::unique_ptr<Impl> m_impl; //!< Implementation details
};
} // namespace sf

View File

@ -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

View File

@ -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<Impl> m_impl; //!< Implementation details
std::unique_ptr<Impl> m_impl; //!< Implementation details
};
} // namespace sf

View File

@ -25,6 +25,7 @@
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Audio/InputSoundFile.hpp>
#include <SFML/Audio/Music.hpp>
#include <SFML/System/Err.hpp>
@ -34,85 +35,92 @@
#include <mutex>
#include <ostream>
#include <cassert>
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()
{
// 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
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> 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> 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> 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<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
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<std::size_t>(loopEnd - currentOffset);
// Fill the chunk parameters
data.samples = m_samples.data();
data.sampleCount = static_cast<std::size_t>(m_file->read(m_samples.data(), toFill));
data.samples = m_impl->samples.data();
data.sampleCount = static_cast<std::size_t>(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<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.
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<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
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
{

View File

@ -233,6 +233,14 @@ SoundStream::SoundStream() : m_impl(std::make_unique<Impl>(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<SoundChannel>& channelMap)
{

View File

@ -4,6 +4,6 @@
static_assert(!std::is_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_assignable_v<sf::AudioResource>);
static_assert(std::is_nothrow_move_assignable_v<sf::AudioResource>);

View File

@ -7,8 +7,6 @@
#include <AudioUtil.hpp>
#include <SystemUtil.hpp>
#include <array>
#include <fstream>
#include <thread>
#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_assignable_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_constructible_v<sf::Music>);
STATIC_CHECK(std::is_nothrow_move_assignable_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);
}
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<std::byte> 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));

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_move_constructible_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>);
}

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_assignable_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>);
}

View File

@ -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;