Remove default empty state of sf::InputSoundFile and sf::OutputSoundFile

This commit is contained in:
Chris Thrasher 2024-05-20 19:42:43 -06:00
parent 53ade4baf1
commit e9fadbbcb3
11 changed files with 167 additions and 194 deletions

View File

@ -33,6 +33,7 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <optional>
#include <vector> #include <vector>
#include <cstddef> #include <cstddef>
@ -64,10 +65,10 @@ public:
/// ///
/// \param filename Path of the sound file to load /// \param filename Path of the sound file to load
/// ///
/// \return True if the file was successfully opened /// \return Input sound file if the file was successfully opened, otherwise `std::nullopt`
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromFile(const std::filesystem::path& filename); [[nodiscard]] static std::optional<InputSoundFile> openFromFile(const std::filesystem::path& filename);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Open a sound file in memory for reading /// \brief Open a sound file in memory for reading
@ -78,10 +79,10 @@ 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 the file was successfully opened /// \return Input sound file if the file was successfully opened, otherwise `std::nullopt`
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromMemory(const void* data, std::size_t sizeInBytes); [[nodiscard]] static std::optional<InputSoundFile> openFromMemory(const void* data, std::size_t sizeInBytes);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Open a sound file from a custom stream for reading /// \brief Open a sound file from a custom stream for reading
@ -91,10 +92,10 @@ public:
/// ///
/// \param stream Source stream to read from /// \param stream Source stream to read from
/// ///
/// \return True if the file was successfully opened /// \return Input sound file if the file was successfully opened, otherwise `std::nullopt`
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromStream(InputStream& stream); [[nodiscard]] static std::optional<InputSoundFile> openFromStream(InputStream& stream);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the total number of audio samples in the file /// \brief Get the total number of audio samples in the file
@ -211,6 +212,14 @@ public:
void close(); void close();
private: private:
////////////////////////////////////////////////////////////
/// \brief Default constructor
///
/// Useful for implementing close()
///
////////////////////////////////////////////////////////////
InputSoundFile() = default;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Deleter for input streams that only conditionally deletes /// \brief Deleter for input streams that only conditionally deletes
/// ///
@ -228,6 +237,16 @@ private:
bool owned{true}; bool owned{true};
}; };
////////////////////////////////////////////////////////////
/// \brief Constructor from reader, stream, and attributes
///
////////////////////////////////////////////////////////////
InputSoundFile(std::unique_ptr<SoundFileReader>&& reader,
std::unique_ptr<InputStream, StreamDeleter>&& stream,
std::uint64_t sampleCount,
unsigned int sampleRate,
std::vector<SoundChannel>&& channelMap);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -248,10 +248,10 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
InputSoundFile m_file; //!< The streamed music file std::optional<InputSoundFile> m_file; //!< The streamed music file
std::vector<std::int16_t> m_samples; //!< Temporary buffer of samples std::vector<std::int16_t> m_samples; //!< Temporary buffer of samples
std::recursive_mutex m_mutex; //!< Mutex protecting the data std::recursive_mutex m_mutex; //!< Mutex protecting the data
Span<std::uint64_t> m_loopSpan; //!< Loop Range Specifier Span<std::uint64_t> m_loopSpan; //!< Loop Range Specifier
}; };
} // namespace sf } // namespace sf

View File

