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:
dennis.sheirer
2015-05-11 09:37:50 +00:00
parent bc5080aa4a
commit fccde3d112
14 changed files with 495 additions and 89 deletions

View 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 ) );
// }
}

View 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 );
}
}