mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
280 lines
7.8 KiB
Java
280 lines
7.8 KiB
Java
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;
|
|
}
|
|
}
|