package apes.models;
import java.awt.Point;
import java.util.Observable;
import javax.sound.sampled.SourceDataLine;
/**
* This class plays an internal format.
*
* @author Johan Andersson (johandy@student.chalmers.se)
*/
public class Player extends Observable implements Runnable
{
/**
* How large is a chunk.
*/
private final static int CHUNK_SIZE = 1024;
/**
* The data line.
*/
private SourceDataLine line;
/**
* Different status a Player can be in.
*/
public enum Status
{
PLAY, PAUSE, STOP, WAIT
};
/**
* This variable holds the current status for the player.
*/
private Status status;
/**
* A Player is always connected to an internal format.
*/
private InternalFormat internalFormat;
/**
* What sample (not samples are we on).
*/
private int currentSample;
/**
* The fixed position.
*/
private int fixed;
/**
* The moving position.
*/
private int moving;
/**
* Tells how long a wind should be. The larger value, the less wind amount.
*/
private int wind;
/**
* This class must be run as a thread. Otherwise nothing can be done while
* playing.
*/
private Thread thread;
/**
* Creates a new <code>Player</code>.
*
* @param internalFormat The internal format that is to be played.
*/
public Player(InternalFormat internalFormat)
{
this.internalFormat = internalFormat;
this.wind = Config.getInstance().getIntOption("wind");
setStatus(Status.WAIT);
thread = new Thread(this);
thread.start();
}
/**
* Plays the internal format.
*/
public void play()
{
setStatus(Status.PLAY);
}
/**
* <p>
* Pauses playing if any.
* </p>
* <p>
* TODO: Should stop at once when pause is pressed. But calling line.stop()
* does not help.
* </p>
*/
public void pause()
{
setStatus(Status.PAUSE);
}
/**
* Stops playing if any.
*/
public void stop()
{
setStatus(Status.STOP);
}
/**
* Goes forward.
*/
public void forward()
{
int temp = currentSample + getWindLength();
int start = getStart();
int stop = getStop();
int max = stop;
if(stop == 0 || start == stop)
{
max = getSampleAmount();
}
setCurrentSample(temp >= max ? max : temp);
}
/**
* Goes backward.
*/
public void backward()
{
int temp = currentSample - getWindLength();
int start = getStart();
int stop = getStop();
int min = start;
if(start == stop)
{
min = 0;
}
setCurrentSample(temp < min ? min : temp);
}
/**
* Returns the wind length.
*
* @return The wins length.
*/
private int getWindLength()
{
return getSampleAmount() / wind;
}
/**
* Returns the selection as a point. x is start and y i stop.
*
* @return The selection.
*/
public Point getSelection()
{
return new Point(getStart(), getStop());
}
/**
* Returns the <code>InternalFormat</code> connected to this player.
*
* @return the <code>InternalFormat</code>.
*/
public InternalFormat getInternalFormat()
{
return internalFormat;
}
/**
* Set the <code>InternalFormat</code> for this player.
*/
public void setInternalFormat(InternalFormat internalFormat)
{
this.internalFormat = internalFormat;
}
/**
* Returns current status.
*
* @return Current status.
*/
public Status getStatus()
{
return this.status;
}
/**
* Sets the status to <code>status</code>
*
* @param status The new status.
*/
private void setStatus(Status status)
{
this.status = status;
}
/**
* Sets the line for this player.
*
* @param line The line.
*/
public void setLine(SourceDataLine line)
{
this.line = line;
}
/**
* Returns true if the player is playing. False otherwise.
*
* @return true if playing. False otherwise.
*/
public boolean isPlaying()
{
return status == Status.PLAY;
}
/**
* Returns the number of samples.
*
* @return The number of samples.
*/
public int getSampleAmount()
{
return internalFormat.getSampleAmount() - 1;
}
/**
* Returns currentSample.
*
* @return currentSample.
*/
public int getCurrentSample()
{
return currentSample;
}
/**
* Sets the current sample position.
*
* @param currentSample The position.
*/
public void setCurrentSample(int currentSample)
{
this.currentSample = currentSample;
setChangedAndNotifyAll();
}
/**
* Returns start position.
*
* @return The start position.
*/
public int getStart()
{
return Math.min(fixed, moving);
}
/**
* Sets the start position.
*
* @param start the start position.
*/
public void setStart(int start)
{
if(moving < fixed)
{
moving = start;
}
else
{
fixed = start;
}
setChangedAndNotifyAll();
}
/**
* Returns stop position.
*
* @return The stop position.
*/
public int getStop()
{
return Math.max(fixed, moving);
}
/**
* Sets the stop position.
*
* @param stop the stop position.
*/
public void setStop(int stop)
{
if(moving > fixed)
{
moving = stop;
}
else
{
fixed = stop;
}
setChangedAndNotifyAll();
}
/**
* Sets the moving mark to <code>mark</code>.
*
* @param mark The <code>mark</code>.
*/
public void setMark(int mark)
{
moving = mark;
setChangedAndNotifyAll();
}
/**
* Change mark closest to <code>mark</code> to <code>mark.</code>.
*
* @param mark The mark.
*/
public void setClosestMark(int mark)
{
int fromMoving = Math.abs(mark - moving);
int fromFixed = Math.abs(mark - fixed);
if(fromMoving > fromFixed)
{
int temp = fixed;
fixed = moving;
moving = temp;
}
setMark(mark);
}
/**
* Set all marks to <code>mark</code>.
*
* @param mark The mark.
*/
public void setAllMarks(int mark)
{
moving = mark;
fixed = mark;
currentSample = mark;
setChangedAndNotifyAll();
}
/**
* Increases <code>currentSample</code> by one step.
*/
private void increaseCurrentSample()
{
setCurrentSample(currentSample + CHUNK_SIZE);
}
/**
* Set changed and notify all observers.
*/
private void setChangedAndNotifyAll()
{
setChanged();
notifyObservers();
}
/**
* Returns true if playing is allowed. False otherwise.
*
* @return True if playing is allowed. False otherwise.
*/
private boolean playingAllowed()
{
int start = getStart();
int stop = getStop();
if((stop != 0 && start != stop && currentSample > stop) || (currentSample > getSampleAmount()))
return false;
return true;
}
/**
* Will run in a thread. And for each time in the loop the current status is
* checked. And depending on status, sound will be played, paused or stopped.
*/
public void run()
{
while(true)
{
switch (status)
{
case WAIT:
break;
case PLAY:
if(playingAllowed())
{
int chunk = CHUNK_SIZE;
if(getSampleAmount() < currentSample + CHUNK_SIZE)
chunk = getSampleAmount() - currentSample;
byte[] data = internalFormat.getChunk(currentSample, chunk);
line.write(data, 0, data.length);
increaseCurrentSample();
}
else
{
if(currentSample > getSampleAmount())
{
stop();
}
else
{
pause();
setCurrentSample(getStart());
}
}
// If this is not present there will be no playing.
try
{
Thread.sleep(0);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
break;
case STOP:
currentSample = getStart() == getStop() ? 0 : getStart();
setStatus(Status.WAIT);
break;
case PAUSE:
setStatus(Status.WAIT);
break;
}
}
}
}