Can't figure out how to do Java Audio properly.

The clip class has a loop and a stop method. But after you call stop the clip will get closed automatically so don’t call start or loop on it after you stopped it.
For a machine gun effect you need a delay. You probably have to create another Thread for that. You can communicate with that Thread through a volatile variable.

EDIT:
I updated my AudioData class


public class AudioData {
    private byte[] data;
    private AudioFormat format;
    private volatile boolean machineGunActive = false;
    private HashSet<Clip> clipsPlaying = new HashSet<>();
    
    private synchronized void removeClip(Clip clip) {
        clipsPlaying.remove(clip);
    }
    private synchronized void addClip(Clip clip) {
        clipsPlaying.add(clip);
    }
    private synchronized void stopAllClips() {
        for(Clip clip: clipsPlaying) clip.stop();
        clipsPlaying.clear();
    }
    
    AudioData(String fileName) {
       try {
           AudioInputStream sounds = AudioSystem.getAudioInputStream(AudioData.class.getResource(fileName));
           format = sounds.getFormat();
           int size = sounds.available();
           data = new byte[size];
           sounds.read(data);
           sounds.close();
       } catch(Exception e) {
           e.printStackTrace();
       }
    }
    public Clip createClip() {
        try {
            final Clip clip = AudioSystem.getClip();
            clip.addLineListener(new LineListener() {
             public void update(LineEvent e) {
                 LineEvent.Type type = e.getType();
                 if(type == type.STOP) {
                     clip.close();
                     removeClip(clip);
                 }
             }
            });
            clip.open(format, data, 0, data.length);
            return clip;
        } catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
    public void play() {
        Clip clip = createClip();
        addClip(clip);
        clip.start();
    }
    public void loop(int n) {
        Clip clip = createClip();
        addClip(clip);
        clip.loop(n);
    }
    public void loopForever() {
        Clip clip = createClip();
        addClip(clip);
        clip.loop(Clip.LOOP_CONTINUOUSLY);
    }
    public void stop() {
        machineGunActive = false;
        stopAllClips();
    }
    public void stopMachineGun() {
        machineGunActive = false;
    }
    public void playMachineGun(final int delay) {
        machineGunActive = true;
        new Thread() {
            public void run() {
                while(machineGunActive) {
                    play();
                    try {
                        Thread.sleep(delay);
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

Try this


AudioData data = new AudioData("shoot.wav");
data.playMachineGun(100);
Thread.sleep(2000);
data.stopMachineGun();

The difference between stop() and stopMachineGun() is that stop() will stop playing immediately, while stopMachineGun() allows the sound to fade out naturally.

Why would I need a delay? The sound is played when the fire() method is called on the gun, which already has a cooldown.
Anyway, I don’t think you get what I’m saying. Whereas most weapons in my game have a cooldown before they can be used again, the Flamethrower just fires particles continuously as long as the player holds down the fire button. The problem being that the sound is played when it’s fired, and since it’s being looped, that means dozens, or even hundreds, of the Flamethrower sound clip are being created and looped all at once. I tried making it so it wasn’t looped and just relied on the fire() event from the weapon to play the sound, but there are A LOT of particles coming out of this thing, so it still sounds like a machine gun of noise on crack. So what I’m asking is… how can I make it so the sound only plays if there is no Flamethrower sound currently being played? The play() method doesn’t return the clip that is generated to play the audio, so I can’t use that to determine if it’s running.

I thought of using a boolean, but how would I determine when the last clip generated by the play() method stops so I can generate a new one?

Wait, so you want to play the flamethrower sound only if it’s not playing anymore. Can’t you use loop()? Then it will automatically start playing again once it stopped.
Also, I edited my previous post. Take a look at the new code.

You’re missing the point. I already said that when it was looped, I couldn’t stop it because there is no way to stop the Clip once it has been created and started because that Clip only exists in the scope of the play() method in my sounds class. I would need access to that clip in my framework so I can call the stop() method on it under certain conditions. But simply returning the Clip from the play() method wouldn’t work because it would return to the weapon class. So unless I made a global variable for the Flamethrower Clip that changed whenever it’s played, which I think would be a hacky solution, but a bad idea, I can’t get it to stop when the player lets go of the fire button.

Also, forget the “machine gun”… that weapon is not the issue, and works just fine. It’s the Flamethrower that’s the problem, because there is no delay (or rather, only 20 ms delay) in between creation of each particle, which causes the sound to be played very rapidly, causing the noise in question.

EDIT: Just looked at your modified solution, and that MAY work, but I still feel as if there might be a better option. I’ll try it and get back to you.

If there is only a 20ms delay I guess it’s best to use loop(). And then call stop(), once the fire button has been released. You can do that by either making the flamethrower AudioData object globally accessible or by adding a MouseListener when the fire button is pressed. You can add as many MouseListeners to your game as you want. That listener will then stop the sound and remove itself.

Haha, oh wow… my new solution works perfectly for the Flamethrower, but now no other sounds will play… will I ever get this right?
Here’s the code I have now for the play() method in the AudioData class.


public void play(final double gain, final boolean smoothLoop) {
    final Clip clip = createClip();
    FloatControl gainControl = (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);
    float dB = (float)(Math.log(gain) / Math.log(10.0) * 20.0);
    gainControl.setValue(dB);
    active = true;
    new Thread(new Runnable() {
        @Override
        public void run() {
            if(!smoothLoop) clip.start();
            else clip.loop(Clip.LOOP_CONTINUOUSLY);
            while(active) {
                if(!clip.isRunning() && !smoothLoop) active = false;
            }
            clip.stop();
        }
    }).start();
}

Basically, I had a boolean set on each AudioData called active that determines if the sound is currently playing. It’s set to true right before the clip starts playing. It then starts the clip and goes into a loop saying “if active, check to see if it’s running, and if it’s looped. If it’s no longer active and isn’t looped, set active to false”. This ensures that when a clip is finished with playback, it automatically sets active to false and then stops the clip. The looped parameter is provided by the Sounds class. So basically, for normal clips that aren’t looped, it sets active to true, then starts a thread in which it constantly checks that clip to see if it’s still running. If it’s no longer running, active is set to false, and then the clip stops. But for looped clips, like the Flamethrower, it will never set the active boolean to false, meaning it will continue to allow it to loop, never stopping. I then put in the setActive() method to set it to inactive when the mouse is released (among other conditions). So now the Flamethrower starts and stops like it’s supposed to, but none of the other clips even play.

What am I doing wrong?

EDIT: Nevermind, I just had to check if the clip was looped and only call stop() if it was looped.

But now you have an infinite loop that puts 100% load on one CPU core.
That’s what the LineListener is for. So you can react to the Clip stopping without needing a loop.
And as I already mentioned you can also use a MouseListener/KeyListener to react to buttons being pressed/released.

Also if you need an isRunning() method in the AudioData class you can define it like this


public synchronized boolean isRunning() {
    for(Clip clip: clipsPlaying) {
        if(clip.isRunning()) return true;
    }
    return false;
}

But I don’t think you even need that. AudioData has a stop method. Just call it when the button is released.

I don’t know how you think you get an infinite loop out of that. It’s working fine as it is right now.

If you loop a clip this code will run continuously until you set active to false.

while(active) {
    if(!clip.isRunning() && !smoothLoop) active = false;
}

Start a clip with smoothLoop set to true and then look at the Java program in your task manager. One of your CPU cores will have 100% load on it while the clip is playing.

Actually, I don’t use TinySound but I assume that you can use its JARs in Netbeans. In the project tab, select the “Libraries” node -> “Add JAR/Folder” and select all JARs.

Oh, come on! Just when I thought I was in the clear…

Everything works fine when I run the game through NetBeans, but when I Clean & Build it into a JAR and run it, all the sound in the game is cut short… like it only plays half the sound. What could be causing this in the JAR, but not in NetBeans?

You may find you will have some fun with computers running Linux, too, if computers running that OS matter to you.

I was about to respond and say the OP couldn’t find the JARs as per my previous message. Actually, there is a link to them on the GitHub page - assumed it was source only as GitHub deprecated downloads, but they’re still there at the moment.

http://finnkuusisto.github.com/TinySound/releases

To the OP, seriously, use a 3rd-party library like various people on here are suggesting. The way you’re using Clip may just about work on some operating systems, but where it does it’s not going to perform very well. Clip is just not designed / implemented in a useful way for this. You want a system that opens a line to the soundcard and keeps it open, mixing sounds in software - opening a line to the soundcard is not cheap. And neither are Threads. Some of the code on or linked to here is woefully inept - a Thread per sound, guys, seriously?! :emo:

I understand what you’re saying, but right now, I’d like to know why the sounds are being cut short when I run the JAR as opposed to the NetBeans Run Project button.
I may switch to TinySound after all, but first, I want to know what’s causing this.

Nobody? I really need to know why this is happening.

It has probably something to do with threads. Using threads incorrectly can cause all kinds of weird random errors.

A simple case example might help. This isn’t a common problem, or at least, it isn’t one I’ve come across before, or heard anyone mention before. Usually if there is a problem that arises in the transition from IDE to jar, it involves the way in which the resources are addressed. But that shouldn’t be what is happening in your case since you are able to hear the start of your sounds.

I’ve found working with sound has tried my patience to the extreme. I don’t know that there are many game players that really understand it very well, or even many Java programmers. Most of us are just happy to get something to work.

One exception to this is nsigma. He really knows his stuff, and I recommend paying extra attention to any advice he posts.

Is there a way I could convert it from its current functionality to maybe use a Line Listener like was suggested earlier? I’m not sure if that would correct the problem, but it’s worth a try. My code as it is now is:


public void play(final double gain, final boolean smoothLoop) {
    final Clip clip = createClip();
    FloatControl gainControl = (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);
    float dB = (float)(Math.log(gain) / Math.log(10.0) * 20.0);
    gainControl.setValue(dB);
    new Thread(new Runnable() {
        @Override
        public void run() {
            active = true;
            if(!smoothLoop) clip.start();
            else clip.loop(Clip.LOOP_CONTINUOUSLY);
            while(active) {
                if(!clip.isRunning() && !smoothLoop) active = false;
            }
            if(smoothLoop) clip.stop();
        }
    }).start();
}

Here’a an example of the basic form for adding a LineListener, and testing for an event:


		clip.addLineListener(new LineListener()
		{
			@Override
			public void update(LineEvent arg0)
			{
				if (arg0.getType() == LineEvent.Type.STOP)
				{  
					// do something}
				}
			}
		});

But that begs the question of how a looping clip gets a signal to stop in the first place.

If we are talking about a machine gun with a fixed duration, e.g. short bursts, something like this is fine. (No LineListener needed. No while loop needed.)


	clip.start();  // assume looping, volume, etc. has already been set up.
	Thread.sleep(machineGunDuration);
	clip.stop();  // then maybe reset it to the starting frame so you can reuse it

Sometimes I break the Thread.sleep() into smaller increments and check a state variable before continuing to the next sleep, to make the duration more granular. The state variable should be volatile, of course, and will only be as accurate as the limited real time guarantees of the JVM/OS.


	clip.start();
	for (int i = 0; i = 10; i++)
	{
		Thread.sleep(oneTenthMachineGunDuration);
		if (stopClip) break;
	}
	clip.stop();

Am just free-typing this. So, hopefully I didn’t facepalm anything.

Even the 2nd example above uses vastly less cpu than leaving the thread spinning. I’ve only seen spinning threads in “spin locks” where the anticipated duration was very short.

Couple of suggestions/observations which may help in other regards:

  • It is kind of unusual to make a new Clip and play it with every play. Usually one restarts a given clip. If you are doing a single playback, a SourceDataLine starts quicker, so as not to incur the repeated i/o loading. Maybe you are doing this as a quick/dirty way to allow concurrent sounds?

  • Not every OS will support MASTER_GAIN, just a heads up.

  • Not every OS will support multiple outputs (for sure some Linux won’t), hence the recommendation for TinySound which mixes all sounds into a single output line. Of course, to get TinySound, we are back at the annoying task of figuring out how to use GitHub. But since Git is such a common tool for sharing code, the “side trip” is worth it, imho.

Ok, can I just ask how the idea got started that I was talking about the machine gun? The machine gun is not looped, and the sound is played every time the gun is fired… the flamethrower is what’s looped. That being said, it doesn’t seem like anyone has a solution for this, so I’m gonna try my hand at TinySound.