Adds airspy USB tuner support and updates the spectral display

component.

-Airspy supported with 10 and 2.5 MSps sample rates.  Additional
decimation may be added later.

-Spectral display updated to include smoothing and filtering options.

Note: the decimated output of a tuner channel from the airspy is
48,076.9 samples per second, versus the normal 48 kHz decimated rate.
This is 1.0015 times the normal rate and doesn't appear to impact the
decoders or audio processing.  Therefore, no fractional resampling has
been added.
This commit is contained in:
denny
2015-10-30 20:49:22 -04:00
parent 5a91630996
commit 70e62ebb35
80 changed files with 4781 additions and 1014 deletions

View File

@@ -13,8 +13,12 @@ import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import sample.Listener;
import sample.SampleType;
@@ -31,10 +35,11 @@ import source.tuner.frequency.FrequencyChangeEvent.Attribute;
import spectrum.converter.DFTResultsConverter;
import spectrum.converter.RealDecibelConverter;
import spectrum.menu.AveragingItem;
import spectrum.menu.DBScaleItem;
import spectrum.menu.FFTWidthItem;
import spectrum.menu.FFTWindowTypeItem;
import spectrum.menu.FrameRateItem;
import spectrum.menu.SmoothingItem;
import spectrum.menu.SmoothingTypeItem;
import controller.ResourceManager;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
@@ -42,6 +47,9 @@ import controller.channel.ChannelEventListener;
import dsp.filter.Filters;
import dsp.filter.FloatHalfBandFilter;
import dsp.filter.Window.WindowType;
import dsp.filter.halfband.real.HalfBandFilter_RB_RB;
import dsp.filter.hilbert.HilbertTransform;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
public class ChannelSpectrumPanel extends JPanel
implements ChannelEventListener,
@@ -49,9 +57,9 @@ public class ChannelSpectrumPanel extends JPanel
SettingChangeListener,
SpectralDisplayAdjuster
{
private static final long serialVersionUID = 1L;
private static final String CHANNEL_SPECTRUM_AVERAGING_SIZE =
"channel_spectrum_averaging_size";
private static final Logger mLog = LoggerFactory.getLogger( ChannelSpectrumPanel.class );
private static final long serialVersionUID = 1L;
private ResourceManager mResourceManager;
private DFTProcessor mDFTProcessor = new DFTProcessor( SampleType.REAL );
@@ -62,13 +70,13 @@ public class ChannelSpectrumPanel extends JPanel
private Channel mCurrentChannel;
private int mSpectrumAveragingSize;
private int mSampleBufferSize = 2400;
private float[] mSamples = new float[ mSampleBufferSize ];
private int mSamplePointer = 0;
private DecimatingSampleAssembler mDecimatingSampleAssembler;
private HalfBandFilter_RB_RB mDecimatingFilter = new HalfBandFilter_RB_RB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 1.0f, true );
private AtomicBoolean mEnabled = new AtomicBoolean();
public ChannelSpectrumPanel( ResourceManager resourceManager )
@@ -76,20 +84,12 @@ public class ChannelSpectrumPanel extends JPanel
mResourceManager = resourceManager;
mResourceManager.getSettingsManager().addListener( this );
mSpectrumPanel = new SpectrumPanel( mResourceManager );
mSpectrumPanel.setAveraging( 1 );
mOverlayPanel = new ChannelOverlayPanel( mResourceManager );
mSpectrumAveragingSize = 10;
// mSpectrumAveragingSize = mResourceManager.getSettingsManager()
// .getIntegerSetting( CHANNEL_SPECTRUM_AVERAGING_SIZE, 10 ).getValue();
mSpectrumPanel.setAveraging( mSpectrumAveragingSize );
mDFTProcessor.addConverter( mDFTConverter );
mDFTConverter.addListener( mSpectrumPanel );
mDecimatingSampleAssembler = new DecimatingSampleAssembler( mDFTProcessor );
/* Set the DFTProcessor to the decimated 24kHz sample rate */
mDFTProcessor.frequencyChanged(
new FrequencyChangeEvent( Attribute.SAMPLE_RATE, 24000 ) );
@@ -254,10 +254,11 @@ public class ChannelSpectrumPanel extends JPanel
@Override
public void receive( RealBuffer buffer )
{
for( float sample: buffer.getSamples() )
{
mDecimatingSampleAssembler.receive( sample );
}
RealBuffer decimated = mDecimatingFilter.filter( buffer );
//Hack: we're placing real samples in a complex buffer that the DFT
//processor is expecting.
mDFTProcessor.receive( new ComplexBuffer( decimated.getSamples() ) );
}
/**
@@ -367,17 +368,9 @@ public class ChannelSpectrumPanel extends JPanel
*/
JMenu averagingMenu = new JMenu( "Averaging" );
averagingMenu.add(
new AveragingItem( ChannelSpectrumPanel.this, 4 ) );
new AveragingItem( ChannelSpectrumPanel.this, 2 ) );
displayMenu.add( averagingMenu );
/**
* Baseline menu
*/
JMenu baselineMenu = new JMenu( "Baseline" );
baselineMenu.add(
new DBScaleItem( ChannelSpectrumPanel.this, 50 ) );
displayMenu.add( baselineMenu );
/**
* FFT width
*/
@@ -416,6 +409,24 @@ public class ChannelSpectrumPanel extends JPanel
new FFTWindowTypeItem( mDFTProcessor, type ) );
}
/**
* Smoothing menu
*/
JMenu smoothingMenu = new JMenu( "Smoothing" );
if( mSpectrumPanel.getSmoothingType() != SmoothingType.NONE )
{
smoothingMenu.add( new SmoothingItem( ChannelSpectrumPanel.this, 5 ) );
smoothingMenu.add( new JSeparator() );
}
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.GAUSSIAN ) );
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.TRIANGLE ) );
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.RECTANGLE ) );
smoothingMenu.add( new SmoothingTypeItem( ChannelSpectrumPanel.this, SmoothingType.NONE ) );
displayMenu.add( smoothingMenu );
if( contextMenu != null )
{
contextMenu.show( mOverlayPanel,
@@ -443,37 +454,35 @@ public class ChannelSpectrumPanel extends JPanel
mSpectrumPanel.setAveraging( averaging );
}
@Override
public int getDBScale()
{
return mSpectrumPanel.getDBScale();
}
@Override
public void setDBScale( int baseline )
{
mSpectrumPanel.setDBScale( baseline );
}
public class DecimatingSampleAssembler
public void setSampleSize( double sampleSize )
{
private FloatHalfBandFilter mDecimationFilter = new FloatHalfBandFilter(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002f );
private SampleAssembler mSampleAssembler;
public DecimatingSampleAssembler( Listener<ComplexBuffer> listener )
{
mSampleAssembler = new SampleAssembler( listener );
mDecimationFilter.setListener( mSampleAssembler );
}
public void receive( float sample )
{
mDecimationFilter.receive( sample );
}
mSpectrumPanel.setSampleSize( sampleSize );
}
@Override
public int getSmoothing()
{
return mSpectrumPanel.getSmoothing();
}
@Override
public void setSmoothing( int smoothing )
{
mSpectrumPanel.setSmoothing( smoothing );
}
@Override
public SmoothingType getSmoothingType()
{
return mSpectrumPanel.getSmoothingType();
}
@Override
public void setSmoothingType( SmoothingType type )
{
mSpectrumPanel.setSmoothingType( type );
}
public class SampleAssembler implements RealSampleListener
{
private Listener<ComplexBuffer> mListener;

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
@@ -30,13 +30,14 @@ import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import controller.NamingThreadFactory;
import sample.Buffer;
import sample.Listener;
import sample.SampleType;
import sample.complex.ComplexBuffer;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeListener;
import spectrum.converter.DFTResultsConverter;
import controller.NamingThreadFactory;
import dsp.filter.Window;
import dsp.filter.Window.WindowType;
@@ -44,8 +45,7 @@ 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
{
private final static Logger mLog =
LoggerFactory.getLogger( DFTProcessor.class );
@@ -54,7 +54,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
new CopyOnWriteArrayList<DFTResultsConverter>();
private ArrayBlockingQueue<ComplexBuffer> mQueue =
new ArrayBlockingQueue<ComplexBuffer>( 200 );
new ArrayBlockingQueue<ComplexBuffer>( 200 );
private ScheduledExecutorService mScheduler = Executors
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
@@ -74,7 +74,6 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
private int mSampleRate;
private int mFFTFloatsPerFrame;
private float mNewFloatsPerFrame;
private float mNewFloatsPerFrameToConsume;
private float mNewFloatResidual;
private float[] mPreviousFrame = new float[ 8192 ];
@@ -231,7 +230,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
if( !mQueue.offer( sampleBuffer ) )
{
mLog.error( "DFTProcessor - [" + mSampleType.toString()
+ "]queue is full, purging queue, "
+ "] queue is full, purging queue, "
+ "samples[" + sampleBuffer + "]" );
mQueue.clear();
@@ -245,7 +244,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
try
{
ComplexBuffer buffer = mQueue.take();
Buffer buffer = mQueue.take();
mCurrentBuffer = buffer.getSamples();
}
catch ( InterruptedException e )
@@ -500,14 +499,5 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
mFFTFloatsPerFrame = ( mSampleType == SampleType.COMPLEX ?
mFFTWidth.getWidth() * 2 :
mFFTWidth.getWidth() );
if( mFFTFloatsPerFrame < mNewFloatsPerFrame )
{
mNewFloatsPerFrameToConsume = mFFTFloatsPerFrame;
}
else
{
mNewFloatsPerFrameToConsume = mNewFloatsPerFrame;
}
}
}

View File

@@ -1,35 +1,30 @@
package spectrum;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
public interface SpectralDisplayAdjuster
{
/**
/**
* Gets the averaging value indicating the number of FFT results that are
* averaged to produce each FFT results output.
*
* @return averaging value ( 2 - 50 )
*/
public int getAveraging();
/**
* Sets the averaging value indicating the number of FFT results to average
* when producing an FFT output result.
* @param averaging value ( 2 - 50 )
*/
public void setAveraging( int averaging );
/**
* Gets the dBm scale used when plotting the FFT bin results in the FFT
* spectral display. This value determines the smallest displayed power
* spectrum value that is shown at the bottom of the display.
* Sets the smoothing filter averaging window width.
*
* @return averaging value ( 1 - 50 )
* Valid values are odd in the range 3 - 29
*/
public int getDBScale();
public int getSmoothing();
public void setSmoothing( int smoothing );
/**
* Sets the dBm scale from 0 (top of display) to the scale value (bottom).
*
* @param baseline value ( -160 to -30 dBm )
* Sets the type of smoothing filter to use.
*/
public void setDBScale( int baseline );
public SmoothingType getSmoothingType();
public void setSmoothingType( SmoothingType type );
}

View File

@@ -66,10 +66,11 @@ import spectrum.OverlayPanel.ChannelDisplay;
import spectrum.converter.ComplexDecibelConverter;
import spectrum.converter.DFTResultsConverter;
import spectrum.menu.AveragingItem;
import spectrum.menu.DBScaleItem;
import spectrum.menu.FFTWidthItem;
import spectrum.menu.FFTWindowTypeItem;
import spectrum.menu.FrameRateItem;
import spectrum.menu.SmoothingItem;
import spectrum.menu.SmoothingTypeItem;
import com.jidesoft.swing.JideSplitPane;
@@ -79,6 +80,7 @@ import controller.channel.ChannelEvent;
import controller.channel.ChannelEventListener;
import controller.channel.ChannelUtils;
import dsp.filter.Window.WindowType;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
/**
* Spectral Display Panel - comprises a DFTProcessor coupled with a waterfall
@@ -387,6 +389,9 @@ public class SpectralDisplayPanel extends JPanel
//Register the dft processor to receive samples from the tuner
mTuner.addListener( (Listener<ComplexBuffer>)mDFTProcessor );
mDFTConverter.setSampleSize( mTuner.getSampleSize() );
mSpectrumPanel.setSampleSize( mTuner.getSampleSize() );
//Fire frequency and sample rate change events so that everyone
//can init
try
@@ -597,14 +602,6 @@ public class SpectralDisplayPanel extends JPanel
if( event.getComponent() != mWaterfallPanel )
{
/**
* DB Scale menu
*/
JMenu dbScaleMenu = new JMenu( "Scale: 0 to dBm" );
dbScaleMenu.add(
new DBScaleItem( mSpectrumPanel, -100 ) );
displayMenu.add( dbScaleMenu );
/**
* Averaging menu
*/
@@ -666,6 +663,26 @@ public class SpectralDisplayPanel extends JPanel
fftWindowType.add(
new FFTWindowTypeItem( mDFTProcessor, type ) );
}
if( event.getComponent() != mWaterfallPanel )
{
/**
* Smoothing menu
*/
JMenu smoothingMenu = new JMenu( "Smoothing" );
if( mSpectrumPanel.getSmoothingType() != SmoothingType.NONE )
{
smoothingMenu.add( new SmoothingItem( mSpectrumPanel, 5 ) );
smoothingMenu.add( new JSeparator() );
}
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.GAUSSIAN ) );
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.TRIANGLE ) );
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.RECTANGLE ) );
smoothingMenu.add( new SmoothingTypeItem( mSpectrumPanel, SmoothingType.NONE ) );
displayMenu.add( smoothingMenu );
}
if( contextMenu != null )
{

View File

@@ -36,8 +36,13 @@ import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import buffer.FloatArrayCircularAveragingBuffer;
import controller.ResourceManager;
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;
public class SpectrumPanel extends JPanel
implements DFTResultsListener,
@@ -51,9 +56,9 @@ public class SpectrumPanel extends JPanel
/* 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 FloatArrayCircularAveragingBuffer mFFTAveragingBuffer;
private int mAveraging = 4;
private int mDBScale = -100; //dBm
private float mDBScale;
/**
* Spectral Display Color Settings
@@ -65,6 +70,11 @@ public class SpectrumPanel extends JPanel
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 );
private Color mColorSpectrumBackground;
private Color mColorSpectrumGradientTop;
@@ -73,11 +83,20 @@ public class SpectrumPanel extends JPanel
private float mSpectrumInset = 20.0f;
private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter();
private ResourceManager mResourceManager;
public SpectrumPanel( ResourceManager resourceManager )
{
mResourceManager = resourceManager;
RENDERING_HINTS.put( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
setSampleSize( 16.0 );
mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT );
getSettings();
@@ -89,12 +108,16 @@ 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( Math.pow( 2.0, sampleSize ) ) );
}
public void setAveraging( int size )
{
mAveraging = size;
mFFTAveragingBuffer =
new FloatArrayCircularAveragingBuffer( mAveraging );
}
public int getAveraging()
@@ -102,19 +125,6 @@ public class SpectrumPanel extends JPanel
return mAveraging;
}
/**
* Sets the lowest decibel scale value to show on the display
*/
public void setDBScale( int dbScale )
{
mDBScale = dbScale;
}
public int getDBScale()
{
return mDBScale;
}
public void clearSpectrum()
{
mDisplayFFTBins = null;
@@ -180,18 +190,36 @@ public class SpectrumPanel extends JPanel
*/
public void receive( float[] currentFFTBins )
{
if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) )
{
currentFFTBins = new float[ currentFFTBins.length ];
}
//Construct and/or resize our fft results variables
if( mDisplayFFTBins == null ||
mDisplayFFTBins.length != currentFFTBins.length )
{
mDisplayFFTBins = new float[ currentFFTBins.length ];
mDisplayFFTBins = currentFFTBins;
}
/**
* Store the new FFT bins in the buffer and get the average of the FFT
* bin buffer contents into the display fft bins variable
*/
mDisplayFFTBins = mFFTAveragingBuffer.get( currentFFTBins );
//Smooth across the bins
float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins );
//Average bins over multiple 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();
}
@@ -204,14 +232,8 @@ public class SpectrumPanel extends JPanel
Graphics2D graphics = (Graphics2D) g;
graphics.setBackground( mColorSpectrumBackground );
RenderingHints renderHints =
new RenderingHints( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
renderHints.put( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
graphics.setRenderingHints( renderHints );
graphics.setRenderingHints( RENDERING_HINTS );
drawSpectrum( graphics );
}
@@ -231,7 +253,7 @@ public class SpectrumPanel extends JPanel
//Define the gradient
GradientPaint gradient = new GradientPaint( 0,
0,
getSize().height / 4,
mColorSpectrumGradientTop,
0,
getSize().height,
@@ -253,7 +275,7 @@ public class SpectrumPanel extends JPanel
{
float insideHeight = size.height - mSpectrumInset;
float scalor = insideHeight / ( -mDBScale );
float scalor = insideHeight / mDBScale;
float insideWidth = size.width;
@@ -271,7 +293,7 @@ public class SpectrumPanel extends JPanel
}
else
{
height = (float)( Math.abs( mDisplayFFTBins[ x ] ) * scalor );
height = mDisplayFFTBins[ x ] * scalor;
if( height > insideHeight )
{
@@ -316,5 +338,54 @@ public class SpectrumPanel extends JPanel
}
@Override
public void settingDeleted( Setting setting ) {}
public void settingDeleted( Setting setting ) { /* not implemented */ }
@Override
public int getSmoothing()
{
return mSmoothingFilter.getPointSize();
}
@Override
public void setSmoothing( int smoothing )
{
mSmoothingFilter.setPointSize( smoothing );
}
@Override
public SmoothingType getSmoothingType()
{
return mSmoothingFilter.getSmoothingType();
}
@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 );
}
}
}

