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

Author Topic: Sound doesn't play if it was loaded and then copied in another object  (Read 4361 times)

0 Members and 1 Guest are viewing this topic.

Rocco2300

  • Newbie
  • *
  • Posts: 2
    • View Profile
I have an issue with loading a buffer into a sound if the loading happens in a constructor.

If I have an empty object, that was initialized with the default(empty) constructor, and then I assign it to another object, the sound doesn't play, but if I just call the constructor which has the loading logic by default, it plays the sound fine.

Sorry if I am not being explicit enough, here is the code:

#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>

class Player
{  
    sf::SoundBuffer buf;
    sf::Sound sound;
public:
    Player() { }
    Player(int a)
    {
        if(!buf.loadFromFile("Laser_Shoot2.wav"))
            std::cerr << "error loading sound \n";
        sound.setBuffer(buf);
    }
    void shoot()
    {
        sound.play();
    }
};

int main()
{
    sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
   
    #if true // doesn't play
    Player player;
    player = Player(3);
    player.shoot();
    #else // does play
    Player player(3);
    player.shoot();
    #endif

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        window.clear();
        window.display();
    }

    return 0;
}
 

I have used the precompiler if to test the bug, you can use it to see how it works, or doesn't. I made sure that the buffer is always in memory, which it is, I have checkes with gdb also, but it seems that the function setBuffer is not called for some reason if I copy the object over into an empty one. If I load the buffer again before playing the sound, it works just fine.

At the moment I have tried to find similar issues, but to no avail. I have tried to change the binaries of
the library, I used different audio drivers for the 2 audio outputs I have, doesn't work on either.

- OS: Windows 10 Pro 64-bit Ver. 20H2 OS build 19042.1466
- Compiler: g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
- Dynamic library: I have tried using 2.5.1 and main branch from github
- Audio Drivers: Realtek(R) Audio 6.0.8730.1
- Compiler flags: -g -O2 -std=c++17 -Wall

At the moment I am pretty sure this is not an intended feature of SFML, I have not found anything on the internet as I said, and neither on the forum or github, if I missed something and there was an issue raised, I am sorry, quite tired of debugging.

I have also made a test repo so that you would have all the files I have used:
https://github.com/Rocco2300/sfml-sound-issue
In the repo I have the 2.5.1 SFML version.

kojack

  • Sr. Member
  • ****
  • Posts: 343
  • C++/C# game dev teacher.
    • View Profile
Re: Sound doesn't play if it was loaded and then copied in another object
« Reply #1 on: February 03, 2022, 02:21:30 am »
SFML objects like sounds and textures are iffy to copy around, due to their attachments to other objects.

In your code:
Player player;
player = Player(3);
 
There are two Player objects. I'll refer to them as 1 and 2 to simplify it.
We have player, then we have a temporary made by Player(3). The temp is copied over the top of player.

Sounds are attached to SoundBuffers. The SoundBuffer keeps a list of every Sound that is using it (sharing soundbuffers is good for memory, many sounds to one sound buffer, like many sprites to one texture).

The temp Player is created. It's constructor loads an audio file into buf and attaches sound to buf.
Then when the temp Player is copied over player, the temp buf and sound are copied to the blank player's buf and sound.
So here's where things get tricky.
When a SoundBuffer is copied, it doesn't copy attachments.
When a Sound is copied, it attaches to the same buffer as the original was attached to.
At this point player.buf is set up, but the setBuffer's effect wasn't copied over, just the sample data. The player.sound is attached to the temporary buf, not player.buf!

After the copy, the temporary player is destructed. When a SoundBuffer is destructed, it detaches all Sounds. So player.sound is detached from the temporary buffer.
In the end player.buf is there, but has no sounds, and player.sound is there, but isn't attached to anything.

If you add this to your player class:
Player& operator=(const Player& p)
{
    buf = p.buf;
    sound = p.sound;
    sound.setBuffer(buf);
    return *this;
}
 
That should make it reattach the sound and the buffer when you do player = Player(3);

Although a better way would be to have buffers stored in a sound manager so they can be easily reused. Copying sounds around should be safe, but SoundBuffer is a resource that should only exist once for each audio file, so it's a bit trickier to deal with.

Rocco2300

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: Sound doesn't play if it was loaded and then copied in another object
« Reply #2 on: February 03, 2022, 11:03:54 am »
SFML objects like sounds and textures are iffy to copy around, due to their attachments to other objects.

In your code:
Player player;
player = Player(3);
 
There are two Player objects. I'll refer to them as 1 and 2 to simplify it.
We have player, then we have a temporary made by Player(3). The temp is copied over the top of player.

Sounds are attached to SoundBuffers. The SoundBuffer keeps a list of every Sound that is using it (sharing soundbuffers is good for memory, many sounds to one sound buffer, like many sprites to one texture).

The temp Player is created. It's constructor loads an audio file into buf and attaches sound to buf.
Then when the temp Player is copied over player, the temp buf and sound are copied to the blank player's buf and sound.
So here's where things get tricky.
When a SoundBuffer is copied, it doesn't copy attachments.
When a Sound is copied, it attaches to the same buffer as the original was attached to.
At this point player.buf is set up, but the setBuffer's effect wasn't copied over, just the sample data. The player.sound is attached to the temporary buf, not player.buf!

After the copy, the temporary player is destructed. When a SoundBuffer is destructed, it detaches all Sounds. So player.sound is detached from the temporary buffer.
In the end player.buf is there, but has no sounds, and player.sound is there, but isn't attached to anything.

If you add this to your player class:
Player& operator=(const Player& p)
{
    buf = p.buf;
    sound = p.sound;
    sound.setBuffer(buf);
    return *this;
}
 
That should make it reattach the sound and the buffer when you do player = Player(3);

Although a better way would be to have buffers stored in a sound manager so they can be easily reused. Copying sounds around should be safe, but SoundBuffer is a resource that should only exist once for each audio file, so it's a bit trickier to deal with.

I see, I wanted to make a sound manager but I would have ran in to the same issue of having to declare an empty object in the header and then either copying into it or initializing it with another function, I tried doing this for testing and got frustrated that nothing works.

This seems logical at this point, I will try to wrap all the sound logic in a sound manager and see how this goes, thank you for the reply and help.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Sound doesn't play if it was loaded and then copied in another object
« Reply #3 on: February 03, 2022, 11:09:57 am »
The SFML Game Development book also has a nice example for a resource holder that can be used for textures as well as sound buffers: https://github.com/SFML/SFML-Game-Development-Book/tree/master/02_Resources/Include/Book
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/