Sound sources don't update when they're out out of sight


photo

Recommended Posts

Hi all,

We are running into an issue where apparently sound sources that are further away than maxDistance and out of the viewport are not updated. You wouldn't hear them anyway so that is not an issue, but because they are not updated they also never finish playing. We are triggering a lot of repeated sfx that each get their own soundsource which is deleted when the sound is finished playing, but calling isPlaying() on these sound sources always returns true until you look in their direction or get close enough for them to become audible. The amount of soundsources in the world can become really high in a long session because of this. Is there anything we can do to prevent this?

Thanks in advance

Link to post
  • 1 year later...

Hi,

I'm trying to update my colleagues code to the new Unigine version, but the addUpdateNode function has been removed in 2.11. The migration guide suggests the setUpdateDistanceLimit function as replacement, but this function is only available for meshes, particles and expressions it seems, not for sounds. Is there an alternative way to ensure sounds update correctly when out of range?

A good way to test this is to play an explosion sound that takes 5 seconds, once it starts to play move it out of range and after 5 seconds teleport it back. Expected result is that the explosion is now over, but instead I hear the remaining of the sound effect being played. The script I used:

SoundSource boom;
float boomTime = 0.f;

int init() {
    boom = new SoundSource("Explosion.wav", 0);
    boom.setMaxDistance(50.f);
    boom.setPosition(dvec3(0., 0., 0.));
    boom.play();
    return 1;
}

int update() {
    boomTime += engine.game.getIFps();
    if (boomTime > 5.f) {
        boom.setPosition(dvec3(0., 0., 0.));
    }
    else if (boomTime > 1.f) {
        boom.setPosition(dvec3(0., 0., 100.));
    }	
    return 1;
}

 

Link to post

Bemined

It looks like setUpdateDistanceLimit() is not implemented for sound objects. The correct solution would be to use it.

Some additional time is required to understand if we can include it in 2.13 SDK update or not.

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to post
  • 3 weeks later...

Is it known already whether this is included in 2.13? And meanwhile is there a possible workaround for 2.11 / 2.12?

Link to post

AFAIK, there is no any practical workaround available at this moment.

A new feature to control the sound update distance is planned, but it may be delayed until 2.13.1 or some initial maintenance update.

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to post
  • 1 month later...

We decided to not modify the current API and only changed the internal behavior of how sounds are calculating their update time.

So starting with 2.13 your code will work as expected with no additional changes required.

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to post
  • 4 weeks later...

I tested the sound behaviour in 2.13 and indeed the sound resumes correctly when becoming back in range now.  However, isPlaying() does not seem to update when the sound is out of range, it returns true long after the sound sample should have been finished and only starts to return false when the sound is back in range.

So our logic that deletes sound sources when they are done still does not work. Is there another approach to achieve this?  Basically what we want is to limit the lifetime of the SoundSource to the duration of the sample. We cannot predefine slots and reuse them since the number and location of the sound can be dynamic. Also we need to keep a reference to the sound while it's playing to be able to adjust parameters dynamically. So basically, we want to delete the SoundSource when it finishes, and for our code to be aware of this so we can remove the pointer to the sound from the vector of active sounds.

Link to post

I used the script I posted above to test after a small change:

SoundSource boom;
float boomTime = 0.f;

int init() {
    boom = new SoundSource("Explosion.wav", 0);
    boom.setMaxDistance(50.f);
    boom.setPosition(dvec3(0., 0., 0.));
    boom.play();
    return 1;
}

int update() {
    boomTime += engine.game.getIFps();
	if (boom is SoundSource) {
		if (boomTime > 10.f) {
			boom.setPosition(dvec3(0., 0., 0.));
		}
		else if (boomTime > 1.f) {
			boom.setPosition(dvec3(0., 0., 100.));
		}
		if (!boom.isPlaying()) {
			log.message("Deleting boom after %f seconds\n", boomTime);
			delete boom;
			boom = NULL;
		}
	}
    return 1;
}

Explosion.wav is a sound sample of about 5 seconds long, but when I run this the log message appears after 10 seconds, after the sound source is teleported back in range.

Edited by Bemined
Link to post

Hi Bemined,

However, isPlaying() does not seem to update when the sound is out of range, it returns true long after the sound sample should have been finished and only starts to return false when the sound is back in range.
Yes, you are right. For performance reasons, sound nodes aren't updated when they are "invisible". We can only skip the part that was not listened when the node comes back to a "visible" range.
The problem is that the current state of the SoundSource comes from the OpenAL. Sound must be played to get up-to-date data.
Maybe in your case it would be better to increase the maxDistance of sound sources. Or, for example, delete sound objects after "duration" seconds.

