Problems using Java's Clip class
Asked Answered
T

1

1

I'm having trouble using Java's Clip class in a game I've been developing. Specifically, I think the problem is in using the same clip multiple times simultaneously. I did some googling and some people suggested running the clip in its own thread. I wanted to copy the clip object so it would use a different clip object in every thread to avoid the need to "reset" the clip, but I'm still running into problems. In fact, the problem is that the game seems to freeze up when the same clip plays several times simultaneously.

Someone on Reddit suggested that the issue may be because the Clip class is not synchronous, and that I would have to open the clip every time I want to use it. Obviously, this won't work, since the sounds are used very frequently.

In case someone may be able to point out a fatal flaw with the class, here's the code:

/**
    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),
    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) {
        synchronized(clipCopy) { clipCopy.stop(); }
        clipCopy.setFramePosition(0);
    }

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

So is what I'm trying to do, using an enum and static play() method, possible for what I'm trying to accomplish? Or will I have to find another way? I'm fine with using a different class, or even a library if necessary, but I'd rather stay within core Java.

Can someone think of a way to do what I'm looking to do?

Tenterhook answered 3/10, 2013 at 20:3 Comment(5)
you would need to play music in separate thread, for more info please to search hereResinate
Read my post again. I already said that they're being played in a separate thread. Furthermore, it's not music. They're just short audio clips for things such as gunfire, explosions, etc. The problem is that the Clip class doesn't seem to support playing the same clip several times simultaneously.Tenterhook
1) What does this have to do with AWT? 2) For better help sooner, post an SSCCE. 3) It is better to include code in the question than share a link on paste bin.Trug
Sorry, I put AWT in the tags by mistake, thinking it was part of the AWT package. I have no idea why... Nevertheless... I think I may look into other classes and libraries, but is there ANY way to achieve what I'm trying to do with the Clip class?Tenterhook
Looking into a library called TinySound. It's on Github, but I've never used a code library from Github before. I downloaded the ZIP of the repository, but I have no idea how to include it in my project and use the .java files within. There is no JAR included, so how can I include and use the library in NetBeans?Tenterhook
S
1

Clips do NOT support concurrency. You can restart a Clip at any time, and play it multiple times. But at any given time, there can only be one instance of a given Clip playing at one time.

TinySound is a good library, but I can't recall if his Clips allow concurrent plays either. You might check into that before going all in. There's a substantial thread about this library at Java-gaming.org.

I don't know about Net Beans, but to use a Git Hub source in Eclipse, I follow the instructions provided by Git Hub on how to set up an account and make a clone, then download the clone onto my desktop (using Bash). The instructions aren't easy but this is a very basic use of the product (GitHub) and one in which you should be able to find support.

Once the project is on the desktop, I create a new project in Eclipse with the root directory of the desktop clone provided as the location. Eclipse has additional settings for linking an external library to a project. I'd be really surprised if Net Beans doesn't have something similar.

Eclipse can also create a project or link a library from a zip file. Zips are the same exact format as Jar files. Net Beans should have a way to link to a zip or jar of a project. In Eclipse, they use the term "archived" for projects stored in zips. But I don't know if the zip you have is exactly everything needed to just load as an external project. My experience with GitHub is kind of minimal.

If you want to reuse a gun shot or whatever, you will probably have to make one clip per each concurrent copy you wish to support.

I ended up coding my own clip object and providing multiple cursors into it, and an interface to mix the data from the concurrent cursors. It works pretty well! I'm willing to send you a copy to try out. It is set up to use a continuously running audio mixer (same as TinySound does, I think). But if I send you the code, you might be able to rewrite the Clip part to stand alone. The "PFClip" also allows panning, volume setting and playback at different speeds, so you can vary the speed of the gunshot. Doing so, it sounds like different guns (or explosions if you make them really slow).[Insert pun on getting maximum bang for the buck here.]

The library has a few cool additional features: a PFLoop for looping clips (allows one to overlap the ends in a smooth fashion), and I just made a flanger, stereo chorus and a pitch variable echo. Unlike TinySound, I don't support anything except stereo wav format at 44100fps and 16 bits. I wrote this for a game I'm working on, playable via browser: http://hexara.com/game.html The windchime effect is something which uses concurrent clips.

Forgot! I had another demo of the mixer up from earlier, and it uses a single gunshot in various ways, concurrently: enter link description here The code listed at JGO is stale, but there is a page down the thread with the basic idea that can be used for making your own concurrent clip.

Contact info is provided on the second link site.

Snub answered 4/10, 2013 at 23:53 Comment(2)
So to use the TinySound library, just create a project from those files in the ZIP and make my own JAR of it?Tenterhook
If the zip can be made directly into a project, I'm guessing Net Beans gives you an option to use other projects' code in a given project. Otherwise "cloning" and "pulling" is the way I learned to load GitHub projects to a desktop.Snub

© 2022 - 2024 — McMap. All rights reserved.