mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
330 WIP on redesigning the P25 C4FM decoder. Creates a new decoder
based on the P25 LSM decoder using a costas loop and a gardner detector. Updates the Demodulator Viewer FX application to add in a symbol chart and a phase error chart. The decoder won't lock on yet ... still a wip.
This commit is contained in:
@@ -1,80 +1,168 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 io.github.dsheirer.buffer;
|
||||
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Variance;
|
||||
import org.apache.commons.math3.stat.descriptive.rank.Max;
|
||||
import org.apache.commons.math3.stat.descriptive.rank.Min;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
||||
* fashion, overwriting older samples with newly arrived samples. Initially
|
||||
* fills buffer with 0-valued samples
|
||||
*
|
||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
||||
* Circular sample buffer - allocates a buffer and stores samples in a circular fashion, overwriting older samples with
|
||||
* newly arrived samples. Initially fills buffer with 0-valued (default) samples or with a specified value.
|
||||
*/
|
||||
public class DoubleCircularBuffer
|
||||
{
|
||||
double[] mBuffer;
|
||||
int mBufferPointer = 0;
|
||||
|
||||
public DoubleCircularBuffer( int size )
|
||||
{
|
||||
mBuffer = new double[ size ];
|
||||
|
||||
Arrays.fill( mBuffer, 0.0 );
|
||||
}
|
||||
private Variance mVariance;
|
||||
private Max mMax;
|
||||
private Mean mMean;
|
||||
private Min mMin;
|
||||
private double[] mBuffer;
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
/**
|
||||
* Puts the new value into the buffer and returns the oldest buffer value
|
||||
* that it replaced
|
||||
* @param newValue
|
||||
* @return
|
||||
*/
|
||||
public double get( double newValue )
|
||||
{
|
||||
double oldestSample = mBuffer[ mBufferPointer ];
|
||||
/**
|
||||
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||
* @param size of the buffer
|
||||
* @param initialFillValue to fill the buffer entries
|
||||
*/
|
||||
public DoubleCircularBuffer(int size, double initialFillValue)
|
||||
{
|
||||
mBuffer = new double[size];
|
||||
Arrays.fill(mBuffer, initialFillValue);
|
||||
}
|
||||
|
||||
mBuffer[ mBufferPointer ] = newValue;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if( mBufferPointer >= mBuffer.length )
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
return oldestSample;
|
||||
}
|
||||
/**
|
||||
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
||||
* @param size of the buffer
|
||||
*/
|
||||
public DoubleCircularBuffer(int size)
|
||||
{
|
||||
this(size, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum from the values currently in the buffer
|
||||
*/
|
||||
public double max()
|
||||
{
|
||||
double max = 0.0d;
|
||||
|
||||
for( int x = 0; x < mBuffer.length; x++ )
|
||||
{
|
||||
if( mBuffer[ x ] > max )
|
||||
{
|
||||
max = mBuffer[ x ];
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
/**
|
||||
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
||||
*
|
||||
* @param newValue to add to the buffer
|
||||
* @return oldest value that was overwritten by the new value
|
||||
*/
|
||||
public double get(double newValue)
|
||||
{
|
||||
double oldestSample = mBuffer[mBufferPointer];
|
||||
|
||||
mBuffer[mBufferPointer] = newValue;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if(mBufferPointer >= mBuffer.length)
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
|
||||
return oldestSample;
|
||||
}
|
||||
|
||||
public double[] getAll()
|
||||
{
|
||||
int valuePointer = 0;
|
||||
double[] values = new double[mBuffer.length];
|
||||
int bufferPointer = mBufferPointer;
|
||||
|
||||
while(valuePointer < values.length)
|
||||
{
|
||||
values[valuePointer++] = mBuffer[bufferPointer++];
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the new value into the buffer, overwriting the oldest value
|
||||
* @param newValue to add to the buffer
|
||||
*/
|
||||
public void put(double newValue)
|
||||
{
|
||||
mBuffer[mBufferPointer] = newValue;
|
||||
|
||||
mBufferPointer++;
|
||||
|
||||
if(mBufferPointer >= mBuffer.length)
|
||||
{
|
||||
mBufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum from the values currently in the buffer
|
||||
*/
|
||||
public double max()
|
||||
{
|
||||
if(mMax == null)
|
||||
{
|
||||
mMax = new Max();
|
||||
}
|
||||
|
||||
return mMax.evaluate(mBuffer, 0, mBuffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum from the values currently in the buffer
|
||||
*/
|
||||
public double min()
|
||||
{
|
||||
if(mMin == null)
|
||||
{
|
||||
mMin = new Min();
|
||||
}
|
||||
|
||||
return mMin.evaluate(mBuffer, 0, mBuffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the variance of the values contained in this buffer
|
||||
*/
|
||||
public double variance()
|
||||
{
|
||||
if(mVariance == null)
|
||||
{
|
||||
mVariance = new Variance();
|
||||
}
|
||||
|
||||
return mVariance.evaluate(mBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average/mean of the values contained in this buffer
|
||||
*/
|
||||
public double mean()
|
||||
{
|
||||
if(mMean == null)
|
||||
{
|
||||
mMean = new Mean();
|
||||
}
|
||||
|
||||
return mMean.evaluate(mBuffer, 0, mBuffer.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,24 @@ public class FilterViewer extends Application
|
||||
*/
|
||||
private float[] getFilter()
|
||||
{
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
// .sampleRate(48000)
|
||||
// .gridDensity(16)
|
||||
// .passBandCutoff(2500)
|
||||
// .passBandAmplitude(1.0)
|
||||
// .passBandRipple(0.01)
|
||||
// .stopBandStart(4000)
|
||||
// .stopBandAmplitude(0.0)
|
||||
// .stopBandRipple(0.008)
|
||||
// .build();
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(48000)
|
||||
.gridDensity(16)
|
||||
.passBandCutoff(2500)
|
||||
.sampleRate((int)48000.0)
|
||||
.passBandCutoff(6000)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(4000)
|
||||
.passBandRipple(0.1)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.008)
|
||||
.stopBandStart(6250)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
|
||||
|
||||
@@ -1,54 +1,90 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.filter.fir.complex;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
|
||||
public class ComplexFIRFilter extends FIRFilter
|
||||
{
|
||||
private RealFIRFilter mIFilter;
|
||||
private RealFIRFilter mQFilter;
|
||||
|
||||
/**
|
||||
* Complex FIR Filter for processing complex sample pairs. Wraps two real
|
||||
* FIR filters for processing each of the inphase and quadrature samples.
|
||||
*
|
||||
* @param coefficients - filter taps
|
||||
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
|
||||
*/
|
||||
public ComplexFIRFilter( float[] coefficients, float gain )
|
||||
{
|
||||
mIFilter = new RealFIRFilter( coefficients, gain );
|
||||
mQFilter = new RealFIRFilter( coefficients, gain );
|
||||
}
|
||||
|
||||
public float[] getCoefficients()
|
||||
{
|
||||
return mIFilter.getCoefficients();
|
||||
}
|
||||
|
||||
public float filterInphase( float sample )
|
||||
{
|
||||
return mIFilter.filter( sample );
|
||||
}
|
||||
|
||||
public float filterQuadrature( float sample )
|
||||
{
|
||||
return mQFilter.filter( sample );
|
||||
}
|
||||
|
||||
public Complex filter( Complex sample )
|
||||
{
|
||||
float i = filterInphase( sample.inphase() );
|
||||
float q = filterQuadrature( sample.quadrature() );
|
||||
|
||||
return new Complex( i, q );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mIFilter.dispose();
|
||||
mQFilter.dispose();
|
||||
}
|
||||
private RealFIRFilter mIFilter;
|
||||
private RealFIRFilter mQFilter;
|
||||
|
||||
/**
|
||||
* Complex FIR Filter for processing complex sample pairs. Wraps two real
|
||||
* FIR filters for processing each of the inphase and quadrature samples.
|
||||
*
|
||||
* @param coefficients - filter taps
|
||||
* @param gain - gain to apply to filtered outputs - use 1.0f for no gain
|
||||
*/
|
||||
public ComplexFIRFilter(float[] coefficients, float gain)
|
||||
{
|
||||
mIFilter = new RealFIRFilter(coefficients, gain);
|
||||
mQFilter = new RealFIRFilter(coefficients, gain);
|
||||
}
|
||||
|
||||
public float[] getCoefficients()
|
||||
{
|
||||
return mIFilter.getCoefficients();
|
||||
}
|
||||
|
||||
public float filterInphase(float sample)
|
||||
{
|
||||
return mIFilter.filter(sample);
|
||||
}
|
||||
|
||||
public float filterQuadrature(float sample)
|
||||
{
|
||||
return mQFilter.filter(sample);
|
||||
}
|
||||
|
||||
public Complex filter(Complex sample)
|
||||
{
|
||||
float i = filterInphase(sample.inphase());
|
||||
float q = filterQuadrature(sample.quadrature());
|
||||
|
||||
return new Complex(i, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the complex samples from the buffer and returns a new complex buffer with the filtered output
|
||||
*
|
||||
* @param buffer with complex samples to filter
|
||||
* @return new buffer containing filtered complex samples
|
||||
*/
|
||||
public ComplexBuffer filter(ComplexBuffer buffer)
|
||||
{
|
||||
float[] samples = buffer.getSamples();
|
||||
float[] filtered = new float[samples.length];
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
filtered[x] = filterInphase(samples[x]);
|
||||
filtered[x + 1] = filterQuadrature(samples[x + 1]);
|
||||
}
|
||||
|
||||
return new ComplexBuffer(filtered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mIFilter.dispose();
|
||||
mQFilter.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ package io.github.dsheirer.dsp.filter.fir.complex;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
|
||||
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter
|
||||
implements Listener<ComplexBuffer>
|
||||
public class ComplexFIRFilter_CB_CB extends ComplexFIRFilter implements Listener<ComplexBuffer>
|
||||
{
|
||||
private Listener<ComplexBuffer> mListener;
|
||||
|
||||
|
||||
@@ -1,112 +1,125 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.gain;
|
||||
|
||||
import io.github.dsheirer.buffer.FloatCircularBuffer;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2015 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/>
|
||||
*
|
||||
* This class is modeled using concepts detailed in the GNURadio
|
||||
* implementation of feed forward AGC class located at:
|
||||
* https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/
|
||||
* feedforward_agc_cc_impl.cc
|
||||
*
|
||||
******************************************************************************/
|
||||
public class ComplexFeedForwardGainControl implements ComplexSampleListener
|
||||
{
|
||||
public static final float OBJECTIVE_ENVELOPE = 1.0f;
|
||||
public static final float MINIMUM_ENVELOPE = 0.0001f;
|
||||
public static final float OBJECTIVE_ENVELOPE = 1.0f;
|
||||
public static final float MINIMUM_ENVELOPE = 0.0001f;
|
||||
|
||||
private ComplexSampleListener mListener;
|
||||
private ComplexSampleListener mListener;
|
||||
|
||||
private FloatCircularBuffer mEnvelopeHistory;
|
||||
|
||||
private float mMaxEnvelope = 0.0f;
|
||||
private float mGain = 1.0f;
|
||||
|
||||
/**
|
||||
* Dynamic gain control for incoming sample stream to amplify or attenuate
|
||||
* all samples toward an objective unity)gain, using the maximum envelope
|
||||
* value detected in the stream history window.
|
||||
*
|
||||
* Uses the specified damping factor to limit gain swings. Damping factor
|
||||
* is applied against the delta between current gain value and a recalculated
|
||||
* gain value to limit how quickly the gain value will increase or decrease.
|
||||
*
|
||||
* @param window - history size to use in detecting maximum envelope value
|
||||
* @param damping - a value between 0 < damping <= 1.0;
|
||||
*/
|
||||
public ComplexFeedForwardGainControl( int window )
|
||||
{
|
||||
mEnvelopeHistory = new FloatCircularBuffer( window );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
private FloatCircularBuffer mEnvelopeHistory;
|
||||
|
||||
@Override
|
||||
public void receive( float inphase, float quadrature )
|
||||
{
|
||||
float envelope = Complex.envelope( inphase, quadrature );
|
||||
private float mMaxEnvelope = 0.0f;
|
||||
private float mGain = 1.0f;
|
||||
|
||||
if( envelope > mMaxEnvelope )
|
||||
{
|
||||
mMaxEnvelope = envelope;
|
||||
|
||||
adjustGain();
|
||||
}
|
||||
/**
|
||||
* Dynamic gain control for incoming sample stream to amplify or attenuate
|
||||
* all samples toward an objective unity)gain, using the maximum envelope
|
||||
* value detected in the stream history window.
|
||||
*
|
||||
* Uses the specified damping factor to limit gain swings. Damping factor
|
||||
* is applied against the delta between current gain value and a recalculated
|
||||
* gain value to limit how quickly the gain value will increase or decrease.
|
||||
*
|
||||
* @param window - history size to use in detecting maximum envelope value
|
||||
*/
|
||||
public ComplexFeedForwardGainControl(int window)
|
||||
{
|
||||
mEnvelopeHistory = new FloatCircularBuffer(window);
|
||||
}
|
||||
|
||||
/* Replace oldest envelope value with current envelope value */
|
||||
float oldestEnvelope = mEnvelopeHistory.get( envelope );
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
/* If the oldest envelope value was the max envelope value, then we
|
||||
* have to rediscover the max value from the envelope history */
|
||||
if( mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope )
|
||||
{
|
||||
mMaxEnvelope = MINIMUM_ENVELOPE;
|
||||
|
||||
for( float value: mEnvelopeHistory.getBuffer() )
|
||||
{
|
||||
if( value > mMaxEnvelope )
|
||||
{
|
||||
mMaxEnvelope = value;
|
||||
}
|
||||
}
|
||||
|
||||
adjustGain();
|
||||
}
|
||||
|
||||
/* Apply current gain value to the sample and send to the listener */
|
||||
if( mListener != null )
|
||||
{
|
||||
mListener.receive( inphase *= mGain, quadrature *= mGain );
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustGain()
|
||||
{
|
||||
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
|
||||
}
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
process(inphase, quadrature);
|
||||
|
||||
public void setListener( ComplexSampleListener listener )
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
/* Apply current gain value to the sample and send to the listener */
|
||||
if(mListener != null)
|
||||
{
|
||||
mListener.receive(inphase *= mGain, quadrature *= mGain);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(float inphase, float quadrature)
|
||||
{
|
||||
float envelope = Complex.envelope(inphase, quadrature);
|
||||
|
||||
if(envelope > mMaxEnvelope)
|
||||
{
|
||||
mMaxEnvelope = envelope;
|
||||
|
||||
adjustGain();
|
||||
}
|
||||
|
||||
/* Replace oldest envelope value with current envelope value */
|
||||
float oldestEnvelope = mEnvelopeHistory.get(envelope);
|
||||
|
||||
/* If the oldest envelope value was the max envelope value, then we
|
||||
* have to rediscover the max value from the envelope history */
|
||||
if(mMaxEnvelope == oldestEnvelope && mMaxEnvelope != envelope)
|
||||
{
|
||||
mMaxEnvelope = MINIMUM_ENVELOPE;
|
||||
|
||||
for(float value : mEnvelopeHistory.getBuffer())
|
||||
{
|
||||
if(value > mMaxEnvelope)
|
||||
{
|
||||
mMaxEnvelope = value;
|
||||
}
|
||||
}
|
||||
|
||||
adjustGain();
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustGain()
|
||||
{
|
||||
mGain = OBJECTIVE_ENVELOPE / mMaxEnvelope;
|
||||
}
|
||||
|
||||
public void setListener(ComplexSampleListener listener)
|
||||
{
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||
{
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
float[] processed = new float[samples.length];
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
process(samples[x], samples[x + 1]);
|
||||
|
||||
processed[x] = samples[x] * mGain;
|
||||
processed[x + 1] = samples[x + 1] * mGain;
|
||||
}
|
||||
|
||||
return new ComplexBuffer(processed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ public class Oscillator
|
||||
*/
|
||||
private void update()
|
||||
{
|
||||
float anglePerSample =
|
||||
(float)( 2.0d * Math.PI * mFrequency / mSampleRate );
|
||||
float anglePerSample = (float)( 2.0d * Math.PI * mFrequency / mSampleRate );
|
||||
|
||||
mAnglePerSample = Complex.fromAngle( anglePerSample );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class DQPSKSymbolPhaseErrorCalculator extends QPSKSymbolPhaseErrorCalculator
|
||||
{
|
||||
//Negative 45 degrees rotation to offset for differential additive 45 degrees of rotation
|
||||
public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(Math.PI / -4.0d);
|
||||
|
||||
@Override
|
||||
public void adjust(Complex symbol)
|
||||
{
|
||||
symbol.multiply(DIFFERENTIAL_OFFSET);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class GardnerDetector
|
||||
{
|
||||
private float mErrorInphase;
|
||||
private float mErrorQuadrature;
|
||||
private float mError;
|
||||
|
||||
/**
|
||||
* Gardner detector for recovering symbol timing error.
|
||||
*/
|
||||
public GardnerDetector()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the symbol timing error by evaluating the previous, middle and current symbol instants.
|
||||
*
|
||||
* @param previous symbol
|
||||
* @param middle symbol (ie midway between the previous and current)
|
||||
* @param current symbol
|
||||
* @return symbol timing error
|
||||
*/
|
||||
public float getError(Complex previous, Complex middle, Complex current)
|
||||
{
|
||||
mErrorInphase = (previous.inphase() - current.inphase()) * middle.inphase();
|
||||
mErrorQuadrature = (previous.quadrature() - current.quadrature()) * middle.quadrature();
|
||||
mError = normalize(mErrorInphase + mErrorQuadrature, 1.0f);
|
||||
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains timing error to +/- the maximum value and corrects any floating point invalid numbers
|
||||
*/
|
||||
public static float normalize(float error, float maximum)
|
||||
{
|
||||
if(Float.isNaN(error))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return clip(error, maximum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains value to the range of ( -maximum <> maximum )
|
||||
*/
|
||||
public static float clip(float value, float maximum)
|
||||
{
|
||||
if(value > maximum)
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
else if(value < -maximum)
|
||||
{
|
||||
return -maximum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public interface IQPSKSymbolDecoder
|
||||
{
|
||||
/**
|
||||
* Decodes the symbol and converts it into a Dibit
|
||||
* @param symbol to decode
|
||||
* @return decoded dibit
|
||||
*/
|
||||
Dibit decode(Complex symbol);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public interface ISymbolPhaseErrorCalculator
|
||||
{
|
||||
/**
|
||||
* Adjusts the calculated symbol as necessary before calculating any phase error. This might be useful for
|
||||
* differential encoding to remove a 45 degree rotational spin.
|
||||
* @param symbol to adjust
|
||||
*/
|
||||
void adjust(Complex symbol);
|
||||
|
||||
/**
|
||||
* Calculates the phase error of the symbol relative to an alignment for the constellation
|
||||
* @param symbol to calculate error
|
||||
* @return error value
|
||||
*/
|
||||
float getPhaseError(Complex symbol);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.interpolator.RealInterpolator;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class InterpolatingSymbolBuffer
|
||||
{
|
||||
private Complex mMiddleSample = new Complex(0,0);
|
||||
private Complex mCurrentSample = new Complex(0,0);
|
||||
|
||||
private float[] mDelayLineInphase;
|
||||
private float[] mDelayLineQuadrature;
|
||||
private float mMu; //Sample Counter
|
||||
private float mGainMu = 0.05f; //Sample Counter Gain
|
||||
private float mOmega;
|
||||
private float mGainOmega = 0.1f * mGainMu * mGainMu;
|
||||
private float mOmegaRel = 0.005f;
|
||||
private float mOmegaMid;
|
||||
|
||||
private int mDelayLinePointer = 0;
|
||||
private int mTwiceSamplesPerSymbol;
|
||||
private RealInterpolator mInterpolator = new RealInterpolator(1.0f);
|
||||
|
||||
public InterpolatingSymbolBuffer(float samplesPerSymbol)
|
||||
{
|
||||
mMu = samplesPerSymbol;
|
||||
mTwiceSamplesPerSymbol = (int)Math.floor(2.0 * samplesPerSymbol);
|
||||
mDelayLineInphase = new float[2 * mTwiceSamplesPerSymbol];
|
||||
mDelayLineQuadrature = new float[2 * mTwiceSamplesPerSymbol];
|
||||
mOmega = samplesPerSymbol;
|
||||
mOmegaMid = samplesPerSymbol;
|
||||
|
||||
}
|
||||
|
||||
public void receive(Complex currentSample)
|
||||
{
|
||||
mMu--;
|
||||
|
||||
//Fill up the delay line to use with the interpolator
|
||||
mDelayLineInphase[mDelayLinePointer] = currentSample.inphase();
|
||||
mDelayLineInphase[mDelayLinePointer + mTwiceSamplesPerSymbol] = currentSample.inphase();
|
||||
mDelayLineQuadrature[mDelayLinePointer] = currentSample.quadrature();
|
||||
mDelayLineQuadrature[mDelayLinePointer + mTwiceSamplesPerSymbol] = currentSample.quadrature();
|
||||
|
||||
//Increment pointer and keep pointer in bounds
|
||||
mDelayLinePointer = (mDelayLinePointer + 1) % mTwiceSamplesPerSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal sample counter with additional samples to add to the symbol sample counter.
|
||||
* @param samplesToAdd to the sample counter
|
||||
*/
|
||||
public void increaseSampleCounter(float samplesToAdd)
|
||||
{
|
||||
mMu += samplesToAdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts samples per symbol and symbol timing counters and increments the sample counter to collect another
|
||||
* symbol.
|
||||
*
|
||||
* @param symbolTimingError from a symbol timing error detector
|
||||
*/
|
||||
public void resetAndAdjust(float symbolTimingError)
|
||||
{
|
||||
//mOmega is samples per symbol and is constrained to floating between +/- .005 of the nominal samples per
|
||||
//symbol
|
||||
mOmega = mOmega + mGainOmega * symbolTimingError;
|
||||
mOmega = mOmegaMid + clip(mOmega - mOmegaMid, mOmegaRel);
|
||||
|
||||
//Add another symbol's worth of samples to the counter and adjust timing based on gardner error
|
||||
increaseSampleCounter((mOmega + (mGainMu * symbolTimingError)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this buffer has accumulated a enough samples to represent a full symbol
|
||||
*/
|
||||
public boolean hasSymbol()
|
||||
{
|
||||
return mMu < 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolated middle sample for the symbol. Note: this method should only be invoked after testing for a
|
||||
* complete symbol with the hasSymbol() method.
|
||||
*/
|
||||
public Complex getMiddleSample()
|
||||
{
|
||||
/* Calculate interpolated middle sample and current sample */
|
||||
mMiddleSample.setValues(getInphase(mMu), getQuadrature(mMu));
|
||||
|
||||
return mMiddleSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolated current sample for the symbol. This sample represents the ideal symbol sampling point.
|
||||
* Note: this method should only be invoked after testing for a complete symbol with the hasSymbol() method.
|
||||
*/
|
||||
public Complex getCurrentSample()
|
||||
{
|
||||
float half_omega = mOmega / 2.0f;
|
||||
int half_sps = (int)Math.floor(half_omega);
|
||||
float half_mu = mMu + half_omega - half_sps;
|
||||
|
||||
if(half_mu > 1.0)
|
||||
{
|
||||
half_mu -= 1.0;
|
||||
half_sps += 1;
|
||||
}
|
||||
|
||||
mCurrentSample.setValues(getInphase(half_mu, half_sps), getQuadrature(half_mu, half_sps));
|
||||
|
||||
return mCurrentSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interpolated inphase value for the specified offset
|
||||
* @param interpolation into the buffer to calculate the interpolated sample
|
||||
* @return inphase value of the interpolated sample
|
||||
*/
|
||||
public float getInphase(float interpolation)
|
||||
{
|
||||
return mInterpolator.filter(mDelayLineInphase, mDelayLinePointer, interpolation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interpolated inphase value for the specified offset
|
||||
* @param interpolation into the buffer to calculate the interpolated sample
|
||||
* @return inphase value of the interpolated sample
|
||||
*/
|
||||
public float getInphase(float interpolation, int indexOffet)
|
||||
{
|
||||
return mInterpolator.filter(mDelayLineInphase, mDelayLinePointer + indexOffet, interpolation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interpolated quadrature value for the specified offset
|
||||
* @param interpolation into the buffer to calculate the interpolated sample
|
||||
* @return quadrature value of the interpolated sample
|
||||
*/
|
||||
public float getQuadrature(float interpolation)
|
||||
{
|
||||
return mInterpolator.filter(mDelayLineQuadrature, mDelayLinePointer, interpolation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the interpolated quadrature value for the specified offset
|
||||
* @param interpolation into the buffer to calculate the interpolated sample
|
||||
* @return quadrature value of the interpolated sample
|
||||
*/
|
||||
public float getQuadrature(float interpolation, int indexOffset)
|
||||
{
|
||||
return mInterpolator.filter(mDelayLineQuadrature, mDelayLinePointer + indexOffset, interpolation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains value to the range of ( -maximum <> maximum )
|
||||
*/
|
||||
public static float clip(float value, float maximum)
|
||||
{
|
||||
if(value > maximum)
|
||||
{
|
||||
return maximum;
|
||||
}
|
||||
else if(value < -maximum)
|
||||
{
|
||||
return -maximum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
public class LSMConstellationDecoder implements IQPSKSymbolDecoder
|
||||
{
|
||||
@Override
|
||||
public Dibit decode(Complex symbol)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
182
src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java
Normal file
182
src/main/java/io/github/dsheirer/dsp/psk/QPSKDemodulator.java
Normal file
@@ -0,0 +1,182 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexSampleListener;
|
||||
|
||||
public class QPSKDemodulator implements ComplexSampleListener
|
||||
{
|
||||
private InterpolatingSymbolBuffer mInterpolatingSymbolBuffer;
|
||||
private IPhaseLockedLoop mPLL;
|
||||
private ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator;
|
||||
private IQPSKSymbolDecoder mQPSKSymbolDecoder;
|
||||
private GardnerDetector mGardnerDetector = new GardnerDetector();
|
||||
private Listener<Complex> mSymbolListener;
|
||||
private Listener<Double> mPLLErrorListener;
|
||||
private Listener<Dibit> mDibitListener;
|
||||
|
||||
private Complex mPreviousSample = new Complex(0, 0);
|
||||
private Complex mPreviousMiddleSample = new Complex(0, 0);
|
||||
private Complex mCurrentSample = new Complex(0, 0);
|
||||
private Complex mMiddleSample = new Complex(0, 0);
|
||||
private Complex mPreviousSymbol = new Complex(0, 0);
|
||||
private Complex mMiddleSymbol = new Complex(0, 0);
|
||||
private Complex mCurrentSymbol = new Complex(0, 0);
|
||||
private float mPhaseError;
|
||||
private float mSymbolTimingError;
|
||||
|
||||
/**
|
||||
* Decoder for Quaternary Phase Shift Keying (QPSK) and Differential QPSK (DQPSK). This decoder uses both a
|
||||
* phase-locked loop and a gardner symbol timing error detector to automatically align to the incoming carrier
|
||||
* frequency and to adjust for any changes in symbol timing.
|
||||
*
|
||||
* @param phaseLockedLoop for tracking carrier frequency error
|
||||
* @param symbolPhaseErrorCalculator to calculate symbol phase errors and optionally adjust for differential encoding
|
||||
* @param symbolDecoder to decode demodulated symbols into dibits
|
||||
* @param sampleRate of the incoming complex sample stream
|
||||
* @param symbolRate of the decoded QPSK symbols.
|
||||
*/
|
||||
public QPSKDemodulator(IPhaseLockedLoop phaseLockedLoop, ISymbolPhaseErrorCalculator symbolPhaseErrorCalculator,
|
||||
IQPSKSymbolDecoder symbolDecoder, double sampleRate, double symbolRate)
|
||||
{
|
||||
if(sampleRate < (symbolRate * 2))
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate [" + sampleRate +
|
||||
"] must be at least 2 x symbol rate [" + symbolRate + "]");
|
||||
}
|
||||
|
||||
mPLL = phaseLockedLoop;
|
||||
mSymbolPhaseErrorCalculator = symbolPhaseErrorCalculator;
|
||||
mQPSKSymbolDecoder = symbolDecoder;
|
||||
|
||||
float samplesPerSymbol = (float)(sampleRate / symbolRate);
|
||||
mInterpolatingSymbolBuffer = new InterpolatingSymbolBuffer(samplesPerSymbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a buffer containing complex samples for decoding
|
||||
* @param complexBuffer with complex samples
|
||||
*/
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
float[] samples = complexBuffer.getSamples();
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
receive(samples[x], samples[x + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a complex sample for decoding.
|
||||
* @param inphase value for the sample
|
||||
* @param quadrature value for the sample
|
||||
*/
|
||||
@Override
|
||||
public void receive(float inphase, float quadrature)
|
||||
{
|
||||
//Update current sample with values
|
||||
mCurrentSample.setValues(inphase, quadrature);
|
||||
|
||||
//Mix current sample with costas loop to remove any rotation that is present from a mis-tuned carrier frequency
|
||||
mCurrentSample.multiply(mPLL.incrementAndGetCurrentVector());
|
||||
|
||||
mInterpolatingSymbolBuffer.receive(mCurrentSample);
|
||||
|
||||
//Calculate the symbol once we've stored enough samples
|
||||
if(mInterpolatingSymbolBuffer.hasSymbol())
|
||||
{
|
||||
//Get middle and current samples from the interpolating buffer
|
||||
mMiddleSample = mInterpolatingSymbolBuffer.getMiddleSample();
|
||||
mCurrentSample = mInterpolatingSymbolBuffer.getCurrentSample();
|
||||
|
||||
//Calculate middle and current symbols as the delta between the previous and current samples
|
||||
mMiddleSymbol.setInphase(Complex.multiplyInphase(mMiddleSample.inphase(), mMiddleSample.quadrature(), mPreviousMiddleSample.inphase(), -mPreviousMiddleSample.quadrature()));
|
||||
mMiddleSymbol.setQuadrature(Complex.multiplyQuadrature(mMiddleSample.inphase(), mMiddleSample.quadrature(), mPreviousMiddleSample.inphase(), -mPreviousMiddleSample.quadrature()));
|
||||
mCurrentSymbol.setInphase(Complex.multiplyInphase(mCurrentSample.inphase(), mCurrentSample.quadrature(), mPreviousSample.inphase(), -mPreviousSample.quadrature()));
|
||||
mCurrentSymbol.setQuadrature(Complex.multiplyQuadrature(mCurrentSample.inphase(), mCurrentSample.quadrature(), mPreviousSample.inphase(), -mPreviousSample.quadrature()));
|
||||
|
||||
//Set gain to unity before we calculate the error value
|
||||
mMiddleSymbol.normalize();
|
||||
mCurrentSymbol.normalize();
|
||||
|
||||
//Symbol timing error calculations
|
||||
mSymbolTimingError = mGardnerDetector.getError(mPreviousSymbol, mMiddleSymbol, mCurrentSymbol);
|
||||
mInterpolatingSymbolBuffer.resetAndAdjust(mSymbolTimingError);
|
||||
|
||||
//Store current samples/symbols to use for the next period
|
||||
mPreviousSample.setValues(mCurrentSample);
|
||||
mPreviousMiddleSample.setValues(mMiddleSample);
|
||||
mPreviousSymbol.setValues(mCurrentSymbol);
|
||||
|
||||
//Adjust the current symbol as necessary (e.g. for differential encoding)
|
||||
mSymbolPhaseErrorCalculator.adjust(mCurrentSymbol);
|
||||
|
||||
//Calculate the phase error of the current symbol relative to the expected constellation and provide
|
||||
//feedback to the PLL
|
||||
mPhaseError = mSymbolPhaseErrorCalculator.getPhaseError(mCurrentSymbol);
|
||||
mPLL.adjust(mPhaseError);
|
||||
|
||||
if(mPLLErrorListener != null)
|
||||
{
|
||||
mPLLErrorListener.receive((double)mPhaseError);
|
||||
}
|
||||
|
||||
//Decode the dibit from the symbol and send to the listener
|
||||
if(mDibitListener != null)
|
||||
{
|
||||
mDibitListener.receive(mQPSKSymbolDecoder.decode(mCurrentSymbol));
|
||||
}
|
||||
|
||||
//Send to an external constellation symbol listener when registered
|
||||
if(mSymbolListener != null)
|
||||
{
|
||||
mSymbolListener.receive(mCurrentSymbol);
|
||||
}
|
||||
|
||||
//TODO: Assemble dibits here and broadcast
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive decoded QPSK symbols
|
||||
*/
|
||||
public void setSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive decoded QPSK dibits
|
||||
*/
|
||||
public void setDibitListener(Listener<Dibit> listener)
|
||||
{
|
||||
mDibitListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive PLL error values
|
||||
*/
|
||||
public void setPLLErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLErrorListener = listener;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||
@@ -5,24 +20,7 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
******************************************************************************/
|
||||
public class QPSKPolarSlicer implements Listener<Complex>
|
||||
public class QPSKSymbolDecoder implements Listener<Complex>, IQPSKSymbolDecoder
|
||||
{
|
||||
private Broadcaster<Dibit> mBroadcaster = new Broadcaster<Dibit>();
|
||||
|
||||
@@ -37,7 +35,7 @@ public class QPSKPolarSlicer implements Listener<Complex>
|
||||
* / \
|
||||
* / 11 \
|
||||
*/
|
||||
public QPSKPolarSlicer()
|
||||
public QPSKSymbolDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -60,10 +58,10 @@ public class QPSKPolarSlicer implements Listener<Complex>
|
||||
@Override
|
||||
public void receive( Complex complex )
|
||||
{
|
||||
mBroadcaster.broadcast( decide( complex ) );
|
||||
mBroadcaster.broadcast( decode( complex ) );
|
||||
}
|
||||
|
||||
public static Dibit decide( Complex complex )
|
||||
public Dibit decode(Complex complex )
|
||||
{
|
||||
if( Math.abs( complex.inphase() ) > Math.abs( complex.quadrature() ) )
|
||||
{
|
||||
@@ -0,0 +1,63 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
/**
|
||||
* Calculates the phase error for a QPSK polar-orientation constellation
|
||||
*/
|
||||
public class QPSKSymbolPhaseErrorCalculator implements ISymbolPhaseErrorCalculator
|
||||
{
|
||||
private float mPhaseError;
|
||||
|
||||
@Override
|
||||
public void adjust(Complex symbol)
|
||||
{
|
||||
//No-op - since there is no differential, we don't have to adjust
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPhaseError(Complex symbol)
|
||||
{
|
||||
mPhaseError = 0.0f;
|
||||
|
||||
if(Math.abs(symbol.inphase()) > Math.abs(symbol.quadrature()))
|
||||
{
|
||||
if(symbol.inphase() > 0)
|
||||
{
|
||||
mPhaseError = -symbol.quadrature();
|
||||
}
|
||||
else
|
||||
{
|
||||
mPhaseError = symbol.quadrature();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(symbol.quadrature() > 0)
|
||||
{
|
||||
mPhaseError = symbol.inphase();
|
||||
}
|
||||
else
|
||||
{
|
||||
mPhaseError = -symbol.inphase();
|
||||
}
|
||||
}
|
||||
|
||||
return mPhaseError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk;
|
||||
|
||||
public enum SymbolInversion
|
||||
{
|
||||
DEGREES_90_PLUS(Math.PI / 2.0),
|
||||
DEGREES_90_MINUS(Math.PI / -2.0),
|
||||
DEGREES_180(Math.PI);
|
||||
|
||||
private double mAngleRadians;
|
||||
|
||||
SymbolInversion(double angleRadians)
|
||||
{
|
||||
mAngleRadians = angleRadians;
|
||||
}
|
||||
|
||||
public double getError()
|
||||
{
|
||||
return mAngleRadians;
|
||||
}
|
||||
}
|
||||
337
src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java
Normal file
337
src/main/java/io/github/dsheirer/dsp/psk/pll/CostasLoop.java
Normal file
@@ -0,0 +1,337 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.dsp.mixer.Oscillator;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Costas Loop - phase locked loop designed to automatically synchronize to the incoming carrier frequency in order to
|
||||
* zeroize any frequency offset inherent in the signal due to either mis-tuning or carrier frequency drift.
|
||||
*
|
||||
* Costas loop structure was modeled from KA1RBI's OP25/cqpsk.py
|
||||
*
|
||||
* Loop bandwidth and alpha/beta gain formulas derived from:
|
||||
* http://www.trondeau.com/blog/2011/8/13/control-loop-gain-values.html
|
||||
*/
|
||||
public class CostasLoop implements IPhaseLockedLoop
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CostasLoop.class);
|
||||
public static final double TWO_PI = 2.0 * Math.PI;
|
||||
private DoubleCircularBuffer mErrorBuffer = new DoubleCircularBuffer(10);
|
||||
private double mDamping = Math.sqrt(2.0) / 2.0;
|
||||
private double mAlphaGain;
|
||||
private double mBetaGain;
|
||||
private double mLoopPhase = 0.0;
|
||||
private double mLoopFrequency = 0.0;
|
||||
private double mMaximumLoopFrequency;
|
||||
private Complex mCurrentVector = new Complex(0, 0);
|
||||
|
||||
private Tracking mTracking = Tracking.SEARCHING;
|
||||
private ITrackingStateListener mTrackingStateListener;
|
||||
private boolean mAutomaticTrackingEnabled = true;
|
||||
|
||||
/**
|
||||
* Costas Loop for tracking and correcting frequency error in a received carrier signal.
|
||||
*
|
||||
* @param sampleRate of the incoming samples
|
||||
* @param symbolRate of the digital signal
|
||||
*/
|
||||
public CostasLoop(double sampleRate, double symbolRate)
|
||||
{
|
||||
mMaximumLoopFrequency = TWO_PI * (symbolRate / 2.0) / sampleRate;
|
||||
updateLoopBandwidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrects the current phase tracker when an inverted output is detected. The inversion will take the form of
|
||||
* +/- 90 degrees or +/- 180 degrees, although the latter correction is equal regardless of the sign.
|
||||
*
|
||||
* If the supplied correction value places the loop frequency outside of the max frequency, then the frequency will
|
||||
* be corrected 360 degrees in the opposite direction to maintain within the max frequency bounds.
|
||||
*
|
||||
* @param correction as measured in radians
|
||||
*/
|
||||
public void correctInversion(double correction)
|
||||
{
|
||||
mLoopFrequency += correction;
|
||||
|
||||
if(mLoopFrequency > mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency -= 2.0 * mMaximumLoopFrequency;
|
||||
}
|
||||
|
||||
if(mLoopFrequency < -mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency += 2.0 * mMaximumLoopFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the loop bandwidth and alpha/beta gains according to the current loop synchronization state.
|
||||
*/
|
||||
private void updateLoopBandwidth()
|
||||
{
|
||||
double bandwidth = TWO_PI / mTracking.getLoopBandwidth();
|
||||
|
||||
mAlphaGain = (4.0 * mDamping * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
mBetaGain = (4.0 * bandwidth * bandwidth) / (1.0 + (2.0 * mDamping * bandwidth) + (bandwidth * bandwidth));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tracking state for this costas loop. The tracking state affects the aggressiveness of the alpha/beta
|
||||
* gain values in synchronizing with the signal carrier.
|
||||
*
|
||||
* @param tracking
|
||||
*/
|
||||
public void setTracking(Tracking tracking)
|
||||
{
|
||||
mTracking = tracking;
|
||||
updateLoopBandwidth();
|
||||
|
||||
mLog.debug("Tracking: " + tracking.name());
|
||||
|
||||
if(mTrackingStateListener != null)
|
||||
{
|
||||
mTrackingStateListener.trackingStateChanged(tracking);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener to receive notifications when the tracking state changes
|
||||
*/
|
||||
public void setTrackingStateListener(ITrackingStateListener listener)
|
||||
{
|
||||
mTrackingStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables automatic tracking state updates. When enabled, the loop will automatically increase or
|
||||
* decrease the loop alpha/beta gain values based on the variance of the error tracking values submitted to the
|
||||
* loop control. As the variance decreases, so will the gain values, and vice-versa. Setting this to disabled
|
||||
* will force the tracker to retain the gain values associated with either the default or user-specified tracking
|
||||
* value.
|
||||
*
|
||||
* @param enabled true (default) to enable automatic gain tracking
|
||||
*/
|
||||
public void setAutomaticTracking(boolean enabled)
|
||||
{
|
||||
mAutomaticTrackingEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the phase of the loop for each sample received at the sample rate.
|
||||
*/
|
||||
public void increment()
|
||||
{
|
||||
mLoopPhase += mLoopFrequency;
|
||||
|
||||
/* Keep the loop phase in bounds */
|
||||
normalizePhase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current vector of the loop. Note: this value is updated for the current angle in radians each time this method
|
||||
* is invoked.
|
||||
*/
|
||||
public Complex getCurrentVector()
|
||||
{
|
||||
mCurrentVector.setAngle(mLoopPhase);
|
||||
return mCurrentVector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Complex incrementAndGetCurrentVector()
|
||||
{
|
||||
increment();
|
||||
return getCurrentVector();
|
||||
}
|
||||
|
||||
public double getLoopFrequency()
|
||||
{
|
||||
return mLoopFrequency;
|
||||
}
|
||||
|
||||
public double getMaximumLoopFrequency()
|
||||
{
|
||||
return mMaximumLoopFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the costas loop frequency and phase to adjust for the phase
|
||||
* error value
|
||||
*
|
||||
* @param phaseError - (-)= late and (+)= early
|
||||
*/
|
||||
public void adjust(double phaseError)
|
||||
{
|
||||
mLoopFrequency += (mBetaGain * phaseError);
|
||||
mLoopPhase += mLoopFrequency + (mAlphaGain * phaseError);
|
||||
|
||||
/* Maintain phase between +/- 2 * PI */
|
||||
normalizePhase();
|
||||
|
||||
/* Limit frequency to +/- maximum loop frequency */
|
||||
limitFrequency();
|
||||
|
||||
mErrorBuffer.put(phaseError);
|
||||
|
||||
double variance = mErrorBuffer.variance();
|
||||
|
||||
switch(mTracking)
|
||||
{
|
||||
case SEARCHING:
|
||||
if(Tracking.COARSE.meetsThreshold(variance))
|
||||
{
|
||||
setTracking(Tracking.COARSE);
|
||||
}
|
||||
break;
|
||||
case COARSE:
|
||||
if(Tracking.COARSE.meetsThreshold(variance))
|
||||
{
|
||||
if(Tracking.FINE.meetsThreshold(variance))
|
||||
{
|
||||
setTracking(Tracking.FINE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setTracking(Tracking.SEARCHING);
|
||||
}
|
||||
break;
|
||||
case FINE:
|
||||
if(Tracking.FINE.meetsThreshold(variance))
|
||||
{
|
||||
if(Tracking.LOCKED.meetsThreshold(variance))
|
||||
{
|
||||
setTracking(Tracking.LOCKED);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setTracking(Tracking.COARSE);
|
||||
}
|
||||
break;
|
||||
case LOCKED:
|
||||
if(!Tracking.LOCKED.meetsThreshold(variance))
|
||||
{
|
||||
setTracking(Tracking.FINE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public double getErrorMean()
|
||||
{
|
||||
return mErrorBuffer.mean();
|
||||
}
|
||||
|
||||
public double getErrorVariance()
|
||||
{
|
||||
return mErrorBuffer.variance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the phase tracker to maintain within +/- 2 Pi
|
||||
*/
|
||||
private void normalizePhase()
|
||||
{
|
||||
while(mLoopPhase > TWO_PI)
|
||||
{
|
||||
mLoopPhase -= TWO_PI;
|
||||
}
|
||||
|
||||
while(mLoopPhase < -TWO_PI)
|
||||
{
|
||||
mLoopPhase += TWO_PI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains the frequency within the bounds of +/- loop frequency
|
||||
*/
|
||||
private void limitFrequency()
|
||||
{
|
||||
if(mLoopFrequency > mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency = mMaximumLoopFrequency;
|
||||
}
|
||||
|
||||
if(mLoopFrequency < -mMaximumLoopFrequency)
|
||||
{
|
||||
mLoopFrequency = -mMaximumLoopFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking state listener interface for receiving updates when the tracking state of the costas loop changes
|
||||
*/
|
||||
public interface ITrackingStateListener
|
||||
{
|
||||
void trackingStateChanged(Tracking tracking);
|
||||
}
|
||||
|
||||
public static void main(String[] arguments)
|
||||
{
|
||||
DecimalFormat decimalFormat = new DecimalFormat("0.000000");
|
||||
double sampleRate = 25000.0;
|
||||
int errorFrequency = 411;
|
||||
double loopBandwidth = 50.0;
|
||||
|
||||
Oscillator oscillator = new Oscillator(errorFrequency, (int)sampleRate);
|
||||
CostasLoop costasLoop = new CostasLoop(sampleRate, 4800.0);
|
||||
costasLoop.setTrackingStateListener(new ITrackingStateListener()
|
||||
{
|
||||
@Override
|
||||
public void trackingStateChanged(Tracking tracking)
|
||||
{
|
||||
mLog.debug("Tracking State Changed: " + tracking.name());
|
||||
}
|
||||
});
|
||||
|
||||
for(int x = 0; x < 1000; x++)
|
||||
{
|
||||
Complex complex = oscillator.getComplex();
|
||||
|
||||
oscillator.rotate();
|
||||
costasLoop.increment();
|
||||
|
||||
if(x % 5 == 0)
|
||||
{
|
||||
complex.multiply(costasLoop.getCurrentVector().conjugate());
|
||||
|
||||
double phaseError = complex.angle();
|
||||
costasLoop.adjust(phaseError);
|
||||
|
||||
double loopFrequency = costasLoop.getLoopFrequency() * sampleRate / TWO_PI;
|
||||
double maxFrequency = costasLoop.getMaximumLoopFrequency() * sampleRate / TWO_PI;
|
||||
|
||||
mLog.debug(x + " Frequency: " + errorFrequency +
|
||||
" Loop:" + decimalFormat.format(loopFrequency) +
|
||||
" Max:" + maxFrequency +
|
||||
" Detected Error:" + decimalFormat.format(phaseError) +
|
||||
" Avg:" + decimalFormat.format(costasLoop.getErrorMean()) +
|
||||
" Var:" + decimalFormat.format(costasLoop.getErrorVariance()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
|
||||
/**
|
||||
* Phase-Locked Loop (PLL) interface
|
||||
*/
|
||||
public interface IPhaseLockedLoop
|
||||
{
|
||||
/**
|
||||
* Increments the PLL by one sample period
|
||||
*/
|
||||
void increment();
|
||||
|
||||
/**
|
||||
* Applies an error value to the PLL.
|
||||
*
|
||||
* @param error value in the range of -PI <> +PI
|
||||
*/
|
||||
void adjust(double error);
|
||||
|
||||
/**
|
||||
* Adjusts the current phase of the loop to account for +/-90 or 180 degree inverted phase locks when detected
|
||||
* @param correction to apply to the phase of the loop
|
||||
*/
|
||||
void correctInversion(double correction);
|
||||
|
||||
/**
|
||||
* Current vector of the PLL to use in de-spinning the incoming samples.
|
||||
* @return complex vector
|
||||
*/
|
||||
Complex getCurrentVector();
|
||||
|
||||
/**
|
||||
* Increments the phase rotation and gets the current vector of the PLL to use in de-spinning the incoming samples.
|
||||
* @return complex vector
|
||||
*/
|
||||
Complex incrementAndGetCurrentVector();
|
||||
|
||||
|
||||
}
|
||||
48
src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java
Normal file
48
src/main/java/io/github/dsheirer/dsp/psk/pll/Tracking.java
Normal file
@@ -0,0 +1,48 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.dsp.psk.pll;
|
||||
|
||||
public enum Tracking
|
||||
{
|
||||
SEARCHING(50.0, Math.PI),
|
||||
COARSE(100.0, 0.01),
|
||||
FINE(150.0, 0.001),
|
||||
LOCKED(200.0, 0.0001);
|
||||
|
||||
private double mLoopBandwidth;
|
||||
private double mVarianceThreshold;
|
||||
|
||||
Tracking(double loopBandwidth, double varianceThreshold)
|
||||
{
|
||||
mLoopBandwidth = loopBandwidth;
|
||||
mVarianceThreshold = varianceThreshold;
|
||||
}
|
||||
|
||||
public double getLoopBandwidth()
|
||||
{
|
||||
return mLoopBandwidth;
|
||||
}
|
||||
|
||||
public double getVarianceThreshold()
|
||||
{
|
||||
return mVarianceThreshold;
|
||||
}
|
||||
|
||||
public boolean meetsThreshold(double variance)
|
||||
{
|
||||
return variance < getVarianceThreshold();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.instrument.gui.viewer;
|
||||
|
||||
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPaneFactory;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -44,7 +46,7 @@ public class DemodulatorViewerFX extends Application
|
||||
public void start(Stage primaryStage) throws Exception
|
||||
{
|
||||
mStage = primaryStage;
|
||||
mStage.setTitle("Demodulator Viewer FX");
|
||||
setTitle(null);
|
||||
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.setTop(getMenuBar());
|
||||
@@ -56,6 +58,18 @@ public class DemodulatorViewerFX extends Application
|
||||
mStage.show();
|
||||
}
|
||||
|
||||
private void setTitle(DecoderType decoderType)
|
||||
{
|
||||
if(decoderType != null)
|
||||
{
|
||||
mStage.setTitle("Demodulator Viewer FX - " + decoderType.getDisplayString());
|
||||
}
|
||||
else
|
||||
{
|
||||
mStage.setTitle("Demodulator Viewer FX");
|
||||
}
|
||||
}
|
||||
|
||||
private ViewerDesktop getViewerDesktop()
|
||||
{
|
||||
if(mViewerDesktop == null)
|
||||
@@ -72,9 +86,9 @@ public class DemodulatorViewerFX extends Application
|
||||
{
|
||||
mMenuBar = new MenuBar();
|
||||
|
||||
Menu menuFile = new Menu("File");
|
||||
MenuItem menuItemOpen = new MenuItem("Open ...");
|
||||
menuItemOpen.setOnAction(new EventHandler<ActionEvent>()
|
||||
Menu fileMenu = new Menu("File");
|
||||
MenuItem openMenuItem = new MenuItem("Open ...");
|
||||
openMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
@@ -87,8 +101,8 @@ public class DemodulatorViewerFX extends Application
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem menuItemClose = new MenuItem("Close");
|
||||
menuItemClose.setOnAction(new EventHandler<ActionEvent>()
|
||||
MenuItem closeMenuItem = new MenuItem("Close");
|
||||
closeMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
@@ -97,8 +111,8 @@ public class DemodulatorViewerFX extends Application
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem menuItemExit = new MenuItem("Exit");
|
||||
menuItemExit.setOnAction(new EventHandler<ActionEvent>()
|
||||
MenuItem exitMenuItem = new MenuItem("Exit");
|
||||
exitMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
@@ -107,10 +121,34 @@ public class DemodulatorViewerFX extends Application
|
||||
}
|
||||
});
|
||||
|
||||
menuFile.getItems().addAll(menuItemOpen, getRecentFilesMenu(), menuItemClose, new SeparatorMenuItem(),
|
||||
menuItemExit);
|
||||
fileMenu.getItems().addAll(openMenuItem, getRecentFilesMenu(), closeMenuItem, new SeparatorMenuItem(),
|
||||
exitMenuItem);
|
||||
|
||||
mMenuBar.getMenus().addAll(menuFile);
|
||||
mMenuBar.getMenus().add(fileMenu);
|
||||
|
||||
Menu decoderMenu = new Menu("Decoder");
|
||||
|
||||
for(DecoderType decoderType: DecoderType.values())
|
||||
{
|
||||
if(DecoderPaneFactory.isSupported(decoderType))
|
||||
{
|
||||
MenuItem decoderMenuItem = new MenuItem(decoderType.getShortDisplayString());
|
||||
|
||||
decoderMenuItem.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
getViewerDesktop().setDecoder(decoderType);
|
||||
setTitle(decoderType);
|
||||
}
|
||||
});
|
||||
|
||||
decoderMenu.getItems().add(decoderMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
mMenuBar.getMenus().add(decoderMenu);
|
||||
}
|
||||
|
||||
return mMenuBar;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.instrument.gui.viewer;
|
||||
|
||||
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
@@ -52,6 +53,7 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
|
||||
private HBox mControlsBox;
|
||||
private Label mFileLabel;
|
||||
private EventHandler<ActionEvent> mPlayEventHandler;
|
||||
private DecoderPane mSampleRateListener;
|
||||
|
||||
private IControllableFileSource mControllableFileSource;
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
@@ -65,6 +67,16 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
|
||||
setControlsEnabled(false);
|
||||
}
|
||||
|
||||
public void setSampleRateListener(DecoderPane decoderPane)
|
||||
{
|
||||
mSampleRateListener = decoderPane;
|
||||
|
||||
if(mSampleRateListener != null && mControllableFileSource != null)
|
||||
{
|
||||
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File file)
|
||||
{
|
||||
mLog.debug("loading:" + file.getAbsolutePath());
|
||||
@@ -76,6 +88,11 @@ public class PlaybackController extends HBox implements IFrameLocationListener,
|
||||
mControllableFileSource.setListener(this);
|
||||
((ComplexWaveSource)mControllableFileSource).setListener((Listener<ComplexBuffer>)this);
|
||||
mControllableFileSource.open();
|
||||
|
||||
if(mSampleRateListener != null)
|
||||
{
|
||||
mSampleRateListener.setSampleRate(mControllableFileSource.getSampleRate());
|
||||
}
|
||||
}
|
||||
catch(UnsupportedAudioFileException e)
|
||||
{
|
||||
|
||||
@@ -15,25 +15,22 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.instrument.gui.viewer;
|
||||
|
||||
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPane;
|
||||
import io.github.dsheirer.instrument.gui.viewer.decoder.DecoderPaneFactory;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ViewerDesktop extends BorderPane
|
||||
{
|
||||
private PlaybackController mPlaybackController;
|
||||
private VBox mChartBox;
|
||||
private ComplexSampleLineChart mSampleLineChart;
|
||||
private ComplexPhaseLineChart mPhaseLineChart;
|
||||
private DecoderPane mDecoderPane = DecoderPaneFactory.getDefaultPane();
|
||||
|
||||
public ViewerDesktop()
|
||||
{
|
||||
setBottom(getPlaybackController());
|
||||
getPlaybackController().addListener(getSampleLineChart());
|
||||
getPlaybackController().addListener(getPhaseLineChart());
|
||||
setCenter(getChartBox());
|
||||
setCenter(getDecoderPane());
|
||||
}
|
||||
|
||||
public void load(File file)
|
||||
@@ -42,6 +39,31 @@ public class ViewerDesktop extends BorderPane
|
||||
getPlaybackController().load(file);
|
||||
}
|
||||
|
||||
public void setDecoder(DecoderType decoderType)
|
||||
{
|
||||
getPlaybackController().removeListener(getDecoderPane());
|
||||
getChildren().remove(getDecoderPane());
|
||||
|
||||
mDecoderPane = DecoderPaneFactory.getDecoderPane(decoderType);
|
||||
setCenter(getDecoderPane());
|
||||
getPlaybackController().addListener(getDecoderPane());
|
||||
|
||||
getPlaybackController().setSampleRateListener(getDecoderPane());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder pane
|
||||
*/
|
||||
public DecoderPane getDecoderPane()
|
||||
{
|
||||
if(mDecoderPane == null)
|
||||
{
|
||||
mDecoderPane = DecoderPaneFactory.getDefaultPane();
|
||||
}
|
||||
|
||||
return mDecoderPane;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
getPlaybackController().close();
|
||||
@@ -56,35 +78,4 @@ public class ViewerDesktop extends BorderPane
|
||||
|
||||
return mPlaybackController;
|
||||
}
|
||||
|
||||
private VBox getChartBox()
|
||||
{
|
||||
if(mChartBox == null)
|
||||
{
|
||||
mChartBox = new VBox();
|
||||
mChartBox.getChildren().addAll(getSampleLineChart(), getPhaseLineChart());
|
||||
}
|
||||
|
||||
return mChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mSampleLineChart == null)
|
||||
{
|
||||
mSampleLineChart = new ComplexSampleLineChart(40);
|
||||
}
|
||||
|
||||
return mSampleLineChart;
|
||||
}
|
||||
|
||||
private ComplexPhaseLineChart getPhaseLineChart()
|
||||
{
|
||||
if(mPhaseLineChart == null)
|
||||
{
|
||||
mPhaseLineChart = new ComplexPhaseLineChart(50);
|
||||
}
|
||||
|
||||
return mPhaseLineChart;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.instrument.gui.viewer;
|
||||
package io.github.dsheirer.instrument.gui.viewer.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||
import io.github.dsheirer.dsp.gain.ComplexGain;
|
||||
@@ -28,7 +28,6 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.apache.commons.math3.complex.ComplexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -36,7 +35,7 @@ public class ComplexPhaseLineChart extends LineChart implements Listener<Complex
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ComplexPhaseLineChart.class);
|
||||
|
||||
private static final Complex ANGLE_OFFSET_45_DEGREES = Complex.fromAngle((float)(Math.PI / 4.0));
|
||||
private static final Complex ANGLE_OFFSET_45_DEGREES = Complex.fromAngle(Math.PI / 4.0);
|
||||
private ComplexCircularBuffer mComplexCircularBuffer;
|
||||
private ComplexGain mComplexGain = new ComplexGain(600.0f);
|
||||
private ObservableList<Data<Integer,Float>> mPhaseValues = FXCollections.observableArrayList();
|
||||
@@ -44,8 +43,8 @@ public class ComplexPhaseLineChart extends LineChart implements Listener<Complex
|
||||
|
||||
public ComplexPhaseLineChart(int length)
|
||||
{
|
||||
super(new NumberAxis("Time", 1, length - 10, 1),
|
||||
new NumberAxis("Phase", -Math.PI, Math.PI, 1.0));
|
||||
super(new NumberAxis("Time", 1, length - 10, 2),
|
||||
new NumberAxis("Phase", -Math.PI, Math.PI, Math.PI / 2.0));
|
||||
|
||||
Series<Integer,Float> phaseSeries = new Series<>("Phase", mPhaseValues);
|
||||
ObservableList<Series<Integer,Float>> observableList = FXCollections.observableArrayList(phaseSeries);
|
||||
@@ -13,7 +13,7 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.instrument.gui.viewer;
|
||||
package io.github.dsheirer.instrument.gui.viewer.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.ComplexCircularBuffer;
|
||||
import io.github.dsheirer.dsp.gain.ComplexGain;
|
||||
@@ -44,8 +44,8 @@ public class ComplexSampleLineChart extends LineChart implements Listener<Comple
|
||||
|
||||
public ComplexSampleLineChart(int length)
|
||||
{
|
||||
super(new NumberAxis("Sample Number", 1, length, 1),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.1));
|
||||
super(new NumberAxis("Sample Number", 1, length, 2),
|
||||
new NumberAxis("Value", -1.0, 1.0, 0.25));
|
||||
|
||||
LineChart.Series<Integer,Float> iSampleSeries = new LineChart.Series<>("Inphase", mISamples);
|
||||
LineChart.Series<Integer,Float> qSampleSeries = new LineChart.Series<>("Quadrature", mQSamples);
|
||||
@@ -0,0 +1,73 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.instrument.gui.viewer.chart;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PhaseLineChart extends LineChart implements Listener<Double>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PhaseLineChart.class);
|
||||
|
||||
private DoubleCircularBuffer mDoubleCircularBuffer;
|
||||
private ObservableList<Data<Integer,Double>> mPhaseValues = FXCollections.observableArrayList();
|
||||
|
||||
public PhaseLineChart(int length)
|
||||
{
|
||||
super(new NumberAxis("Time", 1, length, 2),
|
||||
new NumberAxis("Phase", -Math.PI, Math.PI, Math.PI / 2.0));
|
||||
|
||||
Series<Integer,Double> phaseSeries = new Series<>("Phase", mPhaseValues);
|
||||
ObservableList<Series<Integer,Double>> observableList = FXCollections.observableArrayList(phaseSeries);
|
||||
|
||||
setData(observableList);
|
||||
init(length);
|
||||
}
|
||||
|
||||
private void init(int length)
|
||||
{
|
||||
mDoubleCircularBuffer = new DoubleCircularBuffer(length);
|
||||
|
||||
for(int x = 1; x <= length; x++)
|
||||
{
|
||||
mPhaseValues.add(new Data(x, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Double phase)
|
||||
{
|
||||
mDoubleCircularBuffer.put(phase);
|
||||
|
||||
updateChart();
|
||||
}
|
||||
|
||||
private void updateChart()
|
||||
{
|
||||
double[] values = mDoubleCircularBuffer.getAll();
|
||||
|
||||
for(int x = 0; x < values.length; x++)
|
||||
{
|
||||
mPhaseValues.get(x).setYValue(values[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.instrument.gui.viewer.chart;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SymbolChart extends ScatterChart implements Listener<Complex>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SymbolChart.class);
|
||||
|
||||
private ObservableList<ScatterChart.Data> mCurrentPoints = FXCollections.observableArrayList();
|
||||
private ObservableList<ScatterChart.Data> mPreviousPoints = FXCollections.observableArrayList();
|
||||
private ObservableList<ScatterChart.Data> mOldestPoints = FXCollections.observableArrayList();
|
||||
private int mDataPointer;
|
||||
private int mHistory;
|
||||
|
||||
/**
|
||||
* Symbol chart for plotting a series of constellation symbols over time. Maintains three history sets for
|
||||
* current, previous, and oldest symbols.
|
||||
*
|
||||
* @param history size for each of the history sets
|
||||
*/
|
||||
public SymbolChart(int history)
|
||||
{
|
||||
super(new NumberAxis("I", -1.0, 1.0, 0.2),
|
||||
new NumberAxis("Q", -1.0, 1.0, 0.2));
|
||||
|
||||
mHistory = history;
|
||||
|
||||
for(int x = 0; x < mHistory; x++)
|
||||
{
|
||||
mCurrentPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
mPreviousPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
mOldestPoints.add(new XYChart.Data<>(0.0, 0.0));
|
||||
}
|
||||
|
||||
ObservableList<XYChart.Series> data = FXCollections.observableArrayList();
|
||||
data.add(new ScatterChart.Series("Current", mCurrentPoints));
|
||||
data.add(new ScatterChart.Series("Previous", mPreviousPoints));
|
||||
data.add(new ScatterChart.Series("Oldest", mOldestPoints));
|
||||
|
||||
this.setData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Complex complex)
|
||||
{
|
||||
//Transfer previous to oldest
|
||||
XYChart.Data oldest = mOldestPoints.get(mDataPointer);
|
||||
XYChart.Data previous = mPreviousPoints.get(mDataPointer);
|
||||
XYChart.Data current = mCurrentPoints.get(mDataPointer);
|
||||
|
||||
oldest.setXValue(previous.getXValue());
|
||||
oldest.setYValue(previous.getYValue());
|
||||
|
||||
previous.setXValue(current.getXValue());
|
||||
previous.setYValue(current.getYValue());
|
||||
|
||||
current.setXValue(complex.inphase());
|
||||
current.setYValue(complex.quadrature());
|
||||
|
||||
mDataPointer++;
|
||||
|
||||
if(mDataPointer >= mHistory)
|
||||
{
|
||||
mDataPointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.instrument.gui.viewer.decoder;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class DecoderPane extends VBox implements Listener<ComplexBuffer>
|
||||
{
|
||||
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
|
||||
private DecoderType mDecoderType;
|
||||
|
||||
public DecoderPane(DecoderType decoderType)
|
||||
{
|
||||
mDecoderType = decoderType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Package private - used by factory class to create an empty pane
|
||||
*/
|
||||
DecoderPane()
|
||||
{
|
||||
getChildren().add(new Text("Please select a decoder from the menu"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
mComplexBufferBroadcaster.broadcast(complexBuffer);
|
||||
}
|
||||
|
||||
public void addListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(Listener<ComplexBuffer> listener)
|
||||
{
|
||||
mComplexBufferBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.instrument.gui.viewer.decoder;
|
||||
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class DecoderPaneFactory
|
||||
{
|
||||
private static final EnumSet<DecoderType> SUPPORTED_DECODER_TYPES = EnumSet.of(DecoderType.P25_PHASE1);
|
||||
|
||||
/**
|
||||
* Creates a decoder pane for the decoder type
|
||||
*/
|
||||
public static DecoderPane getDecoderPane(DecoderType decoderType)
|
||||
{
|
||||
switch(decoderType)
|
||||
{
|
||||
case P25_PHASE1:
|
||||
return new P25Phase1Pane();
|
||||
}
|
||||
|
||||
return getDefaultPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty decoder pane
|
||||
*/
|
||||
public static DecoderPane getDefaultPane()
|
||||
{
|
||||
return new DecoderPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the decoder type is supported by this factory
|
||||
*/
|
||||
public static boolean isSupported(DecoderType decoderType)
|
||||
{
|
||||
return SUPPORTED_DECODER_TYPES.contains(decoderType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.instrument.gui.viewer.decoder;
|
||||
|
||||
import io.github.dsheirer.instrument.gui.viewer.chart.ComplexPhaseLineChart;
|
||||
import io.github.dsheirer.instrument.gui.viewer.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.instrument.gui.viewer.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.instrument.gui.viewer.chart.SymbolChart;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoder2;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class P25Phase1Pane extends DecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25Phase1Pane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private ComplexSampleLineChart mSampleLineChart;
|
||||
private ComplexPhaseLineChart mPhaseLineChart;
|
||||
private HBox mDecoderChartBox;
|
||||
private SymbolChart mSymbolChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private P25_C4FMDecoder2 mDecoder = new P25_C4FMDecoder2(null);
|
||||
|
||||
public P25Phase1Pane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE1);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(getSampleLineChart());
|
||||
addListener(getPhaseLineChart());
|
||||
addListener(getDecoder());
|
||||
|
||||
getDecoder().setSymbolListener(getSymbolChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
|
||||
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
private P25_C4FMDecoder2 getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolChart()
|
||||
{
|
||||
if(mSymbolChart == null)
|
||||
{
|
||||
mSymbolChart = new SymbolChart(20);
|
||||
}
|
||||
|
||||
return mSymbolChart;
|
||||
}
|
||||
|
||||
private HBox getDecoderChartBox()
|
||||
{
|
||||
if(mDecoderChartBox == null)
|
||||
{
|
||||
mDecoderChartBox = new HBox();
|
||||
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart());
|
||||
}
|
||||
|
||||
return mDecoderChartBox;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPhaseLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPhaseLineChart(), Priority.ALWAYS);
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getPhaseLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mSampleLineChart == null)
|
||||
{
|
||||
mSampleLineChart = new ComplexSampleLineChart(40);
|
||||
}
|
||||
|
||||
return mSampleLineChart;
|
||||
}
|
||||
|
||||
private ComplexPhaseLineChart getPhaseLineChart()
|
||||
{
|
||||
if(mPhaseLineChart == null)
|
||||
{
|
||||
mPhaseLineChart = new ComplexPhaseLineChart(50);
|
||||
}
|
||||
|
||||
return mPhaseLineChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(80);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
@@ -22,39 +22,39 @@ import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public abstract class Decoder extends Module
|
||||
implements IMessageProvider, Listener<Message>
|
||||
public abstract class Decoder extends Module implements IMessageProvider
|
||||
{
|
||||
/* This has to be a broadcaster in order for references to persist */
|
||||
protected Listener<Message> mMessageListener;
|
||||
/* This has to be a broadcaster in order for references to persist */
|
||||
private Listener<Message> mMessageDistributor = new MessageDistributor();
|
||||
protected Listener<Message> mMessageListener;
|
||||
|
||||
/**
|
||||
* Decoder - parent class for all decoders, demodulators and components.
|
||||
*/
|
||||
public Decoder()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Decoder - parent class for all decoders, demodulators and components.
|
||||
*/
|
||||
public Decoder()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the decoder type (ie protocol)
|
||||
*/
|
||||
public abstract DecoderType getDecoderType();
|
||||
/**
|
||||
* Identifies the decoder type (ie protocol)
|
||||
*/
|
||||
public abstract DecoderType getDecoderType();
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for receiving decoded messages from this decoder
|
||||
*/
|
||||
@Override
|
||||
public void setMessageListener( Listener<Message> listener )
|
||||
public void setMessageListener(Listener<Message> listener)
|
||||
{
|
||||
mMessageListener = listener;
|
||||
}
|
||||
mMessageListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener from receiving decoded messages from all attached
|
||||
@@ -63,23 +63,29 @@ public abstract class Decoder extends Module
|
||||
@Override
|
||||
public void removeMessageListener()
|
||||
{
|
||||
mMessageListener = null;
|
||||
mMessageListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the message to the registered message listener
|
||||
* Message listener for receiving the output from this decoder
|
||||
*/
|
||||
public void broadcast( Message message )
|
||||
protected Listener<Message> getMessageListener()
|
||||
{
|
||||
if( mMessageListener != null )
|
||||
{
|
||||
mMessageListener.receive( message );
|
||||
}
|
||||
return mMessageDistributor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Message message )
|
||||
{
|
||||
broadcast( message );
|
||||
}
|
||||
/**
|
||||
* Distributes/forwards messages from sub-class decoder implementations to the registered message listener.
|
||||
*/
|
||||
public class MessageDistributor implements Listener<Message>
|
||||
{
|
||||
@Override
|
||||
public void receive(Message message)
|
||||
{
|
||||
if(mMessageListener != null)
|
||||
{
|
||||
mMessageListener.receive(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class Fleetsync2Decoder extends Decoder implements IFilteredRealBufferLis
|
||||
mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
|
||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||
|
||||
mMessageProcessor.setMessageListener( this );
|
||||
mMessageProcessor.setMessageListener( getMessageListener() );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
|
||||
@@ -100,7 +100,7 @@ public class LJ1200Decoder extends Decoder implements IFilteredRealBufferListene
|
||||
mMessageProcessor = new LJ1200MessageProcessor( aliasList );
|
||||
mTowerMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mTransponderMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mMessageProcessor.setMessageListener( this );
|
||||
mMessageProcessor.setMessageListener( getMessageListener() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -74,7 +74,7 @@ public class LTRNetDecoder extends Decoder
|
||||
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
|
||||
mLTRMessageProcessor.setMessageListener( this );
|
||||
mLTRMessageProcessor.setMessageListener( getMessageListener() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,7 +64,7 @@ public class LTRStandardDecoder extends Decoder implements IUnFilteredRealBuffer
|
||||
|
||||
mLTRMessageProcessor = new LTRStandardMessageProcessor( direction, aliasList );
|
||||
mLTRMessageFramer.addMessageListener( mLTRMessageProcessor );
|
||||
mLTRMessageProcessor.setMessageListener( this );
|
||||
mLTRMessageProcessor.setMessageListener( getMessageListener() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -102,7 +102,7 @@ public class MDCDecoder extends Decoder implements IFilteredRealBufferListener,
|
||||
/* Message Processor */
|
||||
mMessageProcessor = new MDCMessageProcessor( aliasList );
|
||||
mMessageFramer.addMessageListener( mMessageProcessor );
|
||||
mMessageProcessor.addMessageListener( this );
|
||||
mMessageProcessor.addMessageListener(getMessageListener());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
|
||||
@@ -148,7 +148,7 @@ public class MPT1327Decoder extends Decoder implements IFilteredRealBufferListen
|
||||
|
||||
/* Fully decoded and framed messages processor */
|
||||
mMessageProcessor = new MPT1327MessageProcessor(aliasList);
|
||||
mMessageProcessor.setMessageListener(this);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
mControlMessageFramer.addMessageListener(mMessageProcessor);
|
||||
mTrafficMessageFramer.addMessageListener(mMessageProcessor);
|
||||
}
|
||||
|
||||
@@ -1,70 +1,91 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.instrument.Instrumentable;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeListener;
|
||||
import io.github.dsheirer.source.tuner.frequency.IFrequencyChangeProvider;
|
||||
|
||||
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
|
||||
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider
|
||||
{
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
|
||||
public P25Decoder( AliasList aliasList )
|
||||
{
|
||||
mMessageProcessor = new P25MessageProcessor( aliasList );
|
||||
mMessageProcessor.setMessageListener( this );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mMessageProcessor.dispose();
|
||||
mMessageProcessor = null;
|
||||
}
|
||||
private P25MessageProcessor mMessageProcessor;
|
||||
private AliasList mAliasList;
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
public P25Decoder(AliasList aliasList)
|
||||
{
|
||||
return DecoderType.P25_PHASE1;
|
||||
mAliasList = aliasList;
|
||||
mMessageProcessor = new P25MessageProcessor(mAliasList);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
}
|
||||
|
||||
protected P25MessageProcessor getMessageProcessor()
|
||||
{
|
||||
return mMessageProcessor;
|
||||
}
|
||||
|
||||
public abstract Modulation getModulation();
|
||||
|
||||
public enum Modulation
|
||||
{
|
||||
CQPSK( "Simulcast (LSM)", "LSM" ),
|
||||
C4FM( "Normal (C4FM)", "C4FM" );
|
||||
|
||||
private String mLabel;
|
||||
private String mShortLabel;
|
||||
|
||||
private Modulation( String label, String shortLabel )
|
||||
{
|
||||
mLabel = label;
|
||||
mShortLabel = shortLabel;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String getShortLabel()
|
||||
{
|
||||
return mShortLabel;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
protected AliasList getAliasList()
|
||||
{
|
||||
return mAliasList;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mMessageProcessor.dispose();
|
||||
mMessageProcessor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.P25_PHASE1;
|
||||
}
|
||||
|
||||
protected P25MessageProcessor getMessageProcessor()
|
||||
{
|
||||
return mMessageProcessor;
|
||||
}
|
||||
|
||||
public abstract Modulation getModulation();
|
||||
|
||||
public enum Modulation
|
||||
{
|
||||
CQPSK("Simulcast (LSM)", "LSM"),
|
||||
C4FM("Normal (C4FM)", "C4FM");
|
||||
|
||||
private String mLabel;
|
||||
private String mShortLabel;
|
||||
|
||||
private Modulation(String label, String shortLabel)
|
||||
{
|
||||
mLabel = label;
|
||||
mShortLabel = shortLabel;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public String getShortLabel()
|
||||
{
|
||||
return mShortLabel;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,10 +19,7 @@
|
||||
package io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.instrument.tap.Tap;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.FloatTap;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
|
||||
import io.github.dsheirer.sample.real.RealBuffer;
|
||||
@@ -30,7 +27,6 @@ import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
@@ -105,93 +101,6 @@ public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferLi
|
||||
return mSymbolFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup( "P25 C4FM Decoder" );
|
||||
|
||||
group.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
|
||||
group.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mSymbolFilter.getTapGroups() );
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instrumentation tap to monitor internal processing.
|
||||
*/
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.registerTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
||||
FloatTap symbolTap = (FloatTap)tap;
|
||||
mSymbolFilter.setListener( symbolTap );
|
||||
symbolTap.setListener( mC4FMSlicer );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
||||
DibitTap slicerTap = (DibitTap)tap;
|
||||
|
||||
if( mC4FMSlicer != null )
|
||||
{
|
||||
mC4FMSlicer.addListener( slicerTap );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap.
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.unregisterTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
|
||||
mSymbolFilter.setListener( mC4FMSlicer );
|
||||
break;
|
||||
case INSTRUMENT_C4FM_SLICER_OUTPUT:
|
||||
DibitTap slicerTap = (DibitTap)tap;
|
||||
|
||||
if( mC4FMSlicer != null )
|
||||
{
|
||||
mC4FMSlicer.removeListener( slicerTap );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.module.decode.p25;
|
||||
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.DQPSKSymbolPhaseErrorCalculator;
|
||||
import io.github.dsheirer.dsp.psk.IQPSKSymbolDecoder;
|
||||
import io.github.dsheirer.dsp.psk.ISymbolPhaseErrorCalculator;
|
||||
import io.github.dsheirer.dsp.psk.QPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
||||
import io.github.dsheirer.sample.complex.IComplexBufferListener;
|
||||
import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25_C4FMDecoder2 extends P25Decoder implements IComplexBufferListener, Listener<ComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25_C4FMDecoder2.class);
|
||||
private static final double SYMBOL_RATE = 4800;
|
||||
private double mSampleRate;
|
||||
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter mBasebandFilter;
|
||||
private QPSKDemodulator mQPSKDemodulator;
|
||||
private IPhaseLockedLoop mPhaseLockedLoop;
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
|
||||
private QPSKSymbolDecoder mQPSKSlicer = new QPSKSymbolDecoder();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
private ISymbolPhaseErrorCalculator mSymbolPhaseErrorCalculator = new DQPSKSymbolPhaseErrorCalculator();
|
||||
private IQPSKSymbolDecoder mSymbolDecoder = new QPSKSymbolDecoder();
|
||||
private Listener<Complex> mSymbolListener;
|
||||
private Listener<Double> mPLLPhaseErrorListener;
|
||||
|
||||
public P25_C4FMDecoder2(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
|
||||
setSampleRate(48000.0);
|
||||
|
||||
mStreamConverter.setListener(mAGC);
|
||||
}
|
||||
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
if(sampleRate <= SYMBOL_RATE * 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate must be at least twice the symbol rate [4800]");
|
||||
}
|
||||
|
||||
mSampleRate = sampleRate;
|
||||
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
|
||||
|
||||
mPhaseLockedLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
|
||||
mQPSKDemodulator = new QPSKDemodulator(mPhaseLockedLoop, mSymbolPhaseErrorCalculator, mSymbolDecoder,
|
||||
mSampleRate, SYMBOL_RATE);
|
||||
mQPSKDemodulator.setSymbolListener(mSymbolListener);
|
||||
mQPSKDemodulator.setPLLErrorListener(mPLLPhaseErrorListener);
|
||||
|
||||
//Message framer can trigger a symbol-inversion correction to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mPhaseLockedLoop);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setDibitListener(mMessageFramer);
|
||||
}
|
||||
|
||||
public void setSymbolListener(Listener<Complex> listener)
|
||||
{
|
||||
mSymbolListener = listener;
|
||||
mQPSKDemodulator.setSymbolListener(listener);
|
||||
}
|
||||
|
||||
public void setPLLPhaseErrorListener(Listener<Double> listener)
|
||||
{
|
||||
mPLLPhaseErrorListener = listener;
|
||||
mQPSKDemodulator.setPLLErrorListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
ComplexBuffer filtered = mBasebandFilter.filter(complexBuffer);
|
||||
ComplexBuffer amplified = mAGC.filter(filtered);
|
||||
mQPSKDemodulator.receive(amplified);
|
||||
}
|
||||
|
||||
private double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
private float[] getBasebandFilter()
|
||||
{
|
||||
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||
|
||||
if(filter == null)
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate((int)getSampleRate())
|
||||
.passBandCutoff(6000)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.1)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandStart(6250)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
filter = FilterFactory.getTaps(specification);
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mStreamConverter.dispose();
|
||||
mStreamConverter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
|
||||
mQPSKSlicer.dispose();
|
||||
mQPSKSlicer = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
return new Listener<FrequencyChangeEvent>()
|
||||
{
|
||||
@Override
|
||||
public void receive(FrequencyChangeEvent frequencyChangeEvent)
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return P25_C4FMDecoder2.this;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 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/>
|
||||
******************************************************************************/
|
||||
@@ -23,13 +23,8 @@ import io.github.dsheirer.dsp.filter.Window.WindowType;
|
||||
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
|
||||
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
|
||||
import io.github.dsheirer.dsp.psk.LSMDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.QPSKPolarSlicer;
|
||||
import io.github.dsheirer.instrument.tap.Tap;
|
||||
import io.github.dsheirer.dsp.psk.QPSKSymbolDecoder;
|
||||
import io.github.dsheirer.instrument.tap.TapGroup;
|
||||
import io.github.dsheirer.instrument.tap.stream.ComplexSampleTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.ComplexTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.DibitTap;
|
||||
import io.github.dsheirer.instrument.tap.stream.QPSKTap;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexBufferToStreamConverter;
|
||||
@@ -38,73 +33,72 @@ import io.github.dsheirer.source.tuner.frequency.FrequencyChangeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_LSMDecoder.class );
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25_LSMDecoder.class);
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
||||
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
||||
private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
||||
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
||||
private static final String INSTRUMENT_BASEBAND_FILTER_OUTPUT = "Tap Point: Baseband Filter Output";
|
||||
private static final String INSTRUMENT_AGC_OUTPUT = "Tap Point: AGC Output";
|
||||
private static final String INSTRUMENT_LSM_DEMODULATOR_OUTPUT = "Tap Point: LSM Demodulator Output";
|
||||
private static final String INSTRUMENT_QPSK_SLICER_OUTPUT = "Tap Point: QPSK Slicer Output";
|
||||
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl( 32 );
|
||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||
private QPSKPolarSlicer mQPSKSlicer = new QPSKPolarSlicer();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
public P25_LSMDecoder( AliasList aliasList )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter_CB_CB( FilterFactory.getLowPass(
|
||||
48000, 7250, 8000, 60, WindowType.HANNING, true ), 1.0f );
|
||||
|
||||
mBasebandFilter.setListener( mStreamConverter );
|
||||
|
||||
mStreamConverter.setListener( mAGC );
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
|
||||
mAGC.setListener( mLSMDemodulator );
|
||||
|
||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
||||
|
||||
mMessageFramer = new P25MessageFramer( aliasList, mLSMDemodulator );
|
||||
mQPSKSlicer.addListener( mMessageFramer );
|
||||
|
||||
mMessageFramer.setListener( getMessageProcessor() );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mStreamConverter.dispose();
|
||||
mStreamConverter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
private ComplexFIRFilter_CB_CB mBasebandFilter;
|
||||
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
|
||||
private ComplexFeedForwardGainControl mAGC =
|
||||
new ComplexFeedForwardGainControl(32);
|
||||
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
|
||||
private QPSKSymbolDecoder mQPSKSlicer = new QPSKSymbolDecoder();
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
mLSMDemodulator.dispose();
|
||||
mLSMDemodulator = null;
|
||||
|
||||
mQPSKSlicer.dispose();
|
||||
mQPSKSlicer = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
public P25_LSMDecoder(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
|
||||
mBasebandFilter = new ComplexFIRFilter_CB_CB(FilterFactory.getLowPass(
|
||||
48000, 7250, 8000, 60, WindowType.HANNING, true), 1.0f);
|
||||
|
||||
mBasebandFilter.setListener(mStreamConverter);
|
||||
|
||||
mStreamConverter.setListener(mAGC);
|
||||
|
||||
mAGC.setListener(mLSMDemodulator);
|
||||
|
||||
mLSMDemodulator.setSymbolListener(mQPSKSlicer);
|
||||
|
||||
mMessageFramer = new P25MessageFramer(aliasList, mLSMDemodulator.getCostasLoop());
|
||||
mQPSKSlicer.addListener(mMessageFramer);
|
||||
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = null;
|
||||
|
||||
mStreamConverter.dispose();
|
||||
mStreamConverter = null;
|
||||
|
||||
mAGC.dispose();
|
||||
mAGC = null;
|
||||
|
||||
mLSMDemodulator.dispose();
|
||||
mLSMDemodulator = null;
|
||||
|
||||
mQPSKSlicer.dispose();
|
||||
mQPSKSlicer = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
|
||||
@@ -130,130 +124,34 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return mBasebandFilter;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of instrumentation taps for monitoring internal processing
|
||||
*/
|
||||
@Override
|
||||
public List<TapGroup> getTapGroups()
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
if( mAvailableTaps == null )
|
||||
{
|
||||
mAvailableTaps = new ArrayList<>();
|
||||
|
||||
TapGroup group = new TapGroup( "P25 LSM Decoder" );
|
||||
|
||||
group.add( new ComplexTap( INSTRUMENT_BASEBAND_FILTER_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new ComplexTap( INSTRUMENT_AGC_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new QPSKTap( INSTRUMENT_LSM_DEMODULATOR_OUTPUT, 0, 1.0f ) );
|
||||
group.add( new DibitTap( INSTRUMENT_QPSK_SLICER_OUTPUT, 0, 0.1f ) );
|
||||
|
||||
mAvailableTaps.add( group );
|
||||
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mAvailableTaps.addAll( mLSMDemodulator.getTapGroups() );
|
||||
}
|
||||
}
|
||||
|
||||
return mAvailableTaps;
|
||||
return mBasebandFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void registerTap( Tap tap )
|
||||
public Modulation getModulation()
|
||||
{
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mLSMDemodulator.registerTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
ComplexSampleTap baseband = (ComplexSampleTap)tap;
|
||||
mStreamConverter.setListener( baseband );
|
||||
baseband.setListener( mAGC );
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
ComplexSampleTap agcSymbol = (ComplexSampleTap)tap;
|
||||
mAGC.setListener( agcSymbol );
|
||||
agcSymbol.setListener( mLSMDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
QPSKTap qpsk = (QPSKTap)tap;
|
||||
mLSMDemodulator.setSymbolListener( qpsk );
|
||||
qpsk.setListener( mQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.addListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
return Modulation.CQPSK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the instrumentation tap
|
||||
*/
|
||||
@Override
|
||||
public void unregisterTap( Tap tap )
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
if( mLSMDemodulator != null )
|
||||
{
|
||||
mLSMDemodulator.unregisterTap( tap );
|
||||
}
|
||||
|
||||
switch( tap.getName() )
|
||||
{
|
||||
case INSTRUMENT_BASEBAND_FILTER_OUTPUT:
|
||||
mStreamConverter.setListener( mAGC );
|
||||
break;
|
||||
case INSTRUMENT_AGC_OUTPUT:
|
||||
mAGC.setListener( mLSMDemodulator );
|
||||
break;
|
||||
case INSTRUMENT_LSM_DEMODULATOR_OUTPUT:
|
||||
mLSMDemodulator.setSymbolListener( mQPSKSlicer );
|
||||
break;
|
||||
case INSTRUMENT_QPSK_SLICER_OUTPUT:
|
||||
mQPSKSlicer.removeListener( (DibitTap)tap );
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException( "Unrecognized tap: " +
|
||||
tap.getName() );
|
||||
}
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class PassportDecoder extends Decoder
|
||||
|
||||
mPassportMessageProcessor = new PassportMessageProcessor( aliasList );
|
||||
mPassportMessageFramer.addMessageListener( mPassportMessageProcessor );
|
||||
mPassportMessageProcessor.setMessageListener( this );
|
||||
mPassportMessageProcessor.setMessageListener( getMessageListener() );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -104,8 +104,8 @@ public class Tait1200Decoder extends Decoder implements IFilteredRealBufferListe
|
||||
mMessageFramerGPS.addMessageListener( mMessageAProcessor );
|
||||
mMessageFramerANI.addMessageListener( mMessageBProcessor );
|
||||
|
||||
mMessageAProcessor.setMessageListener( this );
|
||||
mMessageBProcessor.setMessageListener( this );
|
||||
mMessageAProcessor.setMessageListener(getMessageListener());
|
||||
mMessageBProcessor.setMessageListener(getMessageListener());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
|
||||
@@ -42,6 +42,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
||||
private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class);
|
||||
|
||||
public static final int AUDIO_SAMPLE_RATE = 8000;
|
||||
public static final int BASEBAND_SAMPLE_RATE = 48000;
|
||||
public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds
|
||||
|
||||
private Map<String,AudioPacketWaveRecorder> mRecorders = new HashMap<>();
|
||||
@@ -229,7 +230,7 @@ public class RecorderManager implements Listener<AudioPacket>
|
||||
sb.append(SystemProperties.getInstance().getApplicationFolder("recordings"));
|
||||
sb.append(File.separator).append(channelName).append("_baseband");
|
||||
|
||||
return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString());
|
||||
return new ComplexBufferWaveRecorder(BASEBAND_SAMPLE_RATE, sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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/>
|
||||
******************************************************************************/
|
||||
@@ -27,412 +27,427 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class Complex implements Serializable
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( Complex.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(Complex.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private float mLeft;
|
||||
private float mRight;
|
||||
|
||||
public Complex( float left, float right )
|
||||
{
|
||||
mLeft = left;
|
||||
mRight = right;
|
||||
}
|
||||
|
||||
public Complex()
|
||||
{
|
||||
this( 0.0f, 0.0f );
|
||||
}
|
||||
|
||||
public void setInphase( float inphase )
|
||||
{
|
||||
mLeft = inphase;
|
||||
}
|
||||
|
||||
public void setQuadrature( float quadrature )
|
||||
{
|
||||
mRight = quadrature;
|
||||
}
|
||||
|
||||
public void setValues( float inphase, float quadrature )
|
||||
{
|
||||
mLeft = inphase;
|
||||
mRight = quadrature;
|
||||
}
|
||||
private float mRight;
|
||||
|
||||
public Complex copy()
|
||||
{
|
||||
return new Complex( mLeft, mRight );
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "I:" );
|
||||
sb.append( mLeft );
|
||||
sb.append( " Q:" );
|
||||
sb.append( mRight );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new sample representing the conjugate of this one
|
||||
*/
|
||||
public Complex conjugate()
|
||||
{
|
||||
return new Complex( mLeft, -mRight );
|
||||
}
|
||||
public Complex(float left, float right)
|
||||
{
|
||||
mLeft = left;
|
||||
mRight = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this sample by the scalor value
|
||||
*/
|
||||
public void multiply( float scalor )
|
||||
{
|
||||
mLeft *= scalor;
|
||||
mRight *= scalor;
|
||||
}
|
||||
|
||||
public static Complex multiply( Complex sample, float scalor )
|
||||
{
|
||||
return new Complex( sample.left() * scalor,
|
||||
sample.right() * scalor );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the inphase component of multiplying two complex numbers, A and B.
|
||||
*/
|
||||
public static float multiplyInphase( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
||||
{
|
||||
return ( inphaseA * inphaseB ) - ( quadratureA * quadratureB );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the quadrature component of multiplying two complex numbers, A and B.
|
||||
*/
|
||||
public static float multiplyQuadrature( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
||||
{
|
||||
return ( quadratureA * inphaseB ) + ( inphaseA * quadratureB );
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this sample by the multiplier sample
|
||||
*/
|
||||
public void multiply( Complex multiplier )
|
||||
{
|
||||
float inphase = multiplyInphase( inphase(), quadrature(),
|
||||
multiplier.inphase(), multiplier.quadrature() );
|
||||
float quadrature = multiplyQuadrature( inphase(), quadrature(),
|
||||
multiplier.inphase(), multiplier.quadrature() );
|
||||
|
||||
mLeft = inphase;
|
||||
mRight = quadrature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies both samples returning a new sample with the results
|
||||
*/
|
||||
public static Complex multiply( Complex sample1, Complex sample2 )
|
||||
{
|
||||
float inphase = multiplyInphase( sample1.inphase(), sample1.quadrature(),
|
||||
sample2.inphase(), sample2.quadrature() );
|
||||
float quadrature = multiplyQuadrature( sample1.inphase(), sample1.quadrature(),
|
||||
sample2.inphase(), sample2.quadrature() );
|
||||
public Complex()
|
||||
{
|
||||
this(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
return new Complex( inphase, quadrature );
|
||||
}
|
||||
public void setInphase(float inphase)
|
||||
{
|
||||
mLeft = inphase;
|
||||
}
|
||||
|
||||
public static Complex multiply( Complex sample,
|
||||
Float inphase, Float quadrature )
|
||||
{
|
||||
float i = multiplyInphase( sample.inphase(), sample.quadrature(),
|
||||
inphase, quadrature );
|
||||
float q = multiplyQuadrature( sample.inphase(), sample.quadrature(),
|
||||
inphase, quadrature );
|
||||
|
||||
return new Complex( i, q );
|
||||
}
|
||||
|
||||
public static Complex multiply( Float inphase, Float quadrature, Complex sample )
|
||||
{
|
||||
float i = multiplyInphase( inphase, quadrature, sample.inphase(), sample.quadrature() );
|
||||
float q = multiplyQuadrature( inphase, quadrature, sample.inphase(), sample.quadrature() );
|
||||
|
||||
return new Complex( i, q );
|
||||
}
|
||||
public void setQuadrature(float quadrature)
|
||||
{
|
||||
mRight = quadrature;
|
||||
}
|
||||
|
||||
public static Complex multiply( float inphaseA, float quadratureA, float inphaseB, float quadratureB )
|
||||
{
|
||||
float i = multiplyInphase( inphaseA, quadratureA, inphaseB, quadratureB );
|
||||
float q = multiplyQuadrature( inphaseA, quadratureA, inphaseB, quadratureB );
|
||||
|
||||
return new Complex( i, q );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the adder sample value to this sample
|
||||
*/
|
||||
public void add( Complex adder )
|
||||
{
|
||||
mLeft += adder.left();
|
||||
mRight += adder.right();
|
||||
}
|
||||
public void setValues(float inphase, float quadrature)
|
||||
{
|
||||
mLeft = inphase;
|
||||
mRight = quadrature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the two complex samples returning a new complex sample with the result
|
||||
*/
|
||||
public static Complex add( Complex first, Complex second )
|
||||
{
|
||||
return new Complex( first.left() + second.left(),
|
||||
first.right() + second.right() );
|
||||
}
|
||||
public void setValues(Complex sampleToCopy)
|
||||
{
|
||||
mLeft = sampleToCopy.left();
|
||||
mRight = sampleToCopy.right();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the two complex samples returning a new complex sample with the result
|
||||
*/
|
||||
public static Complex subtract( Complex first, Complex second )
|
||||
{
|
||||
return new Complex( first.left() - second.left(),
|
||||
first.right() - second.right() );
|
||||
}
|
||||
public Complex copy()
|
||||
{
|
||||
return new Complex(mLeft, mRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnitude of this sample
|
||||
*/
|
||||
public float magnitude()
|
||||
{
|
||||
return (float)Math.sqrt( magnitudeSquared() );
|
||||
}
|
||||
|
||||
public static float magnitude( float inphase, float quadrature )
|
||||
{
|
||||
return (float)Math.sqrt( ( inphase * inphase ) + ( quadrature * quadrature ) );
|
||||
}
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
/**
|
||||
* Magnitude squared of this sample
|
||||
*/
|
||||
public float magnitudeSquared()
|
||||
{
|
||||
return norm();
|
||||
}
|
||||
sb.append("I:");
|
||||
sb.append(mLeft);
|
||||
sb.append(" Q:");
|
||||
sb.append(mRight);
|
||||
|
||||
/**
|
||||
* Norm of this sample = ( i * i ) + ( q * q )
|
||||
*/
|
||||
public float norm()
|
||||
{
|
||||
return (float)( ( inphase() * inphase() ) +
|
||||
( quadrature() * quadrature() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vector length to 1 (unit circle)
|
||||
*/
|
||||
public void normalize()
|
||||
{
|
||||
float magnitude = magnitude();
|
||||
|
||||
if( magnitude != 0 )
|
||||
{
|
||||
multiply( (float)( 1.0f / magnitude() ) );
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vector length to 1 (unit circle),
|
||||
* avoiding square root multiplication
|
||||
*/
|
||||
public void fastNormalize()
|
||||
{
|
||||
multiply( (float)( 1.95f - magnitudeSquared() ) );
|
||||
}
|
||||
|
||||
public float left()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
public float right()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
/**
|
||||
* Returns a new sample representing the conjugate of this one
|
||||
*/
|
||||
public Complex conjugate()
|
||||
{
|
||||
return new Complex(mLeft, -mRight);
|
||||
}
|
||||
|
||||
public float inphase()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of in-phase component
|
||||
*/
|
||||
public float inPhaseAbsolute()
|
||||
{
|
||||
return Math.abs( mLeft );
|
||||
}
|
||||
/**
|
||||
* Multiplies this sample by the scalor value
|
||||
*/
|
||||
public void multiply(float scalor)
|
||||
{
|
||||
mLeft *= scalor;
|
||||
mRight *= scalor;
|
||||
}
|
||||
|
||||
public float quadrature()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of quadrature component
|
||||
*/
|
||||
public float quadratureAbsolute()
|
||||
{
|
||||
return Math.abs( mRight );
|
||||
}
|
||||
public static Complex multiply(Complex sample, float scalor)
|
||||
{
|
||||
return new Complex(sample.left() * scalor,
|
||||
sample.right() * scalor);
|
||||
}
|
||||
|
||||
public float x()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
public float y()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
public float real()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
/**
|
||||
* Calculates the inphase component of multiplying two complex numbers, A and B.
|
||||
*/
|
||||
public static float multiplyInphase(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||
{
|
||||
return (inphaseA * inphaseB) - (quadratureA * quadratureB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of real component
|
||||
*/
|
||||
public float realAbsolute()
|
||||
{
|
||||
return Math.abs( mLeft );
|
||||
}
|
||||
/**
|
||||
* Calculates the quadrature component of multiplying two complex numbers, A and B.
|
||||
*/
|
||||
public static float multiplyQuadrature(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||
{
|
||||
return (quadratureA * inphaseB) + (inphaseA * quadratureB);
|
||||
}
|
||||
|
||||
public float imaginary()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
/**
|
||||
* Multiplies this sample by the multiplier sample
|
||||
*/
|
||||
public void multiply(Complex multiplier)
|
||||
{
|
||||
float inphase = multiplyInphase(inphase(), quadrature(),
|
||||
multiplier.inphase(), multiplier.quadrature());
|
||||
float quadrature = multiplyQuadrature(inphase(), quadrature(),
|
||||
multiplier.inphase(), multiplier.quadrature());
|
||||
|
||||
/**
|
||||
* Absolute value of imaginary component
|
||||
*/
|
||||
public float imaginaryAbsolute()
|
||||
{
|
||||
return Math.abs( mRight );
|
||||
}
|
||||
mLeft = inphase;
|
||||
mRight = quadrature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the greater absolute value between left and right values
|
||||
*/
|
||||
public float maximumAbsolute()
|
||||
{
|
||||
if( Math.abs( mLeft ) > Math.abs( mRight ) )
|
||||
{
|
||||
return Math.abs( mLeft );
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.abs( mRight );
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Multiplies both samples returning a new sample with the results
|
||||
*/
|
||||
public static Complex multiply(Complex sample1, Complex sample2)
|
||||
{
|
||||
float inphase = multiplyInphase(sample1.inphase(), sample1.quadrature(),
|
||||
sample2.inphase(), sample2.quadrature());
|
||||
float quadrature = multiplyQuadrature(sample1.inphase(), sample1.quadrature(),
|
||||
sample2.inphase(), sample2.quadrature());
|
||||
|
||||
/**
|
||||
* Creates a new complex sample representing the angle with unit circle
|
||||
* magnitude
|
||||
* @param angle in radians
|
||||
* @return
|
||||
*/
|
||||
public static Complex fromAngle( float angle )
|
||||
{
|
||||
return new Complex( (float)Math.cos( angle ),
|
||||
(float)Math.sin( angle ) );
|
||||
}
|
||||
return new Complex(inphase, quadrature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle of this sample in radians
|
||||
*/
|
||||
public float angle()
|
||||
{
|
||||
return (float)Math.atan2( y(), x() );
|
||||
}
|
||||
public static Complex multiply(Complex sample,
|
||||
Float inphase, Float quadrature)
|
||||
{
|
||||
float i = multiplyInphase(sample.inphase(), sample.quadrature(),
|
||||
inphase, quadrature);
|
||||
float q = multiplyQuadrature(sample.inphase(), sample.quadrature(),
|
||||
inphase, quadrature);
|
||||
|
||||
public float angleDegrees()
|
||||
{
|
||||
return (float)Math.toDegrees(angle());
|
||||
}
|
||||
return new Complex(i, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains the i and q quantities to +/- value.
|
||||
*
|
||||
* @param value - maximum absolute value
|
||||
*/
|
||||
public void clip( float value )
|
||||
{
|
||||
if( mLeft > value )
|
||||
{
|
||||
mLeft = value;
|
||||
}
|
||||
else if( mLeft < -value )
|
||||
{
|
||||
mLeft = -value;
|
||||
}
|
||||
|
||||
if( mRight > value )
|
||||
{
|
||||
mRight = value;
|
||||
}
|
||||
else if( mRight < -value )
|
||||
{
|
||||
mRight = -value;
|
||||
}
|
||||
}
|
||||
public static Complex multiply(Float inphase, Float quadrature, Complex sample)
|
||||
{
|
||||
float i = multiplyInphase(inphase, quadrature, sample.inphase(), sample.quadrature());
|
||||
float q = multiplyQuadrature(inphase, quadrature, sample.inphase(), sample.quadrature());
|
||||
|
||||
/**
|
||||
* Angle of this sample in degrees
|
||||
*/
|
||||
public float polarAngle()
|
||||
{
|
||||
return (float)Math.toDegrees( angle() );
|
||||
}
|
||||
return new Complex(i, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an approximate magnitude value for this sample.
|
||||
*/
|
||||
public float envelope()
|
||||
{
|
||||
return envelope( mLeft, mRight );
|
||||
}
|
||||
|
||||
public static float envelope( float inphase, float quadrature )
|
||||
{
|
||||
float inphaseAbsolute = Math.abs( inphase );
|
||||
float quadratureAbsolute = Math.abs( quadrature );
|
||||
|
||||
if( inphaseAbsolute > quadratureAbsolute )
|
||||
{
|
||||
return inphaseAbsolute + ( 0.4f * quadratureAbsolute );
|
||||
}
|
||||
else
|
||||
{
|
||||
return quadratureAbsolute + ( 0.4f * inphaseAbsolute );
|
||||
}
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
double angle = Math.PI; //180 degrees
|
||||
|
||||
Complex s1 = new Complex( (float)Math.sin( angle ), (float)Math.cos( angle ) );
|
||||
public static Complex multiply(float inphaseA, float quadratureA, float inphaseB, float quadratureB)
|
||||
{
|
||||
float i = multiplyInphase(inphaseA, quadratureA, inphaseB, quadratureB);
|
||||
float q = multiplyQuadrature(inphaseA, quadratureA, inphaseB, quadratureB);
|
||||
|
||||
Complex tap = new Complex( 1.0f, -0.1f );
|
||||
return new Complex(i, q);
|
||||
}
|
||||
|
||||
Complex convolved = Complex.multiply( s1, tap );
|
||||
|
||||
mLog.debug( "s: " + s1.toString() +
|
||||
" t: " + tap.toString() +
|
||||
" convolved: " + convolved.toString() );
|
||||
|
||||
}
|
||||
/**
|
||||
* Adds the adder sample value to this sample
|
||||
*/
|
||||
public void add(Complex adder)
|
||||
{
|
||||
mLeft += adder.left();
|
||||
mRight += adder.right();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the two complex samples returning a new complex sample with the result
|
||||
*/
|
||||
public static Complex add(Complex first, Complex second)
|
||||
{
|
||||
return new Complex(first.left() + second.left(),
|
||||
first.right() + second.right());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the two complex samples returning a new complex sample with the result
|
||||
*/
|
||||
public static Complex subtract(Complex first, Complex second)
|
||||
{
|
||||
return new Complex(first.left() - second.left(),
|
||||
first.right() - second.right());
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnitude of this sample
|
||||
*/
|
||||
public float magnitude()
|
||||
{
|
||||
return (float)Math.sqrt(magnitudeSquared());
|
||||
}
|
||||
|
||||
public static float magnitude(float inphase, float quadrature)
|
||||
{
|
||||
return (float)Math.sqrt((inphase * inphase) + (quadrature * quadrature));
|
||||
}
|
||||
|
||||
/**
|
||||
* Magnitude squared of this sample
|
||||
*/
|
||||
public float magnitudeSquared()
|
||||
{
|
||||
return norm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Norm of this sample = ( i * i ) + ( q * q )
|
||||
*/
|
||||
public float norm()
|
||||
{
|
||||
return (float)((inphase() * inphase()) +
|
||||
(quadrature() * quadrature()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vector length to 1 (unit circle)
|
||||
*/
|
||||
public void normalize()
|
||||
{
|
||||
float magnitude = magnitude();
|
||||
|
||||
if(magnitude != 0)
|
||||
{
|
||||
multiply((float)(1.0f / magnitude()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vector length to 1 (unit circle),
|
||||
* avoiding square root multiplication
|
||||
*/
|
||||
public void fastNormalize()
|
||||
{
|
||||
multiply((float)(1.95f - magnitudeSquared()));
|
||||
}
|
||||
|
||||
public float left()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
public float right()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
public float inphase()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of in-phase component
|
||||
*/
|
||||
public float inPhaseAbsolute()
|
||||
{
|
||||
return Math.abs(mLeft);
|
||||
}
|
||||
|
||||
public float quadrature()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of quadrature component
|
||||
*/
|
||||
public float quadratureAbsolute()
|
||||
{
|
||||
return Math.abs(mRight);
|
||||
}
|
||||
|
||||
public float x()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
public float y()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
public float real()
|
||||
{
|
||||
return mLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of real component
|
||||
*/
|
||||
public float realAbsolute()
|
||||
{
|
||||
return Math.abs(mLeft);
|
||||
}
|
||||
|
||||
public float imaginary()
|
||||
{
|
||||
return mRight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of imaginary component
|
||||
*/
|
||||
public float imaginaryAbsolute()
|
||||
{
|
||||
return Math.abs(mRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the greater absolute value between left and right values
|
||||
*/
|
||||
public float maximumAbsolute()
|
||||
{
|
||||
if(Math.abs(mLeft) > Math.abs(mRight))
|
||||
{
|
||||
return Math.abs(mLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Math.abs(mRight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new complex sample representing the angle with unit circle
|
||||
* magnitude
|
||||
*
|
||||
* @param angle in radians
|
||||
* @return
|
||||
*/
|
||||
public static Complex fromAngle(double angle)
|
||||
{
|
||||
return new Complex((float)Math.cos(angle), (float)Math.sin(angle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this complex instance with the specified angle in radians.
|
||||
* @param angle in radians
|
||||
*/
|
||||
public void setAngle(double angle)
|
||||
{
|
||||
setInphase((float)Math.cos(angle));
|
||||
setQuadrature((float)Math.sin(angle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle of this sample in radians
|
||||
*/
|
||||
public float angle()
|
||||
{
|
||||
return (float)Math.atan2(y(), x());
|
||||
}
|
||||
|
||||
public float angleDegrees()
|
||||
{
|
||||
return (float)Math.toDegrees(angle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrains the i and q quantities to +/- value.
|
||||
*
|
||||
* @param value - maximum absolute value
|
||||
*/
|
||||
public void clip(float value)
|
||||
{
|
||||
if(mLeft > value)
|
||||
{
|
||||
mLeft = value;
|
||||
}
|
||||
else if(mLeft < -value)
|
||||
{
|
||||
mLeft = -value;
|
||||
}
|
||||
|
||||
if(mRight > value)
|
||||
{
|
||||
mRight = value;
|
||||
}
|
||||
else if(mRight < -value)
|
||||
{
|
||||
mRight = -value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Angle of this sample in degrees
|
||||
*/
|
||||
public float polarAngle()
|
||||
{
|
||||
return (float)Math.toDegrees(angle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an approximate magnitude value for this sample.
|
||||
*/
|
||||
public float envelope()
|
||||
{
|
||||
return envelope(mLeft, mRight);
|
||||
}
|
||||
|
||||
public static float envelope(float inphase, float quadrature)
|
||||
{
|
||||
float inphaseAbsolute = Math.abs(inphase);
|
||||
float quadratureAbsolute = Math.abs(quadrature);
|
||||
|
||||
if(inphaseAbsolute > quadratureAbsolute)
|
||||
{
|
||||
return inphaseAbsolute + (0.4f * quadratureAbsolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
return quadratureAbsolute + (0.4f * inphaseAbsolute);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
double angle = Math.PI; //180 degrees
|
||||
|
||||
Complex s1 = new Complex((float)Math.sin(angle), (float)Math.cos(angle));
|
||||
|
||||
Complex tap = new Complex(1.0f, -0.1f);
|
||||
|
||||
Complex convolved = Complex.multiply(s1, tap);
|
||||
|
||||
mLog.debug("s: " + s1.toString() +
|
||||
" t: " + tap.toString() +
|
||||
" convolved: " + convolved.toString());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 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 io.github.dsheirer.source;
|
||||
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
@@ -10,22 +25,24 @@ import java.io.IOException;
|
||||
*/
|
||||
public interface IControllableFileSource
|
||||
{
|
||||
/**
|
||||
* Opens the file source
|
||||
*/
|
||||
public void open() throws IOException, UnsupportedAudioFileException;
|
||||
|
||||
public void close() throws IOException;
|
||||
|
||||
public File getFile();
|
||||
|
||||
public void next( int frames ) throws IOException;
|
||||
|
||||
public void next( int frames, boolean broadcast ) throws IOException;
|
||||
|
||||
public long getFrameCount() throws IOException;
|
||||
|
||||
public void setListener( IFrameLocationListener listener );
|
||||
|
||||
public void removeListener( IFrameLocationListener listener );
|
||||
/**
|
||||
* Opens the file source
|
||||
*/
|
||||
public void open() throws IOException, UnsupportedAudioFileException;
|
||||
|
||||
public void close() throws IOException;
|
||||
|
||||
public File getFile();
|
||||
|
||||
public void next(int frames) throws IOException;
|
||||
|
||||
public void next(int frames, boolean broadcast) throws IOException;
|
||||
|
||||
public long getFrameCount() throws IOException;
|
||||
|
||||
public int getSampleRate();
|
||||
|
||||
public void setListener(IFrameLocationListener listener);
|
||||
|
||||
public void removeListener(IFrameLocationListener listener);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user