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