Spectrum/Waterfall display zoom feature - work in progress (issue #33)

Implements zoom in/out by mouse scroll wheel and by right-click context
menu.

Still have to add ability to drag spectrum left/right when zoomed in.
This commit is contained in:
denny
2015-11-11 20:07:37 -05:00
parent c178e99a5d
commit 7455077cfa
12 changed files with 559 additions and 280 deletions

View File

@@ -38,6 +38,7 @@ import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import net.miginfocom.swing.MigLayout;

View File

@@ -35,7 +35,7 @@ import source.tuner.frequency.FrequencyChangeEvent.Attribute;
import spectrum.converter.DFTResultsConverter;
import spectrum.converter.RealDecibelConverter;
import spectrum.menu.AveragingItem;
import spectrum.menu.FFTWidthItem;
import spectrum.menu.DFTSizeItem;
import spectrum.menu.FFTWindowTypeItem;
import spectrum.menu.FrameRateItem;
import spectrum.menu.SmoothingItem;
@@ -377,9 +377,9 @@ public class ChannelSpectrumPanel extends JPanel
JMenu fftWidthMenu = new JMenu( "FFT Width" );
displayMenu.add( fftWidthMenu );
for( FFTWidth width: FFTWidth.values() )
for( DFTSize width: DFTSize.values() )
{
fftWidthMenu.add( new FFTWidthItem( mDFTProcessor, width ) );
fftWidthMenu.add( new DFTSizeItem( mDFTProcessor, width ) );
}
/**

View File

@@ -45,7 +45,9 @@ import dsp.filter.Window.WindowType;
* Processes both complex samples or float samples and dispatches a float array
* of DFT results, using configurable fft size and output dispatch timelines.
*/
public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeListener
public class DFTProcessor implements Listener<ComplexBuffer>,
FrequencyChangeListener,
IDFTWidthChangeProcessor
{
private final static Logger mLog =
LoggerFactory.getLogger( DFTProcessor.class );
@@ -59,8 +61,8 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
private ScheduledExecutorService mScheduler = Executors
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
private FFTWidth mFFTWidth = FFTWidth.FFT04096;
private FFTWidth mNewFFTWidth = FFTWidth.FFT04096;
private DFTSize mDFTSize = DFTSize.FFT04096;
private DFTSize mNewDFTSize = DFTSize.FFT04096;
private double[] mWindow;
@@ -68,7 +70,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
* with minimal bin leakage/smearing */
private WindowType mWindowType = Window.WindowType.HANNING;
private FloatFFT_1D mFFT = new FloatFFT_1D( mFFTWidth.getWidth() );
private FloatFFT_1D mFFT = new FloatFFT_1D( mDFTSize.getSize() );
private int mFrameRate;
private int mSampleRate;
@@ -112,12 +114,12 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
if( mSampleType == SampleType.COMPLEX )
{
mWindow = Window.getWindow( mWindowType,
mFFTWidth.getWidth() * 2 );
mDFTSize.getSize() * 2 );
}
else
{
mWindow = Window.getWindow( mWindowType,
mFFTWidth.getWidth() );
mDFTSize.getSize() );
}
}
@@ -140,14 +142,14 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
* Queues an FFT size change request. The scheduled executor will apply
* the change when it runs.
*/
public void setFFTSize( FFTWidth width )
public void setDFTSize( DFTSize size )
{
mNewFFTWidth = width;
mNewDFTSize = size;
}
public FFTWidth getFFTWidth()
public DFTSize getDFTSize()
{
return mFFTWidth;
return mDFTSize;
}
public int getFrameRate()
@@ -444,9 +446,9 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
*/
private void checkFFTSize()
{
if( mNewFFTWidth.getWidth() != mFFTWidth.getWidth() )
if( mNewDFTSize.getSize() != mDFTSize.getSize() )
{
mFFTWidth = mNewFFTWidth;
mDFTSize = mNewDFTSize;
calculateConsumptionRate();
@@ -454,14 +456,14 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
if( mSampleType == SampleType.COMPLEX )
{
mPreviousFrame = new float[ mFFTWidth.getWidth() * 2 ];
mPreviousFrame = new float[ mDFTSize.getSize() * 2 ];
}
else
{
mPreviousFrame = new float[ mFFTWidth.getWidth() ];
mPreviousFrame = new float[ mDFTSize.getSize() ];
}
mFFT = new FloatFFT_1D( mFFTWidth.getWidth() );
mFFT = new FloatFFT_1D( mDFTSize.getSize() );
}
}
@@ -497,7 +499,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>, FrequencyChangeLis
( mSampleType == SampleType.COMPLEX ? 2.0f : 1.0f );
mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ?
mFFTWidth.getWidth() * 2 :
mFFTWidth.getWidth() );
mDFTSize.getSize() * 2 :
mDFTSize.getSize() );
}
}

