When seeking an sf::Music object to certain playback offsets, it will not loop at the end despite setLoop having been called on it.
The following example reproduces this issue on Linux Mint 18.3 (the example requires testsound.ogg, found in the attachments, to be present in the working directory, though any stereo Ogg sound should work):
#include <SFML/Audio/Music.hpp>
#include <SFML/System/Sleep.hpp>
#include <SFML/System/Time.hpp>
#include <iostream>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
std::vector<char> readFile(std::string fileName)
{
std::ifstream f(fileName);
std::vector<char> data;
f.seekg(0, std::ios::end);
data.resize(f.tellg());
f.seekg(0, std::ios::beg);
f.read(data.data(), data.size());
return data;
}
std::unique_ptr<sf::Music> play(const std::vector<char> & buffer, sf::Time offset)
{
std::unique_ptr<sf::Music> music = std::make_unique<sf::Music>();
music->openFromMemory(buffer.data(), buffer.size());
music->setLoop(true);
music->setVolume(20);
music->setPitch(1);
music->play();
if (offset != sf::Time::Zero)
{
music->setPlayingOffset(offset);
std::cout << "Playing at " << offset.asMicroseconds() << " us" << std::endl;
}
else
{
std::cout << "Playing from start" << std::endl;
}
return std::move(music);
}
int main()
{
auto buffer = readFile("testsound.ogg");
std::unique_ptr<sf::Music> music;
music = play(buffer, sf::Time::Zero); // Playing from the start: sound loops correctly
sf::sleep(sf::seconds(5));
music = play(buffer, sf::microseconds(20)); // Issue occurs here: sound does not loop
sf::sleep(sf::seconds(5));
music = play(buffer, sf::microseconds(25)); // No issue here, sound loops properly
sf::sleep(sf::seconds(5));
return 0;
}
Playing the example sound from the start or seeking to 25 microseconds loops the sound just fine, but seeking to 20 microseconds causes the sound to stop at the end instead.
Through debugging, I found that in the erroneous case, when reaching the end of the sound, SoundStream::fillAndPushBuffer exceeds the retry limit and requests a stop (https://github.com/SFML/SFML/blob/4043f71156a7149f8d4bafe15d3ac73c440fae7b/src/SFML/Audio/SoundStream.cpp#L458).
This is due to neither of the two conditions in Music::onLoop (https://github.com/SFML/SFML/blob/4043f71156a7149f8d4bafe15d3ac73c440fae7b/src/SFML/Audio/Music.cpp#L217) being met: the current sample position is just one sample short of the music's ending, but no more full multi-channel samples can be read, therefore never allowing the loop condition to trigger.
This playback offset misalignment was caused by InputSoundFile::seek(Uint64) (https://github.com/SFML/SFML/blob/4043f71156a7149f8d4bafe15d3ac73c440fae7b/src/SFML/Audio/InputSoundFile.cpp#L232), which accepts offsets that end up between full multi-channel samples. I fixed this issue locally by dividing/multiplying the offset by the channel count, changing InputSoundFile::seek(Uint64)'s function body to the following:
void InputSoundFile::seek(Uint64 sampleOffset)
{
if (m_reader && m_channelCount != 0)
{
// The reader handles an overrun gracefully, but we
// pre-check to keep our known position consistent
m_sampleOffset = std::min(sampleOffset / m_channelCount * m_channelCount, m_sampleCount);
m_reader->seek(m_sampleOffset);
}
}
I haven't familiarized myself too deeply with SFML's sound streaming classes and audio streaming in general, so I might be completely mistaken about most or all of what I've written above. It could also be a completely unrelated issue instead, as the phenomenon has proven rather difficult to reproduce reliably. :)
I think you're close to the right solution.
If you look at the call chain, seek(sample) is called by seek(time), which is implemented as:
void InputSoundFile::seek(Time timeOffset)
{
seek(static_cast<Uint64>(timeOffset.asSeconds() * m_sampleRate * m_channelCount));
}
I think it needs a fix: we need to round before multiplying by the channel count (time * sampleRate is a "logical" (multi-channel) sample index, so it is already an integer -- and then we multiply by channelCount to get the corresponding "physical" sample index).
void InputSoundFile::seek(Time timeOffset)
{
seek(static_cast<Uint64>(timeOffset.asSeconds() * m_sampleRate) * m_channelCount);
}
But then maybe we should apply your fix too, in cases where seek(sample) is called directly.
Hi,
I know this topic is almost one year old,but the problem described here is exactly what I have, so I feel like I shouldn't create a new topic for the same issue.
I have two musics, musicA and musicB, same length, one with bass, the other with bass + drums + guitar.
Whenever I finish a level in my game, I want to pause musicA, get the currentOffset and start musicB.
In practice it works fine and all, but the loop is screwed up depending on where you seek. Most of the time it results in the music stopping right at the end without looping.
void Level::switchMusic(bool toVictoryScreen) {
if (toVictoryScreen) {
_music.pause();
const sf::Time musicOffset = _music.getPlayingOffset();
_musicVictory.setPlayingOffset(musicOffset);
_musicVictory.play();
}
else {
_musicVictory.pause();
const sf::Time musicOffset = _musicVictory.getPlayingOffset();
_music.setPlayingOffset(musicOffset);
_music.play();
}
}
Is there any fix yet for this issue?
OS: WIndows 10 and/or Ubuntu 18.04
SFML 2.5.1