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

This is driving me insane. I have asked on Reddit and StackOverflow, and besides the fact that everyone is quick to jump down my throat about supposedly not “searching beforehand” (which I have, by the way… extensively), nobody has given me an answer.

The problem being, that I cannot seem to make the Clip class in Java’s sampled sound package work the way I need it to. So I either need someone to look at the code for my Sounds class and tell me what fatal problem is causing my game to freeze up when I try to play the same sound multiple times simultaneously, or refer me to another library/package I can use.

So anyway, I’m trying to make use of the Clip class. (http://docs.oracle.com/javase/7/docs/api/javax/sound/sampled/Clip.html)
I’m pre-loading the various sounds into an enumerator class and calling them statically as needed (ie: Sounds.WHATEVER.play()), but once I added sounds that would be played several times at once, simultaneously, the game started freezing up. I can only assume that the Clip class is not capable of doing this, but just in case I’m just doing something wrong… can someone look at my Sounds class and tell me if that is the case, or if I’m just doing something wrong?


/**
    This file is part of Generic Zombie Shooter.

    Generic Zombie Shooter is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Generic Zombie Shooter is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Generic Zombie Shooter.  If not, see <http://www.gnu.org/licenses/>.
 **/
package genericzombieshooter.misc;

import java.io.IOException;
import java.net.URL;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * Contains all pre-loaded sounds.
 * @author Darin Beaudreau
 */
public enum Sounds {
    // Weapon-Related
    POPGUN("shoot2.wav", false),
    RTPS("shoot1.wav", false),
    BOOMSTICK("shotgun1.wav", false),
    FLAMETHROWER("flamethrower.wav", true),
    THROW("throw2.wav", false),
    EXPLOSION("explosion2.wav", false),
    LANDMINE_ARMED("landmine_armed.wav", false),
    TELEPORT("teleport.wav", false),
    
    // Zombie-Related
    MOAN1("zombie_moan_01.wav", false),
    MOAN2("zombie_moan_02.wav", false),
    MOAN3("zombie_moan_03.wav", false),
    MOAN4("zombie_moan_04.wav", false),
    MOAN5("zombie_moan_05.wav", false),
    MOAN6("zombie_moan_06.wav", false),
    MOAN7("zombie_moan_07.wav", false),
    MOAN8("zombie_moan_08.wav", false),
    POISONCLOUD("poison_cloud.wav", false),
    
    // Game Sounds
    POWERUP("powerup.wav", false),
    PURCHASEWEAPON("purchase_weapon.wav", false),
    BUYAMMO("buy_ammo2.wav", false),
    POINTBUY("point_buy.wav", false),
    PAUSE("pause.wav", false),
    UNPAUSE("unpause.wav", false);
    
    private Clip clip;
    private boolean looped;

    Sounds(String filename, boolean loop) {
        openClip(filename, loop);
    }

    private synchronized void openClip(String filename, boolean loop) {
        try {
            URL audioFile = Sounds.class.getResource("/resources/sounds/" + filename);

            AudioInputStream audio = AudioSystem.getAudioInputStream(audioFile);
            AudioFormat format = audio.getFormat();
            DataLine.Info info = new DataLine.Info(Clip.class, format);
            clip = (Clip) AudioSystem.getLine(info);

            clip.open(audio);
        } catch (UnsupportedAudioFileException uae) {
            System.out.println(uae);
        } catch (IOException ioe) {
            System.out.println(ioe);
        } catch (LineUnavailableException lue) {
            System.out.println(lue);
        }
        looped = loop;
    }

    public synchronized void play() {
        play(1.0);
    }
    
    public synchronized void play(final double gain) {
        Runnable soundPlay = new Runnable() {
            @Override
            public void run() {
                Clip clipCopy = (Clip)clip;
                FloatControl gainControl = (FloatControl)clipCopy.getControl(FloatControl.Type.MASTER_GAIN);
                float dB = (float)(Math.log(gain) / Math.log(10.0) * 20.0);
                gainControl.setValue(dB);
                if(!looped) reset(clipCopy);
                clipCopy.loop((looped)?Clip.LOOP_CONTINUOUSLY:0);
                
            }
        };
        new Thread(soundPlay).start();
    }
    
    public synchronized void reset() {
        reset(clip);
    }
    
    public synchronized void reset(Clip clipCopy) {
        clipCopy.setFramePosition(0);
    }

    public static void init() {
        values();
    }
}

If I need to use a different package or library, I will, but I really want to stick to core Java, at least for this project.

So, is there some fatal error with the way I’m handling this? Or is the Clip class just not capable of playing sound this way?
I can provide information on how I’m playing the sound if need be, but really, all I’m doing is statically calling the play method for the required sound when that sound is supposed to be played (ie: firing a weapon, zombies moaning, etc). I think the reason this freezing never occurred before is because I was never playing the same sound multiple times simultaneously.

So… can someone spare me the insanity and tell me what it is exactly that I’m doing wrong?

Hi

Maybe try to use another Mixer. If you want to use a sound library, you can try Paul Lamb’s Sound System or TinySound. Personally, I use Paul Lamb’s Sound System with its plugin based on JOAL and my game works very well both with OpenJDK and Oracle Java ;D

I gave TinySound a look and all I could find was a Github page. I downloaded the ZIP of the project, but I can’t figure out how to include it in my project in NetBeans and use the library. What do I do with the ZIP? I’ve tried including it as a library, adding it to the project, etc… but I can’t import anything from the kuusisto package, nor can NetBeans find the TinySound class.

Why do you create a Thread? Clips always play in the background, you don’t need Threads for that.
The easiest way to play audio clips is this.

public static void play(String name) {
   try{
     AudioInputStream sounds = AudioSystem.getAudioInputStream(Sounds.class.getResource(name));
     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();
         }
     });
     clip.open(sounds);
     clip.start();
   } catch(Exception e){
       e.printStackTrace();
   }
}

