mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -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:
349
src/main/java/io/github/dsheirer/gui/control/DbPowerMeter.java
Normal file
349
src/main/java/io/github/dsheirer/gui/control/DbPowerMeter.java
Normal file
@@ -0,0 +1,349 @@
|
||||
package io.github.dsheirer.gui.control;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JComponent;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Power meter for displaying signal power levels in dB scale with optional peak value and squelch threshold lines.
|
||||
*/
|
||||
public class DbPowerMeter extends JComponent
|
||||
{
|
||||
public static final double DEFAULT_MINIMUM_POWER = -110.0d;
|
||||
public static final double DEFAULT_MAXIMUM_POWER = 0.0d;
|
||||
private static final int BAR_WIDTH = 30;
|
||||
private static final int PADDING = 3;
|
||||
private static final int DOUBLE_PADDING = PADDING * 2;
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||
private static final Color COLOR_BAR = Color.LIGHT_GRAY;
|
||||
private static final Color COLOR_THRESHOLD = Color.BLUE;
|
||||
private static final Color COLOR_PEAK = Color.PINK;
|
||||
|
||||
private double mMinimumValue = DEFAULT_MINIMUM_POWER;
|
||||
private double mMaximumValue = DEFAULT_MAXIMUM_POWER;
|
||||
private double mExtent = mMaximumValue - mMinimumValue;
|
||||
private double mPeak = mMinimumValue;
|
||||
private double mPower = mMinimumValue;
|
||||
private double mSquelchThreshold = mMinimumValue;
|
||||
|
||||
private boolean mPeakVisible = false;
|
||||
private boolean mSquelchThresholdVisible = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*/
|
||||
public DbPowerMeter()
|
||||
{
|
||||
setPreferredSize(new Dimension(80, 100));
|
||||
setBorder(BorderFactory.createTitledBorder("Power (dB)"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets peak, power and squelch to minimums
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mPeak = mMinimumValue;
|
||||
mPower = mMinimumValue;
|
||||
mSquelchThreshold = mMinimumValue;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method for rendering the control
|
||||
*/
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
|
||||
//Draw meter border
|
||||
g.setColor(getForeground());
|
||||
g.drawRect(getInsets().left, getInsets().top, BAR_WIDTH, getHeight() - getInsets().top - getInsets().bottom);
|
||||
|
||||
//Draw filled bar
|
||||
g.setColor(COLOR_BAR);
|
||||
int fillHeight = getHeight() - getInsets().top - getInsets().bottom - DOUBLE_PADDING;
|
||||
int height = (int) (fillHeight * getPowerPercent() - PADDING);
|
||||
int top = (fillHeight - height) + getInsets().top + PADDING;
|
||||
g.fillRect(getInsets().left + PADDING, top, BAR_WIDTH - DOUBLE_PADDING, height);
|
||||
|
||||
//Draw threshold
|
||||
if(isSquelchThresholdVisible())
|
||||
{
|
||||
g.setColor(COLOR_THRESHOLD);
|
||||
drawPercentLine(g, getSquelchThresholdPercent());
|
||||
}
|
||||
|
||||
//Draw peak line
|
||||
if(isPeakVisible())
|
||||
{
|
||||
g.setColor(COLOR_PEAK);
|
||||
drawPercentLine(g, getPeakPercent());
|
||||
}
|
||||
|
||||
//Draw legend/scale
|
||||
g.setColor(getForeground());
|
||||
for(double legendValue: getScaleValues())
|
||||
{
|
||||
drawScaleText(g, legendValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line as a percentage of the value range (extent) using the current graphics color.
|
||||
*/
|
||||
private void drawPercentLine(Graphics g, double percentValue)
|
||||
{
|
||||
int totalHeight = getHeight() - getInsets().top - getInsets().bottom - DOUBLE_PADDING;
|
||||
int height = (int) (totalHeight * percentValue - PADDING);
|
||||
int top = (totalHeight - height) + getInsets().top + PADDING;
|
||||
int left = getInsets().left;
|
||||
g.drawLine(left, top, left + BAR_WIDTH, top);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a tick and text for the value
|
||||
* @param g graphics object
|
||||
* @param value to draw
|
||||
*/
|
||||
private void drawScaleText(Graphics g, double value)
|
||||
{
|
||||
double percent = getPercent(value);
|
||||
int totalHeight = getHeight() - getInsets().top - getInsets().bottom - DOUBLE_PADDING;
|
||||
int height = (int) (totalHeight * percent - PADDING);
|
||||
int top = (totalHeight - height) + getInsets().top + PADDING;
|
||||
int left = getInsets().left + BAR_WIDTH - PADDING;
|
||||
int textOffset = getFontMetrics(getFont()).getMaxAscent() / 2 - 2;
|
||||
String label = DECIMAL_FORMAT.format(value);
|
||||
g.drawString(label, left + DOUBLE_PADDING, top + textOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale values to display, dynamically adapted to the height of the control
|
||||
*/
|
||||
private List<Double> getScaleValues()
|
||||
{
|
||||
double ascent = getFontMetrics(getFont()).getMaxAscent() * 2;
|
||||
double labelCount = getHeight() / ascent;
|
||||
double interval = getExtent() / labelCount;
|
||||
List<Double> values = new ArrayList<>();
|
||||
for(double x = 0; x > getMinimumValue(); x -= interval)
|
||||
{
|
||||
values.add(x);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current power level
|
||||
* @return power level (dB)
|
||||
*/
|
||||
public double getPower()
|
||||
{
|
||||
return mPower;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current power level constrained to the minimum and maximum values for this control.
|
||||
* @param power (dB)
|
||||
*/
|
||||
public void setPower(double power)
|
||||
{
|
||||
if(power > getMaximumValue())
|
||||
{
|
||||
mPower = getMaximumValue();
|
||||
}
|
||||
else if(power < getMinimumValue())
|
||||
{
|
||||
mPower = getMinimumValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
mPower = power;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Squelch threshold value.
|
||||
* @return threshold (dB)
|
||||
*/
|
||||
public double getSquelchThreshold()
|
||||
{
|
||||
return mSquelchThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch thresold value
|
||||
* @param squelchThreshold (dB)
|
||||
*/
|
||||
public void setSquelchThreshold(double squelchThreshold)
|
||||
{
|
||||
mSquelchThreshold = squelchThreshold;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum displayable power level
|
||||
* @return minimum (dB) - defaults to -110 dB
|
||||
*/
|
||||
public double getMinimumValue()
|
||||
{
|
||||
return mMinimumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum displayable power level
|
||||
* @param minimumValue (dB)
|
||||
*/
|
||||
public void setMinimumValue(double minimumValue)
|
||||
{
|
||||
mMinimumValue = minimumValue;
|
||||
updateExtent();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum displayable power level
|
||||
* @return maximum (dB) - defaults to 0 dB
|
||||
*/
|
||||
public double getMaximumValue()
|
||||
{
|
||||
return mMaximumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum displayable power level
|
||||
* @param maximumValue (dB)
|
||||
*/
|
||||
public void setMaximumValue(double maximumValue)
|
||||
{
|
||||
mMaximumValue = maximumValue;
|
||||
updateExtent();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the min/max extent
|
||||
*/
|
||||
private void updateExtent()
|
||||
{
|
||||
mExtent = getMaximumValue() - getMinimumValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extent or range of displayable values.
|
||||
* @return extent (dB)
|
||||
*/
|
||||
private double getExtent()
|
||||
{
|
||||
return mExtent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peak value line
|
||||
* @return peak (dB)
|
||||
*/
|
||||
public double getPeak()
|
||||
{
|
||||
return mPeak;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the peak value line
|
||||
* @param peak (db)
|
||||
*/
|
||||
public void setPeak(double peak)
|
||||
{
|
||||
mPeak = peak;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the peak line is set to visible and the current peak value is within the min/max range.
|
||||
*/
|
||||
public boolean isPeakVisible()
|
||||
{
|
||||
return mPeakVisible & isValidValue(getPeak());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the peak line visibility
|
||||
* @param peakVisible value
|
||||
*/
|
||||
public void setPeakVisible(boolean peakVisible)
|
||||
{
|
||||
mPeakVisible = peakVisible;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch threshold line is set to visible and the current squelch threshold value is
|
||||
* within the min/max displayable value range.
|
||||
* @return true if visible.
|
||||
*/
|
||||
public boolean isSquelchThresholdVisible()
|
||||
{
|
||||
return mSquelchThresholdVisible & isValidValue(getSquelchThreshold());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the squelch threshold line
|
||||
* @param squelchThresholdVisible visibility
|
||||
*/
|
||||
public void setSquelchThresholdVisible(boolean squelchThresholdVisible)
|
||||
{
|
||||
mSquelchThresholdVisible = squelchThresholdVisible;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the percentage of the min/max extent for the value
|
||||
* @param value to calculate
|
||||
* @return percentage of displayable value range
|
||||
*/
|
||||
private double getPercent(double value)
|
||||
{
|
||||
return (value - getMinimumValue()) / getExtent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Power as percentage of displayable range
|
||||
*/
|
||||
private double getPowerPercent()
|
||||
{
|
||||
return getPercent(mPower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Squelch threshold as percentage of displayable range
|
||||
*/
|
||||
private double getSquelchThresholdPercent()
|
||||
{
|
||||
return getPercent(mSquelchThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peak as percentage of displayable range
|
||||
*/
|
||||
private double getPeakPercent()
|
||||
{
|
||||
return getPercent(mPeak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the value is valid, meaning it falls within the min/max range of displayable values.
|
||||
*/
|
||||
private boolean isValidValue(double value)
|
||||
{
|
||||
return getMinimumValue() <= value && value <= getMaximumValue();
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class IntegerFormatter extends TextFormatter<Integer>
|
||||
*/
|
||||
public static class IntegerFilter implements UnaryOperator<Change>
|
||||
{
|
||||
private String DECIMAL_REGEX = "[0-9].*";
|
||||
private String DECIMAL_REGEX = "\\-?[0-9].*";
|
||||
private int mMinimum;
|
||||
private int mMaximum;
|
||||
|
||||
|
||||
@@ -710,6 +710,9 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mptMenu.getItems().add(new AddTalkgroupItem(Protocol.MPT1327));
|
||||
mptMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.MPT1327));
|
||||
|
||||
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
|
||||
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
|
||||
|
||||
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
|
||||
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
|
||||
passportMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.PASSPORT));
|
||||
@@ -723,7 +726,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
lojackMenu.getItems().add(new AddLojackItem());
|
||||
|
||||
mAddIdentifierButton.getItems().addAll(p25Menu, dmrMenu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu,
|
||||
passportMenu, taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
nbfmMenu, passportMenu, taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
}
|
||||
|
||||
return mAddIdentifierButton;
|
||||
|
||||
@@ -253,10 +253,14 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
||||
"Format: 0 - FFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
|
||||
"Format: 1 - 65535"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
|
||||
"Format: 1 - FFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||
"Format: 0 - 65535"));
|
||||
"Format: 0 - 65535"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||
"Format: 0 - FFFF"));
|
||||
"Format: 0 - FFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||
"Format: 0 - FFFFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||
@@ -265,7 +269,7 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
||||
"Format: 0 - FFFFFF"));
|
||||
}
|
||||
|
||||
public class TalkgroupDetail
|
||||
public static class TalkgroupDetail
|
||||
{
|
||||
private Protocol mProtocol;
|
||||
private IntegerFormat mIntegerFormat;
|
||||
|
||||
@@ -83,7 +83,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelConfigurationEditor.class);
|
||||
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private UserPreferences mUserPreferences;
|
||||
protected UserPreferences mUserPreferences;
|
||||
protected EditorModificationListener mEditorModificationListener = new EditorModificationListener();
|
||||
private Button mPlayButton;
|
||||
private TextField mSystemField;
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
package io.github.dsheirer.gui.playlist.channel;
|
||||
|
||||
import io.github.dsheirer.gui.control.DbPowerMeter;
|
||||
import io.github.dsheirer.gui.control.HexFormatter;
|
||||
import io.github.dsheirer.gui.control.IntegerFormatter;
|
||||
import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor;
|
||||
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
|
||||
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
|
||||
@@ -34,15 +37,23 @@ import io.github.dsheirer.module.log.EventLogType;
|
||||
import io.github.dsheirer.module.log.config.EventLogConfiguration;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
@@ -64,12 +75,20 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
private TitledPane mEventLogPane;
|
||||
private TitledPane mRecordPane;
|
||||
private TitledPane mSourcePane;
|
||||
private ToggleSwitch mAudioRecordSwitch;
|
||||
private TextField mTalkgroupField;
|
||||
private TextField mSquelchThresholdField;
|
||||
private TextFormatter<Integer> mTalkgroupTextFormatter;
|
||||
private IntegerFormatter mSquelchTextFormatter = new IntegerFormatter((int)DbPowerMeter.DEFAULT_MINIMUM_POWER,
|
||||
(int)DbPowerMeter.DEFAULT_MAXIMUM_POWER);
|
||||
private ToggleSwitch mBasebandRecordSwitch;
|
||||
private SegmentedButton mBandwidthButton;
|
||||
|
||||
private SourceConfigurationEditor mSourceConfigurationEditor;
|
||||
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
|
||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
||||
private IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
|
||||
private HexFormatter mHexFormatter = new HexFormatter(1, 65535);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -109,20 +128,37 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
mDecoderPane = new TitledPane();
|
||||
mDecoderPane.setText("Decoder: NBFM");
|
||||
mDecoderPane.setExpanded(false);
|
||||
mDecoderPane.setExpanded(true);
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setPadding(new Insets(10,10,10,10));
|
||||
gridPane.setHgap(10);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
Label bandwidthLabel = new Label("Channel Bandwidth");
|
||||
GridPane.setHalignment(bandwidthLabel, HPos.LEFT);
|
||||
GridPane.setHalignment(bandwidthLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(bandwidthLabel, 0, 0);
|
||||
gridPane.getChildren().add(bandwidthLabel);
|
||||
|
||||
GridPane.setConstraints(getBandwidthButton(), 1, 0);
|
||||
gridPane.getChildren().add(getBandwidthButton());
|
||||
|
||||
Label squelchLabel = new Label("Squelch Threshold");
|
||||
GridPane.setHalignment(squelchLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(squelchLabel, 2, 0);
|
||||
gridPane.getChildren().add(squelchLabel);
|
||||
|
||||
GridPane.setConstraints(getSquelchThresholdField(), 3, 0);
|
||||
gridPane.getChildren().add(getSquelchThresholdField());
|
||||
|
||||
Label talkgroupLabel = new Label("Talkgroup To Assign");
|
||||
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(talkgroupLabel, 4, 0);
|
||||
gridPane.getChildren().add(talkgroupLabel);
|
||||
|
||||
GridPane.setConstraints(getTalkgroupField(), 5, 0);
|
||||
gridPane.getChildren().add(getTalkgroupField());
|
||||
|
||||
mDecoderPane.setContent(gridPane);
|
||||
|
||||
//Special handling - the pill button doesn't like to set a selected state if the pane is not expanded,
|
||||
@@ -174,20 +210,12 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
gridPane.setHgap(10);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
GridPane.setConstraints(getAudioRecordSwitch(), 0, 0);
|
||||
gridPane.getChildren().add(getAudioRecordSwitch());
|
||||
|
||||
Label recordAudioLabel = new Label("Audio");
|
||||
GridPane.setHalignment(recordAudioLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(recordAudioLabel, 1, 0);
|
||||
gridPane.getChildren().add(recordAudioLabel);
|
||||
|
||||
GridPane.setConstraints(getBasebandRecordSwitch(), 0, 1);
|
||||
GridPane.setConstraints(getBasebandRecordSwitch(), 0, 0);
|
||||
gridPane.getChildren().add(getBasebandRecordSwitch());
|
||||
|
||||
Label recordBasebandLabel = new Label("Channel (Baseband I&Q)");
|
||||
GridPane.setHalignment(recordBasebandLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(recordBasebandLabel, 1, 1);
|
||||
GridPane.setConstraints(recordBasebandLabel, 1, 0);
|
||||
gridPane.getChildren().add(recordBasebandLabel);
|
||||
|
||||
mRecordPane.setContent(gridPane);
|
||||
@@ -255,24 +283,114 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
|
||||
mBandwidthButton.getToggleGroup().selectedToggleProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
|
||||
//Note: there is a weird timing bug with the segmented button where the toggles are not added to
|
||||
//the toggle group until well after the control is rendered. We attempt to setItem() on the
|
||||
//decode configuration and we're unable to correctly set the bandwidth setting. As a work
|
||||
//around, we'll listen for the toggles to be added and update them here. This normally only
|
||||
//happens when we first instantiate the editor and load an item for editing the first time.
|
||||
mBandwidthButton.getToggleGroup().getToggles().addListener(new ListChangeListener<Toggle>()
|
||||
{
|
||||
@Override
|
||||
public void onChanged(Change<? extends Toggle> c)
|
||||
{
|
||||
//This change event happens when the toggles are added -- we don't need to inspect the change event
|
||||
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
|
||||
{
|
||||
//Capture current modified state so that we can reapply after adjusting control states
|
||||
boolean modified = modifiedProperty().get();
|
||||
|
||||
DecodeConfigNBFM config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
|
||||
DecodeConfigNBFM.Bandwidth bandwidth = config.getBandwidth();
|
||||
if(bandwidth == null)
|
||||
{
|
||||
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
|
||||
}
|
||||
|
||||
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
|
||||
{
|
||||
toggle.setSelected(toggle.getUserData() == bandwidth);
|
||||
}
|
||||
|
||||
modifiedProperty().set(modified);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mBandwidthButton;
|
||||
}
|
||||
|
||||
private ToggleSwitch getAudioRecordSwitch()
|
||||
private TextField getTalkgroupField()
|
||||
{
|
||||
if(mAudioRecordSwitch == null)
|
||||
if(mTalkgroupField == null)
|
||||
{
|
||||
mAudioRecordSwitch = new ToggleSwitch();
|
||||
mAudioRecordSwitch.setDisable(true);
|
||||
mAudioRecordSwitch.setTextAlignment(TextAlignment.RIGHT);
|
||||
mAudioRecordSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
mTalkgroupField = new TextField();
|
||||
mTalkgroupField.setTextFormatter(mTalkgroupTextFormatter);
|
||||
}
|
||||
|
||||
return mAudioRecordSwitch;
|
||||
return mTalkgroupField;
|
||||
}
|
||||
|
||||
private TextField getSquelchThresholdField()
|
||||
{
|
||||
if(mSquelchThresholdField == null)
|
||||
{
|
||||
mSquelchThresholdField = new TextField();
|
||||
mSquelchThresholdField.setTextFormatter(mSquelchTextFormatter);
|
||||
}
|
||||
|
||||
return mSquelchThresholdField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the talkgroup editor's text formatter.
|
||||
* @param value to set in the control.
|
||||
*/
|
||||
private void updateTextFormatter(int value)
|
||||
{
|
||||
if(mTalkgroupTextFormatter != null)
|
||||
{
|
||||
mTalkgroupTextFormatter.valueProperty().removeListener(mTalkgroupValueChangeListener);
|
||||
}
|
||||
|
||||
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(Protocol.NBFM);
|
||||
|
||||
if(format == null)
|
||||
{
|
||||
format = IntegerFormat.DECIMAL;
|
||||
}
|
||||
|
||||
if(format == IntegerFormat.DECIMAL)
|
||||
{
|
||||
mTalkgroupTextFormatter = mDecimalFormatter;
|
||||
getTalkgroupField().setTooltip(new Tooltip("1 - 65,535"));
|
||||
}
|
||||
else
|
||||
{
|
||||
mTalkgroupTextFormatter = mDecimalFormatter;
|
||||
getTalkgroupField().setTooltip(new Tooltip("1 - FFFF"));
|
||||
}
|
||||
|
||||
mTalkgroupTextFormatter.setValue(value);
|
||||
|
||||
getTalkgroupField().setTextFormatter(mTalkgroupTextFormatter);
|
||||
mTalkgroupTextFormatter.valueProperty().addListener(mTalkgroupValueChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change listener to detect when talkgroup value has changed and set modified property to true.
|
||||
*/
|
||||
public class TalkgroupValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ToggleSwitch getBasebandRecordSwitch()
|
||||
{
|
||||
if(mBasebandRecordSwitch == null)
|
||||
@@ -294,20 +412,17 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
getBandwidthButton().setDisable(false);
|
||||
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)config;
|
||||
DecodeConfigNBFM.Bandwidth bandwidth = decodeConfigNBFM.getBandwidth();
|
||||
|
||||
if(bandwidth == null)
|
||||
{
|
||||
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
|
||||
}
|
||||
final DecodeConfigNBFM.Bandwidth bandwidth = (decodeConfigNBFM.getBandwidth() != null ?
|
||||
decodeConfigNBFM.getBandwidth() : DecodeConfigNBFM.Bandwidth.BW_12_5);
|
||||
|
||||
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
|
||||
{
|
||||
toggle.setSelected(toggle.getUserData() == bandwidth);
|
||||
}
|
||||
|
||||
getAudioRecordSwitch().setDisable(false);
|
||||
getAudioRecordSwitch().selectedProperty().set(decodeConfigNBFM.getRecordAudio());
|
||||
updateTextFormatter(decodeConfigNBFM.getTalkgroup());
|
||||
|
||||
mSquelchTextFormatter.setValue(decodeConfigNBFM.getSquelchThreshold());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -318,8 +433,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
toggle.setSelected(false);
|
||||
}
|
||||
|
||||
getAudioRecordSwitch().setDisable(true);
|
||||
getAudioRecordSwitch().selectedProperty().set(false);
|
||||
updateTextFormatter(0);
|
||||
getTalkgroupField().setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +461,15 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
|
||||
config.setBandwidth(bandwidth);
|
||||
|
||||
config.setRecordAudio(getAudioRecordSwitch().isSelected());
|
||||
Integer talkgroup = mTalkgroupTextFormatter.getValue();
|
||||
|
||||
if(talkgroup == null)
|
||||
{
|
||||
talkgroup = 1;
|
||||
}
|
||||
|
||||
config.setTalkgroup(talkgroup);
|
||||
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
|
||||
getItem().setDecodeConfiguration(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -413,15 +413,18 @@ public class RadioReferenceDecoder
|
||||
case "DMR":
|
||||
return Protocol.DMR;
|
||||
case "LTR":
|
||||
if(flavor.getName().contentEquals("Standard") || flavor.getName().contentEquals("Net"))
|
||||
if(flavor != null)
|
||||
{
|
||||
return Protocol.LTR;
|
||||
if(flavor.getName().contentEquals("Standard") || flavor.getName().contentEquals("Net"))
|
||||
{
|
||||
return Protocol.LTR;
|
||||
}
|
||||
else if(flavor.getName().contentEquals("Passport"))
|
||||
{
|
||||
return Protocol.PASSPORT;
|
||||
}
|
||||
}
|
||||
else if(flavor.getName().contentEquals("Passport"))
|
||||
{
|
||||
return Protocol.PASSPORT;
|
||||
}
|
||||
break;
|
||||
return Protocol.LTR;
|
||||
case "MPT-1327":
|
||||
return Protocol.MPT1327;
|
||||
case "Project 25":
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package io.github.dsheirer.gui.power;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
|
||||
import io.github.dsheirer.gui.control.DbPowerMeter;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.swing.IconFontSwing;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Display for channel power and squelch details
|
||||
*/
|
||||
public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
|
||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||
private static final String NOT_AVAILABLE = "Not Available";
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private ProcessingChain mProcessingChain;
|
||||
private DbPowerMeter mPowerMeter = new DbPowerMeter();
|
||||
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
|
||||
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||
private JLabel mPowerLabel;
|
||||
private JLabel mPeakLabel;
|
||||
private JLabel mSquelchLabel;
|
||||
private JLabel mSquelchValueLabel;
|
||||
private JButton mSquelchUpButton;
|
||||
private JButton mSquelchDownButton;
|
||||
private double mSquelchThreshold;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*/
|
||||
public ChannelPowerPanel(PlaylistManager playlistManager)
|
||||
{
|
||||
mPlaylistManager = playlistManager;
|
||||
|
||||
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
|
||||
mPowerMeter.setPeakVisible(true);
|
||||
mPowerMeter.setSquelchThresholdVisible(true);
|
||||
add(mPowerMeter);
|
||||
|
||||
JPanel valuePanel = new JPanel();
|
||||
valuePanel.setLayout(new MigLayout("", "[right][left][][]", ""));
|
||||
|
||||
mPeakLabel = new JLabel("0");
|
||||
valuePanel.add(new JLabel("Peak:"));
|
||||
valuePanel.add(mPeakLabel, "wrap");
|
||||
|
||||
mPowerLabel = new JLabel("0");
|
||||
valuePanel.add(new JLabel("Power:"));
|
||||
valuePanel.add(mPowerLabel, "wrap");
|
||||
|
||||
mSquelchLabel = new JLabel("Squelch:");
|
||||
mSquelchLabel.setEnabled(false);
|
||||
valuePanel.add(mSquelchLabel);
|
||||
mSquelchValueLabel = new JLabel(NOT_AVAILABLE);
|
||||
mSquelchValueLabel.setEnabled(false);
|
||||
valuePanel.add(mSquelchValueLabel, "wrap");
|
||||
|
||||
IconFontSwing.register(FontAwesome.getIconFont());
|
||||
Icon iconUp = IconFontSwing.buildIcon(FontAwesome.ANGLE_UP, 12);
|
||||
mSquelchUpButton = new JButton(iconUp);
|
||||
mSquelchUpButton.setEnabled(false);
|
||||
mSquelchUpButton.addActionListener(e -> broadcast(SourceEvent.requestSquelchThreshold(null, mSquelchThreshold + 1)));
|
||||
valuePanel.add(mSquelchUpButton);
|
||||
|
||||
Icon iconDown = IconFontSwing.buildIcon(FontAwesome.ANGLE_DOWN, 12);
|
||||
mSquelchDownButton = new JButton(iconDown);
|
||||
mSquelchDownButton.setEnabled(false);
|
||||
mSquelchDownButton.addActionListener(e -> broadcast(SourceEvent.requestSquelchThreshold(null, mSquelchThreshold - 1)));
|
||||
valuePanel.add(mSquelchDownButton);
|
||||
|
||||
add(valuePanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel's decode configuration with a new squelch threshold value
|
||||
*/
|
||||
private void setConfigSquelchThreshold(int threshold)
|
||||
{
|
||||
if(mProcessingChain != null)
|
||||
{
|
||||
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
|
||||
|
||||
if(channel != null && channel.getDecodeConfiguration() instanceof ISquelchConfiguration)
|
||||
{
|
||||
ISquelchConfiguration configuration = (ISquelchConfiguration)channel.getDecodeConfiguration();
|
||||
configuration.setSquelchThreshold(threshold);
|
||||
mPlaylistManager.schedulePlaylistSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mProcessingChain != null)
|
||||
{
|
||||
mProcessingChain.broadcast(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets controls when changing processing chain source. Note: this must be called on the Swing
|
||||
* dispatch thread because it directly invokes swing components.
|
||||
*/
|
||||
private void reset()
|
||||
{
|
||||
mPeakMonitor.reset();
|
||||
mPowerMeter.reset();
|
||||
|
||||
mPeakLabel.setText("0");
|
||||
mPowerLabel.setText("0");
|
||||
|
||||
mSquelchLabel.setEnabled(false);
|
||||
mSquelchValueLabel.setText("Not Available");
|
||||
mSquelchValueLabel.setEnabled(false);
|
||||
mSquelchUpButton.setEnabled(false);
|
||||
mSquelchDownButton.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive notifications of request to provide display of processing chain details.
|
||||
*/
|
||||
@Override
|
||||
public void receive(ProcessingChain processingChain)
|
||||
{
|
||||
if(mProcessingChain != null)
|
||||
{
|
||||
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
||||
}
|
||||
|
||||
//Invoking reset - we're on the Swing dispatch thread here
|
||||
reset();
|
||||
|
||||
mProcessingChain = processingChain;
|
||||
|
||||
if(mProcessingChain != null)
|
||||
{
|
||||
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
|
||||
}
|
||||
|
||||
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor for source event stream to capture power level and squelch related source events.
|
||||
*/
|
||||
private class SourceEventProcessor implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_CHANNEL_POWER ->
|
||||
{
|
||||
final double power = sourceEvent.getValue().doubleValue();
|
||||
final double peak = mPeakMonitor.process(power);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mPowerMeter.setPower(power);
|
||||
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
|
||||
|
||||
mPowerMeter.setPeak(peak);
|
||||
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
case NOTIFICATION_SQUELCH_THRESHOLD ->
|
||||
{
|
||||
final double threshold = sourceEvent.getValue().doubleValue();
|
||||
mSquelchThreshold = threshold;
|
||||
setConfigSquelchThreshold((int)threshold);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mPowerMeter.setSquelchThreshold(threshold);
|
||||
mSquelchLabel.setEnabled(true);
|
||||
mSquelchValueLabel.setEnabled(true);
|
||||
mSquelchValueLabel.setText(DECIMAL_FORMAT.format(threshold));
|
||||
mSquelchDownButton.setEnabled(true);
|
||||
mSquelchUpButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/main/java/io/github/dsheirer/gui/power/PeakMonitor.java
Normal file
51
src/main/java/io/github/dsheirer/gui/power/PeakMonitor.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package io.github.dsheirer.gui.power;
|
||||
|
||||
/**
|
||||
* Peak value monitor
|
||||
*/
|
||||
public class PeakMonitor
|
||||
{
|
||||
private double mInitialValue;
|
||||
private double mPeak;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param initialValue for the peak value
|
||||
*/
|
||||
public PeakMonitor(double initialValue)
|
||||
{
|
||||
mInitialValue = initialValue;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current peak value
|
||||
*/
|
||||
public double getPeak()
|
||||
{
|
||||
return mPeak;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the value and returns the current peak value
|
||||
* @param value to process
|
||||
* @return current peak value
|
||||
*/
|
||||
public double process(double value)
|
||||
{
|
||||
if(value > mPeak)
|
||||
{
|
||||
mPeak = value;
|
||||
}
|
||||
|
||||
return getPeak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the peak value to the initial value
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mPeak = mInitialValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user