View File

@@ -1,3 +1,20 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 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
* 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.converter;
import org.slf4j.Logger;
@@ -6,52 +23,60 @@ import org.slf4j.LoggerFactory;
public class ComplexDecibelConverter extends DFTResultsConverter
{
private final static Logger mLog =
LoggerFactory.getLogger( DFTResultsConverter.class );
LoggerFactory.getLogger( ComplexDecibelConverter.class );
private float mDynamicRangeReference = (float)Math.pow( 2.0, 16.0 );
/**
* Converts the output of the JTransforms FloatFFT_1D.complexForward()
* calculation into a normalized power spectrum in decibels, per description
* in Lyons, Understanding Digital Signal Processing, 3e, page 141.
* calculation into the power spectrum in decibels, normalized to the
* sample bit depth.
*/
public ComplexDecibelConverter()
{
}
/**
* Specifies the bit depth to establish the maximum dynamic range. All
* FFT bin values will be scaled according to this value.
*/
@Override
public void setSampleSize( double size )
{
assert( 2.0 <= size && size <= 32.0 );
mDynamicRangeReference = (float)Math.pow( 2.0d, size );
}
@Override
public void receive( float[] results )
{
float[] processed = new float[ results.length / 2 ];
/* Output from JTransforms 3.0 has the lower and upper halves of the
* spectrum swapped. */
int half = processed.length / 2;
for( int x = 0; x < results.length; x += 2 )
{
float magnitude = (float)Math.sqrt( results[ x ] *
results[ x ] + results[ x + 1 ] * results[ x + 1 ] );
/* Place upper half of magnitudes in lower half of results, vice-versa */
//Calculate the magnitude squared value from each bin's real and
//imaginary value, scale it to the available dynamic range and
//convert to dB.
float normalizedMagnitude = 10.0f * (float)Math.log10(
( ( results[ x ] * results[ x ] ) +
( results[ x + 1 ] * results[ x + 1 ] ) ) /
mDynamicRangeReference );
// We have to swap the upper and lower halves of the JTransforms
// DFT results for correct display
if( x / 2 >= half )
{
processed[ x / 2 - half ] = magnitude;
processed[ x / 2 - half ] = normalizedMagnitude;
}
else
{
processed[ x / 2 + half ] = magnitude;
processed[ x / 2 + half ] = normalizedMagnitude;
}
}
/* Use the DC component as the largest magnitude value to keep the
* display from jumping when a strong bursting signal exists */
float largestMagnitude = processed[ half ];
if( largestMagnitude > 0 )
{
/* Convert magnitudes to normized power spectrum dBm */
for( int i = 0; i < processed.length; i++ )
{
processed[ i ] = 20.0f *
(float)Math.log10( processed[ i ] / largestMagnitude );
}
dispatch( processed );
}
dispatch( processed );
}
}