@ -34,6 +34,7 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <optional>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
@ -58,13 +59,14 @@ public:
/// \param channelCount Number of channels in the sound /// \param channelCount Number of channels in the sound
/// \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 the file was successfully opened /// \return Output sound file if the file was successfully opened
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
[[nodiscard]] bool openFromFile(const std::filesystem::path& filename, [[nodiscard]] static std::optional<OutputSoundFile> openFromFile(
unsigned int sampleRate, const std::filesystem::path& filename,
unsigned int channelCount, unsigned int sampleRate,
const std::vector<SoundChannel>& channelMap); unsigned int channelCount,
const std::vector<SoundChannel>& channelMap);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Write audio samples to the file /// \brief Write audio samples to the file
@ -82,6 +84,12 @@ public:
void close(); void close();
private: private:
////////////////////////////////////////////////////////////
/// \brief Constructor from writer
///
////////////////////////////////////////////////////////////
explicit OutputSoundFile(std::unique_ptr<SoundFileWriter>&& writer);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -65,51 +65,36 @@ void InputSoundFile::StreamDeleter::operator()(InputStream* ptr) const
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool InputSoundFile::openFromFile(const std::filesystem::path& filename) std::optional<InputSoundFile> InputSoundFile::openFromFile(const std::filesystem::path& filename)
{ {
// If the file is already open, first close it
close();
// Find a suitable reader for the file type // Find a suitable reader for the file type
auto reader = SoundFileFactory::createReaderFromFilename(filename); auto reader = SoundFileFactory::createReaderFromFilename(filename);
if (!reader) if (!reader)
return false; return std::nullopt;
// Wrap the file into a stream // Wrap the file into a stream
auto file = std::make_unique<FileInputStream>(); auto file = std::make_unique<FileInputStream>();
// Open it // Open it
if (!file->open(filename)) if (!file->open(filename))
return false; return std::nullopt;
// Pass the stream to the reader // Pass the stream to the reader
const auto info = reader->open(*file); auto info = reader->open(*file);
if (!info) if (!info)
return false; return std::nullopt;
// Take ownership of successfully opened reader and stream return InputSoundFile(std::move(reader), std::move(file), info->sampleCount, info->sampleRate, std::move(info->channelMap));
m_reader = std::move(reader);
m_stream = std::move(file);
// Retrieve the attributes of the open sound file
m_sampleCount = info->sampleCount;
m_sampleRate = info->sampleRate;
m_channelMap = info->channelMap;
return true;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool InputSoundFile::openFromMemory(const void* data, std::size_t sizeInBytes) std::optional<InputSoundFile> InputSoundFile::openFromMemory(const void* data, std::size_t sizeInBytes)
{ {
// If the file is already open, first close it
close();
// Find a suitable reader for the file type // Find a suitable reader for the file type
auto reader = SoundFileFactory::createReaderFromMemory(data, sizeInBytes); auto reader = SoundFileFactory::createReaderFromMemory(data, sizeInBytes);
if (!reader) if (!reader)
return false; return std::nullopt;
// Wrap the memory file into a stream // Wrap the memory file into a stream
auto memory = std::make_unique<MemoryInputStream>(); auto memory = std::make_unique<MemoryInputStream>();
@ -118,56 +103,35 @@ bool InputSoundFile::openFromMemory(const void* data, std::size_t sizeInBytes)
memory->open(data, sizeInBytes); memory->open(data, sizeInBytes);
// Pass the stream to the reader // Pass the stream to the reader
const auto info = reader->open(*memory); auto info = reader->open(*memory);
if (!info) if (!info)
return false; return std::nullopt;
// Take ownership of successfully opened reader and stream return InputSoundFile(std::move(reader), std::move(memory), info->sampleCount, info->sampleRate, std::move(info->channelMap));
m_reader = std::move(reader);
m_stream = std::move(memory);
// Retrieve the attributes of the open sound file
m_sampleCount = info->sampleCount;
m_sampleRate = info->sampleRate;
m_channelMap = info->channelMap;
return true;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool InputSoundFile::openFromStream(InputStream& stream) std::optional<InputSoundFile> InputSoundFile::openFromStream(InputStream& stream)
{ {
// If the file is already open, first close it
close();
// Find a suitable reader for the file type // Find a suitable reader for the file type
auto reader = SoundFileFactory::createReaderFromStream(stream); auto reader = SoundFileFactory::createReaderFromStream(stream);
if (!reader) if (!reader)
return false; return std::nullopt;
// Don't forget to reset the stream to its beginning before re-opening it // Don't forget to reset the stream to its beginning before re-opening it
if (stream.seek(0) != 0) if (stream.seek(0) != 0)
{ {
err() << "Failed to open sound file from stream (cannot restart stream)" << std::endl; err() << "Failed to open sound file from stream (cannot restart stream)" << std::endl;
return false; return std::nullopt;
} }
// Pass the stream to the reader // Pass the stream to the reader
const auto info = reader->open(stream); auto info = reader->open(stream);
if (!info) if (!info)
return false; return std::nullopt;
// Take ownership of reader and store a reference to the stream without taking ownership return InputSoundFile(std::move(reader), {&stream, false}, info->sampleCount, info->sampleRate, std::move(info->channelMap));
m_reader = std::move(reader);
m_stream = {&stream, false};
// Retrieve the attributes of the open sound file
m_sampleCount = info->sampleCount;
m_sampleRate = info->sampleRate;
m_channelMap = info->channelMap;
return true;
} }
@ -268,4 +232,19 @@ void InputSoundFile::close()
m_channelMap.clear(); m_channelMap.clear();
} }
////////////////////////////////////////////////////////////
InputSoundFile::InputSoundFile(std::unique_ptr<SoundFileReader>&& reader,
std::unique_ptr<InputStream, StreamDeleter>&& stream,
std::uint64_t sampleCount,
unsigned int sampleRate,
std::vector<SoundChannel>&& channelMap) :
m_reader(std::move(reader)),
m_stream(std::move(stream)),
m_sampleCount(sampleCount),
m_sampleRate(sampleRate),
m_channelMap(std::move(channelMap))
{
}
} // namespace sf } // namespace sf

View File

@ -34,6 +34,8 @@
#include <mutex> #include <mutex>
#include <ostream> #include <ostream>
#include <cassert>
namespace sf namespace sf
{ {
@ -52,7 +54,8 @@ bool Music::openFromFile(const std::filesystem::path& filename)
stop(); stop();
// Open the underlying sound file // Open the underlying sound file
if (!m_file.openFromFile(filename)) m_file = sf::InputSoundFile::openFromFile(filename);
if (!m_file)
return false; return false;
// Perform common initializations // Perform common initializations
@ -69,7 +72,8 @@ bool Music::openFromMemory(const void* data, std::size_t sizeInBytes)
stop(); stop();
// Open the underlying sound file // Open the underlying sound file
if (!m_file.openFromMemory(data, sizeInBytes)) m_file = sf::InputSoundFile::openFromMemory(data, sizeInBytes);
if (!m_file)
return false; return false;
// Perform common initializations // Perform common initializations
@ -86,7 +90,8 @@ bool Music::openFromStream(InputStream& stream)
stop(); stop();
// Open the underlying sound file // Open the underlying sound file
if (!m_file.openFromStream(stream)) m_file = sf::InputSoundFile::openFromStream(stream);
if (!m_file)
return false; return false;
// Perform common initializations // Perform common initializations
@ -99,7 +104,8 @@ bool Music::openFromStream(InputStream& stream)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Time Music::getDuration() const Time Music::getDuration() const
{ {
return m_file.getDuration(); assert(m_file && "Music::getDuration() Cannot get duration until music is opened");
return m_file->getDuration();
} }
@ -116,7 +122,8 @@ 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
if (getChannelCount() == 0 || m_file.getSampleCount() == 0) assert(m_file && "Music::setLoopPoints() Cannot set loop points unit music is opened");
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;
@ -129,7 +136,7 @@ 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_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;
@ -141,7 +148,7 @@ 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_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_loopSpan.offset && samplePoints.length == m_loopSpan.length)
@ -172,10 +179,12 @@ 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_mutex); const std::lock_guard lock(m_mutex);
std::size_t toFill = m_samples.size(); std::size_t toFill = m_samples.size();
std::uint64_t currentOffset = m_file.getSampleOffset(); std::uint64_t currentOffset = m_file->getSampleOffset();
const std::uint64_t loopEnd = m_loopSpan.offset + m_loopSpan.length; 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.
@ -186,11 +195,11 @@ bool Music::onGetData(SoundStream::Chunk& data)
// Fill the chunk parameters // Fill the chunk parameters
data.samples = m_samples.data(); data.samples = m_samples.data();
data.sampleCount = static_cast<std::size_t>(m_file.read(m_samples.data(), toFill)); data.sampleCount = static_cast<std::size_t>(m_file->read(m_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_file->getSampleCount()) &&
(currentOffset != loopEnd || m_loopSpan.length == 0); (currentOffset != loopEnd || m_loopSpan.length == 0);
} }
@ -198,28 +207,32 @@ bool Music::onGetData(SoundStream::Chunk& data)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
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_mutex); const std::lock_guard lock(m_mutex);
m_file.seek(timeOffset); 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_mutex);
const std::uint64_t currentOffset = m_file.getSampleOffset(); const std::uint64_t currentOffset = m_file->getSampleOffset();
if (getLoop() && (m_loopSpan.length != 0) && (currentOffset == m_loopSpan.offset + m_loopSpan.length)) if (getLoop() && (m_loopSpan.length != 0) && (currentOffset == m_loopSpan.offset + m_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_file->seek(m_loopSpan.offset);
return static_cast<std::int64_t>(m_file.getSampleOffset()); return static_cast<std::int64_t>(m_file->getSampleOffset());
} }
else if (getLoop() && (currentOffset >= m_file.getSampleCount())) else if (getLoop() && (currentOffset >= m_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_file->seek(0);
return 0; return 0;
} }
return std::nullopt; return std::nullopt;
@ -231,13 +244,13 @@ void Music::initialize()
{ {
// Compute the music positions // Compute the music positions
m_loopSpan.offset = 0; m_loopSpan.offset = 0;
m_loopSpan.length = m_file.getSampleCount(); m_loopSpan.length = m_file->getSampleCount();
// Resize the internal buffer so that it can contain 1 second of audio samples // Resize the internal buffer so that it can contain 1 second of audio samples
m_samples.resize(m_file.getSampleRate() * m_file.getChannelCount()); 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_file->getChannelCount(), m_file->getSampleRate(), m_file->getChannelMap());
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -33,27 +33,24 @@
namespace sf namespace sf
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool OutputSoundFile::openFromFile(const std::filesystem::path& filename, std::optional<OutputSoundFile> OutputSoundFile::openFromFile(
unsigned int sampleRate, const std::filesystem::path& filename,
unsigned int channelCount, unsigned int sampleRate,
const std::vector<SoundChannel>& channelMap) unsigned int channelCount,
const std::vector<SoundChannel>& channelMap)
{ {
// If the file is already open, first close it
close();
// Find a suitable writer for the file type // Find a suitable writer for the file type
m_writer = SoundFileFactory::createWriterFromFilename(filename); auto writer = SoundFileFactory::createWriterFromFilename(filename);
if (!m_writer) if (!writer)
return false; return std::nullopt;
// Pass the stream to the reader // Pass the stream to the reader
if (!m_writer->open(filename, sampleRate, channelCount, channelMap)) if (!writer->open(filename, sampleRate, channelCount, channelMap))
{ {
close(); return std::nullopt;
return false;
} }
return true; return OutputSoundFile(std::move(writer));
} }
@ -72,4 +69,10 @@ void OutputSoundFile::close()
m_writer.reset(); m_writer.reset();
} }
////////////////////////////////////////////////////////////
OutputSoundFile::OutputSoundFile(std::unique_ptr<SoundFileWriter>&& writer) : m_writer(std::move(writer))
{
}
} // namespace sf } // namespace sf

