package ae.audio;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import com.jcraft.jogg.Packet;
import com.jcraft.jogg.Page;
import com.jcraft.jogg.StreamState;
import com.jcraft.jogg.SyncState;
import com.jcraft.jorbis.Block;
import com.jcraft.jorbis.Comment;
import com.jcraft.jorbis.DspState;
import com.jcraft.jorbis.Info;
//TODO: REWRITE THIS!!!
public class OGGLoader {
private final int BUFSIZE = 4096 * 2;
private int convsize = BUFSIZE * 2;
private byte[] convbuffer = new byte[convsize];
private SyncState oy;
private StreamState os;
private Page og;
private Packet op;
private Info vi;
private Comment vc;
private DspState vd;
private Block vb;
private SourceDataLine outputLine;
private int rate;
private int channels;
private BufferedInputStream bitStream=null;
private byte[] buffer=null;
private int bytes=0;
private Thread player=null;
private float balance;
private float gain = -1;
private boolean paused;
// private float oldGain;
/**
* Create a new clip based on a reference into the class path
*
* @param ref The reference into the class path which the ogg can be read from
* @throws IOException Indicated a failure to find the resource
*/
public OGGLoader(URL ref) throws IOException {
try {
init(ref.openStream());
} catch (IOException e) {
throw new IOException("Couldn't find: "+ref);
}
}
/**
* Create a new clip based on a reference into the class path
*
* @param in The stream from which the ogg can be read from
* @throws IOException Indicated a failure to read from the stream
*/
public OGGLoader(InputStream in) throws IOException {
init(in);
}
/**
* Set the default gain value (default volume)
*/
public void setDefaultGain() {
setGain(-1);
}
/**
* Attempt to set the global gain (volume ish) for the play back. If the control is not supported
* this method has no effect. 1.0 will set maximum gain, 0.0 minimum gain
*
* @param gain The gain value
*/
public void setGain(float gain) {
if (gain != -1) {
if ((gain < 0) || (gain > 1)) {
throw new IllegalArgumentException("Volume must be between 0.0 and 1.0");
}
}
this.gain = gain;
if (outputLine == null) {
return;
}
try {
FloatControl control = (FloatControl) outputLine.getControl(FloatControl.Type.MASTER_GAIN);
if (gain == -1) {
control.setValue(0);
} else {
float max = control.getMaximum();
float min = control.getMinimum(); // negative values all seem to be zero?
float range = max - min;
control.setValue(min+(range*gain));
}
} catch (IllegalArgumentException e) {
// gain not supported
e.printStackTrace();
}
}
/**
* Attempt to set the balance between the two speakers. -1.0 is full left speak, 1.0 if full right speaker.
* Anywhere in between moves between the two speakers. If the control is not supported
* this method has no effect
*
* @param balance The balance value
*/
public void setBalance(float balance) {
this.balance = balance;
if (outputLine == null) {
return;
}
try {
FloatControl control = (FloatControl) outputLine.getControl(FloatControl.Type.BALANCE);
control.setValue(balance);
} catch (IllegalArgumentException e) {
// balance not supported
}
}
/**
* Check the state of the play back
*
* @return True if the playback has been stopped
*/
private boolean checkState() {
while (paused && (player != null)) {
synchronized (player) {
if (player != null) {
try {
player.wait();
} catch (InterruptedException e) {
// ignored
}
}
}
}
return stopped();
}
/**
* Pause the play back
*/
public void pause() {
paused = true;
// oldGain = gain;
setGain(0);
}
/**
* Check if the stream is paused
*
* @return True if the stream is paused
*/
public boolean isPaused() {
return paused;
}
// /**
// * Resume the play back
// */
// public void resume() {
// if (!paused) {
// play();
// return;
// }
//
// paused = false;
//
// synchronized (player) {
// if (player != null) {
// player.notify();
// }
// }
// setGain(oldGain);
// }
/**
* Check if the clip has been stopped
*
* @return True if the clip has been stopped
*/
public boolean stopped() {
return ((player == null) || (!player.isAlive()));
}
/**
* Initialise the ogg clip
*
* @param in The stream we're going to read from
* @throws IOException Indicates a failure to read from the stream
*/
private void init(InputStream in) throws IOException {
if (in == null) {
throw new IOException("Couldn't find input source");
}
bitStream = new BufferedInputStream(in);
bitStream.mark(Integer.MAX_VALUE);
}
/**
* Play the clip once
*/
public Thread playerThread() {
stop();
try {
bitStream.reset();
} catch (IOException e) {
// ignore if no mark
}
player = new Thread() {
public void run() {
try {
playStream(Thread.currentThread());
} catch (InternalException e) {
e.printStackTrace();
}
try {
bitStream.reset();
} catch (IOException e) {
e.printStackTrace();
}
};
};
player.setDaemon(true);
return player;
}
/**
* Loop the clip - maybe for background music
*/
public void loop() {
stop();
try {
bitStream.reset();
} catch (IOException e) {
// ignore if no mark
}
player = new Thread() {
public void run() {
while (player == Thread.currentThread()) {
try {
playStream(Thread.currentThread());
} catch (InternalException e) {
e.printStackTrace();
player = null;
}
try {
bitStream.reset();
} catch (IOException e) {
}
}
};
};
player.setDaemon(true);
player.start();
}
public void play() {
stop();
try {
bitStream.reset();
} catch (IOException e) {
// ignore if no mark
}
player = new Thread() {
public void run() {
try {
playStream(Thread.currentThread());
} catch (InternalException e) {
e.printStackTrace();
player = null;
}
};
};
player.setDaemon(true);
player.start();
}
/**
* Stop the clip playing
*/
public void stop() {
if (stopped()) {
return;
}
outputLine.flush();
player = null;
}
/**
* Close the stream being played from
*/
public void close() {
try {
if (bitStream != null)
bitStream.close();
} catch (IOException e) {
}
}
/*
* Taken from the JOrbis Player
*/
private void initJavaSound(int channels, int rate) {
try {
AudioFormat audioFormat = new AudioFormat(rate, 16,
channels, true, // PCM_Signed
false // littleEndian
);
DataLine.Info info = new DataLine.Info(SourceDataLine.class,
audioFormat, AudioSystem.NOT_SPECIFIED);
if (!AudioSystem.isLineSupported(info)) {
throw new Exception("Line " + info + " not supported.");
}
try {
outputLine = (SourceDataLine) AudioSystem.getLine(info);
// outputLine.addLineListener(this);
outputLine.open(audioFormat);
} catch (LineUnavailableException ex) {
throw new Exception("Unable to open the sourceDataLine: " + ex);
} catch (IllegalArgumentException ex) {
throw new Exception("Illegal Argument: " + ex);
}
this.rate = rate;
this.channels = channels;
setBalance(balance);
setGain(gain);
} catch (Exception ee) {
System.out.println(ee);
}
}
/*
* Taken from the JOrbis Player
*/
private SourceDataLine getOutputLine(int channels, int rate) {
if (outputLine == null || this.rate != rate
|| this.channels != channels) {
if (outputLine != null) {
outputLine.drain();
outputLine.stop();
outputLine.close();
}
initJavaSound(channels, rate);
outputLine.start();
}
return outputLine;
}
/*
* Taken from the JOrbis Player
*/
private void initJOrbis(){
oy=new SyncState();
os=new StreamState();
og=new Page();
op=new Packet();
vi=new Info();
vc=new Comment();
vd=new DspState();
vb=new Block(vd);
buffer=null;
bytes=0;
oy.init();
}
/*
* Taken from the JOrbis Player
*/
private void playStream(Thread me) throws InternalException {
boolean chained = false;
initJOrbis();
while (true) {
if (checkState()) {
return;
}
int eos = 0;
int index = oy.buffer(BUFSIZE);
buffer = oy.data;
try {
bytes = bitStream.read(buffer, index, BUFSIZE);
} catch (Exception e) {
throw new InternalException(e);
}
oy.wrote(bytes);
if (chained) {
chained = false;
} else {
if (oy.pageout(og) != 1) {
if (bytes < BUFSIZE)
break;
throw new InternalException("Input does not appear to be an Ogg bitstream.");
}
}
os.init(og.serialno());
os.reset();
vi.init();
vc.init();
if (os.pagein(og) < 0) {
// error; stream version mismatch perhaps
throw new InternalException("Error reading first page of Ogg bitstream data.");
}
if (os.packetout(op) != 1) {
// no page? must not be vorbis
throw new InternalException("Error reading initial header packet.");
}
if (vi.synthesis_headerin(vc, op) < 0) {
// error case; not a vorbis header
throw new InternalException("This Ogg bitstream does not contain Vorbis audio data.");
}
int i = 0;
while (i < 2) {
while (i < 2) {
if (checkState()) {
return;
}
int result = oy.pageout(og);
if (result == 0)
break; // Need more data
if (result == 1) {
os.pagein(og);
while (i < 2) {
result = os.packetout(op);
if (result == 0)
break;
if (result == -1) {
throw new InternalException("Corrupt secondary header. Exiting.");
}
vi.synthesis_headerin(vc, op);
i++;
}
}
}
index = oy.buffer(BUFSIZE);
buffer = oy.data;
try {
bytes = bitStream.read(buffer, index, BUFSIZE);
} catch (Exception e) {
throw new InternalException(e);
}
if (bytes == 0 && i < 2) {
throw new InternalException("End of file before finding all Vorbis headers!");
}
oy.wrote(bytes);
}
convsize = BUFSIZE / vi.channels;
vd.synthesis_init(vi);
vb.init(vd);
float[][][] _pcmf = new float[1][][];
int[] _index = new int[vi.channels];
getOutputLine(vi.channels, vi.rate);
while (eos == 0) {
while (eos == 0) {
if (player != me) {
return;
}
int result = oy.pageout(og);
if (result == 0)
break; // need more data
if (result == -1) { // missing or corrupt data at this page
// position
// System.err.println("Corrupt or missing data in
// bitstream;
// continuing...");
} else {
os.pagein(og);
if (og.granulepos() == 0) { //
chained = true; //
eos = 1; //
break; //
} //
while (true) {
if (checkState()) {
return;
}
result = os.packetout(op);
if (result == 0)
break; // need more data
if (result == -1) { // missing or corrupt data at
// this page position
// no reason to complain; already complained
// above
// System.err.println("no reason to complain;
// already complained above");
} else {
// we have a packet. Decode it
int samples;
if (vb.synthesis(op) == 0) { // test for
// success!
vd.synthesis_blockin(vb);
}
while ((samples = vd.synthesis_pcmout(_pcmf,
_index)) > 0) {
if (checkState()) {
return;
}
float[][] pcmf = _pcmf[0];
int bout = (samples < convsize ? samples
: convsize);
// convert doubles to 16 bit signed ints
// (host order) and
// interleave
for (i = 0; i < vi.channels; i++) {
int ptr = i * 2;
// int ptr=i;
int mono = _index[i];
for (int j = 0; j < bout; j++) {
int val = (int) (pcmf[i][mono + j] * 32767.);
if (val > 32767) {
val = 32767;
}
if (val < -32768) {
val = -32768;
}
if (val < 0)
val = val | 0x8000;
convbuffer[ptr] = (byte) (val);
convbuffer[ptr + 1] = (byte) (val >>> 8);
ptr += 2 * (vi.channels);
}
}
outputLine.write(convbuffer, 0, 2
* vi.channels * bout);
vd.synthesis_read(bout);
}
}
}
if (og.eos() != 0)
eos = 1;
}
}
if (eos == 0) {
index = oy.buffer(BUFSIZE);
buffer = oy.data;
try {
bytes = bitStream.read(buffer, index, BUFSIZE);
} catch (Exception e) {
throw new InternalException(e);
}
if (bytes == -1) {
break;
}
oy.wrote(bytes);
if (bytes == 0)
eos = 1;
}
}
os.clear();
vb.clear();
vd.clear();
vi.clear();
}
oy.clear();
}
private class InternalException extends Exception {
private static final long serialVersionUID = 1L;
public InternalException(Exception e) {
super(e);
}
public InternalException(String msg) {
super(msg);
}
}
}