If you want to play two clips at the same time you just call it twice.
e.g.


play("shoot.wav");
play("explosion.wav");

Except that if I close the sound, I have to reload the data, which isn’t good for sounds that are played a lot. Also, when I said playing two sounds at the same time, I meant the SAME sound, not two different ones. So two calls to the same sound overlapping each other. THAT is why I use threads. Or at least, that’s what some page on Google suggested.

Clip can’t seem to do that, which is why it’s not going to work out.

I don’t think playing a clip in a new thread allows you to play them simultaneously, to play two audio files simultaneously, you’ll need two clips.

That said, it isn’t very difficult to do. You’ll want to cache all open clips with SoftReferences (or WeakReferences, but SoftReferences are more appropriate for caching.) When they’re playing keep a hard references to them, and when they aren’t keep a softreferences (via some caching module) This allows you to very quickly reset an unused to clip if you want to play it again and it’s not currently being played (but it doesn’t solve the problem of playing two clips simultaneously yet.) If the JRE decides it needs more memory and destroys your clip instance, you’ll need to make sure you close it before giving it away to the JRE.

You’ll also want to softreferences their raw data (for clips you want to play simultaneously) that you use to initialize the clip. By doing this, you minimize filesystem IO by caching a copy of the file in memory (with a byte buffer etc) which will allow you to very quickly initialize a new clip instance.

If you use this method, it is then important that you use an uncompressed audio format (like WAV) to minimize the pre-processing overhead induced by the compression. Which is fine since compressing small audio files (like the moan of a zombie, or gun fire) is almost pointless to do individually.

DON’T do this for large audio files like background music since it will probably kick everything out of the cache eventually. If you want to do this for compressed audio formats you need cache their decompressed raw data, otherwise initializing the clip will be expensive (due to the redundant process of decompressing the cached data.)

There are other more clever ways of doing it, but you’d have to ask someone more experienced with the Java Sound API.