View File

@ -70,9 +70,8 @@ SoundBuffer::~SoundBuffer()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::optional<SoundBuffer> SoundBuffer::loadFromFile(const std::filesystem::path& filename) std::optional<SoundBuffer> SoundBuffer::loadFromFile(const std::filesystem::path& filename)
{ {
InputSoundFile file; if (auto file = InputSoundFile::openFromFile(filename))
if (file.openFromFile(filename)) return initialize(*file);
return initialize(file);
else else
return std::nullopt; return std::nullopt;
} }
@ -81,9 +80,8 @@ std::optional<SoundBuffer> SoundBuffer::loadFromFile(const std::filesystem::path
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::optional<SoundBuffer> SoundBuffer::loadFromMemory(const void* data, std::size_t sizeInBytes) std::optional<SoundBuffer> SoundBuffer::loadFromMemory(const void* data, std::size_t sizeInBytes)
{ {
InputSoundFile file; if (auto file = InputSoundFile::openFromMemory(data, sizeInBytes))
if (file.openFromMemory(data, sizeInBytes)) return initialize(*file);
return initialize(file);
else else
return std::nullopt; return std::nullopt;
} }
@ -92,9 +90,8 @@ std::optional<SoundBuffer> SoundBuffer::loadFromMemory(const void* data, std::si
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::optional<SoundBuffer> SoundBuffer::loadFromStream(InputStream& stream) std::optional<SoundBuffer> SoundBuffer::loadFromStream(InputStream& stream)
{ {
InputSoundFile file; if (auto file = InputSoundFile::openFromStream(stream))
if (file.openFromStream(stream)) return initialize(*file);
return initialize(file);
else else
return std::nullopt; return std::nullopt;
} }
@ -136,11 +133,10 @@ std::optional<SoundBuffer> SoundBuffer::loadFromSamples(
bool SoundBuffer::saveToFile(const std::filesystem::path& filename) const bool SoundBuffer::saveToFile(const std::filesystem::path& filename) const
{ {
// Create the sound file in write mode // Create the sound file in write mode
OutputSoundFile file; if (auto file = OutputSoundFile::openFromFile(filename, getSampleRate(), getChannelCount(), getChannelMap()))
if (file.openFromFile(filename, getSampleRate(), getChannelCount(), getChannelMap()))
{ {
// Write the samples to the opened file // Write the samples to the opened file
file.write(m_samples.data(), m_samples.size()); file->write(m_samples.data(), m_samples.size());
return true; return true;
} }

View File

@ -15,37 +15,25 @@ TEST_CASE("[Audio] sf::InputSoundFile")
{ {
SECTION("Type traits") SECTION("Type traits")
{ {
STATIC_CHECK(!std::is_default_constructible_v<sf::InputSoundFile>);
STATIC_CHECK(!std::is_copy_constructible_v<sf::InputSoundFile>); STATIC_CHECK(!std::is_copy_constructible_v<sf::InputSoundFile>);
STATIC_CHECK(!std::is_copy_assignable_v<sf::InputSoundFile>); STATIC_CHECK(!std::is_copy_assignable_v<sf::InputSoundFile>);
STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::InputSoundFile>); STATIC_CHECK(std::is_nothrow_move_constructible_v<sf::InputSoundFile>);
STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::InputSoundFile>); STATIC_CHECK(std::is_nothrow_move_assignable_v<sf::InputSoundFile>);
} }
SECTION("Construction")
{
const sf::InputSoundFile inputSoundFile;
CHECK(inputSoundFile.getSampleCount() == 0);
CHECK(inputSoundFile.getChannelCount() == 0);
CHECK(inputSoundFile.getSampleRate() == 0);
CHECK(inputSoundFile.getDuration() == sf::Time::Zero);
CHECK(inputSoundFile.getTimeOffset() == sf::Time::Zero);
CHECK(inputSoundFile.getSampleOffset() == 0);
}
SECTION("openFromFile()") SECTION("openFromFile()")
{ {
sf::InputSoundFile inputSoundFile;
SECTION("Invalid file") SECTION("Invalid file")
{ {
CHECK(!inputSoundFile.openFromFile("does/not/exist.wav")); CHECK(!sf::InputSoundFile::openFromFile("does/not/exist.wav"));
} }
SECTION("Valid file") SECTION("Valid file")
{ {
SECTION("flac") SECTION("flac")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac")); const auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
CHECK(inputSoundFile.getSampleCount() == 87'798); CHECK(inputSoundFile.getSampleCount() == 87'798);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -56,7 +44,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("mp3") SECTION("mp3")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.mp3")); const auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.mp3").value();
CHECK(inputSoundFile.getSampleCount() == 87'798); CHECK(inputSoundFile.getSampleCount() == 87'798);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -67,7 +55,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("ogg") SECTION("ogg")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/doodle_pop.ogg")); const auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/doodle_pop.ogg").value();
CHECK(inputSoundFile.getSampleCount() == 2'116'992); CHECK(inputSoundFile.getSampleCount() == 2'116'992);
CHECK(inputSoundFile.getChannelCount() == 2); CHECK(inputSoundFile.getChannelCount() == 2);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -78,7 +66,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("wav") SECTION("wav")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/killdeer.wav")); const auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/killdeer.wav").value();
CHECK(inputSoundFile.getSampleCount() == 112'941); CHECK(inputSoundFile.getSampleCount() == 112'941);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 22'050); CHECK(inputSoundFile.getSampleRate() == 22'050);
@ -91,9 +79,8 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("openFromMemory()") SECTION("openFromMemory()")
{ {
const auto memory = loadIntoMemory("Audio/killdeer.wav"); const auto memory = loadIntoMemory("Audio/killdeer.wav");
sf::InputSoundFile inputSoundFile; const auto inputSoundFile = sf::InputSoundFile::openFromMemory(memory.data(), memory.size()).value();
REQUIRE(inputSoundFile.openFromMemory(memory.data(), memory.size()));
CHECK(inputSoundFile.getSampleCount() == 112'941); CHECK(inputSoundFile.getSampleCount() == 112'941);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 22'050); CHECK(inputSoundFile.getSampleRate() == 22'050);
@ -104,12 +91,11 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("openFromStream()") SECTION("openFromStream()")
{ {
sf::InputSoundFile inputSoundFile;
sf::FileInputStream stream; sf::FileInputStream stream;
SECTION("Invalid stream") SECTION("Invalid stream")
{ {
CHECK(!inputSoundFile.openFromStream(stream)); CHECK(!sf::InputSoundFile::openFromStream(stream));
} }
SECTION("Valid stream") SECTION("Valid stream")
@ -117,7 +103,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("flac") SECTION("flac")
{ {
REQUIRE(stream.open("Audio/ding.flac")); REQUIRE(stream.open("Audio/ding.flac"));
REQUIRE(inputSoundFile.openFromStream(stream)); const auto inputSoundFile = sf::InputSoundFile::openFromStream(stream).value();
CHECK(inputSoundFile.getSampleCount() == 87'798); CHECK(inputSoundFile.getSampleCount() == 87'798);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -129,7 +115,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("mp3") SECTION("mp3")
{ {
REQUIRE(stream.open("Audio/ding.mp3")); REQUIRE(stream.open("Audio/ding.mp3"));
REQUIRE(inputSoundFile.openFromStream(stream)); const auto inputSoundFile = sf::InputSoundFile::openFromStream(stream).value();
CHECK(inputSoundFile.getSampleCount() == 87'798); CHECK(inputSoundFile.getSampleCount() == 87'798);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -141,7 +127,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("ogg") SECTION("ogg")
{ {
REQUIRE(stream.open("Audio/doodle_pop.ogg")); REQUIRE(stream.open("Audio/doodle_pop.ogg"));
REQUIRE(inputSoundFile.openFromStream(stream)); const auto inputSoundFile = sf::InputSoundFile::openFromStream(stream).value();
CHECK(inputSoundFile.getSampleCount() == 2'116'992); CHECK(inputSoundFile.getSampleCount() == 2'116'992);
CHECK(inputSoundFile.getChannelCount() == 2); CHECK(inputSoundFile.getChannelCount() == 2);
CHECK(inputSoundFile.getSampleRate() == 44'100); CHECK(inputSoundFile.getSampleRate() == 44'100);
@ -153,7 +139,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("wav") SECTION("wav")
{ {
REQUIRE(stream.open("Audio/killdeer.wav")); REQUIRE(stream.open("Audio/killdeer.wav"));
REQUIRE(inputSoundFile.openFromStream(stream)); const auto inputSoundFile = sf::InputSoundFile::openFromStream(stream).value();
CHECK(inputSoundFile.getSampleCount() == 112'941); CHECK(inputSoundFile.getSampleCount() == 112'941);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
CHECK(inputSoundFile.getSampleRate() == 22'050); CHECK(inputSoundFile.getSampleRate() == 22'050);
@ -166,11 +152,9 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("seek(std::uint64_t)") SECTION("seek(std::uint64_t)")
{ {
sf::InputSoundFile inputSoundFile;
SECTION("flac") SECTION("flac")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac")); auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
inputSoundFile.seek(1'000); inputSoundFile.seek(1'000);
CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(22'675)); CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(22'675));
CHECK(inputSoundFile.getSampleOffset() == 1'000); CHECK(inputSoundFile.getSampleOffset() == 1'000);
@ -178,7 +162,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("mp3") SECTION("mp3")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.mp3")); auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.mp3").value();
inputSoundFile.seek(1'000); inputSoundFile.seek(1'000);
CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(22'675)); CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(22'675));
CHECK(inputSoundFile.getSampleOffset() == 1'000); CHECK(inputSoundFile.getSampleOffset() == 1'000);
@ -186,7 +170,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("ogg") SECTION("ogg")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/doodle_pop.ogg")); auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/doodle_pop.ogg").value();
inputSoundFile.seek(1'000); inputSoundFile.seek(1'000);
CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(11'337)); CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(11'337));
CHECK(inputSoundFile.getSampleOffset() == 1'000); CHECK(inputSoundFile.getSampleOffset() == 1'000);
@ -194,7 +178,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("wav") SECTION("wav")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/killdeer.wav")); auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/killdeer.wav").value();
inputSoundFile.seek(1'000); inputSoundFile.seek(1'000);
CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(45'351)); CHECK(inputSoundFile.getTimeOffset() == sf::microseconds(45'351));
CHECK(inputSoundFile.getSampleOffset() == 1'000); CHECK(inputSoundFile.getSampleOffset() == 1'000);
@ -203,8 +187,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("seek(Time)") SECTION("seek(Time)")
{ {
sf::InputSoundFile inputSoundFile; auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac"));
inputSoundFile.seek(sf::milliseconds(100)); inputSoundFile.seek(sf::milliseconds(100));
CHECK(inputSoundFile.getSampleCount() == 87'798); CHECK(inputSoundFile.getSampleCount() == 87'798);
CHECK(inputSoundFile.getChannelCount() == 1); CHECK(inputSoundFile.getChannelCount() == 1);
@ -216,21 +199,15 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("read()") SECTION("read()")
{ {
sf::InputSoundFile inputSoundFile; auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
std::array<std::int16_t, 4> samples{};
SECTION("Unloaded file")
{
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 0);
}
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac"));
SECTION("Null address") SECTION("Null address")
{ {
CHECK(inputSoundFile.read(nullptr, 10) == 0); CHECK(inputSoundFile.read(nullptr, 10) == 0);
} }
std::array<std::int16_t, 4> samples{};
SECTION("Zero count") SECTION("Zero count")
{ {
CHECK(inputSoundFile.read(samples.data(), 0) == 0); CHECK(inputSoundFile.read(samples.data(), 0) == 0);
@ -240,7 +217,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
{ {
SECTION("flac") SECTION("flac")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac")); inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
CHECK(samples == std::array<std::int16_t, 4>{0, 1, -1, 4}); CHECK(samples == std::array<std::int16_t, 4>{0, 1, -1, 4});
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
@ -249,7 +226,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("mp3") SECTION("mp3")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/ding.mp3")); inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.mp3").value();
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
CHECK(samples == std::array<std::int16_t, 4>{0, -2, 0, 2}); CHECK(samples == std::array<std::int16_t, 4>{0, -2, 0, 2});
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
@ -258,7 +235,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("ogg") SECTION("ogg")
{ {
REQUIRE(inputSoundFile.openFromFile("Audio/doodle_pop.ogg")); inputSoundFile = sf::InputSoundFile::openFromFile("Audio/doodle_pop.ogg").value();
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
CHECK(samples == std::array<std::int16_t, 4>{-827, -985, -1168, -1319}); CHECK(samples == std::array<std::int16_t, 4>{-827, -985, -1168, -1319});
CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4); CHECK(inputSoundFile.read(samples.data(), samples.size()) == 4);
@ -274,8 +251,7 @@ TEST_CASE("[Audio] sf::InputSoundFile")
SECTION("close()") SECTION("close()")
{ {
sf::InputSoundFile inputSoundFile; auto inputSoundFile = sf::InputSoundFile::openFromFile("Audio/ding.flac").value();
REQUIRE(inputSoundFile.openFromFile("Audio/ding.flac"));
inputSoundFile.close(); inputSoundFile.close();
CHECK(inputSoundFile.getSampleCount() == 0); CHECK(inputSoundFile.getSampleCount() == 0);
CHECK(inputSoundFile.getChannelCount() == 0); CHECK(inputSoundFile.getChannelCount() == 0);

View File

@ -37,7 +37,6 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("Construction") SECTION("Construction")
{ {
const sf::Music music; const sf::Music music;
CHECK(music.getDuration() == sf::Time::Zero);
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
CHECK(length == sf::Time::Zero); CHECK(length == sf::Time::Zero);
@ -55,7 +54,6 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("Invalid file") SECTION("Invalid file")
{ {
REQUIRE(!music.openFromFile("does/not/exist.wav")); REQUIRE(!music.openFromFile("does/not/exist.wav"));
CHECK(music.getDuration() == sf::Time::Zero);
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
CHECK(length == sf::Time::Zero); CHECK(length == sf::Time::Zero);
@ -89,7 +87,6 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("Invalid buffer") SECTION("Invalid buffer")
{ {
REQUIRE(!music.openFromMemory(memory.data(), memory.size())); REQUIRE(!music.openFromMemory(memory.data(), memory.size()));
CHECK(music.getDuration() == sf::Time::Zero);
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
CHECK(length == sf::Time::Zero); CHECK(length == sf::Time::Zero);
@ -124,7 +121,6 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("Invalid stream") SECTION("Invalid stream")
{ {
CHECK(!music.openFromStream(stream)); CHECK(!music.openFromStream(stream));
CHECK(music.getDuration() == sf::Time::Zero);
const auto [offset, length] = music.getLoopPoints(); const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::Time::Zero); CHECK(offset == sf::Time::Zero);
CHECK(length == sf::Time::Zero); CHECK(length == sf::Time::Zero);
@ -178,32 +174,15 @@ TEST_CASE("[Audio] sf::Music", runAudioDeviceTests())
SECTION("setLoopPoints()") SECTION("setLoopPoints()")
{ {
sf::Music music; sf::Music music;
REQUIRE(music.openFromFile("Audio/killdeer.wav"));
SECTION("No file") music.setLoopPoints({sf::seconds(1), sf::seconds(2)});
{ const auto [offset, length] = music.getLoopPoints();
music.setLoopPoints({sf::Time::Zero, sf::Time::Zero}); CHECK(offset == sf::seconds(1));
const auto [offset, length] = music.getLoopPoints(); CHECK(length == sf::seconds(2));
CHECK(offset == sf::Time::Zero); CHECK(music.getChannelCount() == 1);
CHECK(length == sf::Time::Zero); CHECK(music.getSampleRate() == 22050);
CHECK(music.getChannelCount() == 0); CHECK(music.getStatus() == sf::Music::Status::Stopped);
CHECK(music.getSampleRate() == 0); CHECK(music.getPlayingOffset() == sf::Time::Zero);
CHECK(music.getStatus() == sf::Music::Status::Stopped); CHECK(!music.getLoop());
CHECK(music.getPlayingOffset() == sf::Time::Zero);
CHECK(!music.getLoop());
}
SECTION("Loaded file")
{
REQUIRE(music.openFromFile("Audio/killdeer.wav"));
music.setLoopPoints({sf::seconds(1), sf::seconds(2)});
const auto [offset, length] = music.getLoopPoints();
CHECK(offset == sf::seconds(1));
CHECK(length == sf::seconds(2));
CHECK(music.getChannelCount() == 1);
CHECK(music.getSampleRate() == 22050);
CHECK(music.getStatus() == sf::Music::Status::Stopped);
CHECK(music.getPlayingOffset() == sf::Time::Zero);
CHECK(!music.getLoop());
}
} }
} }

View File

@ -2,6 +2,7 @@
#include <type_traits> #include <type_traits>
static_assert(!std::is_default_constructible_v<sf::OutputSoundFile>);
static_assert(!std::is_copy_constructible_v<sf::OutputSoundFile>); static_assert(!std::is_copy_constructible_v<sf::OutputSoundFile>);
static_assert(!std::is_copy_assignable_v<sf::OutputSoundFile>); static_assert(!std::is_copy_assignable_v<sf::OutputSoundFile>);
static_assert(std::is_nothrow_move_constructible_v<sf::OutputSoundFile>); static_assert(std::is_nothrow_move_constructible_v<sf::OutputSoundFile>);

View File

@ -8,7 +8,6 @@
int main() int main()
{ {
// Audio // Audio
[[maybe_unused]] const sf::InputSoundFile inputSoundFile;
[[maybe_unused]] const sf::SoundBufferRecorder soundBufferRecorder; [[maybe_unused]] const sf::SoundBufferRecorder soundBufferRecorder;
[[maybe_unused]] const sf::Music music; [[maybe_unused]] const sf::Music music;