Fading sounds with go.animate, kidnaps the sound (SOLVED)

This is just weird. I’m playing a sound and at some point before it ends, the code fades out the gain with go.animate. Afterwards, the sound cannot be played again.

Here is minimal code that I think should work, but doesn’t.


function init(self)
	msg.post(".", "acquire_input_focus")
	msg.post("@render:", "use_fixed_fit_projection", { near = -1, far = 1 })
	
	sound.play("/music#waggs away", {gain=0.3})
	timer.delay(10, false, FadeOut)
end

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed then
		print("Play again!")
		
		sound.stop("/music#waggs away")
	    sound.play("/music#waggs away", {gain=0.3})
	end
end

function FadeOut(self)
    go.animate("/music#waggs away", "gain", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_LINEAR, 6, 0, CleanUpAnimate)
end

function CleanUpAnimate(self)
		go.cancel_animations("/music#waggs away", 'gain')
		print('Click to play sound again.')
end

NOTE: Do not click the mouse until the sound’s first play is fully complete.

Also, notice that when the animate call is made, the sound’s volume jumps louder before fading.

Here’s a zip of the project.

Bed for Tests.zip (2.8 MB)

You’re not setting the gain on the individual voice, but the sound component.

1 Like

How does one address an individual voice?

I tried changing the address to “/music”, like some examples. It seems to work but I got an error:

ERROR:SCRIPT: attempt to concatenate a function value
stack traceback:
[C]: - 1: in function animate
main/main.script:21: in function <main/main.script:20>

In this example, it talks about setting the component’s gain. I’m so confused.

1 Like

I was just as puzzled until I read the section about gain here:

3 Likes

Yeah, we have no way of setting the individual voice properties other than at the ”sound_play()” time.
Think of the component as the parent of the voice. If you alter its properties, it will be used for its children too.

2 Likes

so it looks like the solution is to use go.set to reset the gain for the component.

        go.set("/music#waggs away", "gain', 0.3)
	sound.stop("/music#waggs away")
	 sound.play("/music#waggs away", {gain=0.3})

So, a big part of my confusion is the inconsistency of what a URL means. The same URL can mean two different things in this case. I hope Defold’s workers realize how confusing that can be. I understand, that under the hood, they are always talking about a component, but effectively they cause different results. Because sounds are created like a factory made them. Whereas static components are always individual entities. Context sensitive URLs are causing this problem.

Sounds are not exactly like factories. Sounds are “play and forget”. A call to sound.play() will “create and play an instance of the sound component” (ie a voice). The difference from a factory is that once you have called sound.play() you have no direct control over the individual voice. It will play with the gain that you specified in the call to sound.play().

This means that you can have multiple instances of the same sound playing at the same time. With different gains.

And like you saw from the documentation the final gain of the sound depends not only on the gain of the “play and forget” sound instance but also on the overall gain on the component, the group and the master.

So when you use go.set() or go.animate() you modify the gain of the component, which in turn impacts the gain of any playing sound instances.

To me this is logical and easy to reason about, but perhaps this needs to be clarified further?

2 Likes

That’s the best description of what’s going on that I’ve read so for. Thanks, @britzl. When I first wrote that sentence I used the word ‘similar’ instead of like. Factories and sound objects both spawn instances. That’s what I meant. The evidence for misunderstanding is these two lines of code.

go.set("/music#waggs away", "gain', 0.3)
sound.play("/music#waggs away", {gain=0.3})

The url is the same, but what gets set is different. I misunderstood a url to refer to an instance, which seems pretty easy to do when you’re dealing with other elements of the engine.