330 - updates P25 C4FM decoder to use the new DQPSK demodulator and

removes the previous FM demodulator based decoder.
This commit is contained in:
Denny
2018-03-17 09:45:23 -04:00
parent 646bd78037
commit 8868c1f954
10 changed files with 181 additions and 462 deletions

View File

@@ -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.
*

View File

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

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}

View File

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

View File

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

View File

@@ -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()
{
}
}

View File

@@ -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()
{
}
}

View File

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

View File

@@ -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;
}