mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#36 NBFM squelch control. Updates NBFM decoder to incorporate squelch control. Squelch is a configuration item and also available to adjust at runtime with new 'Channel' tab in Now Playing window. All decoders were updated to produce continuous channel power (dB) measurements that are displayed in the Channel tab's new Power Meter. (#1100)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
279
src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java
Normal file
279
src/main/java/io/github/dsheirer/dsp/squelch/PowerSquelch.java
Normal file
@@ -0,0 +1,279 @@
|
||||
package io.github.dsheirer.dsp.squelch;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Power squelch
|
||||
*
|
||||
* Modeled after gnuradio complex power squelch:
|
||||
* https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/pwr_squelch_cc_impl.cc
|
||||
*/
|
||||
public class PowerSquelch implements Listener<SourceEvent>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(PowerSquelch.class);
|
||||
private State mState = State.MUTE;
|
||||
private SinglePoleIirFilter mFilter;
|
||||
private double mPower = 0.0f;
|
||||
private double mSquelchThreshold;
|
||||
private int mRampThreshold;
|
||||
private int mRampCount;
|
||||
private boolean mSquelchChanged = false;
|
||||
private boolean mStateChange = false;
|
||||
private int mPowerLevelBroadcastCount = 0;
|
||||
private int mPowerLevelBroadcastThreshold;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* Testing against a 12.5 kHz analog FM modulated signal, the following parameters provided a
|
||||
* good responsiveness. A threshold of -80.0 dB seemed to trigger significant flapping during un-squelching.
|
||||
* - alpha: 0.0001
|
||||
* - threshold: 78.0 dB
|
||||
* - ramp: 4 (samples)
|
||||
*
|
||||
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||
* the slower the squelch response.
|
||||
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||
* @param ramp count of samples before transition between mute and unmute. Setting this value to zero
|
||||
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
||||
*/
|
||||
public PowerSquelch(double alpha, double squelchThreshold, int ramp)
|
||||
{
|
||||
mFilter = new SinglePoleIirFilter(alpha);
|
||||
setSquelchThreshold(squelchThreshold);
|
||||
mRampThreshold = ramp;
|
||||
mPowerLevelBroadcastThreshold = 25000; //Based on a default sample rate of 50 kHz
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate to effect the frequency of power level notifications where the notifications are
|
||||
* sent twice a second.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mPowerLevelBroadcastThreshold = sampleRate / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Squelch threshold value
|
||||
* @return value in decibels
|
||||
*/
|
||||
public double getSquelchThreshold()
|
||||
{
|
||||
return 10.0 * Math.log10(mSquelchThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold
|
||||
* @param squelchThreshold in decibels
|
||||
*/
|
||||
public void setSquelchThreshold(double squelchThreshold)
|
||||
{
|
||||
mSquelchThreshold = Math.pow(10.0, squelchThreshold / 10.0);
|
||||
broadcast(SourceEvent.squelchThreshold(null, squelchThreshold));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||
* threshold value.
|
||||
* @param inphase complex sample component
|
||||
* @param quadrature complex sample component
|
||||
*/
|
||||
public void process(double inphase, double quadrature)
|
||||
{
|
||||
mPower = mFilter.filter(inphase * inphase + quadrature * quadrature);
|
||||
|
||||
mPowerLevelBroadcastCount++;
|
||||
if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0)
|
||||
{
|
||||
mPowerLevelBroadcastCount = 0;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPower)));
|
||||
}
|
||||
|
||||
mStateChange = false;
|
||||
|
||||
switch(mState)
|
||||
{
|
||||
case MUTE -> {
|
||||
if(!mute())
|
||||
{
|
||||
if(mRampThreshold > 0)
|
||||
{
|
||||
mState = State.ATTACK;
|
||||
mRampCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
mState = State.UNMUTE;
|
||||
mStateChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
case ATTACK -> {
|
||||
if(mRampCount >= mRampThreshold)
|
||||
{
|
||||
mState = State.UNMUTE;
|
||||
mStateChange = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mRampCount++;
|
||||
}
|
||||
}
|
||||
case DECAY -> {
|
||||
if(mRampCount <= 0)
|
||||
{
|
||||
mState = State.MUTE;
|
||||
mStateChange = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mRampCount--;
|
||||
}
|
||||
}
|
||||
case UNMUTE -> {
|
||||
if(mute())
|
||||
{
|
||||
if(mRampThreshold > 0)
|
||||
{
|
||||
mState = State.DECAY;
|
||||
mRampCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
mState = State.MUTE;
|
||||
mStateChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Signal that the squelch state has changed if we modified the squelch state above.
|
||||
setSquelchChanged(mStateChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current state is muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mState == State.MUTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current state is unmuted
|
||||
*/
|
||||
public boolean isUnmuted()
|
||||
{
|
||||
return mState == State.UNMUTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current state is fading in (attach) or fading out (decay)
|
||||
*/
|
||||
public boolean isFading()
|
||||
{
|
||||
return isAttack() || isDecay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the squelch is in a ramp up attack state
|
||||
*/
|
||||
public boolean isAttack()
|
||||
{
|
||||
return mState == State.ATTACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the squelch is in a ramp down decay state
|
||||
*/
|
||||
public boolean isDecay()
|
||||
{
|
||||
return mState == State.DECAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current power level
|
||||
* @return current power level in dB
|
||||
*/
|
||||
public float getPower()
|
||||
{
|
||||
return (float)(10.0 * Math.log10(mPower));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current power level is below the threshold, indicating that the state should be muted.
|
||||
*/
|
||||
private boolean mute()
|
||||
{
|
||||
return mPower < mSquelchThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed (muted > unmuted, or vice-versa)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag
|
||||
*/
|
||||
public void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive power level notifications and squelch threshold requests
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to an optional register listener
|
||||
*/
|
||||
private void broadcast(SourceEvent event)
|
||||
{
|
||||
if(mSourceEventListener != null)
|
||||
{
|
||||
mSourceEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method to receive requests for squelch threshold change
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
||||
{
|
||||
setSquelchThreshold(sourceEvent.getValue().doubleValue());
|
||||
}
|
||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
||||
{
|
||||
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State of squelch processing
|
||||
*/
|
||||
private enum State
|
||||
{
|
||||
ATTACK,
|
||||
DECAY,
|
||||
MUTE,
|
||||
UNMUTE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user