From fbce0391a8bb31fd7613d2e2291d4122448484b5 Mon Sep 17 00:00:00 2001 From: fd Date: Thu, 26 Feb 2015 23:37:30 -0500 Subject: [PATCH 1/4] 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. --- src/itdelatrisu/opsu/audio/HitSound.java | 8 +- src/itdelatrisu/opsu/audio/MultiClip.java | 95 +++++++++++++++++++ .../opsu/audio/SoundController.java | 47 ++++----- src/itdelatrisu/opsu/audio/SoundEffect.java | 6 +- 4 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 src/itdelatrisu/opsu/audio/MultiClip.java diff --git a/src/itdelatrisu/opsu/audio/HitSound.java b/src/itdelatrisu/opsu/audio/HitSound.java index 9217e20b..ed23b8f4 100644 --- a/src/itdelatrisu/opsu/audio/HitSound.java +++ b/src/itdelatrisu/opsu/audio/HitSound.java @@ -79,7 +79,7 @@ public enum HitSound implements SoundController.SoundComponent { private String filename; /** The Clip associated with the hit sound. */ - private HashMap clips; + private HashMap 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(); + this.clips = new HashMap(); } /** @@ -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); } diff --git a/src/itdelatrisu/opsu/audio/MultiClip.java b/src/itdelatrisu/opsu/audio/MultiClip.java new file mode 100644 index 00000000..3bdbf8e6 --- /dev/null +++ b/src/itdelatrisu/opsu/audio/MultiClip.java @@ -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 clips = new LinkedList(); + + /** 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; + } +} diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index e447551b..7fbcc023 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -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(); } } diff --git a/src/itdelatrisu/opsu/audio/SoundEffect.java b/src/itdelatrisu/opsu/audio/SoundEffect.java index 3c7d125f..fd5a78fa 100644 --- a/src/itdelatrisu/opsu/audio/SoundEffect.java +++ b/src/itdelatrisu/opsu/audio/SoundEffect.java @@ -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; } } From 8a8024aadbdfc57722d57f54c73dff72c5c10f87 Mon Sep 17 00:00:00 2001 From: fd Date: Fri, 27 Feb 2015 00:38:47 -0500 Subject: [PATCH 2/4] Nevermind --- src/itdelatrisu/opsu/audio/SoundController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index 7fbcc023..fe9fc5e2 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -87,9 +87,6 @@ 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, From 8512f7c3c5d1e601fed227f78152d6190c24bac8 Mon Sep 17 00:00:00 2001 From: fd Date: Fri, 27 Feb 2015 19:22:29 -0500 Subject: [PATCH 3/4] Fixes loading mp3 multiclip since AudioInputStream.availble didn't work. --- src/itdelatrisu/opsu/audio/HitSound.java | 2 - src/itdelatrisu/opsu/audio/MultiClip.java | 53 ++++++++++++++------- src/itdelatrisu/opsu/audio/SoundEffect.java | 2 - 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/itdelatrisu/opsu/audio/HitSound.java b/src/itdelatrisu/opsu/audio/HitSound.java index ed23b8f4..8b7d6f44 100644 --- a/src/itdelatrisu/opsu/audio/HitSound.java +++ b/src/itdelatrisu/opsu/audio/HitSound.java @@ -20,8 +20,6 @@ package itdelatrisu.opsu.audio; import java.util.HashMap; -import javax.sound.sampled.Clip; - /** * Hit sounds. */ diff --git a/src/itdelatrisu/opsu/audio/MultiClip.java b/src/itdelatrisu/opsu/audio/MultiClip.java index 3bdbf8e6..b6375dd2 100644 --- a/src/itdelatrisu/opsu/audio/MultiClip.java +++ b/src/itdelatrisu/opsu/audio/MultiClip.java @@ -1,7 +1,5 @@ package itdelatrisu.opsu.audio; -import itdelatrisu.opsu.ErrorHandler; - import java.io.IOException; import java.util.LinkedList; @@ -21,28 +19,49 @@ public class MultiClip { AudioFormat format; /** The data for this audio sample */ - byte[] buffer; + byte[] audioData; /** The name given to this clip */ String name; - /** Constructor - * @param name - * @throws LineUnavailableException */ + /** Size of a single buffer */ + final int BUFFER_SIZE = 0x1000; + + /** Constructor */ 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"); + + LinkedList allBufs = new LinkedList(); + + 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(); } @@ -88,7 +107,7 @@ public class MultiClip { } Clip t = AudioSystem.getClip(); if (format != null) - t.open(format, buffer, 0, buffer.length); + t.open(format, audioData, 0, audioData.length); clips.add(t); return t; } diff --git a/src/itdelatrisu/opsu/audio/SoundEffect.java b/src/itdelatrisu/opsu/audio/SoundEffect.java index fd5a78fa..03cabe3d 100644 --- a/src/itdelatrisu/opsu/audio/SoundEffect.java +++ b/src/itdelatrisu/opsu/audio/SoundEffect.java @@ -18,8 +18,6 @@ package itdelatrisu.opsu.audio; -import javax.sound.sampled.Clip; - /** * Sound effects. */ From 955a184d2c66e1756d83638527fe5ecd73f1f220 Mon Sep 17 00:00:00 2001 From: fd Date: Sat, 28 Feb 2015 18:28:58 -0500 Subject: [PATCH 4/4] Global max clips reuse clips close extraclips in ranking --- src/itdelatrisu/opsu/audio/MultiClip.java | 56 ++++++++++++++++--- .../opsu/audio/SoundController.java | 52 +++++++++++++++++ src/itdelatrisu/opsu/states/GameRanking.java | 1 + 3 files changed, 102 insertions(+), 7 deletions(-) diff --git a/src/itdelatrisu/opsu/audio/MultiClip.java b/src/itdelatrisu/opsu/audio/MultiClip.java index b6375dd2..c26db9f4 100644 --- a/src/itdelatrisu/opsu/audio/MultiClip.java +++ b/src/itdelatrisu/opsu/audio/MultiClip.java @@ -1,11 +1,11 @@ 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.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.FloatControl; import javax.sound.sampled.LineUnavailableException; @@ -27,6 +27,8 @@ public class MultiClip { /** Size of a single buffer */ final int BUFFER_SIZE = 0x1000; + static LinkedList allMultiClips = new LinkedList(); + /** Constructor */ public MultiClip(String name, AudioInputStream audioIn) throws IOException, LineUnavailableException { this.name = name; @@ -64,6 +66,7 @@ public class MultiClip { } } getClip(); + allMultiClips.add(this); } /** @@ -83,6 +86,9 @@ public class MultiClip { 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 @@ -96,19 +102,55 @@ public class MultiClip { } /** * Returns a Clip that is not playing from the list - * if one is not available a new one is created + * if one is not available a new one is created if able * @return the Clip */ private Clip getClip() throws LineUnavailableException, IOException{ - for(Clip c : clips){ + for(Iterator ita = clips.listIterator(); ita.hasNext(); ) { + Clip c = ita.next(); if(!c.isRunning()){ + ita.remove(); + clips.add(c); return c; } } - Clip t = AudioSystem.getClip(); - if (format != null) - t.open(format, audioData, 0, audioData.length); - clips.add(t); + + 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 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(); + } + } } diff --git a/src/itdelatrisu/opsu/audio/SoundController.java b/src/itdelatrisu/opsu/audio/SoundController.java index fe9fc5e2..f9e1d476 100644 --- a/src/itdelatrisu/opsu/audio/SoundController.java +++ b/src/itdelatrisu/opsu/audio/SoundController.java @@ -27,6 +27,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; 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.AudioInputStream; @@ -298,4 +301,53 @@ public class SoundController { 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 clipList = new HashSet(); + + /** List of clips to be closed */ + static LinkedList clipsToClose = new LinkedList(); + + /** + * 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 ita = clipsToClose.listIterator(); ita.hasNext(); ){ + Clip c = ita.next(); + c.close(); + ita.remove(); + } + } + }.start(); + } } diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 4bec2b88..965128c2 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -185,6 +185,7 @@ public class GameRanking extends BasicGameState { retryButton.resetHover(); exitButton.resetHover(); } + SoundController.destroyExtraClips(); } @Override