Index: trunk/src/org/apollo/audio/ApolloPlaybackMixer.java
===================================================================
--- trunk/src/org/apollo/audio/ApolloPlaybackMixer.java	(revision 1006)
+++ trunk/src/org/apollo/audio/ApolloPlaybackMixer.java	(revision 1007)
@@ -324,5 +324,11 @@
 		if (playbackThread == null) {
 		
-			playbackThread = new PlaybackThread();
+			AudioFormat audio_format = SampledAudioManager.getInstance().getDefaultPlaybackFormat();
+			if (audio_format.getChannels()==2) {
+				playbackThread = new StereoPlaybackThread();
+			}
+			else {
+				playbackThread = new MonoPlaybackThread();
+			}
 			playbackThread.start();
 			
@@ -451,12 +457,12 @@
 	 *
 	 */
-	private class PlaybackThread extends Thread {
-		
-		private SourceDataLine srcDataLine; // never null
-
-		private boolean isStopping = false;
-		
-		private int bufferFrameLength;
-		private boolean isOutputBigEndian;
+	private abstract class PlaybackThread extends Thread {
+		
+		protected SourceDataLine srcDataLine; // never null
+
+		protected boolean isStopping = false;
+		
+		protected int bufferFrameLength;
+		protected boolean isOutputBigEndian;
 		
 		/**
@@ -465,6 +471,6 @@
 		 * @throws LineUnavailableException
 		 */
-		PlaybackThread() throws LineUnavailableException {
-			super("Apollo Playback Mixer Thread");
+		protected PlaybackThread(String threadName) throws LineUnavailableException {
+			super(threadName);
 			super.setPriority(Thread.MAX_PRIORITY);
 			
@@ -494,5 +500,5 @@
 		 * 		If failed to acquire the source data line.
 		 */
-		private void aquireSourceDataLine() throws LineUnavailableException {
+		protected void aquireSourceDataLine() throws LineUnavailableException {
 
 			// Select an audio output format
@@ -508,5 +514,5 @@
 			
 			// Cache useful data
-			bufferFrameLength = srcDataLine.getBufferSize() / 2;
+			bufferFrameLength = srcDataLine.getBufferSize() / (2*2); // 2=stereo, 2=16-bit
 			isOutputBigEndian = srcDataLine.getFormat().isBigEndian();
 			
@@ -527,5 +533,5 @@
 		
 		/**
-		 * Note: even if all tracks have been proccessed in the audio pipeline, it will
+		 * Note: even if all tracks have been processed in the audio pipeline, it will
 		 * commence another pass to check for new tracks added to the graph before finishing.
 		 * 
@@ -540,8 +546,366 @@
 		 * @return the best audio format for playback...
 		 */
-		private AudioFormat getAudioFormat() {
+		protected AudioFormat getAudioFormat() {
 			return SampledAudioManager.getInstance().getDefaultPlaybackFormat();
 		}
 
+		/**
+		 * The audio mixing pipeline
+		 */
+		public abstract void run();
+	
+
+		
+	}
+
+    private class StereoPlaybackThread extends PlaybackThread {
+		
+
+		
+		/**
+		 * Initantly prepares for audio playback: Opens the (stereo) source data line for output
+		 * 
+		 * @throws LineUnavailableException
+		 */
+		StereoPlaybackThread() throws LineUnavailableException {
+			super("Apollo Stereo Playback Mixer Thread");
+			
+		}
+		
+	
+		/**
+		 * The audio mixing pipeline
+		 */
+		public void run() {
+
+			// Notify observers that some audio has started playing
+			ApolloPlaybackMixer.this.fireSubjectChangedLaterOnSwingThread(
+				new SubjectChangedEvent(ApolloSubjectChangedEvent.PLAYBACK_STARTED));
+			
+			// All tracks to play per pass
+			List<TrackSequence> tracksToPlay = new LinkedList<TrackSequence>();
+			
+			// Keeps track of tracks to remove
+			List<TrackSequence> completedTracks = new LinkedList<TrackSequence>();
+			
+			// The buffer written directly to the source data line
+			byte[] sampleBuffer = new byte[2 * 2 * bufferFrameLength]; // 2=stereo, 2=16-bit samples
+			
+			// The mixed frames, where each element refers to a frame
+			int[] mixedFrameBufferL = new int[bufferFrameLength];
+			int[] mixedFrameBufferR = new int[bufferFrameLength];
+			
+			// Helpers declared outside loop for mz efficiency
+			int msbL, lsbL;
+			int msbR, lsbR;
+			int sampleL;
+			int sampleR;
+			int totalFramesMixed;
+			int trackCount; // tracks to play at a given pass
+			boolean isMoreQueued; // True if there are more tracks queued. 
+			int frameIndex; 
+			int i;
+			
+			// Begin writing to the source data line
+			if (srcDataLine.isOpen())
+				srcDataLine.start();
+			else return;
+
+			// keep playing as long as line is open (and there is something to play)
+			try 
+			{
+				while (srcDataLine.isOpen()) { // The audio mixing pipline
+
+					// First decide on which tracks to play ... and remove any finished tracks.
+					synchronized(sequenceGraph) {
+						
+						// If there are no more tracks queued for playing, then exit the
+						// playback thread.
+						if (sequenceGraph.isEmpty()) 
+							return;
+		
+						isMoreQueued = false;
+						completedTracks.clear();
+						tracksToPlay.clear();
+						
+						for (TrackSequence ts : sequenceGraph) {
+							
+							// Has this track sequence finished?
+							if (ts.currentFrame > ts.endFrame || ts.stopPending)
+								completedTracks.add(ts);
+							
+							// Is this track playing / is meant to start laying in this pass?
+							else if (ts.initiationFrame <= (timelineFrame + bufferFrameLength))
+								tracksToPlay.add(ts);
+	
+							// If it is not time to play the track yet, then
+							// neither will it for all proceeding tracks either
+							// since they are ordered by their initiation time.
+							else break;
+							
+						}
+						
+						// Get rid of tracks that have finished playing. Notify models that they have stopped
+						for (TrackSequence staleTS : completedTracks) {
+							
+							sequenceGraph.remove(staleTS);
+							
+							staleTS.onStopped((staleTS.currentFrame > staleTS.endFrame)
+									? staleTS.endFrame : staleTS.currentFrame);
+							
+							//removeTrackFromGraph(staleTS, staleTS.endFrame);
+						}
+
+						trackCount = tracksToPlay.size();
+						isMoreQueued = sequenceGraph.size() > trackCount;
+						
+						// If there is nothing queued and there are no tracks to play,
+						// then playback is finished.
+						if (!isMoreQueued && trackCount == 0) 
+							return;
+						
+					} // release lock
+					
+					totalFramesMixed = 0; // this will be set to the maximum amount of frames that were mixed accross all tracks
+					
+					// Clear audio buffer
+					for (i = 0; i < bufferFrameLength; i++) {
+						// TODO: Efficient way of clearing buffer?
+						mixedFrameBufferL[i] = 0;
+						mixedFrameBufferR[i] = 0; 
+					}
+					
+					// Perform Mixing : 
+					// Convert the sample size to 16-bit always for best precision while
+					// processing audio in the mix pipeline....
+					for (TrackSequence ts : tracksToPlay) {
+						
+						// Notify model that initiated
+						if (!ts.isPlaying()) ts.onInitiated(timelineFrame);
+
+						// Skip muted / unsoloed tracks - they add nothing to the sample mix
+						if (ts.isMuted || (isSoloEnable && !ts.isSolo)) { 
+							
+							// Make sure start where initiated, if not already initiated
+							if (ts.initiationFrame >= timelineFrame && ts.initiationFrame < (timelineFrame + bufferFrameLength)) {
+								
+								// Get index in frame buffer where to initiate
+								frameIndex = (int)(ts.initiationFrame - timelineFrame);
+								
+								// Calcuate the length of frames to buffer - adjust silent tracks position
+								ts.currentFrame += (bufferFrameLength - frameIndex);
+
+							} else { // skip full buffer of bytes ... silenced
+								
+								ts.currentFrame += bufferFrameLength; // currentFrame can go outside endframe boundry of the track
+				
+							}
+							
+							totalFramesMixed = bufferFrameLength;
+							
+						} else { // Get samples and add to mix
+	
+							// If the track is yet to initiate - part way through the buffer, then start adding bytes
+							// at initiation point
+							if (ts.initiationFrame >= timelineFrame && ts.initiationFrame < (timelineFrame + bufferFrameLength)) {
+								
+								frameIndex = (int)(ts.initiationFrame - timelineFrame);
+								
+							} else { 
+								
+								frameIndex = 0; 
+								
+							}
+		
+							// For each frame
+							for (;frameIndex < bufferFrameLength && ts.currentFrame <= ts.endFrame; frameIndex++) {
+								
+								// Get sample according to byte order
+								int base_posL = ts.currentFrame * (2*2); // 2=stereo, 2=16-bit
+								int base_posR = base_posL+2;
+								
+								if (ts.isBigEndian) {
+									
+									// First byte is MSB (high order)
+									msbL = (int)ts.playbackAudioBytes[base_posL];
+									 
+									 // Second byte is LSB (low order)
+									lsbL = (int)ts.playbackAudioBytes[base_posL + 1];
+									
+									// And again for the right channel
+									msbR= (int)ts.playbackAudioBytes[base_posR];
+									lsbR = (int)ts.playbackAudioBytes[base_posR + 1];
+								
+								 } else {
+									 
+									// First byte is LSB (low order)
+									lsbL = (int)ts.playbackAudioBytes[base_posL];
+									 
+									// Second byte is MSB (high order)
+									msbL = (int)ts.playbackAudioBytes[base_posL+1];
+									
+									// And again for the right channel
+									lsbR = (int)ts.playbackAudioBytes[base_posR];
+									msbR = (int)ts.playbackAudioBytes[base_posR+1];
+								}
+								
+								sampleL = (msbL << 0x8) | (0xFF & lsbL);
+								sampleR = (msbR << 0x8) | (0xFF & lsbR);
+								
+								// Apply track volume
+								sampleL = (int)(sampleL * ts.volume);
+								sampleR = (int)(sampleR * ts.volume);
+								
+								// Add to current mix
+								mixedFrameBufferL[frameIndex] += sampleL;
+								mixedFrameBufferR[frameIndex] += sampleR;
+								
+								// Get next sample
+								ts.currentFrame++;
+							}
+							
+							
+							// Keep track of total frames mixed in buffer
+							if (frameIndex > totalFramesMixed)
+								totalFramesMixed = frameIndex;
+						}
+		
+					} // Mix in next track
+	
+					// totalFramesMixed is the amount of frames to play.
+					// If it is zero then it means that there are tracks yet to be initiated, and nothing currently playing
+					assert (totalFramesMixed <= bufferFrameLength);
+					assert (totalFramesMixed > 0 ||
+							(totalFramesMixed == 0 && trackCount == 0 && isMoreQueued));
+					
+					// Post mix with master settings 
+					if (isMasterMuteOn) { // Silence sample buffer if master mute is on
+	
+						for (i = 0; i < sampleBuffer.length; i++) {
+							sampleBuffer[i] = 0;
+						}
+						
+						// Let the muted bytes play
+						totalFramesMixed = bufferFrameLength;
+						
+					} else { // otherwise apply master volume
+						
+						for (i = 0; i < totalFramesMixed; i++) {
+		
+							// Average tracks
+							//mixedFrameBuffer[i] /= trackCount; // depreciated
+							
+							// Apply master volume
+							mixedFrameBufferL[i] = (int)(mixedFrameBufferL[i] * masterVolume);
+							mixedFrameBufferR[i] = (int)(mixedFrameBufferR[i] * masterVolume);
+							
+							// Clip
+							if (mixedFrameBufferL[i] > Short.MAX_VALUE) mixedFrameBufferL[i] = Short.MAX_VALUE;
+							else if (mixedFrameBufferL[i] < Short.MIN_VALUE) mixedFrameBufferL[i] = Short.MIN_VALUE; 
+							
+							if (mixedFrameBufferR[i] > Short.MAX_VALUE) mixedFrameBufferR[i] = Short.MAX_VALUE;
+							else if (mixedFrameBufferR[i] < Short.MIN_VALUE) mixedFrameBufferR[i] = Short.MIN_VALUE; 
+							
+							// Convert to output format
+							lsbL = (mixedFrameBufferL[i] & 0xFF);
+							msbL = ((mixedFrameBufferL[i] >> 8) & 0xFF);
+							lsbR = (mixedFrameBufferR[i] & 0xFF);
+							msbR = ((mixedFrameBufferR[i] >> 8) & 0xFF);
+							
+							int base_posL = i * (2 * 2); // 2=stereo, 2=16-bits
+							int base_posR = base_posL + 2;
+							if (isOutputBigEndian) {
+								sampleBuffer[base_posL]   = (byte)msbL;
+								sampleBuffer[base_posL+1] = (byte)lsbL;
+								sampleBuffer[base_posR]   = (byte)msbR;
+								sampleBuffer[base_posR+1] = (byte)lsbR;
+							} else {
+								sampleBuffer[base_posL]   = (byte)lsbL;
+								sampleBuffer[base_posL+1] = (byte)msbL;
+								sampleBuffer[base_posR]   = (byte)lsbR;
+								sampleBuffer[base_posR+1] = (byte)msbR;
+							}
+
+						}
+
+					}
+					
+					// Generate silence only if there are more tracks to be played.
+					// Note that this could be false, but a track might have been queued after
+					// setting the isMoreQueued flag. In such cases... silence is not wanted anyway!
+					if (isMoreQueued) {
+						for (i = totalFramesMixed; i < bufferFrameLength; i++) { // will skip if no need to generate silence
+							int base_posL = i * (2 * 2); // 2=stereo, 2=16-bits
+							int base_posR = base_posL + 2;
+							
+							sampleBuffer[base_posL]   = 0;
+							sampleBuffer[base_posL+1] = 0;
+							sampleBuffer[base_posR]   = 0;
+							sampleBuffer[base_posR+1] = 0;
+						}
+						// Ensure that full buffer is played ... including the silence
+						totalFramesMixed = bufferFrameLength;
+					}
+	
+					// Write processed bytes to line out stream and update the timeline frame
+					srcDataLine.write(
+							sampleBuffer, 
+							0, 
+							totalFramesMixed * (2 * 2)); // 2=stereo, 2=16-bits
+					
+					// Update timeline counter for sequencing management
+					timelineFrame += totalFramesMixed;
+					
+					// The timelineFrame should always be larger or equal to the live frame position
+					assert(timelineFrame >= srcDataLine.getLongFramePosition());
+					
+				} // Next pass
+			
+			} finally { 
+				
+				isStopping = true;
+				
+				// Ensure line freed
+				if (srcDataLine.isOpen()) {
+					srcDataLine.drain(); // avoids chopping off last buffered chunk
+					srcDataLine.close();
+				}
+				
+				// Clear sequence graph. 
+				synchronized(sequenceGraph) {
+					
+					for (TrackSequence track : sequenceGraph) {
+						
+						track.onStopped((track.currentFrame > track.endFrame)
+								? track.endFrame : track.currentFrame);
+					}
+					
+					sequenceGraph.clear();
+					
+				}
+
+				// Notify observers that playback has finished.
+				ApolloPlaybackMixer.this.fireSubjectChangedLaterOnSwingThread(
+						new SubjectChangedEvent(ApolloSubjectChangedEvent.PLAYBACK_STOPPED));
+				
+			}
+			
+		}
+	}
+    
+    private class MonoPlaybackThread extends PlaybackThread {
+		
+
+		
+		/**
+		 * Initantly prepares for audio playback: Opens the (stereo) source data line for output
+		 * 
+		 * @throws LineUnavailableException
+		 */
+		MonoPlaybackThread() throws LineUnavailableException {
+			super("Apollo Mono Playback Mixer Thread");
+			
+		}
+		
 		/**
 		 * The audio mixing pipeline
@@ -637,5 +1001,5 @@
 					
 					// Clear audio buffer
-					for (i = 0; i < bufferFrameLength; i++) // TODO: Effecient way of clearing buffer?
+					for (i = 0; i < bufferFrameLength; i++) // TODO: Efficient way of clearing buffer?
 						mixedFrameBuffer[i] = 0; 
 					
@@ -746,5 +1110,5 @@
 							//mixedFrameBuffer[i] /= trackCount; // depreciated
 							
-							// Apply mastar volume
+							// Apply master volume
 							mixedFrameBuffer[i] = (int)(mixedFrameBuffer[i] * masterVolume);
 							
@@ -781,5 +1145,5 @@
 					}
 	
-					// Write proccessed bytes to line out stream and update the timeline frame
+					// Write processed bytes to line out stream and update the timeline frame
 					srcDataLine.write(
 							sampleBuffer, 
@@ -826,7 +1190,5 @@
 		}
 		
-
-		
-	}
+    }
 
 	
