Merge pull request #25 from fluddokt/AudioTest
Allows multiple instance of the same clip to be played simultaneously.
This commit is contained in:
commit
540b1c49f0
|
@ -20,8 +20,6 @@ package itdelatrisu.opsu.audio;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import javax.sound.sampled.Clip;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hit sounds.
|
* Hit sounds.
|
||||||
*/
|
*/
|
||||||
|
@ -79,7 +77,7 @@ public enum HitSound implements SoundController.SoundComponent {
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
/** The Clip associated with the hit sound. */
|
/** The Clip associated with the hit sound. */
|
||||||
private HashMap<SampleSet, Clip> clips;
|
private HashMap<SampleSet, MultiClip> clips;
|
||||||
|
|
||||||
/** Total number of hit sounds. */
|
/** Total number of hit sounds. */
|
||||||
public static final int SIZE = values().length;
|
public static final int SIZE = values().length;
|
||||||
|
@ -90,7 +88,7 @@ public enum HitSound implements SoundController.SoundComponent {
|
||||||
*/
|
*/
|
||||||
HitSound(String filename) {
|
HitSound(String filename) {
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.clips = new HashMap<SampleSet, Clip>();
|
this.clips = new HashMap<SampleSet, MultiClip>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,7 +98,7 @@ public enum HitSound implements SoundController.SoundComponent {
|
||||||
public String getFileName() { return filename; }
|
public String getFileName() { return filename; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Clip getClip() {
|
public MultiClip getClip() {
|
||||||
return (currentSampleSet != null) ? clips.get(currentSampleSet) : null;
|
return (currentSampleSet != null) ? clips.get(currentSampleSet) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +107,7 @@ public enum HitSound implements SoundController.SoundComponent {
|
||||||
* @param s the sample set
|
* @param s the sample set
|
||||||
* @param clip the Clip
|
* @param clip the Clip
|
||||||
*/
|
*/
|
||||||
public void setClip(SampleSet s, Clip clip) {
|
public void setClip(SampleSet s, MultiClip clip) {
|
||||||
clips.put(s, clip);
|
clips.put(s, clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
156
src/itdelatrisu/opsu/audio/MultiClip.java
Normal file
156
src/itdelatrisu/opsu/audio/MultiClip.java
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package itdelatrisu.opsu.audio;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
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[] audioData;
|
||||||
|
|
||||||
|
/** The name given to this clip */
|
||||||
|
String name;
|
||||||
|
|
||||||
|
/** Size of a single buffer */
|
||||||
|
final int BUFFER_SIZE = 0x1000;
|
||||||
|
|
||||||
|
static LinkedList<MultiClip> allMultiClips = new LinkedList<MultiClip>();
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
public MultiClip(String name, AudioInputStream audioIn) throws IOException, LineUnavailableException {
|
||||||
|
this.name = name;
|
||||||
|
if(audioIn != null){
|
||||||
|
format = audioIn.getFormat();
|
||||||
|
|
||||||
|
LinkedList<byte[]> allBufs = new LinkedList<byte[]>();
|
||||||
|
|
||||||
|
int readed = 0;
|
||||||
|
boolean hasData = true;
|
||||||
|
while (hasData) {
|
||||||
|
readed = 0;
|
||||||
|
byte[] tbuf = new byte[BUFFER_SIZE];
|
||||||
|
while (readed < tbuf.length) {
|
||||||
|
int read = audioIn.read(tbuf, readed, tbuf.length - readed);
|
||||||
|
if (read < 0) {
|
||||||
|
hasData = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
readed += read;
|
||||||
|
}
|
||||||
|
allBufs.add(tbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
audioData = new byte[(allBufs.size() - 1) * BUFFER_SIZE + readed];
|
||||||
|
|
||||||
|
int cnt = 0;
|
||||||
|
for (byte[] tbuf : allBufs) {
|
||||||
|
int size = BUFFER_SIZE;
|
||||||
|
if (cnt == allBufs.size() - 1) {
|
||||||
|
size = readed;
|
||||||
|
}
|
||||||
|
System.arraycopy(tbuf, 0, audioData, BUFFER_SIZE * cnt, size);
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getClip();
|
||||||
|
allMultiClips.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
if(clip == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 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 if able
|
||||||
|
* @return the Clip
|
||||||
|
*/
|
||||||
|
private Clip getClip() throws LineUnavailableException, IOException{
|
||||||
|
for(Iterator<Clip> ita = clips.listIterator(); ita.hasNext(); ) {
|
||||||
|
Clip c = ita.next();
|
||||||
|
if(!c.isRunning()){
|
||||||
|
ita.remove();
|
||||||
|
clips.add(c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Clip t = SoundController.newClip();
|
||||||
|
if(t == null){
|
||||||
|
if(clips.isEmpty()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
t = clips.removeFirst();
|
||||||
|
t.stop();
|
||||||
|
clips.add(t);
|
||||||
|
} else {
|
||||||
|
if (format != null)
|
||||||
|
t.open(format, audioData, 0, audioData.length);
|
||||||
|
clips.add(t);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys all but one clip
|
||||||
|
*/
|
||||||
|
protected void destroyAllButOne(){
|
||||||
|
for(Iterator<Clip> ita = clips.listIterator(); ita.hasNext(); ) {
|
||||||
|
Clip c = ita.next();
|
||||||
|
if(clips.size()>1){
|
||||||
|
ita.remove();
|
||||||
|
SoundController.destroyClip(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys all but one clip for all MultiClips
|
||||||
|
*/
|
||||||
|
protected static void destroyExtraClips() {
|
||||||
|
for(MultiClip mc : MultiClip.allMultiClips){
|
||||||
|
mc.destroyAllButOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,13 +27,15 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
import javax.sound.sampled.AudioInputStream;
|
import javax.sound.sampled.AudioInputStream;
|
||||||
import javax.sound.sampled.AudioSystem;
|
import javax.sound.sampled.AudioSystem;
|
||||||
import javax.sound.sampled.Clip;
|
import javax.sound.sampled.Clip;
|
||||||
import javax.sound.sampled.DataLine;
|
import javax.sound.sampled.DataLine;
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ public class SoundController {
|
||||||
* Returns the Clip associated with the sound component.
|
* Returns the Clip associated with the sound component.
|
||||||
* @return the Clip
|
* @return the Clip
|
||||||
*/
|
*/
|
||||||
public Clip getClip();
|
public MultiClip getClip();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sample volume multiplier, from timing points [0, 1]. */
|
/** Sample volume multiplier, from timing points [0, 1]. */
|
||||||
|
@ -71,7 +73,7 @@ public class SoundController {
|
||||||
* @param isMP3 true if MP3, false if WAV
|
* @param isMP3 true if MP3, false if WAV
|
||||||
* @return the loaded and opened clip
|
* @return the loaded and opened clip
|
||||||
*/
|
*/
|
||||||
private static Clip loadClip(String ref, boolean isMP3) {
|
private static MultiClip loadClip(String ref, boolean isMP3) {
|
||||||
try {
|
try {
|
||||||
URL url = ResourceLoader.getResource(ref);
|
URL url = ResourceLoader.getResource(ref);
|
||||||
|
|
||||||
|
@ -79,7 +81,7 @@ public class SoundController {
|
||||||
InputStream in = url.openStream();
|
InputStream in = url.openStream();
|
||||||
if (in.available() == 0) {
|
if (in.available() == 0) {
|
||||||
in.close();
|
in.close();
|
||||||
return AudioSystem.getClip();
|
return new MultiClip(ref, null);
|
||||||
}
|
}
|
||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
|
@ -98,11 +100,9 @@ public class SoundController {
|
||||||
}
|
}
|
||||||
DataLine.Info info = new DataLine.Info(Clip.class, format);
|
DataLine.Info info = new DataLine.Info(Clip.class, format);
|
||||||
if(AudioSystem.isLineSupported(info)){
|
if(AudioSystem.isLineSupported(info)){
|
||||||
Clip clip = (Clip) AudioSystem.getLine(info);
|
return new MultiClip(ref, audioIn);
|
||||||
clip.open(audioIn);
|
|
||||||
return clip;
|
|
||||||
}else{
|
}else{
|
||||||
// try to find closest matching line
|
//Try to find closest matching line
|
||||||
Clip clip = AudioSystem.getClip();
|
Clip clip = AudioSystem.getClip();
|
||||||
AudioFormat[] formats = ((DataLine.Info) clip.getLineInfo()).getFormats();
|
AudioFormat[] formats = ((DataLine.Info) clip.getLineInfo()).getFormats();
|
||||||
int bestIndex = -1;
|
int bestIndex = -1;
|
||||||
|
@ -149,11 +149,10 @@ public class SoundController {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (bestIndex >= 0) {
|
if (bestIndex >= 0) {
|
||||||
clip.open(AudioSystem.getAudioInputStream(formats[bestIndex], audioIn));
|
return new MultiClip(ref, AudioSystem.getAudioInputStream(formats[bestIndex], audioIn));
|
||||||
} else
|
} else
|
||||||
// still couldn't find anything, try the default clip format
|
// still couldn't find anything, try the default clip format
|
||||||
clip.open(AudioSystem.getAudioInputStream(clip.getFormat(), audioIn));
|
return new MultiClip(ref, AudioSystem.getAudioInputStream(clip.getFormat(), audioIn));
|
||||||
return clip;
|
|
||||||
}
|
}
|
||||||
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException | RuntimeException e) {
|
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException | RuntimeException e) {
|
||||||
ErrorHandler.error(String.format("Failed to load file '%s'.", ref), e, true);
|
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 clip the Clip to play
|
||||||
* @param volume the volume [0, 1]
|
* @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
|
if (clip == null) // clip failed to load properly
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (volume > 0f) {
|
if (volume > 0f) {
|
||||||
// stop clip if running
|
try {
|
||||||
if (clip.isRunning()) {
|
clip.start(volume);
|
||||||
clip.stop();
|
} catch (LineUnavailableException | IOException e) {
|
||||||
clip.flush();
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,4 +301,53 @@ public class SoundController {
|
||||||
|
|
||||||
return currentFileIndex * 100 / (SoundEffect.SIZE + (HitSound.SIZE * SampleSet.SIZE));
|
return currentFileIndex * 100 / (SoundEffect.SIZE + (HitSound.SIZE * SampleSet.SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Max number of clips that can be created */
|
||||||
|
static int MAX_CLIP = 100;
|
||||||
|
|
||||||
|
/** List of clips that has been created */
|
||||||
|
static HashSet<Clip> clipList = new HashSet<Clip>();
|
||||||
|
|
||||||
|
/** List of clips to be closed */
|
||||||
|
static LinkedList<Clip> clipsToClose = new LinkedList<Clip>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new clip if it still below the max number of clips
|
||||||
|
*/
|
||||||
|
protected static Clip newClip() throws LineUnavailableException{
|
||||||
|
if(clipList.size() < MAX_CLIP) {
|
||||||
|
Clip c = AudioSystem.getClip();
|
||||||
|
clipList.add(c);
|
||||||
|
return c;
|
||||||
|
} else {
|
||||||
|
System.out.println("Can't newClip");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a clip to be closed
|
||||||
|
*/
|
||||||
|
protected static void destroyClip(Clip c) {
|
||||||
|
if(clipList.remove(c)){
|
||||||
|
clipsToClose.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Destroys all extra Clips
|
||||||
|
*/
|
||||||
|
public static void destroyExtraClips() {
|
||||||
|
MultiClip.destroyExtraClips();
|
||||||
|
new Thread(){
|
||||||
|
public void run(){
|
||||||
|
for(ListIterator<Clip> ita = clipsToClose.listIterator(); ita.hasNext(); ){
|
||||||
|
Clip c = ita.next();
|
||||||
|
c.close();
|
||||||
|
ita.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
package itdelatrisu.opsu.audio;
|
package itdelatrisu.opsu.audio;
|
||||||
|
|
||||||
import javax.sound.sampled.Clip;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sound effects.
|
* Sound effects.
|
||||||
*/
|
*/
|
||||||
|
@ -47,7 +45,7 @@ public enum SoundEffect implements SoundController.SoundComponent {
|
||||||
private String filename;
|
private String filename;
|
||||||
|
|
||||||
/** The Clip associated with the sound effect. */
|
/** The Clip associated with the sound effect. */
|
||||||
private Clip clip;
|
private MultiClip clip;
|
||||||
|
|
||||||
/** Total number of sound effects. */
|
/** Total number of sound effects. */
|
||||||
public static final int SIZE = values().length;
|
public static final int SIZE = values().length;
|
||||||
|
@ -67,11 +65,11 @@ public enum SoundEffect implements SoundController.SoundComponent {
|
||||||
public String getFileName() { return filename; }
|
public String getFileName() { return filename; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Clip getClip() { return clip; }
|
public MultiClip getClip() { return clip; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the Clip for the sound.
|
* Sets the Clip for the sound.
|
||||||
* @param clip the clip
|
* @param clip the clip
|
||||||
*/
|
*/
|
||||||
public void setClip(Clip clip) { this.clip = clip; }
|
public void setClip(MultiClip clip) { this.clip = clip; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,7 @@ public class GameRanking extends BasicGameState {
|
||||||
retryButton.resetHover();
|
retryButton.resetHover();
|
||||||
exitButton.resetHover();
|
exitButton.resetHover();
|
||||||
}
|
}
|
||||||
|
SoundController.destroyExtraClips();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue
Block a user