plectrum

Plectrum: instrument tuner for Android
Log | Files | Refs | README | LICENSE

AudioGenerator.java (7876B)


      1 /*
      2 *      _______                       _____   _____ _____  
      3 *     |__   __|                     |  __ \ / ____|  __ \ 
      4 *        | | __ _ _ __ ___  ___  ___| |  | | (___ | |__) |
      5 *        | |/ _` | '__/ __|/ _ \/ __| |  | |\___ \|  ___/ 
      6 *        | | (_| | |  \__ \ (_) \__ \ |__| |____) | |     
      7 *        |_|\__,_|_|  |___/\___/|___/_____/|_____/|_|     
      8 *                                                         
      9 * -------------------------------------------------------------
     10 *
     11 * TarsosDSP is developed by Joren Six at IPEM, University Ghent
     12 *  
     13 * -------------------------------------------------------------
     14 *
     15 *  Info: http://0110.be/tag/TarsosDSP
     16 *  Github: https://github.com/JorenSix/TarsosDSP
     17 *  Releases: http://0110.be/releases/TarsosDSP/
     18 *  
     19 *  TarsosDSP includes modified source code by various authors,
     20 *  for credits and info, see README.
     21 * 
     22 */
     23 
     24 
     25 package be.tarsos.dsp;
     26 
     27 import java.nio.ByteOrder;
     28 import java.util.List;
     29 import java.util.concurrent.CopyOnWriteArrayList;
     30 import java.util.logging.Logger;
     31 
     32 import be.tarsos.dsp.io.TarsosDSPAudioFormat;
     33 
     34 
     35 /**
     36  * This class plays a file and sends float arrays to registered AudioProcessor
     37  * implementors. This class can be used to feed FFT's, pitch detectors, audio players, ...
     38  * Using a (blocking) audio player it is even possible to synchronize execution of
     39  * AudioProcessors and sound. This behavior can be used for visualization.
     40  * @author Joren Six
     41  */
     42 public class AudioGenerator implements Runnable {
     43 
     44 
     45 	/**
     46 	 * Log messages.
     47 	 */
     48 	private static final Logger LOG = Logger.getLogger(AudioGenerator.class.getName());
     49 
     50 
     51 	/**
     52 	 * This buffer is reused again and again to store audio data using the float
     53 	 * data type.
     54 	 */
     55 	private float[] audioFloatBuffer;
     56 
     57 
     58 	/**
     59 	 * A list of registered audio processors. The audio processors are
     60 	 * responsible for actually doing the digital signal processing
     61 	 */
     62 	private final List<AudioProcessor> audioProcessors;
     63 
     64 	
     65 	private final TarsosDSPAudioFormat format;
     66 
     67 	/**
     68 	 * The floatOverlap: the number of elements that are copied in the buffer
     69 	 * from the previous buffer. Overlap should be smaller (strict) than the
     70 	 * buffer size and can be zero. Defined in number of samples.
     71 	 */
     72 	private int floatOverlap, floatStepSize;	
     73 	
     74 	private int samplesProcessed;
     75 	
     76 	/**
     77 	 * The audio event that is send through the processing chain.
     78 	 */
     79 	private AudioEvent audioEvent;
     80 	
     81 	/**
     82 	 * If true the dispatcher stops dispatching audio.
     83 	 */
     84 	private boolean stopped;
     85 	
     86 
     87 	/**
     88 	 * Create a new generator.
     89 	 * @param audioBufferSize
     90 	 *            The size of the buffer defines how much samples are processed
     91 	 *            in one step. Common values are 1024,2048.
     92 	 * @param bufferOverlap
     93 	 *            How much consecutive buffers overlap (in samples). Half of the
     94 	 *            AudioBufferSize is common (512, 1024) for an FFT.
     95 	 */
     96 	public AudioGenerator(final int audioBufferSize, final int bufferOverlap){
     97 		
     98 		this(audioBufferSize,bufferOverlap,44100);
     99 	}
    100 	
    101 	public AudioGenerator(final int audioBufferSize, final int bufferOverlap,final int samplerate){
    102 		
    103 		audioProcessors = new CopyOnWriteArrayList<AudioProcessor>();
    104 		
    105 
    106 		format = getTargetAudioFormat(samplerate);
    107 		
    108 			
    109 		setStepSizeAndOverlap(audioBufferSize, bufferOverlap);
    110 		
    111 		audioEvent = new AudioEvent(format);
    112 		audioEvent.setFloatBuffer(audioFloatBuffer);
    113 		
    114 		stopped = false;
    115 
    116 		
    117 		samplesProcessed = 0;
    118 	}
    119 	
    120 	/**
    121 	 * Constructs the target audio format. The audio format is one channel
    122 	 * signed PCM of a given sample rate.
    123 	 * 
    124 	 * @param targetSampleRate
    125 	 *            The sample rate to convert to.
    126 	 * @return The audio format after conversion.
    127 	 */
    128 	private TarsosDSPAudioFormat getTargetAudioFormat(int targetSampleRate) {
    129 		TarsosDSPAudioFormat audioFormat = new TarsosDSPAudioFormat(TarsosDSPAudioFormat.Encoding.PCM_SIGNED, 
    130 	        		targetSampleRate, 
    131 	        		2 * 8, 
    132 	        		1, 
    133 	        		2 * 1, 
    134 	        		targetSampleRate, 
    135 	                ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder()));
    136 		 return audioFormat;
    137 	}
    138 	
    139 
    140 	
    141 	/**
    142 	 * Set a new step size and overlap size. Both in number of samples. Watch
    143 	 * out with this method: it should be called after a batch of samples is
    144 	 * processed, not during.
    145 	 * 
    146 	 * @param audioBufferSize
    147 	 *            The size of the buffer defines how much samples are processed
    148 	 *            in one step. Common values are 1024,2048.
    149 	 * @param bufferOverlap
    150 	 *            How much consecutive buffers overlap (in samples). Half of the
    151 	 *            AudioBufferSize is common (512, 1024) for an FFT.
    152 	 */
    153 	public void setStepSizeAndOverlap(final int audioBufferSize, final int bufferOverlap){
    154 		audioFloatBuffer = new float[audioBufferSize];
    155 		floatOverlap = bufferOverlap;
    156 		floatStepSize = audioFloatBuffer.length - floatOverlap;		
    157 	}
    158 	
    159 
    160 	/**
    161 	 * Adds an AudioProcessor to the chain of processors.
    162 	 * 
    163 	 * @param audioProcessor
    164 	 *            The AudioProcessor to add.
    165 	 */
    166 	public void addAudioProcessor(final AudioProcessor audioProcessor) {
    167 		audioProcessors.add(audioProcessor);
    168 		LOG.fine("Added an audioprocessor to the list of processors: " + audioProcessor.toString());
    169 	}
    170 	
    171 	/**
    172 	 * Removes an AudioProcessor to the chain of processors and calls processingFinished.
    173 	 * 
    174 	 * @param audioProcessor
    175 	 *            The AudioProcessor to remove.
    176 	 */
    177 	public void removeAudioProcessor(final AudioProcessor audioProcessor) {
    178 		audioProcessors.remove(audioProcessor);
    179 		audioProcessor.processingFinished();
    180 		LOG.fine("Remove an audioprocessor to the list of processors: " + audioProcessor.toString());
    181 	}
    182 
    183 	public void run() {
    184 		
    185 	
    186 		
    187 		//Read the first (and in some cases last) audio block.
    188 		generateNextAudioBlock();
    189 
    190 
    191 		// As long as the stream has not ended
    192 		while (!stopped) {
    193 			
    194 			//Makes sure the right buffers are processed, they can be changed by audio processors.
    195 			for (final AudioProcessor processor : audioProcessors) {
    196 				if(!processor.process(audioEvent)){
    197 					//skip to the next audio processors if false is returned.
    198 					break;
    199 				}	
    200 			}
    201 			
    202 			if(!stopped){
    203 				audioEvent.setBytesProcessed(samplesProcessed * format.getFrameSize());
    204 					
    205 				// Read, convert and process consecutive overlapping buffers.
    206 				// Slide the buffer.
    207 				generateNextAudioBlock();
    208 			}
    209 		}
    210 
    211 		// Notify all processors that no more data is available. 
    212 		// when stop() is called processingFinished is called explicitly, no need to do this again.
    213 		// The explicit call is to prevent timing issues.
    214 		if(!stopped){
    215 			stop();
    216 		}
    217 	}
    218 	
    219 
    220 	/**
    221 	 * Stops dispatching audio data.
    222 	 */
    223 	public void stop() {
    224 		stopped = true;
    225 		for (final AudioProcessor processor : audioProcessors) {
    226 			processor.processingFinished();
    227 		}
    228 	}
    229 
    230 	/**
    231 	 * Reads the next audio block. It tries to read the number of bytes defined
    232 	 * by the audio buffer size minus the overlap. If the expected number of
    233 	 * bytes could not be read either the end of the stream is reached or
    234 	 * something went wrong.
    235 	 * 
    236 	 * The behavior for the first and last buffer is defined by their corresponding the zero pad settings. The method also handles the case if
    237 	 * the first buffer is also the last.
    238 	 * 
    239 	 */
    240 	private void generateNextAudioBlock() {
    241 		assert floatOverlap < audioFloatBuffer.length;
    242 		
    243 		//Shift the audio information using array copy since it is probably faster than manually shifting it.
    244 		// No need to do this on the first buffer
    245 		if(audioFloatBuffer.length == floatOverlap + floatStepSize ){
    246 			System.arraycopy(audioFloatBuffer, floatStepSize, audioFloatBuffer,0 ,floatOverlap);
    247 		}
    248 		samplesProcessed += floatStepSize;
    249 	}
    250 	
    251 	public void resetTime(){
    252 		samplesProcessed=0;
    253 	}
    254 	
    255 	public TarsosDSPAudioFormat getFormat(){
    256 		return format;
    257 	}
    258 	
    259 	/**
    260 	 * 
    261 	 * @return The currently processed number of seconds.
    262 	 */
    263 	public float secondsProcessed(){
    264 		return samplesProcessed / format.getSampleRate() / format.getChannels() ;
    265 	}
    266 	
    267 }