diff --git a/.classpath b/.classpath index 893c713b..cecdafa7 100644 --- a/.classpath +++ b/.classpath @@ -24,6 +24,6 @@ - + diff --git a/build/build.xml b/build/build.xml index 88541ed7..5b068185 100644 --- a/build/build.xml +++ b/build/build.xml @@ -6,7 +6,7 @@ - + @@ -94,7 +94,7 @@ - diff --git a/src/bits/SoftSyncDetector.java b/src/bits/SoftSyncDetector.java index 9c89f479..15cbc858 100644 --- a/src/bits/SoftSyncDetector.java +++ b/src/bits/SoftSyncDetector.java @@ -31,6 +31,11 @@ public class SoftSyncDetector implements ISyncProcessor } } + public void setThreshold( int threshold ) + { + mThreshold = threshold; + } + public void setListener( ISyncDetectListener listener ) { mListener = listener; diff --git a/src/decode/p25/C4FMSymbolFilter.java b/src/decode/p25/C4FMSymbolFilter.java index 65b2c286..bafff11f 100644 --- a/src/decode/p25/C4FMSymbolFilter.java +++ b/src/decode/p25/C4FMSymbolFilter.java @@ -234,6 +234,8 @@ public class C4FMSymbolFilter implements FrequencyCorrectionResetListener, { if( mReset ) { + mGainController.reset(); + mCoarseFrequencyCorrection = 0.0f; mFineFrequencyCorrection = 0.0f; mSymbolSpread = 2.0f; @@ -346,11 +348,12 @@ public class C4FMSymbolFilter implements FrequencyCorrectionResetListener, /* send frequency correction */ if( Math.abs( mCoarseFrequencyCorrection ) > COARSE_FREQUENCY_DEADBAND ) { - mFrequencyCorrectionControl.adjust( - 500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 ) ); + int adjustment = 500 * ( mCoarseFrequencyCorrection > 0 ? 1 : -1 ); + + mFrequencyCorrectionControl.adjust( adjustment ); - /* reset internal frequency error tracker */ - mCoarseFrequencyCorrection = 0; + /* reset internal frequency error tracker */ + mCoarseFrequencyCorrection = 0; } if( mSymbolSpreadTap != null ) diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java index c62cda80..63cb42f7 100644 --- a/src/decode/p25/P25Decoder.java +++ b/src/decode/p25/P25Decoder.java @@ -128,13 +128,13 @@ public class P25Decoder extends Decoder implements Instrumentable * modulation (C4FM or CQPSK) */ if( mSourceSampleType == SampleType.COMPLEX ) { - mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass( - 48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0 ); - - this.addComplexListener( mBasebandFilter ); - if( modulation == Modulation.CQPSK ) { + mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass( + 48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0 ); + + this.addComplexListener( mBasebandFilter ); + mAGC = new ComplexFeedForwardGainControl( 32 ); mBasebandFilter.setListener( mAGC ); @@ -154,6 +154,11 @@ public class P25Decoder extends Decoder implements Instrumentable } else /* C4FM */ { + mBasebandFilter = new ComplexFIRFilter( FilterFactory.getLowPass( + 48000, 6750, 7500, 60, WindowType.HANNING, true ), 1.0 ); + + this.addComplexListener( mBasebandFilter ); + mFMDemodulator = new FMDiscriminator( 1.0f ); mBasebandFilter.setListener( mFMDemodulator ); @@ -161,8 +166,6 @@ public class P25Decoder extends Decoder implements Instrumentable * can process the output as if it were coming from any other * real sample source */ mFMDemodulator.setListener( getRealReceiver() ); - - } } diff --git a/src/decode/p25/P25MessageFramer.java b/src/decode/p25/P25MessageFramer.java index dc8aac08..7d02e706 100644 --- a/src/decode/p25/P25MessageFramer.java +++ b/src/decode/p25/P25MessageFramer.java @@ -43,7 +43,11 @@ public class P25MessageFramer implements Listener LoggerFactory.getLogger( P25MessageFramer.class ); /* Determines the threshold for sync pattern soft matching */ - private static final int SYNC_PATTERN_SOFT_MATCH_THRESHOLD = 4; + private static final int SYNC_MATCH_THRESHOLD = 4; + private static final int SYNC_IN_CALL_THRESHOLD = 8; + + private SoftSyncDetector mPrimarySyncDetector = new SoftSyncDetector( + FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_MATCH_THRESHOLD ); /* Costas Loop phase lock error correction values. A phase lock error of * 90 degrees requires a correction of 1/4 of the symbol rate (1200Hz). An @@ -89,10 +93,7 @@ public class P25MessageFramer implements Listener { mAliasList = aliasList; - SoftSyncDetector primarySyncDetector = new SoftSyncDetector( - FrameSync.P25_PHASE1_NORMAL.getSync(), SYNC_PATTERN_SOFT_MATCH_THRESHOLD ); - - primarySyncDetector.setListener( new ISyncDetectListener() + mPrimarySyncDetector.setListener( new ISyncDetectListener() { @Override public void syncDetected() @@ -108,7 +109,7 @@ public class P25MessageFramer implements Listener } } ); - mMatcher.add( primarySyncDetector ); + mMatcher.add( mPrimarySyncDetector ); /** * We use two message assemblers to catch any sync detections, so that @@ -276,6 +277,9 @@ public class P25MessageFramer implements Listener case HDU: mComplete = true; dispatch( new HDUMessage( mMessage.copy(), mDUID, mAliasList ) ); + + /* We're in a call now, lower the sync match threshold */ + mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD ); break; case LDU1: mComplete = true; @@ -285,10 +289,16 @@ public class P25MessageFramer implements Listener /* Convert the LDU1 message into a link control LDU1 message */ dispatch( LDULCMessageFactory.getMessage( ldu1 ) ); + + /* We're in a call now, lower the sync match threshold */ + mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD ); break; case LDU2: mComplete = true; dispatch( new LDU2Message( mMessage.copy(), mDUID, mAliasList ) ); + + /* We're in a call now, lower the sync match threshold */ + mPrimarySyncDetector.setThreshold( SYNC_IN_CALL_THRESHOLD ); break; case PDU0: @@ -328,6 +338,10 @@ public class P25MessageFramer implements Listener { mComplete = true; } + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); + break; case PDU1: /* Remove interleaving */ @@ -390,6 +404,9 @@ public class P25MessageFramer implements Listener { mComplete = true; } + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); break; case PDU3: /* Remove interleaving */ @@ -456,10 +473,16 @@ public class P25MessageFramer implements Listener mComplete = true; } + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); break; case TDU: dispatch( new TDUMessage( mMessage.copy(), mDUID, mAliasList ) ); mComplete = true; + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); break; case TDULC: TDULinkControlMessage tdulc = new TDULinkControlMessage( @@ -470,6 +493,9 @@ public class P25MessageFramer implements Listener dispatch( tdulc ); mComplete = true; + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); break; case TSBK1: /* Remove interleaving */ @@ -512,6 +538,9 @@ public class P25MessageFramer implements Listener { mComplete = true; } + + /* Set sync match threshold to normal */ + mPrimarySyncDetector.setThreshold( SYNC_MATCH_THRESHOLD ); break; case TSBK2: /* Remove interleaving */ diff --git a/src/decode/p25/audio/IMBEAudioFormat.java b/src/decode/p25/audio/IMBEAudioFormat.java index ac0eee1e..348640c7 100644 --- a/src/decode/p25/audio/IMBEAudioFormat.java +++ b/src/decode/p25/audio/IMBEAudioFormat.java @@ -12,9 +12,8 @@ public class IMBEAudioFormat public static final float IMBE_FRAME_RATE = 50; public static final float IMBE_SAMPLE_RATE = 50; - public static final float PCM_8KHZ_SAMPLE_RATE = 8000; - public static final float PCM_48KHZ_SAMPLE_RATE = 48000; - public static final float PCM_FRAME_RATE = 8000; + public static final float PCM_8KHZ_RATE = 8000; + public static final float PCM_48KHZ_RATE = 48000; public static final int IMBE_FRAME_SIZE_BYTES = 18; public static final int IMBE_SAMPLE_SIZE_BITS = 144; @@ -33,20 +32,20 @@ public class IMBEAudioFormat public static AudioFormat PCM_SIGNED_8KHZ_16BITS = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, - PCM_8KHZ_SAMPLE_RATE, + PCM_8KHZ_RATE, PCM_SAMPLE_SIZE_BITS, ONE_CHANNEL, PCM_FRAME_SIZE_BYTES, - PCM_FRAME_RATE, + PCM_8KHZ_RATE, LITTLE_ENDIAN ); public static AudioFormat PCM_SIGNED_48KHZ_16BITS = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, - PCM_8KHZ_SAMPLE_RATE, + PCM_48KHZ_RATE, PCM_SAMPLE_SIZE_BITS, ONE_CHANNEL, PCM_FRAME_SIZE_BYTES, - PCM_FRAME_RATE, + PCM_48KHZ_RATE, LITTLE_ENDIAN ); } diff --git a/src/decode/p25/audio/IMBETargetDataLine.java b/src/decode/p25/audio/IMBETargetDataLine.java index a402400f..b3bfd088 100644 --- a/src/decode/p25/audio/IMBETargetDataLine.java +++ b/src/decode/p25/audio/IMBETargetDataLine.java @@ -77,11 +77,11 @@ public class IMBETargetDataLine implements TargetDataLine return 0; } - private int getBytesAvailable() - { - return 1920; +// private int getBytesAvailable() +// { +// return 1920; // return mFrame.length; - } +// } /** * Primary inject point for submitting IMBE frame data into the audio diff --git a/src/decode/p25/audio/P25AudioOutput.java b/src/decode/p25/audio/P25AudioOutput.java index 7bfcfcd7..bd096d28 100644 --- a/src/decode/p25/audio/P25AudioOutput.java +++ b/src/decode/p25/audio/P25AudioOutput.java @@ -1,13 +1,15 @@ package decode.p25.audio; import java.io.IOException; -import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Line; import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; import javax.sound.sampled.SourceDataLine; import message.Message; @@ -39,15 +41,17 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, private final static Logger mLog = LoggerFactory.getLogger( P25AudioOutput.class ); /* 1 IMBE frame = 160 samples/320 bytes @ 8kHz or 1920 bytes at 48kHz */ - private static final int PROCESSED_AUDIO_FRAME_SIZE = 320; + private static final int PROCESSED_8KHZ_AUDIO_FRAME_SIZE = 320; + private static final int PROCESSED_48KHZ_AUDIO_FRAME_SIZE = 1920; + private int mFrameSize = 1920; + private IMBETargetDataLine mIMBETargetDataLine = new IMBETargetDataLine(); private SourceDataLine mOutput; private AudioInputStream mIMBEToPCMConverter; -// private AudioInputStream mUpsampler; - private ArrayBlockingQueue mIMBEFrameQueue = - new ArrayBlockingQueue( 200 ); - private ArrayBlockingQueue mProcessedAudioQueue = - new ArrayBlockingQueue( 200 ); + private LinkedTransferQueue mIMBEFrameQueue = + new LinkedTransferQueue(); + private LinkedTransferQueue mProcessedAudioQueue = + new LinkedTransferQueue(); private AtomicBoolean mConverting = new AtomicBoolean(); private AtomicBoolean mDispatching = new AtomicBoolean(); @@ -130,55 +134,110 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, private void loadConverter() { try - { - mIMBEToPCMConverter = AudioSystem - .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS, - new AudioInputStream( mIMBETargetDataLine ) ); - - if( mIMBEToPCMConverter != null ) - { - mLog.info( "IMBE audio converter library loaded successfully" ); - - try - { - mOutput = AudioSystem.getSourceDataLine( - IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS ); + { + mOutput = AudioSystem.getSourceDataLine( + IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS ); - if( mOutput != null ) - { - /* Open the audio line with room for two buffers */ - mOutput.open( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS, - PROCESSED_AUDIO_FRAME_SIZE * 2 ); - - mCanProcessAudio = true; - } - else - { - mLog.debug( "Couldn't create output line" ); - } - } - catch ( LineUnavailableException lue ) + if( mOutput != null ) + { + mFrameSize = PROCESSED_48KHZ_AUDIO_FRAME_SIZE; + + /* Open the audio line with room for two buffers */ + mOutput.open( IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS, + mFrameSize * 2 ); + + mCanProcessAudio = true; + } + else + { + mLog.debug( "Couldn't obtain a 48kHz source data line " + + "- attempting 8kHz rate" ); + + mOutput = AudioSystem.getSourceDataLine( + IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS ); + + if( mOutput != null ) + { + mFrameSize = PROCESSED_8KHZ_AUDIO_FRAME_SIZE; + + /* Open the audio line with room for two buffers */ + mOutput.open( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS, + mFrameSize * 2 ); + + mCanProcessAudio = true; + } + else { - mLog.error( "AudioOutput - couldn't open audio output line " - + "for playback", lue ); + mLog.error( "Couldn't obtain a 48 kHz or 8 kHz " + + "source data line - no decoded IMBE audio " + + "will be available" ); + + logSourceLines(); } - catch( IllegalArgumentException iae ) - { - mLog.debug( "Error fetching or opening audio output for 8kHz 16 bit audio", iae ); - } - catch( Exception e ) - { - mLog.error( "Error with audio output", e ); - } - } - else - { - mLog.info( "could not load IMBE audio converter library" ); - } - } + } + } + catch ( LineUnavailableException lue ) + { + mLog.error( "AudioOutput - couldn't open audio output line " + + "for playback", lue ); + } catch( IllegalArgumentException iae ) { - mLog.error( "could NOT find/load IMBE audio converter library", iae ); + mLog.error( "Error fetching or opening audio output for " + + "48kHz or 8kHz 16 bit audio", iae ); + } + catch( Exception e ) + { + mLog.error( "Error with audio output", e ); + } + + if( mCanProcessAudio ) + { + try + { + if( mOutput.getFormat().getSampleRate() == IMBEAudioFormat.PCM_48KHZ_RATE ) + { + mIMBEToPCMConverter = AudioSystem + .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_48KHZ_16BITS, + new AudioInputStream( mIMBETargetDataLine ) ); + } + else + { + mIMBEToPCMConverter = AudioSystem + .getAudioInputStream( IMBEAudioFormat.PCM_SIGNED_8KHZ_16BITS, + new AudioInputStream( mIMBETargetDataLine ) ); + } + + if( mIMBEToPCMConverter != null ) + { + mLog.info( "IMBE audio converter library loaded successfully" ); + } + else + { + mLog.info( "could not load IMBE audio converter library" ); + } + } + catch( IllegalArgumentException iae ) + { + mLog.error( "could NOT find/load IMBE audio converter library", iae ); + } + } + } + + private void logSourceLines() + { + Mixer.Info[] infos = AudioSystem.getMixerInfo(); + + for( Mixer.Info info: AudioSystem.getMixerInfo() ) + { + Mixer mixer = AudioSystem.getMixer( info ); + + Line.Info[] lineInfos = mixer.getSourceLineInfo(); + + for( Line.Info lineInfo: lineInfos ) + { + mLog.debug( "Source Line: " + lineInfo.toString() ); + } } } @@ -240,11 +299,13 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, { if( frame.length == 18 ) { + long start = System.currentTimeMillis(); + /* Insert an 18-byte imbe frame into the stream */ mIMBETargetDataLine.receive( frame ); - /* Read 1920 bytes of converted audio from output */ - byte[] audio = new byte[ PROCESSED_AUDIO_FRAME_SIZE ]; + /* Read one frame of converted audio from output */ + byte[] audio = new byte[ mFrameSize ]; try { @@ -252,6 +313,8 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, if( read > 0 ) { + mLog.debug( "Conversion Time: " + ( System.currentTimeMillis() - start ) ); + receive( audio ); } else @@ -292,7 +355,7 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, { if( mDispatching.compareAndSet( false, true ) ) { - mOutput.drain(); +// mOutput.drain(); /* Write 2 frames of audio and then start the line */ writeAudioFrame(); @@ -300,10 +363,12 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, mOutput.start(); - while( mEmptyFrameCount < 3 ) + while( mEmptyFrameCount < 4 ) { writeAudioFrame(); } + + mLog.debug( "Stopping ..." ); mOutput.stop(); @@ -318,12 +383,17 @@ public class P25AudioOutput implements IAudioOutput, IAudioTypeListener, if( audio == null ) { mEmptyFrameCount++; + mLog.debug( " ... inserting empty" ); + } + else + { + mEmptyFrameCount = 0; } /* Send empty audio if we're not currently enabled to playback */ if( audio == null || !mEnabled.get() ) { - audio = new byte[ PROCESSED_AUDIO_FRAME_SIZE ]; + audio = new byte[ mFrameSize ]; } if( mOutput != null ) diff --git a/src/dsp/filter/PolyphaseFIRInterpolatingFilter.java b/src/dsp/filter/PolyphaseFIRInterpolatingFilter.java new file mode 100644 index 00000000..0a0829ad --- /dev/null +++ b/src/dsp/filter/PolyphaseFIRInterpolatingFilter.java @@ -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 ) ); +// } +} diff --git a/src/dsp/filter/equalizer/CMAEqualizer.java b/src/dsp/filter/equalizer/CMAEqualizer.java new file mode 100644 index 00000000..506b725e --- /dev/null +++ b/src/dsp/filter/equalizer/CMAEqualizer.java @@ -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 +{ + 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 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 listener ) + { + mListener = listener; + } + + private void calculateError( ComplexSample sample ) + { + mError = ComplexSample.multiply( sample, sample.norm() - mModulus ); + mError.clip( 1.0f ); + } +} diff --git a/src/dsp/gain/DirectGainControl.java b/src/dsp/gain/DirectGainControl.java index 78223427..72ed0f6e 100644 --- a/src/dsp/gain/DirectGainControl.java +++ b/src/dsp/gain/DirectGainControl.java @@ -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() { diff --git a/src/dsp/gain/GainController.java b/src/dsp/gain/GainController.java index b1f15e8a..a38bc3fe 100644 --- a/src/dsp/gain/GainController.java +++ b/src/dsp/gain/GainController.java @@ -5,4 +5,6 @@ public interface GainController public abstract void increase(); public abstract void decrease(); + + public abstract void reset(); } \ No newline at end of file diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index a22f6ff0..f2c1a95f 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -203,7 +203,7 @@ public class TunerChannelSource extends ComplexSource long frequencyErrorOffset = mTunerFrequency - mTunerChannel.getFrequency() - mTunerFrequencyError; - + mSineWaveGenerator.setFrequency( frequencyErrorOffset ); break; case FREQUENCY: @@ -211,6 +211,8 @@ public class TunerChannelSource extends ComplexSource /* If the frequency is updated, the AFC will also get reset, so * we don't include the frequency error value here */ + mTunerFrequencyError = 0; + long frequencyOffset = frequency - mTunerChannel.getFrequency(); mSineWaveGenerator.setFrequency( frequencyOffset );