Merge remote-tracking branch 'ita/master' into AudioTest

Conflicts:
	src/itdelatrisu/opsu/states/SongMenu.java
This commit is contained in:
fd
2015-03-01 13:14:44 -05:00
5 changed files with 140 additions and 141 deletions

View File

@@ -6,87 +6,101 @@ import java.util.LinkedList;
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.Clip; import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl; import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.LineUnavailableException;
//http://stackoverflow.com/questions/1854616/in-java-how-can-i-play-the-same-audio-clip-multiple-times-simultaneously /**
* Extension of Clip that allows playing multiple copies of a Clip simultaneously.
* http://stackoverflow.com/questions/1854616/
*
* @author fluddokt (https://github.com/fluddokt)
*/
public class MultiClip { public class MultiClip {
/** A list of clips used for this audio sample */ /** Maximum number of extra clips that can be created at one time. */
LinkedList<Clip> clips = new LinkedList<Clip>(); private static final int MAX_CLIPS = 20;
/** The format of this audio sample */ /** A list of all created MultiClips. */
AudioFormat format; private static final LinkedList<MultiClip> ALL_MULTICLIPS = new LinkedList<MultiClip>();
/** The data for this audio sample */ /** Size of a single buffer. */
byte[] audioData; private static final int BUFFER_SIZE = 0x1000;
/** The name given to this clip */ /** Current number of extra clips created. */
String name; private static int extraClips = 0;
/** Size of a single buffer */ /** Current number of clip-closing threads in execution. */
final int BUFFER_SIZE = 0x1000; private static int closingThreads = 0;
static LinkedList<MultiClip> allMultiClips = new LinkedList<MultiClip>(); /** A list of clips used for this audio sample. */
private LinkedList<Clip> clips = new LinkedList<Clip>();
/** Constructor */ /** The format of this audio sample. */
private AudioFormat format;
/** The data for this audio sample. */
private byte[] audioData;
/** The name given to this clip. */
private String name;
/**
* Constructor.
* @param name the clip name
* @param audioIn the associated AudioInputStream
*/
public MultiClip(String name, AudioInputStream audioIn) throws IOException, LineUnavailableException { public MultiClip(String name, AudioInputStream audioIn) throws IOException, LineUnavailableException {
this.name = name; this.name = name;
if(audioIn != null){ if (audioIn != null) {
format = audioIn.getFormat(); format = audioIn.getFormat();
LinkedList<byte[]> allBufs = new LinkedList<byte[]>(); LinkedList<byte[]> allBufs = new LinkedList<byte[]>();
int readed = 0; int totalRead = 0;
boolean hasData = true; boolean hasData = true;
while (hasData) { while (hasData) {
readed = 0; totalRead = 0;
byte[] tbuf = new byte[BUFFER_SIZE]; byte[] tbuf = new byte[BUFFER_SIZE];
while (readed < tbuf.length) { while (totalRead < tbuf.length) {
int read = audioIn.read(tbuf, readed, tbuf.length - readed); int read = audioIn.read(tbuf, totalRead, tbuf.length - totalRead);
if (read < 0) { if (read < 0) {
hasData = false; hasData = false;
break; break;
} }
readed += read; totalRead += read;
} }
allBufs.add(tbuf); allBufs.add(tbuf);
} }
audioData = new byte[(allBufs.size() - 1) * BUFFER_SIZE + readed]; audioData = new byte[(allBufs.size() - 1) * BUFFER_SIZE + totalRead];
int cnt = 0; int cnt = 0;
for (byte[] tbuf : allBufs) { for (byte[] tbuf : allBufs) {
int size = BUFFER_SIZE; int size = BUFFER_SIZE;
if (cnt == allBufs.size() - 1) { if (cnt == allBufs.size() - 1)
size = readed; size = totalRead;
}
System.arraycopy(tbuf, 0, audioData, BUFFER_SIZE * cnt, size); System.arraycopy(tbuf, 0, audioData, BUFFER_SIZE * cnt, size);
cnt++; cnt++;
} }
} }
getClip(); getClip();
allMultiClips.add(this); ALL_MULTICLIPS.add(this);
} }
/** /**
* Returns the name of the clip * Returns the name of the clip.
* @return the name * @return the name
*/ */
public String getName() { public String getName() { return name; }
return name;
}
/** /**
* Plays the clip with the specified volume. * Plays the clip with the specified volume.
* @param volume the volume the play at * @param volume the volume the play at
* @throws IOException
* @throws LineUnavailableException
*/ */
public void start(float volume) throws LineUnavailableException, IOException { public void start(float volume) throws LineUnavailableException {
Clip clip = getClip(); Clip clip = getClip();
if (clip == null)
if(clip == null)
return; return;
// PulseAudio does not support Master Gain // PulseAudio does not support Master Gain
@@ -100,57 +114,85 @@ public class MultiClip {
clip.setFramePosition(0); clip.setFramePosition(0);
clip.start(); clip.start();
} }
/** /**
* Returns a Clip that is not playing from the list * Returns a Clip that is not playing from the list.
* if one is not available a new one is created if able * If no clip is available, then a new one is created if under MAX_CLIPS.
* @return the Clip * Otherwise, an existing clip will be returned.
* @return the Clip to play
*/ */
private Clip getClip() throws LineUnavailableException, IOException{ private Clip getClip() throws LineUnavailableException {
for(Iterator<Clip> ita = clips.listIterator(); ita.hasNext(); ) { // TODO:
Clip c = ita.next(); // Occasionally, even when clips are being closed in a separate thread,
if(!c.isRunning()){ // playing any clip will cause the game to hang until all clips are
ita.remove(); // closed. Why?
if (closingThreads > 0)
return null;
// search for existing stopped clips
for (Iterator<Clip> iter = clips.iterator(); iter.hasNext();) {
Clip c = iter.next();
if (!c.isRunning()) {
iter.remove();
clips.add(c); clips.add(c);
return c; return c;
} }
} }
Clip t = SoundController.newClip(); Clip c = null;
if(t == null){ if (extraClips >= MAX_CLIPS) {
if(clips.isEmpty()){ // use an existing clip
if (clips.isEmpty())
return null; return null;
} c = clips.removeFirst();
t = clips.removeFirst(); c.stop();
t.stop(); clips.add(c);
clips.add(t);
} else { } else {
// create a new clip
c = AudioSystem.getClip();
if (format != null) if (format != null)
t.open(format, audioData, 0, audioData.length); c.open(format, audioData, 0, audioData.length);
clips.add(t); clips.add(c);
if (clips.size() != 1)
extraClips++;
} }
return t; return c;
} }
/** /**
* Destroys all but one clip * Destroys all extra clips.
*/ */
protected void destroyAllButOne(){ public static void destroyExtraClips() {
for(Iterator<Clip> ita = clips.listIterator(); ita.hasNext(); ) { if (extraClips == 0)
Clip c = ita.next(); return;
if(clips.size()>1){
ita.remove(); // find all extra clips
SoundController.destroyClip(c); final LinkedList<Clip> clipsToClose = new LinkedList<Clip>();
for (MultiClip mc : MultiClip.ALL_MULTICLIPS) {
for (Iterator<Clip> iter = mc.clips.iterator(); iter.hasNext();) {
Clip c = iter.next();
if (mc.clips.size() > 1) { // retain last Clip in list
iter.remove();
clipsToClose.add(c);
}
} }
} }
// close clips in a new thread
new Thread() {
@Override
public void run() {
closingThreads++;
for (Clip c : clipsToClose) {
c.stop();
c.flush();
c.close();
} }
closingThreads--;
}
}.start();
/** // reset extra clip count
* Destroys all but one clip for all MultiClips extraClips = 0;
*/
protected static void destroyExtraClips() {
for(MultiClip mc : MultiClip.allMultiClips){
mc.destroyAllButOne();
}
} }
} }

View File

@@ -27,9 +27,6 @@ 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;
@@ -102,7 +99,7 @@ public class SoundController {
if(AudioSystem.isLineSupported(info)){ if(AudioSystem.isLineSupported(info)){
return new MultiClip(ref, audioIn); return new MultiClip(ref, audioIn);
}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;
@@ -237,7 +234,7 @@ public class SoundController {
if (volume > 0f) { if (volume > 0f) {
try { try {
clip.start(volume); clip.start(volume);
} catch (LineUnavailableException | IOException e) { } catch (LineUnavailableException e) {
ErrorHandler.error(String.format("Could not start a clip '%s'.", clip.getName()), e, true); ErrorHandler.error(String.format("Could not start a clip '%s'.", clip.getName()), e, true);
} }
} }
@@ -301,53 +298,4 @@ 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();
}
} }

View File

@@ -109,8 +109,13 @@ public class LinearBezier extends Curve {
if (iter.hasNext()) { if (iter.hasNext()) {
curBezier = iter.next(); curBezier = iter.next();
curPoint = 0; curPoint = 0;
} else } else {
curPoint = curBezier.points() - 1; curPoint = curBezier.points() - 1;
if (lastDistanceAt == distanceAt) {
// out of points even though the preferred distance hasn't been reached
break;
}
}
} }
} }
Vec2f thisCurve = curBezier.getCurve()[curPoint]; Vec2f thisCurve = curBezier.getCurve()[curPoint];

