Remove default empty state of sf::SoundBuffer

This commit is contained in:
Chris Thrasher 2024-05-18 15:37:06 -06:00
parent 1a40f01957
commit add6422e6b
9 changed files with 87 additions and 100 deletions

View File

@ -13,9 +13,7 @@
void playSound() void playSound()
{ {
// Load a sound buffer from a wav file // Load a sound buffer from a wav file
sf::SoundBuffer buffer; const auto buffer = sf::SoundBuffer::loadFromFile("resources/killdeer.wav").value();
if (!buffer.loadFromFile("resources/killdeer.wav"))
return;
// Display sound information // Display sound information
std::cout << "killdeer.wav:" << '\n' std::cout << "killdeer.wav:" << '\n'

View File

@ -49,10 +49,8 @@ int main()
window.setVerticalSyncEnabled(true); window.setVerticalSyncEnabled(true);
// Load the sounds used in the game // Load the sounds used in the game
sf::SoundBuffer ballSoundBuffer; const auto ballSoundBuffer = sf::SoundBuffer::loadFromFile(resourcesDir() / "ball.wav").value();
if (!ballSoundBuffer.loadFromFile(resourcesDir() / "ball.wav")) sf::Sound ballSound(ballSoundBuffer);
return EXIT_FAILURE;
sf::Sound ballSound(ballSoundBuffer);
// Create the SFML logo texture: // Create the SFML logo texture:
sf::Texture sfmlLogoTexture; sf::Texture sfmlLogoTexture;

View File

@ -274,12 +274,7 @@ private:
/// ///
/// Usage example: /// Usage example:
/// \code /// \code
/// sf::SoundBuffer buffer; /// const auto buffer = sf::SoundBuffer::loadFromFile("sound.wav").value();
/// if (!buffer.loadFromFile("sound.wav"))
/// {
/// // Handle error...
/// }
///
/// sf::Sound sound(buffer); /// sf::Sound sound(buffer);
/// sound.play(); /// sound.play();
/// \endcode /// \endcode

View File

@ -34,6 +34,7 @@
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <filesystem> #include <filesystem>
#include <optional>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@ -54,12 +55,6 @@ class InputStream;
class SFML_AUDIO_API SoundBuffer class SFML_AUDIO_API SoundBuffer
{ {
public: public:
////////////////////////////////////////////////////////////
/// \brief Default constructor
///
////////////////////////////////////////////////////////////
SoundBuffer() = default;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Copy constructor /// \brief Copy constructor
/// ///
@ -82,12 +77,12 @@ public:
/// ///
/// \param filename Path of the sound file to load /// \param filename Path of the sound file to load
/// ///
/// \return True if loading succeeded, false if it failed /// \return Sound buffer if loading succeeded, `std::nullopt` if it failed
/// ///
/// \see loadFromMemory, loadFromStream, loadFromSamples, saveToFile /// \see loadFromMemory, loadFromStream, loadFromSamples, saveToFile
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool loadFromFile(const std::filesystem::path& filename); [[nodiscard]] static std::optional<SoundBuffer> loadFromFile(const std::filesystem::path& filename);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Load the sound buffer from a file in memory /// \brief Load the sound buffer from a file in memory
@ -98,12 +93,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 Sound buffer if loading succeeded, `std::nullopt` if it failed
/// ///
/// \see loadFromFile, loadFromStream, loadFromSamples /// \see loadFromFile, loadFromStream, loadFromSamples
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool loadFromMemory(const void* data, std::size_t sizeInBytes); [[nodiscard]] static std::optional<SoundBuffer> loadFromMemory(const void* data, std::size_t sizeInBytes);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Load the sound buffer from a custom stream /// \brief Load the sound buffer from a custom stream
@ -113,12 +108,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 Sound buffer if loading succeeded, `std::nullopt` if it failed
/// ///
/// \see loadFromFile, loadFromMemory, loadFromSamples /// \see loadFromFile, loadFromMemory, loadFromSamples
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool loadFromStream(InputStream& stream); [[nodiscard]] static std::optional<SoundBuffer> loadFromStream(InputStream& stream);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Load the sound buffer from an array of audio samples /// \brief Load the sound buffer from an array of audio samples
@ -131,16 +126,17 @@ public:
/// \param sampleRate Sample rate (number of samples to play per second) /// \param sampleRate Sample rate (number of samples to play per second)
/// \param channelMap Map of position in sample frame to sound channel /// \param channelMap Map of position in sample frame to sound channel
/// ///
/// \return True if loading succeeded, false if it failed /// \return Sound buffer if loading succeeded, `std::nullopt` if it failed
/// ///
/// \see loadFromFile, loadFromMemory, saveToFile /// \see loadFromFile, loadFromMemory, saveToFile
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool loadFromSamples(const std::int16_t* samples, [[nodiscard]] static std::optional<SoundBuffer> loadFromSamples(
std::uint64_t sampleCount, const std::int16_t* samples,
unsigned int channelCount, std::uint64_t sampleCount,
unsigned int sampleRate, unsigned int channelCount,
const std::vector<SoundChannel>& channelMap); unsigned int sampleRate,
const std::vector<SoundChannel>& channelMap);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Save the sound buffer to an audio file /// \brief Save the sound buffer to an audio file
@ -247,6 +243,12 @@ public:
private: private:
friend class Sound; friend class Sound;
////////////////////////////////////////////////////////////
/// \brief Construct from vector of samples
///
////////////////////////////////////////////////////////////
explicit SoundBuffer(std::vector<std::int16_t>&& samples);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Initialize the internal state after loading a new sound /// \brief Initialize the internal state after loading a new sound
/// ///
@ -255,7 +257,7 @@ private:
/// \return True on successful initialization, false on failure /// \return True on successful initialization, false on failure
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool initialize(InputSoundFile& file); [[nodiscard]] static std::optional<SoundBuffer> initialize(InputSoundFile& file);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Update the internal buffer with the cached audio samples /// \brief Update the internal buffer with the cached audio samples
@ -341,14 +343,8 @@ private:
/// ///
/// Usage example: /// Usage example:
/// \code /// \code
/// // Declare a new sound buffer /// // Load a new sound buffer from a file
/// sf::SoundBuffer buffer; /// const auto buffer = sf::SoundBuffer::loadFromFile("sound.wav").value();
///
/// // Load it from a file
/// if (!buffer.loadFromFile("sound.wav"))
/// {
/// // error...
/// }
/// ///
/// // Create a sound source bound to the buffer /// // Create a sound source bound to the buffer
/// sf::Sound sound1(buffer); /// sf::Sound sound1(buffer);

View File

@ -97,8 +97,8 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::vector<std::int16_t> m_samples; //!< Temporary sample buffer to hold the recorded data std::vector<std::int16_t> m_samples; //!< Temporary sample buffer to hold the recorded data
SoundBuffer m_buffer; //!< Sound buffer that will contain the recorded data std::optional<SoundBuffer> m_buffer; //!< Sound buffer that will contain the recorded data
}; };
} // namespace sf } // namespace sf

View File

@ -68,52 +68,55 @@ SoundBuffer::~SoundBuffer()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundBuffer::loadFromFile(const std::filesystem::path& filename) std::optional<SoundBuffer> SoundBuffer::loadFromFile(const std::filesystem::path& filename)
{ {
InputSoundFile file; InputSoundFile file;
if (file.openFromFile(filename)) if (file.openFromFile(filename))
return initialize(file); return initialize(file);
else else
return false; return std::nullopt;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundBuffer::loadFromMemory(const void* data, std::size_t sizeInBytes) std::optional<SoundBuffer> SoundBuffer::loadFromMemory(const void* data, std::size_t sizeInBytes)
{ {
InputSoundFile file; InputSoundFile file;
if (file.openFromMemory(data, sizeInBytes)) if (file.openFromMemory(data, sizeInBytes))
return initialize(file); return initialize(file);
else else
return false; return std::nullopt;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundBuffer::loadFromStream(InputStream& stream) std::optional<SoundBuffer> SoundBuffer::loadFromStream(InputStream& stream)
{ {
InputSoundFile file; InputSoundFile file;
if (file.openFromStream(stream)) if (file.openFromStream(stream))
return initialize(file); return initialize(file);
else else
return false; return std::nullopt;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundBuffer::loadFromSamples(const std::int16_t* samples, std::optional<SoundBuffer> SoundBuffer::loadFromSamples(
std::uint64_t sampleCount, const std::int16_t* samples,
unsigned int channelCount, std::uint64_t sampleCount,
unsigned int sampleRate, unsigned int channelCount,
const std::vector<SoundChannel>& channelMap) unsigned int sampleRate,
const std::vector<SoundChannel>& channelMap)
{ {
if (samples && sampleCount && channelCount && sampleRate && !channelMap.empty()) if (samples && sampleCount && channelCount && sampleRate && !channelMap.empty())
{ {
// Copy the new audio samples // Copy the new audio samples
m_samples.assign(samples, samples + sampleCount); SoundBuffer soundBuffer(std::vector<std::int16_t>(samples, samples + sampleCount));
// Update the internal buffer with the new samples // Update the internal buffer with the new samples
return update(channelCount, sampleRate, channelMap); if (!soundBuffer.update(channelCount, sampleRate, channelMap))
return std::nullopt;
return soundBuffer;
} }
else else
{ {
@ -124,7 +127,7 @@ bool SoundBuffer::loadFromSamples(const std::int16_t* samples,
<< "channels: " << channelCount << ", " << "channels: " << channelCount << ", "
<< "samplerate: " << sampleRate << ")" << std::endl; << "samplerate: " << sampleRate << ")" << std::endl;
return false; return std::nullopt;
} }
} }
@ -206,21 +209,30 @@ SoundBuffer& SoundBuffer::operator=(const SoundBuffer& right)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundBuffer::initialize(InputSoundFile& file) SoundBuffer::SoundBuffer(std::vector<std::int16_t>&& samples) : m_samples(std::move(samples))
{
}
////////////////////////////////////////////////////////////
std::optional<SoundBuffer> SoundBuffer::initialize(InputSoundFile& file)
{ {
// Retrieve the sound parameters // Retrieve the sound parameters
const std::uint64_t sampleCount = file.getSampleCount(); const std::uint64_t sampleCount = file.getSampleCount();
// Read the samples from the provided file // Read the samples from the provided file
m_samples.resize(static_cast<std::size_t>(sampleCount)); std::vector<std::int16_t> samples(static_cast<std::size_t>(sampleCount));
if (file.read(m_samples.data(), sampleCount) == sampleCount) if (file.read(samples.data(), sampleCount) == sampleCount)
{ {
// Update the internal buffer with the new samples // Update the internal buffer with the new samples
return update(file.getChannelCount(), file.getSampleRate(), file.getChannelMap()); SoundBuffer soundBuffer(std::move(samples));
if (!soundBuffer.update(file.getChannelCount(), file.getSampleRate(), file.getChannelMap()))
return std::nullopt;
return soundBuffer;
} }
else else
{ {
return false; return std::nullopt;
} }
} }

View File

@ -48,7 +48,7 @@ SoundBufferRecorder::~SoundBufferRecorder()
bool SoundBufferRecorder::onStart() bool SoundBufferRecorder::onStart()
{ {
m_samples.clear(); m_samples.clear();
m_buffer = SoundBuffer(); m_buffer.reset();
return true; return true;
} }
@ -69,7 +69,12 @@ void SoundBufferRecorder::onStop()
if (m_samples.empty()) if (m_samples.empty())
return; return;
if (!m_buffer.loadFromSamples(m_samples.data(), m_samples.size(), getChannelCount(), getSampleRate(), getChannelMap())) m_buffer = sf::SoundBuffer::loadFromSamples(m_samples.data(),
m_samples.size(),
getChannelCount(),
getSampleRate(),
getChannelMap());
if (!m_buffer)
err() << "Failed to stop capturing audio data" << std::endl; err() << "Failed to stop capturing audio data" << std::endl;
} }
@ -77,7 +82,8 @@ void SoundBufferRecorder::onStop()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const SoundBuffer& SoundBufferRecorder::getBuffer() const const SoundBuffer& SoundBufferRecorder::getBuffer() const
{ {
return m_buffer; assert(m_buffer && "SoundBufferRecorder::getBuffer() Cannot return reference to null buffer");
return *m_buffer;
} }
} // namespace sf } // namespace sf

View File

@ -25,8 +25,7 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests())
STATIC_CHECK(std::has_virtual_destructor_v<sf::Sound>); STATIC_CHECK(std::has_virtual_destructor_v<sf::Sound>);
} }
sf::SoundBuffer soundBuffer; const auto soundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
SECTION("Construction") SECTION("Construction")
{ {
@ -52,8 +51,8 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests())
SECTION("Assignment") SECTION("Assignment")
{ {
const sf::SoundBuffer emptySoundBuffer; const sf::SoundBuffer otherSoundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
sf::Sound soundCopy(emptySoundBuffer); sf::Sound soundCopy(otherSoundBuffer);
soundCopy = sound; soundCopy = sound;
CHECK(&soundCopy.getBuffer() == &soundBuffer); CHECK(&soundCopy.getBuffer() == &soundBuffer);
CHECK(!soundCopy.getLoop()); CHECK(!soundCopy.getLoop());
@ -64,7 +63,7 @@ TEST_CASE("[Audio] sf::Sound", runAudioDeviceTests())
SECTION("Set/get buffer") SECTION("Set/get buffer")
{ {
const sf::SoundBuffer otherSoundBuffer; const sf::SoundBuffer otherSoundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
sf::Sound sound(soundBuffer); sf::Sound sound(soundBuffer);
sound.setBuffer(otherSoundBuffer); sound.setBuffer(otherSoundBuffer);
CHECK(&sound.getBuffer() == &otherSoundBuffer); CHECK(&sound.getBuffer() == &otherSoundBuffer);

View File

@ -14,6 +14,7 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
{ {
SECTION("Type traits") SECTION("Type traits")
{ {
STATIC_CHECK(!std::is_default_constructible_v<sf::SoundBuffer>);
STATIC_CHECK(std::is_copy_constructible_v<sf::SoundBuffer>); STATIC_CHECK(std::is_copy_constructible_v<sf::SoundBuffer>);
STATIC_CHECK(std::is_copy_assignable_v<sf::SoundBuffer>); STATIC_CHECK(std::is_copy_assignable_v<sf::SoundBuffer>);
STATIC_CHECK(std::is_move_constructible_v<sf::SoundBuffer>); STATIC_CHECK(std::is_move_constructible_v<sf::SoundBuffer>);
@ -22,20 +23,9 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>); STATIC_CHECK(!std::is_nothrow_move_assignable_v<sf::SoundBuffer>);
} }
SECTION("Construction")
{
const sf::SoundBuffer soundBuffer;
CHECK(soundBuffer.getSamples() == nullptr);
CHECK(soundBuffer.getSampleCount() == 0);
CHECK(soundBuffer.getSampleRate() == 44100);
CHECK(soundBuffer.getChannelCount() == 1);
CHECK(soundBuffer.getDuration() == sf::Time::Zero);
}
SECTION("Copy semantics") SECTION("Copy semantics")
{ {
sf::SoundBuffer soundBuffer; const auto soundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
SECTION("Construction") SECTION("Construction")
{ {
@ -49,8 +39,8 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
SECTION("Assignment") SECTION("Assignment")
{ {
sf::SoundBuffer soundBufferCopy; sf::SoundBuffer soundBufferCopy = sf::SoundBuffer::loadFromFile("Audio/doodle_pop.ogg").value();
soundBufferCopy = soundBuffer; soundBufferCopy = soundBuffer;
CHECK(soundBufferCopy.getSamples() != nullptr); CHECK(soundBufferCopy.getSamples() != nullptr);
CHECK(soundBufferCopy.getSampleCount() == 87798); CHECK(soundBufferCopy.getSampleCount() == 87798);
CHECK(soundBufferCopy.getSampleRate() == 44100); CHECK(soundBufferCopy.getSampleRate() == 44100);
@ -61,16 +51,14 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
SECTION("loadFromFile()") SECTION("loadFromFile()")
{ {
sf::SoundBuffer soundBuffer;
SECTION("Invalid filename") SECTION("Invalid filename")
{ {
CHECK(!soundBuffer.loadFromFile("does/not/exist.wav")); CHECK(!sf::SoundBuffer::loadFromFile("does/not/exist.wav"));
} }
SECTION("Valid file") SECTION("Valid file")
{ {
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac")); const auto soundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
CHECK(soundBuffer.getSamples() != nullptr); CHECK(soundBuffer.getSamples() != nullptr);
CHECK(soundBuffer.getSampleCount() == 87798); CHECK(soundBuffer.getSampleCount() == 87798);
CHECK(soundBuffer.getSampleRate() == 44100); CHECK(soundBuffer.getSampleRate() == 44100);
@ -81,19 +69,17 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
SECTION("loadFromMemory()") SECTION("loadFromMemory()")
{ {
sf::SoundBuffer soundBuffer;
SECTION("Invalid memory") SECTION("Invalid memory")
{ {
CHECK(!soundBuffer.loadFromMemory(nullptr, 0)); CHECK(!sf::SoundBuffer::loadFromMemory(nullptr, 0));
constexpr std::array<std::byte, 5> memory{}; constexpr std::array<std::byte, 5> memory{};
CHECK(!soundBuffer.loadFromMemory(memory.data(), memory.size())); CHECK(!sf::SoundBuffer::loadFromMemory(memory.data(), memory.size()));
} }
SECTION("Valid memory") SECTION("Valid memory")
{ {
const auto memory = loadIntoMemory("Audio/ding.flac"); const auto memory = loadIntoMemory("Audio/ding.flac");
REQUIRE(soundBuffer.loadFromMemory(memory.data(), memory.size())); const auto soundBuffer = sf::SoundBuffer::loadFromMemory(memory.data(), memory.size()).value();
CHECK(soundBuffer.getSamples() != nullptr); CHECK(soundBuffer.getSamples() != nullptr);
CHECK(soundBuffer.getSampleCount() == 87798); CHECK(soundBuffer.getSampleCount() == 87798);
CHECK(soundBuffer.getSampleRate() == 44100); CHECK(soundBuffer.getSampleRate() == 44100);
@ -105,17 +91,16 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
SECTION("loadFromStream()") SECTION("loadFromStream()")
{ {
sf::FileInputStream stream; sf::FileInputStream stream;
sf::SoundBuffer soundBuffer;
SECTION("Invalid stream") SECTION("Invalid stream")
{ {
CHECK(!soundBuffer.loadFromStream(stream)); CHECK(!sf::SoundBuffer::loadFromStream(stream));
} }
SECTION("Valid stream") SECTION("Valid stream")
{ {
REQUIRE(stream.open("Audio/ding.flac")); REQUIRE(stream.open("Audio/ding.flac"));
REQUIRE(soundBuffer.loadFromStream(stream)); const auto soundBuffer = sf::SoundBuffer::loadFromStream(stream).value();
CHECK(soundBuffer.getSamples() != nullptr); CHECK(soundBuffer.getSamples() != nullptr);
CHECK(soundBuffer.getSampleCount() == 87798); CHECK(soundBuffer.getSampleCount() == 87798);
CHECK(soundBuffer.getSampleRate() == 44100); CHECK(soundBuffer.getSampleRate() == 44100);
@ -129,13 +114,11 @@ TEST_CASE("[Audio] sf::SoundBuffer", runAudioDeviceTests())
const auto filename = std::filesystem::temp_directory_path() / "ding.flac"; const auto filename = std::filesystem::temp_directory_path() / "ding.flac";
{ {
sf::SoundBuffer soundBuffer; const auto soundBuffer = sf::SoundBuffer::loadFromFile("Audio/ding.flac").value();
REQUIRE(soundBuffer.loadFromFile("Audio/ding.flac"));
REQUIRE(soundBuffer.saveToFile(filename)); REQUIRE(soundBuffer.saveToFile(filename));
} }
sf::SoundBuffer soundBuffer; const auto soundBuffer = sf::SoundBuffer::loadFromFile(filename).value();
REQUIRE(soundBuffer.loadFromFile(filename));
CHECK(soundBuffer.getSamples() != nullptr); CHECK(soundBuffer.getSamples() != nullptr);
CHECK(soundBuffer.getSampleCount() == 87798); CHECK(soundBuffer.getSampleCount() == 87798);
CHECK(soundBuffer.getSampleRate() == 44100); CHECK(soundBuffer.getSampleRate() == 44100);