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 040 * sizes 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 must be 043 * a power of 2 and the underlying audio stream reader may be returning sample 044 * 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. 048 * If this is set to 0 or below, the windows will be consecutive and will 049 * not 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 * @param sizeRequired The size of the chunks required (in samples) 076 */ 077 public FixedSizeSampleAudioProcessor( final int sizeRequired ) 078 { 079 this.requiredSampleSetSize = sizeRequired; 080 } 081 082 /** 083 * Create processor that will process chunks of the given size. 084 * @param stream An audio stream to process 085 * @param sizeRequired The size of the chunks required (in samples) 086 */ 087 public FixedSizeSampleAudioProcessor( final AudioStream stream, final int sizeRequired ) 088 { 089 super( stream ); 090 this.requiredSampleSetSize = sizeRequired; 091 } 092 093 /** 094 * Constructor that takes the size of the window and the size of the 095 * window overlap. 096 * @param nSamplesInWindow The number of samples in the window 097 * @param nSamplesOverlap The size of the overlap 098 */ 099 public FixedSizeSampleAudioProcessor( final int nSamplesInWindow, 100 final int nSamplesOverlap ) 101 { 102 this( nSamplesInWindow ); 103 this.setWindowStep( nSamplesOverlap ); 104 } 105 106 /** 107 * Chainable constructor that takes the size of the window and 108 * the number of samples overlap. 109 * 110 * @param as The chained audio stream 111 * @param nSamplesInWindow Samples in window 112 * @param nSamplesOverlap Samples in window overlap 113 */ 114 public FixedSizeSampleAudioProcessor( final AudioStream as, final int nSamplesInWindow, 115 final int nSamplesOverlap ) 116 { 117 this( as, nSamplesInWindow ); 118 this.setWindowStep( nSamplesOverlap ); 119 } 120 121 /** 122 * {@inheritDoc} 123 * @see org.openimaj.audio.processor.AudioProcessor#nextSampleChunk() 124 */ 125 @Override 126 public SampleChunk nextSampleChunk() 127 { 128// System.out.println( "Sample Buffer: "+(sampleBuffer != null? 129// sampleBuffer.getNumberOfSamples() : "null")); 130 131 // Get the samples. If there's more samples than we need in the 132 // buffer, we'll just use that, otherwise we'll get a new sample 133 // chunk from the stream. 134 SampleChunk s = null; 135 if( this.sampleBuffer != null && 136 this.sampleBuffer.getNumberOfSamples() >= this.requiredSampleSetSize ) 137 { 138 s = this.sampleBuffer; 139 this.sampleBuffer = null; 140 } 141 else 142 { 143 s = this.getUnderlyingStream().nextSampleChunk(); 144 if( s != null ) 145 s = s.clone(); 146 147 // If we have something in our buffer, prepend it to the new 148 // sample chunk 149 if( this.sampleBuffer != null && this.sampleBuffer.getNumberOfSamples() > 0 150 && s != null ) 151 { 152 // Prepend the contents of the sample buffer to the new sample 153 // chunk 154 s.prepend( this.sampleBuffer ); 155 this.sampleBuffer = null; 156 } 157 } 158 159 // Sample buffer will always be null here 160 // It will be reinstated later with the left-overs after processing. 161 // From this point on we'll only work on the SampleChunk s. 162 163 // Catch the end of the stream. As the sample buffer is always empty 164 // at this point, the only time s can be null is that if the 165 // nextSampleChunk() above returned null. In which case, there's no 166 // more audio, so we return null. 167 if( s == null ) 168 { 169 if( this.sampleBuffer != null ) 170 { 171 s = this.sampleBuffer; 172 this.sampleBuffer = null; 173 return s; 174 } 175 else 176 return null; 177 } 178 179 // Now check how many samples we have to start with 180 int nSamples = s.getNumberOfSamples(); 181 182 // If we don't have enough samples, we'll keep getting chunks until 183 // we have enough or until the end of the stream is reached. 184 boolean endOfStream = false; 185 while( !endOfStream && nSamples < this.requiredSampleSetSize ) 186 { 187 final SampleChunk nextSamples = this.getUnderlyingStream().nextSampleChunk(); 188 if( nextSamples != null ) 189 { 190 // Append the new samples onto the end of the sample chunk 191 s.append( nextSamples ); 192 193 // Check how many samples we now have. 194 nSamples = s.getNumberOfSamples(); 195 } 196 else endOfStream = true; 197 } 198 199 // If we have the right number of samples, 200 // or we've got to the end of the stream 201 // then we just return the chunk we have. 202 SampleChunk ss; 203 if( !endOfStream && (this.overlapping || nSamples > this.requiredSampleSetSize) ) 204 { 205 // We must now have too many samples... 206 // Store the excess back into the buffer 207 int start = 0; 208 if( this.overlapping ) 209 start = this.windowStep; 210 else start = this.requiredSampleSetSize; 211 212 // Store the rest into the sample buffer 213 this.sampleBuffer = s.getSampleSlice( start, nSamples-start ); 214 215 // Process a slice of the sample chunk 216 ss = s.getSampleSlice( 0, this.requiredSampleSetSize ); 217 } 218 else 219 { 220 ss = s; 221 222 if( ss.getNumberOfSamples() < this.requiredSampleSetSize ) 223 ss.pad( this.requiredSampleSetSize ); 224 } 225 226 try 227 { 228 // Return the processed samples 229 return this.process( ss ); 230 } 231 catch( final Exception e ) 232 { 233 // If there's an error, log it and return the unprocessed samples 234 e.printStackTrace(); 235 return ss; 236 } 237 } 238 239 /** 240 * Set the step of each overlapping window. 241 * @param overlap The step of each overlapping window. 242 */ 243 public void setWindowStep( final int overlap ) 244 { 245 this.windowStep = overlap; 246 this.overlapping = true; 247 if( overlap <= 0 ) 248 this.overlapping = false; 249 } 250 251 /** 252 * Returns the step of each overlapping window. 253 * @return The step of each overlapping window. 254 */ 255 public int getWindowStep() 256 { 257 return this.windowStep; 258 } 259 260 /** 261 * Set the size of the window required. Should be called before 262 * the object has been used to process anything. The result is undefined 263 * if it's called during processing and will probably lead to some 264 * sort of bounds error. 265 * @param sizeRequired The size required. 266 */ 267 public void setWindowSize( final int sizeRequired ) 268 { 269 this.requiredSampleSetSize = sizeRequired; 270 } 271 272 /** 273 * Returns the size of the sample window. 274 * @return The size of the sample window. 275 */ 276 public int getWindowSize() 277 { 278 return this.requiredSampleSetSize; 279 } 280 281 /** 282 * Returns whether the windows are overlapping or not. 283 * @return whether the windows are overlapping or not. 284 */ 285 public boolean isOverlapping() 286 { 287 return this.overlapping; 288 } 289 290 /** 291 * {@inheritDoc} 292 * 293 * The default operation of the {@link FixedSizeSampleAudioProcessor} is 294 * simply to change the shape of the sample chunk. You may override 295 * this method to process the samples directly. 296 * 297 * @see org.openimaj.audio.processor.AudioProcessor#process(org.openimaj.audio.SampleChunk) 298 */ 299 @Override 300 public SampleChunk process( final SampleChunk sample ) throws Exception 301 { 302 return sample; 303 } 304}