/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 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.polyphase;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Polyphase interpolating FIR filter with an internal circular buffer as
* described by the dspGuru at:
*
* http://www.dspguru.com/dsp/faqs/multirate/interpolation
* See sections 3.4.2 and 3.4.3 )
*/
public class PolyphaseFIRInterpolatingFilter
{
private final static Logger mLog =
LoggerFactory.getLogger( PolyphaseFIRInterpolatingFilter.class );
private double[] mBuffer;
private int mBufferPointer = 0;
private double[] mTaps;
private Phase[] mPhases;
/**
* Constructs the interpolating filter. Coefficients length must be an
* integer multiple of the interpolation value. Each sample processed by
* the filter will generate a quantity of samples equal to the interpolation
* value.
*
* @param coefficients - low pass filter coefficients sized for the desired
* interpolated sample rate with a cutoff frequency of no more than half of
* the original sample rate.
*
* @param interpolation - integral interpolation quantity
*/
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
{
/* Ensure coefficients are an integral multiple of the interpolation factor */
Validate.isTrue(coefficients.length % interpolation == 0);
mTaps = coefficients;
mPhases = new Phase[ interpolation ];
int phaseSize = coefficients.length / interpolation;
mBuffer = new double[ phaseSize ];
for( int x = 0; x < interpolation; x++ )
{
int coefficientIndex = 0;
int[] indexes = new int[ phaseSize ];
for( int y = x; y < coefficients.length; y += interpolation )
{
indexes[ coefficientIndex++ ] = y;
}
/* Create a phase using the tap indexes and the interpolation value
* for the gain value, to offset the loss induced in upsampling from
* one sample to (interpolation) samples. */
mPhases[ interpolation - x - 1 ] = new Phase( indexes, interpolation );
}
}
/**
* Inserts the sample into the filter and returns (interpolation) number
* of interpolated samples in the return array.
*
* @param sample - float sample
* @return - array of interpolated samples, size = interpolation quantity
*/
public float[] interpolate( float sample )
{
mBuffer[ mBufferPointer ] = (double)sample;
mBufferPointer++;
mBufferPointer %= mBuffer.length;
float[] samples = new float[ mPhases.length ];
for( int x = 0; x < mPhases.length; x++ )
{
samples[ x ] = mPhases[ x ].filter();
}
return samples;
}
/**
* Upsamples and interpolates the array of samples, returning an array that
* is upsampled and low-pass filtered by the interpolation quantity.
*
* @param samples - array of float samples
* @return - array of interpolated samples, size = interpolation * samples size
*/
public float[] interpolate( float[] samples )
{
int pointer = 0;
float[] interpolated = new float[ samples.length * mPhases.length ];
for( float sample: samples )
{
mBuffer[ mBufferPointer ] = (double)sample;
mBufferPointer++;
mBufferPointer %= mBuffer.length;
for( int x = 0; x < mPhases.length; x++ )
{
interpolated[ pointer ] = mPhases[ x ].filter();
pointer++;
}
}
return interpolated;
}
/**
* Single phase element of a polyphase interpolating FIR filter. Each phase
* element shares a common buffer. The tap indexes argument defines the
* filter taps used by this phase element to generate an interpolated value
* from the shared circular buffer of samples.
*/
public class Phase
{
private int[] mIndexes;
private double mGain;
public Phase( int[] tapIndexes, int gain )
{
mIndexes = tapIndexes;
mGain = gain;
}
public float filter()
{
double accumulator = 0;
for( int x = 0; x < mIndexes.length; x++ )
{
accumulator += mTaps[ mIndexes[ x ] ] *
mBuffer[ ( x + mBufferPointer ) % mIndexes.length ];
}
return (float)( accumulator * mGain );
}
}
// public static void main( String[] args )
// {
// int interpolation = 6;
//
// PolyphaseFIRInterpolatingFilter p =
// new PolyphaseFIRInterpolatingFilter( IMBESynthesizer.INTERPOLATION_TAPS, interpolation );
//
// Oscillator o = new Oscillator( 500, 8000 );
//
// int iterations = 40;
//
// float[] in = new float[ iterations ];
// float[] out = new float[ iterations * interpolation ];
//
// int inIndex = 0;
// int outIndex = 0;
//
// for( int x = 0; x < iterations; x++ )
// {
// float next = o.nextFloat();
//
// in[ inIndex++ ] = next;
//
// float[] samples = p.interpolate( next );
//
// System.arraycopy( samples, 0, out, outIndex, samples.length );
//
// outIndex += interpolation;
// }
//
// mLog.debug( "In: " + Arrays.toString( in ) );
// mLog.debug( "Out: " + Arrays.toString( out ) );
// }
}