1

I play sound effects (WAV files) in a game with javax.sound.sampled.Clip. However, the effect that has 2 seconds is not played entirely, only a part of it. If I add clip.loop(2) then the sound is played multiple times correctly, i.e. the whole 2 seconds. What is wrong and how to fix it?

package com.zetcode;

import java.io.IOException;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JOptionPane;

public enum SoundEffect {

    EXPLODE("resources/explosion.wav"),
    HITGROUND("resources/impact.wav"),
    CANNON("resources/cannon.wav");

    private Clip clip;

    SoundEffect(String soundFileName) {
        try {
            URL url = this.getClass().getClassLoader().getResource(soundFileName);

            try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url)) {
                clip = AudioSystem.getClip();
                clip.open(audioInputStream);
            }
        } catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
            JOptionPane.showMessageDialog(null, "Cannot play sound", "Error",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    public void play() {

        if (clip.isRunning()) {
            clip.stop();
        }
        clip.setFramePosition(0);
        clip.start();
    }

    static void init() {
        values();
    }    
}
Jan Bodnar
  • 10,969
  • 6
  • 68
  • 77
  • *"then the sound is played multiple times correctly"* I see three sound files mentioned in the code. Which one are you referring to? *`clip.loop(2)`* Out of curiosity, what is the effect of `clip.loop(1)`? – Andrew Thompson Jun 11 '18 at 12:58
  • The code was taken indirectly from https://www.ntu.edu.sg/home/ehchua/programming/java/J8c_PlayingSound.html. It launches sound like this: `SoundEffect.HITGROUND.play()`,`SoundEffect.CANNON.play()`. The impact and cannon sounds are very short; the explosion is 2 seconds -- this one does not play correctly. The `loop(1)` plays the explosion twice correctly. – Jan Bodnar Jun 11 '18 at 13:20

1 Answers1

1

Are you aware and accounting for the fact that when a clip is played (via start() method), the thread playing the code is a daemon? Unlike regular threads, a daemon thread will not prevent a Java program from closing. If your program ends before the sound has finished executing, the daemon status thread also ends even if it is not finished.

Are you trying to run the sample code SoundClipTest or SoundEffectDemo? It looks to me like SoundClipTest is going to exit as soon as the program executes the start() method, whereas the SoundEffectDemo will persist and allow the sound to finish playing.


** Edit 1 **


Am just now noticing that you have made your SoundEffect an enum. I've never seen that approach used before.

One approach I've used is to create a class called GameSound, and have its constructor preload each Clip individually and hold them in memory, ready for playback. Then, I create a method or methods for their playback.

gameSound.playExplosion();

The GameSound class would have instance variables for each sound effect.

private Clip explosion;
private Clip cannon;

And GameSound constructor would have:

URL url = this.getClass().getResource("resources/explosion.wav");
AudioInputStream ais = AudioSystem.getAudioInputStream(url); 
explosion = AudioSystem.getClip();
explosion.open(ais);

and GameSound would have method:

public void playExplosion()
{
    if (explosion.isRunning()) {
        explosion.stop();
    }
    explosion.setFramePosition(0);
    explosion.start();
}

If you prefer to have the play method take an argument and use that on a switch structure to select which sound to play, that is fine, too, especially as it eliminates some code duplication.

This approach might work better (easy to add to GameSound as code gets more complex), and shouldn't be too difficult to code. I'd be curious to learn whether or not changing to this plan makes the problem go away or not.


** Edit 2 **


Sorry for mistakes in the above code. I fixed them when I made the following example. One thought, though, when I went to listen to the sound effects you linked, several of the explosions were kind of truncated to begin with. Have you played the sounds in another app (like Windows Groove Music, or from the website where you got them) and verified that they are different when you play them in your code?

Here is a quickie "GameSound". I downloaded the Explosion+2.wav file from your reference as it was longer than the others and has a clear ending, and am referencing that.

package stackoverflow.janbodnar;

