--- /dev/null
+package javajs.util;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.SourceDataLine;
+
+import sun.audio.AudioData;
+import sun.audio.AudioDataStream;
+import sun.audio.AudioPlayer;
+
+
+/**
+ * The JSAudioThread adds a layer to JSThread that is specific for audio.
+ * This class utilizes JSAudioLine, which is not fully fleshed out.
+ *
+ * Two very simple interfaces,
+ *
+ * (new JSAudioThread()).playULawData(byte[] b);
+ *
+ * and
+ *
+ * (new JSAudioThread(audioFormat)).playOnce(byte[] b, offset, length);
+ *
+ * allow straightforward audio production without having to work with
+ * SourceDataLine directly.
+ *
+ * As a JSThread, this class implements myInit(), isLooping(), myLoop(),
+ * whenDone(), onException(), and doFinally().
+ *
+ * If the constructor JSAudioThread(JSAudioThreadUser, AudioFormat, byte[]) is
+ * used, then the JSAudioThreadUser must simply implement fillAudioBuffer(),
+ * checkSoundStatus(), and audioThreadExiting() for very simple streaming audio.
+ * JSAudioThread will then take care of all the timing issues.
+ *
+ * But the implementer is welcome to override any of the JSThread overrides
+ * themselves in order to customize this further.
+ *
+ * The standard streaming case, then is:
+ *
+ * audioThread = new JSAudioThread(audioUser, audioFormat, audioByteBuffer);
+ * audioThread.start();
+ *
+ * where audioUser provides
+ *
+ * checkSoundStatus() (called in isLooping()),
+ *
+ * fillAudioBuffer() (called in myLoop()),
+ *
+ * and
+ *
+ * audioThreadExiting() (called in doFinally()).
+ *
+ * @author Bob Hanson
+ *
+ */
+public class JSAudioThread extends JSThread {
+
+ protected Owner owner;
+ protected boolean done;
+ protected int myBufferLength;
+ protected SourceDataLine line;
+ protected int rate, nChannels, bitsPerSample;
+
+ protected byte[] audioByteBuffer;
+ protected int audioBufferByteLength;
+
+ private AudioFormat audioFormat;
+ private int myBufferOffset;
+ private int playCount;
+
+ public JSAudioThread(Owner owner, AudioFormat audioFormat, byte[] audioByteBuffer) {
+ this.owner = owner;
+ setFormat(audioFormat);
+ setBuffer(audioByteBuffer);
+ }
+
+ /**
+ * A convenience constructor requiring standard settings of
+ * signed (for 8-bit) and littleEndian (for 16-bit)
+ *
+ * @param owner
+ * @param rate
+ * @param bitsPerSample
+ * @param nChannels
+ * @param audioByteBuffer
+ */
+ public JSAudioThread(Owner owner, int rate, int bitsPerSample, int nChannels, byte[] audioByteBuffer) {
+ this.owner = owner;
+ setFormat(new AudioFormat(rate, bitsPerSample, nChannels, true, false));
+ setBuffer(audioByteBuffer);
+ }
+
+ /**
+ * primarily available for (new JSAudioThread()).playULawData
+ *
+ */
+ public JSAudioThread() {
+ }
+
+ /**
+ * primarily available for (new JSAudioThread()).playOnce
+ *
+ */
+ public JSAudioThread(AudioFormat audioFormat) {
+ setFormat(audioFormat);
+ }
+
+ /**
+ *
+ * A simple 8-bit uLaw data player
+ *
+ * @param data
+ */
+ public void playULawData(byte[] data) {
+ // this constructor uses default new AudioFormat(ULAW,8000,8,1,1,8000,true)
+ // threading is taken care of by the browser in JavaScript
+ AudioPlayer.player.start(new AudioDataStream(new AudioData(data)));
+ }
+
+
+ /**
+ * Just play once through; no additions
+ *
+ * @param data
+ */
+ public void playOnce(byte[] data, int offset, int length) {
+ setBuffer(data);
+ myBufferOffset = offset;
+ myBufferLength = length;
+ playCount = 1;
+ start();
+ }
+
+ public void setBuffer(byte[] audioByteBuffer) {
+ this.audioByteBuffer = audioByteBuffer;
+ audioBufferByteLength = audioByteBuffer.length;
+ }
+
+ public SourceDataLine getLine() {
+ return line;
+ }
+
+ public AudioFormat getFormat() {
+ return audioFormat;
+ }
+
+ public void setFormat(AudioFormat audioFormat) {
+ this.audioFormat = audioFormat;
+ rate = (int) audioFormat.getSampleRate();
+ bitsPerSample = audioFormat.getSampleSizeInBits();
+ nChannels = audioFormat.getChannels();
+ }
+
+ public void resetAudio() {
+ if (line == null)
+ return;
+ line.flush();
+ line.close();
+ line = null;
+ }
+
+ /**
+ * Standard initialization of a SourceDataLine
+ *
+ */
+ @Override
+ protected boolean myInit() {
+ try {
+ DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
+ if (line != null)
+ line.close();
+ line = (SourceDataLine) AudioSystem.getLine(info);
+ line.open(audioFormat, audioBufferByteLength);
+ line.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean isLooping() {
+ return !done && (--playCount >= 0 || owner != null && owner.checkSoundStatus());
+ }
+
+ @Override
+ protected boolean myLoop() {
+ if (!done) {
+ if ((myBufferLength = (owner == null ? myBufferLength : owner.fillAudioBuffer())) <= 0)
+ return !(done = true);
+ try {
+ if (line == null)
+ myInit();
+ line.write(audioByteBuffer, myBufferOffset, myBufferLength);
+ } catch (Exception e) {
+ e.printStackTrace();
+ done = true;
+ }
+ }
+ return !done;
+ }
+
+ @Override
+ protected void whenDone() {
+ done = true;
+ resetAudio();
+ }
+
+ @Override
+ protected int getDelayMillis() {
+ // about 25% of the actual play time
+ return 1000 // ms/sec
+ * (myBufferLength * 8 / bitsPerSample) // * samples
+ / rate // * seconds/sample)
+ / nChannels // / number of channels
+ / 4; // * 25%
+ }
+
+ @Override
+ protected void onException(Exception e) {
+ e.printStackTrace();
+ }
+
+ @Override
+ protected void doFinally() {
+ if (owner != null)
+ owner.audioThreadExiting();
+ }
+
+ public interface Owner {
+
+ /**
+ *
+ * @return true if thread should continue; false if not
+ *
+ */
+ boolean checkSoundStatus();
+
+ /** fill audio buffer
+ *
+ * @return number of bytes to write to audio line
+ *
+ */
+ int fillAudioBuffer();
+
+ /**
+ * called from the finally clause when complete
+ *
+ */
+ void audioThreadExiting();
+
+
+ }
+
+}
+