/*
*
      
_______
                       
_____
   
_____ _____
  

*
     
|__
   
__|
                     
|
  
__ \ / ____|__ \
 

*
        
| | __ _ _ __ ___
  
______| || | (___ | |__) |
*
        
| |/ _` | '__/ __|/ _ \/ __| |
  
| |\___ \|___/
 

*
        
| | (_| | |
  
\__ \ (_) \__ \ |__| |____) | |
     

*
        
|_|\__,_|_|
  
|___/\___/|___/_____/|_____/|_|
     

*
                                                         

* -------------------------------------------------------------
*
* TarsosDSP is developed by Joren Six at IPEM, University Ghent
*
  

* -------------------------------------------------------------
*
*
  
Info:
 
http://0110.be/tag/TarsosDSP
*
  
Github:
 
https://github.com/JorenSix/TarsosDSP
*
  
Releases:
 
http://0110.be/releases/TarsosDSP/
*
  

*
  
TarsosDSP includes modified source code by various authors,
*
  
for credits and info, see README.
*
 

*/


package be.tarsos.dsp;

import java.nio.ByteOrder;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;

import be.tarsos.dsp.io.TarsosDSPAudioFormat;


/**
 
* This class plays a file and sends float arrays to registered AudioProcessor
 
* implementors. This class can be used to feed FFT's, pitch detectors, audio players, ...
 
* Using a (blocking) audio player it is even possible to synchronize execution of
 
* AudioProcessors and sound. This behavior can be used for visualization.
 
* @author Joren Six
 
*/

