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

Author Topic: Problem With Vector / Audio Operations [SOLVED]  (Read 5488 times)

0 Members and 1 Guest are viewing this topic.

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Problem With Vector / Audio Operations [SOLVED]
« on: January 18, 2022, 11:22:22 am »
Hello and good day!

So I'm still new with C++ and it fights me every chance it gets. If you hear me yelling I'm probably yelling at the compiler and not my kids ;)

The problem I'm having today is more C++ related than Audio module related but I happen to be working with audio so I'm posting it here. I'd post it in the discord or ask around in IRC but I can't seem to get either working. It could be my horrible internet connection, or my Brave browser, but I put shields down so I don't know...

So I'm trying to do a Game::Sounds mixer class but for the life of me I can't get the std::vector to behave. I've made a std::vector for both the sf::Sound and the sf::SoundBuffer variables. Do I need a separate vector for both or is there a better way?

I would like to put the Sounds in a vector as they're played. I'll push them to the vector when played and pop them out when they're finished. It doesn't have to be a std::vector, I could use an array or whatever an std::list is but I figured it would be best for the job at hand.

Anywho, I wrote this micro example with a dummied sf::Sound and sf::SoundBuffer for testing purposes; it closely resembles what I actually have, minus a bunch of code irrelevant to the topic. Same problems. It can be easily tested @ ...

https://www.onlinegdb.com/online_c++_compiler

Code: [Select]
//-- Game/Sounds.hpp --//

#ifndef GAME_SOUNDS
#define GAME_SOUNDS

#include <vector>
#include <iostream>
#include <string>


// Dummy SFML sf:: namespace ;)
namespace sf {
    class Sound {};
    class SoundBuffer {};
}

namespace Game {
static const int SOUND_MAX = 16;
class Sounds {
public:
// Public Member Declarations
        std::vector<sf::Sound> m_SoundV[16];
        std::vector<sf::SoundBuffer> m_BufferV[16];
        // Constructor and Destructor
        Sounds();
        ~Sounds();
// Public Function Declarations
bool play(const std::string& l_name, float volume, float pitch);
int size();
};
} // End of Game::Sounds

#endif




//-- Game/Sounds.cpp --//

namespace Game {

bool Sounds::play(const std::string& l_name, float volume, float pitch) {
// Create new sound and buffer
sf::Sound sound;
sf::SoundBuffer buffer;


// Omitted: Load sound / return error on fail
// Omitted: Set volume and pitch


// Add sound and buffer to vector
m_SoundV.push_back(sound);
m_BufferV.push_back(buffer);
// For example simplicity, we just return true (no error)
return true;
}

int Sounds::size() {
    // Get size of sound vector
    return m_SoundV.size();
}
} // End of Game::Sounds




//-- Main.cpp --//

int main() {
    Game::Sounds soundmixer;
    soundmixer.play("Sample", 100.0f, 1.0f);
    std::cout << soundmixer.size() << std::endl;
    return 0;
}

Here is a snapshot of the errors I'm currently having...

Code: [Select]
main.cpp:53:11: error: request for member ‘push_back’ in ‘((Game::Sounds*)this)->Game::Sounds::m_SoundV’, which is of non-class type ‘std::vector [16]’
   53 |  m_SoundV.push_back(sound);
      |           ^~~~~~~~~
main.cpp:54:12: error: request for member ‘push_back’ in ‘((Game::Sounds*)this)->Game::Sounds::m_BufferV’, which is of non-class type ‘std::vector [16]’
   54 |  m_BufferV.push_back(buffer);
      |            ^~~~~~~~~
main.cpp: In member function ‘int Game::Sounds::size()’:
main.cpp:61:21: error: request for member ‘size’ in ‘((Game::Sounds*)this)->Game::Sounds::m_SoundV’, which is of non-class type ‘std::vector [16]’
   61 |     return m_SoundV.size();