View File

@@ -18,6 +18,12 @@ public abstract class DFTResultsConverter
public DFTResultsConverter()
{
}
/**
* Specifies the sample size in bits to establish the maximum dynamic range.
* All FFT bin values will be scaled according to this value.
*/
public abstract void setSampleSize( double size );
public void dispose()
{

View File

@@ -1,7 +1,31 @@
package spectrum.converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RealDecibelConverter extends DFTResultsConverter
{
private static final Logger mLog = LoggerFactory.getLogger( RealDecibelConverter.class );
private float mDynamicRangeReference;
public RealDecibelConverter()
{
setSampleSize( 24.0 );
}
/**
* Specifies the bit depth to establish the maximum dynamic range. All
* FFT bin values will be scaled according to this value.
*/
@Override
public void setSampleSize( double size )
{
assert( 2.0 <= size && size <= 32.0 );
mDynamicRangeReference = (float)Math.pow( 2.0d, size );
}
/**
* Converts the output of the JTransforms FloatFFT_1D.realForward()
* calculation into a normalized power spectrum in decibels, per description
@@ -10,29 +34,22 @@ public class RealDecibelConverter extends DFTResultsConverter
@Override
public void receive( float[] results )
{
float dc = Math.abs( results[ 0 ] );
float[] processed = new float[ results.length / 4 ];
/* Ensure we have power in the DC power bin (bin 0), otherwise don't
* dispatch the results */
if( dc != 0 )
int index = 0;
for( int x = 0; x < processed.length; x ++ )
{
/* Note: we're only processing 1/4 of the output samples */
float[] processed = new float[ results.length / 4 + 1 ];
index = x * 2;
for( int x = 0; x < processed.length - 2; x ++ )
{
int index = ( x + 1 ) * 2;
float normalizedMagnitude = 10.0f * (float)Math.log10(
( ( results[ index ] * results[ index ] ) +
( results[ index + 1 ] * results[ index + 1 ] ) ) /
mDynamicRangeReference );
double magnitude = ( ( results[ index ] * results[ index ] ) +
( results[ index + 1 ] * results[ index + 1 ] ) );
processed[ x ] = 10.0f *
(float)( Math.log10( magnitude / dc ) );
}
processed[ processed.length - 1 ] = processed[ 0 ];
dispatch( processed );
processed[ x ] = normalizedMagnitude;
}
dispatch( processed );
}
}

