mirror of
https://github.com/SFML/SFML.git
synced 2024-11-24 20:31:05 +08:00
Remove default empty state of sf::Music
This commit is contained in:
parent
e7d67cfa2a
commit
52ce862a00
@ -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'
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>);
|
||||
|
@ -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));
|
||||
|
@ -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>);
|
||||
}
|
||||
|
||||
|
@ -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>);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user