I think DrZoidberg’s solution is the best option and the easiest way to achieve overlapping sounds (two sounds at the same time).
There is no need for threads.

Of course you can also play the same sound twice at the same time.


play("shoot.wav");
play("shoot.wav");

You can also add a delay to get an echo effect.


play("shoot.wav");
Thread.sleep(500);
play("shoot.wav");

That the data has to be loaded twice should not be a problem since the OS has a file cache. But if you think it’s an issue you could load the wav files into byte arrays and then create clips from those.

True, I think OPs point was that it causes a lot of traffic to the filesystem, which isn’t good if you’re playing a machine gun for example.

I’ve tried it, and it was an issue for me.

It’s also very inefficient for compressed audio formats.

I don’t want to seem stupid, but I didn’t understand a lot of what you just said. Like I said, I don’t have to stick to the Clip class… I was just hoping I could stay within the core Java packages.

I checked out TinySound, but I don’t know how to include it in my project. I’m using NetBeans. I downloaded a ZIP of the TinySound repository on Github, but I’m not sure what to do with it. There aren’t any JARs inside, so I can’t use it as a library. How do I incorporate the .java files as a library?

Also, I removed the threads for playing the sound.

But yeah, loading the sound files on the fly isn’t going to work. It would cause too much latency. Like Jeremy said, for a machine gun (and my game happens to have an Assault Rifle), it just won’t work. My sounds are used too often.

You should stick to the Java Sound Library if you can, otherwise you don’t get the advantage of plugging in other SPIs for different audio formats etc. Also, if you can it is always best to avoid any native dependencies.

I’ve put some code for loading audio files in the paste-bin. It is hacked up and it may not be 100% proper, but it should work fine. Any missing code you can write pretty easily.
http://www.java-gaming.org/?action=pastebin&id=730

I only use WAV files, so I don’t see why I would need to worry about other audio formats. Also, I took a look at that Audio class you posted. Does it support the multiple plays of the same Clip simultaneously? Is it streamed? Like I said, streaming is absolutely not an option.

Yeah, it will let you play the same audio file twice, and it adapts the ByteBuffer object to an InputStream for the JavaSound API, but it reuses the ByteBuffer - it doesn’t load the same resource more than once and it caches the raw data to initialize a new clip if once is needed. It also caches the clip objects for re-use.

It works for machine guns etc.

Also, I noticed a couple of weird imports at the top… specifically, the “jeva” package. Was that a typo, or is that an external library? Can I just include this file and use it as is, or do I need a library to use with it?

Those are external libraries, just delete them. You’ll get a problem with AudioException not being found, and Core.getService(IResourceLibrary.class).openResourceRaw() or something like that. It’s pretty easy to guess what they’re supposed to do. Just write your own AudioException and openResourceRaw(string resource) is supposed to read the resource into a bytebuffer.

You should be able to just copy and paste it, noting the package will probably need to be changed.

How do I do that? Is it a simple function that you could just post here?

Then do it like this


public class AudioData {
    byte[] data;
    AudioFormat format;
    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();
             }
            });
            clip.open(format, data, 0, data.length);
            return clip;
        } catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

and then you can make your machine gun with this code


AudioData data = new AudioData("shoot.wav");
for(int i = 0; i < 100; i++) {
    data.createClip().start();
    Thread.sleep(100);
}

However of course it would be better to use Clip.loop for a machine gun but then the audio file may need some editing to make it sound correctly.

Why would I loop the sound for my Assault Rifle? It’s a single shot that plays whenever a bullet is fired.

So anyway, what you’re saying is that I could have a class with final static AudioData objects, and then when I need to play the clip, call the createClip() method and start it?

I’m using a similar way of DrZoidberg but had some small difference. My classes are located here.See classes WavSound, SoundState, WavPlayer.

You could use your Sounds enum but put AudioData objects in there instead of clips.
Or use SHC’s classes, they look good too. Although I’m not sure if you can make a machine gun with them.
SHC, maybe you could post a machine gun example using your library?