mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
330 - updates P25 C4FM decoder to use the new DQPSK demodulator and
removes the previous FM demodulator based decoder.
This commit is contained in:
@@ -32,7 +32,7 @@ public class DQPSKSymbolEvaluator implements IDQPSKSymbolEvaluator
|
||||
private Dibit mSymbolDecision = Dibit.D00_PLUS_1;
|
||||
|
||||
/**
|
||||
* Data-directed symbol phase and timing error detector and symbol decision slicer.
|
||||
* Decision-directed symbol phase and timing error detector and symbol decision slicer.
|
||||
*
|
||||
* Symbol decision is based on the closest reference quadrant for the sampled symbol.
|
||||
*
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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
|
||||
{
|
||||
//45 degrees rotation to orient the symbol to a polar axis to make error calculation easy/efficient
|
||||
public static final Complex DIFFERENTIAL_OFFSET = Complex.fromAngle(Math.PI / 4.0d);
|
||||
private Complex mSymbol = new Complex(0, 0);
|
||||
|
||||
public DQPSKSymbolPhaseErrorCalculator()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPhaseError(Complex symbol)
|
||||
{
|
||||
mSymbol.setValues(symbol);
|
||||
mSymbol.multiply(DIFFERENTIAL_OFFSET);
|
||||
return super.getPhaseError(mSymbol);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 DQPSKTimingErrorDetector
|
||||
{
|
||||
private static final float PI_4_ROTATION = (float)(Math.PI / 4.0);
|
||||
|
||||
/**
|
||||
* Custom early/late symbol timing error detector designed to optimize the sample timing point such that the
|
||||
* current symbol's inphase and quadrature approach the optimal 45 degree (.707) sampling instant.
|
||||
*/
|
||||
public DQPSKTimingErrorDetector()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a symbol sampling time error by comparing the current complex sample against a normal QPSK or
|
||||
* Pi/4 rotated four symbol constellation. The error signal (advance/retard) is determined by the slope
|
||||
* of each of the inphase and quadrature legs as detected by comparing an immediately preceding sample against
|
||||
* the current sample and then calculating the error distance of the sampled symbol from the ideal Pi/4 sampling
|
||||
* location.
|
||||
*
|
||||
* Note: both the preceding and the current symbols MUST be normalized (ie symbol.normalize()) to the unit circle
|
||||
* prior to invoking this method.
|
||||
*
|
||||
* @param precedingSymbol
|
||||
* @param currentSymbol
|
||||
* @return timing error signal used to advance (positive) or retard (negative) the sampling point
|
||||
*/
|
||||
public float getError(Complex precedingSymbol, Complex currentSymbol)
|
||||
{
|
||||
return getError(precedingSymbol.inphase(), currentSymbol.inphase()) +
|
||||
getError(precedingSymbol.quadrature(), currentSymbol.quadrature());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates timing error of current value relative to an ideal 45 degree or Pi/4 radians (.707) value.
|
||||
*
|
||||
* @param early sample that closely precedes the current sampling instant - used to determine slope
|
||||
* @param current sampling value
|
||||
* @return error as the distance of the current value from an ideal value and the sign (advance/retard)
|
||||
* appropriate for the detected slope
|
||||
*/
|
||||
private float getError(float early, float current)
|
||||
{
|
||||
if(early < current) //Ascending slope
|
||||
{
|
||||
if(current > 0.0)
|
||||
{
|
||||
//Positive quadrature hemisphere, ascending slope
|
||||
return PI_4_ROTATION - current;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Negative quadrature hemisphere, ascending slope
|
||||
return -PI_4_ROTATION - current;
|
||||
}
|
||||
}
|
||||
else //Descending slope
|
||||
{
|
||||
if(current > 0.0)
|
||||
{
|
||||
//Positive quadrature hemisphere, descending slope
|
||||
return current - PI_4_ROTATION;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Negative quadrature hemisphere, descending slope
|
||||
return current + PI_4_ROTATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import io.github.dsheirer.instrument.gui.viewer.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.instrument.gui.viewer.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.Message;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoder2Instrumented;
|
||||
import io.github.dsheirer.module.decode.p25.P25_C4FMDecoderInstrumented;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
@@ -44,7 +44,7 @@ public class P25Phase1Pane extends DecoderPane
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private DoubleLineChart mSamplesPerSymbolLineChart;
|
||||
private Broadcaster<ComplexBuffer> mFilteredBufferBroadcaster = new Broadcaster<>();
|
||||
private P25_C4FMDecoder2Instrumented mDecoder = new P25_C4FMDecoder2Instrumented(null);
|
||||
private P25_C4FMDecoderInstrumented mDecoder = new P25_C4FMDecoderInstrumented(null);
|
||||
|
||||
public P25Phase1Pane()
|
||||
{
|
||||
@@ -90,7 +90,7 @@ public class P25Phase1Pane extends DecoderPane
|
||||
getSampleLineChart().setSamplesPerSymbol((int)samplesPerSymbol);
|
||||
}
|
||||
|
||||
private P25_C4FMDecoder2Instrumented getDecoder()
|
||||
private P25_C4FMDecoderInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
@@ -246,9 +246,7 @@ public class DecoderFactory
|
||||
switch(modulation)
|
||||
{
|
||||
case C4FM:
|
||||
modules.add(new FMDemodulatorModule(P25_C4FM_IQ_FILTER));
|
||||
modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
|
||||
modules.add(new P25_C4FMDecoder(aliasList, decodeConfig.getAFCMaximumCorrection()));
|
||||
modules.add(new P25_C4FMDecoder(aliasList));
|
||||
modules.add(new P25DecoderState(aliasList, channelType, P25Decoder.Modulation.C4FM,
|
||||
p25Config.getIgnoreDataCalls()));
|
||||
break;
|
||||
|
||||
@@ -698,8 +698,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
||||
@Override
|
||||
public void syncDetected()
|
||||
{
|
||||
mLog.warn("Phase Inversion SYNC detected - applying correction: " + mCorrection);
|
||||
// mPhaseLockedLoop.correctInversion(mCorrection);
|
||||
mPhaseLockedLoop.correctInversion(mCorrection);
|
||||
|
||||
/* Since we detected a sync pattern, start a message assembler */
|
||||
for(P25MessageAssembler assembler : mAssemblers)
|
||||
|
||||
@@ -1,153 +1,200 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* 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 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.
|
||||
* 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/>
|
||||
* 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.tap.TapGroup;
|
||||
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.psk.DQPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.IFilteredRealBufferListener;
|
||||
import io.github.dsheirer.sample.real.RealBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
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.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
|
||||
public class P25_C4FMDecoder extends P25Decoder implements IComplexBufferListener, Listener<ComplexBuffer>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
|
||||
|
||||
/* Instrumentation Taps */
|
||||
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
|
||||
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
|
||||
private final static Logger mLog = LoggerFactory.getLogger(P25_C4FMDecoder.class);
|
||||
protected static final double SYMBOL_RATE = 4800;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
protected DQPSKDemodulator mQPSKDemodulator;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
protected double mSampleRate;
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter mBasebandFilter;
|
||||
|
||||
private List<TapGroup> mAvailableTaps;
|
||||
private C4FMSymbolFilter mSymbolFilter;
|
||||
private C4FMSlicer mC4FMSlicer;
|
||||
private P25MessageFramer mMessageFramer;
|
||||
|
||||
/**
|
||||
* P25 Phase 1 C4FM Decoder processes real buffers of un-filtered,
|
||||
* demodulated audio and produces decoded P25 Phase 1 Messages.
|
||||
*
|
||||
* Provides Frequency Control to steer external tuner source and incorporates
|
||||
* internal gain control.
|
||||
*
|
||||
* Note: use the P25AudioModule to convert the decoded messages into audio.
|
||||
*
|
||||
* @param aliasList - optional (can be null) list of alias values for network
|
||||
* infrastructure and subscriber identities that will be included in each
|
||||
* decoded message
|
||||
*/
|
||||
public P25_C4FMDecoder(AliasList aliasList, int frequencyCorrectionMaximum )
|
||||
{
|
||||
super( aliasList );
|
||||
|
||||
/* Shape gain and frequency offsets to optimize sample stream */
|
||||
mSymbolFilter = new C4FMSymbolFilter( frequencyCorrectionMaximum );
|
||||
public P25_C4FMDecoder(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
setSampleRate(48000.0);
|
||||
}
|
||||
|
||||
/* Convert samples to symbols */
|
||||
mC4FMSlicer = new C4FMSlicer();
|
||||
mSymbolFilter.setListener( mC4FMSlicer );
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
if(sampleRate <= SYMBOL_RATE * 2)
|
||||
{
|
||||
throw new IllegalArgumentException("Sample rate must be at least twice the symbol rate [4800]");
|
||||
}
|
||||
|
||||
/* Sync pattern detection and message construction */
|
||||
mMessageFramer = new P25MessageFramer( aliasList );
|
||||
mC4FMSlicer.addListener( mMessageFramer );
|
||||
|
||||
/* Process and broadcast messages */
|
||||
mMessageFramer.setListener( getMessageProcessor() );
|
||||
}
|
||||
mSampleRate = sampleRate;
|
||||
mBasebandFilter = new ComplexFIRFilter(getBasebandFilter(), 1.0f);
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mSymbolFilter.dispose();
|
||||
mSymbolFilter = null;
|
||||
|
||||
mMessageFramer.dispose();
|
||||
mMessageFramer = null;
|
||||
mCostasLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
|
||||
|
||||
mC4FMSlicer.dispose();
|
||||
mC4FMSlicer = null;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<RealBuffer> getFilteredRealBufferListener()
|
||||
{
|
||||
return mSymbolFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
|
||||
mQPSKDemodulator = new DQPSKDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
@Override
|
||||
public void start( ScheduledExecutorService executor )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
//Message framer can trigger a symbol-inversion correction to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setDibitListener(mMessageFramer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
ComplexBuffer filtered = filter(complexBuffer);
|
||||
mQPSKDemodulator.receive(filtered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.setFrequencyChangeListener( listener );
|
||||
}
|
||||
}
|
||||
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||
{
|
||||
return mBasebandFilter.filter(complexBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFrequencyChangeListener()
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
mSymbolFilter.setFrequencyChangeListener( null );
|
||||
}
|
||||
}
|
||||
private double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
|
||||
{
|
||||
if( mSymbolFilter != null )
|
||||
{
|
||||
return mSymbolFilter.getFrequencyChangeListener();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private float[] getBasebandFilter()
|
||||
{
|
||||
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||
|
||||
if(filter == null)
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate((int)getSampleRate())
|
||||
.passBandCutoff(5100)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandStart(6500)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
filter = FilterFactory.getTaps(specification);
|
||||
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
|
||||
}
|
||||
if(filter != null)
|
||||
{
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Couldn't design a C4FM symbol filter for sample rate: " + mSampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = 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)
|
||||
{
|
||||
switch(frequencyChangeEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return P25_C4FMDecoder.this;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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.psk.DQPSKDemodulator;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
|
||||
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
|
||||
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
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);
|
||||
protected static final double SYMBOL_RATE = 4800;
|
||||
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
|
||||
protected DQPSKDemodulator mQPSKDemodulator;
|
||||
protected CostasLoop mCostasLoop;
|
||||
protected P25MessageFramer mMessageFramer;
|
||||
protected double mSampleRate;
|
||||
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
|
||||
private ComplexFIRFilter mBasebandFilter;
|
||||
|
||||
public P25_C4FMDecoder2(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
setSampleRate(48000.0);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
mCostasLoop = new CostasLoop(mSampleRate, SYMBOL_RATE);
|
||||
|
||||
mInterpolatingSampleBuffer = new InterpolatingSampleBufferInstrumented((float)(sampleRate / SYMBOL_RATE));
|
||||
mQPSKDemodulator = new DQPSKDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
|
||||
|
||||
//Message framer can trigger a symbol-inversion correction to the PLL when detected
|
||||
mMessageFramer = new P25MessageFramer(getAliasList(), mCostasLoop);
|
||||
mMessageFramer.setListener(getMessageProcessor());
|
||||
mQPSKDemodulator.setDibitListener(mMessageFramer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexBuffer complexBuffer)
|
||||
{
|
||||
ComplexBuffer filtered = filter(complexBuffer);
|
||||
mQPSKDemodulator.receive(filtered);
|
||||
}
|
||||
|
||||
protected ComplexBuffer filter(ComplexBuffer complexBuffer)
|
||||
{
|
||||
return mBasebandFilter.filter(complexBuffer);
|
||||
}
|
||||
|
||||
private double getSampleRate()
|
||||
{
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
private float[] getBasebandFilter()
|
||||
{
|
||||
float[] filter = mBasebandFilters.get(getSampleRate());
|
||||
|
||||
if(filter == null)
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate((int)getSampleRate())
|
||||
.passBandCutoff(5100)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandStart(6500)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
filter = FilterFactory.getTaps(specification);
|
||||
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
|
||||
}
|
||||
if(filter != null)
|
||||
{
|
||||
mBasebandFilters.put(getSampleRate(), filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Couldn't design a C4FM symbol filter for sample rate: " + mSampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
|
||||
mBasebandFilter.dispose();
|
||||
mBasebandFilter = 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)
|
||||
{
|
||||
switch(frequencyChangeEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
mCostasLoop.reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<ComplexBuffer> getComplexBufferListener()
|
||||
{
|
||||
return P25_C4FMDecoder2.this;
|
||||
}
|
||||
|
||||
public Modulation getModulation()
|
||||
{
|
||||
return Modulation.C4FM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mCostasLoop.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(ScheduledExecutorService executor)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import io.github.dsheirer.sample.complex.ComplexBuffer;
|
||||
|
||||
public class P25_C4FMDecoder2Instrumented extends P25_C4FMDecoder2
|
||||
public class P25_C4FMDecoderInstrumented extends P25_C4FMDecoder
|
||||
{
|
||||
private Listener<Double> mPLLPhaseErrorListener;
|
||||
private Listener<Double> mPLLFrequencyListener;
|
||||
@@ -32,7 +32,7 @@ public class P25_C4FMDecoder2Instrumented extends P25_C4FMDecoder2
|
||||
private Listener<ComplexBuffer> mFilteredSymbolListener;
|
||||
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
|
||||
|
||||
public P25_C4FMDecoder2Instrumented(AliasList aliasList)
|
||||
public P25_C4FMDecoderInstrumented(AliasList aliasList)
|
||||
{
|
||||
super(aliasList);
|
||||
}
|
||||
@@ -469,6 +469,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
|
||||
{
|
||||
//Frequency correction requests are the only change requests supported from downstream components
|
||||
case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE:
|
||||
mLog.debug("Got a freq corr change request: " + event.getValue().intValue());
|
||||
setFrequencyCorrection(event.getValue().intValue());
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user