Allows multiple instance of the same clip to be played simultaneously.

This might fix some issues with some sounds not being played sometimes.

Also might require more memory since more clips are created.
This commit is contained in:
fd 2015-02-26 23:37:30 -05:00
parent ad5277882e
commit fbce0391a8
4 changed files with 119 additions and 37 deletions

View File

@ -79,7 +79,7 @@ public enum HitSound implements SoundController.SoundComponent {
private String filename;
/** The Clip associated with the hit sound. */
private HashMap<SampleSet, Clip> clips;
private HashMap<SampleSet, MultiClip> clips;
/** Total number of hit sounds. */
public static final int SIZE = values().length;
@ -90,7 +90,7 @@ public enum HitSound implements SoundController.SoundComponent {
*/
HitSound(String filename) {
this.filename = filename;
this.clips = new HashMap<SampleSet, Clip>();
this.clips = new HashMap<SampleSet, MultiClip>();
}
/**
@ -100,7 +100,7 @@ public enum HitSound implements SoundController.SoundComponent {
public String getFileName() { return filename; }
@Override
public Clip getClip() {
public MultiClip getClip() {
return (currentSampleSet != null) ? clips.get(currentSampleSet) : null;
}
@ -109,7 +109,7 @@ public enum HitSound implements SoundController.SoundComponent {
* @param s the sample set
* @param clip the Clip
*/
public void setClip(SampleSet s, Clip clip) {
public void setClip(SampleSet s, MultiClip clip) {
clips.put(s, clip);
}

View File

@ -0,0 +1,95 @@
package itdelatrisu.opsu.audio;
import itdelatrisu.opsu.ErrorHandler;
import java.io.IOException;
import java.util.LinkedList;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
//http://stackoverflow.com/questions/1854616/in-java-how-can-i-play-the-same-audio-clip-multiple-times-simultaneously
public class MultiClip {
/** A list of clips used for this audio sample */
LinkedList<Clip> clips = new LinkedList<Clip>();
/** The format of this audio sample */
AudioFormat format;
/** The data for this audio sample */
byte[] buffer;
/** The name given to this clip */
String name;
/** Constructor
* @param name
* @throws LineUnavailableException */
public MultiClip(String name, AudioInputStream audioIn) throws IOException, LineUnavailableException {
this.name = name;
if(audioIn != null){
buffer = new byte[audioIn.available()];
int readed= 0;
while(readed < buffer.length) {
int read = audioIn.read(buffer, readed, buffer.length-readed);
if(read < 0 )
break;
readed += read;
}
format = audioIn.getFormat();
} else {
System.out.println("Null multiclip");
}
getClip();
}
/**
* Returns the name of the clip
* @return the name
*/
public String getName() {
return name;
}
/**
* Plays the clip with the specified volume.
* @param volume the volume the play at
* @throws IOException
* @throws LineUnavailableException
*/
public void start(float volume) throws LineUnavailableException, IOException {
Clip clip = getClip();
// PulseAudio does not support Master Gain
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
// set volume
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
}
clip.setFramePosition(0);
clip.start();
}
/**
* Returns a Clip that is not playing from the list
* if one is not available a new one is created
* @return the Clip
*/
private Clip getClip() throws LineUnavailableException, IOException{
for(Clip c : clips){
if(!c.isRunning()){
return c;
}
}
Clip t = AudioSystem.getClip();
if (format != null)
t.open(format, buffer, 0, buffer.length);
clips.add(t);
return t;
}
}

View File

@ -33,7 +33,6 @@ 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;
@ -50,7 +49,7 @@ public class SoundController {
* Returns the Clip associated with the sound component.
* @return the Clip
*/
public Clip getClip();
public MultiClip getClip();
}
/** Sample volume multiplier, from timing points [0, 1]. */
@ -71,7 +70,7 @@ public class SoundController {
* @param isMP3 true if MP3, false if WAV
* @return the loaded and opened clip
*/
private static Clip loadClip(String ref, boolean isMP3) {
private static MultiClip loadClip(String ref, boolean isMP3) {
try {
URL url = ResourceLoader.getResource(ref);
@ -79,7 +78,7 @@ public class SoundController {
InputStream in = url.openStream();
if (in.available() == 0) {
in.close();
return AudioSystem.getClip();
return new MultiClip(ref, null);
}
in.close();
@ -88,6 +87,9 @@ public class SoundController {
// GNU/Linux workaround
// Clip clip = AudioSystem.getClip();
AudioFormat format = audioIn.getFormat();
//TODO Is this really needed? since the code below will find out the format isn't supported
// and will pretty much do the same thing I think. -fluddokt
if (isMP3) {
AudioFormat decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), 16,
@ -97,12 +99,10 @@ public class SoundController {
audioIn = decodedAudioIn;
}
DataLine.Info info = new DataLine.Info(Clip.class, format);
if (AudioSystem.isLineSupported(info)) {
Clip clip = (Clip) AudioSystem.getLine(info);
clip.open(audioIn);
return clip;
} else {
// try to find closest matching line
if(AudioSystem.isLineSupported(info)){
return new MultiClip(ref, audioIn);
}else{
//Try to find closest matching line
Clip clip = AudioSystem.getClip();
AudioFormat[] formats = ((DataLine.Info) clip.getLineInfo()).getFormats();
int bestIndex = -1;
@ -149,11 +149,10 @@ public class SoundController {
break;
}
if (bestIndex >= 0) {
clip.open(AudioSystem.getAudioInputStream(formats[bestIndex], audioIn));
return new MultiClip(ref, AudioSystem.getAudioInputStream(formats[bestIndex], audioIn));
} else
// still couldn't find anything, try the default clip format
clip.open(AudioSystem.getAudioInputStream(clip.getFormat(), audioIn));
return clip;
return new MultiClip(ref, AudioSystem.getAudioInputStream(clip.getFormat(), audioIn));
}
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException | RuntimeException e) {
ErrorHandler.error(String.format("Failed to load file '%s'.", ref), e, true);
@ -231,28 +230,16 @@ public class SoundController {
* @param clip the Clip to play
* @param volume the volume [0, 1]
*/
private static void playClip(Clip clip, float volume) {
private static void playClip(MultiClip clip, float volume) {
if (clip == null) // clip failed to load properly
return;
if (volume > 0f) {
// stop clip if running
if (clip.isRunning()) {
clip.stop();
clip.flush();
try {
clip.start(volume);
} catch (LineUnavailableException | IOException e) {
ErrorHandler.error(String.format("Could not start a clip '%s'.", clip.getName()), e, true);
}
// PulseAudio does not support Master Gain
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
// set volume
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
}
// play clip
clip.setFramePosition(0);
clip.start();
}
}

View File

@ -47,7 +47,7 @@ public enum SoundEffect implements SoundController.SoundComponent {
private String filename;
/** The Clip associated with the sound effect. */
private Clip clip;
private MultiClip clip;
/** Total number of sound effects. */
public static final int SIZE = values().length;
@ -67,11 +67,11 @@ public enum SoundEffect implements SoundController.SoundComponent {
public String getFileName() { return filename; }
@Override
public Clip getClip() { return clip; }
public MultiClip getClip() { return clip; }
/**
* Sets the Clip for the sound.
* @param clip the clip
*/
public void setClip(Clip clip) { this.clip = clip; }
public void setClip(MultiClip clip) { this.clip = clip; }
}