mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 00:57:31 -05:00
Build 0.1.3 - adds support for 48 kHz audio output from JMBE library for P25 audio. Resolves issue with Automatic Frequency Control on P25 C4FM channels where the channel was not tracking changes in tuner frequency.
This commit is contained in:
186
src/dsp/filter/PolyphaseFIRInterpolatingFilter.java
Normal file
186
src/dsp/filter/PolyphaseFIRInterpolatingFilter.java
Normal file
@@ -0,0 +1,186 @@
|
||||
package dsp.filter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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 */
|
||||
assert( 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 ) );
|
||||
// }
|
||||
}
|
||||
99
src/dsp/filter/equalizer/CMAEqualizer.java
Normal file
99
src/dsp/filter/equalizer/CMAEqualizer.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package dsp.filter.equalizer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Listener;
|
||||
import sample.complex.ComplexSample;
|
||||
|
||||
public class CMAEqualizer implements Listener<ComplexSample>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( CMAEqualizer.class );
|
||||
|
||||
public static final int TAP_COUNT = 4;
|
||||
|
||||
private float mModulus;
|
||||
private float mMu;
|
||||
private ComplexSample mError = new ComplexSample( 0.0f, 0.0f );
|
||||
|
||||
private ComplexSample[] mTaps = new ComplexSample[ TAP_COUNT ];
|
||||
private ComplexSample[] mBuffer = new ComplexSample[ TAP_COUNT ];
|
||||
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
private Listener<ComplexSample> mListener;
|
||||
|
||||
public CMAEqualizer( float modulus, float mu )
|
||||
{
|
||||
mModulus = modulus;
|
||||
mMu = mu;
|
||||
|
||||
mTaps[ 0 ] = new ComplexSample( 1.0f, 1.0f );
|
||||
mBuffer[ 0 ] = new ComplexSample( 1.0f, 1.0f );
|
||||
|
||||
for( int x = 1; x < TAP_COUNT; x++ )
|
||||
{
|
||||
mTaps[ x ] = new ComplexSample( 0.0f, 0.0f );
|
||||
mBuffer[ x ] = new ComplexSample( 1.0f, 1.0f );
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTaps()
|
||||
{
|
||||
for( int x = 0; x < TAP_COUNT; x++ )
|
||||
{
|
||||
// tap -= mu * conj(sample) * error
|
||||
|
||||
mTaps[ x ] = ComplexSample.subtract( mTaps[ x ],
|
||||
ComplexSample.multiply( ComplexSample.multiply(
|
||||
mBuffer[ x ].conjugate(), mMu ), mError ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( ComplexSample sample )
|
||||
{
|
||||
mBuffer[ mBufferPointer ] = sample;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
mBufferPointer = mBufferPointer % TAP_COUNT;
|
||||
|
||||
ComplexSample equalized = filter();
|
||||
|
||||
calculateError( equalized );
|
||||
|
||||
updateTaps();
|
||||
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( equalized );
|
||||
}
|
||||
|
||||
mLog.debug( "Tap 0:" + mTaps[ 0 ].toString() + " error:" + mError.toString() );
|
||||
}
|
||||
|
||||
private ComplexSample filter()
|
||||
{
|
||||
ComplexSample sum = new ComplexSample( 0.0f, 0.0f );
|
||||
|
||||
for( int x = 0; x < TAP_COUNT; x++ )
|
||||
{
|
||||
sum = ComplexSample.add( sum,
|
||||
ComplexSample.multiply( mBuffer[ x ], mTaps[ x ] ) );
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public void setListener( Listener<ComplexSample> listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void calculateError( ComplexSample sample )
|
||||
{
|
||||
mError = ComplexSample.multiply( sample, sample.norm() - mModulus );
|
||||
mError.clip( 1.0f );
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( DirectGainControl.class );
|
||||
|
||||
private float mDefaultGain;
|
||||
private float mGain;
|
||||
private float mMinimum;
|
||||
private float mMaximum;
|
||||
@@ -49,7 +50,8 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
public DirectGainControl( float gain, float minimum, float maximum, float increment )
|
||||
{
|
||||
assert( minimum > 0 );
|
||||
|
||||
|
||||
mDefaultGain = gain;
|
||||
mGain = gain;
|
||||
mMinimum = minimum;
|
||||
mMaximum = maximum;
|
||||
@@ -62,6 +64,12 @@ public class DirectGainControl implements GainController, RealSampleListener
|
||||
mListener.receive( (float)( sample * mGain ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mGain = mDefaultGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increase()
|
||||
{
|
||||
|
||||
@@ -5,4 +5,6 @@ public interface GainController
|
||||
public abstract void increase();
|
||||
|
||||
public abstract void decrease();
|
||||
|
||||
public abstract void reset();
|
||||
}
|
||||
Reference in New Issue
Block a user