/** * Try to find a mixer that both supports this line and supports * as much line as possible */privatestaticfinalMixergetBestFittedMixer(DataLine.Infoinfo){MixercurrentMixer=null;Mixer.Info[] mi=AudioSystem.getMixerInfo();MixerbestMixer=null;for(inti=0;i<mi.length;i++) {currentMixer=AudioSystem.getMixer(mi[i]);if(currentMixer.isLineSupported(info))if(bestMixer==null||bestMixer.getMaxLines(info)<currentMixer.getMaxLines(info))bestMixer=currentMixer; }//The best mixer cannot be null as AudioSystem.isLineSupported returned truereturn(bestMixer);}

I'm now improving the gain system. I will submit my modifications on the SVN repository of TUER, I encourage you to watch the source code. As there was no license, I had to put one (to avoid some legal problems and because TUER is considered as a 100% open source project), I hope it is not problematic, sorry.

I've looked at the code and I think it is caused by threading bugs. A new thread is started every time play is invoked. This is done without waiting for the previous thread to stop. This causes bugs as the threads change the same shared member variables. I'll try to fix this if/when I get the time and send the changes to kev. Until then I've made a wrapper that creates a new OggClip every time play is invoked. That way OggClip.play() is never invoked more than once. Here it is:

On my view, having to create several clips to play the same sound several times even though one of the clips is still been played is not a bug but what happens when you play a clip that is still been played is a bug.

I think I'm going to rewrite something to play ogg samples because EasyOgg samples are streamed at real time whereas I would like to load data prior to playback in order to get better performance, more accurate control (better stop mechanism) and no more need of creating lots of threads. Maybe Pulpcore has a better support of ogg files.

I thought about trying to fix the stopping bug, but found out that it would be easier to start from scratch. One of the problems is that the constructor takes an InputStream. I only need it to take an URL which makes things much easier. I can just open up a new stream from the URL instead of synchronizing it with the previous player thread. It is trivial to add a constructor that takes a byte array containing the ogg file. So it is easily possible to extend it so the sound can be streamed from memory, without passing an InputStream to the constructor. Also if you use an URL the whole ogg file will not be loaded into memory like EasyOgg does.

I've got a working version that has almost the same api as EasyOgg. I've replaced the setGain with setVolume that converts the normalised value between 0-1 to a gain dB using the following formula:

1

floatgain = (float) (Math.log(volume) / Math.log(10.0) * 20.0);

No need for a -1 "default" gain value.

I also use OggInputStream to decode the file. It is easier to use and it has some bug fixes in it. It will play sounds whose length is less than the buffer size.

Also added a fade duration that will fade the sound in and out on pause, resume and stop. I use it because it sounds better, but I assume it can also be used to remove clicking.

I fixed this bug but I use a different approach, I use sounds (Clip) that are loaded prior to playback to avoid input/output during the game. I will submit my modifications on my SVN repository in some days. Nevertheless, I have modified a little bit the API to fit into my needs and I've been writing a sound manager to be able to play several sounds together but by avoiding any blocking call and by minimizing the use of sound resource, it is very important to prevent the playback from decreasing the frame rate.

About licensing, as EasyOgg is inspired of JOrbisPlayer (under GPL license), TUER is under GPL license and my classes reuses a piece of code written by Vincent Stahl extracted from d3caster (under GPL license), obviously my classes are under GPL license, I hope it is not a problem (don't forget the viral clause).

/*This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/packagesound;

/** * This class handles an OGG sound sample whose data are loaded prior * to playback to improve the performance (instead of streaming at real time). * The only drawback of this method is not to support samples with variable rates. * Moreover, the use is split in several steps to handle the resource carefully. * It is well fitted for programs that require a fine control on sounds, for example * games using lots of noises. It is possible to prepare a sample without * opening it in order to avoid busying a line uselessly. On the other hand, it is * possible to close a sample for the same reason. I do not try to reopen the * underlying clip because it is highly probable to fail and it is more efficient to * close the previous line cleanly and then to get a fresh line by taking into * account the modifications of the context that might have happened between the * latest opening and the latest closure (for example, the mixer used previously might * have become unavailable). * @author Julien Gouesse * */publicfinalclassSample{

/** * Open a sample to prepare it to be played. * Do it prior to play the sample. * @throws IllegalArgumentException */privatevoidopen()throwsIllegalArgumentException{if(clip!=null&&clip.isOpen())thrownewUnsupportedOperationException("Impossible to open an already opened sample");//Now we are sure that the clip is null or closedDataLine.Infoinfo = newDataLine.Info(Clip.class,audioFormat,AudioSystem.NOT_SPECIFIED); if(AudioSystem.isLineSupported(info)) {Mixermixer=getBestFittedMixer(info);try{clip=(Clip)mixer.getLine(info);clip.open(audioFormat,uncompressedDataArray,0,uncompressedDataArray.length); } catch(LineUnavailableExceptionlue) {thrownewIllegalArgumentException("Sound sample not supported",lue);} }elsethrownewIllegalArgumentException("Sound sample not supported, no line supported"); }

/** * Reopen a closed sample * Notice that a new line might be used * @return true if the sample is already usable or if the operation has been successful * false if the operation has failed */publicfinalbooleanreopen(){booleansuccess=true;if(clip!=null&&!clip.isOpen())try{open();}catch(IllegalArgumentExceptioniae) {success=false;}return(success); }

/** * Release the line and the native resources * NB: On Mac, the JVM 1.5 freezes after this call */publicfinalvoidclose(){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to close a closed sample or a never-opened sample");try{clip.close();}catch(SecurityExceptionse) {se.printStackTrace();} }

/** * Play the sample once from the beginning */publicfinalvoidplay(){loop(1); }

/** * Resume a paused sample from the paused frame and play it count times * Do nothing if the sample was not paused * @param count */publicvoidresume(intcount){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to resume a closed sample");if(!clip.isRunning())clip.loop(count); }

/** * Play it indefinitely from the beginning */publicfinalvoidloop(){loop(Clip.LOOP_CONTINUOUSLY); }

/** * Play it count times from the beginning * @param count */publicfinalvoidloop(intcount){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to play a closed sample");if(clip.isRunning())stop(); clip.loop(count); }

/** * Stop the sample and rewind it */publicfinalvoidstop(){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to stop a closed sample");clip.stop();clip.setFramePosition(0); }

/** * Try to find a mixer that both supports this line and supports * as much line as possible */privatestaticfinalMixergetBestFittedMixer(DataLine.Infoinfo){MixercurrentMixer=null;Mixer.Info[] mi=AudioSystem.getMixerInfo();MixerbestMixer=null;for(inti=0;i<mi.length;i++) {currentMixer=AudioSystem.getMixer(mi[i]);if(currentMixer.isLineSupported(info))if(bestMixer==null||bestMixer.getMaxLines(info)<currentMixer.getMaxLines(info))bestMixer=currentMixer; }//The best mixer cannot be null as AudioSystem.isLineSupported returned truereturn(bestMixer); }}

I will add the gain and the balance control later, it doesn't require a lot of time.

That was actually the OggInputStream I was referring to (I wrote it btw) Did not know there were another OggInputStream around.

Anyway, I've attached my new OggClip.

edit: I've added fallback support for loading to JavaSound, so it is possible to send in wav files and it should work.

I was trying it out, but I can't seem to stop the music in my game. The same clip works fine when using your test case.What I'm doing is stopping the music then closing the window and creating another window, but seems the music just keeps on playing.I even tried turning fade off and adding a few second wait. I can't see what is wrong... Also makes a clicking noise for a few seconds.Also be great if there was a setdefaultvolume method.Keep up the good work.

Edit: I think it might be to do with that I'm using another thread to stop it. Any solutions to solve this?

Seems isStopped doesn't return the correct value after something has finished playing.Also line 280, variable "OggInputStream oggIn = new OggInputStream(in)" in OggClip class is never closed.

This should be fixed in the attached version.

Quote from: zammbi

I was trying it out, but I can't seem to stop the music in my game. The same clip works fine when using your test case.What I'm doing is stopping the music then closing the window and creating another window, but seems the music just keeps on playing.I even tried turning fade off and adding a few second wait. I can't see what is wrong... Also makes a clicking noise for a few seconds.

Sorry, but I can't reproduce, and I have no idee what is wrong. In the attached file it tests stopping the clip from a separate thread. Seems to work here.

Quote from: zammbi

Also be great if there was a setdefaultvolume method.

What should it do? The default volume is 1 which will play the clip without changing the gain. A volume of 0 will mute. Volume larger than 1 is allowed and will increase the sound.

I don't know if someone might be interested in what I have written in order to play ogg sounds... I've tested it and it works fine, it is very accurate, I only have some problems when I resume a loop. I'm going to commit my modifications on my SVN repository.

My source code is below, the method void resume(int count) doesn't work, I tried to use setLoopPoints, flush, start, etc... without success. If you have an idea, please let me know. You need the class OggInputStream (I gave the pointer to download it in a previous post above) to compile and use my class. Don't forget to open a sample before using it.

/*This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/packagesound;

/** * This class handles an OGG sound sample whose data are loaded prior * to playback to improve the performance (instead of streaming at real time). * The only drawback of this method is not to support samples with variable rates. * Moreover, the use is split in several steps to handle the resource carefully. * It is well fitted for programs that require a fine control on sounds, for example * games using lots of noises. It is possible to prepare a sample without * opening it in order to avoid busying a line uselessly. On the other hand, it is * possible to close a sample for the same reason. I do not try to reopen the * underlying clip because it is highly probable to fail and it is more efficient to * close the previous line cleanly and then to get a fresh line by taking into * account the modifications of the context that might have happened between the * latest opening and the latest closure (for example, the mixer used previously might * have become unavailable). * @author Julien Gouesse * */publicfinalclassSample{

/** * Open a sample to prepare it to be played. * Do it prior to play the sample. * @throws IllegalArgumentException */publicfinalvoidopen()throwsIllegalArgumentException{if(clip!=null&&clip.isOpen())thrownewUnsupportedOperationException("Impossible to open an already opened sample");//Now we are sure that the clip is null or closedDataLine.Infoinfo = newDataLine.Info(Clip.class,audioFormat,AudioSystem.NOT_SPECIFIED); if(AudioSystem.isLineSupported(info)) {Mixermixer=getBestFittedMixer(info);try{clip=(Clip)mixer.getLine(info);clip.open(audioFormat,uncompressedDataArray,0,uncompressedDataArray.length); } catch(LineUnavailableExceptionlue) {thrownewIllegalArgumentException("Sound sample not supported",lue);} }elsethrownewIllegalArgumentException("Sound sample not supported, no line supported"); }

/** * Reopen a closed sample * Notice that a new line might be used * @return true if the sample is already usable or if the operation has been successful * false if the operation has failed */publicfinalbooleanreopen(){booleansuccess=true;if(clip!=null&&!clip.isOpen())try{open();}catch(IllegalArgumentExceptioniae) {success=false;}return(success); }

/** * Release the line and the native resources * NB: On Mac, the JVM 1.5 freezes after this call */publicfinalvoidclose(){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to close a closed sample or a never-opened sample");try{clip.close();}catch(SecurityExceptionse) {se.printStackTrace();} }

/** * Play the sample once from the beginning */publicfinalvoidplay(){loop(1); }

/** * Resume a paused sample from the paused frame and play it count times * Do nothing if the sample was not paused * @param count */publicvoidresume(intcount){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to resume a closed sample"); if(!clip.isRunning()) {//FIXME: the line below should not be used, that's the only way I've found//to repair this class when resuming a loop//clip.setFramePosition(0);clip.loop(count); } }

/** * Play it indefinitely from the beginning */publicfinalvoidloop(){loop(Clip.LOOP_CONTINUOUSLY); }

/** * Play it count times from the beginning * @param count */publicfinalvoidloop(intcount){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to play a closed sample");if(clip.isRunning())stop(); clip.loop(count); }

/** * Stop the sample and rewind it */publicfinalvoidstop(){if((clip!=null&&!clip.isOpen())||clip==null)thrownewUnsupportedOperationException("Impossible to stop a closed sample");clip.stop();clip.setFramePosition(0); }

/** * Try to find a mixer that both supports this line and supports * as much line as possible */privatestaticfinalMixergetBestFittedMixer(DataLine.Infoinfo){MixercurrentMixer=null;Mixer.Info[] mi=AudioSystem.getMixerInfo();MixerbestMixer=null;for(inti=0;i<mi.length;i++) {currentMixer=AudioSystem.getMixer(mi[i]);if(currentMixer.isLineSupported(info))if(bestMixer==null||bestMixer.getMaxLines(info)<currentMixer.getMaxLines(info))bestMixer=currentMixer; }//The best mixer cannot be null as AudioSystem.isLineSupported returned truereturn(bestMixer); }}

Haven't changed it since last time I posted. I have to admit that playing a sound is a fairly heavy operation. Since it creates a new thread. You could modify the code to add a playOnceThenPause method. I don't have the time to add it. I also think it is too specialized for it to be a valuable addition to the class. However I will help you if you want to do it yourself. You have to set a flag that will pause the playback after playing it once. At line 423 you have to check for this flag and set the paused flag to true.

I've discovered another problem with OggClip when I used it in my application. Streaming more than one sound with JavaSound will cause stuttering. It's because the JavaSound implementation is crap/broken.

java-gaming.org is not responsible for the content posted by its members, including references to external websites,
and other references that may or may not have a relation with our primarily
gaming and game production oriented community.
inquiries and complaints can be sent via email to the info‑account of the
company managing the website of java‑gaming.org