Merge branch 'develop' into feature/JAL-3551Pymol
[jalview.git] / unused / javajs / util / JSAudioThread.java
diff --git a/unused/javajs/util/JSAudioThread.java b/unused/javajs/util/JSAudioThread.java
new file mode 100644 (file)
index 0000000..3bcb006
--- /dev/null
@@ -0,0 +1,258 @@
+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();
+               
+
+       }
+
+}
+