import java.io.IOException;
import java.net.URL;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class GameSound {

    private final Clip explosion;

    public GameSound() throws LineUnavailableException, 
    IOException, UnsupportedAudioFileException
    {
        URL url = this.getClass().getResource(
                "resources/Explosion+2.wav");
        AudioInputStream ais = AudioSystem.getAudioInputStream(url);
        explosion = AudioSystem.getClip();
        explosion.open(ais);
    }

    public void playExplosion()
    {
        if (explosion.isRunning()) {
            explosion.stop();
        }
        explosion.setFramePosition(0);
        explosion.start();
    }
}

And here is a simple Swing button that calls GameSound.

package stackoverflow.janbodnar;

import java.io.IOException;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class GameSoundTest extends JFrame
{
    private static final long serialVersionUID = 1L;

    private final GameSound gameSound;

    public GameSoundTest() throws LineUnavailableException,
    IOException, UnsupportedAudioFileException
    {
        gameSound = new GameSound();

        JButton button = new JButton("Play Explosion");
        button.addActionListener(e -> gameSound.playExplosion());
        getContentPane().add(button);
    }

    private static void createAndShowGUI()
    {
        JFrame frame;
        try 
        {
            frame = new GameSoundTest();
            frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
            frame.setBounds(0, 0, 200, 200);
            frame.setVisible(true);
        } 
        catch (LineUnavailableException | IOException 
                | UnsupportedAudioFileException e) 
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }
}

This all assumes, of course, that there is a subfolder called "resources" with the explosion SFX in it.

Phil Freihofner
  • 7,645
  • 1
  • 20
  • 41
  • I found that the sound code comes from the mentioned link (indirectly), but I am developing a whole new game -- I am not running SoundClipTest. The sounds are played in reaction to things like collision or key release (cannon shot). So the game runs, shorter sounds are played, the 2 seconds explosion does not fully play for some reason. I did not know it is a daemon. I thought the problem might be with threads, I used Swing timers and Java util timers for my game cycle, both do the same thing. – Jan Bodnar Jun 12 '18 at 18:10
  • Side note: The Swing Timer might do weird things on the EDT if it is required to play a sound. I'd stick with Util Timer, or better, use an ExecutorService. – Phil Freihofner Jun 12 '18 at 19:28
  • I already tried using a class instead of enum. (The enum was not my idea, I copied it when I was looking for Java sound example.) Changing it to class game my all sorts of errors, sounds not playing at all or `LineUnavailableExceptions`. Your approach (creating three different clips) has fixed those additional errors. But the 2s explosion still does not completely play. – Jan Bodnar Jun 13 '18 at 06:27
  • I also tried different explosion files, which I took from https://www.freesoundeffects.com/free-sounds/explosion-10070/, all do not play completely. – Jan Bodnar Jun 13 '18 at 06:33
  • Added a code example, including one of the longer explosions from your freesoundeffects site. – Phil Freihofner Jun 13 '18 at 20:56
  • Yes I have verified that the samples were correct on vlc. I run your example (I had to add getClassLoader() call) and it did the same. Then I tested the example on Windows 7 (so far I did all the testing on Linux) and it worked OK. So it looks like the problem is Linux specific. Tomorrow I will also try it on Windows 10 at work. – Jan Bodnar Jun 14 '18 at 14:01
  • Glad to hear about the positive progress. I am wondering at what point you added the getClassLoader() call. I just verified that the code I wrote doesn't make use of this, except in as much as getResource() delegates to the class loader of the class. – Phil Freihofner Jun 14 '18 at 19:04
  • In `this.getClass().getClassLoader().getResource()`. Without it, I was getting `NullPointerException`. – Jan Bodnar Jun 14 '18 at 19:14
  • Weird. I don't get that result at all. Does "this" refer to GameSound? On the line prior I put the following> System.out.println("this:" + this); I get: this:stackoverflow.janbodnar.GameSound@c907418 Your package structure and hashcode will differ, of course. – Phil Freihofner Jun 14 '18 at 19:38
  • Do you have in our code `"resources/explosion.wav"` or `"/resources/explosion.wav"`? The leading slash makes the difference. https://stackoverflow.com/questions/14739550/difference-between-getclass-getclassloader-getresource-and-getclass-getres – Jan Bodnar Jun 14 '18 at 20:18
  • No leading slash. – Phil Freihofner Jun 14 '18 at 20:30