summaryrefslogtreecommitdiff
path: root/src/SFML/Audio/Music.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/SFML/Audio/Music.cpp')
-rw-r--r--src/SFML/Audio/Music.cpp271
1 files changed, 271 insertions, 0 deletions
diff --git a/src/SFML/Audio/Music.cpp b/src/SFML/Audio/Music.cpp
new file mode 100644
index 0000000..141d690
--- /dev/null
+++ b/src/SFML/Audio/Music.cpp
@@ -0,0 +1,271 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2018 Laurent Gomila (laurent@sfml-dev.org)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+// you must not claim that you wrote the original software.
+// If you use this software in a product, an acknowledgment
+// in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+// and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Audio/Music.hpp>
+#include <SFML/Audio/ALCheck.hpp>
+#include <SFML/System/Lock.hpp>
+#include <SFML/System/Err.hpp>
+#include <fstream>
+
+
+namespace sf
+{
+////////////////////////////////////////////////////////////
+Music::Music() :
+m_file (),
+m_loopSpan (0, 0)
+{
+
+}
+
+
+////////////////////////////////////////////////////////////
+Music::~Music()
+{
+ // We must stop before destroying the file
+ stop();
+}
+
+
+////////////////////////////////////////////////////////////
+bool Music::openFromFile(const std::string& filename)
+{
+ // First stop the music if it was already running
+ stop();
+
+ // Open the underlying sound file
+ if (!m_file.openFromFile(filename))
+ return false;
+
+ // Perform common initializations
+ initialize();
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////
+bool Music::openFromMemory(const void* data, std::size_t sizeInBytes)
+{
+ // First stop the music if it was already running
+ stop();
+
+ // Open the underlying sound file
+ if (!m_file.openFromMemory(data, sizeInBytes))
+ return false;
+
+ // Perform common initializations
+ initialize();
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////
+bool Music::openFromStream(InputStream& stream)
+{
+ // First stop the music if it was already running
+ stop();
+
+ // Open the underlying sound file
+ if (!m_file.openFromStream(stream))
+ return false;
+
+ // Perform common initializations
+ initialize();
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////
+Time Music::getDuration() const
+{
+ return m_file.getDuration();
+}
+
+
+////////////////////////////////////////////////////////////
+Music::TimeSpan Music::getLoopPoints() const
+{
+ return TimeSpan(samplesToTime(m_loopSpan.offset), samplesToTime(m_loopSpan.length));
+}
+
+
+////////////////////////////////////////////////////////////
+void Music::setLoopPoints(TimeSpan timePoints)
+{
+ Span<Uint64> samplePoints(timeToSamples(timePoints.offset), timeToSamples(timePoints.length));
+
+ // Check our state. This averts a divide-by-zero. GetChannelCount() is cheap enough to use often
+ if (getChannelCount() == 0 || m_file.getSampleCount() == 0)
+ {
+ sf::err() << "Music is not in a valid state to assign Loop Points." << std::endl;
+ return;
+ }
+
+ // Round up to the next even sample if needed
+ samplePoints.offset += (getChannelCount() - 1);
+ samplePoints.offset -= (samplePoints.offset % getChannelCount());
+ samplePoints.length += (getChannelCount() - 1);
+ samplePoints.length -= (samplePoints.length % getChannelCount());
+
+ // Validate
+ if (samplePoints.offset >= m_file.getSampleCount())
+ {
+ sf::err() << "LoopPoints offset val must be in range [0, Duration)." << std::endl;
+ return;
+ }
+ if (samplePoints.length == 0)
+ {
+ sf::err() << "LoopPoints length val must be nonzero." << std::endl;
+ return;
+ }
+
+ // Clamp End Point
+ samplePoints.length = std::min(samplePoints.length, m_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)
+ return;
+
+ // When we apply this change, we need to "reset" this instance and its buffer
+
+ // Get old playing status and position
+ Status oldStatus = getStatus();
+ Time oldPos = getPlayingOffset();
+
+ // Unload
+ stop();
+
+ // Set
+ m_loopSpan = samplePoints;
+
+ // Restore
+ if (oldPos != Time::Zero)
+ setPlayingOffset(oldPos);
+
+ // Resume
+ if (oldStatus == Playing)
+ play();
+}
+
+
+////////////////////////////////////////////////////////////
+bool Music::onGetData(SoundStream::Chunk& data)
+{
+ Lock lock(m_mutex);
+
+ std::size_t toFill = m_samples.size();
+ Uint64 currentOffset = m_file.getSampleOffset();
+ Uint64 loopEnd = m_loopSpan.offset + m_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))
+ toFill = static_cast<std::size_t>(loopEnd - currentOffset);
+
+ // Fill the chunk parameters
+ data.samples = &m_samples[0];
+ data.sampleCount = static_cast<std::size_t>(m_file.read(&m_samples[0], 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);
+}
+
+
+////////////////////////////////////////////////////////////
+void Music::onSeek(Time timeOffset)
+{
+ Lock lock(m_mutex);
+ m_file.seek(timeOffset);
+}
+
+
+////////////////////////////////////////////////////////////
+Int64 Music::onLoop()
+{
+ // Called by underlying SoundStream so we can determine where to loop.
+ Lock lock(m_mutex);
+ Uint64 currentOffset = m_file.getSampleOffset();
+ 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
+ // 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();
+ }
+ else if (getLoop() && (currentOffset >= m_file.getSampleCount()))
+ {
+ // If we're at the EOF, reset to 0
+ m_file.seek(0);
+ return 0;
+ }
+ return NoLoop;
+}
+
+
+////////////////////////////////////////////////////////////
+void Music::initialize()
+{
+ // 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());
+}
+
+////////////////////////////////////////////////////////////
+Uint64 Music::timeToSamples(Time position) const
+{
+ // Always ROUND, no unchecked truncation, hence the addition in the numerator.
+ // This avoids most precision errors arising from "samples => Time => samples" conversions
+ // Original rounding calculation is ((Micros * Freq * Channels) / 1000000) + 0.5
+ // We refactor it to keep Int64 as the data type throughout the whole operation.
+ return ((position.asMicroseconds() * getSampleRate() * getChannelCount()) + 500000) / 1000000;
+}
+
+
+////////////////////////////////////////////////////////////
+Time Music::samplesToTime(Uint64 samples) const
+{
+ Time position = Time::Zero;
+
+ // Make sure we don't divide by 0
+ if (getSampleRate() != 0 && getChannelCount() != 0)
+ position = microseconds((samples * 1000000) / (getChannelCount() * getSampleRate()));
+
+ return position;
+}
+
+} // namespace sf