Why is the vector being so naughty? When I originally tried it, I was trying to construct the vectors in many such a ways; using const, using static, attempting a std::vector<T*> template stuff (which I don't understand yet), passing references& and pointers*. No matter what I do I can't seem to catch a break with it. I've been crunching on reading material and tutorials, even finished a 1300+ page book on C++ but somehow I haven't been able to figure out vectors.

At one point I was doing something like vector->push_back(obj); not that I thought that was the correct way to do it, but it seemed to shut the compiler up for a second.

Once I get better at C++ and have suffered my metaphorical beatings, I shouldn't be blowing these threads up too much. ;)

EDIT: Follow-up question. This is an old function, utilizing an sf::Sound and an sf::SoundBuffer.

Code: [Select]
bool Audio::playSE(const sf::String& l_name, float volume, float pitch) {
    if (l_name.isEmpty()) {
        // Stop the SE
        m_SE.stop();
        // Nothing wrong with stopping the SE
        return true;
    }
    // Get sound buffer
    //sf::SoundBuffer buffer;
    // If unable to open from file
    if (!m_SE.openFromFile(l_name)) {
        // Error opening file
        return false;
    }
    // Set volume, pitch and loop
    m_SE.setVolume(volume);
    m_SE.setPitch(pitch);
    m_SE.setLoop(false);
    // Play the SE
    //m_SE.setBuffer(buffer);
    m_SE.play();
    // Opened file successfully (just kidding...)
    return true;
}

^Does the buffer go out of scope here as soon as the function is finished? I've never heard a single sound doing this. Do I have to carry buffer on to some outside update() function within my game loop and manage it until the entire sound is finished playing? In the example before this one, I had a vector for sounds AND buffers; do I need them both or would there be a better way?

I did read the tutorial a few times. When I swapped out the sf::Sound for an sf::Music I didn't have to use a sf::SoundBuffer but I'm trying to do things proper as I'm a very by the book kind of guy. I can only guess how many sounds would be played at once and I don't want to bog it down with a bunch of sf::Music objects (which does work for me but I know its wrong and I don't want to initiate a bunch of heavy Musics when I need to play a bunch of consecutive lightweight Sounds.)
« Last Edit: January 22, 2022, 11:19:39 am by Kain Nobel »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Problem With Vector / Audio Operations
« Reply #1 on: January 18, 2022, 11:47:59 am »
You're currently declaring an array of std::vector by writing std::vector<T> var[10]. The [10] means a vector of 10 elements and the elements will be std::vector<T>.

I guess what you wanted is a std::vector<T> with 10 elements default-initialized. For that you need to call the constructor with the number of items to initialize it with and you can't do it with the []-operator.

For sf::Sound I highly recommend using std::deque that way you can push_back newly playing sounds and pop_front sounds that stopped playing. And on top of that you also don't lose the connection between sf::Sound instanced and sf::SoundBuffer instances, like you would with a vector if a new push requires the reallocation of all its elements.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

kojack

  • Sr. Member
  • ****
  • Posts: 343
  • C++/C# game dev teacher.
    • View Profile
Re: Problem With Vector / Audio Operations
« Reply #2 on: January 18, 2022, 01:21:23 pm »
You also need to be a little careful with sounds and sound buffers, due to the way they are linked to each other internally (sounds know which buffer they are using, buffers know every sound using them).
For example, if you had this code:
        std::vector<sf::SoundBuffer> sbv;
        std::vector<sf::Sound> sv;

        {
                sf::SoundBuffer sb;
                sb.loadFromFile("data/NFF-soft-confirm.wav");
                sf::Sound s(sb);

                sbv.push_back(sb);
                sv.push_back(s);
                sv.back().play();
        }
 
This will start trying to play the sound, then immediately stop (usually too fast to tell, if you aren't debug stepping through the code).
The reason is there are 2 sounds and 2 buffers alive at once, the local ones (s and sb) and the copies in the vectors. When a sound is pushed into a vector, it is duplicated. The duplicate connects itself to the buffer sb, since that's what sound s is connected to.
When buffer sb goes out of scope at the end of the braces, it's destructor tells every sound using that buffer to remove their buffer connection. Both sounds (s and the one in the vector) lose their buffer.

So the connection (and any play operations) needs to happen after pushing the sound and sound buffer, otherwise it will be lost.
And as eXpl0it3r says, vectors themselves can shuffle around (as they fill up, they will move everything to larger blocks of memory) which will break the connection, so something like a deque will help.

Using any complex class (like sounds, sprites, textures, etc) as a value in an std::vector or other container is always something to be careful of, since the duplication while inserting/pushing might mess things up on cleanup. If I'm not familiar with the internals of a class, I'd always heap allocate it and push the address instead.

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Problem With Vector / Audio Operations
« Reply #3 on: January 20, 2022, 12:16:33 pm »
Here is a (stupid) piece from my SoundMix update() function. I know it would require more logic than that, of course, but the erase function throws an error and I need to address this next. I think I know what I need to do I just don't know how to write it, I guess.

A piece of the function for erasing...
Code: [Select]
    // End method if no sound is playing
    if (m_SoundV[i].getStatus() == sf::Sound::Stopped) {
        m_SoundV.erase(m_SoundV[i]); // I also tried .erase(i);
        m_BufferV.erase(m_BufferV[i]); // I also tried .erase(i);
        return;
    }

The errors I get on erasing
Code: [Select]
||=== Build: Debug in Game (compiler: GNU GCC Compiler) ===|
C:\Game\src\game\soundmix.cpp||In member function 'void Game::Sound::update(int)':|
C:\Game\src\game\soundmix.cpp|605|error: no matching function for call to 'std::deque<sf::Sound>::erase(__gnu_cxx::__alloc_traits<std::allocator<sf::Sound> >::value_type&)'|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1742|note: candidate: std::deque<_Tp, _Alloc>::iterator std::deque<_Tp, _Alloc>::erase(std::deque<_Tp, _Alloc>::const_iterator) [with _Tp = sf::Sound; _Alloc = std::allocator<sf::Sound>; std::deque<_Tp, _Alloc>::iterator = std::_Deque_iterator<sf::Sound, sf::Sound&, sf::Sound*>; std::deque<_Tp, _Alloc>::const_iterator = std::_Deque_iterator<sf::Sound, const sf::Sound&, const sf::Sound*>]|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1742|note:   no known conversion for argument 1 from '__gnu_cxx::__alloc_traits<std::allocator<sf::Sound> >::value_type {aka sf::Sound}' to 'std::deque<sf::Sound>::const_iterator {aka std::_Deque_iterator<sf::Sound, const sf::Sound&, const sf::Sound*>}'|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1766|note: candidate: std::deque<_Tp, _Alloc>::iterator std::deque<_Tp, _Alloc>::erase(std::deque<_Tp, _Alloc>::const_iterator, std::deque<_Tp, _Alloc>::const_iterator) [with _Tp = sf::Sound; _Alloc = std::allocator<sf::Sound>; std::deque<_Tp, _Alloc>::iterator = std::_Deque_iterator<sf::Sound, sf::Sound&, sf::Sound*>; std::deque<_Tp, _Alloc>::const_iterator = std::_Deque_iterator<sf::Sound, const sf::Sound&, const sf::Sound*>]|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1766|note:   candidate expects 2 arguments, 1 provided|
C:\Game\src\game\soundmix.cpp|606|error: no matching function for call to 'std::deque<sf::SoundBuffer>::erase(__gnu_cxx::__alloc_traits<std::allocator<sf::SoundBuffer> >::value_type&)'|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1742|note: candidate: std::deque<_Tp, _Alloc>::iterator std::deque<_Tp, _Alloc>::erase(std::deque<_Tp, _Alloc>::const_iterator) [with _Tp = sf::SoundBuffer; _Alloc = std::allocator<sf::SoundBuffer>; std::deque<_Tp, _Alloc>::iterator = std::_Deque_iterator<sf::SoundBuffer, sf::SoundBuffer&, sf::SoundBuffer*>; std::deque<_Tp, _Alloc>::const_iterator = std::_Deque_iterator<sf::SoundBuffer, const sf::SoundBuffer&, const sf::SoundBuffer*>]|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1742|note:   no known conversion for argument 1 from '__gnu_cxx::__alloc_traits<std::allocator<sf::SoundBuffer> >::value_type {aka sf::SoundBuffer}' to 'std::deque<sf::SoundBuffer>::const_iterator {aka std::_Deque_iterator<sf::SoundBuffer, const sf::SoundBuffer&, const sf::SoundBuffer*>}'|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1766|note: candidate: std::deque<_Tp, _Alloc>::iterator std::deque<_Tp, _Alloc>::erase(std::deque<_Tp, _Alloc>::const_iterator, std::deque<_Tp, _Alloc>::const_iterator) [with _Tp = sf::SoundBuffer; _Alloc = std::allocator<sf::SoundBuffer>; std::deque<_Tp, _Alloc>::iterator = std::_Deque_iterator<sf::SoundBuffer, sf::SoundBuffer&, sf::SoundBuffer*>; std::deque<_Tp, _Alloc>::const_iterator = std::_Deque_iterator<sf::SoundBuffer, const sf::SoundBuffer&, const sf::SoundBuffer*>]|
C:\TDM-GCC-32\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_deque.h|1766|note:   candidate expects 2 arguments, 1 provided|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 1 second(s)) ===|

I did not assign a template (well, I attempted to) for the std::deque but it was somehow working GREAT until I decided I wanted to start deleting things that aren't playing anymore. I *think* I need to assign templates instead of std::deque<sf::Sound> and std::deque<sf::SoundBuffer>. I don't understand WHY C++ tutorials confuse the hell out of me but the whole template thing does confuse me and I can't get it to behave. I know almost exactly what I want to do but I'm all hung up on syntax.

If the tutorials I've read online weren't confusing me so much I wouldn't ask but how would you define the template I need to write in? What I had (but commented out) was something along the lines of...

Code: [Select]
// In HEADER...

template<typename S> sf::Sound;
template<typename B> sf::SoundBuffer;

namespace Game {
class SoundMix {
    public:
        std::deque<S> m_SoundV;
        std::deque<B> m_BufferV;
};
}

I only removed the template stuff because it wouldn't compile but I think it is needed - is it? The program that I'm compiling right now can play tons of sounds concurrently but I don't have a way to erase them and they're NOT templated, I passed the actual sf::Sound and sf::SoundBuffer into the std::deque<obj> as I defined them in the header. I would imagine these things would build up quick and I need to purge them like they were yesterday's brunch.

I'm a Ruby programmer so I'll post how I'm trying to do it in a mock Ruby style (it should be easy enough to read, even if you've never programmed Ruby - Ruby is EXTREMELY SIMPLE to program, its almost like speaking life into a machine with plain language.)

Code: [Select]
@sounds.each_index {|i| @sounds.delete_at(i) if @sounds[i].stopped?}
@buffers.each_index {|i| @buffers.delete_at(i) if @buffers[i].no_connections?}

What do I need to do to perform a function like that with the C++? And how should the template be? I'm almost to where I need to be thanks to both of you :)
« Last Edit: January 20, 2022, 12:24:12 pm by Kain Nobel »

kojack

  • Sr. Member
  • ****
  • Posts: 343
  • C++/C# game dev teacher.
    • View Profile
Re: Problem With Vector / Audio Operations
« Reply #4 on: January 20, 2022, 02:40:15 pm »
The erase function requires an iterator to tell it which one to erase. In your code:
m_SoundV.erase(m_SoundV[i]);
m_BufferV.erase(m_BufferV[i]);
the value you are passing to erase is the actual sound or soundbuffer object, not an iterator to it.

An iterator is like a pointer, it's an object made to move through containers. STD likes them a lot.
Given an index number i, you can turn it into an iterator by adding it to another iterator, like begin(), which is the iterator of the first thing in the deque:
m_SoundV.erase(m_SoundV.begin()+i);
m_BufferV.erase(m_BufferV.begin()+i);

I find iterators to be annoying, but there are some containers (maps, lists) that can't be used with an index number lookup the iterator has extra logic for them to step 1 at a time.

Something that can go wrong with erasing is values move down to fill the gap. If you erase at position 5, whatever was at position 6 will now be position 5 and so on. Your code is ok, since it returns immediately after the erasing, it doesn't keep stepping through the deque. But it does mean you can only stop one sound each time. If several stopped, it takes one pass for each. The way around that is to make it take a step back if it erases. So if you erase at position 5, you need to check 5 again since a new item is there now. A bit like this:
  for(int i=0;i<m_soundV.size();++i)
{
    if (m_SoundV[i].getStatus() == sf::Sound::Stopped) {
        m_SoundV.erase(m_SoundV.begin()+i);
        m_BufferV.erase(m_BufferV.begin()+i);
        --i;
    }
}
This way the --i takes a step back, to counteract the ++i step forwards in the loop, so if anything is erased, you won't skip the next one, they all get checked.

The parts about templates you've written look like you meant typedefs. There's no need to make any templates for an std::deque, it already is one.

A typedef lets you give an alternative name to a type, usually to make it easier to use.
typedef sf::Sound S;
std::deque<S> m_SoundV;
In that code, S is another name for sf::Sound. Or you could shorten it even further:
typedef std::deque<sf::Sound> S;
S m_soundV;
Now S is the entire deque type.
But none of these are actually needed, they just reduce your typing if you use std::deque<sf::Sound> a lot.
I think that's what you meant there.


Anyway, one little SFML optimisation tip. SoundBuffers are large, they could take megabytes each (they are raw uncompressed audio, like a WAV file). Sounds are tiny. You can have many Sounds all sharing a single SoundBuffer. If 100 gunshots are playing at once, you can have 100 Sounds and 1 SoundBuffer. So usually you don't remove a SoundBuffer, you keep it around, since making a new one uses a lot of memory and slows down to read the file from disk again.
The same with sprites and textures, you can have hundreds of Sprites all using 1 Texture.
« Last Edit: January 20, 2022, 05:26:54 pm by eXpl0it3r »

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Problem With Vector / Audio Operations
« Reply #5 on: January 21, 2022, 04:53:25 am »
Cool, I'm starting to understand a little bit better as I go. It seems the entire module from top to bottom is now complete, all I have to do is schedule "chores" for buffer cleanup every once in a while ;)

Thank you both for your help! I am a quick learner so I should be needing less and less help as time goes on; I'll try to do what I can around here to help as well :D Of course, at this point in time, my help would be the blind leading the blind, haha, but I'm catching on and studying more and more every day.

SOLVED!

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Problem With Vector / Audio Operations [SOLVED]
« Reply #6 on: January 21, 2022, 08:50:37 am »
Btw. if your sounds are or similar lengths (few seconds), you can also go with a lazy clean-up method.
Meaning you just push_back() new sounds and every now and then check run pop_front() while front() has stopped playing.
This can lead to sounds in the middle of the deque having finished, but they too will eventually be removed. That way you don't have to do any erase magic and can just push_back() new stuff and pop_front() finished stuff.

This works really well if you're not expecting to queue hundreds of sounds at the same time and have no sound that plays for many seconds or even minutes
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Kain Nobel

  • Newbie
  • *
  • Posts: 38
    • View Profile
Re: Problem With Vector / Audio Operations [Un-SOLVED]
« Reply #7 on: January 22, 2022, 04:36:57 am »
Update: I've marked this as Un-SOLVED but I'm putting it back as SOLVED because I've implemented a satisfactory buffer cleanup! I'll leave this lingering question just in case anybody has a good answer. However, I'm not stuck on it so we're good now :D

(click to show/hide)

This is basically what I did, just for reference to anybody new who might need to do it. I basically time the longest sound, double that time, and do a full clear of the buffers if the time maxes out. I'm sure an even BETTER implementation COULD be made, sure, but this works for me.

Code: [Select]
bool SoundMix::play(const sf::String& l_name, float volume, float pitch) {
    // End function doing nothing if no name given
    if (l_name.isEmpty()) { return true; }
    // Create sound and sound buffer
    sf::Sound sound;
    sf::SoundBuffer buffer;
    // If unable to open from file
    if (!buffer.loadFromFile(l_name)) {
        // Print to console
        cout << "Unable to load Sound Effect" << endl;
        // Error opening file
        return false;
    }
    // Set volume, pitch and loop
    sound.setVolume(volume);
    sound.setPitch(pitch);
    sound.setLoop(false);
    // If sound duration is more than current max time
    if (buffer.getDuration() > m_timeSEMax) {
        // Restart timer and set time max duration
        m_timeSE.restart();
        m_timeSEMax = (buffer.getDuration());
        // Give the buffers some extra time by doubling it
        m_timeSEMax += (buffer.getDuration());
    }
    // Add to vectors
    m_SoundV.push_back(sound);
    m_BufferV.push_back(buffer);
    // Set the last buffer on the stack
    m_SoundV[m_SoundV.size() - 1].setBuffer(m_BufferV[m_BufferV.size() - 1]);
    // Play the sound
    m_SoundV[m_SoundV.size() - 1].play();
    cout << "Playing SE : " << m_SoundV.size() << endl;
    // Opened file successfully
    return true;
}
void Audio::updateBuffers() {
    // End function if no buffers
    if (m_BufferV.empty()) { return; }
    // Clear buffers if enough time has passed by
    if (m_timeSE.getElapsedTime() >= m_timeSEMax) {
        cout << "Erasing Buffers : " << m_BufferV.size();
        // Clear buffers and restart timer
        m_BufferV.clear();
        m_timeSE.restart();
        cout << " ; Buffer Size " << m_BufferV.size() << endl;
    }
}[code]
« Last Edit: January 22, 2022, 11:19:22 am by Kain Nobel »