Is it possible to get sound events? for example a looping sound is end and start again?

is it possible to get sound events? for example a looping sound is end and start again?

1 Like

With an AudioNode, you can specify looping using the AudioNode.setLooping(true);

1 Like

no, I need to run a method when a sound ends.

1 Like

You need to make onStop() & onPlay() listeners , there’s option of using AudioBuffers.getDuration() in an ASync thread & listen for thread end time ie when it finishes

-Check AudioBuffer class In jme javaDocs

EDIT: If you are on desktop , then use javax ActionListener or Runnable interface & specify your onStopListener() inside , then schedule this ActionListener using Timer javax class & not Timer javaUtil by the duration of the effect your playing using AudioBuffer

If you are on Android , there are multiple options using CountDownTimer , or a Service , or a FutureTask

NOTE : If you are looping the song , & wanna run a method each time the effect starts & stops, this may not work for you , think of something else :slightly_smiling_face:

1 Like

Hi @yn97 , if you still searching around , this is my code for it , please try it , it works on my container :smiley:

package main_WorldMap;

import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.scene.Node;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

/**
 *
 * @author Pavly G.
 */
public class SoundEffects {
    
    private final AudioNode audioNode;
    private Node rootNode;
    public OnAudioStopsListener onAudioStopsListener;
    private Timer timer;
    private int orderOfEffect=0;
    private ActionListener ac=new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent arg0) {
            /*your listener , NB: donot initialize that Object because this is an interface ,
            it would be better to be handled with setOnAudioStopsListener in your own source */
            onAudioStopsListener.onStops(++orderOfEffect,audioNode);
            /*stop the timer when it finishes playing the effect*/
            timer.stop();
            /*do this only if you are looping the song */
            timer.start();
            
        }
    };
    
    /**
     * Creates anaudioNode & attach it to the rootNode
     * @param assetManager the game assetManager
     * @param rootNode the game rootNode
     * @param effect name of the sound effect that you would like to play(the effect must be in /assets/Sounds/effects/)
     * @param Looping specify whether you need to loop it or not 
     */
    public SoundEffects(AssetManager assetManager,Node rootNode,String effect,boolean Looping){
        this.rootNode=rootNode;
        audioNode=new AudioNode(assetManager, effect, AudioData.DataType.Stream);
        audioNode.setPositional(false);
        rootNode.attachChild(audioNode);
        audioNode.setLooping(Looping);
    }
    /**
     * Plays the auidoNode
     * @return boolean specifies whether the audioNode is playing or not  
     */
    public boolean play(){
        try{
            audioNode.play();
            return true;
        }catch(Exception ex){
            System.err.println(ex.getMessage());
            return false;
        }
    }
    /**
     * Stops the auidoNode 
     * @return  boolean specifies whether the audioNode stops or not
     */
    public boolean stop(){
        try{
            audioNode.stop();
            return true;
        }catch(Exception ex){
            System.err.println(ex.getMessage());
            return false;
        }
    }
    /**
     * Stops the audioNode & removes it from the rootNode
     * @return boolean specifies whether the audioNode stops & removed from rootNode or not
     */
    public boolean removeEffect(){
       try{ 
        audioNode.stop();
        audioNode.removeFromParent();
        return true;
       }catch(NullPointerException ex){
           System.err.println(ex.getMessage());
           return false;
       }
    }
    /**
     * Re-attach the audioNode to the rootNode w/o recreating the Node
     * @return true if attach is successful - false if not 
     */
    public boolean reAttach(){
       try{
          rootNode.attachChild(audioNode);
           return true;
        }catch(Exception e){
           System.err.println(e.getMessage());
           return false;
        }
    }
    /**
     * starting looping the effect . with Events attached
     */
    public void startLooping(){
        audioNode.setLooping(true);
        rootNode.attachChild(audioNode);
        audioNode.play();
        /*notice getDuration(); is in seconds , but timer accepts time in ms */
        timer=new Timer((int) (audioNode.getAudioData().getDuration()*1000), ac);
        timer.start();
    }
    public interface OnAudioStopsListener{
        /*Listener method
         OrderOfEffect is the order of loop turn
         auidoNode is your auidoNode
        */
        void onStops(int orderOfEffect,AudioNode audioNode);
    }
    public void setOnAudioStopsListener(OnAudioStopsListener onAudioStopsListener){
        this.onAudioStopsListener=onAudioStopsListener;
    }
}

EDIT: notice that orderOfEffect Var is the number of the loop turns , ie , the first run of the song or the third or the forth , each run this number is incremented

To call that in your container , do this:

    /**
         * Handling Ambient Music
         */
        SoundEffects ambientMusic=new SoundEffects(assetManager, rootNode, "Sounds/Doug Maxwell - Light Years Away [Ambient](MP3_160K).wav", true);
        ambientMusic.setOnAudioStopsListener(new SoundEffects.OnAudioStopsListener() {
            @Override
            public void onStops(int orderOfEffect, AudioNode audioNode) {
                System.err.println("Stopped "+orderOfEffect);
            }
        });
        ambientMusic.startLooping();

please do tell me if this helps :slight_smile:

1 Like

This is just an awful class. Especially in a system that’s already polling every frame. It brings an extra thread into things where it doesn’t belong, it’s not particularly accurate, etc…

It’s not clear what OP wants to use this for but even polling would be more accurate than the timer thread.

Either add a control that accumulates time from tpf and does something (basically the cheaper non-multithreaded equivalent of the Timer) or have a control that watches for when time returns to the beginning.

But in 23 or so years of Java development, I’ve never seen an appropriate “in the wild” use-case for javax.swing.Timer that wasn’t about writing a custom Swing control. And even then it’s questionable.

2 Likes

Okay :ok_hand:

I was thinking of this but ,There is something that is a little bit confusing about it , is that effects are independent of game frames , because game frames vary from cpu to another , I mean that update(tpf) updates for each new frame not for each new second in the song , so can you please discuss in more details how to do this ?

Yes, but javax.swing.Timer is going to be no more accurate. It’s sleeping on another thread basically and there is no telling exactly when it will wake up.

Without knowing why OP needs this information, it’s impossible to say for sure… but USUALLY it’s to trigger some VISUAL effect or some GAME effect… both of which are slaves to update(tpf) already.

1 Like

What about ExecutorService , CompletableTask & FutureTask ?

These are no more accurate then sleep, really. If the CPUs are very busy, you are at the whim of the scheduler.

And again we come back to what would you use this event for?

1 Like

I think if there’s a function effect.getCurrentPosition(); you can do this with a control or an app state otherwise not !

I’ve been folks personal javadoc google too many times already today. I’ll let someone else take a look for the method name.

1 Like