Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: sf::Music fails to loop after seeking  (Read 8771 times)

0 Members and 1 Guest are viewing this topic.

Marukyu

  • Newbie
  • *
  • Posts: 36
    • View Profile
sf::Music fails to loop after seeking
« on: April 19, 2019, 07:55:46 pm »
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.

This is due to neither of the two conditions in Music::onLoop 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), 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. :)

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: sf::Music fails to loop after seeking
« Reply #1 on: April 25, 2019, 10:23:14 am »
Thanks for letting us know. I'll look into this as soon as possible.
Do you have any suggested fix for this issue?
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Marukyu

  • Newbie
  • *
  • Posts: 36
    • View Profile
Re: sf::Music fails to loop after seeking
« Reply #2 on: April 25, 2019, 01:21:11 pm »
My suggestion would be to round the sample index to a multiple of the channel count when seeking an sf::InputSoundFile, in order to avoid ending up "between" multi-channel samples in m_sampleOffset:

https://github.com/Marukyu/SFML/commit/a6bd278c0c4509cb948e3b18ff9812d970b7b8e0

I haven't gotten around to testing the fix too extensively yet (e.g. sound file types other than Ogg/Vorbis, channel counts other than stereo), but it appeared to work for my use case without any obvious adverse effects.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Re: sf::Music fails to loop after seeking
« Reply #3 on: April 25, 2019, 01:58:50 pm »
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.
Laurent Gomila - SFML developer

CvxFous

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: sf::Music fails to loop after seeking
« Reply #4 on: March 24, 2020, 09:43:59 pm »
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
« Last Edit: March 24, 2020, 09:48:02 pm by CvxFous »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: sf::Music fails to loop after seeking
« Reply #5 on: March 25, 2020, 10:31:30 am »
Since music streams directly from the file, there might be some issue in the decoder. What file format are you using? Can you provide the two files?
As a work around, maybe you could try using just a WAV.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

CvxFous

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: sf::Music fails to loop after seeking
« Reply #6 on: March 25, 2020, 08:07:11 pm »
Hi, it can be any file, I tested it even with twice the same sound.

The format is .ogg

One thing important to know is that sometimes it does work, depending on the offset seeked

EDIT: I'll try converting my file to .wav to see if this is any different.

EDIT2: So with wav it still doesn't work properly, I've observed three scenarios:
- It does work properly (1/10 tests)
- It loops but start the second loop at a different point in time before setting it back to the beginning, then the third and the loop after are correct (8/10 tests)
- It loops at a different starting point to a different ending point (1/10 tests)

What is going on?

Files used for this test:
« Last Edit: March 25, 2020, 08:26:02 pm by CvxFous »

 

anything