Delay on Java.Clip Mute Controls - java

So i'm writing this sort of game in Java where you play like Guitar Hero. I can play both parts of the song (the song and the guitar) and so far so good.
Next, i needed to mute the guitar part when someone missed a key. The problem is, there's a 1sec or so delay from the moment i mute the Clip until it actually mutes.
How can this delay be fixed?
try {
audioIn = AudioSystem.getAudioInputStream(new File("guitar.wav"));
guitar = AudioSystem.getClip();
guitar.open(audioIn);
} catch (Exception e) {}
guitar.start();
volume = (BooleanControl) guitar.getControl(BooleanControl.Type.MUTE);
An in the game loop:
if (missedKey()) {
volume.setValue(true);
} else {
volume.setValue(false);
}

Ended up following Radiodef's answer and use byte streams. I have a buffer filled with 0's and i either output that or the normal audio to the stream.

Related

How do I efficiently close a large number of clips as soon as they have finished playing?

I have a game where sound effects are played using this method:
AudioInputStream inputStream = AudioSystem.getAudioInputStream(
TheGame.class.getResource(url));
Clip clip = AudioSystem.getClip();
clip.open(inputStream);
clip.start();
However, not closing the clips obviously builds up memory, and the fixes I've tried cause the game to freeze up occasionally.
I tried :
creating a new LineListener for every single clip that closes the clip when it stops. freezes up game OFTEN.
Having one LineListener that listens to every clip that closes them when they stop. freezes often as well.
Same as #2, but dividing the clips among multiple LineListeners, I've tried 4, 6, and 10, and all freeze up the game at least once.
Never closing the clips, eventually the game will stop playing sounds and freeze up
setting the clips to null as soon as they're opened, same result as #4
I'm not sure why closing clips seems to take so much time that it freezes up the entire game, is there a more time-efficient way?
Here is the code for the LineListener's update method I'm using:
#Override
public void update(LineEvent event) {
if (event.getType() == LineEvent.Type.STOP){
Clip c = (Clip) event.getSource();
c.close();
c.removeLineListener(this);
}
}
As per my comment above, you should reuse the existing Clip object.
Map<String, Clip> clips = new HashMap<String, Clip>();
public synchronized void play(String url) {
Clip clip = clips.get(url);
if(clip == null) {
AudioInputStream inputStream = AudioSystem.getAudioInputStream(
TheGame.class.getResource(url));
Clip clip = AudioSystem.getClip();
clip.open(inputStream);
clips.put(url, clip);
}
if(clip.isRunning())
clip.stop();
clip.setFramePosition(0);
clip.start();
}
You will need to get a bit more creative if you need to overlap the same sound with itself. Note that I have placed this in a synchronized method to protect the Map from concurrent modification if you should happen to be calling this from multiple threads.

JavaFX MediaPlayer highly inaccurate seeking

I'm using 320 kbps roughly 1 hour long MP3 files. The project I'm working on would seek in a collection of music inside an MP3 file so that it can shuffle the songs. I would give the timestamps to the program and it would seek to the song. It would work if JavaFX's seek method wasn't highly inaccurate.
After using MediaPlayer.seek(duration) The MediaPlayer.getCurrentTime() returns the duration we seeked to as expected. However if we listen to the mp3 file(either without seeking or in an external mp3 player) we realize that the time reported and reality is very different, sometimes even seconds.
For example MediaPlayer.seek(Duration.millis(2000)) results seeking to 0 seconds. A 2 second failure rate is not acceptable.
With WAV it seems to work. Though it does not with MP3.
The two workarounds I think so far are possible:
Writing an MP3 Decoder and Player which doesn't have the bug
Using uncompressed WAV files
Does anyone know anything better?
If anyone needs the source code there isn't much more in it:
public static void main(String[] args) {
MediaPlayer player = null;
JFXPanel fxPanel = new JFXPanel(); //To initialize JavaFX
try {
String url = new File("E:\\Music\\test.mp3").toURI().toURL().toExternalForm();
player = new MediaPlayer(new Media(url));
System.out.println("File loaded!");
} catch (MalformedURLException e) {
//e.printStackTrace();
System.out.println("Error with filename!");
System.exit(0);
}
player.play();
System.out.println("Playing!");
while (true)
{
Scanner reader = new Scanner(System.in);
String string = reader.nextLine();
if (string.equals("Exit")) System.exit(0);
else if (string.equals("Seek"))
{
player.seek(Duration.millis(2000)); //this seeks to the beggining of the file
player.pause();
try {
Thread.sleep(100); //player.getCurrentTime() doesn't update immediately
} catch (InterruptedException e) { }
System.out.println("Time: " + player.getCurrentTime().toMillis() + " / " + player.getTotalDuration().toMillis());
player.play();
}
}
}
I would recommend using the javazoom library. It is an open source java library that already has this stuff written without errors(At least none that I found).
Source
http://www.javazoom.net/index.shtml
Place your call to the seek method off the UI thread or your UI will hang.
new Thread(() ->player.seek(Duration.millis(2000))).start();