View File

@@ -18,14 +18,15 @@ public class AveragingItem extends JSlider implements ChangeListener
public AveragingItem( SpectralDisplayAdjuster adjuster, int defaultValue )
{
super( JSlider.HORIZONTAL, 2, 50, adjuster.getAveraging() );
super( JSlider.HORIZONTAL, 1, 20, adjuster.getAveraging() );
mDefaultValue = defaultValue;
mAdjuster = adjuster;
setMajorTickSpacing( 10 );
setMinorTickSpacing( 5 );
setMajorTickSpacing( 5 );
setMinorTickSpacing( 1 );
setPaintTicks( true );
setPaintLabels( true );
addChangeListener( this );

View File

@@ -2,34 +2,50 @@ package spectrum.menu;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Hashtable;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import spectrum.SpectralDisplayAdjuster;
import dsp.filter.smoothing.SmoothingFilter;
public class DBScaleItem extends JSlider implements ChangeListener
public class SmoothingItem extends JSlider implements ChangeListener
{
private static final long serialVersionUID = 1L;
private SpectralDisplayAdjuster mAdjuster;
private int mDefaultValue;
public DBScaleItem( SpectralDisplayAdjuster adjuster, int defaultValue )
public SmoothingItem( SpectralDisplayAdjuster adjuster, int defaultValue )
{
super( JSlider.HORIZONTAL, -160, -30, adjuster.getDBScale() );
mDefaultValue = defaultValue;
super( JSlider.HORIZONTAL,
SmoothingFilter.SMOOTHING_MINIMUM,
SmoothingFilter.SMOOTHING_MAXIMUM,
adjuster.getSmoothing() );
mDefaultValue = defaultValue;
mAdjuster = adjuster;
setMajorTickSpacing( 40 );
setMinorTickSpacing( 10 );
setSnapToTicks( true );
setMajorTickSpacing( 6 );
setMinorTickSpacing( 2 );
setPaintTicks( true );
setPaintLabels( true );
addChangeListener( this );
Hashtable<Integer,JLabel> labels = new Hashtable<>();
labels.put( new Integer( 3 ), new JLabel( "3" ) );
labels.put( new Integer( 9 ), new JLabel( "9" ) );
labels.put( new Integer( 15 ), new JLabel( "15" ) );
labels.put( new Integer( 21 ), new JLabel( "21" ) );
labels.put( new Integer( 27 ), new JLabel( "27" ) );
setLabelTable( labels );
addChangeListener( this );
addMouseListener( new MouseListener()
{
@Override
@@ -37,7 +53,7 @@ public class DBScaleItem extends JSlider implements ChangeListener
{
if( event.getClickCount() == 2 )
{
DBScaleItem.this.setValue( mDefaultValue );
SmoothingItem.this.setValue( mDefaultValue );
}
}
@@ -52,7 +68,10 @@ public class DBScaleItem extends JSlider implements ChangeListener
public void stateChanged( ChangeEvent event )
{
int value = ((JSlider)event.getSource()).getValue();
mAdjuster.setDBScale( value );
if( value % 2 == 1 )
{
mAdjuster.setSmoothing( value );
}
}
}

View File

@@ -0,0 +1,36 @@
package spectrum.menu;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBoxMenuItem;
import spectrum.SpectralDisplayAdjuster;
import dsp.filter.smoothing.SmoothingFilter.SmoothingType;
public class SmoothingTypeItem extends JCheckBoxMenuItem
{
private static final long serialVersionUID = 1L;
private SpectralDisplayAdjuster mAdjuster;
private SmoothingType mSmoothingType;
public SmoothingTypeItem( SpectralDisplayAdjuster adjuster, SmoothingType type )
{
super( type.name() );
mAdjuster = adjuster;
mSmoothingType = type;
setSelected( mAdjuster.getSmoothingType() == type );
addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mAdjuster.setSmoothingType( mSmoothingType );
}
} );
}
}