001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.audio.processor; 034 035import org.openimaj.audio.AudioStream; 036import org.openimaj.audio.SampleChunk; 037 038/** 039 * Provides an audio processor that will process sample chunks of specific sizes 040 * when the incoming stream's sample chunk size is unknown. 041 * <p> 042 * This class has applications for FFT (for example) where the input sample size 043 * must be a power of 2 and the underlying audio stream reader may be returning 044 * sample chunks of any size. 045 * <p> 046 * The processor can also provide overlapping sample windows. Call 047 * {@link #setWindowStep(int)} to determine the slide of each sliding window. If 048 * this is set to 0 or below, the windows will be consecutive and will not 049 * overlap. 050 * <p> 051 * The only assumption made by the class about the samples is that they are 052 * whole numbers of bytes (8, 16, 24, 32 bits etc.). This is a pretty reasonable 053 * assumption. 054 * 055 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 056 * 057 * @created 11 Jul 2011 058 */ 059public class FixedSizeSampleAudioProcessor extends AudioProcessor 060{ 061 /** The size of each required sample chunk */ 062 private int requiredSampleSetSize = 512; 063 064 /** Our buffer of sample chunks stored between calls to process() */ 065 private SampleChunk sampleBuffer = null; 066 067 /** The number of samples overlap required between each window */ 068 private int windowStep = 0; 069 070 /** Whether or not the windows are overlapping */ 071 private boolean overlapping = false; 072 073 /** 074 * Create processor that will process chunks of the given size. 075 * 076 * @param sizeRequired 077 * The size of the chunks required (in samples) 078 */ 079 public FixedSizeSampleAudioProcessor(final int sizeRequired) 080 { 081 this.requiredSampleSetSize = sizeRequired; 082 } 083 084 /** 085 * Create processor that will process chunks of the given size. 086 * 087 * @param stream 088 * An audio stream to process 089 * @param sizeRequired 090 * The size of the chunks required (in samples) 091 */ 092 public FixedSizeSampleAudioProcessor(final AudioStream stream, final int sizeRequired) 093 { 094 super(stream); 095 this.requiredSampleSetSize = sizeRequired; 096 } 097 098 /** 099 * Constructor that takes the size of the window and the size of the window 100 * overlap. 101 * 102 * @param nSamplesInWindow 103 * The number of samples in the window 104 * @param nSamplesOverlap 105 * The size of the overlap 106 */ 107 public FixedSizeSampleAudioProcessor(final int nSamplesInWindow, 108 final int nSamplesOverlap) 109 { 110 this(nSamplesInWindow); 111 this.setWindowStep(nSamplesOverlap); 112 } 113 114 /** 115 * Chainable constructor that takes the size of the window and the number of 116 * samples overlap. 117 * 118 * @param as 119 * The chained audio stream 120 * @param nSamplesInWindow 121 * Samples in window 122 * @param nSamplesOverlap 123 * Samples in window overlap 124 */ 125 public FixedSizeSampleAudioProcessor(final AudioStream as, final int nSamplesInWindow, 126 final int nSamplesOverlap) 127 { 128 this(as, nSamplesInWindow); 129 this.setWindowStep(nSamplesOverlap); 130 } 131 132 /** 133 * {@inheritDoc} 134 * 135 * @see org.openimaj.audio.processor.AudioProcessor#nextSampleChunk() 136 */ 137 @Override 138 public SampleChunk nextSampleChunk() 139 { 140 // System.out.println( "Sample Buffer: "+(sampleBuffer != null? 141 // sampleBuffer.getNumberOfSamples() : "null")); 142 143 // Get the samples. If there's more samples than we need in the 144 // buffer, we'll just use that, otherwise we'll get a new sample 145 // chunk from the stream. 146 SampleChunk s = null; 147 if (this.sampleBuffer != null && 148 this.sampleBuffer.getNumberOfSamples() >= this.requiredSampleSetSize) 149 { 150 s = this.sampleBuffer; 151 this.sampleBuffer = null; 152 } 153 else 154 { 155 s = this.getUnderlyingStream().nextSampleChunk(); 156 if (s != null) 157 s = s.clone(); 158 159 // If we have something in our buffer, prepend it to the new 160 // sample chunk 161 if (this.sampleBuffer != null && this.sampleBuffer.getNumberOfSamples() > 0 162 && s != null) 163 { 164 // Prepend the contents of the sample buffer to the new sample 165 // chunk 166 s.prepend(this.sampleBuffer); 167 this.sampleBuffer = null; 168 } 169 } 170 171 // Sample buffer will always be null here 172 // It will be reinstated later with the left-overs after processing. 173 // From this point on we'll only work on the SampleChunk s. 174 175 // Catch the end of the stream. As the sample buffer is always empty 176 // at this point, the only time s can be null is that if the 177 // nextSampleChunk() above returned null. In which case, there's no 178 // more audio, so we return null. 179 if (s == null) 180 { 181 if (this.sampleBuffer != null) 182 { 183 s = this.sampleBuffer; 184 this.sampleBuffer = null; 185 return s; 186 } 187 else 188 return null; 189 } 190 191 // Now check how many samples we have to start with 192 int nSamples = s.getNumberOfSamples(); 193 194 // If we don't have enough samples, we'll keep getting chunks until 195 // we have enough or until the end of the stream is reached. 196 boolean endOfStream = false; 197 while (!endOfStream && nSamples < this.requiredSampleSetSize) 198 { 199 final SampleChunk nextSamples = this.getUnderlyingStream().nextSampleChunk(); 200 if (nextSamples != null) 201 { 202 // Append the new samples onto the end of the sample chunk 203 s.append(nextSamples); 204 205 // Check how many samples we now have. 206 nSamples = s.getNumberOfSamples(); 207 } 208 else 209 endOfStream = true; 210 } 211 212 // If we have the right number of samples, 213 // or we've got to the end of the stream 214 // then we just return the chunk we have. 215 SampleChunk ss; 216 if (!endOfStream && (this.overlapping || nSamples > this.requiredSampleSetSize)) 217 { 218 // We must now have too many samples... 219 // Store the excess back into the buffer 220 int start = 0; 221 if (this.overlapping) 222 start = this.windowStep; 223 else 224 start = this.requiredSampleSetSize; 225 226 // Store the rest into the sample buffer 227 this.sampleBuffer = s.getSampleSlice(start, nSamples - start); 228 229 // Process a slice of the sample chunk 230 ss = s.getSampleSlice(0, this.requiredSampleSetSize); 231 } 232 else 233 { 234 ss = s; 235 236 if (ss.getNumberOfSamples() < this.requiredSampleSetSize) 237 ss.pad(this.requiredSampleSetSize); 238 } 239 240 try 241 { 242 // Return the processed samples 243 return this.process(ss); 244 } catch (final Exception e) 245 { 246 // If there's an error, log it and return the unprocessed samples 247 e.printStackTrace(); 248 return ss; 249 } 250 } 251 252 /** 253 * Set the step of each overlapping window. 254 * 255 * @param overlap 256 * The step of each overlapping window. 257 */ 258 public void setWindowStep(final int overlap) 259 { 260 this.windowStep = overlap; 261 this.overlapping = true; 262 if (overlap <= 0) 263 this.overlapping = false; 264 } 265 266 /** 267 * Returns the step of each overlapping window. 268 * 269 * @return The step of each overlapping window. 270 */ 271 public int getWindowStep() 272 { 273 return this.windowStep; 274 } 275 276 /** 277 * Set the size of the window required. Should be called before the object 278 * has been used to process anything. The result is undefined if it's called 279 * during processing and will probably lead to some sort of bounds error. 280 * 281 * @param sizeRequired 282 * The size required. 283 */ 284 public void setWindowSize(final int sizeRequired) 285 { 286 this.requiredSampleSetSize = sizeRequired; 287 } 288 289 /** 290 * Returns the size of the sample window. 291 * 292 * @return The size of the sample window. 293 */ 294 public int getWindowSize() 295 { 296 return this.requiredSampleSetSize; 297 } 298 299 /** 300 * Returns whether the windows are overlapping or not. 301 * 302 * @return whether the windows are overlapping or not. 303 */ 304 public boolean isOverlapping() 305 { 306 return this.overlapping; 307 } 308 309 /** 310 * {@inheritDoc} 311 * 312 * The default operation of the {@link FixedSizeSampleAudioProcessor} is 313 * simply to change the shape of the sample chunk. You may override this 314 * method to process the samples directly. 315 * 316 * @see org.openimaj.audio.processor.AudioProcessor#process(org.openimaj.audio.SampleChunk) 317 */ 318 @Override 319 public SampleChunk process(final SampleChunk sample) throws Exception 320 { 321 return sample; 322 } 323}