package org.myrobotlab.audio;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.service.AudioFile;
import org.myrobotlab.service.data.AudioData;
import org.slf4j.Logger;
// FIXME - make runnable
public class AudioProcessor extends Thread {
static Logger log = LoggerFactory.getLogger(AudioProcessor.class);
// REFERENCES -
// http://www.javalobby.org/java/forums/t18465.html
// http://sourcecodebrowser.com/libjlayer-java/1.0/classjavazoom_1_1jl_1_1player_1_1_audio_device_base__coll__graph.png
// - does JavaZoom spi decoder just need to be in the classpath ? - because
// there is not any direct reference to it
// it seems to make sense - some how the file gets decoded enough - so that
// a audio decoder can be slected from some
// internal registry ... i think
Queue<String> commands = new ConcurrentLinkedQueue<String>();
int currentTrackCount = 0;
int trackCount = 0;
float volume = 1.0f;
// float targetVolume = currentVolume;
float targetVolume = volume;
float balance = 0.0f;
float targetBalance = balance;
AudioFile audioFile = null;
public boolean pause = false;
public boolean isPlaying = false;
boolean isRunning = false;
public String queueName;
public boolean repeat;
public String waitForKey = null;
Object lock = new Object();
BlockingQueue<AudioData> track = new LinkedBlockingQueue<AudioData>();
public AudioProcessor(AudioFile audioFile, String queueName) {
super(String.format("%s:track", queueName));
this.audioFile = audioFile;
this.queueName = queueName;
}
// FIXME play with this thread - but block incoming thread
public AudioData play(AudioData data) {
log.info(String.format("playing %s", data.toString()));
AudioInputStream din = null;
try {
File file = new File(data.file);
AudioInputStream in = AudioSystem.getAudioInputStream(file);
AudioFormat baseFormat = in.getFormat();
AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, baseFormat.getSampleRate(), 16, baseFormat.getChannels(), baseFormat.getChannels() * 2,
baseFormat.getSampleRate(), false);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, decodedFormat);
SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
if (line != null) {
line.open(decodedFormat);
byte[] buffer = new byte[4096];
// Start
line.start();
int nBytesRead = 0;
volume = 0; // new file being played or repeat - targetVolume should be
// old volume - needs setting to target
isPlaying = true;
audioFile.invoke("publishAudioStart", data);
while (isPlaying && (nBytesRead = din.read(buffer, 0, buffer.length)) != -1) {
// byte[] goofy = new byte[4096];
/*
* HEE HEE .. if you want to make something sound "bad" i'm sure its
* clipping as 130 pushes some of the values over the high range for
* (int i = 0; i < data.length; ++i){ data[i] = (byte)(data[i] + 130);
* }
*/
if (volume != targetVolume) {
if (line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volume = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
float scaled = (float) (Math.log(targetVolume) / Math.log(10.0) * 20.0);
volume.setValue(scaled);
}
volume = targetVolume;
}
if (balance != targetBalance) {
try {
FloatControl control = (FloatControl) line.getControl(FloatControl.Type.BALANCE);
control.setValue(balance);
balance = targetBalance;
} catch (Exception e) {
Logging.logError(e);
}
}
// BooleanControl
// muteControl=(BooleanControl)source.getControl(BooleanControl.Type.MUTE);
/*
* if (volume == 0) { muteControl.setValue(true); }
*/
// the buffer of raw data could be published from here
// if a reference of the service is passed in
line.write(buffer, 0, nBytesRead);
if (pause) {
// Object lock = myService.getLock(queueName); // getLocks my lock
synchronized (lock) {
log.info("pausing");
// notify all waiting for me
List<Object> locks = audioFile.getLocksWaitingFor(queueName);
for (int i = 0; i < locks.size(); ++i) {
locks.get(i).notifyAll();
}
lock.wait();
}
}
}
// Stop
line.drain();
line.stop();
line.close();
din.close();
audioFile.invoke("publishAudioEnd", data);
} else {
log.error("line is null !");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (din != null) {
try {
din.close();
} catch (IOException e) {
}
}
}
return data;
}
@Override
public void run() {
isRunning = true;
try {
AudioData data = null;
int lastTrackPlayed = currentTrackCount;
while (isRunning) {
if (pause) {
// Object lock = myService.getLock(queueName);
synchronized (lock) {
lock.wait();
}
}
if (!repeat || data == null) {
data = track.take();
++currentTrackCount;
}
// check to see if we should be waiting for another track to finish
if (waitForKey != null) {
Object waitForLock = audioFile.getWaitForLock(waitForKey);
synchronized (waitForLock) {
waitForLock.wait();
}
}
play(data);
if (currentTrackCount != lastTrackPlayed) {
String key = String.format("%s:%s", queueName, currentTrackCount);
Object waitForLock = audioFile.getWaitForLock(key);
if (waitForLock != null) {
synchronized (waitForLock) {
waitForLock.notify();
}
}
}
}
} catch (Exception e) {
isRunning = false;
}
// default waits on queued audio requests
}
public void setVolume(float volume) {
targetVolume = volume;
}
public static void main(String[] args) {
/*
* AudioPlayer player = new AudioPlayer();
*
* // jlp.play("NeroSoundTrax_test1_PCM_Stereo_CBR_16SS_6000Hz.wav");
* AudioData data = new AudioData("aaa.mp3"); // data.volume = 120.0f;
* data.balance = -1;
*
* player.play(data);
*/
}
public float getVolume() {
return volume;
}
public AudioData add(AudioData data) {
++trackCount;
track.add(data); // COOL - should change state to QUEUED'
return data;
}
public Object getLock() {
return lock;
}
public boolean isRunning() {
return isRunning;
}
}