Source code: marf/Preprocessing/FFTFilter/FFTFilter.java
1 package marf.Preprocessing.FFTFilter;
2
3 import java.util.Vector;
4
5 import marf.MARF;
6 import marf.Preprocessing.IFilter;
7 import marf.Preprocessing.IPreprocessing;
8 import marf.Preprocessing.Preprocessing;
9 import marf.Preprocessing.PreprocessingException;
10 import marf.Storage.ModuleParams;
11 import marf.Storage.Sample;
12 import marf.math.Algorithms;
13 import marf.math.MathException;
14 import marf.util.Arrays;
15
16
17 /**
18 * <p>FFTFilter class implements filtering using the FFT algorithm.</p>
19 * <p>Derivatives must set frequency response based on the type of filter they are.</p>
20 *
21 * $Id: FFTFilter.java,v 1.23 2005/08/05 22:19:55 mokhov Exp $
22 *
23 * @author Stephen Sinclair
24 * @author Serguei Mokhov
25 * @version $Revision: 1.23 $
26 * @since 0.0.1
27 */
28 public abstract class FFTFilter
29 extends Preprocessing
30 implements IFilter
31 {
32 /**
33 * Default size of the frequency response vector, 128.
34 */
35 public static transient final int DEFAULT_FREQUENCY_RESPONSE_SIZE = 128;
36
37 /**
38 * Frequency repsonse to be multiplied by the incoming value.
39 */
40 protected transient double[] adFreqResponse = null;
41
42 /**
43 * Pipelined constructor.
44 * @param poPreprocessing followup preprocessing module
45 * @throws PreprocessingException
46 * @since 0.3.0.3
47 */
48 public FFTFilter(IPreprocessing poPreprocessing)
49 throws PreprocessingException
50 {
51 super(poPreprocessing);
52 genereateResponseCoefficients();
53 }
54
55 /**
56 * FFTFilter Constructor.
57 * @param poSample incoming sample
58 * @throws PreprocessingException
59 */
60 public FFTFilter(Sample poSample)
61 throws PreprocessingException
62 {
63 super(poSample);
64 genereateResponseCoefficients();
65 }
66
67 /**
68 * FFTFilter implementation of <code>preprocess()</code>.
69 * <p>It does call <code>removeNoise()</code> and <code>removeSilence()</code>
70 * if they were explicitly requested by an app <em>before</em> applying filtering.</p>
71 *
72 * <b>NOTE</b>: it alters inner Sample by resetting its data array to the new
73 * filtered values.
74 *
75 * @return <code>true</code> if there was something filtered out
76 * @throws PreprocessingException if the frequency response is null
77 */
78 public boolean preprocess()
79 throws PreprocessingException
80 {
81 if(this.adFreqResponse == null)
82 throw new PreprocessingException
83 (
84 "FFTFilter.preprocess() - frequency response is null"
85 );
86
87 boolean bChanges = normalize();
88
89 double[] adSampleData = this.oSample.getSampleArray();
90 double[] adFilteredData = new double[adSampleData.length];
91
92 // By default we do not remove noise or silence
93 boolean bRemoveNoise = false;
94 boolean bRemoveSilence = false;
95
96 // Exract any additional params if supplied
97
98 ModuleParams oModuleParams = MARF.getModuleParams();
99
100 if(oModuleParams != null)
101 {
102 Vector oParams = oModuleParams.getPreprocessingParams();
103
104 if(oParams.size() > 0)
105 {
106 bRemoveNoise = ((Boolean)oParams.elementAt(0)).booleanValue();
107 bRemoveSilence = ((Boolean)oParams.elementAt(1)).booleanValue();
108 }
109 }
110
111 if(bRemoveNoise == true)
112 bChanges |= removeNoise();
113
114 if(bRemoveSilence == true)
115 bChanges |= removeSilence();
116
117 bChanges |= filter(adSampleData, adFilteredData);
118
119 this.oSample.setSampleArray(adFilteredData);
120
121 return bChanges;
122 }
123
124 /**
125 * Sets frequency response.
126 * Derivatives must call this method before any preprocessing occurs.
127 *
128 * @param padPesponse desired frequency response coefficients
129 */
130 public final void setFrequencyResponse(final double[] padPesponse)
131 {
132 this.adFreqResponse = new double[padPesponse.length * 2];
133
134 // Forward copy
135 Arrays.copy(this.adFreqResponse, 0, padPesponse);
136
137 // Backward copy
138 for(int i = 0; i < padPesponse.length; i++)
139 {
140 this.adFreqResponse[this.adFreqResponse.length - i - 1] = padPesponse[i];
141 }
142 }
143
144 /**
145 * Perform a filter by the following algorithm:
146 * (1) sample -> window -> FFT -> buffer<br />
147 * (2) buffer * frequency response<br />
148 * (3) buffer -> IFFT -> window -> sample.
149 *
150 * Window used is square root of Hamming window, because
151 * the sum at half-window overlap is a constant, which
152 * avoids amplitude distortion from noise.
153 *
154 * Also, start sampling at <code>-responseSize/2</code>, in order to avoid
155 * amplitude distortion of the first half of the first window.
156 *
157 * @param padSample incoming sample analog data
158 * @param padFiltered will contain data after the filter was applied.
159 * "filtered" must be at least as long as "sample".
160 *
161 * @return <code>true</code> if some filtering actually happened
162 *
163 * @throws PreprocessingException if the filtered and sample data
164 * arrays are not of the same size, the frequency response was not
165 * set, or there was an underlying FeatureExctractionException while
166 * executing the underlying FFT algorithm.
167 */
168 public final boolean filter(final double[] padSample, double[] padFiltered)
169 throws PreprocessingException
170 {
171 try
172 {
173 int iResponseSize = this.adFreqResponse.length;
174
175 double[] adBuffer = new double[iResponseSize];
176 double[] adBufferImag = new double[iResponseSize];
177 double[] adOutputReal = new double[iResponseSize];
178 double[] adOutputImag = new double[iResponseSize];
179
180 if(padFiltered.length < padSample.length)
181 throw new PreprocessingException
182 (
183 "FFTFilter: Output buffer not long enough (" +
184 padFiltered.length + " < " + padSample.length + ")."
185 );
186
187 int i;
188
189 int iPosition = -iResponseSize / 2;
190
191 while(iPosition < padSample.length)
192 {
193 for(i = 0; i < iResponseSize; i++)
194 {
195 if(((iPosition + i) < padSample.length) && ((iPosition + i) >= 0))
196 adBuffer[i] = padSample[iPosition + i] * Algorithms.Hamming.sqrtHamming(i, iResponseSize);
197 else
198 adBuffer[i] = 0;
199
200 adBufferImag[i] = 0;
201 }
202
203 Algorithms.FFT.doFFT(adBuffer, adBufferImag, adOutputReal, adOutputImag, 1);
204
205 for(i = 0; i < iResponseSize; i++)
206 {
207 adOutputReal[i] *= this.adFreqResponse[i];
208 adOutputImag[i] *= this.adFreqResponse[i];
209 }
210
211 Algorithms.FFT.doFFT(adOutputReal, adOutputImag, adBuffer, adBufferImag, -1);
212
213 // Copy & normalize
214 for(i = 0; (i < iResponseSize) && ((iPosition + i) < padSample.length); i++)
215 {
216 if((iPosition + i) >= 0)
217 padFiltered[iPosition + i] +=
218 adBuffer[i] * Algorithms.Hamming.sqrtHamming(i, iResponseSize) / (double)iResponseSize;
219 }
220
221 iPosition += iResponseSize / 2;
222 }
223
224 return true;
225 }
226 catch(NullPointerException e)
227 {
228 throw new PreprocessingException("FFTFilter: frequency response hasn't been set.");
229 }
230 catch(MathException e)
231 {
232 throw new PreprocessingException("FFTFilter: " + e.getMessage());
233 }
234 }
235
236 /**
237 * Creates frequency response coefficients and sets applies
238 * them to the frequency response vector. Must be overridden
239 * by individual filters.
240 *
241 * @since 0.3.0
242 */
243 public abstract void genereateResponseCoefficients();
244
245 /**
246 * Returns source code revision information.
247 * @return revision string
248 * @since 0.3.0.2
249 */
250 public static String getMARFSourceCodeRevision()
251 {
252 return "$Revision: 1.23 $";
253 }
254 }
255
256 // EOF