JavaFX audio not working properly?

So, I’m currently using a system that implements JavaFX into my game. The problem is: I’m wanting to repeatedly play the same sound effect multiple times. I’m making a Brick Breaker game, and I’m replaying the constant sound effect whenever a brick is hit. And, if you’ve ever played Brick Breaker, the ball can become stuck at the top and hit a ton of bricks sequentially, thus causing a ton of sound effects. Well, when I try to implement this with one MediaPlayer OR AudioClip JavaFX object, it only plays it once, and unless a slight amount of time goes by between bricks, it doesn’t play it again. My only work around is by, every time a brick is hit by the ball, create a NEW Audio object, then immediately play it. This WORKS, though, it is incredibly inefficient and slows my game down tremendously, and I have a powerful PC. Can anyone help me with this endeavor?

 
package com.joshuacrotts.pt.main;

import java.util.ArrayList;

import com.joshuacrotts.standards.StandardAudio;

public class SongBox {
	
	public static ArrayList<StandardAudio> songs;
	public static ArrayList<StandardAudio> sfx;
	
	public SongBox(){
		
		SongBox.songs = new ArrayList<StandardAudio>();
		SongBox.songs.add(new StandardAudio("Resources/Audio/Music/menu.wav",false)); //Menu music
		SongBox.songs.add(new StandardAudio("Resources/Audio/Music/level1.wav",false));//Level 1 music
		SongBox.songs.add(new StandardAudio("Resources/Audio/Music/level2.wav",false));//Level 2 music
		
		SongBox.sfx = new ArrayList<StandardAudio>();
		SongBox.sfx.add(new StandardAudio("Resources/Audio/SFX/menuselect.wav",true));//Clicking on a button
		SongBox.sfx.add(new StandardAudio("Resources/Audio/SFX/explode_1.wav",true));//Breaking a brick
		SongBox.sfx.add(new StandardAudio("Resources/Audio/SFX/whoosh.wav",true));//Getting an item
		
	//	resetVolumes();
	}
	
	public void resetVolumes(){
		for(int i = 0; i<songs.size(); i++){
			songs.get(i).resetVolume();
		}
		
		for(int i = 0; i<sfx.size(); i++){
			sfx.get(i).resetVolume();
		}
	}
	
	/**
	 * This method will add a sound to the arraylists.
	 * @param audio
	 * @param sfx
	 * 
	 * This is mainly so I can have more than one sound effect playing at once.
         * THIS IS THE METHOD IM REFERENCING IN THE FORUM POST ****************
	 */
	public static void addSound(String audio, boolean sfx){
		if(!sfx){
			SongBox.songs.add(new StandardAudio(audio,true));
			SongBox.songs.get(SongBox.songs.size()-1).play();
		}else{
			SongBox.sfx.add(new StandardAudio(audio,true));
			SongBox.sfx.get(SongBox.sfx.size()-1).play();
		}
	}
	public static void clearSFX(){
		for(int i = 1; i<SongBox.sfx.size(); i++){
			SongBox.sfx.remove(i);
			i--;
		}
	}
}

//Below is my Audio (StandardAudio) class:

package com.joshuacrotts.standards;

import java.io.File;

import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
/**
 * 
 * Takes an audio source in and plays it. 
 * 
 */
public class StandardAudio{

	private String fileName;
	//private Media media;
	private AudioClip sound;
	private boolean sfx;
	
	public StandardAudio(String fileName, boolean sfx)
	{
		this.sfx = sfx;
		new javafx.embed.swing.JFXPanel();
		
		try{
			this.sound = new AudioClip(new File(fileName).toURI().toString());
			//this.sound = new MediaPlayer(this.media);
			
		}catch(Exception e)
		{
			e.printStackTrace();
		}
		
		this.sound.setVolume(1);
	}
	
	public void play()
	{
		if(this.sound.isPlaying())
			return;
		else
			this.sound.play();
		if(!sfx)
			this.loop();
	}
	
	
	public void adjustVolume(double val) {
		this.sound.setVolume(this.sound.getVolume() + val);
	}
	
	public double getVolume(){
		return this.sound.getVolume();
	}
	