public class AudioGenerator implements Runnable {


	
/**
	 
* Log messages.
	 
*/
	
private static final Logger LOG = Logger.getLogger(AudioGenerator.class.getName());


	
/**
	 
* This buffer is reused again and again to store audio data using the float
	 
* data type.
	 
*/

	
private float[] audioFloatBuffer;


	
/**
	 
* A list of registered audio processors. The audio processors are
	 
* responsible for actually doing the digital signal processing
	 
*/

	
private final List<AudioProcessor> audioProcessors;

	

	
private final TarsosDSPAudioFormat format;

	
/**
	 
* The floatOverlap: the number of elements that are copied in the buffer
	 
* from the previous buffer. Overlap should be smaller (strict) than the
	 
* buffer size and can be zero. Defined in number of samples.
	 
*/

	
private int floatOverlap, floatStepSize;
	

	

	
private int samplesProcessed;
	

	
/**
	 
* The audio event that is send through the processing chain.
	 
*/

	
private AudioEvent audioEvent;
	

	
/**
	 
* If true the dispatcher stops dispatching audio.
	 
*/
	
private boolean stopped;
	


	
/**
	 
* Create a new generator.
	 
* @param audioBufferSize
	 
*
            
The size of the buffer defines how much samples are processed
	 
*
            
in one step. Common values are 1024,2048.
	 
* @param bufferOverlap
	 
*
            
How much consecutive buffers overlap (in samples). Half of the
	 
*
            
AudioBufferSize is common (512, 1024) for an FFT.
	 
*/

	
public AudioGenerator(final int audioBufferSize, final int bufferOverlap){
		

		
this(audioBufferSize,bufferOverlap,44100);
	
}
	

	
public AudioGenerator(final int audioBufferSize, final int bufferOverlap,final int samplerate){
		

		
audioProcessors = new CopyOnWriteArrayList<AudioProcessor>();
		


		
format = getTargetAudioFormat(samplerate);
		

			

		
setStepSizeAndOverlap(audioBufferSize, bufferOverlap);
		

		
audioEvent = new AudioEvent(format);
		
audioEvent.setFloatBuffer(audioFloatBuffer);
		

		
stopped = false;

		

		
samplesProcessed = 0;
	
}
	

	
/**
	 
* Constructs the target audio format. The audio format is one channel
	 
* signed PCM of a given sample rate.
	 
*
 

	 
* @param targetSampleRate
	 
*
            
The sample rate to convert to.
	 
* @return The audio format after conversion.
	 
*/

	
private TarsosDSPAudioFormat getTargetAudioFormat(int targetSampleRate) {
		
TarsosDSPAudioFormat audioFormat = new TarsosDSPAudioFormat(TarsosDSPAudioFormat.Encoding.PCM_SIGNED,
	        		
targetSampleRate,
	        		
2 * 8,
	        		
1,
	        		
2 * 1,
	        		
targetSampleRate,
	                
ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder()));
		 
return audioFormat;
	
}
	


	

	
/**
	 
* Set a new step size and overlap size. Both in number of samples. Watch
	 
* out with this method: it should be called after a batch of samples is
	 
* processed, not during.
	 
*
 

	 
* @param audioBufferSize
	 
*
            
The size of the buffer defines how much samples are processed
	 
*
            
in one step. Common values are 1024,2048.
	 
* @param bufferOverlap
	 
*
            
How much consecutive buffers overlap (in samples). Half of the
	 
*
            
AudioBufferSize is common (512, 1024) for an FFT.
	 
*/

	
public void setStepSizeAndOverlap(final int audioBufferSize, final int bufferOverlap){
		
audioFloatBuffer = new float[audioBufferSize];
		
floatOverlap = bufferOverlap;
		
floatStepSize = audioFloatBuffer.length - floatOverlap;
		

	
}
	


	
/**
	 
* Adds an AudioProcessor to the chain of processors.
	 
*
 

	 
* @param audioProcessor
	 
*
            
The AudioProcessor to add.
	 
*/

	
public void addAudioProcessor(final AudioProcessor audioProcessor) {
		
audioProcessors.add(audioProcessor);
		
LOG.fine("Added an audioprocessor to the list of processors: " + audioProcessor.toString());
	
}
	

	
/**
	 
* Removes an AudioProcessor to the chain of processors and calls processingFinished.
	 
*
 

	 
* @param audioProcessor
	 
*
            
The AudioProcessor to remove.
	 
*/

	
public void removeAudioProcessor(final AudioProcessor audioProcessor) {
		
audioProcessors.remove(audioProcessor);
		
audioProcessor.processingFinished();
		
LOG.fine("Remove an audioprocessor to the list of processors: " + audioProcessor.toString());
	
}

	
public void run() {
		

	

		

		
//Read the first (and in some cases last) audio block.
		
generateNextAudioBlock();


		
// As long as the stream has not ended
		
while (!stopped) {
			

			
//Makes sure the right buffers are processed, they can be changed by audio processors.
			
for (final AudioProcessor processor : audioProcessors) {
				
if(!processor.process(audioEvent)){
					
//skip to the next audio processors if false is returned.
					
break;
				
}
	

			
}
			

			
if(!stopped){
				
audioEvent.setBytesProcessed(samplesProcessed * format.getFrameSize());
					

				
// Read, convert and process consecutive overlapping buffers.
				
// Slide the buffer.
				
generateNextAudioBlock();
			
}
		
}

		
// Notify all processors that no more data is available.
 

		
// when stop() is called processingFinished is called explicitly, no need to do this again.
		
// The explicit call is to prevent timing issues.
		
if(!stopped){
			
stop();
		
}
	
}
	


	
/**
	 
* Stops dispatching audio data.
	 
*/
	
public void stop() {
		
stopped = true;
		
for (final AudioProcessor processor : audioProcessors) {
			
processor.processingFinished();
		
}
	
}

	
/**
	 
* Reads the next audio block. It tries to read the number of bytes defined
	 
* by the audio buffer size minus the overlap. If the expected number of
	 
* bytes could not be read either the end of the stream is reached or
	 
* something went wrong.
	 
*
 

	 
* The behavior for the first and last buffer is defined by their corresponding the zero pad settings. The method also handles the case if
	 
* the first buffer is also the last.
	 
*
 

	 
*/

	
private void generateNextAudioBlock() {
		
assert floatOverlap < audioFloatBuffer.length;
		

		
//Shift the audio information using array copy since it is probably faster than manually shifting it.
		
// No need to do this on the first buffer
		
if(audioFloatBuffer.length == floatOverlap + floatStepSize ){
			
System.arraycopy(audioFloatBuffer, floatStepSize, audioFloatBuffer,0 ,floatOverlap);
		
}
		
samplesProcessed += floatStepSize;
	
}
	

	
public void resetTime(){
		
samplesProcessed=0;
	
}
	

	
public TarsosDSPAudioFormat getFormat(){
		
return format;
	
}
	

	
/**
	 
*
 

	 
* @return The currently processed number of seconds.
	 
*/

	
public float secondsProcessed(){
		
return samplesProcessed / format.getSampleRate() / format.getChannels() ;
	
}
	

}