/******************************************************************************* * SDR Trunk * Copyright (C) 2014 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> ******************************************************************************/ package dsp.filter; import java.util.ArrayList; import org.apache.commons.lang3.Validate; import sample.real.RealSampleListener; public class FloatHalfBandFilter implements RealSampleListener { private RealSampleListener mListener; private ArrayList<Float> mBuffer; private int mBufferSize = 1; //Temporary initial value private int mBufferPointer = 0; private float mGain; private float[] mCoefficients; private int[][] mIndexMap; private int mCenterCoefficient; private int mCenterCoefficientMapIndex; private boolean mDispatchFlag = false; /** * Half-Band filter with decimation by 2 against real valued floats. * * Takes advantage of the symmetrical nature of FIR filter coefficients by * adding oldest and newest sample first, then multiplying once by the * corresponding coefficient * * Also, takes advantage of the 0-valued FIR half-band coefficents inherent * in the half-band filter, and does not calculate those coefficients. * * This reduces the workload to (tap-size - 1) / 4 + 1 calculations per sample. * * @param filter - filter coefficients * @param gain - gain multiplier. Use 1.0 for unity/no gain */ public FloatHalfBandFilter( Filters filter, float gain ) { mCoefficients = filter.getCoefficients(); mBuffer = new ArrayList<Float>(); mBufferSize = mCoefficients.length; //Fill the buffer with zero valued samples for( int x = 0; x < mCoefficients.length; x++ ) { mBuffer.add( 0.0f ); } generateIndexMap( mCoefficients.length ); mGain = gain; } public void dispose() { mListener = null; } /** * Calculate the filtered value by applying the coefficients against * the complex samples in mBuffer */ public void receive( float newSample ) { //Add the new sample to the buffer mBuffer.set( mBufferPointer, newSample ); //Increment & Adjust the buffer pointer for circular wrap around mBufferPointer++; if( mBufferPointer >= mBufferSize ) { mBufferPointer = 0; } //Toggle the flag every time, so that we can calculate and dispatch a //sample every other time, when the flag is true (ie decimate by 2) mDispatchFlag = !mDispatchFlag; //Calculate a filtered sample when the flag is true if( mDispatchFlag ) { //Convolution - multiply filter coefficients by the circular buffer //samples to calculate a new filtered value float accumulator = 0; //Start with the center tap value accumulator += mCoefficients[ mCenterCoefficient ] * mBuffer.get( mIndexMap[ mBufferPointer ][ mCenterCoefficientMapIndex ] ); //For the remaining coefficients, add the symmetric samples, oldest and newest //first, then multiply by the single coefficient for( int x = 0; x < mCenterCoefficientMapIndex; x += 2 ) { accumulator += mCoefficients[ x ] * ( mBuffer.get( mIndexMap[ mBufferPointer ][ x ] ) + mBuffer.get( mIndexMap[ mBufferPointer ][ x + 1 ] ) ); } //We're almost finished ... apply gain, cast the floats to shorts and //send it on it's merry way if( mListener != null ) { mListener.receive( (float)( accumulator * mGain ) ); } } } /** * Creates an n X (n + 1 / 2) index map enabling quick access to the * circular buffer samples. * * As the buffer shifts right with each subsequent sample, we have to move * the index pointers with it, for efficient access of the samples. * * The first array index value in the index map corresponds to the current * buffer pointer location. * * The second array index value points to the samples that should be * multiplied by the coefficients as follows: * * 0 = center tap sample, to be multiplied by center coefficient * * 0 = sample( 1 ) * 1 = sample( size - 1 ) * * Indexes 0 and 1 will be multiplied by coefficient( 0 ). * * Subsequent indexes 3, 4, etc, point to the oldest and newest samples that * correspond to the matching ( 3 ) coefficient index. * * @param odd-sized number of filter taps (ie coefficients) and buffer */ private void generateIndexMap( int size ) { //Ensure we have an odd size Validate.isTrue(size % 2 == 1); int mapWidth = ( ( size + 1 ) / 2 ) + 1; //Set center tap index for coefficients array mCenterCoefficient = ( size - 1 ) / 2; mCenterCoefficientMapIndex = mCenterCoefficient + 1; mIndexMap = new int[ size ][ mapWidth ]; //Setup the first row, buffer pointer index 0, as a starting point for( int x = 0; x < mapWidth - 2; x += 2 ) { mIndexMap[ 0 ][ x ] = x; mIndexMap[ 0 ][ x + 1 ] = size - 1 - x; } //Place center tap index in last element mIndexMap[ 0 ][ mCenterCoefficientMapIndex ] = mCenterCoefficient; //For each subsequent row, increment the previous row's value by 1, //subtracting size as needed, to keep the values between 0 and size - 1 for( int x = 1; x < size; x++ ) { for( int y = 0; y < mapWidth; y++ ) { mIndexMap[ x ][ y ] = mIndexMap[ x - 1 ][ y ] + 1; if( mIndexMap[ x ][ y ] >= size ) { mIndexMap[ x ][ y ] -= size; } } } } /** * Registers a listener for filtered samples */ public void setListener( RealSampleListener listener ) { mListener = listener; } /** * Removes (if exists) a registered filtered sample listener */ public void clearListener() { mListener = null; } }