Best regards,
Alexander

Link to post
  • 1 month later...

Ok, I successfully implemented some custom logic now to delete the sounds after the length of the sound, and together with the fix in 2.13 the sounds appear to work fine now. However it's not really an ideal solution, we had to add code for this at five places since we have multiple use cases for sounds that only play one time. And there are probably many more places we would like to add sound effects to but have not yet done so because it's a lot of work right now.

Would it be possible to have something like setDeleteOnFinish(true) in a future version of Unigine? It would make working with sound effects a lot easier. I guess the same also applies to particles, we have some particles now like a smoke grenade that runs for a limited time only. Once it runs out of fuel the emitter will be disabled, but we can't delete the emitter yet since particles that were spawned before turning off the emitter should remain until the end of their life time. So like sounds this requires some custom update function to delete the node when all particles are exhausted.

Link to post

Do you currently have any issues with keeping sounds always in RAM? How many memory sound samples are currently using?

Maybe be it would be easier to always keep them (if they consume reasonable amount of memory - like 1-5%) or there is a specific use case when you need to delete them and create over and over again?

Thanks!

 

How to submit a good bug report
---
FTP server for test scenes and user uploads:

Link to post

Most of these sounds are one-shot sounds triggered upon a certain event. For example a ship sailing through the ocean plays sound from impacts with the waves. Impacts like this can happen quite often, so after a minute you might already have hundreds of sounds. Also, often a new sound will have to be started before the previous sound finished playing, so we cannot use a single SoundSource attached to the ship that is reused each time an impact occurs.

Link to post

Hello Bemined

you can write you own controller for soundSource.

something like whis

class SoundController
{
public:
	static SoundController *get() // its singleton
	{
		static SoundController instance;
		return &instance;
	}

	void playOnce(const SoundSourcePtr &sound)
	{
		if (!sound)
			return;
		auto &s = sounds[sound->getID()];
		s.sound = sound;
		s.time = 0.0f;
		s.len = sound->getLength();
		s.end = false;
		sound->play();
	}

	bool isPlaying(const SoundSourcePtr &sound)
	{
		if (!sound || sound.isDeleted())
			return false;
		auto &s = sounds[sound->getID()];
		return (s.sound && s.end && s.time < s.len);
	}

	void clear()
	{
		for (auto &it : sounds)
		{
			if (!it.data.sound || it.data.sound.isDeleted())
			{
				sounds.remove(it.key);
				continue;
			} else
			{
				it.data.sound.deleteLater();
				sounds.remove(it.key);
				continue;
			}
		}
	}

private:
	SoundController()
	{
      // add own world logic for automatic updates
		world_logic = new SoundWorldLogic();
		Engine::get()->addWorldLogic(world_logic);
	};

	class SoundWorldLogic: public WorldLogic
	{
		virtual int update() override
		{
			SoundController::get()->update();
			return 1;
		}
		virtual int shutdown() override
		{
			SoundController::get()->clear();
			return 1;
		}
	};

	void update()
	{
		float ifps = Game::getIFps();

		for (auto &it : sounds)
		{
			if (!it.data.sound || it.data.sound.isDeleted())
			{
				sounds.remove(it.key);
				continue;
			}
			if (it.data.end)
			{
				it.data.sound.deleteLater();
				sounds.remove(it.key);
				continue;
			}
			it.data.time += ifps;
			if (it.data.time >= it.data.len)
			{
				it.data.end = true;
			}
		}
	}

	struct SoundStruct
	{
		SoundSourcePtr sound;
		float time = 0.0f;
		float len = 0.0f;
		bool end = true;
	};
	Map<int, SoundStruct> sounds;
	SoundWorldLogic *world_logic = nullptr;
};

int AppWorldLogic::init()
{
	SoundSourcePtr ss = SoundSource::create("sound.wav", 0); // create sound
	ss->setMaxDistance(50.f);
	ss->setPosition(dvec3(0., 0., 0.));
	// ... some addtitional settings ...

	SoundController::get()->playOnce(ss); // ask SoundController to play and check this sound


	return 1;
}

 

This is just a prototype for an example - there may be errors and bugs, and its not better solution. The best way is to create your own pool of sounds:
do not constantly create new sounds, but take from already created sounds that are in a state of expectation. This will help optimize memory and speed. 

Link to post