mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
484 lines
13 KiB
Java
484 lines
13 KiB
Java
/*******************************************************************************
|
|
* sdrtrunk
|
|
* Copyright (C) 2014-2017 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 spectrum;
|
|
|
|
import dsp.filter.smoothing.GaussianSmoothingFilter;
|
|
import dsp.filter.smoothing.NoSmoothingFilter;
|
|
import dsp.filter.smoothing.RectangularSmoothingFilter;
|
|
import dsp.filter.smoothing.SmoothingFilter;
|
|
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
|
|
import dsp.filter.smoothing.TriangularSmoothingFilter;
|
|
import org.apache.commons.lang3.Validate;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import settings.ColorSetting;
|
|
import settings.ColorSetting.ColorSettingName;
|
|
import settings.Setting;
|
|
import settings.SettingChangeListener;
|
|
import settings.SettingsManager;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.geom.GeneralPath;
|
|
import java.awt.geom.Line2D;
|
|
import java.util.Arrays;
|
|
|
|
public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster
|
|
{
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class);
|
|
|
|
private static final RenderingHints RENDERING_HINTS =
|
|
new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
|
static
|
|
{
|
|
RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
|
}
|
|
|
|
private Color mColorSpectrumBackground;
|
|
private Color mColorSpectrumGradientTop;
|
|
private Color mColorSpectrumGradientBottom;
|
|
private Color mColorSpectrumLine;
|
|
|
|
//Defines the panel inset along the bottom for frequency display
|
|
private float mSpectrumInset = 20.0f;
|
|
|
|
//Current DFT output bins in dB
|
|
private float[] mDisplayFFTBins = new float[1];
|
|
|
|
//Averaging across multiple DFT result sets
|
|
private int mAveraging = 4;
|
|
|
|
//Smoothing across bins in the same DFT result set
|
|
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
|
|
|
|
//Reference dB value set according to the source sample size
|
|
private float mDBScale;
|
|
|
|
private int mZoom = 0;
|
|
private int mZoomWindowOffset = 0;
|
|
;
|
|
|
|
private SettingsManager mSettingsManager;
|
|
|
|
public SpectrumPanel(SettingsManager settingsManager)
|
|
{
|
|
mSettingsManager = settingsManager;
|
|
|
|
if(mSettingsManager != null)
|
|
{
|
|
mSettingsManager.addListener(this);
|
|
}
|
|
|
|
setSampleSize(16.0);
|
|
|
|
mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT);
|
|
|
|
getColors();
|
|
|
|
setAveraging(mAveraging);
|
|
}
|
|
|
|
public void dispose()
|
|
{
|
|
if(mSettingsManager != null)
|
|
{
|
|
mSettingsManager.removeListener(this);
|
|
}
|
|
|
|
mSettingsManager = null;
|
|
}
|
|
|
|
/**
|
|
* DFTResultsListener interface for receiving the processed data
|
|
* to display
|
|
*/
|
|
public void receive(float[] currentFFTBins)
|
|
{
|
|
//Prevent arrays of NaN values from being rendered. The first few
|
|
//DFT result sets on startup will contain NaN values
|
|
if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0]))
|
|
{
|
|
currentFFTBins = new float[currentFFTBins.length];
|
|
}
|
|
|
|
//Construct and/or resize our DFT results variables
|
|
if(mDisplayFFTBins == null ||
|
|
mDisplayFFTBins.length != currentFFTBins.length)
|
|
{
|
|
mDisplayFFTBins = currentFFTBins;
|
|
}
|
|
|
|
//Apply smoothing across the bins of the DFT results
|
|
float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins);
|
|
|
|
//Apply averaging over multiple DFT output frames
|
|
if(mAveraging > 1)
|
|
{
|
|
float gain = 1.0f / (float)mAveraging;
|
|
|
|
for(int x = 0; x < mDisplayFFTBins.length; x++)
|
|
{
|
|
mDisplayFFTBins[x] +=
|
|
(smoothedBins[x] - mDisplayFFTBins[x]) * gain;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mDisplayFFTBins = smoothedBins;
|
|
}
|
|
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void paintComponent(Graphics g)
|
|
{
|
|
super.paintComponent(g);
|
|
|
|
Graphics2D graphics = (Graphics2D)g;
|
|
|
|
graphics.setBackground(mColorSpectrumBackground);
|
|
|
|
graphics.setRenderingHints(RENDERING_HINTS);
|
|
|
|
drawSpectrum(graphics);
|
|
}
|
|
|
|
/**
|
|
* Draws the current fft spectrum with a line and a gradient fill.
|
|
*/
|
|
private void drawSpectrum(Graphics2D graphics)
|
|
{
|
|
Dimension size = getSize();
|
|
|
|
//Draw the background
|
|
Rectangle background = new Rectangle(0, 0, size.width, size.height);
|
|
graphics.setColor(mColorSpectrumBackground);
|
|
graphics.draw(background);
|
|
graphics.fill(background);
|
|
|
|
//Define the gradient
|
|
GradientPaint gradient = new GradientPaint(0,
|
|
(getSize().height - mSpectrumInset) / 2,
|
|
mColorSpectrumGradientTop,
|
|
0,
|
|
getSize().height,
|
|
mColorSpectrumGradientBottom);
|
|
|
|
graphics.setBackground(mColorSpectrumBackground);
|
|
|
|
GeneralPath spectrumShape = new GeneralPath();
|
|
|
|
//Start at the lower right inset point
|
|
spectrumShape.moveTo(size.getWidth(),
|
|
size.getHeight() - mSpectrumInset);
|
|
|
|
//Draw to the lower left
|
|
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
|
|
|
float[] bins = getBins();
|
|
|
|
//If we have FFT data to display ...
|
|
if(bins != null)
|
|
{
|
|
float insideHeight = size.height - mSpectrumInset;
|
|
|
|
float scalor = insideHeight / -mDBScale;
|
|
|
|
/* Calculate based on bin size - 1, since bin 0 is rendered at zero
|
|
* and the last bin is rendered at the width */
|
|
float binSize = (float)size.width / ((float)(bins.length));
|
|
|
|
for(int x = 0; x < bins.length; x++)
|
|
{
|
|
float height;
|
|
|
|
height = bins[x] * scalor;
|
|
|
|
if(height > insideHeight)
|
|
{
|
|
height = insideHeight;
|
|
}
|
|
|
|
if(height < 0)
|
|
{
|
|
height = 0;
|
|
}
|
|
|
|
float xAxis = (float)x * binSize;
|
|
|
|
spectrumShape.lineTo(xAxis, height);
|
|
}
|
|
}
|
|
//Otherwise show an empty spectrum
|
|
else
|
|
{
|
|
//Draw Left Size
|
|
graphics.setPaint(gradient);
|
|
spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset);
|
|
//Draw Middle
|
|
spectrumShape.lineTo(size.getWidth(),
|
|
size.getHeight() - mSpectrumInset);
|
|
}
|
|
|
|
//Draw Right Side
|
|
spectrumShape.lineTo(size.getWidth(),
|
|
size.getHeight() - mSpectrumInset);
|
|
|
|
graphics.setPaint(gradient);
|
|
graphics.draw(spectrumShape);
|
|
graphics.fill(spectrumShape);
|
|
|
|
graphics.setPaint(mColorSpectrumLine);
|
|
|
|
//Draw the bottom line under the spectrum
|
|
graphics.draw(new Line2D.Float(0,
|
|
size.height - mSpectrumInset,
|
|
size.width,
|
|
size.height - mSpectrumInset));
|
|
}
|
|
|
|
/**
|
|
* Sets the current zoom level
|
|
*
|
|
* 0 No Zoom
|
|
* 1 2x Zoom
|
|
* 2 4x Zoom
|
|
* 3 8x Zoom
|
|
* 4 16x Zoom
|
|
* 5 32x Zoom
|
|
* 6 64x Zoom
|
|
*
|
|
* @param zoom level, 0 - 6.
|
|
*/
|
|
public void setZoom(int zoom)
|
|
{
|
|
Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom);
|
|
|
|
mZoom = zoom;
|
|
}
|
|
|
|
/**
|
|
* Sets the offset to define which subset of zoomed bins to display
|
|
*
|
|
* @param offset
|
|
*/
|
|
public void setZoomWindowOffset(int offset)
|
|
{
|
|
mZoomWindowOffset = offset;
|
|
}
|
|
|
|
/**
|
|
* Sets the source sample size in bits which defines the lowest dB value to
|
|
* display in the spectrum
|
|
*
|
|
* @param sampleSize in bits
|
|
*/
|
|
public void setSampleSize(double sampleSize)
|
|
{
|
|
Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0);
|
|
|
|
mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1)));
|
|
}
|
|
|
|
/**
|
|
* Defines the number of DFT result sets to average across
|
|
*/
|
|
public void setAveraging(int size)
|
|
{
|
|
mAveraging = size;
|
|
}
|
|
|
|
/**
|
|
* DFT result sets averaging value
|
|
*/
|
|
public int getAveraging()
|
|
{
|
|
return mAveraging;
|
|
}
|
|
|
|
/**
|
|
* Clears the spectral display
|
|
*/
|
|
public void clearSpectrum()
|
|
{
|
|
mDisplayFFTBins = null;
|
|
|
|
repaint();
|
|
}
|
|
|
|
private void getColors()
|
|
{
|
|
mColorSpectrumBackground =
|
|
getColor(ColorSettingName.SPECTRUM_BACKGROUND);
|
|
|
|
mColorSpectrumGradientBottom =
|
|
getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM);
|
|
|
|
mColorSpectrumGradientTop =
|
|
getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP);
|
|
|
|
mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current setting for the named color setting
|
|
*/
|
|
private Color getColor(ColorSettingName name)
|
|
{
|
|
ColorSetting setting = mSettingsManager.getColorSetting(name);
|
|
|
|
return setting.getColor();
|
|
}
|
|
|
|
/**
|
|
* Listener interface to receive setting change notifications
|
|
*/
|
|
@Override
|
|
public void settingChanged(Setting setting)
|
|
{
|
|
if(setting instanceof ColorSetting)
|
|
{
|
|
ColorSetting colorSetting = (ColorSetting)setting;
|
|
|
|
switch(((ColorSetting)setting).getColorSettingName())
|
|
{
|
|
case SPECTRUM_BACKGROUND:
|
|
mColorSpectrumBackground = colorSetting.getColor();
|
|
break;
|
|
case SPECTRUM_GRADIENT_BOTTOM:
|
|
mColorSpectrumGradientBottom = colorSetting.getColor();
|
|
break;
|
|
case SPECTRUM_GRADIENT_TOP:
|
|
mColorSpectrumGradientTop = colorSetting.getColor();
|
|
break;
|
|
case SPECTRUM_LINE:
|
|
mColorSpectrumLine = colorSetting.getColor();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private int getZoomMultiplier()
|
|
{
|
|
return (int)Math.pow(2.0, mZoom);
|
|
}
|
|
|
|
/**
|
|
* Returns the DFT result bins, or a zoomed and offset version of the bins
|
|
* when the display is zoomed.
|
|
*/
|
|
private float[] getBins()
|
|
{
|
|
if(mZoom == 0)
|
|
{
|
|
return mDisplayFFTBins;
|
|
}
|
|
else
|
|
{
|
|
int length = mDisplayFFTBins.length / getZoomMultiplier();
|
|
|
|
int offset = mZoomWindowOffset;
|
|
|
|
if((offset + length) >= mDisplayFFTBins.length)
|
|
{
|
|
offset = mDisplayFFTBins.length - length;
|
|
}
|
|
|
|
if(offset < 0)
|
|
{
|
|
offset = 0;
|
|
}
|
|
|
|
return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void settingDeleted(Setting setting)
|
|
{ /* not implemented */ }
|
|
|
|
/**
|
|
* Returns the current inter-bin smoothing setting
|
|
*/
|
|
@Override
|
|
public int getSmoothing()
|
|
{
|
|
return mSmoothingFilter.getPointSize();
|
|
}
|
|
|
|
/**
|
|
* Sets the bin smoothing value to define how wide the smoothing window is
|
|
* when averaging across DFT bins
|
|
*/
|
|
@Override
|
|
public void setSmoothing(int smoothing)
|
|
{
|
|
mSmoothingFilter.setPointSize(smoothing);
|
|
}
|
|
|
|
/**
|
|
* Returns the current smoothing filter type
|
|
*/
|
|
@Override
|
|
public SmoothingType getSmoothingType()
|
|
{
|
|
return mSmoothingFilter.getSmoothingType();
|
|
}
|
|
|
|
/**
|
|
* Sets the smoothing filter type
|
|
*/
|
|
@Override
|
|
public void setSmoothingType(SmoothingType type)
|
|
{
|
|
if(mSmoothingFilter.getSmoothingType() != type)
|
|
{
|
|
int pointSize = getSmoothing();
|
|
|
|
synchronized(mSmoothingFilter)
|
|
{
|
|
switch(type)
|
|
{
|
|
case GAUSSIAN:
|
|
mSmoothingFilter = new GaussianSmoothingFilter();
|
|
break;
|
|
case RECTANGLE:
|
|
mSmoothingFilter = new RectangularSmoothingFilter();
|
|
break;
|
|
case TRIANGLE:
|
|
mSmoothingFilter = new TriangularSmoothingFilter();
|
|
break;
|
|
case NONE:
|
|
default:
|
|
mSmoothingFilter = new NoSmoothingFilter();
|
|
break;
|
|
}
|
|
}
|
|
|
|
setSmoothing(pointSize);
|
|
}
|
|
}
|
|
}
|