Package net.jsoundsystem

Source Code of net.jsoundsystem.AudioThread

/******************************************************************************
JSoundSystem is a simple and easy sound API to use sound in your Java applications.
Copyright (c) 2014, Johan Jansen
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used
to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************/

package net.jsoundsystem;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import net.jsoundsystem.utils.Vector3f;


class AudioThread extends Thread {

  //Effect mixer modifiers. They are used in a bitmap so each much be a power of two
  private static final int MIX_VOLUME   =    1 << 0;
  private static final int MIX_PANNING   =   1 << 1;
  private static final int MIX_SPEED     =   1 << 2;

  //Thread sound effects
  private boolean looping, paused, stopped;
  private boolean killThread;
 
  private float volume;
  private float panning;
  private float speed;
 
  //3D sound simulation
  private boolean simulate3DEffect;
  private Vector3f source;
  private float lastVolume;

  //Playback data
  private AudioFormat soundFormat;
  private byte[] soundData;
  private File filePath;
  private SourceDataLine audioChannel;

 
  /**
   * Constructs a new SoundThread, should only be called by the SoundSystem
   * @throws IOException
   * @throws UnsupportedAudioFileException
   */
  AudioThread( File path, byte[] data, AudioFormat format ) throws UnsupportedAudioFileException, IOException {
    super( path.getName() );
    filePath = path;
    setPriority( Thread.MIN_PRIORITY );    //Sounds have low priority
    setDaemon(true);            //And run independently
   
    //Set default values
    volume = 1.00f;
    speed = 1.00f;
    panning = 0.00f;
   
    //Get the audio format for this sound
    soundFormat = format;
    soundData = data;
  }
 
  public void enableSpatializedSound(){
    source = new Vector3f();
    simulate3DEffect = true;
  }

  protected void setLooped( boolean looping ){
    this.looping = looping;
  }

  protected void setPanning( float panning ){
    this.panning = panning;
    mixSoundEffects( audioChannel, MIX_PANNING );
  }

  protected void setVolume( float volume ){
    this.volume = volume;
    mixSoundEffects( audioChannel, MIX_VOLUME );
  }

  protected void setSpeed( float speed ){
    this.speed = speed;
    mixSoundEffects( audioChannel, MIX_SPEED );
  }

  /**
   * Internal run function inherited from the Thread class. This is where the actual sound playing
   * happens. This function should not be run directly, but rather activated by the play() function.
   */
  public void run() {

    try {

      //Keep data ready until we are disposed of
      while( !killThread ){
       
        //Open and reserve a sound channel
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, soundFormat);
        audioChannel = (SourceDataLine) AudioSystem.getLine(info);
        audioChannel.open( soundFormat );
        JSoundSystem.channelsPlaying++;
       
        //Ready the sound stream
        InputStream stream = null;
       
        //It's a sound loaded into memory
        if( soundData != null ) {
          stream = new ByteArrayInputStream(soundData);
         
          //Use mark if supported
          if( stream.markSupported() ) stream.mark( stream.available() );
        }

        //This might be do once or in infinity, depending on the loop variable
        do{
          //Reset sound and reopen a sound stream if needed
          if( stream != null && stream.markSupported() ) stream.reset();
          else {
            //Need to reopen the sound to reset it
            if( stream != null) stream.close();
            stream = JSoundSystem.getAudioInputStream(filePath);
            if( stream.markSupported() ) stream.mark( stream.available() );
          }

          //begin playing
          audioChannel.start();

          //Apply various sound effects
          mixSoundEffects( audioChannel, MIX_PANNING | MIX_VOLUME | MIX_SPEED );

          //This actually plays the sound
          int len = 0;         
          int bytesPerFrame = soundFormat.getFrameSize();

          // some audio formats may have unspecified frame size
          // in that case we may read any amount of bytes
          if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) bytesPerFrame = 1;

          // Set an arbitrary buffer size of 1024 frames.
          int numBytes = 1024 * bytesPerFrame;
          byte[] audioBytes = new byte[numBytes];
         
          //Keep playing as long as there is data left and sound has not been stopped
          while ( !stopped && (len = stream.read(audioBytes) ) != -1 ) {
           
            //Pause until we are told to continue
            if( paused ) sleepThread();
           
            //Update 3D sound effects
            if( simulate3DEffect ) update3DSound();

            audioChannel.write(audioBytes, 0, len);         
          }

          //Finish the rest of the data
          if( !stopped ) audioChannel.drain();

          //Release resources
          audioChannel.stop();
          stream.close();
        }
        while( looping && !stopped );

        audioChannel.close();
        audioChannel = null;
        JSoundSystem.channelsPlaying--;

        //Pause until further notice
        sleepThread();
      }
    } catch(LineUnavailableException e){
      System.err.println("Could not play sound ("+ getName() +"): Audio Drivers doesnt support more than " + JSoundSystem.channelsPlaying + "sound channels.");
    } catch (Exception e)  {
      System.err.println("Error playing sound ("+ getName() +"): " + e);
      e.printStackTrace();
    }
   
    //This thread should be killed now
  }

  private void sleepThread(){
    if( killThread ) return;
    paused = true;

    //This causes the thread to sleep until a notify() is called
    synchronized (this) {
      try {
        wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  private void resumeThread(){
    //No need to wake up if we are not sleeping
    if( !paused || killThread ) return;

    synchronized (this) {
      this.notifyAll()//Wake up!
      paused = false;

      if( audioChannel != null ) audioChannel.start();
    }
  }

  /**
   * Begins playing the sound or resumes if it was paused
   */
  protected void play(){
    if( killThread ) return;
    stopped = false;

    //Begin playing for the first time
    if( !this.isAlive() ){
      start();
      return;
    }

    //Already playing, try to unpause
    resumeThread();   
  }

  protected void pause() {
    paused = true;
    if( audioChannel != null ) audioChannel.stop();
  }

  protected void dispose(){
    stopped = true;
    killThread = true;
  }
 
  /**
   * JJ> This updates sound mixer effects like volume and sound balance to an active audio channel
   * @param line Which audio line to update
   * @param effects A bitmask with the effects to update
   */
  private void mixSoundEffects( Line line, int effects ) {
   
    //No need to mix something that isn't playing
    if( audioChannel == null ) return;
         
    //Adjust sound speed
    if( (effects & MIX_SPEED) != 0 && line.isControlSupported(FloatControl.Type.SAMPLE_RATE) ) {
      FloatControl gainControl = (FloatControl)line.getControl(FloatControl.Type.SAMPLE_RATE);
      float sampleRate = soundFormat.getFrameRate() * speed;
      sampleRate = Math.max(gainControl.getMinimum(), Math.min(sampleRate, gainControl.getMaximum()));
      gainControl.setValue(sampleRate);
    }
   
    //Adjust sound balance
    if( (effects & MIX_PANNING) != 0 && line.isControlSupported(FloatControl.Type.PAN) ) {
      FloatControl gainControl = (FloatControl)line.getControl(FloatControl.Type.PAN);
      panning = Math.max(gainControl.getMinimum(), Math.min(panning, gainControl.getMaximum()));
      gainControl.setValue(panning);
    }

    //Set sound volume
    if( (effects & MIX_VOLUME) != 0 && line.isControlSupported(FloatControl.Type.MASTER_GAIN) ) {
      FloatControl gainControl = (FloatControl)line.getControl(FloatControl.Type.MASTER_GAIN)
      float gain = (float)(Math.log(volume)/Math.log(10.0f)*20.0f);
      gain = Math.max(gainControl.getMinimum(), Math.min(gain, gainControl.getMaximum()));
      gainControl.setValue(gain)
    }
  }

  protected boolean isPlaying() {
    return !paused && !stopped;
  }

  protected void stopPlaying(){
    resumeThread();
    stopped = true;
   
    if( audioChannel != null ){
      audioChannel.stop();
      audioChannel.flush();
    }
  }

  private void update3DSound() {
    Vector3f listenerPosition = JSoundSystem.getListenerPosition();
    float distance = listenerPosition.getDistance(source);
   
        //Calculate how loud the sound is
    float newVolume = (JSoundSystem.maxDistance - distance) / JSoundSystem.maxDistance;
      if (newVolume <= 0) newVolume = 0;

        //Calculate if the sound is left or right oriented
        float newPanning = (2.00f/JSoundSystem.maxDistance) * -(listenerPosition.x - source.x);
       
        //Now actually update the effects
        setVolume( (newVolume + lastVolume) / 2);
        setPanning( newPanning );
        lastVolume = newVolume;
  }
 
  protected void setSourcePosition( Vector3f pos ){
    source = pos;
  }

  public boolean isPaused() {
    return paused;
  }
 
  /**
   * This function makes an exact copy of this JSoundThread, also cloning the sound data, format, etc.
   */
  public AudioThread clone() {
    AudioThread copy;
   
    //Try to clone the actual thread
    try {
      copy = new AudioThread( filePath, soundData, soundFormat );
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
   
    //Copy attributes
    copy.volume = this.volume;
    copy.looping = this.looping;
    copy.speed = this.speed;
    copy.source = new Vector3f(source);
   
    //Finished cloning
    return copy;
  }
 
  public AudioFormat getAudioFormat() {
    return soundFormat;
  }
 
 
  public void invertSoundData(){
    //Simply change reference to the byte array instead of actually modifying
    //the array itself. Others might be using the old non-inversed array through
    //the clone() method. Slower, but it's safe.
   
    byte[] inverseData = new byte[soundData.length];
   
    for( int i = 0; i < soundData.length; i++ ){
      inverseData[soundData.length-i-1] = soundData[i];
    }
   
    soundData = inverseData;
  }
}
TOP

Related Classes of net.jsoundsystem.AudioThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.