Playing the same sound many times

My problem is that I don’t know how to play play the same clip many times simultaneously…

If an event in the game happens two times I must play the same clip two times.

This is the code I use now:


if(!soundOn) return true;
            
            Clip snd=getSound(name);
            
            if(snd==null){
                  Debug.err("! SoundBank.playSound() Sound '"+name+"' not present!");
                  return false;
            }
            
            if(snd.isRunning()){
                  snd.stop();
            }
            snd.setMicrosecondPosition(0);
            snd.start();

Infact I should not stop the sound, but run another
“instance”…

Any idea?

I would suggest that you load your clip to a byte array and then use AudioSystem.getLine to get a new line for each clip. Here is a short example how to play audio.wav 32 times simultaneously (i.e. 32 polyphony):


import javax.sound.sampled.*;
import java.io.*;

public class MultiClip {
   public static void main(String[] args) {
      try {
         AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File("audio.wav"));
         AudioFormat af                    = audioInputStream.getFormat();
         int size                          = (int) (af.getFrameSize() * audioInputStream.getFrameLength());
         byte[] audio                      = new byte[size];
         DataLine.Info info                = new DataLine.Info(Clip.class, af, size);
         audioInputStream.read(audio, 0, size);

         for(int i=0; i < 32; i++) {
            Clip clip = (Clip) AudioSystem.getLine(info);
            clip.open(af, audio, 0, size);
            clip.start();
         }
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }
}

This works fine.
However when do I clip.stop() any of the 32 samples?
(Because so they run forver, even silently…)

Good solution.
Thanks.

To Archimedes:
The sounds stops when it finish, so there’s no problem
if you don’t stop them…

The only problem is with the GarbageCollector: every
time you play a sound you create a new object that
remains in “suspension” until the GC comes…

[quote]Good solution.
To Archimedes:
The sounds stops when it finish, so there’s no problem
if you don’t stop them…

The only problem is with the GarbageCollector: every
time you play a sound you create a new object that
remains in “suspension” until the GC comes…
[/quote]
Ah I see. Thanks for the info.
The thing with the GC is a kind of problem I think when you play various wav samples repeated times in your game. Very soon your Java application takes more and more memory… and then… it stops when the GC comes? :slight_smile:
“Funny.”
:wink:

Bad news…

This is the class that I use to encapsulate a WAV.
The only propetary function is “Debug(String,int)”, it
simply prints out a string :slight_smile:


class Sample{
      
      /* Number of unstopped sounds */
      static int numberOfActiveClips=0;
      
      /* Number of clips requested */
      static int numberOfRequestedClips=0;
      
      /* Binary data */
      byte[]      wav;
      /* Size of binary data */
      int            size;
      
      DataLine.Info      info;
      AudioFormat            af;
      
      Vector                  clipList=new Vector();
      
      /* Constructor */
      public Sample(AudioInputStream audioInputStream){
            af          = audioInputStream.getFormat();
          size      = (int) (af.getFrameSize() * audioInputStream.getFrameLength());
          wav            = new byte[size];
          info      = new DataLine.Info(Clip.class, af, size);
          
            try{
                audioInputStream.read(wav, 0, size);
             } catch(Exception e){ e.printStackTrace(); }
      }
      
      /* Retrivies a Clip from the stored binary data */
      public Clip getClip(){
            Clip clip,c;
            
            /* Stop and remove finished clips */
            Iterator iter=clipList.iterator();            
            while(iter.hasNext()){
                  c=(Clip)iter.next();
                  if(!c.isRunning()){
                        c.stop();
                        c.flush();
                        iter.remove();
                        numberOfActiveClips--;
                  }
            }
            
            try{
                  clip = (Clip) AudioSystem.getLine(info); 
                    clip.open(af, wav, 0, size); 
              } catch(Exception e){ e.printStackTrace(); return null; }
              
              /* Add this clip to the list of unstopped clips */
              clipList.add(clip);
              numberOfActiveClips++;
              
              numberOfRequestedClips++;
              
              return clip;
      }
      
