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.filters;
034
035import org.openimaj.audio.AudioFormat;
036
037/**
038 *      A default non-isoceles triangular filter definition, where the low, mid
039 *      and top frequencies are defined.
040 *
041 *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
042 *  @created 13 Dec 2012
043 *      @version $Author$, $Revision$, $Date$
044 */
045public class TriangularFilter
046{
047        /** The lowest frequency of the filter */
048        protected double lowFrequency = 0;
049
050        /** The highest frequency of the filter */
051        protected double highFrequency = 20000;
052
053        /** The centre (peak) frequency of the filter */
054        protected double centreFrequency = 10000;
055
056        /** The height of the filter */
057        protected double filterAmplitude;
058
059        /** The slope of first half of the filter. */
060        protected double lowSlope;
061
062        /** The slope of the second half of the filter */
063        protected double highSlope;
064
065        /**
066         *
067         *      @param low
068         *      @param centre
069         *      @param high
070         */
071        public TriangularFilter( final double low, final double centre, final double high )
072    {
073                // Ensure the end frequency is greater than the start frequency
074                if( high <= low )
075                        throw new IllegalArgumentException( "Triangular Filter start and end " +
076                                        "frequencies are incorrect: "+high+" <= "+low );
077
078                this.lowFrequency = low;
079                this.highFrequency = high;
080                this.centreFrequency = centre;
081
082                this.filterAmplitude = 2f / (this.highFrequency - this.lowFrequency);
083                this.lowSlope = this.filterAmplitude / (this.centreFrequency - this.lowFrequency);
084                this.highSlope = this.filterAmplitude / (this.highFrequency - this.centreFrequency);
085
086    }
087
088        /**
089         *      Returns the lowest frequency of the filter
090         *      @return The low frequency of the filter
091         */
092        public double getLowFrequency()
093        {
094                return this.lowFrequency;
095        }
096
097        /**
098         *      Sets the low frequency
099         *      @param lowFrequency The new low frequency
100         */
101        public void setLowFrequency( final double lowFrequency )
102        {
103                this.lowFrequency = lowFrequency;
104                if( this.highFrequency <= this.lowFrequency )
105                        throw new IllegalArgumentException( "Triangular Filter start and end " +
106                                        "frequencies are incorrect: "+this.highFrequency+" <= "+this.lowFrequency );
107        }
108
109        /**
110         *      Gets the high frequency
111         *      @return The high frequency
112         */
113        public double getHighFrequency()
114        {
115                return this.highFrequency;
116        }
117
118        /**
119         *      Set the high frequency of the filter
120         *      @param highFrequency The new high frequency
121         */
122        public void setHighFrequency( final double highFrequency )
123        {
124                this.highFrequency = highFrequency;
125                if( this.highFrequency <= this.lowFrequency )
126                        throw new IllegalArgumentException( "Triangular Filter start and end " +
127                                        "frequencies are incorrect: "+highFrequency+" <= "+this.lowFrequency );
128        }
129
130        /**
131         *      Get the centre frequency
132         *      @return The centre frequency
133         */
134        public double getCentreFrequency()
135        {
136                return this.centreFrequency;
137        }
138
139        /**
140         *      Set the new centre frequency
141         *      @param centreFrequency The new centre frequency
142         */
143        public void setCentreFrequency( final double centreFrequency )
144        {
145                this.centreFrequency = centreFrequency;
146                if( this.centreFrequency <= this.lowFrequency || this.centreFrequency >= this.highFrequency )
147                        throw new IllegalArgumentException( "Triangular Filter start and end " +
148                                        "frequencies are incorrect: centre frequency "+centreFrequency );
149        }
150
151        /**
152         *      Requires a power spectrum and produces an output the filter
153         *
154         *      @param frequencySpectrum The power spectrum
155         *      @param format The format of the samples used to create the spectrum
156         *      @return The output power for the filter
157         */
158        public double process( final float[] frequencySpectrum, final AudioFormat format )
159        {
160                double output = 0d;
161
162                // The size of each bin in Hz (using the first channel as examplar)
163                final double binSize = (format.getSampleRateKHz()*1000)
164                                / (frequencySpectrum.length/2);
165
166                final int startBin = (int)(this.lowFrequency / binSize);
167                final int endBin = (int)(this.highFrequency / binSize);
168
169                // Now apply the filter to the spectrum and accumulate the output
170                for( int x = startBin; x < endBin; x++ )
171                {
172                        // Ensure we're within the bounds of the spectrum
173                        if( x >= 0 && x < frequencySpectrum.length )
174                        {
175                                final double binFreq = binSize * x;
176                                final double weight = this.getWeightAt(binFreq);
177                                output += weight * frequencySpectrum[x];
178                        }
179                }
180
181                return output;
182        }
183
184        /**
185         *      Returns a set of values that represent the response of this filter
186         *      when the linear frequency is split in the given number of bins. The
187         *      result will have <code>nSpectrumBins</code> length.
188         *
189         *      @param nSpectrumBins The number of bins in a spectrum.
190         *      @param maxFreq The maximum frequency (sample rate)
191         *      @return The response curve.
192         */
193        public double[] getResponseCurve( final int nSpectrumBins, final double maxFreq )
194        {
195                final double[] curve = new double[nSpectrumBins];
196                final double binSize = maxFreq / nSpectrumBins;
197
198                for( int x = 0; x < nSpectrumBins; x++ )
199                        curve[x] = this.getWeightAt( binSize * x);
200
201                return curve;
202        }
203
204        /**
205         *      Returns the weighting provided by this filter at the given frequency (Hz).
206         *      @param frequency The frequency (Hz) to get the weight for
207         *      @return The weight at the given frequency (Hz) for this filter
208         */
209        public double getWeightAt( final double frequency )
210        {
211                // Up or down slope depending on whether we're left or
212                // right of the centre frequency
213                double weight = 0;
214                if( frequency < this.centreFrequency )
215                                weight = this.filterAmplitude - this.lowSlope * (this.centreFrequency - frequency);
216                else    weight = this.filterAmplitude - this.highSlope * (frequency - this.centreFrequency);
217
218                if( weight < 0 ) weight = 0;
219
220                return weight;
221        }
222
223        /**
224         *      Returns the filter amplitude
225         *      @return The filter amplitude
226         */
227        public double getFilterAmplitude()
228        {
229                return this.filterAmplitude;
230        }
231        
232        /**
233         *      Set the filter amplitude
234         *      @param fa The filter amplitude
235         */
236        public void setFilterAmplitude( final double fa )
237        {
238                this.filterAmplitude = fa;
239                this.lowSlope = this.filterAmplitude / (this.centreFrequency - this.lowFrequency);
240                this.highSlope = this.filterAmplitude / (this.highFrequency - this.centreFrequency);
241        }
242
243        /**
244         *      {@inheritDoc}
245         *      @see java.lang.Object#toString()
246         */
247        @Override
248        public String toString()
249        {
250//              return ""+this.centreFrequency;
251                return "tf{"+this.lowFrequency+"->"+this.centreFrequency+"->"+this.highFrequency+"}";
252        }
253
254}