mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Initial code check in continued
This commit is contained in:
4
src/spectrum/.directory
Normal file
4
src/spectrum/.directory
Normal file
@@ -0,0 +1,4 @@
|
||||
[Dolphin]
|
||||
Timestamp=2014,2,4,19,35,46
|
||||
Version=3
|
||||
ViewMode=1
|
||||
546
src/spectrum/DFTProcessor.java
Normal file
546
src/spectrum/DFTProcessor.java
Normal file
@@ -0,0 +1,546 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import log.Log;
|
||||
import sample.Listener;
|
||||
import source.Source;
|
||||
import source.Source.SampleType;
|
||||
import source.tuner.FrequencyChangeListener;
|
||||
import dsp.filter.Window;
|
||||
import dsp.filter.Window.WindowType;
|
||||
import edu.emory.mathcs.jtransforms.fft.FloatFFT_1D;
|
||||
|
||||
/**
|
||||
* 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<Float[]>,
|
||||
DFTResultsProvider,
|
||||
FrequencyChangeListener
|
||||
{
|
||||
private CopyOnWriteArrayList<DFTResultsListener> mListeners =
|
||||
new CopyOnWriteArrayList<DFTResultsListener>();
|
||||
|
||||
private ArrayBlockingQueue<Float[]> mQueue =
|
||||
new ArrayBlockingQueue<Float[]>( 200 );
|
||||
|
||||
private ScheduledExecutorService mScheduler =
|
||||
Executors.newScheduledThreadPool(1);
|
||||
|
||||
private FFTWidth mFFTWidth = FFTWidth.FFT04096;
|
||||
private FFTWidth mNewFFTWidth = FFTWidth.FFT04096;
|
||||
|
||||
private double[] mWindow;
|
||||
private WindowType mWindowType = Window.WindowType.HANNING;
|
||||
|
||||
private FloatFFT_1D mFFT = new FloatFFT_1D( mFFTWidth.getWidth() );
|
||||
|
||||
private int mFrameRate;
|
||||
private int mSampleRate;
|
||||
private float mOverlapPerFrame;
|
||||
private float mOverlapResidual;
|
||||
private float[] mPreviousFrame = new float[ 8192 ];
|
||||
|
||||
private Float[] mCurrentBuffer;
|
||||
private int mCurrentBufferPointer = 0;
|
||||
|
||||
private SampleType mSampleType;
|
||||
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
|
||||
public DFTProcessor( SampleType sampleType )
|
||||
{
|
||||
setSampleType( sampleType );
|
||||
setFrameRate( 20 );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
clearScheduler();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
Log.error( "DFTProcessor - exception shutting down processor - " +
|
||||
e.getLocalizedMessage() );
|
||||
}
|
||||
|
||||
mScheduler.shutdown();
|
||||
|
||||
mListeners.clear();
|
||||
mQueue.clear();
|
||||
mWindow = null;
|
||||
mCurrentBuffer = null;
|
||||
}
|
||||
|
||||
public WindowType getWindowType()
|
||||
{
|
||||
return mWindowType;
|
||||
}
|
||||
|
||||
public void setWindowType( WindowType windowType )
|
||||
{
|
||||
mWindowType = windowType;
|
||||
|
||||
if( mSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
mWindow = Window.getWindow( mWindowType,
|
||||
mFFTWidth.getWidth() * 2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
mWindow = Window.getWindow( mWindowType,
|
||||
mFFTWidth.getWidth() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the processor mode to Float or Complex, depending on the sample
|
||||
* types that will be delivered for processing
|
||||
*/
|
||||
public void setSampleType( SampleType type )
|
||||
{
|
||||
mSampleType = type;
|
||||
setWindowType( mWindowType );
|
||||
}
|
||||
|
||||
public Source.SampleType getSampleType()
|
||||
{
|
||||
return mSampleType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues an FFT size change request. The scheduled executor will apply
|
||||
* the change when it runs.
|
||||
*/
|
||||
public void setFFTSize( FFTWidth width )
|
||||
{
|
||||
mNewFFTWidth = width;
|
||||
}
|
||||
|
||||
public FFTWidth getFFTWidth()
|
||||
{
|
||||
return mFFTWidth;
|
||||
}
|
||||
|
||||
public int getFrameRate()
|
||||
{
|
||||
return mFrameRate;
|
||||
}
|
||||
|
||||
public void setFrameRate( int framesPerSecond )
|
||||
{
|
||||
//TODO: make sure frame rate & sample rate sample requirement doesn't
|
||||
//expect overlap greater than the previous frame length
|
||||
|
||||
if( framesPerSecond < 1 || framesPerSecond > 1000 )
|
||||
{
|
||||
throw new IllegalArgumentException( "DFTProcessor cannot run "
|
||||
+ "more than 1000 times per second -- requested setting:"
|
||||
+ framesPerSecond );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFrameRate = framesPerSecond;
|
||||
|
||||
calculateOverlap();
|
||||
|
||||
/**
|
||||
* Shutdown the scheduler and clear out any remaining tasks
|
||||
*/
|
||||
try
|
||||
{
|
||||
clearScheduler();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
Log.error( "DFTProcessor - exception while awaiting shutdown of "
|
||||
+ "calculation scheduler for reset" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the scheduler
|
||||
*/
|
||||
mScheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
/**
|
||||
* Schedule the DFT to run calculations at a fixed rate
|
||||
*/
|
||||
int initialDelay = 0;
|
||||
int period = (int)( 1000 / mFrameRate );
|
||||
TimeUnit unit = TimeUnit.MILLISECONDS;
|
||||
|
||||
mScheduler.scheduleAtFixedRate( new DFTCalculationTask(),
|
||||
initialDelay, period, unit );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the scheduler and blocks up to 100 milliseconds until all
|
||||
* tasks have completed.
|
||||
*/
|
||||
private void clearScheduler() throws InterruptedException
|
||||
{
|
||||
mScheduler.shutdown();
|
||||
|
||||
mScheduler.awaitTermination( 100, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public int getCalculationsPerSecond()
|
||||
{
|
||||
return mFrameRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the sample into a transfer queue for future processing.
|
||||
*/
|
||||
@Override
|
||||
public void receive( Float[] samples )
|
||||
{
|
||||
if( !mQueue.offer( samples ) )
|
||||
{
|
||||
Log.error( "DFTProcessor - queue is full, purging queue, samples[" + samples + "]" );
|
||||
mQueue.clear();
|
||||
mQueue.offer( samples );
|
||||
}
|
||||
}
|
||||
|
||||
private void getNextBuffer()
|
||||
{
|
||||
mCurrentBuffer = null;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try
|
||||
{
|
||||
mCurrentBuffer = mQueue.take();
|
||||
}
|
||||
catch ( InterruptedException e )
|
||||
{
|
||||
mCurrentBuffer = null;
|
||||
}
|
||||
|
||||
mCurrentBufferPointer = 0;
|
||||
}
|
||||
|
||||
private float[] getSamples()
|
||||
{
|
||||
int remaining;
|
||||
|
||||
if( mSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
remaining = mFFTWidth.getWidth() * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
remaining = mFFTWidth.getWidth();
|
||||
}
|
||||
|
||||
float[] currentFrame = new float[ mFFTWidth.getWidth() * 2 ];
|
||||
int currentFramePointer = 0;
|
||||
|
||||
/* Get the integer component of the overlap, leaving the fractional
|
||||
* residual for the next frame */
|
||||
float requiredOverlap = mOverlapPerFrame + mOverlapResidual;
|
||||
int overlap = (int)requiredOverlap;
|
||||
mOverlapResidual = requiredOverlap - overlap;
|
||||
|
||||
/* If positive overlap, we have to re-use samples from previous frame */
|
||||
if( overlap > 0 )
|
||||
{
|
||||
int previousFramePointer;
|
||||
int length;
|
||||
|
||||
if( mSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
length = overlap * 2;
|
||||
previousFramePointer = mPreviousFrame.length - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
length = overlap;
|
||||
previousFramePointer = (int)(mPreviousFrame.length / 2) - length;
|
||||
}
|
||||
|
||||
System.arraycopy( mPreviousFrame, previousFramePointer,
|
||||
currentFrame, currentFramePointer, length );
|
||||
|
||||
remaining -= length;
|
||||
currentFramePointer += length;
|
||||
}
|
||||
|
||||
/* If negative overlap, we have to throw away samples */
|
||||
else
|
||||
{
|
||||
if( mSampleType == SampleType.COMPLEX )
|
||||
{
|
||||
purge( -( overlap * 2 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
purge( -overlap );
|
||||
}
|
||||
}
|
||||
|
||||
while( mRunning.get() && remaining > 0 )
|
||||
{
|
||||
if( mCurrentBuffer == null ||
|
||||
mCurrentBufferPointer == mCurrentBuffer.length )
|
||||
{
|
||||
getNextBuffer();
|
||||
}
|
||||
|
||||
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
|
||||
|
||||
if( samplesAvailable >= remaining )
|
||||
{
|
||||
while( remaining > 0 )
|
||||
{
|
||||
currentFrame[ currentFramePointer ] =
|
||||
(float)mCurrentBuffer[ mCurrentBufferPointer ];
|
||||
|
||||
currentFramePointer++;
|
||||
mCurrentBufferPointer++;
|
||||
remaining --;
|
||||
}
|
||||
|
||||
mCurrentBufferPointer += remaining;
|
||||
remaining = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
while( samplesAvailable > 0 )
|
||||
{
|
||||
currentFrame[ currentFramePointer ] =
|
||||
(float)mCurrentBuffer[ mCurrentBufferPointer ];
|
||||
|
||||
currentFramePointer++;
|
||||
mCurrentBufferPointer++;
|
||||
samplesAvailable--;
|
||||
remaining--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If frames are overlapping, store current frame to use next time */
|
||||
if( mOverlapPerFrame > 0 )
|
||||
{
|
||||
mPreviousFrame = null;
|
||||
mPreviousFrame = Arrays.copyOf( currentFrame, currentFrame.length );
|
||||
}
|
||||
|
||||
return currentFrame;
|
||||
}
|
||||
|
||||
private void calculate()
|
||||
{
|
||||
float[] samples = getSamples();
|
||||
|
||||
Window.apply( mWindow, samples );
|
||||
|
||||
if( mSampleType == SampleType.FLOAT )
|
||||
{
|
||||
mFFT.realForwardFull( samples );
|
||||
}
|
||||
else
|
||||
{
|
||||
mFFT.complexForward( samples );
|
||||
}
|
||||
|
||||
dispatch( samples );
|
||||
}
|
||||
|
||||
|
||||
private void purge( int samplesToPurge )
|
||||
{
|
||||
if( samplesToPurge <= 0 )
|
||||
{
|
||||
throw new IllegalArgumentException( "DFTProcessor - cannot purge "
|
||||
+ "negative sample amount" );
|
||||
}
|
||||
|
||||
while( mRunning.get() && samplesToPurge > 0 )
|
||||
{
|
||||
if( mCurrentBuffer == null ||
|
||||
mCurrentBufferPointer == mCurrentBuffer.length )
|
||||
{
|
||||
getNextBuffer();
|
||||
}
|
||||
|
||||
int samplesAvailable = mCurrentBuffer.length - mCurrentBufferPointer;
|
||||
|
||||
if( samplesAvailable >= samplesToPurge )
|
||||
{
|
||||
mCurrentBufferPointer += samplesToPurge;
|
||||
samplesToPurge = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
samplesToPurge -= samplesAvailable;
|
||||
mCurrentBufferPointer = mCurrentBuffer.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a calculated DFT results set, reformats the data, and sends it
|
||||
* out to all registered listeners.
|
||||
*/
|
||||
private void dispatch( float[] results )
|
||||
{
|
||||
float dc = Math.abs( results[ 0 ] );
|
||||
|
||||
/* Ensure we have power in the DC power bin (bin 0), otherwise don't
|
||||
* dispatch the results */
|
||||
if( dc != 0 )
|
||||
{
|
||||
float[] processed = new float[ results.length / 2 ];
|
||||
|
||||
int half = results.length / 2;
|
||||
int quarter = results.length / 4;
|
||||
|
||||
for( int x = 2; x < half; x += 2 )
|
||||
{
|
||||
processed[ quarter + ( x / 2 ) ] =
|
||||
(float)( Math.log10(
|
||||
(double)( Math.abs( results[ x ] ) /
|
||||
dc ) ) );
|
||||
}
|
||||
|
||||
for( int x = 0; x < quarter - 1; x++ )
|
||||
{
|
||||
processed[ x ] =
|
||||
(float)( Math.log10(
|
||||
(double)( Math.abs( results[ half + ( ( x + 1 ) * 2 ) ] ) /
|
||||
dc ) ) );
|
||||
}
|
||||
|
||||
//Real bin n/2 -1
|
||||
processed[ quarter - 1 ] = (float)( Math.log10(
|
||||
(double)( Math.abs( results[ 1 ] ) / dc ) ) );
|
||||
|
||||
Iterator<DFTResultsListener> it = mListeners.iterator();
|
||||
|
||||
while( it.hasNext() )
|
||||
{
|
||||
it.next().receive( processed );
|
||||
}
|
||||
}
|
||||
|
||||
results = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener( DFTResultsListener listener )
|
||||
{
|
||||
mListeners.add( listener );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener( DFTResultsListener listener )
|
||||
{
|
||||
mListeners.remove( listener );
|
||||
}
|
||||
|
||||
private class DFTCalculationTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
/* Only run if we're not currently running */
|
||||
if( mRunning.compareAndSet( false, true ) )
|
||||
{
|
||||
checkFFTSize();
|
||||
|
||||
calculate();
|
||||
|
||||
mRunning.set( false );
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.error( "DFTProcessor - calculation already in progress"
|
||||
+ " - skipping this iteration" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a queued FFT width change request and applies it. This
|
||||
* method will only be accessed by the scheduled executor that gains
|
||||
* access to run a calculate method, thus providing thread safety.
|
||||
*/
|
||||
private void checkFFTSize()
|
||||
{
|
||||
if( mNewFFTWidth.getWidth() != mFFTWidth.getWidth() )
|
||||
{
|
||||
mFFTWidth = mNewFFTWidth;
|
||||
|
||||
calculateOverlap();
|
||||
|
||||
mWindow = Window.getWindow( WindowType.HANNING,
|
||||
( mFFTWidth.getWidth() * 2 ) );
|
||||
|
||||
mPreviousFrame = new float[ mFFTWidth.getWidth() * 2 ];
|
||||
|
||||
mFFT = new FloatFFT_1D( mFFTWidth.getWidth() );
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBuffer()
|
||||
{
|
||||
mQueue.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frequencyChanged( int frequency, int bandwidth )
|
||||
{
|
||||
mSampleRate = bandwidth;
|
||||
|
||||
calculateOverlap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive overlap means we have to re-use samples from the previous
|
||||
* frame
|
||||
*
|
||||
* Negative overlap means we have to throw away samples
|
||||
*/
|
||||
private void calculateOverlap()
|
||||
{
|
||||
mOverlapPerFrame = 0.0f;
|
||||
mOverlapResidual = 0.0f;
|
||||
|
||||
int requiredSampleRate = mFFTWidth.getWidth() * mFrameRate;
|
||||
|
||||
int overlap = requiredSampleRate - mSampleRate;
|
||||
|
||||
mOverlapPerFrame = (float)overlap / (float)mFrameRate;
|
||||
}
|
||||
}
|
||||
26
src/spectrum/DFTResultsListener.java
Normal file
26
src/spectrum/DFTResultsListener.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
/**
|
||||
* Interface for passing the output of DFT processing
|
||||
*/
|
||||
public interface DFTResultsListener
|
||||
{
|
||||
public void receive( float[] results );
|
||||
}
|
||||
25
src/spectrum/DFTResultsProvider.java
Normal file
25
src/spectrum/DFTResultsProvider.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
public interface DFTResultsProvider
|
||||
{
|
||||
public void addListener( DFTResultsListener listener );
|
||||
|
||||
public void removeListener( DFTResultsListener listener );
|
||||
}
|
||||
46
src/spectrum/FFTWidth.java
Normal file
46
src/spectrum/FFTWidth.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
public enum FFTWidth
|
||||
{
|
||||
FFT00512( 512 ),
|
||||
FFT01024( 1024 ),
|
||||
FFT02048( 2048 ),
|
||||
FFT04096( 4096 ),
|
||||
FFT08192( 8192 ),
|
||||
FFT16384( 16384 ),
|
||||
FFT32768( 32768 );
|
||||
|
||||
private int mWidth;
|
||||
|
||||
private FFTWidth( int width )
|
||||
{
|
||||
mWidth = width;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public String getLabel()
|
||||
{
|
||||
return String.valueOf( mWidth );
|
||||
}
|
||||
}
|
||||
590
src/spectrum/OverlayPanel.java
Normal file
590
src/spectrum/OverlayPanel.java
Normal file
@@ -0,0 +1,590 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Point;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import source.tuner.FrequencyChangeListener;
|
||||
import source.tuner.TunerChannel;
|
||||
import controller.ResourceManager;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.Channel.ChannelEvent;
|
||||
import controller.channel.Channel.ChannelType;
|
||||
import controller.channel.ChannelListener;
|
||||
|
||||
public class OverlayPanel extends JPanel
|
||||
implements ChannelListener,
|
||||
FrequencyChangeListener,
|
||||
SettingChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static DecimalFormat sFORMAT = new DecimalFormat( "000.000" );
|
||||
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" );
|
||||
private int mFrequency = 0;
|
||||
private int mBandwidth = 0;
|
||||
private Point mCursorLocation = new Point( 0, 0 );
|
||||
private boolean mCursorVisible = false;
|
||||
|
||||
/**
|
||||
* Colors used by this component
|
||||
*/
|
||||
private Color mColorChannelConfig;
|
||||
private Color mColorChannelConfigProcessing;
|
||||
private Color mColorChannelConfigSelected;
|
||||
private Color mColorSpectrumBackground;
|
||||
private Color mColorSpectrumCursor;
|
||||
private Color mColorSpectrumLine;
|
||||
|
||||
//All channels
|
||||
private ArrayList<Channel> mChannels = new ArrayList<Channel>();
|
||||
|
||||
//Currently visible/displayable channels
|
||||
private CopyOnWriteArrayList<Channel> mVisibleChannels =
|
||||
new CopyOnWriteArrayList<Channel>();
|
||||
|
||||
//Defines the offset at the bottom of the spectral display to account for
|
||||
//the frequency labels
|
||||
private float mSpectrumInset = 20.0f;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
|
||||
/**
|
||||
* Translucent overlay panel for displaying channel configurations,
|
||||
* processing channels, selected channels, frequency labels and lines, and
|
||||
* a cursor with a frequency readout.
|
||||
*/
|
||||
public OverlayPanel( ResourceManager resourceManager )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
//Set the background transparent, so the spectrum display can be seen
|
||||
setOpaque( false );
|
||||
|
||||
//Fetch color settings from settings manager
|
||||
setColors();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mChannels.clear();
|
||||
mVisibleChannels.clear();
|
||||
|
||||
mResourceManager = null;
|
||||
}
|
||||
|
||||
public void setCursorLocation( Point point )
|
||||
{
|
||||
mCursorLocation = point;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setCursorVisible( boolean visible )
|
||||
{
|
||||
mCursorVisible = visible;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the color settings from the settings manager
|
||||
*/
|
||||
private void setColors()
|
||||
{
|
||||
mColorChannelConfig = getColor( ColorSettingName.CHANNEL_CONFIG );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors for setting changes. Colors can be changed by external actions
|
||||
* and will automatically update in this class
|
||||
*/
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
switch( colorSetting.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_LINE:
|
||||
mColorSpectrumLine = colorSetting.getColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the channel configs, lines, labels, and cursor
|
||||
*/
|
||||
@Override
|
||||
public void paintComponent( Graphics g )
|
||||
{
|
||||
super.paintComponent( g );
|
||||
|
||||
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 );
|
||||
|
||||
drawFrequencies( graphics );
|
||||
drawChannels( graphics );
|
||||
drawCursor( graphics );
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a cursor on the panel, whenever the mouse is hovering over the
|
||||
* panel
|
||||
*/
|
||||
private void drawCursor( Graphics2D graphics )
|
||||
{
|
||||
if( mCursorVisible )
|
||||
{
|
||||
drawFrequencyLine( graphics,
|
||||
mCursorLocation.x,
|
||||
mColorSpectrumCursor );
|
||||
|
||||
String frequency = sCURSOR_FORMAT.format(
|
||||
getFrequencyFromAxis( mCursorLocation.getX() ) / 1000000.0D );
|
||||
|
||||
graphics.drawString( frequency ,
|
||||
mCursorLocation.x + 5,
|
||||
mCursorLocation.y );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the frequency lines and labels every 10kHz
|
||||
*/
|
||||
private void drawFrequencies( Graphics2D graphics )
|
||||
{
|
||||
int minFrequency = getMinFrequency();
|
||||
int maxFrequency = getMaxFrequency();
|
||||
|
||||
//TODO: the increment should be changeable according to the overall bandwidth
|
||||
// so that when we move to higher sample rates, the display reacts accordingly
|
||||
|
||||
int frequency;
|
||||
|
||||
if( mBandwidth < 200000 )
|
||||
{
|
||||
//Start with the first diplayable frequency (ie whole 10kHz increment)
|
||||
frequency = minFrequency - ( minFrequency % 10000 );
|
||||
frequency += 10000;
|
||||
|
||||
//Draw a line and label at each 10kHz interval
|
||||
while( frequency < maxFrequency )
|
||||
{
|
||||
drawFrequencyLineAndLabel( graphics, frequency );
|
||||
|
||||
frequency += 10000;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Start with the first diplayable frequency (ie whole 100kHz increment)
|
||||
frequency = minFrequency - ( minFrequency % 100000 );
|
||||
frequency += 100000;
|
||||
|
||||
//Draw a line and label at each 10kHz interval
|
||||
while( frequency < maxFrequency )
|
||||
{
|
||||
drawFrequencyLineAndLabel( graphics, frequency );
|
||||
|
||||
frequency += 100000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a vertical line and a corresponding frequency label at the bottom
|
||||
*/
|
||||
private void drawFrequencyLineAndLabel( Graphics2D graphics, int frequency )
|
||||
{
|
||||
float xAxis = getAxisFromFrequency( frequency );
|
||||
|
||||
drawFrequencyLine( graphics, xAxis, mColorSpectrumLine );
|
||||
|
||||
graphics.setColor( mColorSpectrumLine );
|
||||
|
||||
drawFrequencyLabel( graphics, xAxis, frequency );
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a vertical line at the xaxis
|
||||
*/
|
||||
private void drawFrequencyLine( Graphics2D graphics, float xaxis, Color color )
|
||||
{
|
||||
graphics.setColor( color );
|
||||
|
||||
graphics.draw( new Line2D.Float( xaxis, 0,
|
||||
xaxis, (float)(getSize().getHeight()) - mSpectrumInset ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x-axis value corresponding to the frequency
|
||||
*/
|
||||
public float getAxisFromFrequency( int frequency )
|
||||
{
|
||||
float canvasMiddle = (float)getSize().getWidth() / 2;
|
||||
|
||||
//Determine frequency offset from middle
|
||||
int frequencyOffset = mFrequency - frequency;
|
||||
|
||||
//Determine ratio of offset to bandwidth
|
||||
float ratio = (float)frequencyOffset / (float)mBandwidth;
|
||||
|
||||
//Calculate offset against the total width
|
||||
float xOffset = (float)getSize().getWidth() * ratio;
|
||||
|
||||
//Apply the offset against the canvas middle
|
||||
return canvasMiddle - xOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the frequency corresponding to the x-axis value
|
||||
*/
|
||||
public int getFrequencyFromAxis( double xAxis )
|
||||
{
|
||||
float width = (float)getSize().getWidth();
|
||||
|
||||
double offset = xAxis / width;
|
||||
|
||||
return getMinFrequency() + (int)( mBandwidth * offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a frequency label at the x-axis position, at the bottom of the panel
|
||||
*/
|
||||
private void drawFrequencyLabel( Graphics2D graphics,
|
||||
float xaxis,
|
||||
int frequency )
|
||||
{
|
||||
String label = sFORMAT.format( (float)frequency / 1000000.0f );
|
||||
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics( this.getFont() );
|
||||
|
||||
Rectangle2D rect = fontMetrics.getStringBounds( label, graphics );
|
||||
|
||||
float offset = (float)rect.getWidth() / 2;
|
||||
|
||||
graphics.drawString( label, xaxis - offset,
|
||||
(float)getSize().getHeight() - ( mSpectrumInset * 0.2f ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws visible channel configs as translucent shaded frequency regions
|
||||
*/
|
||||
private void drawChannels( Graphics2D graphics )
|
||||
{
|
||||
for( Channel channel: mVisibleChannels )
|
||||
{
|
||||
//Choose the correct background color to use
|
||||
if( channel.getSelected() )
|
||||
{
|
||||
graphics.setColor( mColorChannelConfigSelected );
|
||||
}
|
||||
else if( channel.isProcessing() )
|
||||
{
|
||||
graphics.setColor( mColorChannelConfigProcessing );
|
||||
}
|
||||
else
|
||||
{
|
||||
graphics.setColor( mColorChannelConfig );
|
||||
}
|
||||
|
||||
TunerChannel tunerChannel = channel.getTunerChannel();
|
||||
|
||||
if( tunerChannel != null )
|
||||
{
|
||||
float xAxis = getAxisFromFrequency(
|
||||
(int)( tunerChannel.getFrequency() ) );
|
||||
|
||||
float width = (float)( tunerChannel.getBandwidth() ) /
|
||||
(float)mBandwidth * (float)getSize().getWidth();
|
||||
|
||||
Rectangle2D.Float box =
|
||||
new Rectangle2D.Float( xAxis - ( width / 2 ),
|
||||
0,
|
||||
width,
|
||||
(float)( getSize().getHeight() -
|
||||
mSpectrumInset ) );
|
||||
|
||||
//Fill the box with the correct color
|
||||
graphics.fill( box );
|
||||
|
||||
graphics.draw( box );
|
||||
|
||||
//Change to the line color to render the channel name, etc.
|
||||
graphics.setColor( mColorSpectrumLine );
|
||||
|
||||
//Draw the labels starting at yAxis position 0
|
||||
float yAxis = 0;
|
||||
|
||||
//Draw the system label and adjust the y-axis position
|
||||
yAxis += drawLabel( graphics,
|
||||
channel.getSystem().getName(),
|
||||
this.getFont(),
|
||||
xAxis,
|
||||
yAxis,
|
||||
width );
|
||||
|
||||
//Draw the site label and adjust the y-axis position
|
||||
yAxis += drawLabel( graphics,
|
||||
channel.getSite().getName(),
|
||||
this.getFont(),
|
||||
xAxis,
|
||||
yAxis,
|
||||
width );
|
||||
|
||||
//Draw the channel label and adjust the y-axis position
|
||||
yAxis += drawLabel( graphics,
|
||||
channel.getName(),
|
||||
this.getFont(),
|
||||
xAxis,
|
||||
yAxis,
|
||||
width );
|
||||
|
||||
//Draw the decoder label
|
||||
drawLabel( graphics,
|
||||
channel.getDecodeConfiguration().getDecoderType()
|
||||
.getShortDisplayString(),
|
||||
this.getFont(),
|
||||
xAxis,
|
||||
yAxis,
|
||||
width );
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Draws a textual label at the x/y position, clipping the end of the text
|
||||
* to fit within the maxwidth value.
|
||||
*
|
||||
* @return height of the drawn label
|
||||
*/
|
||||
private float drawLabel( Graphics2D graphics, String text, Font font,
|
||||
float x, float baseY, float maxWidth )
|
||||
{
|
||||
FontMetrics fontMetrics = graphics.getFontMetrics( font );
|
||||
|
||||
Rectangle2D label = fontMetrics.getStringBounds( text, graphics );
|
||||
|
||||
float offset = (float)label.getWidth() / 2;
|
||||
float y = baseY + (float)label.getHeight();
|
||||
|
||||
/**
|
||||
* If the label is wider than the max width, left justify the text and
|
||||
* clip the end of it
|
||||
*/
|
||||
if( offset > ( maxWidth / 2 ) )
|
||||
{
|
||||
label.setRect( x - ( maxWidth / 2 ),
|
||||
y - label.getHeight(),
|
||||
maxWidth,
|
||||
label.getHeight() ); //* 2
|
||||
|
||||
graphics.setClip( label );
|
||||
|
||||
graphics.drawString( text, x - ( maxWidth / 2 ), y );
|
||||
|
||||
graphics.setClip( null );
|
||||
}
|
||||
else
|
||||
{
|
||||
graphics.drawString( text, x - offset, y );
|
||||
}
|
||||
|
||||
return (float)label.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency change event handler
|
||||
*/
|
||||
@Override
|
||||
public void frequencyChanged( int frequency, int bandwidth )
|
||||
{
|
||||
mBandwidth = bandwidth;
|
||||
mFrequency = frequency;
|
||||
|
||||
/**
|
||||
* Reset the visible channel configs list
|
||||
*/
|
||||
mVisibleChannels.clear();
|
||||
|
||||
int minimum = getMinFrequency();
|
||||
int maximum = getMaxFrequency();
|
||||
|
||||
for( Channel channel: mChannels )
|
||||
{
|
||||
if( channel.isWithin( minimum, maximum ) )
|
||||
{
|
||||
mVisibleChannels.add( channel );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel change event handler
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings( "incomplete-switch" )
|
||||
public void occurred( Channel channel, ChannelEvent component )
|
||||
{
|
||||
switch( component )
|
||||
{
|
||||
case CHANNEL_ADDED:
|
||||
if( !mChannels.contains( channel ) )
|
||||
{
|
||||
mChannels.add( channel );
|
||||
}
|
||||
|
||||
if( channel.isWithin( getMinFrequency(), getMaxFrequency() ) &&
|
||||
!mVisibleChannels.contains( channel ) )
|
||||
{
|
||||
mVisibleChannels.add( channel );
|
||||
}
|
||||
break;
|
||||
case CHANNEL_DELETED:
|
||||
mChannels.remove( channel );
|
||||
mVisibleChannels.remove( channel );
|
||||
break;
|
||||
case PROCESSING_STOPPED:
|
||||
if( channel.getChannelType() == ChannelType.TRAFFIC )
|
||||
{
|
||||
mChannels.remove( channel );
|
||||
mVisibleChannels.remove( channel );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently displayed minimum frequency
|
||||
*/
|
||||
private int getMinFrequency()
|
||||
{
|
||||
return mFrequency - ( mBandwidth / 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently displayed maximum frequency
|
||||
*/
|
||||
private int getMaxFrequency()
|
||||
{
|
||||
return mFrequency + ( mBandwidth / 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of channel configs that contain the frequency within their
|
||||
* min/max frequency settings.
|
||||
*/
|
||||
public ArrayList<Channel> getChannelsAtFrequency( int frequency )
|
||||
{
|
||||
ArrayList<Channel> configs = new ArrayList<Channel>();
|
||||
|
||||
for( Channel config: mVisibleChannels )
|
||||
{
|
||||
TunerChannel channel = config.getTunerChannel();
|
||||
|
||||
if( channel != null &&
|
||||
channel.getMinFrequency() <= frequency &&
|
||||
channel.getMaxFrequency() >= frequency )
|
||||
{
|
||||
configs.add( config );
|
||||
}
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting )
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
29
src/spectrum/Pausable.java
Normal file
29
src/spectrum/Pausable.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
/**
|
||||
* Interface to add pause action to moving displays like the spectrum and
|
||||
* waterfall panels
|
||||
*/
|
||||
public interface Pausable
|
||||
{
|
||||
public void setPaused( boolean paused );
|
||||
|
||||
public boolean isPaused();
|
||||
}
|
||||
785
src/spectrum/SpectralDisplayPanel.java
Normal file
785
src/spectrum/SpectralDisplayPanel.java
Normal file
@@ -0,0 +1,785 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JLayeredPane;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JSeparator;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import log.Log;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import sample.Listener;
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.ColorSettingMenuItem;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import settings.SettingsManager;
|
||||
import source.Source.SampleType;
|
||||
import source.SourceException;
|
||||
import source.tuner.FrequencyChangeListener;
|
||||
import source.tuner.Tuner;
|
||||
import source.tuner.TunerSelectionListener;
|
||||
|
||||
import com.jidesoft.swing.JideSplitPane;
|
||||
|
||||
import controller.ResourceManager;
|
||||
import controller.channel.Channel;
|
||||
import controller.channel.Channel.ChannelEvent;
|
||||
import controller.channel.ChannelListener;
|
||||
import decode.DecoderType;
|
||||
import dsp.filter.Window.WindowType;
|
||||
|
||||
/**
|
||||
* Spectral Display Panel - comprises a DFTProcessor coupled with a waterfall
|
||||
* display and a spectrum display, with an overlay of frequency data and channel
|
||||
* data.
|
||||
*
|
||||
* Processes float or complex sample data and displays the frequency content
|
||||
* in the spectral and waterfall displays.
|
||||
*/
|
||||
public class SpectralDisplayPanel extends JPanel
|
||||
implements ChannelListener,
|
||||
Listener<Float[]>,
|
||||
FrequencyChangeListener,
|
||||
SettingChangeListener,
|
||||
TunerSelectionListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
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 JLayeredPane mLayeredPanel;
|
||||
private SpectrumPanel mSpectrumPanel;
|
||||
private WaterfallPanel mWaterfallPanel;
|
||||
private OverlayPanel mChannelPanel;
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private ResourceManager mResourceManager;
|
||||
private Tuner mTuner;
|
||||
|
||||
public SpectralDisplayPanel( ResourceManager resourceManager )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
setColors();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
/* De-register from receiving samples when the window closes */
|
||||
clearTuner();
|
||||
|
||||
mResourceManager.getChannelManager().removeListener( this );
|
||||
|
||||
mDFTProcessor.dispose();
|
||||
mDFTProcessor = null;
|
||||
|
||||
mSpectrumPanel.dispose();
|
||||
mSpectrumPanel = null;
|
||||
|
||||
mWaterfallPanel.dispose();
|
||||
mWaterfallPanel = null;
|
||||
|
||||
mChannelPanel.dispose();
|
||||
mChannelPanel = null;
|
||||
|
||||
mResourceManager = null;
|
||||
|
||||
mTuner = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the color settings from the settings manager
|
||||
*/
|
||||
private void setColors()
|
||||
{
|
||||
mColorChannelConfig = getColor( ColorSettingName.CHANNEL_CONFIG );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors for setting changes. Colors can be changed by external actions
|
||||
* and will automatically update in this class
|
||||
*/
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
if( mChannelPanel != null )
|
||||
{
|
||||
mChannelPanel.settingChanged( setting );
|
||||
}
|
||||
|
||||
if( mWaterfallPanel != null )
|
||||
{
|
||||
mWaterfallPanel.settingChanged( setting );
|
||||
}
|
||||
|
||||
if( mSpectrumPanel != null )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides JComponent method to return false, since we have overlapping
|
||||
* panels with the spectrum and channel panels
|
||||
*/
|
||||
public boolean isOptimizedDrawingEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow]", "[grow]") );
|
||||
|
||||
//Register for any future settings (ie color) changes
|
||||
mResourceManager.getSettingsManager().addListener( this );
|
||||
|
||||
/**
|
||||
* The layered pane holds the overlapping spectrum and channel panels
|
||||
* and manages the sizing of each panel with the resize listener
|
||||
*/
|
||||
mLayeredPanel = new JLayeredPane();
|
||||
mLayeredPanel.addComponentListener( new ResizeListener() );
|
||||
|
||||
/**
|
||||
* Create a mouse adapter to handle mouse events over the spectrum
|
||||
* and waterfall panels
|
||||
*/
|
||||
MouseEventProcessor mouser = new MouseEventProcessor();
|
||||
|
||||
//Create the spectrum panel
|
||||
mSpectrumPanel = new SpectrumPanel( mResourceManager );
|
||||
|
||||
//Create the channel panel
|
||||
mChannelPanel = new OverlayPanel( mResourceManager );
|
||||
mChannelPanel.addMouseListener( mouser );
|
||||
mChannelPanel.addMouseMotionListener( mouser );
|
||||
|
||||
//Add the spectrum and channel panels to the layered panel
|
||||
mLayeredPanel.add( mSpectrumPanel, new Integer( 0 ), 0 );
|
||||
mLayeredPanel.add( mChannelPanel, new Integer( 1 ), 0 );
|
||||
|
||||
//Create the waterfall
|
||||
mWaterfallPanel = new WaterfallPanel( mResourceManager );
|
||||
mWaterfallPanel.addMouseListener( mouser );
|
||||
mWaterfallPanel.addMouseMotionListener( mouser );
|
||||
|
||||
/* Attempt to set a 50/50 split preferred size for the split pane */
|
||||
double totalHeight = mLayeredPanel.getPreferredSize().getHeight() +
|
||||
mWaterfallPanel.getPreferredSize().getHeight();
|
||||
|
||||
mLayeredPanel.setPreferredSize( new Dimension( (int)mLayeredPanel
|
||||
.getPreferredSize().getWidth(), (int)( totalHeight / 2.0d ) ) );
|
||||
|
||||
mWaterfallPanel.setPreferredSize( new Dimension( (int)mWaterfallPanel
|
||||
.getPreferredSize().getWidth(), (int)( totalHeight / 2.0d ) ) );
|
||||
|
||||
//Create the split pane to hold the layered pane and the waterfall
|
||||
JideSplitPane splitPane = new JideSplitPane( JSplitPane.VERTICAL_SPLIT );
|
||||
splitPane.setDividerSize( 5 );
|
||||
splitPane.add( mLayeredPanel );
|
||||
splitPane.add( mWaterfallPanel );
|
||||
|
||||
add( splitPane, "grow" );
|
||||
|
||||
/**
|
||||
* Setup DFTProcessor to process samples and register the waterfall and
|
||||
* spectrum panel to receive the processed dft results
|
||||
*/
|
||||
mDFTProcessor = new DFTProcessor( SampleType.COMPLEX );
|
||||
mDFTProcessor.addListener( (DFTResultsListener)mSpectrumPanel );
|
||||
mDFTProcessor.addListener( (DFTResultsListener)mWaterfallPanel );
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives frequency change events -- primarily from tuner components.
|
||||
*/
|
||||
@Override
|
||||
public void frequencyChanged( int frequency, int bandwidth )
|
||||
{
|
||||
mChannelPanel.frequencyChanged( frequency, bandwidth );
|
||||
mDFTProcessor.frequencyChanged( frequency, bandwidth );
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel config change events
|
||||
*/
|
||||
@Override
|
||||
public void occurred( Channel config, ChannelEvent component )
|
||||
{
|
||||
mChannelPanel.occurred( config, component );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( Float[] samples )
|
||||
{
|
||||
mDFTProcessor.receive( samples );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner selected events
|
||||
*/
|
||||
@Override
|
||||
public void tunerSelected( Tuner tuner )
|
||||
{
|
||||
clearTuner();
|
||||
|
||||
mDFTProcessor.clearBuffer();
|
||||
|
||||
mTuner = tuner;
|
||||
|
||||
if( mTuner != null )
|
||||
{
|
||||
//Register to receive frequency change events
|
||||
mTuner.addListener( (FrequencyChangeListener)this );
|
||||
|
||||
//Register the dft processor to receive samples from the tuner
|
||||
mTuner.addListener( (Listener<Float[]>)mDFTProcessor );
|
||||
|
||||
//Fire a frequency change event so that everyone can init
|
||||
try
|
||||
{
|
||||
frequencyChanged( mTuner.getFrequency(),
|
||||
mTuner.getSampleRate() );
|
||||
}
|
||||
catch ( SourceException e )
|
||||
{
|
||||
Log.info( "DFTProcessor - exception during new tuner setup - "
|
||||
+ "couldn't get frequency from the tuner" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuner de-selection cleanup method
|
||||
*/
|
||||
public void clearTuner()
|
||||
{
|
||||
if( mTuner != null )
|
||||
{
|
||||
//Deregister for frequency change events from the tuner
|
||||
mTuner.removeListener( (FrequencyChangeListener)this );
|
||||
|
||||
//Deregister the dft processor from receiving samples
|
||||
mTuner.removeListener( (Listener<Float[]>)mDFTProcessor );
|
||||
mTuner = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the sizing of the layered pane and resizes the spectrum and
|
||||
* channel panels whenever the layered pane is resized
|
||||
*/
|
||||
public class ResizeListener implements ComponentListener
|
||||
{
|
||||
@Override
|
||||
public void componentResized( ComponentEvent e )
|
||||
{
|
||||
Component c = e.getComponent();
|
||||
|
||||
mSpectrumPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() );
|
||||
mChannelPanel.setBounds( 0, 0, c.getWidth(), c.getHeight() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden( ComponentEvent arg0 ) {}
|
||||
@Override
|
||||
public void componentMoved( ComponentEvent arg0 ) {}
|
||||
@Override
|
||||
public void componentShown( ComponentEvent arg0 ) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse event handler for the channel panel.
|
||||
*/
|
||||
public class MouseEventProcessor implements MouseMotionListener, MouseListener
|
||||
{
|
||||
|
||||
@Override
|
||||
public void mouseMoved( MouseEvent event )
|
||||
{
|
||||
update( event );
|
||||
}
|
||||
@Override
|
||||
public void mouseDragged( MouseEvent event )
|
||||
{
|
||||
update( event );
|
||||
}
|
||||
|
||||
private void update( MouseEvent event )
|
||||
{
|
||||
if( event.getComponent() == mChannelPanel )
|
||||
{
|
||||
mChannelPanel.setCursorLocation( event.getPoint() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mWaterfallPanel.setCursorLocation( event.getPoint() );
|
||||
mWaterfallPanel.setCursorFrequency(
|
||||
mChannelPanel.getFrequencyFromAxis( event.getPoint().x ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered( MouseEvent e )
|
||||
{
|
||||
if( e.getComponent() == mChannelPanel )
|
||||
{
|
||||
mChannelPanel.setCursorVisible( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
mWaterfallPanel.setCursorVisible( true );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited( MouseEvent e )
|
||||
{
|
||||
mChannelPanel.setCursorVisible( false );
|
||||
mWaterfallPanel.setCursorVisible( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the context menu.
|
||||
*/
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent event )
|
||||
{
|
||||
if( SwingUtilities.isRightMouseButton( event ) )
|
||||
{
|
||||
JPopupMenu contextMenu = new JPopupMenu();
|
||||
|
||||
if( event.getComponent() == mWaterfallPanel )
|
||||
{
|
||||
contextMenu.add( new PauseItem( mWaterfallPanel, "Pause" ) );
|
||||
}
|
||||
|
||||
int frequency =
|
||||
mChannelPanel.getFrequencyFromAxis( event.getX() );
|
||||
|
||||
if( event.getComponent() == mChannelPanel )
|
||||
{
|
||||
ArrayList<Channel> channels =
|
||||
mChannelPanel.getChannelsAtFrequency( frequency );
|
||||
|
||||
for( Channel channel: channels )
|
||||
{
|
||||
JMenu menu = channel.getContextMenu();
|
||||
|
||||
if( menu != null )
|
||||
{
|
||||
contextMenu.add( menu );
|
||||
}
|
||||
}
|
||||
|
||||
if( !channels.isEmpty() )
|
||||
{
|
||||
contextMenu.add( new JSeparator() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JMenu frequencyMenu = new JMenu(
|
||||
sCURSOR_FORMAT.format( (float)frequency / 1000000.0f ) );
|
||||
|
||||
JMenu decoderMenu = new JMenu( "Add Decoder" );
|
||||
|
||||
for( DecoderType type: DecoderType.getAvailableDecoders() )
|
||||
{
|
||||
decoderMenu.add( new DecoderItem( mResourceManager,
|
||||
frequency,
|
||||
type ) );
|
||||
|
||||
}
|
||||
|
||||
frequencyMenu.add( decoderMenu );
|
||||
|
||||
contextMenu.add( frequencyMenu );
|
||||
|
||||
contextMenu.add( new JSeparator() );
|
||||
|
||||
/**
|
||||
* Color Menus
|
||||
*/
|
||||
JMenu colorMenu = new JMenu( "Color" );
|
||||
|
||||
SettingsManager sm = mResourceManager.getSettingsManager();
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.CHANNEL_CONFIG ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.CHANNEL_CONFIG_PROCESSING ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.CHANNEL_CONFIG_SELECTED ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.SPECTRUM_CURSOR ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.SPECTRUM_LINE ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.SPECTRUM_BACKGROUND ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_BOTTOM ) );
|
||||
|
||||
colorMenu.add( new ColorSettingMenuItem( sm,
|
||||
ColorSettingName.SPECTRUM_GRADIENT_TOP ) );
|
||||
|
||||
contextMenu.add( colorMenu );
|
||||
|
||||
/**
|
||||
* Display items: fft and frame rate
|
||||
*/
|
||||
JMenu displayMenu = new JMenu( "Display" );
|
||||
contextMenu.add( displayMenu );
|
||||
|
||||
/**
|
||||
* FFT width
|
||||
*/
|
||||
JMenu fftWidthMenu = new JMenu( "FFT Width" );
|
||||
displayMenu.add( fftWidthMenu );
|
||||
|
||||
for( FFTWidth width: FFTWidth.values() )
|
||||
{
|
||||
fftWidthMenu.add( new FFTWidthItem( mDFTProcessor, width ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* DFT Processor Frame Rate
|
||||
*/
|
||||
JMenu frameRateMenu = new JMenu( "Frame Rate" );
|
||||
displayMenu.add( frameRateMenu );
|
||||
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 14 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 16 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 18 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 20 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 25 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 30 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 40 ) );
|
||||
frameRateMenu.add( new FrameRateItem( mDFTProcessor, 50 ) );
|
||||
|
||||
/**
|
||||
* FFT Window Type
|
||||
*/
|
||||
JMenu fftWindowType = new JMenu( "Window Type" );
|
||||
displayMenu.add( fftWindowType );
|
||||
|
||||
for( WindowType type: WindowType.values() )
|
||||
{
|
||||
fftWindowType.add(
|
||||
new FFTWindowTypeItem( mDFTProcessor, type ) );
|
||||
}
|
||||
|
||||
if( contextMenu != null )
|
||||
{
|
||||
if( event.getComponent() == mChannelPanel )
|
||||
{
|
||||
contextMenu.show( mChannelPanel,
|
||||
event.getX(),
|
||||
event.getY() );
|
||||
}
|
||||
else
|
||||
{
|
||||
contextMenu.show( mWaterfallPanel,
|
||||
event.getX(),
|
||||
event.getY() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed( MouseEvent e ) {}
|
||||
@Override
|
||||
public void mouseReleased( MouseEvent e ) {}
|
||||
}
|
||||
|
||||
public class PauseItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Pausable mPausable;
|
||||
|
||||
public PauseItem( Pausable pausable, String label )
|
||||
{
|
||||
super( label );
|
||||
|
||||
final boolean paused = pausable.isPaused();
|
||||
|
||||
setSelected( paused );
|
||||
|
||||
mPausable = pausable;
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mPausable.setPaused( !paused );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class FFTWidthItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private FFTWidth mFFTWidth;
|
||||
|
||||
public FFTWidthItem( DFTProcessor processor, FFTWidth width )
|
||||
{
|
||||
super( width.getLabel() );
|
||||
|
||||
mDFTProcessor = processor;
|
||||
mFFTWidth = width;
|
||||
|
||||
if( processor.getFFTWidth() == mFFTWidth )
|
||||
{
|
||||
setSelected( true );
|
||||
}
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
mDFTProcessor.setFFTSize( mFFTWidth );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class FFTWindowTypeItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private WindowType mWindowType;
|
||||
|
||||
public FFTWindowTypeItem( DFTProcessor processor, WindowType windowType )
|
||||
{
|
||||
super( windowType.toString() );
|
||||
|
||||
mDFTProcessor = processor;
|
||||
mWindowType = windowType;
|
||||
|
||||
if( processor.getWindowType() == mWindowType )
|
||||
{
|
||||
setSelected( true );
|
||||
}
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
mDFTProcessor.setWindowType( mWindowType );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameRateItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private DFTProcessor mDFTProcessor;
|
||||
private int mFrameRate;
|
||||
|
||||
public FrameRateItem( DFTProcessor processor, int frameRate )
|
||||
{
|
||||
super( String.valueOf( frameRate ) );
|
||||
|
||||
mDFTProcessor = processor;
|
||||
mFrameRate = frameRate;
|
||||
|
||||
if( processor.getFrameRate() == mFrameRate )
|
||||
{
|
||||
setSelected( true );
|
||||
}
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent arg0 )
|
||||
{
|
||||
mDFTProcessor.setFrameRate( mFrameRate );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Context menu item to provide one-click access to starting a channel
|
||||
* processing with the selected decoder
|
||||
*/
|
||||
public class DecoderItem extends JMenuItem
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
private int mFrequency;
|
||||
private DecoderType mDecoder;
|
||||
|
||||
public DecoderItem( ResourceManager manager, int frequency, DecoderType type )
|
||||
{
|
||||
super( type.getDisplayString() );
|
||||
|
||||
mResourceManager = manager;
|
||||
mFrequency = frequency;
|
||||
mDecoder = type;
|
||||
|
||||
addActionListener( new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e )
|
||||
{
|
||||
mResourceManager.getController()
|
||||
.createChannel( mFrequency, mDecoder );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting ) {}
|
||||
}
|
||||
84
src/spectrum/SpectrumFrame.java
Normal file
84
src/spectrum/SpectrumFrame.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.EventQueue;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import source.tuner.Tuner;
|
||||
import controller.ResourceManager;
|
||||
|
||||
public class SpectrumFrame extends JFrame implements WindowListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
private SpectralDisplayPanel mSpectralDisplayPanel;
|
||||
|
||||
public SpectrumFrame( ResourceManager resourceManager, Tuner tuner )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
setTitle( "SDRTRunk [" + tuner.getName() + "]" );
|
||||
setBounds( 100, 100, 1280, 600 );
|
||||
setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
||||
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow]", "[grow]") );
|
||||
|
||||
mSpectralDisplayPanel = new SpectralDisplayPanel( resourceManager );
|
||||
mSpectralDisplayPanel.tunerSelected( tuner );
|
||||
add( mSpectralDisplayPanel, "grow" );
|
||||
|
||||
/* Register spectral display on channel manager to get channel updates */
|
||||
mResourceManager.getChannelManager().addListener( mSpectralDisplayPanel );
|
||||
|
||||
/* Register a shutdown listener */
|
||||
this.addWindowListener( this );
|
||||
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
setVisible( true );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed( WindowEvent arg0 )
|
||||
{
|
||||
mSpectralDisplayPanel.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated( WindowEvent arg0 ) {}
|
||||
@Override
|
||||
public void windowClosing( WindowEvent arg0 ) {}
|
||||
@Override
|
||||
public void windowDeactivated( WindowEvent arg0 ) {}
|
||||
@Override
|
||||
public void windowDeiconified( WindowEvent arg0 ) {}
|
||||
@Override
|
||||
public void windowIconified( WindowEvent arg0 ) {}
|
||||
@Override
|
||||
public void windowOpened( WindowEvent arg0 ) {}
|
||||
}
|
||||
257
src/spectrum/SpectrumPanel.java
Normal file
257
src/spectrum/SpectrumPanel.java
Normal file
@@ -0,0 +1,257 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Line2D;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import buffer.FloatArrayCircularAveragingBuffer;
|
||||
import controller.ResourceManager;
|
||||
|
||||
public class SpectrumPanel extends JPanel
|
||||
implements DFTResultsListener,
|
||||
SettingChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/* 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 =
|
||||
new FloatArrayCircularAveragingBuffer( 4 );
|
||||
|
||||
/**
|
||||
* Spectral Display Color Settings
|
||||
*/
|
||||
private static final String sSPECTRUM_BACKGROUND = "spectrum_background";
|
||||
private static final String sSPECTRUM_GRADIENT_TOP = "spectrum_gradient_top";
|
||||
private static final String sSPECTRUM_GRADIENT_BOTTOM = "spectrum_gradient_bottom";
|
||||
private static final String sSPECTRUM_LINE = "spectrum_line";
|
||||
private static final int sSPECTRUM_TRANSLUCENCY = 128;
|
||||
|
||||
private Color mColorSpectrumBackground;
|
||||
private Color mColorSpectrumGradientTop;
|
||||
private Color mColorSpectrumGradientBottom;
|
||||
private Color mColorSpectrumLine;
|
||||
|
||||
private float mSpectrumInset = 20.0f;
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
|
||||
public SpectrumPanel( ResourceManager resourceManager )
|
||||
{
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
setColors();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mResourceManager = null;
|
||||
}
|
||||
|
||||
private void setColors()
|
||||
{
|
||||
mColorSpectrumBackground =
|
||||
getColor( ColorSettingName.SPECTRUM_BACKGROUND );
|
||||
|
||||
mColorSpectrumGradientBottom =
|
||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM );
|
||||
|
||||
mColorSpectrumGradientTop =
|
||||
getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP );
|
||||
|
||||
mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DFTResultsListener interface method - for receiving the processed data
|
||||
* to display
|
||||
*/
|
||||
public void receive( float[] currentFFTBins )
|
||||
{
|
||||
//Construct and/or resize our fft results variables
|
||||
if( mDisplayFFTBins == null ||
|
||||
mDisplayFFTBins.length != currentFFTBins.length )
|
||||
{
|
||||
mDisplayFFTBins = new float[ currentFFTBins.length ];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent( Graphics g )
|
||||
{
|
||||
super.paintComponent( g );
|
||||
|
||||
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 );
|
||||
|
||||
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,
|
||||
0,
|
||||
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 );
|
||||
|
||||
//If we have FFT data to display ...
|
||||
if( mDisplayFFTBins != null )
|
||||
{
|
||||
float insideHeight = size.height - mSpectrumInset;
|
||||
|
||||
float insideWidth = size.width;
|
||||
|
||||
float binSize = insideWidth / ( mDisplayFFTBins.length - 1 );
|
||||
float heightMultiplier = (float)( insideHeight * .15 );
|
||||
|
||||
float spectrumMiddle = insideHeight * .23f;
|
||||
|
||||
for( int x = 0; x < mDisplayFFTBins.length; x++ )
|
||||
{
|
||||
float height = spectrumMiddle - ( mDisplayFFTBins[ x ] * heightMultiplier );
|
||||
|
||||
if( height > size.getHeight() - mSpectrumInset )
|
||||
{
|
||||
height = (float)size.getHeight() - mSpectrumInset;
|
||||
}
|
||||
|
||||
spectrumShape.lineTo( binSize * x, 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 ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting ) {}
|
||||
}
|
||||
94
src/spectrum/WaterfallColorModel.java
Normal file
94
src/spectrum/WaterfallColorModel.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.image.IndexColorModel;
|
||||
|
||||
|
||||
public class WaterfallColorModel
|
||||
{
|
||||
public static IndexColorModel getDefaultColorModel()
|
||||
{
|
||||
int bitDepth = 8;
|
||||
int indexSize = 256;
|
||||
|
||||
byte[] red = new byte[ indexSize ];
|
||||
byte[] green = new byte[ indexSize ];
|
||||
byte[] blue = new byte[ indexSize ];
|
||||
|
||||
//Background noise
|
||||
for( int x = 0; x < 16; x++ ) //Blue
|
||||
{
|
||||
red[ x ] = (byte)0;
|
||||
|
||||
green[ x ] = (byte)0;
|
||||
|
||||
blue[ x ] = (byte)127;
|
||||
}
|
||||
|
||||
for( int x = 16; x < 32; x++ ) //Blue
|
||||
{
|
||||
red[ x ] = (byte)0;
|
||||
|
||||
green[ x ] = (byte)0;
|
||||
|
||||
blue[ x ] = (byte)191;
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 191;
|
||||
|
||||
for( int x = 32; x < 60; x++ )
|
||||
{
|
||||
red[ x ] = (byte)r;
|
||||
r += 9;
|
||||
|
||||
green[ x ] = (byte)g;
|
||||
g += 9;
|
||||
|
||||
blue[ x ] = (byte)b;
|
||||
b -= 6;
|
||||
}
|
||||
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 0;
|
||||
|
||||
for( int x = 60; x < 188; x++ ) //Yellow
|
||||
{
|
||||
red[ x ] = (byte)r;
|
||||
|
||||
green[ x ] = (byte)g;
|
||||
g -= 2;
|
||||
|
||||
blue[ x ] = (byte)b;
|
||||
}
|
||||
|
||||
for( int x = 188; x < 256; x++ ) //Red
|
||||
{
|
||||
red[ x ] = (byte)255;
|
||||
|
||||
green[ x ] = (byte)0;
|
||||
|
||||
blue[ x ] = (byte)0;
|
||||
}
|
||||
|
||||
return new IndexColorModel( bitDepth, indexSize, red, green, blue );
|
||||
}
|
||||
}
|
||||
287
src/spectrum/WaterfallPanel.java
Normal file
287
src/spectrum/WaterfallPanel.java
Normal file
@@ -0,0 +1,287 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 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 java.awt.Color;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.MemoryImageSource;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import settings.ColorSetting;
|
||||
import settings.ColorSetting.ColorSettingName;
|
||||
import settings.Setting;
|
||||
import settings.SettingChangeListener;
|
||||
import buffer.FloatAveragingBuffer;
|
||||
import controller.ResourceManager;
|
||||
|
||||
/**
|
||||
* Produces a JPanel filled with a waterfall image derived from a continuous
|
||||
* stream of received Discrete Fourier Transform (DFT) results from the
|
||||
* DFTProcessor class.
|
||||
*/
|
||||
public class WaterfallPanel extends JPanel implements DFTResultsListener,
|
||||
Pausable,
|
||||
SettingChangeListener
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static DecimalFormat sCURSOR_FORMAT = new DecimalFormat( "000.00000" );
|
||||
private static final String sSPECTRUM_CURSOR = "spectrum_cursor";
|
||||
private static final String sPAUSED = "PAUSED";
|
||||
|
||||
private byte[] mPixels;
|
||||
private int mFFTSize = 1024;
|
||||
private int mImageHeight = 700;
|
||||
private MemoryImageSource mMemoryImageSource;
|
||||
private ColorModel mColorModel = WaterfallColorModel.getDefaultColorModel();
|
||||
private Color mColorSpectrumCursor;
|
||||
private Image mWaterfallImage;
|
||||
|
||||
float mAverage = 0.0f;
|
||||
double mAlpha = 0.95d;
|
||||
int mScalor = 85;
|
||||
int temp = 0;
|
||||
|
||||
private Point mCursorLocation = new Point( 0, 0 );
|
||||
private boolean mCursorVisible = false;
|
||||
private int mCursorFrequency = 0;
|
||||
private AtomicBoolean mPaused = new AtomicBoolean();
|
||||
|
||||
private ResourceManager mResourceManager;
|
||||
|
||||
private FloatAveragingBuffer mIntraBinAveragingBuffer = new FloatAveragingBuffer( 9 );
|
||||
|
||||
public WaterfallPanel( ResourceManager resourceManager )
|
||||
{
|
||||
super();
|
||||
|
||||
mResourceManager = resourceManager;
|
||||
|
||||
mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR );
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mResourceManager = null;
|
||||
mMemoryImageSource = null;
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
mPixels = new byte[ mFFTSize * mImageHeight ];
|
||||
|
||||
mMemoryImageSource = new MemoryImageSource( mFFTSize,
|
||||
mImageHeight,
|
||||
mColorModel,
|
||||
mPixels,
|
||||
0,
|
||||
mFFTSize );
|
||||
|
||||
mMemoryImageSource.setAnimated( true );
|
||||
|
||||
mWaterfallImage = createImage( mMemoryImageSource );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pausable interface - pauses updates to the screen
|
||||
*/
|
||||
public void setPaused( boolean paused )
|
||||
{
|
||||
mPaused.set( paused );
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
public boolean isPaused()
|
||||
{
|
||||
return mPaused.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors for setting changes. Colors can be changed by external actions
|
||||
* and will automatically update in this class
|
||||
*/
|
||||
@Override
|
||||
public void settingChanged( Setting setting )
|
||||
{
|
||||
if( setting instanceof ColorSetting )
|
||||
{
|
||||
ColorSetting colorSetting = (ColorSetting)setting;
|
||||
|
||||
if( ( (ColorSetting) setting ).getColorSettingName() ==
|
||||
ColorSettingName.SPECTRUM_CURSOR )
|
||||
{
|
||||
mColorSpectrumCursor = colorSetting.getColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCursorLocation( Point point )
|
||||
{
|
||||
mCursorLocation = point;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setCursorFrequency( int frequency )
|
||||
{
|
||||
mCursorFrequency = frequency;
|
||||
}
|
||||
|
||||
public void setCursorVisible( boolean visible )
|
||||
{
|
||||
mCursorVisible = visible;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void paintComponent( Graphics g )
|
||||
{
|
||||
g.drawImage( mWaterfallImage, 0, 0, getWidth(), mImageHeight, this );
|
||||
|
||||
Graphics2D graphics = (Graphics2D) g;
|
||||
|
||||
if( mCursorVisible )
|
||||
{
|
||||
|
||||
graphics.setColor( mColorSpectrumCursor );
|
||||
|
||||
graphics.draw( new Line2D.Float( mCursorLocation.x,
|
||||
0,
|
||||
mCursorLocation.x,
|
||||
(float)(getSize().getHeight() ) ) );
|
||||
|
||||
String frequency = sCURSOR_FORMAT.format( mCursorFrequency / 1000000.0D );
|
||||
|
||||
graphics.drawString( frequency ,
|
||||
mCursorLocation.x + 5,
|
||||
mCursorLocation.y );
|
||||
|
||||
}
|
||||
|
||||
if( mPaused.get() )
|
||||
{
|
||||
graphics.drawString( sPAUSED, 20, 20 );
|
||||
}
|
||||
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( float[] update )
|
||||
{
|
||||
//If our FFT size changes, reset our pixel map and image source
|
||||
if( mFFTSize != update.length )
|
||||
{
|
||||
mFFTSize = 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 );
|
||||
|
||||
//Convert the power spectrum bin values into a byte (0-255)
|
||||
//value to use with the color mapping
|
||||
|
||||
double averagingTotal = 0.0;
|
||||
|
||||
for( int x = 0; x < update.length; x++ )
|
||||
{
|
||||
float value = update[ x ];
|
||||
|
||||
averagingTotal += value;
|
||||
|
||||
//Average the values toward zero
|
||||
float pixel = value - mAverage;
|
||||
|
||||
//If we're below the average, make the negative value a positive
|
||||
if( pixel < 0 )
|
||||
{
|
||||
// pixel = -pixel;
|
||||
pixel = 0.0f;
|
||||
}
|
||||
|
||||
//Scale the value
|
||||
pixel *= mScalor;
|
||||
|
||||
// mPixels[ x ] = (byte)pixel;
|
||||
mPixels[ x ] = (byte)( mIntraBinAveragingBuffer.get( pixel ) );
|
||||
}
|
||||
|
||||
//Update the average value
|
||||
mAverage = (float) averagingTotal / update.length;
|
||||
|
||||
if( !mPaused.get() )
|
||||
{
|
||||
EventQueue.invokeLater( new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if( mMemoryImageSource != null )
|
||||
{
|
||||
mMemoryImageSource.newPixels( mPixels, mColorModel, 0, mFFTSize );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public class Handler implements MouseListener
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e )
|
||||
{
|
||||
//Display context menu to reset the display reset();
|
||||
}
|
||||
|
||||
public void mousePressed( MouseEvent e ) {}
|
||||
public void mouseReleased( MouseEvent e ) {}
|
||||
public void mouseEntered( MouseEvent e ) {}
|
||||
public void mouseExited( MouseEvent e ){}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settingDeleted( Setting setting ) {}
|
||||
}
|
||||
Reference in New Issue
Block a user