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 }