package ddf.minim;
import java.io.IOException;
import java.util.Map;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javazoom.spi.mpeg.sampled.convert.DecodedMpegAudioInputStream;
import org.tritonus.share.sampled.AudioUtils;
import org.tritonus.share.sampled.FloatSampleBuffer;
class MMP3File extends Thread implements AudioRecording {
private SignalSplitter splitter;
private EffectsChain effects;
// file reading stuff
private String fileName;
private Map properties;
private long lengthInMillis;
private boolean play;
private boolean loop;
private int numLoops;
private DecodedMpegAudioInputStream ais;
private byte[] rawBytes;
// line writing stuff
private AudioFormat format;
private SourceDataLine line;
private FloatSampleBuffer buffer;
private boolean finished; //Sources are invalid now
private boolean STEREO_OUTPUT = true; //Bypass stereoline for faster samples to listeners
private boolean atEOF; //Symbolizes whether the "cursor" is at the end of the file.
MMP3File(String fn, Map props, AudioInputStream stream, SourceDataLine sdl,
int bufferSize) {
format = sdl.getFormat();
line = sdl;
buffer = new FloatSampleBuffer(format.getChannels(), bufferSize, format
.getSampleRate());
Minim.debug("FloatSampleBuffer has " + buffer.getSampleCount()
+ " samples.");
finished = false;
fileName = fn;
properties = props;
if (properties != null && properties.containsKey("duration")) {
Long dur = (Long) properties.get("duration");
lengthInMillis = dur.longValue() / 1000;
} else {
lengthInMillis = -1;
}
play = loop = false;
numLoops = 0;
ais = (DecodedMpegAudioInputStream) stream;
rawBytes = new byte[buffer.getByteArrayBufferSize(format)];
splitter = new SignalSplitter(format, bufferSize);
effects = new EffectsChain();
}
public void run() {
try {
line.open(format, bufferSize() * format.getFrameSize() * 4);
} catch (LineUnavailableException e) {
Minim.error("Error opening SourceDataLine: " + e.getMessage());
}
line.start();
while (!finished) {
//int size = buffer.getByteArrayBufferSize(ais.getFormat());
rawBytes = new byte[rawBytes.length];
atEOF = false;
try {
// read bytes if we're playing
if (play) {
int toRead = rawBytes.length;
int bytesRead = 0;
while (bytesRead < toRead) {
int actualRead = ais.read(rawBytes, bytesRead, toRead
- bytesRead);
// -1 means end of file
if (actualRead == -1) {
if (loop) {
// reset the stream
if (numLoops == Clip.LOOP_CONTINUOUSLY) {
rewind();
}
// reset the stream, decrement loop count
else if (numLoops > 0) {
rewind();
numLoops--;
}
// otherwise just stop playing
else {
loop = false;
play = false;
atEOF = true;
}
} else {
atEOF = true;
}
} // if bytesRead == -1
else {
atEOF = false;
bytesRead += actualRead;
}
}// while bytesRead < toRead
} // if play
} catch (IOException e) {
Minim.error("AudioPlayer: error reading from the file - "
+ e.getMessage());
}
// convert the bytes to floating point samples
int frameCount = rawBytes.length / format.getFrameSize();
buffer.setSamplesFromBytes(rawBytes, 0, format, 0, frameCount);
// process the samples and broadcast them to our listeners
if (type() == Minim.MONO) {
float[] samp = buffer.getChannel(0);
if (effects.hasEnabled()) {
effects.process(samp);
}
splitter.samples(samp);
} else {
float[] sampL = buffer.getChannel(0);
float[] sampR = buffer.getChannel(1);
if (effects.hasEnabled()) {
effects.process(sampL, sampR);
}
splitter.samples(sampL, sampR);
}
// finally convert them back to bytes and write to our line
byte[] bytes = buffer.convertToByteArray(format);
if (STEREO_OUTPUT)
line.write(bytes, 0, bytes.length);
}
line.drain();
line.stop();
line.close();
line = null;
try {
ais.close();
} catch (IOException e) {
Minim.error("Couldn't close the stream");
}
ais = null;
}
public void play() {
play = true;
loop = false;
}
public void play(int millis) {
cue(millis);
play();
}
public boolean isPlaying() {
return play;
}
public void pause() {
play = false;
}
public void rewind() {
Minim.debug("Rewinding...");
try {
ais.close();
} catch (IOException e) {
Minim.error("Couldn't close the stream.");
}
AudioInputStream encIn = Minim.getAudioInputStream(fileName);
// converts the stream to PCM audio from mp3 audio
ais = (DecodedMpegAudioInputStream) Minim.getAudioInputStream(format,
encIn);
}
public void loop() {
loop(Clip.LOOP_CONTINUOUSLY);
}
public void loop(int n) {
loop = true;
numLoops = n;
play = true;
}
public int length() {
return (int) lengthInMillis;
}
public int position() {
Long pos = (Long) ais.properties().get("mp3.position.microseconds");
return (int) (pos.longValue() / 1000);
}
public void cue(int millis) {
if (millis < 0) {
rewind();
return;
}
if (millis > length())
millis = length();
///XXX???
if (millis > position()) {
Minim.debug("Skipping for cue.");
skip(millis - position());
} else {
Minim.debug("Rewind and skip for cue.");
rewind();
skip(millis);
}
}
public void skip(int millis) {
if (millis > 0) {
if (false) { //HACKED WAY:
boolean playstate = play;
play = false;
long toSkip = position() - millis;
ais.skip(-toSkip);
play = playstate;
return;
}
Minim.debug("Skipping forward by " + millis + " milliseconds.");
// if it puts us past the end of the file, only skip what's left
if (position() + millis > length()) {
millis = length() - position();
}
Minim.debug("Skipping " + millis + " millis.");
// don't want the io thread to read while we're skipping
boolean playstate = play;
play = false;
long toSkip = AudioUtils.millis2Bytes(millis, format);
byte[] skipBytes = new byte[(int) toSkip];
long totalSkipped = 0;
try {
// it's only able to read about 2 seconds at a time
// so we've got to loop until we've skipped the requested amount
while (totalSkipped < toSkip) {
totalSkipped += ais.read(skipBytes, 0, skipBytes.length);
}
} catch (IOException e) {
Minim.error("Unable to skip due to read error: "
+ e.getMessage());
}
Minim.debug("Total actually skipped was " + totalSkipped
+ ", which is "
+ AudioUtils.bytes2Millis(totalSkipped, format)
+ " milliseconds.");
// restore the play state
play = playstate;
} else if (millis < 0) {
Minim
.debug("Skipping backwards by " + (-millis)
+ " milliseconds.");
// to skip backwards we need to rewind
// and then cue to the new position
// remember that millis is negative, that's why we add
if (position() > 0) {
int pos = position() + millis;
rewind();
if (pos > 0) {
cue(pos);
}
}
}
}
public boolean isLooping() {
return loop;
}
public int type() {
return format.getChannels();
}
public void open() {
start();
}
public void close() {
finished = true;
}
public void addEffect(AudioEffect effect) {
effects.add(effect);
}
public void clearEffects() {
effects.clear();
}
public void disableEffect(int i) {
effects.disable(i);
}
public void disableEffect(AudioEffect effect) {
effects.disable(effect);
}
public int effectCount() {
return effects.size();
}
public void effects() {
effects.enableAll();
}
public boolean hasEffect(AudioEffect e) {
return effects.contains(e);
}
public void enableEffect(int i) {
effects.enable(i);
}
public void enableEffect(AudioEffect effect) {
effects.enable(effect);
}
public AudioEffect getEffect(int i) {
return effects.get(i);
}
public boolean isEffected() {
return effects.hasEnabled();
}
public boolean isEnabled(AudioEffect effect) {
return effects.isEnabled(effect);
}
public void noEffects() {
effects.disableAll();
}
public void removeEffect(AudioEffect effect) {
effects.remove(effect);
}
public AudioEffect removeEffect(int i) {
return effects.remove(i);
}
public void addListener(AudioListener listener) {
splitter.addListener(listener);
}
public int bufferSize() {
return splitter.bufferSize();
}
public AudioFormat getFormat() {
return format;
}
public void removeListener(AudioListener listener) {
splitter.removeListener(listener);
}
public DataLine getDataLine() {
return line;
}
public float sampleRate() {
return splitter.sampleRate();
}
public Map getProperties() {
return this.properties;
}
public AudioInputStream getAudioInputStream() {
return ais;
}
public void hearNot(boolean ByPassSpeakerSystem) {
STEREO_OUTPUT = !ByPassSpeakerSystem;
}
public boolean isAtEndOfStream() {
return atEOF;
}
}