	public void fadeToBlack() {
		
		double val = -0.05D;
		this.adjustVolume(val);
		if(this.getVolume() <= 0) {
			this.sound.stop();
			this.sound.setVolume(1);
		}
	}
	
	public void stop()
	{
		this.sound.stop();
		//audioClip.close();
	}
	public String getFileName()
	{
		return fileName;
	}
	
	/**
	 * @param x determines if the loop is infinite. If it's 1, it is. Else, it's not.
	 */
	public void loop(){
		this.sound.setCycleCount(MediaPlayer.INDEFINITE);
	}
	
	public void resetVolume(){
		this.sound.setVolume(1);
	}
}


That’s true, I am experiencing that, too. Even though the JavaDocs of JavaFX’s AudioClip class says that it is capable of being played multiple times simultaneously, that does not seem to be entirely true. Trying to play a single very short AudioClip at intervals of around 100ms gets me a very disturbing “pattern” which also shifts/changes over time.

People usually work around something like this by creating a pre-allocated pool and obtaining ready instances off that pool.
In the case of AudioClip, this could look like so:


private static final int NUM_BUFFERS = 5;

/**
 * Pool of AudioClips for a specific sound name, each.
 */
private Map<String, List<AudioClip>> clips = new HashMap<String, List<AudioClip>>();

/**
 * Create a pool of a number of AudioClips for the sound with the given name.
 */
private void init(String name) {
	List<AudioClip> cs = new ArrayList<AudioClip>();
	clips.put(name, cs);
	for (int i = 0; i < NUM_BUFFERS; i++) {
		AudioClip clip = new AudioClip(convertNameToUrlSomewhow(name));
		cs.add(clip);
	}
}

/**
 * Obtain a ready AudioClip from the pool for the sound with the given name.
 * @return <code>null</code> if no AudioClip in the pool is ready yet
 */
private AudioClip get(String name) {
	List<AudioClip> cs = clips.get(name);
	for (AudioClip clip : cs) {
		if (!clip.isPlaying()) {
			/* Even though the clip is not playing anymore, we have to stop it
			 * otherwise it wouldn't play right from the start. */
			clip.stop();
			return clip;
		}
	}
	/* No AudioClip ready yet! */
	return null;
}

Everytime you want to play a sound with a given name, you just call get(name). This of course only works for very short sounds since in this scheme an AudioClip can only be reused once it is done playing. So the number of pool instances you need depends on the length of the audio track divided by the shortest interval between two successive plays.

I’ve not used AudioClip yet, myself. I’m puzzled about a couple of things.

For starters, the following from the API seems contradictory.

[quote]Playback behavior is fire and forget: once one of the play methods is called the only operable control is stop(). An AudioClip may also be played multiple times simultaneously.
[/quote]
How do you play an AudioClip concurrently if the only operable method after a play is stop()? Maybe the intention was to say each play() is fire and forget?

Secondly, why test for isPlaying() in your code before executing Play()? Do you get an illegal state exception if you call play and it is already playing? There’s no illegal state mentioned in the API. Even if the AudioClip does allow concurrent playback, then this test will definitely prevent it. (And if there is silence at the end of the cue, the point where it actually finishes may be beyond the point where the cue goes silent.)

I’d try losing this test before getting into creating a pool of AudioClips. The pool should not be needed if the second line quoted above was written in plain English. (Pools can help if working with javax.audio.sampled.Clip, as they don’t allow concurrent playback.)

Another approach is to stop and restart with each play. Then, when the plays are coming faster than the cue can end, the cue will stop without finishing and restart immediately. This can work reasonably well in a lot of circumstances.

In other words:

    public void play()
    {
       this.sound.stop();  // no need to test isPlaying()
       this.sound.play();
    }

Else, if concurrent playback works, seems like simply this should work:

    public void play()
    {
        this.sound.play();
    }

The only hitch here is if the distance between the play calls is only a couple millis, the two playbacks could cause artifacts resulting from comb-filtering.

Well, this comment is amusing - wonder if that’s still the up-to-date source?! ::slight_smile: Good to hear AudioClip in JavaFX is just as broken as the JavaSound one then.