[Solved] AudioNode fade effect

Hello everyone,
I have (again) a question: I’d like to have dynamic music in my game,
so when you come near an enemy, it should play fight music and stop the normal music.
I’d know how to do this, but I’d just sound completely ugly. So, is there any way to have an audio node fade out or in over a specific time?
Thank you in advance for answers.

Well I used to do this with with setVolume() and it worked as perfectly as it could. I don’t know if there’s any other way.

How about attaching a Control which simply controls the volume?
For example this:

final float volPerSec = 0.5f; // 2 Seconds for a complete fade out
public void controlUpdate(float tpf) {
AudioNode n = (AudioNode)spatial; // You should do some checking :p
float vol = n.getVolume() - volPerSec * tpf; // Similar to your "Walking Skill", hence the tpf. VolPerSec is the velocity
if (vol < 0) {
 n.setVolume(0);
 n.detachControl(this); // Don't need it anymore
} else {
 n.setVolume(vol);
}

This is ofcourse pseudo code, but if you’d use the same for an “FadeInControl” (but adding this time), you could make one Node FadeIn and one FadeOut

1 Like

Done.
How about this:

package Audio.Volume;

import com.jme3.audio.AudioNode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 *
 * @author Robbi Blechdose
 *
 */
public class VolumeControl extends AbstractControl implements Savable, Cloneable
{
    private float amountToFadePerSec;
    private boolean isFadeOut;
    
    public VolumeControl() {}
    
    public VolumeControl(boolean isFadeOut, float amountToFadePerSec)
    {
        this.amountToFadePerSec = amountToFadePerSec;
        this.isFadeOut = isFadeOut;
    }
    
    @Override
    public Control cloneForSpatial(Spatial spatial)
    {
        return super.cloneForSpatial(spatial);
    }

    @Override
    public void setSpatial(Spatial spatial)
    {
        super.setSpatial(spatial);
    }

    @Override
    public void controlUpdate(float tpf)
    {
        if(isFadeOut)
        {
            float vol = ((AudioNode)spatial).getVolume();
            
            if(vol > 0)
            {
                ((AudioNode)spatial).setVolume(vol - (amountToFadePerSec * tpf));
            }
            else
            {
                vol = 0;
                ((AudioNode)spatial).removeControl(this);
            }
        }
        else
        {
            float vol = ((AudioNode)spatial).getVolume();
            
            if(vol < 0.2)
            {
                ((AudioNode)spatial).setVolume(vol + (amountToFadePerSec * tpf));
            }
            else
            {
                vol = 0.2f;
                ((AudioNode)spatial).removeControl(this);
            }
        }
    }

    @Override
    public void write(JmeExporter ex) throws IOException
    {
        super.write(ex);
    }

    @Override
    public void read(JmeImporter im) throws IOException
    {
        super.read(im);
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {}
}

EDIT: Fixed something with fade in :smiley:

Hm well that’s one way of doing it. How about leaving a control permanently attached and then just make a goToVolume(float vol) method?

Could do that.
But I’m fine with this solution right now.

  1. You can move float vol out of the if(isFadeOut)
  2. There is a reason I moved the subtraction out into another var. What if you call setVolume(negative). Maybe it crashes, maybe it does clamp it to 0, who knows.

Also in Fade out, you don’t really set the Volume to zero, only to the latest value > 0. Also vol=0; is unneccesary, replace that with setVolume(0);
This also applies to FadeIn.

The next is: Make “AudioNode audio” a private field in your control. Then Override the setSpatial() method, in which you once do:

if (spatial instanceof AudioNode) {
audio = (AudioNode)spatial;
} else {
 System.err.println("Can't attach this control to a non-AudioNode");
 spatial.removeControl(this); // If it doesn't work, then simply check if audio == null in update.
}
1 Like

Right.
I’m so dumb. :smiley:
Here we go, new version, this time with my audio volume setting multiplier:

package Audio.Volume;

import com.jme3.audio.AudioNode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 *
 * @author Robbi Blechdose
 *
 */
public class VolumeControl extends AbstractControl implements Savable, Cloneable
{
    private float amountToFadePerSec;
    private boolean isFadeOut;
    private float volumeMultiplier;
    
    public VolumeControl() {}
    
    public VolumeControl(boolean isFadeOut, float amountToFadePerSec, float volumeMultiplier)
    {
        this.amountToFadePerSec = amountToFadePerSec;
        this.isFadeOut = isFadeOut;
        this.volumeMultiplier = volumeMultiplier;
    }
    
    @Override
    public Control cloneForSpatial(Spatial spatial)
    {
        return super.cloneForSpatial(spatial);
    }

    @Override
    public void setSpatial(Spatial spatial)
    {
        super.setSpatial(spatial);
    }

    @Override
    public void controlUpdate(float tpf)
    {
        float vol = ((AudioNode)spatial).getVolume();
        
        if(isFadeOut)
        {
            float volToApply = vol - (amountToFadePerSec * volumeMultiplier * tpf);
            
            if(volToApply > 0)
            {
                ((AudioNode)spatial).setVolume(volToApply);
            }
            else
            {
                ((AudioNode)spatial).setVolume(0);
                ((AudioNode)spatial).removeControl(this);
            }
        }
        else
        {
            float volToApply = vol + (amountToFadePerSec * volumeMultiplier * tpf);
            
            if(volToApply <= (0.2 * volumeMultiplier))
            {
                ((AudioNode)spatial).setVolume(volToApply);
            }
            else
            {
                ((AudioNode)spatial).setVolume(0.2f * volumeMultiplier);
                ((AudioNode)spatial).removeControl(this);
            }
        }
    }

    @Override
    public void write(JmeExporter ex) throws IOException
    {
        super.write(ex);
    }

    @Override
    public void read(JmeImporter im) throws IOException
    {
        super.read(im);
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {}
}

I don’t get the use of volumeMultiplier? Should this be the setting for the maximum music volume? If so it should replace 0.2 and be removed from the FadeOut.
If not, I have no clue what you plan to do which couldn’t be done by changing amountToFadePerSec :smiley:

Also you still forgot the setSpatial cast audio node thing :smiley:

The thing is,
the music is far too loud by default, so I just add the 0.2 the volumeMultiplier is indeed the value I can set in the main menu using a slider.
And yes, I’ll add the cast thing. :smiley:

1 Like