      public static void debug(){
            Debug.print("-- Number of active clips: "+numberOfActiveClips,0);
            Debug.print("-- Number of requested clips: "+numberOfRequestedClips,0);
      }
}

To resolve the problem that clips may live forever, I check
every time a clip is required, what old clips aren’t running
and I stop them.

If I use ONLY one or two Sample, I can request as many
clips as I want and all goes right.

If I use 5/6 Samples after some requests (exactly 64 sample!) Java VM gives me the
following error:


javax.sound.sampled.LineUnavailableException: No Free Voices
        at com.sun.media.sound.MixerClip.nSetup(Native Method)
        at com.sun.media.sound.MixerClip.getValidVoiceId(MixerClip.java:608)
        at com.sun.media.sound.MixerClip.implOpen(MixerClip.java:590)
        at com.sun.media.sound.MixerClip.open(MixerClip.java:159)
        at fishbros.pyro.Sample.getClip(SoundBank.java:77)
        at fishbros.pyro.SoundBank.getSound(SoundBank.java:201)
        at fishbros.pyro.SoundBank.playSound(SoundBank.java:249)
        at fishbros._teranoid.Ball.controlCollision(Ball.java:421)
        at fishbros._teranoid.Ball.live(Ball.java:489)
        at fishbros.pyro.EntityRoster.makeEmLive(EntityRoster.java:157)
        at fishbros._teranoid.Game.nextFrame(Game.java:290)
        at fishbros.pyro.App.run(App.java:299)
        at java.lang.Thread.run(Thread.java:536)

It seems like there are no more Line avaible!
What can I do more than simply stop() clips to release lines???

And why if I use only two samples all is OK?

PS: When the GC collectors comes it only slow down
your App for few milleseconds.

I’ve discovered that the problem was not related with
the number of samples…

The game that generates a “No voices” error is using
sample PAN (to position audio left or right)


      public void setPan(String name,float pan) {
            Clip clip=getSound(name);
            
            if(clip==null) return;            
            try {
                  FloatControl panControl = 
                        (FloatControl) clip.getControl(FloatControl.Type.PAN);
                  panControl.setValue(pan);
            } catch (Exception ex) {
                  ex.printStackTrace();
            }
      }

It seems there’s no way to release a Clip after you have
set a PAN value on it…

In the other cases the Class is PERFECT!

javax.sound.sampled.LineUnavailableException: No Free Voices 

simply means you don’t have any more free lines left. Seems that your soundcard supports 64 polyphony (mine only supports 32!). I think your problem is that you never close the lines (sorry about my bad example). Stop method does not release the line, it simply stops the playback. As the Java documentation says:

[quote]"public void stop() Stops the line. A stopped line should cease I/O activity. If the line is open and running, however, it should retain the resources required to resume activity. A stopped line should retain any audio data in its buffer instead of discarding it, so that upon resumption the I/O can continue where it left off, if possible.
[/quote]
So… instead of stop() method, try close()

It might make sense to create a sound server class that picks a line that isn’t active and writes the wave data to it… but basically keeps all the lines around to be reused by they next sound it gets. You would set a fixed limit for the number of simultaneous sounds, if you the class is asked to play a sound and all the lines are busy that sound would just get skipped.

DataLine.Info info = new DataLine.Info(Clip.class, af, size);

One potential problem with re-using the DataLines is the code above. DataLine.Info requires that you state the used audio format and buffer size, so if you want to re-use the same line, you have to make sure it supports ALL the needed formats and the buffer size is big enough. Once again, the Java documentation will come to rescue. You can create a DataLine.Info with multiple audio formats:

DataLine.Info(Class lineClass, AudioFormat[] formats, int minBufferSize, int maxBufferSize) 

I perfectly understand what you say…

But simply using stop() instead of close() never
generate a “all lines busy” situation.
Infact the new version of my code breaks the 64
poliphony limit, this means that in an unknown way
lines are really released.

I’ll try using close()…

Using close() instead of stop() fixes the problem with
panned sounds but…

close() is slower than stop() and so using it to
release lines creates choppy sounds…

I think I’ll continue to use stop() and eliminate panned
sounds :slight_smile: