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

public final class SoundPlayer implements LineListener {
	String _filepath;						// path to the file to be loaded
	
	Clip _clip;								// Java Sound API
	FloatControl _gainCtl;			// Mixer Control for GAIN CHANGE
	FloatControl _panCtl;			// Mixer Control for PANNING	
	
	public boolean 	_isReady	= false;		// if ready to play 
	public double 	_duration	= 0;			// duration in ms
	private boolean _playing 	= false;		// if the file actually playing or not
    private boolean _verbose	= false;		

	public SoundPlayer(String path) {
		this._filepath = path;	
       	this._isReady = loadClip(_filepath);
		prepareControls();						
    }
	
	private boolean loadClip(String path){
		try{
			// 1. access the audio file as a  stream
			AudioInputStream stream = AudioSystem.getAudioInputStream(getClass().getResource(path));
			
			// 2. Get the original audio format of the file and convert it to a suitable format (if necessary)
			AudioFormat format = stream.getFormat();
			
			if ( (format.getEncoding() == AudioFormat.Encoding.ULAW) ||
	           (format.getEncoding() == AudioFormat.Encoding.ALAW)) {
	            AudioFormat newFormat = 
	                 new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
	                                format.getSampleRate(),
	                                format.getSampleSizeInBits()*2,
	                                format.getChannels(),
	                                format.getFrameSize()*2,
	                                format.getFrameRate(), true);  // big endian
		
				stream = AudioSystem.getAudioInputStream(newFormat, stream);
				System.out.println("Convert audio fomat: " + newFormat);
				format = newFormat;
			}
			
			// 3. Gather information for line creation
		      DataLine.Info info = new DataLine.Info(Class.forName("javax.sound.sampled.Clip"), format);
			
			// 4. Check if the DataLine format is supported or not on this system
			if (!AudioSystem.isLineSupported(info)){
				System.out.println("Unsupported Clip File: " + path);
				return false;
			}
			
			// 5. Make Empty clip
			_clip = (Clip)AudioSystem.getLine(info);	// Clip is a subclass of the DataLine class
			
			// 6. Start monitoring line event
			_clip.addLineListener(this);
			
			// 7. Open the InputStream
			_clip.open(stream);
			
			// 8. Get duration in MillSeconds
			_duration = _clip.getMicrosecondLength() / 1000.0;
			
			// 9. Close Input Stream when Done
			stream.close();  			// done with input stream
		}
		catch (Exception e){
			System.out.println("Cannot load sound file. Unknown error occurred.");
			return false;
		}
		// return true if suceeds.
		return true;	
	}
	
	private void prepareControls(){
		if (_clip == null) return; 		// do nothing
	
		// Volume Control
		if (_clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
			_gainCtl = (FloatControl) _clip.getControl(FloatControl.Type.MASTER_GAIN);
			if (_verbose) System.out.println("Gain control found.");
		} else { 
			System.out.println("MASTER_GAIN Control is Not Avaliable"); 	
		}		
		
		// J2SE 1.3 - 1.4 
		if (_clip.isControlSupported(FloatControl.Type.PAN)) {
			_panCtl = (FloatControl) _clip.getControl(FloatControl.Type.PAN);
			if (_verbose) System.out.println("Pan control found.");
		}
		// J2SE 1.5 has BALANCE Control
		else if (_clip.isControlSupported(FloatControl.Type.BALANCE)) {
			_panCtl = (FloatControl) _clip.getControl(FloatControl.Type.BALANCE);
			if (_verbose) System.out.println("BALANCE control found.");
		} else {
			System.out.println("Neither PAN nor BALANCE Control is Not Avaliable"); 
		}	
	}
	
	public void play(){
		if (_clip != null){
			if (_verbose) System.out.println("start");
			_clip.start();
			_playing = true;
		} 
	}

	public void update(LineEvent event){
		if (event.getType() == LineEvent.Type.STOP){			
			if (_verbose) System.out.println("stop");
			_clip.stop();
			_playing = false;
		}
	}
	
	public void setGain(float vol){
		if (_gainCtl==null) {
			if (_verbose) System.out.println("MASTER_GAIN Control is not available.");
			return; // 
		}
		
		if (vol < 0.0 ) vol = 0.0f;
		else if (vol > 1.0) vol = 1.0f;
		
		float range = _gainCtl.getMaximum() - _gainCtl.getMinimum();
		float gain = (range * vol) + _gainCtl.getMinimum();
		_gainCtl.setValue(gain);	
	}
	
	public void setPan(float pan){
		if (_panCtl==null) {
			if (_verbose) System.out.println("PAN/BALANCE Control is not available.");
			return; // 
		}
		
		if (pan < -1.0 ) pan = -1.0f;
		else if (pan > 1.0) pan = 1.0f;
		
		_panCtl.setValue(pan);	
	}
	
	public void setVerbose(boolean value){ _verbose = value; }
	
	public double durationInSec(){
		return _duration/1000.0; 	// duration in sec
	}
	
	
}