disable other sounds in java

I wrote a program in Java using the pi4j lib to make sound whenever a (physical) button is clicked. This program works, but it now plays all the sounds interchangeably. I want that when you click on 2,3,4 or more buttons you only hear one sound.
This is the code I hope you can help.
public class ButtonSoundsProject{
public static void main(String args[]) throws InterruptedException {
System.out.println("Toy has been started!");
// create gpio controller
final GpioController gpio = GpioFactory.getInstance();
// provision gpio pin #02 as an input pin with its internal pull down resistor enabled
GpioPinDigitalInput[] pins = {
gpio.provisionDigitalInputPin(RaspiPin.GPIO_00, PinPullResistance.PULL_DOWN),
gpio.provisionDigitalInputPin(RaspiPin.GPIO_01, PinPullResistance.PULL_DOWN),
gpio.provisionDigitalInputPin(RaspiPin.GPIO_02, PinPullResistance.PULL_DOWN),
gpio.provisionDigitalInputPin(RaspiPin.GPIO_03, PinPullResistance.PULL_DOWN),
gpio.provisionDigitalInputPin(RaspiPin.GPIO_04, PinPullResistance.PULL_DOWN),
gpio.provisionDigitalInputPin(RaspiPin.GPIO_05, PinPullResistance.PULL_DOWN),};
final ArrayList<String> soundList = new ArrayList<String>();
soundList.add("/home/pi/Sounds/Sound1.wav");
soundList.add("/home/pi/Sounds/Sound2.wav");
soundList.add("/home/pi/Sounds/Sound3.wav");
soundList.add("/home/pi/Sounds/Sound4.wav");
soundList.add("/home/pi/Sounds/Sound5.wav");
soundList.add("/home/pi/Sounds/Sound6.wav");
soundList.add("/home/pi/Sounds/Sound7.wav");
soundList.add("/home/pi/Sounds/Sound8.wav");
soundList.add("/home/pi/Sounds/Sound9.wav");
soundList.add("/home/pi/Sounds/Sound10.wav");
soundList.add("/home/pi/Sounds/Sound11.wav");
soundList.add("/home/pi/Sounds/Sound12.wav");
// create and register gpio pin listener
GpioPinListenerDigital listener = new GpioPinListenerDigital() {
#Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
// display pin state on console
final int randomNum = 0 + (int) (Math.random() * 12);
System.out.println(randomNum);
System.out.println(" --> GPIO PIN STATE CHANGE: " + event.getPin() + " = " + event.getState());
InputStream in;
try {
System.out.println(soundList.get(randomNum).toString());
String filepath = soundList.get(randomNum).toString();
in = new FileInputStream(new File(filepath));
AudioStream as = new AudioStream(in);
AudioPlayer.player.start(as);
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
gpio.addListener(listener, pins);
for (;;) {
Thread.sleep(500);
}
}
}
As stated in the comments, I can't give you advise regarding the AudioStream and AudioPlayer classes because I don't seem to have those in my JDK. Since my method is similar, I'll give you what I have, and you can hopefully take it from there.
Basically, the solution is to stop and/or "mute" that audio clip. This is how I accomplish it using the javax.sound package.:
private Clip currentAudioClip; // Keep a reference to the current clip being played
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
// Call this every time regardless.
// If nothing is playing, this will do nothing.
stopAudio();
String filepath = soundList.get(randomNum)
URL soundFileUrl = new File(filePath).toURI().toURL();
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(soundFileUrl);
Line.Info lineInfo = new Line.Info(Clip.class);
Line line = AudioSystem.getLine(lineInfo);
currentAudioClip = (Clip) line;
currentAudioClip.open(audioInputStream);
audioClip.start();
// Alternative if you want to loop continuously. Comment out the `.start` line to use this.
// audioClip.loop(Clip.LOOP_CONTINUOUSLY);
}
public void stopAudio(){
if(audioClip != null){
muteLine(); // A gotcha I discovered (see explanation below)
audioClip.stop();
// audioClip.loop(0); // if you chose to loop, use this instead of `.stop()`
audioClip.flush();
audioClip = null;
}
}
public void muteLine(){
BooleanControl muteControl = (BooleanControl) audioClip.getControl(BooleanControl.Type.MUTE);
if(muteControl != null){
muteControl.setValue(true); // True to mute the line, false to unmute
}
}
In short, every time a pin state change event is fired, the previous audio clip will be ceased, and a new one should play. You shouldn't get any sound overlapping with this.
Also note that this is a slight modification of my original code, so let me know if there are any issues
Note about the GOTCHA
I wrote a question over on the Raspberry PI Stackexchange about an odd problem I encountered. The problem was that I discovered my audio clip would not cease playing on command. It would continue playing for a seemingly arbitrary amount of time. The stranger thing is that I only observed this while testing the app on the raspberry; it worked perfectly fine on my local machine (and even on several other machines).
It is possible my issue is related to the "looping" of my clip; if that is the case, and you simply want the clip to play for its length and no further, you may not encounter that issue, and you can probably dispense with the "muting" code I included. However, if you do observe the same issue, at least you have a solution.
Hope this helps, let me know if you have any questions.

Audio Clip won't loop continuously

Can anyone point me in the right direction as to why this code will not play this audio clip continuously? It plays it once and stops.
final Clip clip = AudioSystem.getClip();
final AudioInputStream inputStream = AudioSystem.getAudioInputStream(new File("Alarm_Police.wav"));
clip.open(inputStream);
clip.loop(Clip.LOOP_CONTINUOUSLY);
If you are running a bigger application, this answer may not apply. But for a simple test with only that piece of code, this may help:
Clip.loop() starts it's own thread, but that thread will not keep the JVM alive. So to make it work, make sure the clip is not the only thread.
If I leave out Thread.sleep(..) from this snippet, I get the same issue as you;
import java.io.File;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
public class Snippet {
public static void main(String[] args) throws Exception {
AudioInputStream inputStream = AudioSystem.getAudioInputStream(new File("notify.wav"));
Clip clip = AudioSystem.getClip();
clip.open(inputStream);
clip.loop(Clip.LOOP_CONTINUOUSLY);
Thread.sleep(10000); // looping as long as this thread is alive
}
}
In order to play the complete audio file you would need to know the length of time to sleep. An alternative would be to:
while(clip.isRunning())
{
Thread.sleep(100);
}
This keeps sleeping (in 100ms increments) until the isRunning() state has turned to false.
You may need an initial sleep before this loop so that the isRunning() state has time to set.
My audio file contains 20 seconds of an alarm beep. I need it continuously to ring. Instead of using thread, i went on with the piece of code shown below.
while(true){
clip.start();
clip.loop(clip.LOOP_CONTINUOUSLY);
}
Think this would help. Thanks.

Determine when to close a sound-playing thread in Java

I am playing of a sound-file in Java, and is looking for a simple way to determine when the sound-file has finished playing so I can kill the thread. Is there a simple way to accomplish this?
Sorry this is a little late, but I just ran into an issue today that sounds suspiciously familiar to this one. In some game code, Im using javax.sound.sampled.Clip to play various sounds, I found that if I didn't explicitly call line.close() once it was finished, the count of native threads in the profiler would just sky-rocket until I got an OutOfMemory error.
// this just opens a line to play the sample
Clip clip = AudioSystem.getClip();
clip.open( audioFormat, sounddata, 0, sounddata.length);
clip.start();
// at this point, there is a native thread created 'behind the scenes'
// unless I added this, it never goes away:
clip.addLineListener( new LineListener() {
public void update(LineEvent evt) {
if (evt.getType() == LineEvent.Type.STOP) {
evt.getLine().close();
}
}
});
My presumption is that the clip creates a thread to meter out the sample bytes into the line, but the thread hangs around after that in case you want to re-use the clip again. My second presumption is that somewhere something in my code must have a reference to the clip, or vice-versa, but at any rate, the snippet above duct-taped the problem.
Hope this is useful to someone.
Comments
You play sound either synchronously or asynchronously.
In case you play it synchronously (blocking the calling thread), you know when the sound ends playing — your code gain control then.
In case you play it asynchronously (allowing a different thread to be created), the helper thread will terminate itself right after finishing playing.
P.S.
Please share your observations on memory leaking and reasons underneath the question.
Why do you want to kill the thread? It will go away on its own once the it terminates.
If you insist, set up a synchronized method you can call to set a "die now" flag; check that flag periodically.
I dont use clips, it takes more time to load in memory, depending the size of the file you are reading.
I preefer reading the bytes, and use this method I created:
public void play(File file) throws UnsupportedAudioFileException, IOException, LineUnavailableException, InterruptedException
{
AudioInputStream encoded = AudioSystem.getAudioInputStream(file);
AudioFormat encodedFormat = encoded.getFormat();
AudioFormat decodedFormat = this.getDecodedFormat(encodedFormat);
line = AudioSystem.getSourceDataLine(decodedFormat);
currentDecoded = AudioSystem.getAudioInputStream(decodedFormat, encoded);
line.open(decodedFormat);
line.start();
byte[] b = new byte[this.bufferSize];
int i = 0;
synchronized(lock){
while(true)
{
i = currentDecoded.read(b, 0, b.length);
if(i == -1)
break;
line.write(b, 0, i);
if(paused == true)
{
line.stop();
lock.wait();
line.start();
}
}
}
line.drain();
line.stop();
line.close();
currentDecoded.close();
encoded.close();
}
it uses this method:
protected AudioFormat getDecodedFormat(AudioFormat format)
{
AudioFormat decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED, // Encoding to use
format.getSampleRate(), // sample rate (same as base format)
16, // sample size in bits (thx to Javazoom)
format.getChannels(), // # of Channels
format.getChannels()*2, // Frame Size
format.getSampleRate(), // Frame Rate
false // Big Endian
);
return decodedFormat;
}

Categories