View File

@@ -17,7 +17,7 @@
******************************************************************************/
package spectrum;
public enum FFTWidth
public enum DFTSize
{
FFT00512( 512 ),
FFT01024( 1024 ),
@@ -29,12 +29,12 @@ public enum FFTWidth
private int mWidth;
private FFTWidth( int width )
private DFTSize( int width )
{
mWidth = width;
}
public int getWidth()
public int getSize()
{
return mWidth;
}

View File

@@ -0,0 +1,7 @@
package spectrum;
public interface IDFTWidthChangeProcessor
{
public void setDFTSize( DFTSize size );
public DFTSize getDFTSize();
}

View File

@@ -67,6 +67,10 @@ public class OverlayPanel extends JPanel
private Point mCursorLocation = new Point( 0, 0 );
private boolean mCursorVisible = false;
private DFTSize mDFTSize = DFTSize.FFT04096;
private int mZoom = 0;
private int mDFTZoomWindowOffset = 0;
/**
* Colors used by this component
*/
@@ -118,6 +122,11 @@ public class OverlayPanel extends JPanel
mResourceManager = null;
}
public void setDFTSize( DFTSize size )
{
mDFTSize = size;
}
public ChannelDisplay getChannelDisplay()
{
return mChannelDisplay;
@@ -142,6 +151,25 @@ public class OverlayPanel extends JPanel
repaint();
}
/**
* Sets the current zoom level (2^zoom)
*
* 0 No Zoom
* 1 2x Zoom
* 2 4x Zoom
* 3 8x Zoom
* 4 16x Zoom
* 5 32x Zoom
*
* @param zoom level, 0 - 5.
* @param offset into the FFT bins for start zoom window
*/
public void setZoom( int zoom, int offset )
{
mZoom = zoom;
mDFTZoomWindowOffset = offset;
}
/**
* Fetches the color settings from the settings manager
*/
@@ -237,7 +265,7 @@ public class OverlayPanel extends JPanel
drawChannels( graphics );
drawCursor( graphics );
}
/**
* Draws a cursor on the panel, whenever the mouse is hovering over the
* panel
@@ -256,6 +284,12 @@ public class OverlayPanel extends JPanel
graphics.drawString( frequency ,
mCursorLocation.x + 5,
mCursorLocation.y );
if( mZoom != 0 )
{
graphics.drawString( "Zoom: " + (int)Math.pow( 2.0, mZoom ) + "x",
mCursorLocation.x + 17, mCursorLocation.y + 11 );
}
}
}
@@ -264,8 +298,8 @@ public class OverlayPanel extends JPanel
*/
private void drawFrequencies( Graphics2D graphics )
{
long minFrequency = getMinFrequency();
long maxFrequency = getMaxFrequency();
long minFrequency = getMinDisplayFrequency();
long maxFrequency = getMaxDisplayFrequency();
int major = mLabelSizeMonitor.getMajorTickIncrement( graphics );
int minor = mLabelSizeMonitor.getMinorTickIncrement( graphics );
@@ -365,21 +399,18 @@ public class OverlayPanel extends JPanel
/**
* Returns the x-axis value corresponding to the frequency
*/
public double getAxisFromFrequency( long frequency )
private double getAxisFromFrequency( long frequency )
{
double canvasMiddle = getSize().getWidth() / 2.0d;
//Determine frequency offset from middle
double frequencyOffset = mFrequency - frequency;
double frequencyOffset = frequency - getMinDisplayFrequency();
//Determine ratio of offset to bandwidth
double ratio = frequencyOffset / (double)mBandwidth;
double ratio = frequencyOffset / (double)getDisplayBandwidth();
//Calculate offset against the total width
double xOffset = getSize().getWidth() * ratio;
//Apply the offset against the canvas middle
return canvasMiddle - xOffset;
double xOffset = (double)getSize().getWidth() * ratio;
return xOffset;
}
/**
@@ -448,7 +479,7 @@ public class OverlayPanel extends JPanel
double xAxis = getAxisFromFrequency( tunerChannel.getFrequency() );
double width = (double)( tunerChannel.getBandwidth() ) /
(double)mBandwidth * getSize().getWidth();
(double)getDisplayBandwidth() * getSize().getWidth();
Rectangle2D.Double box =
new Rectangle2D.Double( xAxis - ( width / 2.0d ),
@@ -646,11 +677,16 @@ public class OverlayPanel extends JPanel
repaint();
}
public int getBandwidth()
{
return mBandwidth;
}
/**
* Currently displayed minimum frequency
*/
private long getMinFrequency()
public long getMinFrequency()
{
return mFrequency - ( mBandwidth / 2 );
}
@@ -658,11 +694,38 @@ public class OverlayPanel extends JPanel
/**
* Currently displayed maximum frequency
*/
private long getMaxFrequency()
public long getMaxFrequency()
{
return mFrequency + ( mBandwidth / 2 );
}
public boolean containsFrequency( long frequency )
{
return Math.abs( mFrequency - frequency ) <= ( mBandwidth / 2 );
}
private long getMinDisplayFrequency()
{
double bandwidthPerBin = (double)mBandwidth / (double)( mDFTSize.getSize() - 1 );
return getMinFrequency() + (int)( mDFTZoomWindowOffset * bandwidthPerBin );
}
private long getMaxDisplayFrequency()
{
return getMinDisplayFrequency() + getDisplayBandwidth();
}
private int getDisplayBandwidth()
{
if( mZoom != 0 )
{
return mBandwidth / (int)Math.pow( 2.0, mZoom );
}
return mBandwidth;
}
/**
* Returns a list of channel configs that contain the frequency within their
* min/max frequency settings.

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 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
@@ -17,7 +17,6 @@
******************************************************************************/
package spectrum;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
@@ -28,18 +27,27 @@ import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import module.decode.DecoderType;
import net.miginfocom.swing.MigLayout;
@@ -50,7 +58,6 @@ import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.ColorSettingMenuItem;
import settings.Setting;
@@ -66,7 +73,7 @@ import spectrum.OverlayPanel.ChannelDisplay;
import spectrum.converter.ComplexDecibelConverter;
import spectrum.converter.DFTResultsConverter;
import spectrum.menu.AveragingItem;
import spectrum.menu.FFTWidthItem;
import spectrum.menu.DFTSizeItem;
import spectrum.menu.FFTWindowTypeItem;
import spectrum.menu.FrameRateItem;
import spectrum.menu.SmoothingItem;
@@ -94,27 +101,25 @@ public class SpectralDisplayPanel extends JPanel
implements ChannelEventListener,
Listener<ComplexBuffer>,
FrequencyChangeListener,
IDFTWidthChangeProcessor,
SettingChangeListener,
TunerSelectionListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( SpectralDisplayPanel.class );
public static final int NO_ZOOM = 0;
public static final int MAX_ZOOM = 6;
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" );
/**
* Colors used by this component
*/
private Color mColorChannelConfig;
private Color mColorChannelConfigProcessing;
private Color mColorChannelConfigSelected;
private Color mColorSpectrumBackground;
private Color mColorSpectrumCursor;
private Color mColorSpectrumGradientBottom;
private Color mColorSpectrumGradientTop;
private Color mColorSpectrumLine;
private DFTSize mDFTSize = DFTSize.FFT04096;
private int mZoom = 0;
private int mDFTZoomWindowOffset = 0;
private JScrollPane mScrollPane;
private JLayeredPane mLayeredPanel;
private SpectrumPanel mSpectrumPanel;
private WaterfallPanel mWaterfallPanel;
@@ -128,8 +133,6 @@ public class SpectralDisplayPanel extends JPanel
{
mResourceManager = resourceManager;
setColors();
init();
}
@@ -159,56 +162,84 @@ public class SpectralDisplayPanel extends JPanel
mTuner = null;
}
/**
* Fetches the color settings from the settings manager
* Queues an FFT size change request. The scheduled executor will apply
* the change when it runs.
*/
private void setColors()
public void setDFTSize( DFTSize size )
{
mColorChannelConfig = getColor( ColorSettingName.CHANNEL_CONFIG );
mDFTProcessor.setDFTSize( size );
mOverlayPanel.setDFTSize( size );
mDFTSize = size;
mColorChannelConfigProcessing =
getColor( ColorSettingName.CHANNEL_CONFIG_PROCESSING );
mColorChannelConfigSelected =
getColor( ColorSettingName.CHANNEL_CONFIG_SELECTED );
mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR );
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
mColorSpectrumBackground =
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
mColorSpectrumGradientBottom =
getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
mColorSpectrumGradientTop =
getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
setZoom( 0, 0, 0 );
}
@Override
public DFTSize getDFTSize()
{
return mDFTSize;
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
*/
private Color getColor( ColorSettingName name )
{
ColorSetting setting = mResourceManager.getSettingsManager()
.getColorSetting( name );
return setting.getColor();
}
public int getZoom()
{
return mZoom;
}
/**
* Sets the current zoom level (2^zoom)
*
* 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 - 5.
* @param frequency under the mouse to maintain while zooming
* @param xAxisOffset where to maintain the frequency under the mouse
*/
public void setZoom( int zoom, long frequency, double windowOffset )
{
if( zoom != mZoom )
{
mZoom = zoom;
double zoomTotal = Math.pow( 2.0, mZoom );
/**
* Stores the named color setting
*/
private void setColor( ColorSettingName name, Color color, int translucency )
{
Color adjustedColor = ColorSetting.getTranslucent( color, translucency );
mResourceManager.getSettingsManager()
.setColorSetting( name, adjustedColor );
}
double overallOffset = getBinOffset( frequency );
double windowBinOffset = ( (double)mDFTSize.getSize() /
(double)zoomTotal ) *
windowOffset;
mDFTZoomWindowOffset = (int)( overallOffset - windowBinOffset );
mSpectrumPanel.setZoom( mZoom, mDFTZoomWindowOffset );
mOverlayPanel.setZoom( mZoom, mDFTZoomWindowOffset );
mWaterfallPanel.setZoom( mZoom, mDFTZoomWindowOffset );
}
}
/**
* Calculates the overall offset of the frequency from the current minimum
* frequency in terms of total FFT width
* @param frequency
* @return
*/
private double getBinOffset( long frequency )
{
if( mOverlayPanel.containsFrequency( frequency ) )
{
return (double)mDFTSize.getSize() *
( (double)( frequency - mOverlayPanel.getMinFrequency() ) /
(double)mOverlayPanel.getBandwidth() );
}
return 0.0;
}
/**
* Monitors for setting changes. Colors can be changed by external actions
@@ -231,41 +262,6 @@ public class SpectralDisplayPanel extends JPanel
{
mSpectrumPanel.settingChanged( setting );
}
if( setting instanceof ColorSetting )
{
ColorSetting colorSetting = (ColorSetting)setting;
switch( ((ColorSetting)setting).getColorSettingName() )
{
case CHANNEL_CONFIG:
mColorChannelConfig = colorSetting.getColor();
break;
case CHANNEL_CONFIG_PROCESSING:
mColorChannelConfigProcessing = colorSetting.getColor();
break;
case CHANNEL_CONFIG_SELECTED:
mColorChannelConfigSelected = colorSetting.getColor();
break;
case SPECTRUM_BACKGROUND:
mColorSpectrumBackground = colorSetting.getColor();
break;
case SPECTRUM_CURSOR:
mColorSpectrumCursor = 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;
}
}
}
/**
@@ -304,6 +300,7 @@ public class SpectralDisplayPanel extends JPanel
mOverlayPanel = new OverlayPanel( mResourceManager );
mOverlayPanel.addMouseListener( mouser );
mOverlayPanel.addMouseMotionListener( mouser );
mOverlayPanel.addMouseWheelListener( mouser );
//Add the spectrum and channel panels to the layered panel
mLayeredPanel.add( mSpectrumPanel, new Integer( 0 ), 0 );
@@ -313,6 +310,7 @@ public class SpectralDisplayPanel extends JPanel
mWaterfallPanel = new WaterfallPanel( mResourceManager );
mWaterfallPanel.addMouseListener( mouser );
mWaterfallPanel.addMouseMotionListener( mouser );
mWaterfallPanel.addMouseWheelListener( mouser );
/* Attempt to set a 50/50 split preferred size for the split pane */
double totalHeight = mLayeredPanel.getPreferredSize().getHeight() +
@@ -330,7 +328,9 @@ public class SpectralDisplayPanel extends JPanel
splitPane.add( mLayeredPanel );
splitPane.add( mWaterfallPanel );
add( splitPane, "grow" );
mScrollPane = new JScrollPane( splitPane );
add( mScrollPane, "grow" );
/**
* Setup DFTProcessor to process samples and register the waterfall and
@@ -451,8 +451,30 @@ public class SpectralDisplayPanel extends JPanel
/**
* Mouse event handler for the channel panel.
*/
public class MouseEventProcessor implements MouseMotionListener, MouseListener
public class MouseEventProcessor implements MouseMotionListener,
MouseListener,
MouseWheelListener
{
@Override
public void mouseWheelMoved( MouseWheelEvent e )
{
int zoom = mZoom - e.getWheelRotation();
if( zoom < NO_ZOOM )
{
zoom = NO_ZOOM;
}
else if( zoom > MAX_ZOOM )
{
zoom = MAX_ZOOM;
}
long frequency = mOverlayPanel.getFrequencyFromAxis( e.getX() );
double windowOffset = (double)e.getX() / (double)getWidth();
setZoom( zoom, frequency, windowOffset );
}
@Override
public void mouseMoved( MouseEvent event )
@@ -631,9 +653,9 @@ public class SpectralDisplayPanel extends JPanel
JMenu fftWidthMenu = new JMenu( "FFT Width" );
displayMenu.add( fftWidthMenu );
for( FFTWidth width: FFTWidth.values() )
for( DFTSize width: DFTSize.values() )
{
fftWidthMenu.add( new FFTWidthItem( mDFTProcessor, width ) );
fftWidthMenu.add( new DFTSizeItem( SpectralDisplayPanel.this, width ) );
}
/**
@@ -683,6 +705,18 @@ public class SpectralDisplayPanel extends JPanel
displayMenu.add( smoothingMenu );
}
/*
* Zoom menu
*/
JMenuItem zoomMenu = new JMenu( "Zoom" );
double windowOffset = (double)event.getX() / (double)getWidth();
zoomMenu.add( new ZoomItem( frequency, windowOffset ) );
contextMenu.add( zoomMenu );
if( contextMenu != null )
{
if( event.getComponent() == mOverlayPanel )
@@ -741,6 +775,47 @@ public class SpectralDisplayPanel extends JPanel
}
}
public class ZoomItem extends JSlider
{
private static final long serialVersionUID = 1L;
private long mFrequency;
private double mWindowOffset;
public ZoomItem( long frequency, double windowOffset )
{
super( NO_ZOOM, MAX_ZOOM, mZoom );
mFrequency = frequency;
mWindowOffset = windowOffset;
Hashtable<Integer,JComponent> labels = new Hashtable<>();
labels.put( new Integer( 0 ), new JLabel( "1x" ) );
labels.put( new Integer( 1 ), new JLabel( "2x" ) );
labels.put( new Integer( 2 ), new JLabel( "4x" ) );
labels.put( new Integer( 3 ), new JLabel( "8x" ) );
labels.put( new Integer( 4 ), new JLabel( "16x" ) );
labels.put( new Integer( 5 ), new JLabel( "32x" ) );
labels.put( new Integer( 6 ), new JLabel( "64x" ) );
setLabelTable( labels );
setMajorTickSpacing( 1 );
setMinorTickSpacing( 1 );
setPaintTicks( true );
setPaintLabels( true );
this.addChangeListener( new ChangeListener()
{
@Override
public void stateChanged( ChangeEvent e )
{
setZoom( getValue(), mFrequency, mWindowOffset );
}
} );
}
}
public class ChannelDisplayItem extends JCheckBoxMenuItem
{
private static final long serialVersionUID = 1L;

View File

@@ -1,6 +1,6 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014,2015 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
@@ -51,40 +51,42 @@ public class SpectrumPanel extends JPanel
SpectralDisplayAdjuster
{
private static final long serialVersionUID = 1L;
private final static Logger mLog =
LoggerFactory.getLogger( SpectrumPanel.class );
/* Set display bins size to 1, so that we're guaranteed a reset to the
* correct width once the first sample set arrives */
private float[] mDisplayFFTBins = new float[ 1 ];
private int mAveraging = 4;
private float mDBScale;
/**
* Spectral Display Color Settings
*/
private static final String SPECTRUM_BACKGROUND = "spectrum_background";
private static final String SPECTRUM_GRADIENT_TOP = "spectrum_gradient_top";
private static final String SPECTRUM_GRADIENT_BOTTOM = "spectrum_gradient_bottom";
private static final String SPECTRUM_LINE = "spectrum_line";
private static final String SPECTRUM_AVERAGING_SIZE = "spectrum_averaging_size";
private static final int SPECTRUM_TRANSLUCENCY = 128;
private static final RenderingHints RENDERING_HINTS =
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
//Color settings
private static final String SPECTRUM_BACKGROUND = "spectrum_background";
private static final String SPECTRUM_GRADIENT_TOP = "spectrum_gradient_top";
private static final String SPECTRUM_GRADIENT_BOTTOM = "spectrum_gradient_bottom";
private static final String SPECTRUM_LINE = "spectrum_line";
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 mZoomBinOffset = 0;;
private ResourceManager mResourceManager;
@@ -109,104 +111,30 @@ public class SpectrumPanel extends JPanel
mResourceManager = null;
}
public void setSampleSize( double sampleSize )
{
assert( 2.0 <= sampleSize && sampleSize <= 32.0 );
mDBScale = (float)( 20.0 * Math.log10( 1 / Math.pow( 2.0, sampleSize - 1 ) ) );
}
public void setAveraging( int size )
{
mAveraging = size;
}
public int getAveraging()
{
return mAveraging;
}
public void clearSpectrum()
{
mDisplayFFTBins = null;
repaint();
}
private void getSettings()
{
mColorSpectrumBackground =
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
mColorSpectrumGradientBottom =
getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
mColorSpectrumGradientTop =
getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
mAveraging = 4;
}
private Color getColor( ColorSettingName name )
{
ColorSetting setting =
mResourceManager.getSettingsManager().getColorSetting( name );
return setting.getColor();
}
@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;
}
}
}
/**
* DFTResultsListener interface method - for receiving the processed data
* 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 fft results variables
//Construct and/or resize our DFT results variables
if( mDisplayFFTBins == null ||
mDisplayFFTBins.length != currentFFTBins.length )
{
mDisplayFFTBins = currentFFTBins;
}
//Smooth across the bins
//Apply smoothing across the bins of the DFT results
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
//Average bins over multiple frames
//Apply averaging over multiple DFT output frames
if( mAveraging > 1 )
{
float gain = 1.0f / (float)mAveraging;
@@ -221,7 +149,6 @@ public class SpectrumPanel extends JPanel
mDisplayFFTBins = smoothedBins;
}
repaint();
}
@@ -270,17 +197,19 @@ public class SpectrumPanel extends JPanel
//Draw to the lower left
spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset );
float[] bins = getBins();
//If we have FFT data to display ...
if( mDisplayFFTBins != null )
if( bins != null )
{
float insideHeight = size.height - mSpectrumInset;
float scalor = insideHeight / mDBScale;
float scalor = insideHeight / -mDBScale;
float insideWidth = size.width;
int binCount = mDisplayFFTBins.length;
int binCount = bins.length;
float binSize = insideWidth / ( binCount );
@@ -288,24 +217,17 @@ public class SpectrumPanel extends JPanel
{
float height;
if( mDisplayFFTBins[ x ] > 0 )
height = bins[ x ] * scalor;
if( height > insideHeight )
{
height = insideHeight;
}
if( height < 0 )
{
height = 0;
}
else
{
height = mDisplayFFTBins[ x ] * scalor;
if( height > insideHeight )
{
height = insideHeight;
}
if( height < 0 )
{
height = 0;
}
}
spectrumShape.lineTo( ( x * binSize ), height );
}
@@ -338,27 +260,188 @@ public class SpectrumPanel extends JPanel
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
*
* @param zoom level, 0 - 5.
*/
public void setZoom( int zoom, int offset )
{
assert( 0 <= zoom && zoom <= 5 );
mZoom = zoom;
setZoomBinOffset( offset );
}
/**
* Sets the offset to define which subset of zoomed bins to display
*
* @param offset
*/
public void setZoomBinOffset( int offset )
{
mZoomBinOffset = 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 )
{
assert( 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 getSettings()
{
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 =
mResourceManager.getSettingsManager().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 float[] getBins()
{
if( mZoom == 0 )
{
return mDisplayFFTBins;
}
else
{
int zoom = (int)Math.pow( 2.0, mZoom );
int length = mDisplayFFTBins.length / zoom;
int offset = mZoomBinOffset;
if( ( offset + length ) > mDisplayFFTBins.length )
{
offset = mDisplayFFTBins.length - length;
}
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 )
{

View File

@@ -60,7 +60,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
private static final String PAUSED = "PAUSED";
private byte[] mPixels;
private int mFFTSize = 1024;
private int mDFTSize = 4096;
private int mImageHeight = 700;
private MemoryImageSource mMemoryImageSource;
private ColorModel mColorModel = WaterfallColorModel.getDefaultColorModel();
@@ -71,6 +71,8 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
private boolean mCursorVisible = false;
private long mCursorFrequency = 0;
private AtomicBoolean mPaused = new AtomicBoolean();
private int mZoom = 0;
private int mDFTZoomOffset = 0;
private ResourceManager mResourceManager;
@@ -93,14 +95,14 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
private void reset()
{
mPixels = new byte[ mFFTSize * mImageHeight ];
mPixels = new byte[ mDFTSize * mImageHeight ];
mMemoryImageSource = new MemoryImageSource( mFFTSize,
mMemoryImageSource = new MemoryImageSource( mDFTSize,
mImageHeight,
mColorModel,
mPixels,
0,
mFFTSize );
mDFTSize );
mMemoryImageSource.setAnimated( true );
@@ -122,6 +124,25 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
return mPaused.get();
}
/**
* Sets the current zoom level (2^zoom)
*
* 0 No Zoom
* 1 2x Zoom
* 2 4x Zoom
* 3 8x Zoom
* 4 16x Zoom
* 5 32x Zoom
*
* @param zoom level, 0 - 5.
* @param offset into the DFT bins for display
*/
public void setZoom( int zoom, int offset )
{
mZoom = zoom;
mDFTZoomOffset = offset;
}
/**
* Fetches a named color setting from the settings manager. If the setting
* doesn't exist, creates the setting using the defaultColor
@@ -171,10 +192,31 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
repaint();
}
private int getPixelOffset( int multiplier )
{
if( mZoom != 0 )
{
return -(int)( (double)getWidth() * multiplier /
(double)mDFTSize *
(double)( mDFTZoomOffset ) );
}
return 0;
}
public void paintComponent( Graphics g )
{
g.drawImage( mWaterfallImage, 0, 0, getWidth(), mImageHeight, this );
int multiplier = (int)Math.pow( 2.0, mZoom );
int offset = getPixelOffset( multiplier );
g.drawImage( mWaterfallImage,
offset,
0,
getWidth() * multiplier,
mImageHeight,
this );
Graphics2D graphics = (Graphics2D) g;
@@ -194,6 +236,11 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
mCursorLocation.x + 5,
mCursorLocation.y );
if( mZoom != 0 )
{
graphics.drawString( "Zoom: " + (int)Math.pow( 2.0, mZoom ) + "x",
mCursorLocation.x + 17, mCursorLocation.y + 11 );
}
}
if( mPaused.get() )
@@ -208,16 +255,16 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
public void receive( float[] update )
{
//If our FFT size changes, reset our pixel map and image source
if( mFFTSize != update.length )
if( mDFTSize != update.length )
{
mFFTSize = update.length;
mDFTSize = update.length;
reset();
}
//Move the pixels down a row to make room for the new results
System.arraycopy( mPixels, 0,
mPixels, mFFTSize, mPixels.length - mFFTSize );
mPixels, mDFTSize, mPixels.length - mDFTSize );
/**
* Find the average value and scale the display to it
@@ -260,7 +307,7 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener,
{
if( mMemoryImageSource != null )
{
mMemoryImageSource.newPixels( mPixels, mColorModel, 0, mFFTSize );
mMemoryImageSource.newPixels( mPixels, mColorModel, 0, mDFTSize );
}
}
} );

View File

@@ -78,7 +78,8 @@ public class ComplexDecibelConverter extends DFTResultsConverter
{
int halfResults = results.length / 2;
float dftScalor = 1.0f / (float)( halfResults ^ 2 );
// float dftBinSizeScalor = 1.0f / (float)Math.pow( halfResults, 2.0 );
float dftBinSizeScalor = 1.0f / (float)halfResults;
float[] processed = new float[ halfResults ];
@@ -91,7 +92,7 @@ public class ComplexDecibelConverter extends DFTResultsConverter
//Convert the scaled value to decibels.
float decibels = 10.0f * (float)Math.log10(
( ( results[ x ] * results[ x ] ) +
( results[ x + 1 ] * results[ x + 1 ] ) ) * dftScalor );
( results[ x + 1 ] * results[ x + 1 ] ) ) * dftBinSizeScalor );
// We have to swap the upper and lower halves of the JTransforms
// DFT results for correct display

View File

@@ -21,7 +21,7 @@ public class RealDecibelConverter extends DFTResultsConverter
@Override
public void receive( float[] results )
{
float dftScalor = 1.0f / (float)( results.length ^ 2 );
float dftBinSizeScalor = 1.0f / (float)results.length;
float[] processed = new float[ results.length / 4 ];
@@ -33,7 +33,7 @@ public class RealDecibelConverter extends DFTResultsConverter
processed[ x ] = 20.0f * (float)Math.log10(
( ( results[ index ] * results[ index ] ) +
( results[ index + 1 ] * results[ index + 1 ] ) ) * dftScalor );
( results[ index + 1 ] * results[ index + 1 ] ) ) * dftBinSizeScalor );
}
dispatch( processed );

View File

@@ -5,24 +5,24 @@ import java.awt.event.ActionListener;
import javax.swing.JCheckBoxMenuItem;
import spectrum.DFTProcessor;
import spectrum.FFTWidth;
import spectrum.DFTSize;
import spectrum.IDFTWidthChangeProcessor;
public class FFTWidthItem extends JCheckBoxMenuItem
public class DFTSizeItem extends JCheckBoxMenuItem
{
private static final long serialVersionUID = 1L;
private DFTProcessor mDFTProcessor;
private FFTWidth mFFTWidth;
private IDFTWidthChangeProcessor mDFTProcessor;
private DFTSize mDFTSize;
public FFTWidthItem( DFTProcessor processor, FFTWidth width )
public DFTSizeItem( IDFTWidthChangeProcessor processor, DFTSize size )
{
super( width.getLabel() );
super( size.getLabel() );
mDFTProcessor = processor;
mFFTWidth = width;
mDFTSize = size;
if( processor.getFFTWidth() == mFFTWidth )
if( processor.getDFTSize() == mDFTSize )
{
setSelected( true );
}
@@ -32,7 +32,7 @@ public class FFTWidthItem extends JCheckBoxMenuItem
@Override
public void actionPerformed( ActionEvent arg0 )
{
mDFTProcessor.setFFTSize( mFFTWidth );
mDFTProcessor.setDFTSize( mDFTSize );
}
} );
}