View File

@@ -185,7 +185,6 @@ public class GameRanking extends BasicGameState {
retryButton.resetHover(); retryButton.resetHover();
exitButton.resetHover(); exitButton.resetHover();
} }
SoundController.destroyExtraClips();
} }
@Override @Override

View File

@@ -35,6 +35,7 @@ import itdelatrisu.opsu.ScoreData;
import itdelatrisu.opsu.SongSort; import itdelatrisu.opsu.SongSort;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.HitSound; import itdelatrisu.opsu.audio.HitSound;
import itdelatrisu.opsu.audio.MultiClip;
import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.MusicController;
import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.audio.SoundEffect;
@@ -917,6 +918,9 @@ public class SongMenu extends BasicGameState {
if (resetGame) { if (resetGame) {
((Game) game.getState(Opsu.STATE_GAME)).resetGameData(); ((Game) game.getState(Opsu.STATE_GAME)).resetGameData();
// destroy extra Clips
MultiClip.destroyExtraClips();
// destroy skin images, if any // destroy skin images, if any
for (GameImage img : GameImage.values()) { for (GameImage img : GameImage.values()) {
if (img.isSkinnable()) if (img.isSkinnable())
@@ -1261,6 +1265,7 @@ public class SongMenu extends BasicGameState {
Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString())); Display.setTitle(String.format("%s - %s", game.getTitle(), osu.toString()));
OsuParser.parseHitObjects(osu); OsuParser.parseHitObjects(osu);
HitSound.setDefaultSampleSet(osu.sampleSet); HitSound.setDefaultSampleSet(osu.sampleSet);
MultiClip.destroyExtraClips();
((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.NEW); ((Game) game.getState(Opsu.STATE_GAME)).setRestart(Game.Restart.NEW);
game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black)); game.enterState(Opsu.STATE_GAME, new FadeOutTransition(Color.black), new FadeInTransition(Color.black));
} }