diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index fc658253..b996796a 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -40,10 +40,13 @@ import io.github.dsheirer.record.RecorderManager; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.SourceManager; +import io.github.dsheirer.source.tuner.Tuner; import io.github.dsheirer.source.tuner.TunerEvent; import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.source.tuner.TunerSpectralDisplayManager; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +import io.github.dsheirer.spectrum.ClearTunerMenuItem; +import io.github.dsheirer.spectrum.ShowTunerMenuItem; import io.github.dsheirer.spectrum.SpectralDisplayPanel; import io.github.dsheirer.util.ThreadPool; import io.github.dsheirer.util.TimeStamp; @@ -174,10 +177,10 @@ public class SDRTrunk implements Listener mapService, mSettingsManager, mSourceManager, tunerModel); mSpectralPanel = new SpectralDisplayPanel(mChannelModel, - mChannelProcessingManager, mSettingsManager); + mChannelProcessingManager, mSettingsManager, tunerModel); TunerSpectralDisplayManager tunerSpectralDisplayManager = new TunerSpectralDisplayManager(mSpectralPanel, - mChannelModel, mChannelProcessingManager, mSettingsManager); + mChannelModel, mChannelProcessingManager, mSettingsManager, tunerModel); tunerModel.addListener(tunerSpectralDisplayManager); tunerModel.addListener(this); @@ -325,6 +328,15 @@ public class SDRTrunk implements Listener JMenu viewMenu = new JMenu("View"); viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel)); + viewMenu.add(new JSeparator()); + + for(Tuner tuner: mSourceManager.getTunerModel().getTuners()) + { + viewMenu.add(new ShowTunerMenuItem(mSpectralPanel, tuner)); + } + + viewMenu.add(new JSeparator()); + viewMenu.add(new ClearTunerMenuItem(mSpectralPanel)); menuBar.add(viewMenu); diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerEvent.java b/src/main/java/io/github/dsheirer/source/tuner/TunerEvent.java index 95fcee04..e4f85cc4 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerEvent.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerEvent.java @@ -29,6 +29,7 @@ public class TunerEvent CHANNEL_COUNT, FREQUENCY, SAMPLE_RATE, + CLEAR_MAIN_SPECTRAL_DISPLAY, REQUEST_MAIN_SPECTRAL_DISPLAY, REQUEST_NEW_SPECTRAL_DISPLAY; } diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java index 77f028d0..54004b71 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerModel.java @@ -1,5 +1,6 @@ package io.github.dsheirer.source.tuner; +import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.source.Source; import io.github.dsheirer.source.SourceException; @@ -9,6 +10,7 @@ import io.github.dsheirer.source.tuner.channel.TunerChannel; import io.github.dsheirer.source.tuner.channel.TunerChannelSource; import io.github.dsheirer.source.tuner.configuration.TunerConfiguration; import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel; +import io.github.dsheirer.spectrum.SpectralDisplayPanel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -177,10 +179,16 @@ public class TunerModel extends AbstractTableModel implements Listener 0) + SystemProperties properties = SystemProperties.getInstance(); + boolean enabled = properties.get(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, true); + + if(enabled && mTuners.size() > 0) { - broadcast(new TunerEvent(mTuners.get(0), - Event.REQUEST_MAIN_SPECTRAL_DISPLAY)); + broadcast(new TunerEvent(mTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY)); + } + else + { + broadcast(new TunerEvent(null, Event.CLEAR_MAIN_SPECTRAL_DISPLAY)); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerSpectralDisplayManager.java b/src/main/java/io/github/dsheirer/source/tuner/TunerSpectralDisplayManager.java index ed53b8e2..8e14aaf3 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerSpectralDisplayManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerSpectralDisplayManager.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 + * + ******************************************************************************/ package io.github.dsheirer.source.tuner; import io.github.dsheirer.controller.channel.ChannelModel; @@ -7,49 +22,55 @@ import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.spectrum.SpectralDisplayPanel; import io.github.dsheirer.spectrum.SpectrumFrame; -import java.awt.*; +import java.awt.EventQueue; public class TunerSpectralDisplayManager implements Listener { - private SpectralDisplayPanel mSpectralDisplayPanel; - private ChannelModel mChannelModel; - private ChannelProcessingManager mChannelProcessingManager; - private SettingsManager mSettingsManager; + private SpectralDisplayPanel mSpectralDisplayPanel; + private ChannelModel mChannelModel; + private ChannelProcessingManager mChannelProcessingManager; + private SettingsManager mSettingsManager; + private TunerModel mTunerModel; - public TunerSpectralDisplayManager( SpectralDisplayPanel panel, - ChannelModel channelModel, - ChannelProcessingManager channelProcessingManager, - SettingsManager settingsManager ) - { - mSpectralDisplayPanel = panel; - mChannelModel = channelModel; - mChannelProcessingManager = channelProcessingManager; - mSettingsManager = settingsManager; - } - - @Override - public void receive( TunerEvent event ) - { - switch( event.getEvent() ) - { - case REQUEST_MAIN_SPECTRAL_DISPLAY: - mSpectralDisplayPanel.showTuner( event.getTuner() ); - break; - case REQUEST_NEW_SPECTRAL_DISPLAY: - final SpectrumFrame frame = new SpectrumFrame( mChannelModel, - mChannelProcessingManager, mSettingsManager, event.getTuner() ); - - EventQueue.invokeLater( new Runnable() - { - @Override - public void run() - { - frame.setVisible( true ); - } - } ); - break; - default: - break; - } - } + public TunerSpectralDisplayManager(SpectralDisplayPanel panel, + ChannelModel channelModel, + ChannelProcessingManager channelProcessingManager, + SettingsManager settingsManager, + TunerModel tunerModel) + { + mSpectralDisplayPanel = panel; + mChannelModel = channelModel; + mChannelProcessingManager = channelProcessingManager; + mSettingsManager = settingsManager; + mTunerModel = tunerModel; + } + + @Override + public void receive(TunerEvent event) + { + switch(event.getEvent()) + { + case CLEAR_MAIN_SPECTRAL_DISPLAY: + mSpectralDisplayPanel.clearTuner(); + break; + case REQUEST_MAIN_SPECTRAL_DISPLAY: + mSpectralDisplayPanel.showTuner(event.getTuner()); + break; + case REQUEST_NEW_SPECTRAL_DISPLAY: + final SpectrumFrame frame = new SpectrumFrame(mChannelModel, + mChannelProcessingManager, mSettingsManager, mTunerModel, event.getTuner()); + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + frame.setVisible(true); + } + }); + break; + default: + break; + } + } } diff --git a/src/main/java/io/github/dsheirer/spectrum/ClearTunerMenuItem.java b/src/main/java/io/github/dsheirer/spectrum/ClearTunerMenuItem.java new file mode 100644 index 00000000..3da730c8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/spectrum/ClearTunerMenuItem.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 + * + ******************************************************************************/ +package io.github.dsheirer.spectrum; + +import io.github.dsheirer.properties.SystemProperties; + +import javax.swing.JMenuItem; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ClearTunerMenuItem extends JMenuItem +{ + private SpectralDisplayPanel mSpectralDisplayPanel; + + public ClearTunerMenuItem(SpectralDisplayPanel spectralDisplayPanel) + { + super("Disable Spectrum & Waterfall"); + + mSpectralDisplayPanel = spectralDisplayPanel; + + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mSpectralDisplayPanel.clearTuner(); + SystemProperties properties = SystemProperties.getInstance(); + properties.set(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, false); + } + }); + } +} + diff --git a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java b/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java index 83debd0c..44958d9b 100644 --- a/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java +++ b/src/main/java/io/github/dsheirer/spectrum/DFTProcessor.java @@ -165,12 +165,15 @@ public class DFTProcessor implements Listener, ISourceEve public void start() { - //Schedule the DFT to run calculations at a fixed rate - int initialDelay = 0; - int period = (int) (1000 / mFrameRate); + if(mProcessorTaskHandle == null) + { + //Schedule the DFT to run calculations at a fixed rate + int initialDelay = 0; + int period = (int) (1000 / mFrameRate); - mProcessorTaskHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, - TimeUnit.MILLISECONDS); + mProcessorTaskHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new DFTCalculationTask(), initialDelay, period, + TimeUnit.MILLISECONDS); + } } public void stop() @@ -179,9 +182,15 @@ public class DFTProcessor implements Listener, ISourceEve if(mProcessorTaskHandle != null) { mProcessorTaskHandle.cancel(true); + mProcessorTaskHandle = null; } } + public boolean isRunning() + { + return mProcessorTaskHandle != null; + } + public void restart() { stop(); diff --git a/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java b/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java index 2f621334..98df172d 100644 --- a/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/OverlayPanel.java @@ -342,6 +342,10 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, ISourc { minor = 1; } + if(label == 0) + { + label = 1; + } //Adjust the start frequency to a multiple of the minor tick spacing long frequency = minFrequency - (minFrequency % minor); diff --git a/src/main/java/io/github/dsheirer/spectrum/ShowTunerMenuItem.java b/src/main/java/io/github/dsheirer/spectrum/ShowTunerMenuItem.java new file mode 100644 index 00000000..cf0ce41d --- /dev/null +++ b/src/main/java/io/github/dsheirer/spectrum/ShowTunerMenuItem.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * sdr-trunk + * Copyright (C) 2014-2018 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 + * + ******************************************************************************/ +package io.github.dsheirer.spectrum; + +import io.github.dsheirer.source.tuner.Tuner; + +import javax.swing.JMenuItem; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ShowTunerMenuItem extends JMenuItem +{ + private SpectralDisplayPanel mSpectralDisplayPanel; + private Tuner mTuner; + + public ShowTunerMenuItem(SpectralDisplayPanel spectralDisplayPanel, Tuner tuner) + { + super(tuner != null ? "Show: " + tuner.getName() : "(empty)"); + mSpectralDisplayPanel = spectralDisplayPanel; + mTuner = tuner; + + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + mSpectralDisplayPanel.showTuner(mTuner); + } + }); + } + }); + } +} diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java index c0c72abc..c6888e31 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectralDisplayPanel.java @@ -22,7 +22,6 @@ import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.controller.channel.ChannelUtils; import io.github.dsheirer.dsp.filter.Window.WindowType; import io.github.dsheirer.dsp.filter.smoothing.SmoothingFilter.SmoothingType; -import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.properties.SystemProperties; import io.github.dsheirer.sample.Listener; import io.github.dsheirer.sample.SampleType; @@ -33,6 +32,7 @@ import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.ISourceEventProcessor; import io.github.dsheirer.source.SourceEvent; import io.github.dsheirer.source.tuner.Tuner; +import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.spectrum.OverlayPanel.ChannelDisplay; import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter; import io.github.dsheirer.spectrum.converter.DFTResultsConverter; @@ -71,7 +71,6 @@ import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Hashtable; @@ -82,9 +81,8 @@ public class SpectralDisplayPanel extends JPanel implements Listener + * sdr-trunk + * Copyright (C) 2014-2018 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 + * ******************************************************************************/ package io.github.dsheirer.spectrum; @@ -21,10 +19,11 @@ import io.github.dsheirer.controller.channel.ChannelModel; import io.github.dsheirer.controller.channel.ChannelProcessingManager; import io.github.dsheirer.settings.SettingsManager; import io.github.dsheirer.source.tuner.Tuner; +import io.github.dsheirer.source.tuner.TunerModel; import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import javax.swing.JFrame; +import java.awt.EventQueue; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; @@ -33,52 +32,70 @@ public class SpectrumFrame extends JFrame implements WindowListener private static final long serialVersionUID = 1L; private SpectralDisplayPanel mSpectralDisplayPanel; - - public SpectrumFrame( ChannelModel channelModel, - ChannelProcessingManager channelProcessingManager, - SettingsManager settingsManager, - Tuner tuner ) - { - 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( channelModel, - channelProcessingManager, settingsManager ); + public SpectrumFrame(ChannelModel channelModel, + ChannelProcessingManager channelProcessingManager, + SettingsManager settingsManager, + TunerModel tunerModel, + Tuner tuner) + { + setTitle("SDRTRunk [" + tuner.getName() + "]"); + setBounds(100, 100, 1280, 600); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - mSpectralDisplayPanel.showTuner( tuner ); - add( mSpectralDisplayPanel, "grow" ); + setLayout(new MigLayout("insets 0 0 0 0", "[grow]", "[grow]")); - /* Register a shutdown listener */ - this.addWindowListener( this ); - - EventQueue.invokeLater( new Runnable() + mSpectralDisplayPanel = new SpectralDisplayPanel(channelModel, + channelProcessingManager, settingsManager, tunerModel); + + mSpectralDisplayPanel.showTuner(tuner); + add(mSpectralDisplayPanel, "grow"); + + /* Register a shutdown listener */ + this.addWindowListener(this); + + EventQueue.invokeLater(new Runnable() { public void run() { - setVisible( true ); + 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 ) {} + @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) + { + } } diff --git a/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java b/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java index 764e63ac..affc3ac7 100644 --- a/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/SpectrumPanel.java @@ -327,8 +327,7 @@ public class SpectrumPanel extends JPanel implements DFTResultsListener, Setting */ public void clearSpectrum() { - mDisplayFFTBins = null; - + Arrays.fill(mDisplayFFTBins, 0.0f); repaint(); } diff --git a/src/main/java/io/github/dsheirer/spectrum/WaterfallPanel.java b/src/main/java/io/github/dsheirer/spectrum/WaterfallPanel.java index cec5c673..cdc1f507 100644 --- a/src/main/java/io/github/dsheirer/spectrum/WaterfallPanel.java +++ b/src/main/java/io/github/dsheirer/spectrum/WaterfallPanel.java @@ -1,17 +1,17 @@ /******************************************************************************* * SDR Trunk * 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 * 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 ******************************************************************************/ @@ -25,126 +25,143 @@ import io.github.dsheirer.settings.SettingsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.swing.*; -import java.awt.*; +import javax.swing.JPanel; +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.geom.Line2D; import java.awt.image.ColorModel; import java.awt.image.MemoryImageSource; import java.text.DecimalFormat; +import java.util.Arrays; public class WaterfallPanel extends JPanel implements DFTResultsListener, - Pausable, - SettingChangeListener + Pausable, + SettingChangeListener { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private final static Logger mLog = - LoggerFactory.getLogger( WaterfallPanel.class ); + private final static Logger mLog = + LoggerFactory.getLogger(WaterfallPanel.class); - private static DecimalFormat CURSOR_FORMAT = new DecimalFormat( "0.00000" ); - private static final String PAUSED = "PAUSED"; + private static DecimalFormat CURSOR_FORMAT = new DecimalFormat("0.00000"); + private static final String PAUSED = "PAUSED - Right Click to Unpause"; + private static final String DISABLED = "DISABLED - Right Click to Select a Tuner"; - private byte[] mPixels; - private byte[] mPausedPixels; + private byte[] mPixels; + private byte[] mPausedPixels; private int mDFTSize = 4096; private int mImageHeight = 700; private MemoryImageSource mMemoryImageSource; private ColorModel mColorModel = WaterfallColorModel.getDefaultColorModel(); - private Color mColorSpectrumCursor; + private Color mColorSpectrumCursor; private Image mWaterfallImage; - private Point mCursorLocation = new Point( 0, 0 ); - private boolean mCursorVisible = false; - private long mCursorFrequency = 0; - private boolean mPaused = false; - private int mZoom = 0; - private int mDFTZoomWindowOffset = 0; - - private SettingsManager mSettingsManager; + private Point mCursorLocation = new Point(0, 0); + private boolean mCursorVisible = false; + private long mCursorFrequency = 0; + private boolean mPaused = false; + private boolean mDisabled = true; + private int mZoom = 0; + private int mDFTZoomWindowOffset = 0; - /** - * Displays a scrolling window of multiple DFT frequency bin outputs over - * time. Maps DFT frequency bin decibel values into a 256 bucket color map - * for display. - * - * @param settingsManager - */ - public WaterfallPanel( SettingsManager settingsManager ) - { - super(); + private SettingsManager mSettingsManager; - mSettingsManager = settingsManager; - - mSettingsManager.addListener( this ); - - mColorSpectrumCursor = getColor( ColorSettingName.SPECTRUM_CURSOR ); + /** + * Displays a scrolling window of multiple DFT frequency bin outputs over + * time. Maps DFT frequency bin decibel values into a 256 bucket color map + * for display. + * + * @param settingsManager + */ + public WaterfallPanel(SettingsManager settingsManager) + { + super(); - reset(); - } + mSettingsManager = settingsManager; - /** - * Prepares this instance for disposal - */ - public void dispose() - { - if( mSettingsManager != null ) - { - mSettingsManager.removeListener( this ); - } - - mSettingsManager = null; - mMemoryImageSource = null; - } + mSettingsManager.addListener(this); - /** - * Resets the memory image source and byte backing array when the DFT point - * size has changed - */ - private void reset() - { - mPixels = new byte[ mDFTSize * mImageHeight ]; + mColorSpectrumCursor = getColor(ColorSettingName.SPECTRUM_CURSOR); - mMemoryImageSource = new MemoryImageSource( mDFTSize, - mImageHeight, - mColorModel, - mPixels, - 0, - mDFTSize ); - - mMemoryImageSource.setAnimated( true ); + reset(); + } + + /** + * Prepares this instance for disposal + */ + public void dispose() + { + if(mSettingsManager != null) + { + mSettingsManager.removeListener(this); + } + + mSettingsManager = null; + mMemoryImageSource = null; + } + + /** + * Resets the memory image source and byte backing array when the DFT point + * size has changed + */ + private void reset() + { + mPixels = new byte[mDFTSize * mImageHeight]; + + mMemoryImageSource = new MemoryImageSource(mDFTSize, + mImageHeight, + mColorModel, + mPixels, + 0, + mDFTSize); + + mMemoryImageSource.setAnimated(true); + + mWaterfallImage = createImage(mMemoryImageSource); - mWaterfallImage = createImage( mMemoryImageSource ); - repaint(); - } - - /** - * Pausable interface - pauses updates to the waterfall - */ - public void setPaused( boolean paused ) - { - if( paused ) - { - mPausedPixels = mPixels.clone(); - } + } + + /** + * Pausable interface - pauses updates to the waterfall + */ + public void setPaused(boolean paused) + { + if(paused) + { + mPausedPixels = mPixels.clone(); + } + + mPaused = paused; + + repaint(); + } + + /** + * Returns current pause state + * + * @return true if paused, false otherwise + */ + public boolean isPaused() + { + return mPaused; + } + + /** + * Indicates if the waterfall is currently disabled. + */ + public boolean isDisabled() + { + return mDisabled; + } - mPaused = paused; - - repaint(); - } - - /** - * Returns current pause state - * @return true if paused, false otherwise - */ - public boolean isPaused() - { - return mPaused; - } - /** * Sets the current zoom level (2^zoom) - * + * * 0 No Zoom * 1 2x Zoom * 2 4x Zoom @@ -152,280 +169,294 @@ public class WaterfallPanel extends JPanel implements DFTResultsListener, * 4 16x Zoom * 5 32x Zoom * 6 64x Zoom - * + * * @param zoom level, 0 - 6. * @param offset into the DFT bins for display */ - public void setZoom( int zoom ) - { - mZoom = zoom; - } - - /** - * Multiplier for the current zoom level - */ - private int getZoomMultiplier() - { - return (int)Math.pow( 2.0, mZoom ); - } - - /** - * Sets the zoom window offset from zero - * @param offset in DFT bins - */ - public void setZoomWindowOffset( int offset ) - { - mDFTZoomWindowOffset = offset; - } - - /** - * 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 = mSettingsManager.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 ) + public void setZoom(int zoom) { - if( setting instanceof ColorSetting ) - { - ColorSetting colorSetting = (ColorSetting)setting; - - if( ( (ColorSetting) setting ).getColorSettingName() == - ColorSettingName.SPECTRUM_CURSOR ) - { - mColorSpectrumCursor = colorSetting.getColor(); - } - } + mZoom = zoom; } - @Override - public void settingDeleted( Setting setting ) { /* Not implemented */ } - - /** - * Sets the display location of the cursor. Cursor location monitoring is - * handled external to this class. - * - * @param point - */ - public void setCursorLocation( Point point ) - { - mCursorLocation = point; - - repaint(); - } - - /** - * Sets the current cursor display frequency. Cursor location frequency - * monitoring is handled external to this class. - * - * @param frequency - */ - public void setCursorFrequency( long frequency ) - { - mCursorFrequency = frequency; - } - - /** - * Toggles the visibility of the cursor - * @param visible - */ - public void setCursorVisible( boolean visible ) - { - mCursorVisible = visible; - - repaint(); - } - - /** - * Calculates the x-axis pixel offset from zero where to start rendering the - * waterfall image - * - * @param multiplier - current zoom multiplier - * @return x-axis pixel offset - */ - private double getPixelOffset( int multiplier ) - { - double offset = 0; - - if( mZoom != 0 ) - { - double binPixelWidth = getBinPixelWidth( multiplier ); - - offset = -binPixelWidth * (double)( mDFTZoomWindowOffset ); - } - - return offset; - } - - private double getBinPixelWidth( int multiplier ) - { - return ( (double)getWidth() * (double)multiplier ) / (double)mDFTSize; - } - - /** - * Renders the screen at each refresh - */ - public void paintComponent( Graphics g ) - { - super.paintComponent( g ); - - int multiplier = getZoomMultiplier(); - - double binPixelWidth = getBinPixelWidth( multiplier ); - - int offset = (int)( getPixelOffset( multiplier ) - binPixelWidth ); - - g.drawImage( mWaterfallImage, - offset, - 0, - ( getWidth() * multiplier ) + (int)binPixelWidth, - mImageHeight, - this ); - - Graphics2D graphics = (Graphics2D) g; - - graphics.setColor( mColorSpectrumCursor ); - - if( mCursorVisible ) - { - - - graphics.draw( new Line2D.Float( mCursorLocation.x, - 0, - mCursorLocation.x, - (float)(getSize().getHeight() ) ) ); - - String frequency = CURSOR_FORMAT.format( mCursorFrequency / 1000000.0D ); - - graphics.drawString( frequency , - mCursorLocation.x + 5, - mCursorLocation.y ); - } - - if( mPaused ) - { - graphics.drawString( PAUSED, 20, 20 ); - } - - paintZoomIndicator( graphics ); - - graphics.dispose(); - } - - /** - * When zoom level is greater than zero, paints a small indicator at the - * bottom center of the screen showing the location of the zoom window - * within the overall DFT results window - */ - private void paintZoomIndicator( Graphics2D graphics ) - { - if( mZoom != 0 ) - { - int width = getWidth() / 4; - - int x = ( getWidth() / 2 ) - ( width / 2 ); - - //Draw the outer window - graphics.drawRect( x, getHeight() - 12, width, 10 ); - - int zoomWidth = width / getZoomMultiplier(); - - int windowOffset = 0; - - if( mDFTZoomWindowOffset != 0 ) - { - windowOffset = (int)( ( (double)mDFTZoomWindowOffset / - (double)mDFTSize ) * width ); - } - - //Draw the zoom window - graphics.fillRect( x + windowOffset, getHeight() - 12, zoomWidth, 10 ); - - //Draw the zoom text - graphics.drawString( "Zoom: " + getZoomMultiplier() + "x", - x + width + 3, getHeight() - 2 ); - } - } - - /** - * Implements the DFT results listener interface method. This is the - * primary method for receiving new frequency bin results. - */ - @Override - public void receive( float[] update ) + /** + * Multiplier for the current zoom level + */ + private int getZoomMultiplier() { - //If our FFT size changes, reset our pixel map and image source - if( mDFTSize != update.length ) - { - mDFTSize = update.length; + return (int)Math.pow(2.0, mZoom); + } - reset(); - } + /** + * Sets the zoom window offset from zero + * + * @param offset in DFT bins + */ + public void setZoomWindowOffset(int offset) + { + mDFTZoomWindowOffset = offset; + } - //Move the pixels down a row to make room for the new results - System.arraycopy( mPixels, 0, - mPixels, mDFTSize, mPixels.length - mDFTSize ); - - /** - * Find the average value and scale the display to it - */ - double sum = 0.0d; - - for( int x = 0; x < update.length - 1; x++ ) - { - sum += update[ x ]; - } - - float average = (float)( sum / (double)update.length - 1 ); + /** + * 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 = mSettingsManager.getColorSetting(name); - float scale = 256.0f / average; - - for( int x = 0; x < update.length - 1; x++ ) - { - float value = ( average - update[ x ] ) * scale; + return setting.getColor(); + } - if( value < 0 ) - { - mPixels[ x ] = 0; - } - else if( value > 255 ) - { - mPixels[ x ] = (byte)255; - } - else - { - mPixels[ x ] = (byte)value; - } - } + /** + * 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; - //Task the swing event thread to update the display - EventQueue.invokeLater( new Runnable() - { - @Override + if(((ColorSetting)setting).getColorSettingName() == + ColorSettingName.SPECTRUM_CURSOR) + { + mColorSpectrumCursor = colorSetting.getColor(); + } + } + } + + @Override + public void settingDeleted(Setting setting) + { /* Not implemented */ } + + /** + * Sets the display location of the cursor. Cursor location monitoring is + * handled external to this class. + * + * @param point + */ + public void setCursorLocation(Point point) + { + mCursorLocation = point; + + repaint(); + } + + /** + * Sets the current cursor display frequency. Cursor location frequency + * monitoring is handled external to this class. + * + * @param frequency + */ + public void setCursorFrequency(long frequency) + { + mCursorFrequency = frequency; + } + + /** + * Toggles the visibility of the cursor + * + * @param visible + */ + public void setCursorVisible(boolean visible) + { + mCursorVisible = visible; + + repaint(); + } + + /** + * Calculates the x-axis pixel offset from zero where to start rendering the + * waterfall image + * + * @param multiplier - current zoom multiplier + * @return x-axis pixel offset + */ + private double getPixelOffset(int multiplier) + { + double offset = 0; + + if(mZoom != 0) + { + double binPixelWidth = getBinPixelWidth(multiplier); + + offset = -binPixelWidth * (double)(mDFTZoomWindowOffset); + } + + return offset; + } + + private double getBinPixelWidth(int multiplier) + { + return ((double)getWidth() * (double)multiplier) / (double)mDFTSize; + } + + /** + * Renders the screen at each refresh + */ + public void paintComponent(Graphics g) + { + super.paintComponent(g); + + int multiplier = getZoomMultiplier(); + + double binPixelWidth = getBinPixelWidth(multiplier); + + int offset = (int)(getPixelOffset(multiplier) - binPixelWidth); + + g.drawImage(mWaterfallImage, + offset, + 0, + (getWidth() * multiplier) + (int)binPixelWidth, + mImageHeight, + this); + + Graphics2D graphics = (Graphics2D)g; + + graphics.setColor(mColorSpectrumCursor); + + if(mCursorVisible) + { + graphics.draw(new Line2D.Float(mCursorLocation.x, 0, mCursorLocation.x, (float)(getSize().getHeight()))); + String frequency = CURSOR_FORMAT.format(mCursorFrequency / 1000000.0D); + graphics.drawString(frequency, mCursorLocation.x + 5, mCursorLocation.y); + } + + if(mDisabled) + { + graphics.drawString(DISABLED, 20, 20); + } + else if(mPaused) + { + graphics.drawString(PAUSED, 20, 20); + } + + paintZoomIndicator(graphics); + + graphics.dispose(); + } + + /** + * When zoom level is greater than zero, paints a small indicator at the + * bottom center of the screen showing the location of the zoom window + * within the overall DFT results window + */ + private void paintZoomIndicator(Graphics2D graphics) + { + if(mZoom != 0) + { + int width = getWidth() / 4; + + int x = (getWidth() / 2) - (width / 2); + + //Draw the outer window + graphics.drawRect(x, getHeight() - 12, width, 10); + + int zoomWidth = width / getZoomMultiplier(); + + int windowOffset = 0; + + if(mDFTZoomWindowOffset != 0) + { + windowOffset = (int)(((double)mDFTZoomWindowOffset / + (double)mDFTSize) * width); + } + + //Draw the zoom window + graphics.fillRect(x + windowOffset, getHeight() - 12, zoomWidth, 10); + + //Draw the zoom text + graphics.drawString("Zoom: " + getZoomMultiplier() + "x", + x + width + 3, getHeight() - 2); + } + } + + /** + * Implements the DFT results listener interface method. This is the + * primary method for receiving new frequency bin results. + */ + @Override + public void receive(float[] update) + { + mDisabled = false; + + //If our FFT size changes, reset our pixel map and image source + if(mDFTSize != update.length) + { + mDFTSize = update.length; + + reset(); + } + + //Move the pixels down a row to make room for the new results + System.arraycopy(mPixels, 0, mPixels, mDFTSize, mPixels.length - mDFTSize); + + /** + * Find the average value and scale the display to it + */ + double sum = 0.0d; + + for(int x = 0; x < update.length - 1; x++) + { + sum += update[x]; + } + + float average = (float)(sum / (double)update.length - 1); + + float scale = 256.0f / average; + + for(int x = 0; x < update.length - 1; x++) + { + float value = (average - update[x]) * scale; + + if(value < 0) + { + mPixels[x] = 0; + } + else if(value > 255) + { + mPixels[x] = (byte)255; + } + else + { + mPixels[x] = (byte)value; + } + } + + //Task the swing event thread to update the display + EventQueue.invokeLater(new Runnable() + { + @Override public void run() { - if( mMemoryImageSource != null ) - { - if( mPaused ) - { - mMemoryImageSource.newPixels( mPausedPixels, mColorModel, 0, mDFTSize ); - } - else - { - mMemoryImageSource.newPixels( mPixels, mColorModel, 0, mDFTSize ); - } - } + if(mMemoryImageSource != null) + { + if(mPaused) + { + mMemoryImageSource.newPixels(mPausedPixels, mColorModel, 0, mDFTSize); + } + else + { + mMemoryImageSource.newPixels(mPixels, mColorModel, 0, mDFTSize); + } + } } - } ); + }); + } + + public void clearWaterfall() + { + Arrays.fill(mPixels, (byte)0); + mDisabled = true; + + EventQueue.invokeLater(new Runnable() + { + @Override + public void run() + { + mMemoryImageSource.newPixels(mPixels, mColorModel, 0, mDFTSize); + } + }); } }